Django中的自定义过滤器

一、为什么要自定义Django中的自定义过滤器:

Django中提供了很多内置的过滤器和标签,详见链接django官网,主要有以下几个:

  • autoescape(自动转义)
  • block(模板继承)
  • csrf_token(跨站伪造请求)
  • extends(模板继承)
  • filter(过滤器)
  • for(循环)
  • if(判断)
  • include(加载模板)
    等等,还有很多详见官网上的内容。
    但是虽然Django内置了很多的标签和过滤器但是很多时候我们还需要自定义的过滤器来达到我们网页的效果,这个时候就要考虑我们自己定义一个过滤器和标签了,下面我以一个简单的过滤器作为示例介绍下django的自定义过滤器的写法和规则。

二、下面开始我们自己定义模板和过滤器,我这里以过滤器作为例子说明:

  1. 代码层

一般的自己定义的过滤器和标签都是和我们django中的app(小应用)绑定在一起的。定义之后,你就可以在模板中使用{% load %}标签加载你自己定义的标签和过滤器了。
在此之前,你要确保你的小应用在INSTALLED_APPS中进行了注册,然后所有你在模板中加载你自定义的小应用的时候django会在小应用内部进行查找,如果你的小应用没有注册,那么django就会找不到。

特别注意:你在添加完你自定义的templatetags模块后,在你使用你自定义的过滤器和标签之前,你需要手动重启django的服务器。(django不会自动重启)

假如我们有一个小应用叫做polls,首先你在你的小应用内新建一个templatetags文件夹,确保文件夹下有init.py文件(python2会自带,python3可能没有,如果没有的话,那就手动创建)。然后在这个文件夹内新建一个你将来想在html模板中使用的自定义标签和过滤器的名字,在这里我们给它命名为poll_extras.py(切记不要和别的小应用内的名字重复)。
所以现在看起来你的代码的文件目录看起来是下面的这个样子:

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

你将来就像{% load poll_extras %}来在html模板中加载你自定义的模板和过滤器。
你在templatetags这个python包中定义多少个标签和过滤器并没有限制,但是特别要注意的是你将来在html的模板中使用的是你定义的这些py文件的名字比如上面的就是poll_extras,而不是django中你注册的小应用的名字
为了让我们自定义的模板和过滤器可用,那么django要求我们自定义的标签和过滤器必须在django中进行注册,那么我们就需要在我们的poll_extras.py的文件最上面几行添加以下几行代码:

from django import template

register = template.Library()

另外的,模板标签模块也可以通过’libraries’ 参数被注册到DjangoTemplates中。如果要在加载模板标记时使用模板标记模块名称中的不同标签,那么这将非常有用。它还使您能够注册标签,而无需安装应用程序。

在屏幕之后:
对于大量的示例,请阅读Django的默认过滤器和标记的源代码。它们分别在django / template / defaultfilters.py和django / template / defaulttags.py中。
更多关于加载标签的信息,请查看它的文档

2、写我们自定义的模板过滤器
自定义过滤器只是一个接受一个或两个参数的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()

Once you’ve written your filter definition, you need to register it with your Library instance, to make it available to Django’s template language:

一旦你写了自己的过滤器定义,你需要把它注册为Library的实例,让它能够被Django的模板语言找到,如下:

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

Library.filter()方法接受两个参数:

  1. 过滤器的名字—-一个字符串
  2. 编译函数 - 一个Python函数(不是作为字符串的函数的名称)。
    你也可以使用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。这些参数在下面的过滤器和自动转义以及过滤器和时区中描述。

  • 期望是字符串的模板过滤器

如果你编写的模板过滤器只需要一个字符串作为第一个参数,你应该使用decorator stringfilter。这会将一个对象转换为其字符串值,然后传递给你的函数:

from django import template
from django.template.defaultfilters import stringfilter

register = template.Library()

@register.filter
@stringfilter
def lower(value):
    return value.lower()

This way, you’ll be able to pass, say, an integer to this filter, and it won’t cause an AttributeError (because integers don’t have lower() methods).

这样的话,你将能够把一个int类型值传递到这个过滤器,并且它不会导致出现一个AttributeError (属性错误)(因为int类型并没有lower()方法)。

  • 过滤器和自动转义

当写一个自定义过滤器的时候,想一想过滤器是怎么和django的自动转义行为工作的。注意有三种类型的字符串可以被传递到模板代码中:

  1. 原生字符串是python自带的字符或unicode类型。在输出上,如果自动转义生效的话,它们是被转义的,否则的话表现没有任何改变进行展示。
  2. 安全字符串是在展示的时候已经进行过更深入的转义并被标记为安全的字符串。一切必要的转义都已经完成了。它们通常用于包含原始HTML的输出,该原始HTML将会被在客户端进行展示和使用。
    Internally, these strings are of type SafeBytes or SafeText. They share a common base class of SafeData, so you can test for them using code like:

内部的,这些字符串都是SafeBytes or SafeText(安全字节或安全文本)。它们共享了一个共同的称为SafeData(安全数据)的基类,所以你可以使用下面的代码进行测试:

if isinstance(value, SafeData):
    # Do something with the "safe" string.
    ...

模板过滤器的代码必须是下面两种情形之一:

  1. 你的过滤器不会在结果中引入任何不存在的HTML不安全字符(<,>,’,“或&),在这种情况下,您可以让Django为您处理所有自动转义处理。所有你需要做的是在注册你的过滤器功能时将is_safe标志设置为True,如下所示:
@register.filter(is_safe=True)
def myfilter(value):
    return value

