Jinja2
本文全文都來自:http://docs.jinkan.org/docs/jinja2/index.html
簡介
jinja2 是一個模仿 Django 模板語言而重新開發的模板引擎,因為 Django 模板引擎限制比較多,因此有人開發出了 jinja2 這個庫。所以如果你比較熟悉 Django 模板,那么 jinja2 你也很快就能了解。
安裝
pip install Jinja2
基本使用
from jinja2 import Template
# 使用字符串,創建一個模板。模板包含了一個變量 {{ name }}
temp = Template("<p>{{ name }}</p>")
# 給模板傳遞變量,並渲染
content = temp.render(name="Hello") # 可以傳遞關鍵字參數
# content = temp.render({"name": "Hello"}) # 也可以傳遞字典類型
print(content)
{{}}
是用來在模板中聲明變量的。模板自帶了一個render()
函數,可以給模板傳遞變量(context
,也叫上下文變量),然后渲染它的內容。
Template 對象
模板對象, 就是我們上面用到的:
from jinja2 import Template
temp = Template("<p>{{ name }}</p>")
content = temp.render(name="Hello")
print(content)
比較大的模板文件,可以使用生成器,來減少內存壓力:
from jinja2 import Template
temp = Template("{{ name }}")
gen = temp.generate(name="Wang") # 返回的是生成器
for g in gen:
print(g) # 渲染后的內容
Template
對象常見的屬性和方法:
globals
該模板的全局變量字典。修改這個字典是不安全的,因為它可能與其它模板或加載這個模板的環境共享這個全局字典。
name
模板的加載名。如果模板是從字符串加載的,這個值為 None 。
filename
模板文件的文件名,如果不是從文件中加載的,這個值為 None 。
render
([context])根據傳遞的上下文,來渲染模板
generate
([context])和
render
類似。如果一個模板文件很大,我們可以使用這個函數,來返回一個生成器緩解內存壓力。然后迭代這個生成器,來獲取渲染后的內容。
stream
([context])和
generate()
一樣,也能渲染模板,只不過返回一個TemplateStream
對象。
TemplateStream 對象
當我們對 Template
對象使用 .stream()
,就可以渲染模板並返回一個模板流。它有兩個方法:
disable_buffering()
禁用輸出時緩存
dump(fp, encoding=None, errors='strict')
它可以將整個數據流保存到文件中,譬如:
Template('Hello {{ name }}!').stream(name='foo').dump('hello.html')
Environment 對象
除了上面我們介紹的直接使用 Template
對象,我們還可以使用 Environment
對象,來保存一些配置,譬如:
from jinja2 import Environment, PackageLoader, Template
# 聲明一個 package 加載器,會自動去 temp 這個 python包下的 templates 文件夾找所有的文件,即:./temp/templates/*.*
loader = PackageLoader("temp", "templates")
env = Environment(loader=loader) # 生成環境
template = env.get_template("test.html") # 加載某個文件,會從:./temp/templates/ 路徑下自動查找這個 test.html
print(template.render(name="ahha"))
Environemt 的好處是,可以保存一些配置,以后所有使用這個環境所獲取的模板,都會統一使用這個配置。
我們可以通過
env.get_template()
來獲取某個模板對象,然后通過template_obj.render(context)
來渲染模板。
Environment 參數:
- block_start_string
- 聲明 block 時的起始字符串,默認:
'{%'
. (所謂 block,可以看作一個代碼塊,譬如:{% if 3 ==3 %} ... {% endif %}
)- block_end_string
- 聲明 block 時的結束字符串,默認:
'%}'
.- variable_start_string
- 聲明 變量 時的起始字符串,默認:
'{{'
.- variable_end_string
- 聲明 變量 時的結尾字符串,默認:
'}}'
.- comment_start_string
- 聲明 注釋 時的起始字符串,默認:
'{#'
.- comment_end_string
- 注釋的結尾字符串:
'#}'
.- line_statement_prefix
- 行語句的起始字符, 模板中以此字符開頭的行, 會被當成語句執行 行語句.
- line_comment_prefix
- 注釋的起始字符,模板中如果以此字符開頭,會被當成注釋(上面我們說過默認的注釋是{# #}): See also 行語句.
- newline_sequence
- 為序列開啟新行。必須是
'\r'
,'\n'
or'\r\n'
. 默認:'\n'
- extensions
- 擴展插件
- autoescape
- 如果設置成 True,會自動轉義一些特殊字符
- loader
- 模板加載器
- auto_reload
- 如果模板更改,會自動重新加載模板(為了性能,最好設置成 False)
Environment 對象的一些屬性:
filters
該環境的過濾器字典。只要沒有加載過模板,添加新過濾器或刪除舊的都是安全的。自定義過濾器見 自定義過濾器 。有效的過濾器名稱見 標識符的說明 。
tests
該環境的測試函數字典。只要沒有加載過模板,修改這個字典都是安全的。 自定義測試見 see 自定義測試 。有效的測試名見 標識符的說明 。
globals
一個全局變量字典。這些變量在模板中總是可用。只要沒有加載過模板,修 改這個字典都是安全的。更多細節見 全局命名空間 。有效的 對象名見 標識符的說明 。
add_extension
(extension)給環境添加一個擴展
from_string
(source, globals=None, template_class=None)從字符串加載一個模板。
get_or_select_template
(template_name_or_list, parent=None, globals=None)如果給了一個可迭代的模板列表,會使用
select_template()
,否則會使用get_template()
get_template
(name, parent=None, globals=None)從加載器加載模板。如果
parent
參數不為空,則會拼接 parent 文件夾路徑來獲取模板的真實路徑。
join_path
(template, parent)連接模板和 parent路徑
list_templates
(extensions=None, filter_func=None)返回所有的模板列表。
select_template
(names, parent=None, globals=None)和
get_template()
很像,但是會嘗試多次獲取模板.如果找不到模板會拋出TemplatesNotFound
Loader 加載器
在環境對象這一小節中,我們用到了加載器,下面有幾種不同的加載器:
FileSystemLoader(pathes, encoding='utf-8')
文件系統加載器,可以直接使用路徑,或者路徑列表作為參數,來加載這些路徑下的所有模板文件:
loader = FileSystemLoader('./temp/templates')
loader = FileSystemLoader(['./temp/templates', './temp/others'])
PackageLoader(package_name, package_path='templates', encoding='utf-8')
python包加載器,會從python的包(帶有__init__.py
)中的 templates
文件夾下加載所有的模板:
loader = PackageLoader('package1', 'templates')
DictLoader(mapping)
字典加載器,可以使用一個字典對象加載模板,字典的鍵是模板名,值是模板的文本字符串:
from jinja2 import Environment, DictLoader
loader = DictLoader({'index.html': 'source {{ name }} here'})
env = Environment(loader=loader)
template = env.get_template("index.html")
print(template.render(name="TEST"))
PrefixLoader(mapping, delimiter='/')
一個前綴加載器,接收一個字典,字典的鍵是前綴,字典的值是一個加載器,之后就可以使用 前綴+delimiter+模板名
來加載模板:
from jinja2 import Environment, PackageLoader, PrefixLoader
loader = PrefixLoader({
'app1': PackageLoader('temp', "templates")
}, delimiter="&")
env = Environment(loader=loader)
# 直接使用 app1 + delimiter + 模板名 就可以找到模板
template = env.get_template("app1&test.html") # 前提是 test.html 需要存在於 ./temp/templates 這個路徑下
print(template.render(name="TEST"))
ChoiceLoader(loaders)
一個可選加載器,接收一個加載器列表。如果第一個加載器找不到相應的模板,則會從第二個加載器開始找,並以此類推 ...
from jinja2 import Environment, ChoiceLoader, FileSystemLoader
loader = ChoiceLoader([
FileSystemLoader('./'), # 這個路徑下沒有模板
FileSystemLoader('./temp/templates') # 這個路徑有模板:test.html
])
env = Environment(loader=loader)
template = env.get_template("test.html") # 依然能找到
print(template.render(name="TEST"))
轉義
為了安全起見,所有用戶輸入的文本,都應該進行轉義,因為用戶可能輸入不安全的 html 字符,從而進行 跨站腳本 攻擊(Cross Site Scripting)
from markupsafe import Markup
s = "<p>Hello world!</p>" # html 字符串
m = Markup(s) # markup 對象
t = m.striptags() # 清除標簽,只剩下文本
e = Markup.escape(s) # 轉義特殊字符
o = e.unescape() # 特殊字符重新轉義回文本
print(
m, # <p>Hello world!</p>
t, # Hello world!
e, # <p>Hello world!</p>
o, # <p>Hello world!</p>
sep='\n'
)
過濾器
過濾器就是 python 函數,只不過它可以用特殊的方式,在模板中使用,渲染模板的時候,會自動執行這個函數。
過濾器的語法糖是:|
譬如:
<p>
{{ 40|addFilter(30) }}
</p>
假設我們有一個叫做
addFilter
的特殊過濾器函數,上面的代碼會調用addFilter(40, 30)
然后將返回值渲染到頁面上。
from jinja2 import Environment
# 一個普通函數
def addFilter(x, y):
return x + y
env = Environment()
env.filters['addFilter'] = addFilter # 添加一個過濾器
con = env.from_string("""
{{ 40|addFilter(30) }}
""").render()
print(con)
測試
所謂測試,其實就是判斷語句,比如 python 的如下代碼:
x = 2
if x == 2:
return True
else:
return False
jinja2 示例:
from jinja2 import Environment, FileSystemLoader, Template
from jinja2.nodes import EvalContext
# 一個普通的函數
def is_odd(n):
if n % 2 == 0:
return False
else:
return True
env = Environment()
# 給環境添加一個自定義的測試
env.tests["odd"] = is_odd
# 從字符串加載一個模板
temp = env.from_string("""
<p>
{% if 3 is odd %} <!-- 這是一個代碼塊,odd 不是一個普通字符串,而是 odd 測試函數; '3 is odd',相當於執行: is_odd(3) -->
<span>3 is odd</span>
{% else %}
<span>3 is not odd</span>
{% endif %}
</p>
""")
# 渲染模板,返回內容
content = temp.render()
print(content)
模板語法
注釋
在模板中,注釋使用 {# ... #}
表示:
<p>
{# this is comment,
and this is comment too.
#}
</p>
變量
變量在模板中的語法,用 {{
和 }}
括起來:
<p>
{{ name }} <!-- name 就是一個變量 -->
{{ obj.name }} <!-- 提取 obj 對象的屬性 -->
{{ obj["name"] }} <!-- 和 obj.name 等效 -->
</p>
針對上述模板,我們后台如下:
from jinja2 import Template
class Obj:
name = "wang"
temp = Template("""<p>
{{ name }} <!-- name 就是一個變量 -->
{{ obj.name }} <!-- 提取 obj 對象的屬性 -->
{{ obj["name"] }} <!-- 和 obj.name 等效 -->
</p>""")
gen = temp.render(obj=Obj(), name="Fake")
print(gen)
可以看出,我們可以像是使用普通的 python 語法一樣,在模板中提取屬性或者字典的值
消除空白
jinja2 會嚴格按照模板渲染,也就是說,如果你的模板中寫入了空格,或者在標簽之間換行了,渲染的內容也會原封不動的換行:
譬如:
from jinja2 import Template
class Obj:
name = "wang"
temp = Template("""<p> {# <p>后面有個換行符 #}
{{ name }} {{ obj.name }} {# 這兩個變量在一行 #}
{{ obj["name"] }} {# 后面也有換行符 #}
</p>""")
gen = temp.render(obj=Obj(), name="Fake")
print(gen)
會渲染成:
<p>
Fake wang
wang
</p>
如果你想將三個變量和標簽都顯示在一行,只能這樣:
temp = Template("""<p>{{ name }}{{ obj.name }}{{ obj["name"] }}</p>""")
會渲染成:
<p>Fakewangwang</p>
如果我們想要在模板中好看(模板中換行),但是實際渲染的效果要在一行,可以使用 -
符號。
譬如:
temp = Template("""<p>
{{- name -}}
{{- obj.name -}}
{{- obj["name"] -}}
</p>""")
會渲染成:
<p>Fakewangwang</p>
要點1:
-
可以不成對出現要點2:
-
和{{
或}}
之間沒有空格要點3:
{{-
代表消除變量之前的空白符,-}}
代表消除變量之后的空白符。要點4:
-
不僅可以用在{{ .. }}
上,也可以用在{% .. %}
上
轉義自身語法
如果你想要轉義 {{
本身,可以使用:
{{ '{{' }}
對於較大的段落,可以使用 raw
來將里面的內容全部當作原生字符
{% raw %}
<ul>
{% for item in seq %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% endraw %}
行語句
我們之前曾經提到過行語句。其實就是自定義一個符號,然后在模板中,所有以這個符號開頭的字符串,都會被當作語句來執行,譬如:
from jinja2 import Template
s = """
<p>
# for i in [
'a',
'b',
'c'
]:
{{ i }}
# endfor
</p>
"""
temp = Template(s, line_statement_prefix="#") # 以 # 作為語句定義符號
gen = temp.render()
print(gen)
上面的以
#
開頭的行,會作為語句執行,並且語句結尾可以加冒號,並且如果遇到[],()等,可以換行
模板 block
編程語言有 繼承 的概念,模板也可以有。我們可以寫一個基本模板,然后讓子模板繼承這個模板
基本模板:
mother.html
{% block title %} {# 聲明一個名為 title 的block #}
<p>This is title</p>
{% block content %} {# title 內部嵌套了一個名為 content 的block #}
{% endblock %}
{% endblock %}
{% block foot %}
<span>This is foot</span>
{% endblock %}
上面我們編寫了一個母版,它里面定義了很多的
block
, 每個 block 都有自己的名字(block的名字不能重復):{% block blok_name %}...{% endblock %}
,在 block 中,我們可以寫入一些 html 代碼,讓子模板繼承。各個 block 之間是可以嵌套的
注意每個 block 要有一個
{% endblock %}
子模板:son.html
{% extends "mother.html" %} {# 繼承母版 #}
{% block content %} {# 重寫某個block #}
<span>This is content, and the mother.html doesn't have this.</span>
{% endblock %}
{% block foot %}
{{ super() }} {# 繼承母版中的 foot block 的內容 #}
<span>New foot content</span>
{% endblock %}
{% extend %}
非常關鍵:它告訴模板要繼承另一個模板。並且這個標簽要放在模板的最上面。當然,繼承的標簽可以寫路徑:
{% extends "layout/default.html" %}
如果子模板沒有重寫母版中的某個block,則會默認使用母版中的block。
命名 block 的結束標簽
針對一個block,我們還可以在 endblock 時寫上它的名字,當然像上面的例子一樣不寫也行。
{% block sidebar %}
{% endblock sidebar %}
塊作用域
一個 block 的內容,無法和block外部的內容互動,它有自己的作用域。譬如,你想在一個 for 循環中循環某個block,而block卻無法獲取for循環的作用域:
{% extends "mother.html" %} {# 繼承母版 #}
{% for i in [1,2,3] %}
{% block foot scoped %} {# 后面加了一個 scoped, 就可以獲取 for 循環中的變量了 #}
{{ i }}
{% endblock %}
{% endfor %}
轉義字符串
from jinja2 import Template
temp = Template("""
{{ value|safe }} {# safe 是一個過濾器,不轉義字符 :<script>test</script> #}
{{ value|e }} {# e 過濾器,會轉義字符 :<script>test</script> #}
""")
x = temp.render(value="<script>test</script>")
print(x)
當然,你也可以自動轉義:
{% autoescape true %}
自動轉義在這塊文本中是開啟的。
{% endautoescape %}
{% autoescape false %}
自動轉義在這塊文本中是關閉的。
{% endautoescape %}
控制結構
For
for 可以用來遍歷序列,如列表,字典等。
{% for item in items %} <!-- 類似 for 這種代碼塊里面用到的變量,不需要額外加 {{}}, 譬如 items 就是一個變量 -->
{{ item }}
{% endfor %}
{% for key, value in my_dict.items() %}
{{ key }}
{% endfor %}
{% for i in [0, 1, 2] if not i %}
{{ i }}
{% else %} {# else 會在 for 循環沒有成功執行的情況下執行 #}
<span>List is empty</span>
{% endfor %}
查看循環的索引(循環到第幾個元素了)
from jinja2 import Template
temp = Template("""
{% for key, value in my_dict.items() %}
{{ key }}
{{ loop.index }} {# loop 是 jinjia2 的一個特殊對象,可以獲取當前循環的索引位置 #}
{% endfor %}
""")
x = temp.render(my_dict={"a":"b", "b":"a"})
print(x)
loop 的幾個特殊屬性:
變量 描述 loop.index 當前循環迭代的次數(從 1 開始) loop.index0 當前循環迭代的次數(從 0 開始) loop.revindex 到循環結束需要迭代的次數(從 1 開始) loop.revindex0 到循環結束需要迭代的次數(從 0 開始) loop.first 如果是第一次迭代,為 True 。 loop.last 如果是最后一次迭代,為 True 。 loop.length 序列中的項目數。 loop.cycle 在一串序列間期取值的輔助函數。見下面的解釋。
from jinja2 import Template
temp = Template("""
{% for i in [1,2,3] %}
{{ loop.cycle('A', "C", "B") }} {# loop.cycle會循環執行里面的A-C-B #}
{% endfor %}
""")
x = temp.render()
print(x)
If
和python中的if一樣:
{% if x %}
...
{% elif y %}
...
{% else %}
...
{% endif %}
跳出循環
continue, break
import jinja2.ext
from jinja2 import Template
temp = Template("""
{% for i in [1,2,3] %}
{% if i == 1 %}
{{ i }}
{% continue %}
{% else %}
{% break %}
{% endif %}
{% endfor %}
""", extensions=[jinja2.ext.loopcontrols]) # 要額外加載一個擴展,才能使用 continue 和 break
x = temp.render()
print(x)
宏
宏類似於函數。我們可以定義一個宏,然后定義宏的內容。以后我們可以像調用函數一樣調用宏。
{# 聲明了一個名為 input 的宏,它還帶有幾個參數 #}
{% macro input(name, value='', type='text', size=20) -%}
<input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}">
{%- endmacro %}
{# 調用宏,並傳參 #}
<p>{{ input('username') }}</p>
<p>{{ input('password', type='password') }}</p>
include
include 可以直接將另一個模板包含進當前模板,相當於將另一個模板直接嵌套進來。
{% include 'header.html' %}
{% include "sidebar.html" ignore missing %} {# ignore missing:如果找不到模板,可以忽略 #}
{% include ['special_sidebar.html', 'sidebar.html'] ignore missing %} {# 可以導入列表 #}
{% include "sidebar.html" ignore missing without context %} {# without context 可以不攜帶上下文 #}
{% include "sidebar.html" ignore missing with context %} {# with context 可以攜帶上下文 #}
什么是上下文:
上下文其實就是模板中定義的變量,我們渲染時會將上下文傳遞給模板:
template.render(context)
,而我們嵌套其他模板時,也可以將它們中的上下文包含進來,這樣在當前模板中也可以使用被嵌套模板中的上下文。
導入
假設現有:forms.html ,定義了兩個宏
{% macro input(name, value='', type='text') -%}
<input type="{{ type }}" value="{{ value|e }}" name="{{ name }}">
{%- endmacro %}
{%- macro textarea(name, value='', rows=10, cols=40) -%}
<textarea name="{{ name }}" rows="{{ rows }}" cols="{{ cols
}}">{{ value|e }}</textarea>
{%- endmacro %}
我們可以在其他模板中,像導入模塊一樣導入它:
{# 導入整個模塊 #}
{% import 'forms.html' as forms %}
<dl>
<dt>Username</dt>
<dd>{{ forms.input('username') }}</dd>
<dt>Password</dt>
<dd>{{ forms.input('password', type='password') }}</dd>
</dl>
<p>{{ forms.textarea('comment') }}</p>
{# 也可使用 from .. import .. as . 來單獨導入模板中的宏 #}
{% from 'forms.html' import input as input_field, textarea as txt_field %}
<dl>
<dt>Username</dt>
<dd>{{ input_field('username') }}</dd>
<dt>Password</dt>
<dd>{{ input_field('password', type='password') }}</dd>
</dl>
<p>{{ txt_field('comment') }}</p>
還可以導入時帶入上下文:
{% from 'forms.html' import input with context %}
表達式
在模板中,可以正常使用python中常見的表達式:
數學計算:
+
-
*
/
//
%
譬如:
{{ 1 + 2 }}
字面量:
dict
list
tuple
str
true
false
譬如:
<ul>
{% for href, caption in [('index.html', 'Index'), ('about.html', 'About'),
('downloads.html', 'Downloads')] %}
<li><a href="{{ href }}">{{ caption }}</a></li>
{% endfor %}
</ul>
或者:
{% for key in {‘dict’: ‘of’, ‘key’: ‘and’, ‘value’: ‘pairs’} %}
...
{% endfor %}
比較運算:
==
>=
<=
!=
>
<
邏輯運算:
and
or
not
is # 用於測試
in
| # 用於過濾器
() # 調用函數
./[] # 用來獲取對象的屬性
譬如:
{% if 1 in [1, 2, 3] and 2==2 %}
...
{% endif %}
內置過濾器和測試
內置過濾器
內置測試
callable() |
escaped() |
lessthan() |
number() |
string() |
---|---|---|---|---|
defined() |
even() |
lower() |
odd() |
undefined() |
divisibleby() |
greaterthan() |
mapping() |
sameas() |
upper() |
equalto() |
iterable() |
none() |
sequence() |