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.
194 lines
8.0 KiB
194 lines
8.0 KiB
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'<span class="unfold-badge unfold-badge--success">{_("Active")}</span>')
|
|
return mark_safe(f'<span class="unfold-badge unfold-badge--warning">{_("Inactive")}</span>')
|
|
|
|
@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'<a href="{url}" class="unfold-link">{_("Questions")}: {obj.questions_count}</a>')
|
|
|
|
# 🔔 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)
|