Django5模板引擎
4943字约16分钟
2025-02-16
Django 作为 Web框架,需要一种很便利的方法动态地生成HTML 网页,因此有了模板这个概念。模板 包含所需HTML 的部分代码以及一些特殊语法,特殊语法用于描述如何将视图传递的数据动态插入 HTML 网页中。
Django可以配置一个或多个模板引擎(甚至是О个,如前后端分离,Django只提供API接口,无须使用模 板引擎),模板引擎有Django模板语言(Django Template Language,DTL)和Jinja3。Django模板语言 是Django 内置的功能之一,Jinja3是当前Python流行的模板语言。本章分别讲述Django模板语言和 Jinja3的使用方法。
Django5内置模板引擎
Django 内置的模板引擎包含模板上下文(亦可称为模板变量)、标签和过滤器,各个功能说明如下:
- 模板上下文是以变量的形式写入模板文件里面,变量值由视图函数或视图类传递所得。
- 标签是对模板上下文进行控制输出,比如模板上下文的判断和循环控制等。
- 模板继承隶属于标签,它是将每个模板文件重复的代码抽取出来并写在一个共用的模板文件中,其 他模板文件通过继承共用模板文件来实现完整的网页输出。
- 过滤器是对模板上下文进行操作处理,比如模板上下文的内容截取、替换或格式转换等。
模板上下文
模板上下文是模板中基本的组成单位,上下文的数据由视图函数或视图类传递。它以表 示,variable是上下文的名称,它支持 Python 所有的数据类型,如字典、列表、元组、字符串、整型或 实例化对象等。上下文的数据格式不同,在模板里的使用方式也有所差异。
使用变量的一些注意点如下:
- 当模板引擎遇到一个变量,将计算这个变量,然后输出结果
- 变量名必须由字母、数字、下划线、点组成,不能由数字和下划线开头
- 当模板引擎遇到 “ . ” 的时候,按以下顺序进行解析 
- 如果变量不存在,不会引发异常,模板会插入空字符串 ''
- 在模板中使用变量或方法时,不能出现 ()、[]、{}
- 调用方法时,不能传递参数
我们通过一个实例来学习下:
views.py,index方法:
# 定义人类
class Person:
    # 属性 姓名
    name = None
    # 属性 年龄
    age = None
    def __init__(self, name, age):
        self.name = name
        self.age = age
      
def index(request):
    str = "模板变量"
    myDict = {"tom": '666', 'cat': '999', 'wzw': '333'}
    # 创建一个对象 zhangsan
    zhangsan = Person("张三", 21)
    myList = ["java", "python", "c"]
    myTuple = ("python", 222, 3.14, False)
    content_value = {"msg": str, "msg2": myDict, "msg3": zhangsan, "msg4":myList, "msg5": myTuple}
    return render(request, 'index.html', context=content_value)index.html:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
字符串:{{ msg }}<br>
字典类型:{{ msg2.tom }},{{ msg2.cat }},{{ msg2.wzw }}<br>
对象:{{ msg3.name }},{{ msg3.age }}<br>
列表:{{ msg4.0 }},{{ msg4.1 }},{{ msg4.3 }},{{ msg4.2 }}<br>
元组:{{ msg5.0 }},{{ msg5.4 }},{{ msg5.1 }},{{ msg5.2 }},{{ msg5.3 }}
</body>
</html>测试,浏览器输入: http://127.0.0.1:8000/index/

