How method annotate() works in Django QuerySet?

Method annotate()  specifies the calculated value for each object in the QuerySet.

For example, we have the following models: 

  • Author
  • Post

Model Post has a ForeignKey field called author related to the model Author. Each author can write multiple posts. 

if you are retrieving a list of authors and want to know how many posts are written by each author. Here annotate() method comes for help. 

models.py

from django.db import models


class Author(models.Model):
    name = models.CharField(max_length=200)
    email = models.EmailField()

    def __str__(self):
        return self.name


class Post(models.Model):
    title = models.CharField(max_length=256)
    slug = models.SlugField(unique=True)
    text = models.TextField()
    author = models.ForeignKey(Author, on_delete=models.CASCADE)

    def __str__(self):
        return self.title

In the author_list view we will render the authors list ordered by post count in descending order. When an annotate() clause is specified, each author object in the QuerySet will be annotated with the post count called num_posts. Then we can order the queryset by num_posts.

views.py

from django.shortcuts import render
from django.db.models import Count

from . import models


def author_list(request):
    authors = models.Author.objects.all()
    authors = authors.annotate(num_posts=Count('post')).order_by('-num_posts')
    context = {
        'authors': authors
    }
    return render(
        request,
        'blog/authors.html',
        context=context
    )

Our blog/authors.html template looks like this:

<body>
<div class="container mt-5">
    <table class="table">
        <thead>
        <tr>
            <th scope="col">#</th>
            <th scope="col">Author</th>
            <th scope="col">Post count:</th>
        </tr>
        </thead>
        <tbody>
        {% for author in authors %}
            <tr>
                <th scope="row">1</th>
                <td>{{ author.name }}</td>
                <td>{{ author.num_posts }}</td>
            </tr>
        {% endfor %}
        </tbody>
    </table>
</div>
</body>
</html>

The screenshot of the authors page is shown below: