Skip to content

Exercise: The Detail Page

You'll:

  • write AnimalDetailView as a DetailView
  • 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 in animals/queries.py, and the base template
  • static/, the stylesheet, its fonts, and the favicon
  • AnimalDetailView's class line, and the detail template's HTML with TODOs
  • 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:

shell
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's templates/ folder to DIRS, and STATICFILES_DIRS)
  • config/urls.py (modified, includes the animals app'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 a ListView, and AnimalDetailView; 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 model by the URL's pk. If there isn't one, it raises Http404, as get_object_or_404() did.
  • The template: animals/animal_detail.html, named after the model, as ListView's is.
  • The context: The row, as animal and as object.

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:

animals/views.py
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:

animals/templates/animals/animal_detail.html
{% 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

shell
uv run pytest

All tests should pass.

Step 4: Look at an Animal's Page

  1. Start the server with uv run manage.py runserver.
  2. Open http://127.0.0.1:8000/.
  3. 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

shell
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.