Loading…

Don’t repeat Yourself – Understanding Django Template Inheritance

Django comes with a powerful template language. The Django tutorial only scratches the surface of Django’s templating capabilities and doesn’t get to one of the most powerful features: template inheritance. In this article, I will show you how to use the template tags {% block %} and {% extends %} to avoid repeating the same code over and over.

The templates in the tutorial look like this:

{% load static %}

<link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}" />

{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

Conveniently, this template completely omits all the cruft that usually makes up an HTML page, like the <head>, <title> and <body> tags.

Let’s turn the template for the polls list into a full, valid HTML page:

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <title>My amazing Polls application</title>
    <link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}" />
</head>

<body>
    <div id="sidebar">
        <ul>
            <li><a href="/polls/">Polls</a></li>
            <li><a href="/admin/">Admin</a></li>
        </ul>
    </div>

    <div id="content">
        {% if latest_question_list %}
            <ul>
            {% for question in latest_question_list %}
                <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
            {% endfor %}
            </ul>
        {% else %}
            <p>No polls are available.</p>
        {% endif %}
    </div>
</body>
</html>

Only the highlighted parts are really unique to this template, everything else will be mostly the same for all pages of our project. You could add the common parts to every template, but this would violate the DRY principle: don’t repeat yourself.

Even if you only have a handful of pages, making sure that the common parts of all templates match is a cumbersome and error-prone process.

This is where template inheritance comes into play. With template inheritance, you can define a base template that your templates extend.

A base template looks just like a normal template, but it defines blocks that can be overwritten by child templates. The {% block ... %} tag in the extended template defines a placeholder, the corresponding {% block ... %} in the extending template defines the content that goes into the placeholder.

In object-oriented lingo, a base template is like an abstract superclass. Any template that extends the base template is like a class inheriting from an abstract superclass. A block in a base template is like a method in a superclass that you can overwrite.

Prerequisites

Before we dive further into the code, I want to show you how you can easily follow along. The code in this post is part of my project Django beyond the tutorial which is based on the Poll application you build in the Django tutorial. I’ve uploaded the complete code to Github. If you are not familiar with Git, you can download a Zip file with the code:

Alternatively, you can clone the repository using your favorite Git client or on the command line like this:

$ git clone https://github.com/consideratecode/django-beyond.git

There are two tags related to this post:

If you get lost along the way, compare your work with [https://github.com/consideratecode/django-beyond/compare/template_inheritance_start…template_inheritance_end](the diff between these tags).

Refer to the included for further setup instructions.

Building blocks

Let’s look at a concrete example using the {% block %} tag: a base template that defines two blocks, title for the page title, and content for the main page content. Note that it also loads the static template tag library and links the style sheet. The stylesheet is still in the polls app, we’ll fix that later.

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <title>{% block title %}My amazing Polls application{% endblock %}</title>
    <link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}" />
</head>

<body>
    <div id="sidebar">
        {% block sidebar %}
        <ul>
            <li><a href="/polls/">Polls</a></li>
            <li><a href="/admin/">Admin</a></li>
        </ul>
        {% endblock %}
    </div>

    <div id="content">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

Now we change our original template to extend our new base template. We have to add an {% extends %} tag to indicate that this template extends another template, and we have to wrap our content in {% block content %}{% endtag %}. We can also remove the <link rel="stylesheet"...> tag, as it is now part of our base template.

{% extends 'base.html' %}
{% block content %}

{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}
{% endblock %}

If we now open our browser and look at the page source, we can see that the {% block content %} tag in the base.html template has been replaced with the contents of the {% block content %} tag in the polls/details.html template. Our template doesn’t overwrite the {% block title %}, so the content from the base template is used.


<!DOCTYPE html>
<html lang="en">
<head>
    <title>My amazing Polls application</title>
    <link rel="stylesheet" type="text/css" href="/static/polls/style.css" />
</head>

<body>
    <div id="sidebar">
        <ul>
            <li><a href="/polls/">Polls</a></li>
            <li><a href="/admin/">Admin</a></li>
        </ul>
    </div>

    <div id="content">
        
            <ul>
            
                <li><a href="/polls/1/">How are you today?</a></li>
            
            </ul>
        
    </div>
</body>
</html>

Note that the argument to {% extends %}, the name of the template you want to extend, has to be passed in quotes, but the argument to {% block %}, the name of the block you want to overwrite, does not go into quotes.1

We can now do the same for our other templates. Here are two more examples:

{% extends 'base.html' %}
{% block content %}
<h1>{{ question.question_text }}</h1>

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
{% for choice in question.choice_set.all %}
    <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
    <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
{% endfor %}
<input type="submit" value="Vote" />
</form>
{% endblock %}
{% extends 'base.html' %}
{% block content %}
<h1>{{ question.question_text }}</h1>

<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>

<a href="{% url 'polls:detail' question.id %}">Vote again?</a>
{% endblock %}

Don’t forget to change the templates located in templates:

  • templates/accounts/profile.html
  • templates/registration/login.html
  • templates/registration/logged_out.html

