- max_attempts enforcement in start_attempt: raises AttemptLimitError when quota of finalized attempts is exhausted (in_progress not counted) - Timer enforcement: start_attempt sets expired_at = now + time_limit_seconds; check_attempt_not_expired auto-finalizes and raises AttemptExpiredError on deadline breach - finalize_timed_out_attempt: grades partial responses, sets TIMED_OUT status - expire_timed_out_attempts Celery task: idempotent sweep of overdue in-progress attempts - Views: 409 on limit breach (start), 410 on expired response submit; timed_out result on submit - 14 unit + integration tests covering policy boundary conditions and API responses Co-Authored-By: Paperclip <noreply@paperclip.ing>
25 lines
717 B
Python
25 lines
717 B
Python
from celery import shared_task
|
|
from django.utils.timezone import now
|
|
|
|
|
|
@shared_task(name="quizzes.expire_timed_out_attempts")
|
|
def expire_timed_out_attempts():
|
|
"""
|
|
Finalize all in-progress attempts whose deadline has passed.
|
|
Scheduled by django-celery-beat; safe to run multiple times (idempotent per attempt).
|
|
"""
|
|
from .models import AttemptStatus, QuizAttempt
|
|
from .services import finalize_timed_out_attempt
|
|
|
|
expired = QuizAttempt.objects.filter(
|
|
status=AttemptStatus.IN_PROGRESS,
|
|
expired_at__lt=now(),
|
|
).select_related("quiz")
|
|
|
|
count = 0
|
|
for attempt in expired:
|
|
finalize_timed_out_attempt(attempt)
|
|
count += 1
|
|
|
|
return {"finalized": count}
|