优化更新
This commit is contained in:
@@ -15,8 +15,8 @@ Flask 使用 `Blueprint` 机制实现模块化路由注册。每个业务模块
|
||||
|
||||
from flask import Blueprint, request
|
||||
from src.core.permission import check_permission
|
||||
from src.core.response import R
|
||||
from src.common.utils.request import parse_request_body, parse_id_list
|
||||
from src.core import response as R
|
||||
from src.common.utils.request import parse_request_body, parse_batch_ids
|
||||
|
||||
# 定义 Blueprint
|
||||
# 参数说明:
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
岗位模型定义,包含名称、排序、状态等属性。
|
||||
"""
|
||||
|
||||
from sqlalchemy import Column, String, Integer, text
|
||||
from sqlalchemy import text
|
||||
from src.core.database import db
|
||||
|
||||
from src.common.base_db import BaseDB
|
||||
from src.common.base_model import BaseModel
|
||||
@@ -36,11 +37,11 @@ class Position(BaseModel, BaseDB):
|
||||
# 字段定义
|
||||
# ============================================================
|
||||
# 岗位名称
|
||||
name = Column(String(255), nullable=False, index=True, comment="岗位名称")
|
||||
# 岗位状态:1-在用 2-停用
|
||||
status = Column(Integer, default=0, server_default=text('0'), index=True, comment="岗位状态:1-在用 2-停用")
|
||||
name = db.Column(db.String(255), nullable=False, index=True, comment="岗位名称")
|
||||
# 岗位状态:0-停用 1-启用
|
||||
status = db.Column(db.Integer, default=0, server_default=text('0'), index=True, comment="岗位状态:0-停用 1-启用")
|
||||
# 岗位排序
|
||||
sort = Column(Integer, default=0, server_default=text('0'), comment="岗位排序")
|
||||
sort = db.Column(db.Integer, default=0, server_default=text('0'), comment="岗位排序")
|
||||
|
||||
# ============================================================
|
||||
# 内置方法
|
||||
|
||||
@@ -56,7 +56,7 @@ flask db upgrade
|
||||
|
||||
### Q6: 批量删除报 500 错误
|
||||
|
||||
**原因**:`parse_id_list` 函数未正确导入或使用。
|
||||
**原因**:`parse_batch_ids` 函数未正确导入或使用。
|
||||
|
||||
**解决**:确保正确导入并使用:
|
||||
|
||||
|
||||
@@ -10,31 +10,147 @@ src/modules/system/position/models.py
|
||||
|
||||
## 完整代码
|
||||
|
||||
以下为 `src/modules/system/position/models.py` 的核心代码(省略版权头):
|
||||
|
||||
```python
|
||||
from sqlalchemy import Column, String, Integer, text
|
||||
"""
|
||||
岗位模型模块
|
||||
=============================================================
|
||||
|
||||
提供岗位管理的数据模型定义,用于定义员工岗位信息。
|
||||
|
||||
企业级特性:
|
||||
1. 使用枚举类管理状态常量,确保类型安全
|
||||
2. 状态文本自动转换,便于前端展示
|
||||
3. 便捷的查询方法,提高开发效率
|
||||
4. 继承 BaseModel 和 BaseDB,复用通用功能
|
||||
5. __init__ 由 BaseModel 统一管理,无需重复定义
|
||||
"""
|
||||
|
||||
from enum import IntEnum
|
||||
from typing import Optional, List
|
||||
|
||||
from src.common.base_db import BaseDB
|
||||
from src.common.base_model import BaseModel
|
||||
from config.database import DB_PREFIX
|
||||
from config import DB_PREFIX
|
||||
from sqlalchemy import text
|
||||
from src.core.database import db
|
||||
from src.common.utils.request import like_contains
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 岗位状态枚举
|
||||
# ============================================================
|
||||
|
||||
class PositionStatus(IntEnum):
|
||||
"""
|
||||
岗位状态枚举
|
||||
|
||||
使用 IntEnum 确保类型安全,避免魔法数字。
|
||||
可直接用于数据库查询和比较。
|
||||
"""
|
||||
DISABLED = 0 # 停用
|
||||
ENABLED = 1 # 启用
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 岗位模型
|
||||
# ============================================================
|
||||
|
||||
class Position(BaseModel, BaseDB):
|
||||
"""岗位模型类"""
|
||||
# 表名配置
|
||||
"""
|
||||
岗位模型
|
||||
|
||||
用于存储员工岗位信息,如:技术总监、产品经理等。
|
||||
继承自 BaseModel(通用字段)和 BaseDB(数据库操作方法)。
|
||||
"""
|
||||
|
||||
# 表名:使用配置前缀 + 模型名
|
||||
__tablename__ = DB_PREFIX + "position"
|
||||
__table_comment__ = "岗位表"
|
||||
|
||||
# 业务字段定义
|
||||
# 岗位名称
|
||||
name = Column(String(255), nullable=False, index=True, comment="岗位名称")
|
||||
# 岗位状态:1-在用 2-停用
|
||||
status = Column(Integer, default=0, server_default=text('0'), index=True, comment="岗位状态:1-在用 2-停用")
|
||||
# 岗位排序
|
||||
sort = Column(Integer, default=0, server_default=text('0'), comment="岗位排序")
|
||||
# ============================================================
|
||||
# 字段定义
|
||||
# ============================================================
|
||||
|
||||
def __repr__(self):
|
||||
"""返回岗位名称作为字符串表示"""
|
||||
return '岗位:{}'.format(self.name)
|
||||
# 岗位名称:必填字段,最长255字符
|
||||
name = db.Column(
|
||||
db.String(255),
|
||||
nullable=False,
|
||||
index=True,
|
||||
comment="岗位名称"
|
||||
)
|
||||
|
||||
# 岗位状态:使用枚举确保类型安全
|
||||
# 默认值为停用,创建后需手动启用
|
||||
status = db.Column(
|
||||
db.Integer,
|
||||
index=True,
|
||||
default=PositionStatus.DISABLED,
|
||||
server_default=text('0'),
|
||||
comment="岗位状态:0-停用 1-启用"
|
||||
)
|
||||
|
||||
# 岗位排序:数值越小越靠前
|
||||
sort = db.Column(
|
||||
db.Integer,
|
||||
default=0,
|
||||
server_default=text('0'),
|
||||
comment="岗位排序"
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# 注意:__init__ 方法在 BaseModel 中已实现,无需重复定义
|
||||
# BaseModel 已处理驼峰转换、字段过滤等逻辑
|
||||
# ============================================================
|
||||
|
||||
# ============================================================
|
||||
# 便捷属性
|
||||
# ============================================================
|
||||
|
||||
@property
|
||||
def status_text(self) -> str:
|
||||
"""获取岗位状态文本:将数字状态转换为可读的中文文本"""
|
||||
status_map = {
|
||||
PositionStatus.DISABLED: '停用',
|
||||
PositionStatus.ENABLED: '启用'
|
||||
}
|
||||
return status_map.get(self.status, '未知')
|
||||
|
||||
@property
|
||||
def is_enabled(self) -> bool:
|
||||
"""判断岗位是否启用"""
|
||||
return self.status == PositionStatus.ENABLED
|
||||
|
||||
@property
|
||||
def is_disabled(self) -> bool:
|
||||
"""判断岗位是否停用"""
|
||||
return self.status == PositionStatus.DISABLED
|
||||
|
||||
# ============================================================
|
||||
# 自定义查询方法
|
||||
# ============================================================
|
||||
|
||||
@classmethod
|
||||
def get_enabled(cls) -> List['Position']:
|
||||
"""获取所有启用的岗位,按排序值升序排列"""
|
||||
return cls.query.filter(
|
||||
cls.is_delete == 0,
|
||||
cls.status == PositionStatus.ENABLED
|
||||
).order_by(cls.sort.asc()).all()
|
||||
|
||||
@classmethod
|
||||
def get_by_name(cls, name: Optional[str]) -> Optional['Position']:
|
||||
"""根据岗位名称精确查询"""
|
||||
if not name:
|
||||
return None
|
||||
return cls.query.filter(
|
||||
cls.name == name,
|
||||
cls.is_delete == 0
|
||||
).first()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'岗位:{self.name}'
|
||||
```
|
||||
|
||||
## 代码解析
|
||||
@@ -48,7 +164,34 @@ class Position(BaseModel, BaseDB):
|
||||
| 基类 | 来源 | 提供能力 |
|
||||
|:----|:----|:----|
|
||||
| `BaseModel` | `src/common/base_model.py` | 通用字段:id、create_user、create_time、update_user、update_time、is_delete |
|
||||
| `BaseDB` | `src/common/base_db.py` | `save()` 持久化方法、`delete()` 删除方法、`to_dict()` 序列化方法 |
|
||||
| `BaseDB` | `src/common/base_db.py` | `save()` 持久化方法(仅 flush,不 commit)、`to_dict()` 序列化方法 |
|
||||
|
||||
### 状态枚举
|
||||
|
||||
项目推荐使用 `IntEnum` 管理状态常量,避免魔法数字:
|
||||
|
||||
```python
|
||||
class PositionStatus(IntEnum):
|
||||
DISABLED = 0 # 停用
|
||||
ENABLED = 1 # 启用
|
||||
```
|
||||
|
||||
| 值 | 含义 |
|
||||
|:----|:----|
|
||||
| 0 | 停用 / 禁用 |
|
||||
| 1 | 启用 / 在用 |
|
||||
|
||||
软删除字段 `is_delete` 由基类提供:`0` = 正常,`1` = 已删除。
|
||||
|
||||
### 字段定义风格
|
||||
|
||||
项目使用 `db.Column`(从 `src.core.database` 导入的 `db` 实例),而非直接导入 `Column`:
|
||||
|
||||
```python
|
||||
from src.core.database import db
|
||||
|
||||
name = db.Column(db.String(255), nullable=False, index=True, comment="岗位名称")
|
||||
```
|
||||
|
||||
### 表名配置
|
||||
|
||||
@@ -56,71 +199,17 @@ class Position(BaseModel, BaseDB):
|
||||
__tablename__ = DB_PREFIX + "position"
|
||||
```
|
||||
|
||||
`DB_PREFIX` 从 `.env` 读取(默认 `flask_`),最终表名为 `flask_position`。所有业务表都使用此前缀,便于区分框架表与业务表。
|
||||
|
||||
### 字段定义
|
||||
|
||||
```python
|
||||
name = Column(String(255), nullable=False, index=True, comment="岗位名称")
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
|:----|:----|
|
||||
| `String(255)` | 字段类型,VARCHAR(255) |
|
||||
| `nullable=False` | 非空约束 |
|
||||
| `index=True` | 创建索引,加速查询 |
|
||||
| `comment` | 字段注释,同步到数据库 |
|
||||
|
||||
```python
|
||||
status = Column(Integer, default=0, server_default=text('0'), index=True, comment="岗位状态:1-在用 2-停用")
|
||||
```
|
||||
|
||||
`default=0` 是 Python 端默认值,`server_default=text('0')` 是数据库端默认值。两者都设置可确保无论通过 ORM 还是直接 SQL 插入,都能正确赋默认值。
|
||||
|
||||
### 枚举值约定
|
||||
|
||||
项目中状态字段统一使用整数枚举:
|
||||
|
||||
| 值 | 含义 |
|
||||
|:----|:----|
|
||||
| 1 | 启用 / 在用 |
|
||||
| 2 | 停用 / 禁用 |
|
||||
|
||||
软删除字段 `is_delete` 由基类提供:`0` = 正常,`1` = 已删除。
|
||||
|
||||
## 数据表 DDL(参考)
|
||||
|
||||
模型定义对应的 MySQL 建表语句:
|
||||
|
||||
```sql
|
||||
CREATE TABLE `flask_position` (
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '岗位名称',
|
||||
`status` int DEFAULT '0' COMMENT '岗位状态:1-在用 2-停用',
|
||||
`sort` int DEFAULT '0' COMMENT '岗位排序',
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`create_user` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人',
|
||||
`create_time` datetime DEFAULT (now()) COMMENT '创建时间',
|
||||
`update_user` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '更新人',
|
||||
`update_time` datetime DEFAULT (now()) COMMENT '更新时间',
|
||||
`is_delete` int DEFAULT '0' COMMENT '删除标识:0-正常 1-已删除',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_flask_position_name` (`name`) USING BTREE,
|
||||
KEY `idx_flask_position_status` (`status`) USING BTREE,
|
||||
KEY `idx_flask_position_is_delete` (`is_delete`) USING BTREE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='岗位表';
|
||||
```
|
||||
|
||||
:::tip 温馨提示
|
||||
实际开发中不需要手写 DDL。模型定义完成后,框架会通过 `Base.metadata.create_all()` 自动建表,或通过 `flask db migrate` 生成迁移脚本。
|
||||
:::
|
||||
`DB_PREFIX` 从 `.env` 读取(默认 `flask_`),最终表名为 `flask_position`。
|
||||
|
||||
## 开发要点
|
||||
|
||||
1. **业务字段仅定义差异部分**:通用字段(id、时间、软删除等)由基类自动提供
|
||||
2. **表名必须加 `DB_PREFIX` 前缀**:保持命名一致性
|
||||
3. **合理使用索引**:查询频繁的字段(如 name、status)设置 `index=True`
|
||||
4. **同时设置 `default` 和 `server_default`**:确保 ORM 和直 SQL 两种场景都能正确赋默认值
|
||||
3. **使用 `db.Column` 而非直接导入 `Column`**:保持与项目一致的导入风格
|
||||
4. **状态字段使用 IntEnum**:避免魔法数字,提高代码可读性
|
||||
5. **同时设置 `default` 和 `server_default`**:确保 ORM 和直 SQL 两种场景都能正确赋默认值
|
||||
6. **`__init__` 由 BaseModel 统一管理**:无需在子类中重复定义
|
||||
|
||||
## 总结
|
||||
|
||||
数据模型继承 `BaseModel`(公共字段)和 `BaseDB`(save/delete 方法),表名必须加 `DB_PREFIX` 前缀。查询频繁的字段设置索引,同时设置 `default` 和 `server_default` 确保 ORM 和直 SQL 两种场景都能正确赋默认值。
|
||||
数据模型继承 `BaseModel`(公共字段)和 `BaseDB`(save 方法),表名必须加 `DB_PREFIX` 前缀。状态字段使用 `IntEnum` 枚举类管理,字段定义使用 `db.Column` 风格。`__init__` 由 BaseModel 统一处理驼峰转换和字段过滤,子类无需重复定义。
|
||||
|
||||
@@ -10,115 +10,173 @@ src/modules/system/position/schema.py
|
||||
|
||||
## 完整代码
|
||||
|
||||
以下为 `src/modules/system/position/schema.py` 的核心代码(省略版权头):
|
||||
|
||||
```python
|
||||
from wtforms import StringField, IntegerField
|
||||
"""
|
||||
岗位表单验证模块
|
||||
=============================================================
|
||||
|
||||
提供岗位相关的表单验证功能,包括:
|
||||
- 岗位添加/更新表单验证
|
||||
- 岗位状态设置表单验证
|
||||
"""
|
||||
|
||||
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):
|
||||
"""岗位创建/编辑表单"""
|
||||
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之间')
|
||||
])
|
||||
"""
|
||||
岗位表单验证类
|
||||
|
||||
用于添加和更新岗位时的数据验证。
|
||||
添加时 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('岗位ID', validators=[
|
||||
DataRequired(message='岗位ID不能为空'),
|
||||
NumberRange(min=1, message='岗位ID必须大于0')
|
||||
])
|
||||
status = IntegerField('岗位状态', validators=[
|
||||
DataRequired(message='岗位状态不能为空'),
|
||||
NumberRange(min=1, max=2, message='岗位状态必须是1或2')
|
||||
])
|
||||
"""岗位状态设置表单验证类"""
|
||||
|
||||
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` | 创建/编辑通用表单 | 自带可选 `id` 字段(`Optional`),编辑时传入 |
|
||||
| 特定操作表单 | 无额外字段,按需定义 |
|
||||
| `BaseForm` | 创建/编辑通用表单 | 继承 `flask_wtf.FlaskForm`,支持 ArrayField 处理 |
|
||||
| `PositionStatusForm` | 状态更新专用 | 只包含 `id` 和 `status` 两个字段 |
|
||||
|
||||
创建和编辑共用 `PositionForm`:创建时不传 `id`,编辑时传入 `id`。`BaseForm` 的 `id` 字段定义为 `IntegerField('ID', validators=[Optional()])`,创建时可省略。
|
||||
### id 字段
|
||||
|
||||
创建和编辑共用 `PositionForm`:创建时不传 `id`,编辑时传入 `id`。`id` 字段定义为 `Optional()`,创建时可省略:
|
||||
|
||||
```python
|
||||
id = IntegerField(
|
||||
label='岗位ID',
|
||||
validators=[Optional(), NumberRange(min=1, message='岗位ID必须大于0')],
|
||||
description='岗位唯一标识,更新时必填,添加时自动生成'
|
||||
)
|
||||
```
|
||||
|
||||
### Validator 约束
|
||||
|
||||
```python
|
||||
name = StringField('岗位名称', validators=[
|
||||
DataRequired(message='岗位名称不能为空'),
|
||||
Length(min=1, max=150, message='岗位名称长度必须在1-150个字符之间')
|
||||
])
|
||||
```
|
||||
| Validator | 含义 | 示例 |
|
||||
|:----|:----|:----|
|
||||
| `DataRequired` | 必填字段,不允许空值 | `DataRequired(message='岗位名称不能为空')` |
|
||||
| `Length(max=150)` | 字符串长度限制 | `Length(max=150, message='...')` |
|
||||
| `NumberRange(min=1, max=2)` | 数值范围限制 | `NumberRange(min=1, max=2, message='...')` |
|
||||
| `Optional` | 可选字段 | `Optional()` |
|
||||
|
||||
| Validator | 含义 |
|
||||
|:----|:----|
|
||||
| `DataRequired` | 必填字段,不允许空值 |
|
||||
| `Length(min=1, max=150)` | 字符串长度限制 |
|
||||
| `NumberRange(min=1, max=2)` | 数值范围限制 |
|
||||
| `Optional` | 可选字段 |
|
||||
### 自定义校验器
|
||||
|
||||
通过定义 `validate_{field_name}` 方法实现自定义校验:
|
||||
|
||||
```python
|
||||
status = IntegerField('岗位状态', validators=[
|
||||
DataRequired(message='岗位状态不能为空'),
|
||||
NumberRange(min=1, max=2, message='岗位状态必须是1或2')
|
||||
])
|
||||
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("岗位名称已存在")
|
||||
```
|
||||
|
||||
| 参数 | 含义 |
|
||||
|:----|:----|
|
||||
| `min=1` | 最小值(大于等于) |
|
||||
| `max=2` | 最大值(小于等于) |
|
||||
| `message` | 校验失败时的错误信息 |
|
||||
|
||||
```python
|
||||
sort = IntegerField('岗位排序', validators=[
|
||||
DataRequired(message='岗位排序不能为空'),
|
||||
NumberRange(min=0, max=99999, message='岗位排序必须在0-99999之间')
|
||||
])
|
||||
```
|
||||
|
||||
排序字段允许 0 到 99999 的范围。
|
||||
|
||||
### 状态更新单独建类
|
||||
|
||||
```python
|
||||
# ============================================================
|
||||
# 设置状态表单类
|
||||
# ============================================================
|
||||
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')
|
||||
])
|
||||
```
|
||||
|
||||
状态更新接口只需要 `id` 和 `status` 两个字段,与创建/编辑表单分开定义,接口语义更清晰。
|
||||
- 方法名必须为 `validate_{field_name}`
|
||||
- 校验失败抛出 `ValidationError(错误消息)`
|
||||
- 唯一性校验通过 `Repository.exists_by(filters, exclude_id)` 实现,自动过滤软删除记录
|
||||
|
||||
## 如何在 Router 中使用
|
||||
|
||||
@@ -141,7 +199,7 @@ def add():
|
||||
return R.ok(msg="添加成功")
|
||||
```
|
||||
|
||||
`parse_request_body` 函数自动解析请求体 JSON。校验失败时,Service 层抛出 `BizError`,由异常处理器自动返回标准错误响应:
|
||||
校验失败时,由框架自动返回标准错误响应:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -151,36 +209,15 @@ def add():
|
||||
}
|
||||
```
|
||||
|
||||
## 自定义校验器
|
||||
|
||||
如果需要自定义校验逻辑,可以在表单类中定义 `validate_` 开头的方法:
|
||||
|
||||
```python
|
||||
class PositionForm(BaseForm):
|
||||
"""岗位创建/编辑表单"""
|
||||
name = StringField('岗位名称', validators=[
|
||||
DataRequired(message='岗位名称不能为空'),
|
||||
Length(max=150, message='岗位名称长度不得超过150个字符')
|
||||
])
|
||||
|
||||
def validate_name(self, field):
|
||||
"""自定义校验:检查岗位名称是否已存在"""
|
||||
from src.modules.system.position.repository import PositionRepository
|
||||
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('岗位名称已存在')
|
||||
```
|
||||
|
||||
## 开发要点
|
||||
|
||||
1. **创建/编辑共用一个表单类**:继承 `BaseForm`,利用其内置的可选 `id` 字段
|
||||
2. **特殊操作单独建类**:如状态更新、排序调整等,只包含必要的字段
|
||||
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` 继承公共字段(id 等),使用 `validators` 声明约束,自定义 `validate_` 方法实现唯一性校验。唯一性校验使用 `Repository.exists_by({'field': value}, exclude_id=current_id)` 方法,自动过滤软删除记录并支持排除自身。特殊操作单独建类,`message` 必填用于返回给前端的错误信息。
|
||||
WTForms Schema 通过 `BaseForm` 继承公共能力,使用 `validators` 声明约束,自定义 `validate_` 方法实现唯一性校验。唯一性校验使用 `Repository.exists_by({'field': value}, exclude_id=current_id)` 方法,自动过滤软删除记录并支持排除自身。特殊操作单独建类,`message` 必填用于返回给前端的错误信息。
|
||||
|
||||
Reference in New Issue
Block a user