模板标签
标签是对模板上下文进行控制输出,它是以{% tag %}表示的,其中 tag是标签的名称,Django内置了许 多模板标签,比如{% if %}(判断标签)、{% for %}(循环标签)或{% url %}(路由标签)等。
常用内置标签如下:
| 标签 | 描述 | 
|---|---|
| {% for %} | 遍历输出上下文的内容 | 
| {% if %} | 对上下文进行条件判断 | 
| {% csrf_token %} | 生成csrf token的标签,用于防护跨站请求伪造攻击 | 
| {% url %} | 引用路由配置的地址,生成相应的路由地址 | 
| {% with %} | 将上下文名重新命名 | 
| {% load %} | 加载导入 Django的标签库 | 
| {% static %} | 读取静态资源的文件内容 | 
| {% extends xxx %} | 模板继承,xxx为模板文件名,使当前模板继承xxx模板 | 
| {% block xxx %} | 重写父类模板的代码 | 
在for标签中,模板还提供了一些特殊的变量来获取for标签的循环信息,变量说明如下:
| 标签 | 描述 | 
|---|---|
| forloop.counter | 获取当前循环的索引,从1开始计算 | 
| forloop.counter0 | 获取当前循环的索引,从0开始计算 | 
| forloop.revcounter | 索引从最大数开始递减,直到索引到1位置 | 
| forloop.revcounter0 | 索引从最大数开始递减,直到索引到0位置 | 
| forloop.first | 当遍历的元素为第一项时为真 | 
| forloop.last | 当遍历的元素为最后一项时为真 | 
| forloop.parentloop | 在嵌套的for循环中,获取上层for循环的forloop | 
我们修改index.html:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
字符串:{{ msg }}<br>
字典类型:{{ msg2.tom }},{{ msg2.cat }},{{ msg2.wzw }}<br>
对象:{{ msg3.name }},{{ msg3.age }}<br>
列表:{{ msg4.0 }},{{ msg4.1 }},{{ msg4.3 }},{{ msg4.2 }}<br>
元组:{{ msg5.0 }},{{ msg5.4 }},{{ msg5.1 }},{{ msg5.2 }},{{ msg5.3 }}
<h3>模板标签</h3>
<p>遍历for标签:</p>
{% for item in msg4 %}
    <p>这个是第{{ forloop.counter }}次循环</p>
    {% if forloop.first %}
        <p>这个是第一项:{{ item }}</p>
    {% elif forloop.last %}
        <p>这个是最后一项:{{ item }}</p>
    {% endif %}
{% endfor %}
<p>判断if标签:</p>
{% if msg == '模板变量' %}
    <p>模板变量</p>
{% elif msg == '模板变量2' %}
    <p>模板变量2</p>
{% else %}
    <p>其他</p>
{% endif %}
<p>url标签</p>
<a href="{% url 'index' %}">请求index</a>
<p>with标签</p>
{% with info=msg %}
    {{ info }}
{% endwith %}
</body>
</html>用url标签的时候 第二个参数是路由名称,所以urls.py里,修改下:
path('index/', helloWorld.views.index, name="index"),测试,浏览器输入: http://127.0.0.1:8000/index/

模板继承
Django模板继承是一个强大的工具,可以将通用页面元素(例如页眉、页脚、侧边栏等)分离出来,并 在多个页面之间共享他们。
模板继承和 Python 语言中类的继承含义是一样的,在 Django 中模板只是一个文本文件,如 HTML。
模板继承是 Django 模板语言中最强大的部分。模板继承使你可以构建基本的“骨架”模板,将通用的功能 或者属性写在基础模板中,也叫基类模板或者父模板。子模板可以继承父类模板,子模板继承后将自动 拥有父类中的属性和方法,我们还可以在子模板中对父模板进行重写,即重写父模板中方法或者属性, 从而实现子模板的定制。模板继承大大提高了代码的可重用性,减轻开发人员的工作量。
在模板继承中最常用了标签就是 {% block %} 与 {% extends %} 标签,其中 {% block% } 标签与 {% endblock %} 标签成对出现
我们新建一个基础模版base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
  <title>
      {% block title %}
      Python222学院
      {% endblock %}
  </title>
</head>
<body>
<div id="head">
    <img src="http://127.0.0.1:8000/static/logo.png"/>
</div>
<div id="content">
    {% block content %}
        欢迎进入Python222学院
    {% endblock %}
</div>
<div id="footer">
    版权所有 www.python222.com
