Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatic restriction lifting #25

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cms/db/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ class Contest(Base):
CheckConstraint("min_user_test_interval > '0 seconds'"),
nullable=True)

# Time after which the minimum interval restriction for submissions is lifted
restricted_time = Column(Interval, nullable=True)

# The scores for this contest will be rounded to this number of
# decimal places.
score_precision = Column(
Expand Down
1 change: 1 addition & 0 deletions cms/server/admin/handlers/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ def post(self, contest_id):
self.get_int(attrs, "max_user_test_number")
self.get_timedelta_sec(attrs, "min_submission_interval")
self.get_timedelta_sec(attrs, "min_user_test_interval")
self.get_timedelta_sec(attrs, "restricted_time")

self.get_string(attrs, "timezone", empty=None)
self.get_int(attrs, "score_precision")
Expand Down
9 changes: 9 additions & 0 deletions cms/server/admin/templates/contest.html
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,15 @@ <h1>Contest configuration</h1>
</td>
<td><input type="text" name="min_user_test_interval" value="{{ contest.min_user_test_interval.total_seconds()|int if contest.min_user_test_interval is not none else "" }}"></td>
</tr>
<tr>
<td>
<span class="info" title="The time (in seconds) after which the minimum interval restriction for submissions is lifted.
Leave empty to keep forever.
Give a positive value to specify the time from the contest start, give a negative value to specify the time before the contest end."></span>
Lift minimum submission interval restriction after
</td>
<td><input type="text" name="restricted_time" value="{{ contest.restricted_time.total_seconds()| int if contest.restricted_time is not none else "" }}"></td>
</tr>
</table>
<input type="submit"
value="Update"
Expand Down
5 changes: 3 additions & 2 deletions cms/server/contest/handlers/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,14 +195,15 @@ def render_params(self):
group.analysis_stop if group.analysis_enabled
else None,
group.per_user_time, participation.starting_time,
self.contest.restricted_time,
participation.delay_time, participation.extra_time)

ret["actual_phase"], ret["current_phase_begin"], \
ret["current_phase_end"], ret["valid_phase_begin"], \
ret["valid_phase_end"] = res

if ret["actual_phase"] == 0:
ret["phase"] = 0
if ret["actual_phase"] == 0 or ret["actual_phase"] == .5:
ret["phase"] = ret["actual_phase"]

# set the timezone used to format timestamps
ret["timezone"] = get_timezone(participation.user, self.contest)
Expand Down
4 changes: 2 additions & 2 deletions cms/server/contest/handlers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ class PrintingHandler(ContestHandler):

"""
@tornado_web.authenticated
@actual_phase_required(0)
@actual_phase_required(0, 0.5)
@multi_contest
def get(self):
participation = self.current_user
Expand All @@ -404,7 +404,7 @@ def get(self):
**self.r_params)

@tornado_web.authenticated
@actual_phase_required(0)
@actual_phase_required(0, 0.5)
@multi_contest
def post(self):
try:
Expand Down
6 changes: 3 additions & 3 deletions cms/server/contest/handlers/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class TaskDescriptionHandler(ContestHandler):

"""
@tornado_web.authenticated
@actual_phase_required(0, 3)
@actual_phase_required(0, 0.5, 3)
@multi_contest
def get(self, task_name):
task = self.get_task(task_name)
Expand All @@ -64,7 +64,7 @@ class TaskStatementViewHandler(FileHandler):

