flask模板的基本用法(定界符、模板語法、渲染模板),模板輔助工具(上下文、全局對象、過濾器、測試器、模板環境對象) --

flask模板

在動態web程序中,視圖函數返回的HTML數據每每須要根據相應的變量(好比查詢參數)動態生成。html

當HTML代碼保存到單獨的文件中時,咱們無法再使用字符串格式化或拼接字符串的當時在HTML代碼中插入變量,這時咱們須要使用模板引擎(template engine)。藉助模板引擎,咱們能夠再HTML文件中使用特殊的語法來標記變量,這類包含固定內容和動態部分的可重用文件稱爲模板(template)。python

模板引擎的做用就是讀取並執行模板中的特殊語法標記,並根據傳入的數據將變量替換爲實際值,輸出最終的HTML頁面,這個過程被稱爲渲染(rendering)。web

Flask默認使用的模板引擎是jinja2,他是一個功能齊全的python模板引擎,輸了設置變量,還容許咱們在模板中添加if判斷,執行for迭代,調整函數等,以各類方式 控制模板的輸出。flask

對於jinja2來講,模板能夠是任何格式的純文本文件,好比HTML、XML、CSV等。安全

模板的基本用法

下面介紹一下如何使用jinja建立HTML模板,並在視圖函數中渲染模板,最終實現HTML響應的動態化session

建立模板

假設咱們須要編寫一個用戶的電影清單頁面,模板中須要顯示用戶信息以及用戶收藏的電影列表,包含電影的名字和年份。首先建立一些虛擬數據用於測試顯示效果:數據結構

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支持的語法在模板中操做這些變量。app

template/watchlist.html:ide

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ user.username }}'s Watchlist</title>
</head>
<body>
<a href = "{{ url_for('hello') }}">&larr; 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保留保留符號外,一般會被用來顯示不容易經過鍵盤輸入的字符。這裏的&larr;會顯示爲左箭頭,另外,&copy;用來顯示版權標誌。函數

在模板中添加python語句和表達式時,須要使用特定的定界符把他們標示出來。watchlist.html中設計的模板語法,咱們會在下面逐一介紹。首先,能夠看到上面的代碼中看到Jinja2裏常見的三種定界符:

常見的三種定界符

 

一、  語句

好比if判斷、for循環等:

{% … %}

二、  表達式

好比字符串、變量、函數調用等:

{{ … }}

三、  註釋

{# … #}

另外,在模板中,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">&larr; 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’) }}」>&larr; 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(' &#9835;')

和註冊全局函數相似,你能夠在app.template_filter()中使用name關鍵字設置過濾器的名稱,默認會使用函數名稱。過濾器函數須要接收被處理的值做爲輸入,返回處理後的值。過濾器函數接收s做爲過濾的變量值,返回處理後的值。咱們建立的musical過濾器會在被過濾的變量字符後邊添加一個音符圖標,由於音符經過HTML實體&#9835;表示,咱們使用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
相關文章
相關標籤/搜索