Don`t repeat yourself
在使用Flask-WTF的時候,常會用下面這樣的程式碼來驗證表單資料的合法性:
from flask import Flask
app = Flask(__name__)
@app.route(`/`, methods=[`GET`, `POST`])
def index():
form = TestForm()
# 判斷是否合法
if not form.validate_on_submit():
return `err`, 400
# 主要邏輯
複製程式碼
對於有很多提交介面的專案來說,需要在每個路由下寫相同的的邏輯,造成了大量的程式碼重複。在Flask-Login中,要把一個路由設定為登入後才能訪問,只需要在路由上加一個@login_required裝飾器,不需要額外的程式碼。能不能像Flask-Login一樣,用裝飾器來封裝對錶單的驗證邏輯呢?
實現表單驗證裝飾器
由於不同路由使用的表單類不一樣,所以需要為裝飾器傳入一個表單類引數,並且在路由函式中需要用到表單中的值,所以還需要將驗證通過的表單傳給路由函式。
上程式碼:
def validate_form(self, form_cls):
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
if not form.validate_on_submit():
return `error`, 400
return fn(form, *args, **kwargs)
return wrapper
return decorator
複製程式碼
使用方式如下:
@validate_form(TestForm) # 需要傳入要驗證的表單類
@app.route(`/`, methods=[`GET`, `POST`])
def index(form):
# 執行到這裡說明表單驗證通過
複製程式碼
經過在專案中的應用,發現裝飾器還是有一些缺陷:
- 無法自定義處理非法表單的邏輯
- 不支援get方式提交的表單(檢視validate_on_submit()原始碼可知其只支援對post和put方式提交的表單進行驗證)
豐富一下
要自定義處理非法表單的邏輯,需要增加一個可以傳入自定義邏輯的介面。表單非法時介面的返回往往是一致的,所以我們為所有應用裝飾器的路由傳入一個統一的處理邏輯。將裝飾器封裝在一個類中,在類中新增一個配置處理邏輯的方法。
from functools import wraps
from flask import request
class FormValidator(object):
def __init__(self, error_handler=None):
self._error_handler = error_handler
def validate_form(self, form_cls):
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
if not form.validate_on_submit() and self._error_handler:
return self._error_handler(form.errors)
return fn(form, *args, **kwargs)
return wrapper
return decorator
def error_handler(self, fn):
self._error_handler = fn
return fn
複製程式碼
error_handler也是一個裝飾器,被它修飾的方法就是處理非法表單的方法。
@form_validator.error_handler
def error_handler(errors):
return jsonify({`errors`: errors}), 400
複製程式碼
接下來支援get方法,在flask中,我們可以通過request.args來獲取到get方法提交的引數。思路是用獲取到的引數生成一個表單類的例項,然後就可以通過呼叫表單類的validate()方法來判斷是否合法了。修改validate_form裝飾器:
def validate_form(self, form_cls):
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
if request.method == `GET`:
form = form_cls(formdata=request.args)
elif request.method in (`POST`, `PUT`):
form = form_cls()
else:
return fn(*args, **kwargs)
if not form.validate() and self._error_handler:
return self._error_handler(form.errors)
return fn(form, *args, **kwargs)
return wrapper
return decorator
複製程式碼
大功告成!使用上面的裝飾器,就可以免除在路由函式中重複寫表單驗證邏輯,並且同時支援put、post和get方法提交的表單。
#開箱即用
筆者已經把上面的程式碼封裝成了一個庫釋出到了PyPI,想直接用的朋友可以使用pip install flask-wtf-decorators
安裝,專案原始碼也已經發布到Github。