jinja2 簡單整理


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,  # &lt;p&gt;Hello world!&lt;/p&gt;
	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 過濾器,會轉義字符 :&lt;script&gt;test&lt;/script&gt; #}
""")

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 %}


內置過濾器和測試

內置過濾器

abs() float() lower() select() truncate()
attr() forceescape() map() selectattr() upper()
batch() format() pprint() slice() urlencode()
capitalize() groupby() random() sort() urlize()
center() indent() reject() string() wordcount()
default() int() rejectattr() striptags() wordwrap()
dictsort() join() replace() sum() xmlattr()
escape() last() reverse() title()
filesizeformat() length() round() tojson()
first() list() safe() trim()

內置測試

callable() escaped() lessthan() number() string()
defined() even() lower() odd() undefined()
divisibleby() greaterthan() mapping() sameas() upper()
equalto() iterable() none() sequence()


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM