django自定義模板標籤和過濾器
Django雖然為我們內建了二十多種標籤和六十多種過濾器,但是需求是各種各樣的,總有一款你cover不到。Django為我們提供了自定義的機制,可以通過使用Python程式碼,自定義標籤和過濾器來擴充套件模板引擎,然後使用{% load %}標籤。
一、前置步驟
Django對於自定義標籤和過濾器是有前置要求的,首先一條就是程式碼佈局和檔案組織。
你可以為你的自定義標籤和過濾器新開一個app,也可以在原有的某個app中新增。
不管怎麼樣,第一步,在app中新建一個templatetags
包(名字固定,不能變,只能是這個),和views.py、models.py等檔案處於同一級別目錄下。這是一個包!不要忘記建立__init__.py
在新增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.py
和django/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提供了大量的快捷方式,使得編寫標籤比較容易。 對於我們一般的自定義標籤來說,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_link
和home_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
,就表示不需要傳遞引數給這個模板標籤。它會自己去獲取上下文。