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), подсчитает количество вхождений каждой группы и аннотирует подсчет для каждого результата.

Вернуться на верх