Skip to content

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:

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:

templates/base.html
{% 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:

HTML
{% 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:

config/settings.py
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.