Skip to content

Django Template System

This chapter details Django's template system, including template syntax, template inheritance, filters, tags, context processors, and other core concepts, helping you create dynamic and maintainable HTML pages.

Template System Overview

What are Django Templates

Django templates are text files that can generate any text-based format (HTML, XML, CSV, etc.). Templates contain variables and tags that are replaced with actual values when rendered.

html
<!-- Basic template example -->
<!DOCTYPE html>
<html>
<head>
    <title>{{ page_title }}</title>
</head>
<body>
    <h1>Welcome, {{ user.username }}!</h1>
    
    {% if articles %}
        <ul>
        {% for article in articles %}
            <li><a href="{{ article.get_absolute_url }}">{{ article.title }}</a></li>
        {% endfor %}
        </ul>
    {% else %}
        <p>No articles yet</p>
    {% endif %}
</body>
</html>

Template Configuration

python
# settings.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            BASE_DIR / 'templates',  # Project-level template directory
        ],
        'APP_DIRS': True,  # Search for templates in app 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',
                'myapp.context_processors.custom_context',  # Custom context processor
            ],
        },
    },
]

Template Directory Structure

myproject/
├── templates/                    # Project-level templates
│   ├── base.html                # Base template
│   ├── 404.html                 # Error pages
│   ├── 500.html
│   └── registration/            # Authentication-related templates
│       ├── login.html
│       └── signup.html
├── blog/
│   └── templates/
│       └── blog/                # Application-level templates
│           ├── article_list.html
│           ├── article_detail.html
│           └── category.html
└── accounts/
    └── templates/
        └── accounts/
            ├── profile.html
            └── settings.html

Template Syntax Basics

Variables

html
<!-- Basic variables -->
{{ variable }}
{{ user.username }}
{{ article.title }}

<!-- Dictionary access -->
{{ data.key }}
{{ user.profile.bio }}

<!-- List access -->
{{ items.0 }}
{{ articles.first.title }}

<!-- Method calls (no parameters) -->
{{ article.get_absolute_url }}
{{ user.get_full_name }}

<!-- Default values -->
{{ variable|default:"default value" }}
{{ user.email|default:"email not set" }}

Tags

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

<!-- Loops -->
{% for article in articles %}
    <div class="article">
        <h3>{{ article.title }}</h3>
        <p>{{ article.content|truncatewords:30 }}</p>
        
        <!-- Loop variables -->
        <small>Article {{ forloop.counter }}</small>
        
        {% if forloop.first %}
            <span class="badge">Latest</span>
        {% endif %}
        
        {% if forloop.last %}
            <hr>
        {% endif %}
    </div>
{% empty %}
    <p>No articles yet</p>
{% endfor %}

<!-- Loop variables explained -->
{{ forloop.counter }}      <!-- Current iteration (1-indexed) -->
{{ forloop.counter0 }}     <!-- Current iteration (0-indexed) -->
{{ forloop.revcounter }}   <!-- Remaining iterations (to 1) -->
{{ forloop.revcounter0 }}  <!-- Remaining iterations (to 0) -->
{{ forloop.first }}        <!-- Whether first iteration -->
{{ forloop.last }}         <!-- Whether last iteration -->
{{ forloop.parentloop }}   <!-- Parent loop object -->

<!-- URL generation -->
<a href="{% url 'blog:article_detail' article.id %}">{{ article.title }}</a>
<a href="{% url 'blog:category' category.slug %}">{{ category.name }}</a>

<!-- Static files -->
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<img src="{% static 'images/logo.png' %}" alt="Logo">

<!-- Include other templates -->
{% include 'partials/header.html' %}
{% include 'partials/sidebar.html' with articles=latest_articles %}

