You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
436 lines
16 KiB
436 lines
16 KiB
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']
|