阅读: 15981 评论:3


Django虽然为我们内置了二十多种标签和六十多种过滤器,但是需求是各种各样的,总有一款你cover不到。Django为我们提供了自定义的机制,可以通过使用Python代码,自定义标签和过滤器来扩展模板引擎,然后使用{% load %}标签。

一、前置步骤

Django对于自定义标签和过滤器是有前置要求的,首先一条就是代码布局和文件组织。

你可以为你的自定义标签和过滤器新开一个app,也可以在原有的某个app中添加。

不管怎么样,第一步,在app中新建一个templatetags(名字固定,不能变,只能是这个),和views.py、models.py等文件处于同一级别目录下。这是一个包!不要忘记创建__init__.py文件以使得该目录可以作为Python的包。

在添加templatetags包后,需要重新启动服务器,然后才能在模板中使用标签或过滤器。

  • 将你自定义的标签和过滤器将放在templatetags包下的一个模块里。
  • 这个模块的名字是后面载入标签时使用的标签名,所以要谨慎的选择名字以防与其他应用下的自定义标签和过滤器名字冲突,当然更不能与Django内置的冲突。

假设你自定义的标签/过滤器在一个名为poll_extras.py的文件中,那么你的app目录结构看起来应该是这样的:

  1. polls/
  2. __init__.py
  3. models.py
  4. templatetags/
  5. __init__.py
  6. poll_extras.py
  7. views.py

为了让{% load xxx %}标签正常工作,包含自定义标签的app必须在INSTALLED_APPS中注册。然后你就可以在模板中像如下这样使用:

  1. {% load poll_extras %}

在templatetags包中放多少个模块没有限制。只需要记住{% load xxx %}将会载入给定模块名中的标签/过滤器,而不是app中所有的标签和过滤器。

要在模块内自定义标签,首先,这个模块必须包含一个名为register的变量,它是template.Library的一个实例,所有的标签和过滤器都是在其中注册的。 所以把如下的内容放在你的模块的顶部:

  1. from django import template
  2. register = template.Library()

友情提示:可以阅读Django的默认过滤器和标记的源代码。它们分别位于django/template/defaultfilters.pydjango/template/defaulttags.py中。它们是最好的范例!

二、自定义模板过滤器

1. 编写过滤器

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

注意:这个Python函数的第一个参数是你要过滤的对象,第二个参数才是你自定义的参数。而且最多总共只能有两个参数,所以你只能自定义一个参数!这是过滤器的先天限制。

  • 变量的值:不一定是字符串形式。
  • 参数的值:可以有一个初始值,或者完全不要这个参数。

例如,在{{ var|foo:"bar" }}中,foo过滤器应当传入变量var和参数”bar”。

由于模板语言没有提供异常处理,任何从过滤器中抛出的异常都将会显示为服务器错误。

下面是一个定义过滤器的例子:

  1. def cut(value, arg):
  2. """将value中的所有arg部分切除掉"""
  3. return value.replace(arg, '')

下面是这个过滤器的使用方法:

  1. {{ somevariable|cut:"0" }}

大多数过滤器没有参数,在这种情况下,你的过滤器函数不带额外的参数即可,但基本的value参数是必带的。例如:

  1. def lower(value): # Only one argument.
  2. """Converts a string into all lowercase"""
  3. return value.lower()

2. 注册过滤器

类原型:django.template.Library.filter()

一旦你写好了过滤器函数,就需要注册它,方法是调用register.filter,比如:

  1. register.filter('cut', cut)
  2. register.filter('lower', lower)

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

  • 过滤器的名称:一个字符串对象
  • 编译的函数 :你刚才写的过滤器函数

还可以把register.filter()用作装饰器,以如下的方式注册过滤器:

  1. @register.filter(name='cut')
  2. def cut(value, arg):
  3. return value.replace(arg, '')
  4. @register.filter
  5. def lower(value):
  6. return value.lower()

上面第二个例子没有声明name参数,Django将使用函数名作为过滤器的名字。

自定义过滤器就是这么简单,使用起来也和普通的过滤器没什么区别。我们用Python的方式解决了HTML的问题。

三、自定义模板标签

标签比过滤器更复杂,因为标签可以做任何事情。Django提供了大量的快捷方式,使得编写标签比较容易。 对于我们一般的自定义标签来说,simple_tag是最重要的,它帮助你将一个Python函数注册为一个简单的模版标签。

1. simple_tag

原型:django.template.Library.simple_tag()

为了简单化模版标签的创建,Django提供一个辅助函数simple_tag,这个函数是django.template.Library的一个方法。

