本篇介绍Django的模板语言(The Django template language)。
通过这篇文章,你能了解到:
- 模板是什么?
- 变量与过滤器
- 使用标签来执行一段逻辑
- 自定义过滤器,simple_tag,inclusion_tag
- 使用模板继承和组件来简化代码
模板
模板只是一个text文件,他可以生成任意基于文本的格式(HTML,XML,CSV等)。
模板中包含变量,这些变量在评估模板时将替换为值,而变量则包含控制模板逻辑的标记。
Django模板中只需要记两种特殊符号:
{{ }} 和 { % % }
{{ }} 表示变量
{ % % } 表示逻辑相关的操作。
推荐阅读 官方文档 。
变量
{{ 变量名(key)}} —> v
变量名由字母数字和下划线组成,变量名称中不能包含空格或标点符号。
点 .
在模板语言中有特殊的含义,遇到一个点时,会按以下顺序去查找:
字典查找
属性或方法查找 (方法不能带参数)
数字索引查找
这个顺序体现在:
1 | dic = { |
1 | {{ dic.keys }} # 显示 xxxxx , 而不是dict_keys(['cc']) |
note:
- 没有
[]
这种写法。所以对于列表的话就没有[index]
而是.index
、对于字典的话就没有[key]
而是.key
- 没有
()
这种写法,所以对于方法是不需要加括号的,不识别括号 - 索引只能是正向索引,负数识别不了
1 | # views.py |
1 | # index.html |
note:模板中可以直接使用request
Filters
使用过滤器修改变量的显示结果。
语法:
{{ value|filter_name}}、{{ value|filter_name:参数}} 过滤器最多只有一个参数!
{{ text|escape|linebreaks }} 可以一次使用多个管道符
note: 只有 :
左右不能有空格,不能有空格!
default
提供默认值
如果传过来的变量不存在/或为空,也可以用default,来进行显示。
如果使用不存在的变量,模板系统将插入
string_if_invalid
选项的值,默认情况下设置为(空字符串)。
1 | # index.html |
修改setting.py 的 OPTIONS , 当这个传递的变量在views.py
中没有定义,就是用默认值
如果 default 与 它一起,还是显示”找不到”。
1 | TEMPLATES = [ |
slice
切片:参数和以前使用方式是一样的。
1 | # index.html |
filesizeformat
将值格式化为一个 “人类可读的” 文件尺寸 (例如 ‘1KB’, ‘4.1 MB’, ‘102 bytes’ 等等)
1 | context = { |
1 | # index.html |
add
- 数字的加法
- 字符串拼接
- 列表拼接
1 | {{ '1'|add:'3' }} # 数字的加法(可以同时是字符串) |
lower/upper/title
- 小写
- 大写
- 标题
1 | context = { |
1 | {{ st }} # HeLLo |
ljust/rjust/center
- 左对齐
- 右对齐
- 居中
1 | {{ st|ljust:"15" }} |
note:但是由于HTML会有空白折叠,所以很鸡肋。
结果只是显示:
1 | HeLLo HeLLo HeLLo |
length/length_is
- 0 返回 value 的长度
1 | {{ st|length }} # 5 |
first/last
取第一个/最后一个元素
不用
.0
或.-1
方式是因为找不到的话会调用string_if_invalid
.first
和.last
在找不到的话,显示为空
1 | li = [] |
1 | {{ li.0 }} # 找不到 |
join
使用字符串拼接列表。同 python 的 str.join(list)
1 | {{ name_list|join:'--' }} |
truncatechars
如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列(“…”)结尾。(三个点也算在内)
truncatechars:3 三个点
truncatechars:1 三个点
1 | context = { |
1 | {{ value|truncatechars:9 }} # {{ value|truncatechars:9 }} |
truncatewords
按空格截断,有几个空格截几次。
1 | {{ value|truncatewords:2 }} |
date
日期格式化:
'Y-m-d H:i:s'
note:字符串中写的规则和python的datetime模块中是不一样的,不需要加
%
,而且有些字母表示也不一样。
1 | context = { |
1 | {{ datetime_now|date:'Y-m-d H:i:s' }} |
设置显示格式
settings中可以配置,配置后,就会设置显示格式:
1 | USE_L10N = False # 使这个设置生效 |
1 | {{ datetime_now }} # 2019-08-28 17:24:18 |
safe
背景
XSS:跨站脚本攻击(Cross Site Scripting),为了不和层叠样式表(Cascading Style Sheets,
CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。Web存在XSS跨站脚本攻击,所以为了安全,Django中的每个模板都会自动转义每个变量标记的输出。
例如:评论中,写的代码,需要进行转义,不然的话展示页面的时候会自动执行!
转义:就是把html语言的关键字过滤掉。例如Django中这5个字符被转义为HTML实体,防止浏览器将其作为HTML元素:
<
转换为<
>
转换为>
'
(单引号)转换为'
"
(双引号)转换为"
&
转换为&
推荐阅读 DOMXSS典型场景分析与修复指南
safe
- 告诉django不需要做转义,可以执行。
1 | context = { |
1 | {{ js }} # 默认使转义 |
上面是在模板中标记安全,还可以在views.py 中标记安全
1 | from django.utils.safestring import mark_safe |
1 | {{ js1 }} # 执行js代码 |
推荐阅读:其它过滤器
自定义filter
自定义过滤器步骤:
先做app01下创建一个python包,名为
templatestags
在这个包内创建py文件,文件名可以自定义 my_tags.py
在这个创建的py文件内写入:
1
2
3
4
5
6from django import template
register = template.Library() # register的名字不能变
或
from django.template import Library
register = Library()在这个文件内定义函数
1
2def new_upper(value,arg=None): # arg最多有一个
return value.upper()给函数加上装饰器就成为了过滤器
1
2
3
4# @register.filter(name='xxx') # 加上(name='xxx'),就改变了最终过滤器的名字。
def new_upper(value,arg=None):
return value.upper()
在模板中使用:
1 | 导入: |
Tags
注释(Comments)
- 这里的注释表示不会再浏览器中渲染的,而js或html的注释还是会进行渲染的。
1 | {# ... #} |
widthratio
- 计算乘除
1 | {% widthratio 200 1 5 %} # 200/1*5 = 1000 |
for
1 | <ul> |
forloop字典:
forloop是一个字典:
1 | {'parentloop': {}, 'counter0': 0, 'counter': 1, 'revcounter': 5, 'revcounter0': 4, 'first': True, 'last': False} |
key | Description |
---|---|
forloop.counter | 当前循环的索引值(从1开始) |
forloop.counter0 | 当前循环的索引值(从0开始) |
forloop.revcounter | 当前循环的倒序索引值(到1结束) |
forloop.revcounter0 | 当前循环的倒序索引值(到0结束) |
forloop.first | 当前循环是不是第一次循环(布尔值) |
forloop.last | 当前循环是不是最后一次循环(布尔值) |
forloop.parentloop | 本层循环的外层循环 |
例子:使偶数行偶数列对应的元素的背景变为红色
内层循环决定列,外层循环决定行
模板内使用
divisibleby
表示能否整除,从而选出偶数列。
1 | <table> |
for … empty
1 | {% for i in ll %} |
if、elif、else
<
等比较符 左右必须有个空格
1 | {% if p1.age < 18 %} |
note:
- 条件中可以加过滤器,但是不能嵌套标签。
- 条件中不支持 算数运算
- 条件中不支持 %取余 –> 过滤器 divisibleby:2 能否被2整除
- 条件中也不支持连续判断 a > b >c –> a > b and a < c 是可以的
- python的连续判断:10>5>1 True(10>5) and True(5>1)
- js的连续判断: 10>5==1 True(10>5)==1
- 条件中如果写了连续判断,虽然会飘红,但逻辑和js的连续判断相同
- if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断。
1 | {% if 10>5>1 %} |
with
- 定义一个中间变量
1 | {{person_list.1.name}} |
csrf_token
背景
csrf:跨站请求伪造
要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。
在Django中,服务器会给客户端发送带有 csrf_token 的表单,如果回执中没有这个 csrf_token ,则不做接收。
使用
1 | <form action="" method="post"> |
- 标签放在form标签中,form表单中有一个隐藏的标签 name = ‘csrfmiddlewaretoken’ value
关于静态文件配置
如果在配置中将 /static/
给变了,那么按照以前的方式,都该改变导入的前缀。
为了更加灵活,我们将这样做
- 标签
1 | {% load static %} |
1 | {% get_static_prefix %} # 获取静态文件前缀 |
补充
1 | {% url 'home' %} |
当我们在url中设置name参数为 home,就可以反向解析。
详见 Django的路由系统
自定义tag
simple_tag
- 优:和自定义filter相比,可以定义更多的参数
- 缺:他是一个tag,不能嵌套
1 |
|
1 | {% join_str 'v1' 'v2' k3='v3' k4='v4' %} |
note:要用空格隔开
inclusion_tag
- 组件虽然细粒度,但它不能灵活的使用变量
inclusion_tag
可以返回一个动态的页面,是一个不错的选择
1 |
|
- 分页的html,由于页数是可变的,这需要传参得到,context充当这个运输工具。
1 | {# page.html #} |
- 这样在使用的时候先load,再写tag标签即可,这这个html就加入了分页!
1 | # author_list.html |
定义 filter simple_tag inclusion_tag
相同的步骤
先做app01下创建一个python包,名为
templatetags
在这个包内创建py文件,文件名可以自定义 my_tags.py
在这个创建的py文件内写入:
1
2
3
4
5
6from django import template
register = template.Library() # register的名字不能变
或
from django.template import Library
register = Library()定义函数 + 装饰器
1
2
3
4
5
6
7
8
9
10
11
def show_a_plus(value, arg=None): # arg最多有一个
return mark_safe(f"<a href='value'>{arg}</a>")
def join_str(*args, **kwargs): # 参数多少都可以
return '_'.join(args) + '-' + '*'.join(kwargs.values())
def page(num):
return {'num':range(1,num+1)}
不同点
参数:
- 自定义filter的参数数量是有限的
- inclusion_tag 后必须加模板的名字
- simple_tag无限制
返回值:
- inclusion_tag 的返回值必须是一个字典 ,相当于render的第三个参数,做渲染
- 自定义filter 和 simple_tag对返回值没有限制
inclusion_tag 多了一步渲染的过程 ,另外两个至少是做一个返回。
过滤器可以放在 if 判断中,tag不行。
模板继承
- Django模板引擎中最强大、最复杂的是模板继承。模板继承允许您构建一个基础“骨架”模板,其中包含站点的所有常用元素,并定义子模板可以覆盖的块。
模版:
- html页面,提取多个页面的公共部分
- 定义多个 block 块,需要让子页面覆盖填写
1 |
|
继承:
{ % entends ‘模板文件名’ % }
重写 block 来覆盖母版。
note:
block可大可小,可以是一个table,form,可以是一个属性(字符串)。
'模板的文件名'
是一个字符串(值),如果不加引号,就会当成一个变量extends 上面不要写入其它内容,extends前面写入内容,是会在html页面内显示的,如果写在后面且不在block内,则不会显示。
要显示的内容都要写在block块内。
对于每个子页面单独需要的样式,专门定义个block块,然后再子页面中补全。
理解:
- 母版相当于一块布,布里面有一些补丁;在继承的时候,复制了一块布过来,这些补丁可以自行更改。
- 解析子模版并在被父模版包含的情况下展现其被父模版定义的内容。
组件
组件:html 页面, 包含一小段代码
目的:更加细粒度的拿到代码段。
可以使用字符串也可以使用变量名
理解:类似python中的import,更准确的是一种”将子模版渲染并嵌入当前HTML中“的变种方法,而不应该看作是”解析子模版并在被父模版包含的情况下展现其被父模版定义的内容”。这意味着在不同的被包含的子模版之间并不共享父模版的状态,每一个子包含都是完全独立的渲染过程。
1 | {% include '组件名' %} |
小结
本篇介绍了django的模板系统,目标定制一个动态的,简洁的页面。
下一篇为 Django的View,这是第二个需要了解的细节,学习了这一块,就可以完善我们的业务逻辑。