from django.contrib import admin, messages from django.db.models import Q from django.db.models import Count from django.utils.safestring import mark_safe from django.urls import reverse from django.shortcuts import redirect from django.utils.translation import gettext_lazy as _ from unfold.admin import ModelAdmin from unfold.decorators import display, action from apps.course.models import CourseLesson from apps.course.models import Course from apps.course.models.course import extract_text_from_json from apps.quiz.models import Quiz from apps.quiz.admin.question import QuestionAdminInline from apps.quiz.admin.participant import QuizParticipantInline from utils.admin import project_admin_site, admin_url_generator class QuizAdmin(ModelAdmin): search_fields = ['title', 'description'] list_display = ['title_display', 'description_display', 'lesson', 'each_question_timing', 'status_display', 'questions_display'] list_filter = ['each_question_timing', 'status'] inlines = [QuestionAdminInline, QuizParticipantInline] compressed_fields = True # 🔔 ADD THE TOP ACTION BUTTON actions_detail = ['manage_all_participants','go_to_course'] def get_queryset(self, request): queryset = super().get_queryset(request).annotate( questions_count=Count('questions') ) # اولویت اول: staff یا admin - دسترسی کامل if (request.user.is_staff or request.user.has_role('admin') or request.user.has_role('super_admin')): return queryset # اولویت دوم: professor - فقط کوئیزهای دوره‌های خود if request.user.has_role('professor'): return queryset.filter(lesson__course__professor=request.user) return queryset.none() def _find_matching_course_ids(self, search_term): 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(self, search_term): 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 get_exclude(self, request, obj=None): if not obj: # اگر obj وجود ندارد یعنی صفحه Add است return ['course'] return [] # 🔔 قفل کردن (Readonly) فیلد کورس در زمان مشاهده و ویرایش def get_readonly_fields(self, request, obj=None): if obj: # اگر obj وجود دارد یعنی صفحه Change/Detail است return ['course'] return [] def get_form(self, request, obj=None, **kwargs): form = super().get_form(request, obj, **kwargs) # محدود کردن انتخاب lesson بر اساس سطح دسترسی کاربر if (request.user.is_staff or request.user.has_role('admin') or request.user.has_role('super_admin')): # اولویت اول: staff یا admin - دسترسی کامل form.base_fields['lesson'].queryset = CourseLesson.objects.all() elif request.user.has_role('professor'): # اولویت دوم: professor - فقط CourseLesson های دوره‌های خود form.base_fields['lesson'].queryset = CourseLesson.objects.filter(course__professor=request.user) else: # سایر کاربران - عدم دسترسی form.base_fields['lesson'].queryset = CourseLesson.objects.none() form.base_fields['lesson'].widget.can_add_related = False return form def get_search_results(self, request, queryset, search_term): queryset, use_distinct = super().get_search_results(request, queryset, search_term) matching_course_ids = self._find_matching_course_ids(search_term) matching_lesson_ids = self._find_matching_lesson_ids(search_term) matching_quiz_ids = [] normalized = (search_term or "").strip().lower() if normalized: for quiz in self.model.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_quiz_ids.append(quiz.id) if matching_course_ids or matching_lesson_ids or matching_quiz_ids: queryset = queryset | self.model.objects.filter( Q(id__in=matching_quiz_ids) | Q(course_id__in=matching_course_ids) | Q(lesson_id__in=matching_lesson_ids) ) use_distinct = True return queryset, use_distinct @display(description=_('Title')) def title_display(self, obj): return extract_text_from_json(obj.title) @display(description=_('Description')) def description_display(self, obj): return extract_text_from_json(obj.description) @display(description=_('Status'), ordering='status') def status_display(self, obj): if obj.status: return mark_safe(f'{_("Active")}') return mark_safe(f'{_("Inactive")}') @display(description=_('Questions'), ordering='questions_count') def questions_display(self, obj): url = reverse('admin:quiz_question_changelist') + f'?quiz={obj.id}' return mark_safe(f'{_("Questions")}: {obj.questions_count}') # 🔔 THE REDIRECT LOGIC FOR THE NEW BUTTON @action( description=_("Manage All Participants"), icon="groups", ) def manage_all_participants(self, request, object_id): """Redirect to the pre-filtered Quiz Participant changelist for this quiz.""" quiz = self.get_object(request, object_id) if not quiz: messages.error(request, _("Quiz not found")) return redirect(admin_url_generator(request, "quiz_quiz_changelist")) # Generate base URL for quiz participant list base_url = admin_url_generator(request, "quiz_quizparticipant_changelist") # Append the filter query parameter url = f"{base_url}?quiz__id__exact={object_id}" return redirect(url) @action( description=_("View Course"), icon="school", # آیکون کلاه فارغ‌التحصیلی ) def go_to_course(self, request, object_id): """دکمه‌ای برای رفتن به صفحه جزئیات کورسِ این کوییز""" quiz = self.get_object(request, object_id) # پیدا کردن کورس از طریق درس (Lesson) if quiz and quiz.lesson and quiz.lesson.course_id: url = reverse('admin:course_course_change', args=[quiz.lesson.course_id]) return redirect(url) messages.error(request, _("Course not found for this quiz.")) return redirect(request.META.get('HTTP_REFERER', '/')) project_admin_site.register(Quiz, QuizAdmin)