The Role of Templates in Django
Templates are a crucial component of Django's MVT (Model-View-Template) architecture. They define how data is presented to users, separating the presentation logic from the business logic in your views and models.
Django's template system provides a powerful, flexible language for creating HTML, XML, or any text-based format. It allows you to:
- Display dynamic data from your views
- Apply presentation logic (e.g., formatting, conditional rendering)
- Create reusable components through template inheritance
- Extend functionality with custom tags and filters
Templates in Django are like blueprints for a building's interior design. Just as an interior designer creates plans that specify where furniture should go, what colors to use, and how spaces should flow together, Django templates specify how data should be presented, formatted, and organized on a webpage. The template itself doesn't contain the actual furniture (data), but it defines exactly how that furniture should be arranged when it arrives.
Template Configuration
Before diving into the template language, let's understand how templates are configured in Django.
Template Directories
Django looks for templates in specific directories defined in your settings:
# settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
BASE_DIR / 'templates', # Project-level templates
],
'APP_DIRS': True, # Look for templates in app-level 'templates' directories
'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',
# Custom context processors
'myapp.context_processors.site_settings',
],
},
},
]
With this configuration, Django will look for templates in:
- The project-level
templatesdirectory - Each app's
templatesdirectory
Template File Structure
A common structure for templates is:
myproject/
├── templates/ # Project-level templates
│ ├── base.html
│ ├── navbar.html
│ └── footer.html
│
├── myapp/
│ ├── templates/ # App-level templates
│ │ └── myapp/ # Namespace to avoid conflicts
│ │ ├── home.html
│ │ ├── about.html
│ │ └── contact.html
Note the namespace folder inside the app-level templates directory. This is a best practice to avoid naming conflicts between apps.
Context Processors
Context processors are functions that add additional variables to the template context. Django includes several built-in context processors, and you can add your own:
# myapp/context_processors.py
def site_settings(request):
"""Add site settings to template context."""
return {
'site_name': 'My Awesome Site',
'site_description': 'A site about awesome things',
'contact_email': 'info@example.com',
}
Variables from context processors are available in all templates across your project.
Template configuration is like setting up the infrastructure for an interior design firm. You establish where design blueprints are stored (template directories), how designers can access shared resources (context processors), and how to organize projects for different clients (namespace folders).
Template Variables and Filters
One of the primary functions of templates is to display dynamic data. This is done using variables and filters.
Variables
Variables in templates are enclosed in double curly braces, like {{ variable }}. They represent values from the context dictionary passed by the view:
# In the view
def user_profile(request, username):
user = get_object_or_404(User, username=username)
return render(request, 'profiles/profile.html', {
'user': user,
'posts': user.posts.all(),
})
# In the template
<h1>{{ user.username }}'s Profile</h1>
<p>Joined: {{ user.date_joined }}</p>
<p>Posts: {{ posts.count }}</p>
You can access attributes, dictionary keys, and list indices with dot notation:
{{ user.username }} # Access an attribute
{{ user.get_full_name }} # Call a method (no arguments allowed)
{{ settings.SITE_NAME }} # Access a dictionary key
{{ posts.0.title }} # Access the first item in a list, then its title attribute
Filters
Filters transform the display of a variable. They're applied using the pipe character (|):
{{ value|filter_name:argument }}
Common built-in filters include:
-
date: Formats a date according to a given format
{{ user.date_joined|date:"F j, Y" }} -
default: Sets a default value if the variable is false or empty
{{ user.bio|default:"No bio provided." }} -
length: Returns the length of a string or list
{{ posts|length }} -
capfirst: Capitalizes the first character
{{ user.username|capfirst }} -
upper/lower: Converts to upper or lowercase
{{ user.username|upper }} -
truncatechars/truncatewords: Truncates a string to the specified number of characters/words
{{ post.content|truncatewords:50 }} -
linebreaks: Replaces line breaks with paragraph tags
{{ post.content|linebreaks }} -
safe: Marks a string as not requiring further HTML escaping
{{ post.html_content|safe }} -
join: Joins a list with the given string
{{ tags|join:", " }} -
slice: Returns a portion of a list
{{ posts|slice:":5" }} -
filesizeformat: Formats a number as a human-readable file size
{{ attachment.size|filesizeformat }}
Filters can be chained:
{{ post.title|lower|truncatechars:20 }}
Custom Filters
You can create your own filters:
# myapp/templatetags/custom_filters.py
from django import template
import markdown
register = template.Library()
@register.filter
def markdown_format(text):
"""Convert markdown text to HTML."""
return markdown.markdown(text)
@register.filter
def currency(value, currency_code='USD'):
"""Format a number as currency."""
symbols = {'USD': '$', 'EUR': '€', 'GBP': '£'}
symbol = symbols.get(currency_code, '')
return f"{symbol}{value:.2f}"
To use custom filters, load them in your template:
{% load custom_filters %}
<div class="post-content">
{{ post.content|markdown_format }}
</div>
<p>Price: {{ product.price|currency:"EUR" }}</p>
Variables and filters in templates are like the placeholders and styling guidelines in an interior design blueprint. Just as a blueprint might specify "Place client's artwork HERE, scaled to fit frame" or "Paint walls in client's chosen color, with semi-gloss finish," templates might specify "Display the user's name HERE, capitalized" or "Display the post content, with line breaks converted to paragraphs."
Tags and Control Structures
Tags provide the logic and control structures in Django templates. They're enclosed in {% %} and can perform tasks like looping, conditional rendering, and including other templates.
Conditional Tags
The if, elif, and else tags allow conditional rendering:
{% if user.is_authenticated %}
<p>Welcome, {{ user.username }}!</p>
{% if user.is_staff %}
<p>You have admin privileges.</p>
{% endif %}
{% elif request.user_agent.is_mobile %}
<p>Welcome, mobile user!</p>
{% else %}
<p>Welcome, guest! Please <a href="{% url 'login' %}">log in</a>.</p>
{% endif %}
Conditional expressions can use operators like ==, !=, <, >, <=, >=, in, not in, is, is not, as well as and, or, and not:
{% if posts|length > 0 and category.name == "Technology" %}
<h2>Latest Technology Posts</h2>
{% endif %}
{% if user.username in staff_list or user.is_superuser %}
<p>You have staff privileges.</p>
{% endif %}
Loop Tags
The for tag allows iteration over lists, querysets, and other iterables:
<ul>
{% for post in posts %}
<li>
<a href="{% url 'post_detail' post.id %}">{{ post.title }}</a>
<small>by {{ post.author.username }}</small>
</li>
{% empty %}
<li>No posts found.</li>
{% endfor %}
</ul>
Inside a for loop, you have access to several variables:
forloop.counter: The current iteration (1-indexed)forloop.counter0: The current iteration (0-indexed)forloop.revcounter: The number of iterations from the end (1-indexed)forloop.revcounter0: The number of iterations from the end (0-indexed)forloop.first: True if this is the first iterationforloop.last: True if this is the last iterationforloop.parentloop: The parent loop, if this is a nested loop
<table>
<thead>
<tr>
<th>#</th>
<th>Title</th>
<th>Author</th>
</tr>
</thead>
<tbody>
{% for post in posts %}
<tr class="{% if forloop.first %}first{% endif %} {% if forloop.last %}last{% endif %} {% cycle 'odd' 'even' %}">
<td>{{ forloop.counter }}</td>
<td>{{ post.title }}</td>
<td>{{ post.author.username }}</td>
</tr>
{% endfor %}
</tbody>
</table>
URL Tags
The url tag generates URLs based on the URL patterns defined in your project:
<a href="{% url 'post_detail' post.id %}">{{ post.title }}</a>
You can include multiple arguments and keyword arguments:
<a href="{% url 'post_archive' year=post.published_date.year month=post.published_date.month %}">
Archive for {{ post.published_date|date:"F Y" }}
</a>
Include and Block Tags
The include tag allows you to include another template:
{% include "navbar.html" %}
{% include "post_card.html" with post=post only %}
The with keyword allows you to pass variables to the included template, and the only keyword restricts the included template to only those variables.
The block tag is used for template inheritance, which we'll cover in more detail later:
{% block content %}
<h1>Default content</h1>
<p>This will be replaced if a child template defines this block.</p>
{% endblock %}
Comment Tags
The comment tag allows you to add comments that won't be rendered:
{% comment "Optional note" %}
This is a multiline comment.
It won't be rendered in the final HTML.
{% endcomment %}
For single-line comments, you can use the {# #} syntax:
{# This is a single-line comment #}
Load Tags
The load tag loads custom template tags and filters:
{% load custom_tags custom_filters %}
Static Tags
The static tag generates URLs for static files:
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<img src="{% static 'images/logo.png' %}" alt="Logo">
Tags and control structures in templates are like the instructions and decision points in an interior design plan. Just as a design plan might say "If the room is larger than 200 sq ft, add a reading nook in the corner" or "For each window, add curtains matching the wall color," templates might say "If the user is logged in, show a personalized greeting" or "For each post, display its title and author."
Template Inheritance
Template inheritance is one of the most powerful features of Django's template system. It allows you to create a base template with common elements and then extend it in child templates.
Creating a Base Template
The base template defines the overall structure and blocks that can be overridden by child templates:
<!-- 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/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
{% block extra_css %}{% endblock %}
</head>
<body>
<header>
{% include "navbar.html" %}
{% block header %}
<div class="container">
<h1>{% block header_title %}Welcome to My Site{% endblock %}</h1>
</div>
{% endblock %}
</header>
<main class="container">
{% if messages %}
<div class="messages">
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% block content %}
<p>Default content</p>
{% endblock %}
</main>
<footer>
{% block footer %}
{% include "footer.html" %}
{% endblock %}
</footer>
<script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
Extending the Base Template
Child templates extend the base template and override specific blocks:
<!-- templates/blog/post_list.html -->
{% extends "base.html" %}
{% block title %}Blog Posts{% endblock %}
{% block header_title %}Latest Blog Posts{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8">
{% for post in posts %}
<div class="card mb-4">
<div class="card-body">
<h2 class="card-title">{{ post.title }}</h2>
<p class="card-text">{{ post.excerpt }}</p>
<a href="{% url 'post_detail' post.id %}" class="btn btn-primary">Read More</a>
</div>
<div class="card-footer text-muted">
Posted on {{ post.published_date|date:"F j, Y" }} by {{ post.author.username }}
</div>
</div>
{% empty %}
<p>No posts found.</p>
{% endfor %}
{% include "blog/pagination.html" with page=posts %}
</div>
<div class="col-md-4">
{% include "blog/sidebar.html" %}
</div>
</div>
{% endblock %}
Multi-Level Inheritance
You can create intermediate templates that extend the base template and are further extended by specific templates:
<!-- templates/blog/base.html -->
{% extends "base.html" %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'css/blog.css' %}">
{% endblock %}
{% block header %}
<div class="blog-header">
<div class="container">
<h1 class="blog-title">{% block blog_title %}The Blog{% endblock %}</h1>
<p class="lead blog-description">{% block blog_description %}A blog about interesting things.{% endblock %}</p>
</div>
</div>
{% endblock %}
<!-- templates/blog/post_detail.html -->
{% extends "blog/base.html" %}
{% block title %}{{ post.title }} | Blog{% endblock %}
{% block blog_title %}{{ post.title }}{% endblock %}
{% block blog_description %}By {{ post.author.username }} on {{ post.published_date|date:"F j, Y" }}{% endblock %}
{% block content %}
<div class="post">
{% if post.image %}
<img src="{{ post.image.url }}" alt="{{ post.title }}" class="img-fluid mb-4">
{% endif %}
<div class="post-content">
{{ post.content|linebreaks }}
</div>
<div class="post-meta">
<span class="categories">
Categories:
{% for category in post.categories.all %}
<a href="{% url 'category_posts' category.slug %}">{{ category.name }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %}
</span>
<span class="tags">
Tags:
{% for tag in post.tags.all %}
<a href="{% url 'tag_posts' tag.slug %}">#{{ tag.name }}</a>{% if not forloop.last %} {% endif %}
{% endfor %}
</span>
</div>
</div>
<div class="comments">
<h3>Comments ({{ post.comments.count }})</h3>
{% for comment in post.comments.all %}
<div class="comment">
<div class="comment-meta">
{{ comment.author.username }} on {{ comment.created_at|date:"F j, Y g:i A" }}
</div>
<div class="comment-content">
{{ comment.content|linebreaks }}
</div>
</div>
{% empty %}
<p>No comments yet.</p>
{% endfor %}
{% if user.is_authenticated %}
<h4>Add a Comment</h4>
<form method="post" action="{% url 'add_comment' post.id %}">
{% csrf_token %}
{{ comment_form.as_p }}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
{% else %}
<p><a href="{% url 'login' %}?next={{ request.path }}">Log in</a> to add a comment.</p>
{% endif %}
</div>
{% endblock %}
Block Inheritance
You can extend a parent block using {{ block.super }}:
<!-- templates/base.html -->
{% block footer %}
<p>© 2023 My Site. All rights reserved.</p>
{% endblock %}
<!-- templates/blog/base.html -->
{% block footer %}
{{ block.super }}
<p>Blog powered by Django.</p>
{% endblock %}
Template inheritance is like the concept of master plans and specific room layouts in interior design. The base template is like a master plan that defines the overall structure of a building—the location of walls, plumbing, electrical systems, etc. Child templates are like detailed room layouts that work within the constraints of the master plan but add specific furniture arrangements, color schemes, and decorative elements for each room. Multi-level inheritance is like having intermediate plans for different types of rooms (e.g., all bathrooms share certain features, but each individual bathroom has its own specific design).
Custom Template Tags
While Django provides many built-in template tags, you can also create custom tags for specialized functionality. There are different types of custom tags, each with its own use case.
Setting Up Template Tags
Custom template tags are defined in a Python module inside an app's templatetags package:
myapp/
├── templatetags/
│ ├── __init__.py
│ └── custom_tags.py
In custom_tags.py, you register your tags with a template library:
from django import template
register = template.Library()
Simple Tags
Simple tags take arguments and return a value that's inserted into the template:
@register.simple_tag
def get_recent_posts(count=5):
"""Return the most recent posts."""
return Post.objects.filter(status='published').order_by('-published_date')[:count]
@register.simple_tag(takes_context=True)
def get_user_posts(context, count=5):
"""Return the most recent posts by the current user."""
request = context['request']
if request.user.is_authenticated:
return Post.objects.filter(author=request.user).order_by('-published_date')[:count]
return []
To use these tags in a template:
{% load custom_tags %}
{% get_recent_posts as recent_posts %}
<ul>
{% for post in recent_posts %}
<li>{{ post.title }}</li>
{% endfor %}
</ul>
{% get_user_posts as user_posts %}
{% if user_posts %}
<h3>Your Posts</h3>
<ul>
{% for post in user_posts %}
<li>{{ post.title }}</li>
{% endfor %}
</ul>
{% endif %}
Inclusion Tags
Inclusion tags render a template with a given context:
@register.inclusion_tag('tags/recent_posts.html')
def show_recent_posts(count=5):
"""Render the recent posts template."""
posts = Post.objects.filter(status='published').order_by('-published_date')[:count]
return {'posts': posts}
@register.inclusion_tag('tags/user_info.html', takes_context=True)
def show_user_info(context):
"""Render the user info template."""
request = context['request']
return {'user': request.user}
The templates for inclusion tags:
<!-- templates/tags/recent_posts.html -->
<div class="recent-posts">
<h3>Recent Posts</h3>
{% if posts %}
<ul>
{% for post in posts %}
<li>
<a href="{% url 'post_detail' post.id %}">{{ post.title }}</a>
<small>{{ post.published_date|date:"M d" }}</small>
</li>
{% endfor %}
</ul>
{% else %}
<p>No recent posts.</p>
{% endif %}
</div>
<!-- templates/tags/user_info.html -->
<div class="user-info">
{% if user.is_authenticated %}
<p>Welcome, {{ user.username }}!</p>
<p><a href="{% url 'logout' %}">Log Out</a></p>
{% else %}
<p><a href="{% url 'login' %}">Log In</a> or <a href="{% url 'register' %}">Register</a></p>
{% endif %}
</div>
To use inclusion tags in a template:
{% load custom_tags %}
<div class="sidebar">
{% show_user_info %}
{% show_recent_posts 3 %}
</div>
Assignment Tags
Assignment tags are like simple tags but specifically designed to assign values to variables:
@register.assignment_tag # In Django 1.9+, use simple_tag instead
def get_popular_posts(count=5):
"""Return the most popular posts."""
return Post.objects.annotate(comment_count=Count('comments')).order_by('-comment_count')[:count]
To use assignment tags (or simple tags with as):
{% load custom_tags %}
{% get_popular_posts as popular_posts %}
<ul>
{% for post in popular_posts %}
<li>{{ post.title }} ({{ post.comment_count }} comments)</li>
{% endfor %}
</ul>
Advanced Custom Tags
For more complex scenarios, you can define a custom template node:
from django import template
from django.utils.safestring import mark_safe
register = template.Library()
class RatingNode(template.Node):
def __init__(self, rating_var):
self.rating_var = template.Variable(rating_var)
def render(self, context):
try:
rating = self.rating_var.resolve(context)
stars = '★' * int(rating) + '☆' * (5 - int(rating))
return mark_safe(f'<div class="rating">{stars}</div>')
except template.VariableDoesNotExist:
return ''
@register.tag
def show_rating(parser, token):
try:
tag_name, rating_var = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError(
"%r tag requires a single argument" % token.contents.split()[0]
)
return RatingNode(rating_var)
To use this custom tag:
{% load custom_tags %}
{% show_rating post.rating %}
Custom template tags are like specialized tools and techniques in interior design. Simple tags are like measuring tools that provide specific information (e.g., room dimensions). Inclusion tags are like pre-designed furniture arrangements that can be dropped into different rooms. Assignment tags are like custom formulas for calculating quantities (e.g., how much paint is needed for a room). Advanced custom tags are like specialized construction techniques that achieve unique visual effects.
Form Rendering in Templates
Django provides several ways to render forms in templates, from automatic rendering to complete manual control.
Automatic Form Rendering
The simplest way to render a form is using its as_p, as_ul, or as_table methods:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Submit</button>
</form>
This renders the entire form, including all fields, labels, and error messages, with minimal styling.
Rendering Form Fields Individually
For more control, you can render each form field individually:
<form method="post">
{% csrf_token %}
<div class="form-group">
{{ form.username.label_tag }}
{{ form.username }}
{% if form.username.errors %}
<div class="text-danger">
{% for error in form.username.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
{% if form.username.help_text %}
<small class="form-text text-muted">{{ form.username.help_text }}</small>
{% endif %}
</div>
<div class="form-group">
{{ form.email.label_tag }}
{{ form.email }}
{% if form.email.errors %}
<div class="text-danger">
{% for error in form.email.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="form-group">
{{ form.password.label_tag }}
{{ form.password }}
{% if form.password.errors %}
<div class="text-danger">
{% for error in form.password.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
{% if form.non_field_errors %}
<div class="alert alert-danger">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
Looping Over Form Fields
You can also loop over all form fields, which is useful for rendering forms with many fields:
<form method="post">
{% csrf_token %}
{% for field in form %}
<div class="form-group">
{{ field.label_tag }}
{{ field }}
{% if field.errors %}
<div class="text-danger">
{% for error in field.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
{% if field.help_text %}
<small class="form-text text-muted">{{ field.help_text }}</small>
{% endif %}
</div>
{% endfor %}
{% if form.non_field_errors %}
<div class="alert alert-danger">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
Custom Widget Attributes
You can customize form widgets by adding CSS classes and other attributes:
# In your form class
class ContactForm(forms.Form):
name = forms.CharField(
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Your Name'})
)
email = forms.EmailField(
widget=forms.EmailInput(attrs={'class': 'form-control', 'placeholder': 'Your Email'})
)
message = forms.CharField(
widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 5, 'placeholder': 'Your Message'})
)
Rendering Forms with Crispy Forms
The django-crispy-forms package provides more control over form rendering with less template code:
# In your form class
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit, Row, Column
class ContactForm(forms.Form):
name = forms.CharField()
email = forms.EmailField()
subject = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.layout = Layout(
Row(
Column('name', css_class='form-group col-md-6 mb-0'),
Column('email', css_class='form-group col-md-6 mb-0'),
css_class='form-row'
),
'subject',
'message',
Submit('submit', 'Send Message', css_class='btn btn-primary')
)
In your template:
{% load crispy_forms_tags %}
<form method="post">
{% csrf_token %}
{% crispy form %}
</form>
Form rendering in templates is like creating custom form layouts for interior design clients. Automatic form rendering is like using standard form templates that follow best practices but offer limited customization. Rendering fields individually is like creating a fully custom design that precisely meets specific requirements. Looping over form fields is like applying the same design pattern to multiple rooms while still allowing for some customization. Crispy Forms is like using a specialized design tool that automates complex layout tasks while still allowing for creative control.
Template Context Processors
Template context processors add variables to the context of every template rendered with a RequestContext. Django includes several built-in context processors, and you can create your own as well.
Built-in Context Processors
Django's built-in context processors include:
-
django.template.context_processors.debug: Adds the
debugandsql_queriesvariables -
django.template.context_processors.request: Adds the
requestvariable (the current HttpRequest object) -
django.contrib.auth.context_processors.auth: Adds the
uservariable (the current user) andperms(the permissions proxy) -
django.contrib.messages.context_processors.messages: Adds the
messagesvariable (a list of messages using the messages framework) -
django.template.context_processors.media: Adds the
MEDIA_URLvariable (the value ofsettings.MEDIA_URL) -
django.template.context_processors.static: Adds the
STATIC_URLvariable (the value ofsettings.STATIC_URL) -
django.template.context_processors.csrf: Adds the
csrf_tokenvariable for CSRF protection
Creating Custom Context Processors
You can create your own context processors to add variables to the context of every template:
# myapp/context_processors.py
from django.conf import settings
from .models import Category, Tag
def site_settings(request):
"""Add site settings to the template context."""
return {
'SITE_NAME': settings.SITE_NAME,
'SITE_DESCRIPTION': settings.SITE_DESCRIPTION,
'CONTACT_EMAIL': settings.CONTACT_EMAIL,
}
def common_variables(request):
"""Add common variables to the template context."""
return {
'categories': Category.objects.all(),
'tags': Tag.objects.all(),
'current_year': timezone.now().year,
}
Then, add your context processors to the TEMPLATES setting:
# settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / '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',
'myapp.context_processors.site_settings',
'myapp.context_processors.common_variables',
],
},
},
]
Now, these variables are available in all templates:
<footer>
<div class="container">
<p>© {{ current_year }} {{ SITE_NAME }}. All rights reserved.</p>
<p>Contact us at <a href="mailto:{{ CONTACT_EMAIL }}">{{ CONTACT_EMAIL }}</a></p>
</div>
</footer>
<div class="sidebar">
<h3>Categories</h3>
<ul>
{% for category in categories %}
<li><a href="{% url 'category_posts' category.slug %}">{{ category.name }}</a></li>
{% endfor %}
</ul>
<h3>Tags</h3>
<div class="tags">
{% for tag in tags %}
<a href="{% url 'tag_posts' tag.slug %}" class="tag">#{{ tag.name }}</a>
{% endfor %}
</div>
</div>
Template context processors are like the standard building codes and client preferences that apply to every room in a building. Just as every room needs to comply with the same electrical code, fire safety regulations, and client color preferences, every template needs access to certain common variables like the current user, site settings, and global navigation elements. Context processors ensure these common elements are consistently available across all templates without having to pass them manually from each view.
Template Caching
Django provides several ways to cache templates, which can significantly improve performance for dynamic sites.
Template Fragment Caching
The cache template tag allows you to cache portions of a template:
{% load cache %}
{% cache 600 sidebar %}
{# Expensive sidebar content that rarely changes #}
<div class="sidebar">
<h3>Categories</h3>
<ul>
{% for category in categories %}
<li>
<a href="{% url 'category_posts' category.slug %}">{{ category.name }}</a>
<span class="count">({{ category.post_count }})</span>
</li>
{% endfor %}
</ul>
<h3>Recent Posts</h3>
<ul>
{% for post in recent_posts %}
<li>
<a href="{% url 'post_detail' post.id %}">{{ post.title }}</a>
<small>{{ post.published_date|date:"M d" }}</small>
</li>
{% endfor %}
</ul>
</div>
{% endcache %}
The first argument (600) is the cache timeout in seconds. The second argument (sidebar) is the cache fragment name. This fragment will be cached for 10 minutes.
Varying Cache by User or Query
You can vary the cache based on the current user or other variables:
{% load cache %}
{% cache 600 sidebar request.user.username %}
{# User-specific sidebar content #}
<div class="sidebar">
<h3>Your Recent Activity</h3>
<ul>
{% for activity in user_activities %}
<li>{{ activity.description }}</li>
{% endfor %}
</ul>
</div>
{% endcache %}
{% cache 300 post_list page %}
{# Paginated post list that varies by page #}
<div class="post-list">
{% for post in posts %}
<div class="post">
<h2>{{ post.title }}</h2>
<p>{{ post.excerpt }}</p>
</div>
{% endfor %}
</div>
{% endcache %}
Using Low-Level Cache API
For more control, you can use Django's low-level cache API in a custom template tag:
from django import template
from django.core.cache import cache
from django.utils.safestring import mark_safe
import hashlib
register = template.Library()
@register.simple_tag
def cached_include(template_name, *args, **kwargs):
"""Include a template and cache the result."""
# Generate a unique cache key based on the template and args
key_parts = [template_name] + list(args) + [f"{k}={v}" for k, v in sorted(kwargs.items())]
cache_key = hashlib.md5(":".join(key_parts).encode()).hexdigest()
# Try to get the cached content
content = cache.get(cache_key)
if content is None:
# Render the template and cache the result
context = kwargs.get('context', {})
content = template.loader.render_to_string(template_name, context)
cache.set(cache_key, content, timeout=3600) # Cache for 1 hour
return mark_safe(content)
To use this custom tag:
{% load custom_cache_tags %}
{% cached_include "sidebar.html" %}
View-Level Caching
You can also cache entire views, which includes the rendered template:
from django.views.decorators.cache import cache_page
@cache_page(60 * 15) # Cache for 15 minutes
def post_list(request):
posts = Post.objects.filter(status='published').order_by('-published_date')
return render(request, 'blog/post_list.html', {'posts': posts})
Template caching is like using prefabricated components in building construction to save time and resources. Just as a builder might use pre-made cabinets, doors, or wall sections instead of building everything from scratch for each project, a web developer can cache rendered template fragments instead of regenerating them for each request. Fragment caching is like using prefabricated components for specific parts of a building while still customizing other parts. Varying cache by user is like having a standard prefabricated component that comes in different sizes or colors to match different client needs.
Template Best Practices
Here are some best practices to follow when working with Django templates:
- Use template inheritance: Create a base template with the common structure and extend it in child templates. This promotes code reuse and consistency.
-
Keep templates DRY (Don't Repeat Yourself): Extract repeated code into separate templates and include them where needed.
{% include "pagination.html" with page=posts %} {% include "search_form.html" %} -
Use meaningful block names: Choose descriptive names for template blocks to make it clear what they contain.
{% block content %}{% endblock %} {# Good #} {% block b1 %}{% endblock %} {# Bad #} -
Namespace app templates: Place app templates in a subdirectory named after the app to avoid naming conflicts.
templates/ ├── blog/ │ └── post_list.html ├── users/ │ └── profile.html -
Limit logic in templates: Keep complex logic in views or models, not in templates.
{# Bad - Complex logic in template #} {% if user.is_staff or user.is_superuser or user.groups.all|join:','|search:'editors' or user.id == post.author.id %} <a href="{% url 'post_edit' post.id %}">Edit</a> {% endif %} {# Good - Logic in view or model #} {% if user_can_edit %} <a href="{% url 'post_edit' post.id %}">Edit</a> {% endif %} - Use custom template tags for complex operations: If you need complex logic in templates, create custom template tags.
-
Be careful with the 'safe' filter: Only use the 'safe' filter for content that you're sure doesn't contain malicious HTML.
{{ user_input }} {# Good - Automatically escaped #} {{ user_input|safe }} {# Potentially dangerous - Only use for trusted content #} -
Use URL names, not hardcoded URLs: Use the 'url' tag with URL names to generate URLs, making them easier to maintain.
<a href="/blog/posts/{{ post.id }}/"> {# Bad - Hardcoded URL #} <a href="{% url 'post_detail' post.id %}"> {# Good - URL name #} -
Use the static tag for static files: Use the 'static' tag to generate URLs for static files, making them work with different storage backends.
<img src="/static/images/logo.png"> {# Bad - Hardcoded path #} <img src="{% static 'images/logo.png' %}"> {# Good - Static tag #} -
Comment your templates: Add comments to explain complex or non-obvious sections of your templates.
{# This section displays the user's recent activity if available #} {% if user_activities %} <div class="recent-activity"> {# ... #} </div> {% endif %} - Use consistent indentation and formatting: Keep your templates readable with consistent indentation and formatting.
- Consider using a frontend framework for complex UI: For complex, interactive UIs, consider using a frontend framework like React or Vue.js with Django REST framework.
Following these best practices will help you create templates that are maintainable, efficient, and secure. These practices are like the principles of good interior design—consistency, modularity, safety, accessibility, and clarity. Just as a well-designed building has a consistent style, clear navigation, accessible spaces, and thoughtful details, a well-designed template system has consistent structure, clear organization, accessible content, and thoughtful implementation.
Practice Activity: Django Templates
Let's put what we've learned into practice by working with Django templates:
Activity 1: Template Hierarchy
Create a template hierarchy for a blog application:
- Create a base template with common elements (header, footer, navigation)
- Create a base blog template that extends the main base template
- Create templates for post list, post detail, and post form that extend the blog base template
- Use blocks for title, content, and any other sections that might vary between pages
Activity 2: Template Tags and Filters
Practice using template tags and filters:
- Create a template that displays a list of posts with proper formatting
- Use conditional tags to show different content for authenticated and anonymous users
- Use loop tags to iterate over posts and show their details
- Use filters to format dates, truncate content, and capitalize titles
Activity 3: Custom Template Tags
Create custom template tags and filters:
- Create a simple tag that returns recent posts
- Create an inclusion tag that renders a user profile sidebar
- Create a custom filter that formats text as markdown
- Use your custom tags and filters in templates
Activity 4: Form Rendering
Practice rendering forms in templates:
- Render a form using automatic rendering (
form.as_p) - Render the same form manually, field by field
- Render the form using a loop over all fields
- Customize the form rendering with CSS classes and other attributes
Summary
In this lecture, we've explored Django's template language, covering:
- The role of templates in Django's MVT architecture
- Template configuration and file structure
- Template variables and filters for displaying dynamic data
- Template tags and control structures for adding logic
- Template inheritance for creating consistent layouts
- Custom template tags for extending template functionality
- Form rendering in templates
- Template context processors for adding global variables
- Template caching for improving performance
- Best practices for working with templates
Templates are a crucial part of Django applications, serving as the bridge between your