模板 是一个纯文本文件,或是一个用Django模板语言标记过的普通的Python字符串。 模板可以包含模板标签和变量。
模板标签 是在一个模板里面起作用的的标记。 这个定义故意搞得模糊不清。 例如,一个模版标签能够产生作为控制结构的内容(一个 if语句或for 循环), 可以获取数据库内容,或者访问其他的模板标签。
模板标签被 {% 和 %} 包围:
变量 是一个在模板里用来输出值的标记
变量标签被 {{ 和 }} 包围
context 是一个传递给模板的名称到值的映射(类似Python字典)。
模板 渲染 就是是通过从context获取值来替换模板中变量并执行所有的模板
模版变量(variable) : {{person_name}}
模版系统引用变量{{ foo.bar }}时的,句点查找规则
字典类型查找 (比如 foo["bar"] )
属性查找 (比如 foo.bar )
方法调用 (比如 foo.bar() ) #仅在方法无需传入参数时,其调用才有效。 否则将会转移到下一个查找类型
列表类型索引查找 (比如 foo[bar] )
注意:句点查找可以多级深度嵌套,例:poson.name.upper,如果一个变量不存在,模板系统会把它展示为空字符串
避免方法被模版调用
def delete(self):
delete.alters_data = True #方法的alters_data属性可避免方法被模板调用
模版标签(template tag):{%ifordered_warranty%} .......{% endif %}
if...and....or....not... 注:and 和 or不能同用,不支持用圆括号,不支持elif,一定要用 endif关闭
for..in...[reversed]....endfor, ,一定要用 endfor关闭,reversed(反向迭代)是可选的,不支持break,continue
forloop.counter 总是一个表示当前循环的执行次数的整数计数器,从1始
orloop.counter0 类似于 forloop.counter ,但是它是从0计数的。
forloop.revcounter 是表示循环中剩余项的整型变量。forloop.revcounter0 =forloop.revcounter -1
forloop.first 是一个布尔值,如果该迭代是第一次执行,它为真
forloop.parentloop 是一个指向当前循环的上一级循环的 forloop 对象的引用(在嵌套循环的情况下)
ifequal/ifnotequal....endifequal 比较两个变量的值是相等/不等 支持可选的 {%else%},只有模板变量,字符串,整数,小数可作为 参数
模版系统示例
>>> from django.template import Context, Template
>>> t = Template('My name is {{ name }}.')#也可引用对象方法(无法传参)、属性,使用列表索引等
>>> c = Context({'name': 'Stephane'})# Context 类,可选的参数:一个字典类型映射变量和它们的值(值=任意对象)
# Context 类可使用字典语法修改
>>> t.render(c)#返回的值是一个Unicode对象,不是普通的Python字符串t.render()输出是不经python解释的源码,print t.render()输出的是经python解释过的效果。
u'My name is Stephane.'
模版注释:
This is a
{# this is not a comment #} #只能单行
{% comment %}#可多行
This is a
multi-line comment.
{% endcomment %}
test.
过滤器(filter) :{{ ship_date|date:"F j, Y" }} #便捷的转换变量输出格式方式 将变量ship_date传递给date过滤器,
同时指定参数”F j,Y”。date根据参数进行格式输出。过滤器用管道符(|)来调用
{{ name|lower }}#显示的内容是变量name被过滤器lower处理后的结果,它功能是转换文本为小写。
{{ my_list|first|upper }}#查找列表的第一个元素并将其转化为大写
{{ bio|truncatewords:"30" }}显示变量 bio 的前30个词
addslashes : 添加反斜杠到任何反斜杠、单引号或者双引号前面。这在处理包含JavaScript的文本时是非常有用的
{{ pub_date|date:"F j, Y" }}date : 按指定的格式字符串参数格式化 date 或者 datetime 对象
length : 返回变量的长度,任何知道怎么测定长度的Python 对象(也就是说有 __len__() 方法的对象)
使用模版文件 #get_template可以使用子目录
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()
t = get_template('current_datetime.html')#在文件系统中找出模板,并返回一个编译好的 Template
html = t.render(Context({'current_date': now}))
return HttpResponse(html)
模版文件路径设置
TEMPLATE_DIRS = ( #在settings.py里
os.path.join(os.path.dirname(__file__), 'templates').replace('\\','/'), #当前项目路径下templates,单元素元组中必须使用逗号
)
更方便的使用模版文件 #render_to_response() 只是对 get_template() 的简单封装
from django.shortcuts import render_to_response
import datetime
def current_datetime(request):
now=datetime.datetime.now()
return render_to_response('mytp.html',{'current_time':now})
可以使用locals()返回的字典对所有局部变量的名称与值进行映射,还包含了 request
current_date = datetime.datetime.now()
return render_to_response('current_datetime.html', locals())
嵌套模版 #所包含的模版执行时的 context 和包含它们的模版是一样的。
{%include%} :允许在(模版中)包含其它的模板的内容。
模版继承
#base.html
<body>{% block content %}{% endblock %}</body>#block标签中可设置默认内容,基模板中的 block标签越多越好
#excents.html
{% extends "base.html" %}#模版引擎以 {%extends%}判断是否子模版,必须为模版中的第一个模版标记
{% block content %}
<p>It is now {{ current_date }}.</p>
{% endblock %}
#{{block.super}}标签可访问父模板中的块的内容,可用于追加内容
#extends的参数一般是字符串,但如果直到运行时方能确定父模板名,这个参数也可是变量。 这使得能够实现很酷的动态功能!
继承不改变context的工作方式,可以使用多层继承,一般是下面的3层法
1,创建base.html 模版,在其中定义站点的主要外观感受,这些都是不常修改的部分
2,为网站的每个区域创建base_section.html模版,这些模版对base.html进行扩展,并包含该区域特定的风格与设计
3,为每种类型的页面设置独立的模版,如论坛页面或者图片库,这些模版拓展相应区域模版。
高级模板功能:
from django.template import RequestContext
def custom_proc(request): #context处理器接收 HttpRequest ,返回字典,字典中包含可在模板context中使用的变量
return { 'app': 'My app', 'user': request.user, 'ip_address': request.META['REMOTE_ADDR'}
def view_1(request):# RequestContext processors版本 processors(处理器)
t = loader.get_template('template1.html')
c = RequestContext(request, {'message': 'I am view 1.'},processors=[custom_proc]) # RequestContext=RC
# 1. RC 第一参数需要 HttpRequest 对象( request );2. RC 可选参数 processors ,context处理器函数的列表或元组
return t.render(c)
def view_1(request):# render_to_response context_instance版本
return render_to_response('template1.html',{'message': 'I am view 1.'},
context_instance=RequestContext(request, processors=[custom_proc]))#context_instance=CI
全局 context处理器的支持
TEMPLATE_CONTEXT_PROCESSORS(模板上下文处理器) 指定了哪些contextprocessors总是默认被使用。这样就省去了每次使用 RequestContext 都指定 processors 的麻烦
默认情况下, TEMPLATE_CONTEXT_PROCESSORS 设置如下:
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.auth', # 用户信息,权限等
'django.core.context_processors.debug',#是否debug模式,记录请求期间的每个SQL查询以及查询所耗费的时间
'django.core.context_processors.i18n', #全球化
'django.core.context_processors.request',每个 RequestContext 将包含变量 request,默认是不启用
'django.core.context_processors.media',
)
每个函数使用了和上文中我们的 custom_proc 相同的接口,它们以request对象作为参数,返回一个会被合并传给context的字典: 接收一个request对象作为参数,返回一个包含了将被合并到context中的项的字典。注意:顺序引用,同名覆盖
写Context处理器的一些建议
编写处理器的一些建议:
每个context处理器完成尽可能小的功能。 使用多个处理器是容易的,可以根据逻辑块来分解功能以便将来复用。
要注意 TEMPLATE_CONTEXT_PROCESSORS 里的context processor 将会在基于这个settings.py的每个 模板中有效,所以变量的命名不要和模板的变量冲突。 变量名是大小写敏感的,所以processor的变量全用大写是个不错的主意。
不论它们存放在哪个物理路径下,只要在你的Python搜索路径中,你就可以在 TEMPLATE_CONTEXT_PROCESSORS 设置里指向它们。 建议你把它们放在应用或者工程目录下名为 context_processors.py 的文件里。
转义
data='<br>'
{{ data|safe }} #变量级转义关闭
{% autoescape off %} #模板级转义关闭
Hello {{ name }}
{% autoescape on %}#模板级转义打开 嵌套 可以通过include标签作用到其他标签,
Hello{{say}}
{% endautoescape %}
{% endautoescape %}
模板加载的内幕
两种方法加载模板
1.django.template.loader.get_template(template_name) : get_template 根据给定的模板名称返回一个已编译的模板(一个 Template 对象)。 如果模板不存在,就触发 TemplateDoesNotExist 的异常。
2.django.template.loader.select_template(template_name_list) : select_template 很像 get_template ,不过它是以模板名称的列表作为参数的。 它会返回列表中存在的第一个模板。如果模板都不存在,将会触发TemplateDoesNotExist异常。
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 包含 ('myproject.polls','myproject.music') ,那么 get_template('foo.html') 会按这个顺序查找模板:
/path/to/myproject/polls/templates/foo.html
/path/to/myproject/music/templates/foo.html
请注意加载器在首次被导入的时候会执行一个优化: 它会缓存一个列表,这个列表包含了 INSTALLED_APPS 中带有 templates 子目录的包。这个加载器默认启用。
django.template.loaders.eggs.load_template_source : 这个加载器类似 app_directories ,只不过它从Python eggs而不是文件系统中加载模板。 这个加载器默认被禁用;如果你使用eggs来发布你的应用,那么你就需要启用它。Python eggs可以将Python代码压缩到一个文件中。
Django按照 TEMPLATE_LOADERS 设置中的顺序使用模板加载器。 它逐个使用每个加载器直至找到一个匹配的模板。
扩展模板系统
绝大部分的模板定制是以自定义标签/过滤器的方式来完成的。尽管Django模板语言自带了许多内建标签和过滤器,但是你可能还是需要组建你自己的标签和过滤器库来满足你的需要。 幸运的是,定义你自己的功能非常容易。
1.创建一个模板库
模板库可以放在某个app下,也可以单独做为一个app,并添加到 INSTALLED_APPS,
在app下创建templatetags目录, 这个目录应当和 models.py 、 views.py 等处于同一层次.
在 templatetags 中创建两个空文件:
一个 __init__.py (告诉Python这是 一个包含了Python代码的包)
一个用来存放你自定义的标签/过滤器定义的文件。 第二个文件的名字将用来加载标签。
如自定义标签/过滤器在一个叫作 poll_extras.py 的文件中,你需要在模板中写入如下内容:
{% load poll_extras %}
2.自定义模板过滤器
完整的模板库例子
from django import template
register = template.Library() #作为合法的模板库,模块需要包含一个名为register的模块级变量
@register.filter(name='cut')
def cut(value, arg): #一个cut过滤器
return value.replace(arg, '')
3.自定义模板标签
标签要比过滤器复杂些,因为标签几乎能做任何事情。
当Django编译一个模板时,它将原始模板分成一个个 节点 。每个节点都是 django.template.Node 的一个实例,并且具备 render() 方法。 于是,一个已编译的模板就是 节点 对象的一个列表。
当你调用一个已编译模板的 render() 方法时,模板就会用给定的context来调用每个在它的节点列表上的所有节点的 render() 方法。 这些渲染的结果合并起来,形成了模板的输出。 因此,要自定义模板标签,你需要指明原始模板标签如何转换成节点(编译函数)和节点的render()方法完成的功能 。
自定义标签时的所有步骤
(1)编写编译函数
from django import template
register = template.Library()
def do_current_time(parser, token): #parser和token,parser是模板解析器对象,token是正在被解析的语句
try:
tag_name, format_string = token.split_contents()
#token.contents 是包含有标签原始内容的字符串; token.split_contents() 方法按空格拆分参数同时保证引号中的字符串不拆分
except ValueError:
msg = '%r tag requires a single argument' % token.split_contents()[0] #token.split_contents()[0]总是记录标签的名字
raise template.TemplateSyntaxError(msg) #语法错误的有用信息
return CurrentTimeNode(format_string[1:-1]) #CurrentTimeNode ,包含了节点需要知道的关于这个标签的全部信息
(2)编写模板节点
编写自定义标签的第二步就是定义一个拥有 render() 方法的 Node 子类。 继续前面的例子,我们需要定义 CurrentTimeNode :
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)
这两个函数( __init__() 和 render() )与模板处理中的两步(编译与渲染)直接对应。
(3)注册标签
@register.tag(name="current_time") #register.tag装饰器
def do_current_time(parser,token): #编译函数 parser模板解析器对象token正在解析的语句
try:
tag_name,format_string=token.split_contents()#按空格拆分参数同时保证引号中的字符串不拆分
except ValueError:
msg='%r tag requires a single argment' % token.split_contents()[0]
#token.split_contents()[0]总是记录标签的名字
raise template.TemplatesSyntaxError(msg)
return CurrentTimeNode(format_string[1:-1])
#包含节点需知道的关于这个标签的全部信息。 此例中只传递了参数 "%Y-%m-%d %I:%M %p" 。模板标签开头和结尾的引号使用 format_string[1:-1] 除去
#模板标签编译函数 必须 返回一个 Node 子类,返回其它值都是错的
#register.tag('current_time', do_current_time) # 模板标签的名字 编译函数
class CurrentTimeNode(template.Node): #拥有 render() 方法的 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)
(4)在上下文中设置变量
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_time2']=now.strftime(self.format_string)
return '' #render() 总是返回一个字符串,如果模板标签只是设置变量,就返回空字符串
(5)分析直至另一个模板标签
@register.tag(name="comment2")
def do_comment2(parser,token):
nodelist=parser.parse(('endcomment2',))#接收需要分析模板标签名元组,返回django.template.NodeList实例
parser.delete_first_token()#清除 endcomment2 防止重复处理
return CommentNode()
class CommentNode(template.Node):
def render(self,context): #只返回一个空字符,在标签间的内容被忽略
return ''
(6)分析直至另外一个模板标签并保存内容
def do_upper(parser, token):
nodelist = parser.parse(('endupper',))
parser.delete_first_token()
return UpperNode(nodelist)
class UpperNode(template.Node):
def __init__(self, nodelist):
self.nodelist = nodelist
def render(self, context):
output = self.nodelist.render(context)#对节点列表中的每个 Node 简单的调用 render() 。
return output.upper()
(7)注册简单标签的快捷方式
@register.simple_tag
class CurrentTimeNode(template.Node):
#....
(8)包含标签
# poll_extras.py
@register.inclusion_tag('book_snippet.html')
def books_for_author(author):
books = Book.objects.filter(authors__id=author)
return {'books': books}
#register.inclusion_tag('book_snippet.html')(books_for_author)
# book_snippet.html
<ul>{% for book in books %}<li>{{ book.title }}</li>{% endfor %}</ul>
访问父模板的context
@register.inclusion_tag('link.html', takes_context=True)
def jump_link(context):
return {'link': context['home_link'],'title': context['home_title'],}
模板 link.html 可能包含下面的东西
Jump directly to <a href="{{ link }}">{{ title }}</a>.
不带参数地调用自定义标签
{% jump_link %}
(9)编写自定义模板加载器
load_template_source(template_name, template_dirs=None)
#template_name 加载模板的名称, template_dirs 可选的代替TEMPLATE_DIRS的搜索目录列表
加载成功: 它返回一个元组:(template_source,template_path) 。
在这里的 template_source 就是将被模板引擎编译的的模板字符串,而 template_path 是被加载的模板的路径。
加载失败:触发 django.template.TemplateDoesNotExist 异常
from django.conf import settings
from django.template import TemplateDoesNotExist
import zipfile
def load_template_source(template_name, template_dirs=None):
"Template loader that loads templates from a ZIP file."
template_zipfiles = getattr(settings, "TEMPLATE_ZIP_FILES", [])
# Try each ZIP file in TEMPLATE_ZIP_FILES.
for fname in template_zipfiles:
try:
z = zipfile.ZipFile(fname)
source = z.read(template_name)
except (IOError, KeyError):
continue
z.close()
# We found a template, so return the source.
template_path = "%s:%s" % (fname, template_name)
return (source, template_path)
# If we reach here, the template couldn't be loaded
raise TemplateDoesNotExist(template_name)
# This loader is always usable (since zipfile is included with Python)
load_template_source.is_usable = True
我们要想使用它,还差最后一步,就是把它加入到 TEMPLATE_LOADERS 。 如果我们将这个代码放入一个叫mysite.zip_loader的包中,那么我们要把mysite.zip_loader.load_template_source加到TEMPLATE_LOADERS中。
(10)配置独立模式下的模板系统
......