Django Template Language

Creating Dynamic Web Pages with Django's Powerful Templating System

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:

graph LR A[Views] -- Context Dictionary --> B[Template Engine] B -- Rendered HTML --> C[HTTP Response] D[Template Files] --> B style B fill:#f9f,stroke:#333,stroke-width:2px

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:

  1. The project-level templates directory
  2. Each app's templates directory

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:

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:

<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:

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:

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:

  1. Create a base template with common elements (header, footer, navigation)
  2. Create a base blog template that extends the main base template
  3. Create templates for post list, post detail, and post form that extend the blog base template
  4. 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:

  1. Create a template that displays a list of posts with proper formatting
  2. Use conditional tags to show different content for authenticated and anonymous users
  3. Use loop tags to iterate over posts and show their details
  4. Use filters to format dates, truncate content, and capitalize titles

Activity 3: Custom Template Tags

Create custom template tags and filters:

  1. Create a simple tag that returns recent posts
  2. Create an inclusion tag that renders a user profile sidebar
  3. Create a custom filter that formats text as markdown
  4. Use your custom tags and filters in templates

Activity 4: Form Rendering

Practice rendering forms in templates:

  1. Render a form using automatic rendering (form.as_p)
  2. Render the same form manually, field by field
  3. Render the form using a loop over all fields
  4. Customize the form rendering with CSS classes and other attributes

Summary

In this lecture, we've explored Django's template language, covering:

Templates are a crucial part of Django applications, serving as the bridge between your