Add order and score for course problems (#124)

* Add order and grade for course problems

* Fix delete problems bug
This commit is contained in:
Phuoc Anh Kha Le 2024-09-03 21:26:20 +07:00 committed by GitHub
parent 67888bcd27
commit c833dc06d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 483 additions and 102 deletions

View file

@ -564,6 +564,11 @@ urlpatterns = [
course.CourseStudentResults.as_view(), course.CourseStudentResults.as_view(),
name="course_grades", name="course_grades",
), ),
url(
r"^/grades/lesson/(?P<id>\d+)$",
course.CourseStudentResultsLesson.as_view(),
name="course_grades_lesson",
),
] ]
), ),
), ),

View file

@ -0,0 +1,44 @@
# Generated by Django 3.2.21 on 2024-09-02 05:28
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("judge", "0191_deprecate_old_org_image"),
]
operations = [
migrations.CreateModel(
name="CourseLessonProblem",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("order", models.IntegerField(default=0, verbose_name="order")),
("score", models.IntegerField(default=0, verbose_name="score")),
(
"lesson",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="lesson_problems",
to="judge.courselesson",
),
),
(
"problem",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="judge.problem"
),
),
],
),
]

View file

@ -61,7 +61,7 @@ from judge.models.ticket import Ticket, TicketMessage
from judge.models.volunteer import VolunteerProblemVote from judge.models.volunteer import VolunteerProblemVote
from judge.models.pagevote import PageVote, PageVoteVoter from judge.models.pagevote import PageVote, PageVoteVoter
from judge.models.bookmark import BookMark, MakeBookMark from judge.models.bookmark import BookMark, MakeBookMark
from judge.models.course import Course, CourseRole, CourseLesson from judge.models.course import Course, CourseRole, CourseLesson, CourseLessonProblem
from judge.models.notification import Notification, NotificationProfile from judge.models.notification import Notification, NotificationProfile
from judge.models.test_formatter import TestFormatterModel from judge.models.test_formatter import TestFormatterModel

View file

@ -165,3 +165,12 @@ class CourseLesson(models.Model):
problems = models.ManyToManyField(Problem, verbose_name=_("problem"), blank=True) problems = models.ManyToManyField(Problem, verbose_name=_("problem"), blank=True)
order = models.IntegerField(verbose_name=_("order"), default=0) order = models.IntegerField(verbose_name=_("order"), default=0)
points = models.IntegerField(verbose_name=_("points")) points = models.IntegerField(verbose_name=_("points"))
class CourseLessonProblem(models.Model):
lesson = models.ForeignKey(
CourseLesson, on_delete=models.CASCADE, related_name="lesson_problems"
)
problem = models.ForeignKey(Problem, on_delete=models.CASCADE)
order = models.IntegerField(verbose_name=_("order"), default=0)
score = models.IntegerField(verbose_name=_("score"), default=0)

View file

