django 自定义标签和过滤器

 Django支持自定义标签和过滤器。起初还不太重视它这项功能,但最近试了试自定义标签。发现django这个功能实在是太爽了。

 

首先在你项目的一个app中建立一个python源文件夹,(即文件夹里面要包含一个__init__.py.)文件夹名为templatetags.  此文件夹便是存放自定义标签和过滤器的源码的地方了。

 

再如果是在templatetags文件夹中定义了标签,如 test_tags.py,要如何使用我们自定义的test_tags.py呢。很简单,只要在django的模板中加入{% load test_tags %},在test_tags.py 源文件中的自定义标签就可以在有load语句的模板中使用了。

 

下面详细的来谈谈建立自定义标签的过程以及方法。

 

1.建立项目,app的不说。只要在随意一个app中建立上文提到的templatetags文件夹。

这里是有点不理解的地方,在任意一个app建立的tags别的app能够使用吗?起初对此很疑惑。以为在一个app下建立的tags就这一个app能使用。为了大家都能够使用自己定义的tags,我还想把templatetags单独拿出来,跟普通app在项目当中是平级地位。这种思路搞了很久发现行不通。无奈只好打算使用copy在每一个app都复制一份templatetags。(当然这只是我起初的错误想法)但后来发现,居然一个app中有,其他的app中就可以直接使用此自定义的文件了。只需要在需要的模板当中(不管模板是在你的那个app中)调用load语句将自定义的文件load进来便可以。

随后看了看一些文档,只要templatetags所在位置是settings.py中INSTALLED_APPS中配置过的,或是在TEMPLATE_DIRS配置过的,任意一个位置便可以。

一定记得要在templatetags文件夹中包含__init__.py文件。空文件便可。

 

2.编写自定义文件代码test_tags.py。

 

现看看简单一点的过滤器(filter).

我的一个简单代码如下

#!/usr/bin/env python
#coding:utf-8

from django import template

register = template.Library()

def percent_decimal(value):
    
    value = float(str(value))
    value = round(value, 3)
    value = value * 100
    
    return str(value) + '%'
register.filter('percent_decimal', percent_decimal) 

以上代码的意思是将传过来的小数转换成百分比显示。(django自带一个widthratio标签页可以完成此问题,但它的误差太大,小数位直接截掉了)。

其中register = template.Library(),register.filter('percent_decimal', percent_decimal) 两句是将所写代码注册到能用标签。最后一句的register.filter('percent_decimal', percent_decimal) 。第一个参数是字符串,就只在模板中使用时候的字符串比如{{12.09|percent_decimal}},名字可以自由取得。后面一个参数接受一个函数名,这个便是上面自定义的percent_decimal方法了。此方法中的value参数便是从模板中传递过来的参数。比如{{12.09|percent_decimal}}, percent_decimal方法接受到的value参数便是
12.09.

 

再来看看如何自定义tags。

自定义tags相对自定义filter要复杂一点。但仔细研究的话难度也不大。

自定义tags最基本的格式如下

from django import template

register = template.Library()

class TestNode(template.Node):
    def __init__(self):
        pass

    def render(self, context):
        return "xxxxx"

def test(parser, token):
    return TestNode()

register.tag('my_tag', test)

 其中包的引入和注册跟filter类型。主要的功能代码是一个类(继承自template.Node,所有的自定义tags都必须从这个类继承)和一个方法,以上代码什么功能都没有做,但我们要先搞明白它的实现机制。

如果从模板中使用以上的自定义tags,比如{% my_tag aaa.bbb %},则会调用上面代码注册的test方法,其中test方法中有两个参数,一个是parser,这个作用是挺大的,稍后说明。另一个是token,这个便是从模板当中使用的这个标签中所包含的所有字符串。如果使用的是{{ my_tag aaa.bbb }}则,token的值为“my_tag aaa.bbb”。注意是字符串类型。

