AnnotatableField django package.

I thought this might be useful so I published it on pypi as dj-annotatable-field

Why?

from django.db import models

class Owner(models.Model):
    @property
    def dog_count(self):
        return self.dogs.count()   

class Dog(models.Model):
    owner = models.ForeignKey(Owner, on_delete=models.DO_NOTHING, related_name="dogs")

The dog_count python calculated property is subject to n+1 issues without proper prefetching/logic or checking for an annotation. However, the calculated property is definitely convenient to have declared on the object (if they’re already prefetched, or working in the shell, or on a test, etc). One solution is the following:

  1. check for a db annotation first
  2. if no db annotation calculate result in python
from django.db import models
from django.db.models import Count

class Owner(models.Model):
    @property
    def dog_count(self):
        count = getattr(self, '_dog_count', None)
        if count is not None:
            return count
        return self.dogs.count()

Owner.objects.annotate(_dog_count=Count('dogs'))

This library contains the AnnotatedField to encapsulate this pattern

from django.db import models
from django.db.models import Count
from dj_annotatable_field import AnnotatableField, AnnotatableFieldsManager

class Owner(models.Model):
    dog_count = AnnotatableField(Count('dogs'))
    
    objects = AnnotatableFieldsManager()
    
    @dog_count.source
    def dog_count_source(self):
        return self.dogs.count()

Owner.objects.annotate_fields()
1 Like