Reduce boilerplate with template inheritance
Django templates provide some of the most beautiful and straightforward
template mechanisms out there.
Template inheritance allows you reduce duplication and enjoy writing HTML more.
Let me show you my preferred pattern.
First, create a template called base.html. This template will contain HTML
common to all other templates. Thus, it should be as minimal as possible.
I use it to:
- Load the CSS stylesheet.
- Insert Javascript.
- Define generic blocks that other templates should override.
Here’s an example from my
simple-django
project template:
{% load i18n %}
{% load static %}
<!DOCTYPE html>
<html lang="{{ LANGUAGE_CODE|default:'en' }}">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>{% block seo_title %}{% endblock %} | Simple Django</title>
<meta name="description" content="{% block seo_description %}{% endblock %}">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="author" content="Josh Michael Karamuth">
{% block css %}
<link href="{% static 'css/main.min.css' %}" rel="stylesheet">
{% endblock %}
{% include 'partials/_head-styles.html' %}
</head>
<body>
{% block flash_messages %}
<div class="container">
{% include 'partials/_flash-messages.html' %}
</div>
{% endblock %}
{% block layout %}{% endblock %}
{% block modal %}{% endblock %}
{% block js %}
<script src="{% static 'js/popper.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/main.min.js' %}"></script>
{% endblock %}
<script>
document.addEventListener("DOMContentLoaded", function () {
{% block onbodyload %}
app.main.init();
{% endblock %}
})
</script>
</body>
</html>
This template is minimal but provides strategic blocks like layout,
flash_messages, *css, js, and so on for easy extensibility.
After that, you need to need to figure out what kind of layouts you’ll need.
Since I use
Bootstrap
, I like to have
different layouts that match Bootstrap’s grid system.
For example, I might have a fullbleed.html template that provides an
uncontained layout:
{% extends "base.html" %}
{% block flash_messages %}{% endblock %}
{% block layout %}
{% include "partials/_navbar.html" %}
<div class="container">
{% include "partials/_flash-messages.html" %}
</div>
{% block inner %}{% endblock %}
{% endblock %}
Then I might have a layout called col-4.html that I will use for web pages
where the content needs to be presented in a packed format:
{% extends "fullbleed.html" %}
{% block inner %}
<div class="container my-5">
<div class="row">
<div class="col-lg-4"></div>
<div class="col-lg-4">
<h1 class="mb-10 text-center">{% block headline %}{% endblock %}</h1>
{% block content %}{% endblock %}
</div>
<div class="col-lg-4"></div>
</div>
</div>
{% endblock %}
Notice how col-4.html extends fullbleed.html. That’s the beauty of Django
templates — it supports multiple inheritance. You can simply create different
generic layouts whenever you want and extend then when you want to specialize.
Many ways to enrich templates with data
Django templates are dumb but that’s by design. You can’t simply call an API
and dump the json contents in a list for example. But that’s a good thing because it
allows you to practice separation of concerns where you write specialized code
so that future developers can easily find and work on them in isolation.
From the view
Instead of looking for ways to call your API from templates, do it from your
view and pass the data in a context:
def list_pets(request):
res = requests.get('http://pet-api.com')
ctx = {
'pets': res.json()['pets']
}
return TemplateResponse(request, 'pets/list.html', ctx)
In your template, you simply access the object with {{ pets }}
.
Simple enough but what if you want to reuse the logic used to contact your API
across multiple views?
That’s where template tags shine.
Template tags allow you to call Python code from templates. Thanks to template
tags, you can do complicated stuff in your templates without turning them into
a mess.
To create a tag for the above logic, create a Python package called
templatetags inside your app’s directory. Then inside, create a file called
pets_tags.py. Using appname_tags is a common pattern when naming template
tags.
Then inside that file, put the following:
from django import template
register = template.Library()
def get_pets_list():
res = requests.get('http://pet-api.com')
return res.json['pets']
Now you can simply use this tag in any template:
{% load pets_tags %}
{% block content %}
{% get_pets_list as pets_list %}
{% endblock %}
But what if you have data that needs to be present in all templates?
That’s where context processors come in.
Global state with context processors
Context processors are great for providing global access to some data. One
example is the request object. You can access request in any template
because it’s provided by a context processor located at
django.template.context_processors.request.
You can easily create your own but first make sure that you really need the
data to be globally accessible.
In our pets_list case, we don’t really need this in every template. If we
put this in a context processor, we will encounter a performance penalty at
every render. Then we’ll have to cache it or lazy load the list, which
introduces complexity.
Context processors are mostly useful for simple data. For example, I like to use
HCaptcha
to protect forms.
To use HCaptcha, the Javascript client library needs something called a
site key. Instead of copying and pasting this site key all over the place,
it’s better to store it in Django’s settings module and load it into a template
via a context processor.
In your settings.py:
HCAPTCHA_SITE_KEY="my-site-key"
Then in file called context_processors.py inside once of your apps:
from django.conf import settings
def hcaptcha_site_key(request):
return settings.HCAPTCHA_SITE_KEY
Then activate the context processor in your TEMPLATES setting:
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [
BASE_DIR / "templates",
],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
...
"core.context_processors.hcaptcha_site_key",
],
"debug": DEBUG or TEST,
},
},
]
Now you can access the site key as hcaptcha_site_key
in any of your templates.
Since this data will be accessed by Javascript, it’s a good idea to use Django’s
json_script
filter to load this into a template.
Personally, I like to load this in the base.html template like so:
{{ hcaptcha_site_key|json_script:"hcaptchaSiteKey" }}
Then whenever I need to access this in my script:
const hCaptchaSiteKey = JSON.parse(document.getElementById("hcaptchaSiteKey").textContent);
You can also dump the data inside a hidden tag like so:
<span id="hCaptchaSiteKey" data-sitekey="{{ hcaptcha_site_key }}"></span>
But json_script will actually serialize your data to JSON, which ensures
its integrity when accessed via JS.
The easiest translation system ever
Unless you’re American, you might want to provide your app’s interface in a
variety of languages.
If you use a framework like React to write your HTML, this process is as
uncomfortable as playing video games on Linux. But with Django, it’s a simple
matter of loading a template tag called i18n and using two tags for
translating your strings.
Here’s how to translate a simple string:
{% extends "base.html" %}
{% load i18n %}
{% block seo_title %}
{% trans "My pet shop" %}
{% endblock %}
...
Now when you use Django’s translation engine, the string “My pet shop” will
automatically be translated to any languages you support. How cool is that?
Sometimes, you want to translate strings that contain dynamic data. That’s where
the blocktrans tag comes in.
Let’s say you want your string to contain an object’s attribute. In this example,
the context contains an object pet with multiple attributes, one of which is
name:
{% extends "base.html" %}
{% load i18n %}
{% block headline %}
{% blocktrans with pet_name=pet.name %}
A cat named {{ pet_name }}
{% endblocktrans %}
{% endblock %}
Now you can translate the string A cat named without losing context.
If you thought that internationalization was supposed to be hard, then be glad
that you chose to use Django to build your app.
Reusable components with partials
A lot of developers brag about their React components. As if React is the
framework that pioneered reusable components. Guess what, it didn’t.
Using partials, you can easily create components using Django templates.
I like to use Bootstrap icons in my projects. Instead of copying and pasting
the code required to use the svg icon when I want to use it, I create a
component called _icon.html instead:
{% load static %}
<svg
class="{{ class_name|default:'bi' }}"
width="{{ width|default:32 }}"
height="{{ height|default:32 }}"
fill="{{ fill_color|default:'currentColor' }}"
>
<use xlink:href="{% static 'icons/bootstrap-icons.svg' %}#{{ icon_name }}" />
</svg>
This component has the following props:
- class_name
- width
- height
- fill_color
- icon_name
icon_name is required while the others have default values.
Whenever I want to use an icon, I do this:
{% include "components/_icon.html" with icon_name="phone" %}
Do you still believe that React is the only way to write components?
Django template is your mate
The concepts above merely scratched the surface of what Django templates can
do and yet, you can build some pretty cool user interfaces using only those.
Not only can you build cool interfaces but these interfaces will also be
simple and maintainable unlike interfaces built using JS frameworks which
require you to rewrite your whole app every six months.
So, before considering a frontend framework, make sure you’re aware of what
Django provides out of the box, especially if you care about simplicity.
I encourage you to take full advantage of Django templates whenever you can.
Read the docs
to gain a better understanding that will help you make informed decisions about
your tech stack.