</div>
</body>
</html>再写一个course.html,继承base.html
{% extends 'base.html' %}
<!-- 重写title -->
{% block title %}
    课程页面-Python222
{% endblock %}
<!-- 重写content -->
{% block content %}
    Django5课程-模板引擎章节
{% endblock %}我们来测试下吧。
views.py里新建一个to_course方法:
def to_course(request):
    """
    跳转课程页面
    :param request:
    :return:
    """
    return render(request, 'course.html')urls.py里加一个映射:
path('toCourse/', helloWorld.views.to_course)浏览器输入: http://127.0.0.1:8000/toCourse/

我们发现模板里的标题和内容被course页面修改了,其他的没变。
这里我们再优化下,直接写死静态路径是不是很不好啊。
<div id="head">
    <img src="http://127.0.0.1:8000/static/logo.png"/>
</div>这时候我们就能用上 {% load static %} ,加载项目中的静态文件,包括图片,css,js文件,字体文 件等。
<img src="{% static 'logo.png' %}"/>完整base.html:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>
        {% block title %}
        Python222学院
        {% endblock %}
    </title>
</head>
{% load static %}
<body>
<div id="head">
    <img src="{% static 'logo.png' %}"/>
</div>
<div id="content">
    {% block content %}
        欢迎进入Python222学院
    {% endblock %}
</div>
<div id="footer">
    版权所有 www.python222.com
