289 lines
9.9 KiB
Python
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) |