flask模板
在動態web程序中,視圖函數返回的HTML數據往往需要根據相應的變量(比如查詢參數)動態生成。
當HTML代碼保存到單獨的文件中時,我們沒法再使用字符串格式化或拼接字符串的當時在HTML代碼中插入變量,這時我們需要使用模板引擎(template engine)。借助模板引擎,我們可以再HTML文件中使用特殊的語法來標記變量,這類包含固定內容和動態部分的可重用文件稱為模板(template)。
模板引擎的作用就是讀取並執行模板中的特殊語法標記,並根據傳入的數據將變量替換為實際值,輸出最終的HTML頁面,這個過程被稱為渲染(rendering)。
Flask默認使用的模板引擎是jinja2,他是一個功能齊全的python模板引擎,輸了設置變量,還允許我們在模板中添加if判斷,執行for迭代,調整函數等,以各種方式 控制模板的輸出。
對於jinja2來說,模板可以是任何格式的純文本文件,比如HTML、XML、CSV等。
模板的基本用法
下面介紹一下如何使用jinja創建HTML模板,並在視圖函數中渲染模板,最終實現HTML響應的動態化
創建模板
假設我們需要編寫一個用戶的電影清單頁面,模板中需要顯示用戶信息以及用戶收藏的電影列表,包含電影的名字和年份。首先創建一些虛擬數據用於測試顯示效果:
user = {
'username': 'Grey Li',
'bio': 'A boy who loves movies and music.'
}
movies = [
{'name' : 'My Neighbor Totoro','year':'1988'},
{'name': 'Three Colours trilogy', 'year': '1993'},
{'name': 'Forrest Gump', 'year': '1994'},
{'name': 'Perfect Blue', 'year': '1997'},
{'name': 'The Matrix', 'year': '1999'},
{'name': 'Memento', 'year': '2000'},
{'name': 'The Bucket list', 'year': '2007'},
{'name': 'Black Swan', 'year': '2010'},
{'name': 'Gone Girl', 'year': '2014'},
{'name': 'CoCo', 'year': '2017'}
我們在templates目錄下創建一個watchlist.html作為模板文件,然后使用jinja2支持的語法在模板中操作這些變量。
template/watchlist.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ user.username }}'s Watchlist</title>
</head>
<body>
<a href = "{{ url_for('hello') }}">← Return</a>
<h2>{{ user.username }}</h2>
{% if user.bio %}
<i>{{ user.bio }}</i>
{% else %}
<i>This user has not provided a bio.</i>
{% endif %}
{# 下面是電影清單(這是注釋) #}
<h5>{{ user.username }}'s Watchlist ({{ movies[length] }}):</h5>
<ul>
{% for movie in movies %}
<li>{{ movie.name }} - {{ movie.year }}</li>
{% endfor %}
</ul>
</body>
</html>
在模板中使用的&larr;是HTML實體,HTML實體除了用來轉義HTML保留保留符號外,通常會被用來顯示不容易通過鍵盤輸入的字符。這里的←會顯示為左箭頭,另外,©用來顯示版權標志。
在模板中添加python語句和表達式時,需要使用特定的定界符把他們標示出來。watchlist.html中設計的模板語法,我們會在下面逐一介紹。首先,可以看到上面的代碼中看到Jinja2里常見的三種定界符:
常見的三種定界符
1、 語句
比如if判斷、for循環等:
{% … %}
2、 表達式
比如字符串、變量、函數調用等:
{{ … }}
3、 注釋
{# … #}
另外,在模板中,Jinja2支持使用“.”獲取變量的屬性,比如user字典中的username鍵值通過“.”獲取,即user.username,在效果上等同於user[‘username’]。
模板語法
利用jinja2這樣的模板引擎,我們可以將一部分的程序邏輯放到模板中去。簡單地說,我們可以在模板中使用python語句和表達式來操作數據的輸出。但需要注意的是,jinja2並不支持所有python語法。並且出於效率和代碼組織等方面的考慮,我們應該適度使用模板,僅把和輸出控制有關的邏輯操作放到模板中。
jinja2允許你在模板中使用大部分python對象,比如字符串、列表、字典、元組、整型、浮點型、布爾值。它支持基本的運算符號(+、-、*、/等)、比較符號(比如==、!=等)、邏輯符號(and、or、not和括號)以及in、is、None和布爾值(True、False)。
jinja2提供了多種控制結構來控制模板的輸出,其中for和if是最常用的兩種。jinja2里,語句使用{% … %}表示,尤其需要注意的是,在語句結束的地方,必須添加結束標簽:
{% if user.bio %}
<i>{{ user.bio }}</i>
{% else %}
<i>This user has not provided a bio.</i>
{% endif %}
在這個if語句里,如果user.bio已經定義,就渲染{%if user.bio%}和{%else%}之間的內容,否則就渲染{%else%}和{%endif%}之間的默認內容。末尾的{%endif%}用來聲明if語句的結束,這一行不能省略。
和python里一樣,for語句用來迭代一個序列:
<ul>
{% for movie in movies %}
<li>{{ movie.name }} - {{ movie.year }}</li>>
{% endfor %}
</ul>>
和其他語句一樣,你需要在for循環的結尾使用endfor標簽聲明for語句的結束。在for循環內,jinja2提供了多個特殊變量,常用的for循環變量如圖:

渲染模板
渲染一個模板,就是執行模板的代碼,並傳入所有在模板中使用的變量,渲染后的結果就是我們要返回給客戶端的HTML響應。在視圖函數中渲染模板時,我們並不直接使用jinja2提供的函數,而是使用flask提供的渲染函數render_template()
from flask import Flask,render_template
@app.route('/watchlist')
def watchlist():
return render_template('watchlist.html',user=user,movies = movies)
在render_template()函數中,我們首先傳入的模板的文件名作為參數。Flask會在程序根目錄下的templates文件夾里尋找模板文件,所以這里傳入的文件路徑是相對於templates根目錄的。除了模板文件路徑,我們還以關鍵字參數的形式傳入了模板中使用的變量值,以user為里:左邊的user表示傳入模板的變量名稱,右邊的user則是要傳入的對象。
除了render_template()函數,Flask還提供了一個render_template_string()函數用來渲染模板字符串。
其他類型的變量通過相同的方式傳入。傳入jinja2中的變量值可以是字符串、列表和字典,也可以是函數、類和類實例,這完全取決於你在視圖函數傳入的值。
例如:
<p>這時列表my_list的第一個元素:{{ my_list[0] }}</p>
<p>這是元祖my_tuple的第一個元素:{{ my_tutple[0] }}</p>
<p>這是字典my_dict的鍵為name的值:{{ my_dict[‘name’] }}</p>
<p>這是函數my_func的返回值:{{ my_func() }}</p>
<p>這是對象my_object調用某方法的返回值:{{ my_object.name() }}</p>
如果你想傳入函數在模板中調用,那么需要傳入函數對象本身,而不是函數調用(函數的返回值),所以近些出函數名稱即可。當把函數傳入模板后,我們可以像在python腳本中一樣通過添加括號的方式調用,而且你也可以在括號中傳入參數。
根據我們傳入的虛擬數據,render_template()渲染后返回的HTML數據如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Grey Li's Watchlist</title>
</head>
<body>
<a href = "/hello">← Return</a>
<h2>Grey Li</h2>
<i>A boy who loves movies and music.</i>
<h5>Grey Li's Watchlist ():</h5>
<ul>
<li>My Neighbor Totoro - 1988</li>
<li>Three Colours trilogy - 1993</li>
<li>Forrest Gump - 1994</li>
<li>Perfect Blue - 1997</li>
<li>The Matrix - 1999</li>
<li>Memento - 2000</li>
<li>The Bucket list - 2007</li>
<li>Black Swan - 2010</li>
<li>Gone Girl - 2014</li>
<li>CoCo - 2017</li>
</ul>>
</body>
</html>
在和渲染前的模板文件對比時你會發現,原模板中所有的jinja語句、表達式、注釋都會在執行后被移除,而所有的變量都會替換為對應的數據,訪問127.0.0.7:5000/watchlist即可以看到渲染后的頁面:

模板輔助工具
除了基本語法,jinja2還提供了許多方便的工具,這些工具可以讓你更方便的控制模板的輸出。為了方便測試,我們在示例程序的templates目錄下創建了一個根頁面模板index.html。返回主頁的index視圖和watchlist視圖類似:
from flask import render_template
@app.route('/')
def index():
return render_template('index.html')
上下文
模板上下文包含了很多變量,其中包含我們調用render_template()函數時手動傳入的變量以及flask默認傳入的變量。
除了渲染時傳入變量,也可以在模板中定義變量,使用set標簽:
{% set navigation = [(‘/’, ‘Home’), (‘/about’, ‘About’)]%}
你也可以將一部分模板數據定義為變量,使用set和endset標簽聲明開始和結束:
{% set navigation %}
<li><a href="/">Home</a></li>
<li><a>href="/about"></a></li>
{% endset %}
內置上下文變量
Flask在模板上下文中提供了一些內置變量,可以在模板中直接使用

自定義上下文
如果多個模板都需要使用同一變量,那么比起在多個視圖函數中重復傳入,更好的辦法是能夠設置一個模板全局變量。flask提供了一個app.context_processor裝飾器,可以用來注冊模板上下文處理函數,它可以幫我們完成統一傳入變量的工作。模板上下文處理函數需要返回一個包含變量鍵值對的字典
注冊模板上下文處理函數:
@app.context_processor
def inject_foo():
foo = 'I am foo.'
return dict(foo=foo)#等同於return {'foo': foo}
當我們調用render_remplate()函數渲染任意一個模板時,所有使用app.context_processor裝飾器注冊的模板上下文處理函數(包括flask內置的上下文處理函數)都會被執行,這些函數的返回值會被添加到模板中,因此我們可以在模板中直接使用foo變量。
和在render_remplate()函數中傳入變量類似,除了字符串、列表等數據結構,你也可以傳入函數、類或類實例。
除了使用app.context_processor裝飾器,也可以直接將其作為方法調用,傳入模板上下文處理函數。
def inject_foo():
foo = "I am foo."
return dict(foo=foo)
app.context_processor(inject_foo)
使用lambda可以簡化為:
app.context_processor(lambda:dict(foo='I am foo.'))
全局對象
全局對象是指在所有的模板中都可以直接使用的對象,包括在模板中導入的模板
設置全局函數
jinja2在模板中默認提供了一些全局函數,常用的三個函數:

除了jinja2內置的全局函數,flask也在模板中內置了兩個全局函數

flask除了把g、session、config、request對象注冊上下文變量,也將他們設置為全局變量,因此可以全局使用。
url_for()用來獲取URL,用法和在python腳本相同。在前面給出的watchlist.html模板中,用來返回主頁的鏈接直接寫出。在實際的代碼中,這個URL使用url_for()生成,傳入index視圖的端點(index):
<a href=”{{ url_for(‘index’) }}”>← Return</a>
自定義全局函數
除了使用app.context_processor注冊模板上下文處理函數來傳入變量,我們也可以使用app.template_global裝飾器直接將函數注冊為模板全局函數。比如,下面例子把bar()函數注冊為模板全局函數。
@app.template_global()
def bar():
return 'I am bar.'
默認使用函數的原名稱傳入模板,在app.template_global()裝飾器中使用name參數可以指定一個自定義名稱。app.template_global()僅能用於注冊全局函數。
你可以直接使用app.add_template_global()方法注冊自定義全局函數,傳入函數對象和可選的自定義名稱(name),比如app.add_template_global(your_global_function)。
過濾器
在jinja2中,過濾器(filter)是一些可以用來修改和過濾變量值的特殊函數,過濾器和變量用一個數顯(管道符號)隔開,需要參數的過濾器可以像函數一樣使用括號傳遞。
下面是一個堆name變量使用title過濾器的例子:
{{ name|title }}
這會將name變量的值標題化,相當於在python里調用name.title()。再比如,在前面的示例模板watchlist.html中使用length獲取movies列表的長度,類似於在python中調用len(movies):
{{ movies|length }}
另一種用法是將過濾器作用於一部分模板數據,使用filter標簽和endfilter標簽聲明開始和結束。比如,下面是用upper過濾器講一段文字轉換為大寫:
{% filter upper %}
This text becomes uppercase.
{% endfilter % }
內置過濾器
jinja2提供了許多內置過濾器,常用的過濾器有:


在使用過濾器時,列表中過濾器函數的第一個參數表示被過濾的變量值(value)或字符串(s),即豎線符號左側的值,其他的參數可以通過添加括號傳入。
另外,過濾器可以疊加使用,下面的示例為name變量設置默認值,並將其標題化:
<h1>Hello, {{ name|default('陌生人')|title }}!</h1>
在之前學習XSS攻擊的防范措施時,主要是對用戶輸入的文本進行轉義,根據flask的設置,jinja2會自動對模板中的變量進行轉義,所以我們不用手動使用escape過濾器或調用escape()函數對變量進行轉義。
默認的自動公開其轉義僅針對.html、.htm、.xml以及.xtml后綴的文件,用於渲染模板字符串的render_template_string()函數也會對所有傳入的字符串進行轉義。
在確保變量值安全的情況下,如果你想避免轉義,將變量作為HTML解析,可以對變量使用safe過濾器:
{{ santitized_text|safe }}
另一種將文本記為安全的方法是在渲染前將變量轉換為markup對象:
from fflask import Markup
@app.route('/hello6')
def hello6():
text = Markup('<h>Hello, Flask!</h1>')
return render_template('index.html',text=text)
這時在模板中可以直接使用{{ text }}
絕對不要直接對用戶輸入的內容使用safe過濾器,否則容易被植入惡意代碼,導致XSS攻擊。
自定義過濾器
如果內置的過濾器不能滿足你的需要,還可以添加自定義過濾器。使用app.template_filter()裝飾器可以注冊自定義過濾器
例子:
from flask import Markup
@app.template_filter()
def musical(s):
return s + Markup(' ♫')
和注冊全局函數類似,你可以在app.template_filter()中使用name關鍵字設置過濾器的名稱,默認會使用函數名稱。過濾器函數需要接收被處理的值作為輸入,返回處理后的值。過濾器函數接收s作為過濾的變量值,返回處理后的值。我們創建的musical過濾器會在被過濾的變量字符后邊添加一個音符圖標,因為音符通過HTML實體♫表示,我們使用Markup類將它標記為圈圈字符。在使用時和其他過濾器用法相同:
{{ name|musical }}
你可直接一個app.add_template_filter()方法注冊自定義過濾器,傳入函數對象和可選的自定義名稱(name),比如app.add_template_filter(your_filer_function)。
測試器
在jinja2中,測試器(Test)是一些用來測試變量或表達式,返回布爾值(True或False)的特殊函數。比如,number測試器用來判斷一個變量或表達式是否是數字,我們使用is連接變量和測試器:
{% if age is number %}
{{ age * 365 }}
{% else %}
無效的數字
{% endif %}
內置測試器
jinja2內置了許多測試器,常用的測試器及用法說明如下表:

在使用測試器時,is的左側是測試器函數的第一個參數(value),其他參數可以添加括號傳入,也可以在右側使用空格連接,以sames為例:
{% if foo is sameas(bar) %}…
等同於:
{% if foo is samesas bar %}…
自定義測試器
和過濾器類似,我們可以使用flask提供的app.template_test()裝飾器來注冊一個自定義測試器。下例中,我們創建一個baz過濾器,用來驗證被測值是否為baz:
@app.template_test()
def baz(n):
if n == 'baz':
return True
return False
測試器的名稱默認為函數名稱,你可以在app.template_test()中使用name關鍵字指定自定義名稱。測試器函數需要接收被測試的值作為輸入,返回布爾值。
你可以直接使用app.add_template_test()方法注冊自定義測試器,傳入函數對象和可選的自定義名稱(name),比如app.add_template_test(your_test_function)。
模板環境對象
在jinja2中,渲染行為由jinja2.Environment類控制,所有的配置選項、上下文變量、全局函數、過濾器和測試器都存儲在Environment實例上。當於flask結合后,我們並不單獨創建Environment對象,而是使用flask創建的Environment對象,它存儲在app.jinja_env屬性上。
在程序中,我們可以使用app.jinja_env更改jinja2設置。比如,可以自定義所有的定界符。下面使用variable_start_string和variable_end_string分別自定義變量定界符的開始和結束符號:
app = Flask(__name__)
app.jinja_env.variable_start_string = ’[[’
app.jinja_env.variable_end_string = ‘}}’
在實際開發中,如果修改jinja2的定界符,那么需要注意與擴展提供模板的兼容問題,一般不建議修改。
模板環境中的全局函數、過濾器和測試器分別存儲在Environment對象的globals、filters和tests屬性中,這三個屬性都是字典對象。除了使用flask提供的裝飾器和方法注冊自定義函數,我們也可以直接操作這三個字典添加相應的函數或變量,這通過向對應的字典屬性中添加一個鍵值對實現,把名稱作為鍵傳入模板,對應的函數對象或變量作為值。下面是幾個簡單的例子。
添加自定義全局對象
使用app.jinja_env.globals分別向模板中添加全局函數bar和局部變量foo:
def bar():
return "I am bar"
foo = "i am foo"
app.jinja_env.globals['bar'] = bar
app.jinja_env.globals['foo'] = foo
添加自定義過濾器
使用app.jinja_env.filters向模板中添加自定義過濾器smiling:
def smiling(s):
return s + ' :'
app.jinja_env.filters['smiling'] = smiling
添加自定義測試器
使用app.jinja_env.tests向模板中添加自定義測試器baz
def baz(n):
if n == 'baz':
return True
return False
app.jinja_env.tests['baz'] = baz
