日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Flask入门系列(转载)

發(fā)布時(shí)間:2023/12/10 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Flask入门系列(转载) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一、入門系列:

Flask入門系列(一)–Hello World

項(xiàng)目開發(fā)中,經(jīng)常要寫一些小系統(tǒng)來輔助,比如監(jiān)控系統(tǒng),配置系統(tǒng)等等。用傳統(tǒng)的Java寫,太笨重了,連PHP都嫌麻煩。一直在尋找一個(gè)輕量級的后臺框架,學(xué)習(xí)成本低,維護(hù)簡單。發(fā)現(xiàn)Flask后,我立馬被它的輕巧所吸引,它充分發(fā)揮了Python語言的優(yōu)雅和輕便,連Django這樣強(qiáng)大的框架在它面前都覺得繁瑣。可以說簡單就是美。這里我們不討論到底哪個(gè)框架語言更好,只是從簡單這個(gè)角度出發(fā),Flask絕對是佼佼者。這一系列文章就會給大家展示Flask的輕巧之美。

系列文章

  • Flask入門系列(一)–Hello World
  • Flask入門系列(二)–路由
  • Flask入門系列(三)–模板
  • Flask入門系列(四)–請求,響應(yīng)及會話
  • Flask入門系列(五)–錯(cuò)誤處理及消息閃現(xiàn)
  • Flask入門系列(六)–數(shù)據(jù)庫集成

Hello World

程序員的經(jīng)典學(xué)習(xí)方法,從Hello World開始。不要忘了,先安裝python, pip,然后運(yùn)行”pip install Flask”,環(huán)境就裝好了。當(dāng)然本人還是強(qiáng)烈建議使用virtualenv來安裝環(huán)境。細(xì)節(jié)就不多說了,讓我們寫個(gè)Hello World吧:

1

2

3

4

5

6

7

8

9

from flask import Flask

app = Flask(__name__)

?

@app.route('/')

def index():

????return '<h1>Hello World</h1>'

?

if __name__ == '__main__':

????app.run()

一個(gè)Web應(yīng)用的代碼就寫完了,對,就是這么簡單!保存為”hello.py”,打開控制臺,到該文件目錄下,運(yùn)行

$ python hello.py

看到”* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)”字樣后,就說明服務(wù)器啟動完成。打開你的瀏覽器,訪問”http://127.0.0.1:5000/”,一個(gè)碩大的”Hello World”映入眼簾:)。

