Skip to content

Commit

Permalink
Implement task level language restrictions (cms-dev#1155)
Browse files Browse the repository at this point in the history
  • Loading branch information
SvizelPritula committed Jun 29, 2022
1 parent 0401c53 commit cf36fe8
Show file tree
Hide file tree
Showing 13 changed files with 105 additions and 5 deletions.
2 changes: 1 addition & 1 deletion cms/db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@

# Instantiate or import these objects.

version = 44
version = 45

engine = create_engine(config.database, echo=config.database_debug,
pool_timeout=60, pool_recycle=120)
Expand Down
8 changes: 8 additions & 0 deletions cms/db/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@ class Task(Base):
FilenameSchemaArray,
nullable=False,
default=[])

# The list of names of languages allowed for this task.
# None means no limit
languages = Column(
ARRAY(String),
nullable=True,
default=None)


# The language codes of the statements that will be highlighted to
# all users for this task.
Expand Down
4 changes: 4 additions & 0 deletions cms/server/admin/handlers/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ def post(self, task_id):
self.get_submission_format(attrs)
self.get_string(attrs, "feedback_level")

limit_languages = bool(self.get_argument("limit_languages", False))
attrs["languages"] = self.get_arguments(
"languages") if limit_languages else None

self.get_string(attrs, "token_mode")
self.get_int(attrs, "token_max_number")
self.get_timedelta_sec(attrs, "token_min_interval")
Expand Down
22 changes: 22 additions & 0 deletions cms/server/admin/templates/task.html
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,28 @@ <h2 id="title_task_configuration" class="toggling_on">Task configuration</h2>
</select>
</td>
</tr>
<tr>
<td>
<span class="info" title="Whether this task should accept only a subset of all allowed programming languages."></span>
<label for="limit_languages">Restrict programming languages</label>
</td>
<td>
<input type="checkbox" id="limit_languages" name="limit_languages" {{ "checked" if task.languages != None else "" }}/>
</td>
</tr>
<tr>
<td>
<span class="info" title="Programming languages that contestants can use to solve this task. Only relevant if Restrict programming languages is checked."></span>
Allowed programming languages
</td>
<td class="wrapping-options">
{% for lang in LANGUAGES %}
{% if lang.name in contest.languages %}
<label><input type="checkbox" name="languages" value="{{ lang.name }}" {{ "checked" if task.languages == None or lang.name in task.languages else "" }}>{{ lang.name }}</label>
{% endif %}
{% endfor %}
</td>
</tr>
<tr><td colspan=2><h2>Tokens parameters (<a href="http://cms.readthedocs.org/en/{{ rtd_version }}/Configuring%20a%20contest.html#feedback-to-contestants" target="_blank">documentation</a>)</h2></td></tr>
<tr>
<td>
Expand Down
12 changes: 10 additions & 2 deletions cms/server/contest/submission/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,14 @@ def accept_submission(sql_session, file_cacher, participation, task, timestamp,
N_("Invalid archive format!"),
N_("The submitted archive could not be opened."))

allowed_languages = set(contest.languages)
if (task.languages != None):
allowed_languages &= set(task.languages)