"""
@tornado_web.authenticated
@actual_phase_required(0, 3)
@actual_phase_required(0, 0.5, 3)
@multi_contest
def get(self, task_name, lang_code):
task = self.get_task(task_name)
Expand All @@ -90,7 +90,7 @@ class TaskAttachmentViewHandler(FileHandler):

"""
@tornado_web.authenticated
@actual_phase_required(0, 3)
@actual_phase_required(0, 0.5, 3)
@multi_contest
def get(self, task_name, filename):
task = self.get_task(task_name)
Expand Down
16 changes: 8 additions & 8 deletions cms/server/contest/handlers/tasksubmission.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class SubmitHandler(ContestHandler):
"""

@tornado_web.authenticated
@actual_phase_required(0, 3)
@actual_phase_required(0, 0.5, 3)
@multi_contest
def post(self, task_name):
task = self.get_task(task_name)
Expand All @@ -78,15 +78,15 @@ def post(self, task_name):

# Only set the official bit when the user can compete and we are not in
# analysis mode.
official = self.r_params["actual_phase"] == 0
official = self.r_params["actual_phase"] == 0 or self.r_params["actual_phase"] == 0.5

query_args = dict()

try:
submission = accept_submission(
self.sql_session, self.service.file_cacher, self.current_user,
task, self.timestamp, self.request.files,
self.get_argument("language", None), official)
self.get_argument("language", None), official, self.r_params["actual_phase"] == 0)
self.sql_session.commit()
except UnacceptableSubmission as e:
logger.info("Sent error: `%s' - `%s'", e.subject, e.formatted_text)
Expand All @@ -112,7 +112,7 @@ class TaskSubmissionsHandler(ContestHandler):

"""
@tornado_web.authenticated
@actual_phase_required(0, 3)
@actual_phase_required(0, 0.5, 3)
@multi_contest
def get(self, task_name):
participation = self.current_user
Expand Down Expand Up @@ -246,7 +246,7 @@ def add_task_score(self, participation, task, data):
task.score_precision, translation=self.translation)

@tornado_web.authenticated
@actual_phase_required(0, 3)
@actual_phase_required(0, 0.5, 3)
@multi_contest
def get(self, task_name, submission_num):
task = self.get_task(task_name)
Expand Down Expand Up @@ -311,7 +311,7 @@ class SubmissionDetailsHandler(ContestHandler):
refresh_cookie = False

@tornado_web.authenticated
@actual_phase_required(0, 3)
@actual_phase_required(0, 0.5, 3)
@multi_contest
def get(self, task_name, submission_num):
task = self.get_task(task_name)
Expand Down Expand Up @@ -355,7 +355,7 @@ class SubmissionFileHandler(FileHandler):

"""
@tornado_web.authenticated
@actual_phase_required(0, 3)
@actual_phase_required(0, 0.5, 3)
@multi_contest
def get(self, task_name, submission_num, filename):
if not self.contest.submissions_download_allowed:
Expand Down Expand Up @@ -401,7 +401,7 @@ class UseTokenHandler(ContestHandler):

"""
@tornado_web.authenticated
@actual_phase_required(0)
@actual_phase_required(0, 0.5)
@multi_contest
def post(self, task_name, submission_num):
task = self.get_task(task_name)
Expand Down
12 changes: 6 additions & 6 deletions cms/server/contest/handlers/taskusertest.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class UserTestInterfaceHandler(ContestHandler):

"""
@tornado_web.authenticated
@actual_phase_required(0)
@actual_phase_required(0, 0.5)
@multi_contest
def get(self):
participation = self.current_user
Expand Down Expand Up @@ -117,7 +117,7 @@ class UserTestHandler(ContestHandler):
refresh_cookie = False

@tornado_web.authenticated
@actual_phase_required(0)
@actual_phase_required(0, 0.5)
@multi_contest
def post(self, task_name):
if not self.r_params["testing_enabled"]:
Expand Down Expand Up @@ -163,7 +163,7 @@ class UserTestStatusHandler(ContestHandler):
refresh_cookie = False

@tornado_web.authenticated
@actual_phase_required(0)
@actual_phase_required(0, 0.5)
@multi_contest
def get(self, task_name, user_test_num):
if not self.r_params["testing_enabled"]:
Expand Down Expand Up @@ -218,7 +218,7 @@ class UserTestDetailsHandler(ContestHandler):
refresh_cookie = False

@tornado_web.authenticated
@actual_phase_required(0)
@actual_phase_required(0, 0.5)
@multi_contest
def get(self, task_name, user_test_num):
if not self.r_params["testing_enabled"]:
Expand All @@ -243,7 +243,7 @@ class UserTestIOHandler(FileHandler):

"""
@tornado_web.authenticated
@actual_phase_required(0)
@actual_phase_required(0, 0.5)
@multi_contest
def get(self, task_name, user_test_num, io):
if not self.r_params["testing_enabled"]:
Expand Down Expand Up @@ -277,7 +277,7 @@ class UserTestFileHandler(FileHandler):

