第四章:Django 模板系统(Templates) 核心摘要:Django 模板系统是 MTV 架构中实现“展示层”的关键组件,通过变量、标签、过滤器与继承机制,实现逻辑与视图的彻底解耦,兼顾安全性、可维护性与开发效率。本章系统讲解模板语法、内置功能、继承体系、自定义扩展及生产级最佳实践。 4.1 模板的作用与优势 在传统 Web 开发中,HTML 与业务逻辑混杂导致代码臃肿、协作低效、安全风险高。Django 采用 模型-模板-视图(MTV) 设计模式,将数据层(Model)、展示层(Template)与控制层(View)严格分离。模板层专司用户界面渲染——接收视图传入的数据上下文(Context),生成最终的 HTML 响应,不执行任何数据库操作或复杂计算。
核心摘要:Django 模板系统是 MTV 架构中实现“展示层”的关键组件,通过变量、标签、过滤器与继承机制,实现逻辑与视图的彻底解耦,兼顾安全性、可维护性与开发效率。本章系统讲解模板语法、内置功能、继承体系、自定义扩展及生产级最佳实践。
在传统 Web 开发中,HTML 与业务逻辑混杂导致代码臃肿、协作低效、安全风险高。Django 采用 模型-模板-视图(MTV) 设计模式,将数据层(Model)、展示层(Template)与控制层(View)严格分离。模板层专司用户界面渲染——接收视图传入的数据上下文(Context),生成最终的 HTML 响应,不执行任何数据库操作或复杂计算。
其核心优势体现在以下五方面:
关注点分离(Separation of Concerns)
模板仅处理展示逻辑,Python 代码专注业务规则。前端工程师可独立修改 HTML/CSS,后端开发者专注数据处理,大幅提升跨职能协作效率。
高度可复用(Code Reusability)
依托模板继承({% extends %})与片段包含({% include %}),公共结构(如页头、导航、页脚)只需编写一次,全站复用。
默认安全防护(Security by Default)
所有变量输出自动进行 HTML 转义,有效防御跨站脚本攻击(XSS)。仅当明确信任内容来源时,才需使用 |safe 过滤器解除转义。
卓越可读性与可维护性(Readability & Maintainability)
模板语法简洁直观,非程序员亦可理解基础结构;逻辑清晰、层级分明,显著降低长期维护成本。
灵活可扩展(Flexibility)
支持自定义模板标签与过滤器,可无缝集成复杂业务逻辑(如权限控制、时间计算、富文本处理),满足企业级应用需求。
Django 模板语言(DTL)是一套专为展示层设计的轻量级、安全的模板引擎,并非 Python 代码,拥有独立语法规则,包含四大核心元素:
| 元素 | 语法格式 | 作用说明 |
|---|---|---|
| 变量 | {{ variable }} |
渲染视图传入的数据,自动 HTML 转义 |
| 标签 | {% tag %} |
控制模板逻辑(条件、循环、继承、URL 解析等) |
| 过滤器 | {{ variable|filter }} |
修改变量显示格式(大小写、截断、日期格式化等) |
| 注释 | {# comment #} |
模板内说明性文字,不输出到 HTML,不被解析 |
视图层(views.py)
from django.shortcuts import render def user_list_view(request): context = { 'page_title': '用户管理', 'users': [ {'name': 'Alice', 'role': 'admin', 'joined': '2023-01-15'}, {'name': 'Bob', 'role': 'editor', 'joined': '2023-03-22'}, {'name': 'Charlie', 'role': 'viewer', 'joined': '2023-05-10'} ] } return render(request, 'users/list.html', context)
模板层(templates/users/list.html)
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>{{ page_title }}</title> </head> <body> <header> <h1>{{ page_title }}</h1> </header> <main> <p>共 {{ users|length }} 位用户:</p> <ul> {% for user in users %} <li> <strong>{{ user.name|title }}</strong> ({{ user.role|upper }}|加入于 {{ user.joined|date:"Y年m月d日" }}) </li> {% empty %} <li>暂无用户数据</li> {% endfor %} </ul> </main> </body> </html>
✅ 关键要点:
{{ page_title }}直接渲染字符串;|title和|upper为链式过滤器,提升可读性;{% for %}内置{% empty %}处理空列表场景;{{ user.joined|date:"Y年m月d日" }}实现本地化日期格式。
DTL 标签是模板逻辑控制的核心,分为三类:控制流标签、变量操作标签、功能型标签。
{% if %} 系列{% if user.is_authenticated %} <p>欢迎回来,{{ user.username }}!</p> {% if user.is_staff %} <a href="{% url 'admin:index' %}">进入后台</a> {% endif %} {% else %} <a href="{% url 'login' %}">请登录</a> {% endif %}
{% for %} 与循环变量{% for post in latest_posts %} <article class="{% if forloop.first %}featured{% endif %}"> <h2>{{ post.title }}</h2> <time>{{ post.pub_date|date:"M d, Y" }}</time> <p>{{ post.excerpt|truncatewords:30 }}</p> {% if not forloop.last %}<hr>{% endif %} </article> {% empty %} <p>暂无文章发布。</p> {% endfor %}
✅ 循环变量说明:
forloop.first/last、counter(1起)、counter0(0起)、revcounter(倒序)、parentloop(嵌套循环)。
| 标签 | 用途 | 示例 |
|---|---|---|
{% with %} |
创建局部作用域,简化复杂表达式 | {% with total=order.items.count %}共{{ total }}件{% endwith %} |
{% csrf_token %} |
生成防 CSRF 令牌(POST 表单必需) | <form method="post">{% csrf_token %}... |
{% url 'name' arg1 arg2 %} |
反向解析 URL,避免硬编码 | <a href="{% url 'article_detail' pk=article.id %}">{{ article.title }}</a> |
{% static 'path' %} |
生成静态资源 URL(CSS/JS/图片) | <img src="{% static 'images/logo.svg' %}" alt="Logo"> |
{% include 'partial.html' %} |
复用模板片段(推荐用于组件化) | {% include 'components/navbar.html' %} |
⚠️ 重要配置:使用
{% static %}前,模板顶部需声明{% load static %};项目需配置STATIC_URL与STATICFILES_DIRS。
{% extends %} 与 {% block %}模板继承是构建可维护网站的基石。通过定义基础模板(base.html),各子模板仅覆盖必要区块,确保全局风格统一。
基础模板(templates/base.html)
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>{% block title %}MySite{% endblock %} - Django 应用</title> <link rel="stylesheet" href="{% static 'css/base.css' %}"> {% block extra_css %}{% endblock %} </head> <body> <header>{% block header %}<h1>MySite</h1>{% endblock %}</header> <main class="container">{% block content %}{% endblock %}</main> <footer>{% block footer %}<p>© 2024 MySite</p>{% endblock %}</footer> <script src="{% static 'js/base.js' %}"></script> {% block extra_js %}{% endblock %} </body> </html>
子模板(templates/articles/list.html)
{% extends 'base.html' %} {% load static %} {% block title %}文章列表 - {{ block.super }}{% endblock %} {% block extra_css %} <link rel="stylesheet" href="{% static 'css/articles.css' %}"> {% endblock %} {% block header %} <nav> <a href="{% url 'home' %}">首页</a> <a href="{% url 'articles:list' %}">所有文章</a> </nav> <h1>最新文章</h1> {% endblock %} {% block content %} <section class="articles-grid"> {% for article in articles %} <article> <h2><a href="{% url 'articles:detail' article.pk %}">{{ article.title|truncatechars:50 }}</a></h2> <p class="meta">{{ article.author }} • {{ article.pub_date|date:"Y-m-d" }}</p> <p>{{ article.summary|truncatewords:20 }}</p> </article> {% endfor %} </section> {% endblock %} {% block extra_js %} <script src="{% static 'js/articles.js' %}"></script> {% endblock %}
✅ 继承机制要点:
{% extends %}必须是模板第一行;{% block.super %}复用父模板内容,实现增量覆盖;- 未被子模板覆盖的
block(如footer)自动继承父模板内容。
过滤器是模板中数据格式化的“管道”,支持链式调用,语法为 {{ variable|filter1|filter2:param }}。
| 类别 | 过滤器 | 说明 | 示例 |
|---|---|---|---|
| 字符串处理 | lower, upper, title, capfirst |
大小写转换 | `{{ "hello" |
truncatechars:10, truncatewords:5 |
截断内容 | `{{ "Hello world!" | |
urlencode, urlize, striptags |
URL/HTML 安全处理 | `{{ "
Hello " |
|
| 数字处理 | intcomma, floatformat:2, add:"5" |
格式化与计算 | `{{ 1000000 |
| 日期时间 | date:"Y-m-d", time:"H:i", timesince |
时间格式化与相对时间 | `{{ post.pub_date |
| 列表处理 | first, last, join:", ", random |
列表操作 | `{{ items |
| HTML 安全 | safe, escape, linebreaks |
渲染控制 | `{{ raw_html |
| 默认值 | default:"N/A", default_if_none:"—" |
空值兜底 | `{{ user.nickname |
<!-- 安全渲染文章摘要:去HTML标签 → 截断200字符 → 换行转<br> --> {{ article.content|striptags|truncatechars:200|linebreaks }} <!-- 显示作者头像:空则用默认图,URL 编码防止特殊字符问题 --> <img src="{% static 'images/avatars/'|add:article.author.avatar|urlencode %}" alt="{{ article.author.name }}">
当内置功能无法满足业务需求时,可通过 Python 编写自定义逻辑。
templatetags/ 子目录(含 __init__.py);blog_tags.py);@register.filter 或 @register.simple_tag 注册;{% load blog_tags %} 加载使用。myapp/templatetags/date_filters.py
from django import template from django.utils import timezone from django.utils.translation import gettext as _ register = template.Library() @register.filter def cn_date(value, format_string="Y年m月d日"): """中文日期格式化过滤器""" if not value: return "" # 转换为本地时区 local_time = timezone.template_localtime(value) return local_time.strftime(format_string).replace("Y", str(local_time.year))
模板中使用
{% load date_filters %} <p>发布时间:{{ article.pub_date|cn_date }}</p> <!-- 输出:2024年06月15日 -->
myapp/templatetags/auth_tags.py
from django import template from django.contrib.auth.decorators import login_required register = template.Library() @register.simple_tag(takes_context=True) def user_can_edit(context, obj): """判断当前用户是否有编辑对象权限""" request = context['request'] return request.user.has_perm(f'{obj._meta.app_label}.change_{obj._meta.model_name}') and request.user.is_authenticated
模板中使用
{% load auth_tags %} {% user_can_edit article as can_edit %} {% if can_edit %} <a href="{% url 'articles:edit' article.pk %}">编辑</a> {% endif %}
✅ 最佳实践:
- 过滤器用于数据转换(无副作用);
- 简单标签用于生成内容(如当前时间、权限检查);
- 包含标签(
@register.inclusion_tag)用于渲染子模板(如分页组件)。
模板渲染由 render() 函数驱动,其底层依赖 Django 的模板引擎配置与查找策略。
render() 函数标准用法from django.shortcuts import render def product_detail(request, pk): product = get_object_or_404(Product, pk=pk) context = { 'product': product, 'related_products': Product.objects.filter(category=product.category)[:4], 'is_in_wishlist': request.user.is_authenticated and product in request.user.wishlist.all() } return render(request, 'products/detail.html', context)
settings.py)import os from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent.parent TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ BASE_DIR / 'templates', # 项目级模板(全局复用) BASE_DIR / 'apps/core/templates', # 核心应用模板 ], 'APP_DIRS': True, # 自动扫描各 app/templates/ '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', 'django.template.context_processors.static', # 自定义处理器(如站点信息) 'myproject.context_processors.site_info', ], }, }, ]
DIRS 中从左到右依次查找;templates/ 目录(APP_DIRS=True);| 原则 | 推荐做法 | 反例 |
|---|---|---|
| 逻辑分离 | 视图处理数据、权限、业务规则;模板仅负责渲染 | 在模板中调用 user.profile.get_full_name() 等复杂方法 |
| 继承优先 | 全站统一 base.html,按模块拆分子模板(articles/base.html, users/base.html) |
每个页面独立编写完整 HTML 结构 |
| URL 与静态资源 | 100% 使用 {% url %} 和 {% static %} |
<a href="/articles/123/">, <img src="/static/logo.png"> |
| XSS 防护 | 默认信任转义,仅对可信富文本使用 ` | safe` |
| 目录结构 | 按应用组织:templates/<app_name>/<feature>/xxx.html |
所有模板平铺在 templates/ 下 |
| 性能优化 | 避免模板中数据库查询(N+1 问题);预取关联数据(select_related, prefetch_related) |
{% for comment in post.comment_set.all %}(未预取) |
| 可测试性 | 为关键模板编写单元测试,验证上下文与渲染结果 | 仅依赖手动浏览器测试 |
Django 模板系统远不止是“填空式 HTML 生成器”。它是一套安全、分层、可扩展的展示架构:
base.html + block + include 构建可复用的 UI 组件库;掌握本章内容,意味着您已具备构建专业级 Django Web 应用界面的完整能力。后续章节将深入模型关系、表单处理、用户认证与 REST API 开发,逐步构建功能完备的企业级应用。
关键词:Django 模板、MTV 架构、模板继承、Django 模板标签、Django 过滤器、
{% url %}、{% static %}、自定义模板标签、XSS 防护、模板最佳实践