from rest_framework import serializers from drf_spectacular.utils import extend_schema_field, OpenApiTypes from apps.lms.models import ( Course, CourseModule, Lesson, QuizQuestion, CourseEnrollment, LessonProgress, DigitalCertificate, ) class QuizQuestionStudentSerializer(serializers.ModelSerializer): class Meta: model = QuizQuestion fields = ['id', 'question_text', 'options', 'order'] class QuizQuestionAdminSerializer(serializers.ModelSerializer): class Meta: model = QuizQuestion fields = ['id', 'question_text', 'options', 'correct_option_index', 'explanation', 'order'] class LessonListSerializer(serializers.ModelSerializer): is_completed = serializers.SerializerMethodField() class Meta: model = Lesson fields = [ 'id', 'title', 'slug', 'lesson_type', 'duration_minutes', 'order', 'is_preview', 'is_completed', ] @extend_schema_field(OpenApiTypes.BOOL) def get_is_completed(self, obj) -> bool: request = self.context.get('request') if request and request.user and request.user.is_authenticated: return LessonProgress.objects.filter( user=request.user, lesson=obj, is_completed=True ).exists() return False class LessonDetailSerializer(serializers.ModelSerializer): quiz_questions = serializers.SerializerMethodField() is_completed = serializers.SerializerMethodField() quiz_score = serializers.SerializerMethodField() class Meta: model = Lesson fields = [ 'id', 'title', 'slug', 'lesson_type', 'duration_minutes', 'video_url', 'content', 'slides_attachment', 'order', 'is_preview', 'quiz_questions', 'is_completed', 'quiz_score', ] @extend_schema_field(QuizQuestionStudentSerializer(many=True)) def get_quiz_questions(self, obj): questions = obj.quiz_questions.all().order_by('order', 'id') request = self.context.get('request') if request and request.user and request.user.is_authenticated and (request.user.is_staff or getattr(request.user, 'is_super_admin', False)): return QuizQuestionAdminSerializer(questions, many=True).data return QuizQuestionStudentSerializer(questions, many=True).data @extend_schema_field(OpenApiTypes.BOOL) def get_is_completed(self, obj) -> bool: request = self.context.get('request') if request and request.user and request.user.is_authenticated: progress = LessonProgress.objects.filter(user=request.user, lesson=obj).first() return progress.is_completed if progress else False return False @extend_schema_field(OpenApiTypes.FLOAT) def get_quiz_score(self, obj): request = self.context.get('request') if request and request.user and request.user.is_authenticated: progress = LessonProgress.objects.filter(user=request.user, lesson=obj).first() return progress.quiz_score if progress else None return None class CourseModuleSerializer(serializers.ModelSerializer): lessons = LessonListSerializer(many=True, read_only=True) class Meta: model = CourseModule fields = ['id', 'title', 'description', 'order', 'lessons'] class CourseListSerializer(serializers.ModelSerializer): total_lessons = serializers.IntegerField(source='total_lessons_count', read_only=True) total_students = serializers.IntegerField(source='total_students_count', read_only=True) is_enrolled = serializers.SerializerMethodField() progress_percentage = serializers.SerializerMethodField() class Meta: model = Course fields = [ 'id', 'title', 'slug', 'category', 'language', 'level', 'cover_image', 'instructor_name', 'instructor_title', 'instructor_avatar', 'organization_name', 'duration_hours', 'rating', 'reviews_count', 'is_featured', 'total_lessons', 'total_students', 'is_enrolled', 'progress_percentage', 'created_at', ] @extend_schema_field(OpenApiTypes.BOOL) def get_is_enrolled(self, obj) -> bool: request = self.context.get('request') if request and request.user and request.user.is_authenticated: return CourseEnrollment.objects.filter(user=request.user, course=obj).exists() return False @extend_schema_field(OpenApiTypes.INT) def get_progress_percentage(self, obj) -> int: request = self.context.get('request') if request and request.user and request.user.is_authenticated: enrollment = CourseEnrollment.objects.filter(user=request.user, course=obj).first() return enrollment.progress_percentage if enrollment else 0 return 0 class CourseDetailSerializer(serializers.ModelSerializer): modules = CourseModuleSerializer(many=True, read_only=True) total_lessons = serializers.IntegerField(source='total_lessons_count', read_only=True) total_students = serializers.IntegerField(source='total_students_count', read_only=True) is_enrolled = serializers.SerializerMethodField() progress_percentage = serializers.SerializerMethodField() certificate_code = serializers.SerializerMethodField() class Meta: model = Course fields = [ 'id', 'title', 'slug', 'category', 'language', 'level', 'cover_image', 'instructor_name', 'instructor_title', 'instructor_avatar', 'institution', 'organization_name', 'description', 'duration_hours', 'rating', 'reviews_count', 'is_published', 'is_featured', 'modules', 'total_lessons', 'total_students', 'is_enrolled', 'progress_percentage', 'certificate_code', 'created_at', 'updated_at', ] @extend_schema_field(OpenApiTypes.BOOL) def get_is_enrolled(self, obj) -> bool: request = self.context.get('request') if request and request.user and request.user.is_authenticated: return CourseEnrollment.objects.filter(user=request.user, course=obj).exists() return False @extend_schema_field(OpenApiTypes.INT) def get_progress_percentage(self, obj) -> int: request = self.context.get('request') if request and request.user and request.user.is_authenticated: enrollment = CourseEnrollment.objects.filter(user=request.user, course=obj).first() return enrollment.progress_percentage if enrollment else 0 return 0 @extend_schema_field(OpenApiTypes.STR) def get_certificate_code(self, obj): request = self.context.get('request') if request and request.user and request.user.is_authenticated: cert = DigitalCertificate.objects.filter(user=request.user, course=obj).first() return cert.certificate_code if cert else None return None class CourseCreateUpdateSerializer(serializers.ModelSerializer): class Meta: model = Course fields = [ 'id', 'title', 'category', 'language', 'level', 'cover_image', 'instructor_name', 'instructor_title', 'instructor_avatar', 'institution', 'organization_name', 'description', 'duration_hours', 'is_published', 'is_featured', ] class SingleQuizAnswerSerializer(serializers.Serializer): question_id = serializers.IntegerField() selected_option = serializers.IntegerField() class QuizSubmitRequestSerializer(serializers.Serializer): answers = SingleQuizAnswerSerializer(many=True) class QuizQuestionResultItemSerializer(serializers.Serializer): question_id = serializers.IntegerField() is_correct = serializers.BooleanField() selected_option = serializers.IntegerField() correct_option = serializers.IntegerField() explanation = serializers.CharField(allow_blank=True, allow_null=True) class QuizResultSerializer(serializers.Serializer): score = serializers.FloatField(help_text="Percentage score achieved (0-100)") passed = serializers.BooleanField(help_text="True if score >= 70%") total_questions = serializers.IntegerField() correct_count = serializers.IntegerField() question_results = QuizQuestionResultItemSerializer(many=True) is_lesson_completed = serializers.BooleanField() course_progress_percentage = serializers.IntegerField() certificate_code = serializers.CharField(allow_null=True, required=False) class LessonCompleteResponseSerializer(serializers.Serializer): message = serializers.CharField() lesson_id = serializers.IntegerField() is_completed = serializers.BooleanField() course_progress_percentage = serializers.IntegerField() certificate_code = serializers.CharField(allow_null=True, required=False) class CourseProgressSerializer(serializers.Serializer): course_id = serializers.IntegerField() course_title = serializers.CharField() progress_percentage = serializers.IntegerField() completed_lessons_count = serializers.IntegerField() total_lessons_count = serializers.IntegerField() is_completed = serializers.BooleanField() certificate_code = serializers.CharField(allow_null=True) class DigitalCertificateSerializer(serializers.ModelSerializer): course_title = serializers.CharField(source='course.title', read_only=True) course_slug = serializers.CharField(source='course.slug', read_only=True) recipient_name = serializers.CharField(source='user.get_full_name', read_only=True) organization = serializers.CharField(source='course.organization_name', read_only=True) class Meta: model = DigitalCertificate fields = [ 'id', 'certificate_code', 'uuid', 'recipient_name', 'course_title', 'course_slug', 'organization', 'issue_date', 'final_score', 'certificate_file', 'is_revoked', 'created_at', ] class PublicCertificateVerifySerializer(serializers.Serializer): certificate_code = serializers.CharField() uuid = serializers.UUIDField() recipient_name = serializers.CharField() course_title = serializers.CharField() organization = serializers.CharField(allow_null=True) issue_date = serializers.DateField() final_score = serializers.FloatField() is_valid = serializers.BooleanField()