Django深入模板引擎

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wolaiye320/article/details/51909232

虽然和Django的模板语言的大多数交互都是模板作者的工作,但你可能想定制和扩展模板引擎,让它做一些它不能做的事情,或者是以其他方式让你的工作更轻松。

如果你想扩展模板系统或者只是对它的工作原理感觉到好奇,涉及了你需要了解的东西。

如果你想把Django的模版系统作为另外一个应用程序的一部分(比如,仅使用django的模板系统而不使用Django框架的其他部分),那你一定要读一下配置独立模式下的模版系统这一节。

模板语言回顾

首先,让我们快速回顾一下若干专业术语

模板是一个纯文本文件,或是一个用Django模板语言标记过的普通的Python字符串,一个模板可以包含区块标签和变量。

区块标签是在一个模板里面起作用的的标记,这个定义故意说的很含糊,比如,一个区块标签可以生成内容,可以作为一个控制结构(if语句或for循环),可以获取数据库内容,或者访问其他的模板标签。

区块标签被{%%}环绕:

{% if is_logged_in %}

Thanks for logging in!

{% else %}

Please log in.

{% endif %}

变量是一个在模板里用来输出值的标记。

变量标签被{{}}环绕:

My first name is {{ first_name }}. My last name is {{ last_name }}.

context是一个传递给模板的名称到值的映射(类似Python字典)。

模板渲染就是是通过从context获取值来替换模板中变量并执行所有的区块标签。


本章的其余部分讨论了扩展模板引擎的方法。首先,我们快速的看一下第四章遗留的内容。

RequestContextContext处理器

你需要一段context来解析模板。一般情况下,这是一个django.template.Context的实例,不过在Django中还可以用一个特殊的子类,django.template.RequestContext,这个运用起来稍微有些不同。RequestContext默认地在模板context中加入了一些变量,如HttpRequest对象或当前登录用户的相关信息。

当你不想在一系例模板中都明确指定一些相同的变量时,你应该使用RequestContext。例如,看下面的四个视图:

from django.template import loader, Context

def view_1(request):

# ...

t = loader.get_template('template1.html')

c = Context({

'app': 'My app',

'user': request.user,

'ip_address': request.META['REMOTE_ADDR'],

'message': 'I am view 1.'

})

return t.render(c)

def view_2(request):

# ...

t = loader.get_template('template2.html')

c = Context({

'app': 'My app',

'user': request.user,

'ip_address': request.META['REMOTE_ADDR'],

'message': 'I am the second view.'

})

return t.render(c)

def view_3(request):

# ...

t = loader.get_template('template3.html')

c = Context({

'app': 'My app',

'user': request.user,

'ip_address': request.META['REMOTE_ADDR'],

'message': 'I am the third view.'

})

return t.render(c)

def view_4(request):

# ...

t = loader.get_template('template4.html')

c = Context({

'app': 'My app',

'user': request.user,

'ip_address': request.META['REMOTE_ADDR'],

'message': 'I am the fourth view.'

})

return t.render(c)

(注意,在这些例子中,我们故意使用render_to_response()这个快捷方法,而选择手动载入模板,手动构造context对象然后渲染模板。是为了能够清晰的说明所有步骤。)

每个视图都给模板传入了三个相同的变量:appuserip_address。如果我们能把这些冗余去掉会不会看起来更好?

创建RequestContextcontext处理器就是为了解决这个问题。Context处理器允许你设置一些变量,它们会在每个context中自动被设置好,而不必每次调用render_to_response()时都指定。要点就是,当你渲染模板时,你要用RequestContext而不是Context

最直接的做法是用context处理器来创建一些处理器并传递给RequestContext。上面的例子可以用context processors改写如下:

from django.template import loader, RequestContext

def custom_proc(request):

"A context processor that provides 'app', 'user' and 'ip_address'."

return {

'app': 'My app',

'user': request.user,

'ip_address': 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 the second view.'},

processors=[custom_proc])

return t.render(c)

def view_3(request):

# ...

t = loader.get_template('template3.html')

