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 is None), 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 evaluation recipe.

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 and UserStatistics with data from a new Submission.

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 be SUPERSEDED.

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 raises NotImplementedError 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.

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 and Public 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):

  1. 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 or None to return all rounds of the controller’s contest

can_see_round(request_or_context, round)[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)[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’s None, 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.

is_onsite()[source]

Determines whether the contest is on-site.

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 the Contact email contest setting

and defaults to the value of From:.

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. for main_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:

  1. A user submits a submission.

  2. The view calls the form which calls the ProblemInstanceController.

  3. Next:

  • If the submission was submitted to a problem instance without an attached contest, then ProblemInstanceController calls the MaxEProblemController, so we have only one validation - for letter e.

  • If the submission was submitted to a problem instance with an attached contest, then ProblemInstanceController calls the MinZContestController, so we have two validations - for letter z which checks our contest controller. Moreover, it calls the MaxEProblemController, so we have also validation for letter e, 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.