[Fixed]-Combine prefetch_related and annotate in Django

10๐Ÿ‘

I think you should be able to achieve that by doing this:

from django.db.models import F, Sum

(ModelA.objects.all()
    .prefetch_related('modelb_set', 'modelb__modelc_set')\
    .values('name')\  # group values by modela.name, read: https://docs.djangoproject.com/en/1.9/topics/db/aggregation/
    .annotate(name = F('name'),
              total_amount = Sum('modelb__modelc__amount')))

and in your template you should use:

{% for modela in modela_list %}
    {{ modela.name }}, {{ modela.total_amount }}
{% endfor %}
๐Ÿ‘คMiao ZhiCheng

0๐Ÿ‘

You can simplify this down to:

from django.db.models import F, Sum

ModelA.objects.all()
    .prefetch_related('modelb__modelc_set') \
    .values('name') \
    .annotate(name=F('name'), total_amount=Sum('modelb__modelc__amount'))

I know this works in later versions of Django.

๐Ÿ‘คnatehawkboss

0๐Ÿ‘

i did it by using django internals to check if related queryset is evaluated / prefetched

from django.db.models import Sum, QuerySet, Model


def is_evaluated_queryset(qs):
    return isinstance(qs, QuerySet) and qs._result_cache is not None


class Order(Model):
    """Order can have multiple payments"""
    
    def get_total_payment(self):
        """ returns sum of all the payments for this order"""

        payments_qs = self.payment_set.all()
        if is_evaluated_queryset(payments_qs):
            # qs is prefetched - better not to use aggregate
            return sum(payment.amount for payment in payments_qs)
        else:
            return payments_qs.aggregate(total=Sum('amount'))['total'] or 0
๐Ÿ‘คpymen

Leave a comment