c = RequestContext(request, {'message': 'I am the third view.'},

processors=[custom_proc])

return t.render(c)

def view_4(request):

# ...

t = loader.get_template('template4.html')

c = RequestContext(request, {'message': 'I am the fourth view.'},

processors=[custom_proc])

return t.render(c)

我们来通读一下代码:

§ 首先,我们定义一个函数custom_proc。这是一个context处理器,它接收一个HttpRequest对象,然后返回一个字典,这个字典中包含了可以在模板context中使用的变量。它就做了这么多。

§ 我们在这四个视图函数中用RequestContext代替了Context。在context对象的构建上有两个不同点。一,RequestContext的第一个参数需要传递一个HttpRequest对象,就是传递给视图函数的第一个参数(request)。二,RequestContext有一个可选的参数processors,这是一个包含context处理器函数的list或者tuple。在这里,我们传递了我们之前定义的函数curstom_proc

§ 每个视图的context结构里不再包含appuserip_address等变量,因为这些由custom_proc函数提供了。

§ 每个视图仍然具有很大的灵活性,可以引入我们需要的任何模板变量。在这个例子中,message模板变量在每个视图中都不一样。

在前面,我们介绍了render_to_response()这个快捷方式,它可以省掉调用loader.get_template(),然后创建一个Context对象,最后再调用模板对象的render()方法。为了讲解context处理器底层是如何工作的,在上面的例子中我们没有使用render_to_response()。但是建议选择render_to_response()作为context的处理器。像这样,使用context_instance参数:

from django.shortcuts import render_to_response

from django.template import RequestContext

def custom_proc(request):

"A context processor that provides 'app', 'user' and 'ip_address'."

return {

'app': 'My app',

'user': request.user,

'ip_address': request.META['REMOTE_ADDR']

}

def view_1(request):

# ...

return render_to_response('template1.html',

{'message': 'I am view 1.'},

context_instance=RequestContext(request, processors=[custom_proc]))

def view_2(request):

# ...

return render_to_response('template2.html',

{'message': 'I am the second view.'},

context_instance=RequestContext(request, processors=[custom_proc]))

def view_3(request):

# ...

return render_to_response('template3.html',

{'message': 'I am the third view.'},

context_instance=RequestContext(request, processors=[custom_proc]))

def view_4(request):

# ...

return render_to_response('template4.html',

{'message': 'I am the fourth view.'},

context_instance=RequestContext(request, processors=[custom_proc]))

在这,我们将每个视图的模板渲染代码写成了一个单行。

虽然这是一种改进,但是,请考虑一下这段代码的简洁性,我们现在不得不承认的是在另外一方面有些过分了。我们以代码冗余(在processors调用中)的代价消除了数据上的冗余(我们的模板变量)。由于你不得不一直键入processors,所以使用context处理器并没有减少太多的打字次数。

Django因此提供对全局context处理器的支持。TEMPLATE_CONTEXT_PROCESSORS指定了总是使用哪些context processors。这样就省去了每次使用RequestContext都指定processors的麻烦^_^

默认情况下,TEMPLATE_CONTEXT_PROCESSORS设置如下:

TEMPLATE_CONTEXT_PROCESSORS = (

'django.core.context_processors.auth',

'django.core.context_processors.debug',

'django.core.context_processors.i18n',

'django.core.context_processors.media',

)

这个设置是一个可调用函数的Tuple,其中的每个函数使用了和上文中我们的custom_proc相同的接口:接收一个request对象作为参数,返回一个包含了将被合并到context中的项的字典。请注意TEMPLATE_CONTEXT_PROCESSORS中的值是以strings 的形式给出的,这意味着这些处理器必须在你的python路径中的某处(这样你才能在设置中引用它们)

每个处理器将会按照顺序应用。也就是说如果你在第一个处理器里面向context添加了一个变量,而第二个处理器添加了同样名字的变量,那么第二个将会覆盖第一个。

Django提供了几个简单的context处理器,有些在默认情况下被启用的。

django.core.context_processors.auth

如果TEMPLATE_CONTEXT_PROCESSORS包含了这个处理器,那么每个RequestContext将包含这些变量:

