模板层
一、模板简介
在刚刚介绍完的视图层中我们提到,浏览器发送的请求信息会转发给视图进行处理,而视图在经过一系列处理后必须要有返回信息给浏览器。如果我们要返回html标签、css等数据给浏览器进行渲染,我们可以在视图中这么做
from django.shortcuts import HttpResponse
import time
# 返回静态内容(静态页面):页面内容固定不变
def index(request):
html = "<html><body><h1>静态内容,固定不变</h1></body></html>"
return HttpResponse(html)
# 返回动态内容(动态页面):后台处理会用变量值填充内容,每次得到的内容都可能不同
def current_datetime(request):
now_time = time.strftime('%Y-%m-%d %X')
html = "<html><body><h1>动态内容,%s</h1></body></html>" % now_time
return HttpResponse(html)
上例所示,我们直接将HTML代码放到视图里,然后进行返回,这可以使我们很直观地看清楚浏览器从发送请求到看到前端界面内容的这个过程中视图的基本工作原理,但是这种将前端代码与后端代码完全耦合到了一起开发方式会使得程序的可维护性与可扩展性变差
前端界面一旦需要重新设计、修改,则必须对后端的Python代码进行相应的修改。 然而前端界面的修改往往比后端 Python 代码的修改要频繁得多,因此如果可以在不修改 Python 代码的情况下变更前端界面的设计,那将会方便得多。
我们可以很容易想到的解决方案就是
# 1、首先将前端代码放入单独的HTML文件中
# 2、然后编写查找/加载这些文件的统一方法/API(否则需要在每个视图里重复编写查找/加载的代码)。
作为一个成熟的Web框架,上述方案早已被Django实现:
# 1、Django提供了模板系统 (Template System)用来专门定制html文件,一个html文件称之为一个模板
对于静态页面来说,直接编写就好
而针对动态页面,django额外提供了专门的模板语言(Django template language,简称DTL),允许我们在页
面中嵌入模板变量,这便为后期为页面动态填充内容提供了可能性。
DTL是模板系统的核心,因此django的模板系统也被等同于DTL
详见第三小节
# 2、Django定义了一套标准的API用来查找/加载(读取并进行预处理称之为加载)并且渲染模板
渲染rendering指的是用上下文数据context data插入/填充模板并返回结果,结果为字符串。
将要插入/填充入模板的变量组织到一个字典里,该字典称之为上个文context data
详见第二小节->2.1
一个简单的示例如下
templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>静态内容,固定不变</h1>
</body>
</html>
templates/current_datetime.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>动态内容,{{ now }}</h1>
</body>
</html>
views.py
from django.shortcuts import render
import time
# 返回静态内容(静态页面)
def index(request):
return render(request,'index.html')
# 返回动态内容(动态页面)
def current_datetime(request):
now_time = time.strftime('%Y-%m-%d %X')
context={"now":now_time}
return render(request,'current_datetime.html',context)
urls.py
from django.urls import path
from app01.views import *
urlpatterns = [
path('index/', index),
path('current_datetime/', current_datetime),
]
二、模板的使用
1、标准API
Django内置了一套标准API(兼容任何模板引擎的BACKEND)用于查找/加载、渲染模板。历史原因,模板的相关实现都存在于django.template名称空间中。
# 例如
# 1、django.template.loader 加载器,定义了函数get_template与select_template用于模板的查找
get_template(template_name, using=None)
select_template(template_name_list, using=None)
# 2、django.template.backends 模板引擎后端实现
django.template.backends.django.DjangoTemplates
django.template.backends.jinja2.Jinja2
# 3、Template.render(context=None, request=None) 用于渲染模板
from django.template.backends.django import Template
from django.template.backends.jinja2 import Template
# 4、django.template.loader.render_to_string 是一个快捷方法,内部就是调用1和3的API
render_to_string(template_name, context=None, request=None, using=None)
# 若想深入了解,可以参照官网来阅读源码https://docs.djangoproject.com/en/3.0/topics/templates/#usage
上述API了解即可,因为我们在日常开发过程中,常用的一个render方法(from django.shortcuts import render),其内部已经整合了上述API。
from django.shortcuts import render
# 通过查看源码会发现render内部就是在调用上述API
render(request, template_name, context=None, content_type=None, status=None, using=None)
render参数介绍,详细使用见下一小节 1、request就是一个HttpRequest对象
2、template_name:
可以是一个单独的模板
'story_detail.html'
'news/story_detail.html'
也可以是一个模板列表
['story_1_detail.html', 'story_detail.html']
3、context:
是一个字典,包含了将要插入/填充入模板的变量,称之为上个文数据(context data)
4、content_type
指定响应的内容类型,如content_type='text/plain; charset=utf-8'
5、status:
指定响应的状态码,如status=200
6、using:
指定使用的模板引擎名字,如using='name1',代表使用名为name1的模板引擎
2、模板引擎配置
要解析DTL,需要有专门的解析器,称之为template engine模板引擎,需要在settings.py中配置TEMPLATES列表,其默认值为空,但在创建项目时会自动生成如下配置
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(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',
],
},
},
]
列表中一个字典就是一个引擎配置,一个django项目可以配置一个或多个模板引擎(当然,如果你不需要使用模板,也可以不配置),如下
TEMPLATES = [
# 引擎配置一
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
# ... 同上,篇幅问题,下述带有同上字样的请读者在测试时自行填充上述代码 ...
},
},
# 引擎配置二
{
'BACKEND': 'django.template.backends.jinja2.Jinja2',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
# ... 同上 ...
},
},
]
(1)关键配置项之BACKEND:
每个引擎都需要有专门的后端程序BACKEND,BACKEND的值为要导入的python类,Django为自己的模块系统以及非常受欢迎的Jinja2模板系统内置了后端
django.template.backends.django.DjangoTemplates
django.template.backends.jinja2.Jinja2
而其他模板语言的后端需要从第三方获得。
#1、基于安全性考虑,最好不要使用不知名作者开发的第三方模板系统
#2、如果你不是必须选择其他的模板系统,还是推荐使用DTL
(2)关键配置项之DIRS与APP_DIRS
由于大多数引擎加载的模板都是文件,这就涉及到文件的路径查找,所以在每个引擎配置的顶级都包含两个相似的配置来专门负责路径查找问题
'DIRS': [],
1、列表中包含一系列的目录,引擎会按照列表的顺序依次查找模板文件
2、列表为空则代表没有对应的查找目录
'APP_DIRS': True, # 默认为False
1、APP_DIRS 值为True时,会去按照INSTALLED_APPS = []中注册的app顺序,依次去每个app目录下的templates目录中查找模板文件
2、APP_DIRS 值为False时,不会检索app目录下的templates目录
查找的优先级为: djanog会先依次检索每个引擎下的DIRS,然后按照APP_DIRS的规定去找模板,直到找到为止。下面我们就依次举例进行验证
我们先将ARR_DIRS设置为False来讨论加载顺序,如下
TEMPLATES = [
{
# django引擎
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
'/home/html/example.com',
'/home/html/default',
],
'APP_DIRS': False, # 关闭去每个APP下查找,不设置默认值为False
'OPTIONS': {
# ... 同上 ...
},
},
{
# jinja2引擎
'BACKEND': 'django.template.backends.jinja2.Jinja2',
'DIRS': [
'/home/html/jinja2',
],
'APP_DIRS': False, # 关闭去每个APP下查找,不设置默认值为False
'OPTIONS': {
# ... 同上 ...
},
},
]
若调用render(request,‘story_detail.html’),查找顺序如下,找到为止
# 1、先检索列表中的第一个引擎,即'django' 引擎
/home/html/example.com/story_detail.html
/home/html/default/story_detail.html
# 2、再检索列表中的第二个引擎,即'jinja2' 引擎
/home/html/jinja2/story_detail.html
若调用render(request,[‘story_1_detail.html’, ‘story_detail.html’]),查找顺序如下,找到为止
# 一:首先查找模板story_1_detail.html
# 1.1、先检索列表中的第一个引擎,即'django' 引擎
/home/html/example.com/story_1_detail.html
/home/html/default/story_1_detail.html
# 1.2、再检索列表中的第二个引擎,即'jinja2' 引擎
/home/html/jinja2/story_1_detail.html
# 二:其次查找模板story_detail.html
# 2.1、先检索列表中的第一个引擎,即'django' 引擎
/home/html/example.com/story_detail.html
/home/html/default/story_detail.html
# 2.2、再检索列表中的第二个引擎,即'jinja2' 引擎
/home/html/jinja2/story_detail.html
所以针对[‘story_1_detail.html’, ‘story_detail.html’],后一个模板通常作为第一个模板的备胎,从而保障第一个模板在未找到时依然能够找到模板作为替代品,这样便可以使我们的代码更为灵活,如下
def article(request, article_id):
first_template = 'article%s.html' % article_id
bak_template = 'article.html'
return render(request, [first_template, bak_template])
然后我们ARR_DIRS设置为True来讨论完整的加载顺序,需要事先在每个app目录下顶层建立目录templates(注意,目录名字必须为templates),
/Users/linhaifeng/PycharmProjects/EgonPro
├── app01
│ ├── templates *# 目录名必须为templates*
├── app02
│ ├── templates # 目录名必须为templates
然后配置如下
TEMPLATES = [
{
# django引擎
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
'/home/html/aaa',
'/home/html/bbb',
],
'APP_DIRS': True, # 关闭去每个APP下查找,不设置默认值为False
'OPTIONS': {
# ... 同上 ...
},
},
{
# jinja2引擎
'BACKEND': 'django.template.backends.jinja2.Jinja2',
'DIRS': [
'/home/html/ccc',
],
'APP_DIRS': True, # 关闭去每个APP下查找,不设置默认值为False
'OPTIONS': {
# ... 同上 ...
},
},
]
若调用render(request,‘story_detail.html’),查找顺序如下,找到为止
# 一:djanog会先依次检索每个引擎下的DIRS
# 1.1、先检索列表中的第一个引擎,即'django' 引擎
/home/html/aaa/story_detail.html
/home/html/bbb/story_detail.html
# 1.2、再检索列表中的第二个引擎,即'jinja2' 引擎
/home/html/ccc/story_detail.html
# 二:因为APP_DIRS:True,所以接下来会按照INSTALLED_APPS中注册app的顺序,依次去每个app下的templates目录里查找
INSTALLED_APPS = [
# ......
'app02.apps.App02Config',
'app01.apps.App01Config',
]
# 2.1 先检索app02下的tempaltes目录
/Users/linhaifeng/PycharmProjects/EgonPro/app02/templates/story_detail.html
# 2.2 再检索app01下的tempaltes目录
/Users/linhaifeng/PycharmProjects/EgonPro/app01/templates/story_detail.html
ps:针对render(request,[‘story_1_detail.html’, ‘story_detail.html’]),会按照列表中规定的模板顺序依次重复上述查找步骤,直到某一模板查找成功为止。
在一个项目的实际开发过程中,通常会包含多个app,多个app会有一些公共的模板文件,同时每个app也都会有自己对应的模板文件,要想清晰地组织这些模板,有如下两种解决方案
方案一:设置ARR_DIRS为False
#一:在项目根目录下的templates目录中新建子目录用来区分不同的模板归属
/Users/linhaifeng/PycharmProjects/EgonPro
├── templates
│ ├── base # 用于存放公共模板
│ │ ├── base.html
│ ├── app01 # 用于存放app01单独的模板
│ │ ├── index.html
│ ├── app02 # 用于存放app02单独的模板
│ │ ├── index.html
#二:配置如下
TEMPLATES = [
# 引擎配置一
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': False,
'OPTIONS': {
# ... 同上 ...
},
},
]
#三:渲染方式
render(request,'base/base.html')
render(request,'app01/index.html')
render(request,'app02/index.html')
方案二:设置ARR_DIRS为True
#一:在ARR_DIRS为True的情况下,会检索每个app的templates目录,所以需要事先创建好下述目录:
1、项目根目录/templates
2、项目根目录/app01/templates # 目录名字必须为templates
3、项目根目录/app02/templates # 目录名字必须为templates
注意:如果只创建到这一层就结束了,会出现冲突
比如render(request,'index.html'),在三个目录中都存在同名模板的情况下,查找优先级会是1,2,3(假设只有配置了一个引擎,且app的注册顺序为app01、app02)。
如果我们既想清晰地组织模板的目录结构,又想找到指定的模板、避免冲突,那么需要在1、2、3的基础上继续建立子目录,子目录名无所谓,但其发挥的作用,相当于名称空间了,如下
/Users/linhaifeng/PycharmProjects/EgonPro
├── templates
│ ├── base
│ │ ├── base.html
├── app02
│ ├── templates
│ │ └── app01
│ │ └── index.html
├── app02
│ ├── templates
│ │ └── app02
│ │ └── index.html
#二:配置如下
TEMPLATES = [
# 引擎配置一
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
# ... 同上 ...
},
},
]
#三:渲染方式
# 最终找到的位置/templates/base/base.html
render(request,'base/base.html')
# 最终找到的位置/app01/templates/app01/index.html
render(request,'app01/index.html')
# 最终找到的位置/app02/templates/app02/index.html
render(request,'app02/index.html')
(3)关键配置项目之NAME
在调用render()时,除非指定了引擎,否则会按照列表规定的顺序依次使用引擎来查找/加载模板,直到查找/加载成功为止。
如果想选用指定的模板引擎,需要使用参数NAME为每个模板引擎设定唯一的名字,然后在render中使用using参数指定即可
#一:settings.py配置如下
TEMPLATES = [
# 引擎配置一
{
'NAME': 'b1',
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'aaa')],
'APP_DIRS': True,
'OPTIONS': {
# ... 同上 ...
},
},
# 引擎配置二
{
'NAME': 'b2',
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'bbb')],
'APP_DIRS': True,
'OPTIONS': {
# ... 同上 ...
},
},
]
#二:为render函数指定using参数
render(request, 'index.html',using='b1') # b1->限定使用引擎配置一
render(request, 'index.html',using='b2') # b2->限定使用引擎配置二
ps:如果没有设定参数NAME,那么引擎的名字默认为BACKEND按照点为分隔符的倒数第二个值,例如
'django.template.backends.django.DjangoTemplates' 引擎名字为django
'django.template.backends.jinja2.Jinja2' 引擎名字为jinjia2
(4)关键配置项之OPTIONS
OPTIONS值为一字典,包含了要传递到模板后端的额外参数。比如
TEMPLATES = [
{
# ......
'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',
],
},
},
]
我们在模板中常用的变量{{ request }}就是’django.template.context_processors.request’的功劳。
更多配置项请参照官网https://docs.djangoproject.com/en/3.0/topics/templates/#module-django.template.backends.django
三、DTL语法
Django模板指的是用Django模板语言(DTL)标记的文本文档(如HTML、XML、CSV等任何文本文档都可以)或者python字符串(文本文档也是由字符组成)。
Ps: DTL一定要在django的模板引擎下使用,不要使用jinja2引擎或者其他模板引擎
DTL的语法主要由四部分构成:变量、过滤器、标签、注释,如下
{% extends "base_generic.html" %}
{% block title %}{{ section.title }}{% endblock %}
{% block content %}
{# <h1>hello</h1> #}
<h1>{{ section.title }}</h1>
{% for story in story_list %}
<h2>
<a href="{{ story.get_absolute_url }}">
{{ story.headline|upper }}
</a>
</h2>
<p>{{ story.tease|truncatewords:"100" }}</p>
{% endfor %}
{% endblock %}
让我们来分别作详细介绍
1、DTL之变量
1.变量的基本使用
模板中的变量格式为:{{ 变量名 }}。变量名由字母数字和下划线组成,但是不能以下划线开头否则会因为引擎无法解析导致服务端错误。
如果模板中的数据不是固定死的,而是动态变化的,则必须在html中嵌入变量,如下
test.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>{{ msg }}</p>
<p>{{ dic }}</p>
<p>{{ obj }}</p>
<p>{{ li }}</p>
</body>
</html>
我们需要在视图函数中为模板test.html的变量名msg、li、dic、obj、obj_li赋值,views.py内容如下
from django.shortcuts import render
def test(request):
# 传给模板的变量值可以是任意python类型,如下
msg='hello world'
dic={'k1':1,'k2':2}
class Person(object):
def __init__(self,name,age):
self.name=name
self.age=age
obj=Person('egon',18)
li = [1,'aaa',obj]
return render(request,'test.html',{'msg':msg,'dic':dic,'obj':obj,'li':li})
# 注意:
# 1、render函数的第三个参数称之为上下文数据,包含了要传给模板的变量值,是一个字典,该字典中的key必须与模板文件中的变量名相对应,render函数会去templates目录下找到模板文件,然后根据字典中的key对应到模板文件中的变量名进行赋值操作,最后将赋值后的模板文件内容返回给浏览器
# 2、可以将render函数的第三个参数简写为locals(),如下
return render(request,'test.html',locals()) #locals()会将函数test内定义的名字与值转换为字典中的k与v
2.深度查询之句点符的使用
变量名中不能有空格或者标点符号,但是有一个例外,点(".")可以出现在变量中,点后的可以是字典相关(字典的key或者字典内置方法)、对象的属性或方法、数字索引,如下所示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{# 视图函数参照上例 #}
<!--调用字符串对象的upper方法,注意不要加括号-->
<p>{{ msg.upper }}</p>
<!--取字典中k1对应的值-->
<p>{{ dic.k1 }}</p>
<!--取对象的name属性-->
<p>{{ obj.name }}</p>
<!--取列表的第2个元素,然后变成大写-->
<p>{{ li.1.upper }}</p>
<!--取列表的第3个元素,并取该元素的age属性-->
<p>{{ li.2.age }}</p>
</body>
</html>
请注意四点
(1)在渲染页面时,若变量不存在,模板引擎默认用配置项string_if_invalid的值作为替代品,配置如下,默认值为空字符串”,可以进行配置
# 1、setting.py配置如下
TEMPLATES = [
# 引擎配置一
{
'NAME': 'b1',
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'aaa')],
'APP_DIRS': True,
'OPTIONS': {
'string_if_invalid':'egon是大帅比',
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
# 2、渲染时针对不存在的变量,值都会被替换.
(2)当点后的值是可调用对象时,模板引擎在渲染时会当做无参函数进行调用,将调用的返回值插入模板。(Django不推荐我们在模板中做过多的逻辑处理,所以模板中的方法调全部为无参调用,即便是遇到有参的需求,那也应该是在后端处理,然后为模板提供处理结果即可)
(3)约定俗成:以下划线开头的变量属性通常被视为私有属性,可能无法访问
例如在视图函数中的变量是下划线开头的
_x = 222
在模板中是无法引用的
{{ _x }} 报错*
*
ps:__x也属于下划线开头的
(4)点后值的查找优先级如下
1、先进行: 字典key的查找
2、然后进行:属性或方法的查找
3、最后进行:数字索引的查找
针对上述优先级,需要尤其注意冲突问题,例如
def test(request):
from collections import defaultdict
msg='helloegon'
d=defaultdict(int)
for s in msg:
d[s]+=1
return render(request, 'test.html', {'d':d})
针对defaultdict字典,在访问某一个不存在的key时会返回一个默认值,这就为冲突埋下了祸根:
在模板中插入{{ d.items }},我们的本意是想调用字典d的方法items(),但实际情况是会首先查找字典的key,所以items会被首先当做key来进行查找,很明显字典d中不存在名为items的key,而针对defaultdict类型的字典,会在key不存在时返回一个默认值(本例返回默认值为0),所以此时并不会调用items()方法。
针对上述问题的解决方案就是需要在视图中事先将defaultdict转换为dict类型
d=dict(d)
然后再传递给模板,这样在渲染{{ d.items }}时,先去字典中查找键items,无法找到也不会返回任何默认值,所以会转而查找d的属性或方法,从而调用到方法items()
2、DTL之过滤器
1.常用过滤器基本使用
过滤器类似于python的内置函数,用来把变量值加以修饰后再显示,具体语法如下
#1、
{{ 变量名|过滤器名 }}
#2、链式调用:上一个过滤器的结果继续被下一个过滤器处理
{{ 变量名|过滤器1|过滤器2 }}
#3、有的过滤器取需要参数
{{ 变量名|过滤器名:传给过滤器的参数 }}
常用内置过滤器
#0、default
#作用:如果一个变量值是False或者为空、None,使用default后指定的默认值,否则,使用变量本身的值,如果value=’‘则输出“nothing”
{{ value|default:"nothing" }}
#1、default_if_none
#作用:如果只针对value是None这一种情况来设置默认值,需要使用default_if_none
#只有在value=None的情况下,才会输出“None...”,
{{ value|default_if_none:"None..." }}
#2、length
#作用:返回值的长度。它对字符串、列表、字典等容器类型都起作用,如果value是 ['a', 'b', 'c', 'd'],那么输出是4
{{ value|length }}
#3、filesizeformat
#作用:将值的格式化为一个"人类可读的"文件尺寸(如13KB、4.1 MB、102bytes等等),如果 value 是 12312312321,输出将会是 11.5 GB
{{ value|filesizeformat }}
#4、date
#作用:将日期按照指定的格式输出,如果value=datetime.datetime.now(),按照格式Y-m-d则输出2019-02-02
{{ value|date:"Y-m-d" }}
#5、slice
#作用:对输出的字符串进行切片操作,顾头不顾尾,如果value=“egon“,则输出"eg"
{{ value|slice:"0:2" }}
#6、truncatechars
#作用:如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列(“...”)结尾,如果value=”hello world egon 嘎嘎“,则输出"hello...",注意8个字符也包含末尾的3个点
{{ value|truncatechars:8 }}
#7、truncatewords
#作用:同truncatechars,但truncatewords是按照单词截断,注意末尾的3个点不算作单词,如果value=”hello world egon 嘎嘎“,则输出"hello world ..."
{{ value|truncatewords:2 }}
2. HTML的自动转义与关闭
模板在生成HTML时,如果变量中包含一些具有语法意义的特殊字符,则会影响HTML的结果,比如
Hello, {{ name }}
针对变量{{ name }},如果用户注册自己的用户名name是一个中规中矩的用户名时,上述代码并无问题,但如果用户恶意注册用户名为下述内容
# 注册时,用户输入自己的用户名就是下述字符
<script>alert('hello')</script>
后台取出name = "alert(‘hello’)",然后执行render渲染的结果为
Hello, <script>alert('hello')</script>
上述结果交给浏览器后,意味着浏览器将弹出一个JavaScript警报框!试想,如果是一个博客类网站,恶意作者在自己提交的文章中掺杂了类似上面这种恶意代码,这意味着每个读者在读取他的文章时,自己的浏览器都会弹出一个JavaScript警报框!
类似的,如果注册的用户名包含'<‘号,如下
<b>egon
后台取出name = "egon",然后执行render渲染的结果为
Hello, <b>egon
上述结果交给浏览器后,有没有对应的闭合标签,意味着其后的内容都会被加粗
综上,可以确定的是:用户提交的数据不应该被盲目地信任并直接插入到我们的的网页中,因为恶意用户可能会利用这种漏洞来做潜在的坏事。这种类型的安全攻击称为跨站点脚本(XSS,详见:https://en.wikipedia.org/wiki/Cross-site_scripting)攻击。
好在django已经为了做了相应的处理:django的模板引擎在生成模板时,默认就会对所有变量的值进行转移,具体是针对变量值中包含的以下五个字符的转义
# 1、使用DTL,以下5种特殊符号默认就会被转义成对应的html命名实体
1、< 被转换成 <
2、> 被转换成 >
3、' 单引号被转换成 '
4、" 双引号被转换成 "
5、& 被转换成 &
# 首先经过转义后得到模板,然后递交给浏览器解析,上述内容均会被当成普通字符输出
例如:
针对value="<script>alert(123)</script>",模板变量{{ value }}会被渲染成<script>alert(123)</script>交给浏览器后会被解析成普通字符”<script>alert(123)</script>“,失去了js代码的语法意义
虽然django默认会将所有模板变量进行转义,但有时候我们需要关闭自动转义,比如我们存入数据库的就是一段HTML代码,当我们取出来时就想让浏览器解析其中的语法呈现结果,而不是显示一堆普通字符。那如何关闭自动转义呢?具体操作如下
(1)针对单个变量->使用过滤器safe
{{ value|safe }}
比如如value='<a href="https://www.baidu.com">点我啊</a>',经过过滤器safe的处理,浏览器在进行解析时就会将其当做超链接显示,不加safe过滤器则会当做普通字符显示’<a href="https://www.baidu.com">点我啊</a>‘
(2)针对模板块->使用标签autoescape
autoescape通过参数on和off来控制开启或关闭模板块的整体转义行为,示例如下
{# 对包含在标签内的模板块的转义行为进行整体关闭 #}
{% autoescape off %}
<p>
不会转义 {{ name }}.
</p>
{# 支持嵌套,设置嵌套的模板块整体开启转义功能 #}
{% autoescape on %}
<p>
会被转义: {{ name }}
</p>
{% endautoescape %}
{% endautoescape %}
autoescape的效果会遗传给子模板(使用标签extends继承当前模板),也会留给引入了当前模板的模板(使用标签include引入当前模板),例如
base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% autoescape on %}
<h1>
{% block title %}{% endblock %}
</h1>
{% block content %}{% endblock %}
{% endautoescape %}
</body>
</html>
child.html
{# 继承父模板base.html #}
{% extends "base.html" %}
{# 定制title块中的内容,其中包含的内容没有特殊字符,转义是否开启没有影响,显示结果均为egon & dsb #}
{% block title %}egon & dsb{% endblock %}
{# 定制content块中的内容,如果变量name="<b>egon</b>",由于content模板块在父模板中被设置成关闭转义,所以子模板也跟着关闭,生成的值为<b>egon</b>,交给浏览器后显示结果为加粗的egon #}
{% block content %}{{ name }}{% endblock %}
ps: autoescape的使用涉及到的标签与模板继承的概念,请读者自行查看后续章节
我们在介绍常用的内置过滤器时提到过:过滤器的参数可以是字符串,比如default过滤器,而default过滤器默认不会对字符串进行转义
{{ value1|default:"<a href='http://www.egonav.com'>egon草裙舞</a>" }}
即便是我们用标签{% autoescape on %}开启了自动转义,过滤器default仍然不会对字符串进行转义,所以上述代码在value值为Flase/空/None的情况下,浏览器展示的就是一个超链接,如果我们仅仅只想把defaul的值显示成字符串,那么需要我们手动把默认值里的特殊符号写成命名实体,如下
{{ value1|default:"<a href='http://www.egonav.com'>egon草裙舞</a>" }}
当我们要显示的默认值是“3 < 2”时,因为小于号<与数值2并不会组成一个特殊标签,所以浏览器会显示出“3 < 2”
{{ data|default:"3 < 2" }} {# Bad! Don't do this. #}
但是我们还是应该写成下面这种格式,这才是通用的书写格式
{{ data|default:"3 < 2" }}
3.其他过滤器(了解)
查看更多
111