181 lines
8.5 KiB
Python
181 lines
8.5 KiB
Python
|
|
"""班级管理和考勤路由"""
|
||
|
|
from datetime import date
|
||
|
|
from flask import render_template, request, redirect, url_for, flash, session
|
||
|
|
from models import (db, Class_, Course, Teacher, Student, ClassStudent,
|
||
|
|
StudentAccount, AttendanceRecord)
|
||
|
|
from utils import (
|
||
|
|
login_required, log_operation, permission_required, export_excel,
|
||
|
|
filter_classes_query, filter_students_query, teacher_can_access_class,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def register_routes(app):
|
||
|
|
|
||
|
|
@app.route('/classes', endpoint='class_list')
|
||
|
|
@login_required
|
||
|
|
@permission_required('class_view')
|
||
|
|
def class_list():
|
||
|
|
keyword = request.args.get('keyword', '')
|
||
|
|
query = filter_classes_query(Class_.query)
|
||
|
|
if keyword:
|
||
|
|
query = query.filter(Class_.name.contains(keyword))
|
||
|
|
classes = query.order_by(Class_.id.desc()).all()
|
||
|
|
courses = {c.id: c.name for c in Course.query.all()}
|
||
|
|
teachers = {t.id: t.name for t in Teacher.query.all()}
|
||
|
|
return render_template('class_list.html', classes=classes,
|
||
|
|
courses=courses, teachers=teachers, keyword=keyword)
|
||
|
|
|
||
|
|
@app.route('/classes/add', methods=['GET', 'POST'], endpoint='class_add')
|
||
|
|
@login_required
|
||
|
|
@permission_required('class_add')
|
||
|
|
def class_add():
|
||
|
|
if request.method == 'POST':
|
||
|
|
cls = Class_(
|
||
|
|
name=request.form.get('name'),
|
||
|
|
course_id=int(request.form.get('course_id')),
|
||
|
|
teacher_id=int(request.form.get('teacher_id')),
|
||
|
|
max_students=int(request.form.get('max_students', 20)),
|
||
|
|
status=int(request.form.get('status', 1)),
|
||
|
|
remark=request.form.get('remark', '')
|
||
|
|
)
|
||
|
|
db.session.add(cls)
|
||
|
|
db.session.commit()
|
||
|
|
log_operation('创建班级', f'班级:{cls.name}')
|
||
|
|
flash('班级创建成功', 'success')
|
||
|
|
return redirect(url_for('class_list'))
|
||
|
|
|
||
|
|
courses = Course.query.filter_by(status=1).all()
|
||
|
|
teachers = Teacher.query.filter_by(status=1).all()
|
||
|
|
return render_template('class_form.html', class_=None,
|
||
|
|
courses=courses, teachers=teachers)
|
||
|
|
|
||
|
|
@app.route('/classes/edit/<int:id>', methods=['GET', 'POST'], endpoint='class_edit')
|
||
|
|
@login_required
|
||
|
|
@permission_required('class_edit')
|
||
|
|
def class_edit(id):
|
||
|
|
cls = db.session.get(Class_, id)
|
||
|
|
if request.method == 'POST':
|
||
|
|
cls.name = request.form.get('name')
|
||
|
|
cls.course_id = int(request.form.get('course_id'))
|
||
|
|
cls.teacher_id = int(request.form.get('teacher_id'))
|
||
|
|
cls.max_students = int(request.form.get('max_students', 20))
|
||
|
|
cls.status = int(request.form.get('status', 1))
|
||
|
|
cls.remark = request.form.get('remark', '')
|
||
|
|
db.session.commit()
|
||
|
|
log_operation('编辑班级', f'班级ID:{id}')
|
||
|
|
flash('班级更新成功', 'success')
|
||
|
|
return redirect(url_for('class_list'))
|
||
|
|
|
||
|
|
courses = Course.query.filter_by(status=1).all()
|
||
|
|
teachers = Teacher.query.filter_by(status=1).all()
|
||
|
|
return render_template('class_form.html', class_=cls,
|
||
|
|
courses=courses, teachers=teachers)
|
||
|
|
|
||
|
|
@app.route('/classes/detail/<int:id>', endpoint='class_detail')
|
||
|
|
@login_required
|
||
|
|
@permission_required('class_view')
|
||
|
|
def class_detail(id):
|
||
|
|
cls = db.session.get(Class_, id)
|
||
|
|
if not cls:
|
||
|
|
flash('班级不存在', 'danger')
|
||
|
|
return redirect(url_for('class_list'))
|
||
|
|
course = db.session.get(Course, cls.course_id) if cls else None
|
||
|
|
teacher = db.session.get(Teacher, cls.teacher_id) if cls else None
|
||
|
|
class_students = ClassStudent.query.filter_by(class_id=id, status=1).all()
|
||
|
|
attendance_records = (
|
||
|
|
AttendanceRecord.query.filter_by(class_id=id)
|
||
|
|
.order_by(AttendanceRecord.date.desc(), AttendanceRecord.id.desc())
|
||
|
|
.limit(20)
|
||
|
|
.all()
|
||
|
|
)
|
||
|
|
student_ids = [cs.student_id for cs in class_students]
|
||
|
|
students = {s.id: s for s in Student.query.filter(Student.id.in_(student_ids)).all()} if student_ids else {}
|
||
|
|
# 计算可用学生(未在该班级的)
|
||
|
|
enrolled_ids = set(student_ids)
|
||
|
|
base = filter_students_query(Student.query.filter_by(status=1))
|
||
|
|
available_students = base.filter(~Student.id.in_(enrolled_ids)).all() if enrolled_ids else base.all()
|
||
|
|
return render_template('class_detail.html', class_=cls, course=course,
|
||
|
|
teacher=teacher, class_students=class_students,
|
||
|
|
students=students, available_students=available_students,
|
||
|
|
attendance_records=attendance_records)
|
||
|
|
|
||
|
|
@app.route('/classes/<int:class_id>/add_student', methods=['POST'], endpoint='class_add_student')
|
||
|
|
@login_required
|
||
|
|
@permission_required('class_edit')
|
||
|
|
def class_add_student(class_id):
|
||
|
|
student_id = int(request.form.get('student_id'))
|
||
|
|
existing = ClassStudent.query.filter_by(class_id=class_id,
|
||
|
|
student_id=student_id, status=1).first()
|
||
|
|
if existing:
|
||
|
|
flash('该学员已在班级中', 'warning')
|
||
|
|
else:
|
||
|
|
cs = ClassStudent(class_id=class_id, student_id=student_id,
|
||
|
|
join_date=date.today(), status=1)
|
||
|
|
db.session.add(cs)
|
||
|
|
db.session.commit()
|
||
|
|
log_operation('添加班级学员', f'班级ID:{class_id} 学员ID:{student_id}')
|
||
|
|
flash('学员添加成功', 'success')
|
||
|
|
return redirect(url_for('class_detail', id=class_id))
|
||
|
|
|
||
|
|
@app.route('/classes/<int:class_id>/remove_student/<int:student_id>', endpoint='class_remove_student')
|
||
|
|
@login_required
|
||
|
|
@permission_required('class_edit')
|
||
|
|
def class_remove_student(class_id, student_id):
|
||
|
|
cs = ClassStudent.query.filter_by(class_id=class_id,
|
||
|
|
student_id=student_id, status=1).first()
|
||
|
|
if cs:
|
||
|
|
cs.status = 0
|
||
|
|
db.session.commit()
|
||
|
|
log_operation('移除班级学员', f'班级ID:{class_id} 学员ID:{student_id}')
|
||
|
|
flash('学员已移除', 'success')
|
||
|
|
return redirect(url_for('class_detail', id=class_id))
|
||
|
|
|
||
|
|
@app.route('/classes/<int:class_id>/attendance', methods=['GET', 'POST'], endpoint='class_attendance')
|
||
|
|
@login_required
|
||
|
|
@permission_required('attendance_add')
|
||
|
|
def class_attendance(class_id):
|
||
|
|
cls = db.session.get(Class_, class_id)
|
||
|
|
if not cls:
|
||
|
|
flash('班级不存在', 'danger')
|
||
|
|
return redirect(url_for('class_list'))
|
||
|
|
class_students = ClassStudent.query.filter_by(class_id=class_id, status=1).all()
|
||
|
|
student_ids = [cs.student_id for cs in class_students]
|
||
|
|
students = {s.id: s for s in Student.query.filter(Student.id.in_(student_ids)).all()} if student_ids else {}
|
||
|
|
|
||
|
|
if request.method == 'POST':
|
||
|
|
att_date_str = request.form.get('att_date') or date.today().isoformat()
|
||
|
|
att_date = __import__('datetime').datetime.strptime(att_date_str, '%Y-%m-%d').date()
|
||
|
|
for cs in class_students:
|
||
|
|
status = request.form.get(f'status_{cs.student_id}', 'present')
|
||
|
|
remark = request.form.get(f'remark_{cs.student_id}', '')
|
||
|
|
record = AttendanceRecord(
|
||
|
|
class_id=class_id, student_id=cs.student_id,
|
||
|
|
date=att_date, status=status, remark=remark
|
||
|
|
)
|
||
|
|
db.session.add(record)
|
||
|
|
db.session.commit()
|
||
|
|
log_operation('考勤录入', f'班级ID:{class_id} 日期:{att_date_str}')
|
||
|
|
flash('考勤录入成功', 'success')
|
||
|
|
return redirect(url_for('class_detail', id=class_id))
|
||
|
|
|
||
|
|
return render_template('attendance_form.html', class_=cls,
|
||
|
|
class_students=class_students, students=students,
|
||
|
|
today=date.today().isoformat())
|
||
|
|
|
||
|
|
@app.route('/attendance', endpoint='attendance_select')
|
||
|
|
@login_required
|
||
|
|
@permission_required('attendance_view')
|
||
|
|
def attendance_select():
|
||
|
|
"""考勤入口:选择班级"""
|
||
|
|
classes = filter_classes_query(Class_.query.filter_by(status=1)).all()
|
||
|
|
courses = {c.id: c.name for c in Course.query.all()}
|
||
|
|
return render_template('attendance_select.html', classes=classes, courses=courses)
|
||
|
|
|
||
|
|
|
||
|
|
@app.route('/classes/export', endpoint='class_export')
|
||
|
|
@login_required
|
||
|
|
@permission_required('class_export')
|
||
|
|
def class_export():
|
||
|
|
from utils import export_classes as do_export
|
||
|
|
return do_export()
|