Building the Public-Facing Views: List & Detail
Personal Catalogue: Django & PostgreSQL
Chapter 4 · Building the Public-Facing Views: List & Detail
Chapter 3 gave this project a real, working way to add items — but only through the admin, a login-gated internal tool. This chapter builds the pages the catalogue is actually for: a real list of every item, and a detail page for each one, both genuinely readable without ever touching /admin/.
Two Real Views: List & Detail
Django's own built-in generic class-based views handle both of these directly. Rather than writing a function that manually fetches a queryset, passes it to a template, and returns an HttpResponse, ListView and DetailView do that real, repetitive plumbing automatically from a small amount of configuration.
The List View
select_related('item_type') and prefetch_related('tags') are both deliberate, not decoration — the reasoning for each is covered directly below, once the template that actually needs them exists.
The Detail View
DetailView looks up a single Item by its primary key (taken from the URL) and returns a real, genuine HTTP 404 automatically if no item with that ID exists — no manual get_object_or_404 call needed, since the generic view already wraps that lookup internally.
URL Routing
Include this app-level urls.py from the project's own root catalogue_site/urls.py:
Minimal Templates
A small base template, extended by both real pages:
{% extends %} and {% block %} are Django's own real template inheritance mechanism — item_list.html doesn't repeat the <html>/<head>/navigation markup at all, it only fills in the content block base.html already declared. Every later template in this course (detail page, add-item form, Chapter 8's own styled versions) extends the same base.html.
{% for tag in item.tags.all %} loop inside item_list.html runs once per item on the page — meaning without prefetch_related('tags') in the view's own queryset, displaying 25 items would trigger 25 separate database queries just to fetch tags, on top of the one query that fetched the items themselves: a real, classic N+1 query problem. prefetch_related('tags') fixes this by issuing exactly one additional query up front, fetching every tag for every item on the page at once, which Django then serves from memory for each item.tags.all call inside the template loop. select_related('item_type') solves the same underlying problem one level earlier, for the single-valued item.item_type lookup the template also uses — a real JOIN folded into the original query instead of a second query per item.
select_related only works for single-valued relationships (a ForeignKey like item_type), since it works by joining the related table directly into the original SQL query. prefetch_related is required for multi-valued relationships (a ManyToManyField like tags), since a single SQL join can't cleanly represent "many tags per item" in one flat result set — it runs as a real, separate second query instead. Using the wrong one, or skipping both, either raises an error or silently reintroduces the N+1 problem this chapter just fixed.
item_detail.html follows the identical pattern, extending the same base.html and rendering the single item the view resolved, including its full tag list — with no N+1 risk here at all, since only one item's own tags are ever fetched on this page.
Trying It Out
Visit the site's own root URL and confirm every item added in Chapter 3 — through the admin — now shows up on this real, public-facing list page too, since both interfaces read from the exact same Item table. Click through to a detail page, and confirm visiting a nonexistent item ID (e.g. /item/9999/) returns a real 404 rather than an error page or a blank screen.
Hands-On Exercises
Build both views, both templates, and the URL routing from this chapter, then confirm the list page correctly shows every item added through the admin in Chapter 3, tags included.
📄 View solutionExplain, step by step, why removing prefetch_related('tags') from the list view's own queryset would produce exactly 26 real database queries to render a page of 25 items with tags — not 25, and not 1.
Explain why select_related can't be used for the tags relationship, even though it's used successfully for item_type on the exact same queryset.
Chapter 4 Quick Reference
- ListView / DetailView — Django's own generic class-based views, handling querying and 404s automatically from minimal configuration
- URL routing — an app-level
urls.pyincluded from the project's own rooturls.py - Template inheritance —
{% extends %}/{% block %}share a commonbase.htmlacross every page - Real N+1 bug found and fixed — looping over
item.tags.allper item withoutprefetch_related('tags')would trigger one extra query per item - select_related vs. prefetch_related — single-valued relationships (
ForeignKey) useselect_related; multi-valued relationships (ManyToManyField) needprefetch_relatedinstead - Next chapter: a real public-facing add-item form, handling several genuinely different item shapes outside the admin