1. 程式人生 > 實用技巧 >定製Django的Tag和Filter(二)

定製Django的Tag和Filter(二)

配置

(1)最常見的放置自定義Tag和Filter的地方是在Django的app下。當一個app被新增到settings.py的INSTALLED_APPS 後,任何在它下面的合法位置將自動的可在templates中被呼叫。合法位置就是在app下的templatetags子資料夾下,但不要忘記新增init.py檔案來表明它是個包。

自定義的tag或者filter就在templatetag資料夾下的py檔案中,在template中呼叫的是該py檔案的名字,比如

app名字是polls,tags/filter在poll_extras.py中:

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

在template中這樣載入定義在poll_extras.py中的tags/filters:

{% load poll_extras %}

包含定製的tags/filters的app必須在INSTALLED_APPS,並且{% load ... %}也是在INSTALLED_APPS從上往下搜尋。

(2)當然放置Tag和Filter的檔案也不一定非得在app中,可以在project的任何地方,但是這種情況下,需要在DjangoTemplateslibraries中註冊,比如,將放置tag/filters的檔案testcommon.py放置在工程的新建common資料夾下。

然後在settings.py的TEMPLATE

中註冊該檔案,需要注意的是在templates中load的是‘commonfile'。

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [TEMPLATE_DIR],
        'APP_DIRS': True,
        'OPTIONS': {
            # 'string_if_invalid':InvalidTemplateVariable('%s'),
            # 'autoescape':False,
            'context_processors': [
                
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                'banners.processors.onsale',
                
            ],
            'libraries':{'commonfile':'testBegin.common.testcommon'},
        },
    },
]

testcommon.py中定義commonfilter:

from django import template
from django.utils.html import escape
from django.utils.safestring import mark_safe
register=template.Library()
@register.filter(needs_autoescape=True)
def commonfilter(value,autoescape=True):
    if autoescape:
        value=escape(value)
    result='<b>%s</b>'%value 
    return mark_safe(result)

最後在template中呼叫:

{% load commonfile %}
{{ coffee|commonfilter }}  #其中coffee變數為'mocamoca'

那能不能像built-in filter一樣,不用每次都宣告{% load custom filter %}?可以的,只需要在settings.py中的'TEMPLATES'的'OPTIONS'欄位內加入'builtins':['location of files containing filter'],比如:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [TEMPLATE_DIR],
        'APP_DIRS': True,
        'OPTIONS': {
            # 'string_if_invalid':InvalidTemplateVariable('%s'),
            # 'autoescape':False,
            'context_processors': [
                
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                'banners.processors.onsale',
                
            ],
            'builtins':['testBegin.common.testcommon'],
        },
    },
]

寫自定義的模板filter

自定義filter是有1個或者2個引數的Python函式:

  • 輸入變數的值(不一定必須是string)

  • 值的引數,可以有預設值

    比如,{{ var|foo:'bar' }},foo被摻入了變數var和引數'bar'。

    下面是個簡單的例子:

    #bannerTags.py
    from django import template
    register=template.Library()
    
    @register.filter()
    def john(value,arg):
        return value.replace(arg,'john')
    
    {% load bannerTags %}
    {{ coffee|john:'m' }}
    

    coffee的值為'macomaco',

@register.filter可以沒有引數,直接用@register.filter來裝飾,也可以有引數,起到特定的功能:

  • name:預設的filter的名字就是函式名字,而name引數可以改變filter的名字

@register.filter(name='cutname')
def cut(value, arg):
    return value.replace(arg, '')
{{ coffee|cutname:'m' }}
  • 此外,還有is_safe,needs_autoscape,expects_localtime.

字元期望的Filter(Template filters that expect strings)

django.template.defaultfilters.stringfilter()

在上面的例子中,如果coffee的值是11,則會報錯:

解決這個只需要再加層裝飾器就可以了:

from django import template
from django.template.defaultfilters import stringfilter
register=template.Library()
@register.filter
@stringfilter
def john(value,arg):
    return value.replace(arg,'john')

可以發現已經沒有報錯了:

Filter 和 auto-escaping

from django import template
from django.template.defaultfilters import stringfilter
register=template.Library()
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe

@register.filter
@stringfilter
def john(value,arg):
    return value.replace(arg,'john')
@register.filter(is_safe=True)
def myfilter0(value):
    return value 
@register.filter()
def myfilter(value):
    return mark_safe(value)

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

@register.filter()
def myfilter1(value):
    value=conditional_escape(value)
    return mark_safe(value)

@register.filter()
def myfilter2(value):
    value=conditional_escape(value)
    return value

@register.filter()
def myfilter3(value):
    return value
{% load bannerTags %}
{{ coffee|john:'a' }}
<br>
{{ coffee|testautoes }}--testautoes
<br>
{{ coffee|myfilter }}--myfilter
<br>
{{ coffee|myfilter0 }}--myfilter0
<br>
{{ coffee|myfilter1 }}--myfilter1
<br>
{{ coffee|myfilter2 }}--myfilter2
<br>
{{ coffee|myfilter3 }}--myfilter3

當在settings.py的Templates的'OPTIONS'新增autoexcape:False時,

