Files
djangoadmin-wiki/DjangoAdmin_Flask_NaiveVue/zh/volume2/schema.md
T
2026-09-13 23:01:19 +08:00

224 lines
7.4 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` 约束字段格式,框架在接收请求体时自动执行校验。
## 文件位置
```
src/modules/system/position/schema.py
```
## 完整代码
以下为 `src/modules/system/position/schema.py` 的核心代码(省略版权头):
```python
"""
岗位表单验证模块
=============================================================
提供岗位相关的表单验证功能,包括:
- 岗位添加/更新表单验证
- 岗位状态设置表单验证
"""
from wtforms import ValidationError, StringField, IntegerField
from wtforms.validators import DataRequired, Length, NumberRange, Optional
from src.common.forms.base_form import BaseForm
from src.modules.system.position.repository import PositionRepository
# ============================================================
# 岗位表单验证(添加和更新共用)
# ============================================================
class PositionForm(BaseForm):
"""
岗位表单验证类
用于添加和更新岗位时的数据验证。
添加时 id 为 None,更新时 id 必须有值。
"""
# 岗位ID:更新时必填,添加时可选
id = IntegerField(
label='岗位ID',
validators=[
Optional(), # 允许为空(添加时)
NumberRange(min=1, message='岗位ID必须大于0')
],
description='岗位唯一标识,更新时必填,添加时自动生成'
)
# 岗位名称:必填,最长150字符,不可重复
name = StringField(
label='岗位名称',
validators=[
DataRequired(message='岗位名称不能为空'),
Length(max=150, message='岗位名称长度不得超过150个字符')
],
description='岗位显示名称,如:技术总监、产品经理'
)
# 岗位状态:必填,1-在用,2-停用
status = IntegerField(
label='岗位状态',
validators=[
DataRequired(message='岗位状态不能为空'),
NumberRange(min=1, max=2, message='岗位状态值在1~2之间')
],
description='岗位状态:1-在用,2-停用'
)
# 岗位排序:可选,0-99999之间的整数
sort = IntegerField(
label='岗位排序',
validators=[
Optional(),
NumberRange(min=0, max=99999, message='岗位排序值在0~99999之间')
],
description='排序值,数值越小越靠前'
)
# ============================================================
# 自定义验证方法
# ============================================================
def validate_name(self, field):
"""
自定义验证:岗位名称不能重复
验证逻辑:
1. 添加时:检查数据库中是否存在同名记录
2. 更新时:排除当前记录自身,检查其他记录是否同名
"""
if not field.data:
return
# 获取当前记录的ID(更新时存在,添加时为None)
current_id = self.id.data if self.id.data else None
# 唯一性校验(软删过滤 + 更新时排除自身)
if PositionRepository.exists_by({'name': field.data}, exclude_id=current_id):
raise ValidationError("岗位名称已存在")
# ============================================================
# 状态设置表单
# ============================================================
class PositionStatusForm(BaseForm):
"""岗位状态设置表单验证类"""
id = IntegerField(
label='岗位ID',
validators=[
DataRequired(message='岗位ID不能为空'),
NumberRange(min=1, message='岗位ID必须大于0')
],
description='要修改状态的岗位ID'
)
status = IntegerField(
label='岗位状态',
validators=[
DataRequired(message='岗位状态不能为空'),
NumberRange(min=1, max=2, message='岗位状态值在1~2之间')
],
description='目标状态:1-在用,2-停用'
)
```
## 代码解析
### 基类选择
| 基类 | 用途 | 说明 |
|:----|:----|:----|
| `BaseForm` | 创建/编辑通用表单 | 继承 `flask_wtf.FlaskForm`,支持 ArrayField 处理 |
| `PositionStatusForm` | 状态更新专用 | 只包含 `id` 和 `status` 两个字段 |
### id 字段
创建和编辑共用 `PositionForm`:创建时不传 `id`,编辑时传入 `id`。`id` 字段定义为 `Optional()`,创建时可省略:
```python
id = IntegerField(
label='岗位ID',
validators=[Optional(), NumberRange(min=1, message='岗位ID必须大于0')],
description='岗位唯一标识,更新时必填,添加时自动生成'
)
```
### Validator 约束
| Validator | 含义 | 示例 |
|:----|:----|:----|
| `DataRequired` | 必填字段,不允许空值 | `DataRequired(message='岗位名称不能为空')` |
| `Length(max=150)` | 字符串长度限制 | `Length(max=150, message='...')` |
| `NumberRange(min=1, max=2)` | 数值范围限制 | `NumberRange(min=1, max=2, message='...')` |
| `Optional` | 可选字段 | `Optional()` |
### 自定义校验器
通过定义 `validate_{field_name}` 方法实现自定义校验:
```python
def validate_name(self, field):
"""自定义验证:岗位名称不能重复"""
if not field.data:
return
current_id = self.id.data if self.id.data else None
if PositionRepository.exists_by({'name': field.data}, exclude_id=current_id):
raise ValidationError("岗位名称已存在")
```
- 方法名必须为 `validate_{field_name}`
- 校验失败抛出 `ValidationError(错误消息)`
- 唯一性校验通过 `Repository.exists_by(filters, exclude_id)` 实现,自动过滤软删除记录
## 如何在 Router 中使用
```python
from flask import Blueprint, request
from src.common.utils.request import parse_request_body
from src.modules.system.position import service as position
from src.core import response as R
position_blue = Blueprint('position', __name__, url_prefix='/position')
@position_blue.route('/add', methods=['POST'])
def add():
"""新增岗位"""
json_data, err = parse_request_body()
if err:
return R.failed(msg=err)
position.add_position(json_data)
return R.ok(msg="添加成功")
```
校验失败时,由框架自动返回标准错误响应:
```json
{
"code": 1,
"data": null,
"msg": "岗位名称不能为空"
}
```
## 开发要点
1. **创建/编辑共用一个表单类**:继承 `BaseForm`,显式定义 `id` 字段(`Optional`)
2. **特殊操作单独建类**:如状态更新,只包含必要的字段
3. **合理设置 Validator**:`DataRequired` / `Length` / `NumberRange` 等,让框架自动校验
4. **message 必填**:用于返回给前端的错误信息,方便调试
5. **唯一性校验**:通过自定义 `validate_` 方法实现,使用 Repository 层的 `exists_by(filters, exclude_id)` 方法
6. **可选字段用 `Optional()`**:如 `sort` 排序字段,不传时使用数据库默认值
## 总结
WTForms Schema 通过 `BaseForm` 继承公共能力,使用 `validators` 声明约束,自定义 `validate_` 方法实现唯一性校验。唯一性校验使用 `Repository.exists_by({'field': value}, exclude_id=current_id)` 方法,自动过滤软删除记录并支持排除自身。特殊操作单独建类,`message` 必填用于返回给前端的错误信息。