[Fixed]-Django custom prefetch and conditional annotate

1👍

Not saying this is the best practice or anything, but wanted to provide a potential way of dealing with such a situation.

Let’s say you have a ChantQuerySet class:

class ChantQuerySet(models.QuerySet):
    def with_audio(self):
        return self.filter(chants__has_audio_versions=True, chants__has_audio=True,
                           chants__flag_reject=False, chants__active=False)

Which you use as a manager doing something like below, probably:

class Chant(models.Model):
    # ...
    objects = ChantQuerySet.as_manager()

I would suggest storing the filter in the QuerySet:

from django.db.models import Q

class ChantQuerySet(models.QuerySet):
    @property
    def with_audio_filter(self):
        return Q(chants__has_audio_versions=True, chants__has_audio=True,
                 chants__flag_reject=False, chants__active=False)

    def with_audio(self):
        return self.filter(self.with_audio_filter)

This gives you the ability to do this:

Chant.objects.annotate(
    unreleased_count=Count(Case(
        When(ChantQuerySet.with_audio_filter, then=1),
        output_field=IntegerField()))
).filter(unreleased_count__gt=0)

Now you are able to change the filter only in one place, should you need to do so, without having to change it everywhere. To me it makes sense to store this filter in the QuerySet and personally I see nothing wrong with that, but that’s just me.

One thing that I’d change though, is to either make the with_audio_filter property cached, or store it in a field in the constructor when initializing ChantQuerySet.

Leave a comment