模板系统基本知识
模版是一个文本,用于分离文档的表现形式和内容。模板定义了占位符以及各种用于规范文档该如何显示个部分基本逻辑。模板通常用于产生HTML,但Django的模板也能产生任何基于文本格式的文档。
模板介绍
'DIRS': [os.path.join(BASE_DIR,'templates')],
Python解释器的工作原理
转到项目所在的目录输入python manage.py shell启动交互界面。
manage.py shell和python命令都可以启动交互解释器,但manage.py shell又一个重要的不同之处:在启动解释器之前,它告诉Django使用哪个配置文件。Django框架的大部分子系统,包括模板系统都依赖于配置文件;如果Django不知道使用哪个配置文件,这些系统将不能工作。Django搜索DJANGO_SETTINGS_MODULE环境变量,它被设置在settings.py中。当运行命令python manage.py shell,它会自动帮你处理DJANGO_SETTINGS_MODULE。
创建模板对象
直接导入django.template中的Template类,然后实例化。* 加载:根据所给定的标识找到模板然后预处理,通常将它编译好放在内存中。
>>> from django.template import Template
>>> t=Template('My name is {{ name }}')
>>> t
得到一个template对象
0x7f46ded2edd8为对象的ID
当创建一个Template对象。模板系统在内部编译这个模板到内部格式,并做优化做好渲染准备。
模板渲染
使用Context数据对模板进行插值并返回生成的字符串,一个context是一系列变量和它们值的集合。context在Django里表现为Context类。在django.template模块里。它的构造函数带有一个可选的参数,一个字典映射变量和它们的值。调用render()方法并传递context来填充。
render()也称快捷函数减少创建模板、渲染模板的重复代码
>>> from django.template import Template,Context
>>> t=Template('My name is {{ name }}')
>>> c=Context({'name':'LiMing'})
>>> t.render(c)
‘My name is LiMing’
Django模板系统的基本规则:
>>> from django.template import Template,Context
>>> t=Template('Hello {{ name }}')
>>> t.render(Context({'name':'John'}))
'Hello John'
>>> t.render(Context({'name':'liming'}))
'Hello liming'
像这样只进行一次模板创建然后多次调用render()方法渲染会更高效。
Django模板解析非常快捷。大部分的解析工作都是在后台通过对简短正则表达式一次性调用来完成。这和基于XML的模板引擎形成鲜明对比,那些引擎承担了XML解析器的开销,而且比Django模板渲染慢好几个数量级。
深度变量的查找
模板系统可以处理更复杂的数据结构,如dict、list和自定义的对象,在Django模板中遍历复杂数据结构的关键是句点字符(.)
dict需要通过字典键访问该字典的值,可使用一个句点:
>>> from django.template import Template,Context
>>> person={'name':'Sally','age':'43'}
>>> t=Template('{{ person.name }} is {{ person.age }} years old')
>>> c=Context({ 'person':person})
>>> t.render(c)
'Sally is 43 years old'
访问对象的属性
>>> from django.template import Template,Context
>>> import datetime
>>> d=datetime.date(1993,5,2)
>>> d.year
1993
>>> d.month
5
>>> d.day
2
>>> t=Template('The month is {{ date.month}} and the year is {{ date.year}}')
>>> c=Context({'date':d})
>>> t.render(c)
'The month is 5 and the year is 1993'
通过实例变量加一点来访问他的属性,这个方法适用于任意对象。
也可以引用对象的方法,但是只能调用不传递参数的方法。
>>> from django.template import Template,Context
>>> t=Template('{{ var }}--{{ var.upper }}--{{ var.isdigit }}')
>>> t.render(Context({'var':'hello'}))
'hello--HELLO--False'
>>> t.render(Context({'var':'123'}))
'123--123--True'
列表类型
注意不允许负数列表索引如{{ items.-1}}这样会报错
>>> from django.template import Template,Context
>>> t=Template('Item 2 is {{ items.2 }}')
>>> c=Context({ 'items':['apples','bananas','carrots']})
>>> t.render(c)
'Item 2 is carrots'
总结
当模板系统在变量名中遇到点时,按照以下顺序尝试进行查找:
>>> from django.template import Template,Context
>>> person={'name':'Sally','age':'43'}
>>> t=Template('{{ person.name.upper}} is {{ person.age }} years old')
>>> c=Context({'person':person})
>>> t.render(c)
'SALLY is 43 years old'
方法调用行为
def delete(self):
delete.alters_data=True
模板系统不会执行任何以该方式进行标记的方法。标记之后如果在有上面的情况,那么delete方法将不会被执行,将会静静地错误退出
如何处理无效的变量
默认情况下,如果一个变量不存在,模板系统会把它展示为空字符串,不做任何事情来表示失败。
基本的模板标签和过滤器
if/else
和python中的功能差不多,所以就说一点不同之处。
{% if %}标签不允许在同一个标签同时使用and和or;如果非要使用这种功能可以将它移到模板之外处理然后以模板变量的形式传入结果。或者使用嵌套的{% if %}
没有{% elif %}标签
for
不同之处:
这个标签支持一个可选的{% empty %}分句,通过它我们可以定义当列表为空时的输出内容。
{% for i in athlete_list %}
pass
{% empty %}
The list already null
{% endfor %}
不支持退出循环的操作
不支持continue语句
forloop模板变量
可以提示循环进度信息
属性 | 功能 |
---|---|
forloop.counter | 当前循环执行次数的整数计数器,从1开始 |
forloop.counter0 | 当前循环执行次数的整数计数器,从0开始 |
forloop.revcounter | 循环剩余项个数的整形变量,以1作为结束索引 |
forloop.revcounter0 | 循环剩余项个数的整形变量,以0作为结束索引 |
forloop.first | 一个布尔值,如果是第一次执行,那么为True |
forloop.last | 一个布尔值,如果是最后一次执行,那么为True |
forloop变量仅仅能够在循环中使用。在模板中碰到{% endfor %}标签之后,forloop就不可访问了。在一个{% for %}块中已存在的变量会被移除,以避免forloop变量被覆盖。Django会把这个变量移动到forloop.parentloop中。一旦早模板中定义了forloop这个变量,在{% for %}块中它会在forloop.parentloop被重新命名。
ifequal/ifnotequal
{% ifequal %}标签比较两个值,当他们相等时。显示在{% ifequal %}和{% endifequal %}值中所有的值。也支持可选的{% else %}标签。只有模板变量,字符串,整数和小数可以作为{% ifequal %}的参数。其他类型如列表,字典都等都不能用在其中。
注释
使用{# #}进行注释,但是这种注释不能跨越多行,可以用{% comment %} {% endcomment %} 进行多行注释。
过滤器
过滤器在变量被显示前修改它的值,使用管道复符,和Flask框架中的过滤器用法一样。
为了减少模板加载调用过程及模板本身的冗余(rong)的代码,Django提供了以后宗使用方便且功能强大的API,用于从磁盘中加载模板。
第一步:
将模板的保存位置告诉框架。
在配置文件settings.py中会看到
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
#该设置告诉Django的模板加载机制在那里查找模板
'DIRS': [os.path.join(BASE_DIR,'templates')],
......
第二步:
视图代码,让它使用Django模板加载功能而不是对模板路径硬编码。
from django.template.loader import get_template
from django.template import Context
from django.http import HttpResponse
import datetime
def current_datetime(request):
now=datetime.datetime.now()
'''django.template.loader.get_template()
这个函数以模板名为参数,在文件系统中找出模块位置,
打开并返回一个编译好的Template对象'''
t=get_template('current_datetime.html')
html=t.render({'current_date':now})
return HttpResponse(html)
要确定某个模板文件在系统中的位置,get_template()方法会自动为你链接已经设置的’DIRS’: [os.path.join(BASE_DIR,‘templates’)],和你传入该方法的模拟板名称参数。如果找不到给名称模板,将会引发一个TemplateDoesNotExist异常。如果你的配置文件中的DEBUG = True,则会在页面看到调式的信息。使用python manage.py runserver来启动服务器。
会告诉你找不到current_datetime.html这个文件,因为现在没有创建。
第三步:
创建模板文件
It is now {{ current_date }}.
运行结果
render_to_response()
Django提供了一个捷径,让你一次性的载入某个模板文件,渲染它,然后将此作为HttpResponse返回。
render_to_response()的第一个参数必须是要使用的模版名称。如果第要给丁第二个参数,那么该参数必须是为该模板创建Context时所用的字典。如果不提供第二个参数,那么render_to_response()使用一个空字典。
from django.shortcuts import render_to_response
import datetime
def current_datetime(request):
now=datetime.datetime.now()
return render_to_response('current_datetime.html',{'current_date':now})
locals()技巧
他返回的字典对所有局部变量的名称与值进行映射。没有像之前那样传入Context字典,而时传入了locals()的值,它囊括了函数执行到该时间点是所定义的一切变量。但是并没有带来多大的改进。
def current_datetime(request):
current_date=datetime.datetime.now()
return render_to_response('current_datetime.html',locals())
get_template()
把模板存放于模板目录的子目录;如将模板放在templates的dateapp子目录下。
t=get_template('dateapp/current_datetime.html')
对子目录树的深度没有限制。
include模板标签
{% include %}该标签允许在模板中包含其他模版的内容。标签参数使所要包含模板的名称,可以是一个变量,也可以是字符串。
{% include current_datetime.html %}
包含了current_datetime.html模板。
虽然Django支持{% include %}方法,但是首选方法是使用模板继承方法。本质上来说,模板继承就是县构造一个基础框架模板,而后在其子模板中对它所包含站点公用你分和定义块进行重载。
创建基础框架,并在基础框架使用{% block 了可以继承的部分名字 %}来确定哪一部分可以继承,重载。
继承模板,{% extends “继承的模板名” %}
模板继承的诀窍
如果在模板中使用{% extends %},必须保证其为模板中的第一个模板标记。否则模板继承不起作用。
一般来说,基础模版中的{% block %}越多越好;
如果发现在多个模板之间拷贝代码,应该考虑将这段代码放到基础模板中
不允许在同一模板中定义多个同名的{% block %},因为block标签的工作方式式双向的。block标签不仅挖了一个要填的坑,也定义了在父类模板中这个坑所填的内容。如果重名,父模板将无从知道要使用哪个块的内容;
{% extends %}对传入模版名称使用的加载方法和get_template()相同;
{% extends %}的参数也可以是变量。
RequestContext和Context处理器
RequestContext默认的在模板context中加入了一些变量,如HttpRequest对象或当前登陆用户的相关信息。当你不想在一系列模板中都明确指定一些相同变量时,应该使用RequestContext。创建RequestContext和Context处理器就是为了消去冗余的代码。Context处理器允许你设置一些变量,他们会在每个context中自动被设置好,而不必每次调用render_to_reaponse()时都指定。
from django.template import loader,RequestContext
#这是一个context处理器,接收一个HttpResponse对象
#返回一个字典,包含可以在模板中使用的变量
def custom_proc(request):
return {
'app':'My app',
'user':request.user,
'ip_adress':request.META['REMOTE_ADDR']
}
def view_1(request):
t=loader.get_template('template1.html')
c=RequestContext(request,{'message':'i am view 1.'},
processors=[custom_proc])
return t.render(c)
def view_2(request):
t=loader.get_template('template2.html')
c=RequestContext(request,{'message':'i am view 2.'},
processors=[custom_proc])
return t.render(c)
用RequestContext代替了Context。与Context对象创建的不同:
1.RequestContext的第一个参数需要传递一个HttpResponse对象
2.RequestContext有一个可选参数processors,这好似一个包含context处理器函数的列表或元组。
render_to_response可以简化调用loader.get_template()然后创建一个context对象,最后在调用模板对象render()的过程。
def view_1(request):
return render_to_response('template1.html',
{'message':'i am view 1'},
context_instance=RequestContext(request,processors=[custom_proc]))
以代码冗余的代价消除了数据上的冗余。
Django因此提供了对全局context处理器的支持。
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
context_processors指定的那些总是默认被使用。这样就省去了每次都要指定processors的麻烦。这个设置项是一个可调用的元组,其中每个函数使用了和custom_proc相同的接口。每个处理器将会按照顺序应用。如果同名,后面的会覆盖前面的。
django.core.context_processors.auth
如果 TEMPLATE_CONTEXT_PROCESSORS 包含了这个处理器,那么每个 RequestContext 将包含这些变量:
user :一个 django.contrib.auth.models.User 实例,描述了当前登录用户(或者一个 AnonymousUser 实
例,如果客户端没有登录)。
messages :一个当前登录用户的消息列表(字符串)。 在后台,对每一个请求,这个变量都调用
request.user.get_and_delete_messages() 方法。 这个方法收集用户的消息然后把它们从数据库中删除。
perms : django.core.context_processors.PermWrapper 的一个实例,包含了当前登录用户有哪些权限。
django.core.context_processors.debug
这个处理器把调试信息发送到模板层。 如果 TEMPLATE_CONTEXT_PROCESSORS 包含这个处理器,每一
个 RequestContext 将包含这些变量:
debug :你设置的 DEBUG 的值( True 或 False )。你可以在模板里面用这个变量测试是否处在debug模
式下。
sql_queries :包含类似于 {‘sql’: ..., ‘time’:
的字典的一个列表, 记录了这个请求期间的每个SQL查询以及查询所耗费的时间。 这个列表是按照请求顺序进行排列的。
System Message: WARNING/2 ( , line 315); backlink
Inline literal start-string without end-string.
由于调试信息比较敏感,所以这个context处理器只有当同时满足下面两个条件的时候才有效:
DEBUG 参数设置为 True 。
请求的ip应该包含在 INTERNAL_IPS 的设置里面。
django.core.context_processors.request
如果启用这个处理器,每个 RequestContext 将包含变量 request , 也就是当前的 HttpRequest 对象。 注意这
个处理器默认是不启用的,你需要激活它。
写Context处理器的一些建议
编写处理器的一些建议:
使每个context处理器完成尽可能小的功能。 使用多个处理器是很容易的,所以你可以根据逻辑块来分解功能以便将来复用。
要注意 TEMPLATE_CONTEXT_PROCESSORS 里的context processor 将会在基于这个settings.py的 每个 模板中有效,所以变量的命名不要和模板的变量冲突。 变量名是大小写敏感的,所以processor的变量全用大写是个不错的主意。
不论它们存放在哪个物理路径下,只要在你的Python搜索路径中,你就可以在
TEMPLATE_CONTEXT_PROCESSORS 设置里指向它们。 建议你把它们放在应用或者工程目录下名为context_processors.py 的文件里
html自动转意
从模板生成html的时候,总是有一个风险——变量包了含会影响结果html的字符。用户提交的数据不应该被盲目信任,直接插入到你的页面中。因为一个潜在的恶意的用户能够利用这类漏洞做坏事。 这类漏洞称为被跨域脚本 (XSS) 攻击。
在django里默认情况下,每一个模板自动转意每一个变量标签的输出。这个行为默认是开启的。
如何关闭?
对于单独的变量:
This will not be escaped: {{ data|safe }}
{% autoescape off %}
Hello {{ name }}
{% endautoescape %}
autoescape 标签有两个参数on和off 有时,你可能想阻止一部分自动转意,对另一部分自动转意。
{% autoescape off %}
This will not be auto‐escaped: {{ data }}.
Nor this: {{ other_data }}
{% autoescape on %}
Auto‐escaping applies again: {{ name }}
{% endautoescape %}
{% endautoescape %}
auto-escaping 标签的作用域不仅可以影响到当前模板还可以通过include标签作用到其他标签,就像block标签一样,继承关闭该功能的模板,默认也是关闭的。
模板加载的内幕
一般说来,你会把模板以文件的方式存储在文件系统中,但是你也可以使用自定义的 template loaders 从其他来源加载模板。
Django有两种方法加载模板:
django.template.loader.get_template(template_name) : get_template 根据给定的模板名称返回一个已编译的模板(一个 Template 对象)。 如果模板不存在,就触发 TemplateDoesNotExist 的异常。
django.template.loader.select_template(template_name_list) : select_template 很像get_template ,不过它是以模板名称的列表作为参数的。 它会返回列表中存在的第一个模板。 如果模板都不存在,将会触发 TemplateDoesNotExist 异常。
一些加载器默认被禁用,但是你可以通过编辑 TEMPLATE_LOADERS 设置来激活它们。 TEMPLATE_LOADERS 应当是一个字符串的元组,其中每个字符串都表示一个模板加载器。
django.template.loaders.filesystem.load_template_source : 这个加载器根据 TEMPLATE_DIRS 的设置从文件系统加载模板。它默认是可用的。
django.template.loaders.app_directories.load_template_source : 这个加 载器从文件系统上的Django
应用中加载模板。 对 INSTALLED_APPS 中的每个应用,这个加载器会查找 templates 子目录。 如果这个目录存在,Django就在那里寻找模板,这意味着你可以把模板和你的应用一起保存,从而使得Django应用更容易和默认模板一起发布,加载器在首次被导入的时候会执行一个优化: 它会缓存一个列表,这个列表包含了 INSTALLED_APPS中带有 templates 子目录的包。
扩展模板系统
创建一个模板库
1.决定模板库的位置
2.创建templatetags目录,包含**init.py**文件和一个用来存放自定义标签/过滤器的文件,第二个文件名字将用来加载标签。
如果你的第二个文件叫poll_extras.py,那么在模板中写入
{% load poll_extras %}
{% load %}标签交叉INSTALLED_APPS中的这是,仅允许加载已安装的Django应用程序中的模板库。{% load %}语句使用过指定python模块名而不是应用名来加载标签/过滤器。
作为一个合法的标签库,模块需要一个名为register的模块变量,它是template.Library的实例,是所有注册标签和过滤器的数据结构。
from django import template
register=template.Library()
创建过滤器
**def cut(value,arg):
return value.replace(arg,'')
def lower(value):
return value.lower()
#注册过滤器,需要传入两个参数(过滤器的名称,过滤器函数本身)
register.filter('cut',cut)
register.filter('lower',lower)**
k#使用装饰器
@register.filter(name='cut')
def cut(value,arg):
return value.replace(arg,'')
@register.filter(name='lower')
def lower(value):
return value.lower()
自定义模板标签
当Django编译一个模板时,它的原始模板分成一个个节点,每个节点艘时django.template.Node的一个实例,并且具备render()方法。于是一个已编译模板就是节点对象的一个列表。
Hello,{{ person.name }}
{% ifequal name.birthday today %}
Happy birthday!
{% else %}
Be your to come back on your birthday
{% endifequal %}
编译上面的模板,被变异的模板表现为节点列表的形式:
The time is {% current_time "%Y‐%m‐%d %I:%M %p" %}.
#每个标签编译函数有两个参数parser 和 token,parser时模板解析器对象
#token是正在被解析的语句
def do_current_time(parser,token):
try:
#token.split_contents()按空格拆分参数同时保证引号中的字符串不拆分
#不使用token.contents.split()是因为它不健壮,它值是简单的按照所有
# 空格进行拆分,包括那些引号因起来的字符串中的空格
#token.contents是包含标签原始内容的字符串
tag_name,format_string=token.split_contents()
except ValueError:
# token.split_contents()[0]总是记录标签的名字,就标签没有任何参数
msg='%r tag requires a single argument' %token.split_contents()[0]
raise template.TemplateSyntaxError(msg)
#CurrentTimeNode包含了节点需要知道的关于这个标签的全部信息。
return CurrentTimeNode(format_string[1:-1])
编写模板节点
import datetime
#编写模板节点
class CurrentTimeNode(template.Node):
def __init__(self,format_string):
self.format_string=str(format_string)
def render(self, context):
now=datetime.datetime.now()
return now.strftime((self.format_string))
注册标签
register.filter('current_time',do_current_time())
在上下文中设置变量
要早上下文中设置变量,在render()函数的context对象上使用字典赋值。
class CurrentTimeNode2(template.Node):
def __init__(self,format_string):
self.format_string=str(format_string)
def render(self, context):
now=datetime.datetime.now()
context['current_time']=now.strftime(self.format_string)
return ''
render()应当总是返回一个字符串,所以如果模板标签只是要设置变量,render()就要返回一个空字符串。
但是变量名current_time是硬编码的,这意味着你必须确定你的模板在其他地方都用不到{{ current_time }},因为{% current_time2 %}会盲目覆盖变量值。
import re
class CurrentTimeNode3(template.Node):
def __init__(self,format_string,var_name):
self.format_string=str(format_string)
self.var_name=var_name
def render(self, context):
now=datetime.datetime.now()
context['current_time']=now.strftime(self.format_string)
return ''
def do_current_time(parser,token):
try:
tag_name,arg=token.split_contents(None,1)
except ValueError:
msg='%r tag requires a single argument' %token.split_contents()[0]
raise template.TemplateSyntaxError(msg)
m=re.search(r'(.*?) as (\w+)',arg)
if m:
fmt,var_name=m.groups()
else:
msg='%r tag had invalid argument' %tag_name
raise template.TemplateSyntaxError(msg)
if not (fmt[0] ==fmt[-1] and fmt[0] in ('"',"'")):
msg='%r tags argument should be in quotes' %(tag_name)
raise template.TemplateSyntaxError(msg)
#现在把字符串和变量名传递给CurrentTimeNode3
return CurrentTimeNode(fmt[1:-1],var_name)