这个标志位告诉django如果一个”安全的”字符串被传入到你的过滤器中,结果将会仍然是安全的,如果一个不安全的字符串传入到过滤器中,如果有必要的话,django会自动的对它进行转义。
is_safe是必要的原因是有很多正常的字符串操作,将SafeData对象变成一个正常的str或unicode对象,而不是试图捕获他们所有,因为这是非常困难的,Django过滤器已完成后修复这些损坏。
例如,假设有一个过滤器将字符串xx添加到任何输入的末尾。因为这不会向结果(除了已经存在的任何内容)引入危险的HTML字符,您应该使用is_safe标记您的过滤器:

@register.filter(is_safe=True)
def add_xx(value):
    return '%sxx' % value

当在启用自动转义的模板中使用此过滤器时,每当输入尚未标记为“安全”时,Django将对输出进行转义。
默认情况下,is_safe是False,并且您可以从不需要它的任何过滤器中忽略它。
在确定您的过滤器是否确实将安全字符串安全时,请小心。如果您要删除字符,则可能会在结果中无意间留下不平衡的HTML标记或实体。例如,从输入中删除>可能会将转换为,这将需要在输出上转义以避免导致问题。类似地,删除分号(;)可以转换&amp;进入&amp; amp,这不再是有效的标签,因此需要进一步转义。大多数情况下不会有这个棘手的问题,但在检查代码时,请留意任何问题。
标记过滤器is_safe将强制过滤器的返回值为字符串。如果你的过滤器应该返回一个布尔值或其他非字符串值,标记它is_safe可能会有意想不到的后果(例如将一个布尔值False转换为字符串’False’)。
2. 或者,您的过滤器代码可以手动处理任何必要的转义。当您在结果中引入新的HTML标记时,这是必要的。您希望将输出标记为避免进一步转义,以便您的HTML标记不会进一步转义,因此您需要自己处理输入。
To mark the output as a safe string, use django.utils.safestring.mark_safe().

把结果标记为一个安全的字符串,请使用django.utils.safestring.mark_safe().
但要小心。您需要做的不仅仅是将输出标记为安全。你需要确保它真的是安全的,自动转义是否有效取决于你做什么。这个想法是编写可以在模板中操作自动转义是打开还是关闭的过滤器,让事情对于模板作者更容易。
为了使您的过滤器知道当前的自动转义状态,请在注册过滤器函数时将needs_autoescape标志设置为True。 (如果不指定此标志,则默认为False)。这个标志告诉Django你的过滤器函数想要传递一个额外的关键字参数,称为autoescape,如果自动转义有效则为True,否则为False。建议将autoescape参数的默认值设置为True,这样,如果从Python代码调用函数,它将默认启用转义。

例如,让我们编写一个强调字符串第一个字符的过滤器:

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 = '%s%s' % (esc(first), esc(other))
    return mark_safe(result)

needs_autoescape标志和autoescape关键字参数意味着我们的函数将知道当调用过滤器时自动转义是否有效。我们使用autoescape来决定输入数据是否需要通过django.utils.html.conditional_escape传递。 (在后一种情况下,我们只使用identity函数作为“escape”函数。)conditional_escape()函数与escape()类似,只是它只转义不是SafeData实例的输入。如果将SafeData实例传递给conditional_escape(),则将不更改地返回数据。

最后,在上面的例子中,我们记得将结果标记为安全的,这样我们的HTML直接插入到模板中,而不需要进一步转义。

在这种情况下没有必要担心is_safe标志位(虽然包括它不会对任何东西造成影响)。每当你手动处理自动转义问题并返回一个安全的字符串,is_safe标志无论如何不会再改变。

警告:

在重复使用内置过滤器时避免XSS漏洞(什么是XSS下面的定义取自维基百科)
XSS:跨站脚本(Cross-site scripting,通常简称为XSS)是一种网站应用程序的安全漏洞攻击,是代码注入的一种。它允许恶意用户将代码注入到网页上,其他用户在观看网页时就会受到影响。这类攻击通常包含了HTML以及用户端脚本语言。

XSS攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是JavaScript,但实际上也可以包括Java,VBScript,ActiveX,Flash或者甚至是普通的HTML。攻击成功后,攻击者可能得到更高的权限(如执行一些操作)、私密网页内容、会话和cookie等各种内容。

Django的内置过滤器默认情况下具有autoescape = True,以便获得正确的自动转义行为,并避免跨站点脚本漏洞。
在旧版本的Django中,重新使用Django的内置过滤器时请小心,因为autoescape默认为None。您需要传递autoescape = True才能获得自动转义。
例如,如果你想编写一个名为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
    )

然后再模板中如下使用:

{{ comment|urlize_and_linebreaks }}

等价于下面:

{{ comment|urlize|linebreaksbr }}

三、自定义过滤器的例子:

  1. 假如我们现在需要在网页上实现如下图的效果(对电话号码的中间几位进行替代和用*格式化):
    这里写图片描述
  2. 下面是我的代码的部分(仅供参考和示例):
from django import template
from django.utils.safestring import mark_safe
from django.template.defaultfilters import  \ stringfilter

register = template.Library()

@stringfilter
@register.filter(needs_autoescape=True)
def star_key_format(value, autoescape=True):
    format_tel = value[0:3] + " " + "****" + " " + value[7:]
    return mark_safe(format_tel)

以上仅用自定义过滤器作为一个示例,主要是以django官方的文档作为行文思路,有关广多内容请参考django官方文档Custom template tags and filters。欢迎大家评论、吐槽和交流,感谢大家花时间阅读我的这篇博客。

你可能感兴趣的:(django)