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最基本的格式如下
<pre name="code" class="python">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了。
轉載地址 :http://xiao80xiao.iteye.com/blog/519394