teacher.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. import copy
  4. import os
  5. from typing import Optional
  6. from fastapi import APIRouter, Query, Depends, Path, UploadFile, File
  7. from openpyxl import load_workbook
  8. from sqlalchemy import text
  9. from sqlalchemy.ext.asyncio import AsyncSession
  10. from starlette.background import BackgroundTasks
  11. from admin.api.endpoints.school.utils import check_row
  12. from core.config import settings
  13. from core.security import hashed_password
  14. from crud.school import crud_class, crud_school, crud_grade
  15. from crud.sysdata.role import crud_role
  16. from crud.user import crud_teacher
  17. from models.user import SysUser
  18. from schemas.base import OrderByField, ReturnField
  19. from schemas.school.teacher import (TeacherList, TeacherDetail, NewTeacher,
  20. TeacherInDB, UpdateTeacher)
  21. from utils.depends import get_async_db, get_current_user
  22. router = APIRouter()
  23. # 教师列表
  24. @router.get("/teachers",
  25. response_model=TeacherList,
  26. response_model_exclude_none=True,
  27. summary="教师列表")
  28. async def get_teachers(
  29. page: Optional[int] = None,
  30. size: Optional[int] = None,
  31. name: str = Query("", description="教师姓名"),
  32. tid: int = Query(0, description="教师ID"),
  33. sid: int = Query(0, description="学校ID"),
  34. gid: int = Query(0, description="年级ID"),
  35. cid: str = Query("", description="班级ID"),
  36. rid: int = Query(0, description="职务(角色ID)"),
  37. sub: str = Query("", description="任教科目"),
  38. order: OrderByField = Query("-created_at",
  39. description="排序字段,用逗号分隔,升降序以-判断,默认-created_at"),
  40. res: ReturnField = Query("", description="返回字段,默认列表展示字段, 自定义id,name,phone"),
  41. db: AsyncSession = Depends(get_async_db),
  42. current_user: SysUser = Depends(get_current_user)):
  43. # 过滤条件
  44. filters = []
  45. if name:
  46. filters.append(text(f"name = '{name}'"))
  47. if tid:
  48. filters.append(text(f"id = {tid}"))
  49. if sid:
  50. filters.append(text(f"school_id = {sid}"))
  51. if gid:
  52. filters.append(text(f"grade_id = {gid}"))
  53. if cid:
  54. filters.append(text(f"class_id LIKE '%{cid}%'"))
  55. if rid:
  56. filters.append(text(f"role_id = {rid}"))
  57. if sub:
  58. filters.append(text(f"subject = '{sub}'"))
  59. if ((page is not None) and page >= 1) and ((size is not None)
  60. and size >= 1):
  61. offset = (page - 1) * size
  62. else:
  63. offset = size = None
  64. # 排序
  65. if isinstance(order, str):
  66. order = [text(order)]
  67. total, items = await crud_teacher.find_all(db,
  68. filters=filters,
  69. offset=offset,
  70. limit=size,
  71. order_by=order,
  72. return_fields=res)
  73. return {"total": total, "data": items}
  74. # 创建教师
  75. @router.post("/teachers",
  76. response_model=TeacherDetail,
  77. response_model_exclude_none=True,
  78. summary="创建教师")
  79. async def create_teacher(info: NewTeacher,
  80. db: AsyncSession = Depends(get_async_db),
  81. current_user: SysUser = Depends(get_current_user)):
  82. # 判断学校是否存在
  83. db_school = await crud_school.find_one(db, filters={"id": info.school_id})
  84. if not db_school:
  85. return {"errcode": 404, "mess": "学校不存在!"}
  86. # 判断年级是否存在
  87. db_grade = await crud_grade.find_one(db, filters={"id": info.grade_id})
  88. if not db_grade:
  89. return {"errcode": 404, "mess": "年级不存在!"}
  90. # 判断角色是否存在
  91. db_role = await crud_role.find_one(db, filters={"id": info.role_id})
  92. if not db_role:
  93. return {"errcode": 404, "mess": "角色不存在!"}
  94. # 判断是否存在同年级同名教师
  95. db_teacher = await crud_teacher.find_one(db, filters={"phone": info.phone})
  96. if db_teacher:
  97. return {"errcode": 400, "mess": "手机号重复!"}
  98. # 判断班级是否存在
  99. class_ids = [x.strip() for x in info.class_id.split(",") if x.strip()]
  100. total, db_class = await crud_class.find_all(
  101. db, filters=[text(f"id IN ({info.class_id})")])
  102. if total != len(class_ids):
  103. return {"errcode": 400, "mess": "班级不存在!"}
  104. # 开始创建
  105. obj_in = TeacherInDB(**info.dict(by_alias=True),
  106. school_name=db_school.name,
  107. grade_name=db_grade.name,
  108. class_name=",".join([x.name for x in db_class]),
  109. role_name=db_role.name,
  110. username=info.phone,
  111. password=hashed_password(info.phone[-6:]),
  112. creator_id=current_user.id,
  113. creator_name=current_user.username,
  114. editor_id=current_user.id,
  115. editor_name=current_user.username)
  116. db_obj = await crud_teacher.insert_one(db, obj_in)
  117. return {"data": db_obj}
  118. @router.get("/teachers/{tid}",
  119. response_model=TeacherDetail,
  120. response_model_exclude_none=True,
  121. summary="教师详情")
  122. async def get_teacher(tid: int = Path(..., description="教师ID"),
  123. db: AsyncSession = Depends(get_async_db),
  124. current_user: SysUser = Depends(get_current_user)):
  125. db_obj = await crud_teacher.find_one(db, filters={"id": tid})
  126. return {"data": db_obj}
  127. # 更新教师
  128. @router.put("/teachers/{tid}",
  129. response_model=TeacherDetail,
  130. response_model_exclude_none=True,
  131. summary="更新教师")
  132. async def update_teacher(info: UpdateTeacher,
  133. tid: int = Path(..., description="教师ID"),
  134. db: AsyncSession = Depends(get_async_db),
  135. current_user: SysUser = Depends(get_current_user)):
  136. # 判断提交参数是否为空
  137. info_dict = info.dict(exclude_none=True)
  138. if not info_dict:
  139. return {"errcode": 400, "mess": "提交参数为空!"}
  140. # 判断教师是否存在
  141. db_obj = await crud_teacher.find_one(db, filters={"id": tid})
  142. if not db_obj:
  143. return {"errcode": 404, "mess": "教师不存在!"}
  144. # 判断手机号是否重复
  145. if ("phone" in info_dict) and (db_obj.phone != info_dict["phone"]):
  146. db_teacher = await crud_teacher.find_one(
  147. db, filters=[text(f"id != {tid}"),
  148. text(f"phone = '{info.phone}'")])
  149. if db_teacher:
  150. return {"errcode": 400, "mess": "手机号重复!"}
  151. info.username = info.phone[-6:]
  152. # 判断学校是否存在
  153. if ("school_id"
  154. in info_dict) and (db_obj.school_id != info_dict["school_id"]):
  155. db_school = await crud_school.find_one(db,
  156. filters={"id": info.school_id})
  157. if not db_school:
  158. return {"errcode": 404, "mess": "学校不存在!"}
  159. else:
  160. info.school_name = db_school.name
  161. # 判断年级是否存在
  162. if ("grade_id" in info_dict) and (db_obj.grade_id != info_dict["grade_id"]):
  163. db_grade = await crud_grade.find_one(db, {"id": info.grade_id})
  164. if not db_grade:
  165. return {"errcode": 404, "mess": "年级不存在!"}
  166. else:
  167. info.grade_name = db_grade.name
  168. # 判断班级是否存在
  169. if ("class_id" in info_dict) and (db_obj.class_id != info_dict["class_id"]):
  170. class_ids = [x.strip() for x in info.class_id.split(",") if x.strip()]
  171. total, db_class = await crud_class.find_all(
  172. db, filters=[text(f"id IN ({info.class_id})")])
  173. if total != len(class_ids):
  174. return {"errcode": 400, "mess": "班级不存在!"}
  175. else:
  176. info.class_name = ",".join([x.name for x in db_class])
  177. # 判断角色是否存在
  178. if ("role_id" in info_dict) and (db_obj.role_id != info_dict["role_id"]):
  179. db_role = await crud_role.find_one(db, filters={"id": info.role_id})
  180. if not db_role:
  181. return {"errcode": 404, "mess": "角色不存在!"}
  182. else:
  183. info.role_name = db_role.name
  184. # 更新
  185. info.editor_id = current_user.id
  186. info.editor_name = current_user.username
  187. db_obj = await crud_teacher.update(db, db_obj, info)
  188. return {"data": db_obj}
  189. # 删除教师
  190. @router.delete("/teachers/{tid}",
  191. response_model=TeacherDetail,
  192. response_model_exclude_none=True,
  193. summary="删除教师")
  194. async def delete_teacher(bg_task: BackgroundTasks,
  195. tid: int = Path(..., description="教师ID"),
  196. db: AsyncSession = Depends(get_async_db),
  197. current_user: SysUser = Depends(get_current_user)):
  198. existed = await crud_teacher.count(db, {"id": tid})
  199. if not existed:
  200. return {"errcode": 404, "mess": "教师不存在!"}
  201. else:
  202. await crud_teacher.delete(db, obj_id=tid)
  203. # TODO: 删除关联数据
  204. # bg_task.add_task(delete_related_object, db, cid=id)
  205. return {"data": None}
  206. # 批量导入教师
  207. @router.post("/teachers/bulk",
  208. response_model=TeacherDetail,
  209. response_model_exclude_none=True,
  210. summary="批量导入教师")
  211. async def import_teacher(datafile: UploadFile = File(..., description="数据文件"),
  212. db: AsyncSession = Depends(get_async_db),
  213. current_user: SysUser = Depends(get_current_user)):
  214. # 判断文件格式
  215. if not datafile.filename.endswith(".xlsx"):
  216. return {"errcode": 400, "mess": "文件格式错误!"}
  217. # 把文件写入磁盘,再加载回来
  218. disk_file = os.path.join(settings.UPLOADER_PATH, datafile.filename)
  219. content = await datafile.read()
  220. with open(disk_file, "wb") as f:
  221. f.write(content)
  222. # 返回结果
  223. errors = []
  224. success = 0
  225. teachers = []
  226. counter = 0
  227. # 使用openpyxl读取文件
  228. wb = load_workbook(disk_file)
  229. ws = wb.worksheets[0]
  230. for row in ws.iter_rows(min_row=2,
  231. max_col=ws.max_column,
  232. max_row=ws.max_row,
  233. values_only=True):
  234. row = await check_row(row, 10)
  235. if row is None: # 空行
  236. continue
  237. elif not row: # 字段不完整
  238. errors.append(f"第{row[0]}行: 某些单元格为空!")
  239. continue
  240. # 判断学校是否存在
  241. db_school = await crud_school.find_one(db, filters={"name": row[5]})
  242. if not db_school:
  243. errors.append(f"第{row[0]}行: 学校不存在!")
  244. continue
  245. # 判断年级是否存在
  246. db_grade = await crud_grade.find_one(db,
  247. filters={
  248. "school_id": db_school.id,
  249. "name": row[6]
  250. })
  251. if not db_grade:
  252. errors.append(f"第{row[0]}行: 年级不存在!")
  253. continue
  254. # 判断班级是否存在
  255. class_names = [x.strip() for x in row[7].split(",") if x.strip()]
  256. total, db_class = await crud_class.find_all(
  257. db,
  258. filters=[
  259. text(
  260. f"school_id = {db_school.id} AND grade_id = {db_grade.id} AND name IN ('{row[7]}')"
  261. )
  262. ])
  263. if total != len(class_names):
  264. errors.append(f"第{row[0]}行: 班级不存在!")
  265. continue
  266. class_ids = ",".join([str(x.id) for x in db_class])
  267. # 判断角色是否存在
  268. db_role = await crud_role.find_one(db, filters={"name": row[8]})
  269. if not db_role:
  270. errors.append(f"第{row[0]}行: 角色不存在!")
  271. continue
  272. # 判断是否存在同名教师
  273. existed = await crud_teacher.count(db, filters={"phone": row[4]})
  274. if existed:
  275. errors.append(f"第{row[0]}行: 手机号重复!")
  276. continue
  277. # 创建教师对象
  278. obj_in = TeacherInDB(username=row[4],
  279. password=hashed_password(row[4][-6:]),
  280. name=row[1],
  281. sex=1 if row[2] == "男" else 0,
  282. age=row[3],
  283. phone=row[4],
  284. sid=db_school.id,
  285. school_name=row[5],
  286. gid=db_grade.id,
  287. grade_name=row[6],
  288. cid=class_ids,
  289. class_name=row[7],
  290. rid=db_role.id,
  291. role_name=row[8],
  292. sub=row[9],
  293. creator_id=current_user.id,
  294. creator_name=current_user.username,
  295. editor_id=current_user.id,
  296. editor_name=current_user.username)
  297. teachers.append(obj_in)
  298. success += 1
  299. counter += 1
  300. if counter == 50:
  301. await crud_teacher.insert_many(db, copy.deepcopy(teachers))
  302. teachers.clear()
  303. counter = 0
  304. if counter:
  305. await crud_teacher.insert_many(db, teachers)
  306. # 删除上传文件
  307. os.remove(disk_file)
  308. return {"data": {"success": success, "fail": len(errors), "errors": errors}}