Skip to content

Django Templates

Django’s template layer provides a lightweight template syntax for dynamically rendering backend data into HTML pages. This article covers template variables, filters, tags (for/if/with), custom filters and inclusion tags, and how to use template inheritance and imports.

Template Syntax

Django templates use two kinds of delimiters:

  • {{ }} — for variables and expressions
  • {% %} — for logic (loops, conditions, etc.)
# views.py — pass various Python types to the template
def index(request):
    n = 123
    f = 11.11
    s = 'hello world'
    b = True
    l = ['Alice', 'Bob', 'Carol', 'Dave']
    t = (111, 222, 333, 444)
    d = {'username': 'jason', 'age': 18, 'info': 'interesting person'}
    se = {'a', 'b', 'c'}

    def func():
        print('func was called')
        return 'Your other half is waiting'

    class MyClass(object):
        def get_self(self):
            return 'self'

        @staticmethod
        def get_func():
            return 'func'

        @classmethod
        def get_class(cls):
            return 'cls'

        # When an object is displayed in the template it triggers __str__, just like print()
        def __str__(self):
            return 'MyClass instance'

    obj = MyClass()

    # Pass all local variables to the template at once
    return render(request, 'index.html', locals())

In the template:

<p>{{ n }}</p>
<p>{{ f }}</p>
<p>{{ s }}</p>
<p>{{ b }}</p>
<p>{{ l }}</p>
<p>{{ d }}</p>
<p>{{ t }}</p>
<p>{{ se }}</p>

<!-- Passing a function name: Django automatically calls it (no arguments supported) -->
<p>{{ func }}</p>

<!-- Passing a class name: Django automatically instantiates it -->
<p>{{ MyClass }}</p>

<!-- The template engine auto-detects whether a variable is callable and calls it if so -->
<p>{{ obj }}</p>
<p>{{ obj.get_self }}</p>
<p>{{ obj.get_func }}</p>
<p>{{ obj.get_class }}</p>

Dot Notation

Django templates use the dot (.) for all attribute and index access:

<!-- Dict key access -->
<p>{{ d.username }}</p>

<!-- List index access -->
<p>{{ l.0 }}</p>

<!-- Nested access — mix keys and indexes freely -->
<p>{{ d.hobby.3.info }}</p>

The dot notation works for dict keys, list/tuple indexes, object attributes, and object methods — Django resolves them in that order automatically.

Filters

Filters are built-in template methods that transform a variable’s value before display. Django ships with over 60 filters; each filter accepts at most two parameters.

Syntax: {{ data|filter_name:parameter }}

Common Built-in Filters

<!-- Length of a string or list -->
<p>{{ s|length }}</p>

<!-- Default value — if the variable is falsy, show the fallback -->
<p>{{ b|default:'nothing here' }}</p>

<!-- File size — convert bytes to the largest appropriate unit -->
<p>{{ file_size|filesizeformat }}</p>

<!-- Date formatting -->
<p>{{ current_time|date:'Y-m-d H:i:s' }}</p>

<!-- Slice (supports step) -->
<p>{{ l|slice:'0:4:2' }}</p>

<!-- Truncate characters (appends '...') -->
<p>{{ info|truncatechars:9 }}</p>

<!-- Truncate words (splits on spaces, no '...') -->
<p>{{ egl|truncatewords:9 }}</p>

<!-- Remove a specific character -->
<p>{{ msg|cut:' ' }}</p>

<!-- Join list elements with a separator -->
<p>{{ l|join:'$' }}</p>

<!-- Add a number -->
<p>{{ n|add:10 }}</p>

<!-- Concatenate strings -->
<p>{{ s|add:msg }}</p>

<!-- Mark a string as safe HTML (disable auto-escaping) -->
<p>{{ hhh|safe }}</p>

Marking Strings Safe on the Backend

from django.utils.safestring import mark_safe

res = mark_safe('<h1>Safe HTML</h1>')
# Pass res to the template — it will render as HTML, not escaped text

This is useful when you build HTML strings in the view and want to render them without escaping in the template.

Tags

for Loop

{% for item in l %}
    <p>{{ forloop }}</p>  <!-- forloop context variable -->
    <p>{{ item }}</p>
{% endfor %}

