Django3.1大神之路视频教程重磅推出!长达77小时、17G、233节 ,全方位无死角深入源码的专注教程!包含完整的模型层、数据迁移、 类视图、日志、认证权限和开发工具等更多文字教程未包含的内容。免费章节https://pan.baidu.com/s/1dqGWNwmBnLxhM7DnXiePIQ 提取码:ko4y 。查看视频介绍点我

自定义标签和过滤器

阅读: 17765     评论:5

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

一、前置步骤

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

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

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

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

  • 将你自定义的标签和过滤器将放在templatetags包下的一个模块里。

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

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

polls/
    __init__.py
    models.py
    templatetags/
        __init__.py
        poll_extras.py
    views.py

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

{% load poll_extras %}

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

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

from django import template

register = template.Library()

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

二、自定义模板过滤器

1. 编写过滤器

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

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

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

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

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

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

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

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

{{ somevariable|cut:"0" }}

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

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

2. 注册过滤器

django.template.Library.filter()

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

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将使用函数名作为过滤器的名字。

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

限定为字符串

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() 方法)。

过滤器和时区

如果你编写了一个自定义过滤器,用于处理 datetime 对象,注册过滤器时通常将 expects_localtime 标志置为 True:

@register.filter(expects_localtime=True)
def businesshours(value):
    try:
        return 9 <= value.hour < 17
    except AttributeError:
        return ''

设置该标志后,如果过滤器接收的第一个参数是一个时区敏感的 datetime,Django会在将其传递给过滤器前的某个合适时间将其转换为当前时区的时间,依据模板中的时区转换规则。

过滤器和自动转义

我们都知道,由于HTML语法的特殊性,有一些字符串是不太安全的,所以,在前端页面,为了防止XSS攻击,通常都会考虑字符串转义的问题。

Django内置的过滤器都考虑完善了这点,但你自己编写的过滤器则要确保没有安全漏洞。

首先,可以判断输入值是否是安全字符串,Django提供了一个专门的方法:

from django.utils.safestring import SafeString

if isinstance(value, SafeString):
    # 你可以用这个safe字符串直接做某些操作
    ...
else:
    pass

其次,如果你不想手动判断,可以为过滤器指定is_safe=True参数,该标志告诉 Django,若一个“安全”字符串传给您的过滤器,结果仍会是安全的。若传入了不安全的字符串,Django 会在需要时自动转义。如下所示:

@register.filter(is_safe=True)
def myfilter(value):
    return value

is_safe参数默认为False。

最后,如果你确认字符串是安全的,可以使用make_safe方法标记它,这样Django就不会转义它。下面是一个例子,将字符串的首字母加粗加强:

from django import template
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe

register = template.Library()

@register.filter(needs_autoescape=True)
def initial_letter_filter(text, autoescape=True):
    first, other = text[0], text[1:]
    if autoescape:
        esc = conditional_escape
    else:
        esc = lambda x: x
    result = '<strong>%s</strong>%s' % (esc(first), esc(other))
    return mark_safe(result)

上面,我们给过滤器添加了 needs_autoescape=True 标志(默认 False),该标志告诉 Django 过滤器函数额外接受一个名为 autoescape 关键字参数,autoescape 值为 True 时表示开启自动转义, False 说明关闭。推荐将 autoescape 参数的默认值设为 True,这样,从 Python 代码调用此函数时,默认开启自动转义功能。

is_safe=Trueneeds_autoescape=True 同时存在,不冲突。

三、自定义模板标签

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

1. simple_tag

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

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

比如,我们想编写一个返回当前时间的模版标签,那么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函数有几件值得注意的事项︰

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

如果你的模板标签需要访问当前上下文,可以在注册标签时使用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!

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

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 %}

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

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

2. inclusion_tag()

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

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

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

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

{% show_results poll %}

而输出是这样的:

<ul>
  <li>First choice</li>
  <li>Second choice</li>
  <li>Third choice</li>
</ul>

具体的编写方法:

首先,编写Python函数:

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

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

<ul>
{% for choice in choices %}
    <li> {{ choice }} </li>
{% endfor %}
</ul>

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

@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_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 %}

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

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

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

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

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

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

{% jump_link %}

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


 人类可读性 第四章:Django表单 

评论总数: 5


点击登录后方可评论

user_image

感觉过滤器和标签除了参数数量有限制外没有区别啊!本质上都是函数。过滤器能实现的标签都能实现,那为啥还要过滤器啊?



user_image

还是有区别的。抛开语法不谈,标签更重量级一些,过滤器更简单快捷。



user_image

注册:template.Library()



user_image

博主你好,自定义过滤器和标签在实际开发中常用吗?



user_image

按需啦,客制化