</div>
</body>
</html>过滤器
Django过滤器是一种用于在Django模板中处理数据的技术。过滤器的作用是可以对模板中的变量进行加 工、过滤或格式化,返回一个新的值供模板使用。
过滤器作用是在变量输出时,对输出的变量值做进一步的处理。
我们可以使用过滤器来更改变量的输出显示。
过滤器跟模板标签一样,也是在模板中对函数进行调用
对输出的日期进行格式化处理,或者转换大小写字母等,这些都有对应的过滤器去处理它们。
过滤器的语法格式如下:
{{ 变量 | 过滤器1:参数值1 | 过滤器2:参数值2 ... }}常用内置过滤器如下:
| 过滤器 | 说明 | 
|---|---|
| add | 加法 | 
| addslashes | 添加斜杠 | 
| capfirst | 首字母大写 | 
| center | 文本居中 | 
| cut | 切除字符 | 
| date | 日期格式化 | 
| default | 设置默认值 | 
| default_if_none | 为None设置默认值 | 
| dictsort | 字典排序 | 
| dictsortreversed | 字典反向排序 | 
| divisibleby | 整除判断 | 
| escape | 转义 | 
| escapejs | 转义js代码 | 
| filesizeformat | 文件尺寸人性化显示 | 
| first | 第一个元素 | 
| floatformat | 浮点数格式化 | 
| force_escape | 强制立刻转义 | 
| get_digit | 获取数字 | 
| iriencode | 转换IRI | 
| join | 字符列表链接 | 
| last | 最后一个 | 
| length | 长度 | 
| length_is | 长度等于 | 
| linebreaks | 行转换 | 
| linebreaksbr | 行转换 | 
| linenumbers | 行号 | 
| ljust | 左对齐 | 
| lower | 小写 | 
| make_list | 分割成字符列表 | 
| phone2numeric | 电话号码 | 
| pluralize | 复数形式 | 
| pprint | 调试 | 
| random | 随机获取 | 
| rjust | 右对齐 | 
| safe | 安全确认 | 
| safeseq | 列表安全确认 | 
| slice | 切片 | 
| slugify | 转换成ASCII | 
| stringformat | 字符串格式化 | 
| striptags | 去除HTML中的标签 | 
| time | 时间格式化 | 
| timesince | 从何时开始 | 
| timeuntil | 到何时多久 | 
| title | 所有单词首字母大写 | 
| truncatechars | 截断字符 | 
| truncatechars_html | 截断字符 | 
| truncatewords | 截断单词 | 
| truncatewords_html | 截断单词 | 
| unordered_list | 无序列表 | 
| upper | 大写 | 
| urlencode | 转义url | 
| urlize | url转成可点击的链接 | 
| urlizetrunc | urlize的截断方式 | 
| wordcount | 单词计数 | 
| wordwrap | 单词包裹 | 
| yesno | 将True,False和None,映射成字符串‘yes’,‘no’,‘maybe’ | 
根据给定的格式格式化日期
| 格式字符 | 描述 | 示例输出 | 
|---|---|---|
| a | ‘a.m.’ or ‘p.m.’ | ‘a.m.’ | 
| A | ‘AM’ or ‘PM’ | ‘AM’ | 
| b | 月份,文字形式,3个字幕库,小写 | 'jan' | 
| B | 未实现 | |
| c | ISO 8601格式 | 2008-01-02T10:30:00.000123+02:00 | 
| d | 月的日子,带前导零的2位数字。 | 01'到'31' | 
| D | 周几的文字表述形式,3个字母。 | 'Fri' | 
| e | 时区名称 | ",'GMT,'-500',US/Eastern'等 | 
| E | 月份,分地区。 | |
| f | 时间 | 1',1:30' | 
| g | 12小时格式,无前导零。 | "1'到'12' | 
| G | 24小时格式,无前导零。 | 0'到'23' | 
| h | 12小时格式。 | '01'到'12' | 
| H | 24小时格式。 | '00'到23' | 
| i | 分钟 | 00'到59' | 
| I | 夏令时间,无论是否生效。 | '1'或0 | 
| j | 没有前导零的月份的日子。 | '1'到"31' | 
| l | 星期几,完整英文名 | 'Friday' | 
| L | 布尔值是否是—个闰年。 | True或False | 
| m | 月,2位数字带前导零。 | '01'到'12' | 
| M | 月,文字,3个字母。 | "Jan” | 
| n | 月无前导零。 | '1'到'12' | 
| N | 美联社风格的月份缩写。 | 'Jan.' ,'Feb.','March','May' | 
| o | ISO-8601周编号 | '1999' | 
| O | 与格林威治时间的差,单位小时。 | '+0200' | 
| P | 时间为12小时 | 1:30 p.m.’ , ‘midnight’ , ‘noon’ , ‘12:30 p.m.’ | 
| r | RFC 5322格式化日期。 | 'Thu,21 Dec 2000 16:01:07+0200' | 
| s | 秒,带前导零的2位数字。 | '00'到59 | 
| S | 一个月的英文序数后缀,2个字符。 | 'st' ,'nd', 'rd'或'th' | 
| t | 给定月份的天数。 | 28 to 31 | 
| u | 微秒。 | 000000 to 999999 | 
| U | 自Unix Epoch以来的秒数(1970年1月1日 00:00:00 UTC). | |
| w | 星期几,数字无前导零。 | 'O'(星期日)至'6’(星期六) | 
| W | ISO-8601周数,周数从星期一开始。 | 1,53 | 
| y | 年份,2位数字。 | 99 | 
| Y | 年,4位数。 | '1999' | 
| z | —年中的日子 | 0到365 | 
| Z | 时区偏移量,单位为秒。 | -43200到43200 | 
views.py index函数我们修改下:str改成"hello",再定义一个日期对象
def index(request):
    str = "hello"
    date = datetime.datetime.now()
    myDict = {"tom": '666', 'cat': '999', 'wzw': '333'}
    # 创建一个对象 zhangsan
    zhangsan = Person("张三", 21)
    myList = ["java", "python", "c"]
    myTuple = ("python", 222, 3.14, False)
    content_value = {"msg": str, "msg2": myDict, "msg3": zhangsan, "msg4":myList, "msg5": myTuple, "date": date}
    return render(request, 'index.html', context=content_value)index.html加下:
<p>内置过滤器</p>
capfirst:{{ msg | capfirst }}<br>
length:{{ msg | length }}<br>
date:{{ date }} - >> {{ date | date:'Y-m-d H:i:s' }}运行测试:

