Enhance task numbers, enable late submissions with badges, and fix overdue badges
This commit is contained in:
+2
-2
@@ -102,8 +102,8 @@ class AnswerInline(StackedInline):
|
|||||||
@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"
|
||||||
list_display = ('assignment', 'student', 'status', 'is_time_expired', 'grade', 'submitted_at')
|
list_display = ('assignment', 'student', 'status', 'is_time_expired', 'is_late', 'grade', 'submitted_at')
|
||||||
list_filter = ('status', 'assignment__subject', 'student', 'is_time_expired')
|
list_filter = ('status', 'assignment__subject', 'student', 'is_time_expired', 'is_late')
|
||||||
search_fields = ('student__username', 'assignment__title')
|
search_fields = ('student__username', 'assignment__title')
|
||||||
inlines = [AnswerInline]
|
inlines = [AnswerInline]
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,8 @@ class Question(models.Model):
|
|||||||
ordering = ['order', 'id']
|
ordering = ['order', 'id']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Задача {self.id} к {self.assignment.title}"
|
number = self.order if self.order else self.id
|
||||||
|
return f"Задача {number} к {self.assignment.title}"
|
||||||
|
|
||||||
class Submission(models.Model):
|
class Submission(models.Model):
|
||||||
STATUS_CHOICES = (
|
STATUS_CHOICES = (
|
||||||
@@ -68,6 +69,7 @@ class Submission(models.Model):
|
|||||||
started_at = models.DateTimeField('Время начала', blank=True, null=True)
|
started_at = models.DateTimeField('Время начала', blank=True, null=True)
|
||||||
submitted_at = models.DateTimeField('Дата отправки', auto_now_add=True)
|
submitted_at = models.DateTimeField('Дата отправки', auto_now_add=True)
|
||||||
is_time_expired = models.BooleanField('Время вышло', default=False)
|
is_time_expired = models.BooleanField('Время вышло', default=False)
|
||||||
|
is_late = models.BooleanField('Сдано после дедлайна', default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'Ответ ученика'
|
verbose_name = 'Ответ ученика'
|
||||||
|
|||||||
@@ -166,10 +166,20 @@
|
|||||||
<svg class="w-5 h-5 text-rose-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>
|
<svg class="w-5 h-5 text-rose-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>
|
||||||
Прием ответов завершен
|
Прием ответов завершен
|
||||||
</h4>
|
</h4>
|
||||||
<p class="text-sm">Вы больше не можете отправлять ответы, так как дедлайн истек или время на выполнение закончилось.</p>
|
<p class="text-sm">Вы больше не можете отправлять ответы, так как время на выполнение закончилось.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
{% if is_late %}
|
||||||
|
<div class="mb-6 p-4 rounded-lg border bg-rose-950/20 text-rose-300 border-rose-800/40 flex items-start gap-3 shadow-sm">
|
||||||
|
<svg class="w-5 h-5 text-rose-400 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>
|
||||||
|
<div>
|
||||||
|
<h4 class="font-bold text-rose-300 mb-1 font-heading">Дедлайн прошел</h4>
|
||||||
|
<p class="text-sm text-rose-300/80">Вы выполняете это задание после дедлайна (дорешивание).</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if time_left_seconds is not None %}
|
{% if time_left_seconds is not None %}
|
||||||
<div id="timer-banner" class="sticky top-16 sm:top-20 z-40 mb-8 p-4 rounded-xl border bg-obsidian-base border-accent-blue shadow-lg shadow-accent-blue/10 flex justify-between items-center transition-colors duration-300">
|
<div id="timer-banner" class="sticky top-16 sm:top-20 z-40 mb-8 p-4 rounded-xl border bg-obsidian-base border-accent-blue shadow-lg shadow-accent-blue/10 flex justify-between items-center transition-colors duration-300">
|
||||||
<div class="flex items-center gap-2.5">
|
<div class="flex items-center gap-2.5">
|
||||||
|
|||||||
@@ -22,30 +22,41 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
{% if sub %}
|
{% if sub %}
|
||||||
|
{% if sub.is_late %}
|
||||||
|
<span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-xs font-mono font-medium bg-rose-950/40 text-rose-400 border border-rose-800/50">
|
||||||
|
После дедлайна
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
{% if sub.status == 'GRADED' %}
|
{% if sub.status == 'GRADED' %}
|
||||||
<span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-xs font-mono font-medium bg-emerald-950/40 text-emerald-300 border border-emerald-800/50">
|
<span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-xs font-mono font-medium bg-status-emeraldBg text-status-emerald border border-status-emerald/30">
|
||||||
<svg class="w-3 h-3 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-3 h-3 text-status-emerald" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path d="M5 13l4 4L19 7" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></path>
|
<path d="M5 13l4 4L19 7" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></path>
|
||||||
</svg>
|
</svg>
|
||||||
Принято ({{ sub.grade|default:"-" }})
|
Принято ({{ sub.grade|default:"-" }})
|
||||||
</span>
|
</span>
|
||||||
{% elif sub.status == 'IN_PROGRESS' %}
|
{% elif sub.status == 'IN_PROGRESS' %}
|
||||||
<span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-xs font-mono font-medium bg-accent-blue/20 text-accent-blue border border-accent-blue/30">
|
<span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-xs font-mono font-medium bg-accent-blueSubtle text-accent-blue border border-accent-blueBorder">
|
||||||
<span class="w-1.5 h-1.5 rounded-full bg-accent-blue"></span>
|
<span class="w-1.5 h-1.5 rounded-full bg-accent-blue"></span>
|
||||||
В процессе
|
В процессе
|
||||||
</span>
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-xs font-mono font-medium bg-amber-950/40 text-amber-300 border border-amber-800/50">
|
<span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-xs font-mono font-medium bg-status-amberBg text-status-amber border border-status-amber/30">
|
||||||
<span class="w-1.5 h-1.5 rounded-full bg-amber-400"></span>
|
<span class="w-1.5 h-1.5 rounded-full bg-status-amber"></span>
|
||||||
Ожидает проверки
|
Ожидает проверки
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-xs font-mono font-medium bg-rose-950/40 text-rose-400 border border-rose-800/50">
|
{% if assignment.deadline and now > assignment.deadline %}
|
||||||
<span class="w-1.5 h-1.5 rounded-full bg-rose-400"></span>
|
<span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-xs font-mono font-medium bg-obsidian-borderLight/20 text-txt-muted border border-obsidian-borderLight/50">
|
||||||
|
Дедлайн прошел
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-xs font-mono font-medium bg-status-roseBg text-status-rose border border-status-rose/30">
|
||||||
|
<span class="w-1.5 h-1.5 rounded-full bg-status-rose"></span>
|
||||||
Требует сдачи
|
Требует сдачи
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Assignment Title -->
|
<!-- Assignment Title -->
|
||||||
|
|||||||
+8
-5
@@ -68,9 +68,11 @@ def assignment_detail(request, pk):
|
|||||||
can_submit = True
|
can_submit = True
|
||||||
time_left_seconds = None
|
time_left_seconds = None
|
||||||
needs_start = False
|
needs_start = False
|
||||||
|
is_late = False
|
||||||
|
|
||||||
if assignment.deadline and now > assignment.deadline:
|
if assignment.deadline and now > assignment.deadline:
|
||||||
can_submit = False
|
is_late = True
|
||||||
|
# can_submit = False # Allow late submissions
|
||||||
|
|
||||||
if assignment.time_limit:
|
if assignment.time_limit:
|
||||||
if not submission:
|
if not submission:
|
||||||
@@ -85,11 +87,10 @@ def assignment_detail(request, pk):
|
|||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
# Allow saving this final POST, but mark it as expired
|
# Allow saving this final POST, but mark it as expired
|
||||||
submission.is_time_expired = True
|
submission.is_time_expired = True
|
||||||
# We don't set can_submit = False here so the POST is processed
|
|
||||||
else:
|
else:
|
||||||
can_submit = False
|
can_submit = False
|
||||||
submission.is_time_expired = True
|
submission.is_time_expired = True
|
||||||
submission.status = 'PENDING' # Or graded with 0
|
submission.status = 'PENDING'
|
||||||
submission.save()
|
submission.save()
|
||||||
else:
|
else:
|
||||||
time_left_seconds = int(allowed - elapsed)
|
time_left_seconds = int(allowed - elapsed)
|
||||||
@@ -104,7 +105,8 @@ def assignment_detail(request, pk):
|
|||||||
assignment=assignment,
|
assignment=assignment,
|
||||||
student=request.user,
|
student=request.user,
|
||||||
status='IN_PROGRESS',
|
status='IN_PROGRESS',
|
||||||
started_at=now
|
started_at=now,
|
||||||
|
is_late=is_late
|
||||||
)
|
)
|
||||||
return redirect('lms:assignment_detail', pk=pk)
|
return redirect('lms:assignment_detail', pk=pk)
|
||||||
|
|
||||||
@@ -115,7 +117,7 @@ def assignment_detail(request, pk):
|
|||||||
form = AnswersForm(request.POST, request.FILES, questions=questions)
|
form = AnswersForm(request.POST, request.FILES, questions=questions)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
if not submission:
|
if not submission:
|
||||||
submission = Submission.objects.create(assignment=assignment, student=request.user)
|
submission = Submission.objects.create(assignment=assignment, student=request.user, is_late=is_late)
|
||||||
|
|
||||||
all_auto = True
|
all_auto = True
|
||||||
total_points = 0
|
total_points = 0
|
||||||
@@ -211,4 +213,5 @@ def assignment_detail(request, pk):
|
|||||||
'can_submit': can_submit,
|
'can_submit': can_submit,
|
||||||
'needs_start': needs_start,
|
'needs_start': needs_start,
|
||||||
'time_left_seconds': time_left_seconds,
|
'time_left_seconds': time_left_seconds,
|
||||||
|
'is_late': is_late,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user