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

Task level languages #1209

Open
wants to merge 5 commits into
base: master
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
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 @@
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(

Check warning on line 147 in cms/server/admin/handlers/task.py

View check run for this annotation

Codecov / codecov/patch

cms/server/admin/handlers/task.py#L146-L147

Added lines #L146 - L147 were not covered by tests
"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
33 changes: 33 additions & 0 deletions cms/server/admin/templates/task.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@
});
{% endfor %}

function updateLanguageVisibility() {
if($("#limit-languages").is(':checked')) {
$("#language-list").show();
} else {
$("#language-list").hide();
}
}

$("#limit-languages").change(updateLanguageVisibility);
$(document).ready(updateLanguageVisibility);

{% endblock js_init %}

{% block core %}
Expand Down Expand Up @@ -142,6 +153,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 id="language-list">
<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 task.contest == None or lang.name in task.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
14 changes: 12 additions & 2 deletions cms/server/contest/submission/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@
return self.text % self.text_params


def combine_language_lists(*lists):

Check warning on line 64 in cms/server/contest/submission/workflow.py

View check run for this annotation

Codecov / codecov/patch

cms/server/contest/submission/workflow.py#L64

Added line #L64 was not covered by tests
""" Calculate intersection of one or more collections, skipping Nones """
lists = list(filter(lambda l: l != None, lists))

Check warning on line 66 in cms/server/contest/submission/workflow.py

View check run for this annotation

Codecov / codecov/patch

cms/server/contest/submission/workflow.py#L66

Added line #L66 was not covered by tests

if len(lists) == 0:
return None

Check warning on line 69 in cms/server/contest/submission/workflow.py

View check run for this annotation

Codecov / codecov/patch

cms/server/contest/submission/workflow.py#L68-L69

Added lines #L68 - L69 were not covered by tests

return set.intersection(*map(lambda l: set(l), lists))

Check warning on line 71 in cms/server/contest/submission/workflow.py

View check run for this annotation

Codecov / codecov/patch

cms/server/contest/submission/workflow.py#L71

Added line #L71 was not covered by tests


def accept_submission(sql_session, file_cacher, participation, task, timestamp,
tornado_files, language_name, official):
"""Process a contestant's request to submit a submission.
Expand Down Expand Up @@ -140,7 +150,7 @@
try:
files, language = match_files_and_language(
received_files, language_name, required_codenames,
contest.languages)
combine_language_lists(contest.languages, task.languages))
except InvalidFilesOrLanguage:
raise UnacceptableSubmission(
N_("Invalid submission format!"),
Expand Down Expand Up @@ -311,7 +321,7 @@
try:
files, language = match_files_and_language(
received_files, language_name, required_codenames,
contest.languages)
combine_language_lists(contest.languages, task.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 @@
except Exception as err:
return env.undefined("ScoreType not found: %s" % err)

def intersect(a, b):

Check warning on line 185 in cms/server/jinja2_toolbox.py

View check run for this annotation

Codecov / codecov/patch

cms/server/jinja2_toolbox.py#L185

Added line #L185 was not covered by tests
"""Calculates intersection of two lists."""
return list(set(a) & set(b))

Check warning on line 187 in cms/server/jinja2_toolbox.py

View check run for this annotation

Codecov / codecov/patch

cms/server/jinja2_toolbox.py#L187

Added line #L187 was not covered by tests


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

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

Check warning on line 199 in cms/server/jinja2_toolbox.py

View check run for this annotation

Codecov / codecov/patch

cms/server/jinja2_toolbox.py#L199

Added line #L199 was not covered by tests


@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;
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ def test_failure_due_to_match_files_and_language(self):
self.match_files_and_language.assert_called_with(
self.received_files, self.language_name,
{"foo.%l", "bar.%l", "baz.%l"},
["MockLanguage", "AnotherMockLanguage"])
{"MockLanguage", "AnotherMockLanguage"})

def test_failure_due_to_missing_files(self):
self.task_type.ALLOW_PARTIAL_SUBMISSION = False
Expand Down Expand Up @@ -617,7 +617,7 @@ def test_failure_due_to_match_files_and_language(self):
self.received_files, self.language_name,
{"foo.%l", "bar.%l", "baz.%l", "spam.%l", "ham.%l", "eggs.%l",
"input"},
["MockLanguage", "AnotherMockLanguage"])
{"MockLanguage", "AnotherMockLanguage"})

def test_success_without_missing_files(self):
self.task_type.ALLOW_PARTIAL_SUBMISSION = False
Expand Down
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