The Django Template Language (DTL) is a powerful, secure, and designer-friendly templating system. It provides a clean syntax for displaying dynamic content while maintaining separation between presentation and business logic.
Variables - Display dynamic content
{{ variable }}
{{ object.attribute }}
{{ dictionary.key }}
{{ list.0 }}
Tags - Control logic and structure
{% tag_name %}
{% tag_name argument %}
{% tag_name argument1 argument2 %}
Filters - Transform variable output
{{ variable|filter }}
{{ variable|filter:argument }}
{{ variable|filter1|filter2 }}
Comments - Documentation and notes
{# Single line comment #}
{% comment %}
Multi-line comment
{% endcomment %}
Django resolves variables using this order:
foo['bar']foo.barfoo.bar()foo[bar]# views.py
context = {
'user': request.user,
'post': {
'title': 'My Post',
'tags': ['django', 'python'],
},
'numbers': [1, 2, 3, 4, 5],
}
<!-- template.html -->
{{ user.username }} <!-- Attribute lookup -->
{{ user.get_full_name }} <!-- Method call -->
{{ post.title }} <!-- Dictionary lookup -->
{{ post.tags.0 }} <!-- List index lookup -->
{{ numbers.2 }} <!-- List index: 3 -->
# models.py
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
def get_latest_posts(self):
return self.posts.filter(published=True)[:5]
class Post(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='posts')
tags = models.ManyToManyField('Tag')
@property
def word_count(self):
return len(self.content.split())
class Tag(models.Model):
name = models.CharField(max_length=50)
<!-- Accessing related objects -->
<h1>{{ post.title }}</h1>
<p>By {{ post.author.name }} ({{ post.author.email }})</p>
<p>Word count: {{ post.word_count }}</p>
<!-- Accessing related sets -->
<h3>Tags:</h3>
{% for tag in post.tags.all %}
<span class="tag">{{ tag.name }}</span>
{% endfor %}
<!-- Accessing methods -->
<h3>Author's Latest Posts:</h3>
{% for latest_post in post.author.get_latest_posts %}
<a href="{{ latest_post.get_absolute_url }}">{{ latest_post.title }}</a>
{% endfor %}
if/elif/else:
{% if user.is_authenticated %}
<p>Welcome, {{ user.username }}!</p>
{% if user.is_staff %}
<a href="{% url 'admin:index' %}">Admin Panel</a>
{% elif user.is_superuser %}
<a href="{% url 'admin:index' %}">Super Admin</a>
{% endif %}
{% else %}
<p><a href="{% url 'login' %}">Please log in</a></p>
{% endif %}
Comparison Operators:
{% if posts.count > 0 %}
<p>Found {{ posts.count }} posts</p>
{% endif %}
{% if user.age >= 18 %}
<p>Adult content available</p>
{% endif %}
{% if post.status == 'published' %}
<span class="published">Published</span>
{% endif %}
{% if post.author != user %}
<p>This post is by {{ post.author.name }}</p>
{% endif %}
Logical Operators:
{% if user.is_authenticated and user.is_active %}
<p>Active user</p>
{% endif %}
{% if post.featured or post.sticky %}
<span class="highlight">Featured</span>
{% endif %}
{% if not user.is_anonymous %}
<p>Logged in user</p>
{% endif %}
Membership Tests:
{% if 'admin' in user.groups.all %}
<p>Administrator</p>
{% endif %}
{% if user in post.likes.all %}
<button>Unlike</button>
{% else %}
<button>Like</button>
{% endif %}
Basic for Loop:
<ul>
{% for post in posts %}
<li>{{ post.title }} - {{ post.created_at|date:"Y-m-d" }}</li>
{% empty %}
<li>No posts available</li>
{% endfor %}
</ul>
Loop Variables:
<table>
{% for post in posts %}
<tr class="{% if forloop.first %}first{% elif forloop.last %}last{% endif %}
{% if forloop.counter|divisibleby:2 %}even{% else %}odd{% endif %}">
<td>{{ forloop.counter }}</td>
<td>{{ post.title }}</td>
<td>{{ forloop.revcounter }} remaining</td>
</tr>
{% endfor %}
</table>
Available forloop Variables:
forloop.counter - Current iteration (1-indexed)forloop.counter0 - Current iteration (0-indexed)forloop.revcounter - Reverse counter (1-indexed)forloop.revcounter0 - Reverse counter (0-indexed)forloop.first - True if first iterationforloop.last - True if last iterationforloop.parentloop - Parent loop in nested loopsNested Loops:
{% for category in categories %}
<h2>{{ category.name }}</h2>
<ul>
{% for post in category.posts.all %}
<li>
{{ forloop.parentloop.counter }}.{{ forloop.counter }}
{{ post.title }}
</li>
{% endfor %}
</ul>
{% endfor %}
url Tag:
<!-- Basic URL generation -->
<a href="{% url 'blog:post_detail' post.pk %}">{{ post.title }}</a>
<a href="{% url 'blog:category' category.slug %}">{{ category.name }}</a>
<!-- URL with multiple arguments -->
<a href="{% url 'blog:post_by_date' year=2024 month=3 day=15 %}">March 15, 2024</a>
<!-- URL with query parameters -->
<a href="{% url 'blog:post_list' %}?page=2&category={{ category.slug }}">Next Page</a>
<!-- Store URL in variable -->
{% url 'blog:post_detail' post.pk as post_url %}
<a href="{{ post_url }}">Read More</a>
<meta property="og:url" content="{{ request.build_absolute_uri }}{{ post_url }}">
include Tag:
<!-- Include another template -->
{% include 'blog/includes/post_card.html' %}
<!-- Include with additional context -->
{% include 'blog/includes/post_card.html' with featured=True %}
<!-- Include with only specific context -->
{% include 'blog/includes/post_card.html' with post=featured_post only %}
extends and block Tags:
<!-- child_template.html -->
{% extends 'base.html' %}
{% block title %}{{ post.title }} - {{ block.super }}{% endblock %}
{% block content %}
<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'blog/css/post.css' %}">
{% endblock %}
with Tag:
<!-- Create local variables -->
{% with post.author as author %}
<p>{{ author.name }} ({{ author.email }})</p>
<p>Posts by {{ author.name }}: {{ author.posts.count }}</p>
{% endwith %}
<!-- Multiple variables -->
{% with total=posts.count published=posts.published.count %}
<p>{{ published }} of {{ total }} posts are published</p>
{% endwith %}
spaceless Tag:
{% spaceless %}
<p>
<a href="{% url 'home' %}">Home</a>
<a href="{% url 'about' %}">About</a>
</p>
{% endspaceless %}
<!-- Output: <p><a href="/">Home</a><a href="/about/">About</a></p> -->
verbatim Tag:
{% verbatim %}
<!-- This won't be processed by Django -->
<script>
var data = {{ json_data }}; // This stays as literal text
</script>
{% endverbatim %}
Text Transformation:
{{ "hello world"|title }} <!-- Hello World -->
{{ "LOUD TEXT"|lower }} <!-- loud text -->
{{ "quiet text"|upper }} <!-- QUIET TEXT -->
{{ " spaced "|strip }} <!-- spaced -->
{{ "hello-world"|slugify }} <!-- hello-world -->
Text Truncation:
{{ post.content|truncatewords:50 }}
{{ post.title|truncatechars:30 }}
{{ post.content|truncatewords_html:50 }}
Text Formatting:
{{ post.content|linebreaks }} <!-- Converts \n to <p> and <br> -->
{{ post.content|linebreaksbr }} <!-- Converts \n to <br> -->
{{ post.content|striptags }} <!-- Removes HTML tags -->
{{ post.content|escape }} <!-- HTML escapes -->
{{ trusted_html|safe }} <!-- Marks as safe HTML -->
Number Formatting:
{{ 1234.5678|floatformat:2 }} <!-- 1234.57 -->
{{ 42|add:8 }} <!-- 50 -->
{{ numbers|length }} <!-- Count of items -->
{{ price|floatformat:0 }} <!-- 1235 -->
Date Formatting:
{{ post.created_at|date:"Y-m-d" }} <!-- 2024-03-15 -->
{{ post.created_at|date:"F d, Y" }} <!-- March 15, 2024 -->
{{ post.created_at|time:"H:i" }} <!-- 14:30 -->
{{ post.created_at|timesince }} <!-- 2 hours ago -->
{{ post.created_at|timeuntil }} <!-- in 3 days -->
Custom Date Formats:
{{ post.created_at|date:"l, F j, Y \a\t P" }} <!-- Friday, March 15, 2024 at 2:30 p.m. -->
{{ post.created_at|date:"c" }} <!-- 2024-03-15T14:30:00+00:00 -->
List Operations:
{{ posts|first }} <!-- First item -->
{{ posts|last }} <!-- Last item -->
{{ posts|length }} <!-- Count -->
{{ posts|slice:":5" }} <!-- First 5 items -->
{{ posts|slice:"5:" }} <!-- Items from 5th onward -->
{{ tags|join:", " }} <!-- Join with comma -->
Dictionary Operations:
{{ user_data|dictsort:"name" }} <!-- Sort by 'name' key -->
{{ categories|dictsortreversed:"count" }} <!-- Reverse sort -->
Default Values:
{{ post.excerpt|default:"No excerpt available" }}
{{ user.get_full_name|default:user.username }}
{{ post.image|default_if_none:"placeholder.jpg" }}
Conditional Display:
{{ post.is_featured|yesno:"Featured,Regular" }}
{{ post.is_published|yesno:"Published,Draft,Unknown" }}
{{ post.content|striptags|truncatewords:50|title }}
{{ post.created_at|date:"Y-m-d"|default:"Unknown date" }}
{{ user.email|lower|default:"No email" }}
Creating Custom Filters:
# blog/templatetags/blog_extras.py
from django import template
from django.utils.safestring import mark_safe
import markdown
register = template.Library()
@register.filter
def markdown_to_html(text):
"""Convert markdown to HTML."""
return mark_safe(markdown.markdown(text))
@register.filter
def reading_time(text):
"""Calculate reading time in minutes."""
word_count = len(text.split())
minutes = word_count // 200 # Average reading speed
return max(1, minutes)
Using Custom Filters:
{% load blog_extras %}
<div class="content">
{{ post.content|markdown_to_html }}
</div>
<p class="meta">Reading time: {{ post.content|reading_time }} minutes</p>
Creating Custom Tags:
# blog/templatetags/blog_extras.py
@register.simple_tag
def get_recent_posts(count=5):
"""Get recent published posts."""
return Post.objects.filter(published=True)[:count]
@register.inclusion_tag('blog/includes/recent_posts.html')
def show_recent_posts(count=5):
"""Render recent posts widget."""
posts = Post.objects.filter(published=True)[:count]
return {'posts': posts}
@register.simple_tag(takes_context=True)
def current_url_with_params(context, **kwargs):
"""Get current URL with additional parameters."""
request = context['request']
params = request.GET.copy()
for key, value in kwargs.items():
params[key] = value
return f"{request.path}?{params.urlencode()}"
Using Custom Tags:
{% load blog_extras %}
<!-- Simple tag -->
{% get_recent_posts 3 as recent %}
{% for post in recent %}
<a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
{% endfor %}
<!-- Inclusion tag -->
{% show_recent_posts 5 %}
<!-- Context-aware tag -->
<a href="{% current_url_with_params page=2 %}">Next Page</a>
Multi-level Inheritance:
<!-- base.html -->
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}My Site{% endblock %}</title>
{% block extra_css %}{% endblock %}
</head>
<body>
{% block content %}{% endblock %}
{% block extra_js %}{% endblock %}
</body>
</html>
<!-- blog_base.html -->
{% extends 'base.html' %}
{% block title %}Blog - {{ block.super }}{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'blog/css/blog.css' %}">
{% endblock %}
{% block content %}
<div class="blog-container">
{% block blog_content %}{% endblock %}
</div>
{% endblock %}
<!-- post_detail.html -->
{% extends 'blog/blog_base.html' %}
{% block title %}{{ post.title }} - {{ block.super }}{% endblock %}
{% block blog_content %}
<article>
<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
</article>
{% endblock %}
Modifying Context in Templates:
{% with posts.count as total_posts %}
{% if total_posts > 10 %}
<p>Showing first 10 of {{ total_posts }} posts</p>
{% for post in posts|slice:":10" %}
<!-- Display post -->
{% endfor %}
{% else %}
{% for post in posts %}
<!-- Display all posts -->
{% endfor %}
{% endif %}
{% endwith %}
Complex Context Processing:
{% for category in categories %}
{% with category.posts.published as published_posts %}
{% if published_posts %}
<h2>{{ category.name }} ({{ published_posts.count }})</h2>
{% for post in published_posts|slice:":5" %}
<a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
{% endfor %}
{% endif %}
{% endwith %}
{% endfor %}
Fragment Caching:
{% load cache %}
{% cache 300 post_sidebar post.pk %}
<div class="sidebar">
<h3>Related Posts</h3>
{% for related in post.get_related_posts %}
<a href="{{ related.get_absolute_url }}">{{ related.title }}</a>
{% endfor %}
</div>
{% endcache %}
Conditional Caching:
{% if user.is_authenticated %}
<!-- Don't cache for authenticated users -->
<div class="user-specific-content">
Welcome, {{ user.username }}!
</div>
{% else %}
{% cache 600 anonymous_header %}
<div class="header">
<h1>Welcome to our site!</h1>
</div>
{% endcache %}
{% endif %}
Minimize Database Queries:
<!-- Good: Use select_related/prefetch_related in view -->
{% for post in posts %}
<h3>{{ post.title }}</h3>
<p>By {{ post.author.name }}</p> <!-- No additional query -->
<p>Category: {{ post.category.name }}</p> <!-- No additional query -->
{% endfor %}
<!-- Bad: This causes N+1 queries -->
{% for post in posts %}
<h3>{{ post.title }}</h3>
<p>Comments: {{ post.comments.count }}</p> <!-- Query per post -->
{% endfor %}
Efficient Conditional Rendering:
<!-- Good: Check once, render accordingly -->
{% with user_posts=user.posts.published %}
{% if user_posts %}
<h3>Your Published Posts ({{ user_posts.count }})</h3>
{% for post in user_posts %}
<a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
{% endfor %}
{% else %}
<p>You haven't published any posts yet.</p>
{% endif %}
{% endwith %}
The Django Template Language provides a powerful, secure foundation for creating dynamic web pages. Understanding its syntax, features, and optimization techniques enables you to build efficient, maintainable templates that scale with your application.
Introduction to Django Templates
Django templates are text files that define the structure and layout of your web pages. They combine static HTML with dynamic content using Django's template language, providing a clean separation between presentation and business logic.
Template Inheritance
Template inheritance is one of Django's most powerful features, allowing you to build a base "skeleton" template that contains common elements and define blocks that child templates can override. This promotes code reuse, maintainability, and consistent design across your application.