[Django]-Django annotation with nested filter

15👍

As of Django 1.8 you can do this directly in the ORM:

students = Student.objects.all().annotate(num_excused_absences=models.Sum(
    models.Case(
        models.When(absence__type='Excused', then=1),
    default=0,
    output_field=models.IntegerField()
)))

Answer adapted from another SO question on the same topic

I haven’t tested the sample above but did accomplish something similar in my own app.

5👍

You are correct – django does not allow you to filter the related objects being counted, without also applying the filter to the primary objects, and therefore excluding those primary objects with a no related objects after filtering.

But, in a bit of abstraction leakage, you can count groups by using a values query.

So, I collect the absences in a dictionary, and use that in a loop. Something like this:

# a query for students
students = Students.objects.all()
# a query to count the student attendances, grouped by type.
attendance_counts = Attendence(student__in=students).values('student', 'type').annotate(abs=Count('pk'))
# regroup that into a dictionary {student -> { type -> count }}
from itertools import groupby
attendance_s_t = dict((s, (dict(t, c) for (s, t, c) in g)) for s, g in groupby(attendance_counts, lambda (s, t, c): s))
# then use them efficiently:
for student in students:
    student.absences = attendance_s_t.get(student.pk, {}).get('Excused', 0)

-4👍

Maybe this will work for you:

excused = Student.objects.filter(attendance__type='Excused').annotate(abs=Count('attendance'))

You need to filter the Students you’re looking for first to just those with excused absences and then annotate the count of them.

Here’s a link to the Django Aggregation Docs where it discusses filtering order.

Leave a comment