This is an introduction to the Django Template system. Templates are primarily used for clients who expect the HTTP response to be rendered in web browsers. Consequently, the format of the response is typically in HTML, which can link to various media assets such as JavaScript files, CSS stylesheets, images, and more.

Think of a template as a combination of HTML and its domain-specific language, which includes template tags and filters. Django template syntax is the superset of HTML and it adds dynamic content rendering, making it easy to inject Python objects (the context dictionary/key-value pairs) into HTML. The basic structures one will encounter in Django templates are:

  • Variable Interpolation: {{ value_accessor }} allows one to access and display variables from the context which was passed to the template from the view function. The context is a dictionary of key-value pairs.
  • Comments: {# comment #} lets one add comments within his templates that won't be rendered in the output.
  • Tags: {% tag %} enables the use of various template tags, which can perform operations such as control flow, looping, and including other templates.

During the rendering process, Django composes the final HTML by injecting Python objects into the templates, creating a dynamic user experience. This mechanism is somewhat similar to how PHP templates work, which many developers may already be familiar with.

There are other Template Engines such as Jinja2, Mako, and others. Here focus on the Django template engine django.template.backends.django.DjangoTemplates

Basic Example

A basic example of a Django template would have the following structure:

An example of a Django template structure would start with a base template. The base template contains the common structure of the HTML document, such as the <!DOCTYPE html>, <html>, <head>, and <body> tags, additionally with a "base.html" like:

{% load static %}
<!DOCTYPE html>
<!-- base.html at app/templates/-->
<html>
<head>
    <title>{% block title %}{{ title }}{% endblock %}</title>
</head>
<body>
    <header>
        <h1>{% block header %}{{ heading }}{% endblock %}</h1>
    </header>
    {# This is a comment #}
    {% block content %}
        <p>Hello, {{ user.name }}, you have messages:</p>
        {% if messages %}
            <ul>
                {% for message in messages %}
                    <li>{{ message|truncatewords:30|linebreaks }}</li>
                {% endfor %}
            </ul>
        {% endif %}
    {% endblock %}
    {% comment %}
    <script src="{% static "static_subdir/main.js" %}"></script>
    {% endcoment %}
</body>
</html>

The above showed:

  • Template tags:
    • The {% block %} tag is used to define a block that can be overridden by child templates.
    • The {% if %} tag is used to conditionally render content based on the presence of messages.
    • The {% for %} tag is used to iterate over messages and render them as list items.
    • The {% comment %} tag is used to add comments within the template that won't be rendered.
    • The {% load %} tag is used to load template tags and filters from apps/builtin/external libraries.
  • Template filters:
    • The truncatewords and linebreaks filters are used to truncate the message and add line breaks.
  • Context variables:
    • Global context variables that were not passed from View:
      • The user and messages variables are from context_processors.
      • The static template tag references static files such as JavaScript and CSS files.
    • View context variables:
      • The title and heading variables are passed to the template from a view function.

A complete list of template tags and filters can be found in the Django documentation: Django Template built-in tags and filters

Reusing templates

The inheritance/Composition of templates is crucial for reusing the partial templates across Django apps/projects.

Extending

Use the extends tag to extend a base template.

A simple .html file should be put under {template_dir}/child.html that extends the "base.html"

{% extends 'base.html' %}

{% block content %}
    {{ super.block }}
    <h1>{{ py_cxt_title }}</h1>
    <p>{{ content }}</p>
{% endblock %}

Sometimes, when one needs to get the content of the block from the parent template, the {{ block.super }} variable.

Including

While "extends" tag is used for inheritance, "include" tag is used for composition. It is used to include a template within another template.

example:

<!-- panel_partial.html: -->

<div class="panel panel-default">
    <div class="panel-heading">
        <h3 class="panel-title">{{ title }}</h3>
    </div>
    <div class="panel-body
</div>


<!-- panel_partial.html: -->
{% include 'header.html' with title='My Site' %}

The base.html

It is convenient to bootstrap a project's pages from a stem. Let the base.html be the stem, which includes the common layout and JS libraries.

My Bootstrap base.html includes use of Fontawesome:

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-SgOJa3DmI69IUzQ2PVdRZhwQ+dy64/BUtbMJw1MZ8t5HZApcHrRKUc4W0kG879m7" crossorigin="anonymous">

        <title>{% block title %} Page title {% endblock %}</title>
    </head>
    <body>
        <!-- some sticky top navbar -->
        {% block content %}
        {% endblock %}
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/js/bootstrap.bundle.min.js" integrity="sha384-k6d4wzSIapyDyv1kpU366/PK5hCdSbCRGRCMv+eplOQJWyd1fbcAu9OCUj5zNLiq" crossorigin="anonymous"></script>
        <script src="https://kit.fontawesome.com/9bc0537f0e.js" crossorigin="anonymous"></script>
    </body>
</html>

Asset paths

Where should the template HTML files and assets should be put?

static, templates, media are the common directories in Django projects. The static directory is used to store static files such as CSS, JavaScript, and images. The some_app/templates directory is used to store HTML templates. The media directory is used to store user-uploaded files.

Note "static/" and "templates/" folders are widespread across project root folder, app/plugin folders. "media" is simpler, it is used to store user-uploaded or project dynamic assets.

The details of the directories can be configured in the settings.py file. Refer to Django Settings

Template load order competence

With "django.template.backends.django.DjangoTemplates", there are two types of Template Finders:

  • App dir: the templates/ under each installed app.
  • File System dir: other specified dir defined at DIRS in template settings

By default, Django looks for templates in the templates directory of each app. However, you can customize the template loading order by specifying the DIRS setting in the TEMPLATES configuration.

Note the template loading is "first come first serve" basis. It can be a pitfall if failed to aware it and modify a wrong but identical template. There are cases when the same "static" asset path competes with the candidates of Template files over different directories. There is a mechanism to "break ties"

Django looks for templates in the templates/ directory of each app. However, one can customize the template loading order by specifying the DIRS setting in the TEMPLATES configuration. The latter is FilesystemLoader which takes priority over the app template directory.

If the same template path competes across apps (including those from plugins), the order of INSTALLED_APPS in settings.py matters. The first app in the list will be loaded first. Probably, one would want the order of installed apps to be like:

  1. First Party - Apps that are part of your project.
  2. Third-Party - Apps from third-party packages that you’ve installed.
  3. Contrib - Apps that are included in Django, from django.contrib. For example, django.contrib.admin, django.contrib.comments ( which is not included in the Django package anymore)

Customizing template tags

It is desirable to create Template tags which are Python functions that can be called from within templates. The custom template tags can be used to perform complex operations, such as querying the database, processing data, or rendering HTML.

To create custom template tags and filters:

#create app/templatetags/__init__.py
#   and app/templatetags/customtags.py

#customtags.py:
from django.template import Library
register = Library()

@register.simple_tag(name='tagName')
def sayHello():
  return 'hihi'

@register.filter('filterName')
def filterSay(obj):
  #obj do sth
  return 'hihi'

@register.inclusion_tag('xxx.html')
def render_sth():
  return {}

Using the custom tags in the template:

{% load customtags %}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Custom Tags Example</title>
</head>
<body>
    <h1>{% tagName %}</h1> <!-- Outputs: Hi there! -->
    <p>{{ "World"|filterName }}</p> <!-- Outputs: Hello, World! -->
    <div>
        {% render_something %}
    </div>
</body>
</html>

This enhances the maintainability and readability of your templates, making it easier to build complex web applications.

Customizing context processors

By default, Django provides a set of global context processors that make certain variables and features available to all templates. These context processors allow you to include global data in your templates without needing to pass it explicitly from every view. It could be useful for theme, user, or site-wide settings.

request, auth, messages are the default builtin context and have their own implementation of Context Processor.

Context processor: the default global usable template tag and context

Check settings.py and the inside 'context_processor'

  • It is useful to have a global variable in all templates.
  • Not remedy for all cases, especially involving DB query, every request/template rendering do once. Can consider a template tag or cache.

Example

In an app directory (e.g., appA), create a new file called context_processors.py (or any other filename). In this file, define a context processor function.

#appA/context_processors.py:
from .cart import Cart

def cart(request):
  return { 'cart': Cart(request) }

#project/settings.py
TEMPLATES = [ {'OPTIONS': { 'context_processors': ['cart.context_prcoessors.cart'] }}]

The cart function takes the request object as an argument and returns a dictionary containing the Cart object, which can then be accessed in templates.

At template:

<!-- template.html -->
<h1>Your Shopping Cart</h1>
<ul>
    {% for item in cart.items %}
        <li>{{ item.name }}: {{ item.quantity }}</li>
    {% endfor %}
</ul>

Debugging

During the rendering of context in Templates, how to log the variable?

There are a few workarounds:

  • Render it in HTML. It is dumb but works, need no learning curve.
  • Use django-debug-toolbar, rich in features: check response cycle time, db queries, request history, template load order, context variables, and more.
  • Also there is django-template-debug which is a simple tool to debug Django templates.
  • Using pdb in the template, The Python debugger can be very useful for stepping through rendering processes. Make a simple debug template tag:

    # templatetags/debug.py
    import pdb as dbg              # Change to any *db
    from django.template import Library, Node
    
    register = Library()
    
    class PdbNode(Node):
    
        def render(self, context):
            dbg.set_trace()         # Debugger will stop here
            return ''
    
    @register.tag
    def pdb(parser, token):
        return PdbNode()
    
    <!-- template.html: -->
    {% load debug %}
    {% for item in items %}
        {# Some place you want to break #}
        {% pdb %}
    {% endfor %}
    

Formatting

There is djhtml tool to format django HTML files. It allows custom reindentation.

Ending

While Django Templates are a fundamental part of the framework, they are not always the preferred choice for every project. Many developers use Django as an API backend, combining it with JavaScript frontend frameworks to create dynamic single-page applications (SPAs). This approach often minimizes complexity and communication overhead, which is particularly beneficial for projects that don't require extensive front-end interactivity.

Nonetheless, Django Templates are still a strong option for basic and commonly used web components. They integrate well with front-end libraries like Bootstrap, jQuery, Alpine.js, and HTMX, and there are numerous task-specific JavaScript libraries available to enhance functionality.

There is a clear division of approaches in today's web development landscape. From my perspective, there’s a notable equilibrium between using Django as a full-stack framework and employing it as an API backend with a separate frontend. Personally, I prefer the full-stack Django approach, as it provides a cohesive and efficient development experience.

In summary, Django Templates serve as an excellent starting point for any new web project, offering a simple method to render dynamic HTML while taking full advantage of Django's capabilities and flexibility.

Your thoughts...