Reformat using black

This commit is contained in:
cuom1999 2022-05-14 12:57:27 -05:00
parent efee4ad081
commit a87fb49918
221 changed files with 19127 additions and 7310 deletions

View file

@ -8,7 +8,13 @@ from django.utils.functional import cached_property
from django.utils.translation import gettext, gettext_lazy as _
from jsonfield import JSONField
from lupa import LuaRuntime
from moss import MOSS_LANG_C, MOSS_LANG_CC, MOSS_LANG_JAVA, MOSS_LANG_PYTHON, MOSS_LANG_PASCAL
from moss import (
MOSS_LANG_C,
MOSS_LANG_CC,
MOSS_LANG_JAVA,
MOSS_LANG_PYTHON,
MOSS_LANG_PASCAL,
)
from judge import contest_format
from judge.models.problem import Problem
@ -16,22 +22,39 @@ from judge.models.profile import Organization, Profile
from judge.models.submission import Submission
from judge.ratings import rate_contest
__all__ = ['Contest', 'ContestTag', 'ContestParticipation', 'ContestProblem', 'ContestSubmission', 'Rating']
__all__ = [
"Contest",
"ContestTag",
"ContestParticipation",
"ContestProblem",
"ContestSubmission",
"Rating",
]
class ContestTag(models.Model):
color_validator = RegexValidator('^#(?:[A-Fa-f0-9]{3}){1,2}$', _('Invalid colour.'))
color_validator = RegexValidator("^#(?:[A-Fa-f0-9]{3}){1,2}$", _("Invalid colour."))
name = models.CharField(max_length=20, verbose_name=_('tag name'), unique=True,
validators=[RegexValidator(r'^[a-z-]+$', message=_('Lowercase letters and hyphens only.'))])
color = models.CharField(max_length=7, verbose_name=_('tag colour'), validators=[color_validator])
description = models.TextField(verbose_name=_('tag description'), blank=True)
name = models.CharField(
max_length=20,
verbose_name=_("tag name"),
unique=True,
validators=[
RegexValidator(
r"^[a-z-]+$", message=_("Lowercase letters and hyphens only.")
)
],
)
color = models.CharField(
max_length=7, verbose_name=_("tag colour"), validators=[color_validator]
)
description = models.TextField(verbose_name=_("tag description"), blank=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('contest_tag', args=[self.name])
return reverse("contest_tag", args=[self.name])
@property
def text_color(self, cache={}):
@ -40,105 +63,232 @@ class ContestTag(models.Model):
r, g, b = [ord(bytes.fromhex(i * 2)) for i in self.color[1:]]
else:
r, g, b = [i for i in bytes.fromhex(self.color[1:])]
cache[self.color] = '#000' if 299 * r + 587 * g + 144 * b > 140000 else '#fff'
cache[self.color] = (
"#000" if 299 * r + 587 * g + 144 * b > 140000 else "#fff"
)
return cache[self.color]
class Meta:
verbose_name = _('contest tag')
verbose_name_plural = _('contest tags')
verbose_name = _("contest tag")
verbose_name_plural = _("contest tags")
class Contest(models.Model):
SCOREBOARD_VISIBLE = 'V'
SCOREBOARD_AFTER_CONTEST = 'C'
SCOREBOARD_AFTER_PARTICIPATION = 'P'
SCOREBOARD_VISIBLE = "V"
SCOREBOARD_AFTER_CONTEST = "C"
SCOREBOARD_AFTER_PARTICIPATION = "P"
SCOREBOARD_VISIBILITY = (
(SCOREBOARD_VISIBLE, _('Visible')),
(SCOREBOARD_AFTER_CONTEST, _('Hidden for duration of contest')),
(SCOREBOARD_AFTER_PARTICIPATION, _('Hidden for duration of participation')),
(SCOREBOARD_VISIBLE, _("Visible")),
(SCOREBOARD_AFTER_CONTEST, _("Hidden for duration of contest")),
(SCOREBOARD_AFTER_PARTICIPATION, _("Hidden for duration of participation")),
)
key = models.CharField(max_length=20, verbose_name=_('contest id'), unique=True,
validators=[RegexValidator('^[a-z0-9]+$', _('Contest id must be ^[a-z0-9]+$'))])
name = models.CharField(max_length=100, verbose_name=_('contest name'), db_index=True)
authors = models.ManyToManyField(Profile, help_text=_('These users will be able to edit the contest.'),
related_name='authors+')
curators = models.ManyToManyField(Profile, help_text=_('These users will be able to edit the contest, '
'but will not be listed as authors.'),
related_name='curators+', blank=True)
testers = models.ManyToManyField(Profile, help_text=_('These users will be able to view the contest, '
'but not edit it.'),
blank=True, related_name='testers+')
description = models.TextField(verbose_name=_('description'), blank=True)
problems = models.ManyToManyField(Problem, verbose_name=_('problems'), through='ContestProblem')
start_time = models.DateTimeField(verbose_name=_('start time'), db_index=True)
end_time = models.DateTimeField(verbose_name=_('end time'), db_index=True)
time_limit = models.DurationField(verbose_name=_('time limit'), blank=True, null=True,
help_text=_('Format hh:mm:ss. For example, if you want a 2-hour contest, enter 02:00:00'))
is_visible = models.BooleanField(verbose_name=_('publicly visible'), default=False,
help_text=_('Should be set even for organization-private contests, where it '
'determines whether the contest is visible to members of the '
'specified organizations.'))
is_rated = models.BooleanField(verbose_name=_('contest rated'), help_text=_('Whether this contest can be rated.'),
default=False)
scoreboard_visibility = models.CharField(verbose_name=_('scoreboard visibility'), default=SCOREBOARD_VISIBLE,
max_length=1, help_text=_('Scoreboard visibility through the duration '
'of the contest'), choices=SCOREBOARD_VISIBILITY)
view_contest_scoreboard = models.ManyToManyField(Profile, verbose_name=_('view contest scoreboard'), blank=True,
related_name='view_contest_scoreboard',
help_text=_('These users will be able to view the scoreboard.'))
use_clarifications = models.BooleanField(verbose_name=_('no comments'),
help_text=_("Use clarification system instead of comments."),
default=True)
rating_floor = models.IntegerField(verbose_name=('rating floor'), help_text=_('Rating floor for contest'),
null=True, blank=True)
rating_ceiling = models.IntegerField(verbose_name=('rating ceiling'), help_text=_('Rating ceiling for contest'),
null=True, blank=True)
rate_all = models.BooleanField(verbose_name=_('rate all'), help_text=_('Rate all users who joined.'), default=False)
rate_exclude = models.ManyToManyField(Profile, verbose_name=_('exclude from ratings'), blank=True,
related_name='rate_exclude+')
is_private = models.BooleanField(verbose_name=_('private to specific users'), default=False)
private_contestants = models.ManyToManyField(Profile, blank=True, verbose_name=_('private contestants'),
help_text=_('If private, only these users may see the contest'),
related_name='private_contestants+')
hide_problem_tags = models.BooleanField(verbose_name=_('hide problem tags'),
help_text=_('Whether problem tags should be hidden by default.'),
default=True)
run_pretests_only = models.BooleanField(verbose_name=_('run pretests only'),
help_text=_('Whether judges should grade pretests only, versus all '
'testcases. Commonly set during a contest, then unset '
'prior to rejudging user submissions when the contest ends.'),
default=False)
is_organization_private = models.BooleanField(verbose_name=_('private to organizations'), default=False)
organizations = models.ManyToManyField(Organization, blank=True, verbose_name=_('organizations'),
help_text=_('If private, only these organizations may see the contest'))
og_image = models.CharField(verbose_name=_('OpenGraph image'), default='', max_length=150, blank=True)
logo_override_image = models.CharField(verbose_name=_('Logo override image'), default='', max_length=150,
blank=True,
help_text=_('This image will replace the default site logo for users '
'inside the contest.'))
tags = models.ManyToManyField(ContestTag, verbose_name=_('contest tags'), blank=True, related_name='contests')
user_count = models.IntegerField(verbose_name=_('the amount of live participants'), default=0)
summary = models.TextField(blank=True, verbose_name=_('contest summary'),
help_text=_('Plain-text, shown in meta description tag, e.g. for social media.'))
access_code = models.CharField(verbose_name=_('access code'), blank=True, default='', max_length=255,
help_text=_('An optional code to prompt contestants before they are allowed '
'to join the contest. Leave it blank to disable.'))
banned_users = models.ManyToManyField(Profile, verbose_name=_('personae non gratae'), blank=True,
help_text=_('Bans the selected users from joining this contest.'))
format_name = models.CharField(verbose_name=_('contest format'), default='default', max_length=32,
choices=contest_format.choices(), help_text=_('The contest format module to use.'))
format_config = JSONField(verbose_name=_('contest format configuration'), null=True, blank=True,
help_text=_('A JSON object to serve as the configuration for the chosen contest format '
'module. Leave empty to use None. Exact format depends on the contest format '
'selected.'))
problem_label_script = models.TextField(verbose_name='contest problem label script', blank=True,
help_text='A custom Lua function to generate problem labels. Requires a '
'single function with an integer parameter, the zero-indexed '
'contest problem index, and returns a string, the label.')
points_precision = models.IntegerField(verbose_name=_('precision points'), default=2,
validators=[MinValueValidator(0), MaxValueValidator(10)],
help_text=_('Number of digits to round points to.'))
key = models.CharField(
max_length=20,
verbose_name=_("contest id"),
unique=True,
validators=[RegexValidator("^[a-z0-9]+$", _("Contest id must be ^[a-z0-9]+$"))],
)
name = models.CharField(
max_length=100, verbose_name=_("contest name"), db_index=True
)
authors = models.ManyToManyField(
Profile,
help_text=_("These users will be able to edit the contest."),
related_name="authors+",
)
curators = models.ManyToManyField(
Profile,
help_text=_(
"These users will be able to edit the contest, "
"but will not be listed as authors."
),
related_name="curators+",
blank=True,
)
testers = models.ManyToManyField(
Profile,
help_text=_(
"These users will be able to view the contest, " "but not edit it."
),
blank=True,
related_name="testers+",
)
description = models.TextField(verbose_name=_("description"), blank=True)
problems = models.ManyToManyField(
Problem, verbose_name=_("problems"), through="ContestProblem"
)
start_time = models.DateTimeField(verbose_name=_("start time"), db_index=True)
end_time = models.DateTimeField(verbose_name=_("end time"), db_index=True)
time_limit = models.DurationField(
verbose_name=_("time limit"),
blank=True,
null=True,
help_text=_(
"Format hh:mm:ss. For example, if you want a 2-hour contest, enter 02:00:00"
),
)
is_visible = models.BooleanField(
verbose_name=_("publicly visible"),
default=False,
help_text=_(
"Should be set even for organization-private contests, where it "
"determines whether the contest is visible to members of the "
"specified organizations."
),
)
is_rated = models.BooleanField(
verbose_name=_("contest rated"),
help_text=_("Whether this contest can be rated."),
default=False,
)
scoreboard_visibility = models.CharField(
verbose_name=_("scoreboard visibility"),
default=SCOREBOARD_VISIBLE,
max_length=1,
help_text=_("Scoreboard visibility through the duration " "of the contest"),
choices=SCOREBOARD_VISIBILITY,
)
view_contest_scoreboard = models.ManyToManyField(
Profile,
verbose_name=_("view contest scoreboard"),
blank=True,
related_name="view_contest_scoreboard",
help_text=_("These users will be able to view the scoreboard."),
)
use_clarifications = models.BooleanField(
verbose_name=_("no comments"),
help_text=_("Use clarification system instead of comments."),
default=True,
)
rating_floor = models.IntegerField(
verbose_name=("rating floor"),
help_text=_("Rating floor for contest"),
null=True,
blank=True,
)
rating_ceiling = models.IntegerField(
verbose_name=("rating ceiling"),
help_text=_("Rating ceiling for contest"),
null=True,
blank=True,
)
rate_all = models.BooleanField(
verbose_name=_("rate all"),
help_text=_("Rate all users who joined."),
default=False,
)
rate_exclude = models.ManyToManyField(
Profile,
verbose_name=_("exclude from ratings"),
blank=True,
related_name="rate_exclude+",
)
is_private = models.BooleanField(
verbose_name=_("private to specific users"), default=False
)
private_contestants = models.ManyToManyField(
Profile,
blank=True,
verbose_name=_("private contestants"),
help_text=_("If private, only these users may see the contest"),
related_name="private_contestants+",
)
hide_problem_tags = models.BooleanField(
verbose_name=_("hide problem tags"),
help_text=_("Whether problem tags should be hidden by default."),
default=True,
)
run_pretests_only = models.BooleanField(
verbose_name=_("run pretests only"),
help_text=_(
"Whether judges should grade pretests only, versus all "
"testcases. Commonly set during a contest, then unset "
"prior to rejudging user submissions when the contest ends."
),
default=False,
)
is_organization_private = models.BooleanField(
verbose_name=_("private to organizations"), default=False
)
organizations = models.ManyToManyField(
Organization,
blank=True,
verbose_name=_("organizations"),
help_text=_("If private, only these organizations may see the contest"),
)
og_image = models.CharField(
verbose_name=_("OpenGraph image"), default="", max_length=150, blank=True
)
logo_override_image = models.CharField(
verbose_name=_("Logo override image"),
default="",
max_length=150,
blank=True,
help_text=_(
"This image will replace the default site logo for users "
"inside the contest."
),
)
tags = models.ManyToManyField(
ContestTag, verbose_name=_("contest tags"), blank=True, related_name="contests"
)
user_count = models.IntegerField(
verbose_name=_("the amount of live participants"), default=0
)
summary = models.TextField(
blank=True,
verbose_name=_("contest summary"),
help_text=_(
"Plain-text, shown in meta description tag, e.g. for social media."
),
)
access_code = models.CharField(
verbose_name=_("access code"),
blank=True,
default="",
max_length=255,
help_text=_(
"An optional code to prompt contestants before they are allowed "
"to join the contest. Leave it blank to disable."
),
)
banned_users = models.ManyToManyField(
Profile,
verbose_name=_("personae non gratae"),
blank=True,
help_text=_("Bans the selected users from joining this contest."),
)
format_name = models.CharField(
verbose_name=_("contest format"),
default="default",
max_length=32,
choices=contest_format.choices(),
help_text=_("The contest format module to use."),
)
format_config = JSONField(
verbose_name=_("contest format configuration"),
null=True,
blank=True,
help_text=_(
"A JSON object to serve as the configuration for the chosen contest format "
"module. Leave empty to use None. Exact format depends on the contest format "
"selected."
),
)
problem_label_script = models.TextField(
verbose_name="contest problem label script",
blank=True,
help_text="A custom Lua function to generate problem labels. Requires a "
"single function with an integer parameter, the zero-indexed "
"contest problem index, and returns a string, the label.",
)
points_precision = models.IntegerField(
verbose_name=_("precision points"),
default=2,
validators=[MinValueValidator(0), MaxValueValidator(10)],
help_text=_("Number of digits to round points to."),
)
@cached_property
def format_class(self):
return contest_format.formats[self.format_name]
@ -151,13 +301,20 @@ class Contest(models.Model):
def get_label_for_problem(self):
def DENY_ALL(obj, attr_name, is_setting):
raise AttributeError()
lua = LuaRuntime(attribute_filter=DENY_ALL, register_eval=False, register_builtins=False)
return lua.eval(self.problem_label_script or self.format.get_contest_problem_label_script())
lua = LuaRuntime(
attribute_filter=DENY_ALL, register_eval=False, register_builtins=False
)
return lua.eval(
self.problem_label_script or self.format.get_contest_problem_label_script()
)
def clean(self):
# Django will complain if you didn't fill in start_time or end_time, so we don't have to.
if self.start_time and self.end_time and self.start_time >= self.end_time:
raise ValidationError('What is this? A contest that ended before it starts?')
raise ValidationError(
"What is this? A contest that ended before it starts?"
)
self.format_class.validate(self.format_config)
try:
@ -165,15 +322,21 @@ class Contest(models.Model):
# so test it to see if the script returns a valid label.
label = self.get_label_for_problem(0)
except Exception as e:
raise ValidationError('Contest problem label script: %s' % e)
raise ValidationError("Contest problem label script: %s" % e)
else:
if not isinstance(label, str):
raise ValidationError('Contest problem label script: script should return a string.')
raise ValidationError(
"Contest problem label script: script should return a string."
)
def is_in_contest(self, user):
if user.is_authenticated:
profile = user.profile
return profile and profile.current_contest is not None and profile.current_contest.contest == self
return (
profile
and profile.current_contest is not None
and profile.current_contest.contest == self
)
return False
def can_see_own_scoreboard(self, user):
@ -190,19 +353,26 @@ class Contest(models.Model):
return True
if not user.is_authenticated:
return False
if user.has_perm('judge.see_private_contest') or user.has_perm('judge.edit_all_contest'):
if user.has_perm("judge.see_private_contest") or user.has_perm(
"judge.edit_all_contest"
):
return True
if user.profile.id in self.editor_ids:
return True
if self.view_contest_scoreboard.filter(id=user.profile.id).exists():
return True
if self.scoreboard_visibility == self.SCOREBOARD_AFTER_PARTICIPATION and self.has_completed_contest(user):
if (
self.scoreboard_visibility == self.SCOREBOARD_AFTER_PARTICIPATION
and self.has_completed_contest(user)
):
return True
return False
def has_completed_contest(self, user):
if user.is_authenticated:
participation = self.users.filter(virtual=ContestParticipation.LIVE, user=user.profile).first()
participation = self.users.filter(
virtual=ContestParticipation.LIVE, user=user.profile
).first()
if participation and participation.ended:
return True
return False
@ -211,8 +381,11 @@ class Contest(models.Model):
def show_scoreboard(self):
if not self.can_join:
return False
if (self.scoreboard_visibility in (self.SCOREBOARD_AFTER_CONTEST, self.SCOREBOARD_AFTER_PARTICIPATION) and
not self.ended):
if (
self.scoreboard_visibility
in (self.SCOREBOARD_AFTER_CONTEST, self.SCOREBOARD_AFTER_PARTICIPATION)
and not self.ended
):
return False
return True
@ -249,22 +422,29 @@ class Contest(models.Model):
@cached_property
def author_ids(self):
return Contest.authors.through.objects.filter(contest=self).values_list('profile_id', flat=True)
return Contest.authors.through.objects.filter(contest=self).values_list(
"profile_id", flat=True
)
@cached_property
def editor_ids(self):
return self.author_ids.union(
Contest.curators.through.objects.filter(contest=self).values_list('profile_id', flat=True))
Contest.curators.through.objects.filter(contest=self).values_list(
"profile_id", flat=True
)
)
@cached_property
def tester_ids(self):
return Contest.testers.through.objects.filter(contest=self).values_list('profile_id', flat=True)
return Contest.testers.through.objects.filter(contest=self).values_list(
"profile_id", flat=True
)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('contest_view', args=(self.key,))
return reverse("contest_view", args=(self.key,))
def update_user_count(self):
self.user_count = self.users.filter(virtual=0).count()
@ -289,7 +469,9 @@ class Contest(models.Model):
return
# If the user can view or edit all contests
if user.has_perm('judge.see_private_contest') or user.has_perm('judge.edit_all_contest'):
if user.has_perm("judge.see_private_contest") or user.has_perm(
"judge.edit_all_contest"
):
return
# User is organizer or curator for contest
@ -310,14 +492,16 @@ class Contest(models.Model):
if self.view_contest_scoreboard.filter(id=user.profile.id).exists():
return
in_org = self.organizations.filter(id__in=user.profile.organizations.all()).exists()
in_org = self.organizations.filter(
id__in=user.profile.organizations.all()
).exists()
in_users = self.private_contestants.filter(id=user.profile.id).exists()
if not self.is_private and self.is_organization_private:
if in_org:
return
raise self.PrivateContest()
raise self.PrivateContest()
if self.is_private and not self.is_organization_private:
if in_users:
@ -339,11 +523,14 @@ class Contest(models.Model):
def is_editable_by(self, user):
# If the user can edit all contests
if user.has_perm('judge.edit_all_contest'):
if user.has_perm("judge.edit_all_contest"):
return True
# If the user is a contest organizer or curator
if user.has_perm('judge.edit_own_contest') and user.profile.id in self.editor_ids:
if (
user.has_perm("judge.edit_own_contest")
and user.profile.id in self.editor_ids
):
return True
return False
@ -351,19 +538,39 @@ class Contest(models.Model):
@classmethod
def get_visible_contests(cls, user):
if not user.is_authenticated:
return cls.objects.filter(is_visible=True, is_organization_private=False, is_private=False) \
.defer('description').distinct()
return (
cls.objects.filter(
is_visible=True, is_organization_private=False, is_private=False
)
.defer("description")
.distinct()
)
queryset = cls.objects.defer('description')
if not (user.has_perm('judge.see_private_contest') or user.has_perm('judge.edit_all_contest')):
queryset = cls.objects.defer("description")
if not (
user.has_perm("judge.see_private_contest")
or user.has_perm("judge.edit_all_contest")
):
q = Q(is_visible=True)
q &= (
Q(view_contest_scoreboard=user.profile) |
Q(is_organization_private=False, is_private=False) |
Q(is_organization_private=False, is_private=True, private_contestants=user.profile) |
Q(is_organization_private=True, is_private=False, organizations__in=user.profile.organizations.all()) |
Q(is_organization_private=True, is_private=True, organizations__in=user.profile.organizations.all(),
private_contestants=user.profile)
Q(view_contest_scoreboard=user.profile)
| Q(is_organization_private=False, is_private=False)
| Q(
is_organization_private=False,
is_private=True,
private_contestants=user.profile,
)
| Q(
is_organization_private=True,
is_private=False,
organizations__in=user.profile.organizations.all(),
)
| Q(
is_organization_private=True,
is_private=True,
organizations__in=user.profile.organizations.all(),
private_contestants=user.profile,
)
)
q |= Q(authors=user.profile)
@ -373,51 +580,75 @@ class Contest(models.Model):
return queryset.distinct()
def rate(self):
Rating.objects.filter(contest__end_time__range=(self.end_time, self._now)).delete()
Rating.objects.filter(
contest__end_time__range=(self.end_time, self._now)
).delete()
for contest in Contest.objects.filter(
is_rated=True, end_time__range=(self.end_time, self._now),
).order_by('end_time'):
is_rated=True,
end_time__range=(self.end_time, self._now),
).order_by("end_time"):
rate_contest(contest)
class Meta:
permissions = (
('see_private_contest', _('See private contests')),
('edit_own_contest', _('Edit own contests')),
('edit_all_contest', _('Edit all contests')),
('clone_contest', _('Clone contest')),
('moss_contest', _('MOSS contest')),
('contest_rating', _('Rate contests')),
('contest_access_code', _('Contest access codes')),
('create_private_contest', _('Create private contests')),
('change_contest_visibility', _('Change contest visibility')),
('contest_problem_label', _('Edit contest problem label script')),
("see_private_contest", _("See private contests")),
("edit_own_contest", _("Edit own contests")),
("edit_all_contest", _("Edit all contests")),
("clone_contest", _("Clone contest")),
("moss_contest", _("MOSS contest")),
("contest_rating", _("Rate contests")),
("contest_access_code", _("Contest access codes")),
("create_private_contest", _("Create private contests")),
("change_contest_visibility", _("Change contest visibility")),
("contest_problem_label", _("Edit contest problem label script")),
)
verbose_name = _('contest')
verbose_name_plural = _('contests')
verbose_name = _("contest")
verbose_name_plural = _("contests")
class ContestParticipation(models.Model):
LIVE = 0
SPECTATE = -1
contest = models.ForeignKey(Contest, verbose_name=_('associated contest'), related_name='users', on_delete=CASCADE)
user = models.ForeignKey(Profile, verbose_name=_('user'), related_name='contest_history', on_delete=CASCADE)
real_start = models.DateTimeField(verbose_name=_('start time'), default=timezone.now, db_column='start')
score = models.FloatField(verbose_name=_('score'), default=0, db_index=True)
cumtime = models.PositiveIntegerField(verbose_name=_('cumulative time'), default=0)
is_disqualified = models.BooleanField(verbose_name=_('is disqualified'), default=False,
help_text=_('Whether this participation is disqualified.'))
tiebreaker = models.FloatField(verbose_name=_('tie-breaking field'), default=0.0)
virtual = models.IntegerField(verbose_name=_('virtual participation id'), default=LIVE,
help_text=_('0 means non-virtual, otherwise the n-th virtual participation.'))
format_data = JSONField(verbose_name=_('contest format specific data'), null=True, blank=True)
contest = models.ForeignKey(
Contest,
verbose_name=_("associated contest"),
related_name="users",
on_delete=CASCADE,
)
user = models.ForeignKey(
Profile,
verbose_name=_("user"),
related_name="contest_history",
on_delete=CASCADE,
)
real_start = models.DateTimeField(
verbose_name=_("start time"), default=timezone.now, db_column="start"
)
score = models.FloatField(verbose_name=_("score"), default=0, db_index=True)
cumtime = models.PositiveIntegerField(verbose_name=_("cumulative time"), default=0)
is_disqualified = models.BooleanField(
verbose_name=_("is disqualified"),
default=False,
help_text=_("Whether this participation is disqualified."),
)
tiebreaker = models.FloatField(verbose_name=_("tie-breaking field"), default=0.0)
virtual = models.IntegerField(
verbose_name=_("virtual participation id"),
default=LIVE,
help_text=_("0 means non-virtual, otherwise the n-th virtual participation."),
)
format_data = JSONField(
verbose_name=_("contest format specific data"), null=True, blank=True
)
def recompute_results(self):
with transaction.atomic():
self.contest.format.update_participation(self)
if self.is_disqualified:
self.score = -9999
self.save(update_fields=['score'])
self.save(update_fields=["score"])
recompute_results.alters_data = True
def set_disqualified(self, disqualified):
@ -431,6 +662,7 @@ class ContestParticipation(models.Model):
self.contest.banned_users.add(self.user)
else:
self.contest.banned_users.remove(self.user)
set_disqualified.alters_data = True
@property
@ -444,7 +676,11 @@ class ContestParticipation(models.Model):
@cached_property
def start(self):
contest = self.contest
return contest.start_time if contest.time_limit is None and (self.live or self.spectate) else self.real_start
return (
contest.start_time
if contest.time_limit is None and (self.live or self.spectate)
else self.real_start
)
@cached_property
def end_time(self):
@ -456,8 +692,11 @@ class ContestParticipation(models.Model):
return self.real_start + contest.time_limit
else:
return self.real_start + (contest.end_time - contest.start_time)
return contest.end_time if contest.time_limit is None else \
min(self.real_start + contest.time_limit, contest.end_time)
return (
contest.end_time
if contest.time_limit is None
else min(self.real_start + contest.time_limit, contest.end_time)
)
@cached_property
def _now(self):
@ -476,88 +715,140 @@ class ContestParticipation(models.Model):
def __str__(self):
if self.spectate:
return gettext('%s spectating in %s') % (self.user.username, self.contest.name)
return gettext("%s spectating in %s") % (
self.user.username,
self.contest.name,
)
if self.virtual:
return gettext('%s in %s, v%d') % (self.user.username, self.contest.name, self.virtual)
return gettext('%s in %s') % (self.user.username, self.contest.name)
return gettext("%s in %s, v%d") % (
self.user.username,
self.contest.name,
self.virtual,
)
return gettext("%s in %s") % (self.user.username, self.contest.name)
class Meta:
verbose_name = _('contest participation')
verbose_name_plural = _('contest participations')
verbose_name = _("contest participation")
verbose_name_plural = _("contest participations")
unique_together = ('contest', 'user', 'virtual')
unique_together = ("contest", "user", "virtual")
class ContestProblem(models.Model):
problem = models.ForeignKey(Problem, verbose_name=_('problem'), related_name='contests', on_delete=CASCADE)
contest = models.ForeignKey(Contest, verbose_name=_('contest'), related_name='contest_problems', on_delete=CASCADE)
points = models.IntegerField(verbose_name=_('points'))
partial = models.BooleanField(default=True, verbose_name=_('partial'))
is_pretested = models.BooleanField(default=False, verbose_name=_('is pretested'))
order = models.PositiveIntegerField(db_index=True, verbose_name=_('order'))
output_prefix_override = models.IntegerField(help_text=_('0 to not show testcases, 1 to show'),
verbose_name=_('visible testcases'), null=True, blank=True, default=0)
max_submissions = models.IntegerField(help_text=_('Maximum number of submissions for this problem, '
'or 0 for no limit.'), default=0,
validators=[MinValueValidator(0, _('Why include a problem you '
'can\'t submit to?'))])
problem = models.ForeignKey(
Problem, verbose_name=_("problem"), related_name="contests", on_delete=CASCADE
)
contest = models.ForeignKey(
Contest,
verbose_name=_("contest"),
related_name="contest_problems",
on_delete=CASCADE,
)
points = models.IntegerField(verbose_name=_("points"))
partial = models.BooleanField(default=True, verbose_name=_("partial"))
is_pretested = models.BooleanField(default=False, verbose_name=_("is pretested"))
order = models.PositiveIntegerField(db_index=True, verbose_name=_("order"))
output_prefix_override = models.IntegerField(
help_text=_("0 to not show testcases, 1 to show"),
verbose_name=_("visible testcases"),
null=True,
blank=True,
default=0,
)
max_submissions = models.IntegerField(
help_text=_(
"Maximum number of submissions for this problem, " "or 0 for no limit."
),
default=0,
validators=[
MinValueValidator(0, _("Why include a problem you " "can't submit to?"))
],
)
class Meta:
unique_together = ('problem', 'contest')
verbose_name = _('contest problem')
verbose_name_plural = _('contest problems')
unique_together = ("problem", "contest")
verbose_name = _("contest problem")
verbose_name_plural = _("contest problems")
class ContestSubmission(models.Model):
submission = models.OneToOneField(Submission, verbose_name=_('submission'),
related_name='contest', on_delete=CASCADE)
problem = models.ForeignKey(ContestProblem, verbose_name=_('problem'), on_delete=CASCADE,
related_name='submissions', related_query_name='submission')
participation = models.ForeignKey(ContestParticipation, verbose_name=_('participation'), on_delete=CASCADE,
related_name='submissions', related_query_name='submission')
points = models.FloatField(default=0.0, verbose_name=_('points'))
is_pretest = models.BooleanField(verbose_name=_('is pretested'),
help_text=_('Whether this submission was ran only on pretests.'),
default=False)
submission = models.OneToOneField(
Submission,
verbose_name=_("submission"),
related_name="contest",
on_delete=CASCADE,
)
problem = models.ForeignKey(
ContestProblem,
verbose_name=_("problem"),
on_delete=CASCADE,
related_name="submissions",
related_query_name="submission",
)
participation = models.ForeignKey(
ContestParticipation,
verbose_name=_("participation"),
on_delete=CASCADE,
related_name="submissions",
related_query_name="submission",
)
points = models.FloatField(default=0.0, verbose_name=_("points"))
is_pretest = models.BooleanField(
verbose_name=_("is pretested"),
help_text=_("Whether this submission was ran only on pretests."),
default=False,
)
class Meta:
verbose_name = _('contest submission')
verbose_name_plural = _('contest submissions')
verbose_name = _("contest submission")
verbose_name_plural = _("contest submissions")
class Rating(models.Model):
user = models.ForeignKey(Profile, verbose_name=_('user'), related_name='ratings', on_delete=CASCADE)
contest = models.ForeignKey(Contest, verbose_name=_('contest'), related_name='ratings', on_delete=CASCADE)
participation = models.OneToOneField(ContestParticipation, verbose_name=_('participation'),
related_name='rating', on_delete=CASCADE)
rank = models.IntegerField(verbose_name=_('rank'))
rating = models.IntegerField(verbose_name=_('rating'))
mean = models.FloatField(verbose_name=_('raw rating'))
performance = models.FloatField(verbose_name=_('contest performance'))
last_rated = models.DateTimeField(db_index=True, verbose_name=_('last rated'))
user = models.ForeignKey(
Profile, verbose_name=_("user"), related_name="ratings", on_delete=CASCADE
)
contest = models.ForeignKey(
Contest, verbose_name=_("contest"), related_name="ratings", on_delete=CASCADE
)
participation = models.OneToOneField(
ContestParticipation,
verbose_name=_("participation"),
related_name="rating",
on_delete=CASCADE,
)
rank = models.IntegerField(verbose_name=_("rank"))
rating = models.IntegerField(verbose_name=_("rating"))
mean = models.FloatField(verbose_name=_("raw rating"))
performance = models.FloatField(verbose_name=_("contest performance"))
last_rated = models.DateTimeField(db_index=True, verbose_name=_("last rated"))
class Meta:
unique_together = ('user', 'contest')
verbose_name = _('contest rating')
verbose_name_plural = _('contest ratings')
unique_together = ("user", "contest")
verbose_name = _("contest rating")
verbose_name_plural = _("contest ratings")
class ContestMoss(models.Model):
LANG_MAPPING = [
('C', MOSS_LANG_C),
('C++', MOSS_LANG_CC),
('Java', MOSS_LANG_JAVA),
('Python', MOSS_LANG_PYTHON),
('Pascal', MOSS_LANG_PASCAL),
("C", MOSS_LANG_C),
("C++", MOSS_LANG_CC),
("Java", MOSS_LANG_JAVA),
("Python", MOSS_LANG_PYTHON),
("Pascal", MOSS_LANG_PASCAL),
]
contest = models.ForeignKey(Contest, verbose_name=_('contest'), related_name='moss', on_delete=CASCADE)
problem = models.ForeignKey(Problem, verbose_name=_('problem'), related_name='moss', on_delete=CASCADE)
contest = models.ForeignKey(
Contest, verbose_name=_("contest"), related_name="moss", on_delete=CASCADE
)
problem = models.ForeignKey(
Problem, verbose_name=_("problem"), related_name="moss", on_delete=CASCADE
)
language = models.CharField(max_length=10)
submission_count = models.PositiveIntegerField(default=0)
url = models.URLField(null=True, blank=True)
class Meta:
unique_together = ('contest', 'problem', 'language')
verbose_name = _('contest moss result')
verbose_name_plural = _('contest moss results')
unique_together = ("contest", "problem", "language")
verbose_name = _("contest moss result")
verbose_name_plural = _("contest moss results")