Controllers¶
The main purpose of controllers is implementing various rules. Below are
described the three main types of controllers used for contests and problems.
Every contest and problem use its own controller. If you want to implement
your own controller, you should derive from base problem or contest
controller class: ProblemController
and
ContestController
. For a short overview,
please read their summaries described below. Moreover, you can use already
defined mixins. Also, please pay attention to
a special controller:
ProblemInstanceController
.
An Example of custom controller can be found below.
More types of controllers usually live in controllers.py
file in the root
directory of some OIOIOI Modules.
Problem controller¶
- class oioioi.problems.controllers.ProblemController(*args, **kwargs)[source]¶
Defines rules for handling specific problem.
Every method should:
be called from contest controller
or be specific for problems that this controller controls
Please note that a global problem instance exists for each problem. That problem instance has no contest (
contest
isNone
), so methods can’t be overridden by a contest controller which means they behave in a default way.- adjust_problem()[source]¶
Called whan a (usually new) problem has just got the controller attached or after the problem has been modified.
- get_default_submission_kind(request, problem_instance)[source]¶
Returns default kind of newly created submission by the current user.
The default implementation returns
'IGNORED'
for problem admins. In other cases it returns'NORMAL'
.
- can_submit(request, problem_instance, check_round_times=True)[source]¶
Determines if the current user is allowed to submit a solution for the given problem.
The default implementation checks if the user is not anonymous. Subclasses should also call this default implementation.
- get_submissions_left(request, problem_instance, kind='NORMAL')[source]¶
Returns number of submissions left until reaching problem limit
- fill_evaluation_environ(environ, submission, **kwargs)[source]¶
Fills a minimal environment with evaluation receipt and other values required by the evaluation machinery.
Passed
environ
should already contain entries for the actiual data to be judged (for example the source file to evaluate).Details on which keys need to be present should be specified by particular subclasses.
As the result,
environ
will be filled at least with a suitable evaluationrecipe
.
- finalize_evaluation_environment(environ)[source]¶
This method gets called right before the environ becomes scheduled in the queue. It gets called only for submissions send without a contest
This hook exists for inserting extra handlers to the recipe before judging the solution.
- mixins_for_admin()[source]¶
Returns an iterable of mixins to add to the default
oioioi.problems.admin.ProblemAdmin
for this particular problem.The default implementation returns an empty tuple.
- update_user_result_for_problem(result)[source]¶
Updates a
UserResultForProblem
.Usually this involves looking at submissions and aggregating scores from them. Default implementation takes the latest submission which has a score and copies it to the result.
Saving the
result
is a responsibility of the caller.
- update_problem_statistics(problem_statistics, user_statistics, submission)[source]¶
Updates
ProblemStatistics
andUserStatistics
with data from a newSubmission
.This is called when a new submission is checked, and for performance reasons performs as few database queries as possible. The default implementation only checks the submission report kind, and retrieves the score report for the submission.
By default, only ACTIVE and NORMAL submissions are counted for statistics. If you change this behaviour make sure to also update
change_submission_kind()
.Saving both statistics objects is a responsibility of the caller.
- recalculate_statistics_for_user(user)[source]¶
Recalculates user’s statistics for this problem controller’s problem
Sometimes (for example when a submission’s type changes) we can’t update user statistics quickly, and need to recalculate them. This function by default erases the user statistics for the problem and recalculates them from all of this user’s submissions to every probleminstance of the problem.
- update_report_statuses(submission, queryset)[source]¶
Updates statuses of reports for the newly judged submission.
Usually this involves looking at reports and deciding which should be
ACTIVE
and which should beSUPERSEDED
.- Parameters:
submission – an instance of
oioioi.contests.models.Submission
queryset – a queryset returning reports for the submission
- update_user_results(user, problem_instance)[source]¶
Updates score for problem instance.
Usually this method creates instances (if they don’t exist) of: *
UserResultForProblem
and then calls proper methods of ProblemController to update them.
- render_submission_date(submission, shortened=False)[source]¶
Returns a human-readable representation of the submission date.
In some contests it is more reasonable to show time elapsed since the contest start, in others it’s better to just show the wall clock time.
The default implementation returns the wall clock time.
- render_submission_score(submission)[source]¶
Returns a human-readable representation of the submission score.
The default implementation returns the Unicode representation of
submission.score
.
- get_supported_extra_args(submission)[source]¶
Returns dict of all values which can be provided in extra_args argument to the judge method.
Renders the given submission footer to HTML.
Footer is shown under the submission reports. The default implementation returns an empty string.
- render_report(request, report)[source]¶
Renders the given report to HTML.
Default implementation supports only rendering reports of kind
FAILURE
and raisesNotImplementedError
otherwise.
- filter_visible_reports(request, submission, queryset)[source]¶
Determines which reports the user should be able to see.
It needs to check whether the submission is visible to the user and submission is submitted without contest.
- Parameters:
request – Django request
submission – instance of
Submission
queryset – a queryset, initially filtered at least to select only given submission’s reports
- Returns:
updated queryset
- valid_kinds_for_submission(submission)[source]¶
Returns list of all valid kinds we can change to for the given submission.
Default implementation supports only kinds
NORMAL
,IGNORED
,SUSPECTED
, ‘IGNORED_HIDDEN’.
- results_visible(request, submission)[source]¶
Determines whether it is a good time to show the submission’s results.
This method is not used directly in any code outside of the controllers. It’s a helper method used in a number of other controller methods, as described.
The default implementations returns
True
.
- can_see_submission_status(request, submission)[source]¶
Determines whether a user can see one of his/her submissions’ status.
Default implementation delegates to :meth:
results_visible()
.- Return type:
bool
- can_see_submission_score(request, submission)[source]¶
Determines whether a user can see one of his/her submissions’ score.
Default implementation delegates to :meth:
results_visible()
.- Return type:
bool
- can_see_submission_comment(request, submission)[source]¶
Determines whether a user can see one of his/her submissions’ comment.
Default implementation delegates to :meth:
results_visible()
.- Return type:
bool
- change_submission_kind(submission, kind)[source]¶
Changes kind of the submission, updates user reports for problem, round and contest which may contain given submission, and updates statistics for the submission’s user if necessary.
- filter_my_visible_submissions(request, queryset, filter_user=True)[source]¶
Returns the submissions which the user should see in the problemset in “My submissions” view.
The default implementation returns all submissions belonging to the user for current problem except for author, who gets all his submissions.
Should return the updated queryset.
- get_extra_problem_site_actions(problem)[source]¶
Returns a list of tuples (url, name) that will be displayed under actions in ProblemSite.
- supports_problem_statement()[source]¶
If the ProblemController supports problem statement, opening the problem in a contest shows the associated problem statement or an information that it doesn’t have one. On the other hand, if it doesn’t support the problem statement, opening the problem redirects to submit solution page.
- get_notification_message_submission_judged(submission)[source]¶
Returns a message to show in a notification when a submission has been judged. It doesn’t validate any permissions.
- mixins = []¶
A list of mixins to be automatically mixed in to all instances of the particular class and its subclasses.
Contest controller¶
- class oioioi.contests.controllers.ContestController(*args, **kwargs)[source]¶
Contains the contest logic and rules.
This is the computerized implementation of the contest’s official rules.
- default_view(request)[source]¶
Determines the default landing page for the user from the passed request.
The default implementation returns the list of problems.
- get_contest_participant_info_list(request, user)[source]¶
Returns a list of tuples (priority, info). Each entry represents a fragment of HTML with information about the user’s participation in the contest. This information will be visible for contest admins. It can be any information an application wants to add.
The fragments are sorted by priority (descending) and rendered in that order.
The default implementation returns basic info about the contestant: his/her full name, e-mail, the user id, his/her submissions and round time extensions.
To add additional info from another application, override this method. For integrity, include the result of the parent implementation in your output.
- get_user_public_name(request, user)[source]¶
Returns the name of the user to be displayed in public contest views.
The default implementation returns the user’s full name or username if the former is not available.
- get_round_times(request, round)[source]¶
Determines the times of the round for the user doing the request.
The default implementation returns an instance of
RoundTimes
cached by round_times() method.Round must belong to request.contest. Request is optional (round extensions won’t be included if omitted).
- Returns:
an instance of
RoundTimes
- separate_public_results()[source]¶
Determines if there should be two separate dates for personal results (when participants can see their scores for a given round) and public results (when round ranking is published).
Depending on the value returned, contest admins can see and modify both
Results date
andPublic results date
or only the first one.- Return type:
bool
- order_rounds_by_focus(request, queryset=None)[source]¶
Sorts the rounds in the queryset according to probable user’s interest.
The algorithm works as follows (roughly):
If a round starts or ends in 10 minutes or less or started less than a minute ago, it’s prioritized.
1. Then active rounds are appended. 1. If a round starts in less than 6 hours or has ended in less
than 1 hour, it’s appended.
1. Then come past rounds. 1. Then other future rounds.
See the implementation for corner cases.
- Parameters:
request – the Django request
queryset – the set of
Round
instances to sort orNone
to return all rounds of the controller’s contest
- can_see_round(request_or_context, round, no_admin=False)[source]¶
Determines if the current user is allowed to see the given round.
If not, everything connected with this round will be hidden.
The default implementation checks if the round is not in the future.
- can_see_ranking(request_or_context)[source]¶
Determines if the current user is allowed to see the ranking.
The default implementation checks if there exists a ranking visibility config for current contest and checks if ranking visibility is enabled. If there is no ranking visibility config for current contest or option ‘AUTO’ is chosen, returns default value (calls
default_can_see_ranking()
)
- can_see_problem(request_or_context, problem_instance, no_admin=False)[source]¶
Determines if the current user is allowed to see the given problem.
If not, the problem will be hidden from all lists, so that its name should not be visible either.
The default implementation checks if the user can see the given round (calls
can_see_round()
).
- can_see_statement(request_or_context, problem_instance)[source]¶
Determines if the current user is allowed to see the statement for the given problem.
The default implementation checks if there exists a problem statement config for current contest and checks if statements’ visibility is enabled. If there is no problem statement config for current contest or option ‘AUTO’ is chosen, returns default value (calls
default_can_see_statement()
)
- can_submit(request, problem_instance, check_round_times=True)[source]¶
Determines if the current user is allowed to submit a solution for the given problem.
The default implementation checks if the user is not anonymous, and if the round is active for the given user. Subclasses should also call this default implementation.
- get_default_submission_kind(request, **kwargs)[source]¶
Returns default kind of newly created submission by the current user.
The default implementation returns
'IGNORED'
for non-contestants. In other cases it returns'NORMAL'
.
- get_supported_extra_args(submission)[source]¶
Returns dict of all values which can be provided in extra_args argument to the judge method.
- finalize_evaluation_environment(environ)[source]¶
This method gets called right before the environ becomes scheduled in the queue.
This hook exists for inserting extra handlers to the recipe before judging the solution.
- update_submission_score(submission)[source]¶
Updates status, score and comment in a submission.
Usually this involves looking at active reports and aggregating information from them.
- update_user_result_for_round(result)[source]¶
Updates a
UserResultForRound
.Usually this involves looking at user’s results for problems and aggregating scores from them. Default implementation sums the scores.
Saving the
result
is a responsibility of the caller.
- update_user_result_for_contest(result)[source]¶
Updates a
UserResultForContest
.Usually this involves looking at user’s results for rounds and aggregating scores from them. Default implementation sums the scores.
Saving the
result
is a responsibility of the caller.
- update_user_results(user, problem_instance)[source]¶
Updates score for problem instance, round and contest.
Usually this method creates instances (if they don’t exist) of: *
UserResultForProblem
*UserResultForRound
*UserResultForContest
and then calls proper methods of ContestController to update them.
- filter_my_visible_submissions(request, queryset, filter_user=True)[source]¶
Returns the submissions which the user should see in the “My submissions” view.
The default implementation returns all submissions belonging to the user for the problems that are visible, except for admins, which get all their submissions.
Should return the updated queryset.
- results_visible(request, submission)[source]¶
Determines whether it is a good time to show the submission’s results.
This method is not used directly in any code outside of the controllers. It’s a helper method used in a number of other controller methods, as described.
The default implementations uses the round’s
results_date
. If it’sNone
, results are not available. Admins are always shown the results.
- filter_visible_reports(request, submission, queryset)[source]¶
Determines which reports the user should be able to see.
It need not check whether the submission is visible to the user.
The default implementation uses
results_visible()
.- Parameters:
request – Django request
submission – instance of
Submission
queryset – a queryset, initially filtered at least to select only given submission’s reports
- Returns:
updated queryset
- render_submission(request, submission)[source]¶
Renders the given submission to HTML.
This is usually a table with some basic submission info, source code download etc., displayed on the top of the submission details view, above the reports.
- render_my_submissions_header(request, submissions)[source]¶
Renders header on “My submissions” view.
Default implementation returns empty string.
- adjust_contest()[source]¶
Called when a (usually new) contest has just got the controller attached or after the contest has been modified.
- mixins_for_admin()[source]¶
Returns an iterable of mixins to add to the default
oioioi.contests.admin.ContestAdmin
for this particular contest.The default implementation returns an empty tuple.
- send_email(subject, body, recipients, headers=None)[source]¶
Send an email about something related to this contest (e.g. a submission confirmation).
From:
is set to DEFAULT_FROM_EMAIL,Reply-To:
is taken from theContact email
contest settingand defaults to the value of
From:
.
- mixins = [<class 'oioioi.contests.controllers.NotificationsMixinForContestController'>, <class 'oioioi.contests.controllers.ProblemUploadingContestControllerMixin'>, <class 'oioioi.contestlogo.controllers.LogoContestControllerMixin'>, <class 'oioioi.participants.controllers.EmailShowContestControllerMixin'>, <class 'oioioi.participants.controllers.AnonymousContestControllerMixin'>, <class 'oioioi.rankings.controllers.RankingMixinForContestController'>, <class 'oioioi.printing.controllers.PrintingContestControllerMixin'>, <class 'oioioi.forum.controllers.ContestControllerWithForum'>, <class 'oioioi.disqualification.controllers.DisqualificationContestControllerMixin'>, <class 'oioioi.statistics.controllers.StatisticsMixinForContestController'>, <class 'oioioi.teams.controllers.TeamsMixinForContestController'>, <class 'oioioi.livedata.controllers.LivedataContestControllerMixin'>, <class 'oioioi.questions.controllers.QuestionsContestControllerMixin'>, <class 'oioioi.dashboard.controllers.DashboardDefaultViewMixin'>, <class 'oioioi.quizzes.controllers.QuizContestControllerMixin'>, <class 'oioioi.ipauthsync.controllers.IpAuthSyncControllerMixin'>]¶
A list of mixins to be automatically mixed in to all instances of the particular class and its subclasses.
Problem instance controller¶
- class oioioi.contests.problem_instance_controller.ProblemInstanceController(problem_instance)[source]¶
ProblemInstanceController
decides whether to call problem controller or contest controller. Problem controller will be chosen if the problem instance is not attached to any contest (eg. formain_problem_instance
).Outside functions which want to call one of the above controllers and it is not clear that contest controller exists, should call
ProblemInstanceController
, example:problem_instance.contest.controller.get_submissions_limit() # WRONG problem_instance.controller.get_submissions_limit() # GOOD
From its functions the contest controller can call the problem controller, but the problem controller should not call the contest controller.
For visuals:
Call, for example *.get_submissions_limit() | V ProblemInstanceController | | V V ContestController --> ProblemController
Example¶
Let’s say there are too many e
characters in English language, so we
dislike them. We want a problem controller which will check if the submitted
source code contains no more than MAX_E_COUNT
e
characters:
class MaxEProblemController(ProblemController):
"""Checks if a source code is cool."""
def validate_source_code_coolness(self, problem_instance, source_code):
if source_code.count('e') >= MAX_E_COUNT:
raise ValidationError(
"Too much letters 'e' found in source code!")
return source_code
Now, we have a cool problem controller. We want to use it in some contest!
But wait… We love undervalued z
letters. Let’s create a contest
controller which checks if the source code contains at least MIN_Z_COUNT
z
letters:
class MinZContestController(ContestController):
"""Checks if a source code is even cooler."""
def validate_source_code_coolness(self, problem_instance, source_code):
# Note that here is a call to problem controller
source_code = problem_instance.problem.controller \
.validate_source_code_coolness(request, problem_instance,
source_code)
if source_code.count('z') <= MIN_Z_COUNT:
raise ValidationError(
"Too few letters 'z' found in source code!")
return source_code
The time to bring our great idea to life has come. Let’s say we have a view which uses the following form:
class SuperCoolForm(forms.Form):
# ...
def clean_source_code(self):
try:
pi = ProblemInstance.objects.get(
id=self.cleaned_data['problem_instance_id'])
except ProblemInstance.DoesNotExists:
pass # handle error in a better way!
return pi.validate_source_code_coolness(
pi, self.cleaned_data['source_code'])
Let’s break down how it works:
A user submits a submission.
The view calls the form which calls the
ProblemInstanceController
.Next:
If the submission was submitted to a problem instance without an attached contest, then
ProblemInstanceController
calls theMaxEProblemController
, so we have only one validation - for lettere
.If the submission was submitted to a problem instance with an attached contest, then
ProblemInstanceController
calls theMinZContestController
, so we have two validations - for letterz
which checks our contest controller. Moreover, it calls theMaxEProblemController
, so we have also validation for lettere
, too.
Notes:
You can both define new methods and override existing methods.
Outside the controllers you should call the
ProblemInstanceController
. Please read the class summary if you haven’t done it yet.Controllers are in power only if you assign them to given problem or contest.