§ user:一个django.contrib.auth.models.User实例,描述了当前登录用户(或者一个AnonymousUser实例,如果客户端没有登录)。

§ messages:一个当前登录用户的消息列表(字符串)。在后台,对每一个请求这个变量都调用request.user.get_and_delete_messages()方法。这个方法收集用户的消息然后把它们从数据库中删除。

§ permsdjango.core.context_processors.PermWrapper的一个实例,包含了当前登录用户有哪些权限。

关于userspermissionsmessages的更多内容请参考第12章。

django.core.context_processors.debug

这个处理器把调试信息发送到模板层。如果TEMPLATE_CONTEXT_PROCESSORS包含了这个处理器,RequestContext将包含这些变量:

§ debug:你设置的DEBUG的值(TrueFalse)。你可以在模板里面用这个变量测试是否处在debug模式下。

§ sql_queries:包含类似于{'sql': ..., 'time': ...}的字典的一个列表,记录了这个请求期间的每个SQL查询以及查询所耗费的时间。这个列表是按照请求顺序进行排列的。

由于调试信息比较敏感,所以这个context处理器只有当同时满足下面两个条件的时候才有效:

§ DEBUG参数设置为True

§ 请求的ip应该包含在INTERNAL_IPS的设置里面。

django.core.context_processors.i18n

如果这个处理器启用,每个RequestContext将包含下面的变量:

§ LANGUAGESLANGUAGES选项的值。

§ LANGUAGE_CODE:如果request.LANGUAGE_CODE存在,就等于它;否则,等同于LANGUAGE_CODE设置。

附录E提供了有关这两个设置的更多的信息。

django.core.context_processors.request

如果启用这个处理器,每个RequestContext将包含变量request也就是当前的HttpRequest对象。注意这个处理器默认是不启用的,你需要激活它。

Context处理器的一些建议

编写处理器的一些建议:

§ 使每个context处理器完成尽可能小的功能。使用多个处理器是很容易的,所以你可以根据逻辑块来分解功能以便将来重用。

§ 要注意TEMPLATE_CONTEXT_PROCESSORS里的context processor将会在每个模板中有效,所以要变量的命名不要和模板的变量冲突。变量名是大小写敏感的,所以processor的变量全用大写是个不错的主意。

§ 只要它们存放在你的Python的搜索路径中,它们放在哪个物理路径并不重要,这样你可以在TEMPLATE_CONTEXT_PROCESSORS设置里指向它们。也就是说,你要把它们放在app或者project目录里名为context_processors.py的文件。

模板加载的内幕

一般说来,你会把模板以文件的方式存储在文件系统中,但是你也可以使用自定义的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_DIRS的设置来载入模板。但是,在内部这些函数可以指定一个模板加载器来完成这些繁重的任务。

一些加载器默认被禁用,但是你可以通过编辑TEMPLATE_LOADERS设置来激活它们。TEMPLATE_LOADERS应当是一个字符串的元组,其中每个字符串都表示一个模板加载器。这些模板加载器随Django一起发布。

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来发布你的应用,那么你就需要启用它。

Django按照TEMPLATE_LOADERS设置中的顺序使用模板加载器。它逐个使用每个加载器直至找到一个匹配的模板。

扩展模板系统

既然你已经对模板系统的内幕了解多了一些,让我们来看看如何使用自定义的代码来拓展这个系统吧。

绝大部分的模板定制是以自定义标签和/或过滤器的方式来完成的。尽管Django模板语言自带了许多内建标签和过滤器,但是你可能还是需要组建你自己的标签和过滤器库来满足你的需要。幸运的是,定义你自己的功能非常容易。

创建一个模板库

不管是写自定义标签还是过滤器,第一件要做的事是给template library 创建使Django能够勾入的机制。

创建一个模板库分两步走:

第一,决定哪个Django应用应当拥有这个模板库。如果你通过manage.py startapp创建了一个应用,你可以把它放在那里,或者你可以为模板库单独创建一个应用。

无论你采用何种方式,请确保把你的应用添加到INSTALLED_APPS中。我们稍后会解释这一点。

第二,在适当的Django应用包里创建一个templatetags目录。这个目录应当和models.pyviews.py等处于同一层次。例如:

books/

__init__.py

models.py

templatetags/

views.py

templatetags中创建两个空文件:一个__init__.py(告诉Python这是一个包含了Python代码的包)和一个用来存放你自定义的标签/过滤器定义的文件。第二个文件的名字稍后将用来加载标签。例如,如果你的自定义标签/过滤器在一个叫作poll_extras.py的文件中,你需要在模板中写入如下内容:

{% load poll_extras %}

{% load %}标签检查INSTALLED_APPS中的设置,仅允许加载已安装的Django应用程序中的模板库。这是一个安全特性。它可以让你在一台电脑上部署很多的模板库的代码,而又不用把它们暴露给每一个Django安装。

如果你写了一个不和任何模型/视图关联的模板库,那么得到一个仅包含templatetags包的Django应用程序包是完全正常的。对于在templatetags包中放置多少个模块没有做任何的限制。需要了解的是:{% load %}语句会为指定的Python模块名(而非应用程序名)加载标签或过滤器。

一旦创建了Python模块,你只需根据是要编写过滤器还是标签来相应的编写一些Python代码。

要成为有效的标签库,模块必须包含一个模块级的变量:register,这是一个template.Library的实例。这个template.Library实例是包含所有已注册的标签及过滤器的数据结构。因此,在模块的顶部位置插入下述代码:

from django import template

register = template.Library()

备注

请阅读Django默认的过滤器和标签的源码,那里有大量的例子。他们分别为:django/template/defaultfilters.pydjango/template/defaulttags.py。某些应用程序在django.contrib中也包含模板库。

创建register变量后,你就可以使用它来创建模板的过滤器和标签了。

自定义模板过滤器

自定义过滤器就是有一个或两个参数的Python函数:

§ (输入)变量的值

§ 参数的值,可以是默认值或者完全留空

例如,在过滤器{{ var|foo:"bar" }},过滤器foo会被传入变量var和参数bar的内容。

过滤器函数应该总有返回值,而且不能触发异常,它们都应该静静的失败。如果有一个错误发生,它们要么返回原始的输入字符串,要么返回空的字符串,无论哪个都可以。

这里是一些定义过滤器的例子:

def cut(value, arg):

"Removes all values of arg from the given string"

return value.replace(arg, '')

这里是一些如何使用过滤器的例子:

{{ somevariable|cut:"0" }}

大多数过滤器并不需要参数。下面的例子把参数从你的函数中拿掉了:

def lower(value): # Only one argument.

"Converts a string into all lowercase"

return value.lower()

当你在定义你的过滤器时,你需要用Library实例来注册它,这样就能通过Django的模板语言来使用了:

register.filter('cut', cut)

register.filter('lower', lower)

Library.filter()方法需要两个参数:

§ 过滤器的名称(一个字串)

§ 过滤器函数本身

如果你使用的是Python 2.4或更新,你可以使用register.filter()作为一个装饰器:

@register.filter(name='cut')

def cut(value, arg):

return value.replace(arg, '')

@register.filter

def lower(value):

return value.lower()

像第二个例子中,如果你不使用name参数,那么Django将会使用函数名作为过滤器的名字。

下面是一个完整的模板库的例子,提供了一个cut过滤器:

from django import template

register = template.Library()

@register.filter(name='cut')

def cut(value, arg):

return value.replace(arg, '')

自定义模板标签

标签要比过滤器复杂些,标签几乎能做任何事情。

第四章描述了模板系统的两步处理过程:编译和呈现。为了自定义一个这样的模板标签,你需要告诉Django当遇到你的标签时怎样进行这过程。

Django编译一个模板时,它将原始模板分成一个个节点。每个节点都是django.template.Node的一个实例,并且具备render()方法。于是,一个已编译的模板就是Node对象的一个列表。

