Skip to main content

Templates

Template Basics

Template Configuration

# settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
BASE_DIR / 'templates',
BASE_DIR / 'myapp' / 'templates',
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]

Basic Template Structure

<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{% block title %}My Site{% endblock %}</title>
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}" />
{% block extra_css %}{% endblock %}
</head>
<body>
<nav class="navbar">
<div class="container">
<a href="{% url 'home' %}" class="logo">My Site</a>
<ul class="nav-links">
<li><a href="{% url 'home' %}">Home</a></li>
<li><a href="{% url 'articles' %}">Articles</a></li>
{% if user.is_authenticated %}
<li><a href="{% url 'profile' %}">Profile</a></li>
<li><a href="{% url 'logout' %}">Logout</a></li>
{% else %}
<li><a href="{% url 'login' %}">Login</a></li>
<li><a href="{% url 'register' %}">Register</a></li>
{% endif %}
</ul>
</div>
</nav>

<main class="container">
{% if messages %} {% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %} {% endif %} {% block content %} {% endblock %}
</main>

<footer>
<div class="container">
<p>&copy; 2024 My Site. All rights reserved.</p>
</div>
</footer>

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

Template Inheritance

<!-- templates/articles/list.html -->
{% extends 'base.html' %} {% block title %}Articles - {{ block.super }}{%
endblock %} {% block content %}
<h1>Articles</h1>

<div class="articles-grid">
{% for article in articles %}
<article class="article-card">
<h2>
<a href="{% url 'article_detail' article.slug %}">{{ article.title }}</a>
</h2>
<p class="meta">
By {{ article.author.username }} on {{ article.created_at|date:"F j, Y" }}
</p>
<p>{{ article.content|truncatewords:50 }}</p>
<a href="{% url 'article_detail' article.slug %}" class="read-more"
>Read More</a
>
</article>
{% empty %}
<p>No articles available.</p>
{% endfor %}
</div>

<!-- Pagination -->
{% if is_paginated %}
<div class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
<a href="?page=1">&laquo; first</a>
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}

<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>

