Template Inheritance
Our list page renders a lone <h1>. Every page on our site needs the same HTML around its content: a <head> that loads the stylesheet, and a header with the site's navigation. If we copied that into each template, changing one link would mean editing every file.
A base template holds that layout once. Each page's template extends it and fills in only its own part. If you've used Jinja, {% extends %} and {% block %} work the same way.
The Base Template
The base template belongs to the whole site, not to the animals app, so we'll put it in a templates/ folder at the top of the project, beside manage.py.
The TEMPLATES setting's DIRS lists folders to search for templates, before each app's templates/ folder. Add ours to the empty DIRS list in config/settings.py:
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / "templates"],
"APP_DIRS": True,
The starting branch provides templates/base.html as plain HTML, with each address written out. Make it a template, changing the highlighted lines:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{% block title %}RescueScout{% endblock %}</title>
<link rel="icon" href="{% static 'img/favicon.svg' %}" type="image/svg+xml">
<link rel="stylesheet" href="{% static 'css/site.css' %}">
</head>
<body>
<header>
<nav>
<a href="{% url 'animals:list' %}">Home</a>
</nav>
</header>
<main>
{% for message in messages %}
<p>{{ message }}</p>
{% endfor %}
{% block content %}{% endblock %}
</main>
</body>
</html>
A template extending this one can replace the part marked by a {% block %} tag. The text between {% block title %} and {% endblock %} is the default. If a page doesn't fill in title, its title is "RescueScout". The content block is empty for each page to fill in.
The navigation's link uses {% url %} with our URL pattern's name, animals:list, so the link follows the pattern if its route changes.
The {% for %} loop above the content block shows one-time messages, which we'll add with the application form.
Extending the Base
Replace animals/templates/animals/animal_list.html with a template that extends the base:
{% extends "base.html" %}
{% block content %}
<h1>{{ heading }}</h1>
{% endblock %}
{% extends %} has to be the first tag in the template. The page then fills in the blocks it names, and anything it writes outside a block is ignored. This one leaves title alone, so it gets the base's default.
Open http://127.0.0.1:8000/. The heading is inside the base's layout now, below the navigation, with the site's stylesheet.
Static Files
A site serves static files, such as stylesheets, scripts, and images, as they are. The starting branch provides our stylesheet and site icon in static/ at the top of the project.
Django finds static files in each app's static/ folder, and in the folders STATICFILES_DIRS lists. The starting branch lists ours in config/settings.py, below MEDIA_URL:
STATICFILES_DIRS = [BASE_DIR / "static"]
{% load static %} at the top of base.html loads the library that defines the {% static %} tag. {% static 'css/site.css' %} writes the file's URL from the STATIC_URL setting: /static/css/site.css.
runserver serves static files only while DEBUG is on. A production site collects them into one folder with collectstatic and serves them from there, which is out of scope here.