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') exclude = ('created_by',) 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): if not obj or not obj.grade: return format_html('
Оценка не выставлена или имеет неверный формат. Укажите оценку в формате "X/Y" (например, "3/27").
') try: parts = obj.grade.split('/') earned = float(parts[0].strip()) total = float(parts[1].strip()) percentage = (earned / total) * 100 if total > 0 else 0 is_passed = percentage >= 40 threshold_text = "Выше порога" if is_passed else "Ниже порога (Мин: 40%)" color = "#2ea043" if is_passed else "#f85149" total_int = int(total) if total.is_integer() else total threshold_points = int(total * 0.4) return format_html( '''
Итоговая оценка
{grade}
первичных баллов
Выполнение: {percentage:.1f}% {threshold_text}
0 б Порог: {threshold_points} б Максимум: {total_int} б
''', grade=obj.grade, percentage=percentage, threshold_text=threshold_text, color=color, total_int=total_int, threshold_points=threshold_points ) except Exception: return format_html('
Оценка имеет неверный формат. Используйте формат "X/Y" (например, "3/27").
') 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)