6. 深入模板
6.1 Django模板引擎
Django内置的模板引擎包含模板上下文(亦可称为模板变量), 标签和过滤器, 各个功能说明如下:
● 模板上下文是以变量的形式写入模板文件里面, 变量值由视图函数或视图类传递所得.
● 标签是对模板上下文进行控制输出, 比如模板上下文的判断和循环控制等.
● 模板继承隶属于标签, 它是将每个模板文件重复的代码抽取出来并写在一个共用的模板文件中,
其他模板文件通过继承共用模板文件来实现完整的网页输出.
● 过滤器是对模板上下文进行操作处理, 比如模板上下文的内容截取, 替换或格式转换等.
6.1.1 模板上下文
模板上下文是模板中基本的组成单位, 上下文的数据由视图函数或视图类传递.
它以{{ variable }}表示, variable是上下文的名称, 它支持Python所有的数据类型, 如字典, 列表, 元组, 字符串, 整型或实例化对象等.
上下文的数据格式不同, 在模板里的使用方式也有所差异, 如下所示:
# 1. 假如: variable1 = '字符串或整型'
<div>{{ variable1 }}</div>
# 输出'<div>字符串或整型</div>'
# 2. 假如: variable2 = {'name': '字典或实例化对象'}
<div>{{ variable2.name }}</div>
# 输出'<div>字典或实例化对象</div>'
# 3. 假如: variable3 = ['元组或列表']
<div>{{ variable3.0 }}</div>
# 输出“<div>元组或列表</div>”
从上述代码发现, 如果上下文的数据带有属性, 就可以在上下文的末端使用'.'来获取某个属性的值.
比如上下文为字典或实例化对象, 在上下文末端使用'.'并写入属性名称即可在网页上显示该属性的值;
若上下文为元组或列表, 则在上下文末端使用'.'并设置索引下标来获取元组或列表的某个元素值.
如果视图没有为模板上下文传递数据或者模板上下文的某个属性, 索引下标不存在, Django就会将其设为空值.
例如获取variable2的属性age, 由于上述的variable2并不存在属性age, 因此网页上将会显示'<div></div>'';.
在PyCharm的Debug调试模式里分析Django模板引擎的运行过程.
打开函数render所在的源码文件, 变量content是模板文件的解析结果, 它是由函数render_to_string完成解析过程的, 如图6-1所示.
图6-1 函数render的源码信息
想要分析Django模板引擎的解析过程, 还需要从函数render_to_string深入分析,
通过PyCharm打开函数render_to_string的源码信息, 发现它调用了函数get_template或select_template,
我们沿着函数调用的方向去探究整个解析过程, 梳理函数之间的调用关系, 最终得出模板解析过程, 如图6-2所示.
整个解析过程调用了多个函数和类方法, 每个函数和类方法在源码里都有功能注释, 这里不再详细讲述, 读者可自行在源码里查阅.
图6-2 Django模板引擎的解析过程
6.1.2 自定义标签
标签是对模板上下文进行控制输出, 它是以{% tag %}表示的, 其中tag是标签的名称,
Django内置了许多模板标签, 比如{% if %} (判断标签), {% for %} (循环标签) 或 {% url %} (路由标签)等.
内置的模板标签可以在Django源码(\django\template\defaulttags.py)里找到定义过程,
每个内置标签都有功能注释和使用方法, 本书只列举常用的内置标签, 如表6-1所示.
表6-1 常用的内置标签
标签 | 描述 |
---|
{% for %} | 用于遍历列表, 元组, 字典或任何可迭代对象, 并输出上下文的内容. |
{% if %} | 对上下文中的变量进行条件判断, 根据条件执行不同的模板代码块. |
{% csrf_token %} | 生成一个隐藏的表单字段, 包含CSRF令牌, 用于防止跨站请求伪造攻击. |
{% url %} | 根据Django的URL配置, 生成一个URL, 它通常用于在模板中链接到其他视图. |
{% with %} | 临时重命名一个或多个变量, 使得在{% with %}和{% endwith %}之间的模板代码可以使用新的变量名. |
{% load %} | 加载一个或多个自定义模板标签库, 使得这些库中的标签可以在当前模板中使用. |
{% static %} | 引用静态文件, 例如CSS, JavaScript或图片文件. 它会自动将文件路径转换为正确的URL. |
{% extends %} | 表示当前模板继承自另一个模板(称为父模板). |
| 这允许你创建一个包含通用页面元素(如头部和底部)的基础模板, 并在其他模板中继承这些元素. |
{% block %} | 在子模板中定义可重写的代码块. 在父模板中, 可以使用{% block %}标签来定义默认内容, |
| 而在子模板中, 可以使用相同的 {% block %}标签来重写这些内容. |
在上述常用标签中, 每个标签的使用方法都是各不相同的,
通过简单的例子来进一步了解标签的使用方法, 代码如下:
{% for item in my_list %}
{{ item }}
{% endfor %}
{% if name == "Lily" %}
{{ name }}
{% elif name == "Lucy" %}
{{ name }}
{% else %}
{{ name }}
{% endif %}
<a href="{% url 'index' %}">首页</a>
<a href="{% url 'page' 1 %}">第1页</a>
{% with total=number %}
{{ total }}
{% endwith %}
{% load staticfiles %}
{% static "css/index.css" %}
在for标签中, 模板还提供了一些特殊的变量来获取for标签的循环信息, 变量说明如表6-2所示.
表6-2 for标签模板变量说明
变量 | 描述 |
---|
forloop.counter | 获取当前循环的索引, 从1开始计算. 在循环的每一次迭代中, 这个值会递增. |
forloop.counter0 | 获取当前循环的索引, 从0开始计算. 在循环的每一次迭代中, 这个值会递增. |
forloop.revcounter | 索引从当前循环的最大数开始递减, 直到递减到1位置. 随着循环的进行, 这个值会递减. |
forloop.revcounter0 | 索引从当前循环的最大数开始递减, 直到递减到0位置. 随着循环的进行, 这个值会递减. |
forloop.first | 当遍历的元素为第一项时为真(True). 在循环的第一次迭代中, 这个值将为真. |
forloop.last | 当遍历的元素为最后一项时为真(True). 在循环的最后一次迭代中, 这个值将为真. |
forloop.parentloop | 在嵌套的for循环中, 获取上层for循环的forloop对象. |
| 这使得你可以访问上层循环的相关信息, 如索引或是否为首项/末项等. |
上述变量来自于forloop对象, 该对象是在模板引擎解析for标签时生成的.
通过简单的例子来进一步了解forloop的使用, 例子如下:
{% for name in name_list %}
{% if forloop.counter == 1 %}
<span>这是第一次循环</span>
{% elif forloop.last %}
<span>这是最后一次循环</span>
{% else %}
<span>本次循环次数为: {{forloop.counter }}</span>
{% endif %}
{% endfor %}
除了使用内置的模板标签之外, 我们还可以自定义模板标签.
以MyDjango为例, 在项目的根目录下创建新的文件夹, 文件夹名称可自行命名, 本示例命名为mydefined;
然后在该文件夹下创建初始化文件__init__.py和templatetags文件夹, 其中templatetags文件夹的命名是固定不变的;
最后在templatetags文件夹里创建初始化文件__init__.py和自定义标签文件mytags.py, 项目的目录结构如图6-3所示.
图6-3 目录结构
由于在项目的根目录下创建了mydefined文件夹, 因此在配置文件settings.py的属性INSTALLED_APPS里添加mydefined,
否则Django在运行时无法加载mydefined文件夹的内容, 配置信息如下:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'index',
'mydefined'
]
下一步在项目的mytags.py文件里自定义标签, 我们将定义一个名为reversal的标签,
它是将标签里的数据进行反转处理, 定义过程如下:
from django import template
register = template.Library()
class ReversalNode(template.Node):
def __init__(self, value):
self.value = str(value)
def render(self, context):
return self.value[::-1]
@register.tag(name='reversal')
def do_reversal(parse, token):
try:
tag_name, value = token.split_contents()
except Exception:
raise template.TemplateSyntaxError('syntax')
return ReversalNode(value)
在mytags.py文件里分别定义了类ReversalNode和函数do_reversal, 两者实现功能说明如下:
● 函数do_reversal经过装饰器register.tag(name='reversal')处理, 这是让函数执行模板标签注册,
标签名称由装饰器参数name进行命名, 如果没有设置参数name, 就以函数名作为标签名称.
函数名没有具体要求, 一般以'do_标签名称'或'标签名称'作为命名规范.
● 函数参数parse是解析器对象, 当Django运行时, 它将所有标签和过滤器进行加载并生成到parse对象,
在解析模板文件里面的标签时, Django就会从parse对象查找对应的标签信息.
● 函数参数token是模板文件使用标签时所传递的数据对象, 主要包括标签名和数据内容.
● 函数do_reversal对参数token使用split_contents()方法(Django的内置方法)进行取值处理,
从中获取数据value, 并将value传递给自定义模板节点类ReversalNode.
● 类ReversalNode是将value执行字符串反转处理, 并生成模板节点对象, 用于模板引擎解析HTML语言.
(render方法会在模板渲染过程中被触发.
具体来说, 当Django模板引擎遇到你的自定义标签{% reversal value %}时, 它会执行以下步骤:
* 1. 解析标签: Django模板引擎首先会调用自定义的do_reversal函数.
这个函数会解析标签的内容, 并返回一个ReversalNode实例.
* 2. 渲染节点: 在模板渲染的过程中, Django模板引擎会遍历模板中的所有节点, 并调用它们的render方法.
对于ReversalNode实例, 当它的render方法被调用时, 它会返回反转后的字符串.)
为了验证自定义标签reversal的功能, 我们在index的url.py, views.py和模板文件index.html里编写以下代码:
from django.urls import path
from .views import *
urlpatterns = [
path('', index, name='index'),
]
from django.shortcuts import render
def index(request):
return render(request, 'index.html', locals())
{#导入自定义标签文件mytags#}
{% load mytags %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% reversal 'Django' %}
</body>
</html>
在模板文件index.html中使用自定义标签时, 必须使用{% load mytags %}将自定义标签文件导入,
告知模板引擎从哪里查找自定义标签, 否则无法识别自定义标签, 并提示TemplateSyntaxError异常.
运行MyDjango项目, 在浏览器上访问l 127.0.0.1:8000, 网页上会将'Django'反转显示, 如图6-4所示.
图6-4 自定义标签reversal
综上所述, 我们发现自定义标签reversal的定义方式与内置标签的定义方式是相同的, 两者最大的区别在于:
● 自定义标签需要在项目里搭建目录环境.
● 在使用时需要在模板文件里导入自定义标签文件.
6.1.3 模板继承
模板继承是通过模板标签来实现的, 其作用是将多个模板文件的共同代码集中在一个新的模板文件中,
然后各个模板可以直接调用新的模板文件, 从而生成HTML网页, 这样可以减少模板之间重复的代码, 范例如下:
<!DOCTYPE html>
<html>
<html lang="en">
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
<a href="{% url 'index:index' %}">首页</a>
<h1>Hello Django</h1>
</body>
</html>
上述代码是一个完整的模板文件, 一个完整的模板通常有<head>和<body>两部分,
而每个模板的<head>和<body>的内容都会有所不同, 因此除了这两部分的内容之外, 可以将其他内容写在共用模板文件里.
以MyDjango为例, 在templates文件夹里创建base.html文件, 该文件作为共用模板, 代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
{% block title %}
<title>首页</title>
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>
在base.html的代码中看到, <title>写在模板标签{% block title %}{% endblock %}里面,
而<body>里的内容改为{% block body %}{% endblock %}.
block标签是为其他模板文件调用时提供内容重写的接口, body是对这个接口进行命名.
在一个模板中可以添加多个block标签, 只要每个block标签的命名不相同即可.
接着在模板index.html中调用共用模板base.html, 代码如下:
{% extends "base.html" %}
{% block body %}
<a href="{% url 'index:index' %}">首页</a>
<h1>Hello Django</h1>
{% endblock %}
模板index.html调用共用模板base.html的实质是由模板继承实现的, 调用步骤如下:
● 在模板index.html中使用{% extends "base.html" %}来继承模板base.html的所有代码.
● 通过使用标签{% block title %}或{% block body %}来重写模板base.html的网页内容.
● 如果没有使用标签block重写共用模板的内容, 网页内容将就由共用模板提供.
比如模板index.html没有使用标签{% block title %}重新定义<title>, 那么网页标题内容应由模板base.html设置的<title>提供.
● 标签block必须使用{% endblock %}结束block标签.
从模板index.html看到, 模板继承与Python的类继承原理是一致的, 通过继承方式使其具有父类的功能和属性,
同时也可以通过重写来实现复杂多变的开发需求.
为了验证模板继承是否正确, 运行MyDjango并访问127.0.0.1:8000,
查看网页标题(标题由模板base.html的<title>提供)和网页信息(重写模板base.html的{% block body %}), 如图6-5所示.
图6-5 运行结果
6.1.4 自定义过滤器
过滤器主要是对上下文的内容进行操作处理, 如替换, 反序和转义等.
通过过滤器处理上下文可以将其数据格式或内容转化为我们想要的显示效果, 而且相应减少视图的代码量.
过滤器的使用方法如下:
{{ variable | filter }}
若上下文设有过滤器, 则模板引擎在解析上下文时,
首先由过滤器filter处理上下文variable, 然后将处理后的结果进行解析并显示在网页上.
variable代表模板上下文, 管道符号'|'代表当前上下文使用过滤器,
filter代表某个过滤器. 单个上下文可以支持多个过滤器同时使用, 例如:
{{ variable | filter | lower}}
在使用的过程中, 有些过滤器还可以传入参数, 但仅支持传入一个参数.
带参数的过滤器与参数之间使用冒号隔开, 并且两者之间不能留有空格, 例如:
{{ variable | date:"D d M Y"}}
Django的内置过滤器可以在源码(\django\template\defaultfilters.py)里找到具体的定义过程.
常用内置过滤如表6-3所示.
表6-3 内置过滤器
内置过滤器 | 使用形式 | 说明 |
---|
add | `{{value | add:“2”}}` |
addslashes | `{{value | addslashes}}` |
capfirst | `{{value | capfirst}}` |
cut | `{{value | cut:arg}}` |
date | `{{value | date:“D d MY”}}` |
default | `{{value | default:“nothing”}}` |
default_if_none | `{{value | default_if_none:“null”}}` |
dictsort | `{{value | dictsort:“name”}}` |
dictsortreversed | `{{value | dictsortreversed:“name”}}` |
divisibleby | `{{value | divisibleby:arg}}` |
escape | `{{value | escape}}` |
escapejs | `{{value | escapejs}}` |
filesizeformat | `{{value | filesizeformat}}` |
first | `{{value | first}}` |
floatformat | `{{value | floatformat:arg}} 或 {{value |
get_digit | `{{value | get_digit:“arg”}}` |
iriencode | `{{value | iriencode}}` |
join | `{{value | join:“arg”}}` |
last | `{{value | last}}` |
length | `{{value | length}}` |
length_is | `{{value | length_is:“arg”}}` |
linebreaks | `{{value | linebreaks}}` |
linebreaksbr | `{{value | linebreaksbr}}` |
linenumbers | `{{value | linenumbers}}` |
ljust | `{{value | ljust}}` |
center | `{{value | center}}` |
rjust | `{{value | rjust}}` |
lower | `{{value | lower}}` |
make_list | `{{value | make_list}}` |
pluralize | `{{value | pluralize}} 或 {{value |
random | `{{value | random}}` |
removetags | `{{value | removetags:“tag1 tag2 tag3”}}` |
safe | `{{value | safe}}` |
safeseq | `{{value | safeseq}}` |
slice | `{{some_list | slice:“:2”}}` |
slugify | `{{value | slugify}}` |
striptags | `{{value | striptags}}` |
time | `{{value | time}}或 {{value |
truncatewords | `{{value | truncatewords:2}}` |
upper | `{{value | upper}}` |
urlencode | `{{value | urlencode}}` |
urlize | `{{value | urlize}}` |
wordcount | `{{value | wordcount}}` |
wordwrap | `{{value | wordwrap:5}}` |
timesince | `{{value | timesince:arg}}` |
timeuntil | `{{value | timeuntil}}` |
使用过滤器的过程中, 上下文, 管道符号'|'和过滤器之间没有规定使用空格隔开, 但为了符合编码的规范性, 建议使用空格隔开.
倘若过滤器需要设置参数, 过滤器, 冒号和参数之间不能有空格, 否则会提示异常信息, 如图6-6所示.
图6-6 异常信息
在实际开发中, 如果内置过滤器的功能不太适合开发需求, 我们可以自定义过滤器来解决问题.
以6.1.2小节的MyDjango为例, 在mydefined的templatetags里创建myfilter.py文件, 并在该文件里编写以下代码:
from django import template
register = template.Library()
@register.filter(name='replace')
def do_replace(value, agrs):
old_value = agrs.split(':')[0]
new_value = agrs.split(':')[1]
return value.replace(old_value, new_value)
过滤器与标签的自定义过程有相似之处, 但过滤器的定义过程比标签更简单, 只需定义相关函数即可.
上述定义的过滤器是实现模板上下文的字符替换, 定义过程说明如下:
● 函数do_replace由装饰器register.filter(name='replace')处理, 对函数执行过滤器注册操作.
● 装饰器参数name用于为过滤器命名, 如果没有设置参数name, 就以函数名作为过滤器名.
函数名没有具体要求, 一般以'do_过滤器名称'或'过滤器名称'作为命名规范.
● 参数value代表使用当前过滤器的模板上下文, 参数agrs代表过滤器的参数.
函数将参数agrs以冒号进行分割, 用于参数value(模板上下文)进行字符串替换操作,
函数必须将处理结果返回, 否则在使用过程中会出现异常信息.
为了验证自定义过滤器replace的功能, 将index的views.py和模板文件index.html的代码进行修改:
from django.shortcuts import render
def index(request):
value = 'Hello Python'
return render(request, 'index.html', locals())
{#导入自定义过滤器文件myfilter#}
{% load myfilter %}
<!DOCTYPE html>
<html lang="en">
<head>
<title></title>
<meta charset="UTF-8">
</head>
<body>
<div>替换前: {{ value }}</div>
<br>
<div>替换后:
{{ value | replace:'Python:Django' }}
</div>
</body>
</html>
模板文件index.html使用自定义过滤器时, 需要使用{% load myfilter %}导入过滤器文件,
这样模板引擎才能找到自定义过滤器, 否则会提示TemplateSyntaxError异常.
过滤器replace将模板上下文value进行字符串替., 将value里面的Python替换成Django, 运行结果如图6-7所示.
图6-7 运行结果
6.2 Jinja2模板引擎
Jinja2是Python里面被广泛应用的模板引擎, 它的设计思想来源于Django的模板引擎, 并扩展了其语法和一系列强大的功能.
其中最显著的是增加了沙箱执行功能和可选的自动转义功能, 这对大多数应用的安全性来说是非常重要.
此外, 它还具备以下特性:
● 沙箱执行模式, 模板的每个部分都在引擎的监督之下执行,
模板将会被明确地标记在白名单或黑名单内, 这样对于那些不信任的模板也可以执行.
● 强大的自动HTML转义系统, 可以有效地阻止跨站脚本攻击.
● 模板继承机制, 此机制可以使得所有模板具有相似一致的布局, 也方便开发人员对模板进行修改和管理.
● 高效的执行效率, Jinja2引擎在模板第一次加载时就把源码转换成Python字节码, 加快模板执行时间.
● 调试系统融合了标准的Python的TrackBack功能, 使得模板编译和运行期间的错误能及时被发现和调试.
● 语法配置, 可以重新配置Jinja2, 使得它更好地适应LaTeX或JavaScript的输出.
● 官方文档手册, 此手册指导设计人员更好地使用Jinja2引擎的各种方法.
Django支持Jinja2模板引擎的使用, 由于Jinja2的设计思想来源于Django的模板引擎,
因此Jinja2的使用方法与Django的模板语法有相似之处.
6.2.1 安装与配置
Jinja2支持pip指令安装, 我们按快捷键Windows+R打开'运行'对话框, 然后在对话框中输入'CMD'并按回车键, 进入命令提示符(也称为终端).
在命令提示符下输入以下安装指令: pip install Jinja2
(使用这个: pip install jinja2 -i https://pypi.tuna.tsinghua.edu.cn/simple 清华源下载,
下载最新版本, 老版本不我使用的时候不兼容了...)
输入上述指令后按回车键, 就会自行下载Jinja2最新版本并安装, 我们只需等待安装完成即可.
除了使用pip安装之外, 还可以从网上下载Jinja2的压缩包自行安装.
在浏览器上输入下载网址(www.lfd.uci.edu/~gohlke/pythonlibs/#sendkeys)并找到Jinja2的下载链接,
如图6-8所示(地址失效, 图片最废).
然后将下载的文件放到D盘, 并打开命令提示符窗口, 输入以下安装指令:
pip install D:\Jinja2?2.10?py2.py3?none?any.whl
输入指令后按回车键, 等待安装完成的提示即可.
完成Jinja2的安装后, 需要进一步校验安装是否成功, 再次进入命令提示符窗口,
输入'python'并按回车键, 此时进入Python交互解释器, 在交互解释器下输入校验代码:
>>> import jinja2
>>> jinja2.__version__
'3.1.3'
Jinja2安装成功后, 接着在Django里配置Jinja2模板.
由于Django的内置功能是使用Django的模板引擎, 如果将整个项目都改为Jinja2模板引擎, 就会导致内置功能无法正常使用.
在这种情况下, 既要保证内置功能能够正常使用, 又要使用Jinja2模板引擎, 只能将两个模板引擎共存在同一个项目里.
以MyDjango为例, 在MyDjango文件夹里创建jinja2.py文件,
文件名没有固定的命名要求, 读者可以自行命名, 该文件的作用是将Jinja2模板加载到MyDjango项目, 项目的目录结构如图6-9所示.
图6-9 目录结构
在PyCharm里打开jinja2.py文件, 在文件里定义函数environment,
并在函数里使用Jinja2的类Environment进行实例化, 实例化对象env用于对接Django的运行环境. 文件代码如下:
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
下一步将jinja2.py文件定义的函数environment写到配置文件settings.py中,
否则jinja2.py文件所定义的函数无法作用在MyDjango项目里.
在配置属性TEMPLATES中新增Jinja2模板引擎, 代码如下:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.jinja2.Jinja2',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {'environment': 'MyDjango.jinja2.environment'}
},
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'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是以列表形式表示的, 列表里定义了两个元素, 每个元素是以字典形式表示的, 说明如下:
● 第一个列表元素设置Jinja2模板引擎, 属性OPTIONS的environment是MyDjango文件夹的jinja2.py文件所定义的函数environment,
并且属性DIRS指向项目里的模板文件夹templates, 这说明模板文件夹templates里的所有模板文件皆由Jinja2模板引擎执行解析处理.
● 第二个列表元素设置Django的模板引擎, 属性OPTIONS的context_processors代表Django的内置功能,
如Admin后台系统, 信息提示和认证系统等, 也就是说Django的内置功能所使用的模板还是由Django的模板引擎执行解析处理的.
完成项目环境配置后, 通过简单的示例来验证MyDjango项目是否能同时使用内置模板引擎和Jinja2模板引擎.
在MyDjango的urls.pyindex的urls.py, views.py和模板文件index.html中编写以下代码:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include(('index.urls', 'index'), namespace='index'))
]
from django.urls import path
from .views import *
urlpatterns = [
path('', index, name='index'),
]
from django.shortcuts import render
def index(request):
value = {'name': 'This is Jinja2'}
return render(request, 'index.html', locals())
<!DOCTYPE html>
<html lang="en">
<head>
<title>Jinja2</title>
<meta charset="UTF-8">
</head>
<body>
<div>
{{ value['name'] }}</div>
</body>
</html>
看不舒服可以修改模板语言为Jinja2.
File --> Settings --> Languages & Frameworks --> Template Language --> 选择Jinja2 --> OK.
模板文件index.html的上下文value是字典对象, 而value['name']用于获取字典的属性name,
这种获取方式是Jinja2特有的模板语法, Django内置模板引擎是不支持的.
运行MyDjango项目, 分别访问: 127.0.0.1:8000 和 127.0.0.1:8000/admin, 发现两者都能成功访问.
网站首页是由Jinja2模板引擎解析的, 而Admin后台系统是由Django内置模板引擎解析的, 如图6-10所示.
图6-10 运行结果
6.2.2 模板语法
尽管Jinja2的设计思想来源于Django的模板引擎, 但在功能和使用细节上, Jinja2比Django的模板引擎更为完善,
而且Jinja2的模板语法在使用上与Django的模板引擎存在一定的差异.
由于Jinja2有模板设计人员帮助手册(官方文档: https://jinja.palletsprojects.com/en/3.0.x/)
并且官方文档对模板语法的使用说明较为详细, 因此这里只讲述Jinja3与Django模板语言的使用差异.
以6.2.1小节的MyDjango项目为例, 在模板文件夹templates里创建新的模板文件base.html, 该文件用于模板继承;
然后在根目录下创建文件夹static, 并在该文件夹里放置favicon.ico图片,
新建的文件夹static必须在settings.py中配置STATICFILES_DIRS, 否则Django无法识别文件夹static的静态资源;
STATIC_URL = '/static/'
STATICFILES_DIRS = [
BASE_DIR / 'static',
]
最后分别在模板文件base.html和index.html中编写以下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
{% block title %}
<title>标题</title>
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>
{# 模板继承 #}
{% extends 'base.html' %}
{% block title %}
{# static 标签 #}
{# Django 的用法: {% static 'favicon.png' %} #}
<link rel="icon" href="{{ static('favicon.ico') }}">
<title>Jinja2</title>
{% endblock %}
{% block body %}
<link rel="icon" href="{{ static('favicon.ioc') }}">
{# 使用上下文 #}
{# Django 的用法 {{ value.name }} #}
{# Jinja2 除了支持Django的用法, 还支持以下用法 #}
<div>
{{ value['name'] }}
</div>
{# 使用过滤器 #}
<div>
{{ value['name'] | replace('Jinja2', 'Django') }}
</div>
{# for 循环 #}
{# Django的用法 {% for k, v in value.items %} #}
{% for k, v in value.items() %}
<div> key is {{ k }} </div>
<div> value is {{ v }}</div>
{% endfor %}
{# if 判断 #}
{% if value %}
<div>This is if</div>
{% else %}
<div>This is else</div>
{% endif %}
{# url 标签 #}
{# Django 的用法: {% url 'index:index' %} #}
<a href="{{ url('index:index') }}">首页</a>
{% endblock %}
从上述代码得知, Jinja2与Django模板语法的最大差异在于static函数, url函数和过滤器的使用方式,
而模板继承这一功能上, 两者的使用方式是相同的, 对于Jinja2来说, 它没有模板标签这一概念.
在for循环中, Jinja2提供了一些特殊变量来获取循环信息, 变量说明如表6-4所示.
运行项目, 输入地址: http://127.0.0.1:8000/ .
变量 | 描述 |
---|
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(value) | 若上次迭代的值与当前迭代的值不同, 则返回True |
Jinja2的过滤器与Django内置过滤器的使用方法有相似之处, 也是由管道符号'|'连接模板上下文和过滤器,
但是两者的过滤器名称是不同的, 而且过滤器的参数设置方式也不同.
我们以表格的形式列举Jinja2的常用过滤器, 如表6-5所示.
过滤器 | 使用方式 | 说明 |
---|
abs | `{{ value | abs }}` |
default | `{{ value | default(‘new’) }}` |
escape | `{{ value | escape }}` |
first | `{{ value | first }}` |
last | `{{ value | last }}` |
length | `{{ value | length }}` |
join | `{{ value | join(‘-’) }}` |
safe | `{{ value | safe }}` |
int | `{{ value | int }}` |
float | `{{ value | float }}` |
lower | `{{ value | lower }}` |
upper | `{{ value | upper }}` |
replace | `{{ value | replace(‘a’,‘b’) }}` |
truncate | `{{ value | truncate(9,true) }}` |
striptags | `{{ value | striptags }}` |
trim | `{{ value | trim }}` |
string | `{{ value | string }}` |
wordcount | `{{ value | wordcount }}` |
表6-5 常用过滤器
6.2.3 自定义过滤器
Jinja2支持开发者自定义过滤器, 而且过滤器的自定义过程比Django内置模板更为便捷, 只需将函数注册到Jinja2模板对象即可.
我们以6.2.1小节的MyDjango为例, 在MyDjango文件夹的jinja2.py里定义函数myReplace,
并将函数注册到Jinja2的环境函数environment里, 这样就能完成过滤器的自定义过程.
jinja2.py的代码如下:
from django.contrib.staticfiles.storage import staticfiles_storage
from django.urls import reverse
from jinja2 import Environment
def my_replace(value, old='Jinja2', new='Django'):
return str(value).replace(old, new)
def environment(**options):
env = Environment(**options)
env.globals.update({
'static': staticfiles_storage.url,
'url': reverse
})
env.filters['my_replace'] = my_replace
return env
函数myReplace一共设置了3个参数, 每个参数说明如下:
● 参数value是过滤器的必选参数, 参数名可自行命名, 代表模板上下文.
● 参数old是过滤器的可选参数, 参数名可自行命名, 代表过滤器的第一个参数.
● 参数new是过滤器的可选参数, 参数名可自行命名, 代表过滤器的第二个参数.
函数environment将Jinja2引擎对接Django的运行环境, 它由实例化对象env实现对接过程,
在创建实例化对象env时, 只需在对象env中使用filters方法即可将自定义过滤器注册到Jinja2引擎.
为了验证自定义过滤器myReplace的功能, 我们在模板文件index.html里使用过滤器myReplace, 代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div>
{{ value.name | myReplace }}
</div>
<div>
{{ value.name | myReplace('This','That') }}
</div>
</body>
</html>
如果过滤器myReplace没有设置参数old和new,
就默认将字符串的'Jinja2'替换成'Django', 如果设置参数old和new, 就以设置的参数作为替换条件.
运行MyDjango项目, 在浏览器访问127.0.0.1:8000, 运行结果如图6-11所示.
图6-11 运行结果
6.3 本章小结
Django内置模板引擎包含模板上下文(亦可称为模板变量), 标签和过滤器, 各个功能说明如下:
● 模板上下文是以变量的形式写入模板文件里面的, 变量值由视图函数或视图类传递所得.
● 标签是对模板上下文进行控制输出, 比如模板上下文的判断和循环控制等.
● 模板继承隶属于标签, 它是将每个模板文件重复的代码抽取出来并写在一个共用的模板文件中,
其他模板文件通过继承共用模板文件来实现完整的网页输出.
● 过滤器是对模板上下文进行操作处理, 比如模板上下文的内容截取, 替换或格式转换等.
Jinja2是Python里面被广泛应用的模板引擎, 它的设计思想来源于Django的模板引擎, 并扩展了其语法和一系列强大的功能.
其中最显著的是增加了沙箱执行功能和可选的自动转义功能, 这对大多数应用的安全性来说非常重要.
此外, 它还具备以下特性:
● 沙箱执行模式, 模板的每个部分都在引擎的监督之下执行,
模板将会被明确地标记在白名单或黑名单内, 这样对于那些不信任的模板也可以执行.
● 强大的自动HTML转义系统, 可以有效地阻止跨站脚本攻击.
● 模板继承机制, 此机制可以使得所有模板都具有相似一致的布局, 也方便开发人员对模板进行修改和管理.
● 高效的执行效率, Jinja2引擎在模板第一次加载时就把源码转换成Python字节码, 加快模板执行时间.
● 调试系统融合了标准的Python的TrackBack功能, 使得模板编译和运行期间的错误能及时被发现和调试.
● 语法配置, 可以重新配置Jinja2, 使得它更好地适应LaTeX或JavaScript的输出.
● 官方文档手册, 此手册指导设计人员更好地使用Jinja2引擎的各种方法.