from django.contrib import admin from unfold.admin import ModelAdmin, StackedInline from django.db import models from django.forms import widgets from .models import Subject, Assignment, Submission, Question, Answer @admin.register(Subject) class SubjectAdmin(ModelAdmin): list_display = ('name',) list_display_links = ('name',) class QuestionInline(StackedInline): model = Question extra = 0 class Media: js = ('js/admin_assignment.js',) @admin.register(Assignment) class AssignmentAdmin(ModelAdmin): list_display = ('title', 'subject', 'is_published', 'time_limit', 'deadline', 'allow_late_submissions', 'created_at') list_display_links = ('title',) list_filter = ('subject', 'is_published', 'allow_late_submissions') search_fields = ('title', 'description') fieldsets = ( ('Основная информация', { 'fields': ('title', 'subject', 'short_description', 'description') }), ('Настройки доступа и времени', { 'fields': ('is_published', 'assign_to_all', 'assigned_students', 'deadline', 'time_limit') }), ('Настройки проверки', { 'fields': ('allow_late_submissions', 'passing_threshold') }), ) filter_horizontal = ('assigned_students',) inlines = [QuestionInline] actions = ['make_published', 'make_unpublished', 'delete_selected_assignments'] @admin.action(description="Опубликовать выбранные задания") def make_published(self, request, queryset): updated = queryset.update(is_published=True) self.message_user(request, f"Опубликовано {updated} заданий.") @admin.action(description="Скрыть выбранные задания (в черновик)") def make_unpublished(self, request, queryset): updated = queryset.update(is_published=False) self.message_user(request, f"Скрыто {updated} заданий.") @admin.action(description="Удалить выбранные задания") def delete_selected_assignments(self, request, queryset): deleted_count = 0 for obj in queryset: if self.has_delete_permission(request, obj): obj.delete() deleted_count += 1 self.message_user(request, f"Успешно удалено {deleted_count} заданий.") def get_queryset(self, request): qs = super().get_queryset(request) if request.user.is_superuser or request.user.role == 'ADMIN': return qs return qs.filter(created_by=request.user) def save_model(self, request, obj, form, change): if getattr(obj, 'created_by_id', None) is None: obj.created_by = request.user super().save_model(request, obj, form, change) def has_delete_permission(self, request, obj=None): if request.user.is_superuser or request.user.role == 'ADMIN': return True if request.user.role == 'TEACHER': if obj is not None and obj.created_by != request.user: return False return True return False from django.utils.html import format_html class AnswerInline(StackedInline): model = Answer extra = 0 readonly_fields = ('question', 'get_student_answer') fields = ('question', 'get_student_answer', 'is_correct', 'points', 'comment') def get_student_answer(self, obj): if not obj or not obj.pk: return "" if obj.question.question_type == 'TEXT_AUTO': return obj.text_answer or "Нет ответа" elif obj.question.question_type == 'TABLE_AUTO': import json try: data = json.loads(obj.text_answer) html = '' for row in data: html += f'' html += '
{row[0]}{row[1]}
' return format_html(html) except Exception: return obj.text_answer or "Нет ответа" else: if obj.file_answer: return format_html('Скачать файл', obj.file_answer.url) return "Файл не прикреплен" get_student_answer.short_description = 'Ответ ученика' def has_add_permission(self, request, obj=None): return False from django.utils.html import format_html @admin.register(Submission) class SubmissionAdmin(ModelAdmin): change_list_template = "admin/lms/submission/change_list.html" list_display = ('assignment', 'student', 'status', 'is_time_expired', 'is_late', 'grade', 'submitted_at') list_filter = ('status', 'assignment__subject', 'student', 'is_time_expired', 'is_late') search_fields = ('student__username', 'assignment__title') inlines = [AnswerInline] fieldsets = ( ('Параметры проверки и оценка', { 'fields': ('statistics', ('assignment', 'student'), ('grade', 'status'), ('is_time_expired', 'is_late'), 'teacher_feedback') }), ('Временные метки', { 'fields': (('started_at', 'submitted_at'),) }), ) def get_readonly_fields(self, request, obj=None): if obj: return ('statistics', 'assignment', 'student', 'is_time_expired', 'is_late', 'started_at', 'submitted_at') return ('statistics', 'is_time_expired', 'is_late', 'started_at', 'submitted_at') @admin.display(description='Статистика (сохраните оценку в формате X/Y, чтобы обновить)') def statistics(self, obj): stats = obj.get_statistics() if obj else None if not stats: return format_html('
Оценка не выставлена или имеет неверный формат. Укажите оценку в формате "X/Y" (например, "3/27").
') try: return format_html( '''
Итоговая оценка
{grade}
первичных баллов
Выполнение: {percentage_str}% {threshold_text}
0 б Порог: {threshold_points} б Максимум: {total_int} б
''', grade=obj.grade, percentage_str=stats['percentage_str'], threshold_text=stats['threshold_text'], color=stats['color'], total_int=stats['total'], threshold_points=stats['threshold_points'] ) except Exception as e: return format_html( '
Ошибка при отрисовке статистики: {error}
', error=str(e) ) def save_formset(self, request, form, formset, change): instances = formset.save(commit=False) for instance in instances: if isinstance(instance, Answer): if instance.is_correct and instance.points is None: instance.points = instance.question.points elif instance.is_correct is False and instance.points is None: instance.points = 0 instance.save() for obj in formset.deleted_objects: obj.delete() formset.save_m2m() if formset.model == Answer: submission = formset.instance total_earned = sum((ans.points or 0) for ans in submission.answers.all()) total_max = sum((q.points or 1) for q in submission.assignment.questions.all()) new_grade = f"{total_earned}/{total_max}" if submission.grade != new_grade: submission.grade = new_grade submission.save(update_fields=['grade']) def get_queryset(self, request): qs = super().get_queryset(request) if request.user.is_superuser or request.user.role == 'ADMIN': return qs if request.user.role == 'TEACHER': return qs.filter(assignment__created_by=request.user) return qs.filter(student=request.user)