Skip to content

Django模板系统

本章将详细介绍Django的模板系统,包括模板语法、模板继承、过滤器、标签、上下文处理器等核心概念,帮助你创建动态和可维护的HTML页面。

模板系统概述

什么是Django模板

Django模板是一个文本文件,可以生成任何基于文本的格式(HTML、XML、CSV等)。模板包含变量和标签,在渲染时会被替换为实际的值。

html
<!-- 基本模板示例 -->
<!DOCTYPE html>
<html>
<head>
    <title>{{ page_title }}</title>
</head>
<body>
    <h1>欢迎,{{ 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>暂无文章</p>
    {% endif %}
</body>
</html>

模板配置

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',
                'myapp.context_processors.custom_context',  # 自定义上下文处理器
            ],
        },
    },
]

模板目录结构

myproject/
├── templates/                    # 项目级模板
│   ├── base.html                # 基础模板
│   ├── 404.html                 # 错误页面
│   ├── 500.html
│   └── registration/            # 认证相关模板
│       ├── login.html
│       └── signup.html
├── blog/
│   └── templates/
│       └── blog/                # 应用级模板
│           ├── article_list.html
│           ├── article_detail.html
│           └── category.html
└── accounts/
    └── templates/
        └── accounts/
            ├── profile.html
            └── settings.html

模板语法基础

变量

html
<!-- 基本变量 -->
{{ variable }}
{{ user.username }}
{{ article.title }}

<!-- 字典访问 -->
{{ data.key }}
{{ user.profile.bio }}

<!-- 列表访问 -->
{{ items.0 }}
{{ articles.first.title }}

<!-- 方法调用(无参数) -->
{{ article.get_absolute_url }}
{{ user.get_full_name }}

<!-- 默认值 -->
{{ variable|default:"默认值" }}
{{ user.email|default:"未设置邮箱" }}

标签

html
<!-- 条件判断 -->
{% if user.is_authenticated %}
    <p>欢迎,{{ user.username }}!</p>
{% elif user.is_anonymous %}
    <p>请先登录</p>
{% else %}
    <p>未知用户状态</p>
{% endif %}

<!-- 循环 -->
{% for article in articles %}
    <div class="article">
        <h3>{{ article.title }}</h3>
        <p>{{ article.content|truncatewords:30 }}</p>
        
        <!-- 循环变量 -->
        <small>第 {{ forloop.counter }} 篇文章</small>
        
        {% if forloop.first %}
            <span class="badge">最新</span>
        {% endif %}
        
        {% if forloop.last %}
            <hr>
        {% endif %}
    </div>
{% empty %}
    <p>暂无文章</p>
{% endfor %}

<!-- 循环变量详解 -->
{{ forloop.counter }}      <!-- 当前循环次数(从1开始) -->
{{ forloop.counter0 }}     <!-- 当前循环次数(从0开始) -->
{{ forloop.revcounter }}   <!-- 剩余循环次数(到1结束) -->
{{ forloop.revcounter0 }}  <!-- 剩余循环次数(到0结束) -->
{{ forloop.first }}        <!-- 是否是第一次循环 -->
{{ forloop.last }}         <!-- 是否是最后一次循环 -->
{{ forloop.parentloop }}   <!-- 父循环对象 -->

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

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

<!-- 包含其他模板 -->
{% include 'partials/header.html' %}
{% include 'partials/sidebar.html' with articles=latest_articles %}

