Files
djangoadmin-wiki/DjangoAdmin_Flask_NaiveVue/zh/volume7/wtforms.md
T
2026-09-12 23:12:15 +08:00

148 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# WTForms Schema 规范
## 概述
Schema 层负责请求数据的校验。项目使用 `WTForms` 定义表单类,通过 `validators` 约束字段格式,框架在接收请求体时自动执行校验。
:::tip 设计原则
- 创建/编辑共用一个表单类,继承 `BaseForm`
- 特殊操作单独建类(如状态更新、排序调整)
- `message` 必填,用于返回给前端的错误信息
- 唯一性校验通过自定义 `validate_` 方法实现
:::
## BaseForm 继承
以岗位模块为例,`src/modules/system/position/schema.py` 完整内容:
```python
from wtforms import StringField, IntegerField
from wtforms.validators import DataRequired, Length, NumberRange
from src.common.forms.base_form import BaseForm
# ============================================================
# 岗位表单类
# ============================================================
class PositionForm(BaseForm):
"""岗位创建/编辑表单"""
name = StringField('岗位名称', validators=[
DataRequired(message='岗位名称不能为空'),
Length(min=1, max=150, message='岗位名称长度必须在1-150个字符之间')
])
status = IntegerField('岗位状态', validators=[
DataRequired(message='岗位状态不能为空'),
NumberRange(min=1, max=2, message='岗位状态必须是1或2')
])
sort = IntegerField('岗位排序', validators=[
DataRequired(message='岗位排序不能为空'),
NumberRange(min=0, max=99999, message='岗位排序必须在0-99999之间')
])
# ============================================================
# 设置状态表单类
# ============================================================
class PositionStatusForm(BaseForm):
"""岗位状态更新表单"""
id = IntegerField('岗位ID', validators=[
DataRequired(message='岗位ID不能为空'),
NumberRange(min=1, message='岗位ID必须大于0')
])
status = IntegerField('岗位状态', validators=[
DataRequired(message='岗位状态不能为空'),
NumberRange(min=1, max=2, message='岗位状态必须是1或2')
])
```
## Validator 选择表
| Validator | 作用 | 示例 |
|:----------|:-----|:-----|
| `DataRequired` | 必填字段,不允许空值 | `DataRequired(message='不能为空')` |
| `Length` | 字符串长度限制 | `Length(min=1, max=150)` |
| `NumberRange` | 数值范围限制 | `NumberRange(min=1, max=2)` |
| `Optional` | 可选字段 | `Optional()` |
| `Email` | 邮箱格式 | `Email(message='邮箱格式不正确')` |
| `URL` | URL 格式 | `URL(message='URL格式不正确')` |
| `Regexp` | 正则表达式 | `Regexp(r'^[a-zA-Z]+$')` |
## 自定义校验器
### 唯一性校验
```python
from wtforms import ValidationError
from src.modules.system.position.repository import PositionRepository
class PositionForm(BaseForm):
"""岗位创建/编辑表单"""
name = StringField('岗位名称', validators=[
DataRequired(message='岗位名称不能为空'),
Length(min=1, max=150, message='岗位名称长度必须在1-150个字符之间')
])
def validate_name(self, field):
"""自定义校验:检查岗位名称是否已存在"""
exclude_id = self.id.data if self.id.data else None
if PositionRepository.exists_by([Position.name == field.data], exclude_id):
raise ValidationError('岗位名称已存在')
```
### 跨字段校验
```python
class DateRangeForm(BaseForm):
"""日期范围表单"""
start_date = DateField('开始日期', validators=[DataRequired()])
end_date = DateField('结束日期', validators=[DataRequired()])
def validate_end_date(self, field):
"""自定义校验:结束日期必须大于开始日期"""
if field.data < self.start_date.data:
raise ValidationError('结束日期必须大于开始日期')
```
## 在 Router 中使用
```python
from flask import request
from src.common.utils.request import parse_request_body
@position_blue.route('/add', methods=['POST'])
@check_permission('sys:position:add')
def add():
"""新增岗位"""
from src.modules.system.position.service import add_position
from src.modules.system.position.schema import PositionForm
data = parse_request_body(request, PositionForm)
return add_position(data)
```
## 错误响应格式
校验失败时,框架自动返回标准错误响应:
```json
{
"code": 1,
"data": null,
"msg": "岗位名称不能为空",
"ok": false
}
```
## 开发要点
1. **创建/编辑共用一个表单类**:继承 `BaseForm`,利用其内置的可选 `id` 字段
2. **特殊操作单独建类**:如状态更新、排序调整等,只包含必要的字段
3. **合理设置 Validator**:`DataRequired` / `Length` / `NumberRange` 等,让框架自动校验
4. **message 必填**:用于返回给前端的错误信息,方便调试
5. **唯一性校验**:通过自定义 `validate_` 方法实现,使用 Repository 层的 `exists_by` 方法
6. **字段名与前端一致**:确保前后端字段名对应
## 总结
WTForms Schema 通过 `BaseForm` 继承公共字段(id 等),使用 `validators` 声明约束,自定义 `validate_` 方法实现唯一性校验。特殊操作单独建类,`message` 必填用于返回给前端的错误信息。