Django QuerySet: как агрегировать повторяющиеся элементы и добавить к ним поле количества?
У меня есть ощущение, что решение очень простое, но я, как новичок в Django, не могу его понять...
Учитывая следующий набор запросов:
<QuerySet [
{'id': 2, 'prodclassQuery_id': 1, 'prodDescription': 'Hofbräu Kellerbier 500 ml', 'prodPrice': Decimal('6.50')},
{'id': 1, 'prodclassQuery_id': 1, 'prodDescription': 'Tonic Water 300 ml', 'prodPrice': Decimal('4.50')},
{'id': 3, 'prodclassQuery_id': 2, 'prodDescription': 'Coxinha 6 unidades', 'prodPrice': Decimal('8.00')},
{'id': 3, 'prodclassQuery_id': 2, 'prodDescription': 'Coxinha 6 unidades', 'prodPrice': Decimal('8.00')}]>
Я хочу агрегировать повторяющиеся элементы (на основе id
) и получить следующий QuerySet, добавив поле poQty_
для представления количества повторяющихся элементов (продуктов в моем случае...):
<QuerySet [
{'id': 2, 'prodclassQuery_id': 1, 'prodDescription': 'Hofbräu Kellerbier 500 ml', 'prodPrice': Decimal('6.50'), 'poQty_': 1},
{'id': 1, 'prodclassQuery_id': 1, 'prodDescription': 'Tonic Water 300 ml', 'prodPrice': Decimal('4.50'), 'poQty_': 1},
{'id': 3, 'prodclassQuery_id': 2, 'prodDescription': 'Coxinha 6 unidades', 'prodPrice': Decimal('8.00'), 'poQty_': 2}]>
То, что я пробовал до сих пор с annotate()
в views.py
, не работает, и результаты orders_aggr
- это тот же самый оригинальный QuerySet:
def display_orders(request):
orders = Order.objects.all().order_by('id', 'orderTable', 'menuQuery')
for j in orders:
print(j.prodQuery.values(),) # original QuerySet
orders_aggr = Order.objects.annotate(poQty_=Count('prodQuery__id')).order_by('id', 'orderTable', 'menuQuery')
for j in orders_aggr:
print(j.prodQuery.values(),)
context = {
'orders': orders,
'orders_aggr': orders_aggr
}
return render(request, 'orders.html', context)
Может ли кто-нибудь оказать помощь? Спасибо!!! Дополнительная информация:
models.py
class Product(models.Model):
prodclassQuery = models.ForeignKey(ProductClass, on_delete=models.PROTECT, verbose_name='Product Class', default=1)
prodDescription = models.CharField(max_length=255, verbose_name='Product')
prodPrice = models.DecimalField(max_digits=6, decimal_places=2, verbose_name='Price')
class Meta:
ordering = ['prodclassQuery', 'prodDescription']
def __str__(self):
return self.prodDescription
class Menu(models.Model):
menuActive = models.BooleanField(verbose_name='Active?', default=False)
menuDescription = models.CharField(max_length=255, verbose_name='Menu')
prodQuery = models.ManyToManyField(Product, verbose_name='Product')
class Meta:
ordering = ['menuDescription',]
def __str__(self):
return self.menuDescription
class Order(models.Model):
orderUser = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
orderDtOpen = models.DateTimeField(auto_now_add=True)
orderDtClose = models.DateTimeField(auto_now=True)
orderOpen = models.BooleanField(default=True, verbose_name='Open?')
orderTable = models.CharField(max_length=25, verbose_name='Table')
menuQuery = models.ForeignKey(Menu, on_delete=models.PROTECT, verbose_name='Menu', default=1)
prodQuery = models.ManyToManyField(Product, through='ProductOrder')
class Meta:
models.UniqueConstraint(fields=['orderTable'], condition=models.Q(orderOpen=True), name='unique_open_order_table',
violation_error_message='This table has already a open order')
def save_model(self, request, obj, form, change):
obj.orderuser = request.user
super().save_model(request, obj, form, change)
class ProductOrder(models.Model):
poOrder = models.ForeignKey(Order, on_delete=models.CASCADE, verbose_name='Order', default=1)
poStatus = models.BooleanField(default=True, verbose_name='Order Status')
prodQuery = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name='Product', default=1)
Несколько моментов, во-первых, в display_orders
вы оцениваете набор запросов для Orders.objects
более одного раза, что является избыточным
Думаю, если вы измените вид display_orders
, чтобы он выглядел примерно так
def display_orders(request):
orders = Order.objects.all().order_by(
'id',
'orderTable',
'menuQuery'
).distinct('id').annotate(
poQty_=Count('id')
)
context = {
'orders': orders,
}
return render(request, 'orders.html', context)
Аннотация должна подсчитывать количество отдельных элементов строки и передавать это количество каждому результату
Вызов distinct должен также удалить дубликаты из набора queryset
Попробуйте что-нибудь вроде этого:
def display_orders(request):
orders = Order.objects.all().order_by('id', 'orderTable', 'menuQuery')
orders_aux = [j for i in orders for j in i.prodQuery.values()]
orders_aux1 = []
orders_aggr = []
for i in orders_aux:
if i['id'] not in orders_aux1:
orders_aux1.append(i['id'])
i['poQty_'] = 1
orders_aggr.append(i)
else:
orders_aggr[orders_aux1.index(i['id'])]['poQty_'] += 1
context = {
'orders': orders,
'orders_aggr': orders_aggr
}
return render(request, 'orders.html', context)
Результат:
print(orders_aggr)
[{'id': 2,
'prodclassQuery_id': 1,
'prodDescription': 'Hofbräu Kellerbier 500 ml',
'prodPrice': Decimal('6.50'),
'poQty_': 1},
{'id': 1,
'prodclassQuery_id': 1,
'prodDescription': 'Tonic Water 300 ml',
'prodPrice': Decimal('4.50'),
'poQty_': 1},
{'id': 3,
'prodclassQuery_id': 2,
'prodDescription': 'Coxinha 6 unidades',
'prodPrice': Decimal('8.00'),
'poQty_': 2}]
Обратите внимание, что queryset теперь представляет собой список объектов. Лично я предпочитаю делать подобные вещи внутри метода get_context_data.
Вы также можете использовать комбинацию values()
и annotate()
, так:
from django.db.models import Count
def display_orders(request):
orders = Order.objects.values(
'id',
'orderTable',
'menuQuery'
).annotate(
poQty_=Count('id')
).order_by(
'id',
'orderTable',
'menuQuery'
)
context = {
'orders': orders,
}
return render(request, 'orders.html', context)
Этот код сгруппирует заказы по указанным полям (id, orderTable, menuQuery), подсчитает количество вхождений каждой группы и аннотирует подсчет для каждого результата.