比如,我们想编写一个返回当前时间的模版标签,那么current_time函数从而可以这样写︰

  1. import datetime
  2. from django import template
  3. register = template.Library()
  4. @register.simple_tag
  5. def current_time(format_string):
  6. return datetime.datetime.now().strftime(format_string)

关于simple_tag函数有几件值得注意的事项︰

如果不需要额外的转义,可以使用mark_safe()让输出不进行转义,前提是你绝对确保代码中不包含XSS漏洞。 如果要创建小型HTML片段,强烈建议使用format_html()而不是mark_safe()

如果你的模板标签需要访问当前上下文,可以在注册标签时使用takes_context参数︰

  1. @register.simple_tag(takes_context=True)
  2. def current_time(context, format_string):
  3. timezone = context['timezone']
  4. return your_get_current_time_method(timezone, format_string)

请注意,第一个参数必须称作context!

如果你需要重命名你的标签,可以给它提供自定义的名称︰

  1. register.simple_tag(lambda x: x - 1, name='minusone')
  2. @register.simple_tag(name='minustwo')
  3. def some_function(value):
  4. return value - 2

simple_tag函数可以接受任意数量的位置参数和关键字参数。 像这样:

  1. @register.simple_tag
  2. def my_tag(a, b, *args, **kwargs):
  3. warning = kwargs['warning']
  4. profile = kwargs['profile']
  5. ...
  6. return ...

然后在模板中,可以将任意数量的由空格分隔的参数传递给模板标签。像在Python中一样,关键字参数的值使用等号(”=”)赋予,并且必须在位置参数之后提供。 例子:

  1. {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}

可以将标签结果存储在模板变量中,而不是直接输出。这是通过使用as参数后跟变量名来实现的:

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

2. inclusion_tag()

原型:django.template.Library.inclusion_tag()

另一种常见类型的模板标签是通过渲染一个模板来显示一些数据。例如,Django的Admin界面使用自定义模板标签显示”添加/更改”表单页面底部的按钮。这些按钮看起来总是相同,但链接的目标却是根据正在编辑的对象而变化的。

这种类型的标签被称为”Inclusion 标签”。

下面,展示一个根据给定的tutorials中创建的Poll对象输出一个选项列表的自定义Inclusion标签。在模版中它是这么调用的:

  1. {% show_results poll %}

而输出是这样的:

  1. <ul>
  2. <li>First choice</li>
  3. <li>Second choice</li>
  4. <li>Third choice</li>
  5. </ul>

具体的编写方法:

首先,编写Python函数:

  1. def show_results(poll):
  2. choices = poll.choice_set.all()
  3. return {'choices': choices}

接下来,创建用于标签渲染的模板results.html︰

  1. <ul>
  2. {% for choice in choices %}
  3. <li> {{ choice }} </li>
  4. {% endfor %}
  5. </ul>

最后,通过调用Library对象的inclusion_tag()装饰器方法创建并注册Inclusion标签︰

  1. @register.inclusion_tag('results.html')
  2. def show_results(poll):
  3. ...

或者使用django.template.Template实例注册Inclusion标签︰

  1. from django.template.loader import get_template
  2. t = get_template('results.html')
  3. register.inclusion_tag(t)(show_results)

inclusion_tag函数可以接受任意数量的位置参数和关键字参数。像这样:

  1. @register.inclusion_tag('my_template.html')
  2. def my_tag(a, b, *args, **kwargs):
  3. warning = kwargs['warning']
  4. profile = kwargs['profile']
  5. ...
  6. return ...

然后在模板中,可以将任意数量的由空格分隔的参数传递给模板标签。像在Python中一样,关键字参数的值的设置使用等号(”=”) ,并且必须在位置参数之后提供。例如:

  1. {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}

可以在标签中传递上下文中的参数。比如说,当你想要将上下文context中的home_linkhome_title这两个变量传递给模版。 如下所示:

  1. @register.inclusion_tag('link.html', takes_context=True)
  2. def jump_link(context):
  3. return {
  4. 'link': context['home_link'],
  5. 'title': context['home_title'],
  6. }

注意函数的第一个参数必须叫做context。context必须是一个字典类型。

register.inclusion_tag()这一行,我们指定了takes_context=True和模板的名字。模板link.html很简单,如下所示:

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

然后,当任何时候你想调用这个自定义的标签时,只需要load它本身,不需要添加任何参数,{{ link }}{{ title }}会自动从标签中获取参数值。像这样:

  1. {% jump_link %}

使用takes_context=True,就表示不需要传递参数给这个模板标签。它会自己去获取上下文。