第四章:模板(Templates) 4.1 Django 模板语言(DTL)基础 Django 模板语言(Django Template Language,DTL)是 Django 框架的核心表现层技术,专为安全、高效地将动态数据注入 HTML、XML 或纯文本文档而设计。其核心理念在于在强大功能与开发简洁性之间取得平衡,支持前后端职责清晰分离,显著提升团队协作效率与代码可维护性。 4.1.1 模板的概念与作用 传统 Web 开发中,HTML 结构与动态逻辑常混杂于同一文件,导致可读性差、复用率低、安全风险高。
Django 模板语言(Django Template Language,DTL)是 Django 框架的核心表现层技术,专为安全、高效地将动态数据注入 HTML、XML 或纯文本文档而设计。其核心理念在于在强大功能与开发简洁性之间取得平衡,支持前后端职责清晰分离,显著提升团队协作效率与代码可维护性。
传统 Web 开发中,HTML 结构与动态逻辑常混杂于同一文件,导致可读性差、复用率低、安全风险高。Django 模板机制通过严格分离表现层(HTML/CSS/JS)与业务逻辑层(Python),践行“关注点分离”(Separation of Concerns)原则,成为现代 Web 开发的最佳实践之一。
模板本质是纯文本文件,可为 HTML、XML、CSV、纯文本甚至电子邮件模板。其内容由两部分构成:
| 价值维度 | 具体体现 |
|---|---|
| 前后端解耦 | 前端专注 UI/UX 实现与样式优化;后端专注数据建模与业务逻辑;双方通过明确定义的上下文(Context)契约协作 |
| 高复用性 | 单个模板可被多个视图复用;支持模板继承({% extends %})、模板包含({% include %})与宏定义(自定义标签/过滤器),避免重复编码 |
| 结构清晰性 | 相比 Python 字符串拼接 HTML,DTL 语法天然贴近 HTML 语义,大幅提升模板可读性与协作效率 |
| 内置安全性 | 默认启用 HTML 自动转义,有效防御 XSS(跨站脚本)攻击;关键操作(如表单提交)强制 CSRF 防护 |
用户请求进入 Django 后,经历以下标准化渲染链路:
图 4.1.1 模板渲染流程
视图函数负责数据组装与业务判断,模板引擎专注视图渲染——二者职责分明,共同保障系统健壮性与可扩展性。
DTL 由四大原子组件构成,共同支撑动态模板能力:
| 组件 | 语法形式 | 用途 | 示例 |
|---|---|---|---|
| 变量(Variables) | {{ variable }} |
输出上下文数据 | {{ user.name }}, `{{ article.pub_date |
| 标签(Tags) | {% tag %}...{% endtag %} |
控制逻辑(条件、循环、URL、安全等) | {% if user.is_authenticated %}, {% for post in posts %} |
| 过滤器(Filters) | {{ variable|filter }} |
格式化变量输出 | {{ title|upper }}, {{ content|truncatewords:30 }} |
| 注释(Comments) | {# comment #} |
模板内说明,不输出至前端 | {# 用户头像区域,仅登录用户可见 #} |
变量是 DTL 最基础的数据输出单元,使用双大括号 {{ }} 包裹,语法简洁且语义明确。
变量查找遵循三级优先级规则:
{{ user.profile.bio }} → user['profile']['bio']{{ article.author.get_full_name }} → 调用 get_full_name() 方法{{ skills.0 }} → 等价于 skills[0]✅ 最佳实践:避免深层嵌套(如
{{ a.b.c.d.e }}),应在视图中预处理数据,提升模板可读性与性能。
示例:上下文数据与模板输出
视图传递上下文:
def profile_view(request): context = { 'name': '张三', 'age': 30, 'city': '北京', 'skills': ['Python', 'Django', 'HTML', 'CSS'], 'address': {'street': '科技路', 'zip_code': '100000'} } return render(request, 'profile.html', context)
对应模板 profile.html:
<h1>{{ name }} 的个人资料</h1> <p>年龄:{{ age }} 岁</p> <p>所在地:{{ city }}</p> <p>技能:{{ skills|join:", " }}</p> <p>详细地址:{{ address.street }}(邮编:{{ address.zip_code }})</p>
渲染结果:
<h1>张三 的个人资料</h1> <p>年龄:30 岁</p> <p>所在地:北京</p> <p>技能:Python, Django, HTML, CSS</p> <p>详细地址:科技路(邮编:100000)</p>
标签承载控制流与框架集成能力,是 DTL 的“逻辑引擎”。
{% if %}、{% elif %}、{% else %}支持完整布尔表达式,兼容 and、or、not、in、==、!= 等运算符。
{% 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 %}、{% empty %}支持可迭代对象(QuerySet、list、dict),内置 forloop 变量提供循环元信息。
<ul class="article-list"> {% for article in articles %} <li class="{% if forloop.first %}first{% elif forloop.last %}last{% endif %}"> <h3><a href="{% url 'article_detail' article.id %}">{{ article.title|title }}</a></h3> <time datetime="{{ article.pub_date|date:'c' }}">发布于 {{ article.pub_date|date:"Y年m月d日" }}</time> <p>{{ article.excerpt|default:article.content|truncatewords:25 }}</p> </li> {% empty %} <li class="no-content">暂无文章,敬请期待。</li> {% endfor %} </ul>
| 标签 | 用途 | 关键说明 |
|---|---|---|
{% csrf_token %} |
表单防伪造 | 所有 POST 表单必需,Django 中间件自动校验 |
{% url 'name' arg1 arg2 %} |
URL 反向解析 | 解耦硬编码路径,支持命名空间与参数化路由 |
{% static 'css/main.css' %} |
静态资源定位 | 依赖 STATIC_URL 配置,支持 CDN 扩展 |
{% load humanize %} |
加载扩展库 | 启用 intcomma、naturaltime 等人性化过滤器 |
⚠️ 安全警示:
{% url %}和{% static %}标签自动进行 URL 编码,避免 XSS 风险;手动拼接 URL(如href="/articles/{{ id }}/")属反模式。
过滤器是 DTL 的“数据加工流水线”,通过管道符 | 链式调用,实现轻量级格式化。
| 类别 | 过滤器 | 示例 | 输出 |
|---|---|---|---|
| 文本处理 | lower / upper / title / capfirst |
`{{ "hello WORLD" | title }}` |
| 日期时间 | date:"Y-m-d" / time:"H:i" / timesince |
`{{ post.pub_date | timesince }}` |
| 截断与长度 | truncatechars:50 / truncatewords:10 / length |
`{{ text | truncatewords:8 }}` |
| HTML 安全 | safe / escape / linebreaks |
`{{ html_content | safe }}` |
高级用法示例:
<!-- 链式过滤:先截断再转义,再添加省略号 --> <p>{{ article.content|truncatewords:20|escape }}…</p> <!-- 带参数的过滤器 --> <p>阅读时长:{{ article.content|wordcount|add:"0" }} 字,约 {{ article.content|wordcount|divisibleby:200 }} 分钟</p> <!-- 人性化时间显示(需 {% load humanize %}) --> <time>{{ article.pub_date|naturaltime }}</time> <!-- "2 分钟前" -->
✅ 性能提示:过滤器在模板渲染时实时执行,避免在模板中调用耗时方法(如数据库查询);复杂计算应在视图或模型中完成。
DTL 注释 {# ... #} 仅存在于模板源码中,完全不会输出至 HTML,是提升团队协作效率的关键工具。
{# 用户资料卡片:包含头像、昵称、认证状态 #} <div class="user-card"> {# 头像区域 —— 优先展示 Gravatar,fallback 为默认图标 #} <img src="{{ user.email|gravatar:80 }}" alt="{{ user.get_full_name }}" width="80" height="80"> <h2>{{ user.get_full_name|default:"匿名用户" }}</h2> {# 认证徽章:仅对已验证邮箱用户显示 #} {% if user.profile.email_verified %} <span class="badge verified">✓ 已验证</span> {% endif %} </div>
以下通过一个完整可运行的博客列表页面,串联 DTL 核心语法,强化工程化认知。
blog/models.py)from django.db import models from django.utils import timezone class Article(models.Model): title = models.CharField('标题', max_length=200) slug = models.SlugField('URL别名', unique=True, blank=True) content = models.TextField('正文') excerpt = models.CharField('摘要', max_length=300, blank=True) pub_date = models.DateTimeField('发布日期', default=timezone.now) is_published = models.BooleanField('是否发布', default=True) class Meta: verbose_name = '文章' verbose_name_plural = '文章' ordering = ['-pub_date'] def __str__(self): return self.title
blog/views.py)from django.shortcuts import render from .models import Article def article_list(request): articles = Article.objects.filter( is_published=True ).select_related('author').only( 'title', 'slug', 'excerpt', 'pub_date' ).order_by('-pub_date') return render(request, 'blog/article_list.html', { 'articles': articles, 'page_title': '最新文章' })
templates/blog/article_list.html)<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>{{ page_title }} - {{ SITE_NAME|default:"我的博客" }}</title> <link rel="stylesheet" href="{% static 'css/blog.css' %}"> </head> <body> <header class="site-header"> <h1>{{ page_title }}</h1> <nav> <a href="{% url 'home' %}">首页</a> <a href="{% url 'about' %}">关于</a> </nav> </header> <main class="article-container"> {% if articles %} <ul class="article-list"> {% for article in articles %} <li class="article-item"> <article> <header> <h2> <a href="{% url 'article_detail' article.slug %}"> {{ article.title|truncatewords:8 }} </a> </h2> <time datetime="{{ article.pub_date|date:'c' }}"> {{ article.pub_date|date:"Y年m月d日" }} </time> </header> <div class="excerpt"> {% if article.excerpt %} {{ article.excerpt|linebreaks }} {% else %} {{ article.content|truncatewords:30|linebreaks }} {% endif %} </div> <footer> <a href="{% url 'article_detail' article.slug %}" class="read-more"> 阅读全文 → </a> </footer> </article> </li> {% endfor %} </ul> {% else %} <div class="no-content"> <h2>暂无文章</h2> <p>作者正在努力创作中,敬请期待更新。</p> </div> {% endif %} </main> <footer class="site-footer"> <p>© {% now "Y" %} {{ SITE_NAME|default:"我的博客" }}. 保留所有权利。</p> </footer> </body> </html>
blog/urls.py)from django.urls import path from . import views app_name = 'blog' urlpatterns = [ path('', views.article_list, name='article_list'), path('article/<slug:slug>/', views.article_detail, name='article_detail'), ]
✅ 关键实践亮点
- 使用
select_related/only优化数据库查询slug字段支持 SEO 友好 URLtruncatewords+linebreaks实现安全摘要渲染now模板标签动态生成版权年份app_name支持命名空间化 URL 反向解析
Django 模板语言(DTL)并非简单字符串替换工具,而是融合安全性设计、工程化约束与开发者体验的成熟模板系统。本节核心要点总结如下:
后续学习路径建议:
{% extends %}、{% block %} 构建可复用页面骨架django-debug-toolbar 模板面板、{% debug %} 标签、上下文变量检查{% cache %} 模板缓存、select_related/prefetch_related 查询优化掌握 DTL 基础,是构建可维护、可扩展、高安全 Django 应用的必经之路。从今日起,让每一个模板都成为清晰、健壮、富有表现力的前端契约。