初始化前后端学生管理系统项目
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
from sqlalchemy_fastapi_demo_1.model.Score import Score
|
||||
from sqlalchemy_fastapi_demo_1.model.students import Student
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy_fastapi_demo_1.scheme.Score import ScoreCreate,ScoreUpdate
|
||||
|
||||
class ScoreDAO:
|
||||
"""用户数据访问对象,所有方法均为静态方法,方便调用"""
|
||||
|
||||
@staticmethod
|
||||
def inspect_stu_id_unq(db:Session,stu_id:int):
|
||||
return db.query(Student).filter(Student.stu_id == stu_id,Student.is_deleted==0).first()
|
||||
|
||||
|
||||
# 查所有数据
|
||||
@staticmethod
|
||||
def get_all(db:Session,skip:int=0,limit:int=100):
|
||||
return db.query(Score).offset(skip).limit(limit).all()
|
||||
|
||||
# 查询单个数据
|
||||
@staticmethod
|
||||
def get_by_id(db:Session,stu_id:int,exam_id:int):
|
||||
return db.query(Score).filter(Score.stu_id == stu_id,Score.exam_id==exam_id).first()
|
||||
|
||||
# 添加数据
|
||||
@staticmethod
|
||||
def post_score(db:Session,score_create:ScoreCreate):
|
||||
# 将 Pydantic 模型转为字典,并解包构建 SQLAlchemy 模型实例
|
||||
db_score=Score(**score_create.model_dump())
|
||||
db.add(db_score) #添加到会话
|
||||
db.commit() #提交事务,此时会执行 INSERT,并自动填充自增字段
|
||||
db.refresh(db_score) #刷新对象,获取数据库生成的默认值(如 created_at)
|
||||
return db_score
|
||||
|
||||
# 更改数据
|
||||
@staticmethod
|
||||
def put_score(db:Session,score_id:int,exam_id:int,score_update:ScoreUpdate):
|
||||
db_score=ScoreDAO.get_by_id(db,score_id,exam_id)
|
||||
if not db_score:
|
||||
return None
|
||||
# 只更新客户端显式传入的字段(exclude_unset=True 排除未设置的字段)
|
||||
put_score_data = score_update.model_dump(exclude_unset=True)
|
||||
for k,v in put_score_data.items():
|
||||
setattr(db_score,k,v) # 动态设置属性
|
||||
db.commit()
|
||||
db.refresh(db_score)
|
||||
return db_score
|
||||
|
||||
# 删除
|
||||
@staticmethod
|
||||
def delete_score(db:Session,score_id:int,exam_id:int):
|
||||
db_score=ScoreDAO.get_by_id(db,score_id,exam_id)
|
||||
if not db_score:
|
||||
return False
|
||||
db.delete(db_score)
|
||||
db.commit()
|
||||
return True
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,56 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy_fastapi_demo_1.model.advisors import Advisor
|
||||
from sqlalchemy_fastapi_demo_1.model.students import Student
|
||||
|
||||
def create_advisor(db:Session,advisor_id:int,advisor_name:str):
|
||||
advisor = Advisor(advisor_id=advisor_id,advisor_name=advisor_name)
|
||||
db.add(advisor)
|
||||
db.commit()
|
||||
db.refresh(advisor)
|
||||
return advisor
|
||||
|
||||
|
||||
def search_advisor(db:Session,advisor_id:int|None=None,advisor_name:str|None=None):
|
||||
if advisor_id is None and advisor_name is None:
|
||||
return None
|
||||
query=db.query(Advisor)
|
||||
if advisor_id is not None:
|
||||
query = query.filter(Advisor.advisor_id == advisor_id)
|
||||
if advisor_name is not None:
|
||||
query = query.filter(Advisor.advisor_name==advisor_name)
|
||||
advisor = query.first()
|
||||
return advisor
|
||||
|
||||
|
||||
def list_advisors(db: Session,advisor_name: str|None =None):
|
||||
query = db.query(Advisor)
|
||||
if advisor_name is not None:
|
||||
query = query.filter(Advisor.advisor_name.like(f"%{advisor_name}%"))
|
||||
return query.all()
|
||||
|
||||
|
||||
def list_students_of_advisor(db: Session, advisor_id: int):
|
||||
"""查这个顾问名下还没被逻辑删除的学生"""
|
||||
return db.query(Student).filter(
|
||||
Student.is_deleted == 0,
|
||||
Student.advisor_id == advisor_id,
|
||||
).all()
|
||||
|
||||
|
||||
def update_advisor(db:Session,advisor_id:int,advisor_name:str|None=None):
|
||||
r_advisor = search_advisor(db=db,advisor_id=advisor_id)
|
||||
if r_advisor is None:
|
||||
return None
|
||||
if advisor_name is not None:
|
||||
r_advisor.advisor_name = advisor_name
|
||||
db.commit()
|
||||
return r_advisor
|
||||
|
||||
|
||||
def delete_advisor(db:Session,advisor_id:int):
|
||||
d_advisor = search_advisor(db=db,advisor_id=advisor_id)
|
||||
if d_advisor is None:
|
||||
return None
|
||||
db.delete(d_advisor)
|
||||
db.commit()
|
||||
return advisor_id
|
||||
@@ -0,0 +1,41 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy_fastapi_demo_1.dao.advisor import crud
|
||||
from sqlalchemy_fastapi_demo_1.scheme.advisors import AdvisorIn
|
||||
|
||||
# 成功 (数据, None);失败 (None, "错误码")。
|
||||
|
||||
def create_advisor(db:Session,advisor_id:int,advisor_name:str):
|
||||
exist=crud.search_advisor(db,advisor_id=advisor_id)
|
||||
if exist is not None:
|
||||
return None,"DUPLICATE" #ID主键已被占用
|
||||
try:
|
||||
advisor = crud.create_advisor(db,advisor_id,advisor_name)
|
||||
except IntegrityError:
|
||||
db.rollback()
|
||||
return None, "DUPLICATE"
|
||||
return advisor, None
|
||||
|
||||
|
||||
def update_advisor(db:Session,advisor_id:int,advisor_in:AdvisorIn):
|
||||
exist = crud.search_advisor(db,advisor_id=advisor_id)
|
||||
if exist is None:
|
||||
return None, "NOT_FOUND"
|
||||
u_advisor=crud.update_advisor(db,advisor_id=advisor_id,advisor_name=advisor_in.advisor_name)
|
||||
if u_advisor is None:
|
||||
return None, "NOT_FOUND" # 查过之后被并发删掉了
|
||||
return u_advisor,None
|
||||
|
||||
|
||||
#删除成功返回Advisor ID,删除失败返回None。顾问如连接学生,则拒绝删除。
|
||||
def delete_advisor(db: Session, advisor_id: int):
|
||||
exist = crud.search_advisor(db=db, advisor_id=advisor_id)
|
||||
if exist is None:
|
||||
return None, "NOT_FOUND"
|
||||
# 查一下这个顾问名下有没有学生
|
||||
if crud.list_students_of_advisor(db, advisor_id):
|
||||
return None, "HAS_STUDENTS"
|
||||
result = crud.delete_advisor(db=db, advisor_id=advisor_id)
|
||||
if result is None:
|
||||
return None, "NOT_FOUND" # 查过之后被并发删掉了
|
||||
return result, None
|
||||
@@ -0,0 +1,68 @@
|
||||
# # dao/c_lass.py 数据访问层,直接操作数据库
|
||||
# # 本文件封装对 Classinfo表的所有数据库操作(增、删、改、查)
|
||||
|
||||
from sqlalchemy.orm import Session # 会话类型,标注db参数
|
||||
from sqlalchemy_fastapi_demo_1.model.c_lass import Classinfo # # 导入ORM模型
|
||||
from sqlalchemy_fastapi_demo_1.scheme.c_lass import ClassCreate, ClassUpdate # 导入入参模型
|
||||
from typing import Optional, List # 类型注解用:Optional=可空,List=列表
|
||||
|
||||
|
||||
|
||||
# 新增班级
|
||||
def create_c_lass(db: Session, obj: ClassCreate):# 新增函数:接收会话db和校验后的数据obj,返回Classinfo对象
|
||||
db_obj = Classinfo(class_id=obj.class_id, start_time=obj.start_time)
|
||||
# 把前端传来的数据组装成一个ORM对象(相当于内存里的一条新记录)
|
||||
db.add(db_obj) # 把对象加入会话
|
||||
db.commit() # 提交事务:真正把INSERT语句发给MySQL执行,写进数据库
|
||||
db.refresh(db_obj) # 从数据库重新查一遍这条记录,把数据库自动生成的值(如默认值)刷新回对象
|
||||
return db_obj # 返回这个对象,方便API层序列化成JSON返回前端
|
||||
|
||||
|
||||
# 根据 id 查询单个班级:只查询 is_deleted=0 未删除
|
||||
def get_c_lass(db: Session, class_id: int) -> Optional[Classinfo]:
|
||||
# 按ID查单个班级;Optional = 可能查不到(返回None)
|
||||
return db.query(Classinfo).filter(
|
||||
Classinfo.class_id == class_id,# 条件1:班级编号等于传入的id
|
||||
Classinfo.is_deleted == 0 # 条件2:没被逻辑删除(只查0)
|
||||
).first()
|
||||
# .first() = 取第一条;查不到就返回None,不会报错
|
||||
|
||||
# 分页查询所有班级:过滤已经逻辑删除的数据
|
||||
def get_all_c_lass(db: Session, skip: int = 0, limit: int = 100) -> List[Classinfo]:
|
||||
# 分页查所有班级:skip=跳过几条,limit=最多取几条
|
||||
return db.query(Classinfo)\
|
||||
.filter(Classinfo.is_deleted == 0)\
|
||||
.offset(skip).limit(limit).all() # # 只查没删的; 跳过skip条,取limit条,返回列表
|
||||
|
||||
|
||||
# 修改班级(局部更新:传了哪个字段就改哪个)
|
||||
def update_c_lass(db: Session, class_id: int, update_obj: ClassUpdate):
|
||||
# 修改函数:按id找到班级,把传进来的字段改掉
|
||||
db_class = get_c_lass(db, class_id)
|
||||
|
||||
# 只取前端真正传了的字段
|
||||
update_data = update_obj.model_dump(exclude_unset=True)
|
||||
# 把前端传的修改数据转成字典;exclude_unset=True = 只保留前端真正传了的字段
|
||||
# (没传的字段不出现,就不会被误改成None——就是"局部更新")
|
||||
for key, value in update_data.items():# 循环:把字典里每个字段的值,赋给ORM对象对应属性(内存里改好)
|
||||
setattr(db_class, key, value)
|
||||
db.commit() # 提交:把改动写进数据库
|
||||
db.refresh(db_class) # 从数据库重新查询这条记录,把数据库里的最新值刷新到 Python 对象上,让内存里的对象和数据库保持一致。
|
||||
return db_class # 返回改好的对象
|
||||
|
||||
|
||||
# 逻辑删除:不再db.delete,设置is_deleted=1
|
||||
def delete_c_lass(db: Session, class_id: int) -> bool:
|
||||
# 删除函数:不是物理删除(不删数据库行),是打标记
|
||||
db_class = get_c_lass(db, class_id) # 先查这条班级
|
||||
if not db_class:
|
||||
return False # 查不到返回False,API层返回404
|
||||
# 打删除标记
|
||||
db_class.is_deleted = 1 # 把逻辑删除标记改成1(=已删除
|
||||
db.commit()
|
||||
return True # 返回True表示删除成功
|
||||
|
||||
|
||||
#DAO 层是所有 SQL 的家,API 层不写任何 SQL,只调这里的函数。
|
||||
# 查询统一过滤 `is_deleted == 0`,删了的数据还在数据库里,只是查不到 —— 这就是逻辑删除。
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_, Date
|
||||
from sqlalchemy_fastapi_demo_1.model.employment import EmploymentBase,EmploymentOffer
|
||||
from sqlalchemy_fastapi_demo_1.model.students import Student
|
||||
# ========== 就业基础信息 DAO ==========
|
||||
class EmploymentBaseDAO:
|
||||
@staticmethod
|
||||
#根据学生编号查询
|
||||
def get_by_stu_id(db: Session, stu_id: int):
|
||||
record = db.query(EmploymentBase).filter(and_(
|
||||
EmploymentBase.stu_id == stu_id,
|
||||
EmploymentBase.is_deleted == 0
|
||||
)).first()
|
||||
return record
|
||||
@staticmethod
|
||||
#多条件查询
|
||||
def different_choice_query(
|
||||
db: Session,
|
||||
stu_id: int = None,
|
||||
company_name: str = None,
|
||||
min_salary: float = None,
|
||||
max_salary: float = None,
|
||||
skip: int = 0,
|
||||
limit: int = 10
|
||||
):
|
||||
# 基础查询 + 过滤未删除
|
||||
query = db.query(EmploymentBase).filter(EmploymentBase.is_deleted == 0)
|
||||
|
||||
# 动态拼接条件
|
||||
#学生id查询
|
||||
if stu_id is not None:
|
||||
query = query.filter(EmploymentBase.stu_id == stu_id)
|
||||
#公司名称查询
|
||||
if company_name is not None and company_name.strip() != "":
|
||||
# 模糊匹配,%代表通配符
|
||||
query = query.filter(EmploymentBase.company_name.like(f"%{company_name}%"))
|
||||
#工资范围查询
|
||||
if min_salary is not None:
|
||||
query = query.filter(EmploymentBase.salary >= min_salary)
|
||||
if max_salary is not None:
|
||||
query = query.filter(EmploymentBase.salary <= max_salary)
|
||||
|
||||
# 分页
|
||||
result = query.offset(skip).limit(limit).all()
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
#新增学生就业基础信息
|
||||
def create(db: Session, obj: EmploymentBase):
|
||||
|
||||
student = db.query(Student).filter(
|
||||
Student.stu_id == obj.stu_id,
|
||||
Student.is_deleted == 0
|
||||
).first()
|
||||
if not student:
|
||||
raise HTTPException(status_code=400, detail="学生不存在或已逻辑删除,不能添加就业信息")
|
||||
db.add(obj)
|
||||
db.commit()
|
||||
db.refresh(obj)
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
#更新就业基础信息
|
||||
def update(db: Session, stu_id: int, update_data: dict):
|
||||
record = EmploymentBaseDAO.get_by_stu_id(db, stu_id)
|
||||
if not record:
|
||||
return None
|
||||
|
||||
if "company_name" in update_data:
|
||||
record.company_name = update_data["company_name"]
|
||||
if "salary" in update_data:
|
||||
record.salary = update_data["salary"]
|
||||
if "employment_open_time" in update_data:
|
||||
record.employment_open_time = update_data["employment_open_time"]
|
||||
if "job_time" in update_data:
|
||||
record.job_time = update_data["job_time"]
|
||||
# =========== 统一时间校验 ===========
|
||||
final_emp_open = record.employment_open_time
|
||||
final_job = record.job_time
|
||||
# 两个时间都不为空才校验
|
||||
if final_emp_open and final_job:
|
||||
if final_job < final_emp_open:
|
||||
raise ValueError("job时间不能早于就业开放时间")
|
||||
|
||||
db.commit()
|
||||
db.refresh(record)
|
||||
return record
|
||||
|
||||
@staticmethod
|
||||
#根据学生编号对就业基础表进行逻辑删除
|
||||
def delete(db: Session, stu_id: int):
|
||||
record = EmploymentBaseDAO.get_by_stu_id(db, stu_id)
|
||||
if not record:
|
||||
return False
|
||||
record.is_deleted = 1
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
# ========== 就业Offer协议 DAO ==========
|
||||
class EmploymentOfferDAO:
|
||||
|
||||
@staticmethod
|
||||
#添加就业协议记录
|
||||
def create(db: Session, obj_1: EmploymentOffer,obj_2:EmploymentBase):
|
||||
# 时间校验
|
||||
if obj_1.offer_time < obj_2.employment_open_time:
|
||||
raise ValueError("offer时间不能早于就业开放时间")
|
||||
db.add(obj_1)
|
||||
db.commit()
|
||||
db.refresh(obj_1)
|
||||
return obj_1
|
||||
|
||||
@staticmethod
|
||||
#修改就业协议表
|
||||
def update(db: Session, stu_id: int, offer_id: int, offer_time:Date):
|
||||
"""修改offer"""
|
||||
record_list = EmploymentOfferDAO.different_choice_query(db, stu_id, offer_id)
|
||||
if not record_list:
|
||||
return None
|
||||
record = record_list[0]
|
||||
#查询就业基础信息,拿到就业开放时间准备做时间校验
|
||||
base_record = db.query(EmploymentBase).filter(
|
||||
EmploymentBase.stu_id == stu_id,
|
||||
EmploymentBase.is_deleted == 0
|
||||
).first()
|
||||
#避免就业基础信息被逻辑删除了而就业基础协议记录还存在
|
||||
if not base_record:
|
||||
raise ValueError("未找到该学生就业基础信息")
|
||||
#时间校验
|
||||
if offer_time is not None:
|
||||
if offer_time < base_record.employment_open_time:
|
||||
raise ValueError("offer时间不能早于就业开放时间")
|
||||
record.offer_time = offer_time
|
||||
|
||||
db.commit()
|
||||
db.refresh(record)
|
||||
return record
|
||||
|
||||
@staticmethod
|
||||
#根据协议编号对就业协议表进行逻辑删除
|
||||
def delete(db: Session, stu_id: int,offer_id: int):
|
||||
"""offer逻辑删除"""
|
||||
record_list = EmploymentOfferDAO.different_choice_query(db, stu_id,offer_id)
|
||||
if not record_list:
|
||||
return False
|
||||
record = record_list[0]
|
||||
record.is_deleted = 1
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
#多条件查询就业协议表
|
||||
def different_choice_query(
|
||||
db: Session,
|
||||
stu_id: int =None ,
|
||||
offer_id: int = None,
|
||||
skip: int = 0,
|
||||
limit: int = 10
|
||||
):
|
||||
#基础查询+过滤未删除
|
||||
query = db.query(EmploymentOffer).filter(EmploymentOffer.is_deleted == 0)
|
||||
|
||||
#动态拼接条件
|
||||
#学生id查询(选填)
|
||||
if stu_id is not None:
|
||||
query = query.filter(EmploymentOffer.stu_id == stu_id)
|
||||
# 同时传了offer编号 → 查单个(联合主键两个都查)
|
||||
if offer_id is not None:
|
||||
query = query.filter(EmploymentOffer.offer_id == offer_id)
|
||||
elif stu_id is None and offer_id is not None:
|
||||
raise ValueError("不能单独查询offer编号,必须同时提供学生编号")
|
||||
# 分页
|
||||
result = query.offset(skip).limit(limit).all()
|
||||
return result
|
||||
@@ -0,0 +1,110 @@
|
||||
from sqlalchemy import func, case
|
||||
from sqlalchemy_fastapi_demo_1.model.students import Student
|
||||
from sqlalchemy_fastapi_demo_1.model.Score import Score
|
||||
from sqlalchemy_fastapi_demo_1.model.employment import EmploymentBase,EmploymentOffer
|
||||
from sqlalchemy_fastapi_demo_1.model.c_lass import Classinfo
|
||||
|
||||
def students_age(db,op:str,
|
||||
age_value: int = None,
|
||||
age_min: int = None,
|
||||
age_max: int = None):
|
||||
|
||||
a=db.query(Student).filter(Student.is_deleted == 0)
|
||||
if op == ">":
|
||||
a=a.filter(Student.age > age_value)
|
||||
elif op == "<":
|
||||
a=a.filter(Student.age < age_value)
|
||||
elif op == "=":
|
||||
a=a.filter(Student.age == age_value)
|
||||
elif op == ">=":
|
||||
a=a.filter(Student.age >= age_value)
|
||||
elif op == "<=":
|
||||
a=a.filter(Student.age <= age_value)
|
||||
elif op == "between":
|
||||
if age_min is None or age_max is None:
|
||||
raise ValueError("区间查询必须提供 age_min和age_max")
|
||||
a=a.filter(Student.age.between(age_min, age_max))
|
||||
else:
|
||||
raise ValueError("请使用 >, <, =, >=, <=, between")
|
||||
return a.all()
|
||||
# 多维度班级统计**:统计每个班级的总人数,以及按性别(男、女)细分的人数分布。
|
||||
def class_statistics(db):
|
||||
a = (db.query(Student.class_id,func.count(Student.stu_id).label("total"),
|
||||
func.sum(case((Student.gender == "男", 1), else_=0)).label("male"),
|
||||
func.sum(case((Student.gender == "女", 1), else_=0)
|
||||
).label("female"))
|
||||
.filter(Student.is_deleted==0).group_by(Student.class_id).all())
|
||||
list1 = []
|
||||
for i in a:
|
||||
list1.append({"class_id": i.class_id,"total": i.total,"male": i.male,"female": i.female})
|
||||
return list1
|
||||
|
||||
# - 查询每次考试成绩都在输入分数线(如80分)以上的学生的编号、姓名和成绩。
|
||||
def score_above(db,score_input):
|
||||
result = []
|
||||
for stu in db.query(Student).filter(Student.is_deleted == 0).all():
|
||||
if stu.score and all(s.score >= score_input for s in stu.score):
|
||||
for s in stu.score:
|
||||
result.append((stu.stu_id, stu.stu_name, s.exam_id, s.score))
|
||||
return result
|
||||
# - 查询有输入指定次数(如两次)以上不及格的学生的姓名、班级和不及格成绩明细。
|
||||
def score_fail(db, fail_times, fail_score=60):
|
||||
result = []
|
||||
for stu in db.query(Student).filter(Student.is_deleted == 0).all():
|
||||
fails = [s for s in stu.score if s.score < fail_score]
|
||||
if len(fails) >= fail_times:
|
||||
for s in fails:
|
||||
result.append((stu.stu_name, stu.class_id, s.score))
|
||||
return result
|
||||
|
||||
# - 统计每次考试每个班级的平均分,并支持按分数从高到低或从低到高动态排序。
|
||||
def avg_scores(db, order="desc"):
|
||||
avg = func.avg(Score.score).label("avg_score")
|
||||
a = (db.query(Score.exam_id,Student.class_id,avg)
|
||||
.join(Score.student).filter(Student.is_deleted == 0)
|
||||
.group_by(Score.exam_id,Student.class_id))
|
||||
a = a.order_by(avg.desc() if order == "desc" else avg.asc())
|
||||
return a.all()
|
||||
|
||||
# - 统计就业薪资排名 Top N(动态输入 N)的学生的姓名、班级、就业时间和就业公司。
|
||||
def top_salary(db, n):
|
||||
a = (db.query(Student.stu_name,
|
||||
Student.class_id,
|
||||
EmploymentBase.job_time,
|
||||
EmploymentBase.company_name,
|
||||
EmploymentBase.salary).join(Student, EmploymentBase.stu_id == Student.stu_id)
|
||||
.filter(EmploymentBase.is_deleted == 0, Student.is_deleted == 0)
|
||||
.order_by(EmploymentBase.salary.desc()).offset(n-1).limit(1).first())
|
||||
if not a:
|
||||
return None
|
||||
return {"name": a.stu_name,"class_id": a.class_id,"job_time": str(a.job_time) if a.job_time else None,
|
||||
"company": a.company_name,"salary": a.salary}
|
||||
|
||||
# - 统计每个学生的就业时长(计算公式:offer下发时间 - 就业开放时间)。
|
||||
def stu_every(db):
|
||||
a = (db.query(Student.stu_id,
|
||||
Student.stu_name,
|
||||
EmploymentBase.employment_open_time,
|
||||
EmploymentOffer.offer_time)
|
||||
.join(EmploymentBase, EmploymentBase.stu_id == Student.stu_id)
|
||||
.join(EmploymentOffer, EmploymentOffer.stu_id == Student.stu_id)
|
||||
.filter(Student.is_deleted == 0,EmploymentBase.is_deleted == 0,
|
||||
EmploymentOffer.is_deleted == 0).all())
|
||||
return [{"stu_id": sid,"name": name,
|
||||
"days": (offer_t - open_t).days if open_t and offer_t else None}
|
||||
for sid, name, open_t, offer_t in a]
|
||||
|
||||
# - 统计每个班级的平均就业时长(仅统计进入就业阶段,即有就业开放时间的学生)。
|
||||
def class_avg(db):
|
||||
days = func.datediff(EmploymentOffer.offer_time,
|
||||
EmploymentBase.employment_open_time).label("days")
|
||||
a = (db.query(Student.class_id,
|
||||
func.avg(days).label("avg_days"),
|
||||
func.count().label("count"))
|
||||
.join(EmploymentBase, EmploymentBase.stu_id == Student.stu_id)
|
||||
.join(EmploymentOffer, EmploymentOffer.stu_id == Student.stu_id)
|
||||
.filter(Student.is_deleted == 0,
|
||||
EmploymentBase.is_deleted == 0,
|
||||
EmploymentOffer.is_deleted == 0,
|
||||
EmploymentBase.employment_open_time.isnot(None)).group_by(Student.class_id).all())
|
||||
return [{"class_id": i.class_id, "avg_days": round(float(i.avg_days), 2)} for i in a]
|
||||
@@ -0,0 +1,115 @@
|
||||
# dao/students_dao.py
|
||||
# 本文件封装对 model 表的所有数据库操作(增、删、改、查)
|
||||
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy_fastapi_demo_1.model.c_lass import Classinfo
|
||||
from sqlalchemy_fastapi_demo_1.model.advisors import Advisor
|
||||
from sqlalchemy_fastapi_demo_1.model.students import Student
|
||||
from sqlalchemy_fastapi_demo_1.scheme.students import StudentCreate, StudentUpdate
|
||||
from typing import Optional, List
|
||||
|
||||
|
||||
class StudentDAO:
|
||||
|
||||
@staticmethod
|
||||
def inspect_class_id_unq(db: Session, class_id: int):
|
||||
"""校验班级是否存在:去 c_lass 表查"""
|
||||
return db.query(Classinfo).filter(Classinfo.class_id == class_id).first()
|
||||
|
||||
@staticmethod
|
||||
def inspect_advisor_id_unq(db: Session, advisor_id: int):
|
||||
"""校验顾问是否存在:去 advisors 表查"""
|
||||
return db.query(Advisor).filter(Advisor.advisor_id == advisor_id).first()
|
||||
|
||||
@staticmethod
|
||||
def create_student(db: Session, obj_in: StudentCreate):
|
||||
"""新增学生,检查学号唯一性"""
|
||||
exist = db.query(Student).filter(Student.stu_id == obj_in.stu_id).first()
|
||||
# 只按 stu_id 判断,且包含逻辑删除的记录
|
||||
if exist:
|
||||
if exist.is_deleted == 1:
|
||||
raise HTTPException(status_code=400, detail="该学号已被占用(处于逻辑删除状态),请先恢复")
|
||||
raise HTTPException(status_code=400, detail="该学生已存在")
|
||||
db_obj = Student(**obj_in.model_dump())
|
||||
try:
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
except Exception:
|
||||
db.rollback()
|
||||
raise
|
||||
return db_obj
|
||||
|
||||
@staticmethod
|
||||
def get_students_all(db: Session, skip: int = 0, limit: int = 100):
|
||||
"""分页查询全部学生"""
|
||||
return db.query(Student).filter(Student.is_deleted == 0).offset(skip).limit(limit).all()
|
||||
|
||||
@staticmethod
|
||||
def get_student_by_id(db: Session, stu_id: int):
|
||||
"""根据学生编号单条查询"""
|
||||
return db.query(Student).filter(Student.is_deleted == 0,
|
||||
Student.stu_id == stu_id).first()
|
||||
|
||||
@staticmethod
|
||||
def query_multi_condition(
|
||||
db: Session,
|
||||
stu_id: Optional[int] = None,
|
||||
stu_name: Optional[str] = None,
|
||||
class_id: Optional[int] = None,
|
||||
skip: int = 0,
|
||||
limit: int = 100
|
||||
) :
|
||||
q = db.query(Student).filter(Student.is_deleted == 0)
|
||||
"""多条件选择性查询"""
|
||||
# 只有参数不为None的时候,才加上查询条件
|
||||
if stu_id is not None:
|
||||
q = q.filter(Student.stu_id == stu_id)
|
||||
if stu_name is not None:
|
||||
q = q.filter(Student.stu_name.like(f"%{stu_name}%"))
|
||||
if class_id is not None:
|
||||
q = q.filter(Student.class_id == class_id)
|
||||
|
||||
q = q.offset(skip).limit(limit)
|
||||
return q.all()
|
||||
|
||||
@staticmethod
|
||||
def update_student(db: Session,stu_id:int, obj: StudentUpdate):
|
||||
db_obj = db.query(Student).filter(Student.stu_id == stu_id,
|
||||
Student.is_deleted == 0).first()
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="学生数据不存在或已删除")
|
||||
|
||||
update_data = obj.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(db_obj, field, value)
|
||||
try:
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
except Exception:
|
||||
db.rollback()
|
||||
raise
|
||||
return db_obj
|
||||
|
||||
@staticmethod
|
||||
def delete_student(db: Session, stu_id: int):
|
||||
db_obj = db.query(Student).filter(Student.stu_id == stu_id,
|
||||
Student.is_deleted == 0).first()
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="学生数据不存在或已删除")
|
||||
|
||||
db_obj.is_deleted = 1
|
||||
try:
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
except Exception:
|
||||
db.rollback()
|
||||
raise
|
||||
return True # 返回布尔值,而不是 dict
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# dao/teachers_dao.py
|
||||
# 本文件封装对 Teacher 表的所有数据库操作(增、删、改、查)
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy_fastapi_demo_1.scheme.teachers import TeacherAdd, TeacherUpdate
|
||||
from sqlalchemy_fastapi_demo_1.model.teachers import Teacher
|
||||
from sqlalchemy_fastapi_demo_1.model.c_lass import Classinfo
|
||||
|
||||
class TeacherDAO:
|
||||
@staticmethod
|
||||
def add_teacher(db:Session,teacher_data:TeacherAdd): # 前端返回的teacher_data是1个Pydantic对象
|
||||
db_teacher=Teacher(**(teacher_data.model_dump()))
|
||||
db.add(db_teacher)
|
||||
db.commit()
|
||||
db.refresh(db_teacher)
|
||||
return db_teacher
|
||||
@staticmethod
|
||||
def inspect_teacher_id_unq(db: Session, teacher_id: int):
|
||||
"""
|
||||
根据教师ID获取教师对象(用于主键唯一性检查,查询结果包含被软删除的对象)
|
||||
"""
|
||||
return db.query(Teacher).filter(Teacher.teacher_id == teacher_id).first()
|
||||
@staticmethod
|
||||
def inspect_class_id_unq(db: Session, class_id: int):
|
||||
"""
|
||||
根据班级ID获取班级对象(用于外键检查,查询结果排除被软删除对象,即不包括is_deleted=1的对象)
|
||||
"""
|
||||
return db.query(Classinfo).filter(Classinfo.class_id == class_id,Classinfo.is_deleted==0).first()
|
||||
@staticmethod
|
||||
def delete_light(db: Session, teacher_id: int):
|
||||
"""
|
||||
删除教师
|
||||
:return: True 表示删除成功,False 表示教师ID不存在,已被软删除或硬删除
|
||||
"""
|
||||
db_teacher = TeacherDAO.inspect_teacher_id_unq(db, teacher_id)
|
||||
if (not db_teacher) or db_teacher.is_deleted==1:
|
||||
return False
|
||||
db_teacher.is_deleted=1 # 标记为1逻辑删除
|
||||
db.commit() # 提交事务
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def update(db: Session,teacher_id:int,teacher_data: TeacherUpdate):
|
||||
"""
|
||||
更新用户信息(只更新传入的非空字段)
|
||||
:param db: 数据库会话
|
||||
:param teacher_data: 包含要更新字段的 Pydantic 模型
|
||||
:return: 更新后的 User 对象,如果用户不存在则返回 None
|
||||
"""
|
||||
# 只更新客户端显式传入的字段(exclude_unset=True 排除未设置的字段,不排除设置了None的字段)
|
||||
update_data = teacher_data.model_dump(exclude_unset=True) # 返回要更新字段的字典
|
||||
db_teacher=TeacherDAO.inspect_teacher_id_unq(db, teacher_id)
|
||||
for key, value in update_data.items():
|
||||
setattr(db_teacher, key, value) # 动态设置属性
|
||||
db.commit() # 提交事务
|
||||
db.refresh(db_teacher) # 刷新对象,获取 onupdate 时间等
|
||||
return db_teacher
|
||||
@staticmethod
|
||||
def get_all(db: Session, skip: int = 0, limit: int = 100):
|
||||
"""
|
||||
获取所有用户(支持分页,不显示软删除)
|
||||
:param db: 数据库会话
|
||||
:param skip: 偏移量(跳过前 skip 条,从skip+1条开始返回)
|
||||
:param limit: 最大返回条数
|
||||
:return: 用老师对象列表
|
||||
"""
|
||||
return db.query(Teacher).filter(Teacher.is_deleted==0).offset(skip).limit(limit).all()
|
||||
|
||||
Reference in New Issue
Block a user