Jinja3模版引擎
Jinja是Python里面被广泛应用的模板引擎,最新版本3.1.3 它的设计思想来源于Django的模板引擎,并 扩展了其语法和一系列强大的功能。其中最显著的是增加了沙箱执行功能和可选的自动转义功能,这对 大多数应用的安全性来说是非常重要。此外,它还具备以下特性:
·沙箱执行模式,模板的每个部分都在引擎的监督之下执行,模板将会被明确地标记在白名单或黑名单 内,这样对于那些不信任的模板也可以执行。
强大的自动HTML转义系统,可以有效地阻止跨站脚本攻击。
模板继承机制,此机制可以使得所有模板具有相似一致的布局,也方便开发人员对模板进行修改和管理。
高效的执行效率,Jinja3引擎在模板第一次加载时就把源码转换成Python字节码,加快模板执行时间。 调试系统融合了标准的Python的TrackBack功能,使得模板编译和运行期间的错误能及时被发现和调 试。
语法配置,可以重新配置Jinja3,使得它更好地适应LaTeX或JavaScript 的输出。官方文档手册,此手册 指导设计人员更好地使用Jinja3引擎的各种方法。
Django支持 Jinja3模板引擎的使用,由于Jinja3的设计思想来源于Django 的模板引擎,因此Jinja3的使 用方法与 Django 的模板语法有相似之处。
开源主页: https://github.com/pallets/jinja
官方文档: https://jinja.palletsprojects.com/en/3.1.x/
Jinja3安装与配置
我们用pip命令安装Jinja3
pip install Jinja2 -i https://pypi.tuna.tsinghua.edu.cn/simpleJinja3安装成功后,接着在 Django里配置Jinja3模板。由于 Django的内置功能是使用 Django的模板引 擎,如果将整个项目都改为Jinja3模板引擎,就会导致内置功能无法正常使用。在这种情况下,既要保证 内置功能能够正常使用,又要使用Jinja3模板引擎,只能将两个模板引擎共存在同一个项目里。
首先我们在helloWorld项目库里新建Jinja3.py,用来定义环境参数;
from django.contrib.staticfiles.storage import staticfiles_storage
from django.urls import reverse
from jinja2 import Environment
def environment(**options):
    env = Environment(**options)
    env.globals.update(
        {
            'static': staticfiles_storage.url,
            'url': reverse
        }
    )
    return env然后我们找到项目配置settings.py
TEMPLATES = [
      {
          'BACKEND': 'django.template.backends.jinja2.Jinja2',
          'DIRS': [BASE_DIR / 'templates']
          ,
          'APP_DIRS': True,
          'OPTIONS': {
          'environment': 'helloWorld.Jinja3.environment'
          },
      },
      {
          'BACKEND': 'django.template.backends.django.DjangoTemplates',
          'DIRS': [BASE_DIR / 'helloWorld/templates', 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',
              ],
          },
      },  
]我们找到项目目录下templates的index.html,修改下Jinja3支持的模板语法:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{{ msg2['cat'] }}
</body>
</html>运行测试:

Jinja3模板语法
尽管Jinja3的设计思想来源于Django 的模板引擎,但在功能和使用细节上,Jinja3比Django的模板引擎 更为完善,而且Jinja3的模板语法在使用上与 Django的模板引擎存在一定的差异。
由于Jinja3有模板设计人员帮助手册(官方文档:https://jinja.palletsprojects.com/en/3.1.x/ ), 并且官方文档对模板语法的使用说明较为详细,因此这里只讲述Jinja3与 Django模板语言的使用差异。
我们把helloworld子项目下templates下的index.html复制到父项目下的templates下,然后进行修改:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
字符串:{{ msg }}<br>
字典类型:{{ msg2.tom }},{{ msg2.cat }},{{ msg2.wzw }}<br>
对象:{{ msg3.name }},{{ msg3.age }}<br>
列表:{{ msg4.0 }},{{ msg4.1 }},{{ msg4.3 }},{{ msg4.2 }}<br>
元组:{{ msg5.0 }},{{ msg5.4 }},{{ msg5.1 }},{{ msg5.2 }},{{ msg5.3 }}
<h3>模板标签</h3>
<p>遍历for标签:</p>
{% for item in msg4 %}
    <p>这个是第{{ loop.length }}次循环</p>
    {% if loop.first %}
        <p>这个是第一项:{{ item }}</p>
    {% elif loop.last %}
        <p>这个是最后一项:{{ item }}</p>
    {% endif %}
{% endfor %}
<p>判断if标签:</p>
{% if msg == '模板变量' %}
    <p>模板变量</p>
{% elif msg == '模板变量2' %}
    <p>模板变量2</p>
{% else %}
    <p>其他</p>
{% endif %}
<p>url标签</p>
{#<a href="{% url 'index' %}">请求index</a>#}
<a href="{{ url('index') }}">请求index</a>
<p>with标签</p>
{% with info=msg %}
    {{ info }}
{% endwith %}
</body>
</html>运行测试:

在遍历对象,列表,元组的时候,假如元素或者属性不存在,Jinja3会返回具体的报错信息:no such element
以及url函数用法不一样;在遍历for标签上,属性页不一样,内置的对象是loop
for函数模板变量:
| Variable | Description | 
|---|---|
| loop.index | 循环的当前迭代(索引从1开始) | 
| loop.index0 | 循环的当前迭代(索引从0开始) | 
| loop.revindex | 循环结束时的迭代次数(索引从1开始) | 
| loop.revindex0 | 循环结束时的迭代次数(索引从0开始) | 
| loop.first | 如果是第一次迭代,就为True | 
| loop.last | 如果是最后一次迭代,就为True | 
| loop.length | 序列中的项目数,即循环总次 | 
| loop.cycle | 辅助函数,用于在序列列表之间循环 | 
| loop.depth | 当前递归循环的深度,从1级开始 | 
| loop.depth0 | 当前递归循环的深度,从0级开始 | 
| loop.previtem | 上一次迭代中的对象 | 
| loop.nextitem | 下一次迭代中的对象 | 
| loop.changed(*val) | 若上次迭代的值与当前迭代的值不同,则返回True | 
我们把base.html和course.html也复制一份到父项目templates下;
base.html需要修改下:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>
      {% block title %}
          Python222学院
      {% endblock %}
  </title>
</head>
{# load不支持,要去掉 #}
{#{% load static %}#}
<body>
<div id="head">
{# Django语法 #}
{# <img src="{% static 'logo.png' %}"/>#}
{# Jinja3语法 #}
<img src="{{ static('logo.png') }}"/>
</div>
<div id="content">
    {% block content %}
        欢迎进入Python222学院
    {% endblock %}
</div>
<div id="footer">
    版权所有 www.python222.com
</div>
</body>
</html>运行测试:

相比较Django,Jinja3在static函数用法上也有区别,模版继承用法基本一致。
Jinja3过滤器
Jinja3的过滤器与 Django内置过滤器的使用方法有相似之处,也是由管道符号“|”连接模板上下文和过滤 器,但是两者的过滤器名称是不同的,而且过滤器的参数设置方式也不同。Jinja3常用过滤器如下表格:
| 过滤器 | 说明 | 
|---|---|
| abs | 设置数值的绝对值 | 
| default | 设置默认值 | 
| escape | 转义字符,转成HTML的语法 | 
| first | 获取上下文的第一个元素 | 
| last | 获取上下文的最后一个元素 | 
| length | 获取上下文的长度 | 
| join | 功能与Python的join语法一致 | 
| safe | 将上下文转义处理 | 
| int | 将上下文转换为int类型 | 
| float | 将上下文转换为float类型 | 
| lower | 将字符串转换为小写 | 
| upper | 将字符串转换为大写 | 
| replace | 字符串的替换 | 
| truncate | 字符串的截断 | 
| striptags | 删除字符串中所有的HTML标签 | 
| trim | 截取字符串前面和后面的空白字符 | 
| string | 将上下文转换成字符串 | 
| wordcount | 计算长字符串的单词个数 | 
具体使用可以参考下Jinja3官方文档(https://jinja.palletsprojects.com/en/3.1.x/templates/#filters )
下面我们通过实例来体检下Jinja3的用法吧:
修改index.html:
<p>Jinja3过滤器</p>
first: {{ msg | first }}<br>
last:{{ msg | last }}<br>
length:{{ msg | length }}<br>
upper:{{ msg | upper }}运行结果:

