为了解决应用中展示逻辑的需求,Django的模板语言提供了各式各样的内建标签以及过滤器。然而,你或许会发现模板内建的这些工具集合不一定能全部满足你的功能需要。在Python中,你可以通过自定义标签或过滤器的方式扩展模板引擎的功能,并使用{{ load }}标签在你的模板中进行调用。
自定义模板标签和过滤器必须位于Django 的某个应用中。如果它们与某个已存在的应用相关,那么将其与应用绑在一起才有意义;否则,就应该创建一个新的应用来包含它。
这个应用应该包含一个templatetags 目录,和models.py、views.py等文件处于同一级别目录下。如果目录不存在则创建它——不要忘记创建__init__.py 文件以使得该目录可以作为Python 的包。在添加这个模块以后,在模板里使用标签或过滤器之前你将需要重启服务器。
你的自定义的标签和过滤器将放在templatetags 目录下的一个模块里。这个模块的名字是你稍后将要载入标签时使用的,所以要谨慎的选择名字以防与其他应用下的自定义标签和过滤器名字冲突。
例如,你的自定义标签/过滤器在一个名为poll_extras.py的文件中,那么你的app目录结构看起来应该是这样的:
polls/
__init__.py
models.py
templatetags/
__init__.py
poll_extras.py
views.py
然后你可以在模板中像如下这样使用:
{% load poll_extras %}
为了让{{ load }} 标签工作,包含自定义标签的应用必须在INSTALLED_APPS中。这是一种安全功能︰它允许你在单个主机上Host 许多模板库的Python 代码,而不必让每个Django 都可以访问所有的模板库。
在 templatetags 包中放多少个模块没有限制。只需要记住{% load %} 声明将会载入给定模块名中的标签/过滤器,而不是应用的名称。
为了成为一个可用的标签库,这个模块必须包含一个名为 register的变量,它是template.Library 的一个实例,所有的标签和过滤器都是在其中注册的。所以把如下的内容放在你的模块的顶部:
from django import template
register = template.Library()
幕后
对于大量的示例,请阅读Django的默认过滤器和标记的源代码。它们分别位于django/template/defaultfilters.py 和django/template/defaulttags.py 中。
有关load 标签的更多信息,请阅读其文档。
自定义过滤器就是一个带有一个或两个参数的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()
django.template.Library.filter()¶
一旦你写好了你的自定义过滤器函数,你就开始需要把它注册为你的 Library实例,来让它在Django模板语言中可用:
register.filter('cut', cut)
register.filter('lower', lower)
Library.filter()方法需要两个参数:
你还可以把register.filter()用作装饰器:
@register.filter(name='cut')
def cut(value, arg):
return value.replace(arg, '')
@register.filter
def lower(value):
return value.lower()
如果你像上面第二个例子一样没有声明 name 参数,Django将使用函数名作为过滤器的名字。
最后,register.filter() 还接收三个关键字参数,is_safe、needs_autoescape 和expects_localtime。这些参数将在下边过滤器和自动转义 以及过滤器和时区 章节中介绍。
django.template.defaultfilters.stringfilter()¶
如果你正在编写一个只希望用一个字符串来作为第一个参数的模板过滤器,你应当使用stringfilter装饰器。这将在对象被传入你的函数之前把这个对象转换成它的字符串值:
from django import template
from django.template.defaultfilters import stringfilter
register = template.Library()
@register.filter
@stringfilter
def lower(value):
return value.lower()
用这种方式,你甚至可以给这个过滤器传递一个整数,并且不会出现AttributeError (因为整数没有 lower()方法).
编写一个自定义的过滤器时,请考虑一下过滤器如何与Django 的自定转义行为相互作用。请注意有三种类型的字符串可以传递给模板中的代码:
模板过滤代码最终是这两种中的一个:
警告
Avoiding XSS vulnerabilities when reusing built-in filters
Changed in Django 1.8.
Django的内置过滤器默认情况下设置autoescape=True,以便获得正确的自动转义行为并避免跨站点脚本漏洞。
在旧版本的Django中,重用Django的内置过滤器时要格外注意,因为旧版本中,autoescape默认设置成None。You’ll need to pass autoescape=True to get autoescaping.
例如,如果您想编写一个名为urlize_and_linebreaks的自定义过滤器,它结合了内置的urlize和linebreaksbr过滤器,过滤器将如下所示:
from django.template.defaultfilters import linebreaksbr, urlize
@register.filter(needs_autoescape=True)
def urlize_and_linebreaks(text, autoescape=True):
return linebreaksbr(
urlize(text, autoescape=autoescape),
autoescape=autoescape
)
Then:
{{ comment|urlize_and_linebreaks }}
将等同于︰
{{ comment|urlize|linebreaksbr }}
如果你在编写一个操作datetime 对象的自定义过滤器,那么你注册这个自定义过滤器时通常需要将 expects_localtime 标志设置为True:
@register.filter(expects_localtime=True)
def businesshours(value):
try:
return 9 <= value.hour < 17
except AttributeError:
return ''
当设置了此标志,如果你的过滤器的第一个参数是时区相关的日期时间值,那么在把它传递给你的过滤器之前,Django 会根据模板中的时区转换规则 将其转换为基于当前时区的日期时间值。
标签比过滤器更复杂,因为标签可以做任何事情。Django 提供了大量的快捷方式,使得编写大多数类型的标签更为容易。首先我们要探讨这些快捷方式,然后再解释当快捷方式不够用时如何为这些情况从头开始编写标签。
django.template.Library.simple_tag()¶
许多模板标签接收多个参数 —— 字符串或模板变量 —— 并在基于输入的参数和一些其它外部信息进行一些处理后返回一个字符串。例如,current_time 标签可能接受一个格式字符串,并返回与之对应的格式化后的时间。
为了简化这些类型的标签的创建,Django 提供一个辅助函数simple_tag。这个函数是django.template.Library 的一个方法,接受一个任意数目的参数的函数,将其包装在一个render 函数和上面提到的其他必要位,并在模板系统中注册它。
我们的current_time 函数从而可以这样写︰
import datetime
from django import template
register = template.Library()
@register.simple_tag
def current_time(format_string):
return datetime.datetime.now().strftime(format_string)
关于simple_tag 辅助函数几件值得注意的事项︰
如果你的模板标签需要访问当前上下文,你可以在注册标签时使用takes_context 参数︰
@register.simple_tag(takes_context=True)
def current_time(context, format_string):
timezone = context['timezone']
return your_get_current_time_method(timezone, format_string)
请注意,第一个参数必须称作context。
takes_context 选项的工作方式的详细信息,请参阅包含标签。
如果你需要重命名你的标签,你可以给它提供自定义的名称︰
register.simple_tag(lambda x: x - 1, name='minusone')
@register.simple_tag(name='minustwo')
def some_function(value):
return value - 2
simple_tag 函数可以接受任意数量的位置参数和关键字参数。例如:
@register.simple_tag
def my_tag(a, b, *args, **kwargs):
warning = kwargs['warning']
profile = kwargs['profile']
...
return ...
然后在模板中,可以将任意数量的由空格分隔的参数传递给模板标签。像在Python 中一样,关键字参数的值的设置使用等号("=") ,并且必须在位置参数之后提供。例如:
{% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
django.template.Library.inclusion_tag()¶
另一种常见类型的模板标签是通过渲染另外一个模板来显示一些数据。例如,Django 的Admin 界面使用自定义模板标签显示"添加/更改"表单页面底部的按钮。这些按钮看起来总是相同,但链接的目标根据正在编辑的对象而变化 —— 所以它们是使用小模板展示当前对象详细信息很好的例子。(在Admin 界面这种情况下,它是submit_row 标记)。
这些类型的标签被称为"Inclusion 标签"。
示例最能体现如何编写Inclusion 标签。让我们编写一个根据给定的教程中创建的Poll 对象输出一个选项列表的标签。标签的用法像这样︰
{% show_results poll %}
... 输出将像这样:
<ul>
<li>First choice</li>
<li>Second choice</li>
<li>Third choice</li>
</ul>
首先,定义接收这个参数并产生数据字典作为结果的函数。这里重要的一点是,我们只需要返回一个字典,不需要任何复杂的东西。它将用做模板片段的模板上下文。例如:
def show_results(poll):
choices = poll.choice_set.all()
return {'choices': choices}
接下来,创建用于渲染标签输出的模板。这个模板是标签固定的功能︰标签的编写者指定它,不是模板设计者。在我们的例子中,模板非常简单︰
<ul>
{% for choice in choices %}
<li> {{ choice }} </li>
{% endfor %}
</ul>
现在,通过调用Library 对象的inclusion_tag() 方法创建并注册Inclusion 标签。在我们的示例中,如果上面的模板叫做results.html 文件,并位于模板加载程序搜索的目录,我们将这样注册标签︰
# Here, register is a django.template.Library instance, as before
@register.inclusion_tag('results.html')
def show_results(poll):
...
或者可以使用django.template.Template 实例注册Inclusion标签︰
from django.template.loader import get_template
t = get_template('results.html')
register.inclusion_tag(t)(show_results)
......当首次创建该函数时。
有时,你的Inclusion 标签可能要求一大堆参数,这让模板作者非常痛苦,因为不仅要传递这些参数还要记住它们的顺序。为了解决这个问题, Django 提供了一个takes_context 选项给Inclusion 标签。如果你在创建模板标签时指定takes_context,这个标签将不需要必选参数,当标签被调用的时候底层的Python 函数将有一个参数 —— 模板上下文。
比如说,当你想要写一个 inclusion tag总是应用在上下文中,包含 home_link 和 home_title 这两个用来返回主页的变量。 如下所示:
@register.inclusion_tag('link.html', takes_context=True)
def jump_link(context):
return {
'link': context['home_link'],
'title': context['home_title'],
}
注意函数的第一个参数必须叫做context。
在register.inclusion_tag()这一行,我们指定了takes_context=True 和模板的名字。这里是模板link.html 看起来的样子:
Jump directly to <a href="{{ link }}">{{ title }}</a>.
然后,当任何时候你想调用这个自定义的标签, load 它的 library 然后不需要任何参数就是调用它,就像这样:
{% jump_link %}
注意当你使用takes_context=True,就不需要传递参数给这个模板标签。它会自己去获取上下文。
takes_context 参数默认为False。当它设置为True 时,会传递上下文对象给这个标签,如本示例所示。这是这个示例和前面的inclusion_tag 示例的唯一区别。
inclusion_tag 函数可以接受任意数量的位置参数和关键字参数。例如:
@register.inclusion_tag('my_template.html')
def my_tag(a, b, *args, **kwargs):
warning = kwargs['warning']
profile = kwargs['profile']
...
return ...
然后在模板中,可以将任意数量的由空格分隔的参数传递给模板标签。像在Python 中一样,关键字参数的值的设置使用等号("=") ,并且必须在位置参数之后提供。例子:
{% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
django.template.Library.assignment_tag()¶
为了简单化设置上下文中变量的标签的创建,Django 提供一个辅助函数assignment_tag。这个函数方式的工作方式与simple_tag 相同,不同之处在于它将标签的结果存储在指定的上下文变量中而不是直接将其输出。
我们之前的current_time 函数从而可以这样写︰
@register.assignment_tag
def get_current_time(format_string):
return datetime.datetime.now().strftime(format_string)
然后你可以使用as 参数后面跟随变量的名称将结果存储在模板变量中,并将它输出到你觉得合适的地方︰
{% get_current_time "%Y-%m-%d %I:%M %p" as the_time %}
<p>The time is {{ the_time }}.</p>
如果你的模板标签需要访问当前上下文,你可以在注册标签时使用takes_context 参数:
@register.assignment_tag(takes_context=True)
def get_current_time(context, format_string):
timezone = context['timezone']
return your_get_current_time_method(timezone, format_string)
注意函数的第一个参数必须叫做context。
takes_context 选项的工作方式的详细信息,请参阅包含标签。
assignment_tag 函数可以接受任意数量的位置参数和关键字参数。例如:
@register.assignment_tag
def my_tag(a, b, *args, **kwargs):
warning = kwargs['warning']
profile = kwargs['profile']
...
return ...
然后在模板中,可以将任意数量的由空格分隔的参数传递给模板标签。像在Python 中一样,关键字参数的值的设置使用等号("=") ,并且必须在位置参数之后提供。例子:
{% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile as the_result %}
有时创建自定义模板标签的基本功能是不够的。别担心,Django 给你建立模板标签所需的从底层访问完整的内部。
模板系统的运行分为两步︰编译和渲染。若要定义一个自定义的模板标签,你指定编译如何工作以及渲染如何工作。
当Django 编译一个模板时,它将原始模板文本拆分成节点。每个节点是django.template.Node 的一个实例,并且有一个render() 方法。编译后的模板就是一个简单Node 对象的列表。当你在编译后的模板对象上调用render() 时,该模板将结合给定的上下文调用每个Node 的render()。结果所有串联在一起形成该模板的输出。
因此,若要定义一个自定义的模板标签,你需要指定原始模板标签如何被转换成一个Node(节点) (编译函数),以及该节点的render() 方法会进行的渲染动作
解析器处理每个模板标签时,会调用标签上下文对应的函数和对象本身。这个函数会会返回一个Node实例
For example, let’s write a full implementation of our simple template tag, }} current_time }}, that displays the current date/time, formatted according to a parameter given in the tag, instrftime() syntax. It’s a good idea to decide the tag syntax before anything else. In our case, let’s say the tag should be used like this:
<p>The time is {% current_time "%Y-%m-%d %I:%M %p" %}.</p>
此函数的解析器应抓取参数并创建节点对象:
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])
笔记:
The second step in writing custom tags is to define a Node subclass that has a render() method.
Continuing the above example, we need to define CurrentTimeNode:
import datetime
from django import template
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)
Notes:
Ultimately, this decoupling of compilation and rendering results in an efficient template system, because a template can render multiple contexts without having to be parsed multiple times.
The output from template tags is not automatically run through the auto-escaping filters. However, there are still a couple of things you should keep in mind when writing a template tag.
If the render() function of your template stores the result in a context variable (rather than returning the result in a string), it should take care to call mark_safe() if appropriate. When the variable is ultimately rendered, it will be affected by the auto-escape setting in effect at the time, so content that should be safe from further escaping needs to be marked as such.
Also, if your template tag creates a new context for performing some sub-rendering, set the auto-escape attribute to the current context’s value. The __init__ method for the Context class takes a parameter called autoescape that you can use for this purpose. For example:
from django.template import Context
def render(self, context):
# ...
new_context = Context({'var': obj}, autoescape=context.autoescape)
# ... Do something with new_context ...
This is not a very common situation, but it’s useful if you’re rendering a template yourself. For example:
def render(self, context):
t = context.template.engine.get_template('small_fragment.html')
return t.render(Context({'var': obj}, autoescape=context.autoescape))
Changed in Django 1.8:
The template attribute of Context objects was added in Django 1.8. context.template.engine.get_template must be used instead of django.template.loader.get_template() because the latter now returns a wrapper whose render method doesn’t accept a Context.
If we had neglected to pass in the current context.autoescape value to our new Context in this example, the results would have always been automatically escaped, which may not be the desired behavior if the template tag is used inside a }} autoescape off }} block.
Once a node is parsed, its render method may be called any number of times. Since Django is sometimes run in multi-threaded environments, a single node may be simultaneously rendering with different contexts in response to two separate requests. Therefore, it’s important to make sure your template tags are thread safe.
To make sure your template tags are thread safe, you should never store state information on the node itself. For example, Django provides a builtin cycle template tag that cycles among a list of given strings each time it’s rendered:
{% for o in some_list %}
<tr class="{% cycle 'row1' 'row2' %}">
...
</tr>
{% endfor %}
A naive implementation of CycleNode might look something like this:
import itertools
from django import template
class CycleNode(template.Node):
def __init__(self, cyclevars):
self.cycle_iter = itertools.cycle(cyclevars)
def render(self, context):
return next(self.cycle_iter)
But, suppose we have two templates rendering the template snippet from above at the same time:
The CycleNode is iterating, but it’s iterating globally. As far as Thread 1 and Thread 2 are concerned, it’s always returning the same value. This is obviously not what we want!
To address this problem, Django provides a render_context that’s associated with the context of the template that is currently being rendered. The render_context behaves like a Python dictionary, and should be used to store Node state between invocations of the render method.
Let’s refactor our CycleNode implementation to use the render_context:
class CycleNode(template.Node):
def __init__(self, cyclevars):
self.cyclevars = cyclevars
def render(self, context):
if self not in context.render_context:
context.render_context[self] = itertools.cycle(self.cyclevars)
cycle_iter = context.render_context[self]
return next(cycle_iter)
Note that it’s perfectly safe to store global information that will not change throughout the life of the Node as an attribute. In the case of CycleNode, the cyclevars argument doesn’t change after the Node is instantiated, so we don’t need to put it in the render_context. But state information that is specific to the template that is currently being rendered, like the current iteration of the CycleNode, should be stored in the render_context.
Note
Notice how we used self to scope the CycleNode specific information within the render_context. There may be multiple CycleNodes in a given template, so we need to be careful not to clobber another node’s state information. The easiest way to do this is to always use self as the key into render_context. If you’re keeping track of several state variables, make render_context[self] a dictionary.
Finally, register the tag with your module’s Library instance, as explained in writing custom template filters above. Example:
register.tag('current_time', do_current_time)
The tag() method takes two arguments:
As with filter registration, it is also possible to use this as a decorator:
@register.tag(name="current_time")
def do_current_time(parser, token):
...
@register.tag
def shout(parser, token):
...
If you leave off the name argument, as in the second example above, Django will use the function’s name as the tag name.
Although you can pass any number of arguments to a template tag using token.split_contents(), the arguments are all unpacked as string literals. A little more work is required in order to pass dynamic content (a template variable) to a template tag as an argument.
While the previous examples have formatted the current time into a string and returned the string, suppose you wanted to pass in a DateTimeField from an object and have the template tag format that date-time:
<p>This post was last updated at {% format_time blog_entry.date_updated "%Y-%m-%d %I:%M %p" %}.</p>
Initially, token.split_contents() will return three values:
Now your tag should begin to look like this:
from django import template
def do_format_time(parser, token):
try:
# split_contents() knows not to split quoted strings.
tag_name, date_to_be_formatted, format_string = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError(
"%r tag requires exactly two arguments" % 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 FormatTimeNode(date_to_be_formatted, format_string[1:-1])
You also have to change the renderer to retrieve the actual contents of the date_updated property of the blog_entry object. This can be accomplished by using the Variable() class in django.template.
To use the Variable class, simply instantiate it with the name of the variable to be resolved, and then call variable.resolve(context). So, for example:
class FormatTimeNode(template.Node):
def __init__(self, date_to_be_formatted, format_string):
self.date_to_be_formatted = template.Variable(date_to_be_formatted)
self.format_string = format_string
def render(self, context):
try:
actual_date = self.date_to_be_formatted.resolve(context)
return actual_date.strftime(self.format_string)
except template.VariableDoesNotExist:
return ''
Variable resolution will throw a VariableDoesNotExist exception if it cannot resolve the string passed to it in the current context of the page.
The above examples simply output a value. Generally, it’s more flexible if your template tags set template variables instead of outputting values. That way, template authors can reuse the values that your template tags create.
To set a variable in the context, just use dictionary assignment on the context object in the render() method. Here’s an updated version of CurrentTimeNode that sets a template variable current_time instead of outputting it:
import datetime
from django import template
class CurrentTimeNode2(template.Node):
def __init__(self, format_string):
self.format_string = format_string
def render(self, context):
context['current_time'] = datetime.datetime.now().strftime(self.format_string)
return ''
Note that render() returns the empty string. render() should always return string output. If all the template tag does is set a variable, render() should return the empty string.
Here’s how you’d use this new version of the tag:
{% current_time "%Y-%M-%d %I:%M %p" %}<p>The time is {{ current_time }}.</p>
Variable scope in context
Any variable set in the context will only be available in the same block of the template in which it was assigned. This behavior is intentional; it provides a scope for variables so that they don’t conflict with context in other blocks.
But, there’s a problem with CurrentTimeNode2: The variable name current_time is hard-coded. This means you’ll need to make sure your template doesn’t use {{ current_time }} anywhere else, because the }} current_time }} will blindly overwrite that variable’s value. A cleaner solution is to make the template tag specify the name of the output variable, like so:
{% current_time "%Y-%M-%d %I:%M %p" as my_current_time %}
<p>The current time is {{ my_current_time }}.</p>
To do that, you’ll need to refactor both the compilation function and Node class, like so:
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):
context[self.var_name] = datetime.datetime.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:
raise template.TemplateSyntaxError(
"%r tag requires arguments" % token.contents.split()[0]
)
m = re.search(r'(.*?) as (\w+)', arg)
if not m:
raise template.TemplateSyntaxError("%r tag had invalid arguments" % tag_name)
format_string, var_name = m.groups()
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 CurrentTimeNode3(format_string[1:-1], var_name)
The difference here is that do_current_time() grabs the format string and the variable name, passing both to CurrentTimeNode3.
Finally, if you only need to have a simple syntax for your custom context-updating template tag, you might want to consider using the assignment tag shortcut we introduced above.
Template tags can work in tandem. For instance, the standard }} comment }} tag hides everything until }} endcomment }}. To create a template tag such as this, use parser.parse() in your compilation function.
Here’s how a simplified }} comment }} tag might be implemented:
def do_comment(parser, token):
nodelist = parser.parse(('endcomment',))
parser.delete_first_token()
return CommentNode()
class CommentNode(template.Node):
def render(self, context):
return ''
Note
The actual implementation of }} comment }} is slightly different in that it allows broken template tags to appear between }} comment }} and }} endcomment }}. It does so by callingparser.skip_past('endcomment') instead of parser.parse(('endcomment',)) followed by parser.delete_first_token(), thus avoiding the generation of a node list.
parser.parse() takes a tuple of names of block tags ‘’to parse until’‘. It returns an instance of django.template.NodeList, which is a list of all Node objects that the parser encountered ‘’before’’ it encountered any of the tags named in the tuple.
In "nodelist = parser.parse(('endcomment',))" in the above example, nodelist is a list of all nodes between the }} comment }} and }} endcomment }}, not counting }} comment }} and }}endcomment }} themselves.
After parser.parse() is called, the parser hasn’t yet “consumed” the }} endcomment }} tag, so the code needs to explicitly call parser.delete_first_token().
CommentNode.render() simply returns an empty string. Anything between }} comment }} and }} endcomment }} is ignored.
In the previous example, do_comment() discarded everything between }} comment }} and }} endcomment }}. Instead of doing that, it’s possible to do something with the code between block tags.
For example, here’s a custom template tag, }} upper }}, that capitalizes everything between itself and }} endupper }}.
Usage:
{% upper %}This will appear in uppercase, {{ your_name }}.{% endupper %}
和前面的例子一样,我们使用parser.parse()。但是这次,我们将得到的节点传递给节点:
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)。
For more examples of complex rendering, see the source code of }} for }} in django/template/defaulttags.py and }} if }} in django/template/smartif.py.