diff --git a/cms/db/__init__.py b/cms/db/__init__.py
index 85a734da77..bc0f32a668 100644
--- a/cms/db/__init__.py
+++ b/cms/db/__init__.py
@@ -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)
diff --git a/cms/db/task.py b/cms/db/task.py
index 0c0aa06860..64e3686545 100644
--- a/cms/db/task.py
+++ b/cms/db/task.py
@@ -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.
diff --git a/cms/server/admin/handlers/task.py b/cms/server/admin/handlers/task.py
index ff5d5e8e48..b79601b53b 100644
--- a/cms/server/admin/handlers/task.py
+++ b/cms/server/admin/handlers/task.py
@@ -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")
diff --git a/cms/server/admin/templates/task.html b/cms/server/admin/templates/task.html
index e0e9d86de1..82d973de79 100644
--- a/cms/server/admin/templates/task.html
+++ b/cms/server/admin/templates/task.html
@@ -142,6 +142,28 @@
Task configuration
+
+
+
+ Restrict programming languages
+
+
+
+
+
+
+
+
+ Allowed programming languages
+
+
+ {% for lang in LANGUAGES %}
+ {% if lang.name in contest.languages %}
+ {{ lang.name }}
+ {% endif %}
+ {% endfor %}
+
+
diff --git a/cms/server/contest/submission/workflow.py b/cms/server/contest/submission/workflow.py
index e3641a2bc5..4801c15da3 100644
--- a/cms/server/contest/submission/workflow.py
+++ b/cms/server/contest/submission/workflow.py
@@ -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!"),
@@ -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!"),
diff --git a/cms/server/contest/templates/overview.html b/cms/server/contest/templates/overview.html
index dba269e208..78a9b3f395 100644
--- a/cms/server/contest/templates/overview.html
+++ b/cms/server/contest/templates/overview.html
@@ -205,8 +205,9 @@ {% trans %}Task overview{% endtrans %}
-{% 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("|")) %}
{{ t_iter.name }}
{{ t_iter.title }}
diff --git a/cms/server/contest/templates/task_description.html b/cms/server/contest/templates/task_description.html
index 618613a31b..cfff5f75a9 100644
--- a/cms/server/contest/templates/task_description.html
+++ b/cms/server/contest/templates/task_description.html
@@ -111,7 +111,8 @@ {% trans %}Some details{% endtrans %}
{% 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") %}
{% trans %}Compilation commands{% endtrans %}
{% for l, c in compilation_commands|dictsort(by="key") %}
diff --git a/cms/server/contest/templates/task_submissions.html b/cms/server/contest/templates/task_submissions.html
index 7879743070..72d693b3a8 100644
--- a/cms/server/contest/templates/task_submissions.html
+++ b/cms/server/contest/templates/task_submissions.html
@@ -261,7 +261,9 @@ {% trans %}Submit a solution{% endtrans %}
{% for lang in contest.languages %}
+{% if task.languages == None or lang in task.languages %}
{{ lang }}
+{% endif %}
{% endfor %}
diff --git a/cms/server/contest/templates/test_interface.html b/cms/server/contest/templates/test_interface.html
index 33e2d178b2..5dada2de9b 100644
--- a/cms/server/contest/templates/test_interface.html
+++ b/cms/server/contest/templates/test_interface.html
@@ -119,7 +119,9 @@ {% trans %}Submit a test{% endtrans %}
{% for lang in contest.languages %}
+{% if task.languages == None or lang in task.languages %}
{{ lang }}
+{% endif %}
{% endfor %}
diff --git a/cms/server/jinja2_toolbox.py b/cms/server/jinja2_toolbox.py
index 707daec0ad..23d16f5143 100644
--- a/cms/server/jinja2_toolbox.py
+++ b/cms/server/jinja2_toolbox.py
@@ -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
@@ -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
diff --git a/cmscontrib/updaters/update_45.py b/cmscontrib/updaters/update_45.py
new file mode 100644
index 0000000000..f242809326
--- /dev/null
+++ b/cmscontrib/updaters/update_45.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+
+# Contest Management System - http://cms-dev.github.io/
+# Copyright © 2019 Andrey Vihrov
+
+# 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 .
+
+"""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
diff --git a/cmscontrib/updaters/update_45.sql b/cmscontrib/updaters/update_45.sql
new file mode 100644
index 0000000000..cdf44c333a
--- /dev/null
+++ b/cmscontrib/updaters/update_45.sql
@@ -0,0 +1,5 @@
+begin;
+
+alter table tasks add languages character varying[];
+
+rollback; -- change this to: commit;
diff --git a/docs/Configuring a contest.rst b/docs/Configuring a contest.rst
index 820b922afd..d962c1f4db 100644
--- a/docs/Configuring a contest.rst
+++ b/docs/Configuring a contest.rst
@@ -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.