<!-- Comments -->
{# This is a single-line comment #}
{% comment %}
This is a multi-line comment
Can contain multiple lines of content
{% endcomment %}

Filters

html
<!-- String filters -->
{{ article.title|upper }}                    <!-- Uppercase -->
{{ article.title|lower }}                    <!-- Lowercase -->
{{ article.title|title }}                    <!-- Title case -->
{{ article.title|capfirst }}                 <!-- Capitalize first letter -->
{{ article.content|truncatewords:30 }}       <!-- Truncate to 30 words -->
{{ article.content|truncatechars:100 }}      <!-- Truncate to 100 characters -->
{{ article.content|linebreaks }}             <!-- Convert newlines to <p> and <br> -->
{{ article.content|striptags }}              <!-- Remove HTML tags -->
{{ article.content|escape }}                 <!-- HTML escape -->
{{ article.content|safe }}                   <!-- Mark as safe, don't escape -->

<!-- Number filters -->
{{ price|floatformat:2 }}                    <!-- Format with 2 decimal places -->
{{ number|add:10 }}                          <!-- Add 10 -->
{{ items|length }}                           <!-- Get length -->

<!-- Date filters -->
{{ article.created_at|date:"Y-m-d H:i:s" }} <!-- Format date -->
{{ article.created_at|timesince }}          <!-- Time since now -->
{{ article.created_at|timeuntil }}          <!-- Time until future -->

<!-- List filters -->
{{ articles|first }}                         <!-- First element -->
{{ articles|last }}                          <!-- Last element -->
{{ articles|slice:":5" }}                   <!-- First 5 elements -->
{{ articles|random }}                        <!-- Random element -->
{{ tags|join:", " }}                         <!-- Join with comma -->

<!-- Chained filters -->
{{ article.title|lower|capfirst }}
{{ article.content|striptags|truncatewords:20 }}
{{ user.email|default:"not set"|upper }}

Template Inheritance

Base Template

html
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}My Website{% 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>
    <!-- Navigation bar -->
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="{% url 'home' %}">My Website</a>
            
            <div class="navbar-nav">
                <a class="nav-link" href="{% url 'blog:article_list' %}">Articles</a>
                <a class="nav-link" href="{% url 'about' %}">About</a>
                
                {% if user.is_authenticated %}
                    <a class="nav-link" href="{% url 'profile' %}">{{ user.username }}</a>
                    <a class="nav-link" href="{% url 'logout' %}">Logout</a>
                {% else %}
                    <a class="nav-link" href="{% url 'login' %}">Login</a>
                    <a class="nav-link" href="{% url 'signup' %}">Sign Up</a>
                {% endif %}
            </div>
        </div>
    </nav>

    <!-- Message alerts -->
    {% if messages %}
        <div class="container mt-3">
            {% for message in messages %}
                <div class="alert alert-{{ message.tags }} alert-dismissible fade show">
                    {{ message }}
                    <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
                </div>
            {% endfor %}
        </div>
    {% endif %}

    <!-- Breadcrumb navigation -->
    {% block breadcrumb %}{% endblock %}

    <!-- Main content -->
    <main class="container my-4">
        <div class="row">
            <div class="col-md-8">
                {% block content %}{% endblock %}
            </div>
            
            <div class="col-md-4">
                {% block sidebar %}
                    {% include 'partials/sidebar.html' %}
                {% endblock %}
            </div>
        </div>
    </main>

    <!-- Footer -->
    <footer class="bg-dark text-light py-4 mt-5">
        <div class="container">
            <div class="row">
                <div class="col-md-6">
                    <p>&copy; 2023 My Website. All rights reserved.</p>
                </div>
                <div class="col-md-6 text-end">
                    {% block footer_extra %}{% endblock %}
                </div>
            </div>
        </div>
    </footer>

    <!-- JavaScript -->
    <script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
    {% block extra_js %}{% endblock %}
</body>
</html>

Child Template

html
<!-- blog/templates/blog/article_list.html -->
{% extends 'base.html' %}
{% load static %}

{% block title %}Article List - {{ block.super }}{% endblock %}

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

{% block breadcrumb %}
<nav aria-label="breadcrumb" class="container mt-3">
    <ol class="breadcrumb">
        <li class="breadcrumb-item"><a href="{% url 'home' %}">Home</a></li>
        <li class="breadcrumb-item active">Article List</li>
    </ol>
</nav>
{% endblock %}

{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
    <h1>Article List</h1>
    {% if user.is_authenticated %}
        <a href="{% url 'blog:create_article' %}" class="btn btn-primary">Write Article</a>
    {% endif %}
</div>

<!-- Search and filtering -->
<div class="row mb-4">
    <div class="col-md-8">
        <form method="get" class="d-flex">
            <input type="text" name="search" class="form-control me-2" 
                   placeholder="Search articles..." value="{{ search_query }}">
            <button type="submit" class="btn btn-outline-secondary">Search</button>
        </form>
    </div>
    <div class="col-md-4">
        <select class="form-select" onchange="filterByCategory(this.value)">
            <option value="">All Categories</option>
            {% for category in categories %}
                <option value="{{ category.slug }}" 
                        {% if category.slug == current_category %}selected{% endif %}>
                    {{ category.name }}
                </option>
            {% endfor %}
        </select>
    </div>
</div>

<!-- Article list -->
{% for article in page_obj %}
    <article class="card mb-4">
        {% if article.featured_image %}
            <img src="{{ article.featured_image.url }}" class="card-img-top" alt="{{ article.title }}">
        {% endif %}
        
        <div class="card-body">
            <h5 class="card-title">
                <a href="{{ article.get_absolute_url }}" class="text-decoration-none">
                    {{ article.title }}
                </a>
            </h5>
            
            <p class="card-text">{{ article.content|striptags|truncatewords:30 }}</p>
            
            <div class="d-flex justify-content-between align-items-center">
                <small class="text-muted">
                    <i class="fas fa-user"></i> {{ article.author.username }}
                    <i class="fas fa-calendar ms-2"></i> {{ article.created_at|date:"Y-m-d" }}
                    <i class="fas fa-eye ms-2"></i> {{ article.views }}
                </small>
                
                <span class="badge bg-secondary">{{ article.category.name }}</span>
            </div>
        </div>
    </article>
{% empty %}
    <div class="text-center py-5">
        <h3>No articles yet</h3>
        <p class="text-muted">No articles have been published yet</p>
        {% if user.is_authenticated %}
            <a href="{% url 'blog:create_article' %}" class="btn btn-primary">Write First Article</a>
        {% endif %}
    </div>
{% endfor %}

<!-- Pagination -->
{% if page_obj.has_other_pages %}
    <nav aria-label="Article pagination">
        <ul class="pagination justify-content-center">
            {% if page_obj.has_previous %}
                <li class="page-item">
                    <a class="page-link" href="?page=1{% if search_query %}&search={{ search_query }}{% endif %}">First</a>
                </li>
                <li class="page-item">
                    <a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if search_query %}&search={{ search_query }}{% endif %}">Previous</a>
                </li>
            {% endif %}
            
            {% for num in page_obj.paginator.page_range %}
                {% if page_obj.number == num %}
                    <li class="page-item active">
                        <span class="page-link">{{ num }}</span>
                    </li>
                {% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
                    <li class="page-item">
                        <a class="page-link" href="?page={{ num }}{% if search_query %}&search={{ search_query }}{% endif %}">{{ num }}</a>
                    </li>
                {% endif %}
            {% endfor %}
            
            {% if page_obj.has_next %}
                <li class="page-item">
                    <a class="page-link" href="?page={{ page_obj.next_page_number }}{% if search_query %}&search={{ search_query }}{% endif %}">Next</a>
                </li>
                <li class="page-item">
                    <a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if search_query %}&search={{ search_query }}{% endif %}">Last</a>
                </li>
            {% endif %}
        </ul>
    </nav>
{% endif %}
{% endblock %}

{% block sidebar %}
    <!-- Latest articles -->
    <div class="card mb-4">
        <div class="card-header">
            <h5>Latest Articles</h5>
        </div>
        <div class="card-body">
            {% for article in latest_articles %}
                <div class="mb-2">
                    <a href="{{ article.get_absolute_url }}" class="text-decoration-none">
                        {{ article.title|truncatechars:30 }}
                    </a>
                    <small class="text-muted d-block">{{ article.created_at|date:"m-d" }}</small>
                </div>
            {% endfor %}
        </div>
    </div>
    
    <!-- Popular tags -->
    <div class="card">
        <div class="card-header">
            <h5>Popular Tags</h5>
        </div>
        <div class="card-body">
            {% for tag in popular_tags %}
                <a href="{% url 'blog:tag' tag.slug %}" class="badge bg-light text-dark me-1 mb-1">
                    {{ tag.name }}
                </a>
            {% endfor %}
        </div>
    </div>
{% endblock %}

{% block extra_js %}
<script>
function filterByCategory(categorySlug) {
    const url = new URL(window.location);
    if (categorySlug) {
        url.searchParams.set('category', categorySlug);
    } else {
        url.searchParams.delete('category');
    }
    window.location.href = url.toString();
}
</script>
{% endblock %}

Custom Template Tags and Filters

Creating Template Tag Library

python
# blog/templatetags/__init__.py
# Empty file to make directory a Python package

# blog/templatetags/blog_extras.py
from django import template
from django.utils.safestring import mark_safe
from django.utils.html import format_html
from ..models import Article, Category
import markdown

register = template.Library()

# Simple tags
@register.simple_tag
def total_articles():
    """Return total number of articles"""
    return Article.objects.filter(published=True).count()

@register.simple_tag
def get_categories():
    """Get all categories"""
    return Category.objects.all()

@register.simple_tag(takes_context=True)
def current_time(context, format_string):
    """Get current time"""
    from datetime import datetime
    return datetime.now().strftime(format_string)

# Inclusion tags
@register.inclusion_tag('blog/tags/recent_articles.html')
def show_recent_articles(count=5):
    """Show recent articles"""
    articles = Article.objects.filter(published=True)[:count]
    return {'articles': articles}

@register.inclusion_tag('blog/tags/category_list.html', takes_context=True)
def show_categories(context):
    """Show category list"""
    categories = Category.objects.all()
    return {
        'categories': categories,
        'request': context['request'],
    }

# Assignment tags
@register.simple_tag
def get_popular_articles(count=5):
    """Get popular articles"""
    return Article.objects.filter(published=True).order_by('-views')[:count]

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

@register.filter
def multiply(value, arg):
    """Multiplication filter"""
    try:
        return int(value) * int(arg)
    except (ValueError, TypeError):
        return 0

@register.filter
def get_item(dictionary, key):
    """Get value from dictionary"""
    return dictionary.get(key)

@register.filter
def add_class(field, css_class):
    """Add CSS class to form field"""
    return field.as_widget(attrs={'class': css_class})

# Conditional tags
@register.simple_tag
def url_replace(request, field, value):
    """Replace URL parameter"""
    dict_ = request.GET.copy()
    dict_[field] = value
    return dict_.urlencode()

# Complex custom tags
@register.tag
def get_articles_by_category(parser, token):
    """Get articles by category"""
    try:
        tag_name, category_slug, as_var = token.split_contents()
    except ValueError:
        raise template.TemplateSyntaxError(
            f"{token.contents.split()[0]} tag requires exactly two arguments"
        )
    
    if not (as_var == 'as'):
        raise template.TemplateSyntaxError(
            f"{tag_name} tag's second argument should be 'as'"
        )
    
    return ArticlesByCategoryNode(category_slug, as_var)

class ArticlesByCategoryNode(template.Node):
    def __init__(self, category_slug, var_name):
        self.category_slug = template.Variable(category_slug)
        self.var_name = var_name
    
    def render(self, context):
        try:
            category_slug = self.category_slug.resolve(context)
            articles = Article.objects.filter(
                category__slug=category_slug,
                published=True
            )
            context[self.var_name] = articles
        except template.VariableDoesNotExist:
            context[self.var_name] = []
        
        return ''

Using Custom Tags and Filters

html
<!-- Using in templates -->
{% load blog_extras %}

<!-- Simple tags -->
<p>Website has {% total_articles %} articles in total</p>
<p>Current time: {% current_time "%Y-%m-d %H:%M:%S" %}</p>

<!-- Inclusion tags -->
{% show_recent_articles 10 %}
{% show_categories %}

<!-- Custom filters -->
{{ article.content|markdown_to_html }}
{{ price|multiply:quantity }}
{{ form.email|add_class:"form-control" }}

<!-- Complex tags -->
{% get_articles_by_category "technology" as tech_articles %}
{% for article in tech_articles %}
    <h3>{{ article.title }}</h3>
{% endfor %}

<!-- URL parameter replacement -->
<a href="?{% url_replace request 'page' page_obj.next_page_number %}">Next Page</a>

Inclusion Tag Templates

html
<!-- blog/templates/blog/tags/recent_articles.html -->
<div class="recent-articles">
    <h4>Latest Articles</h4>
    <ul class="list-unstyled">
        {% for article in articles %}
            <li class="mb-2">
                <a href="{{ article.get_absolute_url }}" class="text-decoration-none">
                    {{ article.title|truncatechars:40 }}
                </a>
                <small class="text-muted d-block">{{ article.created_at|date:"m-d" }}</small>
            </li>
        {% endfor %}
    </ul>
</div>

<!-- blog/templates/blog/tags/category_list.html -->
<div class="category-list">
    <h4>Article Categories</h4>
    <ul class="list-unstyled">
        {% for category in categories %}
            <li>
                <a href="{% url 'blog:category' category.slug %}" 
                   class="{% if request.resolver_match.kwargs.slug == category.slug %}active{% endif %}">
                    {{ category.name }}
                    <span class="badge bg-secondary">{{ category.article_set.count }}</span>
                </a>
            </li>
        {% endfor %}
    </ul>
</div>

Context Processors

Custom Context Processors

python
# blog/context_processors.py
from .models import Category, Article

def blog_context(request):
    """Blog-related global context"""
    return {
        'all_categories': Category.objects.all(),
        'recent_articles': Article.objects.filter(published=True)[:5],
        'popular_articles': Article.objects.filter(published=True).order_by('-views')[:5],
        'site_name': 'My Blog',
        'site_description': 'Sharing technology and life',
    }

def navigation_context(request):
    """Navigation-related context"""
    navigation_items = [
        {'name': 'Home', 'url': '/', 'active': request.path == '/'},
        {'name': 'Articles', 'url': '/blog/', 'active': request.path.startswith('/blog/')},
        {'name': 'About', 'url': '/about/', 'active': request.path == '/about/'},
    ]
    
    return {
        'navigation_items': navigation_items,
        'current_path': request.path,
    }

def user_context(request):
    """User-related context"""
    context = {}
    
    if request.user.is_authenticated:
        context.update({
            'user_article_count': Article.objects.filter(author=request.user).count(),
            'user_notifications': get_user_notifications(request.user),
        })
    
    return context

def get_user_notifications(user):
    """Get user notifications (example)"""
    # Can implement actual notification logic here
    return []

Register Context Processors

python
# 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',
                'blog.context_processors.blog_context',        # Custom context processor
                'blog.context_processors.navigation_context',  # Navigation context
                'blog.context_processors.user_context',        # User context
            ],
        },
    },
]

