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

@ -1,32 +1,67 @@
from reversion import revisions
from judge.models.choices import ACE_THEMES, EFFECTIVE_MATH_ENGINES, MATH_ENGINES_CHOICES, TIMEZONE
from judge.models.choices import (
ACE_THEMES,
EFFECTIVE_MATH_ENGINES,
MATH_ENGINES_CHOICES,
TIMEZONE,
)
from judge.models.comment import Comment, CommentLock, CommentVote, Notification
from judge.models.contest import Contest, ContestMoss, ContestParticipation, ContestProblem, ContestSubmission, \
ContestTag, Rating
from judge.models.contest import (
Contest,
ContestMoss,
ContestParticipation,
ContestProblem,
ContestSubmission,
ContestTag,
Rating,
)
from judge.models.interface import BlogPost, MiscConfig, NavigationBar, validate_regex
from judge.models.message import PrivateMessage, PrivateMessageThread
from judge.models.problem import LanguageLimit, License, Problem, ProblemClarification, ProblemGroup, \
ProblemTranslation, ProblemType, Solution, TranslatedProblemForeignKeyQuerySet, TranslatedProblemQuerySet, ProblemPointsVote
from judge.models.problem_data import CHECKERS, ProblemData, ProblemTestCase, problem_data_storage, \
problem_directory_file
from judge.models.problem import (
LanguageLimit,
License,
Problem,
ProblemClarification,
ProblemGroup,
ProblemTranslation,
ProblemType,
Solution,
TranslatedProblemForeignKeyQuerySet,
TranslatedProblemQuerySet,
ProblemPointsVote,
)
from judge.models.problem_data import (
CHECKERS,
ProblemData,
ProblemTestCase,
problem_data_storage,
problem_directory_file,
)
from judge.models.profile import Organization, OrganizationRequest, Profile, Friend
from judge.models.runtime import Judge, Language, RuntimeVersion
from judge.models.submission import SUBMISSION_RESULT, Submission, SubmissionSource, SubmissionTestCase
from judge.models.submission import (
SUBMISSION_RESULT,
Submission,
SubmissionSource,
SubmissionTestCase,
)
from judge.models.ticket import Ticket, TicketMessage
from judge.models.volunteer import VolunteerProblemVote
revisions.register(Profile, exclude=['points', 'last_access', 'ip', 'rating'])
revisions.register(Problem, follow=['language_limits'])
revisions.register(Profile, exclude=["points", "last_access", "ip", "rating"])
revisions.register(Problem, follow=["language_limits"])
revisions.register(LanguageLimit)
revisions.register(Contest, follow=['contest_problems'])
revisions.register(Contest, follow=["contest_problems"])
revisions.register(ContestProblem)
revisions.register(Organization)
revisions.register(BlogPost)
revisions.register(Solution)
revisions.register(Judge, fields=['name', 'created', 'auth_key', 'description'])
revisions.register(Judge, fields=["name", "created", "auth_key", "description"])
revisions.register(Language)
revisions.register(Comment, fields=['author', 'time', 'page', 'score', 'body', 'hidden', 'parent'])
revisions.register(
Comment, fields=["author", "time", "page", "score", "body", "hidden", "parent"]
)
revisions.register(ProblemTranslation)
revisions.register(ProblemPointsVote)
revisions.register(ContestMoss)

View file

@ -8,11 +8,11 @@ from django.utils.translation import gettext_lazy as _
def make_timezones():
data = defaultdict(list)
for tz in pytz.all_timezones:
if '/' in tz:
area, loc = tz.split('/', 1)
if "/" in tz:
area, loc = tz.split("/", 1)
else:
area, loc = 'Other', tz
if not loc.startswith('GMT'):
area, loc = "Other", tz
if not loc.startswith("GMT"):
data[area].append((tz, loc))
return sorted(data.items(), key=itemgetter(0))
@ -21,46 +21,46 @@ TIMEZONE = make_timezones()
del make_timezones
ACE_THEMES = (
('ambiance', 'Ambiance'),
('chaos', 'Chaos'),
('chrome', 'Chrome'),
('clouds', 'Clouds'),
('clouds_midnight', 'Clouds Midnight'),
('cobalt', 'Cobalt'),
('crimson_editor', 'Crimson Editor'),
('dawn', 'Dawn'),
('dreamweaver', 'Dreamweaver'),
('eclipse', 'Eclipse'),
('github', 'Github'),
('idle_fingers', 'Idle Fingers'),
('katzenmilch', 'Katzenmilch'),
('kr_theme', 'KR Theme'),
('kuroir', 'Kuroir'),
('merbivore', 'Merbivore'),
('merbivore_soft', 'Merbivore Soft'),
('mono_industrial', 'Mono Industrial'),
('monokai', 'Monokai'),
('pastel_on_dark', 'Pastel on Dark'),
('solarized_dark', 'Solarized Dark'),
('solarized_light', 'Solarized Light'),
('terminal', 'Terminal'),
('textmate', 'Textmate'),
('tomorrow', 'Tomorrow'),
('tomorrow_night', 'Tomorrow Night'),
('tomorrow_night_blue', 'Tomorrow Night Blue'),
('tomorrow_night_bright', 'Tomorrow Night Bright'),
('tomorrow_night_eighties', 'Tomorrow Night Eighties'),
('twilight', 'Twilight'),
('vibrant_ink', 'Vibrant Ink'),
('xcode', 'XCode'),
("ambiance", "Ambiance"),
("chaos", "Chaos"),
("chrome", "Chrome"),
("clouds", "Clouds"),
("clouds_midnight", "Clouds Midnight"),
("cobalt", "Cobalt"),
("crimson_editor", "Crimson Editor"),
("dawn", "Dawn"),
("dreamweaver", "Dreamweaver"),
("eclipse", "Eclipse"),
("github", "Github"),
("idle_fingers", "Idle Fingers"),
("katzenmilch", "Katzenmilch"),
("kr_theme", "KR Theme"),
("kuroir", "Kuroir"),
("merbivore", "Merbivore"),
("merbivore_soft", "Merbivore Soft"),
("mono_industrial", "Mono Industrial"),
("monokai", "Monokai"),
("pastel_on_dark", "Pastel on Dark"),
("solarized_dark", "Solarized Dark"),
("solarized_light", "Solarized Light"),
("terminal", "Terminal"),
("textmate", "Textmate"),
("tomorrow", "Tomorrow"),
("tomorrow_night", "Tomorrow Night"),
("tomorrow_night_blue", "Tomorrow Night Blue"),
("tomorrow_night_bright", "Tomorrow Night Bright"),
("tomorrow_night_eighties", "Tomorrow Night Eighties"),
("twilight", "Twilight"),
("vibrant_ink", "Vibrant Ink"),
("xcode", "XCode"),
)
MATH_ENGINES_CHOICES = (
('tex', _('Leave as LaTeX')),
('svg', _('SVG with PNG fallback')),
('mml', _('MathML only')),
('jax', _('MathJax with SVG/PNG fallback')),
('auto', _('Detect best quality')),
("tex", _("Leave as LaTeX")),
("svg", _("SVG with PNG fallback")),
("mml", _("MathML only")),
("jax", _("MathJax with SVG/PNG fallback")),
("auto", _("Detect best quality")),
)
EFFECTIVE_MATH_ENGINES = ('svg', 'mml', 'tex', 'jax')
EFFECTIVE_MATH_ENGINES = ("svg", "mml", "tex", "jax")

View file

@ -20,77 +20,98 @@ from judge.models.profile import Profile
from judge.utils.cachedict import CacheDict
__all__ = ['Comment', 'CommentLock', 'CommentVote', 'Notification']
__all__ = ["Comment", "CommentLock", "CommentVote", "Notification"]
comment_validator = RegexValidator(r'^[pcs]:[a-z0-9]+$|^b:\d+$',
_(r'Page code must be ^[pcs]:[a-z0-9]+$|^b:\d+$'))
comment_validator = RegexValidator(
r"^[pcs]:[a-z0-9]+$|^b:\d+$", _(r"Page code must be ^[pcs]:[a-z0-9]+$|^b:\d+$")
)
class VersionRelation(GenericRelation):
def __init__(self):
super(VersionRelation, self).__init__(Version, object_id_field='object_id')
super(VersionRelation, self).__init__(Version, object_id_field="object_id")
def get_extra_restriction(self, where_class, alias, remote_alias):
cond = super(VersionRelation, self).get_extra_restriction(where_class, alias, remote_alias)
field = self.remote_field.model._meta.get_field('db')
lookup = field.get_lookup('exact')(field.get_col(remote_alias), 'default')
cond.add(lookup, 'AND')
cond = super(VersionRelation, self).get_extra_restriction(
where_class, alias, remote_alias
)
field = self.remote_field.model._meta.get_field("db")
lookup = field.get_lookup("exact")(field.get_col(remote_alias), "default")
cond.add(lookup, "AND")
return cond
class Comment(MPTTModel):
author = models.ForeignKey(Profile, verbose_name=_('commenter'), on_delete=CASCADE)
time = models.DateTimeField(verbose_name=_('posted time'), auto_now_add=True)
page = models.CharField(max_length=30, verbose_name=_('associated page'), db_index=True,
validators=[comment_validator])
score = models.IntegerField(verbose_name=_('votes'), default=0)
body = models.TextField(verbose_name=_('body of comment'), max_length=8192)
hidden = models.BooleanField(verbose_name=_('hide the comment'), default=0)
parent = TreeForeignKey('self', verbose_name=_('parent'), null=True, blank=True, related_name='replies',
on_delete=CASCADE)
author = models.ForeignKey(Profile, verbose_name=_("commenter"), on_delete=CASCADE)
time = models.DateTimeField(verbose_name=_("posted time"), auto_now_add=True)
page = models.CharField(
max_length=30,
verbose_name=_("associated page"),
db_index=True,
validators=[comment_validator],
)
score = models.IntegerField(verbose_name=_("votes"), default=0)
body = models.TextField(verbose_name=_("body of comment"), max_length=8192)
hidden = models.BooleanField(verbose_name=_("hide the comment"), default=0)
parent = TreeForeignKey(
"self",
verbose_name=_("parent"),
null=True,
blank=True,
related_name="replies",
on_delete=CASCADE,
)
versions = VersionRelation()
class Meta:
verbose_name = _('comment')
verbose_name_plural = _('comments')
verbose_name = _("comment")
verbose_name_plural = _("comments")
class MPTTMeta:
order_insertion_by = ['-time']
order_insertion_by = ["-time"]
@classmethod
def most_recent(cls, user, n, batch=None):
queryset = cls.objects.filter(hidden=False).select_related('author__user') \
.defer('author__about', 'body').order_by('-id')
queryset = (
cls.objects.filter(hidden=False)
.select_related("author__user")
.defer("author__about", "body")
.order_by("-id")
)
problem_access = CacheDict(lambda code: Problem.objects.get(code=code).is_accessible_by(user))
contest_access = CacheDict(lambda key: Contest.objects.get(key=key).is_accessible_by(user))
problem_access = CacheDict(
lambda code: Problem.objects.get(code=code).is_accessible_by(user)
)
contest_access = CacheDict(
lambda key: Contest.objects.get(key=key).is_accessible_by(user)
)
blog_access = CacheDict(lambda id: BlogPost.objects.get(id=id).can_see(user))
if n == -1:
n = len(queryset)
n = len(queryset)
if user.is_superuser:
return queryset[:n]
if batch is None:
batch = 2 * n
output = []
for i in itertools.count(0):
slice = queryset[i * batch:i * batch + batch]
slice = queryset[i * batch : i * batch + batch]
if not slice:
break
for comment in slice:
if comment.page.startswith('p:') or comment.page.startswith('s:'):
if comment.page.startswith("p:") or comment.page.startswith("s:"):
try:
if problem_access[comment.page[2:]]:
output.append(comment)
except Problem.DoesNotExist:
pass
elif comment.page.startswith('c:'):
elif comment.page.startswith("c:"):
try:
if contest_access[comment.page[2:]]:
output.append(comment)
except Contest.DoesNotExist:
pass
elif comment.page.startswith('b:'):
elif comment.page.startswith("b:"):
try:
if blog_access[comment.page[2:]]:
output.append(comment)
@ -106,50 +127,55 @@ class Comment(MPTTModel):
def link(self):
try:
link = None
if self.page.startswith('p:'):
link = reverse('problem_detail', args=(self.page[2:],))
elif self.page.startswith('c:'):
link = reverse('contest_view', args=(self.page[2:],))
elif self.page.startswith('b:'):
key = 'blog_slug:%s' % self.page[2:]
if self.page.startswith("p:"):
link = reverse("problem_detail", args=(self.page[2:],))
elif self.page.startswith("c:"):
link = reverse("contest_view", args=(self.page[2:],))
elif self.page.startswith("b:"):
key = "blog_slug:%s" % self.page[2:]
slug = cache.get(key)
if slug is None:
try:
slug = BlogPost.objects.get(id=self.page[2:]).slug
except ObjectDoesNotExist:
slug = ''
slug = ""
cache.set(key, slug, 3600)
link = reverse('blog_post', args=(self.page[2:], slug))
elif self.page.startswith('s:'):
link = reverse('problem_editorial', args=(self.page[2:],))
link = reverse("blog_post", args=(self.page[2:], slug))
elif self.page.startswith("s:"):
link = reverse("problem_editorial", args=(self.page[2:],))
except Exception:
link = 'invalid'
link = "invalid"
return link
@classmethod
def get_page_title(cls, page):
try:
if page.startswith('p:'):
return Problem.objects.values_list('name', flat=True).get(code=page[2:])
elif page.startswith('c:'):
return Contest.objects.values_list('name', flat=True).get(key=page[2:])
elif page.startswith('b:'):
return BlogPost.objects.values_list('title', flat=True).get(id=page[2:])
elif page.startswith('s:'):
return _('Editorial for %s') % Problem.objects.values_list('name', flat=True).get(code=page[2:])
return '<unknown>'
if page.startswith("p:"):
return Problem.objects.values_list("name", flat=True).get(code=page[2:])
elif page.startswith("c:"):
return Contest.objects.values_list("name", flat=True).get(key=page[2:])
elif page.startswith("b:"):
return BlogPost.objects.values_list("title", flat=True).get(id=page[2:])
elif page.startswith("s:"):
return _("Editorial for %s") % Problem.objects.values_list(
"name", flat=True
).get(code=page[2:])
return "<unknown>"
except ObjectDoesNotExist:
return '<deleted>'
return "<deleted>"
@cached_property
def page_title(self):
return self.get_page_title(self.page)
def get_absolute_url(self):
return '%s#comment-%d' % (self.link, self.id)
return "%s#comment-%d" % (self.link, self.id)
def __str__(self):
return '%(page)s by %(user)s' % {'page': self.page, 'user': self.author.user.username}
return "%(page)s by %(user)s" % {
"page": self.page,
"user": self.author.user.username,
}
# Only use this when queried with
# .prefetch_related(Prefetch('votes', queryset=CommentVote.objects.filter(voter_id=profile_id)))
@ -165,34 +191,52 @@ class Comment(MPTTModel):
class CommentVote(models.Model):
voter = models.ForeignKey(Profile, related_name='voted_comments', on_delete=CASCADE)
comment = models.ForeignKey(Comment, related_name='votes', on_delete=CASCADE)
voter = models.ForeignKey(Profile, related_name="voted_comments", on_delete=CASCADE)
comment = models.ForeignKey(Comment, related_name="votes", on_delete=CASCADE)
score = models.IntegerField()
class Meta:
unique_together = ['voter', 'comment']
verbose_name = _('comment vote')
verbose_name_plural = _('comment votes')
unique_together = ["voter", "comment"]
verbose_name = _("comment vote")
verbose_name_plural = _("comment votes")
class CommentLock(models.Model):
page = models.CharField(max_length=30, verbose_name=_('associated page'), db_index=True,
validators=[comment_validator])
page = models.CharField(
max_length=30,
verbose_name=_("associated page"),
db_index=True,
validators=[comment_validator],
)
class Meta:
permissions = (
('override_comment_lock', _('Override comment lock')),
)
permissions = (("override_comment_lock", _("Override comment lock")),)
def __str__(self):
return str(self.page)
class Notification(models.Model):
owner = models.ForeignKey(Profile, verbose_name=_('owner'), related_name="notifications", on_delete=CASCADE)
time = models.DateTimeField(verbose_name=_('posted time'), auto_now_add=True)
comment = models.ForeignKey(Comment, null=True, verbose_name=_('comment'), on_delete=CASCADE)
read = models.BooleanField(verbose_name=_('read'), default=False)
category = models.CharField(verbose_name=_('category'), max_length=10)
html_link = models.TextField(default='', verbose_name=_('html link to comments, used for non-comments'), max_length=1000)
author = models.ForeignKey(Profile, null=True, verbose_name=_('who trigger, used for non-comment'), on_delete=CASCADE)
owner = models.ForeignKey(
Profile,
verbose_name=_("owner"),
related_name="notifications",
on_delete=CASCADE,
)
time = models.DateTimeField(verbose_name=_("posted time"), auto_now_add=True)
comment = models.ForeignKey(
Comment, null=True, verbose_name=_("comment"), on_delete=CASCADE
)
read = models.BooleanField(verbose_name=_("read"), default=False)
category = models.CharField(verbose_name=_("category"), max_length=10)
html_link = models.TextField(
default="",
verbose_name=_("html link to comments, used for non-comments"),
max_length=1000,
)
author = models.ForeignKey(
Profile,
null=True,
verbose_name=_("who trigger, used for non-comment"),
on_delete=CASCADE,
)

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")

