Django Templates for Pros

Django Templates for Pros

If you’re new to Django, you might think that templates are old, slow, boring, and not powerful enough to power your modern web apps.

In this post, I’ll show you that Django templates is not only a great way to write HTML, but also powerful enough to meet 99% of needs.

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.

With template tags

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.