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]
    )
)
Вернуться на верх