Browse Source

admin panel Quiz enhancement

moved the Quizzes tab in Course section.

in the course details we have a quiz section ,accessing to all the quizzes of that course

an action button for quizzes to navigate to related course added

quiz model updated with course field
master
Mohsen Taba 4 days ago
parent
commit
6a08428019
  1. 27
      apps/course/admin/course.py
  2. 29
      apps/quiz/admin/quiz.py
  3. 20
      apps/quiz/migrations/0003_quiz_course.py
  4. 7
      apps/quiz/models/quiz.py

27
apps/course/admin/course.py

@ -29,9 +29,11 @@ from utils.json_editor_field import JsonEditorWidget
from apps.course.models import Course, Glossary, Attachment, CourseCategory, Participant, CourseGlossary, CourseAttachment from apps.course.models import Course, Glossary, Attachment, CourseCategory, Participant, CourseGlossary, CourseAttachment
from apps.course.models.lesson import Lesson, CourseLesson from apps.course.models.lesson import Lesson, CourseLesson
from apps.account.models import StudentUser, User from apps.account.models import StudentUser, User
from apps.quiz.models import Quiz
from utils.schema import get_weekly_timing_schema, get_course_feature_schema from utils.schema import get_weekly_timing_schema, get_course_feature_schema
class CourseTableSection(TableSection): class CourseTableSection(TableSection):
verbose_name = _("Course Categories") verbose_name = _("Course Categories")
related_name = "courses" related_name = "courses"
@ -125,6 +127,29 @@ class MinWidthInlineForm(forms.ModelForm):
if hasattr(field, 'empty_label'): if hasattr(field, 'empty_label'):
field.empty_label = _("Select a value") field.empty_label = _("Select a value")
class CourseQuizInline(TabularInline):
model = Quiz
form = MinWidthInlineForm
# فیلدهایی که می‌خواهید در لیست تب نمایش داده شوند
fields = ('title', 'lesson', 'status')
readonly_fields = ('title', 'lesson', 'status')
extra = 0
tab = True
tab_id = "quizzes_tab"
# 🎯 این خط جادویی است! یک لینک برای رفتن به صفحه دیتیل کوییز اضافه می‌کند
show_change_link = True
verbose_name = _("Quiz")
verbose_name_plural = _("Quizzes")
# ما فقط می‌خواهیم این تب نمایشی باشد، پس دسترسی اضافه/تغییر/حذف را در این تب می‌بندیم
def has_add_permission(self, request, obj):
return False
def has_change_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
class CourseAttachmentInline(TabularInline): class CourseAttachmentInline(TabularInline):
model = CourseAttachment model = CourseAttachment
@ -201,7 +226,7 @@ class AddStudentForm(forms.Form):
class CourseAdmin(DirectCourseAdmin): class CourseAdmin(DirectCourseAdmin):
form = CourseForm form = CourseForm
inlines = [CourseLessonInline, CourseAttachmentInline, CourseGlossaryInline, ParticipantInline]
inlines = [CourseLessonInline, CourseAttachmentInline, CourseGlossaryInline, CourseQuizInline, ParticipantInline]
list_display = ('display_header', 'category', 'display_professor', 'status', 'display_price', 'is_online') list_display = ('display_header', 'category', 'display_professor', 'status', 'display_price', 'is_online')
list_filter = [ list_filter = [

29
apps/quiz/admin/quiz.py

@ -23,7 +23,7 @@ class QuizAdmin(ModelAdmin):
compressed_fields = True compressed_fields = True
# 🔔 ADD THE TOP ACTION BUTTON # 🔔 ADD THE TOP ACTION BUTTON
actions_detail = ['manage_all_participants']
actions_detail = ['manage_all_participants','go_to_course']
def get_queryset(self, request): def get_queryset(self, request):
queryset = super().get_queryset(request).annotate( queryset = super().get_queryset(request).annotate(
@ -41,6 +41,17 @@ class QuizAdmin(ModelAdmin):
return queryset.filter(lesson__course__professor=request.user) return queryset.filter(lesson__course__professor=request.user)
return queryset.none() return queryset.none()
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): def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs) form = super().get_form(request, obj, **kwargs)
@ -92,4 +103,20 @@ class QuizAdmin(ModelAdmin):
url = f"{base_url}?quiz__id__exact={object_id}" url = f"{base_url}?quiz__id__exact={object_id}"
return redirect(url) 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) project_admin_site.register(Quiz, QuizAdmin)

20
apps/quiz/migrations/0003_quiz_course.py

@ -0,0 +1,20 @@
# Generated by Django 5.2.12 on 2026-05-16 15:01
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course', '0006_alter_course_professor_alter_course_video_file_and_more'),
('quiz', '0002_alter_participantanswer_answer_timing_and_more'),
]
operations = [
migrations.AddField(
model_name='quiz',
name='course',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='quizzes', to='course.course', verbose_name='Course'),
),
]

7
apps/quiz/models/quiz.py

@ -6,6 +6,7 @@ from apps.account.models import User
class Quiz(models.Model): class Quiz(models.Model):
lesson = models.ForeignKey("course.CourseLesson", verbose_name=_('Lesson'), related_name='quizzes', on_delete=models.CASCADE) lesson = models.ForeignKey("course.CourseLesson", verbose_name=_('Lesson'), related_name='quizzes', on_delete=models.CASCADE)
course = models.ForeignKey("course.Course", verbose_name=_('Course'), related_name='quizzes', on_delete=models.CASCADE , null=True, blank=True)
title = models.CharField(max_length=255, verbose_name=_('Title'), help_text=_("Quiz Title")) title = models.CharField(max_length=255, verbose_name=_('Title'), help_text=_("Quiz Title"))
description = models.CharField(max_length=55, blank=True, null=True, verbose_name=_("Description")) description = models.CharField(max_length=55, blank=True, null=True, verbose_name=_("Description"))
@ -24,6 +25,12 @@ class Quiz(models.Model):
def __repr__(self): def __repr__(self):
return f"Quiz(id={self.id})" return f"Quiz(id={self.id})"
def save(self, *args, **kwargs):
# Automatically set the course based on the selected lesson
if self.lesson_id and hasattr(self.lesson, 'course'):
self.course = self.lesson.course
super().save(*args, **kwargs)
class Question(models.Model): class Question(models.Model):
CHOICES = [ CHOICES = [

Loading…
Cancel
Save