@ -4,16 +4,32 @@ from django.views.generic import ListView, DetailView, View
from django.utils.translation import gettext, gettext_lazy as _ from django.utils.translation import gettext, gettext_lazy as _
from django.http import Http404 from django.http import Http404
from django import forms from django import forms
from django.forms import inlineformset_factory from django.forms import (
inlineformset_factory,
ModelForm,
modelformset_factory,
BaseModelFormSet,
)
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.db.models import Max, F from django.db.models import Max, F
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from judge.models import Course, CourseLesson, Submission, Profile, CourseRole from judge.models import (
Course,
CourseLesson,
Submission,
Profile,
CourseRole,
CourseLessonProblem,
)
from judge.models.course import RoleInCourse from judge.models.course import RoleInCourse
from judge.widgets import HeavyPreviewPageDownWidget, HeavySelect2MultipleWidget from judge.widgets import (
HeavyPreviewPageDownWidget,
HeavySelect2MultipleWidget,
HeavySelect2Widget,
)
from judge.utils.problems import ( from judge.utils.problems import (
user_attempted_ids, user_attempted_ids,
user_completed_ids, user_completed_ids,
@ -36,32 +52,35 @@ def max_case_points_per_problem(profile, problems):
def calculate_lessons_progress(profile, lessons): def calculate_lessons_progress(profile, lessons):
res = {} res = {}
total_achieved_points = 0 total_achieved_points = total_lesson_points = 0
total_points = 0
for lesson in lessons: for lesson in lessons:
problems = list(lesson.problems.all()) problems = lesson.lesson_problems.values_list("problem", flat=True)
if not problems:
res[lesson.id] = {"achieved_points": 0, "percentage": 0}
total_points += lesson.points
continue
problem_points = max_case_points_per_problem(profile, problems) problem_points = max_case_points_per_problem(profile, problems)
num_problems = len(problems) achieved_points = total_points = 0
percentage = 0
for val in problem_points.values(): for lesson_problem in lesson.lesson_problems.all():
if val["case_total"] > 0: val = problem_points.get(lesson_problem.problem.id)
score = val["case_points"] / val["case_total"] if val and val["case_total"]:
percentage += score / num_problems achieved_points += (
val["case_points"] / val["case_total"] * lesson_problem.score
)
total_points += lesson_problem.score
res[lesson.id] = { res[lesson.id] = {
"achieved_points": percentage * lesson.points, "achieved_points": achieved_points,
"percentage": percentage * 100, "total_points": total_points,
"percentage": achieved_points / total_points * 100 if total_points else 0,
} }
total_achieved_points += percentage * lesson.points if total_points:
total_points += lesson.points total_achieved_points += achieved_points / total_points * lesson.points
total_lesson_points += lesson.points
res["total"] = { res["total"] = {
"achieved_points": total_achieved_points, "achieved_points": total_achieved_points,
"total_points": total_points, "total_points": total_lesson_points,
"percentage": total_achieved_points / total_points * 100 if total_points else 0, "percentage": total_achieved_points / total_lesson_points * 100
if total_lesson_points
else 0,
} }
return res return res
@ -157,12 +176,13 @@ class CourseLessonDetail(CourseDetailMixin, DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(CourseLessonDetail, self).get_context_data(**kwargs) context = super(CourseLessonDetail, self).get_context_data(**kwargs)
profile = self.get_profile() profile = self.get_profile()
context["profile"] = profile
context["title"] = self.lesson.title context["title"] = self.lesson.title
context["lesson"] = self.lesson context["lesson"] = self.lesson
context["completed_problem_ids"] = user_completed_ids(profile) context["completed_problem_ids"] = user_completed_ids(profile)
context["attempted_problems"] = user_attempted_ids(profile) context["attempted_problems"] = user_attempted_ids(profile)
context["problem_points"] = max_case_points_per_problem( context["problem_points"] = max_case_points_per_problem(
profile, self.lesson.problems.all() profile, self.lesson.lesson_problems.values_list("problem", flat=True)
) )
return context return context
@ -183,20 +203,65 @@ CourseLessonFormSet = inlineformset_factory(
) )
class CourseLessonProblemForm(ModelForm):
class Meta:
model = CourseLessonProblem
fields = ["order", "problem", "score", "lesson"]
widgets = {
"problem": HeavySelect2Widget(
data_view="problem_select2", attrs={"style": "width: 100%"}
),
"lesson": forms.HiddenInput(),
}
CourseLessonProblemFormSet = modelformset_factory(
CourseLessonProblem, form=CourseLessonProblemForm, extra=5, can_delete=True
)
class EditCourseLessonsView(CourseEditableMixin, FormView): class EditCourseLessonsView(CourseEditableMixin, FormView):
template_name = "course/edit_lesson.html" template_name = "course/edit_lesson.html"
form_class = CourseLessonFormSet form_class = CourseLessonFormSet
def get_problem_formset(self, post=False, lesson=None):
formset = CourseLessonProblemFormSet(
data=self.request.POST if post else None,
prefix=f"problems_{lesson.id}" if lesson else "problems",
queryset=CourseLessonProblem.objects.filter(lesson=lesson).order_by(
"order"
),
)
if lesson:
for form in formset:
form.fields["lesson"].initial = lesson
return formset
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(EditCourseLessonsView, self).get_context_data(**kwargs) context = super(EditCourseLessonsView, self).get_context_data(**kwargs)
if self.request.method == "POST": if self.request.method == "POST":
context["formset"] = self.form_class( context["formset"] = self.form_class(
self.request.POST, self.request.FILES, instance=self.course self.request.POST, self.request.FILES, instance=self.course
) )
context["problem_formsets"] = {
lesson.instance.id: self.get_problem_formset(
post=True, lesson=lesson.instance
)
for lesson in context["formset"].forms
if lesson.instance.id
}
else: else:
context["formset"] = self.form_class( context["formset"] = self.form_class(
instance=self.course, queryset=self.course.lessons.order_by("order") instance=self.course, queryset=self.course.lessons.order_by("order")
) )
context["problem_formsets"] = {
lesson.instance.id: self.get_problem_formset(
post=False, lesson=lesson.instance
)
for lesson in context["formset"].forms
if lesson.instance.id
}
context["title"] = _("Edit lessons for %(course_name)s") % { context["title"] = _("Edit lessons for %(course_name)s") % {
"course_name": self.course.name "course_name": self.course.name
} }
@ -213,8 +278,22 @@ class EditCourseLessonsView(CourseEditableMixin, FormView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
formset = self.form_class(request.POST, instance=self.course) formset = self.form_class(request.POST, instance=self.course)
problem_formsets = [
self.get_problem_formset(post=True, lesson=lesson.instance)
for lesson in formset.forms
if lesson.instance.id
]
for pf in problem_formsets:
if not pf.is_valid():
return self.form_invalid(pf)
if formset.is_valid(): if formset.is_valid():
formset.save() formset.save()
for problem_formset in problem_formsets:
problem_formset.save()
for obj in problem_formset.deleted_objects:
if obj.pk is not None:
obj.delete()
return self.form_valid(formset) return self.form_valid(formset)
else: else:
return self.form_invalid(formset) return self.form_invalid(formset)
@ -239,7 +318,10 @@ class CourseStudentResults(CourseEditableMixin, DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(CourseStudentResults, self).get_context_data(**kwargs) context = super(CourseStudentResults, self).get_context_data(**kwargs)
context["title"] = mark_safe( context["title"] = _("Grades in %(course_name)s</a>") % {
"course_name": self.course.name,
}
context["content_title"] = mark_safe(
_("Grades in <a href='%(url)s'>%(course_name)s</a>") _("Grades in <a href='%(url)s'>%(course_name)s</a>")
% { % {
"course_name": self.course.name, "course_name": self.course.name,
@ -249,3 +331,61 @@ class CourseStudentResults(CourseEditableMixin, DetailView):
context["page_type"] = "grades" context["page_type"] = "grades"
context["grades"] = self.get_grades() context["grades"] = self.get_grades()
return context return context
class CourseStudentResultsLesson(CourseEditableMixin, DetailView):
model = CourseLesson
template_name = "course/grades_lesson.html"
def get_object(self):
try:
self.lesson = CourseLesson.objects.get(
course=self.course, id=self.kwargs["id"]
)
return self.lesson
except ObjectDoesNotExist:
raise Http404()
def get_lesson_grades(self):
students = self.course.get_students()
students.sort(key=lambda u: u.username.lower())
problems = self.lesson.lesson_problems.values_list("problem", flat=True)
lesson_problems = self.lesson.lesson_problems.all()
grades = {}
for s in students:
grades[s] = problem_points = max_case_points_per_problem(s, problems)
achieved_points = total_points = 0
for lesson_problem in lesson_problems:
val = problem_points.get(lesson_problem.problem.id)
if val and val["case_total"]:
achieved_points += (
val["case_points"] / val["case_total"] * lesson_problem.score
)
total_points += lesson_problem.score
grades[s]["total"] = {
"achieved_points": achieved_points,
"total_points": total_points,
"percentage": achieved_points / total_points * 100
if total_points
else 0,
}
return grades
def get_context_data(self, **kwargs):
context = super(CourseStudentResultsLesson, self).get_context_data(**kwargs)
context["lesson"] = self.lesson
context["title"] = _("Grades of %(lesson_name)s</a> in %(course_name)s</a>") % {
"course_name": self.course.name,
"lesson_name": self.lesson.title,
}
context["content_title"] = mark_safe(
_("Grades of %(lesson_name)s</a> in <a href='%(url)s'>%(course_name)s</a>")
% {
"course_name": self.course.name,
"lesson_name": self.lesson.title,
"url": self.course.get_absolute_url(),
}
)
context["page_type"] = "grades"
context["grades"] = self.get_lesson_grades()
return context

View file

@ -15,7 +15,11 @@
<div class="lesson-title"> <div class="lesson-title">
{{ lesson.title }} {{ lesson.title }}
<div class="lesson-points"> <div class="lesson-points">
{{progress['achieved_points'] | floatformat(1)}} / {{lesson.points}} {% if progress['total_points'] %}
{{(progress['achieved_points'] / progress['total_points'] * lesson.points) | floatformat(1)}} / {{lesson.points}}
{% else %}
0 / {{lesson.points}}
{% endif %}
</div> </div>
</div> </div>
<div class="progress-container"> <div class="progress-container">
@ -30,7 +34,6 @@
{% set achieved_points = total_progress['achieved_points'] %} {% set achieved_points = total_progress['achieved_points'] %}
{% set total_points = total_progress['total_points'] %} {% set total_points = total_progress['total_points'] %}
{% set percentage = total_progress['percentage'] %} {% set percentage = total_progress['percentage'] %}
{{_("Total achieved points")}}: {{_("Total achieved points")}}:
<span style="float: right; font-weight: normal;"> <span style="float: right; font-weight: normal;">
{{ achieved_points | floatformat(2) }} / {{ total_points }} ({{percentage|floatformat(1)}}%) {{ achieved_points | floatformat(2) }} / {{ total_points }} ({{percentage|floatformat(1)}}%)

View file

@ -1,67 +1,109 @@
{% extends "course/base.html" %} {% extends "course/base.html" %}
{% block two_col_media %} {% block two_col_media %}
{{ form.media.css }} {{ form.media.css }}
<style type="text/css"> <style type="text/css">
.field-order, .field-title, .field-points { .field-order, .field-title, .field-points {
display: inline-flex; display: inline-flex;
} }
.form-header { .form-header {
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
</style> </style>
{% endblock %} {% endblock %}
{% block js_media %} {% block js_media %}
{{ form.media.js }} {{ form.media.js }}
<script> <script>
$(function() { $(function() {
setTimeout(function() { setTimeout(function() {
if ('DjangoPagedown' in window) { if ('DjangoPagedown' in window) {
DjangoPagedown.init(); DjangoPagedown.init();
} }
}, 3000); }, 3000);
}); });
</script> </script>
{% endblock %} {% endblock %}
{% block middle_content %} {% block middle_content %}
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
{{ formset.management_form }} {{ formset.management_form }}
{% for form in formset %}
<h3 class="toggle {{'open' if form.errors else 'closed'}} form-header"><i class="fa fa-chevron-right fa-fw"></i> {% for lesson_form in formset %}
{% if form.title.value() %} {% set ns = namespace(problem_formset_has_error=false) %}
{{form.order.value()}}. {{form.title.value()}}
{% else %} {% if lesson_form.instance.id %}
+ {{_("Add new")}} {% set problem_formset = problem_formsets[lesson_form.instance.id] %}
{% endif %} {% for form in problem_formset %}
</h3> {% if form.errors %}
<div class="toggled" style="{{'display: none;' if not form.errors}} margin-bottom: 1em"> {% set ns.problem_formset_has_error = true %}
{{form.id}} {% break %}
{% if form.errors %} {% endif %}
<div class="alert alert-danger alert-dismissable"> {% endfor %}
<a href="#" class="close">x</a> {% endif %}
{{_("Please fix below errors")}} <h3 class="toggle {{'open' if lesson_form.errors or ns.problem_formset_has_error else 'closed'}} form-header">
</div> <i class="fa fa-chevron-right fa-fw"></i>
{% endif %} {% if lesson_form.title.value() %}
{% for field in form %} {{lesson_form.order.value()}}. {{lesson_form.title.value()}}
{% if not field.is_hidden %} {% else %}
<div style="margin-bottom: 1em;"> + {{_("Add new")}}
{{ field.errors }} {% endif %}
<label for="{{field.id_for_label }}"><b>{{ field.label }}{% if field.field.required %}<span class="red"> * </span>{% endif %}:</b> </label> </h3>
<div class="org-field-wrapper field-{{field.name}}" id="org-field-wrapper-{{field.html_name}}"> <div class="toggled" style="{{'display: none;' if not lesson_form.errors and not ns.problem_formset_has_error}} margin-bottom: 1em">
{{ field }} {{lesson_form.id}}
</div> {% if lesson_form.errors %}
{% if field.help_text %} <div class="alert alert-danger alert-dismissable">
<small class="org-help-text"><i class="fa fa-exclamation-circle"></i> {{ field.help_text|safe }}</small> <a href="#" class="close">x</a>
{% endif %} {{_("Please fix below errors")}}
</div> </div>
{% endif %} {% endif %}
{% endfor %} {% for field in lesson_form %}
<hr/> {% if not field.is_hidden %}
</div> <div style="margin-bottom: 1em;">
{% endfor %} {{ field.errors }}
<input type="submit" value="{{_('Save')}}" style="float: right"> <label for="{{field.id_for_label }}"><b>{{ field.label }}{% if field.field.required %}<span class="red"> * </span>{% endif %}:</b> </label>
</form> <div class="org-field-wrapper field-{{field.name}}" id="org-field-wrapper-{{field.html_name}}">
{{ field }}
</div>
{% if field.help_text %}
<small class="org-help-text"><i class="fa fa-exclamation-circle"></i> {{ field.help_text|safe }}</small>
{% endif %}
</div>
{% endif %}
{% endfor %}
<!-- Problems Table -->
{% if problem_formset %}
{{ problem_formset.management_form }}
<table class="table">
<thead>
<tr>
{% for field in problem_formset.forms.0 %}
{% if not field.is_hidden %}
<th class="problems-{{field.name}}">
{{field.label}}
</th>
{% endif %}
{% endfor %}
</tr>
</thead>
<tbody>
{% for form in problem_formset %}
<tr>
{% for field in form %}
<td class="problems-{{field.name}}" title="{{ field.help_text|safe if field.help_text }}" style="{{ 'display:none' if field.is_hidden }}">
{{field}}<div class="red">{{field.errors}}</div>
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
<hr/>
</div>
{% endfor %}
<input type="submit" value="{{_('Save')}}" style="float: right">
</form>
{% endblock %} {% endblock %}

View file

@ -74,8 +74,8 @@
{% endblock %} {% endblock %}
{% block middle_content %} {% block middle_content %}
<center><h2>{{title}}</h2></center> <center><h2>{{content_title}}</h2></center>
{% set lessons = course.lessons.all() %} {% set lessons = course.lessons.order_by('order') %}
{{_("Sort by")}}: {{_("Sort by")}}:
<select id="sortSelect"> <select id="sortSelect">
<option value="username">{{_("Username")}}</option> <option value="username">{{_("Username")}}</option>
@ -89,7 +89,7 @@
{% if grades|length > 0 %} {% if grades|length > 0 %}
{% for lesson in lessons %} {% for lesson in lessons %}
<th class="points"> <th class="points">
<a href="{{url('course_lesson_detail', course.slug, lesson.id)}}"> <a href="{{url('course_grades_lesson', course.slug, lesson.id)}}">
{{ lesson.title }} {{ lesson.title }}
<div class="point-denominator">{{lesson.points}}</div> <div class="point-denominator">{{lesson.points}}</div>
</a> </a>
@ -111,9 +111,14 @@
</div> </div>
</td> </td>
{% for lesson in lessons %} {% for lesson in lessons %}
{% set val = grade.get(lesson.id) %}
<td class="partial-score"> <td class="partial-score">
<a href="{{url('course_lesson_detail', course.slug, lesson.id)}}?user={{student.username}}"> <a href="{{url('course_lesson_detail', course.slug, lesson.id)}}?user={{student.username}}">
{{ grade[lesson.id]['percentage'] | floatformat(0) }}% {% if val and val['total_points'] %}
{{ (val['achieved_points'] / val['total_points'] * lesson.points) | floatformat(0) }}
{% else %}
0
{% endif %}
</a> </a>
</td> </td>
{% endfor %} {% endfor %}

View file

@ -0,0 +1,132 @@
{% extends "course/base.html" %}
{% block two_col_media %}
<style type="text/css">
table {
font-size: 15px;
}
td {
height: 2.5em;
}
.user-name {
padding-left: 1em !important;
}
#search-input {
float: right;
margin-bottom: 1em;
}
</style>
{% endblock %}
{% block js_media %}
<script>
$(document).ready(function(){
var $searchInput = $("#search-input");
var $usersTable = $("#users-table");
var tableBorderColor = $('#users-table td').css('border-bottom-color');
$searchInput.on("keyup", function() {
var value = $(this).val().toLowerCase();
$("#users-table tbody tr").filter(function() {
$(this).toggle($(this).text().toLowerCase().indexOf(value) > -1)
});
if(value) {
$('#users-table').css('border-bottom', '1px solid ' + tableBorderColor);
} else {
$('#users-table').css('border-bottom', '');
}
});
$('#sortSelect').select2({
minimumResultsForSearch: -1,
width: "10em",
});
$('#sortSelect').on('change', function() {
var rows = $('#users-table tbody tr').get();
var sortBy = $(this).val();
rows.sort(function(a, b) {
var keyA = $(a).find(sortBy === 'username' ? '.user-name' : 'td:last-child').text().trim();
var keyB = $(b).find(sortBy === 'username' ? '.user-name' : 'td:last-child').text().trim();
if(sortBy === 'total') {
// Convert percentage string to number for comparison
keyA = -parseFloat(keyA.replace('%', ''));
keyB = -parseFloat(keyB.replace('%', ''));
}
else {
keyA = keyA.toLowerCase();
keyB = keyB.toLowerCase();
}
if(keyA < keyB) return -1;
if(keyA > keyB) return 1;
return 0;
});
$.each(rows, function(index, row) {
$('#users-table tbody').append(row);
});
});
});
</script>
{% endblock %}
{% block middle_content %}
<center><h2>{{content_title}}</h2></center>
{% set lesson_problems = lesson.lesson_problems.order_by('order') %}
{{_("Sort by")}}:
<select id="sortSelect">
<option value="username">{{_("Username")}}</option>
<option value="total">{{_("Score")}}</option>
</select>
<input type="text" id="search-input" placeholder="{{_('Search')}}" autofocus>
<table class="table striped" id="users-table">
<thead>
<tr>
<th>{{_('Student')}}</th>
{% if grades|length > 0 %}
{% for lesson_problem in lesson_problems %}
<th class="points">
<a href="{{url('problem_detail', lesson_problem.problem.code)}}">
{{ lesson_problem.problem.name }}
<div class="point-denominator">{{lesson_problem.score}}</div>
</a>
</th>
{% endfor %}
{% endif %}
<th>{{_('Total')}}</th>
</tr>
</thead>
<tbody>
{% for student, grade in grades.items() %}
<tr>
<td class="user-name">
<div>
{{link_user(student)}}
</div>
<div>
{{student.first_name}}
</div>
</td>
{% for lesson_problem in lesson_problems %}
{% set val = grade.get(lesson_problem.problem.id) %}
<td class="partial-score">
<a href="{{url('user_submissions', lesson_problem.problem.code, student.username)}}">
{% if val and val['case_total'] %}
{{ (val['case_points'] / val['case_total'] * lesson_problem.score) | floatformat(0) }}
{% else %}
0
{% endif %}
</a>
</td>
{% endfor %}
<td style="font-weight: bold">
{{ grade['total']['percentage'] | floatformat(0) }}%
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View file

@ -6,14 +6,15 @@
{% endblock %} {% endblock %}
{% block middle_content %} {% block middle_content %}
<center><h2>{{title}}</h2></center> <center><h2>{{title}} - {{profile.username}}</h2></center>
<h3 class="course-content-title">{{_("Content")}}</h3> <h3 class="course-content-title">{{_("Content")}}</h3>
<div> <div>
{{ lesson.content|markdown|reference|str|safe }} {{ lesson.content|markdown|reference|str|safe }}
</div> </div>
<h3 class="course-content-title">{{_("Problems")}}</h3> <h3 class="course-content-title">{{_("Problems")}}</h3>
<ul class="course-problem-list"> <ul class="course-problem-list">
{% for problem in lesson.problems.all() %} {% for lesson_problem in lesson.lesson_problems.order_by('order') %}
{% set problem = lesson_problem.problem %}
<a href="{{url('problem_detail', problem.code)}}"> <a href="{{url('problem_detail', problem.code)}}">
<li> <li>
{% if problem.id in completed_problem_ids %} {% if problem.id in completed_problem_ids %}
@ -26,10 +27,10 @@
<span class="problem-name">{{problem.name}}</span> <span class="problem-name">{{problem.name}}</span>
{% set pp = problem_points[problem.id] %} {% set pp = problem_points[problem.id] %}
<span class="score"> <span class="score">
{% if pp %} {% if pp and pp.case_total %}
{{pp.case_points|floatformat(1)}} / {{pp.case_total|floatformat(0)}} {{(pp.case_points / pp.case_total * lesson_problem.score) |floatformat(1)}} / {{lesson_problem.score|floatformat(0)}}
{% else %} {% else %}
0 0 / {{lesson_problem.score|floatformat(0)}}
{% endif %} {% endif %}
</span> </span>
</li> </li>