View file

@ -10,7 +10,7 @@ from mptt.models import MPTTModel
from judge.models.profile import Organization, Profile
__all__ = ['MiscConfig', 'validate_regex', 'NavigationBar', 'BlogPost']
__all__ = ["MiscConfig", "validate_regex", "NavigationBar", "BlogPost"]
class MiscConfig(models.Model):
@ -21,32 +21,40 @@ class MiscConfig(models.Model):
return self.key
class Meta:
verbose_name = _('configuration item')
verbose_name_plural = _('miscellaneous configuration')
verbose_name = _("configuration item")
verbose_name_plural = _("miscellaneous configuration")
def validate_regex(regex):
try:
re.compile(regex, re.VERBOSE)
except re.error as e:
raise ValidationError('Invalid regex: %s' % e.message)
raise ValidationError("Invalid regex: %s" % e.message)
class NavigationBar(MPTTModel):
class Meta:
verbose_name = _('navigation item')
verbose_name_plural = _('navigation bar')
verbose_name = _("navigation item")
verbose_name_plural = _("navigation bar")
class MPTTMeta:
order_insertion_by = ['order']
order_insertion_by = ["order"]
order = models.PositiveIntegerField(db_index=True, verbose_name=_('order'))
key = models.CharField(max_length=10, unique=True, verbose_name=_('identifier'))
label = models.CharField(max_length=20, verbose_name=_('label'))
path = models.CharField(max_length=255, verbose_name=_('link path'))
regex = models.TextField(verbose_name=_('highlight regex'), validators=[validate_regex])
parent = TreeForeignKey('self', verbose_name=_('parent item'), null=True, blank=True,
related_name='children', on_delete=models.CASCADE)
order = models.PositiveIntegerField(db_index=True, verbose_name=_("order"))
key = models.CharField(max_length=10, unique=True, verbose_name=_("identifier"))
label = models.CharField(max_length=20, verbose_name=_("label"))
path = models.CharField(max_length=255, verbose_name=_("link path"))
regex = models.TextField(
verbose_name=_("highlight regex"), validators=[validate_regex]
)
parent = TreeForeignKey(
"self",
verbose_name=_("parent item"),
null=True,
blank=True,
related_name="children",
on_delete=models.CASCADE,
)
def __str__(self):
return self.label
@ -63,46 +71,61 @@ class NavigationBar(MPTTModel):
class BlogPost(models.Model):
title = models.CharField(verbose_name=_('post title'), max_length=100)
authors = models.ManyToManyField(Profile, verbose_name=_('authors'), blank=True)
slug = models.SlugField(verbose_name=_('slug'))
visible = models.BooleanField(verbose_name=_('public visibility'), default=False)
sticky = models.BooleanField(verbose_name=_('sticky'), default=False)
publish_on = models.DateTimeField(verbose_name=_('publish after'))
content = models.TextField(verbose_name=_('post content'))
summary = models.TextField(verbose_name=_('post summary'), blank=True)
og_image = models.CharField(verbose_name=_('openGraph image'), default='', max_length=150, blank=True)
organizations = models.ManyToManyField(Organization, blank=True, verbose_name=_('organizations'),
help_text=_('If private, only these organizations may see the blog post.'))
is_organization_private = models.BooleanField(verbose_name=_('private to organizations'), default=False)
title = models.CharField(verbose_name=_("post title"), max_length=100)
authors = models.ManyToManyField(Profile, verbose_name=_("authors"), blank=True)
slug = models.SlugField(verbose_name=_("slug"))
visible = models.BooleanField(verbose_name=_("public visibility"), default=False)
sticky = models.BooleanField(verbose_name=_("sticky"), default=False)
publish_on = models.DateTimeField(verbose_name=_("publish after"))
content = models.TextField(verbose_name=_("post content"))
summary = models.TextField(verbose_name=_("post summary"), blank=True)
og_image = models.CharField(
verbose_name=_("openGraph image"), default="", max_length=150, blank=True
)
organizations = models.ManyToManyField(
Organization,
blank=True,
verbose_name=_("organizations"),
help_text=_("If private, only these organizations may see the blog post."),
)
is_organization_private = models.BooleanField(
verbose_name=_("private to organizations"), default=False
)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('blog_post', args=(self.id, self.slug))
return reverse("blog_post", args=(self.id, self.slug))
def can_see(self, user):
if self.visible and self.publish_on <= timezone.now():
if not self.is_organization_private:
return True
if user.is_authenticated and \
self.organizations.filter(id__in=user.profile.organizations.all()).exists():
if (
user.is_authenticated
and self.organizations.filter(
id__in=user.profile.organizations.all()
).exists()
):
return True
if user.has_perm('judge.edit_all_post'):
if user.has_perm("judge.edit_all_post"):
return True
return user.is_authenticated and self.authors.filter(id=user.profile.id).exists()
return (
user.is_authenticated and self.authors.filter(id=user.profile.id).exists()
)
def is_editable_by(self, user):
if not user.is_authenticated:
return False
if user.has_perm('judge.edit_all_post'):
return True
return user.has_perm('judge.change_blogpost') and self.authors.filter(id=user.profile.id).exists()
if not user.is_authenticated:
return False
if user.has_perm("judge.edit_all_post"):
return True
return (
user.has_perm("judge.change_blogpost")
and self.authors.filter(id=user.profile.id).exists()
)
class Meta:
permissions = (
('edit_all_post', _('Edit all posts')),
)
verbose_name = _('blog post')
verbose_name_plural = _('blog posts')
permissions = (("edit_all_post", _("Edit all posts")),)
verbose_name = _("blog post")
verbose_name_plural = _("blog posts")

View file

@ -4,17 +4,31 @@ from django.utils.translation import gettext_lazy as _
from judge.models.profile import Profile
__all__ = ['PrivateMessage', 'PrivateMessageThread']
__all__ = ["PrivateMessage", "PrivateMessageThread"]
class PrivateMessage(models.Model):
title = models.CharField(verbose_name=_('message title'), max_length=50)
content = models.TextField(verbose_name=_('message body'))
sender = models.ForeignKey(Profile, verbose_name=_('sender'), related_name='sent_messages', on_delete=CASCADE)
target = models.ForeignKey(Profile, verbose_name=_('target'), related_name='received_messages', on_delete=CASCADE)
timestamp = models.DateTimeField(verbose_name=_('message timestamp'), auto_now_add=True)
read = models.BooleanField(verbose_name=_('read'), default=False)
title = models.CharField(verbose_name=_("message title"), max_length=50)
content = models.TextField(verbose_name=_("message body"))
sender = models.ForeignKey(
Profile,
verbose_name=_("sender"),
related_name="sent_messages",
on_delete=CASCADE,
)
target = models.ForeignKey(
Profile,
verbose_name=_("target"),
related_name="received_messages",
on_delete=CASCADE,
)
timestamp = models.DateTimeField(
verbose_name=_("message timestamp"), auto_now_add=True
)
read = models.BooleanField(verbose_name=_("read"), default=False)
class PrivateMessageThread(models.Model):
messages = models.ManyToManyField(PrivateMessage, verbose_name=_('messages in the thread'))
messages = models.ManyToManyField(
PrivateMessage, verbose_name=_("messages in the thread")
)

View file