簡單解釋下這段代碼

  • 首先引入了Flask包,并創(chuàng)建一個(gè)Web應(yīng)用的實(shí)例”app”
  • 1

    2

    from flask import Flask

    app = Flask(__name__)

    這里給的實(shí)例名稱就是這個(gè)python模塊名。

  • 定義路由規(guī)則
  • 1

    @app.route('/')

    這個(gè)函數(shù)級別的注解指明了當(dāng)?shù)刂肥歉窂綍r(shí),就調(diào)用下面的函數(shù)。可以定義多個(gè)路由規(guī)則,會在下篇文章里詳細(xì)介紹。說的高大上些,這里就是MVC中的Contoller。

  • 處理請求
  • 1

    2

    def index():

    ????return '<h1>Hello World</h1>'

    當(dāng)請求的地址符合路由規(guī)則時(shí),就會進(jìn)入該函數(shù)。可以說,這里是MVC的Model層。你可以在里面獲取請求的request對象,返回的內(nèi)容就是response。本例中的response就是大標(biāo)題”Hello World”。

  • 啟動Web服務(wù)器
  • 1

    2

    if __name__ == '__main__':

    ????app.run()

    當(dāng)本文件為程序入口(也就是用python命令直接執(zhí)行本文件)時(shí),就會通過”app.run()”啟動Web服務(wù)器。如果不是程序入口,那么該文件就是一個(gè)模塊。Web服務(wù)器會默認(rèn)監(jiān)聽本地的5000端口,但不支持遠(yuǎn)程訪問。如果你想支持遠(yuǎn)程,需要在”run()”方法傳入”host=0.0.0.0″,想改變監(jiān)聽端口的話,傳入”port=端口號”,你還可以設(shè)置調(diào)試模式。具體例子如下:

    1

    2

    if __name__ == '__main__':

    ????app.run(host='0.0.0.0', port=8888, debug=True)

    注意,Flask自帶的Web服務(wù)器主要還是給開發(fā)人員調(diào)試用的,在生產(chǎn)環(huán)境中,你最好是通過WSGI將Flask工程部署到類似Apache或Nginx的服務(wù)器上。

    本例中的代碼可以在這里下載。

    Flask入門系列(二)–路由

    上一篇中,我們用Flask寫了一個(gè)Hello World程序,讓大家領(lǐng)略到了Flask的簡潔輕便。從這篇開始我們將對Flask框架的各功能作更詳細(xì)的介紹,我們首先從路由(Route)開始。

    系列文章

    • Flask入門系列(一)–Hello World
    • Flask入門系列(二)–路由
    • Flask入門系列(三)–模板
    • Flask入門系列(四)–請求,響應(yīng)及會話
    • Flask入門系列(五)–錯(cuò)誤處理及消息閃現(xiàn)
    • Flask入門系列(六)–數(shù)據(jù)庫集成

    路由

    從Hello World中,我們了解到URL的路由可以直接寫在其要執(zhí)行的函數(shù)上。有人會質(zhì)疑,這樣不是把Model和Controller綁在一起了嗎?的確,如果你想靈活的配置Model和Controller,這樣是不方便,但是對于輕量級系統(tǒng)來說,靈活配置意義不大,反而寫在一塊更利于維護(hù)。Flask路由規(guī)則都是基于Werkzeug的路由模塊的,它還提供了很多強(qiáng)大的功能。

    帶參數(shù)的路由

    讓我們在上一篇Hello World的基礎(chǔ)上,加上下面的函數(shù)。并運(yùn)行程序。

    1

    2

    3

    @app.route('/hello/<name>')

    def hello(name):

    ????return 'Hello %s' % name

    當(dāng)你在瀏覽器的地址欄中輸入”http://localhost:5000/hello/man”,你將在頁面上看到”Hello man”的字樣。URL路徑中”/hello/”后面的參數(shù)被作為”hello()”函數(shù)的”name”參數(shù)傳了進(jìn)來。
    你還可以在URL參數(shù)前添加轉(zhuǎn)換器來轉(zhuǎn)換參數(shù)類型,我們再來加個(gè)函數(shù):

    1

    2

    3

    @app.route('/user/<int:user_id>')

    def get_user(user_id):

    ????return 'User ID: %d' % user_id

    試下訪問”http://localhost:5000/user/man”,你會看到404錯(cuò)誤。但是試下”http://localhost:5000/user/123″,頁面上就會有”User ID: 123″顯示出來。參數(shù)類型轉(zhuǎn)換器”int:”幫你控制好了傳入?yún)?shù)的類型只能是整形。目前支持的參數(shù)類型轉(zhuǎn)換器有:

    類型轉(zhuǎn)換器

    作用

    缺省

    字符型,但不能有斜杠

    int:

    整型

    float:

    浮點(diǎn)型

    path:

    字符型,可有斜杠

    另外,大家有沒有注意到,Flask自帶的Web服務(wù)器支持熱部署。當(dāng)你修改好文件并保存后,Web服務(wù)器自動部署完畢,你無需重新運(yùn)行程序。

    多URL的路由

    一個(gè)函數(shù)上可以設(shè)施多個(gè)URL路由規(guī)則

    1

    2

    3

    4

    5

    6

    7

    @app.route('/')

    @app.route('/hello')

    @app.route('/hello/<name>')

    def hello(name=None):

    ????if name is None:

    ????????name = 'World'

    ????return 'Hello %s' % name

    這個(gè)例子接受三種URL規(guī)則,”/”和”/hello”都不帶參數(shù),函數(shù)參數(shù)”name”值將為空,頁面顯示”Hello World”;”/hello/“帶參數(shù),頁面會顯示參數(shù)”name”的值,效果與上面第一個(gè)例子相同。

    HTTP請求方法設(shè)置

    HTTP請求方法常用的有Get, Post, Put, Delete。不熟悉的朋友們可以去度娘查下。Flask路由規(guī)則也可以設(shè)置請求方法。

    1

    2

    3

    4

    5

    6

    7

    8

    from flask import request

    ?

    @app.route('/login', methods=['GET', 'POST'])

    def login():

    ????if request.method == 'POST':

    ????????return 'This is a POST request'

    ????else:

    ????????return 'This is a GET request'

    當(dāng)你請求地址”http://localhost:5000/login”,”GET”和”POST”請求會返回不同的內(nèi)容,其他請求方法則會返回405錯(cuò)誤。有沒有覺得用Flask來實(shí)現(xiàn)Restful風(fēng)格很方便啊?

    URL構(gòu)建方法

    Flask提供了”url_for()”方法來快速獲取及構(gòu)建URL,方法的第一個(gè)參數(shù)指向函數(shù)名(加過”@app.route”注解的函數(shù)),后續(xù)的參數(shù)對應(yīng)于要構(gòu)建的URL變量。下面是幾個(gè)例子:

    1

    2

    3

    4

    url_for('login')????# 返回/login

    url_for('login', id='1')????# 將id作為URL參數(shù),返回/login?id=1

    url_for('hello', name='man')????# 適配hello函數(shù)的name參數(shù),返回/hello/man

    url_for('static', filename='style.css')????# 靜態(tài)文件地址,返回/static/style.css

    靜態(tài)文件位置

    一個(gè)Web應(yīng)用的靜態(tài)文件包括了JS, CSS, 圖片等,Flask的風(fēng)格是將所有靜態(tài)文件放在”static”子目錄下。并且在代碼或模板(下篇會介紹)中,使用”url_for(‘static’)”來獲取靜態(tài)文件目錄。上小節(jié)中第四個(gè)的例子就是通過”url_for()”函數(shù)獲取”static”目錄下的指定文件。如果你想改變這個(gè)靜態(tài)目錄的位置,你可以在創(chuàng)建應(yīng)用時(shí),指定”static_folder”參數(shù)。

    1

    app = Flask(__name__, static_folder='files')

    本例中的代碼可以在這里下載。

    Flask入門系列(三)–模板

    在第一篇中,我們講到了Flask中的Controller和Model,但是一個(gè)完整的MVC,沒有View怎么行?前端代碼如果都靠后臺拼接而成,就太麻煩了。本篇,我們就介紹下Flask中的View,即模板。

    系列文章

    • Flask入門系列(一)–Hello World
    • Flask入門系列(二)–路由
    • Flask入門系列(三)–模板
    • Flask入門系列(四)–請求,響應(yīng)及會話
    • Flask入門系列(五)–錯(cuò)誤處理及消息閃現(xiàn)
    • Flask入門系列(六)–數(shù)據(jù)庫集成

    模板

    Flask的模板功能是基于Jinja2模板引擎實(shí)現(xiàn)的。讓我們來實(shí)現(xiàn)一個(gè)例子吧。創(chuàng)建一個(gè)新的Flask運(yùn)行文件(你應(yīng)該不會忘了怎么寫吧),代碼如下:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    from flask import Flask

    from flask import render_template

    ?

    app = Flask(__name__)

    ?

    @app.route('/hello')

    @app.route('/hello/<name>')

    def hello(name=None):

    ????return render_template('hello.html', name=name)

    ?

    if __name__ == '__main__':

    ????app.run(host='0.0.0.0', debug=True)

    這段代碼同上一篇的多URL路由的例子非常相似,區(qū)別就是”hello()”函數(shù)并不是直接返回字符串,而是調(diào)用了”render_template()”方法來渲染模板。方法的第一個(gè)參數(shù)”hello.html”指向你想渲染的模板名稱,第二個(gè)參數(shù)”name”是你要傳到模板去的變量,變量可以傳多個(gè)。

    那么這個(gè)模板”hello.html”在哪兒呢,變量參數(shù)又該怎么用呢?別急,接下來我們創(chuàng)建模板文件。在當(dāng)前目錄下,創(chuàng)建一個(gè)子目錄”templates”(注意,一定要使用這個(gè)名字)。然后在”templates”目錄下創(chuàng)建文件”hello.html”,內(nèi)容如下:

    1

    2

    3

    4

    5

    6

    7

    <!doctype html>

    <title>Hello Sample</title>

    {% if name %}

    ??<h1>Hello {{ name }}!</h1>

    {% else %}

    ??<h1>Hello World!</h1>

    {% endif %}

    這段代碼是不是很像HTML?接觸過其他模板引擎的朋友們肯定立馬秒懂了這段代碼。它就是一個(gè)HTML模板,根據(jù)”name”變量的值,顯示不同的內(nèi)容。變量或表達(dá)式由”{{ }}”修飾,而控制語句由”{% %}”修飾,其他的代碼,就是我們常見的HTML。

    讓我們打開瀏覽器,輸入”http://localhost:5000/hello/man”,頁面上即顯示大標(biāo)題”Hello man!”。我們再看下頁面源代碼

    1

    2

    3

    4

    <!doctype html>

    <title>Hello from Flask</title>

    ?

    ??<h1>Hello man!</h1>

    果然,模板代碼進(jìn)入了”Hello {{ name }}!”分支,而且變量”{{ name }}”被替換為了”man”。Jinja2的模板引擎還有更多強(qiáng)大的功能,包括for循環(huán),過濾器等。模板里也可以直接訪問內(nèi)置對象如request, session等。對于Jinja2的細(xì)節(jié),感興趣的朋友們可以自己去查查。

    模板繼承

    一般我們的網(wǎng)站雖然頁面多,但是很多部分是重用的,比如頁首,頁腳,導(dǎo)航欄之類的。對于每個(gè)頁面,都要寫這些代碼,很麻煩。Flask的Jinja2模板支持模板繼承功能,省去了這些重復(fù)代碼。讓我們基于上面的例子,在”templates”目錄下,創(chuàng)建一個(gè)名為”layout.html”的模板:

    1

    2

    3

    4

    5

    6

    7

    <!doctype html>

    <title>Hello Sample</title>

    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">

    <div class="page">

    ????{% block body %}

    ????{% endblock %}

    </div>

    再修改之前的”hello.html”,把原來的代碼定義在”block body”中,并在代碼一開始”繼承”上面的”layout.html”:

    1

    2

    3

    4

    5

    6

    7

    8

    {% extends "layout.html" %}

    {% block body %}

    {% if name %}

    ??<h1>Hello {{ name }}!</h1>

    {% else %}

    ??<h1>Hello World!</h1>

    {% endif %}

    {% endblock %}

    打開瀏覽器,再看下”http://localhost:5000/hello/man”頁面的源碼。

    1

    2

    3

    4

    5

    6

    7

    8

    <!doctype html>

    <title>Hello Sample</title>

    <link rel="stylesheet" type="text/css" href="/static/style.css">

    <div class="page">

    ????

    ??<h1>Hello man!</h1>

    ?

    </div>

    你會發(fā)現(xiàn),雖然”render_template()”加載了”hello.html”模板,但是”layout.html”的內(nèi)容也一起被加載了。而且”hello.html”中的內(nèi)容被放置在”layout.html”中”{% block body %}”的位置上。形象的說,就是”hello.html”繼承了”layout.html”。

    HTML自動轉(zhuǎn)義

    我們看下下面的代碼:

    1

    2

    3

    @app.route('/')

    def index():

    ????return '<div>Hello %s</div>' % '<em>Flask</em>'

    打開頁面,你會看到”Hello Flask”字樣,而且”Flask”是斜體的,因?yàn)槲覀兗恿恕眅m”標(biāo)簽。但有時(shí)我們并不想讓這些HTML標(biāo)簽自動轉(zhuǎn)義,特別是傳遞表單參數(shù)時(shí),很容易導(dǎo)致HTML注入的漏洞。我們把上面的代碼改下,引入”Markup”類:

    1

    2

    3

    4

    5

    6

    7

    from flask import Flask, Markup

    ?

    app = Flask(__name__)

    ?

    @app.route('/')

    def index():

    ????return Markup('<div>Hello %s</div>') % '<em>Flask</em>'

    再次打開頁面,”em”標(biāo)簽顯示在頁面上了。Markup還有很多方法,比如”escape()”呈現(xiàn)HTML標(biāo)簽, “striptags()”去除HTML標(biāo)簽。這里就不一一列舉了。

    我們會在Flask進(jìn)階系列里對模板功能作更詳細(xì)的介紹。

    本文中的示例代碼可以在這里下載。

    Flask入門系列(四)–請求,響應(yīng)及會話

    一個(gè)完整的HTTP請求,包括了客戶端的請求Request,服務(wù)器端的響應(yīng)Response,會話Session等。一個(gè)基本的Web框架一定會提供內(nèi)建的對象來訪問這些信息,Flask當(dāng)然也不例外。我們來看看在Flask中該怎么使用這些內(nèi)建對象。

    系列文章

    • Flask入門系列(一)–Hello World
    • Flask入門系列(二)–路由
    • Flask入門系列(三)–模板
    • Flask入門系列(四)–請求,響應(yīng)及會話
    • Flask入門系列(五)–錯(cuò)誤處理及消息閃現(xiàn)
    • Flask入門系列(六)–數(shù)據(jù)庫集成

    Flask內(nèi)建對象

    Flask提供的內(nèi)建對象常用的有request, session, g,通過request,你還可以獲取cookie對象。這些對象不但可以在請求函數(shù)中使用,在模板中也可以使用。

    請求對象request

    引入flask包中的request對象,就可以直接在請求函數(shù)中直接使用該對象了。讓我們改進(jìn)下第二篇中的login方法:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    from flask import request

    ?

    @app.route('/login', methods=['POST', 'GET'])

    def login():

    ????if request.method == 'POST':

    ????????if request.form['user'] == 'admin':

    ????????????return 'Admin login successfully!'

    ????????else:

    ????????????return 'No such user!'

    ????title = request.args.get('title', 'Default')

    ????return render_template('login.html', title=title)

    在第三篇的templates目錄下,添加”login.html”文件

    1

    2

    3

    4

    5

    6

    7

    {% extends "layout.html" %}

    {% block body %}

    <form name="login" action="/login" method="post">

    ????Hello {{ title }}, please login by:

    ????<input type="text" name="user" />

    </form>

    {% endblock %}

    執(zhí)行上面的例子,結(jié)果我就不多描述了。簡單解釋下,request中”method”變量可以獲取當(dāng)前請求的方法,即”GET”, “POST”, “DELETE”, “PUT”等;”form”變量是一個(gè)字典,可以獲取Post請求表單中的內(nèi)容,在上例中,如果提交的表單中不存在”user”項(xiàng),則會返回一個(gè)”KeyError”,你可以不捕獲,頁面會返回400錯(cuò)誤(想避免拋出這”KeyError”,你可以用request.form.get(“user”)來替代)。而”request.args.get()”方法則可以獲取Get請求URL中的參數(shù),該函數(shù)的第二個(gè)參數(shù)是默認(rèn)值,當(dāng)URL參數(shù)不存在時(shí),則返回默認(rèn)值。request的詳細(xì)使用可參閱Flask的官方API文檔。

    會話對象session

    會話可以用來保存當(dāng)前請求的一些狀態(tài),以便于在請求之前共享信息。我們將上面的python代碼改動下:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    from flask import request, session

    ?

    @app.route('/login', methods=['POST', 'GET'])

    def login():

    ????if request.method == 'POST':

    ????????if request.form['user'] == 'admin':

    ????????????session['user'] = request.form['user']

    ????????????return 'Admin login successfully!'

    ????????else:

    ????????????return 'No such user!'

    ????if 'user' in session:

    ????????return 'Hello %s!' % session['user']

    ????else:

    ????????title = request.args.get('title', 'Default')

    ????????return render_template('login.html', title=title)

    ?

    app.secret_key = '123456'

    你可以看到,”admin”登陸成功后,再打開”login”頁面就不會出現(xiàn)表單了。session對象的操作就跟一個(gè)字典一樣。特別提醒,使用session時(shí)一定要設(shè)置一個(gè)密鑰”app.secret_key”,如上例。不然你會得到一個(gè)運(yùn)行時(shí)錯(cuò)誤,內(nèi)容大致是”RuntimeError: the session is unavailable because no secret key was set”。密鑰要盡量復(fù)雜,最好使用一個(gè)隨機(jī)數(shù),這樣不會有重復(fù),上面的例子不是一個(gè)好密鑰。

    我們順便寫個(gè)登出的方法,估計(jì)我不放例子,大家也都猜到怎么寫,就是清除字典里的鍵值:

    1

    2

    3

    4

    5

    6

    from flask import request, session, redirect, url_for

    ?

    @app.route('/logout')

    def logout():

    ????session.pop('user', None)

    ????return redirect(url_for('login'))

    關(guān)于”redirect”方法,我們會在下一篇介紹。

    構(gòu)建響應(yīng)

    在之前的例子中,請求的響應(yīng)我們都是直接返回字符串內(nèi)容,或者通過模板來構(gòu)建響應(yīng)內(nèi)容然后返回。其實(shí)我們也可以先構(gòu)建響應(yīng)對象,設(shè)置一些參數(shù)(比如響應(yīng)頭)后,再將其返回。修改下上例中的Get請求部分:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    from flask import request, session, make_response

    ?

    @app.route('/login', methods=['POST', 'GET'])

    def login():

    ????if request.method == 'POST':

    ????????...

    ????if 'user' in session:

    ????????...

    ????else:

    ????????title = request.args.get('title', 'Default')

    ????????response = make_response(render_template('login.html', title=title), 200)

    ????????response.headers['key'] = 'value'

    ????????return response

    打開瀏覽器調(diào)試,在Get請求用戶未登錄狀態(tài)下,你會看到響應(yīng)頭中有一個(gè)”key”項(xiàng)。”make_response”方法就是用來構(gòu)建response對象的,第二個(gè)參數(shù)代表響應(yīng)狀態(tài)碼,缺省就是200。response對象的詳細(xì)使用可參閱Flask的官方API文檔。

    Cookie的使用

    提到了Session,當(dāng)然也要介紹Cookie嘍,畢竟沒有Cookie,Session就根本沒法用(不知道為什么?查查去)。Flask中使用Cookie也很簡單:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    from flask import request, session, make_response

    import time

    ?

    @app.route('/login', methods=['POST', 'GET'])

    def login():

    ????response = None

    ????if request.method == 'POST':

    ????????if request.form['user'] == 'admin':

    ????????????session['user'] = request.form['user']

    ????????????response = make_response('Admin login successfully!')

    ????????????response.set_cookie('login_time', time.strftime('%Y-%m-%d %H:%M:%S'))

    ????????...

    ????else:

    ????????if 'user' in session:

    ????????????login_time = request.cookies.get('login_time')

    ????????????response = make_response('Hello %s, you logged in on %s' % (session['user'], login_time))

    ????????...

    ?

    ????return response

    例子越來越長了,這次我們引入了”time”模塊來獲取當(dāng)前系統(tǒng)時(shí)間。我們在返回響應(yīng)時(shí),通過”response.set_cookie()”函數(shù),來設(shè)置Cookie項(xiàng),之后這個(gè)項(xiàng)值會被保存在瀏覽器中。這個(gè)函數(shù)的第三個(gè)參數(shù)(max_age)可以設(shè)置該Cookie項(xiàng)的有效期,單位是秒,不設(shè)的話,在瀏覽器關(guān)閉后,該Cookie項(xiàng)即失效。

    在請求中,”request.cookies”對象就是一個(gè)保存了瀏覽器Cookie的字典,使用其”get()”函數(shù)就可以獲取相應(yīng)的鍵值。

    全局對象g

    “flask.g”是Flask一個(gè)全局對象,這里有點(diǎn)容易讓人誤解,其實(shí)”g”的作用范圍,就在一個(gè)請求(也就是一個(gè)線程)里,它不能在多個(gè)請求間共享。你可以在”g”對象里保存任何你想保存的內(nèi)容。一個(gè)最常用的例子,就是在進(jìn)入請求前,保存數(shù)據(jù)庫連接。這個(gè)我們會在介紹數(shù)據(jù)庫集成時(shí)講到。

    本例中的代碼可以在這里下載。

    Flask入門系列(五)–錯(cuò)誤處理及消息閃現(xiàn)

    本篇將補(bǔ)充一些Flask的基本功能,包括錯(cuò)誤處理,URL重定向,日志功能,還有一個(gè)很有趣的消息閃現(xiàn)功能。

    系列文章

    • Flask入門系列(一)–Hello World
    • Flask入門系列(二)–路由
    • Flask入門系列(三)–模板
    • Flask入門系列(四)–請求,響應(yīng)及會話
    • Flask入門系列(五)–錯(cuò)誤處理及消息閃現(xiàn)
    • Flask入門系列(六)–數(shù)據(jù)庫集成

    錯(cuò)誤處理

    使用”abort()”函數(shù)可以直接退出請求,返回錯(cuò)誤代碼:

    1

    2

    3

    4

    5

    from flask import abort

    ?

    @app.route('/error')

    def error():

    ????abort(404)

    上例會顯示瀏覽器的404錯(cuò)誤頁面。有時(shí)候,我們想要在遇到特定錯(cuò)誤代碼時(shí)做些事情,或者重寫錯(cuò)誤頁面,可以用下面的方法:

    1

    2

    3

    @app.errorhandler(404)

    def page_not_found(error):

    ????return render_template('404.html'), 404

    此時(shí),當(dāng)再次遇到404錯(cuò)誤時(shí),即會調(diào)用”page_not_found()”函數(shù),其返回”404.html”的模板頁。第二個(gè)參數(shù)代表錯(cuò)誤代碼。

    不過,在實(shí)際開發(fā)過程中,我們并不會經(jīng)常使用”abort()”來退出,常用的錯(cuò)誤處理方法一般都是異常的拋出或捕獲。裝飾器”@app.errorhandler()”除了可以注冊錯(cuò)誤代碼外,還可以注冊指定的異常類型。讓我們來自定義一個(gè)異常:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    class InvalidUsage(Exception):

    ????status_code = 400

    ?

    ????def __init__(self, message, status_code=400):

    ????????Exception.__init__(self)

    ????????self.message = message

    ????????self.status_code = status_code

    ?

    @app.errorhandler(InvalidUsage)

    def invalid_usage(error):

    ????response = make_response(error.message)

    ????response.status_code = error.status_code

    ????return response

    我們在上面的代碼中定義了一個(gè)異常”InvalidUsage”,同時(shí)我們通過裝飾器”@app.errorhandler()”修飾了函數(shù)”invalid_usage()”,裝飾器中注冊了我們剛定義的異常類。這也就意味著,一但遇到”InvalidUsage”異常被拋出,這個(gè)”invalid_usage()”函數(shù)就會被調(diào)用。寫個(gè)路由試一試吧。

    1

    2

    3

    @app.route('/exception')

    def exception():

    ????raise InvalidUsage('No privilege to access the resource', status_code=403)

    URL重定向

    重定向”redirect()”函數(shù)的使用在上一篇logout的例子中已有出現(xiàn)。作用就是當(dāng)客戶端瀏覽某個(gè)網(wǎng)址時(shí),將其導(dǎo)向到另一個(gè)網(wǎng)址。常見的例子,比如用戶在未登錄時(shí)瀏覽某個(gè)需授權(quán)的頁面,我們將其重定向到登錄頁要求其登錄先。

    1

    2

    3

    4

    5

    6

    7

    8

    from flask import session, redirect

    ?

    @app.route('/')

    def index():

    ????if 'user' in session:

    ????????return 'Hello %s!' % session['user']

    ????else:

    ????????return redirect(url_for('login'), 302)

    “redirect()”的第二個(gè)參數(shù)時(shí)HTTP狀態(tài)碼,可取的值有301, 302, 303, 305和307,默認(rèn)即302(為什么沒有304?留給大家去思考)。

    日志

    提到錯(cuò)誤處理,那一定要說到日志。Flask提供logger對象,其是一個(gè)標(biāo)準(zhǔn)的Python Logger類。修改上例中的”exception()”函數(shù):

    1

    2

    3

    4

    5

    @app.route('/exception')

    def exception():

    ????app.logger.debug('Enter exception method')

    ????app.logger.error('403 error happened')

    ????raise InvalidUsage('No privilege to access the resource', status_code=403)

    執(zhí)行后,你會在控制臺看到日志信息。在debug模式下,日志會默認(rèn)輸出到標(biāo)準(zhǔn)錯(cuò)誤stderr中。你可以添加FileHandler來使其輸出到日志文件中去,也可以修改日志的記錄格式,下面演示一個(gè)簡單的日志配置代碼:

    import logging

    from logging.handlers import TimedRotatingFileHandler

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    server_log = TimedRotatingFileHandler('server.log','D')

    server_log.setLevel(logging.DEBUG)

    server_log.setFormatter(logging.Formatter(

    ????'%(asctime)s %(levelname)s: %(message)s'

    ))

    ?

    error_log = TimedRotatingFileHandler('error.log', 'D')

    error_log.setLevel(logging.ERROR)

    error_log.setFormatter(logging.Formatter(

    ????'%(asctime)s: %(message)s [in %(pathname)s:%(lineno)d]'

    ))

    ?

    app.logger.addHandler(server_log)

    app.logger.addHandler(error_log)

    上例中,我們在本地目錄下創(chuàng)建了兩個(gè)日志文件,分別是”server.log”記錄所有級別日志;”error.log”只記錄錯(cuò)誤日志。我們分別給兩個(gè)文件不同的內(nèi)容格式。另外,我們使用了”TimedRotatingFileHandler”并給了參數(shù)”D”,這樣日志每天會創(chuàng)建一個(gè)新的文件,并將舊文件加日期后綴來歸檔。

    注:執(zhí)行后會生成server.log和error.log倆文件,訪問正常或錯(cuò)誤頁面這倆均無內(nèi)容,不知道是本身相關(guān)代碼有問題,還是訪問方式有問題

    你還可以將錯(cuò)誤信息發(fā)送郵件。更詳細(xì)的日志使用可參閱Python logging官方文檔。

    消息閃現(xiàn)

    “Flask Message”是一個(gè)很有意思的功能,一般一個(gè)操作完成后,我們都希望在頁面上閃出一個(gè)消息,告訴用戶操作的結(jié)果。用戶看完后,這個(gè)消息就不復(fù)存在了。Flask提供的”flash”功能就是為了這個(gè)。我們還是拿用戶登錄來舉例子:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    from flask import render_template, request, session, url_for, redirect, flash

    ?

    @app.route('/')

    def index():

    ????if 'user' in session:

    ????????return render_template('hello.html', name=session['user'])

    ????else:

    ????????return redirect(url_for('login'), 302)

    ?

    @app.route('/login', methods=['POST', 'GET'])

    def login():

    ????if request.method == 'POST':

    ????????session['user'] = request.form['user']

    ????????flash('Login successfully!')

    ????????return redirect(url_for('index'))

    ????else:

    ????????return '''

    ????????<form name="login" action="/login" method="post">

    ????????????Username: <input type="text" name="user" />

    ????????</form>

    ????????'''

    上例中,當(dāng)用戶登錄成功后,就用”flash()”函數(shù)閃出一個(gè)消息。讓我們找回第三篇中的模板代碼,在”layout.html”加上消息顯示的部分:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    <!doctype html>

    <title>Hello Sample</title>

    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">

    {% with messages = get_flashed_messages() %}

    ??{% if messages %}

    ????<ul class="flash">

    ????{% for message in messages %}

    ??????<li>{{ message }}</li>

    ????{% endfor %}

    ????</ul>

    ??{% endif %}

    {% endwith %}

    <div class="page">

    ????{% block body %}

    ????{% endblock %}

    </div>

    上例中”get_flashed_messages()”函數(shù)就會獲取我們在”login()”中通過”flash()”閃出的消息。從代碼中我們可以看出,閃出的消息可以有多個(gè)。模板”hello.html”不用改。運(yùn)行下試試。登錄成功后,是不是出現(xiàn)了一條”Login successfully”文字?再刷新下頁面,你會發(fā)現(xiàn)文字消失了。你可以通過CSS來控制這個(gè)消息的顯示方式。

    “flash()”方法的第二個(gè)參數(shù)是消息類型,可選擇的有”message”, “info”, “warning”, “error”。你可以在獲取消息時(shí),同時(shí)獲取消息類型,還可以過濾特定的消息類型。只需設(shè)置”get_flashed_messages()”方法的”with_categories”和”category_filter”參數(shù)即可。比如,Python部分可改為:

    1

    2

    3

    4

    5

    6

    7

    8

    @app.route('/login', methods=['POST', 'GET'])

    def login():

    ????if request.method == 'POST':

    ????????session['user'] = request.form['user']

    ????????flash('Login successfully!', 'message')

    ????????flash('Login as user: %s.' % request.form['user'], 'info')

    ????????return redirect(url_for('index'))

    ????...

    layout模板部分可改為:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    ...

    {% with messages = get_flashed_messages(with_categories=true, category_filter=["message","error"]) %}

    ??{% if messages %}

    ????<ul class="flash">

    ????{% for category, message in messages %}

    ??????<li class="{{ category }}">{{ category }}: {{ message }}</li>

    ????{% endfor %}

    ????</ul>

    ??{% endif %}

    {% endwith %}

    ...

    運(yùn)行結(jié)果大家就自己試試吧。

    本例中的代碼可以在這里下載。

    Flask入門系列(六)–數(shù)據(jù)庫集成

    轉(zhuǎn)眼,我們要進(jìn)入本系列的最后一篇了。一個(gè)基本的Web應(yīng)用功能其實(shí)已經(jīng)講完了,現(xiàn)在就讓我們引入數(shù)據(jù)庫。簡單起見,我們就使用SQLite3作為例子。

    系列文章

    • Flask入門系列(一)–Hello World
    • Flask入門系列(二)–路由
    • Flask入門系列(三)–模板
    • Flask入門系列(四)–請求,響應(yīng)及會話
    • Flask入門系列(五)–錯(cuò)誤處理及消息閃現(xiàn)
    • Flask入門系列(六)–數(shù)據(jù)庫集成

    集成數(shù)據(jù)庫

    既然前幾篇都用用戶登錄作為例子,我們這篇就繼續(xù)講登錄,只是登錄的信息會由數(shù)據(jù)庫來驗(yàn)證。讓我們先準(zhǔn)備SQLite環(huán)境吧。

    初始化數(shù)據(jù)庫

    怎么安裝SQLite這里就不說了。我們先寫個(gè)數(shù)據(jù)庫表的初始化SQL,保存在”init.sql”文件中:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    drop table if exists users;

    create table users (

    ??id integer primary key autoincrement,

    ??name text not null,

    ??password text not null

    );

    ?

    insert into users (name, password) values ('visit', '111');

    insert into users (name, password) values ('admin', '123');

    運(yùn)行sqlite3命令,初始化數(shù)據(jù)庫。我們的數(shù)據(jù)庫文件就放在”db”子目錄下的”user.db”文件中。

    $ sqlite3 db/user.db < init.sql

    配置連接參數(shù)

    創(chuàng)建配置文件"config.py",保存配置信息:

    1

    2

    3

    4

    #coding:utf8

    DATABASE = 'db/user.db'?????? # 數(shù)據(jù)庫文件位置

    DEBUG = True??????????????????# 調(diào)試模式

    SECRET_KEY = 'secret_key_1'?? # 會話密鑰

    在創(chuàng)建Flask應(yīng)用時(shí),導(dǎo)入配置信息:

    1

    2

    3

    4

    5

    from flask import Flask

    import config

    ?

    app = Flask(__name__)

    app.config.from_object('config')

    這里也可以用"app.config.from_envvar('FLASK_SETTINGS', silent=True)"方法來導(dǎo)入配置信息,此時(shí)程序會讀取系統(tǒng)環(huán)境變量中"FLASK_SETTINGS"的值,來獲取配置文件路徑,并加載此文件。如果文件不存在,該語句返回False。參數(shù)"silent=True"表示忽略錯(cuò)誤。

    建立和釋放數(shù)據(jù)庫連接

    這里要用到請求的上下文裝飾器,我們會在進(jìn)階系列的第一篇里詳細(xì)介紹上下文。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    @app.before_request

    def before_request():

    ????g.db = sqlite3.connect(app.config['DATABASE'])

    ?

    @app.teardown_request

    def teardown_request(exception):

    ????db = getattr(g, 'db', None)

    ????if db is not None:

    ????????db.close()

    我們在"before_request()"里建立數(shù)據(jù)庫連接,它會在每次請求開始時(shí)被調(diào)用;并在"teardown_request()"關(guān)閉它,它會在每次請求關(guān)閉前被調(diào)用。

    查詢數(shù)據(jù)庫

    讓我們?nèi)』厣弦黄卿洸糠值拇a,"index()"和"logout()"請求不用修改,在"login()"請求中,我們會查詢數(shù)據(jù)庫,驗(yàn)證客戶端輸入的用戶名和密碼是否存在:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    @app.route('/login', methods=['POST', 'GET'])

    def login():

    ????if request.method == 'POST':

    ????????name = request.form['user']

    ????????passwd = request.form['passwd']

    ????????cursor = g.db.execute('select * from users where name=? and password=?', [name, passwd])

    ????????if cursor.fetchone() is not None:

    ????????????session['user'] = name

    ????????????flash('Login successfully!')

    ????????????return redirect(url_for('index'))

    ????????else:

    ????????????flash('No such user!', 'error')

    ????????????return redirect(url_for('login'))

    ????else:

    ????????return render_template('login.html')

    模板中加上"login.html"文件

    1

    2

    3

    4

    5

    6

    7

    8

    {% extends "layout.html" %}

    {% block body %}

    <form name="login" action="/login" method="post">

    ????Username: <input type="text" name="user" /><br>

    ????Password: <input type="password" name="passwd" /><br>

    ????<input type="submit" value="Submit" />

    </form>

    {% endblock %}

    終于一個(gè)真正的登錄驗(yàn)證寫完了(前幾篇都是假的),打開瀏覽器登錄下吧。因?yàn)楸容^懶,就不寫CSS美化了,受不了這粗糙界面的朋友們就自己調(diào)吧。

    到目前為止,Flask的基礎(chǔ)功能已經(jīng)介紹完了,是否很想動手寫個(gè)應(yīng)用啦?其實(shí)Flask還有更強(qiáng)大的高級功能,之后會在進(jìn)階系列里介紹。

    本例中的代碼可以在這里下載。

    二、模板引擎詳解:

    Flask中Jinja2模板引擎詳解(一)–控制語句和表達(dá)式

    讓我們開啟Jinja2模板引擎之旅,雖說標(biāo)題是Flask中的Jinja2,其實(shí)介紹的主要是Jinja2本身,Flask是用來做例子的。如果對Flask不熟悉的朋友們建議將本博客的入門系列先看下。怎么,不知道什么是模板引擎?你可以將模板比作MVC模式中的View視圖層,而模板引擎就是用來將模板同業(yè)務(wù)代碼分離,并解析模板語言的程序。你可以耐心地看下本系列文章,就能體會到什么是模板引擎了。

    系列文章

    • Flask中Jinja2模板引擎詳解(一)–控制語句和表達(dá)式
    • Flask中Jinja2模板引擎詳解(二)–上下文環(huán)境
    • Flask中Jinja2模板引擎詳解(三)–過濾器
    • Flask中Jinja2模板引擎詳解(四)–測試器
    • Flask中Jinja2模板引擎詳解(五)–全局函數(shù)
    • Flask中Jinja2模板引擎詳解(六)–塊和宏
    • Flask中Jinja2模板引擎詳解(七)–本地化
    • Flask中Jinja2模板引擎詳解(八)–自定義擴(kuò)展

    回顧

    我們在Flask入門系列第三篇中已經(jīng)介紹了Jinja2模板的基本使用方式,讓我們先回顧下,把其中的代碼拿過來。
    Flask Python代碼:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    from flask import Flask,render_template

    ?

    app = Flask(__name__)

    ?

    @app.route('/hello')

    @app.route('/hello/<name>')

    def hello(name=None):

    ????return render_template('hello.html', name=name)

    ?

    if __name__ == '__main__':

    ????app.run(host='0.0.0.0', debug=True)

    模板代碼:

    1

    2

    3

    4

    5

    6

    7

    <!doctype html>

    <title>Hello Sample</title>

    {% if name %}

    ??<h1>Hello {{ name }}!</h1>

    {% else %}

    ??<h1>Hello World!</h1>

    {% endif %}

    我們了解到,模板的表達(dá)式都是包含在分隔符”{{ }}”內(nèi)的;控制語句都是包含在分隔符”{% %}”內(nèi)的;另外,模板也支持注釋,都是包含在分隔符”{# #}”內(nèi),支持塊注釋。

    表達(dá)式

    表達(dá)式一般有這么幾種:

    • 最常用的是變量,由Flask渲染模板時(shí)傳過來,比如上例中的”name”
    • 也可以是任意一種Python基礎(chǔ)類型,比如字符串{{ “Hello” }},用引號括起;或者數(shù)值,列表,元祖,字典,布爾值。直接顯示基礎(chǔ)類型沒啥意義,一般配合其他表達(dá)式一起用
    • 運(yùn)算。包括算數(shù)運(yùn)算,如{{ 2 + 3 }};比較運(yùn)算,如{{ 2 > 1 }};邏輯運(yùn)算,如{{ False and True }}
    • 過濾器“|”和測試器“is”。這個(gè)在后面會介紹
    • 函數(shù)調(diào)用,如{{ current_time() }};數(shù)組下標(biāo)操作,如{{ arr[1] }}
    • “in”操作符,如{{ 1 in [1,2,3] }}
    • 字符串連接符”~”,作用同Python中的”+”一樣,如{{ “Hello ” ~ name ~ “!” }}
    • “if”關(guān)鍵字,如{{ ‘Hi, %s’ % name if name }}。這里的”if”不是條件控制語句。

    有沒有覺得,這里的表達(dá)式很像Python的語法呀?

    控制語句

    Jinja2的控制語句主要就是條件控制語句if,和循環(huán)控制語句for,語法類似于Python。我們可以改動下上節(jié)的模板代碼:

    1

    2

    3

    4

    5

    6

    7

    {% if name and name == 'admin'??%}

    ????<h1>This is admin console</h1>

    {% elif name %}

    ????<h1>Welcome {{ name }}!</h1>

    {% else %}

    ????<h1>Please login</h1>

    {% endif %}

    上面是一個(gè)條件控制語句的例子,注意if控制語句要用”{% endif %}”來結(jié)束。模板中無法像代碼中一樣靠縮進(jìn)來判斷代碼塊的結(jié)束。再來看個(gè)循環(huán)的例子,我們先改下Python代碼中的”hello”函數(shù),讓其傳兩個(gè)列表進(jìn)模板。

    1

    2

    3

    4

    5

    6

    def hello(name=None):

    ????return render_template('hello-1.html', name=name, digits=[1,2,3,4,5],

    ?????????????????????????? users=[{'name':'John'},

    ??????????????????????????????????{'name':'Tom', 'hidden':True},

    ??????????????????????????????????{'name':'Lisa'}

    ??????????????????????????????????{'name':'Bob'}])

    然后在模板中加上:

    1

    2

    3

    ??{% for digit in digits %}

    ????{{ digit }}

    ??{% endfor %}

    是不是列表被顯示出來了?同if語句一樣,for控制語句要用”{% endfor %}”來結(jié)束。頁面上,每個(gè)元素之間會有空格,如果你不希望有空格,就要在”for”語句的最后,和”endfor”語句的最前面各加上一個(gè)”-“號。如:

    1

    2

    3

    ??{% for digit in digits -%}

    ????{{ digit }}

    ??{%- endfor %}

    現(xiàn)在,你可以看到數(shù)字”12345″被一起顯示出來了。我們再來看個(gè)復(fù)雜的循環(huán)例子:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    <dl>

    {% for user in users if not user.hidden %}

    ??{% if loop.first %}

    ????<div>User List:</div>

    ??{% endif %}

    ??<div class="{{ loop.cycle('odd', 'even') }}">

    ??<dt>User No {{ loop.index }}:</dt>

    ??<dd>{{ user.name }}</dd>

    ??</div>

    ??{% if loop.last %}

    ????<div>Total Users: {{ loop.length }}</div>

    ??{% endif %}

    {% else %}

    ??<li>No users found</li>

    {% endfor %}

    </dl>

    這里有三個(gè)知識點(diǎn)。首先for循環(huán)支持else語句,當(dāng)待遍歷的列表”users”為空或者為None時(shí),就進(jìn)入else語句。

    其次,在for語句后使用if關(guān)鍵字,可以對循環(huán)中的項(xiàng)作過濾。本例中,所有hidden屬性為True的user都會被過濾掉。

    另外,for循環(huán)中可以訪問Jinja2的循環(huán)內(nèi)置變量。本例中,我們會在第一項(xiàng)前顯示標(biāo)題,最后一項(xiàng)后顯示總數(shù),每一項(xiàng)顯示序號。另外,奇偶項(xiàng)的HTML div元素會有不同的class。如果我們加入下面的CSS style,就能看到斑馬線。

    直接在html頁面中寫入下列內(nèi)容即可。

    不過也可單獨(dú)寫入一個(gè)css文件路,不過需要引用才能生效

    1

    2

    3

    4

    5

    <style type="text/css">

    ????.odd {

    ????????background-color: #BDF;

    ????}

    </style>

    Jinja2的循環(huán)內(nèi)置變量主要有以下幾個(gè):

    變量

    內(nèi)容

    loop.index

    循環(huán)迭代計(jì)數(shù)(從1開始)

    loop.index0

    循環(huán)迭代計(jì)數(shù)(從0開始)

    loop.revindex

    循環(huán)迭代倒序計(jì)數(shù)(從len開始,到1結(jié)束)

    loop.revindex0

    循環(huán)迭代倒序計(jì)數(shù)(從len-1開始,到0結(jié)束)

    loop.first

    是否為循環(huán)的第一個(gè)元素

    loop.last

    是否為循環(huán)的最后一個(gè)元素

    loop.length

    循環(huán)序列中元素的個(gè)數(shù)

    loop.cycle

    在給定的序列中輪循,如上例在”odd”和”even”兩個(gè)值間輪循

    loop.depth

    當(dāng)前循環(huán)在遞歸中的層級(從1開始)

    loop.depth0

    當(dāng)前循環(huán)在遞歸中的層級(從0開始)

    關(guān)于遞歸循環(huán),大家看看下面的例子,我就不多介紹了:

    1

    2

    3

    4

    5

    6

    7

    8

    {% for item in [[1,2],[3,4,5]] recursive %}

    ??Depth: {{ loop.depth }}

    ??{% if item[0] %}

    ????{{ loop(item) }}

    ??{% else %}

    ????Number: {{ item }} ;

    ??{% endif %}

    {% endfor %}

    另外,如果你啟用了”jinja2.ext.loopcontrols”擴(kuò)展的話,你還可以在循環(huán)中使用”{% break %}”和”{% continue %}”來控制循環(huán)執(zhí)行。關(guān)于Jinja2的擴(kuò)展,我們會在本系列的第七篇和第八篇中介紹。

    其他常用語句

    忽略模板語法

    有時(shí)候,我們在頁面上就是要顯示”{{ }}”這樣的符號怎么辦?Jinja2提供了”raw”語句來忽略所有模板語法。

    1

    2

    3

    4

    5

    6

    7

    {% raw %}

    ????<ul>

    ????{% for item in items %}

    ????????<li>{{ item }}</li>

    ????{% endfor %}

    ????</ul>

    {% endraw %}

    自動轉(zhuǎn)義

    我們將本文一開始的Flask代碼”hello()”方法改動下:

    1

    2

    3

    4

    5

    6

    @app.route('/hello')

    @app.route('/hello/<name>')

    def hello(name=None):

    ????if name is None:

    ????????name = '<em>World</em>'

    ????return render_template('hello.html', name=name)

    此時(shí),訪問”http://localhost:5000/hello”,頁面上會顯示”Welcome <em>World</em>!”,也就是這個(gè)HTML標(biāo)簽”<em>”被自動轉(zhuǎn)義了。正如我們曾經(jīng)提到過的,Flask會對”.html”, “.htm”, “.xml”, “.xhtml”這四種類型的模板文件開啟HTML格式自動轉(zhuǎn)義。這樣也可以防止HTML語法注入。如果我們不想被轉(zhuǎn)義怎么辦?

    1

    2

    3

    {% autoescape false %}

    ??<h1>Hello {{ name }}!</h1>

    {% endautoescape %}

    將”autoescape”開關(guān)設(shè)為”false”即可,反之,設(shè)為”true”即開啟自動轉(zhuǎn)義。使用”autoescape”開關(guān)前要啟用”jinja2.ext.autoescape”擴(kuò)展,在Flask框架中,這個(gè)擴(kuò)展默認(rèn)已啟用。

    賦值

    使用”set”關(guān)鍵字給變量賦值:

    1

    {% set items = [[1,2],[3,4,5]] %}

    ? 用法可以參考下面的with語句

    with語句

    類似于Python中的”with”關(guān)鍵字,它可以限制with語句塊內(nèi)對象的作用域:

    1

    2

    3

    4

    5

    {% with foo = 1 %}

    ????{% set bar = 2 %}

    ????{{ foo + bar }}

    {% endwith %}

    {# foo and bar are not visible here #}

    使用”with”關(guān)鍵字前要啟用”jinja2.ext.with_”擴(kuò)展,在Flask框架中,這個(gè)擴(kuò)展默認(rèn)已啟用。

    執(zhí)行表達(dá)式

    1

    2

    3

    4

    {% with arr = ['Sunny'] %}

    ??{{ arr.append('Rainy') }}

    ??{{ arr }}

    {% endwith %}

    看上面這段代碼,我們想執(zhí)行列表的”append”操作,這時(shí)使用”{{ arr.append(‘Rainy’) }}”頁面會輸出”None”,換成”{% %}”來執(zhí)行,程序會報(bào)錯(cuò),因?yàn)檫@是個(gè)表達(dá)式,不是語句。那怎么辦?我們可以啟用”jinja2.ext.do”擴(kuò)展。然后在模板中執(zhí)行”do”語句即可:

    1

    2

    3

    4

    {% with arr = ['Sunny'] %}

    ??{% do arr.append('Rainy') %}

    ??{{ arr }}

    {% endwith %}

    默認(rèn)jinja2沒開啟這個(gè),需要啟用

    在py文件中添加這個(gè):app.jinja_env.add_extension("jinja2.ext.do"),表示啟用jinja2的do擴(kuò)展,然后就能在html文件中使用上述語句了

    本篇中的示例代碼可以在這里下載。

    Flask中Jinja2模板引擎詳解(二)–上下文環(huán)境

    Flask每個(gè)請求都有生命周期,在生命周期內(nèi)請求有其上下文環(huán)境Request Context。我們在Flask進(jìn)階系列第一篇中有詳細(xì)介紹。作為在請求中渲染的模板,自然也在請求的生命周期內(nèi),所以Flask應(yīng)用中的模板可以使用到請求上下文中的環(huán)境變量,及一些輔助函數(shù)。本文就會介紹下這些變量和函數(shù)。

    系列文章

    • Flask中Jinja2模板引擎詳解(一)–控制語句和表達(dá)式
    • Flask中Jinja2模板引擎詳解(二)–上下文環(huán)境
    • Flask中Jinja2模板引擎詳解(三)–過濾器
    • Flask中Jinja2模板引擎詳解(四)–測試器
    • Flask中Jinja2模板引擎詳解(五)–全局函數(shù)
    • Flask中Jinja2模板引擎詳解(六)–塊和宏
    • Flask中Jinja2模板引擎詳解(七)–本地化
    • Flask中Jinja2模板引擎詳解(八)–自定義擴(kuò)展

    標(biāo)準(zhǔn)上下文變量和函數(shù)

    請求對象request

    request對象可以用來獲取請求的方法”request.method”,表單”request.form”,請求的參數(shù)”request.args”,請求地址”request.url”等。它本身是一個(gè)字典。在模板中,你一樣可以獲取這些內(nèi)容,只要用表達(dá)式符號”{{ }}”括起來即可。

    1

    <p>{{ request.url }}</p>

    在沒有請求上下文的環(huán)境中,這個(gè)對象不可用。

    會話對象session

    session對象可以用來獲取當(dāng)前會話中保存的狀態(tài),它本身是一個(gè)字典。在模板中,你可以用表達(dá)式符號”{{ }}”來獲取這個(gè)對象。

    Flask代碼如下,別忘了設(shè)置會話密鑰哦:

    注:需要導(dǎo)入from flask import Flask,render_template,session

    1

    2

    3

    4

    5

    6

    @app.route('/')

    def index():

    ????session['user'] = 'guest'

    ????return render_template('hello.html')

    ?

    app.secret_key = '123456'

    模板代碼:

    1

    ??<p>User: {{ session.user }}</p>

    在沒有請求上下文的環(huán)境中,這個(gè)對象不可用。

    全局對象g

    全局變量g,用來保存請求中會用到全局內(nèi)容,比如數(shù)據(jù)庫連接。模板中也可以訪問。

    Flask代碼:

    注:需要導(dǎo)入from flask import Flask,render_template,g

    1

    2

    3

    4

    @app.route('/')

    def index():

    ????g.db = 'mysql'

    ????return render_template('hello.html')

    模板代碼:

    1

    ??<p>DB: {{ g.db }}</p>

    g對象是保存在應(yīng)用上下文環(huán)境中的,也只在一個(gè)請求生命周期內(nèi)有效。在沒有應(yīng)用上下文的環(huán)境中,這個(gè)對象不可用。

    Flask配置對象config

    在Flask入門系列第六篇中,我們曾介紹過如何將配置信息導(dǎo)入Flask應(yīng)用中。導(dǎo)入的配置信息,就保存在”app.config”對象中。這個(gè)配置對象在模板中也可以訪問。

    1

    ??<p>Host: {{ config.DEBUG }}</p>

    結(jié)果返回:Host:True,表示的是開啟了調(diào)試模式

    “config”是全局對象,離開了請求生命周期也可以訪問。

    url_for()函數(shù)

    url_for()函數(shù)可以用來快速獲取及構(gòu)建URL,Flask也將此函數(shù)引入到了模板中,比如下面的代碼,就可以獲取靜態(tài)目錄下的”style.css”文件。

    1

    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">

    該函數(shù)是全局的,離開了請求生命周期也可以調(diào)用。

    get_flashed_messages()函數(shù)

    get_flashed_messages()函數(shù)是用來獲取消息閃現(xiàn)的。具體的示例我們在入門系列第五篇中已經(jīng)講過,這里就不再贅述了。這也是一個(gè)全局可使用的函數(shù)。

    自定義上下文變量和函數(shù)

    自定義變量

    除了Flask提供的標(biāo)準(zhǔn)上下文變量和函數(shù),我們還可以自己定義。下面我們就來先定義一個(gè)上下文變量,在Flask應(yīng)用代碼中,加入下面的函數(shù):

    1

    2

    3

    4

    5

    from flask import current_app

    ?

    @app.context_processor

    def appinfo():

    ????return dict(appname=current_app.name)

    函數(shù)返回的是一個(gè)字典,里面有一個(gè)屬性”appname”,值為當(dāng)前應(yīng)用的名稱。我們曾經(jīng)介紹過,這里的”current_app”對象是一個(gè)定義在應(yīng)用上下文中的代理。函數(shù)用”@app.context_processor”裝飾器修飾,它是一個(gè)上下文處理器,它的作用是在模板被渲染前運(yùn)行其所修飾的函數(shù),并將函數(shù)返回的字典導(dǎo)入到模板上下文環(huán)境中,與模板上下文合并。然后,在模板中”appname”就如同上節(jié)介紹的”request”, “session”一樣,成為了可訪問的上下文對象。我們可以在模板中將其輸出:

    1

    ??<p>Current App is: {{ appname }}</p>

    自定義函數(shù)

    同理我們可以自定義上下文函數(shù),只需將上例中返回字典的屬性指向一個(gè)函數(shù)即可,下面我們就來定義一個(gè)上下文函數(shù)來獲取系統(tǒng)當(dāng)前時(shí)間:

    1

    2

    3

    4

    5

    6

    7

    import time

    ?

    @app.context_processor

    def get_current_time():

    ????def get_time(timeFormat="%b %d, %Y - %H:%M:%S"):

    ????????return time.strftime(timeFormat)

    ????return dict(current_time=get_time)

    我們可以試下在模板中將其輸出:

    1

    2

    ??<p>Current Time is: {{ current_time() }}</p>

    ??<p>Current Day is: {{ current_time("%Y-%m-%d") }}</p>

    上下文處理器可以修飾多個(gè)函數(shù),也就是我們可以定義多個(gè)上下文環(huán)境變量和函數(shù)。

    本篇中的示例代碼可以在這里下載。

    Flask中Jinja2模板引擎詳解(三)–過濾器

    我所了解的模板引擎大部分都會提供類似Jinja2過濾器的功能,只不過叫法不同罷了。比如PHP Smarty中的Modifiers(變量調(diào)節(jié)器或修飾器),FreeMarker中的Build-ins(內(nèi)建函數(shù)),連AngularJS這樣的前端框架也提供了Filter過濾器。它們都是用來在變量被顯示或使用前,對其作轉(zhuǎn)換處理的。可以把它認(rèn)為是一種轉(zhuǎn)換函數(shù),輸入的參數(shù)就是其所修飾的變量,返回的就是變量轉(zhuǎn)換后的值。

    系列文章

    • Flask中Jinja2模板引擎詳解(一)–控制語句和表達(dá)式
    • Flask中Jinja2模板引擎詳解(二)–上下文環(huán)境
    • Flask中Jinja2模板引擎詳解(三)–過濾器
    • Flask中Jinja2模板引擎詳解(四)–測試器
    • Flask中Jinja2模板引擎詳解(五)–全局函數(shù)
    • Flask中Jinja2模板引擎詳解(六)–塊和宏
    • Flask中Jinja2模板引擎詳解(七)–本地化
    • Flask中Jinja2模板引擎詳解(八)–自定義擴(kuò)展

    過濾器使用

    回到我們第一篇開篇的例子,我們在模板中對變量name作如下處理:

    1

    ??<h1>Hello {{ name | upper }}!</h1>

    你會看到name的輸出都變成大寫了。這就是過濾器,只需在待過濾的變量后面加上”|”符號,再加上過濾器名稱,就可以對該變量作過濾轉(zhuǎn)換。上面例子就是轉(zhuǎn)換成全大寫字母。過濾器可以連續(xù)使用:

    1

    ??<h1>Hello {{ name | upper | truncate(3, True) }}!</h1>

    現(xiàn)在name變量不但被轉(zhuǎn)換為大寫,而且當(dāng)它的長度大于3后,只顯示前3個(gè)字符,后面默認(rèn)用”…”顯示。過濾器”truncate”有3個(gè)參數(shù),第一個(gè)是字符截取長度;第二個(gè)決定是否保留截取后的子串,默認(rèn)是False,也就是當(dāng)字符大于3后,只顯示”…”,截取部分也不出現(xiàn);第三個(gè)是省略符號,默認(rèn)是”…”。

    其實(shí)從例子中我們可以猜到,過濾器本質(zhì)上就是一個(gè)轉(zhuǎn)換函數(shù),它的第一個(gè)參數(shù)就是待過濾的變量,在模板中使用時(shí)可以省略去。如果它有第二個(gè)參數(shù),模板中就必須傳進(jìn)去。

    內(nèi)置過濾器 Builtin Filters

    Jinja2模板引擎提供了豐富的內(nèi)置過濾器。這里介紹幾個(gè)常用的。

    字符串操作

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    {# 當(dāng)變量未定義時(shí),顯示默認(rèn)字符串,可以縮寫為d #}

    <p>{{ name | default('No name', true) }}</p>

    ?

    {# 單詞首字母大寫 #}

    <p>{{ 'hello' | capitalize }}</p>

    ?

    {# 單詞全小寫 #}

    <p>{{ 'XML' | lower }}</p>

    ?

    {# 去除字符串前后的空白字符 #}

    <p>{{ '??hello??' | trim }}</p>

    ?

    {# 字符串反轉(zhuǎn),返回"olleh" #}

    <p>{{ 'hello' | reverse }}</p>

    ?

    {# 格式化輸出,返回"Number is 2" #}

    <p>{{ '%s is %d' | format("Number", 2) }}</p>

    ?

    {# 關(guān)閉HTML自動轉(zhuǎn)義 #}

    <p>{{ '<em>name</em>' | safe }}</p>

    ?

    {% autoescape false %}

    {# HTML轉(zhuǎn)義,即使autoescape關(guān)了也轉(zhuǎn)義,可以縮寫為e #}

    <p>{{ '<em>name</em>' | escape }}</p>

    {% endautoescape %}

    數(shù)值操作

    1

    2

    3

    4

    5

    6

    7

    8

    {# 四舍五入取整,返回13.0 #}

    <p>{{ 12.8888 | round }}</p>

    ?

    ?{# 四舍五入向下截取到小數(shù)點(diǎn)后2位,返回12.89 #}

    <p>{{ 12.8888 | round(2) }}</p>

    ?

    {# 向下截取到小數(shù)點(diǎn)后2位,返回12.88 #}

    <p>{{ 12.8888 | round(2, 'floor') }}</p>

    ?

    {# 絕對值,返回12 #}

    <p>{{ -12 | abs }}</p>

    列表操作

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    {# 取第一個(gè)元素 #}

    <p>{{ [1,2,3,4,5] | first }}</p>

    ?

    {# 取最后一個(gè)元素 #}

    <p>{{ [1,2,3,4,5] | last }}</p>

    ?

    {# 返回列表長度,可以寫為count #}

    <p>{{ [1,2,3,4,5] | length }}</p>

    ?

    {# 列表求和 #}

    <p>{{ [1,2,3,4,5] | sum }}</p>

    ?

    {# 列表排序,默認(rèn)為升序 #}

    <p>{{ [3,2,1,5,4] | sort }}</p>

    ?

    {# 合并為字符串,返回"1 | 2 | 3 | 4 | 5" #}

    <p>{{ [1,2,3,4,5] | join(' | ') }}</p>

    ?

    {# 列表中所有元素都全大寫。這里可以用upper,lower,但capitalize無效 #}

    <p>{{ ['tom','bob','ada'] | upper }}</p>

    字典列表操作

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    {% set users=[{'name':'Tom','gender':'M','age':20},

    ??????????????{'name':'John','gender':'M','age':18},

    ??????????????{'name':'Mary','gender':'F','age':24},

    ??????????????{'name':'Bob','gender':'M','age':31},

    ??????????????{'name':'Lisa','gender':'F','age':19}]

    %}

    ?

    ?

    {# 按指定字段排序,這里設(shè)reverse為true使其按降序排 #}

    <ul>

    {% for user in users | sort(attribute='age', reverse=true) %}

    ???? <li>{{ user.name }}, {{ user.age }}</li>

    {% endfor %}

    </ul>

    ?

    {# 列表分組,每組是一個(gè)子列表,組名就是分組項(xiàng)的值 #}

    <ul>

    {% for group in users|groupby('gender') %}

    ????<li>{{ group.grouper }}<ul>

    ????{% for user in group.list %}

    ????????<li>{{ user.name }}</li>

    ????{% endfor %}</ul></li>

    {% endfor %}

    </ul>

    ?

    {# 取字典中的某一項(xiàng)組成列表,再將其連接起來 #}

    <p>{{ users | map(attribute='name') | join(', ') }}</p>

    更全的內(nèi)置過濾器介紹可以從Jinja2的官方文檔中找到。

    Flask內(nèi)置過濾器

    Flask提供了一個(gè)內(nèi)置過濾器”tojson”,它的作用是將變量輸出為JSON字符串。這個(gè)在配合Javascript使用時(shí)非常有用。我們延用上節(jié)字典列表操作中定義的”users”變量

    1

    2

    3

    4

    <script type="text/javascript">

    var users = {{ users | tojson | safe }};

    console.log(users[0].name);

    </script>

    注意,這里要避免HTML自動轉(zhuǎn)義,所以加上safe過濾器。

    注:暫不知道具體用法

    語句塊過濾

    Jinja2還可以對整塊的語句使用過濾器。

    1

    2

    3

    {% filter upper %}

    ????This is a Flask Jinja2 introduction.

    {% endfilter %}

    不過上述這種場景不經(jīng)常用到。

    自定義過濾器

    內(nèi)置的過濾器不滿足需求怎么辦?自己寫唄。過濾器說白了就是一個(gè)函數(shù)嘛,我們馬上就來寫一個(gè)。回到Flask應(yīng)用代碼中:

    注:這個(gè)很有用

    1

    2

    def double_step_filter(l):

    ????return l[::2]

    我們定義了一個(gè)”double_step_filter”函數(shù),返回輸入列表的偶數(shù)位元素(第0位,第2位,..)。怎么把它加到模板中當(dāng)過濾器用呢?Flask應(yīng)用對象提供了”add_template_filter”方法來幫我們實(shí)現(xiàn)。我們加入下面的代碼:

    1

    app.add_template_filter(double_step_filter, 'double_step')

    函數(shù)的第一個(gè)參數(shù)是過濾器函數(shù),第二個(gè)參數(shù)是過濾器名稱。然后,我們就可以愉快地在模板中使用這個(gè)叫”double_step”的過濾器了:

    1

    2

    {# 返回[1,3,5] #}

    <p>{{ [1,2,3,4,5] | double_step }}</p>

    Flask還提供了添加過濾器的裝飾器”template_filter”,使用起來更簡單。下面的代碼就添加了一個(gè)取子列表的過濾器。裝飾器的參數(shù)定義了該過濾器的名稱”sub”。

    1

    2

    3

    @app.template_filter('sub')

    def sub(l, start, end):

    ????return l[start:end]

    我們在模板中可以這樣使用它:

    1

    2

    {# 返回[2,3,4] #}

    <p>{{ [1,2,3,4,5] | sub(1,4) }}</p>

    Flask添加過濾器的方法實(shí)際上是封裝了對Jinja2環(huán)境變量的操作。上述添加”sub”過濾器的方法,等同于下面的代碼。

    1

    app.jinja_env.filters['sub'] = sub

    我們在Flask應(yīng)用中,不建議直接訪問Jinja2的環(huán)境變量。如果離開Flask環(huán)境直接使用Jinja2的話,就可以通過”jinja2.Environment”來獲取環(huán)境變量,并添加過濾器。

    本篇中的示例代碼可以在這里下載。

    Flask中Jinja2模板引擎詳解(四)–測試器

    Jinja2中的測試器Test和過濾器非常相似,區(qū)別是測試器總是返回一個(gè)布爾值,它可以用來測試一個(gè)變量或者表達(dá)式,你需要使用”is”關(guān)鍵字來進(jìn)行測試。測試器一般都是跟著if控制語句一起使用的。下面我們就來深入了解下這個(gè)測試器。

    系列文章

    • Flask中Jinja2模板引擎詳解(一)–控制語句和表達(dá)式
    • Flask中Jinja2模板引擎詳解(二)–上下文環(huán)境
    • Flask中Jinja2模板引擎詳解(三)–過濾器
    • Flask中Jinja2模板引擎詳解(四)–測試器
    • Flask中Jinja2模板引擎詳解(五)–全局函數(shù)
    • Flask中Jinja2模板引擎詳解(六)–塊和宏
    • Flask中Jinja2模板引擎詳解(七)–本地化
    • Flask中Jinja2模板引擎詳解(八)–自定義擴(kuò)展

    測試器使用

    再次取回第一篇開篇的例子,我們在模板中對變量name作如下判斷:

    1

    2

    3

    {% if name is lower %}

    ??<h2>"{{ name }}" are all lower case.</h2>

    {% endif %}

    當(dāng)name變量中的字母都是小寫時(shí),這段文字就會顯示。這就是測試器,在if語句中,變量或表達(dá)式的后面加上is關(guān)鍵字,再加上測試器名稱,就可以對該變量或表達(dá)式作測試,并根據(jù)其測試結(jié)果的真或假,來決定是否進(jìn)入if語句塊。測試器也可以有參數(shù),用括號括起。當(dāng)其只有一個(gè)參數(shù)時(shí),可以省去括號。

    1

    2

    3

    {% if 6 is divisibleby 3 %}

    ??<h2>"divisibleby" test pass</h2>

    {% endif %}

    上例中,測試器”divisibleby”可以判斷其所接收的變量是否可以被其參數(shù)整除。因?yàn)樗挥幸粋€(gè)參數(shù),我們就可以用空格來分隔測試器和其參數(shù)。上面的調(diào)用同”divisibleby(3)”效果一致。測試器也可以配合not關(guān)鍵字一起使用:

    1

    2

    3

    {% if 6 is not divisibleby(4) %}

    ??<h2>"not divisibleby" test pass</h2>

    {% endif %}

    顯然測試器本質(zhì)上也是一個(gè)函數(shù),它的第一個(gè)參數(shù)就是待測試的變量,在模板中使用時(shí)可以省略去。如果它有第二個(gè)參數(shù),模板中就必須傳進(jìn)去。測試器函數(shù)返回的必須是一個(gè)布爾值,這樣才可以用來給if語句作判斷。

    內(nèi)置測試器 Builtin Tests

    同過濾器一樣,Jinja2模板引擎提供了豐富的內(nèi)置測試器。這里介紹幾個(gè)常用的。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    {# 檢查變量是否被定義,也可以用undefined檢查是否未被定義 #}

    {% if name is defined %}

    ????<p>Name is: {{ name }}</p>

    {% endif %}

    ?

    {# 檢查是否所有字符都是大寫 #}

    {% if name is upper %}

    ??<h2>"{{ name }}" are all upper case.</h2>

    {% endif %}

    ?

    {# 檢查變量是否為空 #}

    {% if name is none %}

    ??<h2>Variable is none.</h2>

    {% endif %}

    ?

    {# 檢查變量是否為字符串,也可以用number檢查是否為數(shù)值 #}

    {% if name is string %}

    ??<h2>{{ name }} is a string.</h2>

    {% endif %}

    ?

    {# 檢查數(shù)值是否是偶數(shù),也可以用odd檢查是否為奇數(shù) #}

    {% if 2 is even %}

    ??<h2>Variable is an even number.</h2>

    {% endif %}

    ?

    {# 檢查變量是否可被迭代循環(huán),也可以用sequence檢查是否是序列 #}

    {% if [1,2,3] is iterable %}

    ??<h2>Variable is iterable.</h2>

    {% endif %}

    ?

    {# 檢查變量是否是字典 #}

    {% if {'name':'test'} is mapping %}

    ??<h2>Variable is dict.</h2>

    {% endif %}

    更全的內(nèi)置測試器介紹可以從Jinja2的官方文檔中找到。

    自定義測試器

    如果內(nèi)置測試器不滿足需求,我們就來自己寫一個(gè)。寫法很類似于過濾器,先在Flask應(yīng)用代碼中定義測試器函數(shù),然后通過”add_template_test”將其添加為模板測試器:

    1

    2

    3

    4

    import re

    def has_number(str):

    ????return re.match(r'.*\d+', str)

    app.add_template_test(has_number,'contain_number')

    我們定義了一個(gè)”has_number”函數(shù),用正則來判斷輸入?yún)?shù)是否包含數(shù)字。然后調(diào)用”app.add_template_test”方法,第一個(gè)參數(shù)是測試器函數(shù),第二個(gè)是測試器名稱。之后,我們就可以在模板中使用”contain_number”測試器了:

    1

    2

    3

    {% if name is contain_number %}

    ??<h2>"{{ name }}" contains number.</h2>

    {% endif %}

    同過濾器一樣,Flask提供了添加測試器的裝飾器”template_test”。下面的代碼就添加了一個(gè)判斷字符串是否以某一子串結(jié)尾的測試器。裝飾器的參數(shù)定義了該測試器的名稱”end_with”:

    1

    2

    3

    @app.template_test('end_with')

    def end_with(str, suffix):

    ????return str.lower().endswith(suffix.lower())

    我們在模板中可以這樣使用它:

    1

    2

    3

    {% if name is end_with "me" %}

    ??<h2>"{{ name }}" ends with "me".</h2>

    {% endif %}

    Flask添加測試器的方法是封裝了對Jinja2環(huán)境變量的操作。上述添加”end_with”測試器的方法,等同于下面的代碼。

    1

    app.jinja_env.tests['end_with'] = " end_with "

    我們在Flask應(yīng)用中,不建議直接訪問Jinja2的環(huán)境變量。如果離開Flask環(huán)境直接使用Jinja2的話,就可以通過”jinja2.Environment”來獲取環(huán)境變量,并添加測試器。

    本文中的示例代碼可以在這里下載。

    Flask中Jinja2模板引擎詳解(五)–全局函數(shù)

    介紹完了過濾器和測試器,接下來要講的是Jinja2模板引擎的另一個(gè)輔助函數(shù)功能,即全局函數(shù)Global Functions。如果說過濾器是一個(gè)變量轉(zhuǎn)換函數(shù),測試器是一個(gè)返回布爾值的函數(shù),那全局函數(shù)就可以是任意函數(shù)。可以在任一場景使用,沒有輸入和輸出值的限制。本篇我們就來闡述下這個(gè)全局函數(shù)。

    系列文章

    • Flask中Jinja2模板引擎詳解(一)–控制語句和表達(dá)式
    • Flask中Jinja2模板引擎詳解(二)–上下文環(huán)境
    • Flask中Jinja2模板引擎詳解(三)–過濾器
    • Flask中Jinja2模板引擎詳解(四)–測試器
    • Flask中Jinja2模板引擎詳解(五)–全局函數(shù)
    • Flask中Jinja2模板引擎詳解(六)–塊和宏
    • Flask中Jinja2模板引擎詳解(七)–本地化
    • Flask中Jinja2模板引擎詳解(八)–自定義擴(kuò)展

    全局函數(shù)使用

    還是取出第一篇開篇的代碼,我們在模板中加入下面的代碼:

    1

    2

    3

    4

    5

    <ul>

    {% for num in range(10, 20, 2) %}

    ????<li>Number is "{{ num }}"</li>

    {% endfor %}

    </ul>

    頁面上會顯示”10,12,14,16,18″5個(gè)列表項(xiàng)。全局函數(shù)”range()”的作用同Python里的一樣,返回指定范圍內(nèi)的數(shù)值序列。三個(gè)參數(shù)分別是開始值,結(jié)束值(不包含),間隔。如果只傳兩個(gè)參數(shù),那間隔默認(rèn)為1;如果只傳1個(gè)參數(shù),那開始值默認(rèn)為0。

    由此可見,全局函數(shù)如同其名字一樣,就是全局范圍內(nèi)可以被使用的函數(shù)。其同第二篇介紹的上下文環(huán)境中定義的函數(shù)不同,沒有請求生命周期的限制。

    內(nèi)置全局函數(shù)

    演示幾個(gè)常用的內(nèi)置全局函數(shù)。

    • dict()函數(shù),方便生成字典型變量

    1

    2

    3

    4

    {% set user = dict(name='Mike',age=15) %}

    <p>{{ user | tojson | safe }}</p>

    ?

    {# 顯示 '{"age": 15, "name": "Mike"}' #}

    • joiner()函數(shù),神奇的輔助函數(shù)。它可以初始化為一個(gè)分隔符,然后第一次調(diào)用時(shí)返回空字符串,以后再調(diào)用則返回分隔符。對分隔循環(huán)中的內(nèi)容很有幫助

    1

    2

    3

    4

    5

    6

    {% set sep = joiner("|") %}

    {% for val in range(5) %}

    ????{{ sep() }} <span>{{ val }}</span>

    {% endfor %}

    ?

    {# 顯示 "0 | 1 | 2 | 3 | 4" #}

    • cycler()函數(shù),作用同第一篇介紹的循環(huán)內(nèi)置變量”loop.cycle”類似,在給定的序列中輪循

    1

    2

    3

    4

    5

    6

    7

    {% set cycle = cycler('odd', 'even') %}

    <ul>

    {% for num in range(10, 20, 2) %}

    ????<li class="{{ cycle.next() }}">Number is "{{ num }}",

    ????next line is "{{ cycle.current }}" line.</li>

    {% endfor %}

    </ul>

    基于上一節(jié)的例子,加上”cycler()”函數(shù)的使用,你會發(fā)現(xiàn)列表項(xiàng)<li>的”class”在”odd”和”even”兩個(gè)值間輪循。加入第一篇中的CSS style,就可以看到斑馬線了。

    “cycler()”函數(shù)返回的對象可以做如下操作

      • next(),返回當(dāng)前值,并往下一個(gè)值輪循
      • reset(),重置為第一個(gè)值
      • current,當(dāng)前輪循到的值

    更全的內(nèi)置全局函數(shù)介紹可以從Jinja2的官方文檔中找到。

    自定義全局函數(shù)

    我們當(dāng)然也可以寫自己的全局函數(shù),方法同之前介紹的過濾器啦,測試器啦都很類似。就是將Flask應(yīng)用代碼中定義的函數(shù),通過”add_template_global”將其傳入模板即可:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    import re

    def accept_pattern(pattern_str):

    ????pattern = re.compile(pattern_str, re.S)

    ????def search(content):

    ????????return pattern.findall(content)

    ?

    ????return dict(search=search, current_pattern=pattern_str)

    ?

    app.add_template_global(accept_pattern, 'accept_pattern')

    上例中的accept_pattern函數(shù)會先預(yù)編譯一個(gè)正則,然后返回的字典中包含一個(gè)查詢函數(shù)”search”,之后調(diào)用”search”函數(shù)就可以用編譯好的正則來搜索內(nèi)容了。”app.add_template_global”方法的第一個(gè)參數(shù)是自定義的全局函數(shù),第二個(gè)是全局函數(shù)名稱。現(xiàn)在,讓我們在模板中使用”accept_pattern”全局函數(shù):

    1

    2

    3

    4

    5

    6

    7

    8

    9

    {% with pattern = accept_pattern("<li>(.*?)</li>") %}

    ??{% set founds = pattern.search("<li>Tom</li><li>Bob</li>") %}

    ??<ul>

    ??{% for item in founds %}

    ????<li>Found: {{ item }}</li>

    ??{% endfor %}

    ??</ul>

    ??<p>Current Pattern: {{ pattern.current_pattern }}</p>

    {% endwith %}

    “Tom”和”Bob”被抽取出來了,很牛掰的樣子。你還可以根據(jù)需要在”accept_pattern”的返回字典里定義更多的方法。

    Flask同樣提供了添加全局函數(shù)的裝飾器”template_global”,以方便全局函數(shù)的添加。我們來用它將第二篇中取系統(tǒng)當(dāng)前時(shí)間的函數(shù)”current_time”定義為全局函數(shù)。

    1

    2

    3

    4

    import time

    @app.template_global('end_with')

    def current_time(timeFormat="%b %d, %Y - %H:%M:%S"):

    ????return time.strftime(timeFormat)

    同第二篇中的一樣,我們在模板中可以這樣使用它:

    1

    2

    <p>Current Time is: {{ current_time() }}</p>

    <p>Current Day is: {{ current_time("%Y-%m-%d") }}</p>

    Flask添加全局函數(shù)的方法是封裝了對Jinja2環(huán)境變量的操作。上述添加”current_time”全局函數(shù)的方法,等同于下面的代碼。

    1

    app.jinja_env.globals['current_time'] = current_time

    我們在Flask應(yīng)用中,不建議直接訪問Jinja2的環(huán)境變量。如果離開Flask環(huán)境直接使用Jinja2的話,就可以通過”jinja2.Environment”來獲取環(huán)境變量,并添加全局函數(shù)。

    本文中的示例代碼可以在這里下載。

    Flask中Jinja2模板引擎詳解(六)–塊和宏

    考慮到模板代碼的重用,Jinja2提供了塊 (Block)和宏 (Macro)的功能。塊功能有些類似于C語言中的宏,原理就是代碼替換;而宏的功能有些類似于函數(shù),可以傳入?yún)?shù)。本篇我們就來介紹下塊和宏的用法。

    系列文章

    • Flask中Jinja2模板引擎詳解(一)–控制語句和表達(dá)式
    • Flask中Jinja2模板引擎詳解(二)–上下文環(huán)境
    • Flask中Jinja2模板引擎詳解(三)–過濾器
    • Flask中Jinja2模板引擎詳解(四)–測試器
    • Flask中Jinja2模板引擎詳解(五)–全局函數(shù)
    • Flask中Jinja2模板引擎詳解(六)–塊和宏
    • Flask中Jinja2模板引擎詳解(七)–本地化
    • Flask中Jinja2模板引擎詳解(八)–自定義擴(kuò)展

    塊 (Block)

    在Flask入門系列第三篇介紹模板時(shí),我們提到了模板的繼承。我們在子模板的開頭定義了”{% extend ‘parent.html’ %}”語句來聲明繼承,此后在子模板中由”{% block block_name %}”和”{% endblock %}”所包括的語句塊,將會替換父模板中同樣由”{% block block_name %}”和”{% endblock %}”所包括的部分。

    這就是塊的功能,模板語句的替換。這里要注意幾個(gè)點(diǎn):

  • 模板不支持多繼承,也就是子模板中定義的塊,不可能同時(shí)被兩個(gè)父模板替換。
  • 模板中不能定義多個(gè)同名的塊,子模板和父模板都不行,因?yàn)檫@樣無法知道要替換哪一個(gè)部分的內(nèi)容。
  • 另外,我們建議在”endblock”關(guān)鍵字后也加上塊名,比如”{% endblock block_name %}”。雖然對程序沒什么作用,但是當(dāng)有多個(gè)塊嵌套時(shí),可讀性好很多。

    保留父模板塊的內(nèi)容

    如果父模板中的塊里有內(nèi)容不想被子模板替換怎么辦?我們可以使用”super( )”方法。基于Flask入門系列第三篇的例子,我們將父模板”layout.html”改為:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    <!doctype html>

    <head>

    ????{% block head %}

    ????<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">

    ????<title>{% block title %}{% endblock %}</title>

    ????{% endblock %}

    </head>

    <body>

    ????<div class="page">

    ????{% block body %}

    ????{% endblock %}

    ????</div>

    </body>

    并在子模板里,加上”head”塊和”title”塊:

    1

    2

    3

    4

    5

    6

    7

    {% block title %}Block Sample{% endblock %}

    {% block head %}

    ????{{ super() }}

    ????<style type="text/css">

    ????????h1 { color: #336699; }

    ????</style>

    {% endblock %}

    父模板同子模板的”head”塊中都有內(nèi)容。運(yùn)行后,你可以看到,父模板中的”head”塊語句先被加載,而后是子模板中的”head”塊語句。這就得益于我們在子模板的”head”塊中加上了表達(dá)式”{{ super( ) }}”。效果有點(diǎn)像Java中的”super( )”吧。

    ?

    塊內(nèi)語句的作用域

    默認(rèn)情況下,塊內(nèi)語句是無法訪問塊外作用域中的變量。比如我們在”layout.html”加上一個(gè)循環(huán):

    1

    2

    3

    ????{% for item in range(5) %}

    ????????<li>{% block list %}{% endblock %}</li>

    ????{% endfor %}

    然后在子模板中定義”list”塊并訪問循環(huán)中的”item”變量:

    1

    2

    3

    {% block list %}

    ????<em>{{ item }}</em>

    {% endblock %}

    你會發(fā)現(xiàn)頁面上什么數(shù)字也沒顯示。如果你想在塊內(nèi)訪問這個(gè)塊外的變量,你就需要在塊聲明時(shí)添加”scoped”關(guān)鍵字。比如我們在”layout.html”中這樣聲明”list”塊即可:

    1

    2

    3

    ????{% for item in range(5) %}

    ????????<li>{% block list scoped %}{% endblock %}</li>

    ????{% endfor %}

    宏 (Macro)

    文章的開頭我們就講過,Jinja2的宏功能有些類似于傳統(tǒng)程序語言中的函數(shù),既然是函數(shù)就有其聲明和調(diào)用兩個(gè)部分。那就讓我們先聲明一個(gè)宏:

    1

    2

    3

    {% macro input(name, type='text', value='') -%}

    ????<input type="{{ type }}" name="{{ name }}" value="{{ value|e }}">

    {%- endmacro %}

    代碼中,宏的名稱就是”input”,它有三個(gè)參數(shù)分別是”name”, “type”和”value”,后兩個(gè)參數(shù)有默認(rèn)值。現(xiàn)在,讓我們使用表達(dá)式來調(diào)用這個(gè)宏:

    1

    2

    3

    <p>{{ input('username', value='user') }}</p>

    <p>{{ input('password', 'password') }}</p>

    <p>{{ input('submit', 'submit', 'Submit') }}</p>

    大家可以在頁面上看到一個(gè)文本輸入框,一個(gè)密碼輸入框及一個(gè)提交按鈕。是不是同函數(shù)一樣啊?其實(shí)它還有比函數(shù)更豐富的功能,之后我們來介紹。

    訪問調(diào)用者內(nèi)容

    我們先來創(chuàng)建個(gè)宏”list_users”:

    1

    2

    3

    4

    5

    6

    7

    8

    {% macro list_users(users) -%}

    ??<table>

    ????<tr><th>Name</th><th>Action</th></tr>

    ????{%- for user in users %}

    ??????<tr><td>{{ user.name |e }}</td>{{ caller() }}</tr>

    ????{%- endfor %}

    ??</table>

    {%- endmacro %}

    宏的作用就是將用戶列表顯示在表格里,表格每一行用戶名稱后面調(diào)用了”{{ caller( ) }}”方法,這個(gè)有什么用呢?先別急,我們來寫調(diào)用者的代碼:

    1

    2

    3

    4

    5

    6

    7

    8

    {% set users=[{'name':'Tom','gender':'M','age':20},

    ??????????????{'name':'John','gender':'M','age':18},

    ??????????????{'name':'Mary','gender':'F','age':24}]

    %}

    ?

    {% call list_users(users) %}

    ????<td><input name="delete" type="button" value="Delete"></td>

    {% endcall %}

    與上例不同,這里我們使用了”{% call %}”語句塊來調(diào)用宏,語句塊中包括了一段生成”Delete”按鈕的代碼。運(yùn)行下試試,你會發(fā)現(xiàn)每個(gè)用戶名后面都出現(xiàn)了”Delete”按鈕,也就是”{{ caller( ) }}”部分被調(diào)用者”{% call %}”語句塊內(nèi)部的內(nèi)容替代了。不明覺厲吧!其實(shí)吧,這個(gè)跟函數(shù)傳個(gè)參數(shù)進(jìn)去沒啥大區(qū)別,個(gè)人覺得,主要是有些時(shí)候HTML語句太復(fù)雜(如上例),不方便寫在調(diào)用參數(shù)上,所以就寫在”{% call %}”語句塊里了。

    Jinja2的宏不但能訪問調(diào)用者語句塊的內(nèi)容,還能給調(diào)用者傳遞參數(shù)。嚯,這又是個(gè)什么鬼?我們來擴(kuò)展下上面的例子。首先,我們將表格增加一列性別,并在宏里調(diào)用”caller()”方法時(shí),傳入一個(gè)變量”user.gender”:

    1

    2

    3

    4

    5

    6

    7

    8

    {% macro list_users(users) -%}

    ??<table>

    ????<tr><th>Name</th><th>Gender</th><th>Action</th></tr>

    ????{%- for user in users %}

    ??????<tr><td>{{ user.name |e }}</td>{{ caller(user.gender) }}</tr>

    ????{%- endfor %}

    ??</table>

    {%- endmacro %}

    然后,我們修改下調(diào)用者語句塊:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    {% call(gender) list_users(users) %}

    ????<td>

    ????{% if gender == 'M' %}

    ????<img src="{{ url_for('static', filename='img/male.png') }}" width="20px">

    ????{% else %}

    ????<img src="{{ url_for('static', filename='img/female.png') }}" width="20px">

    ????{% endif %}

    ????</td>

    ????<td><input name="delete" type="button" value="Delete"></td>

    {% endcall %}

    大家注意到,我們在使用”{% call %}”語句時(shí),將其改為了”{% call(gender) … %}”,這個(gè)括號中的”gender”就是用來接受宏里傳來的”user.gender”變量。因此我們就可以在”{% call %}”語句中使用這個(gè)”gender”變量來判斷用戶性別。這樣宏就成功地向調(diào)用者傳遞了參數(shù)。

    ?

    宏的內(nèi)部變量

    上例中,我們看到宏的內(nèi)部可以使用”caller( )”方法獲取調(diào)用者的內(nèi)容。此外宏還提供了兩個(gè)內(nèi)部變量:

    • varargs

    這是一個(gè)列表。如果調(diào)用宏時(shí)傳入的參數(shù)多于宏聲明時(shí)的參數(shù),多出來的沒指定參數(shù)名的參數(shù)就會保存在這個(gè)列表中。

    • kwargs

    這是一個(gè)字典。如果調(diào)用宏時(shí)傳入的參數(shù)多于宏聲明時(shí)的參數(shù),多出來的指定了參數(shù)名的參數(shù)就會保存在這個(gè)字典中。

    讓我們回到第一個(gè)例子input宏,在調(diào)用時(shí)增加其傳入的參數(shù),并在宏內(nèi)將上述兩個(gè)變量打印出來:

    1

    2

    3

    4

    5

    6

    {% macro input(name, type='text', value='') -%}

    ????<input type="{{ type }}" name="{{ name }}" value="{{ value|e }}">

    ????<br /> {{ varargs }}

    ????<br /> {{ kwargs }}

    {%- endmacro %}

    <p>{{ input('submit', 'submit', 'Submit', 'more arg1', 'more arg2', ext='more arg3') }}</p>

    可以看到,varargs變量存了參數(shù)列表”[‘more arg1’, ‘more arg2’]”,而kwargs字典存了參數(shù)”{‘ext’:’more arg3′}”。

    宏的導(dǎo)入

    一個(gè)宏可以被不同的模板使用,所以我們建議將其聲明在一個(gè)單獨(dú)的模板文件中。需要使用時(shí)導(dǎo)入進(jìn)來即可,而導(dǎo)入的方法也非常類似于Python中的”import”。讓我們將第一個(gè)例子中”input”宏的聲明放到一個(gè)”form.html”模板文件中,然后將調(diào)用的代碼改為:

    1

    2

    3

    4

    {% import 'form.html' as form %}

    <p>{{ form.input('username', value='user') }}</p>

    <p>{{ form.input('password', 'password') }}</p>

    <p>{{ form.input('submit', 'submit', 'Submit') }}</p>

    運(yùn)行下,效果是不是同之前的一樣?你也可以采用下面的方式導(dǎo)入:

    1

    2

    3

    4

    {% from 'form.html' import input %}

    <p>{{ input('username', value='user') }}</p>

    <p>{{ input('password', 'password') }}</p>

    <p>{{ input('submit', 'submit', 'Submit') }}</p>

    包含 (Include)

    這里我們再介紹一個(gè)Jinja2模板中代碼重用的功能,就是包含 (Include),使用的方法就是”{% include %}”語句。其功能就是將另一個(gè)模板加載到當(dāng)前模板中,并直接渲染在當(dāng)前位置上。它同導(dǎo)入”import”不一樣,”import”之后你還需要調(diào)用宏來渲染你的內(nèi)容,”include”是直接將目標(biāo)模板渲染出來。它同block塊繼承也不一樣,它一次渲染整個(gè)模板文件內(nèi)容,不分塊。

    我們可以創(chuàng)建一個(gè)”footer.html”模板,并在”layout.html”中包含這個(gè)模板:

    1

    2

    3

    4

    <body>

    ????...

    ????{% include 'footer.html' %}

    </body>

    當(dāng)”include”的模板文件不存在時(shí),程序會拋出異常。你可以加上”ignore missing”關(guān)鍵字,這樣如果模板不存在,就會忽略這段”{% include %}”語句。

    1

    ????{% include 'footer.html' ignore missing %}

    “{% include %}”語句還可以跟一個(gè)模板列表:

    1

    ????{% include ['footer.html','bottom.html','end.html'] ignore missing %}

    上例中,程序會按順序?qū)ふ夷0逦募?#xff0c;第一個(gè)被找到的模板即被加載,而其后的模板都會被忽略。如果都沒找到,那整個(gè)語句都會被忽略。

    本篇中的示例代碼可以在這里下載。

    Flask中Jinja2模板引擎詳解(七)–本地化

    一個(gè)強(qiáng)大的工具一般都支持?jǐn)U展或插件的開發(fā)功能,來允許第三方通過開發(fā)新擴(kuò)展或插件,擴(kuò)充工具本身功能,并可以貢獻(xiàn)給社區(qū)。Jinja2也不例外,Jinja2本身提供了一部分?jǐn)U展,你可以在程序中啟用。同時(shí),你還可以創(chuàng)建自己的擴(kuò)展,來擴(kuò)充模板引擎功能。本篇會先介紹Jinja2自帶的擴(kuò)展”jinja2.ext.i18n”的使用,自定義擴(kuò)展的開發(fā)會放在下一篇闡述。

    系列文章

    • Flask中Jinja2模板引擎詳解(一)–控制語句和表達(dá)式
    • Flask中Jinja2模板引擎詳解(二)–上下文環(huán)境
    • Flask中Jinja2模板引擎詳解(三)–過濾器
    • Flask中Jinja2模板引擎詳解(四)–測試器
    • Flask中Jinja2模板引擎詳解(五)–全局函數(shù)
    • Flask中Jinja2模板引擎詳解(六)–塊和宏
    • Flask中Jinja2模板引擎詳解(七)–本地化
    • Flask中Jinja2模板引擎詳解(八)–自定義擴(kuò)展

    在Flask中啟用Jinja2擴(kuò)展

    任何時(shí)候使用Jinja2時(shí),都需要先創(chuàng)建Jinja2環(huán)境,所以啟用擴(kuò)展的方法就是在創(chuàng)建環(huán)境時(shí)指定:

    1

    2

    from jinja2 import Environment

    jinja_env = Environment(extensions=['jinja2.ext.i18n','jinja2.ext.do'])

    但是你在使用Flask時(shí),其已經(jīng)有了一個(gè)Jinja2環(huán)境,你不能再創(chuàng)建一個(gè),所以你需要想辦法添加擴(kuò)展。Flask對于擴(kuò)展不像過濾器或測試器那樣封裝了添加方法和裝飾器,這樣你就只能直接訪問Flask中的Jinja2環(huán)境變量來添加。

    1

    2

    3

    4

    from flask import Flask

    app = Flask(__name__)

    app.jinja_env.add_extension('jinja2.ext.i18n')

    app.jinja_env.add_extension('jinja2.ext.do')

    注:Flask默認(rèn)已加載了”jinja2.ext.autoescape”和”jinja2.ext.with_”擴(kuò)展。

    Jinja2內(nèi)置擴(kuò)展

    在本系列第一篇中,我們已經(jīng)介紹了四個(gè)Jinja2內(nèi)置擴(kuò)展的使用:”jinja2.ext.autoescape”, “jinja2.ext.with_”, “jinja2.ext.do”和”jinja2.ext.loopcontrols”。除了這幾個(gè)以外,Jinja2還有一個(gè)非常重要的擴(kuò)展,就是提供本地化功能的”jinja2.ext.i18n”。它可以與”gettext”或”babel”聯(lián)合使用,接下來我們采用”gettext”來介紹怎么使用這個(gè)本地化擴(kuò)展。

    注:本地化這個(gè)其實(shí)就是翻譯頁面語言,這個(gè)詳看Flask-Babel

    創(chuàng)建本地化翻譯文件

    建議大家先去了解下Python gettext相關(guān)知識,篇幅關(guān)系本文就不準(zhǔn)備細(xì)講。這里我們使用Python源代碼(記住不是安裝包)中”Tools/i18n”目錄下的工具來創(chuàng)建翻譯文件。

  • 首先我們生成翻譯文件模板,在”Tools/i18n”目錄中找到”pygettext.py”并運(yùn)行
  • $ python pygettext.py

    上述命令會在當(dāng)前目錄下生成一個(gè)名為”message.pot”的翻譯文件模板,內(nèi)容如下:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    # SOME DESCRIPTIVE TITLE.

    # Copyright (C) YEAR ORGANIZATION

    # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.

    #

    msgid ""

    msgstr ""

    "Project-Id-Version: PACKAGE VERSION\n"

    "POT-Creation-Date: 2016-02-22 21:45+CST\n"

    "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"

    "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"

    "Language-Team: LANGUAGE <LL@li.org>\n"

    "MIME-Version: 1.0\n"

    "Content-Type: text/plain; charset=CHARSET\n"

    "Content-Transfer-Encoding: ENCODING\n"

    "Generated-By: pygettext.py 1.5\n"

  • 將”message.pot”中”CHARSET”和”ENCODING”替換成”UTF-8″。同時(shí)你可以更改注釋信息
  • 1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    # Jinja2 i18n Extention Sample

    # Copyright (C) 2016 bjhee.com

    # Billy J. Hee <billy@bjhee.com>, 2016.

    #

    msgid ""

    msgstr ""

    "Project-Id-Version: PACKAGE VERSION\n"

    "POT-Creation-Date: 2016-02-22 21:45+CST\n"

    "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"

    "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"

    "Language-Team: LANGUAGE <LL@li.org>\n"

    "MIME-Version: 1.0\n"

    "Content-Type: text/plain; charset=UTF-8\n"

    "Content-Transfer-Encoding: UTF-8\n"

    "Generated-By: pygettext.py 1.5\n"

    修改完后,將其另存為翻譯文件”lang.po”。

  • 在”lang.po”中添加你要翻譯的文字,比如
  • 1

    2

    msgid "Hello World!"

    msgstr "世界,你好!"

    將其加在文件末尾。這里”msgid”指定了待翻譯的文字,而”msgstr”就是翻譯后的文字。

  • 生成”lang.mo”文件
  • 我們依然使用”Tools/i18n”目錄提供的工具,”msgfmt.py”:

    $ python msgfmt.py lang.po

    執(zhí)行完后,當(dāng)前目錄生成了”lang.mo”文件。注意,只有這個(gè)”*.mo”文件才能被應(yīng)用程序識別。另外,推薦一個(gè)工具Poedit,很強(qiáng)的圖形化po編輯工具,也可以用來生成mo文件,非常好用,Mac和Windows下都能用。

  • 將po, mo文件加入應(yīng)用
  • 我們在當(dāng)前Flask工程下創(chuàng)建子目錄”locale/zh_CN/LC_MESSAGES/”,并將剛才生成的”lang.po”和”lang.mo”文件放到這個(gè)目錄下。這里”locale”子目錄的名字可以更改,其他的個(gè)人建議不要改。

    在模板中使用本地化

    讓我們在Flask應(yīng)用代碼中啟用”jinja2.ext.i18n”,并加載剛創(chuàng)建的翻譯文件。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    #coding:utf8

    import gettext

    from flask import Flask,render_template

    app = Flask(__name__)

    # 加載擴(kuò)展

    app.jinja_env.add_extension('jinja2.ext.i18n')

    ?

    # 'lang'表示翻譯文件名為"lang.mo",'locale'表示所有翻譯文件都在"locale"子目錄下,

    # 'zh_CN'表示二級子目錄,含義上講就是加載中文翻譯。所以下面的代碼會加載文件:

    # "locale/zh_CN/LC_MESSAGES/lang.mo"

    gettext.install('lang', 'locale', unicode=True)

    translations = gettext.translation('lang', 'locale', languages=['zh_CN'])

    translations.install(True)

    app.jinja_env.install_gettext_translations(translations)

    這個(gè)”install_gettext_translations()”方法就是Jinja2提供來加載”gettext”翻譯文件對象的。加載完后,你就可以在模板中使用本地化功能了。方法有兩種:”{% trans %}”語句或”gettext()”方法。我們先來試下”{% trans %}”語句,在模板文件中,我們加上:

    1

    ????<h1>{% trans??%}Hello World!{% endtrans %}</h1>

    運(yùn)行下,有沒有看到頁面上打印了”世界,你好!”,恭喜你,成功了!使用”gettext()”方法如下,效果同”{% trans %}”語句一樣。

    1

    ????<h1>{{ gettext('Hello World!') }}</h1>

    Jinja2還提供了”_( )”方法來替代”gettext( )”,代碼看起來很簡潔,個(gè)人推薦使用這個(gè)方法。

    1

    ????<h1>{{ _('Hello World!') }}</h1>

    上面的例子是在程序中指定本地化語言,你也可以在請求上下文中判斷請求頭”Accept-Language”的內(nèi)容,來動態(tài)的設(shè)置本地化語言。

    翻譯內(nèi)容帶參數(shù)

    有時(shí)候,待翻譯的文字內(nèi)有一個(gè)變量必須在運(yùn)行時(shí)才能確定,怎么辦?我可以在翻譯文字上加參數(shù)。首先,你要在po文件中定義帶參數(shù)的翻譯文字,并生成mo文件:

    1

    2

    msgid "Hello %(user)s!"

    msgstr "%(user)s,你好!"

    然后,你就可以在模板中,使用”{% trans %}”語句或”gettext()”方法來顯示它:

    1

    2

    ????<h1>{% trans user=name %}Hello {{ user }}!{% endtrans %}</h1>

    ????<h1>{{ _('Hello %(user)s!')|format(user=name) }}</h1>

    上例中,我們把模板中的變量”name”賦給了翻譯文字中的變量”user”。翻譯文字上可以有多個(gè)變量。

    新樣式 (Newstyle)

    Jinja2從2.5版本開始,支持新的gettext樣式,使得帶參數(shù)的本地化更簡潔,上面的例子在新樣式中可以寫成:

    1

    ????<h1>{{ _('Hello %(user)s!', user=name) }}</h1>

    不過使用新樣式前,你必須先啟用它。還記得我們介紹過Jinja2加載翻譯文件的方法嗎?對,就是”install_gettext_translations()”。調(diào)用它時(shí),加上”newstyle=True”參數(shù)即可。

    1

    app.jinja_env.install_gettext_translations(translations, newstyle=True)

    單/復(fù)數(shù)支持

    英文有個(gè)特點(diǎn)就是名詞有單/復(fù)數(shù)形式,一般復(fù)數(shù)都是單數(shù)后面加s,而中文就不區(qū)分了,哎,老外就是麻煩。所謂外國人創(chuàng)造的Python gettext,自然也對單/復(fù)數(shù)提供了特殊的支持。讓我們現(xiàn)在po文件中,加上下面的內(nèi)容,并生成mo文件:

    1

    2

    3

    4

    msgid "%(num)d item"

    msgid_plural "%(num)d items"

    msgstr[0] "%(num)d個(gè)物品"

    msgstr[1] "%(num)d個(gè)物品集"

    什么意思呢,這個(gè)”msgid_plural”就是指定了它上面”msgid”文字的復(fù)數(shù)形式。而”msgstr”的[0], [1]分別對應(yīng)了單/復(fù)數(shù)形式翻譯后的內(nèi)容。為什么這么寫?你別管了,照著寫就是了。

    在模板中,我們加上下面的代碼:

    1

    2

    3

    ??{% set items = [1,2,3,4,5] %}

    ??{{ ngettext('%(num)d item', '%(num)d items', items|count) }}<br />

    ??{{ ngettext('%(num)d item', '%(num)d items', items|first) }}

    你會很驚奇的發(fā)現(xiàn),當(dāng)”num”變量為5時(shí),頁面顯示”5個(gè)物品集”;而當(dāng)”num”變量為1時(shí),頁面顯示”1個(gè)物品”。也就是程序自動匹配單/復(fù)數(shù)。很神奇吧!

    本來準(zhǔn)備在本篇把擴(kuò)展都介紹完的,發(fā)現(xiàn)單寫個(gè)”i18n”后篇幅就很長了,只好把自定義擴(kuò)展部分另起一篇。

    本篇中的示例代碼可以在這里下載。

    Flask中Jinja2模板引擎詳解(八)–自定義擴(kuò)展

    說實(shí)話,關(guān)于自定義擴(kuò)展的開發(fā),Jinja2的官方文檔寫得真心的簡單。到目前為止網(wǎng)上可參考的資料也非常少,你必須得好好讀下源碼,還好依然有樂于奉獻(xiàn)的大牛們分享了些文章來幫助我理解怎么開發(fā)擴(kuò)展。本文我就完全借鑒網(wǎng)上前人的例子,來給大家演示一個(gè)Jinja2的自定義擴(kuò)展的開發(fā)方法。

    系列文章

    • Flask中Jinja2模板引擎詳解(一)–控制語句和表達(dá)式
    • Flask中Jinja2模板引擎詳解(二)–上下文環(huán)境
    • Flask中Jinja2模板引擎詳解(三)–過濾器
    • Flask中Jinja2模板引擎詳解(四)–測試器
    • Flask中Jinja2模板引擎詳解(五)–全局函數(shù)
    • Flask中Jinja2模板引擎詳解(六)–塊和宏
    • Flask中Jinja2模板引擎詳解(七)–本地化
    • Flask中Jinja2模板引擎詳解(八)–自定義擴(kuò)展

    Pygments

    Pygments是Python提供語法高亮的工具,官網(wǎng)是pygments.org。我們在介紹Jinja2的自定義擴(kuò)展時(shí)為什么要介紹Pygments呢?因?yàn)镴inja2的功能已經(jīng)很強(qiáng)了,我一時(shí)半會想不出該開發(fā)哪個(gè)有用的擴(kuò)展,寫個(gè)沒意義的擴(kuò)展嘛,又怕誤導(dǎo)了讀者。恰巧網(wǎng)上找到了一位叫Larry的外國友人開發(fā)了一個(gè)基于Pygments的代碼語法高亮擴(kuò)展,感覺非常實(shí)用。他的代碼使用了MIT License,那就我放心拿過來用了,不過還是要注明下這位Larry才是原創(chuàng)。

    你需要先執(zhí)行”pip install pygments”命令安裝Pygments包。代碼中用到Pygments的部分非常簡單,主要就是調(diào)用”pygments.highlight( )”方法來生成HTML文檔。Pygments強(qiáng)的地方是它不把樣式寫在HTML當(dāng)中,這樣就給了我們很大的靈活性。開始寫擴(kuò)展前,讓我們預(yù)先通過代碼

    1

    2

    from pygments.formatters import HtmlFormatter

    HtmlFormatter(style='vim').get_style_defs('.highlight')

    生成樣式內(nèi)容并將其保存在”static/css/style.css”文件中。這個(gè)css文件就是用來高亮語法的。

    想深入了解Pygments的朋友們,可以先把官方文檔看一下。

    編寫擴(kuò)展

    我們在Flask應(yīng)用目錄下,創(chuàng)建一個(gè)”pygments_ext.py”文件,內(nèi)容如下:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    45

    46

    47

    48

    49

    50

    51

    52

    53

    54

    55

    56

    57

    58

    59

    60

    61

    62

    63

    64

    65

    66

    67

    68

    69

    70

    71

    72

    73

    74

    75

    76

    77

    78

    #coding:utf8

    from jinja2 import nodes

    from jinja2.ext import Extension

    ?

    from pygments import highlight

    from pygments.formatters import HtmlFormatter

    from pygments.lexers import guess_lexer, get_lexer_by_name

    ?

    # 創(chuàng)建一個(gè)自定義擴(kuò)展類,繼承jinja2.ext.Extension

    class PygmentsExtension(Extension):

    ????# 定義該擴(kuò)展的語句關(guān)鍵字,這里表示模板中的{% code %}語句會該擴(kuò)展處理

    ????tags = set(['code'])

    ?

    ????def __init__(self, environment):

    ????????# 初始化父類,必須這樣寫

    ????????super(PygmentsExtension, self).__init__(environment)

    ?

    ????????# 在Jinja2的環(huán)境變量中添加屬性,

    ????????# 這樣在Flask中,就可以用app.jinja_env.pygments來訪問

    ????????environment.extend(

    ????????????pygments=self,

    ????????????pygments_support=True

    ????????)

    ?

    ????# 重寫jinja2.ext.Extension類的parse函數(shù)

    ????# 這是處理模板中{% code %}語句的主程序

    ????def parse(self, parser):

    ????????# 進(jìn)入此函數(shù)時(shí),即表示{% code %}標(biāo)簽被找到了

    ????????# 下面的代碼會獲取當(dāng)前{% code %}語句在模板文件中的行號

    ????????lineno = next(parser.stream).lineno

    ?

    ????????# 獲取{% code %}語句中的參數(shù),比如我們調(diào)用{% code 'python' %},

    ????????# 這里就會返回一個(gè)jinja2.nodes.Const類型的對象,值為'python'

    ????????lang_type = parser.parse_expression()

    ?

    ????????# 將參數(shù)封裝為列表

    ????????args = []

    ????????if lang_type is not None:

    ????????????args.append(lang_type)

    ?

    ????????????# 下面的代碼可以支持兩個(gè)參數(shù),參數(shù)之間用逗號分隔,不過本例中用不到

    ????????????# 這里先檢查當(dāng)前處理流的位置是不是個(gè)逗號,是的話就再獲取一個(gè)參數(shù)

    ????????????# 不是的話,就在參數(shù)列表最后加個(gè)空值對象

    ????????????# if parser.stream.skip_if('comma'):

    ????????????#???? args.append(parser.parse_expression())

    ????????????# else:

    ????????????#???? args.append(nodes.Const(None))

    ?

    ????????# 解析從{% code %}標(biāo)志開始,到{% endcode %}為止中間的所有語句

    ????????# 將解析完后的內(nèi)容存在body里,并將當(dāng)前流位置移到{% endcode %}之后

    ????????body = parser.parse_statements(['name:endcode'],drop_needle=True)

    ?

    ????????# 返回一個(gè)CallBlock類型的節(jié)點(diǎn),并將其之前取得的行號設(shè)置在該節(jié)點(diǎn)中

    ????????# 初始化CallBlock節(jié)點(diǎn)時(shí),傳入我們自定義的"_pygmentize"方法的調(diào)用,

    ????????# 兩個(gè)空列表,還有剛才解析后的語句內(nèi)容body

    ????????return nodes.CallBlock(self.call_method('_pygmentize', args),

    ????????????????????????????????[], [], body).set_lineno(lineno)

    ?

    ????# 這個(gè)自定義的內(nèi)部函數(shù),包含了本擴(kuò)展的主要邏輯。

    ????# 其實(shí)上面parse()函數(shù)內(nèi)容,大部分?jǐn)U展都可以重用

    ????def _pygmentize(self, lang_type, caller):

    ????????# 初始化HTML格式器

    ????????formatter = HtmlFormatter(linenos='table')

    ?

    ????????# 獲取{% code %}語句中的內(nèi)容

    ????????# 這里caller()對應(yīng)了上面調(diào)用CallBlock()時(shí)傳入的body

    ????????content = caller()

    ?

    ????????# 將模板語句中解析到了lang_type設(shè)置為我們要高亮的語言類型

    ????????# 如果這個(gè)變量不存在,則讓Pygmentize猜測可能的語言類型

    ????????lexer = None

    ????????if lang_type is None:

    ????????????lexer = guess_lexer(content)

    ????????else:

    ????????????lexer = get_lexer_by_name(lang_type)

    ?

    ????????# 將{% code %}語句中的內(nèi)容高亮,即添加各種<span>, class等標(biāo)簽屬性

    ????????return highlight(content, lexer, formatter)

    這段程序解釋起來太麻煩,我就把注釋都寫在代碼里了。總的來說,擴(kuò)展中核心部分就在”parse()”函數(shù)里,而最關(guān)鍵的就是這個(gè)”parser”對象,它是一個(gè)”jinja2.parser.Parser”的對象。建議大家可以參考下它的源碼。我們使用的主要方法有:

      • parser.stream 獲取當(dāng)前的文檔處理流,它可以基于文檔中的行迭代,所以可以使用”next()”方法向下一行前進(jìn),并返回當(dāng)前行
      • parser.parse_expression() 解析下一個(gè)表達(dá)式,并將結(jié)果返回
      • parser.parse_statements() 解析下一段語句,并將結(jié)果返回。可以連續(xù)解析多行。它有兩個(gè)參數(shù)
  • 第一個(gè)是結(jié)束位置”end_tokens”,上例中是”{% endcode %}”標(biāo)簽,它是個(gè)列表,可是設(shè)置多個(gè)結(jié)束標(biāo)志,遇到其中任意一個(gè)即結(jié)束
  • 第二個(gè)是布爾值”drop_needle”,默認(rèn)為False,即解析完后流的當(dāng)前位置指向結(jié)束語句”{% endcode %}”之前。設(shè)為True時(shí),即將流的當(dāng)前位置設(shè)在結(jié)束語句之后
  • 在”parse()”函數(shù)最后,我們創(chuàng)建了一個(gè)”nodes.CallBlock”的塊節(jié)點(diǎn)對象,并將其返回。初始化時(shí),我們先傳入了”_pygmentize()”方法的調(diào)用;然后兩個(gè)空列表分別對應(yīng)了字段和屬性,本例中用不到,所以設(shè)空;再傳入解析后的語句塊”body”。”CallBlock”節(jié)點(diǎn)初始化完后,還要記得將當(dāng)前行號設(shè)置進(jìn)去。接下來,我們對于語句塊的所有操作,都可以寫在”_pygmentize()”方法里了。

    “_pygmentize()”里的內(nèi)容我就不多介紹了,只需要記得聲明這個(gè)方法時(shí),最后一定要接收一個(gè)參數(shù)caller,它是個(gè)回調(diào)函數(shù),可以獲取之前創(chuàng)建”CallBlock”節(jié)點(diǎn)時(shí)傳入的語句塊內(nèi)容。

    使用自定義擴(kuò)展

    擴(kuò)展寫完了,其實(shí)也沒幾行代碼,就是注釋多了點(diǎn)。現(xiàn)在我們在Flask應(yīng)用代碼中將其啟用:

    1

    2

    3

    4

    5

    from flask import Flask,render_template

    from pygments_ext import PygmentsExtension

    ?

    app = Flask(__name__)

    app.jinja_env.add_extension(PygmentsExtension)

    然后讓我們在模板中試一下:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    <head>

    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">

    </head>

    <body>

    <p>A sample of JS code</p>

    {% autoescape false %}

    {% code 'javascript' %}

    ????var name = 'World';

    ????function foo() {

    ????????console.log('Hello ' + name);

    ????}

    {% endcode %}

    {% endautoescape %}

    </body>

    運(yùn)行下,頁面上這段代碼是不是有VIM的效果呀?這里我們引入了剛才創(chuàng)建在”static/css”目錄下”style.css”樣式文件,另外千萬別忘了要將自動轉(zhuǎn)義關(guān)掉,不然你會看到一堆的HTML標(biāo)簽。

    另外提醒下大家,網(wǎng)上有文章說,對于單條語句,也就是不需要結(jié)束標(biāo)志的語句,”parse()”函數(shù)里無需調(diào)用”nodes.CallBlock”,只需返回”return self.call_method(xxx)”即可。別相信他,看看源碼就知道,這個(gè)方法返回的是一個(gè)”nodes.Expr”表達(dá)式對象,而”parse()”必須返回一個(gè)”nodes.Stmt”語句對象。

    本篇中的示例代碼可以在這里下載。

    ?

    轉(zhuǎn)載于:https://www.cnblogs.com/sanduzxcvbnm/p/9339759.html

    總結(jié)

    以上是生活随笔為你收集整理的Flask入门系列(转载)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。