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:
- Snapshot of the project before the tutorial. Use this as a base if you want to follow along.
- Snapshot of the project after the tutorial. Use this to compare your result afterwards.
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:
- https://github.com/consideratecode/django-beyond/tree/template_inheritance_start – the project before the tutorial.
- https://github.com/consideratecode/django-beyond/tree/template_inheritance_end – the project after the tutorial.
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!
-
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? ↩
Kudos to your great work!!