@ -19,143 +19,286 @@ from judge.models.runtime import Language
from judge.user_translations import gettext as user_gettext
from judge.utils.raw_sql import RawSQLColumn, unique_together_left_join
__all__ = ['ProblemGroup', 'ProblemType', 'Problem', 'ProblemTranslation', 'ProblemClarification',
'License', 'Solution', 'TranslatedProblemQuerySet', 'TranslatedProblemForeignKeyQuerySet']
__all__ = [
"ProblemGroup",
"ProblemType",
"Problem",
"ProblemTranslation",
"ProblemClarification",
"License",
"Solution",
"TranslatedProblemQuerySet",
"TranslatedProblemForeignKeyQuerySet",
]
class ProblemType(models.Model):
name = models.CharField(max_length=20, verbose_name=_('problem category ID'), unique=True)
full_name = models.CharField(max_length=100, verbose_name=_('problem category name'))
name = models.CharField(
max_length=20, verbose_name=_("problem category ID"), unique=True
)
full_name = models.CharField(
max_length=100, verbose_name=_("problem category name")
)
def __str__(self):
return self.full_name
class Meta:
ordering = ['full_name']
verbose_name = _('problem type')
verbose_name_plural = _('problem types')
ordering = ["full_name"]
verbose_name = _("problem type")
verbose_name_plural = _("problem types")
class ProblemGroup(models.Model):
name = models.CharField(max_length=20, verbose_name=_('problem group ID'), unique=True)
full_name = models.CharField(max_length=100, verbose_name=_('problem group name'))
name = models.CharField(
max_length=20, verbose_name=_("problem group ID"), unique=True
)
full_name = models.CharField(max_length=100, verbose_name=_("problem group name"))
def __str__(self):
return self.full_name
class Meta:
ordering = ['full_name']
verbose_name = _('problem group')
verbose_name_plural = _('problem groups')
ordering = ["full_name"]
verbose_name = _("problem group")
verbose_name_plural = _("problem groups")
class License(models.Model):
key = models.CharField(max_length=20, unique=True, verbose_name=_('key'),
validators=[RegexValidator(r'^[-\w.]+$', r'License key must be ^[-\w.]+$')])
link = models.CharField(max_length=256, verbose_name=_('link'))
name = models.CharField(max_length=256, verbose_name=_('full name'))
display = models.CharField(max_length=256, blank=True, verbose_name=_('short name'),
help_text=_('Displayed on pages under this license'))
icon = models.CharField(max_length=256, blank=True, verbose_name=_('icon'), help_text=_('URL to the icon'))
text = models.TextField(verbose_name=_('license text'))
key = models.CharField(
max_length=20,
unique=True,
verbose_name=_("key"),
validators=[RegexValidator(r"^[-\w.]+$", r"License key must be ^[-\w.]+$")],
)
link = models.CharField(max_length=256, verbose_name=_("link"))
name = models.CharField(max_length=256, verbose_name=_("full name"))
display = models.CharField(
max_length=256,
blank=True,
verbose_name=_("short name"),
help_text=_("Displayed on pages under this license"),
)
icon = models.CharField(
max_length=256,
blank=True,
verbose_name=_("icon"),
help_text=_("URL to the icon"),
)
text = models.TextField(verbose_name=_("license text"))
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('license', args=(self.key,))
return reverse("license", args=(self.key,))
class Meta:
verbose_name = _('license')
verbose_name_plural = _('licenses')
verbose_name = _("license")
verbose_name_plural = _("licenses")
class TranslatedProblemQuerySet(SearchQuerySet):
def __init__(self, **kwargs):
super(TranslatedProblemQuerySet, self).__init__(('code', 'name', 'description'), **kwargs)
super(TranslatedProblemQuerySet, self).__init__(
("code", "name", "description"), **kwargs
)
def add_i18n_name(self, language):
queryset = self._clone()
alias = unique_together_left_join(queryset, ProblemTranslation, 'problem', 'language', language)
return queryset.annotate(i18n_name=Coalesce(RawSQL('%s.name' % alias, ()), F('name'),
output_field=models.CharField()))
alias = unique_together_left_join(
queryset, ProblemTranslation, "problem", "language", language
)
return queryset.annotate(
i18n_name=Coalesce(
RawSQL("%s.name" % alias, ()),
F("name"),
output_field=models.CharField(),
)
)
class TranslatedProblemForeignKeyQuerySet(QuerySet):
def add_problem_i18n_name(self, key, language, name_field=None):
queryset = self._clone() if name_field is None else self.annotate(_name=F(name_field))
alias = unique_together_left_join(queryset, ProblemTranslation, 'problem', 'language', language,
parent_model=Problem)
queryset = (
self._clone() if name_field is None else self.annotate(_name=F(name_field))
)
alias = unique_together_left_join(
queryset,
ProblemTranslation,
"problem",
"language",
language,
parent_model=Problem,
)
# You must specify name_field if Problem is not yet joined into the QuerySet.
kwargs = {key: Coalesce(RawSQL('%s.name' % alias, ()),
F(name_field) if name_field else RawSQLColumn(Problem, 'name'),
output_field=models.CharField())}
kwargs = {
key: Coalesce(
RawSQL("%s.name" % alias, ()),
F(name_field) if name_field else RawSQLColumn(Problem, "name"),
output_field=models.CharField(),
)
}
return queryset.annotate(**kwargs)
class Problem(models.Model):
code = models.CharField(max_length=20, verbose_name=_('problem code'), unique=True,
validators=[RegexValidator('^[a-z0-9]+$', _('Problem code must be ^[a-z0-9]+$'))],
help_text=_('A short, unique code for the problem, '
'used in the url after /problem/'))
name = models.CharField(max_length=100, verbose_name=_('problem name'), db_index=True,
help_text=_('The full name of the problem, '
'as shown in the problem list.'))
description = models.TextField(verbose_name=_('problem body'))
authors = models.ManyToManyField(Profile, verbose_name=_('creators'), blank=True, related_name='authored_problems',
help_text=_('These users will be able to edit the problem, '
'and be listed as authors.'))
curators = models.ManyToManyField(Profile, verbose_name=_('curators'), blank=True, related_name='curated_problems',
help_text=_('These users will be able to edit the problem, '
'but not be listed as authors.'))
testers = models.ManyToManyField(Profile, verbose_name=_('testers'), blank=True, related_name='tested_problems',
help_text=_(
'These users will be able to view the private problem, but not edit it.'))
types = models.ManyToManyField(ProblemType, verbose_name=_('problem types'),
help_text=_('The type of problem, '
"as shown on the problem's page."))
group = models.ForeignKey(ProblemGroup, verbose_name=_('problem group'), on_delete=CASCADE,
help_text=_('The group of problem, shown under Category in the problem list.'))
time_limit = models.FloatField(verbose_name=_('time limit'),
help_text=_('The time limit for this problem, in seconds. '
'Fractional seconds (e.g. 1.5) are supported.'),
validators=[MinValueValidator(settings.DMOJ_PROBLEM_MIN_TIME_LIMIT),
MaxValueValidator(settings.DMOJ_PROBLEM_MAX_TIME_LIMIT)])
memory_limit = models.PositiveIntegerField(verbose_name=_('memory limit'),
help_text=_('The memory limit for this problem, in kilobytes '
'(e.g. 64mb = 65536 kilobytes).'),
validators=[MinValueValidator(settings.DMOJ_PROBLEM_MIN_MEMORY_LIMIT),
MaxValueValidator(settings.DMOJ_PROBLEM_MAX_MEMORY_LIMIT)])
code = models.CharField(
max_length=20,
verbose_name=_("problem code"),
unique=True,
validators=[
RegexValidator("^[a-z0-9]+$", _("Problem code must be ^[a-z0-9]+$"))
],
help_text=_(
"A short, unique code for the problem, " "used in the url after /problem/"
),
)
name = models.CharField(
max_length=100,
verbose_name=_("problem name"),
db_index=True,
help_text=_("The full name of the problem, " "as shown in the problem list."),
)
description = models.TextField(verbose_name=_("problem body"))
authors = models.ManyToManyField(
Profile,
verbose_name=_("creators"),
blank=True,
related_name="authored_problems",
help_text=_(
"These users will be able to edit the problem, " "and be listed as authors."
),
)
curators = models.ManyToManyField(
Profile,
verbose_name=_("curators"),
blank=True,
related_name="curated_problems",
help_text=_(
"These users will be able to edit the problem, "
"but not be listed as authors."
),
)
testers = models.ManyToManyField(
Profile,
verbose_name=_("testers"),
blank=True,
related_name="tested_problems",
help_text=_(
"These users will be able to view the private problem, but not edit it."
),
)
types = models.ManyToManyField(
ProblemType,
verbose_name=_("problem types"),
help_text=_("The type of problem, " "as shown on the problem's page."),
)
group = models.ForeignKey(
ProblemGroup,
verbose_name=_("problem group"),
on_delete=CASCADE,
help_text=_("The group of problem, shown under Category in the problem list."),
)
time_limit = models.FloatField(
verbose_name=_("time limit"),
help_text=_(
"The time limit for this problem, in seconds. "
"Fractional seconds (e.g. 1.5) are supported."
),
validators=[
MinValueValidator(settings.DMOJ_PROBLEM_MIN_TIME_LIMIT),
MaxValueValidator(settings.DMOJ_PROBLEM_MAX_TIME_LIMIT),
],
)
memory_limit = models.PositiveIntegerField(
verbose_name=_("memory limit"),
help_text=_(
"The memory limit for this problem, in kilobytes "
"(e.g. 64mb = 65536 kilobytes)."
),
validators=[
MinValueValidator(settings.DMOJ_PROBLEM_MIN_MEMORY_LIMIT),
MaxValueValidator(settings.DMOJ_PROBLEM_MAX_MEMORY_LIMIT),
],
)
short_circuit = models.BooleanField(default=False)
points = models.FloatField(verbose_name=_('points'),
help_text=_('Points awarded for problem completion. '
"Points are displayed with a 'p' suffix if partial."),
validators=[MinValueValidator(settings.DMOJ_PROBLEM_MIN_PROBLEM_POINTS)])
partial = models.BooleanField(verbose_name=_('allows partial points'), default=False)
allowed_languages = models.ManyToManyField(Language, verbose_name=_('allowed languages'),
help_text=_('List of allowed submission languages.'))
is_public = models.BooleanField(verbose_name=_('publicly visible'), db_index=True, default=False)
is_manually_managed = models.BooleanField(verbose_name=_('manually managed'), db_index=True, default=False,
help_text=_('Whether judges should be allowed to manage data or not.'))
date = models.DateTimeField(verbose_name=_('date of publishing'), null=True, blank=True, db_index=True,
help_text=_("Doesn't have magic ability to auto-publish due to backward compatibility"))
banned_users = models.ManyToManyField(Profile, verbose_name=_('personae non gratae'), blank=True,
help_text=_('Bans the selected users from submitting to this problem.'))
license = models.ForeignKey(License, null=True, blank=True, on_delete=SET_NULL,
help_text=_('The license under which this problem is published.'))
og_image = models.CharField(verbose_name=_('OpenGraph image'), max_length=150, blank=True)
summary = models.TextField(blank=True, verbose_name=_('problem summary'),
help_text=_('Plain-text, shown in meta description tag, e.g. for social media.'))
user_count = models.IntegerField(verbose_name=_('number of users'), default=0,
help_text=_('The number of users who solved the problem.'))
ac_rate = models.FloatField(verbose_name=_('solve rate'), default=0)
points = models.FloatField(
verbose_name=_("points"),
help_text=_(
"Points awarded for problem completion. "
"Points are displayed with a 'p' suffix if partial."
),
validators=[MinValueValidator(settings.DMOJ_PROBLEM_MIN_PROBLEM_POINTS)],
)
partial = models.BooleanField(
verbose_name=_("allows partial points"), default=False
)
allowed_languages = models.ManyToManyField(
Language,
verbose_name=_("allowed languages"),
help_text=_("List of allowed submission languages."),
)
is_public = models.BooleanField(
verbose_name=_("publicly visible"), db_index=True, default=False
)
is_manually_managed = models.BooleanField(
verbose_name=_("manually managed"),
db_index=True,
default=False,
help_text=_("Whether judges should be allowed to manage data or not."),
)
date = models.DateTimeField(
verbose_name=_("date of publishing"),
null=True,
blank=True,
db_index=True,
help_text=_(
"Doesn't have magic ability to auto-publish due to backward compatibility"
),
)
banned_users = models.ManyToManyField(
Profile,
verbose_name=_("personae non gratae"),
blank=True,
help_text=_("Bans the selected users from submitting to this problem."),
)
license = models.ForeignKey(
License,
null=True,
blank=True,
on_delete=SET_NULL,
help_text=_("The license under which this problem is published."),
)
og_image = models.CharField(
verbose_name=_("OpenGraph image"), max_length=150, blank=True
)
summary = models.TextField(
blank=True,
verbose_name=_("problem summary"),
help_text=_(
"Plain-text, shown in meta description tag, e.g. for social media."
),
)
user_count = models.IntegerField(
verbose_name=_("number of users"),
default=0,
help_text=_("The number of users who solved the problem."),
)
ac_rate = models.FloatField(verbose_name=_("solve rate"), default=0)
objects = TranslatedProblemQuerySet.as_manager()
tickets = GenericRelation('Ticket')
tickets = GenericRelation("Ticket")
organizations = models.ManyToManyField(Organization, blank=True, verbose_name=_('organizations'),
help_text=_('If private, only these organizations may see the problem.'))
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 problem."),
)
is_organization_private = models.BooleanField(
verbose_name=_("private to organizations"), default=False
)
def __init__(self, *args, **kwargs):
super(Problem, self).__init__(*args, **kwargs)
@ -165,20 +308,30 @@ class Problem(models.Model):
@cached_property
def types_list(self):
return list(map(user_gettext, map(attrgetter('full_name'), self.types.all())))
return list(map(user_gettext, map(attrgetter("full_name"), self.types.all())))
def languages_list(self):
return self.allowed_languages.values_list('common_name', flat=True).distinct().order_by('common_name')
return (
self.allowed_languages.values_list("common_name", flat=True)
.distinct()
.order_by("common_name")
)
def is_editor(self, profile):
return (self.authors.filter(id=profile.id) | self.curators.filter(id=profile.id)).exists()
return (
self.authors.filter(id=profile.id) | self.curators.filter(id=profile.id)
).exists()
def is_editable_by(self, user):
if not user.is_authenticated:
return False
if user.has_perm('judge.edit_all_problem') or user.has_perm('judge.edit_public_problem') and self.is_public:
if (
user.has_perm("judge.edit_all_problem")
or user.has_perm("judge.edit_public_problem")
and self.is_public
):
return True
return user.has_perm('judge.edit_own_problem') and self.is_editor(user.profile)
return user.has_perm("judge.edit_own_problem") and self.is_editor(user.profile)
def is_accessible_by(self, user):
# Problem is public.
@ -188,23 +341,24 @@ class Problem(models.Model):
return True
# If the user can see all organization private problems.
if user.has_perm('judge.see_organization_problem'):
if user.has_perm("judge.see_organization_problem"):
return True
# If the user is in the organization.
if user.is_authenticated and \
self.organizations.filter(id__in=user.profile.organizations.all()):
if user.is_authenticated and self.organizations.filter(
id__in=user.profile.organizations.all()
):
return True
# If the user can view all problems.
if user.has_perm('judge.see_private_problem'):
if user.has_perm("judge.see_private_problem"):
return True
if not user.is_authenticated:
return False
# If the user authored the problem or is a curator.
if user.has_perm('judge.edit_own_problem') and self.is_editor(user.profile):
if user.has_perm("judge.edit_own_problem") and self.is_editor(user.profile):
return True
# If user is a tester.
@ -216,11 +370,18 @@ class Problem(models.Model):
if current is None:
return False
from judge.models import ContestProblem
return ContestProblem.objects.filter(problem_id=self.id, contest__users__id=current).exists()
return ContestProblem.objects.filter(
problem_id=self.id, contest__users__id=current
).exists()
def is_subs_manageable_by(self, user):
return user.is_staff and user.has_perm('judge.rejudge_submission') and self.is_editable_by(user)
return (
user.is_staff
and user.has_perm("judge.rejudge_submission")
and self.is_editable_by(user)
)
@classmethod
def get_visible_problems(cls, user):
# Do unauthenticated check here so we can skip authentication checks later on.
@ -235,15 +396,18 @@ class Problem(models.Model):
# - is_public problems
# - not is_organization_private or in organization or `judge.see_organization_problem`
# - author or curator or tester
queryset = cls.objects.defer('description')
queryset = cls.objects.defer("description")
if not (user.has_perm('judge.see_private_problem') or user.has_perm('judge.edit_all_problem')):
if not (
user.has_perm("judge.see_private_problem")
or user.has_perm("judge.edit_all_problem")
):
q = Q(is_public=True)
if not user.has_perm('judge.see_organization_problem'):
if not user.has_perm("judge.see_organization_problem"):
# Either not organization private or in the organization.
q &= (
Q(is_organization_private=False) |
Q(is_organization_private=True, organizations__in=user.profile.organizations.all())
q &= Q(is_organization_private=False) | Q(
is_organization_private=True,
organizations__in=user.profile.organizations.all(),
)
# Authors, curators, and testers should always have access, so OR at the very end.
@ -256,40 +420,46 @@ class Problem(models.Model):
@classmethod
def get_public_problems(cls):
return cls.objects.filter(is_public=True, is_organization_private=False).defer('description')
return cls.objects.filter(is_public=True, is_organization_private=False).defer(
"description"
)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('problem_detail', args=(self.code,))
return reverse("problem_detail", args=(self.code,))
@cached_property
def author_ids(self):
return self.authors.values_list('id', flat=True)
return self.authors.values_list("id", flat=True)
@cached_property
def editor_ids(self):
return self.author_ids | self.curators.values_list('id', flat=True)
return self.author_ids | self.curators.values_list("id", flat=True)
@cached_property
def tester_ids(self):
return self.testers.values_list('id', flat=True)
return self.testers.values_list("id", flat=True)
@cached_property
def usable_common_names(self):
return set(self.usable_languages.values_list('common_name', flat=True))
return set(self.usable_languages.values_list("common_name", flat=True))
@property
def usable_languages(self):
return self.allowed_languages.filter(judges__in=self.judges.filter(online=True)).distinct()
return self.allowed_languages.filter(
judges__in=self.judges.filter(online=True)
).distinct()
def translated_name(self, language):
if language in self._translated_name_cache:
return self._translated_name_cache[language]
# Hits database despite prefetch_related.
try:
name = self.translations.filter(language=language).values_list('name', flat=True)[0]
name = self.translations.filter(language=language).values_list(
"name", flat=True
)[0]
except IndexError:
name = self.name
self._translated_name_cache[language] = name
@ -310,12 +480,23 @@ class Problem(models.Model):
return ProblemClarification.objects.filter(problem=self)
def update_stats(self):
self.user_count = self.submission_set.filter(points__gte=self.points, result='AC',
user__is_unlisted=False).values('user').distinct().count()
self.user_count = (
self.submission_set.filter(
points__gte=self.points, result="AC", user__is_unlisted=False
)
.values("user")
.distinct()
.count()
)
submissions = self.submission_set.count()
if submissions:
self.ac_rate = 100.0 * self.submission_set.filter(points__gte=self.points, result='AC',
user__is_unlisted=False).count() / submissions
self.ac_rate = (
100.0
* self.submission_set.filter(
points__gte=self.points, result="AC", user__is_unlisted=False
).count()
/ submissions
)
else:
self.ac_rate = 0
self.save()
@ -324,9 +505,13 @@ class Problem(models.Model):
def _get_limits(self, key):
global_limit = getattr(self, key)
limits = {limit['language_id']: (limit['language__name'], limit[key])
for limit in self.language_limits.values('language_id', 'language__name', key)
if limit[key] != global_limit}
limits = {
limit["language_id"]: (limit["language__name"], limit[key])
for limit in self.language_limits.values(
"language_id", "language__name", key
)
if limit[key] != global_limit
}
limit_ids = set(limits.keys())
common = []
@ -346,21 +531,21 @@ class Problem(models.Model):
@property
def language_time_limit(self):
key = 'problem_tls:%d' % self.id
key = "problem_tls:%d" % self.id
result = cache.get(key)
if result is not None:
return result
result = self._get_limits('time_limit')
result = self._get_limits("time_limit")
cache.set(key, result)
return result
@property
def language_memory_limit(self):
key = 'problem_mls:%d' % self.id
key = "problem_mls:%d" % self.id
result = cache.get(key)
if result is not None:
return result
result = self._get_limits('memory_limit')
result = self._get_limits("memory_limit")
cache.set(key, result)
return result
@ -395,105 +580,143 @@ class Problem(models.Model):
return False
# If the user has a full AC submission to the problem (solved the problem).
return self.submission_set.filter(user=user.profile, result='AC', points=F('problem__points')).exists()
return self.submission_set.filter(
user=user.profile, result="AC", points=F("problem__points")
).exists()
class Meta:
permissions = (
('see_private_problem', 'See hidden problems'),
('edit_own_problem', 'Edit own problems'),
('edit_all_problem', 'Edit all problems'),
('edit_public_problem', 'Edit all public problems'),
('clone_problem', 'Clone problem'),
('change_public_visibility', 'Change is_public field'),
('change_manually_managed', 'Change is_manually_managed field'),
('see_organization_problem', 'See organization-private problems'),
('suggest_problem_changes', 'Suggest changes to problem'),
("see_private_problem", "See hidden problems"),
("edit_own_problem", "Edit own problems"),
("edit_all_problem", "Edit all problems"),
("edit_public_problem", "Edit all public problems"),
("clone_problem", "Clone problem"),
("change_public_visibility", "Change is_public field"),
("change_manually_managed", "Change is_manually_managed field"),
("see_organization_problem", "See organization-private problems"),
("suggest_problem_changes", "Suggest changes to problem"),
)
verbose_name = _('problem')
verbose_name_plural = _('problems')
verbose_name = _("problem")
verbose_name_plural = _("problems")
class ProblemTranslation(models.Model):
problem = models.ForeignKey(Problem, verbose_name=_('problem'), related_name='translations', on_delete=CASCADE)
language = models.CharField(verbose_name=_('language'), max_length=7, choices=settings.LANGUAGES)
name = models.CharField(verbose_name=_('translated name'), max_length=100, db_index=True)
description = models.TextField(verbose_name=_('translated description'))
problem = models.ForeignKey(
Problem,
verbose_name=_("problem"),
related_name="translations",
on_delete=CASCADE,
)
language = models.CharField(
verbose_name=_("language"), max_length=7, choices=settings.LANGUAGES
)
name = models.CharField(
verbose_name=_("translated name"), max_length=100, db_index=True
)
description = models.TextField(verbose_name=_("translated description"))
class Meta:
unique_together = ('problem', 'language')
verbose_name = _('problem translation')
verbose_name_plural = _('problem translations')
unique_together = ("problem", "language")
verbose_name = _("problem translation")
verbose_name_plural = _("problem translations")
class ProblemClarification(models.Model):
problem = models.ForeignKey(Problem, verbose_name=_('clarified problem'), on_delete=CASCADE)
description = models.TextField(verbose_name=_('clarification body'))
date = models.DateTimeField(verbose_name=_('clarification timestamp'), auto_now_add=True)
problem = models.ForeignKey(
Problem, verbose_name=_("clarified problem"), on_delete=CASCADE
)
description = models.TextField(verbose_name=_("clarification body"))
date = models.DateTimeField(
verbose_name=_("clarification timestamp"), auto_now_add=True
)
class LanguageLimit(models.Model):
problem = models.ForeignKey(Problem, verbose_name=_('problem'), related_name='language_limits', on_delete=CASCADE)
language = models.ForeignKey(Language, verbose_name=_('language'), on_delete=CASCADE)
time_limit = models.FloatField(verbose_name=_('time limit'),
validators=[MinValueValidator(settings.DMOJ_PROBLEM_MIN_TIME_LIMIT),
MaxValueValidator(settings.DMOJ_PROBLEM_MAX_TIME_LIMIT)])
memory_limit = models.IntegerField(verbose_name=_('memory limit'),
validators=[MinValueValidator(settings.DMOJ_PROBLEM_MIN_MEMORY_LIMIT),
MaxValueValidator(settings.DMOJ_PROBLEM_MAX_MEMORY_LIMIT)])
problem = models.ForeignKey(
Problem,
verbose_name=_("problem"),
related_name="language_limits",
on_delete=CASCADE,
)
language = models.ForeignKey(
Language, verbose_name=_("language"), on_delete=CASCADE
)
time_limit = models.FloatField(
verbose_name=_("time limit"),
validators=[
MinValueValidator(settings.DMOJ_PROBLEM_MIN_TIME_LIMIT),
MaxValueValidator(settings.DMOJ_PROBLEM_MAX_TIME_LIMIT),
],
)
memory_limit = models.IntegerField(
verbose_name=_("memory limit"),
validators=[
MinValueValidator(settings.DMOJ_PROBLEM_MIN_MEMORY_LIMIT),
MaxValueValidator(settings.DMOJ_PROBLEM_MAX_MEMORY_LIMIT),
],
)
class Meta:
unique_together = ('problem', 'language')
verbose_name = _('language-specific resource limit')
verbose_name_plural = _('language-specific resource limits')
unique_together = ("problem", "language")
verbose_name = _("language-specific resource limit")
verbose_name_plural = _("language-specific resource limits")
class Solution(models.Model):
problem = models.OneToOneField(Problem, on_delete=SET_NULL, verbose_name=_('associated problem'),
null=True, blank=True, related_name='solution')
is_public = models.BooleanField(verbose_name=_('public visibility'), default=False)
publish_on = models.DateTimeField(verbose_name=_('publish date'))
authors = models.ManyToManyField(Profile, verbose_name=_('authors'), blank=True)
content = models.TextField(verbose_name=_('editorial content'))
problem = models.OneToOneField(
Problem,
on_delete=SET_NULL,
verbose_name=_("associated problem"),
null=True,
blank=True,
related_name="solution",
)
is_public = models.BooleanField(verbose_name=_("public visibility"), default=False)
publish_on = models.DateTimeField(verbose_name=_("publish date"))
authors = models.ManyToManyField(Profile, verbose_name=_("authors"), blank=True)
content = models.TextField(verbose_name=_("editorial content"))
def get_absolute_url(self):
problem = self.problem
if problem is None:
return reverse('home')
return reverse("home")
else:
return reverse('problem_editorial', args=[problem.code])
return reverse("problem_editorial", args=[problem.code])
def __str__(self):
return _('Editorial for %s') % self.problem.name
return _("Editorial for %s") % self.problem.name
class Meta:
permissions = (
('see_private_solution', 'See hidden solutions'),
)
verbose_name = _('solution')
verbose_name_plural = _('solutions')
permissions = (("see_private_solution", "See hidden solutions"),)
verbose_name = _("solution")
verbose_name_plural = _("solutions")
class ProblemPointsVote(models.Model):
points = models.IntegerField(
verbose_name=_('proposed point value'),
help_text=_('The amount of points you think this problem deserves.'),
verbose_name=_("proposed point value"),
help_text=_("The amount of points you think this problem deserves."),
validators=[
MinValueValidator(100),
MaxValueValidator(600),
],
)
voter = models.ForeignKey(Profile, related_name='problem_points_votes', on_delete=CASCADE, db_index=True)
problem = models.ForeignKey(Problem, related_name='problem_points_votes', on_delete=CASCADE, db_index=True)
voter = models.ForeignKey(
Profile, related_name="problem_points_votes", on_delete=CASCADE, db_index=True
)
problem = models.ForeignKey(
Problem, related_name="problem_points_votes", on_delete=CASCADE, db_index=True
)
vote_time = models.DateTimeField(
verbose_name=_('The time this vote was cast'),
verbose_name=_("The time this vote was cast"),
auto_now_add=True,
blank=True,
)
class Meta:
verbose_name = _('vote')
verbose_name_plural = _('votes')
verbose_name = _("vote")
verbose_name_plural = _("votes")
def __str__(self):
return f'{self.voter}: {self.points} for {self.problem.code}'
return f"{self.voter}: {self.points} for {self.problem.code}"

View file

@ -9,7 +9,13 @@ from django.utils.translation import gettext_lazy as _
from judge.utils.problem_data import ProblemDataStorage, get_file_cachekey
__all__ = ['problem_data_storage', 'problem_directory_file', 'ProblemData', 'ProblemTestCase', 'CHECKERS']
__all__ = [
"problem_data_storage",
"problem_directory_file",
"ProblemData",
"ProblemTestCase",
"CHECKERS",
]
problem_data_storage = ProblemDataStorage()
@ -23,52 +29,83 @@ def problem_directory_file(data, filename):
CHECKERS = (
('standard', _('Standard')),
('floats', _('Floats')),
('floatsabs', _('Floats (absolute)')),
('floatsrel', _('Floats (relative)')),
('rstripped', _('Non-trailing spaces')),
('sorted', _('Unordered')),
('identical', _('Byte identical')),
('linecount', _('Line-by-line')),
('custom', _('Custom checker (PY)')),
('customval', _('Custom validator (CPP)')),
('interact', _('Interactive')),
("standard", _("Standard")),
("floats", _("Floats")),
("floatsabs", _("Floats (absolute)")),
("floatsrel", _("Floats (relative)")),
("rstripped", _("Non-trailing spaces")),
("sorted", _("Unordered")),
("identical", _("Byte identical")),
("linecount", _("Line-by-line")),
("custom", _("Custom checker (PY)")),
("customval", _("Custom validator (CPP)")),
("interact", _("Interactive")),
)
class ProblemData(models.Model):
problem = models.OneToOneField('Problem', verbose_name=_('problem'), related_name='data_files',
on_delete=models.CASCADE)
zipfile = models.FileField(verbose_name=_('data zip file'), storage=problem_data_storage, null=True, blank=True,
upload_to=problem_directory_file)
generator = models.FileField(verbose_name=_('generator file'), storage=problem_data_storage, null=True, blank=True,
upload_to=problem_directory_file)
output_prefix = models.IntegerField(verbose_name=_('output prefix length'), blank=True, null=True)
output_limit = models.IntegerField(verbose_name=_('output limit length'), blank=True, null=True)
feedback = models.TextField(verbose_name=_('init.yml generation feedback'), blank=True)
checker = models.CharField(max_length=10, verbose_name=_('checker'), choices=CHECKERS, blank=True)
checker_args = models.TextField(verbose_name=_('checker arguments'), blank=True,
help_text=_('checker arguments as a JSON object'))
custom_checker = models.FileField(verbose_name=_('custom checker file'),
storage=problem_data_storage,
null=True,
blank=True,
upload_to=problem_directory_file,
validators=[FileExtensionValidator(allowed_extensions=['py'])])
custom_validator = models.FileField(verbose_name=_('custom validator file'),
storage=problem_data_storage,
null=True,
blank=True,
upload_to=problem_directory_file,
validators=[FileExtensionValidator(allowed_extensions=['cpp'])])
interactive_judge = models.FileField(verbose_name=_('interactive judge'),
storage=problem_data_storage,
null=True,
blank=True,
upload_to=problem_directory_file,
validators=[FileExtensionValidator(allowed_extensions=['cpp'])])
problem = models.OneToOneField(
"Problem",
verbose_name=_("problem"),
related_name="data_files",
on_delete=models.CASCADE,
)
zipfile = models.FileField(
verbose_name=_("data zip file"),
storage=problem_data_storage,
null=True,
blank=True,
upload_to=problem_directory_file,
)
generator = models.FileField(
verbose_name=_("generator file"),
storage=problem_data_storage,
null=True,
blank=True,
upload_to=problem_directory_file,
)
output_prefix = models.IntegerField(
verbose_name=_("output prefix length"), blank=True, null=True
)
output_limit = models.IntegerField(
verbose_name=_("output limit length"), blank=True, null=True
)
feedback = models.TextField(
verbose_name=_("init.yml generation feedback"), blank=True
)
checker = models.CharField(
max_length=10, verbose_name=_("checker"), choices=CHECKERS, blank=True
)
checker_args = models.TextField(
verbose_name=_("checker arguments"),
blank=True,
help_text=_("checker arguments as a JSON object"),
)
custom_checker = models.FileField(
verbose_name=_("custom checker file"),
storage=problem_data_storage,
null=True,
blank=True,
upload_to=problem_directory_file,
validators=[FileExtensionValidator(allowed_extensions=["py"])],
)
custom_validator = models.FileField(
verbose_name=_("custom validator file"),
storage=problem_data_storage,
null=True,
blank=True,
upload_to=problem_directory_file,
validators=[FileExtensionValidator(allowed_extensions=["cpp"])],
)
interactive_judge = models.FileField(
verbose_name=_("interactive judge"),
storage=problem_data_storage,
null=True,
blank=True,
upload_to=problem_directory_file,
validators=[FileExtensionValidator(allowed_extensions=["cpp"])],
)
__original_zipfile = None
def __init__(self, *args, **kwargs):
@ -78,10 +115,13 @@ class ProblemData(models.Model):
def save(self, *args, **kwargs):
# Delete caches
if self.__original_zipfile:
try:
try:
files = ZipFile(self.__original_zipfile.path).namelist()
for file in files:
cache_key = 'problem_archive:%s:%s' % (self.problem.code, get_file_cachekey(file))
cache_key = "problem_archive:%s:%s" % (
self.problem.code,
get_file_cachekey(file),
)
cache.delete(cache_key)
except BadZipFile:
pass
@ -90,7 +130,7 @@ class ProblemData(models.Model):
return super(ProblemData, self).save(*args, **kwargs)
def has_yml(self):
return problem_data_storage.exists('%s/init.yml' % self.problem.code)
return problem_data_storage.exists("%s/init.yml" % self.problem.code)
def _update_code(self, original, new):
try:
@ -103,31 +143,60 @@ class ProblemData(models.Model):
if self.generator:
self.generator.name = _problem_directory_file(new, self.generator.name)
if self.custom_checker:
self.custom_checker.name = _problem_directory_file(new, self.custom_checker.name)
self.custom_checker.name = _problem_directory_file(
new, self.custom_checker.name
)
if self.custom_checker:
self.custom_checker.name = _problem_directory_file(new, self.custom_checker.name)
self.custom_checker.name = _problem_directory_file(
new, self.custom_checker.name
)
if self.custom_validator:
self.custom_validator.name = _problem_directory_file(new, self.custom_validator.name)
self.custom_validator.name = _problem_directory_file(
new, self.custom_validator.name
)
self.save()
_update_code.alters_data = True
class ProblemTestCase(models.Model):
dataset = models.ForeignKey('Problem', verbose_name=_('problem data set'), related_name='cases',
on_delete=models.CASCADE)
order = models.IntegerField(verbose_name=_('case position'))
type = models.CharField(max_length=1, verbose_name=_('case type'),
choices=(('C', _('Normal case')),
('S', _('Batch start')),
('E', _('Batch end'))),
default='C')
input_file = models.CharField(max_length=100, verbose_name=_('input file name'), blank=True)
output_file = models.CharField(max_length=100, verbose_name=_('output file name'), blank=True)
generator_args = models.TextField(verbose_name=_('generator arguments'), blank=True)
points = models.IntegerField(verbose_name=_('point value'), blank=True, null=True)
is_pretest = models.BooleanField(verbose_name=_('case is pretest?'))
output_prefix = models.IntegerField(verbose_name=_('output prefix length'), blank=True, null=True)
output_limit = models.IntegerField(verbose_name=_('output limit length'), blank=True, null=True)
checker = models.CharField(max_length=10, verbose_name=_('checker'), choices=CHECKERS, blank=True)
checker_args = models.TextField(verbose_name=_('checker arguments'), blank=True,
help_text=_('checker arguments as a JSON object'))
dataset = models.ForeignKey(
"Problem",
verbose_name=_("problem data set"),
related_name="cases",
on_delete=models.CASCADE,
)
order = models.IntegerField(verbose_name=_("case position"))
type = models.CharField(
max_length=1,
verbose_name=_("case type"),
choices=(
("C", _("Normal case")),
("S", _("Batch start")),
("E", _("Batch end")),
),
default="C",
)
input_file = models.CharField(
max_length=100, verbose_name=_("input file name"), blank=True
)
output_file = models.CharField(
max_length=100, verbose_name=_("output file name"), blank=True
)
generator_args = models.TextField(verbose_name=_("generator arguments"), blank=True)
points = models.IntegerField(verbose_name=_("point value"), blank=True, null=True)
is_pretest = models.BooleanField(verbose_name=_("case is pretest?"))
output_prefix = models.IntegerField(
verbose_name=_("output prefix length"), blank=True, null=True
)
output_limit = models.IntegerField(
verbose_name=_("output limit length"), blank=True, null=True
)
checker = models.CharField(
max_length=10, verbose_name=_("checker"), choices=CHECKERS, blank=True
)
checker_args = models.TextField(
verbose_name=_("checker arguments"),
blank=True,
help_text=_("checker arguments as a JSON object"),
)

View file

@ -16,7 +16,7 @@ from judge.models.choices import ACE_THEMES, MATH_ENGINES_CHOICES, TIMEZONE
from judge.models.runtime import Language
from judge.ratings import rating_class
__all__ = ['Organization', 'Profile', 'OrganizationRequest', 'Friend']
__all__ = ["Organization", "Profile", "OrganizationRequest", "Friend"]
class EncryptedNullCharField(EncryptedCharField):
@ -27,28 +27,65 @@ class EncryptedNullCharField(EncryptedCharField):
class Organization(models.Model):
name = models.CharField(max_length=128, verbose_name=_('organization title'))
slug = models.SlugField(max_length=128, verbose_name=_('organization slug'),
help_text=_('Organization name shown in URL'))
short_name = models.CharField(max_length=20, verbose_name=_('short name'),
help_text=_('Displayed beside user name during contests'))
about = models.TextField(verbose_name=_('organization description'))
registrant = models.ForeignKey('Profile', verbose_name=_('registrant'), on_delete=models.CASCADE,
related_name='registrant+', help_text=_('User who registered this organization'))
admins = models.ManyToManyField('Profile', verbose_name=_('administrators'), related_name='admin_of',
help_text=_('Those who can edit this organization'))
creation_date = models.DateTimeField(verbose_name=_('creation date'), auto_now_add=True)
is_open = models.BooleanField(verbose_name=_('is open organization?'),
help_text=_('Allow joining organization'), default=True)
slots = models.IntegerField(verbose_name=_('maximum size'), null=True, blank=True,
help_text=_('Maximum amount of users in this organization, '
'only applicable to private organizations'))
access_code = models.CharField(max_length=7, help_text=_('Student access code'),
verbose_name=_('access code'), null=True, 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 '
'viewing the organization.'))
name = models.CharField(max_length=128, verbose_name=_("organization title"))
slug = models.SlugField(
max_length=128,
verbose_name=_("organization slug"),
help_text=_("Organization name shown in URL"),
)
short_name = models.CharField(
max_length=20,
verbose_name=_("short name"),
help_text=_("Displayed beside user name during contests"),
)
about = models.TextField(verbose_name=_("organization description"))
registrant = models.ForeignKey(
"Profile",
verbose_name=_("registrant"),
on_delete=models.CASCADE,
related_name="registrant+",
help_text=_("User who registered this organization"),
)
admins = models.ManyToManyField(
"Profile",
verbose_name=_("administrators"),
related_name="admin_of",
help_text=_("Those who can edit this organization"),
)
creation_date = models.DateTimeField(
verbose_name=_("creation date"), auto_now_add=True
)
is_open = models.BooleanField(
verbose_name=_("is open organization?"),
help_text=_("Allow joining organization"),
default=True,
)
slots = models.IntegerField(
verbose_name=_("maximum size"),
null=True,
blank=True,
help_text=_(
"Maximum amount of users in this organization, "
"only applicable to private organizations"
),
)
access_code = models.CharField(
max_length=7,
help_text=_("Student access code"),
verbose_name=_("access code"),
null=True,
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 "
"viewing the organization."
),
)
def __contains__(self, item):
if isinstance(item, int):
@ -56,69 +93,128 @@ class Organization(models.Model):
elif isinstance(item, Profile):
return self.members.filter(id=item.id).exists()
else:
raise TypeError('Organization membership test must be Profile or primany key')
raise TypeError(
"Organization membership test must be Profile or primany key"
)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('organization_home', args=(self.id, self.slug))
return reverse("organization_home", args=(self.id, self.slug))
def get_users_url(self):
return reverse('organization_users', args=(self.id, self.slug))
return reverse("organization_users", args=(self.id, self.slug))
class Meta:
ordering = ['name']
ordering = ["name"]
permissions = (
('organization_admin', 'Administer organizations'),
('edit_all_organization', 'Edit all organizations'),
("organization_admin", "Administer organizations"),
("edit_all_organization", "Edit all organizations"),
)
verbose_name = _('organization')
verbose_name_plural = _('organizations')
verbose_name = _("organization")
verbose_name_plural = _("organizations")
class Profile(models.Model):
user = models.OneToOneField(User, verbose_name=_('user associated'), on_delete=models.CASCADE)
about = models.TextField(verbose_name=_('self-description'), null=True, blank=True)
timezone = models.CharField(max_length=50, verbose_name=_('location'), choices=TIMEZONE,
default=settings.DEFAULT_USER_TIME_ZONE)
language = models.ForeignKey('Language', verbose_name=_('preferred language'), on_delete=models.SET_DEFAULT,
default=Language.get_default_language_pk)
user = models.OneToOneField(
User, verbose_name=_("user associated"), on_delete=models.CASCADE
)
about = models.TextField(verbose_name=_("self-description"), null=True, blank=True)
timezone = models.CharField(
max_length=50,
verbose_name=_("location"),
choices=TIMEZONE,
default=settings.DEFAULT_USER_TIME_ZONE,
)
language = models.ForeignKey(
"Language",
verbose_name=_("preferred language"),
on_delete=models.SET_DEFAULT,
default=Language.get_default_language_pk,
)
points = models.FloatField(default=0, db_index=True)
performance_points = models.FloatField(default=0, db_index=True)
problem_count = models.IntegerField(default=0, db_index=True)
ace_theme = models.CharField(max_length=30, choices=ACE_THEMES, default='github')
last_access = models.DateTimeField(verbose_name=_('last access time'), default=now)
ip = models.GenericIPAddressField(verbose_name=_('last IP'), blank=True, null=True)
organizations = SortedManyToManyField(Organization, verbose_name=_('organization'), blank=True,
related_name='members', related_query_name='member')
display_rank = models.CharField(max_length=10, default='user', verbose_name=_('display rank'),
choices=(('user', 'Normal User'), ('setter', 'Problem Setter'), ('admin', 'Admin')))
mute = models.BooleanField(verbose_name=_('comment mute'), help_text=_('Some users are at their best when silent.'),
default=False)
is_unlisted = models.BooleanField(verbose_name=_('unlisted user'), help_text=_('User will not be ranked.'),
default=False)
ace_theme = models.CharField(max_length=30, choices=ACE_THEMES, default="github")
last_access = models.DateTimeField(verbose_name=_("last access time"), default=now)
ip = models.GenericIPAddressField(verbose_name=_("last IP"), blank=True, null=True)
organizations = SortedManyToManyField(
Organization,
verbose_name=_("organization"),
blank=True,
related_name="members",
related_query_name="member",
)
display_rank = models.CharField(
max_length=10,
default="user",
verbose_name=_("display rank"),
choices=(
("user", "Normal User"),
("setter", "Problem Setter"),
("admin", "Admin"),
),
)
mute = models.BooleanField(
verbose_name=_("comment mute"),
help_text=_("Some users are at their best when silent."),
default=False,
)
is_unlisted = models.BooleanField(
verbose_name=_("unlisted user"),
help_text=_("User will not be ranked."),
default=False,
)
is_banned_problem_voting = models.BooleanField(
verbose_name=_('banned from voting'),
verbose_name=_("banned from voting"),
help_text=_("User will not be able to vote on problems' point values."),
default=False,
)
rating = models.IntegerField(null=True, default=None)
user_script = models.TextField(verbose_name=_('user script'), default='', blank=True, max_length=65536,
help_text=_('User-defined JavaScript for site customization.'))
current_contest = models.OneToOneField('ContestParticipation', verbose_name=_('current contest'),
null=True, blank=True, related_name='+', on_delete=models.SET_NULL)
math_engine = models.CharField(verbose_name=_('math engine'), choices=MATH_ENGINES_CHOICES, max_length=4,
default=settings.MATHOID_DEFAULT_TYPE,
help_text=_('the rendering engine used to render math'))
is_totp_enabled = models.BooleanField(verbose_name=_('2FA enabled'), default=False,
help_text=_('check to enable TOTP-based two factor authentication'))
totp_key = EncryptedNullCharField(max_length=32, null=True, blank=True, verbose_name=_('TOTP key'),
help_text=_('32 character base32-encoded key for TOTP'),
validators=[RegexValidator('^$|^[A-Z2-7]{32}$',
_('TOTP key must be empty or base32'))])
notes = models.TextField(verbose_name=_('internal notes'), null=True, blank=True,
help_text=_('Notes for administrators regarding this user.'))
user_script = models.TextField(
verbose_name=_("user script"),
default="",
blank=True,
max_length=65536,
help_text=_("User-defined JavaScript for site customization."),
)
current_contest = models.OneToOneField(
"ContestParticipation",
verbose_name=_("current contest"),
null=True,
blank=True,
related_name="+",
on_delete=models.SET_NULL,
)
math_engine = models.CharField(
verbose_name=_("math engine"),
choices=MATH_ENGINES_CHOICES,
max_length=4,
default=settings.MATHOID_DEFAULT_TYPE,
help_text=_("the rendering engine used to render math"),
)
is_totp_enabled = models.BooleanField(
verbose_name=_("2FA enabled"),
default=False,
help_text=_("check to enable TOTP-based two factor authentication"),
)
totp_key = EncryptedNullCharField(
max_length=32,
null=True,
blank=True,
verbose_name=_("TOTP key"),
help_text=_("32 character base32-encoded key for TOTP"),
validators=[
RegexValidator("^$|^[A-Z2-7]{32}$", _("TOTP key must be empty or base32"))
],
)
notes = models.TextField(
verbose_name=_("internal notes"),
null=True,
blank=True,
help_text=_("Notes for administrators regarding this user."),
)
@cached_property
def organization(self):
@ -133,33 +229,45 @@ class Profile(models.Model):
@cached_property
def count_unseen_notifications(self):
query = {
'read': False,
"read": False,
}
return self.notifications.filter(**query).count()
_pp_table = [pow(settings.DMOJ_PP_STEP, i) for i in range(settings.DMOJ_PP_ENTRIES)]
def calculate_points(self, table=_pp_table):
from judge.models import Problem
public_problems = Problem.get_public_problems()
data = (
public_problems.filter(submission__user=self, submission__points__isnull=False)
.annotate(max_points=Max('submission__points')).order_by('-max_points')
.values_list('max_points', flat=True).filter(max_points__gt=0)
public_problems.filter(
submission__user=self, submission__points__isnull=False
)
.annotate(max_points=Max("submission__points"))
.order_by("-max_points")
.values_list("max_points", flat=True)
.filter(max_points__gt=0)
)
extradata = (
public_problems.filter(submission__user=self, submission__result='AC').values('id').distinct().count()
public_problems.filter(submission__user=self, submission__result="AC")
.values("id")
.distinct()
.count()
)
bonus_function = settings.DMOJ_PP_BONUS_FUNCTION
points = sum(data)
problems = len(data)
entries = min(len(data), len(table))
pp = sum(map(mul, table[:entries], data[:entries])) + bonus_function(extradata)
if self.points != points or problems != self.problem_count or self.performance_points != pp:
if (
self.points != points
or problems != self.problem_count
or self.performance_points != pp
):
self.points = points
self.problem_count = problems
self.performance_points = pp
self.save(update_fields=['points', 'problem_count', 'performance_points'])
self.save(update_fields=["points", "problem_count", "performance_points"])
return points
calculate_points.alters_data = True
@ -172,9 +280,12 @@ class Profile(models.Model):
def update_contest(self):
from judge.models import ContestParticipation
try:
contest = self.current_contest
if contest is not None and (contest.ended or not contest.contest.is_accessible_by(self.user)):
if contest is not None and (
contest.ended or not contest.contest.is_accessible_by(self.user)
):
self.remove_contest()
except ContestParticipation.DoesNotExist:
self.remove_contest()
@ -182,86 +293,105 @@ class Profile(models.Model):
update_contest.alters_data = True
def get_absolute_url(self):
return reverse('user_page', args=(self.user.username,))
return reverse("user_page", args=(self.user.username,))
def __str__(self):
return self.user.username
@classmethod
def get_user_css_class(cls, display_rank, rating, rating_colors=settings.DMOJ_RATING_COLORS):
def get_user_css_class(
cls, display_rank, rating, rating_colors=settings.DMOJ_RATING_COLORS
):
if rating_colors:
return 'rating %s %s' % (rating_class(rating) if rating is not None else 'rate-none', display_rank)
return "rating %s %s" % (
rating_class(rating) if rating is not None else "rate-none",
display_rank,
)
return display_rank
@cached_property
def css_class(self):
return self.get_user_css_class(self.display_rank, self.rating)
def get_friends(self): #list of usernames, including you
def get_friends(self): # list of usernames, including you
friend_obj = self.following_users.all()
ret = set()
if (friend_obj):
if friend_obj:
ret = set(friend.username for friend in friend_obj[0].users.all())
ret.add(self.username)
return ret
class Meta:
permissions = (
('test_site', 'Shows in-progress development stuff'),
('totp', 'Edit TOTP settings'),
("test_site", "Shows in-progress development stuff"),
("totp", "Edit TOTP settings"),
)
verbose_name = _('user profile')
verbose_name_plural = _('user profiles')
verbose_name = _("user profile")
verbose_name_plural = _("user profiles")
class OrganizationRequest(models.Model):
user = models.ForeignKey(Profile, verbose_name=_('user'), related_name='requests', on_delete=models.CASCADE)
organization = models.ForeignKey(Organization, verbose_name=_('organization'), related_name='requests',
on_delete=models.CASCADE)
time = models.DateTimeField(verbose_name=_('request time'), auto_now_add=True)
state = models.CharField(max_length=1, verbose_name=_('state'), choices=(
('P', 'Pending'),
('A', 'Approved'),
('R', 'Rejected'),
))
reason = models.TextField(verbose_name=_('reason'))
user = models.ForeignKey(
Profile,
verbose_name=_("user"),
related_name="requests",
on_delete=models.CASCADE,
)
organization = models.ForeignKey(
Organization,
verbose_name=_("organization"),
related_name="requests",
on_delete=models.CASCADE,
)
time = models.DateTimeField(verbose_name=_("request time"), auto_now_add=True)
state = models.CharField(
max_length=1,
verbose_name=_("state"),
choices=(
("P", "Pending"),
("A", "Approved"),
("R", "Rejected"),
),
)
reason = models.TextField(verbose_name=_("reason"))
class Meta:
verbose_name = _('organization join request')
verbose_name_plural = _('organization join requests')
verbose_name = _("organization join request")
verbose_name_plural = _("organization join requests")
class Friend(models.Model):
users = models.ManyToManyField(Profile)
current_user = models.ForeignKey(Profile, related_name="following_users", on_delete=CASCADE)
current_user = models.ForeignKey(
Profile, related_name="following_users", on_delete=CASCADE
)
@classmethod
def is_friend(self, current_user, new_friend):
try:
return current_user.following_users.get().users \
.filter(user=new_friend.user).exists()
return (
current_user.following_users.get()
.users.filter(user=new_friend.user)
.exists()
)
except:
return False
@classmethod
def make_friend(self, current_user, new_friend):
friend, created = self.objects.get_or_create(
current_user = current_user
)
friend, created = self.objects.get_or_create(current_user=current_user)
friend.users.add(new_friend)
@classmethod
def remove_friend(self, current_user, new_friend):
friend, created = self.objects.get_or_create(
current_user = current_user
)
friend, created = self.objects.get_or_create(current_user=current_user)
friend.users.remove(new_friend)
@classmethod
def toggle_friend(self, current_user, new_friend):
if (self.is_friend(current_user, new_friend)):
if self.is_friend(current_user, new_friend):
self.remove_friend(current_user, new_friend)
else:
self.make_friend(current_user, new_friend)