"""
@tornado_web.authenticated
@actual_phase_required(0)
@actual_phase_required(0, 0.5)
@multi_contest
def get(self, task_name, user_test_num, filename):
if not self.r_params["testing_enabled"]:
Expand Down
16 changes: 12 additions & 4 deletions cms/server/contest/phase_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

def compute_actual_phase(timestamp, contest_start, contest_stop,
analysis_start, analysis_stop, per_user_time,
starting_time, delay_time, extra_time):
starting_time, restricted_time, delay_time, extra_time):
"""Determine the current phase and when the active phase is.

The "actual phase" of the contest for a certain user is the status
Expand All @@ -43,7 +43,9 @@ def compute_actual_phase(timestamp, contest_start, contest_stop,
already started, its per-user time frame hasn't yet (this
usually means the user still has to click on the "start!"
button in USACO-like contests);
* 0: the user can compete;
* 0: the user can compete;
* 0.5:the user can still compete and the interval restriction between
submissions is lifted
* +1: the user cannot compete because, even if the contest hasn't
stopped yet, its per-user time frame already has (again, this
should normally happen only in USACO-like contests);
Expand Down Expand Up @@ -134,8 +136,14 @@ def compute_actual_phase(timestamp, contest_start, contest_stop,

if actual_start <= timestamp <= actual_stop:
actual_phase = 0
current_phase_begin = actual_start
current_phase_end = actual_stop
if restricted_time is not None:
if restricted_time > timedelta():
lift_time = min(actual_stop, actual_start + restricted_time)
else:
lift_time = max(actual_start, actual_stop + restricted_time)
actual_phase = 0 if timestamp <= lift_time else 0.5
current_phase_begin = actual_start if actual_phase == 0 else lift_time
current_phase_end = actual_stop if actual_phase == .5 or restricted_time is None else lift_time
elif contest_start <= timestamp < actual_start:
# This also includes a funny corner case: the user's start
# is known but is in the future (the admin either set it
Expand Down
51 changes: 35 additions & 16 deletions cms/server/contest/submission/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,12 @@
from .check import check_max_number, check_min_interval
from .file_matching import InvalidFilesOrLanguage, match_files_and_language
from .file_retrieval import InvalidArchive, extract_files_from_tornado
from .utils import fetch_file_digests_from_previous_submission, StorageFailed, \
store_local_copy

from .utils import (
fetch_file_digests_from_previous_submission,
StorageFailed,
store_local_copy,
)
from ..phase_management import compute_actual_phase

logger = logging.getLogger(__name__)

Expand All @@ -62,7 +65,7 @@ def formatted_text(self):


def accept_submission(sql_session, file_cacher, participation, task, timestamp,
tornado_files, language_name, official):
tornado_files, language_name, official, check_interval_restriction):
"""Process a contestant's request to submit a submission.

Parse and validate the data that a contestant sent for a submission
Expand Down Expand Up @@ -106,20 +109,36 @@ def accept_submission(sql_session, file_cacher, participation, task, timestamp,
participation, task=task):
raise UnacceptableSubmission(
N_("Too many submissions!"),
N_("You have reached the maximum limit of "
"at most %d submissions on this task."),
task.max_submission_number)

if not check_min_interval(sql_session, contest.min_submission_interval,
timestamp, participation, contest=contest):
N_(
"You have reached the maximum limit of "
"at most %d submissions on this task."
),
task.max_submission_number,
)

if check_interval_restriction and not check_min_interval(
sql_session,
contest.min_submission_interval,
timestamp,
participation,
contest=contest,
):
raise UnacceptableSubmission(
N_("Submissions too frequent!"),
N_("Among all tasks, you can submit again "
"after %d seconds from last submission."),
contest.min_submission_interval.total_seconds())

if not check_min_interval(sql_session, task.min_submission_interval,
timestamp, participation, task=task):
N_(
"Among all tasks, you can submit again "
"after %d seconds from last submission."
),
contest.min_submission_interval.total_seconds(),
)

if check_interval_restriction and not check_min_interval(
sql_session,
task.min_submission_interval,
timestamp,
participation,
task=task,
):
raise UnacceptableSubmission(
N_("Submissions too frequent!"),
N_("For this task, you can submit again "
Expand Down
4 changes: 2 additions & 2 deletions cms/server/contest/templates/contest.html
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ <h3 id="countdown_box">
<span id="unread_count" class="label label-warning no_unread"></span>
</a>
</li>
{% if actual_phase == 0 or actual_phase == 3 or participation.unrestricted %}
{% if actual_phase == 0 or actual_phase == 0.5 or actual_phase == 3 or participation.unrestricted %}
{% for t_iter in contest.tasks %}
<li class="nav-header">
{{ t_iter.name }}
Expand All @@ -201,7 +201,7 @@ <h3 id="countdown_box">
<li{% if page == "documentation" %} class="active"{% endif %}>
<a href="{{ contest_url("documentation") }}">{% trans %}Documentation{% endtrans %}</a>
</li>
{% if actual_phase == 0 or participation.unrestricted %}{# FIXME maybe >= 0? #}
{% if actual_phase == 0 or actual_phase == 0.5 or participation.unrestricted %}{# FIXME maybe >= 0? #}
{% if testing_enabled %}
<li{% if page == "testing" %} class="active"{% endif %}>
<a href="{{ contest_url("testing") }}">{% trans %}Testing{% endtrans %}</a>
Expand Down
6 changes: 3 additions & 3 deletions cms/server/contest/templates/macro/submission.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
<col class="files"/>
{% set num_cols = num_cols + 1 %}
{% endif %}
{% if can_use_tokens and actual_phase == 0 %}
{% if can_use_tokens and actual_phase == 0 or actual_phase == 0.5%}
<col class="token"/>
{% set num_cols = num_cols + 1 %}
{% endif %}
Expand All @@ -78,7 +78,7 @@
{% if submissions_download_allowed %}
<th class="files">{% trans %}Files{% endtrans %}</th>
{% endif %}
{% if can_use_tokens and actual_phase == 0 %}
{% if can_use_tokens and (actual_phase == 0 or actual_phase == 0.5) %}
<th class="token">{% trans %}Token{% endtrans %}</th>
{% endif %}
</tr>
Expand Down Expand Up @@ -255,7 +255,7 @@
{% endif %}
</td>
{% endif %}
{% if can_use_tokens and actual_phase == 0 %}
{% if can_use_tokens and (actual_phase == 0 or actual_phase == 0.5) %}
<td class="token">
{% if s.token is not none %}
<a class="btn disabled">{% trans %}Played{% endtrans %}</a>
Expand Down
4 changes: 2 additions & 2 deletions cms/server/contest/templates/overview.html
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ <h2>{% trans %}General information{% endtrans %}</h2>
{% elif actual_phase == -1 %}
{% trans %}By clicking on the button below you can start your time frame.{% endtrans %}
{%+ trans %}Once you start, you can submit solutions until the end of the time frame or until the end of the contest, whatever comes first.{% endtrans %}
{% elif actual_phase == 0 %}
{% elif actual_phase == 0 or actual_phase == 0.5 %}
{% trans start_time=participation.starting_time|format_datetime_smart %}You started your time frame at {{ start_time }}.{% endtrans %}
{%+ trans %}You can submit solutions until the end of the time frame or until the end of the contest, whatever comes first.{% endtrans %}
{% elif actual_phase == +1 %}
Expand Down Expand Up @@ -179,7 +179,7 @@ <h2>{% trans %}General information{% endtrans %}</h2>



{% if actual_phase == 0 or actual_phase == 3%}
{% if actual_phase == 0 or actual_phase == 0.5 or actual_phase == 3%}
<h2>{% trans %}Task overview{% endtrans %}</h2>

<table class="table table-bordered table-striped">
Expand Down
Loading