Flask入门系列(转载)
一、入門系列:
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”映入眼簾:)。
簡單解釋下這段代碼
| 1 2 | from flask import Flask app = Flask(__name__) |
這里給的實(shí)例名稱就是這個(gè)python模塊名。
| 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”。
| 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):
另外,我們建議在”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)建翻譯文件。
上述命令會在當(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" |
| 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”。
| 1 2 | msgid "Hello World!" msgstr "世界,你好!" |
將其加在文件末尾。這里”msgid”指定了待翻譯的文字,而”msgstr”就是翻譯后的文字。
我們依然使用”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下都能用。
我們在當(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ù)
在”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)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mock 生成在线图片
- 下一篇: 知识图谱学习--网易云唐宇迪老师课程记录