import json from django.contrib import admin, messages from django.http import HttpResponse from django.shortcuts import redirect, render from django.urls import path, reverse from django.utils.html import format_html from .question_io import ImportError_, export_questions, import_questions, parse from .models import ( Choice, Grade, Lesson, Node, NodeProgress, Question, Unit, UserAnswer, UserLessonCompletion, ) class ChoiceInline(admin.TabularInline): model = Choice extra = 1 @admin.register(Grade) class GradeAdmin(admin.ModelAdmin): list_display = ('id', 'title', 'icon', 'color') @admin.register(Unit) class UnitAdmin(admin.ModelAdmin): list_display = ('id', 'title', 'grade', 'order') list_filter = ('grade',) @admin.register(Node) class NodeAdmin(admin.ModelAdmin): list_display = ('id', 'title', 'unit', 'order') list_filter = ('unit',) @admin.register(Lesson) class LessonAdmin(admin.ModelAdmin): list_display = ('id', 'title', 'node') list_filter = ('node',) @admin.register(Question) class QuestionAdmin(admin.ModelAdmin): """ سوال‌ها علاوه بر ویرایشِ تکی، از همین‌جا فله‌ای وارد و خارج می‌شوند. چرا اینجا و نه در ترمینال: محتوا باید بدونِ دسترسیِ SSH و بدونِ برنامه‌نویس قابلِ تغییر باشد. خروجیِ «دریافتِ فایل» دقیقاً همان قالبی‌ست که «وارد کردن» می‌پذیرد، پس چرخه‌ی ویرایش بسته است. """ list_display = ('id', 'prompt', 'lesson', 'type') list_filter = ('type', 'lesson') inlines = [ChoiceInline] actions = ['export_selected'] change_list_template = 'admin/content/question/change_list.html' def get_urls(self): return [ path( 'import/', self.admin_site.admin_view(self.import_view), name='content_question_import', ), ] + super().get_urls() @admin.action(description='دریافتِ فایلِ سوال‌های انتخاب‌شده') def export_selected(self, request, queryset): payload = json.dumps(export_questions(queryset), ensure_ascii=False, indent=2) response = HttpResponse(payload, content_type='application/json; charset=utf-8') response['Content-Disposition'] = 'attachment; filename="questions.json"' return response def import_view(self, request): if request.method == 'POST': uploaded = request.FILES.get('file') if uploaded is None: self.message_user(request, 'فایلی انتخاب نشده بود.', messages.ERROR) return redirect(request.path) replace = request.POST.get('replace') == 'on' try: rows = parse(uploaded.read()) result = import_questions(rows, replace=replace) except ImportError_ as exc: # خطای قابلِ فهم برای کاربر — هیچ چیزی نوشته نشده. self.message_user(request, str(exc), messages.ERROR) return redirect(request.path) note = f"{result['created']} سوال در {result['lessons']} درس اضافه شد" if result['removed']: note += f" و {result['removed']} ردیفِ قبلی پاک شد" self.message_user(request, note + '.', messages.SUCCESS) return redirect(reverse('admin:content_question_changelist')) return render(request, 'admin/content/question/import.html', { **self.admin_site.each_context(request), 'title': 'وارد کردنِ سوال‌ها', 'opts': self.model._meta, }) @admin.register(UserLessonCompletion) class UserLessonCompletionAdmin(admin.ModelAdmin): list_display = ('id', 'user_id', 'lesson', 'correct_count', 'stars', 'xp_earned', 'coins_earned') list_filter = ('lesson',) @admin.register(UserAnswer) class UserAnswerAdmin(admin.ModelAdmin): list_display = ('id', 'user_id', 'question', 'choice', 'correct', 'xp_earned') list_filter = ('correct',) @admin.register(NodeProgress) class NodeProgressAdmin(admin.ModelAdmin): list_display = ( 'id', 'user_id', 'node', 'status', 'lessons_completed_count', 'started_at', 'last_activity_at', 'completed_at', ) list_filter = ('status', 'node__unit') search_fields = ('user_id',)