一、裝飾器的坑以及解決方法
1、使用裝飾器裝飾兩個視圖函數,代碼如下
from flask import Flask, redirect, render_template, request, session
app = Flask(__name__)
app.secret_key = "wanglili" # 裝飾器函數 def outer(func): def inner(*args, **kwargs): if session.get("user"): # 驗證session ret = func(*args, **kwargs) return ret else: return redirect("/login") return inner @app.route("/") @outer # inner=outer(index) def index(): return render_template("index.html") @app.route("/course") @outer def course(): return render_template("course.html") @app.route("/login", methods=["POST", "GET"]) def login(): if request.method == "GET": return render_template("login.html") if request.form.get("username") == "wll"and request.form.get("password") == "123": session["user"] = request.form.get("username") # 寫入session return redirect("/") return render_template("login.html") app.run(debug=True)
啟動程序有如下錯誤:
我們還發現當裝飾一個視圖函數時可以正常運行,而裝飾兩個或兩個以上視圖函數則會報以上錯誤。
2、解決方式
1)方式一:使用functools模塊
import functools def outer(func): @functools.wraps(func) # 保留原函數func的信息 def inner(*args, **kwargs): if session.get("user"): ret = func(*args, **kwargs) return ret else: return redirect("/login") return inner
2)方式二:使用flask提供的endpoint參數
@app.route("/", endpoint='index') @outer def index(): return render_template("index.html") @app.route("/course", endpoint='course') @outer def course(): return render_template("course.html")
二、flask中的路由
Flask中的路由系統我們已經見過了,並且一直在用,接下來學習幾個有關路由的使用方法。
1、@app.route()裝飾器中的參數
1)methods : 當前 url 地址,允許訪問的請求方式,示例代碼如下
@app.route("/info", methods=["GET", "POST"]) def stu_info(): return "student info"
注意:不寫methods參數時默認是只允許GET請求,寫methods參數時要指定所有希望的允許的請求方式,默認方式會被methods覆蓋。
2)endpoint:反向url地址,默認為視圖函數名,url_for利用它進行反向解析,示例代碼如下
from flask import Flask, url_for
@app.route("/info", methods=["GET", "POST"], endpoint='s_info') def stu_info(): print(url_for('s_info')) # /info return "student info"
注意:endpoint默認為視圖函數名一致,不寫endpoint參數則使用視圖函數名反向解析,即url_for('stu_info'),有則使用endpoint定義的名稱進行反向解析。
3)defaults:視圖函數的參數默認值,代碼如下
@app.route("/info", methods=["GET", "POST"], defaults={"nid": 100}) def stu_info(nid): print(nid) # 100 return "student info"
注意:視圖函數一定要接收這個參數,且形參名稱一定要與defaults參數中的key一致。
4)strict_slashes:url地址結尾符"/"的控制(False : 無論結尾"/"是否存在均可以訪問 , True : 結尾必須不能是"/",即與定義的路由完全一致),示例代碼如下
# 只能訪問 /info @app.route("/info", methods=["GET", "POST"], strict_slashes=True) def stu_info(): return "student info" # 可以訪問 /info 或者 /info/ @app.route("/info", methods=["GET", "POST"], strict_slashes=False) def stu_info(): return "student info"
5)redirect_to:url地址永久重定向(對應狀態碼301)
# 訪問地址 : /info 瀏覽器跳轉至 /infos @app.route("/info", redirect_to="/infos") def student_info(): return "Hello info" @app.route("/infos") def student_infos(): return "Hello infos"
注意:在跳轉之前進行重定向,原url對應的視圖函數沒有執行,立即跳轉到新的url。且使用一次后對瀏覽器會有緩存。
6)subdomain:子域名前綴 subdomian="car" 這樣寫可以得到 car.oldboy.com 前提是app.config["SERVER_NAME"] = "oldboy.com"
app.config["SERVER_NAME"] = "oldboy.com" @app.route("/info",subdomain="car") def student_info(): return "Hello info"
# 訪問地址為: car.oldboy.com/info
2、動態路由參數
from flask import Flask, url_for
# 訪問地址 : http://127.0.0.1:5000/info/1 可以 # 訪問地址:http://127.0.0.1:5000/info/e 報錯 @app.route("/info/<int:nid>", methods=["GET", "POST"], endpoint="s_info") def student_info(nid): print(nid, type(nid)) # 1 <class 'int'> print(url_for("s_info", nid=nid)) # /info/1 print(url_for("s_info", nid=2)) # /info/2 return "Hello info" # 訪問地址 : http://127.0.0.1:5000/info/1 可以 # 訪問地址 : http://127.0.0.1:5000/info/e 可以 @app.route("/info/<string:nid>", methods=["GET", "POST"], endpoint="s_info") def student_info(nid): print(nid, type(nid)) # 1 <class 'str'> return "Hello info" # 訪問地址 : http://127.0.0.1:5000/info/1 可以 # 訪問地址 : http://127.0.0.1:5000/info/e 可以 @app.route("/info/<nid>", methods=["GET", "POST"], endpoint="s_info") def student_info(nid): print(nid, type(nid)) # 1 <class 'str'> return "Hello info"
通過以上示例發現,<int:nid>就是在url后定義一個參數接收url中的參數,通過演示,還發現,當定義int類型時只能傳數字,當定義string類型時可以接收數字和字符串,當不定義類型時,默認是string類型。
注意:這種動態參數路由,在url_for的時候,一定要將動態參數名+參數值添加進去,否則會拋出參數錯誤的異常。
三、flask實例化配置
app = Flask(__name__) # Flask的實例化對象app
實例化對象時的參數如下:
template_folder="temp" 默認模板存放目錄 templates static_folder="static" 默認靜態文件存放目錄 static static_url_path="/static" 訪問靜態文件路由地址,默認是"/"+static_folder static_host=None 指定靜態文件服務器地址 host_matching = False 若不是特別需要時,慎用,否則所有的route 都需要host=""的參數 subdomain_matching = False 理論上來說是用來限制SERVER_NAME子域名的,但是目前還沒有感覺出來區別在哪里 instance_path = None 指向另一個Flask實例的路徑 instance_relative_config = False 是否加載另一個實例的配置 root_path = None 主模塊所在的目錄的絕對路徑,默認項目目錄
四、flask對象配置
Flask對象即Flask的實例化對象app,Flask的對象配置就是在 app.config 中添加鍵值對,例如,app.config["SECRET_KEY"]="/wrwfgs",但是你存進去的鍵必須是config中應該存在的,如果不存在的話,它會默認無用,所以我們來看一下config中有哪些key以及對應的作用?
'DEBUG': False, # 是否開啟Debug模式 'TESTING': False, # 是否開啟測試模式 'SECRET_KEY': None # 在啟用Flask內置Session的時候/開啟flash,一定要有它 'PERMANENT_SESSION_LIFETIME': 31, # days , Session的生命周期(天)默認31天 'SESSION_COOKIE_NAME': 'session', # 在cookies中存放session加密字符串的名字 'PROPAGATE_EXCEPTIONS': None, # 異常傳播(是否在控制台打印LOG) 當Debug或者testing開啟后,自動為True 'PRESERVE_CONTEXT_ON_EXCEPTION': None, # 一兩句話說不清楚,一般不用它 'SECRET_KEY': None, # 之前遇到過,在啟用Session的時候,一定要有它 'PERMANENT_SESSION_LIFETIME': 31, # days , Session的生命周期(天)默認31天 'USE_X_SENDFILE': False, # 是否棄用 x_sendfile 'LOGGER_NAME': None, # 日志記錄器的名稱 'LOGGER_HANDLER_POLICY': 'always', 'SERVER_NAME': None, # 服務訪問域名 'APPLICATION_ROOT': None, # 項目的完整路徑 'SESSION_COOKIE_NAME': 'session', # 在cookies中存放session加密字符串的名字 'SESSION_COOKIE_DOMAIN': None, # 在哪個域名下會產生session記錄在cookies中 'SESSION_COOKIE_PATH': None, # cookies的路徑 'SESSION_COOKIE_HTTPONLY': True, # 控制 cookie 是否應被設置 httponly 的標志, 'SESSION_COOKIE_SECURE': False, # 控制 cookie 是否應被設置安全標志 'SESSION_REFRESH_EACH_REQUEST': True, # 這個標志控制永久會話如何刷新 'MAX_CONTENT_LENGTH': None, # 如果設置為字節數, Flask 會拒絕內容長度大於此值的請求進入,並返回一個 413 狀態碼 'SEND_FILE_MAX_AGE_DEFAULT': 12, # hours 默認緩存控制的最大期限 'TRAP_BAD_REQUEST_ERRORS': False, # 如果這個值被設置為 True ,Flask不會執行 HTTP 異常的錯誤處理,而是像對待其它異常一樣, # 通過異常棧讓它冒泡地拋出。這對於需要找出 HTTP 異常源頭的可怕調試情形是有用的。 'TRAP_HTTP_EXCEPTIONS': False, # Werkzeug 處理請求中的特定數據的內部數據結構會拋出同樣也是“錯誤的請求”異常的特殊的 key errors 。 # 同樣地,為了保持一致,許多操作可以顯式地拋出 BadRequest 異常。 # 因為在調試中,你希望准確地找出異常的原因,這個設置用於在這些情形下調試。 'EXPLAIN_TEMPLATE_LOADING': False, # 如果這個值被設置為True,你只會得到常規的回溯。 'PREFERRED_URL_SCHEME': 'http', # 生成URL的時候如果沒有可用的 URL 模式話將使用這個值 'JSON_AS_ASCII': True, # 默認情況下 Flask 使用 ascii 編碼來序列化對象。如果這個值被設置為 False , # Flask不會將其編碼為 ASCII,並且按原樣輸出,返回它的 unicode 字符串。 # 比如 jsonfiy 會自動地采用 utf-8 來編碼它然后才進行傳輸。 'JSON_SORT_KEYS': True, # 默認情況下 Flask 按照 JSON 對象的鍵的順序來序來序列化它。 # 這樣做是為了確保鍵的順序不會受到字典的哈希種子的影響,從而返回的值每次都是一致的,不會造成無用的額外 HTTP 緩存。 # 你可以通過修改這個配置的值來覆蓋默認的操作。但這是不被推薦的做法因為這個默認的行為可能會給你在性能的代價上帶來改善。 'JSONIFY_PRETTYPRINT_REGULAR': True, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None,
以上這些key都可以被改寫,當然它們也有默認值,如果沒有特殊情況,不要改寫它的默認值。
除了單獨修改某一項的配置外,還可以通過類的方式導入配置:
首先定義一個flask_settings.py,內容如下:
class FlaskDebug(object): # debug模式下的類 DEBUG=True SECRET_KEY="DEBUGmoshidesecret_key" PERMANENT_SESSION_LIFETIME = 7 SESSION_COOKIE_NAME = "debug_session"
然后再flask的啟動文件中使用,如下:
from flask import Flask
app = Flask(__name__)
app.config.from_object(flask_settings.FlaskDebug) # 配置debug模式
五、初識flask藍圖(Blueprint)
作為初學flask的我們,可以將藍圖理解為沒有run方法的Flask對象,接下來學習如何創建以及配置藍圖。
首先創建一個目錄flask_project/app01,在app01目錄中創建一個views.py文件,內容如下:
from flask import Blueprint # 從flask中導入藍圖 bp1 = Blueprint("bp1", __name__) # 實例化一個藍圖對象 @bp1.route('/stuList') # 添加路由並定義視圖函數 def stulist(): return "student list"
在flask_project目錄中創建manage.py文件,內容如下:
from flask import Flask from app01 import views # 導入剛寫好的藍圖所在模塊 app = Flask(__name__) app.register_blueprint(views.bp1) # 在Flask對象中注冊藍圖對象,重要 if __name__ == '__main__': app.run(debug=True)
此時,運行manage.py文件啟動服務,然后訪問http://127.0.0.1:5000/stuList,即可看到返回結果。
上述藍圖的視圖函數返回了一個HttpResponse對象,那么是否可以返回模板頁面呢?當然可以,實例化藍圖(Blueprint)對象時,也可以配置藍圖的模板目錄(template_folder參數)和靜態文件目錄(static_folder參數),也就說藍圖的對象配置與Flask的對象配置方法一樣,在此不過多介紹。
需要特別說明的是藍圖可以指定url前綴(url_prefix參數),方法如下:
方法一:實例化藍圖對象時指定url前綴
bp1 = Blueprint("bp1", __name__, url_prefix='/app01')
方法二:在注冊藍圖時指定url前綴
app.register_blueprint(views.bp1, url_prefix='/app01')
此時,瀏覽器要訪問地址http://127.0.0.1:5000/app01/stuList
六、flask特殊裝飾器
1、flask的中間件
1)@app.before_request # 請求進入視圖函數之前,類似於django中間件的request_process
2)@app.after_request # 響應返回客戶端之前,類似於django中間件的response_process
manage.py代碼如下:
from flask import Flask app = Flask(__name__) @app.before_request def be1(): print('我是be1') @app.before_request def be2(): print('我是be2') @app.before_request def be3(): print('我是be3') @app.after_request def af1(response): print('我是af1') return response @app.after_request def af2(response): print('我是af2') return response @app.after_request def af3(response): print('我是af3') return response @app.route('/index') def index(): print('我是視圖函數') return 'ok' if __name__ == '__main__': app.run(debug=True)
啟動項目,訪問http://127.0.0.1:5000/index,打印結果如下:
我是be1 我是be2 我是be3 我是視圖函數 我是af3 我是af2 我是af1
分析:@app.after_request自上而下依次執行,@app.after_request自下而上依次執行。與django的中間件類似,@app.after_request裝飾的函數不寫return或者returnNone表示放行,順利通過,否則攔截請求,返回響應。@app.after_request裝飾的函數一定要有返回值,且必須有參數接收response對象。
修改be1函數為如下代碼:
@app.before_request def be2(): print('我是be2') return 'error'
執行結果如下:
我是be1 我是af3 我是af2 我是af1
總結:
正常情況下流程:be1 - be2 - be3 - af3 - af2 - af1
異常(be1函數發生異常)情況下流程:be1 - af3 - af2 - af1
2、重定義錯誤頁面返回信息
@app.errorhandler(404)# 參數是錯誤代碼 def error404(error_info):# 注意,一定要加參數接收錯誤信息 print(error_info) # 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again. return '頁面不存在'# 可以返回 三劍客 + 小兒子
當訪問時發生404錯誤時,會看到該函數返回的內容。