diff --git a/dmoj/urls.py b/dmoj/urls.py index 677ea59..0c8855e 100644 --- a/dmoj/urls.py +++ b/dmoj/urls.py @@ -487,7 +487,6 @@ urlpatterns = [ ), ), url(r"^contests/", paged_list_view(contests.ContestList, "contest_list")), - url(r"^course/", paged_list_view(course.CourseList, "course_list" )), url( r"^contests/(?P\d+)/(?P\d+)/$", contests.ContestCalendar.as_view(), @@ -592,6 +591,24 @@ urlpatterns = [ ] ), ), + url(r"^courses/", paged_list_view(course.CourseList, "course_list" )), + url( + r"^courses/(?P[\w-]*)", + include( + [ + url( + r"^$", + course.CourseDetail.as_view(), + name="course_detail" + ), + url( + r"^/grades$", + course.CourseStudentResults.as_view(), + name="grades" + ), + ] + ) + ), url( r"^organizations/$", organization.OrganizationList.as_view(), diff --git a/judge/admin/__init__.py b/judge/admin/__init__.py index 5150d9f..7993af9 100644 --- a/judge/admin/__init__.py +++ b/judge/admin/__init__.py @@ -40,6 +40,7 @@ from judge.models import ( Ticket, VolunteerProblemVote, Course, + course, ) @@ -65,4 +66,5 @@ admin.site.register(Profile, ProfileAdmin) admin.site.register(Submission, SubmissionAdmin) admin.site.register(Ticket, TicketAdmin) admin.site.register(VolunteerProblemVote, VolunteerProblemVoteAdmin) -admin.site.register(Course) +admin.site.register(course.Course) +admin.site.register(course.CourseAssignment) diff --git a/judge/migrations/0151_alter_courseassignment_course.py b/judge/migrations/0151_alter_courseassignment_course.py new file mode 100644 index 0000000..aa5157d --- /dev/null +++ b/judge/migrations/0151_alter_courseassignment_course.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.17 on 2023-02-15 04:28 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('judge', '0150_alter_profile_timezone'), + ] + + operations = [ + migrations.AlterField( + model_name='courseassignment', + name='course', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.course', verbose_name='course'), + ), + ] diff --git a/judge/migrations/0152_alter_courserole_course.py b/judge/migrations/0152_alter_courserole_course.py new file mode 100644 index 0000000..6fa1de8 --- /dev/null +++ b/judge/migrations/0152_alter_courserole_course.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.17 on 2023-02-22 08:08 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('judge', '0151_alter_courseassignment_course'), + ] + + operations = [ + migrations.AlterField( + model_name='courserole', + name='course', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.course', verbose_name='course'), + ), + ] diff --git a/judge/models/contest.py b/judge/models/contest.py index 994e3ce..3d3cc6c 100644 --- a/judge/models/contest.py +++ b/judge/models/contest.py @@ -449,6 +449,13 @@ class Contest(models.Model): "profile_id", flat=True ) + @cached_property + def total_points(self): + total = 0 + for problem in self.problems.all(): + total += problem.points + return total + def __str__(self): return self.name @@ -543,7 +550,7 @@ class Contest(models.Model): return True return False - + @classmethod def get_visible_contests(cls, user, show_own_contests_only=False): if not user.is_authenticated: diff --git a/judge/models/course.py b/judge/models/course.py index 457e0ea..47ca054 100644 --- a/judge/models/course.py +++ b/judge/models/course.py @@ -77,7 +77,7 @@ class Course(models.Model): @classmethod def get_students(cls,course): - return CourseRole.objects.filter(course=course, role="ST").values("user") + return CourseRole.objects.filter(course=course, role="ST") @classmethod def get_assistants(cls,course): @@ -104,7 +104,7 @@ class Course(models.Model): class CourseRole(models.Model): - course = models.OneToOneField( + course = models.ForeignKey( Course, verbose_name=_("course"), on_delete=models.CASCADE, @@ -167,7 +167,7 @@ class CourseResource(models.Model): class CourseAssignment(models.Model): - course = models.OneToOneField( + course = models.ForeignKey( Course, verbose_name=_("course"), on_delete=models.CASCADE, diff --git a/judge/views/course.py b/judge/views/course.py index f24155b..2748370 100644 --- a/judge/views/course.py +++ b/judge/views/course.py @@ -1,6 +1,8 @@ from django.db import models -from judge.models.course import Course -from django.views.generic import ListView +from judge.models.course import Course , CourseAssignment , CourseRole +from judge.models.contest import ContestParticipation, Contest +from django.views.generic import ListView, DetailView +from django.utils import timezone __all__ = [ "CourseList", @@ -10,22 +12,19 @@ __all__ = [ "CourseStudentResults", "CourseEdit", "CourseResourceDetailEdit", - "CourseResourceEdit", + "CourseResourceEdit", ] course_directory_file = "" -class CourseListMixin(object): - def get_queryset(self): - return Course.objects.filter(is_open = "true").values() - class CourseList(ListView): model = Course template_name = "course/list.html" queryset = Course.objects.filter(is_public=True).filter(is_open=True) + def get_context_data(self, **kwargs): - context = super(CourseList,self).get_context_data(**kwargs) - available , enrolling = [] , [] + context = super(CourseList, self).get_context_data(**kwargs) + available, enrolling = [], [] for course in Course.objects.filter(is_public=True).filter(is_open=True): if Course.is_accessible_by(course, self.request.profile): enrolling.append(course) @@ -34,4 +33,61 @@ class CourseList(ListView): context["available"] = available context["enrolling"] = enrolling return context + +class CourseDetail(ListView): + model = CourseAssignment + template_name = "course/course.html" + + def get_queryset(self): + cur_course = Course.objects.get(slug=self.kwargs['slug']) + return CourseAssignment.objects.filter(course=cur_course) + + def get_context_data(self, **kwargs): + context = super(CourseDetail, self).get_context_data(**kwargs) + context['slug'] = self.kwargs['slug'] + return context + +def best_score_user_contest(user , contest): + participated_contests = ContestParticipation.objects.filter(user=user, contest=contest) + progress_point = 0 + for cur_contest in participated_contests: + progress_point = max( progress_point , cur_contest.score_final ) + return progress_point + +def progress_contest(user , contest): + return best_score_user_contest(user, contest) / contest.total_points + +def progress_course(user , course): + assignments = CourseAssignment.objects.filter(course=course) + assignments_total_point = 0 + for assignment in assignments: + assignments_total_point += assignment.points + progress = 0 + for assignment in assignments: + progress += progress_contest(user,assignment.contest) * assignment.points / assignments_total_point + return progress +class CourseStudentResults(ListView): + model = ContestParticipation + template_name = "course/grades.html" + + def get_queryset(self): + cur_course = Course.objects.get(slug=self.kwargs['slug']) + contests = CourseAssignment.objects.filter(course=cur_course) + students = Course.get_students(cur_course) + grades_table = [] + for student in students: + grades_student = [student.user.user.username] + for contest in contests: + grades_student.append( round(progress_contest(student.user, contest.contest) * 100 ) ) + grades_student.append( round(progress_course(student.user, cur_course) * 100) ) + grades_table.append( grades_student ) + return grades_table + + def get_context_data(self, **kwargs): + context = super(CourseStudentResults, self).get_context_data(**kwargs) + cur_course = Course.objects.get(slug=self.kwargs['slug']) + contests = CourseAssignment.objects.filter(course=cur_course) + context['contests'] = contests + return context + diff --git a/templates/course/course.html b/templates/course/course.html new file mode 100644 index 0000000..5a3a6db --- /dev/null +++ b/templates/course/course.html @@ -0,0 +1,15 @@ + + + + + + + Document + + + {% for course in object_list %} +

{{ course.contest }} - {{ course.contest.start_time }} - {{ course.contest.end_time }}

+ {% endfor %} + View grades + + \ No newline at end of file diff --git a/templates/course/grades.html b/templates/course/grades.html new file mode 100644 index 0000000..7b779f9 --- /dev/null +++ b/templates/course/grades.html @@ -0,0 +1,53 @@ + + + + + + + Document + + + + +

Grades

+ + + + + + {% for contest in contests %} + + {% endfor %} + + + {% for student in object_list %} + + {% for data in student %} + + {% endfor %} + + {% endfor %} +
Name{{ contest }}(%)Progress
{{ data }}
+ + + \ No newline at end of file diff --git a/templates/course/list.html b/templates/course/list.html index 5337724..1b0c493 100644 --- a/templates/course/list.html +++ b/templates/course/list.html @@ -9,11 +9,11 @@

Enrolling

{% for course in enrolling %} -

{{ course }}

+

{{ course }}

{% endfor %}

Available

{% for course in available %} -

{{ course }}

+

{{ course }}

{% endfor %} \ No newline at end of file