<!-- 注释 -->
{# 这是单行注释 #}
{% comment %}
这是多行注释
可以包含多行内容
{% endcomment %}

过滤器

html
<!-- 字符串过滤器 -->
{{ article.title|upper }}                    <!-- 转大写 -->
{{ article.title|lower }}                    <!-- 转小写 -->
{{ article.title|title }}                    <!-- 标题格式 -->
{{ article.title|capfirst }}                 <!-- 首字母大写 -->
{{ article.content|truncatewords:30 }}       <!-- 截取30个单词 -->
{{ article.content|truncatechars:100 }}      <!-- 截取100个字符 -->
{{ article.content|linebreaks }}             <!-- 转换换行为<p>和<br> -->
{{ article.content|striptags }}              <!-- 移除HTML标签 -->
{{ article.content|escape }}                 <!-- HTML转义 -->
{{ article.content|safe }}                   <!-- 标记为安全,不转义 -->

<!-- 数字过滤器 -->
{{ price|floatformat:2 }}                    <!-- 保留2位小数 -->
{{ number|add:10 }}                          <!-- 加10 -->
{{ items|length }}                           <!-- 获取长度 -->

<!-- 日期过滤器 -->
{{ article.created_at|date:"Y-m-d H:i:s" }} <!-- 格式化日期 -->
{{ article.created_at|timesince }}          <!-- 距现在多长时间 -->
{{ article.created_at|timeuntil }}          <!-- 距未来多长时间 -->

<!-- 列表过滤器 -->
{{ articles|first }}                         <!-- 第一个元素 -->
{{ articles|last }}                          <!-- 最后一个元素 -->
{{ articles|slice:":5" }}                   <!-- 前5个元素 -->
{{ articles|random }}                        <!-- 随机一个元素 -->
{{ tags|join:", " }}                         <!-- 用逗号连接 -->

<!-- 链式过滤器 -->
{{ article.title|lower|capfirst }}
{{ article.content|striptags|truncatewords:20 }}
{{ user.email|default:"未设置"|upper }}

模板继承

基础模板

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 %}我的网站{% 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>
    <!-- 导航栏 -->
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="{% url 'home' %}">我的网站</a>
            
            <div class="navbar-nav">
                <a class="nav-link" href="{% url 'blog:article_list' %}">文章</a>
                <a class="nav-link" href="{% url 'about' %}">关于</a>
                
                {% if user.is_authenticated %}
                    <a class="nav-link" href="{% url 'profile' %}">{{ user.username }}</a>
                    <a class="nav-link" href="{% url 'logout' %}">退出</a>
                {% else %}
                    <a class="nav-link" href="{% url 'login' %}">登录</a>
                    <a class="nav-link" href="{% url 'signup' %}">注册</a>
                {% endif %}
            </div>
        </div>
    </nav>

    <!-- 消息提示 -->
    {% 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 %}

    <!-- 面包屑导航 -->
    {% block breadcrumb %}{% endblock %}

    <!-- 主要内容 -->
    <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 class="bg-dark text-light py-4 mt-5">
        <div class="container">
            <div class="row">
                <div class="col-md-6">
                    <p>&copy; 2023 我的网站. 保留所有权利.</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>

子模板

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

{% block title %}文章列表 - {{ 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' %}">首页</a></li>
        <li class="breadcrumb-item active">文章列表</li>
    </ol>
</nav>
{% endblock %}

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

<!-- 搜索和过滤 -->
<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="搜索文章..." value="{{ search_query }}">
            <button type="submit" class="btn btn-outline-secondary">搜索</button>
        </form>
    </div>
    <div class="col-md-4">
        <select class="form-select" onchange="filterByCategory(this.value)">
            <option value="">所有分类</option>
            {% for category in categories %}
                <option value="{{ category.slug }}" 
                        {% if category.slug == current_category %}selected{% endif %}>
                    {{ category.name }}
                </option>
            {% endfor %}
        </select>
    </div>
</div>

<!-- 文章列表 -->
{% 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>暂无文章</h3>
        <p class="text-muted">还没有发布任何文章</p>
        {% if user.is_authenticated %}
            <a href="{% url 'blog:create_article' %}" class="btn btn-primary">写第一篇文章</a>
        {% endif %}
    </div>
{% endfor %}

<!-- 分页 -->
{% if page_obj.has_other_pages %}
    <nav aria-label="文章分页">
        <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 %}">首页</a>
                </li>
                <li class="page-item">
                    <a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if search_query %}&search={{ search_query }}{% endif %}">上一页</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 %}">下一页</a>
                </li>
                <li class="page-item">
                    <a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if search_query %}&search={{ search_query }}{% endif %}">末页</a>
                </li>
            {% endif %}
        </ul>
    </nav>
{% endif %}
{% endblock %}

{% block sidebar %}
    <!-- 最新文章 -->
    <div class="card mb-4">
        <div class="card-header">
            <h5>最新文章</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>
    
    <!-- 热门标签 -->
    <div class="card">
        <div class="card-header">
            <h5>热门标签</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 %}

