Django框架-Templates進階用法


    • Template加載機制
一般來說,你在你的文件系統中存入模板,但你也可以使用自定義的template加載器去從其它地方
加載你的模板。
Django有兩種方式去加載你的模板:
1. django.template.loader.get_template(template_name):get_template通過模板名參數,返回
一個模板對象,如果模板不存在,報錯TemplateDoesNotExist
 
2. django.template.loader.select_template(template_name_list):參數爲模板名的列表,返回第一個
存在的模板,如果列表中所有模板都不存在,就報錯TemplateDoesNotExist
 
以上函數都默認在settings.py中TEMPLATE_DIRS屬性下添加的路徑下查找,不過,在內部機制上,這些函數
也可以指定不同的加載器來完成這些任務。
 
 
加載器的設置在settings.py中的TEMPLATE_LOADERS屬性中,它是一個字符串元組類型,每一個字符串代表
一個loader類型。有些被默認開啓,有些是默認關閉的。可以一起使用,直到找到模板爲止。
1. django.template.loaders.filesystem.Loader:默認開啓,從TEMPLATE_DIRS路徑中加載模板
 
2. django.template.loaders.app_directories.Loader:默認開啓,這個裝載器會在每一個INSTALLED_APPS
註冊的app目錄下尋找templates子目錄,如果有的話,就會在子目錄中加載模板。這樣就可以把模板和
app放在一起,方便重用。這個裝載器會有一些優化,在第一次導入的時候,會緩存包含templates子目錄的app
包的列表。
優化的源碼如下:
 
3. django.template.loaders.eggs.Loader:默認關閉,從egg文件中加載模板,egg文件類似jar包,python
中打包發佈代碼的一種方式。和app_directories Loader類似也是從app子目錄template中加載egg文件。

擴展你的模板系統

一般是擴展模板的tag和filter兩個功能。可以用來創建你自己的tag和filter功能庫。

創建模板庫

分爲兩步:
1. 首先決定由模板庫在哪一個註冊的app下放置,你可以放在一個已有的app目錄下,也可以新建一個
專門管理模板庫的app,比如python manage.py startapp myTemplateLibrary。推薦後者,
因爲可以方便將來的重用。
 
2. 在app目錄下創建templatetags子目錄,並在裏面創建兩個文件,__init__.py,用來聲明這是一個
包,另一個是你的tag/filter定義文件。比如myNewLibrary.py,那麼在模板文件中可以這樣使用:
{% load myNewLibrary %}
 
{% load %}只允許導入註冊app目錄下的模板庫。這樣做是爲了保證你的模板庫可以不被其它Django
程序使用。

實現自定義過濾器

1. 創建register變量
在你的模塊文件中,你必須首先創建一個全局register變量,它是用來註冊你自定義標籤和過濾器的,
你需要在你的python文件的開始處,插入幾下代碼:
from django import template
register = template.Library()
 
2. 定義過濾器函數
自定義的過濾器就是一個帶1,2個參數的python函數,一個參數放變量值,一個用來放選項值。
比如{{ var|remove:"bar" }}, var是變量值,"bar"是選項值,remove過濾器可以定義爲:
def remove(var, arg):
    #移除字符串中var的arg字串
    return var.replace(arg, '')
過濾器函數應該總是返回一些信息,即使出錯,也不應該拋出異常,可以返回默認值或者空字符串。
不帶參數的過濾器也很常見:
def lower(value):
    "Converts a string into all lowercase"
    return value.lower()
3. 註冊過濾器函數
#第一個參數是在模板中使用的過濾器的名字
#第二個就是你的過濾器函數引用名
register.filter('remove', remove)
register.filter('lower', lower)
python2.4以上版本,可以使用裝飾符(decorator)功能
@register.filter(name='remove')
def remove(value, arg):
    return value.replace(arg, '')
@register.filter
def lower(value):
    return value.lower()
如果裝飾符不加name,則默認使用函數名來當作使用名。
下面是完整代碼:
from django import template
register = template.Library()
@register.filter(name='remove')
def remove(value, arg):
    return value.replace(arg, '')
@register.filter
def lower(value):
    return value.lower()

實現自定義tag

過程比實現過濾器要複雜,首先回顧一下模板系統的工作流程:
1. 編譯生成模板對象
2. 模板對象使用上下文對象,渲染生成HTML內容
 
如果你要實現自己的tag,就需要告訴Django怎樣對你的tag進行上面的兩個步驟。
 
瞭解模板編譯過程
當Django編譯一個模板時,它把原始的模板文件中的內容變成一個個節點,每一個節點
是django.template.Node的實例,節點都有一個render()函數。因此,一個編譯過的
模板對象可以看成是一個結點對象的列表。例如,模板文件內容:
Hello, {{ person.name }}.
{% ifequal name.birthday today %}
Happy birthday!
{% else %}
Be sure to come back on your birthday
for a splendid surprise message.
{% endifequal %}
被編譯後的Node列表:
  • Text node: "Hello, "
  • Variable node: person.name
  • Text node: ".\n\n"
  • IfEqual node: name.birthday and today
