Files

250 lines
8.4 KiB
Python

# 班级管理路由
from flask import request, jsonify, render_template, session
from datetime import datetime, timedelta
from app.routes import main_bp
from app.models import db, Class, Student, StudentGoal, Goal, User
from app.routes.auth import login_required_json, admin_required
import logging
logger = logging.getLogger(__name__)
@main_bp.route("/classes")
@login_required_json
def classes_page():
"""班级管理页面"""
return render_template("classes.html", active_nav="classes")
@main_bp.route("/api/teachers", methods=["GET"])
@login_required_json
def api_teachers_list():
"""用户列表(用于班主任选择,所有登录用户可访问)"""
users = User.query.order_by(User.name, User.username).all()
return jsonify([{"id": u.id, "name": u.name or u.username} for u in users])
@main_bp.route("/api/classes", methods=["GET"])
@login_required_json
def api_classes_list():
"""班级列表"""
# 支持筛选参数: active=true/false, mine=true/false
active_filter = request.args.get("active")
mine_filter = request.args.get("mine")
query = Class.query
if active_filter is not None:
if active_filter.lower() == "true":
query = query.filter(Class.active == True)
elif active_filter.lower() == "false":
query = query.filter(Class.active == False)
# 我的班级筛选(当前用户作为老师的班级)
if mine_filter and mine_filter.lower() == "true":
user_id = session.get("user_id")
if user_id:
query = query.filter(Class.teacher_id == user_id)
classes = query.order_by(Class.created_at.desc()).all()
return jsonify([c.to_dict() for c in classes])
@main_bp.route("/api/classes", methods=["POST"])
@admin_required
def api_classes_create():
"""新增班级"""
data = request.get_json()
name = data.get("name", "").strip()
description = data.get("description", "")
teacher_id = data.get("teacher_id") # 可以为空
level = data.get("level", "启蒙")
active = data.get("active", True) # 默认进行中
if not name:
return jsonify({"error": "请输入班级名称", "code": "VALIDATION_ERROR"}), 400
if Class.query.filter_by(name=name).first():
return jsonify({"error": "班级名称已存在", "code": "DUPLICATE_NAME"}), 400
try:
cls = Class(name=name, description=description, teacher_id=teacher_id, level=level, active=active)
db.session.add(cls)
db.session.commit()
return jsonify(cls.to_dict())
except Exception as e:
db.session.rollback()
logger.error(f"创建班级失败: {str(e)}")
return jsonify({"error": "操作失败,请稍后重试", "code": "SERVER_ERROR"}), 500
@main_bp.route("/api/classes/<int:class_id>", methods=["PUT"])
@admin_required
def api_classes_update(class_id):
"""编辑班级"""
cls = Class.query.get_or_404(class_id)
data = request.get_json()
if "name" in data and data["name"]:
# 检查名称是否与其他班级冲突
existing = Class.query.filter_by(name=data["name"]).first()
if existing and existing.id != class_id:
return jsonify({"error": "班级名称已存在", "code": "DUPLICATE_NAME"}), 400
cls.name = data["name"]
if "description" in data:
cls.description = data["description"]
if "teacher_id" in data:
cls.teacher_id = data["teacher_id"] if data["teacher_id"] else None
if "level" in data:
cls.level = data["level"]
if "active" in data:
cls.active = data["active"]
try:
db.session.commit()
return jsonify(cls.to_dict())
except Exception as e:
db.session.rollback()
logger.error(f"更新班级失败: {str(e)}")
return jsonify({"error": "操作失败,请稍后重试", "code": "SERVER_ERROR"}), 500
@main_bp.route("/api/classes/<int:class_id>", methods=["DELETE"])
@admin_required
def api_classes_delete(class_id):
"""删除班级(不删除学员,仅解除关联)"""
cls = Class.query.get_or_404(class_id)
try:
# 解除学员与班级的关联
Student.query.filter_by(class_id=class_id).update({"class_id": None})
db.session.delete(cls)
db.session.commit()
return jsonify({"message": "删除成功"})
except Exception as e:
db.session.rollback()
logger.error(f"删除班级失败: {str(e)}")
return jsonify({"error": "操作失败,请稍后重试", "code": "SERVER_ERROR"}), 500
@main_bp.route("/api/classes/<int:class_id>/students", methods=["GET"])
@login_required_json
def api_classes_students(class_id):
"""班级学员列表"""
cls = Class.query.get_or_404(class_id)
# 直接查询而非使用relationship
students = Student.query.filter_by(class_id=class_id).all()
return jsonify(
[
{
"id": s.id,
"name": s.name,
"phone": s.phone,
"practice_time": s.practice_time,
}
for s in students
]
)
@main_bp.route("/api/classes/<int:class_id>/assign", methods=["POST"])
@login_required_json
def api_classes_assign(class_id):
"""分配学员到班级"""
cls = Class.query.get_or_404(class_id)
data = request.get_json()
student_ids = data.get("student_ids", [])
try:
# 1. 先清除这个班级的所有学员关联
Student.query.filter_by(class_id=class_id).update({"class_id": None})
# 2. 再分配选中的学员到这个班级
if student_ids:
Student.query.filter(Student.id.in_(student_ids)).update(
{"class_id": class_id}
)
db.session.commit()
return jsonify({"message": "分配成功"})
except Exception as e:
db.session.rollback()
logger.error(f"分配学员失败: {str(e)}")
return jsonify({"error": "操作失败,请稍后重试", "code": "SERVER_ERROR"}), 500
@main_bp.route("/api/classes/<int:class_id>/goals", methods=["POST"])
@login_required_json
def api_classes_assign_goals(class_id):
"""批量分配目标给班级所有学员"""
cls = Class.query.get_or_404(class_id)
data = request.get_json()
goal_id = data.get("goal_id")
assessment_days = data.get("assessment_days")
assessment_date = data.get("assessment_date")
start_date = data.get("start_date")
start_now = data.get("start_now", True)
if not goal_id:
return jsonify({"error": "请选择目标", "code": "VALIDATION_ERROR"}), 400
if not assessment_days and not assessment_date:
return jsonify({"error": "请选择评估方式", "code": "VALIDATION_ERROR"}), 400
# 检查目标是否存在
goal = Goal.query.get(goal_id)
if not goal:
return jsonify({"error": "目标不存在", "code": "NOT_FOUND"}), 404
# 获取班级所有学员
students = Student.query.filter_by(class_id=class_id).all()
if not students:
return jsonify({"error": "班级没有学员", "code": "NO_STUDENTS"}), 400
# 处理评估日期
final_assessment_date = None
if assessment_date:
final_assessment_date = datetime.fromisoformat(assessment_date)
elif assessment_days:
final_assessment_date = datetime.now() + timedelta(days=int(assessment_days))
# 处理开始日期
final_start_date = None
if start_date:
final_start_date = datetime.fromisoformat(start_date)
elif start_now:
final_start_date = datetime.now()
# 批量创建 StudentGoal
assigned_count = 0
skipped = []
for student in students:
# 检查是否已分配
existing = StudentGoal.query.filter_by(student_id=student.id, goal_id=goal_id).first()
if existing:
skipped.append(student.name)
continue
record = StudentGoal(
student_id=student.id,
goal_id=goal_id,
start_date=final_start_date,
assessment_date=final_assessment_date
)
db.session.add(record)
assigned_count += 1
try:
db.session.commit()
result = {"message": f"成功分配 {assigned_count} 个学员", "assigned": assigned_count}
if skipped:
result["skipped"] = skipped
result["skipped_count"] = len(skipped)
return jsonify(result)
except Exception as e:
db.session.rollback()
logger.error(f"分配目标失败: {str(e)}")
return jsonify({"error": "操作失败,请稍后重试", "code": "SERVER_ERROR"}), 500