Tengo tres modelos:
class BaseModel(Model): deleted = BooleanField(default=False) class Document(BaseModel): def total_price() return DocumentLine.objects.filter( section__in=self.sections.filter(deleted=False), deleted=False, ).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() Como puede ver en LineQuerySet , hay un método que anotará el conjunto de consultas con el precio total de cada línea, según el precio y la cantidad.
Ahora puedo obtener fácilmente el precio total de un documento completo haciendo algo como esto (tenga en cuenta que las líneas y secciones con deleted=True se ignoran):
document = Document.objects.get(pk=1) total_price = document.total_price() Sin embargo, ahora me gustaría generar un conjunto de consultas de varios documentos y anotarlo con el total price de cada documento. Probé una combinación de anotaciones, agregados, haciendo uso de prefetch_related (usando Prefetch) y OuterRef, pero parece que no puedo obtener el resultado que quiero sin que arroje un error.
¿Hay alguna forma de realizar esta operación en un conjunto de consultas, haciendo posible filtrar u ordenar por este campo total_price ?
Puedes anotar con:
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')) ) Cada Document que surja de este conjunto de consultas tendrá un atributo .total_price que es la suma del precio por la cantidad de todas las líneas relacionadas de todas las secciones relacionadas de ese Document .
Una alternativa es trabajar con una expresión Subquery [Django-doc] para determinar la suma, así:
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')).values('total_price')[:1] ) )