Source code for oioioi.contests.handlers

import json
import logging
import pprint
import socket
import time
import traceback
from functools import wraps
from smtplib import SMTPException

from django.core.mail import mail_admins
from django.db import transaction

from oioioi.base.utils.db import require_transaction
from oioioi.contests.models import (
    FailureReport,
    ProblemInstance,
    Submission,
    SubmissionReport,
)
from oioioi.problems.models import ProblemStatistics, UserStatistics

[docs]logger = logging.getLogger(__name__)
[docs]WAIT_FOR_SUBMISSION_RETRIES = 9
[docs]WAIT_FOR_SUBMISSION_SLEEP_SECONDS = 1
# TODO: Improve after migration to Python 3: # def _get_submission_or_skip(*args, submission_class=Submission)
[docs]def _get_submission_or_skip(*args, **kwargs): submission_class = kwargs.get('submission_class', Submission) def wrapper(fn): """A decorator which tries to get a submission by id from env or skips the decorated function if the submission doesn't exist. """ @wraps(fn) @require_transaction def decorated(env, *args, **kwargs): if 'submission_id' not in env: return env try: submission = submission_class.objects.get(id=env['submission_id']) except Submission.DoesNotExist: return env return fn(env, submission, *args, **kwargs) return decorated if len(args) == 1: return wrapper(args[0]) return wrapper
[docs]def wait_for_submission_in_db(env, **kwargs): """Celery may start handling a submission before it is actually saved in the DB. This is a workaround for this. """ for _i in range(WAIT_FOR_SUBMISSION_RETRIES): with transaction.atomic(): if bool(Submission.objects.filter(id=env['submission_id'])): break time.sleep(WAIT_FOR_SUBMISSION_SLEEP_SECONDS) return env
@transaction.atomic @_get_submission_or_skip
[docs]def update_report_statuses(env, submission, **kwargs): problem_instance = submission.problem_instance reports = SubmissionReport.objects.filter(submission=submission) problem_instance.controller.update_report_statuses(submission, reports) return env
@transaction.atomic @_get_submission_or_skip
[docs]def update_submission_score(env, submission, **kwargs): problem_instance = submission.problem_instance problem_instance.controller.update_submission_score(submission) return env
[docs]def update_user_results(env, **kwargs): with transaction.atomic(): try: submission = Submission.objects.get(id=env['submission_id']) except Submission.DoesNotExist: return env user = submission.user if not user: return env problem_instance = ProblemInstance.objects.get(id=env['problem_instance_id']) round = problem_instance.round contest = None if round is not None: assert round.id == env['round_id'] contest = round.contest assert contest.id == env['contest_id'] else: assert 'round_id' not in env assert 'contest_id' not in env problem_instance.controller.update_user_results(user, problem_instance) return env
@transaction.atomic @_get_submission_or_skip
[docs]def update_problem_statistics(env, submission, **kwargs): # Ignore model solutions if not submission.user: return env ( problem_statistics, created, ) = ProblemStatistics.objects.select_for_update().get_or_create( problem=submission.problem_instance.problem ) user_statistics, created = UserStatistics.objects.select_for_update().get_or_create( user=submission.user, problem_statistics=problem_statistics ) submission.problem_instance.controller.update_problem_statistics( problem_statistics, user_statistics, submission ) user_statistics.save() problem_statistics.save() return env
@transaction.atomic @_get_submission_or_skip
[docs]def call_submission_judged(env, submission, **kwargs): contest = submission.problem_instance.contest if contest is None: assert 'contest_id' not in env else: assert contest.id == env['contest_id'] contest.controller.submission_judged(submission, rejudged=env['is_rejudge']) if submission.user is not None and not env['is_rejudge']: logger.info( "Submission %(submission_id)d by user %(username)s" " for problem %(short_name)s was judged", { 'submission_id': submission.pk, 'username': submission.user.username, 'short_name': submission.problem_instance.short_name, }, extra={ 'notification': 'submission_judged', 'user': submission.user, 'submission': submission, }, ) return env
@transaction.atomic @_get_submission_or_skip
[docs]def create_error_report(env, submission, exc_info, **kwargs): """Builds a :class:`oioioi.contests.models.SubmissionReport` for an evaulation which have failed. USES * `env['submission_id']` """ logger.error( "System Error evaluating submission #%s:\n%s", env.get('submission_id', '???'), pprint.pformat(env, indent=4), exc_info=exc_info, ) submission_report = SubmissionReport(submission=submission) submission_report.kind = 'FAILURE' submission_report.save() failure_report = FailureReport(submission_report=submission_report) failure_report.json_environ = json.dumps(env) failure_report.message = u''.join(traceback.format_exception(*exc_info)) failure_report.save() return env
@transaction.atomic @_get_submission_or_skip
[docs]def mail_admins_on_error(env, submission, exc_info, **kwargs): """Sends email to all admins defined in settings.ADMINS on each grading error occurrence. USES * `env['submission_id']` """ try: mail_admins( "System Error evaluating submission #%s" % env.get('submission_id', '???'), u''.join(traceback.format_exception(*exc_info)), ) except (socket.error, SMTPException) as e: logger.error("An error occurred while sending email: %s", e.message) return env