[Django] 自定義 template tags & filters

程式語言:Python
Package:Django

Django 官網文件

簡介:自定義 Tags & Filter

初步設置

  1. 在 APP 下建立 templatetags 資料夾
  2. 資料夾下建立 __init__.py,並建立 mine.py (可自行命名)
  3. 在 template html 中,加入 {% load mine%}
  4. 在 mine.py 頂端加入
    from django import template
    register = template.Library()
    

自定義 template filters

可定義參數
  • 輸入變數的值
  • 參數的值,可以是默認值或者完全留空
範例
# template html
{{ somevariable|cut:"0" }}

# in mine.py
#register.filter('cut', cut)
@register.filter(name='cut')
def cut(value, arg):
    """Removes all values of arg from the given string"""
    return value.replace(arg, '')
# template html
{{ somevariable|lower}}

# in mine.py
#register.filter('lower', lower)
@register.filter
def lower(value): # Only one argument.
    """Converts a string into all lowercase"""
    return value.lower()

自定義 template tags

當 Django 編譯一個模板時
  • 將原始模板分成一個個 節點。
  • 每個節點都是 django.template.Node 的一個實例,並且具備 render() 方法。
於是,一個已編譯的模板就是 節點 對象的一個列表

範例
# template html
<p>The time is {% current_time "%Y-%m-%d %I:%M %p" %}.</p>

# in mine.py
from django import template

register = template.Library()

import datetime

class CurrentTimeNode(template.Node):
    def __init__(self, format_string):
        self.format_string = str(format_string)

    def render(self, context):
        now = datetime.datetime.now()
        return now.strftime(self.format_string)

#register.tag('current_time', do_current_time)
@register.tag(name="current_time")
def do_current_time(parser, token):
    try:
        # split_contents() knows not to split quoted strings.
        tag_name, format_string = token.split_contents()
    except ValueError:
        msg = '%r tag requires a single argument' % token.split_contents()[0]
        raise template.TemplateSyntaxError(msg)
    return CurrentTimeNode(format_string[1:-1])
  • 每個標籤編譯函數有兩個參數,parser 和 token。
    • parser是模板解析器對象。此例無使用。可看後面的例子
    • token是被解析的語句。
      • token.contents 是包含有標籤原始內容的字符串。
        • 此例為 'current_time "%Y-%m-%d %I:%M %p"' 。
      • token.split_contents() 按空格拆分,並保證引號中的字串不拆分。
        • 避免使用 token.contents.split(),因可能會拆分字串中的空白
        • 可拋出 django.template.TemplateSyntaxError
          • 提供所有語法錯誤的有用信息。
        • token.split_contents()[0] 記錄標籤的名字,就算標籤沒有任何參數。此例為 current_time
  • 返回一個 CurrentTimeNode ,包含了節點需要知道的關於這個標籤的全部信息。
    • 此例只傳遞了參數 "%Y-%m-%d %I:%M %p"。
    • 雙引號使用 format_string[1:-1] 去除。
  • 模板標籤編譯函數必須返回一個 Node 子類,返回其它值都是錯的。
使用 context 變數
# template html
{% get_current_time "%Y-%M-%d %I:%M %p" as my_current_time %}
<p>The current time is {{ my_current_time }}.</p>

# in mine.py
import re

class CurrentTimeNode3(template.Node):
    def __init__(self, format_string, var_name):
        self.format_string = str(format_string)
        self.var_name = var_name

    def render(self, context):
        now = datetime.datetime.now()
        context[self.var_name] = now.strftime(self.format_string)
        return ''

