Add statistics block to SubmissionAdmin

This commit is contained in:
Dmitry Belan
2026-09-28 22:46:32 +03:00
parent c7d443e2ee
commit 08eba4f9d7
+71 -2
View File
@@ -99,6 +99,8 @@ class AnswerInline(StackedInline):
def has_add_permission(self, request, obj=None): def has_add_permission(self, request, obj=None):
return False return False
from django.utils.html import format_html
@admin.register(Submission) @admin.register(Submission)
class SubmissionAdmin(ModelAdmin): class SubmissionAdmin(ModelAdmin):
change_list_template = "admin/lms/submission/change_list.html" change_list_template = "admin/lms/submission/change_list.html"
@@ -107,10 +109,77 @@ class SubmissionAdmin(ModelAdmin):
search_fields = ('student__username', 'assignment__title') search_fields = ('student__username', 'assignment__title')
inlines = [AnswerInline] 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): def get_readonly_fields(self, request, obj=None):
if obj: if obj:
return ('assignment', 'student', 'is_time_expired', 'is_late', 'started_at', 'submitted_at') return ('statistics', 'assignment', 'student', 'is_time_expired', 'is_late', 'started_at', 'submitted_at')
return ('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('<div style="color: #8b949e;">Оценка не выставлена или имеет неверный формат. Укажите оценку в формате "X/Y" (например, "3/27").</div>')
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(
'''
<div style="background-color: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 20px; color: #c9d1d9; font-family: ui-sans-serif, system-ui, sans-serif;">
<div style="display: flex; justify-content: space-between; align-items: flex-end;">
<div>
<div style="font-size: 11px; text-transform: uppercase; color: #8b949e; letter-spacing: 0.05em; margin-bottom: 8px;">Итоговая оценка</div>
<div style="display: flex; align-items: baseline; gap: 12px;">
<div style="background-color: #0d1117; border: 1px solid #30363d; border-radius: 6px; padding: 6px 16px; font-size: 24px; font-weight: bold; color: #fff;">
{grade}
</div>
<span style="font-size: 13px; color: #8b949e;">первичных баллов</span>
</div>
</div>
<div style="flex-grow: 1; max-width: 50%; display: flex; flex-direction: column; justify-content: flex-end;">
<div style="display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 8px;">
<span style="color: #c9d1d9;">Выполнение: <b>{percentage:.1f}%</b></span>
<span style="color: {color}; font-weight: 500;">{threshold_text}</span>
</div>
<div style="width: 100%; background-color: #30363d; border-radius: 9999px; height: 6px; overflow: hidden; margin-bottom: 8px;">
<div style="background-color: {color}; width: {percentage}%; height: 100%; border-radius: 9999px;"></div>
</div>
<div style="display: flex; justify-content: space-between; font-size: 11px; color: #8b949e; font-family: ui-monospace, SFMono-Regular, monospace;">
<span>0 б</span>
<span>Порог: {threshold_points} б</span>
<span>Максимум: {total_int} б</span>
</div>
</div>
</div>
</div>
''',
grade=obj.grade,
percentage=percentage,
threshold_text=threshold_text,
color=color,
total_int=total_int,
threshold_points=threshold_points
)
except Exception:
return format_html('<div style="color: #f85149;">Оценка имеет неверный формат. Используйте формат "X/Y" (например, "3/27").</div>')
def get_queryset(self, request): def get_queryset(self, request):
qs = super().get_queryset(request) qs = super().get_queryset(request)