Template Optimization and Best Practices

Template Caching

python
# settings.py
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.redis.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
    }
}

# Enable template caching
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'loaders': [
                ('django.template.loaders.cached.Loader', [
                    'django.template.loaders.filesystem.Loader',
                    'django.template.loaders.app_directories.Loader',
                ]),
            ],
            'context_processors': [
                # ... context processors
            ],
        },
    },
]

Template Fragment Caching

html
<!-- Cache sidebar -->
{% load cache %}
{% cache 300 sidebar request.user.id %}
    <div class="sidebar">
        {% show_recent_articles %}
        {% show_categories %}
    </div>
{% endcache %}

<!-- Cache article list -->
{% cache 600 article_list page_obj.number %}
    {% for article in page_obj %}
        <!-- Article content -->
    {% endfor %}
{% endcache %}

<!-- Conditional caching -->
{% if user.is_authenticated %}
    {% cache 300 user_sidebar user.id %}
        <!-- User-specific sidebar -->
    {% endcache %}
{% else %}
    {% cache 3600 anonymous_sidebar %}
        <!-- Anonymous user sidebar -->
    {% endcache %}
{% endif %}

Template Organization Best Practices

templates/
├── base.html                    # Base template
├── partials/                    # Reusable template fragments
│   ├── header.html
│   ├── footer.html
│   ├── sidebar.html
│   ├── pagination.html
│   └── messages.html
├── layouts/                     # Different layout templates
│   ├── single_column.html
│   ├── two_column.html
│   └── three_column.html
├── components/                  # Component templates
│   ├── article_card.html
│   ├── comment_form.html
│   └── search_form.html
├── emails/                      # Email templates
│   ├── welcome.html
│   ├── password_reset.html
│   └── notification.html
└── errors/                      # Error page templates
    ├── 404.html
    ├── 500.html
    └── 403.html

