289 lines
9.9 KiB
Python

from typing import Any, List, Optional
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Path, Response, status
from sqlalchemy.orm import Session
from app import crud, models, schemas
from app.api.v1.deps import get_db, get_current_active_user, get_teacher_user
router = APIRouter()
@router.get("/", response_model=List[schemas.ExamResult])
def read_exam_results(
db: Session = Depends(get_db),
skip: int = 0,
limit: int = 100,
exam_id: Optional[int] = None,
student_id: Optional[int] = None,
current_user: models.User = Depends(get_current_active_user),
) -> Any:
"""
Retrieve exam results.
"""
# Check permissions
is_admin = any(role.name == "admin" for role in db.query(models.Role).all() if role.id == current_user.role_id)
is_teacher = db.query(models.Teacher).filter(models.Teacher.user_id == current_user.id).first() is not None
# Students can only see their own results
if not (is_admin or is_teacher):
student = db.query(models.Student).filter(models.Student.user_id == current_user.id).first()
if not student:
raise HTTPException(
status_code=403,
detail="You don't have permission to view these exam results",
)
student_id = student.id
# Apply filters
if exam_id and student_id:
exam_result = crud.exam_result.get_by_student_and_exam(
db, student_id=student_id, exam_id=exam_id
)
return [exam_result] if exam_result else []
elif exam_id:
return crud.exam_result.get_by_exam(db, exam_id=exam_id)
elif student_id:
return crud.exam_result.get_by_student(db, student_id=student_id)
else:
return crud.exam_result.get_multi(db, skip=skip, limit=limit)
@router.post("/", response_model=schemas.ExamResult)
def create_exam_result(
*,
db: Session = Depends(get_db),
exam_result_in: schemas.ExamResultCreate,
current_user: models.User = Depends(get_current_active_user),
) -> Any:
"""
Create new exam result (start an exam).
"""
# Check if the exam exists
exam = crud.exam.get(db=db, id=exam_result_in.exam_id)
if not exam:
raise HTTPException(
status_code=404,
detail=f"Exam with ID {exam_result_in.exam_id} not found",
)
# Check if the student exists
student = crud.student.get(db=db, id=exam_result_in.student_id)
if not student:
raise HTTPException(
status_code=404,
detail=f"Student with ID {exam_result_in.student_id} not found",
)
# Check if the current user is the student or has permission
is_admin = any(role.name == "admin" for role in db.query(models.Role).all() if role.id == current_user.role_id)
is_teacher = db.query(models.Teacher).filter(models.Teacher.user_id == current_user.id).first() is not None
if not (is_admin or is_teacher):
if student.user_id != current_user.id:
raise HTTPException(
status_code=403,
detail="You don't have permission to start an exam for this student",
)
# Check if the exam is available
now = datetime.utcnow()
if not exam.is_active:
raise HTTPException(
status_code=400,
detail="This exam is not active",
)
if exam.start_time and exam.start_time > now:
raise HTTPException(
status_code=400,
detail="This exam is not yet available",
)
if exam.end_time and exam.end_time < now:
raise HTTPException(
status_code=400,
detail="This exam has expired",
)
# Check if there's already an active attempt
existing_attempt = crud.exam_result.get_active_exam_attempt(
db, student_id=student.id, exam_id=exam.id
)
if existing_attempt:
raise HTTPException(
status_code=400,
detail="There is already an active attempt for this exam",
)
# Create the exam result
exam_result = crud.exam_result.create(db=db, obj_in=exam_result_in)
return exam_result
@router.get("/{result_id}", response_model=schemas.ExamResultWithAnswers)
def read_exam_result(
*,
db: Session = Depends(get_db),
result_id: int = Path(..., title="The ID of the exam result to get"),
current_user: models.User = Depends(get_current_active_user),
) -> Any:
"""
Get exam result by ID with answers.
"""
exam_result = crud.exam_result.get(db=db, id=result_id)
if not exam_result:
raise HTTPException(
status_code=404,
detail="Exam result not found",
)
# Check permissions
is_admin = any(role.name == "admin" for role in db.query(models.Role).all() if role.id == current_user.role_id)
is_teacher = db.query(models.Teacher).filter(models.Teacher.user_id == current_user.id).first() is not None
# Students can only see their own results
if not (is_admin or is_teacher):
student = db.query(models.Student).filter(models.Student.user_id == current_user.id).first()
if not student or student.id != exam_result.student_id:
raise HTTPException(
status_code=403,
detail="You don't have permission to view this exam result",
)
# Get answers for this exam result
answers = crud.student_answer.get_by_exam_result(db=db, exam_result_id=result_id)
# Create a combined response
result = schemas.ExamResultWithAnswers.from_orm(exam_result)
result.answers = answers
return result
@router.put("/{result_id}", response_model=schemas.ExamResult)
def update_exam_result(
*,
db: Session = Depends(get_db),
result_id: int = Path(..., title="The ID of the exam result to update"),
result_in: schemas.ExamResultUpdate,
current_user: models.User = Depends(get_current_active_user),
) -> Any:
"""
Update an exam result.
"""
exam_result = crud.exam_result.get(db=db, id=result_id)
if not exam_result:
raise HTTPException(
status_code=404,
detail="Exam result not found",
)
# Check permissions
is_admin = any(role.name == "admin" for role in db.query(models.Role).all() if role.id == current_user.role_id)
is_teacher = db.query(models.Teacher).filter(models.Teacher.user_id == current_user.id).first() is not None
# Only teacher and admin can update results directly
if not (is_admin or is_teacher):
raise HTTPException(
status_code=403,
detail="You don't have permission to update this exam result",
)
exam_result = crud.exam_result.update(db=db, db_obj=exam_result, obj_in=result_in)
return exam_result
@router.post("/{result_id}/complete", response_model=schemas.ExamResult)
def complete_exam(
*,
db: Session = Depends(get_db),
result_id: int = Path(..., title="The ID of the exam result to complete"),
current_user: models.User = Depends(get_current_active_user),
) -> Any:
"""
Complete an exam and calculate the score.
"""
exam_result = crud.exam_result.get(db=db, id=result_id)
if not exam_result:
raise HTTPException(
status_code=404,
detail="Exam result not found",
)
# Check if already completed
if exam_result.is_completed:
raise HTTPException(
status_code=400,
detail="This exam is already completed",
)
# Check permissions
is_admin = any(role.name == "admin" for role in db.query(models.Role).all() if role.id == current_user.role_id)
is_teacher = db.query(models.Teacher).filter(models.Teacher.user_id == current_user.id).first() is not None
# Students can only complete their own exams
if not (is_admin or is_teacher):
student = db.query(models.Student).filter(models.Student.user_id == current_user.id).first()
if not student or student.id != exam_result.student_id:
raise HTTPException(
status_code=403,
detail="You don't have permission to complete this exam",
)
# Get the exam details
exam = crud.exam.get(db=db, id=exam_result.exam_id)
if not exam:
raise HTTPException(
status_code=404,
detail="Exam not found",
)
# Get all questions for this exam
questions = crud.question.get_by_exam_id(db=db, exam_id=exam.id)
# Calculate the maximum possible score
max_score = sum(q.points for q in questions)
# Get all student answers for this exam
answers = crud.student_answer.get_by_exam_result(db=db, exam_result_id=exam_result.id)
# Calculate the score
score = sum(a.points_earned for a in answers)
# Complete the exam
exam_result = crud.exam_result.complete_exam(
db=db, db_obj=exam_result, score=score, max_score=max_score
)
return exam_result
@router.delete("/{result_id}", response_model=None, status_code=status.HTTP_204_NO_CONTENT)
def delete_exam_result(
*,
db: Session = Depends(get_db),
result_id: int = Path(..., title="The ID of the exam result to delete"),
current_user: models.User = Depends(get_teacher_user),
) -> Any:
"""
Delete an exam result.
"""
exam_result = crud.exam_result.get(db=db, id=result_id)
if not exam_result:
raise HTTPException(
status_code=404,
detail="Exam result not found",
)
# Only admin can delete results
is_admin = any(role.name == "admin" for role in db.query(models.Role).all() if role.id == current_user.role_id)
if not is_admin:
raise HTTPException(
status_code=403,
detail="Only administrators can delete exam results",
)
crud.exam_result.remove(db=db, id=result_id)
return Response(status_code=status.HTTP_204_NO_CONTENT)