随后调用TestNode类,当中有个render方法,其中有个参数是context,这个context参数就是在执行模板的渲染时由 View 传入的,在这里跟在被渲染的模板中可以调用同样的变量,可以试验在render中打印 "print user",便显示的是登陆用户的信息。

 

大概明白了自定义tags的实现机制后。看一个复杂一点点的代码。

class PermissionLevel(template.Node):
    
    """
    根据级别返回相应值
    """    
    
    def __init__(self, sequence, text_level):
        self.sequence = sequence
        self.text_level = text_level
        pass
    def render(self, context):
        userInfo = context['user']
        level = 4  #拿到用户级别
        
        values = self.sequence.resolve(context, True)
        if self.text_level <= level:
            return str(values)
        else:
             return 'xxx'

def do_permission_level(parser, token):
        
    try:
        tag_name, text_name, text_level = token.split_contents()
    except:
        raise template.TemplateSyntaxError, \
                   "%r 标签语法错误,后面参数为两位,分别为变量名和该变量信息隐私等级" % \
                    token.split_contents[0]
    try:
        text_level = int(text_level)
    except:
        raise TemplateSyntaxError, "permission_level标签语法错误,信息隐私等级应为整型数字"
    sequence = parser.compile_filter(text_name)
    
    return PermissionLevel(sequence, text_level)
     
register.tag('permission_level', do_permission_level)

 

以上的代码完成的功能是判定一个信息的隐私级别,再根据登陆用户的权限来区分显示或不显示。在模板中的用法如下

{% permission_level  objects.count  4 %}

其中permission_level  为标签名,objects.count是从view中传递过来的变量或属性。后面的 4 便是此属性(objects.count)的隐私级别为4,只有登录用户隐私级别大于它的时候才会显示,否则显示为“xxx”。

 

下面仔细来分析一下上面的代码。

tag_name, text_name, text_level = token.split_contents()
这一句将{% permission_level  objects.count  4 %}分解成三个字符,其中text_name为“objects.count ”。这里跟自定义的filter不同,filter 是可以接受非字符的参数的,类型{{ 0.123|floatformat:2 }}也可以是{{ 0.123|floatformat:“2” }},后面的2参数加了引号。其中如果不加引号的话便传递的是非字符对象,像模板中的user是可以通过filter传递过去的。但tags不同,tag传递给注册的函数中的参数是字符串。

 

当时想到做自定义的tags也是项目需要。但自己开始做的时候发现tag传递的是字符串,当时也查了不少资料没找到解决的方法,那时就想放弃自定义tags了。但注意到django自带的tags不也是传递字符吗,像{%if user%}, {% for item in test_list %}这样的,django肯定有什么方法可以将类似“user”, "test_list"这样的字符和view中传递来的对象关联起来。于是便开始硬着头皮看django的源码了。(要知道对于像我这样的菜鸟来说。看源码可不是容易的事情)。

 

到底django是用什么魔法做到字符变对象的呢。起先我以为是这样的,比如{% if user  %},则先提取user取来,再将传递过去的user从context中取去,user=context['user'],但后来一想,单个的对象好想是可以,但如果有句点符号的是怎么做到的呢。就像user.username?难道是再将它通过字符串操作再分离一次,但如果遇到多个句点符号呢?类似

user.profile.realname。照这个思路看源码,发现不是我想的这样的。有些源代码没看太懂,但正是这些没看懂的代码才是关键。

 

再回头看看上面的代码,注册标签的代码方法中有个parser参数,正是这个参数。

对照上面的代码。

sequence = parser.compile_filter(text_name)
将开始从token.split_contents()中分离出来的 像:user,,user.age ,  user.profile.realname等字符串先编译成一个sequence 对象。怎么编译的我也不太明白,不去管它了。

随后在类方法里面有句values = self.sequence.resolve(context, True)
再打印出values来看看,type(values)看看,哈哈。不再是字符串了,是真正的实例对象或是变量。

 

那么一切OK。虽然不知道Django中这两句是怎么样实现的,但不管了。至少现在我们可以自由的定制适合的filter和tags了。

你可能感兴趣的:(django,python)