from rest_framework.generics import ListAPIView, RetrieveAPIView from django.db.models import Count, Q, F from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi from rest_framework.exceptions import NotFound from rest_framework.permissions import IsAuthenticated from rest_framework.filters import SearchFilter from apps.course.serializers import ( CourseListSerializer, CourseCategorySerializer, CourseDetailSerializer, CourseAttachmentSerializer, CourseGlossarySerializer, MyCourseListSerializer ) from apps.course.models import Course, CourseCategory, CourseAttachment, CourseGlossary, Participant from apps.course.doc import * class CourseCategoryAPIView(ListAPIView): queryset = CourseCategory.objects.all() serializer_class = CourseCategorySerializer @swagger_auto_schema( operation_description=doc_course_category(), ) def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) class CourseListAPIView(ListAPIView): serializer_class = CourseListSerializer filter_backends = [SearchFilter] search_fields = ['title', 'category__name', 'professor__fullname'] def get_queryset(self): """ Optimized queryset with select_related for ForeignKey relationships """ return Course.objects.select_related( 'category', 'professor' ).exclude(status=Course.StatusChoices.INACTIVE) @swagger_auto_schema( operation_description=doc_course_list(), manual_parameters=[ openapi.Parameter( 'search', openapi.IN_QUERY, description="Search by course title, category name, or professor's full name", type=openapi.TYPE_STRING, ), openapi.Parameter( 'category_slug', openapi.IN_QUERY, description="Category of the Course", type=openapi.TYPE_STRING, # enum=[category.slug for category in CourseCategory.objects.all()] ), openapi.Parameter( 'status', openapi.IN_QUERY, type=openapi.TYPE_STRING, description="""Status => Upcoming (visible but registration not allowed)---Предстоящие Registering (registration is open)---регистрация Ongoing (course has started, registration closed)---Впроцессе Finished (course has ended)---закончился """, enum=[status for status in ['upcoming', 'registering', 'ongoing', 'finished']] ), openapi.Parameter( 'is_free', openapi.IN_QUERY, description="Ценообразование is_free ", type=openapi.TYPE_BOOLEAN, ), openapi.Parameter( 'is_online', openapi.IN_QUERY, description="Статус участия is_online ", type=openapi.TYPE_BOOLEAN, ), ]) def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def get_queryset(self): queryset = super().get_queryset() request = self.request filters = request.query_params # Handle category_slug with multiple values separated by commas if category_slugs := filters.get('category_slug'): category_slugs_list = category_slugs.split(',') queryset = queryset.filter(category__slug__in=category_slugs_list) # Handle status with multiple values separated by commas if statuses := filters.get('status'): statuses_list = statuses.split(',') queryset = queryset.filter(status__in=statuses_list) if is_free := filters.get('is_free'): is_free = is_free.lower() == 'true' queryset = queryset.filter( Q(is_free=is_free) | Q(price=0) if is_free else Q(is_free=False, price__gt=0) ) if is_online := filters.get('is_online'): is_online = is_online.lower() == 'true' queryset = queryset.filter(is_online=is_online) return queryset class CourseDetailAPIView(RetrieveAPIView): serializer_class = CourseDetailSerializer lookup_field = "slug" def get_queryset(self): """ Optimized queryset with select_related and prefetch_related for all relationships """ return Course.objects.select_related( 'category', 'professor' ).prefetch_related( 'lessons__lesson', 'lessons__completions', 'attachments__attachment', 'glossaries__glossary', 'participants__student', 'room_messages' ) @swagger_auto_schema( operation_description=doc_course_detail(), ) def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) class MyCourseListAPIView(ListAPIView): serializer_class = MyCourseListSerializer permission_classes = [IsAuthenticated] @swagger_auto_schema(manual_parameters=[ openapi.Parameter( 'completed', openapi.IN_QUERY, description="мои курсы completed true", type=openapi.TYPE_BOOLEAN, ), openapi.Parameter( 'certificate', openapi.IN_QUERY, type=openapi.TYPE_BOOLEAN, ), ], operation_description=doc_courses_my_courses(), operation_summary="Home", ) def get(self, request, *args, **kwargs): print(f'--> my-course-> {request}/ {kwargs}') return super().get(request, *args, **kwargs) def get_queryset(self): """ Optimized queryset for user's courses with select_related and prefetch_related """ queryset = Course.objects.select_related( 'category', 'professor' ).prefetch_related( 'lessons__lesson', 'lessons__completions', 'participants__student' ).exclude(status=Course.StatusChoices.INACTIVE) request = self.request filters = request.query_params student = self.request.user qs = queryset.filter(participants__student=student) completed_only = filters.get('completed', '').lower() == 'true' if completed_only == True: # نمایش دوره‌هایی که همه درس‌هایشان توسط کاربر تکمیل شده‌اند qs = qs.annotate( total_lessons=Count('lessons', distinct=True), completed_lessons=Count( 'lessons__completions', filter=Q(lessons__completions__student=student), distinct=True ) ).filter(total_lessons=F('completed_lessons')) elif completed_only == False: # نمایش دوره‌هایی که همه درس‌هایشان تکمیل نشده‌اند qs = qs.annotate( total_lessons=Count('lessons', distinct=True), completed_lessons=Count( 'lessons__completions', filter=Q(lessons__completions__student=student), distinct=True ) ).filter(total_lessons__gt=F('completed_lessons')) if 'completed' not in filters: certificate = filters.get('certificate', '').lower() == 'true' if certificate: qs = qs.exclude( course_certificates__student=student, course_certificates__status__in=['pending', 'approved'] ) return qs class AttachmentListAPIView(ListAPIView): serializer_class = CourseAttachmentSerializer @swagger_auto_schema( manual_parameters=[ openapi.Parameter( 'slug', openapi.IN_PATH, description="Slug of the Course", type=openapi.TYPE_STRING, required=True ) ], operation_description="Retrieve a list of attachments for a given course by its slug." ) def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def get_queryset(self): """ Optimized queryset with select_related for attachment relationship """ course_slug = self.kwargs.get('slug') try: course = Course.objects.get(slug=course_slug) except Course.DoesNotExist: raise NotFound("Course not found") return CourseAttachment.objects.select_related( 'course', 'attachment' ).filter(course=course) class GlossaryListAPIView(ListAPIView): serializer_class = CourseGlossarySerializer filter_backends = [SearchFilter] search_fields = ['glossary__title', 'glossary__description'] def get_queryset(self): """ Optimized queryset with select_related for glossary relationship """ course_slug = self.kwargs.get('slug') try: course = Course.objects.get(slug=course_slug) except Course.DoesNotExist: raise NotFound("Course not found") return CourseGlossary.objects.select_related( 'course', 'glossary' ).filter(course=course)