from rest_framework import serializers # from dj_filer.admin import get_thumbs from utils import get_thumbs from apps.course.models import Course, CourseCategory, Attachment, Glossary, LessonCompletion, Participant, Lesson, CourseAttachment, CourseGlossary, CourseLesson from apps.chat.models import RoomMessage from apps.account.serializers import UserProfileSerializer class CourseCategorySerializer(serializers.ModelSerializer): course_count = serializers.SerializerMethodField() class Meta: model = CourseCategory fields = ['name', 'slug', 'course_count'] def get_course_count(self, obj): return obj.course_count class CourseListSerializer(serializers.ModelSerializer): category = CourseCategorySerializer() thumbnail = serializers.SerializerMethodField() participant_count = serializers.SerializerMethodField() lessons_count = serializers.SerializerMethodField() price = serializers.SerializerMethodField() discount_percentage = serializers.SerializerMethodField() final_price = serializers.SerializerMethodField() is_free = serializers.SerializerMethodField() class Meta: model = Course fields = [ 'id', 'title', 'slug', 'participant_count', 'category', 'thumbnail', 'is_online', 'online_link', 'level', 'duration', 'lessons_count', 'short_description', 'status', 'is_free', 'price', 'discount_percentage', 'final_price', ] def get_thumbnail(self, obj): return get_thumbs(obj.thumbnail, self.context.get('request')) def get_participant_count(self, obj): return obj.participants.count() def get_lessons_count(self, obj): # Use prefetched lessons if available if hasattr(obj, 'lessons') and obj.lessons.all(): lessons_count = sum(1 for lesson in obj.lessons.all() if lesson.is_active) return max(lessons_count, obj.lessons_count) # Fallback to direct query lessons_count = obj.lessons.filter(is_active=True).count() return max(lessons_count, obj.lessons_count) def get_price(self, obj): if obj.is_free or obj.price == 0: return "0.00" return str(obj.price) def get_discount_percentage(self, obj): if obj.is_free or obj.price == 0: return 0 return obj.discount_percentage def get_final_price(self, obj): if obj.is_free or obj.price == 0: return "0.00" return str(obj.final_price) def get_is_free(self, obj): return obj.is_free or obj.price == 0 class CourseDetailSerializer(serializers.ModelSerializer): category = CourseCategorySerializer() professor = serializers.SerializerMethodField() thumbnail = serializers.SerializerMethodField() participant_count = serializers.SerializerMethodField() access = serializers.SerializerMethodField() lessons_complated_count = serializers.SerializerMethodField() lessons_count = serializers.SerializerMethodField() last_lesson_id = serializers.SerializerMethodField() room_id = serializers.SerializerMethodField() user_transaction_status = serializers.SerializerMethodField() price = serializers.SerializerMethodField() discount_percentage = serializers.SerializerMethodField() final_price = serializers.SerializerMethodField() is_free = serializers.SerializerMethodField() is_professor = serializers.SerializerMethodField() class Meta: model = Course fields = [ 'id', 'title', 'slug', 'category', 'access', 'participant_count', 'professor', 'is_professor', 'thumbnail', 'video_type', 'video_file', 'video_link', 'is_online', 'online_link', 'level', 'description', 'duration', 'lessons_count', 'lessons_complated_count', 'short_description', 'status', 'is_free', 'price', 'discount_percentage', 'final_price', 'timing', 'features', 'last_lesson_id', 'room_id', 'user_transaction_status', 'is_group_chat_locked', 'is_professor_chat_locked' ] def get_room_id(self, obj): # Use prefetched room_messages if available if hasattr(obj, 'room_messages') and obj.room_messages.all(): return obj.room_messages.first().id # Fallback to direct query if not prefetched room_message = RoomMessage.objects.filter(course=obj).first() if room_message: return room_message.id return None def get_user_transaction_status(self, obj): from apps.transaction.models import TransactionParticipant if student := self._get_authenticated_user(): latest_transaction = TransactionParticipant.objects.filter( user=student, course=obj, is_deleted=False ).order_by('-created_at').first() if latest_transaction: return latest_transaction.status return None def get_last_lesson_id(self, obj): request = self.context.get('request') if request and request.user.is_authenticated: user = request.user # Use prefetched lessons if available if hasattr(obj, 'lessons') and obj.lessons.all(): lessons = [lesson for lesson in obj.lessons.all() if lesson.is_active] completed_lessons = [] # Check which lessons are completed using prefetched data for lesson in lessons: if hasattr(lesson, 'completions') and lesson.completions.all(): if any(completion.student_id == user.id for completion in lesson.completions.all()): completed_lessons.append(lesson) if completed_lessons: # Find the last completed lesson by priority last_completed = max(completed_lessons, key=lambda x: x.priority) # Find next lesson next_lessons = [l for l in lessons if l.priority > last_completed.priority] if next_lessons: return min(next_lessons, key=lambda x: x.priority).id # If no completed lessons or no next lesson, return first lesson if lessons: return min(lessons, key=lambda x: x.priority).id # Fallback to direct queries if not prefetched last_completed_lesson = LessonCompletion.objects.filter( student=user, course_lesson__course=obj ).order_by('-completed_at').first() if last_completed_lesson: next_lesson = CourseLesson.objects.filter( course=obj, priority__gt=last_completed_lesson.course_lesson.priority, is_active=True ).order_by('priority').first() if not next_lesson: next_lesson = CourseLesson.objects.filter( course=obj, is_active=True ).order_by('priority').first() if next_lesson: return next_lesson.id return None def get_access(self, obj): if user := self._get_authenticated_user(): return self._has_access(user, obj) return False def get_professor(self, obj): """Return the course professor's profile using UserProfileSerializer""" if obj.professor: return UserProfileSerializer(obj.professor, context=self.context).data return None def get_is_professor(self, obj): if professor := self._get_authenticated_user(): return obj.professor == professor return False def get_lessons_count(self, obj): # Use prefetched lessons if available if hasattr(obj, 'lessons') and obj.lessons.all(): lessons_count = sum(1 for lesson in obj.lessons.all() if lesson.is_active) return max(lessons_count, obj.lessons_count) # Fallback to direct query lessons_count = obj.lessons.filter(is_active=True).count() return max(lessons_count, obj.lessons_count) def get_lessons_complated_count(self, obj): if student := self._get_authenticated_user(): if not self._is_participant(student, obj): return None completed_count = self._get_completed_lessons_count(student, obj) # Ensure completed count doesn't exceed total lessons count total_lessons = self.get_lessons_count(obj) return min(completed_count, total_lessons) return None def _has_access(self, user, course): """ Check if the user has access to the course content. Access is granted if: 1. User is the professor of the course. 2. User is staff (admin). 3. User is an active participant in the course. """ if user.is_staff or user.is_superuser: return True if course.professor_id == user.id: return True return Participant.objects.filter( student_id=user.id, course=course, is_active=True ).exists() def _is_participant(self, student, course): """Deprecated: use _has_access instead. Kept for backward compatibility if needed.""" 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_completed_lessons_count(self, student, course): """Helper method to count completed lessons for the student in the given course.""" # Use prefetched completions if available if hasattr(course, 'lessons') and course.lessons.all(): completed_count = 0 for lesson in course.lessons.all(): if hasattr(lesson, 'completions') and lesson.completions.all(): if any(completion.student_id == student.id for completion in lesson.completions.all()): completed_count += 1 return completed_count # Fallback to direct query if not prefetched return LessonCompletion.objects.filter( student=student, course_lesson__course=course ).count() def get_thumbnail(self, obj): return get_thumbs(obj.thumbnail, self.context.get('request')) def get_participant_count(self, obj): # Use prefetched participants if available if hasattr(obj, 'participants') and obj.participants.all(): return len(obj.participants.all()) # Fallback to direct query return obj.participants.count() def get_price(self, obj): if obj.is_free or obj.price == 0: return "0.00" return str(obj.price) def get_discount_percentage(self, obj): if obj.is_free or obj.price == 0: return 0 return obj.discount_percentage def get_final_price(self, obj): if obj.is_free or obj.price == 0: return "0.00" return str(obj.final_price) def get_is_free(self, obj): return obj.is_free or obj.price == 0 class MyCourseListSerializer(serializers.ModelSerializer): category = CourseCategorySerializer() thumbnail = serializers.SerializerMethodField() lessons_count = serializers.SerializerMethodField() lessons_complated_count = serializers.SerializerMethodField() class Meta: model = Course fields = [ 'id', 'title', 'slug', 'category', 'thumbnail', 'lessons_count', 'lessons_complated_count', 'short_description', 'status', ] def get_thumbnail(self, obj): return get_thumbs(obj.thumbnail, self.context.get('request')) def get_lessons_count(self, obj): """Get the actual count of active lessons""" # Use prefetched lessons if available if hasattr(obj, 'lessons') and obj.lessons.all(): lessons_count = sum(1 for lesson in obj.lessons.all() if lesson.is_active) return max(lessons_count, obj.lessons_count) # Fallback to direct query lessons_count = obj.lessons.filter(is_active=True).count() return max(lessons_count, obj.lessons_count) def get_lessons_complated_count(self, obj): if student := self._get_authenticated_user(): if not self._is_participant(student, obj): return None completed_count = self._get_completed_lessons_count(student, obj) # Ensure completed count doesn't exceed total lessons count total_lessons = self.get_lessons_count(obj) return min(completed_count, total_lessons) return None def _is_participant(self, student, course): """Helper method to check if a student is a participant in the given course.""" if student.is_staff or student.is_superuser: return True # اگر کاربر استاد دوره است، دسترسی کامل دارد if course.professor_id == student.id: return True # در غیر این صورت چک می‌کنیم که آیا participant است یا خیر return Participant.objects.filter( student_id=student.id, course=course, is_active=True ).exists() 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_completed_lessons_count(self, student, course): """Helper method to count completed lessons for the student in the given course.""" # Use prefetched completions if available if hasattr(course, 'lessons') and course.lessons.all(): completed_count = 0 for lesson in course.lessons.all(): if hasattr(lesson, 'completions') and lesson.completions.all(): if any(completion.student_id == student.id for completion in lesson.completions.all()): completed_count += 1 return completed_count # Fallback to direct query if not prefetched return LessonCompletion.objects.filter( student=student, course_lesson__course=course ).count() class AttachmentSerializer(serializers.ModelSerializer): class Meta: model = Attachment fields = ['id', 'title', 'file', 'file_size'] class CourseAttachmentSerializer(serializers.ModelSerializer): title = serializers.CharField(source='attachment.title', read_only=True) file = serializers.FileField(source='attachment.file', read_only=True) file_size = serializers.IntegerField(source='attachment.file_size', read_only=True) class Meta: model = CourseAttachment fields = ['id', 'title', 'file', 'file_size'] class GlossarySerializer(serializers.ModelSerializer): class Meta: model = Glossary fields = ['id', 'title', 'description'] class CourseGlossarySerializer(serializers.ModelSerializer): title = serializers.CharField(source='glossary.title', read_only=True) description = serializers.CharField(source='glossary.description', read_only=True) class Meta: model = CourseGlossary fields = ['id', 'title', 'description']