Files
student-management-system/api/stu_api.py
T
2026-09-18 16:53:21 +08:00

100 lines
3.7 KiB
Python

# api/stu_api.py
# 学生信息管理模块:接口层只负责 HTTP 出入参与响应组装,查库/校验/事务交给 dao.stu_dao
# 业务异常(NotFoundError / ConflictError)由 main.py 注册的全局处理器统一转成状态码
from typing import Optional
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from dao import stu_dao
from database import get_db
from schemas.stu_schema import StuInfoCreate, StuInfoUpdate, StuInfoOut
router = APIRouter()
def _to_detail(stu) -> dict:
"""把学生实体组装成详情响应:基础字段 + 班级 / 顾问 / 成绩 / 就业信息。"""
data = StuInfoOut.model_validate(stu).model_dump()
# 班级信息
data["cls"] = None
if stu.cls:
data["cls"] = {
"id": stu.cls.id,
"cls_start_date": stu.cls.cls_start_date.isoformat() if stu.cls.cls_start_date else None,
}
# 顾问信息
data["advisor"] = {"id": stu.adv.id, "name": stu.adv.name} if stu.adv else None
# 成绩列表(过滤已删除)
data["scores"] = [
{
"exam_attempt": s.exam_attempt,
"exam_score": s.exam_score,
"score_level": s.score_level,
}
for s in stu.score
if not s.is_deleted
]
# 就业信息(一对一)
data["employ"] = None
if stu.stu_emp_mgmt and not stu.stu_emp_mgmt.is_deleted:
e = stu.stu_emp_mgmt
data["employ"] = {
"emp_company": e.emp_company,
"salary": e.salary,
"emp_open_time": e.emp_open_time.isoformat() if e.emp_open_time else None,
"send_offer_time": e.send_offer_time.isoformat() if e.send_offer_time else None,
}
return data
@router.get("", summary="分页查询学生列表")
def list_students(
page: int = Query(1, ge=1, description="页码"),
size: int = Query(10, ge=1, le=100, description="每页数量"),
name: Optional[str] = Query(None, description="按姓名模糊查询"),
cls_id: Optional[str] = Query(None, description="按班级编号过滤"),
state: Optional[str] = Query(None, description="按状态过滤"),
db: Session = Depends(get_db),
):
"""查询未删除的学生,支持分页和按姓名/班级/状态过滤。"""
total, items = stu_dao.list_students(
db, page=page, size=size, name=name, cls_id=cls_id, state=state
)
return {
"total": total,
"page": page,
"size": size,
"items": [StuInfoOut.model_validate(x) for x in items],
}
@router.get("/{stu_id}", summary="查询单个学生详情")
def get_student(stu_id: str, db: Session = Depends(get_db)):
"""查询学生详情,并附带班级、顾问、成绩与就业信息。"""
return _to_detail(stu_dao.assert_stu_alive(db, stu_id, "学生不存在或已删除"))
@router.post("", response_model=StuInfoOut, status_code=201, summary="新增学生")
def create_student(payload: StuInfoCreate, db: Session = Depends(get_db)):
"""新增学生(学号唯一、班级与顾问必须存在,由 DAO 校验)。"""
return stu_dao.create_student(db, payload.model_dump())
@router.put("/{stu_id}", response_model=StuInfoOut, summary="更新学生信息")
def update_student(stu_id: str, payload: StuInfoUpdate, db: Session = Depends(get_db)):
"""按传入字段局部更新学生信息。"""
return stu_dao.update_student(db, stu_id, payload.model_dump(exclude_unset=True))
@router.delete("/{stu_id}", summary="删除学生(软删除)")
def delete_student(stu_id: str, db: Session = Depends(get_db)):
"""软删除学生:仅置 is_deleted 标记,数据保留。"""
stu_dao.soft_delete_student(db, stu_id)
return {"message": "删除成功", "id": stu_id}