我们在前面大概了解了,Django的框架结构是MTV,要用到模板Template,模板的功能是生成HTML界面内容。要记住的是:模板主要功能是决定了界面如何显示,而不太关注程序逻辑。
一般情况页面上包含下面两种情况:
- 静态内容:通过css,JavaScript和html代码显示内容
- 动态内容:通过模板语言,动态的生成一些网页内容
所以说为了显示出用户所需要的页面,模板不仅仅是一个html文件,还包括了页面中的模板语言。
模板语言DTL,Django Template Language是主要实现了用变量来替换模板文件里指定的字符串或实现一些简单的逻辑功能。
想要了解更多,可以点击查看官方文档
先要了解一下DTL的格式,其实很简单,就两种,
- 和变量直接相关的,就是直接从后台获取变量值来替换内容
{{ 变量名 }}
- 和简单的逻辑相关的,各种for循环,if条件判定
{% 代码段 %}
而原生的两种方法,一个是tags,一个是filters,我们下面一个个来讲
变量的传递是直接用两个花括号包起来指定的变量名
{{变量名}}
支持的变量类型有很多种:字符串、列表、字典等,甚至对象也可以直接传递。
字符串、数值
name = '小明' age = 22 #模板语言 {{name}} {{age}}
列表
name_list = ['小明','小花','小黑','小紫'] #模板语言 #直接传递列表 {{name_list}} #列表截取 {{name_list.0}}
列表的切片要考后面的filter来实现。
字典
name_dic = {'firstname':'黄','lastname':'小明'} #模板语言 {{name_dic}} {{name_dic.firstname}}
对象
我们先定义一个类并把它实例化
class Person(): def __init__(self,name,age): self.name = name self.age = age def talk(self): return self.name + 'is talking' def __str__(self): return '<Person Object:{}>'.format(self.name) p1=Person('小黑',20)
下面就可以把这个对象传递给模板
{{person}} 直接传对象,相当与调用了p1()方法
{{person.name}}
{{person.age}}
{{person.talk}}
不太好理解的就是第一个,相当于在python文件中调用了p1()这个方法,实现了__str__()函数的调用。并且可以发现,在后面的调用中,不论是对象的属性还是方法我们都没有加括号。
filters的用法有些类似函数,但是传递参数的格式是用一个管道符|,大概是这样的:
{{ value|filter_name:参数 }}
value就是我们需要替换的字符,filter_name类似与函数名,参数通过冒号跟filter分看
defalut
default是当变量在没有值传递进来的时候,可以指定一个值输出
{{name|default:'变量未传值'}}
如果views模板中通过render调用HTML文件的时候,给name赋值了,页面显示的就是我们传递过来的只,否则就直接显示‘变量未赋值’。
length
{{name|length}}
获取元素长度。这里的元素长度可以是字符串的长度,也可以是列表的长度,就是传递过来的是什么类型,就获取对应类型的长度值。
filesizeformat
{{data_size|filesizeformat}}
一般情况data_size都是一个数值或一个数值形式的字符串,通过这个方法可以将数值按照文件大小的方式显示出来:1024就对应1Kb,以此类推,要注意的是这里的换算关系是1024,而不是简化成1000。
slice
{{data|slice:'1:5'}}
切片,操作对象可以是字符串、列表等,和python中的切片一样,支持用负数表示从右索引。要注意的是参数要加引号。
date
{{now|date:'m-d-Y H:m:s'}}
date为时间格式化。now是一个时间的对象,未经格式化的显示效果是这样的:
March 13, 2020, 9:55 a.m.
但是用上面的方式进行格式化后就可以这样显示
03-13-2020 09:03:03
这里有些格式字符串要记住
格式化字符 | 描述 | 示例输出 |
---|---|---|
a | 'a.m.' 或'p.m.' (请注意,这与PHP的输出略有不同,因为这包括符合Associated Press风格的期间) |
'a.m.' |
A | 'AM' 或'PM' 。 |
'AM' |
b | 月,文字,3个字母,小写。 | 'jan' |
B | 未实现。 | |
c | ISO 8601格式。 (注意:与其他格式化程序不同,例如“Z”,“O”或“r”,如果值为naive datetime,则“c”格式化程序不会添加时区偏移量(请参阅datetime.tzinfo ) 。 |
2008-01-02T10:30:00.000123+02:00 或2008-01-02T10:30:00.000123 如果datetime是天真的 |
d | 月的日子,带前导零的2位数字。 | '01' 到'31' |
D | 一周中的文字,3个字母。 | “星期五” |
e | 时区名称 可能是任何格式,或者可能返回一个空字符串,具体取决于datetime。 | '' 、'GMT' 、'-500' 、'US/Eastern' 等 |
E | 月份,特定地区的替代表示通常用于长日期表示。 | 'listopada' (对于波兰语区域,而不是'Listopad' ) |
f | 时间,在12小时的小时和分钟内,如果它们为零,则分钟停留。 专有扩展。 | '1' ,'1:30' |
F | 月,文,长。 | '一月' |
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 | 星期几,文字长。 | '星期五' |
L | 布尔值是否是一个闰年。 | True 或False |
m | 月,2位数字带前导零。 | '01' 到'12' |
M | 月,文字,3个字母。 | “扬” |
n | 月无前导零。 | '1' 到'12' |
N | 美联社风格的月份缩写。 专有扩展。 | 'Jan.' ,'Feb.' ,'March' ,'May' |
o | ISO-8601周编号,对应于使用闰年的ISO-8601周数(W)。 对于更常见的年份格式,请参见Y。 | '1999年' |
O | 与格林威治时间的差异在几小时内。 | '+0200' |
P | 时间为12小时,分钟和'a.m。'/'p.m。',如果为零,分钟停留,特殊情况下的字符串“午夜”和“中午”。 专有扩展。 | '1 am' ,'1:30 pm' / t3>, |
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 |
T | 本机的时区。 | 'EST' ,'MDT' |
u | 微秒。 | 000000 to 999999 |
U | 自Unix Epoch以来的二分之一(1970年1月1日00:00:00 UTC)。 | |
w | 星期几,数字无前导零。 | '0' (星期日)至'6' (星期六) |
W | ISO-8601周数,周数从星期一开始。 | 1 ,53 |
y | 年份,2位数字。 | '99' |
Y | 年,4位数。 | '1999年' |
z | 一年中的日子 | 0 到365 |
Z | 时区偏移量,单位为秒。 UTC以西时区的偏移量总是为负数,对于UTC以东时,它们总是为正。 | -43200 到43200 |
safe
要理解这个safe的概念就要这里就要扯出来一个概念了——XSS攻击,也叫跨站脚本攻击Cross Site Scripting。
比方说有个页面提供了其他用户评论或者留言的窗口,理论上评论的字符串是要显示在页面上的。但是如果游客在评论的时候使了个坏,在评论中加了下面的一段代码
<script>for(var i = 1;i<10;i++){alert(123)}</script>
那么理论上这段代码是直接添加在html脚本里的,那么在每次打开页面的时候就会不停的弹出消息框。这就是最简单的XSS攻击。
但是事实情况是即便我们在留言里加了这句代码也不会发生上面所说的情况,那是因为页面在进行渲染的时候直接把JS代码按照字符串的效果直接显示在页面上。
可是在某些环境下,我们需要直接把字符串按照HTML的脚本渲染,比如我们做一个内容管理系统,后台添加的文章中是经过修饰的,这些修饰可能是通过一个类似于FCKeditor编辑加注了HTML修饰符的文本,如果自动转义的话显示的就是保护HTML标签的源文件。为了在Django中关闭HTML的自动转义有两种方式,其中的一个就是通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义就可以用到这个safe的filter了
a_html = "<a href='http://www.baidu.com'>我是从后端传来的a标签</a>" {{a_html|safe}}
这样就会在页面上直接显示出一个a标签。
truncatechars
{{str|truncatechars:10}}
这个方法用来截取指定数量的字符串,页面上只会显示指定数量的字符串,超出的部分会用...来代替。
自定义filter
这个比较重要,如果Django内置的filters无法满足需要的时候,我们还可以自定义filter。自定义的filter实际上就是可以带有一个或两个参数的Python函数。
自定义的filter前,我们要在app的文件夹下先创建一个名为templatetags的文件夹(不能错,必须是这个文件名),然后在这个文件夹内新建一个py文件,文件名为myfilter.py这个名字是可以自己定的,但是在模板内要导入这个文件的时候要和这个文件名一致
然后编写自定义的filter
from django import template register = template.Library() @register.filter(name = 'filter1') def fun1(arg): #arg就是管道符前面的变量 return '{} 调用了filter1'.format(arg) @register.filter(name = 'filter2') def fun2(arg,arg2): return '{} {}'.format(arg,arg2)
在上面的代码中,我们自定义了两个函数,然后通过装饰器把他修饰成filter,名字也通过name=''的参数指定好了。注意第一个函数定义的时候只带有一个形参,是可以直接调用的。
{{var|filter1}}
用上面的方式,就可以直接调用,页面上显示的效果就是传过来的字符串后加上一个字符串:‘调用了filter1’
而第二个函数fun2我们定义了两个形参arg1和arg2,那么在使用的时候就要在加上一个参数了
{{var|filter:'hehehe'}}
显示出来就会把var的字符串和hehehe拼接在一起。
但是在使用自定义filter的时候一定要记得在模板里进行导入
{% load myfilter %}
因为刚才创建的py文件名就是myfilter,所以这里导入的库名字就是myfilter。
Tags这个东西比较不好解释,我们直接看看效果吧!
for循环
for循环的格式
{% for ......%}
循环体
{% endfor %}
for循环里的可用tag
Variable | Description |
---|---|
forloop.counter |
当前循环的索引值(从1开始) |
forloop.counter0 |
当前循环的索引值(从0开始) |
forloop.revcounter |
当前循环的倒序索引值(从1开始) |
forloop.revcounter0 |
当前循环的倒序索引值(从0开始) |
forloop.first |
当前循环是不是第一次循环(布尔值) |
forloop.last |
当前循环是不是最后一次循环(布尔值) |
forloop.parentloop |
本层循环的外层循环 |
我们可以通过上面这些tag来获取到在for循环内循环体的一些信息。
empty
empty通常被用在for循环里,因为我们一般用for循环来显示模板中的指定内容,但是如果要显示的内容为空的时候,显示出来就是个空的表格
就像上面的图里,在没有内容的时候表格就是个空的,那么用户就不知道是页面刷新出现了问题还是怎么回事,我们就可以用empty来指定内容
{% for publisher in publishers %} <tr> <td>{{publisher.id}}</td> <td>{{publisher.name}}</td> <td> <a class="btn btn-info" href="/edit_publisher/?id={{publisher.id}}">编辑</a> <a class="btn btn-danger" href="/delete_publisher/?id={{publisher.id}}">删除</a> </td> {% empty %} <td>无数据</td> <td>无数据</td> <td>无数据</td> {% endfor %} </tr>
那么显示的效果就是这样的
那么用户在打开页面的时候就能确认是没有数据了。
if判断
if判断支持单一条件的if...else判断,也支持多条件if...elif...else判断,格式如下:
{%if ... %}
代码
{% elif ....%}
代码
{%else%}
代码
{%endif%}
注意最后的endif不要忘记了。
if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断,但是要注意的是不要采用a>b>c这种的连续比较的方法,因为如果a>b的话这个表达式的值就成1了,如果1比c的值小的情况整个表达式的值就为false了。
a=7,b=6,c=5,如果用了{%if a>b>c%}则走的是false的分之。如果需要这种条件的话直接写成{% if a>b and b>c %}。
with语句
with语句用于在使用中间变量的时候,给一个复杂的变量名起一个简化的别名
{% with total=business.employees.count %}
{{ total }} employee{{ total|pluralize }}
{% endwith %}
要注意的是等号的两边一定不要加空格。但是新启用的变量名只能在with到endwith之间的缩进段里面使用。
注释
{#......#}
模板里的注释部分是不会显示在页面上的(这里的显示不是说直接显示在页面上,而是连代码审查或查看源代码里都看不到!)
母版
有些时候,我们的页面的大框架是固定的,比如博客园的首页
在我们点击最上面那排首页、精华什么的链接的时候,整体的框架是不会变的,只是里面的具体内容会发生变化,我们可以把整个页面都复制过来然后每一个修改,这是个比较笨的方法,也可以做一个母版,就是把里面相同的部分拿出来放在一个html文件里,然后把不同的地方空出来留一个block,在子页面里先继承这个母版然后再对block进行替换,就可以了。看看下面的html代码
<!-- 文件名:BaseHtml.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <h1>这是母版的标题</h1> {% block main_page%} {% endblock %} </body> </html>
我们在里面留了个block,可以在子页面里对这个block进行替换。
母版的继承
子页面要使用母版的话,要对母版进行继承。我们上面的母版的文件名是BaseHtml.html,那么就可以用下面的代码来继承。
{% extends 'BaseHtml.html' %}
一定要记得加引号,切记切记~~
子页面的block
子页面在继承了母版以后,就可以对母版中的block进行替换了
{% block main_page %} <p>这是子页面</p> {% endblock %}
替换时注意block的名字要和母版中待替换掉block保持一致(母版中可能有多个带替换的block)
如果很多个页面都要用一个控件(比方说导航条)或者js、css代码的话,我们可以把这段代码放在一个html文件里。然后用下面的方法导入
{% include '组件名.html' %}
要注意的是,母版和组件是不冲突的,其实就是完成了字符串的拼接。但是在母版和组件在使用的时候要注意组件放的位置,一定要放在能显示出来的body内。
静态文件是指css或者js文件,我们前面在使用引入静态文件的时候是在settings.py文件中指定了 STATIC_URL这个目录然后在模板中直接调用的方式。
但是这样是有个问题的,如果一个大的项目里html文件有非常多,但是这个路径需要修改或者要发生什么变动,这种写死的方法是不太合理的,我们就可以用下面的方法
{% load static %} <link rel="stylesheet" href="{% static bootstrap/css/bootstrap.min.css %}">
这样就是去settings.py文件中查找静态文件夹的别名然后自动进行路径的拼接。那么如果我们在设置文件中把STATIC_URL的值改变了,模板文件是不需要修改的。
获取STATIC的值
{% load static %} <img src="{% get_static_prefix"%} image/pic.jpg> </img>
这里就是用get_static_prefix来获取STATIC_URL的值来手动拼接。这种方法不太常用,了解就可以了。
静态文件重复使用
如果一个静态文件需要别重复使用,比如一个文件名为1.jpg的图片,我们就可以用下面的方法来简化代码:
{% load static %} {% static "images/1.jpg" as pic %} <img src="{{pic}}"></img>
自定义的tag分两种:
- simple_tag 对变量进行操作然后替换
- inclusion_tag 用来返回一段html代码
两个建立的方式差不多,就是在注册的时候有些许的区别。自定义tag和自定义filter的效果差不多,但是可以接受超过2个的参数。
simple_tag
simple_tag的创建比较简单,和filter的定义方式差不多
from django import template register = template.Library() @register.simple_tag(name='my_tag1') def my_sum(arg1,arg2,arg3): return "{}+++{}---{}".format(arg1,arg2,arg3)
这个tag就实现了三个字符串的拼接功能。然后在使用的时候,也需要导入这个包
{%load mysimpletag%}
{% my_tag1 'a1' 'b2' 'c3'%}
我们的simple_tag的代码是放在一个mysimpletag.py文件中的,文件路径和前面的自定义filter文件同级。所以用上面的load方式就可以。tag中参数传递是没有管道符的,直接空格分割就可以。
inclusion_tag
inclusion_tag的概念不太好理解,首先它需要一个html文件a,在py文件中定义好函数的逻辑关系,还要有个调用这个tag的html文件b。b在调用tag的时候通过a和py中的函数创建了一段html代码,最后这段代码被嵌套在a中。
先看看这个自定义的tag吧
比方我们需要在页面上动态的显示列表,也就是说有的页面要显示10项,有些页面要显示30项,这就要用到这个inclusion_tag了。
#文件名:mysimpletag.py,路径在app下 from django import template register = template.Library() @register.inclusion_tag('result.html') def show_result(n): n = 1 if n<1 else int(n) data = ["第{}项".format(i) for i in range(1,n+1)] return {'results':data}
show_result函数返回了列表里需要显示的数据,用for循环生成,生成的元素个数又参数传进来。函数的返回值直接填到文件b中,
然后是html文件b
<!--文件名:result.html--> <ul> {% for choice in results %} <li>{{choice}}</li> {% endfor %} </ul>
b文件相当与一个模板文件,在收到待替换掉 字符串以后直接生成一段html文件,就成了一个类似组件的效果。
最后是调用这个tag的html文件a
{%load mysimpletag%}
{%show_result 100 %}
a调用了tag,然后tag生成了一段html代码返回给a,如果另外一个html文件也调用了这个tag,也可以通过传递的参数改变显示的列表数量
要注意的地方:
注册的时候一定要注意文件名不要搞错,要跟对应的html文件名相同。调用tag的时候用的是声明的函数名。
自定义的filter和tag的方法基本上是一样的,按照相应的方法去写就行了,但是什么时候要用filter,什么时候要用tag呢?
要注意传递的参数,一般用tag传递的数据都是和业务逻辑没有关系的数据,而一般通过render传递的数据一般都是和业务逻辑相关的数据,好多都是从数据库里取出来的。