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:
- check for a db annotation first
- 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()