Reusable Template Components

html
<!-- components/article_card.html -->
<div class="card mb-3">
    {% if article.featured_image %}
        <img src="{{ article.featured_image.url }}" class="card-img-top" alt="{{ article.title }}">
    {% endif %}
    
    <div class="card-body">
        <h5 class="card-title">
            <a href="{{ article.get_absolute_url }}">{{ article.title }}</a>
        </h5>
        
        <p class="card-text">{{ article.content|striptags|truncatewords:20 }}</p>
        
        <div class="card-meta">
            <small class="text-muted">
                {{ article.author.username }} · {{ article.created_at|date:"Y-m-d" }}
            </small>
            <span class="badge bg-primary">{{ article.category.name }}</span>
        </div>
    </div>
</div>

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

Responsive Templates

html
<!-- Responsive base template -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}{% endblock %}</title>
    
    {% load static %}
    <!-- Bootstrap CSS -->
    <link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
    <!-- Custom CSS -->
    <link href="{% static 'css/style.css' %}" rel="stylesheet">
    
    {% block extra_css %}{% endblock %}
</head>
<body>
    <!-- Mobile navigation -->
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="{% url 'home' %}">My Website</a>
            
            <!-- Mobile toggle button -->
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" 
                    data-bs-target="#navbarNav">
                <span class="navbar-toggler-icon"></span>
            </button>
            
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav ms-auto">
                    {% for item in navigation_items %}
                        <li class="nav-item">
                            <a class="nav-link {% if item.active %}active{% endif %}" 
                               href="{{ item.url }}">{{ item.name }}</a>
                        </li>
                    {% endfor %}
                </ul>
            </div>
        </div>
    </nav>

    <!-- Main content -->
    <main class="container-fluid">
        <div class="row">
            <!-- Content area -->
            <div class="col-lg-8 col-md-12">
                {% block content %}{% endblock %}
            </div>
            
            <!-- Sidebar - hidden on small screens -->
            <div class="col-lg-4 d-none d-lg-block">
                {% block sidebar %}{% endblock %}
            </div>
        </div>
    </main>

    <!-- Bootstrap JS -->
    <script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
    {% block extra_js %}{% endblock %}
</body>
</html>

Chapter Summary

This chapter detailed Django's template system:

Key Points:

  • Template Syntax: Using variables, tags, filters
  • Template Inheritance: Code reuse through extends and block
  • Custom Tags: Creating your own template tags and filters
  • Context Processors: Providing global variables for all templates
  • Template Optimization: Caching and performance optimization techniques

Important Concepts:

  • DRY Principle: Avoid code duplication through inheritance and inclusion
  • Separation of Concerns: Templates only handle presentation logic
  • Security: Automatic HTML escaping and safe marking
  • Maintainability: Good template organization structure

Best Practices:

  • Use template inheritance to establish consistent page structure
  • Create reusable template components
  • Use caching appropriately to improve performance
  • Keep template logic simple
  • Use semantic HTML structure

In the next chapter, we will learn about Django's static file management to understand how to handle static resources like CSS, JavaScript, and images.

Further Reading

Content is for learning and research only.