Templates and Presentation Layer

The Django Template Language

The Django Template Language (DTL) is a powerful, secure, and designer-friendly templating system. It provides a clean syntax for displaying dynamic content while maintaining separation between presentation and business logic.

The Django Template Language

The Django Template Language (DTL) is a powerful, secure, and designer-friendly templating system. It provides a clean syntax for displaying dynamic content while maintaining separation between presentation and business logic.

Template Syntax Overview

Core Syntax Elements

Variables - Display dynamic content

{{ variable }}
{{ object.attribute }}
{{ dictionary.key }}
{{ list.0 }}

Tags - Control logic and structure

{% tag_name %}
{% tag_name argument %}
{% tag_name argument1 argument2 %}

Filters - Transform variable output

{{ variable|filter }}
{{ variable|filter:argument }}
{{ variable|filter1|filter2 }}

Comments - Documentation and notes

{# Single line comment #}
{% comment %}
Multi-line comment
{% endcomment %}

Variables and Lookups

Variable Resolution

Django resolves variables using this order:

  1. Dictionary lookup: foo['bar']
  2. Attribute lookup: foo.bar
  3. Method call: foo.bar()
  4. List index lookup: foo[bar]
# views.py
context = {
    'user': request.user,
    'post': {
        'title': 'My Post',
        'tags': ['django', 'python'],
    },
    'numbers': [1, 2, 3, 4, 5],
}
<!-- template.html -->
{{ user.username }}        <!-- Attribute lookup -->
{{ user.get_full_name }}   <!-- Method call -->
{{ post.title }}           <!-- Dictionary lookup -->
{{ post.tags.0 }}          <!-- List index lookup -->
{{ numbers.2 }}            <!-- List index: 3 -->

Complex Object Access

# models.py
class Author(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField()
    
    def get_latest_posts(self):
        return self.posts.filter(published=True)[:5]

class Post(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='posts')
    tags = models.ManyToManyField('Tag')
    
    @property
    def word_count(self):
        return len(self.content.split())

class Tag(models.Model):
    name = models.CharField(max_length=50)
<!-- Accessing related objects -->
<h1>{{ post.title }}</h1>
<p>By {{ post.author.name }} ({{ post.author.email }})</p>
<p>Word count: {{ post.word_count }}</p>

<!-- Accessing related sets -->
<h3>Tags:</h3>
{% for tag in post.tags.all %}
    <span class="tag">{{ tag.name }}</span>
{% endfor %}

<!-- Accessing methods -->
<h3>Author's Latest Posts:</h3>
{% for latest_post in post.author.get_latest_posts %}
    <a href="{{ latest_post.get_absolute_url }}">{{ latest_post.title }}</a>
{% endfor %}

Template Tags

Conditional Tags

if/elif/else:

{% if user.is_authenticated %}
    <p>Welcome, {{ user.username }}!</p>
    {% if user.is_staff %}
        <a href="{% url 'admin:index' %}">Admin Panel</a>
    {% elif user.is_superuser %}
        <a href="{% url 'admin:index' %}">Super Admin</a>
    {% endif %}
{% else %}
    <p><a href="{% url 'login' %}">Please log in</a></p>
{% endif %}

Comparison Operators:

{% if posts.count > 0 %}
    <p>Found {{ posts.count }} posts</p>
{% endif %}

{% if user.age >= 18 %}
    <p>Adult content available</p>
{% endif %}

{% if post.status == 'published' %}
    <span class="published">Published</span>
{% endif %}

{% if post.author != user %}
    <p>This post is by {{ post.author.name }}</p>
{% endif %}

Logical Operators:

{% if user.is_authenticated and user.is_active %}
    <p>Active user</p>
{% endif %}

{% if post.featured or post.sticky %}
    <span class="highlight">Featured</span>
{% endif %}

{% if not user.is_anonymous %}
    <p>Logged in user</p>
{% endif %}

Membership Tests:

{% if 'admin' in user.groups.all %}
    <p>Administrator</p>
{% endif %}

{% if user in post.likes.all %}
    <button>Unlike</button>
{% else %}
    <button>Like</button>
{% endif %}

Loop Tags

Basic for Loop:

<ul>
{% for post in posts %}
    <li>{{ post.title }} - {{ post.created_at|date:"Y-m-d" }}</li>
{% empty %}
    <li>No posts available</li>
{% endfor %}
</ul>

Loop Variables:

<table>
{% for post in posts %}
    <tr class="{% if forloop.first %}first{% elif forloop.last %}last{% endif %}
               {% if forloop.counter|divisibleby:2 %}even{% else %}odd{% endif %}">
        <td>{{ forloop.counter }}</td>
        <td>{{ post.title }}</td>
        <td>{{ forloop.revcounter }} remaining</td>
    </tr>
{% endfor %}
</table>

Available forloop Variables:

  • forloop.counter - Current iteration (1-indexed)
  • forloop.counter0 - Current iteration (0-indexed)
  • forloop.revcounter - Reverse counter (1-indexed)
  • forloop.revcounter0 - Reverse counter (0-indexed)
  • forloop.first - True if first iteration
  • forloop.last - True if last iteration
  • forloop.parentloop - Parent loop in nested loops

Nested Loops:

{% for category in categories %}
    <h2>{{ category.name }}</h2>
    <ul>
    {% for post in category.posts.all %}
        <li>
            {{ forloop.parentloop.counter }}.{{ forloop.counter }} 
            {{ post.title }}
        </li>
    {% endfor %}
    </ul>
{% endfor %}

url Tag:

<!-- Basic URL generation -->
<a href="{% url 'blog:post_detail' post.pk %}">{{ post.title }}</a>
<a href="{% url 'blog:category' category.slug %}">{{ category.name }}</a>

<!-- URL with multiple arguments -->
<a href="{% url 'blog:post_by_date' year=2024 month=3 day=15 %}">March 15, 2024</a>

<!-- URL with query parameters -->
<a href="{% url 'blog:post_list' %}?page=2&category={{ category.slug }}">Next Page</a>

<!-- Store URL in variable -->
{% url 'blog:post_detail' post.pk as post_url %}
<a href="{{ post_url }}">Read More</a>
<meta property="og:url" content="{{ request.build_absolute_uri }}{{ post_url }}">

Template Loading Tags

include Tag:

<!-- Include another template -->
{% include 'blog/includes/post_card.html' %}

<!-- Include with additional context -->
{% include 'blog/includes/post_card.html' with featured=True %}

<!-- Include with only specific context -->
{% include 'blog/includes/post_card.html' with post=featured_post only %}

extends and block Tags:

<!-- child_template.html -->
{% extends 'base.html' %}

{% block title %}{{ post.title }} - {{ block.super }}{% endblock %}

{% block content %}
    <h1>{{ post.title }}</h1>
    <p>{{ post.content }}</p>
{% endblock %}

{% block extra_css %}
    <link rel="stylesheet" href="{% static 'blog/css/post.css' %}">
{% endblock %}

Utility Tags

with Tag:

<!-- Create local variables -->
{% with post.author as author %}
    <p>{{ author.name }} ({{ author.email }})</p>
    <p>Posts by {{ author.name }}: {{ author.posts.count }}</p>
{% endwith %}

<!-- Multiple variables -->
{% with total=posts.count published=posts.published.count %}
    <p>{{ published }} of {{ total }} posts are published</p>
{% endwith %}

spaceless Tag:

{% spaceless %}
    <p>
        <a href="{% url 'home' %}">Home</a>
        <a href="{% url 'about' %}">About</a>
    </p>
{% endspaceless %}
<!-- Output: <p><a href="/">Home</a><a href="/about/">About</a></p> -->

verbatim Tag:

{% verbatim %}
    <!-- This won't be processed by Django -->
    <script>
        var data = {{ json_data }};  // This stays as literal text
    </script>
{% endverbatim %}

Template Filters

String Filters

Text Transformation:

{{ "hello world"|title }}           <!-- Hello World -->
{{ "LOUD TEXT"|lower }}             <!-- loud text -->
{{ "quiet text"|upper }}            <!-- QUIET TEXT -->
{{ "  spaced  "|strip }}            <!-- spaced -->
{{ "hello-world"|slugify }}         <!-- hello-world -->

Text Truncation:

{{ post.content|truncatewords:50 }}
{{ post.title|truncatechars:30 }}
{{ post.content|truncatewords_html:50 }}

Text Formatting:

{{ post.content|linebreaks }}       <!-- Converts \n to <p> and <br> -->
{{ post.content|linebreaksbr }}     <!-- Converts \n to <br> -->
{{ post.content|striptags }}        <!-- Removes HTML tags -->
{{ post.content|escape }}           <!-- HTML escapes -->
{{ trusted_html|safe }}             <!-- Marks as safe HTML -->

Numeric Filters

Number Formatting:

{{ 1234.5678|floatformat:2 }}       <!-- 1234.57 -->
{{ 42|add:8 }}                      <!-- 50 -->
{{ numbers|length }}                <!-- Count of items -->
{{ price|floatformat:0 }}           <!-- 1235 -->

Date and Time Filters

Date Formatting:

{{ post.created_at|date:"Y-m-d" }}              <!-- 2024-03-15 -->
{{ post.created_at|date:"F d, Y" }}             <!-- March 15, 2024 -->
{{ post.created_at|time:"H:i" }}                <!-- 14:30 -->
{{ post.created_at|timesince }}                 <!-- 2 hours ago -->
{{ post.created_at|timeuntil }}                 <!-- in 3 days -->

Custom Date Formats:

{{ post.created_at|date:"l, F j, Y \a\t P" }}  <!-- Friday, March 15, 2024 at 2:30 p.m. -->
{{ post.created_at|date:"c" }}                  <!-- 2024-03-15T14:30:00+00:00 -->

List and Dictionary Filters

List Operations:

{{ posts|first }}                   <!-- First item -->
{{ posts|last }}                    <!-- Last item -->
{{ posts|length }}                  <!-- Count -->
{{ posts|slice:":5" }}              <!-- First 5 items -->
{{ posts|slice:"5:" }}              <!-- Items from 5th onward -->
{{ tags|join:", " }}                <!-- Join with comma -->

Dictionary Operations:

{{ user_data|dictsort:"name" }}     <!-- Sort by 'name' key -->
{{ categories|dictsortreversed:"count" }}  <!-- Reverse sort -->

Conditional Filters

Default Values:

{{ post.excerpt|default:"No excerpt available" }}
{{ user.get_full_name|default:user.username }}
{{ post.image|default_if_none:"placeholder.jpg" }}

Conditional Display:

{{ post.is_featured|yesno:"Featured,Regular" }}
{{ post.is_published|yesno:"Published,Draft,Unknown" }}

Chaining Filters

{{ post.content|striptags|truncatewords:50|title }}
{{ post.created_at|date:"Y-m-d"|default:"Unknown date" }}
{{ user.email|lower|default:"No email" }}

Advanced Template Features

Custom Template Tags and Filters

Creating Custom Filters:

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

register = template.Library()

@register.filter
def markdown_to_html(text):
    """Convert markdown to HTML."""
    return mark_safe(markdown.markdown(text))

@register.filter
def reading_time(text):
    """Calculate reading time in minutes."""
    word_count = len(text.split())
    minutes = word_count // 200  # Average reading speed
    return max(1, minutes)

Using Custom Filters:

{% load blog_extras %}

<div class="content">
    {{ post.content|markdown_to_html }}
</div>
<p class="meta">Reading time: {{ post.content|reading_time }} minutes</p>

Creating Custom Tags:

# blog/templatetags/blog_extras.py
@register.simple_tag
def get_recent_posts(count=5):
    """Get recent published posts."""
    return Post.objects.filter(published=True)[:count]

@register.inclusion_tag('blog/includes/recent_posts.html')
def show_recent_posts(count=5):
    """Render recent posts widget."""
    posts = Post.objects.filter(published=True)[:count]
    return {'posts': posts}

@register.simple_tag(takes_context=True)
def current_url_with_params(context, **kwargs):
    """Get current URL with additional parameters."""
    request = context['request']
    params = request.GET.copy()
    for key, value in kwargs.items():
        params[key] = value
    return f"{request.path}?{params.urlencode()}"

Using Custom Tags:

{% load blog_extras %}

<!-- Simple tag -->
{% get_recent_posts 3 as recent %}
{% for post in recent %}
    <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
{% endfor %}

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

<!-- Context-aware tag -->
<a href="{% current_url_with_params page=2 %}">Next Page</a>

Template Inheritance Patterns

Multi-level Inheritance:

<!-- base.html -->
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}My Site{% endblock %}</title>
    {% block extra_css %}{% endblock %}
</head>
<body>
    {% block content %}{% endblock %}
    {% block extra_js %}{% endblock %}
</body>
</html>

<!-- blog_base.html -->
{% extends 'base.html' %}

{% block title %}Blog - {{ block.super }}{% endblock %}

{% block extra_css %}
    <link rel="stylesheet" href="{% static 'blog/css/blog.css' %}">
{% endblock %}

{% block content %}
    <div class="blog-container">
        {% block blog_content %}{% endblock %}
    </div>
{% endblock %}

<!-- post_detail.html -->
{% extends 'blog/blog_base.html' %}

{% block title %}{{ post.title }} - {{ block.super }}{% endblock %}

{% block blog_content %}
    <article>
        <h1>{{ post.title }}</h1>
        <p>{{ post.content }}</p>
    </article>
{% endblock %}

Template Context Manipulation

Modifying Context in Templates:

{% with posts.count as total_posts %}
    {% if total_posts > 10 %}
        <p>Showing first 10 of {{ total_posts }} posts</p>
        {% for post in posts|slice:":10" %}
            <!-- Display post -->
        {% endfor %}
    {% else %}
        {% for post in posts %}
            <!-- Display all posts -->
        {% endfor %}
    {% endif %}
{% endwith %}

Complex Context Processing:

{% for category in categories %}
    {% with category.posts.published as published_posts %}
        {% if published_posts %}
            <h2>{{ category.name }} ({{ published_posts.count }})</h2>
            {% for post in published_posts|slice:":5" %}
                <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
            {% endfor %}
        {% endif %}
    {% endwith %}
{% endfor %}

Performance Optimization

Template Caching

Fragment Caching:

{% load cache %}

{% cache 300 post_sidebar post.pk %}
    <div class="sidebar">
        <h3>Related Posts</h3>
        {% for related in post.get_related_posts %}
            <a href="{{ related.get_absolute_url }}">{{ related.title }}</a>
        {% endfor %}
    </div>
{% endcache %}

Conditional Caching:

{% if user.is_authenticated %}
    <!-- Don't cache for authenticated users -->
    <div class="user-specific-content">
        Welcome, {{ user.username }}!
    </div>
{% else %}
    {% cache 600 anonymous_header %}
        <div class="header">
            <h1>Welcome to our site!</h1>
        </div>
    {% endcache %}
{% endif %}

Efficient Template Patterns

Minimize Database Queries:

<!-- Good: Use select_related/prefetch_related in view -->
{% for post in posts %}
    <h3>{{ post.title }}</h3>
    <p>By {{ post.author.name }}</p>  <!-- No additional query -->
    <p>Category: {{ post.category.name }}</p>  <!-- No additional query -->
{% endfor %}

<!-- Bad: This causes N+1 queries -->
{% for post in posts %}
    <h3>{{ post.title }}</h3>
    <p>Comments: {{ post.comments.count }}</p>  <!-- Query per post -->
{% endfor %}

Efficient Conditional Rendering:

<!-- Good: Check once, render accordingly -->
{% with user_posts=user.posts.published %}
    {% if user_posts %}
        <h3>Your Published Posts ({{ user_posts.count }})</h3>
        {% for post in user_posts %}
            <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
        {% endfor %}
    {% else %}
        <p>You haven't published any posts yet.</p>
    {% endif %}
{% endwith %}

The Django Template Language provides a powerful, secure foundation for creating dynamic web pages. Understanding its syntax, features, and optimization techniques enables you to build efficient, maintainable templates that scale with your application.