自定义模板标签和过滤器

创建模板标签库

python
# blog/templatetags/__init__.py
# 空文件,使目录成为Python包

# 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()

# 简单标签
@register.simple_tag
def total_articles():
    """返回文章总数"""
    return Article.objects.filter(published=True).count()

@register.simple_tag
def get_categories():
    """获取所有分类"""
    return Category.objects.all()

@register.simple_tag(takes_context=True)
def current_time(context, format_string):
    """获取当前时间"""
    from datetime import datetime
    return datetime.now().strftime(format_string)

# 包含标签
@register.inclusion_tag('blog/tags/recent_articles.html')
def show_recent_articles(count=5):
    """显示最新文章"""
    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):
    """显示分类列表"""
    categories = Category.objects.all()
    return {
        'categories': categories,
        'request': context['request'],
    }

# 赋值标签
@register.simple_tag
def get_popular_articles(count=5):
    """获取热门文章"""
    return Article.objects.filter(published=True).order_by('-views')[:count]

# 自定义过滤器
@register.filter
def markdown_to_html(text):
    """将Markdown转换为HTML"""
    return mark_safe(markdown.markdown(text))

@register.filter
def multiply(value, arg):
    """乘法过滤器"""
    try:
        return int(value) * int(arg)
    except (ValueError, TypeError):
        return 0

@register.filter
def get_item(dictionary, key):
    """从字典获取值"""
    return dictionary.get(key)

@register.filter
def add_class(field, css_class):
    """为表单字段添加CSS类"""
    return field.as_widget(attrs={'class': css_class})

# 条件标签
@register.simple_tag
def url_replace(request, field, value):
    """替换URL参数"""
    dict_ = request.GET.copy()
    dict_[field] = value
    return dict_.urlencode()

# 复杂的自定义标签
@register.tag
def get_articles_by_category(parser, token):
    """根据分类获取文章"""
    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 ''

使用自定义标签和过滤器

html
<!-- 在模板中使用 -->
{% load blog_extras %}

<!-- 简单标签 -->
<p>网站共有 {% total_articles %} 篇文章</p>
<p>当前时间:{% current_time "%Y-%m-%d %H:%M:%S" %}</p>

<!-- 包含标签 -->
{% show_recent_articles 10 %}
{% show_categories %}

<!-- 自定义过滤器 -->
{{ article.content|markdown_to_html }}
{{ price|multiply:quantity }}
{{ form.email|add_class:"form-control" }}

<!-- 复杂标签 -->
{% get_articles_by_category "technology" as tech_articles %}
{% for article in tech_articles %}
    <h3>{{ article.title }}</h3>
{% endfor %}

<!-- URL参数替换 -->
<a href="?{% url_replace request 'page' page_obj.next_page_number %}">下一页</a>

包含标签的模板

html
<!-- blog/templates/blog/tags/recent_articles.html -->
<div class="recent-articles">
    <h4>最新文章</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>文章分类</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>

上下文处理器

自定义上下文处理器

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

def blog_context(request):
    """博客相关的全局上下文"""
    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': '我的博客',
        'site_description': '分享技术和生活',
    }

