> For the complete documentation index, see [llms.txt](https://flask-api.gitbook.io/api/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://flask-api.gitbook.io/api/flask-gou-jian-ke-kuo-zhan-de-restfulapi4-li-jie-wtforms-bing-ling-huo-gai-zao-ta.md).

# 4. 理解WTForms并灵活改造她

## 4.1 重写WTForms

#### 优化1

之前的代码，修改完成之后，已经修复了之前的缺陷，但是这样爆出了两个问题： 1.代码太啰嗦了，每个试图函数里，都需要这么写 2.ClientTypeError只是代表客户端类型异常，其他的参数校验不通过也抛出这个异常的话不合适

为了解决上面的问题，我们需要重写wtforms

### 思路

继承原有的wtforms,重写validate\_for\_api，**修改wtforms为抛出异常** 定义一个自定义BaseForm，让其他的Form来继承 app\validators\base.py

```
from wtforms import Form
from app.libs.erro_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:   # 没通过
            # 所有异常类信息在form errors 中 
            raise ParameterException(msg=self.errors) #  抛出异常  # 公共的自定义异常类
```

定义公共异常类

app\libs\erro\_code.py

```
from app.libs.erro import APIException

class ClientTypeErro(APIException):
    code = 400
    msg = 'client is invalid'
    erro_code = 1006


class ParameterException(APIException):
    code = 400
    msg = 'invalid parameter'
    erro_code = 1000
```

以后我们的试图函数就可以这样编写

```
from app.libs.redprint import Redprint

from app.validators.forms import ClientForm,UserEmailForm
from flask import request
from app.libs.enums import ClientTypeEnum
from app.models.user import User


# from werkzeug.exceptions import HTTPException   #  异常
from app.libs.erro_code import ClientTypeErro   # 导入自定义异常

api = Redprint('client')  # 实例化一个Redprint

@api.route('/register', methods = ['POST'] )  # 路由注册  # 因为这里把POST打成PSOT，导致不能使用POST访问，状态码405
def create_client():                         
    # 表单 - 一般网页  json - 一般移动端
    # 注册 登录
    # 参数 校验  接收参数
    # WTForms 验证表单

#用来接收json类型的参数
    data = request.json
# 关键字参数data是wtform中用来接收json参数的方法
    form = ClientForm(data = data)  # data =   来接收json

    form.validate_for_api()
# 替代switchcase-{Enum_name:handle_func}
    promise = {
            ClientTypeEnum.USER_EMAIL: __register_user_by_email#,
            # ClientTypeEnum.USER_MINA: __register_user_by_MINA   # 可在此处构建多种枚举类型
        }
    promise[form.type.data]()

    return 'sucess'  #  暂时返回sucess
#总 ↑

#分 ↓

def __register_user_by_email():
    form = UserEmailForm(data=request.json)
    if form.validate():
        User.register_by_email(form.nickname.data,
                               form.account.data,
                               form.secret.data)


# def __register_user_by_MINA():
#     pass
```

#### 优化2

目前我们每次都需要从request中取出json信息再传入到Form对象中，优化的思路是，直接传入request，在BaseForm中取出json app\validators\base.py

```
from wtforms import Form
from app.libs.erro_code import ParameterException
from flask import request

class BaseForm(Form):
    # def __init__(self, data):
    def __init__(self):
        data = request.json
        super(BaseForm, self).__init__(data=data)   # 调用父类构造函数

    def validate_for_api(self):
        valid = super(BaseForm, self).validate()  # 调用父类的构造方法 # 验证是否通过
        if not valid:   # 没通过
            # 所有异常类信息在form errors 中 
            raise ParameterException(msg=self.errors) #  抛出异常  # 公共的自定义异常类
```

#### 优化3

每次都需要实例化Form对象，再调用validate\_for\_api()方法，我们可以让validate\_for\_api方法返回一个self对象，这样就只需要一行代码就可以解决了 app\validators\base.py

```
from wtforms import Form
from app.libs.erro_code import ParameterException
from flask import request

class BaseForm(Form):
    # def __init__(self, data):
    def __init__(self):
        data = request.json
        super(BaseForm, self).__init__(data=data)   # 调用父类构造函数

    def validate_for_api(self):
        valid = super(BaseForm, self).validate()  # 调用父类的构造方法 # 验证是否通过
        if not valid:   # 没通过
            # 所有异常类信息在form errors 中 
            raise ParameterException(msg=self.errors) #  抛出异常  # 公共的自定义异常类
        return self
```

#### 优化4

操作成功也需要返回json结构，且结构应该和异常的时候一样，所以我们可以定义一个Success继承APIException app\libs\erro\_code.py

```
# from werkzeug.exceptions import HTTPException
# # 自定义异常类

# class ClientTypeErro(HTTPException):
#     code = 400
#     description = (
#         'client is invalid'
#     )


# ↑ 不再继承HTTPException

# ↓ 继承APIException
from app.libs.erro import APIException

class ClientTypeErro(APIException):
    code = 400
    msg = 'client is invalid'
    erro_code = 1006


class ParameterException(APIException):
    code = 400
    msg = 'invalid parameter'
    erro_code = 1000

# 将成功返回也当作一中 APIException，来优化代码
class Success(APIException):
    code = 201
    msg = 'ok'
    erro_code = 0

class ServerError(APIException):
    code = 500
    msg  = 'sorry,we made a mistaake'
    erro_code = 999
```

优化后视图函数 app\api\v1\client.py

```
from app.libs.redprint import Redprint

from app.validators.forms import ClientForm,UserEmailForm
# from flask import request  #  转移到Baseform中去了
from app.libs.enums import ClientTypeEnum
from app.models.user import User


# from werkzeug.exceptions import HTTPException   #  异常
from app.libs.erro_code import ClientTypeErro, ParameterException ,Success    # 导入自定义异常

api = Redprint('client')  # 实例化一个Redprint

@api.route('/register', methods = ['POST'] )  # 路由注册  # 因为这里把POST打成PSOT，导致不能使用POST访问，状态码405
def create_client():                         
    # 表单 - 一般网页  json - 一般移动端
    # 注册 登录
    # 参数 校验  接收参数
    # WTForms 验证表单

#用来接收json类型的参数
    # data = request.json
# 关键字参数data是wtform中用来接收json参数的方法
    # form = ClientForm(data = data)  # data =   来接收json
    # form = ClientForm()   # 转移到 BaseForm 中

    # form.validate_for_api()
    form = ClientForm().validate_for_api()
# 替代switchcase-{Enum_name:handle_func}
    promise = {
            ClientTypeEnum.USER_EMAIL: __register_user_by_email#,
            # ClientTypeEnum.USER_MINA: __register_user_by_MINA   # 可在此处构建多种枚举类型
        }
    promise[form.type.data]()

    return Success()  #  将成功返回也当作一中 APIException
#总 ↑

#分 ↓

def __register_user_by_email():
#     form = UserEmailForm()
# # if form.validate():
#     form.validate_for_api() 
    form = UserEmailForm().validate_for_api()   # 为什么有括号
    User.register_by_email(form.nickname.data,
                               form.account.data,
                               form.secret.data)


# def __register_user_by_MINA():
#     pass
```

我们可以接受定义时候的复杂，但是不能够接受调用的时候复杂

定义是一次性的，但是调用是多次的，如果调用太过于复杂，会使得我们的 代码太过于臃肿

## 4.2 全局异常处理

当系统抛出不是我们自己定义的APIException的时候，返回的结果仍然会变成一个HTML文本。

我们在写代码的过程中，有那么类型的异常： 1.已知异常：我们可以预知的。如枚举转换的时候抛出的异常，这时候我们就会提前使用try-except进行处理。也可以抛出APIException 2.未知异常：完全没有预料到的。会由框架抛出的内置异常

我们可以使用flask给我们提供的处理全局异常的装饰器，采用AOP的设计思想，捕捉所有类型的异常。

```
from app import create_app
from app.libs.erro_code import ServerError
from app.libs.erro import APIException
from werkzeug.exceptions import HTTPException

app = create_app()

@app.errorhandler(Exception)  # python 基类的异常,因为我们要捕捉所有异常
def framework_error(e):
    if isinstance(e, APIException):
        return e
    if isinstance(e, HTTPException):  # 转化成APIException
        code = e.code
        msg = e.description
        error_code = 1007
        return APIException(msg, code, error_code)
    else:
        if not app.config['DEBUG']: # 判断是否在调试模式,不再,直接返回
            return ServerError()
        else:
            raise e 
        return ServerError()


if __name__ == '__main__':
    app.run(debug=True)
```
