
对请求数据的校验,选择使用 WTForms 进行校验,原生 WTForms 是对表单数据进行校验,而 API 请求是需要对请求体的参数进行校验,所以我们需要对他进行覆写。
原生的 Form 输出异常信息会以列表的形式,因为异常可能会有多个,在此我们将其拼接成一句 msg,这样会更便于前端展示。返回校验后的数据可以返回 dict 和 namedtuple 类型,也可以通过 get_data() 获取某一个参数。
# patch/validator.py
class Form(_Form):
    def __init__(self):
        data = request.get_json(silent=True)
        args = request.args.to_dict()
        super(Form, self).__init__(data=data, **args)
    def validate_for_api(self):
        """
        处理异常 拼接异常信息
        """
        valid = super(Form, self).validate()
        if not valid:
            msg = ''
            for index, item in enumerate(self.errors.values()):
                msg += ';'.join(item)
                if index != len(self.errors.values()) - 1:
                    msg += ';'
            raise ParameterError(msg=msg)
        return self
    def get_data(self, *args):
        data_list = []
        for arg in args:
            data_list.append(getattr(self._data, arg, None))
        return data_list[0] if len(data_list) == 1 else tuple(data_list)
    @property
    def _data(self):
        self.validate_for_api()
        key_list, value_list = [], []
        for key, value in self._fields.items():
            if value.data is not None:
                key_list.append(key)
                value_list.append(value.data)
        NamedTuple = namedtuple('NamedTuple', [key for key in key_list])
        return NamedTuple(*value_list)
    @property
    def dt_data(self):
        """
        返回dict类型
        """
        return self._data._asdict()
    @property
    def nt_data(self):
        """
        返回namedtuple类型
        """
        return self._data
在查看了 WTForms 的文档后,我发现他的 IntegerField 在 DataRequired 尽然不支持数字0的,我不得不对它进行覆写,因为在我的理解里 0 也是 IntegerField。
class DataRequired(_DataRequired):
    def __call__(self, form, field):
        if field.type == 'IntegerField' and field.data == 0:
            return
        if not field.data or isinstance(field.data, string_types) and not field.data.strip():
            if self.message is None:
                message = field.gettext('This field is required.')
            else:
                message = self.message
            field.errors[:] = []
            raise StopValidation(message)
使用校验时尽量参考官方文档给出的类型和属性。
class UpdateUserValidator(Form):
    nickname = StringField('昵称')
    avatar = StringField('头像')
    gender = SelectField('性别', choices=GenderEnum.choices(), validators=[Optional()])
    age = IntegerField('年龄', validators=[Optional(), NumberRange(min=1, max=500, message='年龄超出范围')])
    mobile = StringField('手机', validators=[
        Optional(),
        length(min=11, max=11, message='手机号长度为11位'),
        Regexp(r'^1[3-9]\d{9}$', message='手机号不合法'),
    ])
WTForms 所支持的字段
| 字段类型 | 说明 | 
|---|---|
| StringField | 文本字段 | 
| TextAreaField | 多行文本字段 | 
| PasswordField | 密码文本字段 | 
| HiddenField | 隐藏文本字段 | 
| DateField | 文本字段, 值为datetime.date格式 | 
| DateTimeField | 文本字段, 值为datetime.datetime格式 | 
| IntegerField | 文本字段, 值为整数 | 
| DecimalField | 文本字段, 值为decimal.Decimal | 
| FloatField | 文本字段, 值为浮点数 | 
| BooleanField | 复选框, 值为True 和 False | 
| RadioField | 一组单选框 | 
| SelectField | 下拉列表 | 
| SelectMultipleField | 下拉列表, 可选择多个值 | 
| FileField | 文件上传字段 | 
| SubmitField | 表单提交按钮 | 
| FormFiled | 把表单作为字段嵌入另一个表单 | 
| FieldList | 子组指定类型的字段 | 
WTForms支持的表单的验证函数
| 验证函数 | 说明 | 
|---|---|
| 验证是电子邮件地址 | |
| EqualTo | 比较两个字段的值; 常用于要求输入两次密钥进行确认的情况 | 
| IPAddress | 验证IP网络地址 | 
| Length | 验证输入字符串的长度 | 
| NumberRange | 验证输入的值在数字范围内 | 
| Optional | 无输入值时跳过其它验证函数 | 
| DataRequired | 确保字段中有数据 | 
| Regexp | 使用正则表达式验证输入值 | 
| URL | 验证url | 
| AnyOf | 确保输入值在可选值列表中 | 
| NoneOf | 确保输入值不在可选列表中 |