try:
files, language = match_files_and_language(
received_files, language_name, required_codenames,
contest.languages)
allowed_languages)
except InvalidFilesOrLanguage:
raise UnacceptableSubmission(
N_("Invalid submission format!"),
Expand Down Expand Up @@ -308,10 +312,14 @@ def accept_user_test(sql_session, file_cacher, participation, task, timestamp,
N_("Invalid archive format!"),
N_("The submitted archive could not be opened."))

allowed_languages = set(contest.languages)
if (task.languages != None):
allowed_languages &= set(task.languages)

try:
files, language = match_files_and_language(
received_files, language_name, required_codenames,
contest.languages)
allowed_languages)
except InvalidFilesOrLanguage:
raise UnacceptableUserTest(
N_("Invalid test format!"),
Expand Down
3 changes: 2 additions & 1 deletion cms/server/contest/templates/overview.html
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,9 @@ <h2>{% trans %}Task overview{% endtrans %}</h2>
</tr>
</thead>
<tbody>
{% set extensions = "[%s]"|format(contest.languages|map("to_language")|map(attribute="source_extension")|unique|join("|")) %}
{% for t_iter in contest.tasks %}
{% set languages = contest.languages|intersect(t_iter.languages) if t_iter.languages != None else contest.languages %}
{% set extensions = "[%s]"|format(languages|map("to_language")|map(attribute="source_extension")|unique|join("|")) %}
<tr>
<th>{{ t_iter.name }}</th>
<td>{{ t_iter.title }}</td>
Expand Down
3 changes: 2 additions & 1 deletion cms/server/contest/templates/task_description.html
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ <h2>{% trans %}Some details{% endtrans %}</h2>
{% endif %}
{% set compilation_commands = task_type.get_compilation_commands(task.submission_format) %}
{% if compilation_commands is not none %}
{% set compilation_commands = compilation_commands|dictselect("in", contest.languages, by="key") %}
{% set languages = contest.languages|intersect(task.languages) if task.languages != None else contest.languages %}
{% set compilation_commands = compilation_commands|dictselect("in", languages, by="key") %}
<tr>
<th rowspan="{{ compilation_commands|length }}">{% trans %}Compilation commands{% endtrans %}</th>
{% for l, c in compilation_commands|dictsort(by="key") %}
Expand Down
2 changes: 2 additions & 0 deletions cms/server/contest/templates/task_submissions.html
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,9 @@ <h2 style="margin-bottom: 10px">{% trans %}Submit a solution{% endtrans %}</h2>
<div class="controls">
<select name="language">
{% for lang in contest.languages %}
{% if task.languages == None or lang in task.languages %}
<option value="{{ lang }}">{{ lang }}</option>
{% endif %}
{% endfor %}
</select>
</div>
Expand Down
2 changes: 2 additions & 0 deletions cms/server/contest/templates/test_interface.html
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ <h2 style="margin-bottom: 10px">{% trans %}Submit a test{% endtrans %}</h2>
<div class="controls">
<select name="language">
{% for lang in contest.languages %}
{% if task.languages == None or lang in task.languages %}
<option value="{{ lang }}">{{ lang }}</option>
{% endif %}
{% endfor %}
</select>
</div>
Expand Down
5 changes: 5 additions & 0 deletions cms/server/jinja2_toolbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ def safe_get_score_type(env, *, dataset):
except Exception as err:
return env.undefined("ScoreType not found: %s" % err)

def intersect(a, b):
"""Calculates intersection of two lists."""
return list(set(a) & set(b))


def instrument_cms_toolbox(env):
env.globals["get_task_type"] = safe_get_task_type
Expand All @@ -192,6 +196,7 @@ def instrument_cms_toolbox(env):
env.globals["get_icon_for_mimetype"] = get_icon_for_type

env.filters["to_language"] = get_language
env.filters["intersect"] = intersect


@contextfilter
Expand Down
40 changes: 40 additions & 0 deletions cmscontrib/updaters/update_45.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env python3

# Contest Management System - http://cms-dev.github.io/
# Copyright © 2019 Andrey Vihrov <[email protected]>

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""A class to update a dump created by CMS.
Used by DumpImporter and DumpUpdater.
This updater adds the languages to tasks for task level language restrictions
"""

class Updater:

def __init__(self, data):
assert data["_version"] == 44
self.objs = data

def run(self):
for k, v in self.objs.items():
if k.startswith("_"):
continue
if v["_class"] == "Task":
v["languages"] = None

return self.objs
5 changes: 5 additions & 0 deletions cmscontrib/updaters/update_45.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
begin;

alter table tasks add languages character varying[];

rollback; -- change this to: commit;
2 changes: 2 additions & 0 deletions docs/Configuring a contest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ Programming languages

CMS allows to restrict the set of programming languages available to contestants in a certain contest; the configuration is in the contest page in AWS.

If necessary, it is possible to apply language restrictions to individual tasks. This might be useful for tasks that utilize custom graders. Task level restrictions can be enabled in the task page in AWS.

CMS offers out of the box the following combination of languages: C, C++, Pascal, Java (using a JDK), Python 2 and 3, PHP, Haskell, Rust, C#.

C, C++ and Pascal are the default languages, and have been tested thoroughly in many contests.
Expand Down

0 comments on commit cf36fe8

Please sign in to comment.