Django 模板(Template):告别硬编码,实现动态 HTML页面

IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在公众号、今日头条持续发布最新文章,助你少走弯路。


当你开始构建一个真正的 Web 应用时,很快就会厌倦这种方式:

def home(request):
    return HttpResponse("<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>
    <meta charset="utf-8">
    <title>{{ title }}</title>
</head>
<body>
    <h1>欢迎,{{ user_name }}!</h1>
    <p>今天的日期是 {{ today }}</p>
</body>
</html>

视图中使用 render() 将数据传入模板:

from django.shortcuts import render
from datetime import date

def home(request):
    context = {
        'title': '我的博客',
        'user_name': '张三',
        'today': date.today(),
    }
    return render(request, 'blog/index.html', context)

访问页面,你会看到动态生成的 HTML。我们还可以在 Django shell 中直接体验模板的渲染过程,感受它的底层运作:

>>> from django.template import Template, 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>
{% for article in articles %}
    <li>{{ article.title }} —— {{ article.author }}</li>
{% empty %}
    <li>暂无文章</li>
{% endfor %}
</ul>

如果 articles 是空列表,会执行 {% empty %} 下的内容。

视图模拟数据并打印结果:

>>> from django.template import Template, Context
>>> t = Template("""
... <ul>
... {% for art in articles %}
...   <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 条件判断

{% if user.is_authenticated %}
    <p>欢迎回来,{{ user.username }}</p>
{% elif user.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 路径,使用命名路由:

<a href="{% url 'blog:detail' article.id %}">阅读全文</a>

blog:detail 是你在 urls.py 中定义的 name 参数,即使将来 URL 变化,模板也无需修改。

csrf_token

在任何有 POST 表单的模板中,必须添加:

<form method="post">
    {% csrf_token %}
    <!-- 表单内容 -->
</form>

它会生成一个隐藏的 CSRF 令牌,防止跨站请求伪造攻击。

2.3 过滤器 —— {{ variable|filter }}

过滤器就像一个小函数,对变量进行加工后再显示。可以串联使用:{{ text|truncatewords:30|safe }}

常用过滤器及控制台演示:

>>> from django.template import Template, Context
>>> # 1. date 格式化日期
>>> t = Template("{{ today|date:'Y年m月d日' }}")
>>> import datetime
>>> 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>
    {% for post in posts %}
        <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

<div class="pagination">
    {% if page_obj.has_previous %}
        <a href="?page={{ page_obj.previous_page_number }}">上一页</a>
    {% endif %}
    <span>第 {{ page_obj.number }} / {{ page_obj.paginator.num_pages }} 页</span>
    {% if page_obj.has_next %}
        <a href="?page={{ page_obj.next_page_number }}">下一页</a>
    {% endif %}
</div>

在列表页中使用:

{% include "_pagination.html" with page_obj=page_obj %}

with 可以传递额外的变量。如果不写,模板会自动访问当前上下文中的同名变量。

还可以传递固定值:

{% include "name_card.html" with person=user greeting="你好" %}

5. 实战:构建一个动态博客首页

现在我们把学到的知识整合,实现一个简单的博客首页,显示文章列表、分类、用户登录状态。

视图 views.py

from django.shortcuts import render
from datetime import date

def 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)
    return render(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>

    <!-- 用户状态 -->
    {% if user.is_authenticated %}
        <p>你好,{{ user.username }},<a href="/accounts/logout/">退出</a></p>
    {% else %}
        <p><a href="/accounts/login/">登录</a> | <a href="/accounts/register/">注册</a></p>
    {% endif %}

    <!-- 分类导航 -->
    <div class="categories">
        <strong>分类:</strong>
        {% for cat in categories %}
            <a href="#">{{ cat }}</a>{% if not forloop.last %}, {% endif %}
        {% endfor %}
    </div>

    <!-- 文章列表 -->
    {% for post in posts %}
        <article>
            <h2><a href="{% url 'blog:detail' post.id %}">{{ post.title }}</a></h2>
            <p class="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 django import template
import bleach

register = template.Library()

@register.filter(name='truncate_chinese')
def truncate_chinese(value, max_length=30):
    """智能截断中文字符,避免乱码"""
    if len(value) <= max_length:
        return value
    return value[:max_length] + '...'

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

在模板中加载并使用:

{% load blog_extras %}
<p>{{ post.content|truncate_chinese:50 }}</p>
{{ form.username|add_class:"form-control" }}

控制台里直接测试自定义过滤器:

>>> from django.template import Template, Context
>>> # 先确保加载了标签库,在实际 shell 中需要先注册 app
>>> # 模拟效果:
>>> def truncate_chinese(value, max_len=10):
...     return value[:max_len] + '...' if len(value) > max_len else value
...
>>> 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>
{% for comment in comments %}
    <li>{{ comment.author }}: {{ comment.content|truncatechars:30 }}</li>
{% endfor %}
</ul>

使用时:

{% show_latest_comments post.id 3 %}

7. 模板调试与最佳实践

7.1 控制台输出助力调试

开发过程中,你可以在视图中使用 print() 查看传入模板的上下文,或者查看某个对象的类型和属性:

def article_list(request):
    posts = Post.objects.prefetch_related('tags').all()
    print("查询到的文章数量:", posts.count())
    for p in posts:
        print(p.title, p.tags.all())
    return render(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 思维 !

posted @ 2026-05-15 19:47  IT策士  阅读(12)  评论(0)    收藏  举报