""" مرورگرِ محتوا در پنلِ ادمین: پایه → فصل → درس → نوعِ مسئله → سوال‌ها. چرا: فهرستِ پیش‌فرضِ جنگو برای پیدا کردنِ «سوال‌های فلان مبحث» مناسب نیست — باید بینِ چند صفحه‌ی جدا با فیلترهای دراپ‌داونی بالا و پایین رفت، و وقتی ۱۳۰۷ درس و هزاران سوال داری عملاً کار نمی‌کند. اینجا هر ستون فقط وقتی پر می‌شود که ستونِ قبلی انتخاب شده باشد، پس هیچ‌وقت فهرستِ هزارتایی جلوی چشم نیست. کنارِ هر سوال دو عددِ تصمیم‌ساز می‌آید: گزارش‌های بازِ کاربران، و درصدِ پاسخِ غلط — یعنی همان سیگنالی که می‌گوید کدام سوال ایراد دارد یا بیش از حد سخت است. وابستگیِ تازه‌ای اضافه نمی‌کند: چند اندپوینتِ کوچکِ JSON و JavaScript ساده. """ from django.contrib.admin.views.decorators import staff_member_required from django.db.models import Count, Q from django.http import JsonResponse from django.shortcuts import render from django.urls import reverse from .models import Grade, Lesson, Node, Question, Unit @staff_member_required def browser_view(request): return render(request, 'admin/content/browser.html', { 'title': 'مرورِ محتوا', 'grades': Grade.objects.order_by('title').values('id', 'title'), }) @staff_member_required def units_json(request, grade_id: int): rows = ( Unit.objects.filter(grade_id=grade_id) .annotate(node_count=Count('nodes', distinct=True)) .order_by('order', 'id') .values('id', 'title', 'node_count') ) return JsonResponse({'results': list(rows)}) @staff_member_required def nodes_json(request, unit_id: int): rows = ( Node.objects.filter(unit_id=unit_id) .annotate(lesson_count=Count('lessons', distinct=True)) .order_by('order', 'id') .values('id', 'title', 'lesson_count') ) return JsonResponse({'results': list(rows)}) @staff_member_required def lessons_json(request, node_id: int): rows = ( Lesson.objects.filter(node_id=node_id) .annotate(question_count=Count('questions', distinct=True)) .order_by('id') .values('id', 'title', 'question_count') ) return JsonResponse({'results': list(rows)}) @staff_member_required def questions_json(request, lesson_id: int): """ هر سوال با دو سیگنال: گزارشِ بازِ کاربران، و درصدِ پاسخِ غلط. درصدِ غلط فقط وقتی معنا دارد که تعدادِ پاسخ کم نباشد؛ زیرِ ۵ پاسخ عمداً None برمی‌گردد تا یک نمونه‌ی تصادفی سوالِ سالم را «مشکل‌دار» نشان ندهد. """ questions = ( Question.objects.filter(lesson_id=lesson_id) .annotate( open_reports=Count('reports', filter=Q(reports__status='open'), distinct=True), total_answers=Count('answers', distinct=True), wrong_answers=Count('answers', filter=Q(answers__correct=False), distinct=True), ) .prefetch_related('choices') .order_by('id') ) results = [] for question in questions: wrong_percent = ( round(100 * question.wrong_answers / question.total_answers) if question.total_answers >= 5 else None ) results.append({ 'id': question.pk, 'prompt': question.prompt, 'type': question.get_type_display(), 'choices': [ {'value': c.value, 'isCorrect': c.is_correct} for c in question.choices.all() ], 'openReports': question.open_reports, 'totalAnswers': question.total_answers, 'wrongPercent': wrong_percent, 'editUrl': reverse('admin:content_question_change', args=[question.pk]), 'reportsUrl': ( reverse('admin:content_questionreport_changelist') + f'?question__id__exact={question.pk}' ), }) return JsonResponse({ 'results': results, 'addUrl': reverse('admin:content_question_add') + f'?lesson={lesson_id}', })