import datetime
import logging
from django import forms
from django.db.models import Q
from django.shortcuts import redirect
from django.template.response import TemplateResponse
from django.utils.translation import gettext_lazy as _
from oioioi.acm.controllers import ACMContestController
from oioioi.base.utils.query_helpers import Q_always_true
from oioioi.base.utils.redirect import safe_redirect
from oioioi.contests.utils import (
all_non_trial_public_results_visible,
is_contest_admin,
is_contest_observer,
)
from oioioi.pa.models import PAProblemInstanceData, PARegistration
from oioioi.pa.score import PAScore
from oioioi.participants.controllers import (
OnsiteContestControllerMixin,
ParticipantsController,
)
from oioioi.participants.models import Participant
from oioioi.participants.utils import is_participant
from oioioi.programs.controllers import ProgrammingContestController
from oioioi.rankings.controllers import CONTEST_RANKING_KEY, DefaultRankingController
[docs]auditLogger = logging.getLogger(__name__ + ".audit")
[docs]class PARegistrationController(ParticipantsController):
@property
@property
[docs] def participant_admin(self):
from oioioi.pa.admin import PARegistrationParticipantAdmin
return PARegistrationParticipantAdmin
@classmethod
[docs] def anonymous_can_enter_contest(self):
return True
[docs] def allow_login_as_public_name(self):
return True
# Redundant because of filter_visible_contests, but saves a db query
[docs] def can_enter_contest(self, request):
return True
[docs] def visible_contests_query(self, request):
return Q_always_true()
[docs] def can_register(self, request):
return super().is_registration_open(request)
[docs] def can_unregister(self, request, participant):
return False
[docs] def registration_view(self, request):
participant = self._get_participant_for_form(request)
if 'pa_paregistrationformdata' in request.session:
# pylint: disable=not-callable
form = self.form_class(request.session['pa_paregistrationformdata'])
del request.session['pa_paregistrationformdata']
else:
form = self.get_form(request, participant)
form.set_terms_accepted_text(self.get_terms_accepted_phrase())
if request.method == 'POST':
# pylint: disable=maybe-no-member
if form.is_valid():
participant, created = Participant.objects.get_or_create(
contest=self.contest, user=request.user
)
self.handle_validated_form(request, form, participant)
auditLogger.info(
"User %d (%s) registered in %s from IP %s UA: %s",
request.user.id,
request.user.username,
self.contest.id,
request.META.get('REMOTE_ADDR', '?'),
request.META.get('HTTP_USER_AGENT', '?'),
)
if 'next' in request.GET:
return safe_redirect(request, request.GET['next'])
else:
return redirect('default_contest_view', contest_id=self.contest.id)
context = {'form': form, 'participant': participant}
return TemplateResponse(request, self.registration_template, context)
[docs] def mixins_for_admin(self):
from oioioi.participants.admin import TermsAcceptedPhraseAdminMixin
return super(PARegistrationController, self).mixins_for_admin() + (
TermsAcceptedPhraseAdminMixin,
)
[docs] def can_change_terms_accepted_phrase(self, request):
return not PARegistration.objects.filter(
participant__contest=request.contest
).exists()
[docs]class PAContestController(ProgrammingContestController):
[docs] description = _("Algorithmic Engagements")
[docs] def fill_evaluation_environ(self, environ, submission):
environ['test_scorer'] = 'oioioi.pa.utils.pa_test_scorer'
super(PAContestController, self).fill_evaluation_environ(environ, submission)
[docs] def update_user_result_for_problem(self, result):
super(PAContestController, self).update_user_result_for_problem(result)
if result.score is not None:
result.score = PAScore(result.score)
[docs] def registration_controller(self):
return PARegistrationController(self.contest)
[docs] def ranking_controller(self):
return PARankingController(self.contest)
[docs] def separate_public_results(self):
return True
[docs] def can_submit(self, request, problem_instance, check_round_times=True):
if request.user.is_anonymous:
return False
if request.user.has_perm('contests.contest_admin', self.contest):
return True
if not is_participant(request):
return False
return super(PAContestController, self).can_submit(
request, problem_instance, check_round_times
)
[docs] def can_see_publicsolutions(self, request, round):
if all_non_trial_public_results_visible(request):
# Do not show solutions for trial rounds that has future
# publication date (e.g. not set).
return self.get_round_times(request, round).public_results_visible(
request.timestamp
)
return False
[docs] def solutions_must_be_public(self, qs):
return qs.filter(
user__isnull=False,
user__is_superuser=False,
submissionreport__userresultforproblem__isnull=False,
)
[docs] def get_division_choices(self):
return [('A', _("A")), ('B', _("B")), ('NONE', _("None"))]
[docs] def fill_upload_environ(self, request, form, env):
super(PAContestController, self).fill_upload_environ(request, form, env)
env['division'] = form.cleaned_data['division']
env['post_upload_handlers'] += ['oioioi.pa.handlers.save_division']
[docs] def get_default_safe_exec_mode(self):
return 'cpu'
[docs] def get_allowed_languages(self):
return ['C', 'C++', 'Pascal', 'Java']
[docs]A_PLUS_B_RANKING_KEY = 'ab'
[docs]class PARankingController(DefaultRankingController):
"""Problems in a PA style contest are divided into two divisions
(``A`` and ``B``). It is also possible to set a problem's division to
``None``.
There are three types of rankings in a PA style contest.
1) Trial round ranking. The key (the id) of such a ranking is the id of
some trial round (there may be more than one trial round in a
contest). Problems displayed in the ranking are the problems
attached to the round whose division is set to ``None``.
2) Division ``B`` ranking (key = ``B_RANKING_KEY``). It consists
of all problems from non-trial rounds whose division is set to ``B``.
3) ``A + B`` ranking (key = ``A_PLUS_B_RANKING_KEY``). It consists of
problems from both divisions and from all non-trial rounds.
Note that if a problem belongs to ``A`` or ``B`` and is attached to a
trial round, it won't belong to any ranking. The same applies to
problems in non-trial rounds with division set to ``None``.
"""
[docs] description = _("PA style ranking")
[docs] def _rounds_for_ranking(self, request, partial_key=CONTEST_RANKING_KEY):
method = super(PARankingController, self)._rounds_for_ranking
if partial_key not in [A_PLUS_B_RANKING_KEY, B_RANKING_KEY]:
return method(request, partial_key)
else:
rounds = method(request, CONTEST_RANKING_KEY)
return (r for r in rounds if not r.is_trial)
[docs] def _rounds_for_key(self, key):
method = super(PARankingController, self)._rounds_for_key
partial_key = self.get_partial_key(key)
if partial_key not in [A_PLUS_B_RANKING_KEY, B_RANKING_KEY]:
return method(key)
else:
rounds = method(self.replace_partial_key(key, CONTEST_RANKING_KEY))
return (r for r in rounds if not r.is_trial)
[docs] def available_rankings(self, request):
rankings = [
(A_PLUS_B_RANKING_KEY, _("Division A + B")),
(B_RANKING_KEY, _("Division B")),
]
for round in self._rounds_for_ranking(request):
if round.is_trial:
rankings.append((str(round.id), round.name))
return rankings
[docs] def _filter_pis_for_ranking(self, partial_key, queryset):
if partial_key == A_PLUS_B_RANKING_KEY:
return queryset.filter(paprobleminstancedata__division__in=['A', 'B'])
elif partial_key == B_RANKING_KEY:
return queryset.filter(paprobleminstancedata__division='B')
else:
return queryset.filter(paprobleminstancedata__division='NONE')
[docs] def _allow_zero_score(self):
return False
[docs]class PADivCRankingController(PARankingController):
[docs] description = _("PA style ranking (with division C)")
[docs] def available_rankings(self, request):
rankings = [(A_PLUS_B_RANKING_KEY, _("Division A + B + C")),
(B_RANKING_KEY, _("Division B + C"))]
for round in self._rounds_for_ranking(request):
if round.is_trial:
rankings.append((str(round.id), round.name))
return rankings
[docs] def _filter_pis_for_ranking(self, partial_key, queryset):
if partial_key == A_PLUS_B_RANKING_KEY:
return queryset.filter(
paprobleminstancedata__division__in=['A', 'B', 'C'])
elif partial_key == B_RANKING_KEY:
return queryset.filter(paprobleminstancedata__division__in=['B', 'C'])
else:
return queryset.filter(paprobleminstancedata__division='NONE')
[docs]class PAFinalsContestController(ACMContestController):
[docs] description = _("Algorithmic Engagements finals")
[docs] def registration_controller(self):
return ParticipantsController(self.contest)
[docs] def can_print_files(self, request):
return True
[docs] def can_see_livedata(self, request):
return True
[docs] def is_onsite(self):
return True
[docs] def default_can_see_ranking(self, request):
return is_contest_admin(request) or is_contest_observer(request)
[docs] def get_round_freeze_time(self, round):
if not round.end_date:
return None
if round.is_trial:
frozen_ranking_minutes = 15
else:
frozen_ranking_minutes = 60
return round.end_date - datetime.timedelta(minutes=frozen_ranking_minutes)
[docs] def get_safe_exec_mode(self):
return 'cpu'
PAFinalsContestController.mix_in(OnsiteContestControllerMixin)
[docs]class PADivCContestController(PAContestController):
[docs] description = _("Algorithmic Engagements with Division C")
[docs] def ranking_controller(self):
return PADivCRankingController(self.contest)
[docs] def get_division_choices(self):
return [('A', _("A")), ('B', _("B")), ('C', _("C")), ('NONE', _("None"))]