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


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') }}">&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里常見的三種定界符:

常見的三種定界符

 

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">&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

 


免責聲明!

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



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