Flask之勾子,錯誤捕獲以及模板語法
在客戶端和服務器交互的過程中,有些準備工作或掃尾工作需要處理,比如:
-
在請求開始時,建立數據庫連接;
-
在請求開始時,根據需求進行權限校驗;
-
在請求結束時,指定數據的交互格式;
為了讓每個視圖函數避免編寫重復功能的代碼,Flask提供了通用設施的功能,即請求鉤子。
請求鉤子是通過裝飾器的形式實現,Flask支持如下四種請求鉤子:
-
before_first_request
-
在處理第一個請求前執行
-
-
before_request
-
在每次請求前執行
-
如果在某修飾的函數中返回了一個響應,視圖函數將不再被調用
-
-
after_request
-
如果沒有拋出錯誤,在每次請求後執行
-
接受一個參數:視圖函數作出的響應
-
在此函數中可以對響應值在返回之前做最後一步修改處理
-
需要將參數中的響應在此參數中進行返回
-
-
teardown_request:
-
在每次請求後執行
-
接受一個參數:錯誤信息,如果有相關錯誤拋出
-
代碼
1 from flask import Flask 2 from settings.dev import DevConfig 3 ? 4 app = Flask(__name__) 5 # 項目配置 6 app.config.from_object(DevConfig) 7 ? 8 @app.before_first_request 9 def before_first_request(): 10 print("----before_first_request----") 11 print("系統初始化的時候,執行這個鉤子方法") 12 print("會在接收到第一個客戶端請求時,執行這裏的代碼") 13 ? 14 @app.before_request 15 def before_request(): 16 print("----before_request----") 17 print("每一次接收到客戶端請求時,執行這個鉤子方法") 18 print("一般可以用來判斷權限,或者轉換路由參數或者預處理客戶端請求的數據") 19 ? 20 @app.after_request 21 def after_request(response): 22 print("----after_request----") 23 print("在處理請求以後,執行這個鉤子方法") 24 print("一般可以用於記錄會員/管理員的操作歷史,瀏覽歷史,清理收尾的工作") 25 response.headers["Content-Type"] = "application/json" 26 # 必須返回response參數 27 return response 28 ? 29 ? 30 @app.teardown_request 31 def teardown_request(exc): 32 print("----teardown_request----") 33 print("在每一次請求以後,執行這個鉤子方法,如果有異常錯誤,則會傳遞錯誤異常對象到當前方法的參數中") 34 print(exc) 35 ? 36 @app.route("/") 37 def index(): 38 print("----視圖函數----") 39 print("視圖函數被運行了") 40 return "視圖函數被運行了<br>" 41 ? 42 if __name__ == ‘__main__‘: 43 app.run(host="0.0.0.0", port=80) 44 在第1次請求時的打印: 45 46 ----before_first_request---- 47 系統初始化的時候,執行這個鉤子方法 48 會在接收到第一個客戶端請求時,執行這裏的代碼 49 ----before_request---- 50 每一次接收到客戶端請求時,執行這個鉤子方法 51 一般可以用來判斷權限,或者轉換路由參數或者預處理客戶端請求的數據 52 ----視圖函數---- 53 視圖函數被運行了 54 ----after_request---- 55 在處理請求以後,執行這個鉤子方法 56 一般可以用於記錄會員/管理員的操作歷史,瀏覽歷史,清理收尾的工作 57 ----teardown_request---- 58 在每一次請求以後,執行這個鉤子方法,如果有異常錯誤,則會傳遞錯誤異常對象到當前方法的參數中 59 None 60 在第2次請求時的打印: 61 62 ----before_request---- 63 127.0.0.1 - - [08/Apr/2019 09:23:53] "GET / HTTP/1.1" 200 - 64 每一次接收到客戶端請求時,執行這個鉤子方法 65 一般可以用來判斷權限,或者轉換路由參數或者預處理客戶端請求的數據 66 ----視圖函數---- 67 視圖函數被運行了 68 ----after_request---- 69 在處理請求以後,執行這個鉤子方法 70 一般可以用於記錄會員/管理員的操作歷史,瀏覽歷史,清理收尾的工作 71 ----teardown_request---- 72 在每一次請求以後,執行這個鉤子方法,如果有異常錯誤,則會傳遞錯誤異常對象到當前方法的參數中 73 None
異常捕獲
主動拋出HTTP異常
-
abort 方法
-
拋出一個給定狀態代碼的 HTTPException 或者 指定響應,例如想要用一個頁面未找到異常來終止請求,你可以調用 abort(404)。
-
-
參數:
-
code – HTTP的錯誤狀態碼
-
# abort(404) abort(500)
拋出狀態碼的話,只能拋出 HTTP 協議的錯誤狀態碼
捕獲錯誤
-
errorhandler 裝飾器
-
註冊一個錯誤處理程序,當程序拋出指定錯誤狀態碼的時候,就會調用該裝飾器所裝飾的方法
-
-
參數:
-
code_or_exception – HTTP的錯誤狀態碼或指定異常
-
-
例如統一處理狀態碼為500的錯誤給用戶友好的提示:
@app.errorhandler(500) def internal_server_error(e): return ‘服務器搬家了‘
-
捕獲指定異常
@app.errorhandler(ZeroDivisionError) def zero_division_error(e): return ‘除數不能為0‘
上下文
上下文:即語境,語意,在程序中可以理解為在代碼執行到某一時刻時,根據之前代碼所做的操作以及下文即將要執行的邏輯,可以決定在當前時刻下可以使用到的變量,或者可以完成的事情。
Flask中有兩種上下文,請求上下文(request context)和應用上下文(application context)。
Flask中上下文對象:相當於一個容器,保存了 Flask 程序運行過程中的一些信息。
-
application 指的就是當你調用
app = Flask(__name__)
創建的這個對象app
; -
request 指的是每次
http
請求發生時,WSGI server
(比如gunicorn)調用Flask.__call__()
之後,在Flask
對象內部創建的Request
對象; -
application 表示用於響應WSGI請求的應用本身,request 表示每次http請求;
-
application的生命周期大於request,一個application存活期間,可能發生多次http請求,所以,也就會有多個request
請求上下文(request context)
思考:在視圖函數中,如何取到當前請求的相關數據?比如:請求地址,請求方式,cookie等等
在 flask 中,可以直接在視圖函數中使用 request 這個對象進行獲取相關數據,而 request 就是請求上下文的對象,保存了當前本次請求的相關數據,請求上下文對象有:request、session
-
request
-
封裝了HTTP請求的內容,針對的是http請求。舉例:user = request.args.get(‘user‘),獲取的是get請求的參數。
-
-
session
-
用來記錄請求會話中的信息,針對的是用戶信息。舉例:session[‘name‘] = user.id,可以記錄用戶信息。還可以通過session.get(‘name‘)獲取用戶信息。
-
應用上下文(application context)
它的字面意思是 應用上下文,但它不是一直存在的,它只是request context 中的一個對 app 的代理(人),所謂local proxy。它的作用主要是幫助 request 獲取當前的應用,它是伴 request 而生,隨 request 而滅的。
應用上下文對象有:current_app,g
current_app
應用程序上下文,用於存儲應用程序中的變量,可以通過current_app.name打印當前app的名稱,也可以在current_app中存儲一些變量,例如:
-
應用的啟動腳本是哪個文件,啟動時指定了哪些參數
-
加載了哪些配置文件,導入了哪些配置
-
連接了哪個數據庫
-
有哪些可以調用的工具類、常量
-
當前flask應用在哪個機器上,哪個IP上運行,內存多大
current_app.name current_app.test_value=‘value‘
g變量
g 作為 flask 程序全局的一個臨時變量,充當者中間媒介的作用,我們可以通過它傳遞一些數據,g 保存的是當前請求的全局變量,不同的請求會有不同的全局變量,通過不同的thread id區別
g.name=‘abc‘
註意:不同的請求,會有不同的全局變量
兩者區別:
-
請求上下文:保存了客戶端和服務器交互的數據
-
應用上下文:flask 應用程序運行過程中,保存的一些配置信息,比如程序名、數據庫連接、應用信息等
Flask-Script 擴展
安裝命令:
pip install flask-script
集成 Flask-Script到flask應用中
from flask import Flask from flask_script import Manager ? app = Flask(__name__) ? # 把 Manager 類和應用程序實例進行關聯 manager = Manager(app) ? @app.route(‘/‘) def index(): return ‘hello world‘ ? if __name__ == "__main__": manager.run()
class hello(Command): "prints hello world" def run(self): print("hello world") ? manager.add_command(‘hello‘, hello())
Jinja2模板引擎
Flask內置的模板語言,它的設計思想來源於 Django 的模板引擎,並擴展了其語法和一系列強大的功能。
渲染模版函數
-
Flask提供的 render_template 函數封裝了該模板引擎
-
render_template 函數的第一個參數是模板的文件名,後面的參數都是鍵值對,表示模板中變量對應的真實值。
模板基本使用
-
在視圖函數中設置渲染模板
@app.route(‘/‘) def index(): return render_template(‘index.html‘)
-
在項目下創建
templates
文件夾,用於存放所有的模板文件,並在目錄下創建一個模板html文件index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> 我的模板html內容 </body> </html>
{{}} 來表示變量名,這種 {{}} 語法叫做變量代碼塊
<h1>{{ post.title }}</h1>
Jinja2 模版中的變量代碼塊可以是任意 Python 類型或者對象,只要它能夠被 Python 的 str() 方法轉換為一個字符串就可以,比如,可以通過下面的方式顯示一個字典或者列表中的某個元素:
{{your_dict[‘key‘]}} {{your_list[0]}}
用 {%%} 定義的控制代碼塊,可以實現一些語言層次的功能,比如循環或者if語句
{% if user %} {{ user }} {% else %} hello! <ul> {% for index in indexs %} <li> {{ index }} </li> {% endfor %} </ul>
使用 {# #} 進行註釋,註釋的內容不會在html中被渲染出來
{# {{ name }} #}
模板中特有的變量和函數
你可以在自己的模板中訪問一些 Flask 默認內置的函數和對象
config
你可以從模板中直接訪問Flask當前的config對象:
{{config.SQLALCHEMY_DATABASE_URI}}
sqlite:///database.db
request
就是flask中代表當前請求的request對象:
{{request.url}}
http://127.0.0.1
session
為Flask的session對象
{{session.new}}
True
g變量
在視圖函數中設置g變量的 name 屬性的值,然後在模板中直接可以取出
{{ g.name }}
url_for()
url_for會根據傳入的路由器函數名,返回該路由對應的URL,在模板中始終使用url_for()就可以安全的修改路由綁定的URL,則不比擔心模板中渲染出錯的鏈接:
{{url_for(‘home‘)}}
如果我們定義的路由URL是帶有參數的,則可以把它們作為關鍵字參數傳入url_for(),Flask會把他們填充進最終生成的URL中:
{{ url_for(‘post‘, post_id=1)}} /post/1
流程控制
主要包含兩個:
- if/else if /else / endif
- for / endfor
?
if語句
Jinja2 語法中的if語句跟 Python 中的 if 語句相似,後面的布爾值或返回布爾值的表達式將決定代碼中的哪個流程會被執行:
{%if user.is_logged_in() %} <a href=‘/logout‘>Logout</a> {% else %} <a href=‘/login‘>Login</a> {% endif %}
過濾器可以被用在 if 語句中:
{% if comments | length > 0 %} There are {{ comments | length }} comments {% else %} There are no comments {% endif %}
循環語句
-
我們可以在 Jinja2 中使用循環來叠代任何列表或者生成器函數
{% for post in posts %} <div> <h1>{{ post.title }}</h1> <p>{{ post.text | safe }}</p> </div> {% endfor %}
-
循環和if語句可以組合使用,以模擬 Python 循環中的 continue 功能,下面這個循環將只會渲染post.text不為None的那些post:
{% for post in posts if post.text %} <div> <h1>{{ post.title }}</h1> <p>{{ post.text | safe }}</p> </div> {% endfor %}
-
在一個 for 循環塊中你可以訪問這些特殊的變量:
變量 | 描述 |
---|---|
loop.index | 當前循環叠代的次數(從 1 開始) |
loop.index0 | 當前循環叠代的次數(從 0 開始) |
loop.revindex | 到循環結束需要叠代的次數(從 1 開始) |
loop.revindex0 | 到循環結束需要叠代的次數(從 0 開始) |
loop.first | 如果是第一次叠代,為 True 。 |
loop.last | 如果是最後一次叠代,為 True 。 |
loop.length | 序列中的項目數。 |
loop.cycle | 在一串序列間期取值的輔助函數。見下面示例程序。 |
-
在循環內部,你可以使用一個叫做loop的特殊變量來獲得關於for循環的一些信息
-
比如:要是我們想知道當前被叠代的元素序號,並模擬Python中的enumerate函數做的事情,則可以使用loop變量的index屬性,例如:
-
{% for post in posts%} {{loop.index}}, {{post.title}} {% endfor %}
-
會輸出這樣的結果
1, Post title 2, Second Post
-
cycle函數會在每次循環的時候,返回其參數中的下一個元素,可以拿上面的例子來說明:
{% for post in posts%} {{loop.cycle(‘odd‘,‘even‘)}} {{post.title}} {% endfor %}
-
會輸出這樣的結果:
odd Post Title
even Second Post
過濾器
過濾器的本質就是函數。有時候我們不僅僅只是需要輸出變量的值,我們還需要修改變量的顯示,甚至格式化、運算等等,而在模板中是不能直接調用 Python 中的某些方法,那麽這就用到了過濾器。
使用方式:
-
過濾器的使用方式為:變量名 | 過濾器。
{{variable | filter_name(*args)}}
-
如果沒有任何參數傳給過濾器,則可以把括號省略掉
{{variable | filter_name }}
-
如:``,這個過濾器的作用:把變量variable 的值的首字母轉換為大寫,其他字母轉換為小寫
在 jinja2 中,過濾器是可以支持鏈式調用的,示例如下:
{{ "hello world" | reverse | upper }}
常見的內建過濾器
字符串操作
safe:禁用轉義 <p>{{ ‘<em>hello</em>‘ | safe }}</p> capitalize:把變量值的首字母轉成大寫,其余字母轉小寫 <p>{{ ‘hello‘ | capitalize }}</p> lower:把值轉成小寫 <p>{{ ‘HELLO‘ | lower }}</p> upper:把值轉成大寫 <p>{{ ‘hello‘ | upper }}</p> title:把值中的每個單詞的首字母都轉成大寫 <p>{{ ‘hello‘ | title }}</p> reverse:字符串反轉 <p>{{ ‘olleh‘ | reverse }}</p> format:格式化輸出 <p>{{ ‘%s is %d‘ | format(‘name‘,17) }}</p> striptags:渲染之前把值中所有的HTML標簽都刪掉 <p>{{ ‘<em>hello</em>‘ | striptags }}</p> truncate: 字符串截斷 <p>{{ ‘hello every one‘ | truncate(9)}}</p>
列表操作
-
first:取第一個元素 <p>{{ [1,2,3,4,5,6] | first }}</p> last:取最後一個元素 <p>{{ [1,2,3,4,5,6] | last }}</p> length:獲取列表長度 <p>{{ [1,2,3,4,5,6] | length }}</p> sum:列表求和 <p>{{ [1,2,3,4,5,6] | sum }}</p> sort:列表排序 <p>{{ [6,2,3,1,5,4] | sort }}</p>
語句塊過濾
{% filter upper %} #一大堆文字# {% endfilter %}
自定義過濾器
過濾器的本質是函數。當模板內置的過濾器不能滿足需求,可以自定義過濾器。自定義過濾器有兩種實現方式:
-
一種是通過Flask應用對象的 add_template_filter 方法
-
通過裝飾器來實現自定義過濾器
重要:自定義的過濾器名稱如果和內置的過濾器重名,會覆蓋內置的過濾器。
需求:添加列表反轉的過濾器
方式一
通過調用應用程序實例的 add_template_filter 方法實現自定義過濾器。該方法第一個參數是函數名,第二個參數是自定義的過濾器名稱:
def do_listreverse(li): # 通過原列表創建一個新列表 temp_li = list(li) # 將新列表進行返轉 temp_li.reverse() return temp_li ? app.add_template_filter(do_listreverse,‘lireverse‘)
方式二
用裝飾器來實現自定義過濾器。裝飾器傳入的參數是自定義的過濾器名稱。
@app.template_filter(‘lireverse‘) def do_listreverse(li): # 通過原列表創建一個新列表 temp_li = list(li) # 將新列表進行返轉 temp_li.reverse() return temp_li
-
在 html 中使用該自定義過濾器
<br/> my_array 原內容:{{ my_array }} <br/> my_array 反轉:{{ my_array | lireverse }} 運行結果 my_array 原內容:[3, 4, 2, 1, 7, 9] my_array 反轉:[9, 7, 1, 2, 4, 3]
模板繼承
在模板中,可能會遇到以下情況:
-
多個模板具有完全相同的頂部和底部內容
-
多個模板中具有相同的模板代碼內容,但是內容中部分值不一樣
-
多個模板中具有完全相同的 html 代碼塊內容
像遇到這種情況,可以使用 JinJa2 模板中的 繼承 來進行實現
模板繼承是為了重用模板中的公共內容。一般Web開發中,繼承主要使用在網站的頂部菜單、底部。這些內容可以定義在父模板中,子模板直接繼承,而不需要重復書寫。
-
標簽定義的內容
{% block top %} {% endblock %}
-
相當於在父模板中挖個坑,當子模板繼承父模板時,可以進行填充。
-
子模板使用 extends 指令聲明這個模板繼承自哪個模板
-
父模板中定義的塊在子模板中被重新定義,在子模板中調用父模板的內容可以使用super()
父模板代碼:
base.html
{% block top %} 頂部菜單 {% endblock top %} ? {% block content %} {% endblock content %} ? {% block bottom %} 底部 {% endblock bottom %}
子模板代碼:
-
extends指令聲明這個模板繼承自哪
{% extends ‘base.html‘ %} {% block content %} 需要填充的內容 {% endblock content %}
模板繼承使用時註意點:
-
不支持多繼承
-
為了便於閱讀,在子模板中使用extends時,盡量寫在模板的第一行。
-
不能在一個模板文件中定義多個相同名字的block標簽。
-
當在頁面中使用多個block標簽時,建議給結束標簽起個名字,當多個block嵌套時,閱讀性更好。
在 Flask 項目中解決 CSRF 攻擊
在 Flask 中, Flask-wtf 擴展有一套完善的 csrf 防護體系,對於我們開發者來說,使用起來非常簡單
安裝
pip install flask-wtf
-
設置應用程序的 secret_key,用於加密生成的 csrf_token 的值
# session加密的時候已經配置過了.如果沒有在配置項中設置,則如下: app.secret_key = "#此處可以寫隨機字符串#"
-
導入 flask_wtf.csrf 中的 CSRFProtect 類,進行初始化,並在初始化的時候關聯 app
from flask.ext.wtf import CSRFProtect CSRFProtect(app)
-
在表單中使用 CSRF 令牌:
<form method="post" action="/"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" /> </form>
Flask之勾子,錯誤捕獲以及模板語法