從這個例子可以看出幾點:

  • 自定義filter的函式大多都受總的autoescape設定(即settings.py中關於autoescape)的影響,如果總的autoescape被設定為False,則含有”不安全“的符號如'<,>,'將不被轉義,但是當通過conditional_escape處理後,則仍然被轉義,具體表現為上圖'myfilter1'和'myfilter2'
  • 當總的autoescape是True(預設情況),大多含有”不安全“的符號,都要進行轉義,但是通過mark_safe而沒有通過conditional_escape的處理,將直接被渲染。

寫自定義Tags

Tags 比Filter更復雜,因為tags可以做任何事情,Django提供了很多快捷方式來使得寫tags更方便。

Simple tags

django.template.Library.simple_tag()

很多模板tag有很多引數,字串或者模板變數,然後僅僅基於輸入的引數進行一些操作,然後返回結果。比如,‘current_time'可以接受一個字串模板,返回時間的格式化的字元。

為了簡化這種tag的建立過程,Django提供了個幫助函式,simple_tag,這個函式是django.template.Library的方法,它的接受任意的引數,用render函式包裹該函式,所以它就是起了一個裝飾器的作用。

#bannerTags.py
import datetime
from django import template

register = template.Library()

@register.simple_tag
def current_time(format_string):
    return datetime.datetime.now().strftime(format_string)
{% load bannerTags %} 
{% current_time ' %Y/ %m/ %d' as tim %} 
{{tim}}

不像其他tag的用法,如果是autoescape模式,simple_tag就會將它的輸出再輸入conditional_escape()中,來保證正確的HTML,來免於XSS攻擊。

如果十分確定沒有XSS攻擊程式碼,則可以用mark_safe(),而對於建立小的HTML程式碼塊,建議用format_html(),而不是mark_safe().

  • 如果需要重新命名,老規矩,就像Filter一樣,在register.simple_tag()中傳入name即可。

  • 如果需要接受任意數量的positional argument或者keyword arguments,比如:

    @register.simple_tag
    def my_tag(a, b, *args, **kwargs):
        warning = kwargs['warning']
        profile = kwargs['profile']
        ...
        return ...
    

    在模板中,任意的引數,被空格分隔,就會被傳入tag中。與python類似的是,對於keyword argument,用 等於號 '='來表示,而keyword argument跟在python中的類似,也必須放在position arguments之後。

    {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
    
  • 如果不直接輸出tag的結果,而是將該結果儲存起來,也是可以的。通過as就可以達到這個效果,比如:

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

    Inclusion tags

    django.template.Library.inclusion_tag()

    另一種常見的型別是通過render其他的模板來展示資料的,這種tags被稱為'inclusion tags‘。

寫法步驟如下:

(1)首先定義一個函式及其輸入引數,然後返回一個字典,該字典將被當做一個template context傳入被rendered 的其他模板。

#testcommon.py
def show_results(strings):
    choices=strings.split('s')
    return {'choices':choices}

(2)定義被rendered的其他模板,其位置必須能被搜尋到。

# results.html
<ul>
    {% for alpha in choices %}
    {% if alpha %}
    <li> {{ alpha }}</li>
    {% endif %}
    {% endfor %}
</ul>

(3)通過呼叫inclusion_tag來建立,和註冊一個inclusion tag。

#testcommon.py
@register.inclusion_tag('banners/results.html')
def show_results(strings):
    choices=strings.split('s')
    return {'choices':choices}

(4)在模板中使用該tag。

#test.html
{% show_results coffee %}

其路由為:

urlpatterns=[
    url(r'^method',views.method,name='method'),
    url(r'^$',TemplateView.as_view(template_name='banners/test.html',extra_context={'coffee':'moscamsocas'}),name='index'),
]

執行:

  • 也可以在模板中直接傳字串:

    #test.html
    {% show_results 'asbscs' %}
    

還有一種等效的方法是:

#testcommon.py
from django.template.loader import get_template
t=get_template('banners/results.html')
register.inclusion_tag(t)(show_results)

可以看到,本質上與第一種方法是一樣的,只不過是顯式的找到html然後傳遞給register.inclusion_tag,然後再傳入show_results,這個過程本身就是裝飾器的顯式表達。

有時候,inclusion tags需要將大量的引數傳入,這時候對於使用tags的人來說非常痛苦,還要記住引數順序。為解決這種問題,Django為inclusion tag提供了take_context選項,當表明了它,並設定為True,使用模板時,可以不需要引數,相應的python function只需要傳入一個引數context,從而達到直接將被呼叫的template的context_dict傳入tag function,並通過tag function 傳入被rendered的Html。

比如:

@register.inclusion_tag('banners/results.html',takes_context=True)
def show_results(context):
    choices=context['coffee'].split('s')
    return {'coffee':choices }
#被call的test.html
{% show_results  %}
#被rendered的html,可見coffee被傳入
<ul>
    {% for alpha in coffee %}
    {% if alpha %}
    <li> {{ alpha }}</li>
    {% endif %}
    {% endfor %}
</ul>

最後,inclusion_tag也可以接受任意數量的position argument和keyword argument:

如:

@register.inclusion_tag('my_template.html')
def my_tag(a, b, *args, **kwargs):
    warning = kwargs['warning']
    profile = kwargs['profile']
    ...
    return ...
{% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}