当你调用一个已编译模板的render()方法时,模板就会用给定的context来调用每个在它的节点列表上的节点的render()方法。所以,为了定义一个自定义的模板标签,你需要明确这个模板标签转换为一个Node(已编译的函数)和这个noderender()方法。

在下面的章节中,我们将详细解说写一个自定义标签时的所有步骤。

编写编译函数

当遇到一个模板标签(template tag)时,模板解析器就会把标签包含的内容,以及模板解析器自己作为参数调用一个python函数。这个函数负责返回一个和当前模板标签内容相对应的节点(Node)的实例。

例如,写一个显示当前日期的模板标签:{% current_time %},该标签会根据参数指定的strftime格式(参见:http://www.djangoproject.com/r/python/strftime/)显示当前时间。在继续做其它事情以前,先决定标签的语法是一个好主意。在我们的例子里,该标签将会像这样被使用:

<p>The time is {% current_time "%Y-%m-%d %I:%M %p" %}.</p>

备注

没错, 这个模板标签是多余的,Django默认的{% now %}用更简单的语法完成了同样的工作。这个模板标签在这里只是作为一个例子。

这个函数的分析器会获取参数并创建一个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:

msg = '%r tag requires a single argument' % token.contents[0]

raise template.TemplateSyntaxError(msg)

return CurrentTimeNode(format_string[1:-1])


其实这儿包含了不少东西:

§ parser是模板分析器对象,在这个例子中我们没有使用它。

§ token.contents是包含有标签原始内容的字符串。在我们的例子中,它是'current_time "%Y-%m-%d %I:%M %p"'

§ token.split_contents()方法按空格拆分参数同时保证引号中的字符串在一起。应该避免使用token.contents.split()(仅是使用Python的标准字符串拆分),它不够健壮,因为它只是简单的按照所有空格进行拆分,包括那些引号引起来的字符串中的空格。

§ 这个函数负责抛出django.template.TemplateSyntaxError,同时提供所有语法错误的有用信息。

§ 不要把标签名称硬编码在你的错误信息中,因为这样会把标签名称和你的函数耦合在一起。token.split_contents()[0]总会是是你的标签的名称,即使标签没有参数。

§ 这个函数返回一个CurrentTimeNode(稍后我们将创建它),它包含了节点需要知道的关于这个标签的全部信息。在这个例子中,它只是传递了参数"%Y-%m-%d %I:%M %p"。模板标签开头和结尾的引号使用format_string[1:-1]除去。

§ 模板标签编译函数必须返回一个Node子类,返回其它值都是错的。

编写模板节点

编写自定义标签的第二步就是定义一个拥有render()方法的Node子类。继续前面的例子,我们需要定义CurrentTimeNode

import datetime

class CurrentTimeNode(template.Node):

def __init__(self, format_string):

self.format_string = format_string

def render(self, context):

now = datetime.datetime.now()

return now.strftime(self.format_string)

这两个函数(__init__render)与模板处理中的两步(编译与渲染)直接对应。这样,初始化函数仅仅需要存储后面要用到的格式字符串,而render()函数才做真正的工作。

与模板过滤器一样,这些渲染函数应该捕获错误,而不是抛出错误。模板标签只能在编译的时候才能抛出错误。

注册标签

最后,你需要用你的模块Library实例注册这个标签。注册自定义标签与注册自定义过滤器非常类似(如前文所述)。实例化一个template.Library实例然后调用它的tag()方法。例如:

register.tag('current_time', do_current_time)

tag()方法需要两个参数:

模板标签的名字(字符串)。如果被遗漏的话,将会使用编译函数的名字。


编译函数。

和注册过滤器类似,也可以在Python2.4及其以上版本中使用register.tag修饰:

@register.tag(name="current_time")

def do_current_time(parser, token):

# ...

@register.tag

def shout(parser, token):

# ...

如果你像在第二个例子中那样忽略name参数的话,Django会使用函数名称作为标签名称。

在上下文中设置变量

前一节的例子只是简单的返回一个值。很多时候设置一个模板变量而非返回值也很有用。那样,模板作者就只能使用你的模板标签所设置的变量。

要在上下文中设置变量,在render()函数的context对象上使用字典赋值。这里是一个修改过的CurrentTimeNode,其中设定了一个模板变量current_time,并没有返回它:

class CurrentTimeNode2(template.Node):

def __init__(self, format_string):

self.format_string = format_string

def render(self, context):

now = datetime.datetime.now()

context['current_time'] = now.strftime(self.format_string)

return ''

注意render()返回了一个空字符串。render()应当总是返回一个字符串,所以如果模板标签只是要设置变量,render()就应该返回一个空字符串。

你应该这样使用这个新版本的标签:

{% current_time2 "%Y-%M-%d %I:%M %p" %}

<p>The time is {{ current_time }}.</p>

但是CurrentTimeNode2有一个问题:变量名current_time是硬编码的。这意味着你必须确定你的模板在其它任何地方都不使用{{ current_time }},因为{% current_time2 %}会盲目的覆盖该变量的值。

一种更简洁的方案是由模板标签来指定需要设定的变量的名称,就像这样:

{% get_current_time "%Y-%M-%d %I:%M %p" as my_current_time %}

<p>The current time is {{ my_current_time }}.</p>

为此,你需要重构编译函数和Node类,如下所示:

import re

class CurrentTimeNode3(template.Node):

def __init__(self, format_string, var_name):

self.format_string = format_string

self.var_name = var_name

def render(self, context):

now = datetime.datetime.now()

context[self.var_name] = now.strftime(self.format_string)

return ''

def do_current_time(parser, token):

# This version uses a regular expression to parse tag contents.

try:

# Splitting by None == splitting by spaces.

tag_name, arg = token.contents.split(None, 1)

except ValueError:

msg = '%r tag requires arguments' % token.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 arguments' % tag_name

raise template.TemplateSyntaxError(msg)

if not (fmt[0] == fmt[-1] and fmt[0] in ('"', "'")):

msg = "%r tag's argument should be in quotes" % tag_name

raise template.TemplateSyntaxError(msg)

return CurrentTimeNode3(fmt[1:-1], var_name)

现在do_current_time()把格式字符串和变量名传递给CurrentTimeNode3

分析直至另一个块标签

模板标签可以像包含其它标签的块一样工作(想想{% if %}{% for %}等)。要创建一个这样的模板标签,在你的编译函数中使用parser.parse()

标准的{% comment %}标签是这样实现的:

def do_comment(parser, token):

nodelist = parser.parse(('endcomment',))

parser.delete_first_token()

return CommentNode()

class CommentNode(template.Node):

def render(self, context):

return ''

parser.parse()接收一个包含了需要分析块标签名的元组作为参数.它返回一个django.template.NodeList实例,它是一个包含了所有Node对象的列表,这些对象代表了分析器在遇到元组中任一标签名之的内容.

因此在前面的例子中,nodelist是在{% comment %}{% endcomment %}之间所有节点的列表,不包括{% comment %}{% endcomment %}自身。

parser.parse()被调用之后,分析器还没有清除{% endcomment %}标签,因此代码需要显式地调用parser.delete_first_token()来防止该标签被处理两次。

之后CommentNode.render()只是简单地返回一个空字符串。在{% comment %}{% endcomment %}之间的所有内容都被忽略。

分析直至另外一个块标签并保存内容

1

在前一个例子中,do_comment()抛弃了在{% comment %}{% endcomment %}之间的所有内容。同样,也可以对块标签之间的代码进行处理。

例如,这个自定义模板标签:{% upper %},它把自己和{% endupper %}之间的所有内容都变成大写:

{% upper %}

This will appear in uppercase, {{ your_name }}.

{% endupper %}

就像前面的例子一样,我们将使用parser.parse()。这次,我们将产生的nodelist传递给Node

@register.tag

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)