{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
<a href="?page={{ page_obj.paginator.num_pages }}">last &raquo;</a>
{% endif %}
</span>
</div>
{% endif %} {% endblock %}

Template Variables

Variable Output

<!-- Basic variable output -->
<h1>{{ article.title }}</h1>
<p>{{ article.content }}</p>

<!-- Accessing object attributes -->
<p>Author: {{ article.author.username }}</p>
<p>Email: {{ article.author.email }}</p>

<!-- Accessing dictionary items -->
<p>{{ user.profile.bio }}</p>

<!-- Method calls (no parentheses) -->
<p>{{ article.get_absolute_url }}</p>

<!-- List/tuple indexing -->
<p>{{ items.0 }}</p>
<p>{{ items.1 }}</p>

<!-- Default values -->
<p>{{ article.description|default:"No description available" }}</p>

Context Variables

# views.py
def article_detail(request, slug):
article = get_object_or_404(Article, slug=slug)
related_articles = Article.objects.filter(
category=article.category
).exclude(pk=article.pk)[:3]

context = {
'article': article,
'related_articles': related_articles,
'page_title': f'{article.title} - My Blog',
}
return render(request, 'articles/detail.html', context)

Template Tags

Built-in Tags

<!-- Conditional tags -->
{% if user.is_authenticated %}
<p>Welcome, {{ user.username }}!</p>
{% elif user.is_anonymous %}
<p>Please log in.</p>
{% else %}
<p>Unknown user status.</p>
{% endif %}

<!-- Loop tags -->
{% for article in articles %}
<div class="article">
<h3>{{ article.title }}</h3>
<p>{{ article.content|truncatewords:30 }}</p>
</div>
{% empty %}
<p>No articles found.</p>
{% endfor %}

<!-- Loop variables -->
{% for item in items %}
<p>
{{ forloop.counter }}. {{ item }} {% if forloop.first %} (first){% endif %} {%
if forloop.last %} (last){% endif %}
</p>
{% endfor %}

<!-- URL tag -->
<a href="{% url 'article_detail' slug=article.slug %}">{{ article.title }}</a>
<a href="{% url 'category_articles' category.pk %}">{{ category.name }}</a>

<!-- Include tag -->
{% include 'partials/article_card.html' with article=article %} {% include
'partials/sidebar.html' %}

<!-- Block and extends -->
{% extends 'base.html' %} {% block content %}
<h1>Page content</h1>
{% endblock %}

<!-- Load tag -->
{% load static %} {% load humanize %} {% load custom_tags %}

<!-- With tag -->
{% with article.author as author %}
<p>Written by {{ author.username }}</p>
<p>Email: {{ author.email }}</p>
{% endwith %}

<!-- Spaceless tag -->
{% spaceless %}
<p>
<a href="{% url 'home' %}">Home</a>
</p>
{% endspaceless %}

Custom Template Tags

# templatetags/custom_tags.py
from django import template
from django.utils.safestring import mark_safe
from ..models import Article

register = template.Library()

@register.simple_tag
def get_recent_articles(count=5):
return Article.objects.filter(published=True)[:count]

@register.simple_tag(takes_context=True)
def get_user_articles(context, user):
return Article.objects.filter(author=user)

@register.inclusion_tag('partials/article_list.html')
def show_recent_articles(count=5):
articles = Article.objects.filter(published=True)[:count]
return {'articles': articles}

@register.inclusion_tag('partials/article_list.html', takes_context=True)
def show_user_articles(context, user):
articles = Article.objects.filter(author=user)
return {'articles': articles, 'user': context['user']}

@register.filter
def multiply(value, arg):
return value * arg

@register.filter
def split_lines(value):
return value.split('\n')

Using Custom Tags

<!-- Load custom tags -->
{% load custom_tags %}

<!-- Simple tag -->
{% get_recent_articles 3 as recent_articles %} {% for article in recent_articles
%}
<p>{{ article.title }}</p>
{% endfor %}

<!-- Inclusion tag -->
{% show_recent_articles 5 %}

<!-- Custom filter -->
{{ value|multiply:2 }} {{ article.content|split_lines }}

Template Filters

Built-in Filters

<!-- String filters -->
{{ article.title|upper }} {{ article.title|lower }} {{ article.title|title }} {{
article.title|capfirst }} {{ article.content|truncatewords:50 }} {{
article.content|truncatechars:200 }} {{ article.slug|slugify }} {{
article.content|linebreaks }} {{ article.content|striptags }}

<!-- Date filters -->
{{ article.created_at|date:"F j, Y" }} {{ article.created_at|date:"Y-m-d H:i:s"
}} {{ article.created_at|timesince }} {{ article.created_at|timeuntil }}

<!-- Number filters -->
{{ article.price|floatformat:2 }} {{ article.views|add:1 }} {{ total|default:0
}}

<!-- List filters -->
{{ articles|length }} {{ articles|first }} {{ articles|last }} {{
articles|slice:":5" }} {{ categories|join:", " }}

<!-- Logic filters -->
{{ article.published|yesno:"Yes,No" }} {{ article.content|default:"No content"
}} {{ article.description|default_if_none:"No description" }}

<!-- URL filters -->
{{ article.content|urlize }} {{ article.content|urlizetrunc:30 }}

<!-- Safe filters -->
{{ article.content|safe }} {{ article.html_content|mark_safe }}

Custom Filters

# templatetags/custom_filters.py
from django import template
from django.utils.safestring import mark_safe
import markdown

register = template.Library()

@register.filter
def markdown_to_html(value):
return mark_safe(markdown.markdown(value))

@register.filter
def reading_time(content):
words = len(content.split())
minutes = max(1, words // 200) # Assume 200 words per minute
return f"{minutes} min read"

@register.filter
def currency(value):
return f"${value:,.2f}"

@register.filter
def percentage(value, total):
if total == 0:
return "0%"
return f"{(value / total) * 100:.1f}%"

Template Partials

Reusable Components

<!-- templates/partials/article_card.html -->
<div class="article-card">
<h3>
<a href="{% url 'article_detail' article.slug %}">{{ article.title }}</a>
</h3>
<div class="meta">
<span class="author">By {{ article.author.username }}</span>
<span class="date">{{ article.created_at|date:"M j, Y" }}</span>
{% if article.category %}
<span class="category">{{ article.category.name }}</span>
{% endif %}
</div>
<p>{{ article.content|truncatewords:30 }}</p>
<a href="{% url 'article_detail' article.slug %}" class="read-more"
>Read More</a
>
</div>
<!-- templates/partials/pagination.html -->
{% if is_paginated %}
<nav class="pagination">
{% if page_obj.has_previous %}
<a href="?page=1" class="page-link">&laquo; First</a>
<a href="?page={{ page_obj.previous_page_number }}" class="page-link"
>Previous</a
>
{% endif %} {% for num in page_obj.paginator.page_range %} {% if
page_obj.number == num %}
<span class="page-link current">{{ num }}</span>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a href="?page={{ num }}" class="page-link">{{ num }}</a>
{% endif %} {% endfor %} {% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}" class="page-link">Next</a>
<a href="?page={{ page_obj.paginator.num_pages }}" class="page-link"
>Last &raquo;</a
>
{% endif %}
</nav>
{% endif %}

Including Partials

<!-- Main template -->
{% for article in articles %} {% include 'partials/article_card.html' with
article=article %} {% endfor %}

<!-- Include pagination -->
{% include 'partials/pagination.html' %}

<!-- Include with additional context -->
{% include 'partials/sidebar.html' with title="Recent Posts" %}

Forms in Templates

Form Rendering

<!-- Manual form rendering -->
<form method="post">
{% csrf_token %}
<div class="form-group">
<label for="{{ form.title.id_for_label }}">{{ form.title.label }}</label>
{{ form.title }} {% if form.title.errors %}
<div class="error">{{ form.title.errors }}</div>
{% endif %}
</div>

<div class="form-group">
<label for="{{ form.content.id_for_label }}"
>{{ form.content.label }}</label
>
{{ form.content }} {% if form.content.errors %}
<div class="error">{{ form.content.errors }}</div>
{% endif %}
</div>

<button type="submit">Submit</button>
</form>

<!-- Quick form rendering -->
<form method="post">
{% csrf_token %} {{ form.as_p }}
<button type="submit">Submit</button>
</form>

<!-- Form with custom styling -->
<form method="post" class="form">
{% csrf_token %} {% for field in form %}
<div class="form-group">
{{ field.label_tag }} {{ field }} {% if field.errors %}
<div class="error">{{ field.errors }}</div>
{% endif %}
</div>
{% endfor %}
<button type="submit">Submit</button>
</form>

Form Validation Display

<!-- Non-field errors -->
{% if form.non_field_errors %}
<div class="alert alert-danger">{{ form.non_field_errors }}</div>
{% endif %}

<!-- Field-specific errors -->
{% for field in form %} {% if field.errors %}
<div class="field-error">{{ field.label }}: {{ field.errors }}</div>
{% endif %} {% endfor %}

Static Files

Static Files Configuration

# settings.py
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_DIRS = [
BASE_DIR / 'static',
]

Using Static Files

<!-- Load static files -->
{% load static %}

<!-- CSS files -->
<link rel="stylesheet" href="{% static 'css/style.css' %}" />
<link rel="stylesheet" href="{% static 'css/responsive.css' %}" />

<!-- JavaScript files -->
<script src="{% static 'js/jquery.min.js' %}"></script>
<script src="{% static 'js/app.js' %}"></script>

<!-- Images -->
<img src="{% static 'images/logo.png' %}" alt="Logo" />
<img src="{% static 'images/hero-bg.jpg' %}" alt="Hero Background" />

<!-- Other assets -->
<link rel="icon" href="{% static 'favicon.ico' %}" />
<link rel="stylesheet" href="{% static 'fonts/custom-font.css' %}" />

Media Files

Media Files Configuration

# settings.py
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

Using Media Files

<!-- User uploaded files -->
{% if article.image %}
<img src="{{ article.image.url }}" alt="{{ article.title }}" />
{% endif %}

<!-- File downloads -->
{% if document.file %}
<a href="{{ document.file.url }}" download>Download {{ document.name }}</a>
{% endif %}

<!-- With default fallback -->
<img
src="{% if user.profile.avatar %}{{ user.profile.avatar.url }}{% else %}{% static 'images/default-avatar.png' %}{% endif %}"
alt="Avatar"
/>

Template Context

Context Processors

# context_processors.py
from .models import Category

def site_context(request):
return {
'site_name': 'My Blog',
'categories': Category.objects.all(),
}

def user_context(request):
context = {}
if request.user.is_authenticated:
context['user_articles_count'] = request.user.article_set.count()
return context
# settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'myapp.context_processors.site_context',
'myapp.context_processors.user_context',
],
},
},
]

