From ce5ea027d2d1e3e732b640b36333b68ee919b08c Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Sun, 18 Jul 2021 20:22:44 -0500 Subject: [PATCH] Add live contest notification --- dmoj/urls.py | 2 + judge/models/problem.py | 22 +++++++++ judge/views/blog.py | 6 +++ judge/views/contests.py | 70 +++++++++++++++++++++++++-- judge/views/problem.py | 4 ++ logo.png | Bin 0 -> 13880 bytes resources/common.js | 4 ++ templates/blog/list.html | 19 +++++++- templates/contest/clarification.html | 54 +++++++++++++++++++++ templates/problem/list.html | 15 ++++++ templates/problem/problem.html | 38 +++++++++++++++ 11 files changed, 229 insertions(+), 5 deletions(-) create mode 100644 logo.png create mode 100644 templates/contest/clarification.html diff --git a/dmoj/urls.py b/dmoj/urls.py index 91ce3b2..0a73d5e 100644 --- a/dmoj/urls.py +++ b/dmoj/urls.py @@ -225,6 +225,8 @@ urlpatterns = [ url(r'^/participation/disqualify$', contests.ContestParticipationDisqualify.as_view(), name='contest_participation_disqualify'), + url(r'^/clarification$', contests.NewContestClarificationView.as_view(), name='new_contest_clarification'), + url(r'^/$', lambda _, contest: HttpResponsePermanentRedirect(reverse('contest_view', args=[contest]))), ])), diff --git a/judge/models/problem.py b/judge/models/problem.py index 4f7e545..e56b1c7 100644 --- a/judge/models/problem.py +++ b/judge/models/problem.py @@ -11,7 +11,9 @@ from django.db.models.functions import Coalesce from django.urls import reverse from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ +from django.utils import timezone +from judge import event_poster as event from judge.fulltext import SearchQuerySet from judge.models.profile import Organization, Profile from judge.models.runtime import Language @@ -407,6 +409,26 @@ class ProblemClarification(models.Model): description = models.TextField(verbose_name=_('clarification body')) date = models.DateTimeField(verbose_name=_('clarification timestamp'), auto_now_add=True) + def save(self, *args, **kwargs): + super(ProblemClarification, self).save(*args, **kwargs) + + if event.real: + from judge.models import ContestProblem + + now = timezone.now() + # List all ongoing contests containing this problem + contest_problems = ContestProblem.objects.filter( + contest__start_time__lte=now, + contest__end_time__gt=now, + problem=self.problem).values_list('order', 'contest') + + for order, contest_id in contest_problems.iterator(): + event.post('contest_clarification_' + str(contest_id), { + 'problem_label': order, + 'problem_name': self.problem.name, + 'problem_code': self.problem.code, + 'body': self.description + }) class LanguageLimit(models.Model): problem = models.ForeignKey(Problem, verbose_name=_('problem'), related_name='language_limits', on_delete=CASCADE) diff --git a/judge/views/blog.py b/judge/views/blog.py index 7c6cc11..07912b2 100644 --- a/judge/views/blog.py +++ b/judge/views/blog.py @@ -104,6 +104,12 @@ class PostList(ListView): context['open_tickets'] = filter_visible_tickets(tickets, self.request.user, profile)[:10] else: context['open_tickets'] = [] + + if self.request.in_contest: + if self.request.user.is_superuser or \ + self.request.profile in self.request.participation.contest.authors.all() or \ + self.request.profile in self.request.participation.contest.curators.all(): + context['can_edit_contest'] = True return context diff --git a/judge/views/contests.py b/judge/views/contests.py index a20117f..90dc3c2 100644 --- a/judge/views/contests.py +++ b/judge/views/contests.py @@ -18,10 +18,10 @@ from django.db.models.expressions import CombinedExpression from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect from django.shortcuts import get_object_or_404, render from django.template.defaultfilters import date as date_filter -from django.urls import reverse +from django.urls import reverse, reverse_lazy from django.utils import timezone from django.utils.functional import cached_property -from django.utils.html import format_html +from django.utils.html import format_html, escape from django.utils.safestring import mark_safe from django.utils.timezone import make_aware from django.utils.translation import gettext as _, gettext_lazy @@ -32,7 +32,7 @@ from judge import event_poster as event from judge.comments import CommentedDetailView from judge.forms import ContestCloneForm from judge.models import Contest, ContestMoss, ContestParticipation, ContestProblem, ContestTag, \ - Organization, Problem, Profile, Submission + Organization, Problem, Profile, Submission, ProblemClarification from judge.tasks import run_moss from judge.utils.celery import redirect_to_task_status from judge.utils.opengraph import generate_opengraph @@ -40,11 +40,12 @@ from judge.utils.problems import _get_result_data from judge.utils.ranker import ranker from judge.utils.stats import get_bar_chart, get_pie_chart, get_histogram from judge.utils.views import DiggPaginatorMixin, SingleObjectFormView, TitleMixin, generic_message +from judge.widgets import HeavyPreviewPageDownWidget __all__ = ['ContestList', 'ContestDetail', 'ContestRanking', 'ContestJoin', 'ContestLeave', 'ContestCalendar', 'ContestClone', 'ContestStats', 'ContestMossView', 'ContestMossDelete', 'contest_ranking_ajax', 'ContestParticipationList', 'ContestParticipationDisqualify', 'get_contest_ranking_list', - 'base_contest_ranking_list'] + 'base_contest_ranking_list', 'ContestClarificationView'] def _find_contest(request, key, private_check=True): @@ -854,3 +855,64 @@ class ContestTagDetail(TitleMixin, ContestTagDetailAjax): def get_title(self): return _('Contest tag: %s') % self.object.name + + +class ProblemClarificationForm(forms.Form): + body = forms.CharField(widget=HeavyPreviewPageDownWidget(preview=reverse_lazy('comment_preview'), + preview_timeout=1000, hide_preview_button=True)) + + def __init__(self, request, *args, **kwargs): + self.request = request + super(ProblemClarificationForm, self).__init__(*args, **kwargs) + self.fields['body'].widget.attrs.update({'placeholder': _('Issue description')}) + + +class NewContestClarificationView(ContestMixin, TitleMixin, SingleObjectFormView): + form_class = ProblemClarificationForm + template_name = 'contest/clarification.html' + + def get_form_kwargs(self): + kwargs = super(NewContestClarificationView, self).get_form_kwargs() + kwargs['request'] = self.request + return kwargs + + def is_accessible(self): + if not self.request.user.is_authenticated: + return False + if not self.request.in_contest: + return False + if not self.request.participation.contest == self.get_object(): + return False + return self.request.user.is_superuser or \ + self.request.profile in self.request.participation.contest.authors.all() or \ + self.request.profile in self.request.participation.contest.curators.all() + + def get(self, request, *args, **kwargs): + if not self.is_accessible(): + raise Http404() + return super().get(self, request, *args, **kwargs) + + def form_valid(self, form): + problem_code = self.request.POST['problem'] + description = form.cleaned_data['body'] + + clarification = ProblemClarification(description=description) + clarification.problem = Problem.objects.get(code=problem_code) + clarification.save() + + link = reverse('home') + return HttpResponseRedirect(link) + + def get_title(self): + return "New clarification for %s" % self.object.name + + def get_content_title(self): + return mark_safe(escape(_('New clarification for %s')) % + format_html('{1}', reverse('problem_detail', args=[self.object.key]), + self.object.name)) + + def get_context_data(self, **kwargs): + context = super(NewContestClarificationView, self).get_context_data(**kwargs) + context['problems'] = ContestProblem.objects.filter(contest=self.object)\ + .order_by('order') + return context \ No newline at end of file diff --git a/judge/views/problem.py b/judge/views/problem.py index 4c8e345..bfa6bb0 100644 --- a/judge/views/problem.py +++ b/judge/views/problem.py @@ -25,6 +25,7 @@ from django.views.generic import ListView, View from django.views.generic.base import TemplateResponseMixin from django.views.generic.detail import SingleObjectMixin +from judge import event_poster as event from judge.comments import CommentedDetailView from judge.forms import ProblemCloneForm, ProblemSubmitForm from judge.models import ContestProblem, ContestSubmission, Judge, Language, Problem, ProblemGroup, \ @@ -170,8 +171,10 @@ class ProblemDetail(ProblemMixin, SolvedProblemMixin, CommentedDetailView): contest_problem = (None if not authed or user.profile.current_contest is None else get_contest_problem(self.object, user.profile)) context['contest_problem'] = contest_problem + if contest_problem: clarifications = self.object.clarifications + context['last_msg'] = event.last() context['has_clarifications'] = clarifications.count() > 0 context['clarifications'] = clarifications.order_by('-date') context['submission_limit'] = contest_problem.max_submissions @@ -434,6 +437,7 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView context['hot_problems'] = hot_problems(timedelta(days=1), 7) context['point_start'], context['point_end'], context['point_values'] = self.get_noui_slider_points() else: + context['last_msg'] = event.last() context['hot_problems'] = None context['point_start'], context['point_end'], context['point_values'] = 0, 0, {} context['hide_contest_scoreboard'] = self.contest.scoreboard_visibility in \ diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4e1a813646f3219226bafec5777a04033c5b9873 GIT binary patch literal 13880 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 literal 0 HcmV?d00001 diff --git a/resources/common.js b/resources/common.js index 04e8ebe..d2955ab 100644 --- a/resources/common.js +++ b/resources/common.js @@ -322,6 +322,10 @@ window.register_notify = function (type, options) { status_change(); }; +window.notify_clarification = function(msg) { + var message = `Problem ${msg.problem_label} (${msg.problem_name}):\n` + msg.body; + alert(message); +} $(function () { // Close dismissable boxes diff --git a/templates/blog/list.html b/templates/blog/list.html index cf11c72..38ead62 100644 --- a/templates/blog/list.html +++ b/templates/blog/list.html @@ -48,6 +48,14 @@ h3 a { color: lightcyan; } + + #add-clarification { + float: left; + color: chartreuse; + } + #add-clarification:hover { + color: cyan; + } {% endblock %} @@ -104,7 +112,16 @@
{% if request.in_contest and request.participation.contest.use_clarifications %}