當你調用模板對象的render()方法時,它會去調用Node列表上的每一個Node的render方法。最
後輸出的結果就是所有render方法的輸出結果的合併。所以要創建你自己的tag,需要實現你自己的
Node類,實現你自己的render方法。
 
創建tag實戰
下面我們來實現一個tag,調用方法爲:
{% current_time "%Y-%m-%D %I:%M %p" %}
功能是按照給定格式,顯示當前時間,這個格式字符串和time.strftime()中定義的格式一樣
 
這裏是爲了演示一下,格式內容可以參考http://docs.python.org/library/time.html#l2h-1941
這個標籤也支持不需要參數的默認顯示。
 
1. 定義Node節點類,實現render方法
import datetime
from django import template
#這一句還是要的
register = template.Library()
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)
render函數一定返回的是字符串,即使是空字符串
2. 創建Compilation函數
這個函數主要用於獲取模板中的參數,並創建相應的Node類對象
def do_current_time(parser, token):
    try:
        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])
每一個tag的編譯函數都需要兩個參數parser和token:
parser是模板分析對象
token是被parser分析後的內容,可以直接使用
 
token.contents 是tag的內容,這裏token的值是'current_time "%Y-%m-%d %I:%M %p"'
token.split_contents 按空格分割字符串,返回tuple,但保留引號之單位的內容,這裏得到
('current_time', '%Y-%m-%d %I:%M %p')
和實現filter不一樣,如果tag運行出錯,一定要拋出TemplateSyntaxError,返回一些有用的信息。
不要硬編碼你的tag名,使用token.split_contents()[0]可以得到它。
編譯函數總是返回一個Node子類實例,返回其它類型會報錯。
3. 註冊tag
register.tag('current_time', do_current_time)
和註冊filter類似,兩個參數,一個是使用名,一個是對應的函數引用名
python2.4版本以上,也可以使用裝飾符功能
@register.tag(name="current_time")
def do_current_time(parser, token):
# ...
@register.tag
def shout(parser, token):
# ...
不用名字,表示默認使用函數名
完整代碼爲:
from django import template
import datetime
register = template.Library()
@register.filter(name='remove')
def remove(value, arg):
    return value.replace(arg, '')

@register.filter
def lower(value):
    return value.lower()

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)
    def do_current_time(parser, token):
        try:
            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])

