from django.db.models import Q from rest_framework.viewsets import ModelViewSet from rest_framework.generics import ListAPIView from rest_framework.permissions import IsAuthenticated from rest_framework.authentication import TokenAuthentication from rest_framework.decorators import action from rest_framework.response import Response from django.shortcuts import get_object_or_404 from utils.pagination import StandardResultsSetPagination from apps.quiz.models import Quiz, Question, QuizParticipant from apps.course.models import Course, CourseLesson, Participant from apps.course.models.course import extract_text_from_json from apps.account.models import User from apps.account.permissions import IsPanelUser from apps.quiz.serializers.admin import ( AdminCourseSerializer, AdminCourseLessonSerializer, AdminQuizListSerializer, AdminQuizDetailSerializer, AdminQuestionSerializer, AdminQuizParticipantSerializer, AdminQuizAbsenteeSerializer ) def is_professor(request): return getattr(request.user, 'user_type', None) == 'professor' def find_matching_course_ids(search_term: str): normalized = (search_term or "").strip().lower() if not normalized: return [] matching_ids = list( Course.objects.filter( Q(title__icontains=search_term) | Q(slug__icontains=search_term) ).values_list("id", flat=True) ) if matching_ids: return matching_ids for course in Course.objects.all().only("id", "title", "slug"): title_text = extract_text_from_json(course.title).lower() slug_text = extract_text_from_json(course.slug).lower() if normalized in title_text or normalized in slug_text: matching_ids.append(course.id) return matching_ids def find_matching_lesson_ids(search_term: str): normalized = (search_term or "").strip().lower() if not normalized: return [] matching_ids = [] for lesson in CourseLesson.objects.select_related("lesson").all(): lesson_title = extract_text_from_json( lesson.title or (lesson.lesson.title if lesson.lesson else []) ).lower() if normalized in lesson_title: matching_ids.append(lesson.id) return matching_ids def find_matching_quiz_ids(search_term: str): normalized = (search_term or "").strip().lower() if not normalized: return [] matching_ids = [] for quiz in Quiz.objects.all().only("id", "title", "description"): title_text = extract_text_from_json(quiz.title).lower() description_text = extract_text_from_json(quiz.description).lower() if normalized in title_text or normalized in description_text: matching_ids.append(quiz.id) return matching_ids class AdminQuizViewSet(ModelViewSet): """ Admin CRUD ViewSet for Quiz management. Professors can only access quizzes of their own courses. """ permission_classes = [IsAuthenticated, IsPanelUser] authentication_classes = [TokenAuthentication] pagination_class = StandardResultsSetPagination def get_serializer_class(self): if self.action == 'list': return AdminQuizListSerializer return AdminQuizDetailSerializer def get_queryset(self): queryset = Quiz.objects.all().select_related('course', 'lesson') # Professors can only see quizzes of their own courses if is_professor(self.request): queryset = queryset.filter(course__professor_id=self.request.user.id) # Handle Search search_query = self.request.query_params.get('search', None) has_search = False if search_query: has_search = True matching_course_ids = find_matching_course_ids(search_query) matching_lesson_ids = find_matching_lesson_ids(search_query) matching_quiz_ids = find_matching_quiz_ids(search_query) from django.db.models import Case, When, Value, IntegerField queryset = queryset.filter( Q(id__in=matching_quiz_ids) | Q(course_id__in=matching_course_ids) | Q(lesson_id__in=matching_lesson_ids) ).annotate( search_relevance=Case( When(id__in=matching_quiz_ids, then=Value(2)), default=Value(1), output_field=IntegerField() ) ) # Handle Course Filter course_id = self.request.query_params.get('course', None) if course_id: queryset = queryset.filter(course_id=course_id) # Handle Lesson Filter lesson_id = self.request.query_params.get('lesson', None) if lesson_id: try: lesson_obj = CourseLesson.objects.get(id=lesson_id) course = lesson_obj.course lessons = list(CourseLesson.objects.filter( course=course, is_active=True ).order_by('chapter__priority', 'priority')) try: pos = lessons.index(lesson_obj) + 1 queryset = queryset.filter( Q(lesson_id=lesson_id) | (Q(course=course) & Q(lesson_number=pos)) ) except ValueError: queryset = queryset.filter(lesson_id=lesson_id) except CourseLesson.DoesNotExist: queryset = queryset.filter(lesson_id=lesson_id) # Handle Status Filter status_param = self.request.query_params.get('status', None) if status_param is not None: if status_param.lower() == 'true': queryset = queryset.filter(status=True) elif status_param.lower() == 'false': queryset = queryset.filter(status=False) if has_search: return queryset.order_by('-search_relevance', '-id') return queryset.order_by('-id') @action(detail=True, methods=['get']) def participants(self, request, pk=None): """ Returns two lists: 1. Participants: users who have participated in the quiz. 2. Absentees: users who are enrolled in the course but have not participated. """ quiz = self.get_object() # 1. Fetch participants participants_qs = QuizParticipant.objects.filter(quiz=quiz).select_related('user').prefetch_related('answers') participants_serializer = AdminQuizParticipantSerializer(participants_qs, many=True) # 2. Fetch absentees absentees_data = [] if quiz.course: # Enrolled course student IDs enrolled_student_ids = Participant.objects.filter( course=quiz.course, is_active=True ).values_list('student_id', flat=True) # Participated user IDs participated_user_ids = participants_qs.values_list('user_id', flat=True) # Absentee IDs absentee_ids = set(enrolled_student_ids) - set(participated_user_ids) # Fetch User details for absentees absentees_qs = User.objects.filter(id__in=absentee_ids) absentees_serializer = AdminQuizAbsenteeSerializer(absentees_qs, many=True) absentees_data = absentees_serializer.data return Response({ 'participants': participants_serializer.data, 'absentees': absentees_data }) class AdminQuestionViewSet(ModelViewSet): """ Admin CRUD ViewSet for Question management. Professors can only access questions of quizzes belonging to their own courses. """ serializer_class = AdminQuestionSerializer permission_classes = [IsAuthenticated, IsPanelUser] authentication_classes = [TokenAuthentication] pagination_class = None def get_queryset(self): queryset = Question.objects.all() # Professors can only see questions of their own courses' quizzes if is_professor(self.request): queryset = queryset.filter(quiz__course__professor_id=self.request.user.id) quiz_id = self.request.query_params.get('quiz_id', None) if not quiz_id: # Fallback parameter name quiz_id = self.request.query_params.get('quiz', None) if quiz_id: queryset = queryset.filter(quiz_id=quiz_id) return queryset.order_by('priority', 'id') @action(detail=False, methods=['post']) def reorder(self, request): """ Reorders questions in bulk. Accepts a list of question IDs. Format: { "question_ids": [10, 12, 11, 15] } """ question_ids = request.data.get('question_ids', []) if not question_ids: return Response({'error': 'question_ids list is required'}, status=400) # Update priority for each question based on its position in the list from django.db import transaction questions = Question.objects.filter(id__in=question_ids) question_map = {q.id: q for q in questions} with transaction.atomic(): for idx, q_id in enumerate(question_ids): if q_id in question_map: question = question_map[q_id] question.priority = idx + 1 question.save(update_fields=['priority']) return Response({'status': 'success'}) class AdminCourseListView(ListAPIView): """ Dropdown listing helper for all courses. Professors only see their own courses. """ serializer_class = AdminCourseSerializer permission_classes = [IsAuthenticated, IsPanelUser] authentication_classes = [TokenAuthentication] pagination_class = None # Return all courses for dropdown usage def get_queryset(self): queryset = Course.objects.all().order_by('-id') if is_professor(self.request): queryset = queryset.filter(professor_id=self.request.user.id) return queryset class AdminLessonListView(ListAPIView): """ Dropdown listing helper for lessons. Filtered by course_id. Professors only see lessons of their own courses. """ serializer_class = AdminCourseLessonSerializer permission_classes = [IsAuthenticated, IsPanelUser] authentication_classes = [TokenAuthentication] pagination_class = None # Return all matching lessons for dropdown usage def get_queryset(self): course_id = self.request.query_params.get('course_id', None) if course_id: qs = CourseLesson.objects.filter(course_id=course_id).select_related('lesson').order_by('priority') else: qs = CourseLesson.objects.all().select_related('lesson').order_by('-id') # Professors only see lessons of their own courses if is_professor(self.request): qs = qs.filter(course__professor_id=self.request.user.id) return qs