return output.upper()

这里唯一的一个新概念是UpperNode.render()中的self.nodelist.render(context)。它对节点列表中的每个Node简单的调用render()

更多的复杂渲染示例请查看django/template/defaulttags.py中的{% if %}{% for %}{% ifequal %}{% ifchanged %}的代码。

简单标签的快捷方式

许多模板标签接收单一的字符串参数或者一个模板变量引用,然后独立地根据输入变量和一些其它外部信息进行处理并返回一个字符串.例如, 我们先前写的current_time标签就是这样一个例子.我们给它格式字符串, 然后它把时间作为字符串返回.

为了简化这类标签,Django提供了一个帮助函数:simple_tag。这个函数是django.template.Library的一个方法,它接受一个只有一个参数的函数作参数,把它包装在render函数和之前提及过的其他的必要单位中,然后通过模板系统注册标签。

我们之前的的current_time函数于是可以写成这样:

def current_time(format_string):

return datetime.datetime.now().strftime(format_string)

register.simple_tag(current_time)

Python 2.4中,也可以使用修饰语法:

@register.simple_tag

def current_time(token):

...

有关simple_tag辅助函数,需要注意下面一些事情:

§ 传递给我们的函数的只有(单个)参数。

§ 在我们的函数被调用的时候,检查必需参数个数的工作已经完成了,所以我们不需要再做这个工作。

