-
-
概念:模板系统两步工作:编译和渲染,因此个性化tag必须指定编译和渲染的具体工作
-
编译:将raw文本流分割成node,每个node都是django.template.Node类的一个实例,都有一个render()方法,最终生成一个编译好的template对象,其实一个编译好的template对象就是一个node的列表
-
渲 染:当调用一个编译好的template对象后,如果用context对象(这是一个占位符到具体值得映射dict对象)作为参数调用它的 render()方法(template对象也有这个方法),则template对象递归以context对象调用其每一个node的这个render方 法,这样所有的render()输出连起来就是整个渲染好的输出文档
-
-
如何实现的具体步骤
- 实现编译:
- 扫描文本时,每当遇到一个模板tag,django以一个paser对象和tag本身的内容这2个变量作为参数调用一个python函数(编译函数),这个函数对tag的内容进行识别处理,返回一个Node对象
- 例如,如果写一个模板tag,{% current_time %},它以后面跟着的格式化参数来显示当前的日期和时间:
<p>The time is {% current_time "%Y-%m-%d %I:%M %p" %}.</p>
则编译函数的解析参数"%Y-%m-%d %I:%M %p",并返回一个Node对象
from django import template def do_current_time(parser, token):
try: # split_contents() knows not to split quoted strings. tag_name, format_string = token.split_contents() except ValueError: raise template.TemplateSyntaxError, "%r tag requires a single argument" % token.contents.split()[0] if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")): raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name return CurrentTimeNode(format_string[1:-1]
- 上面的例子注意:
- parser是模板parser对象,这里没有使用到
- token.contents是raw文本的内容,即 : current_time "%Y-%m-%d %I:%M %p"
- token.split_contents()按照空格把上面的 contents分割,引号内的内容作为一个整体是不分割的,如果引号内有空格也不分割,但是另一个函数token.conetents.split() 就不管三七二十一,全部按照空格分隔,这样可能不太好,一般建议都用split_contents()比较安全
- 函数抛出异常template.TemplateSyntaxError,这个异常包含错误信息
- 这个异常使用的tag_name信息,不要硬编码tag_name到这个异常中,一般用token.contents.split()[0]这里比较安全,即使contents没有参数
- 这个函数返回了CurrentTimeNode对象,参数里应该包含一切这个Node对象所需要的信息,这个例子里仅仅是参数format_string :"%Y-%m-%d %I:%M %p",可以用format_string[1:-1]去掉两端的双引号
- 语法解析是比较低层次的,因为这样速度比较快
- 实现渲染:
- 第二步是定义一个Node的子类,它带有一个render()参数,紧接上面的编译过程,这里给出渲染的类:
from django import template import datetime class CurrentTimeNode(template.Node): def __init__(self, format_string): self.format_string = format_string def render(self, context): return datetime.datetime.now().strftime(self.format_string)
- 上面的例子注意:
- __init__()从format_string得到参数,django模板系统总是使用__init__()函数作为参数传递入口
- render()类成员函数就是实际上渲染的工作函数
- render()函数不应该抛出异常,一般只应该静静地失败
- 这个编译与渲染的解耦和很有效且高效地实现了模板系统,这样这个模板系统可以服用以前的编译结果,使用多个context对象来渲染一个编译结果
- 自动转义(1.0新功能):
- 模板tag的输出并不会自动(通过auto-escape filter)转义,仍然需要注意如下
- 如果render()函数将结果存储在一个context变量中(而不是作为一个字符串返回)的时候,如果需要,应该小心地调用 mark_safe()函数。当这个context变量最终被渲染的时候,它先前设置的自动转义将起作用,因此那些当前不需要转义的内容应当被标记成安全 的,用mark_safe()函数
- 另外,如果模板tag内部自己创建了一个context来进行子渲染的时候,在那个context上设置和当前context一致的auto-escape属性:context的__init__()函数上有一个参数叫autoescape可以为这个目的设置:
def render(self, context): t = template.loader.get_template('small_fragment.html') return t.render(Context({'var': obj}, autoescape=context.autoescape))
如果忘了传递当前的context.autoescape到新的context上的话,结果将总是被自动转义,当这么模板tag在一个{% autoescape off %}
块中的时候, 这可能不是一个需要的行为
- 注册tag:
- 最后,用本模块的Library实例注册这个自定义的tag:
register.tag('current_time', do_current_time)
- tag()函数有两个参数:
- 模板tag的名字:一个字符串,则编译函数的名字将被用作模板tag的名字
- 编译函数:一个python函数指针,而不是python函数名字符串
- 在python2.4和以上的版本中,也可以用如下修饰符:
@register.tag(name="current_time") def do_current_time(parser, token): # ... @register.tag def shout(parser, token): # ...
如果tag没有带name参数,则python函数名将用作tag名
- 传递模板变量到tag:
- 实现编译:
-
收藏到:
Del.icio.us