register.tag('current_time', do_current_time)
4. 運行
在模板文件中添加:
訪問頁面:
複雜的實現自定義tag的其他幾種方法
1. 在Node類的render函數中設置context
def render(self, context):
    now = datetime.datetime.now()
    #設置context對象的值
    context['current_time'= now.strftime(self.format_string)
    # render函數一定要返回字符串,即使是空串
    return ''
這樣調用的時候,就是如下用法:
{% current_time "%Y-%M-%d %I:%M %p" %}
<p>The time is {{ current_time }}.</p>
但這樣做一個不好的地方就是,current_time變量名是硬編碼,可能會覆蓋相同名字的值。
 
重新設計一個tag的使用格式,如:
{% get_current_time "%Y-%M-%d %I:%M %p" as my_current_time %}
<p>The current time is {{ my_current_time }}.</p>
這樣就需要修改一下編譯函數,Node類和註冊代碼,代碼如下:
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):
    #使用正規表達式來處理token
    try:
    # 使用string.split(sep[, maxsplit]),1代表最大分割數,也就是
    # 分割後會產生maxsplit+1個元素
    # 這裏分割後的結果爲(get_current_time, '"%Y-%M-%d %I:%M %p" as my_current_time')
        tag_name, arg = token.contents.split(None1)
    except ValueError:
        msg = '%r tag requires arguments' % token.contents.split_contents()[0]
        raise template.TemplateSyntaxError(msg)
    #使用()代表正則組,匹配as兩邊的字符串
    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[-1and fmt[0in ('"'"'")):
        msg = "%r tag's argument should be in quotes" % tag_name
        raise template.TemplateSyntaxError(msg)
    # [1:-1]去除格式兩邊的引號
    return CurrentTimeNode3(fmt[1:-1], var_name)

register.tag('get_current_time', do_current_time)
運行結果:
2. 實現塊作用區域的tag
如{% if %}...{% endif %},需要在你的編譯函數中使用parse.parse()
例如我們想要實現{% comment %}...{% endcomment %},功能是
忽略中tag中間的所有內容。
def do_comment(parser, token):
    nodelist = parser.parse(('endcomment',))
    parser.delete_first_token()
    return CommentNode()

class CommentNode(template.Node):
    def render(self, context):
    return ''
parse.parse()的參數是一個包含多個tag名的元組,返回的是它遇到元組中任何一個
tag名之前的所有Node對象列表,所以這裏的nodelist包含{% comment %}和
{% endcomment %}之間的所有node對象,並且不包含它們自身兩個node對象。
 
parser.delete_first_token():因爲執行完parse.parse()之後,{% endcomment %}
tag還在,所以需要顯示調用一次,防止這個tag被處理兩次。
3. 在塊作用tag中保留context內容
代碼如下
{% upper %}
This will appear in uppercase, {{ user_name }}.
{% endupper %}
這裏需要context中的user_name參數,怎麼才能在處理tag的時候,不丟失context信息呢?
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()
只需要保留下nodelist,然後調用self.nodelist.render(contest),就可以間接調用每一個Node
的render函數。
 
 
有更多的例子,可以查看Django源代碼,位置爲:
D:\Python27\Lib\site-packages\django\template\defaulttags.py
4. 快速創建簡單tag的方法
簡單tag的定義,只帶一個參數,返回經過處理的字符串,像之前的current_time標籤一樣。
Django提供了一種simple_tag方法來快速創建類似這樣的tag。
def current_time(format_string):
    try:
        return datetime.datetime.now().strftime(str(format_string))
    except UnicodeEncodeError:
        return ''

register.simple_tag(current_time)
simple_tag參數爲一個函數引用,會把它包裝進render函數中,然後再進行註冊。也不用定義
Node子類了。
 
python2.4以上,可以使用裝飾符
@register.simple_tag
def current_time(token):
# ...
5. 創建Inclusion Tag
另外一種比較普遍的tag類型是隻是渲染其它模塊顯示下內容,這樣的類型叫做Inclusion Tag。
例如,實現以下tag:
{% books_for_author author %}
渲染結果爲:
<ul>
<li>The Cat In The Hat</li>
<li>Hop On Pop</li>
<li>Green Eggs And Ham</li>
</ul>
列出某個作者所有的書。
  • 定義函數
def books_for_author(author):
    books = Book.objects.filter(authors__id=author.id)
    return {'books': books}
  • 創建另一個模板文件book_snippet.html
<ul>
{% for book in books %}
<li>{{ book.title }}</li>
{% endfor %}
</ul>
  • 註冊tag
register.inclusion_tag('book_snippet.html')(books_for_author)
 
有些你的模板可以使用父模板的context內容,Django提供一個takes_context參數來實現,
使用之後,tag不能再帶參數,
@register.inclusion_tag('link.html', takes_context=True)
def jump_link(context):
    return {
        'link': context['home_link'],
        'title': context['home_title'],
}
模板文件link.html爲
Jump directly to <a href="{{ link }}">{{ title }}</a>.
使用方法:
{% jump_link %}

創建自定義模板加載類

可以自定其它的加載行爲,比如從數據庫中加載,從svn中加載,從zip中加載等。
 
需要實現一個接口load_template_source(template_name, template_dirs=None):
template_name就是類似'link.html'這樣的模板名稱
template_dirs是一個可選的路徑列表,爲空就使用TEMPLATE_DIRS屬性定義的路徑。
 
 
如果一個加載器加載模板成功,它將返回一個元組(template_source, template_path)。
template_source:模板文件的內容字符中,會用於被編譯
template_path:模板文件的路徑
 
如果加載失敗,報錯django.template.TemplateDoesNotExist
 
 
每一個加載函數都需要有一個is_usable的函數屬性,對,是函數的屬性,因爲在python中,
函數也是個對象。這個屬性告訴模板引擎在當前的python環境下這個加載器是否可用。
 
例如,之前的eggs加載器是默認關閉的,is_usable=False,因爲需要pkg_resources模塊
中的從egg中讀取信息的功能。不一定每個用戶會安裝,如果安裝了,就可以設置爲True,開啓
功能。
 
 
下面實現一個從zip文件中加載模板的自定義加載器,它使用TEMPLATE_ZIP_FILES作爲搜索路徑,來
代替系統的TEMPLATE_DIRS,路徑上都是zip文件名。
from django.conf import settings
from django.template import TemplateDoesNotExist
import zipfile
def load_template_source(template_name, template_dirs=None):
    "Template loader that loads templates from a ZIP file."
    #從settings.py配置文件中讀取屬性TEMPLATE_ZIP_FILES的值,默認返回空列表
    template_zipfiles = getattr(settings, "TEMPLATE_ZIP_FILES", [])
    # Try each ZIP file in TEMPLATE_ZIP_FILES.
    for fname in template_zipfiles:
        try:
            z = zipfile.ZipFile(fname)
            source = z.read(template_name)
        except (IOErrorKeyError):
            continue
        z.close()
        # 找到一個可用的文件就返回
        template_path = "%s:%s" % (fname, template_name)
        return (source, template_path)
    
    # 如果一個zip文件沒找到,報錯
    raise TemplateDoesNotExist(template_name)

# 設置爲可用
load_template_source.is_usable = True
保存爲zip_loader.py,放在app目錄下,剩下我們需要做的是,在TEMPLATE_LOADERS屬性
中註冊你的加載器:
TEMPLATE_LOADERS = (
    'books.zip_loader.load_template_source',
)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章