<!--
forloop provides these keys:
  counter0    — 0-based iteration count
  counter     — 1-based iteration count
  revcounter  — remaining iterations (1-based)
  revcounter0 — remaining iterations (0-based)
  first       — True on the first iteration
  last        — True on the last iteration
  parentloop  — reference to the outer loop (for nested loops)
-->

if / elif / else

{% if b %}
    <p>condition is true</p>
{% elif s %}
    <p>fallback condition</p>
{% else %}
    <p>default</p>
{% endif %}

Combining for and if

{% for item in lll %}
    {% if forloop.first %}
        <p>First item</p>
    {% elif forloop.last %}
        <p>Last item</p>
    {% else %}
        <p>{{ item }}</p>
    {% endif %}
{% empty %}
    <p>The iterable is empty — nothing to loop over.</p>
{% endfor %}

Iterating Over a Dictionary

{% for key in d.keys %}
    <p>{{ key }}</p>
{% endfor %}

{% for value in d.values %}
    <p>{{ value }}</p>
{% endfor %}

{% for key, value in d.items %}
    <p>{{ key }}: {{ value }}</p>
{% endfor %}

with — Creating Aliases

with lets you alias a deeply nested expression to a short name, valid only within the block:

{% with d.hobby.3.info as nb %}
    <p>{{ nb }}</p>
    <!-- The original long expression also works inside the block -->
    <p>{{ d.hobby.3.info }}</p>
{% endwith %}

Custom Filters, Tags, and inclusion_tag

Setup (Three Required Steps)

  1. Create a folder named exactly templatetags inside your app directory.
  2. Create a Python file with any name inside it, e.g. mytag.py.
  3. Start the file with exactly these two lines:
from django import template

register = template.Library()

Custom Filter

A custom filter can accept at most two arguments (the variable and one extra parameter):

# mytag.py
@register.filter(name='baby')
def my_sum(v1, v2):
    return v1 + v2

Usage in template:

{% load mytag %}
<p>{{ n|baby:666 }}</p>

Custom Tag (simple_tag)

A custom simple tag works like a function and can accept any number of arguments:

@register.simple_tag(name='plus')
def index(a, b, c, d):
    return '%s-%s-%s-%s' % (a, b, c, d)

Usage in template (arguments separated by spaces):

{% load mytag %}
<p>{% plus 'jason' 123 123 123 %}</p>

Custom inclusion_tag

An inclusion_tag renders a partial template and inserts the result at the call site — useful for reusable UI components:

@register.inclusion_tag('left_menu.html')
def left(n):
    data = ['Item {}'.format(i) for i in range(n)]
    return locals()  # passes 'data' to left_menu.html

Usage:

{% load mytag %}
{% left 5 %}
Use an inclusion_tag when a portion of the page requires dynamic data, is rendered by its own sub-template, and appears on multiple pages. It keeps partial pages modular and reusable.

Template Inheritance

Template inheritance lets you define a base layout and override specific sections in child templates — ideal for sites where every page shares the same header, footer, and navigation.

Base Template (home.html)

Mark the sections that child templates are allowed to override using {% block %}:

<!DOCTYPE html>
<html>
<head>
    {% block css %}{% endblock %}
</head>
<body>
    <nav><!-- shared navigation --></nav>

    {% block content %}
        <!-- default content shown when no child overrides this block -->
    {% endblock %}

    {% block js %}{% endblock %}
</body>
</html>

Child Template

{% extends 'home.html' %}

{% block css %}
<style>
    /* page-specific styles */
</style>
{% endblock %}

{% block content %}
    <h1>My Page Content</h1>
    <p>This replaces the parent block.</p>
{% endblock %}

{% block js %}
<script>
    // page-specific scripts
</script>
{% endblock %}

A child template that extends a parent inherits its entire layout. Only blocks that are explicitly redefined will be replaced.

In practice, define at least three overridable blocks in every base template: one for CSS, one for the main content, and one for JavaScript. Each child page can then have its own styles and scripts without duplicating the shared layout.

Template Import (include)

{% include %} inserts a reusable partial template at the point where it is called — no inheritance involved:

{% include 'sidebar.html' %}

Use include when you have a small UI fragment (e.g. a sidebar, a card component, a form widget) that you want to reuse across pages without setting up a full parent-child inheritance relationship.

Last updated on