Files
training-software/quizzes/serializers.py
Paperclip CTO b59d06fddc
Some checks failed
CI / Tests (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / OpenAPI Schema (push) Has been cancelled
feat(TRA-239): implement Quiz Engine — SC/MC + short answer with deterministic randomization
- Quiz, Question, Choice, QuizAttempt, QuestionResponse models with migrations
- seed-based deterministic randomization for questions and choices per attempt
- SC/MC grading (all-or-nothing per question), short-answer regex/keyword matching
- submit_attempt: grades all responses, computes score_percent, marks passed/failed
- DRF API: start attempt, submit response, submit (finalize) attempt, quiz detail
- 25 unit + integration tests covering grading correctness, boundary conditions,
  randomization reproducibility, API flows, and access control

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 09:15:18 +02:00

92 lines
2.9 KiB
Python

from rest_framework import serializers
from .models import Choice, Question, Quiz, QuizAttempt, QuestionResponse
from .services import get_choice_order, get_question_order
class ChoiceSerializer(serializers.ModelSerializer):
class Meta:
model = Choice
fields = ["id", "text", "order"] # is_correct excluded — revealed only in results
class QuestionSerializer(serializers.ModelSerializer):
choices = serializers.SerializerMethodField()
class Meta:
model = Question
fields = ["id", "order", "question_type", "text", "points", "choices"]
def get_choices(self, obj):
seed = self.context.get("seed", 0)
ordered = get_choice_order(obj, seed)
return ChoiceSerializer(ordered, many=True).data
class QuizSerializer(serializers.ModelSerializer):
questions = serializers.SerializerMethodField()
class Meta:
model = Quiz
fields = [
"id", "title", "pass_threshold", "max_attempts",
"time_limit_seconds", "feedback_mode", "questions",
]
def get_questions(self, obj):
seed = self.context.get("seed", 0)
ordered = get_question_order(obj, seed)
return QuestionSerializer(ordered, many=True, context=self.context).data
class ChoiceResultSerializer(serializers.ModelSerializer):
class Meta:
model = Choice
fields = ["id", "text", "order", "is_correct"]
class QuestionResultSerializer(serializers.ModelSerializer):
choices = ChoiceResultSerializer(many=True)
class Meta:
model = Question
fields = ["id", "text", "question_type", "explanation", "choices"]
class QuestionResponseSerializer(serializers.ModelSerializer):
class Meta:
model = QuestionResponse
fields = [
"id", "question", "selected_choice_ids", "text_response",
"is_correct", "points_earned", "answered_at",
]
read_only_fields = ["id", "is_correct", "points_earned", "answered_at"]
class AttemptSerializer(serializers.ModelSerializer):
class Meta:
model = QuizAttempt
fields = [
"id", "quiz", "attempt_number", "started_at", "submitted_at",
"score_percent", "is_passed", "status",
]
read_only_fields = fields
class AttemptResultSerializer(serializers.ModelSerializer):
responses = QuestionResponseSerializer(many=True, read_only=True)
class Meta:
model = QuizAttempt
fields = [
"id", "quiz", "attempt_number", "started_at", "submitted_at",
"score_percent", "is_passed", "status", "responses",
]
read_only_fields = fields
class SubmitResponseSerializer(serializers.Serializer):
question_id = serializers.UUIDField()
selected_choice_ids = serializers.ListField(child=serializers.UUIDField(), default=list)
text_response = serializers.CharField(allow_blank=True, default="")