diff --git a/apps/course/management/commands/sync_quiz_lesson_numbers.py b/apps/course/management/commands/sync_quiz_lesson_numbers.py new file mode 100644 index 0000000..11914ed --- /dev/null +++ b/apps/course/management/commands/sync_quiz_lesson_numbers.py @@ -0,0 +1,86 @@ +from django.core.management.base import BaseCommand +from django.db.models import Q +from apps.quiz.models import Quiz +from apps.course.models import CourseLesson + + +class Command(BaseCommand): + help = "Sync lesson_number for all quizzes connected to a lesson." + + def add_arguments(self, parser): + parser.add_argument( + '--dry-run', + action='store_true', + help='Preview updates without saving changes.', + ) + + def handle(self, *args, **options): + dry_run = options['dry_run'] + updated_quizzes = 0 + + # We filter quizzes that have a lesson assigned + quizzes = Quiz.objects.filter(lesson__isnull=False) + + for quiz in quizzes: + lesson = quiz.lesson + course = quiz.course or lesson.course + + if not course: + self.stdout.write( + self.style.WARNING(f"Quiz {quiz.id} has lesson {lesson.id} but no associated course found.") + ) + continue + + # Find all active lessons for this course to calculate position + lessons_list = list(CourseLesson.objects.filter( + course=course, + is_active=True + ).filter( + Q(chapter__isnull=True) | Q(chapter__is_active=True) + ).order_by('chapter__priority', 'priority')) + + try: + pos = lessons_list.index(lesson) + 1 + except ValueError: + # Lesson might not be active, or not in the course's active list + self.stdout.write( + self.style.WARNING( + f"Quiz {quiz.id}: Lesson {lesson.id} is not active or not in course active list. Skipping." + ) + ) + continue + + needs_update = False + update_fields = [] + + if quiz.lesson_number != pos: + self.stdout.write( + f"Quiz {quiz.id}: lesson_number {quiz.lesson_number} -> {pos}" + ) + quiz.lesson_number = pos + needs_update = True + update_fields.append('lesson_number') + + if not quiz.course: + self.stdout.write(f"Quiz {quiz.id}: setting course to {course.id}") + quiz.course = course + needs_update = True + update_fields.append('course') + + if needs_update: + updated_quizzes += 1 + if not dry_run: + quiz.save(update_fields=update_fields) + + if dry_run: + self.stdout.write( + self.style.WARNING( + f"Dry run complete. {updated_quizzes} quiz(zes) would be updated." + ) + ) + else: + self.stdout.write( + self.style.SUCCESS( + f"Sync complete. {updated_quizzes} quiz(zes) updated." + ) + ) diff --git a/apps/course/views/lesson.py b/apps/course/views/lesson.py index 7055f2e..a5ab621 100644 --- a/apps/course/views/lesson.py +++ b/apps/course/views/lesson.py @@ -305,6 +305,21 @@ class LessonListV2APIView(ListAPIView): } ) ), + 'independent_quizzes': openapi.Schema( + type=openapi.TYPE_ARRAY, + nullable=True, + items=openapi.Schema( + type=openapi.TYPE_OBJECT, + properties={ + 'id': openapi.Schema(type=openapi.TYPE_INTEGER), + 'title': openapi.Schema(type=openapi.TYPE_STRING), + 'description': openapi.Schema(type=openapi.TYPE_STRING, nullable=True), + 'permission': openapi.Schema(type=openapi.TYPE_BOOLEAN), + 'each_question_timing': openapi.Schema(type=openapi.TYPE_INTEGER, nullable=True), + 'is_complated': openapi.Schema(type=openapi.TYPE_BOOLEAN), + } + ) + ), } ) ), @@ -328,3 +343,37 @@ class LessonListV2APIView(ListAPIView): course=course, is_active=True ).order_by('priority', 'id') + + def list(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()) + page = self.paginate_queryset(queryset) + + serializer = self.get_serializer(page if page is not None else queryset, many=True) + + # Get independent quizzes belonging to this course with lesson_number = 0 + course_slug = self.kwargs.get('slug') + course = get_object_or_404(Course, slug__contains=[{"title": course_slug}]) + + from apps.quiz.models import Quiz + from apps.quiz.serializers import QuizListSerializer + + independent_quizzes_qs = Quiz.objects.filter( + course=course, + lesson_number=0, + status=True + ) + independent_quizzes_data = QuizListSerializer( + independent_quizzes_qs, + many=True, + context=self.get_serializer_context() + ).data + + if page is not None: + response = self.get_paginated_response(serializer.data) + response.data['independent_quizzes'] = independent_quizzes_data + return response + + return Response({ + "independent_quizzes": independent_quizzes_data, + "results": serializer.data + })