How to make group by in djano ORM
I've an Order model as below:
class Order(models.Model):
bill = models.ForeignKey(Bill, on_delete=models.PROTECT, null=True, blank=True)
address_from = models.ForeignKey(Address, on_delete=models.PROTECT)
address_to = models.ForeignKey(Address, on_delete=models.PROTECT)
How can I group a queryset from it and iterate in each group like the following:
bill = models.Bill.objects.create()
groups = Order.objects.all().group_by('address_from', 'address_to')
for group in groups:
group.update(bill = bill)
The goal is to set a bill for each group of the orders that have the same address from and address to.
To do what you're looking for, there is no group_by as such, in your place I'd make a list with all the unique combinations of your 2 address fields, For each group, filter the corresponding commands and use update() to assign the specified instance of Bill
You can find more information in the documentation, particularly in the sections on values() and distinct() https://docs.djangoproject.com/en/5.1/ref/models/querysets/
I hope my answer helps solve your problem
I've solved this problem by combining the address from & address to primary keys into annotated unique key - uk - field and filter each uk.
from django.db.models import CharField, Value
from django.db.models.functions import Concat
orders = models.Order.objects.annotate(
addresses_uk=Concat('address_from__pk', Value('-'), 'address_to__pk',
output_field=CharField())
)
for uk in set(orders.values_list('addresses_uk', flat=True)):
bill = models.Bill.objects.create(
address_from=models.Address.objects.get(pk=int(uk.split("-")[0])),
address_to=models.Address.objects.get(pk=int(uk.split("-")[1]))
)
orders.filter(addresses_uk=uk).update(bill=bill)
@Ahmed Ashraf answered the question. We can however retrieve and create data in bulk, which will boost performance a lot, with:
orders = {
(order.address_from, order.adress_to): order
for order in models.Order.objects.filter(bill=None)
}
bills = []
for (item1, item2), order in orders.items():
bill = models.Bill(address_from_id=item1, address_to_id=item2)
bills.append(bill)
order.bill = bill
Bill.objects.bulk_create(bills)
Order.objects.bulk_update(orders.values(), fields=('bill',))
This will work with three queries: one to fetch all orders without a bill
, one to create all bills
, and finally one to update all the Order
s with the created Bill
s.