Exercise: The Detail Page
You'll:
- write
AnimalDetailViewas aDetailView - fill in the detail page's template
- run the tests
- look at an animal's page in the browser
What's provided in your branch:
- the list page as a
ListView, the URL patterns, the queries inanimals/queries.py, and the base template static/, the stylesheet, its fonts, and the faviconAnimalDetailView's class line, and the detail template's HTML withTODOs- tests for the animal's page
Before You Start
Stop the development server with Ctrl+C if it's running. Then set up the starting state for this exercise:
git stash -u
git fetch --all
git checkout -b my-ch05 --no-track origin/ch05
uv sync
If Git prints No local changes to save, there was nothing to set aside.
Files that changed in this chapter:
config/settings.py(modified, adds the project'stemplates/folder toDIRS, andSTATICFILES_DIRS)config/urls.py(modified, includes theanimalsapp's URL patterns)templates/base.html(new, the site's layout)static/(new, the stylesheet, its fonts, and the favicon)animals/urls.py(new, the list and detail pages' patterns)animals/queries.py(new, the list page's queries)animals/views.py(modified, the list page as aListView, andAnimalDetailView; you'll write its body)animals/templates/animals/animal_list.html(new)animals/templates/animals/animal_detail.html(new, you'll fill it in)tests/test_pages.py(new)tests/conftest.py(modified, keeps uploaded photos in a temporary folder)tests/test_admin.py(removed)AGENTS.md(modified, adds djLint's checks and a rule for the pages' views)README.md(modified, describes the pages)
Step 1: Write AnimalDetailView
DetailView does what animal_detail() does:
- The row: It looks up the row in the class's
modelby the URL'spk. If there isn't one, it raisesHttp404, asget_object_or_404()did. - The template:
animals/animal_detail.html, named after the model, asListView's is. - The context: The row, as
animaland asobject.
Our URL's converter is already named pk, and our template already uses animal. The flattened index lists every attribute DetailView has, and ccbv.co.uk shows each one's default.
Open animals/views.py. The detail page's URL pattern points at AnimalDetailView, whose body is a TODO:
class AnimalDetailView(DetailView):
# TODO: Set `model`, so DetailView knows which table to look the animal up in.
pass
Step 2: Fill In the Detail Template
Open animals/templates/animals/animal_detail.html. The photo, the status, the age, and the arrival date are filled in. The HTML for the species, breed, and link lines is there too, so each TODO needs only template code:
{% extends "base.html" %}
{% block title %}{{ animal.name }} · RescueScout{% endblock %}
{% block content %}
{% if animal.photo %}
<img src="{{ animal.photo.url }}" alt="{{ animal.name }}">
{% endif %}
<h1>{{ animal.name }}</h1>
<p>Status: {{ animal.get_status_display }}</p>
{# TODO: Add the species' label in the same way that the status line shows its label. #}
<p>Species:</p>
{# TODO: Show this line only when the animal has a breed, as the photo is shown. #}
<p>Breed: {{ animal.breed }}</p>
<p>Age: {{ animal.age_label }}</p>
<p>Arrived: {{ animal.intake_date|date:"j F Y" }}</p>
{# TODO: Show the description with the linebreaks filter. The filter writes the `<p>` tags, so don't add a `<p>` tag here. #}
{# TODO: Fill in the href with the list page's URL, from its pattern's name in animals/urls.py. #}
<p><a href="">All animals</a></p>
{% endblock %}
The description takes another filter, linebreaks: it turns the blank lines in plain text into paragraphs, and single line breaks into <br> tags. It escapes the text first, so HTML in a description shows as text.
Step 3: Run the Tests
uv run pytest
All tests should pass.
Step 4: Look at an Animal's Page
- Start the server with
uv run manage.py runserver. - Open http://127.0.0.1:8000/.
- Click Seven, seventh on the list.
Seven's page shows:
- the photo
- the status, species, breed, and age
- the date Seven arrived, with the month's name written out
- a description in separate paragraphs
All animals goes back to the list.
Then open Truffle, on the second page. Truffle's breed is unknown, so its page has no breed line.
Success
Your detail page is working if the tests pass, Seven's page shows each detail and the description's paragraphs, and Truffle's has no breed line.
Troubleshooting Tips
djLint is not installed or cannot be executed with the current extension settings
VS Code is using a Python environment other than the project's, so the djLint extension can't find djLint. Run Python: Select Interpreter from the Command Palette and choose the one in .venv.
ImproperlyConfigured: AnimalDetailView is missing a QuerySet
AnimalDetailView has no model, so DetailView doesn't know which table to look the animal up in. Set it in the class's body, in place of pass.
TemplateSyntaxError: … Could not parse the remainder: '()'
A method is called with parentheses, such as animal.get_species_display(). A template calls a method without them, as the status line does.
NoReverseMatch: Reverse for '...' not found
The url tag takes the pattern's name with the app's namespace in front, animals:list. The list's pattern has no converter, so the tag takes no argument after the name.
test_detail_page_leaves_out_an_empty_breed fails
The Breed: line is outside an {% if %}, so an animal with no breed still gets one. Put it inside an {% if %} on animal.breed, as the photo is. On the site, Truffle's page shows an empty Breed: line.
A value is missing from the page, with no error
A misspelled variable renders as an empty string. Compare the name after animal. with the model's fields and methods in animals/models.py.
Compare with the Solution
git diff solutions/ch05 -- animals/views.py animals/templates/animals/animal_detail.html
See the solution on GitHub: animals/views.py and animals/templates/animals/animal_detail.html.