- 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>
92 lines
2.9 KiB
Python
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="")
|