[TOC]
## 第5章 理解WTForms并靈活改造她
WTForms其實是非常強大的驗證插件。但很多同學對WTForms的理解僅僅停留在“驗證表單”上。那WTForms可以用來做API的參數驗證碼?完全可以,但這需要你靈活的使用它,對它做出一些“改變”
### 5-1 重寫WTForms 一
原因:
* 之前做的表單驗證只是將所有驗證錯誤歸結于客戶端類型錯誤,并沒有具體分到底是客戶端傳入的哪個數據有問題
* 代碼寫起來繁瑣,我們需要在每一個需要做表單驗證的視圖函數內寫 else 來拋出異常
### 5-2 重寫WTForms 二
其實我們在執行 form.validate 的時候內部已經產生了一個異常,只是異常沒有拋出來而已,所以我們只需要 wtforms 在執行 form.validate 之后將異常拋出來就可以了。
#### 在 BaseForm 中覆寫 Form
首先新建 ginger/app/validators/base.py
~~~
?from wtforms import Form
??
?from app.libs.error_code import ParameterException
??
??
?class BaseForm(Form):
? ? ?def __init__(self, data):
? ? ? ? ?super(BaseForm, self).__init__(data=data)
??
? ? ?def validate_for_api(self):
? ? ? ? ?valid = super(BaseForm, self).validate()
? ? ? ? ?if not valid:
? ? ? ? ? ? ?raise ParameterException(self.errors)
~~~
1. 因為 BaseForm 需要接收客戶端傳過來的數據,所以在構造函數的參數里傳入 data
2. 在構造函數內調用父類(wtforms 的 Form)的構造函數進行初始化
3. 我們可以覆寫父類的 validate 方法也可以為 api 表單驗證寫一個單獨的 validate\_for\_api 方法
* 在 validate\_for\_api 內調用父類的 validate 方法,就會讓 validate\_for\_api 具備與 validate 相同的功能
* 父類 validate 方法執行過程中產生的異常都保存在 forms.errors 屬性中
* 其次用 valid 接收父類 validate 的返回結果,將結果中的異常拋出
這樣 BaseForm 就具備了拋出異常的能力。
#### 繼承 BaseForm
將 ginger/app/validator/forms.py 內的所有表單都繼承 BaseForm。
~~~
?from app.validators.base import BaseForm as Form
~~~
直接在視圖函數內調用 validate\_for\_api 就可以在表單驗證遇到異常的時候終止程序并拋出異常信息。
~~~
?@api.route('/register', methods=['POST'])
?def create_client():
? ? ?form = ClientForm(data=request.json)
? ? ?form.validate_for_api()
? ? ?promise = {
? ? ? ? ?ClientTypeEnum.USER_EMAIL: __register_by_email,
? ? ? ? ?ClientTypeEnum.USER_MOBILE: __register_by_mobile,
? ? ? ? ?ClientTypeEnum.USER_MINA: __register_by_mina,
? ? ? ? ?ClientTypeEnum.USER_WX: __register_by_wx,
? ? }
? ? ?promise[form.type.data]()
? ? ?return 'register client success'
~~~
### 5-3 可以接受定義的復雜,但不能接受調用的復雜
1. 優化 data=request.json 將 data=request.json 寫入到 BaseForm 中,以后調用就不需要手動傳入
~~~
?class BaseForm(Form):
? ? ?def __init__(self):
? ? ? ? ?data = request.json
? ? ? ? ?super(BaseForm, self).__init__(data=data)
~~~
2. 第一步優化好之后,視圖函數中的代碼如下
~~~
?form = ClientForm()
?form.validate_for_api()
~~~
這里還能不能優化呢?顯然是可以的,可以寫成
~~~
?form = ClientForm().validate_for_api()
~~~
但是 validate\_for\_api 內部并沒有返回 form,所以左邊的 form 賦值就會報錯,怎么辦?強制讓 validate\_for\_api 返回 form!因為 validate\_for\_api 是寫在 BaseForm 內的,此時的 self 就是 form。
~~~
? ? ?def validate_for_api(self):
? ? ? ? ?valid = super(BaseForm, self).validate()
? ? ? ? ?if not valid:
? ? ? ? ? ? ?raise ParameterException(self.errors)
? ? ? ? ?return self
~~~
### 5-4 已知異常與未知異常
到目前為止客戶端傳給服務器的都是 JSON 格式的數據,如果不是 JSON 格式的數據呢?

異常可以分為:
* 已知異常
* 未知異常
### 5-5 全局異常處理
如何捕捉未知異常呢?這就需要用到 AOP 的思想。不管異常產生在哪里,我只需要在全局捕捉異常并處理就可以解決問題了。
在項目的啟動文件內寫全局捕捉錯誤的函數。分類判斷該錯誤是 APIException、HTTPException 還是最基本的 Exception,對應的編寫處理方法。
~~~
?from werkzeug.exceptions import HTTPException
??
?from app.app import create_app
?from app.libs.error import APIException
?from app.libs.error_code import ServerError
??
?app = create_app()
??
??
?@app.errorhandler(Exception)
?def framework_error(e):
? ? ?if isinstance(e, APIException):
? ? ? ? ?return e
? ? ?if isinstance(e, HTTPException):
? ? ? ? ?code = e.code
? ? ? ? ?msg = e.description
? ? ? ? ?error_code = 1007
? ? ? ? ?return APIException(msg, code, error_code)
? ? ?else:
? ? ? ? ?if app.config['DEBUG']:
? ? ? ? ? ? ?raise e
? ? ? ? ?else:
? ? ? ? ? ? ?return ServerError()
??
??
?if __name__ == '__main__':
? ? ?app.run(debug=True)
~~~