Files
test/app/schema/student_schema.py
T
2026-09-21 19:03:31 +08:00

143 lines
5.6 KiB
Python

"""学生模块出入参(需求 2.1)。"""
from __future__ import annotations
from datetime import date
from typing import Any
from pydantic import BaseModel, Field, field_validator, model_validator
from app.model.constants import Education
from app.schema.common import GENDER_VALIDATOR, ORMBase, STRIP_VALIDATOR
class _StudentBase(BaseModel):
name: str = Field(..., min_length=1, max_length=30, description="姓名")
gender: int = Field(1, description="性别 1=男 2=女(也接受 男/女)")
birth_date: str | None = Field(None, description="出生日期 YYYY-MM-DD")
age: int | None = Field(
None, ge=10, le=80,
description="年龄。只在拿不到生日时用:填了 age 没填 birth_date,系统按当年推算一个占位生日",
)
native_place: str | None = Field(None, max_length=60, description="籍贯")
graduate_school: str | None = Field(None, max_length=80, description="毕业院校")
major: str | None = Field(None, max_length=60, description="专业")
education: str | None = Field(None, max_length=20, description="学历,如 大专/本科", examples=[Education.BACHELOR.value])
enroll_date: str | None = Field(None, description="入学时间 YYYY-MM-DD")
graduate_date: str | None = Field(None, description="毕业时间 YYYY-MM-DD")
phone: str | None = Field(None, max_length=20, description="联系电话")
id_card: str | None = Field(None, max_length=30, description="身份证号")
class_id: int | None = Field(None, description="所属班级ID")
advisor_id: int | None = Field(None, description="顾问ID")
remark: str | None = Field(None, description="备注")
_v_gender = GENDER_VALIDATOR
_v_strip = STRIP_VALIDATOR
@field_validator("birth_date", "enroll_date", "graduate_date", mode="before")
@classmethod
def _date_like(cls, v): # noqa: ANN001, ANN201
"""把 date/datetime 统一成 str,真正的解析放在 API 层用 parse_date。"""
if v is None or isinstance(v, str):
return v
if isinstance(v, date):
return v.isoformat()
return v
class StudentCreate(_StudentBase):
stu_no: str | None = Field(None, max_length=30, description="学号。不传则按规则自动生成")
status: int | None = Field(None, ge=1, le=3, description="状态 1=在读 2=进入就业 3=已就业,默认在读")
@model_validator(mode="after")
def _check_age_source(self):
if self.birth_date is None and self.age is None:
raise ValueError("birth_date 和 age 至少要填一个(用于年龄统计)")
return self
class StudentUpdate(BaseModel):
"""全部字段可选,只更新传了的字段。"""
name: str | None = Field(None, min_length=1, max_length=30, description="姓名")
gender: int | None = Field(None, description="性别 1=男 2=女")
birth_date: str | None = Field(None, description="出生日期 YYYY-MM-DD")
age: int | None = Field(None, description="年龄(会折算成占位生日)")
native_place: str | None = Field(None, max_length=60, description="籍贯")
graduate_school: str | None = Field(None, max_length=80, description="毕业院校")
major: str | None = Field(None, max_length=60, description="专业")
education: str | None = Field(None, max_length=20, description="学历")
enroll_date: str | None = Field(None, description="入学时间 YYYY-MM-DD")
graduate_date: str | None = Field(None, description="毕业时间 YYYY-MM-DD")
phone: str | None = Field(None, max_length=20, description="联系电话")
id_card: str | None = Field(None, max_length=30, description="身份证号")
class_id: int | None = Field(None, description="所属班级ID(转班会同步就业表的冗余班级)")
advisor_id: int | None = Field(None, description="顾问ID")
remark: str | None = Field(None, description="备注")
_v_gender = GENDER_VALIDATOR
_v_strip = STRIP_VALIDATOR
class StudentBrief(ORMBase):
id: int
stu_no: str
name: str
gender_text: str | None = None
class_name: str | None = None
status_text: str
class StudentOut(ORMBase):
id: int
stu_no: str = Field(..., description="学号")
name: str
gender: int
gender_text: str | None = Field(None, description="性别文案")
age: int | None = Field(None, description="年龄(按出生日期实时计算)")
birth_date: date | None = None
birth_date_estimated: int = Field(0, description="生日是否为推算值 1=是")
native_place: str | None = None
graduate_school: str | None = None
major: str | None = None
education: str | None = None
enroll_date: date | None = None
graduate_date: date | None = None
phone: str | None = None
id_card: str | None = None
class_id: int | None = None
class_no: str | None = Field(None, description="班级编号")
class_name: str | None = Field(None, description="班级名称(JOIN 得出)")
advisor_id: int | None = None
advisor_name: str | None = None
status: int
status_text: str
remark: str | None = None
score_count: int = Field(0, description="已录入的考核次数")
avg_score: float | None = Field(None, description="平均分")
fail_count: int = Field(0, description="低于红线的次数")
class StudentImportResult(BaseModel):
total: int = Field(0, description="文件里的数据行数")
success: int = Field(0, description="成功导入")
failed: int = Field(0, description="失败行数")
errors: list[dict[str, Any]] = Field(default_factory=list, description="失败明细:行号 + 原因")