Django's template system enables developers to create HTML pages with embedded dynamic content. The framework separates business logic (views) from presentation (templates), allowing one template to serve multiple views and vice versa.
Template Configuration
After creating a Django project, template settings are defined in the project settings file. The DIRS option specifies directories where Django searches for template files. Typically, a templates directory is created at the project root level.
Django processes templates in two phases:
- Loading: Locates and compiles the template file based on the specified path
- Rendering: Interpolates context data into the template and returns the resulting HTML string
To streamline development, Django provides the render() shortcut function that handles both loading and rendering automatically.
Creating the Sample Project
This section demonstrates template features using a sample application. Create a Django project and application configured to use MySQL database.
Define a model class in the application:
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateField()
stock_quantity = models.IntegerField(default=0)
price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
is_active = models.BooleanField(default=True)
class Meta:
db_table = 'products'
Template Language Fundamentals
The Django Template Language (DTL) consists of four primary elements:
Variables
Variables render calculated values using double curly braces:
{{ variable_name }}
Variable names must contain only letters, numbers, and underscores (not starting with underscore). When the template engine encounters dots, it resolves them in this order:
- Dictionary key lookup:
obj['key'] - Attribute or method lookup:
obj.attributeorobj.method() - List index:
obj.0(accesses first element)
Note that methods cannot accept parameters when called from templates.
Example
Create a view in views.py:
def display_variables(request):
context_data = {
'product_name': 'Sample Product',
'product': Product.objects.first()
}
return render(request, 'store/product_detail.html', context_data)
Configure the URL pattern:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^product/$', views.display_variables, name='product_detail'),
]
Create the template file:
<!DOCTYPE html>
<html>
<head>
<title>Product Details</title>
</head>
<body>
<h1>Direct Dictionary Access: {{ product_name }}</h1>
<hr>
<h1>Model Attribute: {{ product.name }}</h1>
</body>
</html>
Tags
Tags provide logic control within templates using percent syntax:
{% tag_name %}
For Loop
{% for item in collection %}
<p>{{ item.name }}</p>
<span>Iteration: {{ forloop.counter }}</span>
{% empty %}
<p>No items found</p>
{% endfor %}
The forloop.counter starts from 1. The {% empty %} block executes when the collection is empty or None.
Condtiional Statements
{% if condition %}
<p>Condition is true</p>
{% elif other_condition %}
<p>First condition false, second true</p>
{% else %}
<p>All conditions false</p>
{% endif %}
Comparison Operators
Operators require spaces on both sides:
== != < > <= >=
Boolean Operators
and or not
Example
Create a view to display product listings:
def display_products(request):
all_products = Product.objects.all()
return render(request, 'store/product_list.html', {'products': all_products})
Configure URL routing:
url(r'^products/$', views.display_products, name='product_list'),
Create the template:
<!DOCTYPE html>
<html>
<head>
<title>Product Catalog</title>
</head>
<body>
<h2>Available Products:</h2>
<ul>
{% for item in products %}
{% if item.stock_quantity < 5 %}
<li style="color: red;">{{ item.name }} - Low Stock</li>
{% elif item.stock_quantity < 20 %}
<li style="color: orange;">{{ item.name }} - Medium Stock</li>
{% else %}
<li style="color: green;">{{ item.name }} - In Stock</li>
{% endif %}
{% empty %}
<li>No products currently available</li>
{% endfor %}
</ul>
</body>
</html>
Filters
Filters transform variables using the pipe character. They can apply to variables and some tags:
{{ value|filter_name:argument }}
Common Filters
length: Returns character count for strings, element count for lists/tuples/dictionaries:
{{ product.name|length }}
default: Returns default value when variable is empty:
{{ user_input|default:'N/A' }}
date: Formats datetime objects:
{{ created_at|date:"Y/m/d H:i" }}
Format specifiers:
- Y: Four-digit year, y: Two-digit year
- m: Month (01-12), d: Day (1-31)
- H: 24-hour hour, h: 12-hour hour
- i: Minutes (00-59), s: Seconds (00-59)
Example
Create a view for filter demonstration:
def filter_demo(request):
products = Product.objects.all()
return render(request, 'store/filter_demo.html', {'products': products})
Configure URL:
url(r'^filters/$', views.filter_demo, name='filter_demo'),
Create template:
<!DOCTYPE html>
<html>
<head>
<title>Filter Demonstration</title>
</head>
<body>
<h2>Product Listings</h2>
<ul>
{% for item in products %}
{% if item.name|length > 10 %}
<li style="background-color: #ffcccc;">
{{ item.name }}
<br>
<small>Created: {{ item.created_at }}</small>
</li>
{% else %}
<li style="background-color: #ccffcc;">
{{ item.name }}
<br>
<small>Formatted: {{ item.created_at|date:"m-d-Y" }}</small>
</li>
{% endif %}
{% endfor %}
</ul>
</body>
</html>
Custom Filters
Create custom filters by registering Python functions in the template system. Custom filters must reside in a templatetags package within an application.
Create the directory structure: your_app/templatetags/
Create __init__.py file in the templatetags directory (can be empty).
Create filters.py with custom filter definitions:
from django.template import Library
register = Library()
@register.filter
def is_even(value):
"""Returns True if value is even, False otherwise"""
return value % 2 == 0
To use custom filters, load the module at the template top:
{% load filters %}
{{ some_value|is_even }}
Filter with Parameters
Create a filter that accepts arguments:
@register.filter
def modulo(value, divisor):
"""Returns the remainder of value divided by divisor"""
return value % divisor
Usage in template:
{% load filters %}
{{ item.id|modulo:3 }}
Comments
Single-line comments using Django comment syntax:
{# This is a comment #}
Django comments can contain any template code, valid or invalid:
{# {% if user.is_authenticated %}Welcome{% endif %} #}
Multi-line comments using the comment tag:
{% comment %}
This is a multi-line comment block
Can contain any template syntax here
{% endcomment %}
Note: HTML comments cannot hide template language syntax; they only comment HTML content.
Template Inheritance
Template inheritance allows defining common structure in a base template and extending it in child templates, reducing code duplication.
Base Template
Define reusable structure with {% block %} tags that child templates can override:
<!DOCTYPE html>
<html>
<head>
<title>{% block page_title %}Default Title{% endblock %}</title>
{% block extra_css %}{% endblock %}
</head>
<body>
<header>
<h1>Site Header</h1>
</header>
<main>
{% block main_content %}
<p>Default content goes here</p>
{% endblock %}
</main>
<footer>
<p>Copyright © 2024</p>
</footer>
{% block extra_js %}{% endblock %}
</body>
</html>
Using block names with endblock tags improves readability:
{% block content %}
...
{% endblock content %}
Child Template
Extend the base template and override specific blocks:
{% extends "base.html" %}
{% block page_title %}Product Catalog{% endblock %}
{% block main_content %}
<h2>Our Products</h2>
<ul>
{% for product in products %}
<li>{{ product.name }}</li>
{% endfor %}
</ul>
{% endblock %}
Access parent block content using {{ block.super }}:
{% block main_content %}
{{ block.super }}
<p>Additional content here</p>
{% endblock %}
Example
View definition:
def show_catalog(request):
context = {
'title': 'Product Catalog',
'products': Product.objects.all()
}
return render(request, 'store/catalog.html', context)
URL configuration:
url(r'^catalog/$', views.show_catalog, name='catalog'),
Base template (store_base.html):
<!DOCTYPE html>
<html>
<head>
<title>{{ title|default:'Store' }}</title>
</head>
<body>
<h2>Store Header</h2>
<hr>
{% block product_section %}
<p>Default product area</p>
{% endblock product_section %}
<hr>
{% block details %}{% endblock %}
<hr>
<h2>Store Footer</h2>
</body>
</html>
Child template (catalog.html):
{% extends 'store_base.html' %}
{% block product_section %}
<ul>
{% for item in products %}
<li>{{ item.name }} - ${{ item.price }}</li>
{% endfor %}
</ul>
{% endblock %}
HTML Escaping
Django automatically escapes certain characters in context variables to prevent XSS attacks:
<becomes<>becomes>'becomes'"becomes"&becomes&
Escaping Example
View definition:
def html_escaping(request):
context = {
'user_content': '<script>alert("xss")</script>'
}
return render(request, 'store/escape_demo.html', context)
URL configuration:
url(r'^escape/$', views.html_escaping, name='escape_demo'),
Template:
<!DOCTYPE html>
<html>
<head>
<title>Escaping Demo</title>
</head>
<body>
<p>Auto-escaped: {{ user_content }}</p>
</body>
</html>
The script tags display as text rather than executing.
Disabling Escaping
Use the safe filter to mark content as trusted:
{{ user_content|safe }}
Use the autoescape block tag to disable escaping for a section:
{% autoescape off %}
{{ user_content }}
{% endautoescape %}
The autoescape tag accepts on and off parameters.
Literal Strings
Hardcoded HTML strings in templates are not escaped:
{{ placeholder|default:'<b>Default Text</b>' }}
To display escaped literal strings, manually encode entities:
{{ placeholder|default:'<b>Encoded</b>' }}
CSRF Protection
Cross-Site Request Forgery (CSRF) attacks trick users into performing unintended actions. Django includes middleware to prevent such attacks.
How It Works
When the CSRF middleware is enabled and {% csrf_token %} is placed in forms, Django:
- Generates a unique token and stores it in the user's session
- Includes the token as a hidden field in rendered forms
- Validates the token on form submission
- Rejects requests with missing or invalid tokens
Implementation
View handling form submission:
def submit_form(request):
if request.method == 'POST':
user_input = request.POST.get('user_input')
return HttpResponse(f'Received: {user_input}')
return render(request, 'store/form_view.html')
URL configuration:
url(r'^form/$', views.submit_form, name='form_submit'),
Template with CSRF token:
<!DOCTYPE html>
<html>
<head>
<title>Form Submission</title>
</head>
<body>
<form method="post" action="{% url 'form_submit' %}">
{% csrf_token %}
<label>Enter value:</label>
<input type="text" name="user_input">
<button type="submit">Submit</button>
</form>
</body>
</html>
Key points:
- Use POST for sensitive operations
- Ensure CSRF middleware is enabled (default in Django)
- Always include
{% csrf_token %}in POST forms
Captcha Implementation
Captchas prevent automated submissions by requiring human verification. This implementation creates image-based captchas.
Dependencies
Install the Pillow library for image generation:
pip install Pillow
Captcha Generation
from PIL import Image, ImageDraw, ImageFont
from django.http import HttpResponse
import random
def generate_captcha(request):
# Define background color
bg_color = (random.randint(50, 150), random.randint(50, 150), 200)
width, height = 120, 40
# Create image
captcha_image = Image.new('RGB', (width, height), bg_color)
draw = ImageDraw.Draw(captcha_image)
# Draw noise points
for _ in range(150):
x = random.randint(0, width)
y = random.randint(0, height)
fill = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
draw.point((x, y), fill=fill)
# Character set for captcha
chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'
captcha_text = ''.join(random.choice(chars) for _ in range(4))
# Draw characters
font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 24)
for i, char in enumerate(captcha_text):
color = (random.randint(0, 100), random.randint(0, 100), random.randint(0, 100))
draw.text((10 + i * 25, 5), char, font=font, fill=color)
# Store in session
request.session['captcha_code'] = captcha_text
# Return image
from io import BytesIO
buffer = BytesIO()
captcha_image.save(buffer, 'PNG')
return HttpResponse(buffer.getvalue(), content_type='image/png')
Captcha Form
def captcha_page(request):
return render(request, 'store/captcha_form.html')
def verify_captcha(request):
if request.method == 'POST':
user_input = request.POST.get('captcha_input', '').upper()
stored_code = request.session.get('captcha_code', '')
if user_input == stored_code:
return HttpResponse('Verification Successful')
return HttpResponse('Verification Failed')
return HttpResponse('Invalid Request')
URL patterns:
url(r'^captcha/$', views.captcha_page, name='captcha_page'),
url(r'^verify/$', views.verify_captcha, name='verify_captcha'),
Template:
<!DOCTYPE html>
<html>
<head>
<title>Captcha Verification</title>
</head>
<body>
<form method="post" action="{% url 'verify_captcha' %}">
{% csrf_token %}
<label>Enter CAPTCHA:</label>
<input type="text" name="captcha_input" required>
<br>
<img id="captcha_img" src="{% url 'generate_captcha' %}" alt="Captcha">
<a href="javascript:void(0)" id="refresh">Refresh</a>
<br>
<button type="submit">Verify</button>
</form>
<script>
document.getElementById('refresh').onclick = function() {
var img = document.getElementById('captcha_img');
img.src = img.src + '?' + new Date().getTime();
};
</script>
</body>
</html>
Reverse URL Resolution
Reverse URL resolution generates URLs from URL configuration names instead of hardcoding paths. This simplifies maintenance when URL patterns change.
Configuration
Define namespace in the project URLs:
from django.conf.urls import url, include
urlpatterns = [
url(r'^store/', include('store.urls', namespace='store')),
]
Define name for URL patterns in application URLs:
from . import views
urlpatterns = [
url(r'^products/$', views.product_list, name='product_list'),
url(r'^product/(?P<pk>\d+)/$', views.product_detail, name='product_detail'),
]
Usage in Templates
<a href="{% url 'store:product_list' %}">All Products</a>
<a href="{% url 'store:product_detail' pk=product.id %}">View Details</a>
Usage in Views
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
def some_view(request):
# Redirect to product list
return redirect(reverse('store:product_list'))
def another_view(request, product_id):
# Redirect to specific product
return redirect(reverse('store:product_detail', kwargs={'pk': product_id}))
Passing URL Parameters
For URL patterns with captured groups:
Positional Arguments:
URL pattern: url(r'^item/(\d+)/(\d+)/$', views.item_view, name='item_view')
Template usage:
<a href="{% url 'store:item_view' 1 2 %}">Item Link</a>
View redirect:
return redirect(reverse('store:item_view', args=(1, 2)))
Keyword Arguments:
URL pattern: url(r'^item/(?P<category>\d+)/(?P<id>\d+)/$', views.item_view, name='item_view')
Template usage:
<a href="{% url 'store:item_view' category=5 id=10 %}">Item Link</a>
View redirect:
return redirect(reverse('store:item_view', kwargs={'category': 5, 'id': 10}))
Summary
Key template concepts covered:
- Variables: Render dynamic values with dot notation resolution
- Tags: Control flow including loops and conditionals
- Filters: Transform values using pipe syntax
- Comments: Hide template code from rendering
- Inheritance: Base templates with overridable blocks
- Escaping: Automatic XSS protection with manual override options
- CSRF: Middleware-based form protection
- Captcha: Image-based verification codes
- Reverse Resolution: Dynamic URL generation from named patterns