Skip to content

Commit

Permalink
Add contest to course (#126)
Browse files Browse the repository at this point in the history
  • Loading branch information
anhkha2003 authored Oct 2, 2024
1 parent 72eada0 commit 3d67fb2
Show file tree
Hide file tree
Showing 22 changed files with 1,257 additions and 432 deletions.
15 changes: 15 additions & 0 deletions dmoj/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,21 @@ def paged_list_view(view, name, **kwargs):
course.CourseStudentResultsLesson.as_view(),
name="course_grades_lesson",
),
url(
r"^/add_contest$",
course.AddCourseContest.as_view(),
name="add_course_contest",
),
url(
r"^/edit_contest/(?P<contest>\w+)$",
course.EditCourseContest.as_view(),
name="edit_course_contest",
),
url(
r"^/contests$",
course.CourseContestList.as_view(),
name="course_contest_list",
),
]
),
),
Expand Down
67 changes: 67 additions & 0 deletions judge/migrations/0194_course_contest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Generated by Django 3.2.21 on 2024-09-30 22:31

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
("judge", "0193_remove_old_course_problems"),
]

operations = [
migrations.AddField(
model_name="contest",
name="is_in_course",
field=models.BooleanField(default=False, verbose_name="contest in course"),
),
migrations.AddField(
model_name="courselesson",
name="is_visible",
field=models.BooleanField(default=True, verbose_name="publicly visible"),
),
migrations.AlterField(
model_name="courselesson",
name="content",
field=models.TextField(verbose_name="lesson content"),
),
migrations.AlterField(
model_name="courselesson",
name="title",
field=models.TextField(verbose_name="lesson title"),
),
migrations.CreateModel(
name="CourseContest",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("order", models.IntegerField(default=0, verbose_name="order")),
("points", models.IntegerField(verbose_name="points")),
(
"contest",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="course",
to="judge.contest",
unique=True,
),
),
(
"course",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="contests",
to="judge.course",
),
),
],
),
]
8 changes: 7 additions & 1 deletion judge/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,13 @@
from judge.models.volunteer import VolunteerProblemVote
from judge.models.pagevote import PageVote, PageVoteVoter
from judge.models.bookmark import BookMark, MakeBookMark
from judge.models.course import Course, CourseRole, CourseLesson, CourseLessonProblem
from judge.models.course import (
Course,
CourseRole,
CourseLesson,
CourseLessonProblem,
CourseContest,
)
from judge.models.notification import Notification, NotificationProfile
from judge.models.test_formatter import TestFormatterModel

Expand Down
19 changes: 17 additions & 2 deletions judge/models/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,10 @@ class Contest(models.Model, PageVotable, Bookmarkable):
verbose_name=_("organizations"),
help_text=_("If private, only these organizations may see the contest"),
)
is_in_course = models.BooleanField(
verbose_name=_("contest in course"),
default=False,
)
og_image = models.CharField(
verbose_name=_("OpenGraph image"), default="", max_length=150, blank=True
)
Expand Down Expand Up @@ -561,6 +565,14 @@ def access_check(self, user):
if not self.is_visible:
raise self.Inaccessible()

if self.is_in_course:
from judge.models import Course, CourseContest

course_contest = CourseContest.objects.filter(contest=self).first()
if Course.is_accessible_by(course_contest.course, user.profile):
return
raise self.Inaccessible()

# Contest is not private
if not self.is_private and not self.is_organization_private:
return
Expand Down Expand Up @@ -612,7 +624,10 @@ def get_visible_contests(cls, user, show_own_contests_only=False):
if not user.is_authenticated:
return (
cls.objects.filter(
is_visible=True, is_organization_private=False, is_private=False
is_visible=True,
is_organization_private=False,
is_private=False,
is_in_course=False,
)
.defer("description")
.distinct()
Expand All @@ -626,7 +641,7 @@ def get_visible_contests(cls, user, show_own_contests_only=False):
)
or show_own_contests_only
):
q = Q(is_visible=True)
q = Q(is_visible=True, is_in_course=False)
q &= (
Q(view_contest_scoreboard=user.profile)
| Q(is_organization_private=False, is_private=False)
Expand Down
23 changes: 20 additions & 3 deletions judge/models/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from django.urls import reverse
from django.db.models import Q

from judge.models import BlogPost, Problem
from judge.models import BlogPost, Problem, Contest
from judge.models.profile import Organization, Profile


Expand Down Expand Up @@ -160,10 +160,11 @@ class CourseLesson(models.Model):
related_name="lessons",
on_delete=models.CASCADE,
)
title = models.TextField(verbose_name=_("course title"))
content = models.TextField(verbose_name=_("course content"))
title = models.TextField(verbose_name=_("lesson title"))
content = models.TextField(verbose_name=_("lesson content"))
order = models.IntegerField(verbose_name=_("order"), default=0)
points = models.IntegerField(verbose_name=_("points"))
is_visible = models.BooleanField(verbose_name=_("publicly visible"), default=True)

def get_absolute_url(self):
return reverse(
Expand All @@ -182,3 +183,19 @@ class CourseLessonProblem(models.Model):
problem = models.ForeignKey(Problem, on_delete=models.CASCADE)
order = models.IntegerField(verbose_name=_("order"), default=0)
score = models.IntegerField(verbose_name=_("score"), default=0)


class CourseContest(models.Model):
course = models.ForeignKey(
Course, on_delete=models.CASCADE, related_name="contests"
)
contest = models.ForeignKey(
Contest, unique=True, on_delete=models.CASCADE, related_name="course"
)
order = models.IntegerField(verbose_name=_("order"), default=0)
points = models.IntegerField(verbose_name=_("points"))

def get_course_of_contest(contest):
course_contest = contest.course.get()
course = course_contest.course
return course
32 changes: 32 additions & 0 deletions judge/utils/contest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from django.db import transaction
from judge.tasks import rescore_contest
from judge.models import (
Contest,
)


def maybe_trigger_contest_rescore(form, contest):
if any(
f in form.changed_data
for f in (
"start_time",
"end_time",
"time_limit",
"format_config",
"format_name",
"freeze_after",
)
):
transaction.on_commit(rescore_contest.s(contest.key).delay)

if any(
f in form.changed_data
for f in (
"authors",
"curators",
"testers",
)
):
Contest._author_ids.dirty(contest)
Contest._curator_ids.dirty(contest)
Contest._tester_ids.dirty(contest)
8 changes: 8 additions & 0 deletions judge/views/contests.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,14 @@ def get_context_data(self, **kwargs):
)
context["editable_organizations"] = self.get_editable_organizations()
context["is_clonable"] = is_contest_clonable(self.request, self.object)

if self.object.is_in_course:
from judge.models import Course, CourseContest

course = CourseContest.get_course_of_contest(self.object)
if Course.is_editable_by(course, self.request.profile):
context["editable_course"] = course

if self.request.in_contest:
context["current_contest"] = self.request.participation.contest
else:
Expand Down
Loading

0 comments on commit 3d67fb2

Please sign in to comment.