from rest_framework import serializers from django.db.models import Q from apps.course.models import Lesson, CourseLesson, Participant, LessonCompletion from apps.course.access import user_has_course_access from apps.quiz.serializers import QuizListSerializer from apps.course.models.course import get_localized_field class LessonSerializer(serializers.ModelSerializer): title = serializers.SerializerMethodField() content_type = serializers.SerializerMethodField() class Meta: model = Lesson fields = ['id', 'title', 'content_type', 'content_file', 'video_link', 'duration'] def _lang(self): request = self.context.get('request') return getattr(request, 'LANGUAGE_CODE', None) or 'en' def get_title(self, obj): return get_localized_field(self._lang(), obj.title) def get_content_type(self, obj): return get_localized_field(self._lang(), obj.content_type) class CourseLessonSerializer(serializers.ModelSerializer): is_active = serializers.SerializerMethodField() is_complated = serializers.SerializerMethodField() quizs = serializers.SerializerMethodField() permission = serializers.SerializerMethodField() title = serializers.SerializerMethodField() content_type = serializers.SerializerMethodField() content_file = serializers.FileField(source='lesson.content_file', read_only=True) video_link = serializers.CharField(source='lesson.video_link', read_only=True) duration = serializers.IntegerField(source='lesson.duration', read_only=True) class Meta: model = CourseLesson fields = ['id', 'title', 'priority', 'is_active', 'permission', 'duration', 'content_type', 'content_file', 'video_link', 'is_complated', 'quizs'] def _lang(self): request = self.context.get('request') return getattr(request, 'LANGUAGE_CODE', None) or 'en' def get_title(self, obj): return get_localized_field(self._lang(), obj.title) def get_content_type(self, obj): return get_localized_field(self._lang(), obj.lesson.content_type) def get_is_active(self, obj): return obj.is_active and self._has_access_for_object(obj) def get_permission(self, obj): return self._has_access_for_object(obj) def _has_access_for_object(self, obj): if user := self._get_authenticated_user(): return self._has_access(user, obj.course) return False def _has_access(self, user, course): return user_has_course_access(user, course) def _is_participant(self, student, course): """Deprecated: use _has_access instead.""" return self._has_access(student, course) def _get_authenticated_user(self): """Helper method to retrieve the authenticated user from the context.""" request = self.context.get('request') return request.user if request and request.user.is_authenticated else None def get_is_complated(self, obj): request = self.context.get('request') if not request or not request.user.is_authenticated: return False user = request.user if not self._has_access(user, obj.course): return False # Use prefetched completions if available if hasattr(obj, 'completions') and obj.completions.all(): return any(completion.student_id == user.id for completion in obj.completions.all()) # Fallback to direct queries is_participant = Participant.objects.filter( student=user, course=obj.course ).exists() if not is_participant: return False return LessonCompletion.objects.filter( student=user, course_lesson=obj ).exists() def get_quizs(self, obj): context_key = f'course_lessons_{obj.course_id}' if context_key not in self.context: lessons_list = list(CourseLesson.objects.filter( course_id=obj.course_id, is_active=True ).filter( Q(chapter__isnull=True) | Q(chapter__is_active=True) ).order_by('chapter__priority', 'priority')) self.context[context_key] = lessons_list else: lessons_list = self.context[context_key] try: pos = lessons_list.index(obj) + 1 except ValueError: pos = None quizzes_key = f'course_quizzes_{obj.course_id}' if quizzes_key not in self.context: from apps.quiz.models import Quiz all_quizzes = list(Quiz.objects.filter(course_id=obj.course_id, status=True)) self.context[quizzes_key] = all_quizzes else: all_quizzes = self.context[quizzes_key] matching_quizzes = [] for quiz in all_quizzes: if (quiz.lesson_id == obj.id) or (pos is not None and quiz.lesson_number == pos): matching_quizzes.append(quiz) if matching_quizzes: return QuizListSerializer(matching_quizzes, many=True, context=self.context).data return None from apps.course.models import CourseChapter # 👇 ADD V2 SERIALIZERS class CourseChapterSerializer(serializers.ModelSerializer): """ V2 API: Returns a Chapter and a nested list of all its active lessons """ is_active = serializers.SerializerMethodField() lessons = serializers.SerializerMethodField() title = serializers.SerializerMethodField() class Meta: model = CourseChapter fields = ['id', 'title', 'priority', 'is_active', 'lessons'] def _lang(self): request = self.context.get('request') return getattr(request, 'LANGUAGE_CODE', None) or 'en' def get_title(self, obj): return get_localized_field(self._lang(), obj.title) def get_is_active(self, obj): if not obj.is_active: return False request = self.context.get('request') if not request or not request.user.is_authenticated: return False return user_has_course_access(request.user, obj.course) def get_lessons(self, obj): # Grab all active lessons tied to this specific chapter chapter_lessons = obj.lessons.filter(is_active=True).order_by('priority') # Re-use the V1 CourseLessonSerializer to serialize the nested items return CourseLessonSerializer(chapter_lessons, many=True, context=self.context).data