用裝飾器封裝Flask-WTF表單驗證邏輯

simpleapples發表於2019-02-20

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。

檢視PyPI

檢視Github

相關文章