def do_current_time(parser, token):
    # This version uses a regular expression to parse tag contents.
    try:
        # Splitting by None == splitting by spaces.
        tag_name, arg = token.contents.split(None, 1)
    except ValueError:
        msg = '%r tag requires arguments' % token.contents[0]
        raise template.TemplateSyntaxError(msg)

    m = re.search(r'(.*?) as (\w+)', arg)
    if m:
        fmt, var_name = m.groups()
    else:
        msg = '%r tag had invalid arguments' % tag_name
        raise template.TemplateSyntaxError(msg)

    if not (fmt[0] == fmt[-1] and fmt[0] in ('"', "'")):
        msg = "%r tag's argument should be in quotes" % tag_name
        raise template.TemplateSyntaxError(msg)

    return CurrentTimeNode3(fmt[1:-1], var_name)

block tag

範例
# template html
{% upper %}
    This will appear in uppercase, {{ user_name }}.
{% endupper %}

# in mine.py
def do_upper(parser, token):
    nodelist = parser.parse(('endupper',))
    parser.delete_first_token()
    return UpperNode(nodelist)

class UpperNode(template.Node):
    def __init__(self, nodelist):
        self.nodelist = nodelist

    def render(self, context):
        output = self.nodelist.render(context)
        return output.upper()
  • parser.parse() 的參數為 tuple,內含需要分析的模板標籤名
    • 返回一個 django.template.NodeList 實例
      • 包含了所有 Node 對象的列表
      • 解析器在 tuple 指定的標籤之前遇到的內容
      • nodelist 不包括開始跟結束標籤,此例為 {% upper %} 和 {% endupper %}。
    • parser.parse() 被調用後,分析器並無清除 {% endcomment %} 標籤,因此需要調用 parser.delete_first_token() 來防止該標籤被處理兩次。
  • 更多的範例可看 django/template/defaulttags.py

Simple tags

  • 傳遞給函數只有單個參數。 
  • 函數被調用時,檢查必需參數個數的工作已經完成,無需再做這個工作。 
  • 參數兩邊的引號(如果有的話)已被截掉,只會接收到一個普通字符串。

範例
import datetime
from django import template

register = template.Library()

#{% current_time1 "%Y-%m-%d %I:%M %p" %}
@register.simple_tag
def current_time1(format_string):
    return datetime.datetime.now().strftime(format_string)

#{% current_time2 "%Y-%m-%d %I:%M %p" %}
@register.simple_tag(takes_context=True)
def current_time2(context, format_string):
    timezone = context['timezone']
    return your_get_current_time_method(timezone, format_string)

#{% minusone 2 %} => 1
register.simple_tag(lambda x: x - 1, name='minusone')

#{% minustwo 2 %} => 0
@register.simple_tag(name='minustwo')
def some_function(value):
    return value - 2

#{% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
@register.simple_tag
def my_tag(a, b, *args, **kwargs):
    warning = kwargs['warning']
    profile = kwargs['profile']
    ...
    return ...
#{% get_current_time "%Y-%m-%d %I:%M %p" as the_time %}
#<p>The time is {{ the_time }}.</p> 

Inclusion tags

通過渲染 其他 template 顯示數據

範例
例:一個能夠產生指定作者對象的書籍清單的標籤。結果如下
  • The Cat In The Hat
  • Hop On Pop
  • Green Eggs And Ham
# template html
{% books_for_author author %}

# in mine.py
#register.inclusion_tag('book_snippet.html')(books_for_author)
@register.inclusion_tag('book_snippet.html')
def books_for_author(author):
    books = Book.objects.filter(authors__id=author.id)
    return {'books': books}

# in book_snippet.html
<ul>
{% for book in books %}
    <li>{{ book.title }}</li>
{% endfor %}
</ul>

有時候,包含標籤需要訪問父模板的 context。
可使用 takes_context 選項。注意函數的第一個參數必須是 context。
例:一個包含標籤,該標籤包含有指向主頁的 home_link 和 home_title 變量
# template html
{% jump_link %}

# in mine.py
@register.inclusion_tag('link.html', takes_context=True)
def jump_link(context):
    return {
        'link': context['home_link'],
        'title': context['home_title'],
    }

# in link.html
Jump directly to <a href="{{ link }}">{{ title }}</a>. 

留言