§ 参数两边的引号(如果有的话)已经被截掉了,所以我们会接收到一个普通字符串。

包含标签

另外一类常用的模板标签是通过渲染其他模板显示数据的。比如说,Django的后台管理界面,它使用了自定义的模板标签来显示新增/编辑表单页面下部的按钮。那些按钮看起来总是一样的,但是链接却随着所编辑的对象的不同而改变。这就是一个使用小模板很好的例子,这些小模板就是当前对象的详细信息。

这些排序标签被称为包含标签。如何写包含标签最好通过举例来说明。我们来写一个可以生成一个选项列表的多选项对象Poll。标签这样使用:

{% show_results poll %}

结果将会像下面这样:

<ul>

<li>First choice</li>

<li>Second choice</li>

<li>Third choice</li>

</ul>

首先,我们定义一个函数,通过给定的参数生成一个字典形式的结果。需要注意的是,我们只需要返回字典类型的结果就行了,它将被用做模板片断的context (译注:dict key 作为变量名在模板中被使用)

def show_books_for_author(author):

books = author.book_set.all()

return {'books': books}

接下来,我们创建用于渲染标签输出的模板。在我们的例子中,模板很简单:

<ul>

{% for book in books %}

<li> {{ book }} </li>

{% endfor %}

</ul>

最后,我们通过对一个Library对象使用inclusion_tag()方法来创建并注册这个包含标签。

在我们的例子中,如果先前的模板在polls/result_snippet.html文件中,那么我们这样注册标签:

register.inclusion_tag('books/books_for_author.html')(show_books_for_author)

和往常一样,我们也可以使用Python 2.4中的修饰语法,所以我们还可以这么写:

@register.inclusion_tag('books/books_for_author.html')

def show_books_for_author(show_books_for_author):

...

有时候,你的包含标签需要访问父模板的context。为了解决这个问题,Django提供了一个takes_context选项。如果你在创建模板标签时,指明了这个选项,这个标签就不需要参数,并且下面的Python函数会带一个参数:就是当这个标签被调用时的模板context

例如,你正在写一个包含标签,该标签包含有指向主页的home_linkhome_title变量。Python函数会像这样:

@register.inclusion_tag('link.html', takes_context=True)

def jump_link(context):

return {

'link': context['home_link'],

'title': context['home_title'],

}

备注

函数的第一个参数必须context

模板link.html可能包含下面的东西:

Jump directly to <a href="{{ link }}">{{ title }}</a>.

然后您想使用自定义标签时,就可以加载它的库,然后不带参数地调用它,就像这样:

{% jump_link %}

编写自定义模板加载器

Djangos 内置的模板加载器(在先前的模板加载内幕章节有叙述)通常会满足你的所有的模板加载需求,但是如果你有特殊的加载需求的话,编写自己的模板加载器也会相当简单。比如:你可以从数据库加载模板,或者使用 SubversionsPython实现直接从Subversion库加载模板,再或者(稍后展示)从zip文件加载模板。