Template Security

XSS Protection

<!-- Automatic escaping -->
<p>{{ user_input }}</p>
<!-- Automatically escaped -->

<!-- Manual escaping -->
<p>{{ user_input|escape }}</p>

<!-- Marking as safe (use carefully) -->
<p>{{ trusted_html|safe }}</p>

<!-- Avoiding XSS in attributes -->
<div class="{{ css_class|escape }}">Content</div>
<a href="{{ url|escape }}">Link</a>

CSRF Protection

<!-- Always include CSRF token in forms -->
<form method="post">
{% csrf_token %}
<!-- form fields -->
</form>

<!-- AJAX CSRF token -->
<script>
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;

fetch('/api/endpoint/', {
method: 'POST',
headers: {
'X-CSRFToken': csrftoken,
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
</script>

Template Debugging

Debug Information

<!-- Debug mode information -->
{% if debug %}
<div class="debug-info">
<p>Debug mode is enabled</p>
<p>SQL queries: {{ sql_queries|length }}</p>
</div>
{% endif %}

<!-- Template debugging -->
{% comment %} This is a comment that won't be rendered Useful for debugging
template issues {% endcomment %}

<!-- Variable debugging -->
<pre>{{ variable|pprint }}</pre>

Template Performance

<!-- Optimize database queries -->
{% for article in articles %}
<p>{{ article.title }} by {{ article.author.username }}</p>
<!-- Instead of: {{ article.author.username }} for each article -->
{% endfor %}

<!-- Use select_related in view -->
<!-- articles = Article.objects.select_related('author') -->

<!-- Cache expensive operations -->
{% load cache %} {% cache 300 sidebar %}
<!-- Expensive sidebar content -->
{% endcache %}