I often find that when I am working in Django, I want to have the ability to get a summary of many objects related to a single object in an efficient manner. For larger projects or more complex scenarios there may be a reason to denormalize the data in your model and precompute such summaries, but certainly in small projects and in many situations, this is something you might want to do. The problem arrises when you are creating list views (either in your API or in templates) that require this summary on multiple objects. For anybody that has worked extensively with Django ORM, you will be intimately familiar with the need in situations like this for the prefetch_related functionality to avoid the possibility of an exponential increase in the number of database calls (depending on object nesting and depth). When creating such logic, one then has to decide if you should use the .all() query to retrieve all the objects in anticipation of them being prefetched to compute your summary value in Python or using a much more efficient targeted query.

For example, let's take a blogging site. You may have a list view of all the bloggers with summary values for the total number of posts and possibly even number of posts in their top categories. This might actually be a good scenario for precomputing these values and storing them somewhere as a part of the blogger's profile...but assuming we don't have that, we may have a data model that looks something like this:

from django.db import models

class Blogger(models.Model):
    name = models.CharField(max_length=50)

class Post(models.Model):
    title = models.CharField(max_length=200)
    body = models.Text()
    published_on = models.DateTimeField(null=True, blank=True)
    author = models.ForeignKey(
        'Blogger', related_name='posts', on_delete=models.CASCADE
    )

Now, let's take the example of wanting to know the total number of posts a blogger has published. This is functionality that you might want to use in various places in the code, so we will put it on the model to make it more useful to us...so now our Blogger model will look like:

from django.db import models
from django.utils.functional import cached_property

class Blogger(models.Model):
    name = models.CharField(max_length=50)

    # using a cached property has the advantage of only computing the
    # value once, but still giving the feel that this is something
    # that is part of the object.
   @cached_property
    def num_posts(self):
        pass . # implementation goes here

And now for our implementation. It would be nice to have a general use function so that when I want to use this functionality I don't have to worry about the decision that was made when writing this method...but rather I can concern myself with what makes sense for my use-case if I should prefetch_related or not. In order to accomplish this, we want to check if this field is going to be prefetched or already has been prefetched and then take the most efficient option in each case:

def num_posts(self):
    qs = self.posts.all()
    if qs._result_cache or qs._prefetch_related_lookups:
        return len(list(qs))
    return qs.count()

So our final solution would look something like:

from django.db import models
from django.utils.functional import cached_property

class Blogger(models.Model):
    name = models.CharField(max_length=50)

   @cached_property
    def num_posts(self):
        qs = self.posts.all()
        if qs._result_cache or qs._prefetch_related_lookups:
            return len(list(qs))
    return qs.count()

class Post(models.Model):
    title = models.CharField(max_length=200)
    body = models.Text()
    published_on = models.DateTimeField(null=True, blank=True)
    author = models.ForeignKey(
        'Blogger', related_name='posts', on_delete=models.CASCADE
    )