Reaping the benefits

So far, the advantages of template inheritance might not be obvious. We have done the grunt work of setting up the base template and changing all our templates to extend it. Now it is time to reap the benefits!

We will add a new link to the navigation menu which allows a user to log in or log out. Simply add this line to templates/base.html, specifically to the <ul> tag in {% block sidebar %}:

{% block sidebar %}
<ul>
    <li><a href="/polls/">Polls</a></li>
    <li><a href="/admin/">Admin</a></li>
    <li>
    {% if user.is_authenticated %}
        <a href="/accounts/logout/">Logout</a>
    {% else %}
        <a href="/accounts/login/">Login</a>
    {% endif %}
    </li>
</ul>
{% endblock %}

Voilá, just like this, we have added a new menu point to all pages.

But wait, aren’t those hardcoded URLs bad practice? Yes, they are! Let’s use the {% url %} tag instead.

{% block sidebar %}
<ul>
    <li><a href="{% url "polls:index" %}">Polls</a></li>
    <li><a href="{% url "admin" %}">Admin</a></li>
    <li>
    {% if user.is_authenticated %}
        <a href="{% url "logout" %}">Logout</a>
    {% else %}
        <a href="{% url "login" %}">Login</a>
    {% endif %}
    </li>
</ul>
{% endblock %}

Re-using block content with {{ block.super }}

In some cases, you want to add something to the content of a block instead of completely overwriting it.

Current the HTML title of our project reads “My amazing Polls application” on all pages. We want to keep that, but prepend a more descriptive title for some pages, e.g.

  • “Current polls – My amazing Polls application” for /polls/ (polls:index):
  • “Poll: {{ question }} – My amazing Polls application” for /polls/<int:pk>/ (polls:detail):

We could achieve that but overwriting the {% block title %} like this:

{% extends 'base.html' %}
{% block title %}Current polls - My amazing Polls application{% endblock %}
{% block content %}
...
{% extends 'base.html' %}
{% block title %}Poll: {{ question }} - My amazing Polls application{% endblock %}
{% block content %}
<h1>{{ question.question_text }}</h1>
...

But this violates the DRY principle again. If we decide to dial up the marketing hyperbole and want to put “The most amazing Polls application” in the title, we have to change every template.

We can avoid that by using {{ block.super }}. This tag gets replaced with the content of the overwritten block.

{% extends 'base.html' %}
{% block title %}Current polls - {{ block.super }}{% endblock %}
{% block content %}
...
{% extends 'base.html' %}
{% block title %}Poll: {{ question }} - {{ block.super }}{% endblock %}
{% block content %}
<h1>{{ question.question_text }}</h1>
...

Housekeeping

We want to keep our amazing application in good shape, so let’s do a little housekeeping.

When creating the base template, we included the <link rel="stylesheet" ...> tag in the <head>, but we left the stylesheet file at its original path in the polls app. This violates the principle of [https://en.wikipedia.org/wiki/Loose_coupling](loose coupling): separate components should have little to no knowledge of each other. In this case, the base.html template has unnecessary knowledge about the polls app.

To fix that, we create a new directory static in the top-level mysite directory (next to the templates directory) and move the stylesheet there.

$ ls
db.sqlite3 manage.py  mysite     polls      template   templates
$ mkdir static
$ mv polls/static/polls/style.css static/

We now have to tell Django to look in this directory for static files as well by adding the STATIC_FILES setting to mysite/settings.py.

...
STATIC_URL = '/static/'

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "static"),
]

While we are at it, we can remove the background image, which is a left-over from the tutorial, from the stylesheet and delete the file.

li a {
    color: green;
}

body {
    background: white url("images/background.gif") no-repeat right bottom;
}
$ rm polls/static/polls/images/background.gif

Summary

Congratulations, you made it to the end! You should now have a solid understanding of template inheritance. We covered:

  • how to extend a base template with the {% extends %} tag.
  • how to define blocks to be overwritten with {% block %} tags.
  • how to access the content of a base template with {{ block.super }}

For further reference, check out the section on [https://docs.djangoproject.com/en/2.0/ref/templates/language/#template-inheritance](Template Inheritance in the Django documentation). If anything remains unclear, don’t hesitate to leave a comment or get in touch directly.

What’s next?

We fiddled with the templates for quite a bit, but frankly, our amazing Polls application doesn’t look that appealing yet.

In a future post, I will show you how to make your site look appealing by using the Bootstrap library. Make sure to subscribe so you don’t miss it!

[subscribe_form_django]


  1. If you pass a string without quotes to {% extends %}, it gets interpreted as a variable, e.g. {% extends base_template %}. This doesn’t work with {% block %}. Django doesn’t complain if you put a block’s name in quotes, e.g. {% block "content" %}, but the quotes would just be part of the block’s name. Makes sense? 

2 thoughts on “Don’t repeat Yourself – Understanding Django Template Inheritance

Leave a Reply