View file

@ -12,38 +12,82 @@ from django.utils.translation import gettext_lazy as _
from judge.judgeapi import disconnect_judge
__all__ = ['Language', 'RuntimeVersion', 'Judge']
__all__ = ["Language", "RuntimeVersion", "Judge"]
class Language(models.Model):
key = models.CharField(max_length=6, verbose_name=_('short identifier'),
help_text=_('The identifier for this language; the same as its executor id for judges.'),
unique=True)
name = models.CharField(max_length=20, verbose_name=_('long name'),
help_text=_('Longer name for the language, e.g. "Python 2" or "C++11".'))
short_name = models.CharField(max_length=10, verbose_name=_('short name'),
help_text=_('More readable, but short, name to display publicly; e.g. "PY2" or '
'"C++11". If left blank, it will default to the '
'short identifier.'),
null=True, blank=True)
common_name = models.CharField(max_length=10, verbose_name=_('common name'),
help_text=_('Common name for the language. For example, the common name for C++03, '
'C++11, and C++14 would be "C++"'))
ace = models.CharField(max_length=20, verbose_name=_('ace mode name'),
help_text=_('Language ID for Ace.js editor highlighting, appended to "mode-" to determine '
'the Ace JavaScript file to use, e.g., "python".'))
pygments = models.CharField(max_length=20, verbose_name=_('pygments name'),
help_text=_('Language ID for Pygments highlighting in source windows.'))
template = models.TextField(verbose_name=_('code template'),
help_text=_('Code template to display in submission editor.'), blank=True)
info = models.CharField(max_length=50, verbose_name=_('runtime info override'), blank=True,
help_text=_("Do not set this unless you know what you're doing! It will override the "
"usually more specific, judge-provided runtime info!"))
description = models.TextField(verbose_name=_('language description'),
help_text=_('Use this field to inform users of quirks with your environment, '
'additional restrictions, etc.'), blank=True)
extension = models.CharField(max_length=10, verbose_name=_('extension'),
help_text=_('The extension of source files, e.g., "py" or "cpp".'))
key = models.CharField(
max_length=6,
verbose_name=_("short identifier"),
help_text=_(
"The identifier for this language; the same as its executor id for judges."
),
unique=True,
)
name = models.CharField(
max_length=20,
verbose_name=_("long name"),
help_text=_('Longer name for the language, e.g. "Python 2" or "C++11".'),
)
short_name = models.CharField(
max_length=10,
verbose_name=_("short name"),
help_text=_(
'More readable, but short, name to display publicly; e.g. "PY2" or '
'"C++11". If left blank, it will default to the '
"short identifier."
),
null=True,
blank=True,
)
common_name = models.CharField(
max_length=10,
verbose_name=_("common name"),
help_text=_(
"Common name for the language. For example, the common name for C++03, "
'C++11, and C++14 would be "C++"'
),
)
ace = models.CharField(
max_length=20,
verbose_name=_("ace mode name"),
help_text=_(
'Language ID for Ace.js editor highlighting, appended to "mode-" to determine '
'the Ace JavaScript file to use, e.g., "python".'
),
)
pygments = models.CharField(
max_length=20,
verbose_name=_("pygments name"),
help_text=_("Language ID for Pygments highlighting in source windows."),
)
template = models.TextField(
verbose_name=_("code template"),
help_text=_("Code template to display in submission editor."),
blank=True,
)
info = models.CharField(
max_length=50,
verbose_name=_("runtime info override"),
blank=True,
help_text=_(
"Do not set this unless you know what you're doing! It will override the "
"usually more specific, judge-provided runtime info!"
),
)
description = models.TextField(
verbose_name=_("language description"),
help_text=_(
"Use this field to inform users of quirks with your environment, "
"additional restrictions, etc."
),
blank=True,
)
extension = models.CharField(
max_length=10,
verbose_name=_("extension"),
help_text=_('The extension of source files, e.g., "py" or "cpp".'),
)
def runtime_versions(self):
runtimes = OrderedDict()
@ -52,25 +96,29 @@ class Language(models.Model):
id = runtime.name
if id not in runtimes:
runtimes[id] = set()
if not runtime.version: # empty str == error determining version on judge side
if (
not runtime.version
): # empty str == error determining version on judge side
continue
runtimes[id].add(runtime.version)
lang_versions = []
for id, version_list in runtimes.items():
lang_versions.append((id, sorted(version_list, key=lambda a: tuple(map(int, a.split('.'))))))
lang_versions.append(
(id, sorted(version_list, key=lambda a: tuple(map(int, a.split(".")))))
)
return lang_versions
@classmethod
def get_common_name_map(cls):
result = cache.get('lang:cn_map')
result = cache.get("lang:cn_map")
if result is not None:
return result
result = defaultdict(set)
for id, cn in Language.objects.values_list('id', 'common_name'):
for id, cn in Language.objects.values_list("id", "common_name"):
result[cn].add(id)
result = {id: cns for id, cns in result.items() if len(cns) > 1}
cache.set('lang:cn_map', result, 86400)
cache.set("lang:cn_map", result, 86400)
return result
@cached_property
@ -83,17 +131,19 @@ class Language(models.Model):
@cached_property
def display_name(self):
if self.info:
return '%s (%s)' % (self.name, self.info)
return "%s (%s)" % (self.name, self.info)
else:
return self.name
@classmethod
def get_python3(cls):
# We really need a default language, and this app is in Python 3
return Language.objects.get_or_create(key='PY3', defaults={'name': 'Python 3'})[0]
return Language.objects.get_or_create(key="PY3", defaults={"name": "Python 3"})[
0
]
def get_absolute_url(self):
return reverse('runtime_list') + '#' + self.key
return reverse("runtime_list") + "#" + self.key
@classmethod
def get_default_language(cls):
@ -107,36 +157,67 @@ class Language(models.Model):
return cls.get_default_language().pk
class Meta:
ordering = ['key']
verbose_name = _('language')
verbose_name_plural = _('languages')
ordering = ["key"]
verbose_name = _("language")
verbose_name_plural = _("languages")
class RuntimeVersion(models.Model):
language = models.ForeignKey(Language, verbose_name=_('language to which this runtime belongs'), on_delete=CASCADE)
judge = models.ForeignKey('Judge', verbose_name=_('judge on which this runtime exists'), on_delete=CASCADE)
name = models.CharField(max_length=64, verbose_name=_('runtime name'))
version = models.CharField(max_length=64, verbose_name=_('runtime version'), blank=True)
priority = models.IntegerField(verbose_name=_('order in which to display this runtime'), default=0)
language = models.ForeignKey(
Language,
verbose_name=_("language to which this runtime belongs"),
on_delete=CASCADE,
)
judge = models.ForeignKey(
"Judge", verbose_name=_("judge on which this runtime exists"), on_delete=CASCADE
)
name = models.CharField(max_length=64, verbose_name=_("runtime name"))
version = models.CharField(
max_length=64, verbose_name=_("runtime version"), blank=True
)
priority = models.IntegerField(
verbose_name=_("order in which to display this runtime"), default=0
)
class Judge(models.Model):
name = models.CharField(max_length=50, help_text=_('Server name, hostname-style'), unique=True)
created = models.DateTimeField(auto_now_add=True, verbose_name=_('time of creation'))
auth_key = models.CharField(max_length=100, help_text=_('A key to authenticate this judge'),
verbose_name=_('authentication key'))
is_blocked = models.BooleanField(verbose_name=_('block judge'), default=False,
help_text=_('Whether this judge should be blocked from connecting, '
'even if its key is correct.'))
online = models.BooleanField(verbose_name=_('judge online status'), default=False)
start_time = models.DateTimeField(verbose_name=_('judge start time'), null=True)
ping = models.FloatField(verbose_name=_('response time'), null=True)
load = models.FloatField(verbose_name=_('system load'), null=True,
help_text=_('Load for the last minute, divided by processors to be fair.'))
description = models.TextField(blank=True, verbose_name=_('description'))
last_ip = models.GenericIPAddressField(verbose_name='Last connected IP', blank=True, null=True)
problems = models.ManyToManyField('Problem', verbose_name=_('problems'), related_name='judges')
runtimes = models.ManyToManyField(Language, verbose_name=_('judges'), related_name='judges')
name = models.CharField(
max_length=50, help_text=_("Server name, hostname-style"), unique=True
)
created = models.DateTimeField(
auto_now_add=True, verbose_name=_("time of creation")
)
auth_key = models.CharField(
max_length=100,
help_text=_("A key to authenticate this judge"),
verbose_name=_("authentication key"),
)
is_blocked = models.BooleanField(
verbose_name=_("block judge"),
default=False,
help_text=_(
"Whether this judge should be blocked from connecting, "
"even if its key is correct."
),
)
online = models.BooleanField(verbose_name=_("judge online status"), default=False)
start_time = models.DateTimeField(verbose_name=_("judge start time"), null=True)
ping = models.FloatField(verbose_name=_("response time"), null=True)
load = models.FloatField(
verbose_name=_("system load"),
null=True,
help_text=_("Load for the last minute, divided by processors to be fair."),
)
description = models.TextField(blank=True, verbose_name=_("description"))
last_ip = models.GenericIPAddressField(
verbose_name="Last connected IP", blank=True, null=True
)
problems = models.ManyToManyField(
"Problem", verbose_name=_("problems"), related_name="judges"
)
runtimes = models.ManyToManyField(
Language, verbose_name=_("judges"), related_name="judges"
)
def __str__(self):
return self.name
@ -148,22 +229,23 @@ class Judge(models.Model):
@cached_property
def runtime_versions(self):
qs = (self.runtimeversion_set.values('language__key', 'language__name', 'version', 'name')
.order_by('language__key', 'priority'))
qs = self.runtimeversion_set.values(
"language__key", "language__name", "version", "name"
).order_by("language__key", "priority")
ret = OrderedDict()
for data in qs:
key = data['language__key']
key = data["language__key"]
if key not in ret:
ret[key] = {'name': data['language__name'], 'runtime': []}
ret[key]['runtime'].append((data['name'], (data['version'],)))
ret[key] = {"name": data["language__name"], "runtime": []}
ret[key]["runtime"].append((data["name"], (data["version"],)))
return list(ret.items())
@cached_property
def uptime(self):
return timezone.now() - self.start_time if self.online else 'N/A'
return timezone.now() - self.start_time if self.online else "N/A"
@cached_property
def ping_ms(self):
@ -171,9 +253,9 @@ class Judge(models.Model):
@cached_property
def runtime_list(self):
return map(attrgetter('name'), self.runtimes.all())
return map(attrgetter("name"), self.runtimes.all())
class Meta:
ordering = ['name']
verbose_name = _('judge')
verbose_name_plural = _('judges')
ordering = ["name"]
verbose_name = _("judge")
verbose_name_plural = _("judges")

