Files
MathApp/content/serializers.py
Zareian 149f1ad9ae return mascotMood on answer submission
The math-app frontend prototype drives a RobotMascot with a fixed set of
moods; this gives that animation an explicit backend signal. Complements
the existing Lesson.mascot_animation / LessonIntro.mascotAnimation, which
are unchanged.

serializers.py
  - AnswerResponseSerializer: new mascotMood field.
views.py
  - QuestionAnswerView: mascotMood = 'happy' for a correct answer else
    'encourage', on both the fresh response and the idempotent-replay
    response.
tests.py
  - QuestionAnswerMascotMoodTests: correct -> happy, wrong -> encourage,
    and the mood is preserved across an idempotent replay.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 02:30:50 +03:30

128 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from .models import Grade
# ---- Grades ----
class GradeSerializer(serializers.ModelSerializer):
class Meta:
model = Grade
fields = ['id', 'title', 'icon', 'color']
class GradesListResponseSerializer(serializers.Serializer):
grades = GradeSerializer(many=True)
# ---- Home (skill map + stats) ----
class NodeStatusSerializer(serializers.Serializer):
id = serializers.CharField()
title = serializers.CharField()
status = serializers.ChoiceField(choices=['locked', 'unlocked', 'completed'])
class UnitWithNodesSerializer(serializers.Serializer):
id = serializers.CharField()
title = serializers.CharField()
nodes = NodeStatusSerializer(many=True)
class StatsSerializer(serializers.Serializer):
progressPercent = serializers.FloatField()
xp = serializers.IntegerField()
coins = serializers.IntegerField()
class HomeResponseSerializer(serializers.Serializer):
units = UnitWithNodesSerializer(many=True)
stats = StatsSerializer()
# ---- Lesson intro ----
class LessonIntroResponseSerializer(serializers.Serializer):
lessonId = serializers.CharField()
title = serializers.CharField()
introText = serializers.CharField()
mascotAnimation = serializers.CharField()
# ---- Lesson questions ----
class ChoiceOptionSerializer(serializers.Serializer):
id = serializers.CharField()
value = serializers.CharField()
class QuestionSerializer(serializers.Serializer):
id = serializers.CharField()
type = serializers.CharField()
prompt = serializers.CharField()
options = ChoiceOptionSerializer(many=True)
class LessonQuestionsResponseSerializer(serializers.Serializer):
questions = QuestionSerializer(many=True)
# ---- Answer submission ----
class AnswerRequestSerializer(serializers.Serializer):
selectedOptionId = serializers.IntegerField()
class AnswerResponseSerializer(serializers.Serializer):
correct = serializers.BooleanField()
correctOptionId = serializers.CharField()
xpEarned = serializers.IntegerField()
heartsRemaining = serializers.IntegerField()
# حالتِ انیمیشنِ ماسکوت («رُبو») برای فرانت: جوابِ درست → happy، غلط →
# encourage. هم‌خوان با RobotMood در MathApp_frontend.
mascotMood = serializers.CharField()
# ---- Lesson completion ----
class LessonCompleteRequestSerializer(serializers.Serializer):
# 🔒 اصلاح امنیتی: قبلاً هیچ محدودیتی روی این دو فیلد نبود؛ کلاینت
# می‌تونست correctCount منفی یا totalQuestions صفر/منفی بفرسته.
# min_value ها فقط شرطِ ساده‌ی «غیرمنفی/مثبت» رو تضمین می‌کنن؛ چکِ
# اصلیِ «correctCount نباید از تعدادِ واقعیِ سوال‌های درس بیشتر باشه»
# در LessonCompleteView (جایی که به Lesson دسترسی داریم) انجام می‌شه.
correctCount = serializers.IntegerField(min_value=0)
totalQuestions = serializers.IntegerField(min_value=1)
class LessonCompleteResponseSerializer(serializers.Serializer):
stars = serializers.IntegerField()
xpEarned = serializers.IntegerField()
coinsEarned = serializers.IntegerField()
stats = StatsSerializer()
# ---- Node progress (تکمیل‌شده / نصفه‌رها / در حال انجام) ----
class NodeProgressSerializer(serializers.Serializer):
nodeId = serializers.CharField(source='node_id')
nodeTitle = serializers.CharField(source='node.title')
unitId = serializers.CharField(source='node.unit_id')
status = serializers.ChoiceField(
choices=['not_started', 'in_progress', 'completed', 'abandoned'],
)
lessonsCompleted = serializers.IntegerField(source='lessons_completed_count')
lessonsTotal = serializers.SerializerMethodField()
startedAt = serializers.DateTimeField(source='started_at', allow_null=True)
completedAt = serializers.DateTimeField(source='completed_at', allow_null=True)
lastActivityAt = serializers.DateTimeField(source='last_activity_at', allow_null=True)
@extend_schema_field(serializers.IntegerField)
def get_lessonsTotal(self, obj):
return obj.node.lessons.count()
class NodeProgressListResponseSerializer(serializers.Serializer):
progress = NodeProgressSerializer(many=True)