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

+ + + + + + + + + + + + + Allowed programming languages + + + {% for lang in LANGUAGES %} + {% if lang.name in contest.languages %} + + {% endif %} + {% endfor %} + +

Tokens parameters (documentation)

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 %}

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 %}

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.