一个模板加载器,也就是TEMPLATE_LOADERS中的每一项,都要能被下面这个接口所调用:

load_template_source(template_name, template_dirs=None)

参数template_name是所加载模板的名称 (和传递给loader.get_template()或者loader.select_template()一样),template_dirs是一个可选的包含除去TEMPLATE_DIRS之外的搜索目录列表。

如果加载器能够成功加载一个模板,它应当返回一个元组:(template_source, template_path)。在这里的template_source就是将被模板引擎编译的的模板字符串,而template_path是被加载的模板的路径。由于那个路径可能会出于调试目显示给用户,因此它应当很快的指明模板从哪里加载而来。

如果加载器加载模板失败,那么就会触发django.template.TemplateDoesNotExist异常。

每个加载函数都应该有一个名为is_usable的函数属性。这个属性是一个布尔值,用于告知模板引擎这个加载器是否在当前安装的Python中可用。例如,如果pkg_resources模块没有安装的话,eggs加载器(它能够从python eggs中加载模板)就应该把is_usable设为False,因为必须通过pkg_resources才能从eggs中读取数据。

一个例子可以清晰地阐明一切。这儿是一个模板加载函数,它可以从ZIP文件中加载模板。它使用了自定义的设置TEMPLATE_ZIP_FILES来取代了TEMPLATE_DIRS用作查找路径,并且它假设在此路径上的每一个文件都是包含模板的ZIP文件:

import zipfile

from django.conf import settings

from django.template import TemplateDoesNotExist

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中去。

使用内置的模板参考

Django管理界面包含一个完整的参考资料,里面有所有的可以在特定网站上使用的模板标签和过滤器。它设计的初衷是Django程序员提供给模板开发人员的一个工具。你可以点击管理页面右上角的文档链接来查看这些资料。

参考说明分为4个部分:标签、过滤器、模型和视图。标签过滤器部分描述了所有内置的标签(实际上,第4章中用到的标签和过滤器都直接来源于那几页)以及一些可用的自定义标签和过滤器库。

视图页面是最有价值的。网站中的每个URL都在这儿有独立的入口。如果相关的视图包含一个文档字符串,点击URL,你就会看到:

§ 生成本视图的视图函数的名字

§ 视图功能的一个简短描述

§ 上下文或一个视图模板中可用的变量的列表

§ 视图使用的模板的名字

要想查看关于视图文档的更详细的例子,请阅读Django的通用object_list视图部分的源代码,它位于django/views/generic/list_detail.py文件中。

通常情况下,由Django构建的网站都会使用数据库对象,模型页面描述了系统中所有类型的对象,以及该对象对应的所有可用字段。

总之,这些文档告诉你在模板中的所有可用的标签、过滤器、变量和对象。

配置独立模式下的模板系统

备注

这部分只针对于对在其他应用中使用模版系统作为输出组件感兴趣的人。如果你是在Django应用中使用模版系统,请略过此部分。

通常,Django会从它的默认配置文件和由DJANGO_SETTINGS_MODULE环境变量所指定的模块中加载它需要的所有配置信息。但是当你想在非Django应用中使用模版系统的时候,采用环境变量并不是很好的方法。比起为模版系统单独采用配置文件并用环境变量来指向它,你可能更希望能够在你的应用中采用一致的配置方法来配置模版系统和其他部分

为了解决这个问题,你需要使用附录E中所描述的手动配置选项。简单来说,你需要引入合适的模板系统,并且在调用任何模板函数之前调用django.conf.settings.configure()来指定任何你想要的设置。

你可能会考虑至少要设置TEMPLATE_DIRS(如果你打算使用模板加载器),DEFAULT_CHARSET(尽管默认的utf-8编码相当好用),以及TEMPLATE_DEBUG。所有可用的选项都在附录E中详细描述,所有以TEMPLATE_开头的选项都可能使你感兴趣的。


转载请注明文章出处:http://blog.csdn.net/wolaiye320/article/details/51909232

猜你喜欢

转载自blog.csdn.net/wolaiye320/article/details/51909232
今日推荐