From 6a084280198facefbcb4042e6b2513b918dee1a9 Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Sat, 16 May 2026 15:46:02 +0330 Subject: [PATCH] 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 --- apps/course/admin/course.py | 27 +++++++++++++++++++++- apps/quiz/admin/quiz.py | 29 +++++++++++++++++++++++- apps/quiz/migrations/0003_quiz_course.py | 20 ++++++++++++++++ apps/quiz/models/quiz.py | 7 ++++++ 4 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 apps/quiz/migrations/0003_quiz_course.py diff --git a/apps/course/admin/course.py b/apps/course/admin/course.py index e4f17b3..423a401 100644 --- a/apps/course/admin/course.py +++ b/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.lesson import Lesson, CourseLesson 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 + class CourseTableSection(TableSection): verbose_name = _("Course Categories") related_name = "courses" @@ -125,6 +127,29 @@ class MinWidthInlineForm(forms.ModelForm): if hasattr(field, 'empty_label'): 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): model = CourseAttachment @@ -201,7 +226,7 @@ class AddStudentForm(forms.Form): class CourseAdmin(DirectCourseAdmin): 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_filter = [ diff --git a/apps/quiz/admin/quiz.py b/apps/quiz/admin/quiz.py index eedfeee..db0dde2 100644 --- a/apps/quiz/admin/quiz.py +++ b/apps/quiz/admin/quiz.py @@ -23,7 +23,7 @@ class QuizAdmin(ModelAdmin): compressed_fields = True # 🔔 ADD THE TOP ACTION BUTTON - actions_detail = ['manage_all_participants'] + actions_detail = ['manage_all_participants','go_to_course'] def get_queryset(self, request): queryset = super().get_queryset(request).annotate( @@ -41,6 +41,17 @@ class QuizAdmin(ModelAdmin): return queryset.filter(lesson__course__professor=request.user) 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): form = super().get_form(request, obj, **kwargs) @@ -92,4 +103,20 @@ class QuizAdmin(ModelAdmin): 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) \ No newline at end of file diff --git a/apps/quiz/migrations/0003_quiz_course.py b/apps/quiz/migrations/0003_quiz_course.py new file mode 100644 index 0000000..ba5c3ae --- /dev/null +++ b/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'), + ), + ] diff --git a/apps/quiz/models/quiz.py b/apps/quiz/models/quiz.py index eebac93..37a9564 100644 --- a/apps/quiz/models/quiz.py +++ b/apps/quiz/models/quiz.py @@ -6,6 +6,7 @@ from apps.account.models import User class Quiz(models.Model): 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")) description = models.CharField(max_length=55, blank=True, null=True, verbose_name=_("Description")) @@ -24,6 +25,12 @@ class Quiz(models.Model): def __repr__(self): 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): CHOICES = [