from typing import Any, List, Optional from fastapi import APIRouter, Depends, HTTPException, Path, Response, status from sqlalchemy.orm import Session from datetime import datetime from app import crud, models, schemas from app.api.v1.deps import get_db, get_current_active_user, get_admin_user router = APIRouter() @router.get("/", response_model=List[schemas.ClassEnrollment]) def read_enrollments( db: Session = Depends(get_db), skip: int = 0, limit: int = 100, student_id: Optional[int] = None, course_id: Optional[int] = None, current_user: models.User = Depends(get_current_active_user), ) -> Any: """ Retrieve class enrollments. """ # Regular users can only see their own enrollments if they are a student if not crud.user.is_admin(current_user): user_student = db.query(models.Student).filter(models.Student.user_id == current_user.id).first() if user_student: student_id = user_student.id else: # Check if they are a teacher, in which case they can see enrollments for their courses user_teacher = db.query(models.Teacher).filter(models.Teacher.user_id == current_user.id).first() if not user_teacher: return [] # Get courses taught by this teacher teacher_courses = db.query(models.Course).filter(models.Course.teacher_id == user_teacher.id).all() if not teacher_courses: return [] course_ids = [course.id for course in teacher_courses] enrollments = db.query(models.ClassEnrollment).filter( models.ClassEnrollment.course_id.in_(course_ids) ).offset(skip).limit(limit).all() return enrollments # Apply filters query = db.query(models.ClassEnrollment) if student_id: query = query.filter(models.ClassEnrollment.student_id == student_id) if course_id: query = query.filter(models.ClassEnrollment.course_id == course_id) enrollments = query.offset(skip).limit(limit).all() return enrollments @router.get("/{enrollment_id}", response_model=schemas.ClassEnrollmentWithDetails) def read_enrollment( *, db: Session = Depends(get_db), enrollment_id: int = Path(..., title="The ID of the enrollment to get"), current_user: models.User = Depends(get_current_active_user), ) -> Any: """ Get class enrollment by ID with student and course details. """ enrollment = db.query(models.ClassEnrollment).filter(models.ClassEnrollment.id == enrollment_id).first() if not enrollment: raise HTTPException( status_code=404, detail="Class enrollment not found", ) # Check permissions if not crud.user.is_admin(current_user): # Students can only see their own enrollments user_student = db.query(models.Student).filter(models.Student.user_id == current_user.id).first() if user_student and user_student.id == enrollment.student_id: pass # OK, student is viewing their own enrollment else: # Check if they are a teacher for this course user_teacher = db.query(models.Teacher).filter(models.Teacher.user_id == current_user.id).first() if not user_teacher: raise HTTPException( status_code=403, detail="Not enough permissions to view this enrollment", ) course = db.query(models.Course).filter( models.Course.id == enrollment.course_id, models.Course.teacher_id == user_teacher.id ).first() if not course: raise HTTPException( status_code=403, detail="Not enough permissions to view this enrollment", ) # Get student and course details student = db.query(models.Student).filter(models.Student.id == enrollment.student_id).first() course = db.query(models.Course).filter(models.Course.id == enrollment.course_id).first() # Create a combined response result = schemas.ClassEnrollmentWithDetails.from_orm(enrollment) result.student = schemas.Student.from_orm(student) if student else None result.course = schemas.Course.from_orm(course) if course else None return result @router.post("/", response_model=schemas.ClassEnrollment) def create_enrollment( *, db: Session = Depends(get_db), enrollment_in: schemas.ClassEnrollmentCreate, current_user: models.User = Depends(get_admin_user), ) -> Any: """ Create new class enrollment. """ # Check if student exists student = db.query(models.Student).filter(models.Student.id == enrollment_in.student_id).first() if not student: raise HTTPException( status_code=404, detail=f"Student with ID {enrollment_in.student_id} not found", ) # Check if course exists course = db.query(models.Course).filter(models.Course.id == enrollment_in.course_id).first() if not course: raise HTTPException( status_code=404, detail=f"Course with ID {enrollment_in.course_id} not found", ) # Check if student is already enrolled in this course for this semester/academic year existing_enrollment = db.query(models.ClassEnrollment).filter( models.ClassEnrollment.student_id == enrollment_in.student_id, models.ClassEnrollment.course_id == enrollment_in.course_id, models.ClassEnrollment.semester == enrollment_in.semester, models.ClassEnrollment.academic_year == enrollment_in.academic_year, ).first() if existing_enrollment: raise HTTPException( status_code=400, detail=f"Student is already enrolled in this course for {enrollment_in.semester} {enrollment_in.academic_year}", ) # Create enrollment with current date if not provided enrollment_data = enrollment_in.dict() if not enrollment_data.get("enrollment_date"): enrollment_data["enrollment_date"] = datetime.utcnow() db_enrollment = models.ClassEnrollment(**enrollment_data) db.add(db_enrollment) db.commit() db.refresh(db_enrollment) return db_enrollment @router.put("/{enrollment_id}", response_model=schemas.ClassEnrollment) def update_enrollment( *, db: Session = Depends(get_db), enrollment_id: int = Path(..., title="The ID of the enrollment to update"), enrollment_in: schemas.ClassEnrollmentUpdate, current_user: models.User = Depends(get_current_active_user), ) -> Any: """ Update a class enrollment. """ enrollment = db.query(models.ClassEnrollment).filter(models.ClassEnrollment.id == enrollment_id).first() if not enrollment: raise HTTPException( status_code=404, detail="Class enrollment not found", ) # Check permissions - only teachers of the course or admins can update enrollments if not crud.user.is_admin(current_user): user_teacher = db.query(models.Teacher).filter(models.Teacher.user_id == current_user.id).first() if not user_teacher: raise HTTPException( status_code=403, detail="Not enough permissions to update this enrollment", ) course = db.query(models.Course).filter( models.Course.id == enrollment.course_id, models.Course.teacher_id == user_teacher.id ).first() if not course: raise HTTPException( status_code=403, detail="Not enough permissions to update this enrollment", ) # Update enrollment fields update_data = enrollment_in.dict(exclude_unset=True) for field, value in update_data.items(): setattr(enrollment, field, value) db.add(enrollment) db.commit() db.refresh(enrollment) return enrollment @router.delete("/{enrollment_id}", response_model=None, status_code=status.HTTP_204_NO_CONTENT) def delete_enrollment( *, db: Session = Depends(get_db), enrollment_id: int = Path(..., title="The ID of the enrollment to delete"), current_user: models.User = Depends(get_admin_user), ) -> Any: """ Delete a class enrollment. """ enrollment = db.query(models.ClassEnrollment).filter(models.ClassEnrollment.id == enrollment_id).first() if not enrollment: raise HTTPException( status_code=404, detail="Class enrollment not found", ) # Check if there are any exam results for this student in this course exam_results = db.query(models.ExamResult).join( models.Exam, models.Exam.id == models.ExamResult.exam_id ).filter( models.ExamResult.student_id == enrollment.student_id, models.Exam.course_id == enrollment.course_id ).first() if exam_results: raise HTTPException( status_code=400, detail="Cannot delete enrollment with associated exam results", ) db.delete(enrollment) db.commit() return Response(status_code=status.HTTP_204_NO_CONTENT)