Personal Catalogue: Django & PostgreSQL — Chapter 6, Exercise 3
====================================================
TASK
Build the TagIndexView and its template, and explain in your own
words why Count('items') is a meaningfully faster approach than
looping over every tag and calling tag.items.count() individually.
SOLUTION
1. catalogue/views.py:
from django.db.models import Count
from .models import Tag
class TagIndexView(ListView):
model = Tag
template_name = 'catalogue/tag_index.html'
context_object_name = 'tags'
queryset = Tag.objects.annotate(item_count=Count('items')).order_by('-item_count')
2. catalogue/urls.py:
path('tags/', views.TagIndexView.as_view(), name='tag_index'),
3. catalogue/templates/catalogue/tag_index.html:
{% extends 'catalogue/base.html' %}
{% block content %}
{% for tag in tags %}
- {{ tag.name }} ({{ tag.item_count }})
{% endfor %}
{% endblock %}
WHY Count('items') IS MEANINGFULLY FASTER THAN A LOOP
The alternative approach - fetching all Tag rows, then looping over
each one and calling tag.items.count() - issues one initial query to
fetch the tags, and then one SEPARATE query per tag to count its
related items. For N tags, that's N+1 total queries, the exact same
kind of query-count problem the site's own N+1 finding in Chapter 4
already covered for tags on the item list page - just occurring here
on the tag side of the relationship instead of the item side.
Count('items'), used with annotate(), instead produces a single SQL
query that joins Tag against Item (via the auto-generated join table)
and groups the results by tag, computing every tag's own item count in
that one query. Whether there are 5 tags or 500, the query count stays
exactly 1 - the database itself does the counting and grouping work in
one pass, rather than the application issuing a separate round-trip
per tag.
ANSWER: Count('items') combined with annotate() computes every tag's
own item total in a single database query, using a real SQL JOIN and
GROUP BY under the hood. Looping over each tag and calling
tag.items.count() individually issues one additional query per tag on
top of the initial tag-fetching query - an N+1 query pattern that gets
measurably slower as the number of tags grows, exactly the same class
of problem the site's own item-list N+1 bug represented, just occurring
on the opposite side of the same tags relationship.
WHY THIS WORKS AS AN ANSWER
----------------------------
It builds the real view/template pair, and explains the performance
difference in terms of actual query counts rather than a vague "it's
more efficient" claim, explicitly connecting it back to the N+1 pattern
already established earlier in the course.