Django - Аннотирование совокупности аннотаций на связанные объекты
У меня есть три модели:
class Document(BaseModel):
def total_price()
return DocumentLine.objects.filter(
section__in=self.sections.all()
).total_price()
class Section(BaseModel):
document = ForeignKey(Document, on_delete=CASCADE, related_name='sections')
class LineQuerySet(QuerySet):
def with_total_price(self):
total_price = F('quantity') * F('price')
return self.annotate(
total_price=ExpressionWrapper(total_price, output_field=DecimalField())
)
def total_price(self):
return self.with_total_prices().aggregate(
Sum('total_price', output_field=DecimalField())
)['total_price__sum'] or Decimal(0.0)
class Line(BaseModel):
objects = LineQuerySet.as_manager()
section = ForeignKey(Section, on_delete=CASCADE, related_name='lines')
price = DecimalField()
quantity = DecimalField()
Как вы можете видеть на LineQuerySet
, есть метод, который аннотирует кверисет с общей ценой каждой строки, основанной на цене и количестве.
Теперь я могу легко получить общую стоимость всего документа, сделав примерно следующее:
document = Document.objects.get(pk=1)
total_price = document.total_price()
Однако теперь я хочу сгенерировать набор запросов из нескольких документов и аннотировать его total price
для каждого документа. Я пробовал комбинацию аннотаций, агрегатов, использование prefetch_related (с помощью Prefetch) и OuterRef, но мне не удается получить желаемый результат без возникновения ошибки.
Есть ли способ выполнить эту операцию в наборе запросов, чтобы потом можно было фильтровать или упорядочивать по этому полю total_price
?
Вы можете аннотировать с помощью:
from django.db.models import F, Sum
Document.objects.filter(
deleted=False,
sections__deleted=False,
section__lines__deleted=False
).annotate(
total_price=Sum(F('sections__lines__price')*F('sections__lines__quantity'))
)
Каждый Document
, возникающий из этого кверисета, будет иметь атрибут .total_price
, который является суммой цены, умноженной на количество всех связанных строк всех связанных разделов этого Document
.
Альтернативой является работа с выражением Subquery
[Django-doc] для определения суммы, так:
from django.db.models import F, OuterRef, Subquery, Sum
Document.objects.annotate(
total_price=Subquery(
Line.objects.values(
document_id=F('section__document_id')
).filter(
deleted=False, section__deleted=False, document__deleted=False
).annotate(
total_price=Sum(F('price') * F('quantity'))
).order_by('document_id').filter(document_id=OuterRef('pk'))[:1]
)
)