IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。
当你开始构建一个真正的 Web 应用时,很快就会厌倦这种方式:
def home(request):returnHttpResponse("<h1>欢迎,张三!</h1><p>今天的日期是 2026-05-15</p>")这种把 HTML 直接写在 Python 代码里的做法,正是典型的硬编码。哪怕只改动一个标点,都要修改视图代码、重启服务器。而当页面结构变复杂时,代码会变成一团糟,毫无可维护性可言。
Django 的模板(Template)系统正是为了解决这个问题而生的。它让你把 HTML 页面独立出来,只负责展示逻辑,而数据则由视图(View)动态注入。这就是MTV 模式中 T(Template)的核心价值。
1. 初识 Django 模板:配置与第一个模板
首先要确保项目已配置好模板引擎。打开settings.py,你会看到类似配置:
TEMPLATES=[{'BACKEND':'django.template.backends.django.DjangoTemplates','DIRS':[BASE_DIR /'templates'],# 全局模板目录'APP_DIRS':True,# 自动查找每个应用下的 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',],},},]接着,在应用目录(比如blog)下创建templates/blog/index.html:
<!DOCTYPE html><html><head><metacharset="utf-8"><title>{{title}}</title></head><body><h1>欢迎,{{user_name}}!</h1><p>今天的日期是{{today}}</p></body></html>视图中使用render()将数据传入模板:
from django.shortcutsimportrender from datetimeimportdatedef home(request): context={'title':'我的博客','user_name':'张三','today':date.today(),}returnrender(request,'blog/index.html', context)访问页面,你会看到动态生成的 HTML。我们还可以在 Django shell 中直接体验模板的渲染过程,感受它的底层运作:
>>>from django.templateimportTemplate, Context>>>t=Template("<h1>欢迎,{{ user_name }}!</h1>")>>>c=Context({"user_name":"李四"})>>>print(t.render(c))<h1>欢迎,李四!</h1>控制台输出正是你看到的最后一行 HTML 字符串。这就是模板的本质:把上下文数据填充到占位符中,生成纯静态 HTML 返回给浏览器。
2. 三大核心语法:变量、标签、过滤器
Django 模板的语法集中体现在三种特殊标记上。
2.1 变量 ——{{ variable }}
变量用双大括号包裹,模板会将其替换为上下文中的对应值。变量名支持点号查找,比如访问字典键、对象属性和列表索引:
# 视图传入context={'book':{'title':'Django 实战','author':'王五'},'numbers':[10,20,30],}<p>书名:{{book.title}}</p><p>作者:{{book.author}}</p><p>第二个数字:{{numbers.1}}</p>在 shell 中验证:
>>>t=Template("书名: {{ book.title }}, 作者: {{ book.author }}")>>>c=Context({"book":{"title":"深入浅出Django","author":"老张"}})>>>print(t.render(c))书名: 深入浅出Django, 作者: 老张2.2 标签 ——{% tag %}
标签用来实现逻辑控制,比如循环、条件判断、URL 生成等。常用的有:
for 循环
<ul>{%forarticleinarticles %}<li>{{article.title}}——{{article.author}}</li>{% empty %}<li>暂无文章</li>{% endfor %}</ul>如果articles是空列表,会执行{% empty %}下的内容。
视图模拟数据并打印结果:
>>>from django.templateimportTemplate, Context>>>t=Template("""...<ul>...{%forartinarticles %}...<li>{{art.title}}</li>...{% empty %}...<li>无数据</li>...{% endfor %}...</ul>...""")>>>c=Context({"articles":[{"title":"Django入门"},{"title":"模板进阶"}]})>>>print(t.render(c).strip())<ul><li>Django入门</li><li>模板进阶</li></ul>if/elif/else 条件判断
{%ifuser.is_authenticated %}<p>欢迎回来,{{user.username}}</p>{%elifuser.is_anonymous %}<p>请先登录</p>{%else%}<p>用户状态未知</p>{% endif %}在 shell 里测试多个分支:
>>>t=Template("{% if score >= 90 %}优秀{% elif score >= 60 %}及格{% else %}不及格{% endif %}")>>>print(t.render(Context({"score":85})))及格>>>print(t.render(Context({"score":55})))不及格url 标签
避免在模板中硬编码 URL 路径,使用命名路由:
<ahref="{% url 'blog:detail' article.id %}">阅读全文</a>blog:detail是你在urls.py中定义的 name 参数,即使将来 URL 变化,模板也无需修改。
csrf_token
在任何有 POST 表单的模板中,必须添加:
<formmethod="post">{% csrf_token %}<!-- 表单内容 --></form>它会生成一个隐藏的 CSRF 令牌,防止跨站请求伪造攻击。
2.3 过滤器 ——{{ variable|filter }}
过滤器就像一个小函数,对变量进行加工后再显示。可以串联使用:{{ text|truncatewords:30|safe }}。
常用过滤器及控制台演示:
>>>from django.templateimportTemplate, Context>>># 1. date 格式化日期>>>t=Template("{{ today|date:'Y年m月d日' }}")>>>importdatetime>>>print(t.render(Context({"today":datetime.date(2026,5,15)})))2026年05月15日>>># 2. default 默认值>>>t=Template("昵称: {{ nickname|default:'匿名用户' }}")>>>print(t.render(Context({})))# 没传 nickname昵称: 匿名用户>>># 3. length 获取长度>>>t=Template("标签数: {{ tags|length }}")>>>print(t.render(Context({"tags":["Python","Django","Web"]})))标签数:3>>># 4. truncatechars 截断字符>>>t=Template("简介: {{ desc|truncatechars:10 }}")>>>print(t.render(Context({"desc":"这是一篇很长很长的文章介绍"})))简介: 这是一篇很长...>>># 5. safe 标记安全内容(慎用)>>>t=Template("{{ html_text|safe }}")>>>print(t.render(Context({"html_text":"<strong>加粗</strong>"})))<strong>加粗</strong># 实际 HTML 会被执行注意:
safe会关闭自动转义,存在 XSS 风险,只应对可信内容使用。
3. 模板继承:消除重复代码
真实项目中,所有页面往往共享相同的头部、导航栏、侧边栏和底部。Django 的模板继承让你只需定义一个基础骨架,其他页面按需填充。
基础模板base.html:
<!DOCTYPE html><html><head><title>{% block title %}我的网站{% endblock %}</title>{% block extra_head %}{% endblock %}</head><body><header><nav>首页|文章|关于</nav></header><main>{% block content %}<!-- 子模板内容会出现在这里 -->{% endblock %}</main><footer>©2026我的网站</footer></body></html>子模板blog/list.html:
{% extends"base.html"%}{% block title %}{{category.name}}- 文章列表{% endblock %}{% block content %}<h1>分类:{{category.name}}</h1>{%forpostinposts %}<article><h2>{{post.title}}</h2><p>{{post.summary|truncatewords:30}}</p></article>{% empty %}<p>该分类下暂无文章。</p>{% endfor %}{% endblock %}在 shell 中,我们无法直接测试继承(因为需要加载模板文件),但可以在视图中通过render观察效果。更直观的方式是编写一个小的视图并查看控制台输出(或直接看浏览器)。不过我们可以模拟块覆盖的思路:
# 模拟 base.html 定义base=Template(""" 开始{% block main %}默认{% endblock %}结束""")# 模拟子模板内容(实际上 extends 会触发模板加载)# 这里仅做概念说明,真实项目中使用文件系统加载器。概念清晰即可:继承让代码复用率大幅提升,新增页面只需关注变化的内容块。
4. 包含与复用组件 ——{% include %}
除了继承,还可以用{% include %}把独立的 UI 片段(如分页条、评论区)抽离成组件,在多个模板中引用。
定义_pagination.html:
<divclass="pagination">{%ifpage_obj.has_previous %}<ahref="?page={{ page_obj.previous_page_number }}">上一页</a>{% endif %}<span>第{{page_obj.number}}/{{page_obj.paginator.num_pages}}页</span>{%ifpage_obj.has_next %}<ahref="?page={{ page_obj.next_page_number }}">下一页</a>{% endif %}</div>在列表页中使用:
{% include"_pagination.html"withpage_obj=page_obj %}with可以传递额外的变量。如果不写,模板会自动访问当前上下文中的同名变量。
还可以传递固定值:
{% include"name_card.html"withperson=usergreeting="你好"%}5. 实战:构建一个动态博客首页
现在我们把学到的知识整合,实现一个简单的博客首页,显示文章列表、分类、用户登录状态。
视图views.py:
from django.shortcutsimportrender from datetimeimportdatedef blog_home(request):# 模拟数据库数据posts=[{'id':1,'title':'深入理解Django模板','summary':'讲解模板语法...','pub_date':date(2026,5,10)},{'id':2,'title':'Python 3.13 新特性','summary':'关于新版本...','pub_date':date(2026,5,12)},]categories=['Django','Python','前端']user=request.user# 当前登录用户context={'posts':posts,'categories':categories,'user':user,}# 打印上下文数据,方便调试print("首页上下文数据:", context)returnrender(request,'blog/home.html', context)控制台会输出:
首页上下文数据:{'posts':[{'id':1,'title':'深入理解Django模板',...},...],'categories':['Django','Python','前端'],'user':<SimpleLazyObject:<User: admin>>}模板blog/home.html:
{% extends"base.html"%}{% block title %}博客首页{% endblock %}{% block content %}<h1>最新文章</h1><!-- 用户状态 -->{%ifuser.is_authenticated %}<p>你好,{{user.username}},<ahref="/accounts/logout/">退出</a></p>{%else%}<p><ahref="/accounts/login/">登录</a>|<ahref="/accounts/register/">注册</a></p>{% endif %}<!-- 分类导航 --><divclass="categories"><strong>分类:</strong>{%forcatincategories %}<ahref="#">{{cat}}</a>{%ifnot forloop.last %},{% endif %}{% endfor %}</div><!-- 文章列表 -->{%forpostinposts %}<article><h2><ahref="{% url 'blog:detail' post.id %}">{{post.title}}</a></h2><pclass="date">{{post.pub_date|date:'Y年m月d日'}}</p><p>{{post.summary|truncatewords:20}}</p></article>{% empty %}<p>还没有文章,赶紧写一篇吧。</p>{% endfor %}<!-- 包含分页组件(示例,无实际分页对象) -->{% comment %}{% include"_pagination.html"%}{% endcomment %}{% endblock %}这个首页完全由数据驱动,结构与展示分离。今后你只需要修改视图中的posts数据来源(例如从数据库查询),模板无需变动。
6. 进阶技巧:自定义过滤器与标签
当内置过滤器无法满足需求时,你可以创建自己的模板工具。
6.1 自定义过滤器
在应用目录下创建templatetags包(含__init__.py),新建blog_extras.py:
from djangoimporttemplateimportbleach register=template.Library()@register.filter(name='truncate_chinese')def truncate_chinese(value,max_length=30):"""智能截断中文字符,避免乱码"""iflen(value)<=max_length:returnvaluereturnvalue[:max_length]+'...'@register.filter def add_class(value, css_class):"""为表单字段添加 CSS 类(简化版)"""returnvalue.as_widget(attrs={"class":css_class})在模板中加载并使用:
{% load blog_extras %}<p>{{post.content|truncate_chinese:50}}</p>{{form.username|add_class:"form-control"}}控制台里直接测试自定义过滤器:
>>>from django.templateimportTemplate, Context>>># 先确保加载了标签库,在实际 shell 中需要先注册 app>>># 模拟效果:>>>def truncate_chinese(value,max_len=10):...returnvalue[:max_len]+'...'iflen(value)>max_lenelsevalue...>>>t=Template("{% load blog_extras %}{{ text|truncate_chinese:6 }}")>>># 实际渲染需要模板引擎加载,这里仅示意...6.2 自定义包含标签
如果某个片段需要一些 Python 逻辑才能渲染,可以用包含标签。比如“最新评论”组件:
@register.inclusion_tag('blog/latest_comments.html')def show_latest_comments(post_id,count=5): comments=Comment.objects.filter(post_id=post_id).order_by('-created')[:count]return{'comments':comments}模板latest_comments.html:
<ul>{%forcommentincomments %}<li>{{comment.author}}:{{comment.content|truncatechars:30}}</li>{% endfor %}</ul>使用时:
{% show_latest_comments post.id3%}7. 模板调试与最佳实践
7.1 控制台输出助力调试
开发过程中,你可以在视图中使用print()查看传入模板的上下文,或者查看某个对象的类型和属性:
def article_list(request): posts=Post.objects.prefetch_related('tags').all()print("查询到的文章数量:", posts.count())forpinposts: print(p.title, p.tags.all())returnrender(request,'list.html',{'posts':posts})服务器控制台将实时输出这些信息,方便你核对数据是否符合预期。
7.2 利用 Django Debug Toolbar
安装django-debug-toolbar,它会在页面侧边栏展示模板渲染的上下文、SQL 查询等信息,比 print 更为强大。
7.3 模板设计原则
逻辑尽量简单:模板中只放展示逻辑,复杂的业务逻辑应放在视图或模型中。
避免大量嵌套:利用继承和包含拆分复杂页面。
静态资源管理:使用
{% static 'css/style.css' %}引用静态文件。安全性:不要轻易使用
safe过滤器,确保转义输出;时刻记住{% csrf_token %}。
8. 总结
Django 模板让你从硬编码 HTML 的泥潭中解脱,实现了:
表现与逻辑分离:HTML 独立为模板文件,Python 代码专注数据处理。
代码复用:通过继承和包含,公共部分只写一次。
可维护性:修改页面结构时,无需触碰视图代码;反之亦然。
高度可扩展:自定义标签和过滤器能满足各种个性化需求。
从最简单的{{ variable }}开始,到构建出结构清晰的动态网站,模板系统始终是 Django 快速开发理念的基石。掌握它,你就能优雅地“告别硬编码,实现动态 HTML 页面”。
还可以去公众号、今日头条搜索「IT策士」,一起升级 IT 思维 !