Подтвердить что ты не робот

Аннотировать сумму двух полей, умноженных

У меня есть три модели, упрощенные для примера:

class Customer(models.Model):
    email = models.CharField(max_length=128)

class Order(models.Model):
    customer = models.ForeignKey(Customer)
    order_status = models.CharField(blank=True, max_length=256)

class Lineitem(models.Model):
    order = models.ForeignKey(Order)
    quantity = models.IntegerField(blank=True)
    price = models.DecimalField(max_digits=6, decimal_places=2)

Я хочу запросить клиентов (возможно, с фильтром) и аннотировать общую сумму, которую они потратили (т.е. сумма (количество * цены)

Я пробовал:
Customer.objects.filter(something).annotate(total_spent=Sum(F('order__lineitem__quantity') * F('order__lineitem__price')))

Похоже, что Sum() не может использоваться с выражениями F(). Есть ли другой способ сделать это?

4b9b3361

Ответ 1

Возможно, вам сейчас не нужен этот ответ, но если вы прочтете документацию о выражение суммы, вам нужно объявить output_field, например:

Customer.objects.filter(something)
                .annotate(total_spent=Sum(
                    F('order__lineitem__quantity') * 
                    F('order__lineitem__price'),   
                    output_field=models.FloatField()
                ))

Ответ 3

Я просто столкнулся с этим, и я не думаю, что это аннотация и будет работать с свойством, см. Django. Можете ли вы использовать свойство как поле в функции агрегации?

Вот что я сделал.

class Customer(models.Model):
    email = models.CharField(max_length=128)

class Order(models.Model):
    customer = models.ForeignKey(Customer)
    order_status = models.CharField(blank=True, max_length=256)

class Lineitem(models.Model):
    order = models.ForeignKey(Order)
    quantity = models.IntegerField(blank=True)
    price = models.DecimalField(max_digits=6, decimal_places=2)
    @property
    def total(self):
        return self.quantity * self.price

Затем используйте сумму и понимание списка:

sum([li.total for li in  LineItem.objects.filter(order__customer=some_customer).filter(somefilter)])

Ответ 4

Вам, вероятно, придется развернуть свой собственный агрегатор. Вы можете найти простую прогулку в примере GROUP_CONCAT, который должен вас начать здесь: http://harkablog.com/inside-the-django-orm-aggregates.html

Ответ 5

Вы можете попробовать использовать свойство в модели LineItem:

class Lineitem(models.Model):
    order = models.ForeignKey(Order)
    quantity = models.IntegerField(blank=True)
    price = models.DecimalField(max_digits=6, decimal_places=2)
    def _get_total(self):
        return quantity * price
    total = property(_get_total)

Затем я думаю, что вы можете аннотировать общую сумму, потраченную с помощью

Customer.objects.filter(something).annotate(total_spent=Sum('order__lineitem__total'))

Я не знаю, как эффективность этого метода относится к другим, но это больше Pythonic/Django-y, чем альтернатива, которая заключается в том, чтобы написать весь запрос SQL вручную, как в

Customer.objects.raw("SELECT ... from <customer_table_name> where ...")

Ответ 6

Аналогично: fooobar.com/info/162035/...

from django.db.models import Sum

q = Task.objects.filter(your-filter-here).annotate(total=Sum('progress', field="progress*estimated_days"))

Отредактировано: благодаря @Max, используя аннотацию вместо агрегированного.