View file

@ -14,92 +14,130 @@ from judge.models.profile import Profile
from judge.models.runtime import Language
from judge.utils.unicode import utf8bytes
__all__ = ['SUBMISSION_RESULT', 'Submission', 'SubmissionSource', 'SubmissionTestCase']
__all__ = ["SUBMISSION_RESULT", "Submission", "SubmissionSource", "SubmissionTestCase"]
SUBMISSION_RESULT = (
('AC', _('Accepted')),
('WA', _('Wrong Answer')),
('TLE', _('Time Limit Exceeded')),
('MLE', _('Memory Limit Exceeded')),
('OLE', _('Output Limit Exceeded')),
('IR', _('Invalid Return')),
('RTE', _('Runtime Error')),
('CE', _('Compile Error')),
('IE', _('Internal Error')),
('SC', _('Short circuit')),
('AB', _('Aborted')),
("AC", _("Accepted")),
("WA", _("Wrong Answer")),
("TLE", _("Time Limit Exceeded")),
("MLE", _("Memory Limit Exceeded")),
("OLE", _("Output Limit Exceeded")),
("IR", _("Invalid Return")),
("RTE", _("Runtime Error")),
("CE", _("Compile Error")),
("IE", _("Internal Error")),
("SC", _("Short circuit")),
("AB", _("Aborted")),
)
class Submission(models.Model):
STATUS = (
('QU', _('Queued')),
('P', _('Processing')),
('G', _('Grading')),
('D', _('Completed')),
('IE', _('Internal Error')),
('CE', _('Compile Error')),
('AB', _('Aborted')),
("QU", _("Queued")),
("P", _("Processing")),
("G", _("Grading")),
("D", _("Completed")),
("IE", _("Internal Error")),
("CE", _("Compile Error")),
("AB", _("Aborted")),
)
IN_PROGRESS_GRADING_STATUS = ('QU', 'P', 'G')
IN_PROGRESS_GRADING_STATUS = ("QU", "P", "G")
RESULT = SUBMISSION_RESULT
USER_DISPLAY_CODES = {
'AC': _('Accepted'),
'WA': _('Wrong Answer'),
'SC': "Short Circuited",
'TLE': _('Time Limit Exceeded'),
'MLE': _('Memory Limit Exceeded'),
'OLE': _('Output Limit Exceeded'),
'IR': _('Invalid Return'),
'RTE': _('Runtime Error'),
'CE': _('Compile Error'),
'IE': _('Internal Error (judging server error)'),
'QU': _('Queued'),
'P': _('Processing'),
'G': _('Grading'),
'D': _('Completed'),
'AB': _('Aborted'),
"AC": _("Accepted"),
"WA": _("Wrong Answer"),
"SC": "Short Circuited",
"TLE": _("Time Limit Exceeded"),
"MLE": _("Memory Limit Exceeded"),
"OLE": _("Output Limit Exceeded"),
"IR": _("Invalid Return"),
"RTE": _("Runtime Error"),
"CE": _("Compile Error"),
"IE": _("Internal Error (judging server error)"),
"QU": _("Queued"),
"P": _("Processing"),
"G": _("Grading"),
"D": _("Completed"),
"AB": _("Aborted"),
}
user = models.ForeignKey(Profile, on_delete=models.CASCADE)
problem = models.ForeignKey(Problem, on_delete=models.CASCADE)
date = models.DateTimeField(verbose_name=_('submission time'), auto_now_add=True, db_index=True)
time = models.FloatField(verbose_name=_('execution time'), null=True, db_index=True)
memory = models.FloatField(verbose_name=_('memory usage'), null=True)
points = models.FloatField(verbose_name=_('points granted'), null=True, db_index=True)
language = models.ForeignKey(Language, verbose_name=_('submission language'), on_delete=models.CASCADE)
status = models.CharField(verbose_name=_('status'), max_length=2, choices=STATUS, default='QU', db_index=True)
result = models.CharField(verbose_name=_('result'), max_length=3, choices=SUBMISSION_RESULT,
default=None, null=True, blank=True, db_index=True)
error = models.TextField(verbose_name=_('compile errors'), null=True, blank=True)
date = models.DateTimeField(
verbose_name=_("submission time"), auto_now_add=True, db_index=True
)
time = models.FloatField(verbose_name=_("execution time"), null=True, db_index=True)
memory = models.FloatField(verbose_name=_("memory usage"), null=True)
points = models.FloatField(
verbose_name=_("points granted"), null=True, db_index=True
)
language = models.ForeignKey(
Language, verbose_name=_("submission language"), on_delete=models.CASCADE
)
status = models.CharField(
verbose_name=_("status"),
max_length=2,
choices=STATUS,
default="QU",
db_index=True,
)
result = models.CharField(
verbose_name=_("result"),
max_length=3,
choices=SUBMISSION_RESULT,
default=None,
null=True,
blank=True,
db_index=True,
)
error = models.TextField(verbose_name=_("compile errors"), null=True, blank=True)
current_testcase = models.IntegerField(default=0)
batch = models.BooleanField(verbose_name=_('batched cases'), default=False)
case_points = models.FloatField(verbose_name=_('test case points'), default=0)
case_total = models.FloatField(verbose_name=_('test case total points'), default=0)
judged_on = models.ForeignKey('Judge', verbose_name=_('judged on'), null=True, blank=True,
on_delete=models.SET_NULL)
judged_date = models.DateTimeField(verbose_name=_('submission judge time'), default=None, null=True)
was_rejudged = models.BooleanField(verbose_name=_('was rejudged by admin'), default=False)
is_pretested = models.BooleanField(verbose_name=_('was ran on pretests only'), default=False)
contest_object = models.ForeignKey('Contest', verbose_name=_('contest'), null=True, blank=True,
on_delete=models.SET_NULL, related_name='+')
batch = models.BooleanField(verbose_name=_("batched cases"), default=False)
case_points = models.FloatField(verbose_name=_("test case points"), default=0)
case_total = models.FloatField(verbose_name=_("test case total points"), default=0)
judged_on = models.ForeignKey(
"Judge",
verbose_name=_("judged on"),
null=True,
blank=True,
on_delete=models.SET_NULL,
)
judged_date = models.DateTimeField(
verbose_name=_("submission judge time"), default=None, null=True
)
was_rejudged = models.BooleanField(
verbose_name=_("was rejudged by admin"), default=False
)
is_pretested = models.BooleanField(
verbose_name=_("was ran on pretests only"), default=False
)
contest_object = models.ForeignKey(
"Contest",
verbose_name=_("contest"),
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
objects = TranslatedProblemForeignKeyQuerySet.as_manager()
@classmethod
def result_class_from_code(cls, result, case_points, case_total):
if result == 'AC':
if result == "AC":
if case_points == case_total:
return 'AC'
return '_AC'
return "AC"
return "_AC"
return result
@property
def result_class(self):
# This exists to save all these conditionals from being executed (slowly) in each row.jade template
if self.status in ('IE', 'CE'):
if self.status in ("IE", "CE"):
return self.status
return Submission.result_class_from_code(self.result, self.case_points, self.case_total)
return Submission.result_class_from_code(
self.result, self.case_points, self.case_total
)
@property
def memory_bytes(self):
@ -111,12 +149,11 @@ class Submission(models.Model):
@property
def long_status(self):
return Submission.USER_DISPLAY_CODES.get(self.short_status, '')
return Submission.USER_DISPLAY_CODES.get(self.short_status, "")
def judge(self, *args, **kwargs):
judge_submission(self, *args, **kwargs)
judge.alters_data = True
def abort(self):
@ -131,8 +168,12 @@ class Submission(models.Model):
return
contest_problem = contest.problem
contest.points = round(self.case_points / self.case_total * contest_problem.points
if self.case_total > 0 else 0, 3)
contest.points = round(
self.case_points / self.case_total * contest_problem.points
if self.case_total > 0
else 0,
3,
)
if not contest_problem.partial and contest.points != contest_problem.points:
contest.points = 0
contest.save()
@ -142,18 +183,22 @@ class Submission(models.Model):
@property
def is_graded(self):
return self.status not in ('QU', 'P', 'G')
return self.status not in ("QU", "P", "G")
@cached_property
def contest_key(self):
if hasattr(self, 'contest'):
if hasattr(self, "contest"):
return self.contest_object.key
def __str__(self):
return 'Submission %d of %s by %s' % (self.id, self.problem, self.user.user.username)
return "Submission %d of %s by %s" % (
self.id,
self.problem,
self.user.user.username,
)
def get_absolute_url(self):
return reverse('submission_status', args=(self.id,))
return reverse("submission_status", args=(self.id,))
@cached_property
def contest_or_none(self):
@ -164,8 +209,14 @@ class Submission(models.Model):
@classmethod
def get_id_secret(cls, sub_id):
return (hmac.new(utf8bytes(settings.EVENT_DAEMON_SUBMISSION_KEY), b'%d' % sub_id, hashlib.sha512)
.hexdigest()[:16] + '%08x' % sub_id)
return (
hmac.new(
utf8bytes(settings.EVENT_DAEMON_SUBMISSION_KEY),
b"%d" % sub_id,
hashlib.sha512,
).hexdigest()[:16]
+ "%08x" % sub_id
)
@cached_property
def id_secret(self):
@ -173,47 +224,61 @@ class Submission(models.Model):
class Meta:
permissions = (
('abort_any_submission', 'Abort any submission'),
('rejudge_submission', 'Rejudge the submission'),
('rejudge_submission_lot', 'Rejudge a lot of submissions'),
('spam_submission', 'Submit without limit'),
('view_all_submission', 'View all submission'),
('resubmit_other', "Resubmit others' submission"),
("abort_any_submission", "Abort any submission"),
("rejudge_submission", "Rejudge the submission"),
("rejudge_submission_lot", "Rejudge a lot of submissions"),
("spam_submission", "Submit without limit"),
("view_all_submission", "View all submission"),
("resubmit_other", "Resubmit others' submission"),
)
verbose_name = _('submission')
verbose_name_plural = _('submissions')
verbose_name = _("submission")
verbose_name_plural = _("submissions")
class SubmissionSource(models.Model):
submission = models.OneToOneField(Submission, on_delete=models.CASCADE, verbose_name=_('associated submission'),
related_name='source')
source = models.TextField(verbose_name=_('source code'), max_length=65536)
submission = models.OneToOneField(
Submission,
on_delete=models.CASCADE,
verbose_name=_("associated submission"),
related_name="source",
)
source = models.TextField(verbose_name=_("source code"), max_length=65536)
def __str__(self):
return 'Source of %s' % self.submission
return "Source of %s" % self.submission
class SubmissionTestCase(models.Model):
RESULT = SUBMISSION_RESULT
submission = models.ForeignKey(Submission, verbose_name=_('associated submission'),
related_name='test_cases', on_delete=models.CASCADE)
case = models.IntegerField(verbose_name=_('test case ID'))
status = models.CharField(max_length=3, verbose_name=_('status flag'), choices=SUBMISSION_RESULT)
time = models.FloatField(verbose_name=_('execution time'), null=True)
memory = models.FloatField(verbose_name=_('memory usage'), null=True)
points = models.FloatField(verbose_name=_('points granted'), null=True)
total = models.FloatField(verbose_name=_('points possible'), null=True)
batch = models.IntegerField(verbose_name=_('batch number'), null=True)
feedback = models.CharField(max_length=50, verbose_name=_('judging feedback'), blank=True)
extended_feedback = models.TextField(verbose_name=_('extended judging feedback'), blank=True)
output = models.TextField(verbose_name=_('program output'), blank=True)
submission = models.ForeignKey(
Submission,
verbose_name=_("associated submission"),
related_name="test_cases",
on_delete=models.CASCADE,
)
case = models.IntegerField(verbose_name=_("test case ID"))
status = models.CharField(
max_length=3, verbose_name=_("status flag"), choices=SUBMISSION_RESULT
)
time = models.FloatField(verbose_name=_("execution time"), null=True)
memory = models.FloatField(verbose_name=_("memory usage"), null=True)
points = models.FloatField(verbose_name=_("points granted"), null=True)
total = models.FloatField(verbose_name=_("points possible"), null=True)
batch = models.IntegerField(verbose_name=_("batch number"), null=True)
feedback = models.CharField(
max_length=50, verbose_name=_("judging feedback"), blank=True
)
extended_feedback = models.TextField(
verbose_name=_("extended judging feedback"), blank=True
)
output = models.TextField(verbose_name=_("program output"), blank=True)
@property
def long_status(self):
return Submission.USER_DISPLAY_CODES.get(self.status, '')
return Submission.USER_DISPLAY_CODES.get(self.status, "")
class Meta:
unique_together = ('submission', 'case')
verbose_name = _('submission test case')
verbose_name_plural = _('submission test cases')
unique_together = ("submission", "case")
verbose_name = _("submission test case")
verbose_name_plural = _("submission test cases")

View file

@ -7,24 +7,43 @@ from judge.models.profile import Profile
class Ticket(models.Model):
title = models.CharField(max_length=100, verbose_name=_('ticket title'))
user = models.ForeignKey(Profile, verbose_name=_('ticket creator'), related_name='tickets',
on_delete=models.CASCADE)
time = models.DateTimeField(verbose_name=_('creation time'), auto_now_add=True)
assignees = models.ManyToManyField(Profile, verbose_name=_('assignees'), related_name='assigned_tickets')
notes = models.TextField(verbose_name=_('quick notes'), blank=True,
help_text=_('Staff notes for this issue to aid in processing.'))
content_type = models.ForeignKey(ContentType, verbose_name=_('linked item type'),
on_delete=models.CASCADE)
object_id = models.PositiveIntegerField(verbose_name=_('linked item ID'))
title = models.CharField(max_length=100, verbose_name=_("ticket title"))
user = models.ForeignKey(
Profile,
verbose_name=_("ticket creator"),
related_name="tickets",
on_delete=models.CASCADE,
)
time = models.DateTimeField(verbose_name=_("creation time"), auto_now_add=True)
assignees = models.ManyToManyField(
Profile, verbose_name=_("assignees"), related_name="assigned_tickets"
)
notes = models.TextField(
verbose_name=_("quick notes"),
blank=True,
help_text=_("Staff notes for this issue to aid in processing."),
)
content_type = models.ForeignKey(
ContentType, verbose_name=_("linked item type"), on_delete=models.CASCADE
)
object_id = models.PositiveIntegerField(verbose_name=_("linked item ID"))
linked_item = GenericForeignKey()
is_open = models.BooleanField(verbose_name=_('is ticket open?'), default=True)
is_open = models.BooleanField(verbose_name=_("is ticket open?"), default=True)
class TicketMessage(models.Model):
ticket = models.ForeignKey(Ticket, verbose_name=_('ticket'), related_name='messages',
related_query_name='message', on_delete=models.CASCADE)
user = models.ForeignKey(Profile, verbose_name=_('poster'), related_name='ticket_messages',
on_delete=models.CASCADE)
body = models.TextField(verbose_name=_('message body'))
time = models.DateTimeField(verbose_name=_('message time'), auto_now_add=True)
ticket = models.ForeignKey(
Ticket,
verbose_name=_("ticket"),
related_name="messages",
related_query_name="message",
on_delete=models.CASCADE,
)
user = models.ForeignKey(
Profile,
verbose_name=_("poster"),
related_name="ticket_messages",
on_delete=models.CASCADE,
)
body = models.TextField(verbose_name=_("message body"))
time = models.DateTimeField(verbose_name=_("message time"), auto_now_add=True)

View file

@ -4,25 +4,36 @@ from django.utils.translation import gettext_lazy as _
from judge.models import Profile, Problem, ProblemType
__all__ = ['VolunteerProblemVote']
__all__ = ["VolunteerProblemVote"]
class VolunteerProblemVote(models.Model):
voter = models.ForeignKey(Profile, related_name='volunteer_problem_votes', on_delete=CASCADE)
problem = models.ForeignKey(Problem, related_name='volunteer_user_votes', on_delete=CASCADE)
voter = models.ForeignKey(
Profile, related_name="volunteer_problem_votes", on_delete=CASCADE
)
problem = models.ForeignKey(
Problem, related_name="volunteer_user_votes", on_delete=CASCADE
)
time = models.DateTimeField(auto_now_add=True)
knowledge_points = models.PositiveIntegerField(verbose_name=_('knowledge points'),
help_text=_('Points awarded by knowledge difficulty'))
thinking_points = models.PositiveIntegerField(verbose_name=_('thinking points'),
help_text=_('Points awarded by thinking difficulty'))
types = models.ManyToManyField(ProblemType, verbose_name=_('problem types'),
help_text=_('The type of problem, '
"as shown on the problem's page."))
feedback = models.TextField(verbose_name=_('feedback'), blank=True)
knowledge_points = models.PositiveIntegerField(
verbose_name=_("knowledge points"),
help_text=_("Points awarded by knowledge difficulty"),
)
thinking_points = models.PositiveIntegerField(
verbose_name=_("thinking points"),
help_text=_("Points awarded by thinking difficulty"),
)
types = models.ManyToManyField(
ProblemType,
verbose_name=_("problem types"),
help_text=_("The type of problem, " "as shown on the problem's page."),
)
feedback = models.TextField(verbose_name=_("feedback"), blank=True)
class Meta:
verbose_name = _('volunteer vote')
verbose_name_plural = _('volunteer votes')
unique_together = ['voter', 'problem']
verbose_name = _("volunteer vote")
verbose_name_plural = _("volunteer votes")
unique_together = ["voter", "problem"]
def __str__(self):
return f'{self.voter} for {self.problem.code}'
return f"{self.voter} for {self.problem.code}"