一 引子
什么是模版系统?这里做一个简单解释。要想明白什么是模版系统,那么我们得先分清楚静态页面和动态页面。我们之前学过的都是静态页面,所谓的静态页面就是浏览器向后端发送一个请求,后端接收到这个请求,然后返回给浏览器一个html页面,这个过程不涉及从数据库取出数据渲染到html页面上,只是单纯的返回一个页面(数据全部在html页面上)。而动态页面就是在给浏览器返回html页面之前,需要后端与数据库之间进行数据交互,然后将数据渲染到html页面上在返回给浏览器。言外之意静态页面不涉及数据库,动态页面需要涉及从数据库取出数据。那么模版系统是什么呢?如果你只是单纯的写静态页面,也就没必有必要用模版系统了,只用动态页面才需要模版系统。
简单来说,模版系统就是在html页面想要展示的数据库或者后端的数据的标签上做上特殊的占位(类似于格式化输出),通过render方法将这些占位的标签里面的数据替换成你想替换的数据,然后再将替换数据之后的html页面返回给浏览器,这个就是模版系统。
模板渲染的官方文档
关于模板渲染你只需要记两种特殊符号(语法):
{{ }}和 {% %}
变量相关的用{{}},逻辑相关的用{%%}。
二、变量
1. 简单示例
接下来,我们先搭一个简单流程:浏览器访问https://127.0.0.1:8000:/index,返回一个index.html页面,我们在views函数中设置一些变量,然后通过模版系统渲染,最终返回给浏览器。
url:
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^index/',views.index), ]
views:
render第三个参数接受一个字典的形式,通过字典的键值对index.html页面进行渲染,这也就是模块渲染。
def index(request): name = '太白金星' age = 18 name_list = ['王阔', '天琪', '傻强', '志晨', '健身哥'] dic = {'classname': '人工智能', 'since': 2019} return render(request, 'index.html',{'name': name, 'age': age,'name_list':name_list, 'dic_class':dic})
html:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <h1>你好,世界!</h1> <ul> {# 通过render进行模版语言渲染然后替换成后端的数据,最终发给浏览器 #} <li>{{ name }}</li> <li>{{ age }}</li> <li>{{ name_list }}</li> <li>{{ dic_class }}</li> </ul> </body> </html>
最终浏览器显示的结果为:
这样,你后端这些变量全部都渲染到前端了。
2. 语法
在Django的模板语言中按此语法使用:{{ 变量名 }}。
当模版引擎遇到一个变量,它将计算这个变量,然后用结果替换掉它本身。 变量的命名包括任何字母数字以及下划线 ("_")的组合。 变量名称中不能有空格或标点符号。
深度查询据点符(.)在模板语言中有特殊的含义。当模版系统遇到点("."),它将以这样的顺序查询:
字典查询(Dictionary lookup)
属性或方法查询(Attribute or method lookup)
数字索引查询(Numeric index lookup)
3. 万能的点 .
通过简单示例我们已经知道模版系统对于变量的渲染是如何做到的,非常简单,接下来我们研究一些深入的渲染,我们不想将整个列表或者字典渲染到html,而是将列表里面的元素、或者字典的某个值渲染到html页面中,那怎么做呢?就是通过万能的点。
views:
def index(request): name_list = ['王阔', '天琪', '傻强', '志晨', '健身哥'] dic = {'classname': '人工智能', 'since': 2019} lis = [1, ['冠状病毒', '武汉加油'],3] # 使用locals是用于测试,实际生产环境中是不可以的。 return render(request, 'index.html', locals())
html:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <h1>你好,世界!</h1> <ul> {# 万能的点 #} <li>{{ name_list.2 }}</li> <li>{{ dic.classname }}</li> <li>{{ lis.1.0 }}</li> </ul> </body> </html>
浏览器的显示结果:
刚才我们尝试的数据类型,那么在一切皆对象的python世界中,我们一个对象是否可以通过模版渲染到html页面中呢?
views:
def index(request): class A: def __init__(self): self.name = 'barry' def func(self): return '太白教你学python' obj = A() return render(request, 'index.html', locals())
html:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <h1>你好,世界!</h1> <ul> {# 万能的点 #} <li>{{ obj.name }}</li> <li>{{ obj.func }}</li> </ul> </body> </html>
浏览器显示的结果:
如图所示,对象也是可以通过模版渲染到html页面中的,但是这里要注意一个点:obj.func是不可以加括号的,所以我们后端设置的函数是不可以有参数的(类中的方法self自动传递)。
注意我们直接在js代码中使用模板语法的时候,模板渲染的时候会有个转义的动作,将s = ['哈哈','xx']这种数据中的元素的引号变为一个特殊符号:这个我们后面会讲到
<script>
// 不加safe的话,引号会被转义。
// var a = {{ s }}
// var a = ['哈哈', 'xx'];
// console.log(a[0])
// 加上safe就正常了
var a = {{ s|safe }};
console.log(a[0])
// 还要注意,当我们模板渲染的时候,后端返回的数据是字符串的话,我们需要将{{ s }}外面加上引号
比如s = '哈哈'
js中的写法
var a = '{{ s }}'
</script>
三、过滤器
1.什么是过滤器
有的时候我们通过render渲染到html的数据并不是我们最终想要的数据,比如后端向前端传递的数据为hello,但是我们html想要显示为HELLO,当然这个在后端是可以提前处理的,但是诸如此类的需求我们可以通过过滤器解决,过滤器给我们提过了很多便捷的方法,加工你传递到html的数据,便于灵活开发。
2.语法
过滤器的语法: {{ value| filter_name:参数 }}
使用管道符"|"来应用过滤器。
例如:{{ name| lower }}会将name变量应用lower过滤器之后再显示它的值。lower在这里的作用是将文本全都变成小写。
注意事项:
- 过滤器支持“链式”操作。即一个过滤器的输出作为另一个过滤器的输入。
- 过滤器可以接受参数,例如:{{ sss|truncatewords:30 }},这将显示sss的前30个词。
- 过滤器参数包含空格的话,必须用引号包裹起来。比如使用逗号和空格去连接一个列表中的元素,如:{{ list|join:', ' }}
- '|'左右没有空格!没有空格!没有空格!
Django的模板语言中提供了大约六十个内置过滤器。
3.常用过滤器
default: 如果一个变量是false或者为空,使用给定的默认值。 否则,使用变量的值。
views: a = '' # 没有变量a或者变量a为空
html: # 显示啥也没有 {{ value|default:"啥也没有"}}
length:返回值的长度,作用于字符串和列表。
views: name_list = ['王阔', '天琪', '傻强', '志晨', '健身哥'] s = '太白金星讲师' html: {{ name_list|length }} # 显示为5 {{s|length }} # 显示为6
filesizeformat: 将值格式化为一个 “人类可读的” 文件尺寸 (例如 '13 KB', '4.1 MB', '102 bytes', 等等)。
views: value = 1048576 html: {{ value|filesizeformat }} # 显示为1.0MB
切片,支持pyhton中可以用切片的所有数据类型
views: name_list = ['王阔', '天琪', '傻强', '志晨', '健身哥'] s = '太白金星讲师' html: {{ name_list|slice:'1:3' }} # ['天琪', '傻强'] {{ s|slice:'1::2' }} # '白星师'
date:时间格式化
views: time = datetime.datetime.now() html: {{ t|date:"Y-m-d H:i:s" }} # 2020-02-11 07:31:29
关于时间日期的可用的参数(除了Y,m,d等等)还有很多,有兴趣的可以去查查看看。
truncatechars:如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列(“...”)结尾。
参数:截断的字符数
views: describe = '1999年3月,马云正式辞去公职,后来被称为18罗汉的马云团队回到杭州,凑够50万元人民币' html: {{ describe|truncatechars:9 }} # 1999年3... # 截断9个字符,三个点也算三个字符
这个我们在浏览网页时经常见到,描述的内容很多,只能用...显示,比如:
truncatewords:在一定数量的字后截断字符串,是截多少个单词。
views: words = 'i love you my country china' html: {{ words|truncatewords:3 }} # i love you...
cut: 移除value中所有的与给出的变量相同的字符串
views:
words = 'i love you my country china'
html:
{{ words|cut:3 }} # iloveyou
join: 设定连接符将可迭代对象的元素连接在一起与字符串的join方法相同。
views: name_list = ['王阔', '天琪', '傻强', '志晨', '健身哥'] dic = {'name':'太白','age': 18} tu = (1, 2, 3) html: <li>{{ name_list|join:'_'}}</li> <li>{{ dic|join:','}}</li> <li>{{ tu|join:'+'}}</li> ''' 王阔_天琪_傻强_志晨_健身哥 name,age 1+2+3 '''
safe
Django的模板中在进行模板渲染的时候会对HTML标签和JS等语法标签进行自动转义,原因显而易见,这样是为了安全,django担心这是用户添加的数据,比如如果有人给你评论的时候写了一段js代码,这个评论一提交,js代码就执行啦,这样你是不是可以搞一些坏事儿了,写个弹窗的死循环,那浏览器还能用吗,是不是会一直弹窗啊,这叫做xss攻击,所以浏览器不让你这么搞,给你转义了。但是有的时候我们可能不希望这些HTML元素被转义,比如我们做一个内容管理系统,后台添加的文章中是经过修饰的,这些修饰可能是通过一个类似于FCKeditor编辑加注了HTML修饰符的文本,如果自动转义的话显示的就是保护HTML标签的源文件。为了在Django中关闭HTML的自动转义有两种方式,如果是一个单独的变量我们可以通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义。
我们去network那个地方看看,浏览器看到的都是渲染之后的结果,通过network的response的那个部分可以看到,这个a标签全部是特殊符号包裹起来的,并不是一个标签,这都是django搞得事情。
比如:value = "<a href='#'>点我</a>" 和 value="<script>alert('123')</script>"
{{ value|safe}}
很多网站,都会对你提交的内容进行过滤,一些敏感词汇、特殊字符、标签、黄赌毒词汇等等,你一提交内容,人家就会检测你提交的内容,如果包含这些词汇,就不让你提交,其实这也是解决xss攻击的根本途径,例如博客园:
timesince(了解)
将日期格式设为自该日期起的时间(例如,“4天,6小时”)。
采用一个可选参数,它是一个包含用作比较点的日期的变量(不带参数,比较点为现在)。 例如,如果since_12是表示2012年6月28日日期实例,并且comment_date是2018年3月1日日期实例:
views: year_12 = datetime.datetime.now().replace(year=2012, month=6, day=28) year_18 = datetime.datetime.now().replace(year=2018, month=3, day=1) html: <li>{{ year_12|timesince:year_18}}</li>
'''
5 years, 8 months
'''
如果year_18不写,默认就是距现在的时间段。
timeuntil(了解)
似于timesince,除了它测量从现在开始直到给定日期或日期时间的时间。 例如,如果今天是2006年6月1日,而conference_date是保留2006年6月29日的日期实例,则{{ conference_date | timeuntil }}将返回“4周”。
使用可选参数,它是一个包含用作比较点的日期(而不是现在)的变量。 如果from_date包含2006年6月22日,则以下内容将返回“1周”:
{{ conference_date|timeuntil:from_date }}
这里简单介绍一些常用的模板的过滤器,更多详见
更多内置过滤器(此链接页面最下面的内置过滤器):https://docs.djangoproject.com/en/1.11/ref/templates/builtins/#filters
四、标签Tags
现在我们已经可以从后端通过模版系统替换掉前端的数据了,但是如果只是替换掉数据,确实不够灵活,比如,我们要想在前端页面展示可迭代对象name_list = ['王阔', '天琪', '傻强', '志晨', '健身哥']每个元素,你如和展示呢?
# 前端页面 <ul> <li>{{ name_list.0 }}</li> <li>{{ name_list.1 }}</li> <li>{{ name_list.2 }}</li> <li>{{ name_list.3 }}</li> </ul>
这样写明显很low,我们要是可以用上for循环就好了。Django这么强大的框架,不可能想不到这点的,这里模版系统给我们提供了另一种标签,就是可以在html页面中进行for循环以及if判断等等。
标签看起来像是这样的: {% tag %}。标签比变量更加复杂:一些在输出中创建文本,一些通过循环或逻辑来控制流程,一些加载其后的变量将使用到的额外信息到模版中。与python语法不同的是:一些标签需要开始和结束标签 (例如{% tag %} ...标签 内容 ... {% endtag %})。
学习下面几种标签之前,我们要重新写一个url、views以及html,方便分类学习不与上面的变量产生冲突。
urls: url(r'^tags/',views.tags), views: def tags(request): num = 10
value = '' name_list = ['王阔', '天琪', '傻强', '志晨', '健身哥'] dic = {'name': '太白金星', 'age': 18} return render(request, 'tags.html', locals()) 先创建一个简单的tags.html即可。
for标签
基本语法:
{% for 变量 in render的可迭代对象 %}
{{ 变量 }}
{% endfor %}
例如:
{% for foo in name_list %}
{{ foo }}
{% endfor %}
便捷用法:
遍历每一个元素: 写个for,然后 tab键自动生成for循环的结构,循环很基础,就这么简单的用,没有什么break之类的,复杂一些的功能,你要通过js。可以利用{% for obj in list reversed %}反向完成循环。
遍历一个列表
<ul> {% for foo in name_list %} <li>{{ foo }}</li> {% endfor %} </ul>
反向遍历一个列表
{% for foo in name_list reversed %} <li>{{ foo }}</li> {% endfor %}
遍历一个字典:有items、keys、values参数
<ul> {% for key,value in dic.items %} <li>{{ key }}: {{ value }}</li> {% endfor %} {% for key in dic.keys %} <li>{{ key }}</li> {% endfor %} {% for value in dic %} <li>{{ value }}</li> {% endfor %} </ul>
forloop的使用
模版系统给我们的for标签还提供了forloop的功能,这个就是获取循环的次数,有多种用法:
forloop.counter 当前循环的索引值(从1开始),forloop是循环器,通过点来使用功能
forloop.counter0 当前循环的索引值(从0开始)
forloop.revcounter 当前循环的倒序索引值(从1开始)
forloop.revcounter0 当前循环的倒序索引值(从0开始)
forloop.first 当前循环是不是第一次循环(布尔值)
forloop.last 当前循环是不是最后一次循环(布尔值)
forloop.parentloop 本层循环的外层循环的对象,再通过上面的几个属性来显示外层循环的计数等
我们通过name_list示例:
<ul> {% for foo in name_list %} <li>{{ forloop.counter }} {{ foo }}</li> {% endfor %} {% for foo in name_list %} {{ forloop.counter0 }} <li>{{ foo }}</li> {% endfor %} </ul>
forloop.counter0数字与元素不在一行是因为我html标签摆放的问题,与方法无关。
{% for foo in name_list %} <li>{{ forloop.revcounter }} {{ foo }}</li> {% endfor %}
{% for foo in name_list %} <li>{{ forloop.first }} {{ foo }}</li> {% endfor %}
forloop.first、forloop.last多用于下面我们讲到if判断条件。 forloop.parentloop我们讲到if标签在演示。
for...empty...组合
如果遍历的可迭代对象是空的或者就没有这个对象,利用这个组合可以提示用户。
<ul> {% for foo in aaa %} <li>{{ foo }}</li> {% empty %} <li>查询的内容啥也没有</li> {% endfor %} {% for foo in value %} <li>{{ foo }}</li> {% empty %} <li>查询的内容啥也没有</li> {% endfor %} </ul>
if标签
基本语法:
{% if %}会对一个变量求值,如果它的值是“True”(存在、不为空、且不是boolean类型的false值),对应的内容块会输出。
{% if 条件 %} 结果 <!--不满足条件,不会生成这个标签--> {% elif 条件 %} 结果 {% else %} <!--也是在if标签结构里面的--> 结果 {% endif %}
elif和else一定要在if endif标签里面,设置多个elif或者没有elif、有没有else都可以。
示例:
{% if dic.age > 18 %} <p>可以干点儿该做的事儿了~</p> {% elif dic.age < 18 %} <p>小孩子,懂什么</p> {% else %} <p> 风华正茂的年龄~</p> {% endif %}
条件也可以与过滤功能配合
% if name_list|length > 4 %} <p>列表元素超过4个</p> {% else %} <p>列表元素太少!</p> {% endif %}
条件也可以加逻辑运算符
if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断,注意条件两边都有空格。
{% if name_list|length > 4 and '王阔' in name_list %} <p>条件都满足</p> {% endif %}
with
,当你需要使用一个“昂贵的”方法(比如访问数据库)很多次的时候是非常有用的,记住!等号的左右不要加空格!!
{% with total=business.employees.count %}
{{ total }} <!--只能在with语句体内用-->
{% endwith %}
{% with business.employees.count as total %}
{{ total }}
{% endwith %}
forloop.first、forloop.last、forloop.parentloop
当时讲for循环时有三个方法,我们没有尝试,因为需要与if条件配合。
forloop.first
{% for foo in name_list %} {% if forloop.first %} {{ foo }} {% else %} <p>只有第一次循环打印</p> {% endif %} {% endfor %}
forloop.parentloop : 一定注意!他是返回本此循环的外层循环对象,这个对象可以调用forloop的各种方法进行获取相应的数据。
测试此方法,我们要在views函数中加一个数据类型:lis = [['A', 'B'], ['C', 'D'], ['E', 'F']]
{% for i in lis %} {% for j in i %} {# <p>{{ forloop.parentloop }}</p>#} <p>{{ forloop.parentloop.counter }} {{ forloop.counter }} {{ j }}</p> {% endfor %} {% endfor %}
注意事项
1. Django的模板语言不支持连续判断,即不支持以下写法:
{% if a > b > c %}
...
{% endif %}
2. Django的模板语言中属性的优先级大于方法(了解)
def xx(request):
d = {"a": 1, "b": 2, "c": 3, "items": "100"}
return render(request, "xx.html", {"data": d})
如上,我们在使用render方法渲染一个页面的时候,传的字典d有一个key是items并且还有默认的 d.items() 方法,此时在模板语言中:
{{ data.items }}
默认会取d的items key的值。
csrf_token标签
这个标签是不是非常熟悉?之前我们以post方式提交表单的时候,会报错,还记得我们在settings里面的中间件配置里面把一个csrf的防御机制给注销了啊,本身不应该注销的,而是应该学会怎么使用它,并且不让自己的操作被forbiden,通过这个标签就能搞定。
接下来我们重写构建一个流程:
urls、views、html:
urlpatterns = [ url(r'^login/',views.login), ] from django.shortcuts import render, HttpResponse, redirect def login(request): if request.method == 'GET': return render(request, 'login.html') else: print(request.POST) # 这里我们就不进行验证了,只是研究csrf_token return HttpResponse('登录成功!!!') <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <h3>登录页面</h3> <form action="" method="post"> 用户名:<input type="text" name="username"> 密码:<input type="text" name="password"> <button>提交</button> </form> </body> </html>