def navigation_context(request):
    """导航相关的上下文"""
    navigation_items = [
        {'name': '首页', 'url': '/', 'active': request.path == '/'},
        {'name': '文章', 'url': '/blog/', 'active': request.path.startswith('/blog/')},
        {'name': '关于', 'url': '/about/', 'active': request.path == '/about/'},
    ]
    
    return {
        'navigation_items': navigation_items,
        'current_path': request.path,
    }

def user_context(request):
    """用户相关的上下文"""
    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):
    """获取用户通知(示例)"""
    # 这里可以实现实际的通知逻辑
    return []

注册上下文处理器

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',        # 自定义上下文处理器
                'blog.context_processors.navigation_context',  # 导航上下文
                'blog.context_processors.user_context',        # 用户上下文
            ],
        },
    },
]

模板优化和最佳实践

模板缓存

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

# 启用模板缓存
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': [
                # ... 上下文处理器
            ],
        },
    },
]

模板片段缓存

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

<!-- 缓存文章列表 -->
{% cache 600 article_list page_obj.number %}
    {% for article in page_obj %}
        <!-- 文章内容 -->
    {% endfor %}
{% endcache %}

<!-- 条件缓存 -->
{% if user.is_authenticated %}
    {% cache 300 user_sidebar user.id %}
        <!-- 用户专用侧边栏 -->
    {% endcache %}
{% else %}
    {% cache 3600 anonymous_sidebar %}
        <!-- 匿名用户侧边栏 -->
    {% endcache %}
{% endif %}

模板组织最佳实践

templates/
├── base.html                    # 基础模板
├── partials/                    # 可重用的模板片段
│   ├── header.html
│   ├── footer.html
│   ├── sidebar.html
│   ├── pagination.html
│   └── messages.html
├── layouts/                     # 不同的布局模板
│   ├── single_column.html
│   ├── two_column.html
│   └── three_column.html
├── components/                  # 组件模板
│   ├── article_card.html
│   ├── comment_form.html
│   └── search_form.html
├── emails/                      # 邮件模板
│   ├── welcome.html
│   ├── password_reset.html
│   └── notification.html
└── errors/                      # 错误页面模板
    ├── 404.html
    ├── 500.html
    └── 403.html

可重用的模板组件

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>

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

响应式模板

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 %}{% endblock %}</title>
    
    {% load static %}
    <!-- Bootstrap CSS -->
    <link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
    <!-- 自定义CSS -->
    <link href="{% static 'css/style.css' %}" rel="stylesheet">
    
    {% block extra_css %}{% endblock %}
</head>
<body>
    <!-- 移动端导航 -->
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="{% url 'home' %}">我的网站</a>
            
            <!-- 移动端切换按钮 -->
            <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 class="container-fluid">
        <div class="row">
            <!-- 内容区域 -->
            <div class="col-lg-8 col-md-12">
                {% block content %}{% endblock %}
            </div>
            
            <!-- 侧边栏 - 在小屏幕上隐藏 -->
            <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>

本章小结

本章详细介绍了Django的模板系统:

关键要点:

  • 模板语法:变量、标签、过滤器的使用
  • 模板继承:通过extends和block实现代码复用
  • 自定义标签:创建自己的模板标签和过滤器
  • 上下文处理器:为所有模板提供全局变量
  • 模板优化:缓存和性能优化技巧

重要概念:

  • DRY原则:通过继承和包含避免重复代码
  • 关注点分离:模板只负责表现层逻辑
  • 安全性:自动HTML转义和安全标记
  • 可维护性:良好的模板组织结构

最佳实践:

  • 使用模板继承建立一致的页面结构
  • 创建可重用的模板组件
  • 合理使用缓存提高性能
  • 保持模板逻辑简单
  • 使用语义化的HTML结构

在下一章中,我们将学习Django的静态文件管理,了解如何处理CSS、JavaScript和图片等静态资源。

延伸阅读

本站内容仅供学习和研究使用。