diff --git a/.github/workflows/env_containers.yml b/.github/workflows/env_containers.yml new file mode 100644 index 0000000000..a779808b37 --- /dev/null +++ b/.github/workflows/env_containers.yml @@ -0,0 +1,19 @@ +name: Build and push base grading containers + +on: + push: + branches: [master] + + workflow_dispatch: + +jobs: + containers_build_and_push: + uses: INGInious/.github/.github/workflows/containers.yml@673723b119b1a92695dd5fea1520f5ae28e38abb + with: + working-directory: base-containers + context-path: context.yml + compose-path: compose.yml + registry: ghcr.io + secrets: + GHCR_USERNAME: ${{ secrets.GHCR_USERNAME }} + GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} diff --git a/.gitignore b/.gitignore index c6095d8153..bbe6b49733 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,10 @@ inginious/backup # Build files build/lib build/scripts-* + +# frontend +inginious/frontend/themes/Makefile +inginious/frontend/themes/test.html +inginious/frontend/themes/package-lock.json +inginious/frontend/themes/node_modules +inginious/frontend/themes/codemirror_scrapper.py diff --git a/base-containers/base/Dockerfile b/base-containers/base/Dockerfile index 26338dc0e8..ffed1c2b0e 100644 --- a/base-containers/base/Dockerfile +++ b/base-containers/base/Dockerfile @@ -5,6 +5,9 @@ ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8 +LABEL org.opencontainers.image.source=https://github.com/UCL-INGI/INGInious-containers +LABEL org.opencontainers.image.description="Base INGInious grading environment." + LABEL org.inginious.grading.agent_version=3 # Install python, needed for scripts used in INGInious + locale support diff --git a/base-containers/context.yml b/base-containers/context.yml new file mode 100644 index 0000000000..b2a7d04bf2 --- /dev/null +++ b/base-containers/context.yml @@ -0,0 +1,5 @@ +images: + base: + default: + - base +base_path: "." diff --git a/base-containers/default/Dockerfile b/base-containers/default/Dockerfile index 7843f3fcf8..f2500d9ed6 100644 --- a/base-containers/default/Dockerfile +++ b/base-containers/default/Dockerfile @@ -2,5 +2,10 @@ #inherit from the base container, which have all the needed script to launch tasks ARG VERSION=latest -FROM ingi/inginious-c-base:${VERSION} +ARG REGISTRY +FROM ${REGISTRY}/inginious/env-base:${VERSION} + +LABEL org.opencontainers.image.source=https://github.com/UCL-INGI/INGInious-containers +LABEL org.opencontainers.image.description="Default INGInious grading environment." + LABEL org.inginious.grading.name="default" diff --git a/doc/api_doc/inginious.frontend.rst b/doc/api_doc/inginious.frontend.rst index 3ed34d7bba..0200483268 100644 --- a/doc/api_doc/inginious.frontend.rst +++ b/doc/api_doc/inginious.frontend.rst @@ -44,12 +44,12 @@ inginious.frontend.app module :undoc-members: :show-inheritance: -.. _inginious.frontend.course_factory: +.. _inginious.frontend.taskset_factory: -inginious.frontend.course_factory module +inginious.frontend.taskset_factory module ------------------------------------------- -.. automodule:: inginious.frontend.course_factory +.. automodule:: inginious.frontend.taskset_factory :members: :undoc-members: :show-inheritance: diff --git a/doc/dev_doc/extensions_doc/plugins.rst b/doc/dev_doc/extensions_doc/plugins.rst index 7227414a33..125d0ae4ef 100644 --- a/doc/dev_doc/extensions_doc/plugins.rst +++ b/doc/dev_doc/extensions_doc/plugins.rst @@ -25,7 +25,7 @@ The following code adds a new page displaying ``This is a simple demo plugin`` o return "This is a simple demo plugin" - def init(plugin_manager, course_factory, client, plugin_config): + def init(plugin_manager, taskset_factory, client, plugin_config): """ Init the plugin """ plugin_manager.add_page("/plugindemo", DemoPage.as_view('demopage')) @@ -36,8 +36,8 @@ This method takes four arguments: - ``plugin_manager`` which is the plugin manager singleton object. The detailed API is available at :ref:`inginious.frontend.plugin_manager`. -- ``course_factory`` which is the course factory singleton object, giving you abstraction to the tasks folder. The detailed - API is available at :ref:`inginious.frontend.course_factory`. +- ``taskset_factory`` which is the course factory singleton object, giving you abstraction to the tasks folder. The detailed + API is available at :ref:`inginious.frontend.taskset_factory`. - ``client`` which is the INGInious client singleton object, giving you access to the backend features, as launching a new job. The detailed API is available at :ref:`inginious.client.client`. @@ -78,7 +78,7 @@ would therefore need to add a hook method. This can be done using the ``add_hook def submission_done(submission, archive, newsub): logging.getLogger("inginious.frontend.plugins.demo").info("Submission " + str(submission['_id']) + " done.") - def init(plugin_manager, course_factory, client, plugin_config): + def init(plugin_manager, taskset_factory, client, plugin_config): """ Init the plugin """ plugin_manager.add_hook("submission_done", submission_done) diff --git a/doc/teacher_doc/course_description.rst b/doc/teacher_doc/course_description.rst index 5a53565286..df1b03462f 100644 --- a/doc/teacher_doc/course_description.rst +++ b/doc/teacher_doc/course_description.rst @@ -25,7 +25,6 @@ file. For instance, this file, for a course with id ``courseid1``, should be pla admins: - demouser name: "[DEMO] Demonstration course" - tutors: [] groups_student_choice: false accessible: true registration: true @@ -41,11 +40,6 @@ the webapp. Here are the possible fields to set: - ``admins`` List of administrators usernames. These users will have complete administrations right on the course. -- ``tutors`` - List of tutors usernames (restricted-rights teaching assistants). These users will have read-only rights on the - course content. They cannot change course parameters nor tasks, cannot replay submissions or wipe the course data. - However, they can manage the audience composition and download all the student submissions. - - ``accessible`` When this field is defined, the course is only visible if within the defined period. A course is always accessible to its admins, and is only hidden to normal users, diff --git a/inginious-autotest b/inginious-autotest index 01c7cd2715..c1c8138630 100755 --- a/inginious-autotest +++ b/inginious-autotest @@ -19,7 +19,7 @@ from inginious.common.tags import Tag from inginious.frontend.tasks import Task from inginious.frontend.task_dispensers.toc import TableOfContents from inginious.common.log import init_logging -from inginious.frontend.course_factory import create_factories +from inginious.frontend.taskset_factory import create_factories from inginious.client.client_sync import ClientSync from inginious.frontend.arch_helper import start_asyncio_and_zmq, create_arch from yaml import load @@ -36,14 +36,14 @@ def import_class(name): return mod -def create_client(config, course_factory, fs_provider): +def create_client(config, taskset_factory, fs_provider): """ Create a new client and return it :param config: dict for configuration :return: a Client object """ zmq_context, t = start_asyncio_and_zmq(config.get("debug_asyncio", False)) - return create_arch(config, fs_provider, zmq_context, course_factory) + return create_arch(config, fs_provider, zmq_context, taskset_factory) def compare_all_outputs(output1, output2, keys): @@ -120,7 +120,7 @@ def compare_output(output1, output2, key): return func.get(key, generic_compare)(output1, output2) -def test_task(yaml_data, task, client, client_sync): +def test_task(yaml_data, taskset, task, client, client_sync): """ Test the task by comparing the new outputs with the old ones :param yaml_data: dict corresponding to the yaml output file for the task @@ -133,13 +133,13 @@ def test_task(yaml_data, task, client, client_sync): time.sleep(1) if task.get_environment() not in client.get_available_containers(): raise Exception('Environment not available') - new_output = client_sync.new_job(0, task, yaml_data['input']) # request the client with input from yaml and given task + new_output = client_sync.new_job(0, taskset, task, yaml_data['input']) # request the client with input from yaml and given task keys = ["result", "grade", "problems", "tests", "custom", "state", "archive", "stdout", "stderr"] old_output = [yaml_data.get(x, None) for x in keys] return compare_all_outputs(old_output, new_output, keys) -def test_web_task(yaml_data, task, config, yaml_path): +def test_web_task(yaml_data, course, task, config, yaml_path): """ Test the correctness of the data and task input, i.e. the content does not raise any exception and the rst contents are compiling @@ -151,7 +151,7 @@ def test_web_task(yaml_data, task, config, yaml_path): """ try: web_task = Task( - task.get_course_id(), + course.get_id(), task.get_id(), yaml_data, task.get_fs(), @@ -172,12 +172,12 @@ def test_web_task(yaml_data, task, config, yaml_path): return yaml_data -def test_submission_yaml(client, course_factory, path, output, client_sync): +def test_submission_yaml(client, taskset_factory, path, output, client_sync): """ Test the content of a submission.test yaml by comparing it to the output of the client for this task and the same input. :param client: Client object - :param course_factory: CourseFactory object + :param taskset_factory: CourseFactory object :param path: String, path to the submission.test :param output: dict, output variable :param client_sync: ClientSync object, client_sync = ClientSync(client) @@ -186,17 +186,17 @@ def test_submission_yaml(client, course_factory, path, output, client_sync): # print(os.path.join(test_path, yaml_file.name)) with open(path, 'r') as yaml: yaml_data = load(yaml, Loader=SafeLoader) - res = test_task(yaml_data, course_factory.get_task(yaml_data["courseid"], yaml_data["taskid"]), client, client_sync) + res = test_task(yaml_data, taskset_factory.get_taskset(yaml_data["courseid"]), taskset_factory.get_task(yaml_data["courseid"], yaml_data["taskid"]), client, client_sync) if res != {}: output[path] = res -def test_task_yaml(path, output, course_factory, task_name, course_name, config): +def test_task_yaml(path, output, taskset_factory, task_name, course_name, config): """ Test the format and content of a task.yaml file and, if incorrect, the data is stored in the output dict :param path: path to the task.yaml :param output: output dictionary - :param course_factory: CourseFactory object + :param taskset_factory: CourseFactory object :param task_name: String, name of the task :param course_name: String, name of the course :param config: dict, contains configuration variable @@ -204,18 +204,18 @@ def test_task_yaml(path, output, course_factory, task_name, course_name, config) """ with open(path, 'r') as yaml_file: yaml_data = load(yaml_file, Loader=SafeLoader) - res = test_web_task(yaml_data, course_factory.get_task(course_name, task_name), config, path) + res = test_web_task(yaml_data, taskset_factory.get_taskset(course_name), taskset_factory.get_task(course_name, task_name), config, path) if res != {}: output[path] = res -def test_all_files(config, client, course_factory): +def test_all_files(config, client, taskset_factory): """ Test each yaml file contained in the dir_path directory, with dir_path specified in the config var, as specified in the test_task function :param config: dict for configuration :param client: backend client of type Client - :param course_factory: CourseFactory object + :param taskset_factory: CourseFactory object :return: None """ test_output = {} @@ -232,9 +232,9 @@ def test_all_files(config, client, course_factory): test_files = os.scandir(test_path) for yaml_file in test_files: if not yaml_file.name.startswith('.') and yaml_file.is_file(): # Exclude possible failures - test_submission_yaml(client, course_factory, yaml_file.path, test_output, client_sync) + test_submission_yaml(client, taskset_factory, yaml_file.path, test_output, client_sync) task_yaml_path = os.path.join(task.path, "task.yaml") - test_task_yaml(task_yaml_path, test_output, course_factory, task.name, os.path.split(dir_path)[1], config) + test_task_yaml(task_yaml_path, test_output, taskset_factory, task.name, os.path.split(dir_path)[1], config) if test_output != {}: # errors in task.yaml ou submission.test output = json.dumps(test_output) if "file" in config: @@ -299,13 +299,13 @@ if __name__ == "__main__": fs_provider = LocalFSProvider(config["task_directory"]) try: - course_factory, _ = create_factories(fs_provider, task_dispensers, problem_types) # used for getting tasks + taskset_factory, _ = create_factories(fs_provider, task_dispensers, problem_types) # used for getting tasks - client = create_client(config, course_factory, fs_provider) + client = create_client(config, taskset_factory, fs_provider) client.start() - test_all_files(config, client, course_factory) + test_all_files(config, client, taskset_factory) except BaseException as e: print("\nAn error has occured: {}\n".format(e), file=sys.stderr) diff --git a/inginious-webapp b/inginious-webapp index ecae80739b..fc4be02cfa 100755 --- a/inginious-webapp +++ b/inginious-webapp @@ -58,12 +58,13 @@ if not configfile: # Load configuration and application (!!! For mod_wsgi, application identifier must be present) config = load_json_or_yaml(configfile) -application, close_app_func = inginious.frontend.app.get_app(config) # Init logging init_logging(config.get('log_level', 'INFO')) logging.getLogger("inginious.webapp").info("http://%s:%d/" % (host, int(port))) +application, close_app_func = inginious.frontend.app.get_app(config) + if 'SERVER_SOFTWARE' in os.environ: # cgi os.environ['FCGI_FORCE_CGI'] = 'Y' diff --git a/inginious-webdav b/inginious-webdav index 9a332f8f6a..5403f0f709 100755 --- a/inginious-webdav +++ b/inginious-webdav @@ -51,11 +51,11 @@ if not configfile: # Load configuration and application (!!! For mod_wsgi, application identifier must be present) config = load_json_or_yaml(configfile) -application = inginious.frontend.webdav.get_app(config) - # Init logging init_logging(config.get('log_level', 'INFO')) logging.getLogger("inginious.webdav").info("http://%s:%d/" % (host, int(port))) +application = inginious.frontend.webdav.get_app(config) + if 'SERVER_SOFTWARE' in os.environ: # cgi os.environ['FCGI_FORCE_CGI'] = 'Y' diff --git a/inginious/__init__.py b/inginious/__init__.py index e5a435240c..00ba212ad3 100644 --- a/inginious/__init__.py +++ b/inginious/__init__.py @@ -16,7 +16,7 @@ try: __version__ = version(__name__) except PackageNotFoundError: - __version__ = "0.7.dev0" + __version__ = "0.9.dev0" MARKETPLACE_URL = "https://marketplace.inginious.org/marketplace.json" DB_VERSION = 15 diff --git a/inginious/agent/__init__.py b/inginious/agent/__init__.py index c2d20a5046..cad9950fba 100644 --- a/inginious/agent/__init__.py +++ b/inginious/agent/__init__.py @@ -167,15 +167,15 @@ async def __handle_new_job(self, message: BackendNewJob): try: if message.environment_type not in self.environments or message.environment not in self.environments[message.environment_type]: - self._logger.warning("Task %s/%s ask for an unknown environment %s/%s", message.course_id, message.task_id, + self._logger.warning("Task %s/%s ask for an unknown environment %s/%s", message.taskset_id, message.task_id, message.environment_type, message.environment) - raise CannotCreateJobException('This environment is not available in this agent. Please contact your course administrator.') + raise CannotCreateJobException('This environment is not available in this agent. Please contact the taskset administrator.') - task_fs = self._fs.from_subfolder(message.course_id).from_subfolder(message.task_id) + task_fs = self._fs.from_subfolder(message.taskset_id).from_subfolder(message.task_id) if not task_fs.exists(): - self._logger.warning("Task %s/%s unavailable on this agent", message.course_id, message.task_id) + self._logger.warning("Task %s/%s unavailable on this agent", message.taskset_id, message.task_id) raise CannotCreateJobException('Task unavailable on agent. Please retry later, the agents should synchronize soon. If the error ' - 'persists, please contact your course administrator.') + 'persists, please contact the taskset administrator.') # Let the subclass run the job await self.new_job(message) @@ -184,14 +184,14 @@ async def __handle_new_job(self, message: BackendNewJob): except TooManyCallsException: self._logger.exception("TooManyCallsException in new_job") await self.send_job_result(job_id=message.job_id, result="crash", - text="An unknown error occurred in the agent. Please contact your course administrator.", + text="An unknown error occurred in the agent. Please contact the taskset administrator.", state=previous_state) except JobNotRunningException: self._logger.exception("JobNotRunningException in new_job") except: self._logger.exception("Unknown exception in new_job") await self.send_job_result(job_id=message.job_id, result="crash", - text="An unknown error occurred in the agent. Please contact your course administrator.", + text="An unknown error occurred in the agent. Please contact the taskset administrator.", state=previous_state) async def send_ssh_job_info(self, job_id: BackendJobId, host: str, port: int, username: str, key: str): diff --git a/inginious/agent/docker_agent/__init__.py b/inginious/agent/docker_agent/__init__.py index 7696ded2ff..b242e05a60 100644 --- a/inginious/agent/docker_agent/__init__.py +++ b/inginious/agent/docker_agent/__init__.py @@ -45,7 +45,7 @@ class DockerRunningJob: sockets_path: str student_path: str systemfiles_path: str - course_common_student_path: str + taskset_common_student_path: str run_cmd: str assigned_external_ports: List[int] student_containers: Set[str] # container ids of student containers @@ -71,7 +71,7 @@ def __init__(self, context, backend_addr, friendly_name, concurrency, tasks_fs: :param backend_addr: address of the backend (for example, "tcp://127.0.0.1:2222") :param friendly_name: a string containing a friendly name to identify agent :param concurrency: number of simultaneous jobs that can be run by this agent - :param tasks_fs: FileSystemProvider for the course / tasks + :param tasks_fs: FileSystemProvider for the taskset / tasks :param address_host: hostname/ip/... to which external client should connect to access to the docker :param external_ports: iterable containing ports to which the docker instance can bind internal ports :param tmp_dir: temp dir that is used by the agent to start new containers @@ -273,7 +273,7 @@ def __get_fd_limit(self): def __new_job_sync(self, message: BackendNewJob, future_results): """ Synchronous part of _new_job. Creates needed directories, copy files, and starts the container. """ - course_id = message.course_id + taskset_id = message.taskset_id task_id = message.task_id debug = message.debug @@ -290,28 +290,28 @@ def __new_job_sync(self, message: BackendNewJob, future_results): except: raise CannotCreateJobException('The agent is unable to parse the parameters') - course_fs = self._fs.from_subfolder(course_id) - task_fs = course_fs.from_subfolder(task_id) + taskset_fs = self._fs.from_subfolder(taskset_id) + task_fs = taskset_fs.from_subfolder(task_id) - if not course_fs.exists() or not task_fs.exists(): - self._logger.warning("Task %s/%s unavailable on this agent", course_id, task_id) + if not taskset_fs.exists() or not task_fs.exists(): + self._logger.warning("Task %s/%s unavailable on this agent", taskset_id, task_id) raise CannotCreateJobException( 'Task unavailable on agent. Please retry later, the agents should synchronize soon. ' - 'If the error persists, please contact your course administrator.') + 'If the error persists, please contact the taskset administrator.') # Check for realistic memory limit value if mem_limit < 20: mem_limit = 20 elif mem_limit > self._max_memory_per_slot: - self._logger.warning("Task %s/%s ask for too much memory (%dMB)! Available: %dMB", course_id, task_id, + self._logger.warning("Task %s/%s ask for too much memory (%dMB)! Available: %dMB", taskset_id, task_id, mem_limit, self._max_memory_per_slot) raise CannotCreateJobException( - 'Not enough memory on agent (available: %dMB). Please contact your course administrator.' % self._max_memory_per_slot) + 'Not enough memory on agent (available: %dMB). Please contact the taskset administrator.' % self._max_memory_per_slot) if environment_type not in self._containers or environment_name not in self._containers[environment_type]: - self._logger.warning("Task %s/%s ask for an unknown environment %s/%s", course_id, task_id, + self._logger.warning("Task %s/%s ask for an unknown environment %s/%s", taskset_id, task_id, environment_type, environment_name) - raise CannotCreateJobException('Unknown container. Please contact your course administrator.') + raise CannotCreateJobException('Unknown container. Please contact the taskset administrator.') environment = self._containers[environment_type][environment_name]["id"] runtime = self._containers[environment_type][environment_name]["runtime"] @@ -342,20 +342,20 @@ def __new_job_sync(self, message: BackendNewJob, future_results): raise CannotCreateJobException('Cannot make container temp directory.') task_path = path_join(container_path, 'task') # tmp_dir/id/task/ - course_path = path_join(container_path, 'course') + taskset_path = path_join(container_path, 'course') sockets_path = path_join(container_path, 'sockets') # tmp_dir/id/socket/ student_path = path_join(task_path, 'student') # tmp_dir/id/task/student/ systemfiles_path = path_join(task_path, 'systemfiles') # tmp_dir/id/task/systemfiles/ - course_common_path = path_join(course_path, 'common') - course_common_student_path = path_join(course_path, 'common', 'student') + taskset_common_path = path_join(taskset_path, 'common') + taskset_common_student_path = path_join(taskset_path, 'common', 'student') # Create the needed directories os.mkdir(sockets_path) os.chmod(container_path, 0o777) os.chmod(sockets_path, 0o777) - os.mkdir(course_path) + os.mkdir(taskset_path) # TODO: avoid copy task_fs.copy_from(None, task_path) @@ -367,21 +367,21 @@ def __new_job_sync(self, message: BackendNewJob, future_results): # Copy common and common/student if needed # TODO: avoid copy - if course_fs.from_subfolder("$common").exists(): - course_fs.from_subfolder("$common").copy_from(None, course_common_path) + if taskset_fs.from_subfolder("$common").exists(): + taskset_fs.from_subfolder("$common").copy_from(None, taskset_common_path) else: - os.mkdir(course_common_path) + os.mkdir(taskset_common_path) - if course_fs.from_subfolder("$common").from_subfolder("student").exists(): - course_fs.from_subfolder("$common").from_subfolder("student").copy_from(None, course_common_student_path) + if taskset_fs.from_subfolder("$common").from_subfolder("student").exists(): + taskset_fs.from_subfolder("$common").from_subfolder("student").copy_from(None, taskset_common_student_path) else: - os.mkdir(course_common_student_path) + os.mkdir(taskset_common_student_path) # Run the container try: container_id = self._docker.sync.create_container(environment, enable_network, mem_limit, task_path, - sockets_path, course_common_path, - course_common_student_path, + sockets_path, taskset_common_path, + taskset_common_student_path, self.__get_fd_limit(), runtime, ports) except Exception as e: @@ -409,7 +409,7 @@ def __new_job_sync(self, message: BackendNewJob, future_results): sockets_path=sockets_path, student_path=student_path, systemfiles_path=systemfiles_path, - course_common_student_path=course_common_student_path, + taskset_common_student_path=taskset_common_student_path, run_cmd=run_cmd, assigned_external_ports=list(ports.values()), student_containers=set(), @@ -489,7 +489,7 @@ async def create_student_container(self, parent_info, socket_id, environment_nam memory_limit, parent_info.student_path, socket_path, parent_info.systemfiles_path, - parent_info.course_common_student_path, + parent_info.taskset_common_student_path, parent_info.environment_type, self.__get_fd_limit(), parent_info.container_id if share_network else None, diff --git a/inginious/agent/docker_agent/_docker_interface.py b/inginious/agent/docker_agent/_docker_interface.py index 67e6cfa218..32363397fb 100644 --- a/inginious/agent/docker_agent/_docker_interface.py +++ b/inginious/agent/docker_agent/_docker_interface.py @@ -110,7 +110,7 @@ def get_host_ip(self, env_with_dig='ingi/inginious-c-default'): return None def create_container(self, image, network_grading, mem_limit, task_path, sockets_path, - course_common_path, course_common_student_path, fd_limit, runtime: str, ports=None): + taskset_common_path, taskset_common_student_path, fd_limit, runtime: str, ports=None): """ Creates a container. :param image: env to start (name/id of a docker image) @@ -118,8 +118,8 @@ def create_container(self, image, network_grading, mem_limit, task_path, sockets :param mem_limit: in Mo :param task_path: path to the task directory that will be mounted in the container :param sockets_path: path to the socket directory that will be mounted in the container - :param course_common_path: - :param course_common_student_path: + :param taskset_common_path: + :param taskset_common_student_path: :param fd_limit: Tuple with soft and hard limits per slot for FS :param runtime: name of the docker runtime to use :param ports: dictionary in the form {docker_port: external_port} @@ -127,8 +127,8 @@ def create_container(self, image, network_grading, mem_limit, task_path, sockets """ task_path = os.path.abspath(task_path) sockets_path = os.path.abspath(sockets_path) - course_common_path = os.path.abspath(course_common_path) - course_common_student_path = os.path.abspath(course_common_student_path) + taskset_common_path = os.path.abspath(taskset_common_path) + taskset_common_student_path = os.path.abspath(taskset_common_student_path) if ports is None: ports = {} @@ -146,8 +146,8 @@ def create_container(self, image, network_grading, mem_limit, task_path, sockets volumes={ task_path: {'bind': '/task'}, sockets_path: {'bind': '/sockets'}, - course_common_path: {'bind': '/course/common', 'mode': 'ro'}, - course_common_student_path: {'bind': '/course/common/student', 'mode': 'ro'} + taskset_common_path: {'bind': '/course/common', 'mode': 'ro'}, + taskset_common_student_path: {'bind': '/course/common/student', 'mode': 'ro'} }, runtime=runtime, ulimits=[nofile_limit] @@ -155,7 +155,7 @@ def create_container(self, image, network_grading, mem_limit, task_path, sockets return response.id def create_container_student(self, runtime: str, image: str, mem_limit, student_path, - socket_path, systemfiles_path, course_common_student_path, + socket_path, systemfiles_path, taskset_common_student_path, parent_runtime: str,fd_limit, share_network_of_container: str=None, ports=None): """ Creates a student container @@ -166,7 +166,7 @@ def create_container_student(self, runtime: str, image: str, mem_limit, student_ :param student_path: path to the task directory that will be mounted in the container :param socket_path: path to the socket that will be mounted in the container :param systemfiles_path: path to the systemfiles folder containing files that can override partially some defined system files - :param course_common_student_path: + :param taskset_common_student_path: :param share_network_of_container: (deprecated) if a container id is given, the new container will share its network stack. :param ports: dictionary in the form {docker_port: external_port} @@ -175,7 +175,7 @@ def create_container_student(self, runtime: str, image: str, mem_limit, student_ student_path = os.path.abspath(student_path) socket_path = os.path.abspath(socket_path) systemfiles_path = os.path.abspath(systemfiles_path) - course_common_student_path = os.path.abspath(course_common_student_path) + taskset_common_student_path = os.path.abspath(taskset_common_student_path) secured_scripts_path = student_path+"/scripts" if ports is None: @@ -205,7 +205,7 @@ def create_container_student(self, runtime: str, image: str, mem_limit, student_ secured_scripts_path: {'bind': '/task/student/scripts'}, socket_path: {'bind': '/__parent.sock'}, systemfiles_path: {'bind': '/task/systemfiles', 'mode': 'ro'}, - course_common_student_path: {'bind': '/course/common/student', 'mode': 'ro'} + taskset_common_student_path: {'bind': '/course/common/student', 'mode': 'ro'} }, runtime=runtime, ulimits=[nofile_limit] diff --git a/inginious/agent/mcq_agent/__init__.py b/inginious/agent/mcq_agent/__init__.py index f468b86d57..d406192e52 100644 --- a/inginious/agent/mcq_agent/__init__.py +++ b/inginious/agent/mcq_agent/__init__.py @@ -19,7 +19,7 @@ def __init__(self, context, backend_addr, friendly_name, concurrency, tasks_file :param context: ZeroMQ context for this process :param backend_addr: address of the backend (for example, "tcp://127.0.0.1:2222") :param friendly_name: a string containing a friendly name to identify agent - :param tasks_filesystem: FileSystemProvider to the course/tasks + :param tasks_filesystem: FileSystemProvider to the taskset/tasks :param problem_types: Problem types dictionary """ super().__init__(context, backend_addr, friendly_name, concurrency, tasks_filesystem) @@ -78,15 +78,15 @@ async def new_job(self, msg: BackendNewJob): # This may pose problem with apps that start multiple MCQAgents in the same process... builtins.__dict__['_'] = translation.gettext - course_fs = self._fs.from_subfolder(msg.course_id) - task_fs = course_fs.from_subfolder(msg.task_id) + taskset_fs = self._fs.from_subfolder(msg.taskset_id) + task_fs = taskset_fs.from_subfolder(msg.task_id) translations_fs = task_fs.from_subfolder("$i18n") if not translations_fs.exists(): translations_fs = task_fs.from_subfolder("student").from_subfolder("$i18n") if not translations_fs.exists(): - translations_fs = course_fs.from_subfolder("$common").from_subfolder("$i18n") + translations_fs = taskset_fs.from_subfolder("$common").from_subfolder("$i18n") if not translations_fs.exists(): - translations_fs = course_fs.from_subfolder("$common").from_subfolder("student")\ + translations_fs = taskset_fs.from_subfolder("$common").from_subfolder("student")\ .from_subfolder("$i18n") if translations_fs.exists() and translations_fs.exists(language + ".mo"): @@ -113,7 +113,7 @@ async def new_job(self, msg: BackendNewJob): problems[key] = (p_result, "\n\n".join(messages)) if need_emul: - self._logger.warning("Task %s/%s is not a pure MCQ but has env=MCQ", msg.course_id, msg.task_id) + self._logger.warning("Task %s/%s is not a pure MCQ but has env=MCQ", msg.taskset_id, msg.task_id) raise CannotCreateJobException("Task wrongly configured as a MCQ") if error_count != 0: diff --git a/inginious/backend/backend.py b/inginious/backend/backend.py index 4fd46db935..1764858eac 100644 --- a/inginious/backend/backend.py +++ b/inginious/backend/backend.py @@ -171,12 +171,12 @@ async def handle_client_get_queue(self, client_addr, _: ClientGetQueue): for job_id, content in self._job_running.items(): agent_friendly_name = self._registered_agents[content.agent_addr].name jobs_running.append((content.msg.job_id, content.client_addr == client_addr, agent_friendly_name, - content.msg.course_id+"/"+content.msg.task_id, + content.msg.taskset_id+"/"+content.msg.task_id, content.msg.launcher, int(content.time_started), self._get_time_limit_estimate(content.msg))) #jobs_waiting: a list of tuples in the form #(job_id, is_current_client_job, info, launcher, max_time) - jobs_waiting = [(job.job_id, job.client_addr == client_addr, job.msg.course_id+"/"+job.msg.task_id, job.msg.launcher, + jobs_waiting = [(job.job_id, job.client_addr == client_addr, job.msg.taskset_id+"/"+job.msg.task_id, job.msg.launcher, self._get_time_limit_estimate(job.msg)) for job in self._waiting_jobs.values()] await ZMQUtils.send_with_addr(self._client_socket, client_addr, BackendGetQueue(jobs_running, jobs_waiting)) @@ -216,7 +216,7 @@ async def update_queue(self): # Send the job to agent self._job_running[job_id] = RunningJob(agent_addr, client_addr, job_msg, time.time()) self._logger.info("Sending job %s %s to agent %s", client_addr, job_id, agent_addr) - await ZMQUtils.send_with_addr(self._agent_socket, agent_addr, BackendNewJob(job_id, job_msg.course_id, job_msg.task_id, + await ZMQUtils.send_with_addr(self._agent_socket, agent_addr, BackendNewJob(job_id, job_msg.taskset_id, job_msg.task_id, job_msg.task_problems, job_msg.inputdata, job_msg.environment_type, job_msg.environment, diff --git a/inginious/client/client.py b/inginious/client/client.py index 2d0823fcb5..4778253439 100644 --- a/inginious/client/client.py +++ b/inginious/client/client.py @@ -47,7 +47,7 @@ def get_available_environments(self) -> Dict[str, List[str]]: pass @abstractmethod - def new_job(self, task, inputdata, callback, launcher_name="Unknown", debug=False, ssh_callback=None): + def new_job(self, priority, taskset, task, inputdata, callback, launcher_name="Unknown", debug=False, ssh_callback=None): """ Add a new job. Every callback will be called once and only once. :type task: Task @@ -97,7 +97,7 @@ def get_job_queue_snapshot(self): - job_id is a job id. It may be from another client. - is_current_client_job is a boolean indicating if the client that asked the request has started the job - agent_name is the agent name - - info is "courseid/taskid" + - info is "tasksetid/taskid" - launcher is the name of the launcher, which may be anything - started_at the time (in seconds since UNIX epoch) at which the job started - max_time the maximum time that can be used, or -1 if no timeout is set @@ -108,7 +108,7 @@ def get_job_queue_snapshot(self): - job_id is a job id. It may be from another client. - is_current_client_job is a boolean indicating if the client that asked the request has started the job - - info is "courseid/taskid" + - info is "tasksetid/taskid" - launcher is the name of the launcher, which may be anything - max_time the maximum time that can be used, or -1 if no timeout is set @@ -269,10 +269,11 @@ def get_available_environments(self) -> Dict[str, List[str]]: """ return self._available_environments - def new_job(self, priority, task, inputdata, callback, launcher_name="Unknown", debug=False, ssh_callback=None): + def new_job(self, priority, taskset, task, inputdata, callback, launcher_name="Unknown", debug=False, ssh_callback=None): """ Add a new job. Every callback will be called once and only once. :param priority: Priority of the job - :type task: Task + :param taskset : Taskset + :param task: Task :param inputdata: input from the student :type inputdata: Storage or dict :param callback: a function that will be called asynchronously in the client's process, with the results. @@ -297,7 +298,7 @@ def new_job(self, priority, task, inputdata, callback, launcher_name="Unknown", safe_callback = _callable_once(callback) if debug == "ssh" and ssh_callback is None: - self._logger.error("SSH callback not set in %s/%s", task.get_course_id(), task.get_id()) + self._logger.error("SSH callback not set in %s/%s", taskset.get_id(), task.get_id()) safe_callback(("crash", "SSH callback not set."), 0.0, {}, {}, {}, None, "", "") return None # wrap ssh_callback to ensure it is called at most once, and that it can always be called to simplify code @@ -307,7 +308,7 @@ def new_job(self, priority, task, inputdata, callback, launcher_name="Unknown", environment = task.get_environment_id() if environment_type not in self._available_environments or environment not in self._available_environments[environment_type]: - self._logger.warning("Env %s/%s not available for task %s/%s", environment_type, environment, task.get_course_id(), + self._logger.warning("Env %s/%s not available for task %s/%s", environment_type, environment, taskset.get_id(), task.get_id()) ssh_callback(None, None, None, None) # ssh_callback must be called once safe_callback(("crash", "Environment not available."), 0.0, {}, {}, "", {}, None, "", "") @@ -315,7 +316,7 @@ def new_job(self, priority, task, inputdata, callback, launcher_name="Unknown", environment_parameters = task.get_environment_parameters() - msg = ClientNewJob(job_id, priority, task.get_course_id(), task.get_id(), task.get_problems_dict(), inputdata, + msg = ClientNewJob(job_id, priority, taskset.get_id(), task.get_id(), task.get_problems_dict(), inputdata, environment_type, environment, environment_parameters, debug, launcher_name) self._loop.call_soon_threadsafe(asyncio.ensure_future, self._create_transaction(msg, task=task, callback=safe_callback, diff --git a/inginious/client/client_buffer.py b/inginious/client/client_buffer.py index fd6b325040..905397f394 100644 --- a/inginious/client/client_buffer.py +++ b/inginious/client/client_buffer.py @@ -16,11 +16,11 @@ def __init__(self, client): self._waiting_jobs = [] self._jobs_done = {} - def new_job(self, priority, task, inputdata, launcher_name="Unknown", debug=False): + def new_job(self, priority, taskset, task, inputdata, launcher_name="Unknown", debug=False): """ Runs a new job. It works exactly like the Client class, instead that there is no callback """ bjobid = uuid.uuid4() self._waiting_jobs.append(str(bjobid)) - self._client.new_job(priority, task, inputdata, + self._client.new_job(priority, taskset, task, inputdata, (lambda result, grade, problems, tests, custom, archive, stdout, stderr: self._callback(bjobid, result, grade, problems, tests, custom, archive, stdout, stderr)), launcher_name, debug) diff --git a/inginious/client/client_sync.py b/inginious/client/client_sync.py index 2d42d6d52f..e1fe79b31c 100644 --- a/inginious/client/client_sync.py +++ b/inginious/client/client_sync.py @@ -13,7 +13,7 @@ class ClientSync(object): def __init__(self, client): self._client = client - def new_job(self, priority, task, inputdata, launcher_name="Unknown", debug=False): + def new_job(self, priority, taskset, task, inputdata, launcher_name="Unknown", debug=False): """ Runs a new job. It works exactly like the Client class, instead that there is no callback and directly returns result, in the form of a tuple @@ -28,7 +28,7 @@ def manage_output(result, grade, problems, tests, custom, state, archive, stdout manage_output.job_return = None - self._client.new_job(priority, task, inputdata, manage_output, launcher_name, debug) + self._client.new_job(priority, taskset, task, inputdata, manage_output, launcher_name, debug) job_semaphore.acquire() job_return = manage_output.job_return return job_return diff --git a/inginious/common/base.py b/inginious/common/base.py index c75c763b70..9b116c8a28 100644 --- a/inginious/common/base.py +++ b/inginious/common/base.py @@ -112,7 +112,7 @@ def dict_from_prefix(prefix, dictionary): >>> od["problem[q0][b][c]"]=2 >>> od["problem[q1][first]"]=1 >>> od["problem[q1][second]"]=2 - >>> AdminCourseEditTask.dict_from_prefix("problem",od) + >>> dict_from_prefix("problem",od) OrderedDict([('q0', OrderedDict([('a', 1), ('b', OrderedDict([('c', 2)]))])), ('q1', OrderedDict([('first', 1), ('second', 2)]))]) """ o_dictionary = OrderedDict() diff --git a/inginious/common/exceptions.py b/inginious/common/exceptions.py index 51c9355343..96040d4926 100644 --- a/inginious/common/exceptions.py +++ b/inginious/common/exceptions.py @@ -10,32 +10,17 @@ class InvalidNameException(Exception): pass -class CourseNotFoundException(Exception): - pass - - class TaskNotFoundException(Exception): pass -class CourseUnreadableException(Exception): - pass - - -class CourseAlreadyExistsException(Exception): - pass - class TaskAlreadyExistsException(Exception): pass + class TaskUnreadableException(Exception): pass class TaskReaderNotFoundException(Exception): pass - - -class ImportCourseException(Exception): - pass - diff --git a/inginious/common/filesystems/local.py b/inginious/common/filesystems/local.py index 88dfc0a02e..3b133a5565 100644 --- a/inginious/common/filesystems/local.py +++ b/inginious/common/filesystems/local.py @@ -25,7 +25,7 @@ def get_needed_args(cls): Only int and str are supported as types. """ return { - "location": (str, True, "On-disk path to the directory containing courses/tasks") + "location": (str, True, "On-disk path to the directory containing tasksets/tasks") } @classmethod diff --git a/inginious/common/log.py b/inginious/common/log.py index f919e9ffa1..3952914dfe 100644 --- a/inginious/common/log.py +++ b/inginious/common/log.py @@ -29,14 +29,6 @@ def init_logging(log_level=logging.DEBUG): oauthlib_log.setLevel(log_level) oauthlib_log.addHandler(ch) -def get_course_logger(coursename): - """ - :param coursename: the course id - :return: a logger object associated to a specific course - """ - return logging.getLogger("inginious.course."+coursename) - - class CustomLogMiddleware: """ WSGI middleware for logging the status in webpy""" diff --git a/inginious/common/messages.py b/inginious/common/messages.py index eecfab9f32..fbf5debfe0 100644 --- a/inginious/common/messages.py +++ b/inginious/common/messages.py @@ -31,7 +31,7 @@ class ClientNewJob: """ Creates a new job """ job_id: ClientJobId # the client-side job id that is associated to this job priority: int # the job priority - course_id: str # course id of the task to run + taskset_id: str # taskset id of the task to run task_id: str # task id of the task to run task_problems: Dict[str, Any] # task dictionary inputdata: Dict[str, Any] # student input data @@ -117,7 +117,7 @@ class BackendGetQueue: - job_id is a job id. It may be from another client. - is_current_client_job is a boolean indicating if the client that asked the request has started the job - agent_name is the agent name - - info is "courseid/taskid" + - info is "tasksetid/taskid" - launcher is the name of the launcher, which may be anything - started_at the time (in seconds since UNIX epoch) at which the job started - max_time the maximum time that can be used, or -1 if no timeout is set @@ -128,7 +128,7 @@ class BackendGetQueue: - job_id is a job id. It may be from another client. - is_current_client_job is a boolean indicating if the client that asked the request has started the job - - info is "courseid/taskid" + - info is "tasksetid/taskid" - launcher is the name of the launcher, which may be anything - max_time the maximum time that can be used, or -1 if no timeout is set @@ -148,7 +148,7 @@ class BackendGetQueue: class BackendNewJob: """ Creates a new job """ job_id: BackendJobId # the backend-side job id that is associated to this job - course_id: str # course id of the task to run + taskset_id: str # taskset id of the task to run task_id: str # task id of the task to run task_problems: Dict[str, Any] # task dictionary inputdata: Dict[str, Any] # student input data diff --git a/inginious/frontend/app.py b/inginious/frontend/app.py index 5f4e8c522b..780833d5ad 100644 --- a/inginious/frontend/app.py +++ b/inginious/frontend/app.py @@ -18,6 +18,7 @@ from werkzeug.exceptions import InternalServerError import inginious.frontend.pages.course_admin.utils as course_admin_utils +import inginious.frontend.pages.taskset_admin.utils as taskset_admin_utils import inginious.frontend.pages.preferences.utils as preferences_utils from inginious.frontend.environment_types import register_base_env_types from inginious.frontend.arch_helper import create_arch, start_asyncio_and_zmq @@ -29,7 +30,7 @@ from inginious.frontend.user_manager import UserManager from inginious.frontend.l10n_manager import L10nManager from inginious import get_root_path, __version__, DB_VERSION -from inginious.frontend.course_factory import create_factories +from inginious.frontend.taskset_factory import create_factories from inginious.common.entrypoints import filesystem_from_config_dict from inginious.common.filesystems.local import LocalFSProvider from inginious.frontend.lti_outcome_manager import LTIOutcomeManager @@ -175,6 +176,15 @@ def get_app(config): available_languages = {"en": "English"} available_languages.update(available_translations) + available_themes = [ + theme_dir for theme_dir in os.listdir(os.path.join(get_root_path(), 'frontend', 'static', 'css', 'themes')) + if theme_dir not in ('codemirror', ) and not theme_dir.startswith(('.', '_')) + ] + available_codemirror_themes= [ + file.split('.')[0] for file in os.listdir(os.path.join(get_root_path(), 'frontend', 'static', 'css', 'themes', 'codemirror')) + if file.endswith('.css') and not file.startswith('main') + ] + l10n_manager = L10nManager() l10n_manager.translations["en"] = gettext.NullTranslations() # English does not need translation ;-) @@ -188,6 +198,8 @@ def get_app(config): template_helper.add_to_template_globals("get_homepath", get_homepath) template_helper.add_to_template_globals("pkg_version", __version__) template_helper.add_to_template_globals("available_languages", available_languages) + template_helper.add_to_template_globals("available_themes", available_themes) + template_helper.add_to_template_globals("available_codemirror_themes", available_codemirror_themes) template_helper.add_to_template_globals("_", _) flask_app.template_helper = template_helper init_flask_maintenance_mapping(flask_app) @@ -217,13 +229,13 @@ def get_app(config): default_problem_types = get_default_displayable_problem_types() - course_factory, task_factory = create_factories(fs_provider, default_task_dispensers, default_problem_types, plugin_manager, database) + taskset_factory, course_factory, task_factory = create_factories(fs_provider, default_task_dispensers, default_problem_types, plugin_manager, database) user_manager = UserManager(database, config.get('superadmins', [])) update_pending_jobs(database) - client = create_arch(config, fs_provider, zmq_context, course_factory) + client = create_arch(config, fs_provider, zmq_context, taskset_factory) lti_outcome_manager = LTIOutcomeManager(database, user_manager, course_factory) @@ -241,6 +253,8 @@ def get_app(config): template_helper.add_to_template_globals("_", _) template_helper.add_to_template_globals("str", str) template_helper.add_to_template_globals("available_languages", available_languages) + template_helper.add_to_template_globals("available_themes", available_themes) + template_helper.add_to_template_globals("available_codemirror_themes", available_codemirror_themes) template_helper.add_to_template_globals("get_homepath", get_homepath) template_helper.add_to_template_globals("pkg_version", __version__) template_helper.add_to_template_globals("allow_registration", config.get("allow_registration", True)) @@ -253,6 +267,9 @@ def get_app(config): template_helper.add_other("course_admin_menu", lambda course, current: course_admin_utils.get_menu(course, current, template_helper.render, plugin_manager, user_manager)) + template_helper.add_other("taskset_admin_menu", + lambda taskset, current: taskset_admin_utils.get_menu(taskset, current, template_helper.render, + user_manager)) template_helper.add_other("preferences_menu", lambda current: preferences_utils.get_menu(config.get("allow_deletion", True), current, template_helper.render, @@ -280,6 +297,7 @@ def flask_internalerror(e): # Insert the needed singletons into the application, to allow pages to call them flask_app.get_homepath = get_homepath flask_app.plugin_manager = plugin_manager + flask_app.taskset_factory = taskset_factory flask_app.course_factory = course_factory flask_app.task_factory = task_factory flask_app.submission_manager = submission_manager @@ -297,6 +315,8 @@ def flask_internalerror(e): flask_app.allow_registration = config.get("allow_registration", True) flask_app.allow_deletion = config.get("allow_deletion", True) flask_app.available_languages = available_languages + flask_app.available_themes = available_themes + flask_app.available_codemirror_themes = available_codemirror_themes flask_app.welcome_page = config.get("welcome_page", None) flask_app.terms_page = config.get("terms_page", None) flask_app.privacy_page = config.get("privacy_page", None) diff --git a/inginious/frontend/arch_helper.py b/inginious/frontend/arch_helper.py index 6015fdf0ac..4790b5864b 100644 --- a/inginious/frontend/arch_helper.py +++ b/inginious/frontend/arch_helper.py @@ -55,13 +55,13 @@ async def _restart_on_cancel(logger, agent): logger.exception("Restarting agent") pass -def create_arch(configuration, tasks_fs, context, course_factory): +def create_arch(configuration, tasks_fs, context, taskset_factory): """ Helper that can start a simple complete INGInious arch locally if needed, or a client to a remote backend. Intended to be used on command line, makes uses of exit() and the logger inginious.frontend. :param configuration: configuration dict :param tasks_fs: FileSystemProvider to the courses/tasks folders :param context: a ZMQ context - :param course_factory: The course factory to be used by the frontend + :param taskset_factory: The course factory to be used by the frontend :param is_testing: boolean :return: a Client object """ @@ -91,7 +91,7 @@ def create_arch(configuration, tasks_fs, context, course_factory): client = Client(context, "inproc://backend_client") backend = Backend(context, "inproc://backend_agent", "inproc://backend_client") agent_docker = DockerAgent(context, "inproc://backend_agent", "Docker - Local agent", concurrency, tasks_fs, debug_host, debug_ports, tmp_dir, ssh_allowed=True) - agent_mcq = MCQAgent(context, "inproc://backend_agent", "MCQ - Local agent", 1, tasks_fs, course_factory.get_task_factory().get_problem_types()) + agent_mcq = MCQAgent(context, "inproc://backend_agent", "MCQ - Local agent", 1, tasks_fs, taskset_factory.get_task_factory().get_problem_types()) asyncio.ensure_future(_restart_on_cancel(logger, agent_docker)) asyncio.ensure_future(_restart_on_cancel(logger, agent_mcq)) diff --git a/inginious/frontend/babel.cfg b/inginious/frontend/babel.cfg index 3281286e2f..179cc8a783 100644 --- a/inginious/frontend/babel.cfg +++ b/inginious/frontend/babel.cfg @@ -1,3 +1,3 @@ [python: inginious/frontend/**.py] [jinja2: inginious/frontend/**.html] -extensions=jinja2.ext.i18n,jinja2.ext.autoescape,jinja2.ext.with_ +extensions=jinja2.ext.i18n diff --git a/inginious/frontend/course_factory.py b/inginious/frontend/course_factory.py index 637eebdc11..05e66d3297 100644 --- a/inginious/frontend/course_factory.py +++ b/inginious/frontend/course_factory.py @@ -4,64 +4,37 @@ # more information about the licensing of this file. """ Factory for loading courses from disk """ -import logging -from inginious.common.filesystems import FileSystemProvider -from inginious.common.log import get_course_logger -from inginious.common.base import id_checker, get_json_or_yaml, loads_json_or_yaml -from inginious.frontend.plugin_manager import PluginManager -from inginious.common.exceptions import InvalidNameException, CourseNotFoundException, CourseUnreadableException, CourseAlreadyExistsException +from pymongo import ReturnDocument +from inginious.frontend.log import get_course_logger + +from inginious.frontend.exceptions import CourseNotFoundException, CourseAlreadyExistsException, TasksetNotFoundException from inginious.frontend.courses import Course -from inginious.frontend.task_factory import TaskFactory class CourseFactory(object): """ Load courses from disk """ - _logger = logging.getLogger("inginious.course_factory") - def __init__(self, filesystem: FileSystemProvider, task_factory, plugin_manager, task_dispensers, database): - self._filesystem = filesystem + def __init__(self, taskset_factory, task_factory, plugin_manager, database): + self._taskset_factory = taskset_factory self._task_factory = task_factory self._plugin_manager = plugin_manager - self._task_dispensers = task_dispensers - self._cache = {} self._database = database + self._migrate_legacy_courses() + def add_task_dispenser(self, task_dispenser): """ :param task_dispenser: TaskDispenser class """ - self._task_dispensers.update({task_dispenser.get_id(): task_dispenser}) + self._taskset_factory.add_task_dispenser(task_dispenser) def get_task_dispensers(self): """ - Returns the supported task dispensers by this course factory + Returns the supported task dispensers by this taskset factory """ - return self._task_dispensers - - def get_course(self, courseid): - """ - :param courseid: the course id of the course - :raise: InvalidNameException, CourseNotFoundException, CourseUnreadableException - :return: an object representing the course, of the type given in the constructor - """ - if not id_checker(courseid): - raise InvalidNameException("Course with invalid name: " + courseid) - if self._cache_update_needed(courseid): - self._update_cache(courseid) - - return self._cache[courseid][0] - - def get_task(self, courseid, taskid): - """ - Shorthand for CourseFactory.get_course(courseid).get_task(taskid) - :param courseid: the course id of the course - :param taskid: the task id of the task - :raise InvalidNameException, CourseNotFoundException, CourseUnreadableException, TaskNotFoundException, TaskUnreadableException - :return: an object representing the task, of the type given in the constructor - """ - return self.get_course(courseid).get_task(taskid) + return self._taskset_factory.get_task_dispensers() def get_task_factory(self): """ @@ -70,184 +43,78 @@ def get_task_factory(self): return self._task_factory def get_course_descriptor_content(self, courseid): - """ - :param courseid: the course id of the course - :raise: InvalidNameException, CourseNotFoundException, CourseUnreadableException - :return: the content of the dict that describes the course - """ - path = self._get_course_descriptor_path(courseid) - return loads_json_or_yaml(path, self._filesystem.get(path).decode("utf-8")) + return self._database.courses.find_one({"_id": courseid}) - def update_course_descriptor_content(self, courseid, content): - """ - Updates the content of the dict that describes the course - :param courseid: the course id of the course - :param content: the new dict that replaces the old content - :raise InvalidNameException, CourseNotFoundException - """ - path = self._get_course_descriptor_path(courseid) - self._filesystem.put(path, get_json_or_yaml(path, content)) + def update_course_descriptor_content(self, courseid, course_content): + self._database.courses.find_one_and_update({"_id": courseid}, {"$set": course_content}) def update_course_descriptor_element(self, courseid, key, value): - """ - Updates the value for the key in the dict that describes the course - :param courseid: the course id of the course - :param key: the element to change in the dict - :param value: the new value that replaces the old one - :raise InvalidNameException, CourseNotFoundException - """ - course_structure = self.get_course_descriptor_content(courseid) - course_structure[key] = value - self.update_course_descriptor_content(courseid, course_structure) + self._database.courses.find_one_and_update({"_id": courseid}, {"$set": {key: value}}) - def get_fs(self): - """ - :return: a FileSystemProvider pointing to the task directory - """ - return self._filesystem + def import_legacy_course(self, database, courseid): + course_desc = self.get_course_descriptor_content(courseid) + database.courses.find_one_and_update({"_id": courseid}, {"$set": course_desc}, upsert=True, + return_document=ReturnDocument.AFTER) - def get_course_fs(self, courseid): - """ - :param courseid: the course id of the course - :return: a FileSystemProvider pointing to the directory of the course - """ - if not id_checker(courseid): - raise InvalidNameException("Course with invalid name: " + courseid) - return self._filesystem.from_subfolder(courseid) + def create_course(self, courseid, descriptor): + existing_course = self._database.courses.find_one({"_id": courseid}) + if existing_course: + raise CourseAlreadyExistsException() + + descriptor["_id"] = courseid + self._database.courses.insert_one(descriptor) + + def get_course(self, courseid): + course_desc = self.get_course_descriptor_content(courseid) + try: + return Course(courseid, course_desc, self._taskset_factory, self._task_factory, self._plugin_manager, self._database) + except Exception as e: + raise CourseNotFoundException() def get_all_courses(self): - """ - :return: a table containing courseid=>Course pairs - """ - course_ids = [f[0:len(f)-1] for f in self._filesystem.list(folders=True, files=False, recursive=False)] # remove trailing "/" - output = {} - for courseid in course_ids: + course_descriptors = self._database.courses.find({}) + result = {} + for course_desc in course_descriptors: + courseid = course_desc["_id"] try: - output[courseid] = self.get_course(courseid) + result[courseid] = Course(courseid, course_desc, self._taskset_factory, self._task_factory, self._plugin_manager, self._database) except Exception: get_course_logger(courseid).warning("Cannot open course", exc_info=True) - return output - def _get_course_descriptor_path(self, courseid): - """ - :param courseid: the course id of the course - :raise InvalidNameException, CourseNotFoundException - :return: the path to the descriptor of the course - """ - if not id_checker(courseid): - raise InvalidNameException("Course with invalid name: " + courseid) - course_fs = self.get_course_fs(courseid) - if course_fs.exists("course.yaml"): - return courseid+"/course.yaml" - if course_fs.exists("course.json"): - return courseid+"/course.json" - raise CourseNotFoundException() - - def create_course(self, courseid, init_content): - """ - Create a new course folder and set initial descriptor content, folder can already exist - :param courseid: the course id of the course - :param init_content: initial descriptor content - :raise: InvalidNameException or CourseAlreadyExistsException - """ - if not id_checker(courseid): - raise InvalidNameException("Course with invalid name: " + courseid) - - course_fs = self.get_course_fs(courseid) - course_fs.ensure_exists() - - if course_fs.exists("course.yaml") or course_fs.exists("course.json"): - raise CourseAlreadyExistsException("Course with id " + courseid + " already exists.") - else: - course_fs.put("course.yaml", get_json_or_yaml("course.yaml", init_content)) - - get_course_logger(courseid).info("Course %s created in the factory.", courseid) + return result def delete_course(self, courseid): - """ - Erase the content of the course folder - :param courseid: the course id of the course - :raise: InvalidNameException or CourseNotFoundException - """ - if not id_checker(courseid): - raise InvalidNameException("Course with invalid name: " + courseid) + self._database.courses.delete_one({"_id": courseid}) - course_fs = self.get_course_fs(courseid) + def _migrate_legacy_courses(self): + courseids = [] - if not course_fs.exists(): - raise CourseNotFoundException() + existing_courses = self._database.courses.find({}) + for course_descriptor in existing_courses: + if "tasksetid" not in course_descriptor: + courseids.append(course_descriptor["_id"]) - course_fs.delete() + for tasksetid, taskset in self._taskset_factory.get_all_tasksets().items(): + if taskset.is_legacy() and not self._database.courses.find_one({"_id": tasksetid}): + courseids.append(tasksetid) - get_course_logger(courseid).info("Course %s erased from the factory.", courseid) + for courseid in courseids: + get_course_logger(courseid).warning("Trying to migrate legacy course {}.".format(courseid)) - def _cache_update_needed(self, courseid): - """ - :param courseid: the (valid) course id of the course - :raise InvalidNameException, CourseNotFoundException - :return: True if an update of the cache is needed, False else - """ - if courseid not in self._cache: - return True - - try: - descriptor_name = self._get_course_descriptor_path(courseid) - last_update = {descriptor_name: self._filesystem.get_last_modification_time(descriptor_name)} - translations_fs = self._filesystem.from_subfolder("$i18n") - if translations_fs.exists(): - for f in translations_fs.list(folders=False, files=True, recursive=False): - lang = f[0:len(f) - 3] - if translations_fs.exists(lang + ".mo"): - last_update["$i18n/" + lang + ".mo"] = translations_fs.get_last_modification_time(lang + ".mo") - except: - raise CourseNotFoundException() - - last_modif = self._cache[courseid][1] - for filename, mftime in last_update.items(): - if filename not in last_modif or last_modif[filename] < mftime: - return True - - return False + try: + taskset_descriptor = self._taskset_factory.get_taskset_descriptor_content(courseid) + cleaned_taskset_descriptor = { + "name": taskset_descriptor["name"], + "admins": taskset_descriptor.get("admins", []), + "description": taskset_descriptor.get( "description", ""), + } + if "task_dispenser" in taskset_descriptor: + cleaned_taskset_descriptor["task_dispenser"] = taskset_descriptor["task_dispenser"] + cleaned_taskset_descriptor["dispenser_data"] = taskset_descriptor.get("dispenser_data", {}) + taskset_descriptor["tasksetid"] = courseid + taskset_descriptor["admins"] = taskset_descriptor.get("admins", []) + taskset_descriptor.get("tutors", []) + self._database.courses.update_one({"_id": courseid}, {"$set": taskset_descriptor}, upsert=True) + self._taskset_factory.update_taskset_descriptor_content(courseid, cleaned_taskset_descriptor) + except TasksetNotFoundException as e: + get_course_logger(courseid).warning("No migration from taskset possible for courseid {}.".format(courseid)) - def _update_cache(self, courseid): - """ - Updates the cache - :param courseid: the (valid) course id of the course - :raise InvalidNameException, CourseNotFoundException, CourseUnreadableException - """ - self._logger.info("Caching course {}".format(courseid)) - path_to_descriptor = self._get_course_descriptor_path(courseid) - try: - course_descriptor = loads_json_or_yaml(path_to_descriptor, self._filesystem.get(path_to_descriptor).decode("utf8")) - except Exception as e: - raise CourseUnreadableException(str(e)) - - last_modif = {path_to_descriptor: self._filesystem.get_last_modification_time(path_to_descriptor)} - translations_fs = self._filesystem.from_subfolder("$i18n") - if translations_fs.exists(): - for f in translations_fs.list(folders=False, files=True, recursive=False): - lang = f[0:len(f) - 3] - if translations_fs.exists(lang + ".mo"): - last_modif["$i18n/" + lang + ".mo"] = translations_fs.get_last_modification_time(lang + ".mo") - - self._cache[courseid] = ( - Course(courseid, course_descriptor, self.get_course_fs(courseid), self._task_factory, self._plugin_manager, self._task_dispensers, self._database), - last_modif - ) - - self._task_factory.update_cache_for_course(courseid) - - -def create_factories(fs_provider, task_dispensers, task_problem_types, plugin_manager=None, database=None): - """ - Shorthand for creating Factories - :param fs_provider: A FileSystemProvider leading to the courses - :param plugin_manager: a Plugin Manager instance. If None, a new Hook Manager is created - :param task_class: - :return: a tuple with two objects: the first being of type CourseFactory, the second of type TaskFactory - """ - if plugin_manager is None: - plugin_manager = PluginManager() - - task_factory = TaskFactory(fs_provider, plugin_manager, task_problem_types) - return CourseFactory(fs_provider, task_factory, plugin_manager, task_dispensers, database), task_factory diff --git a/inginious/frontend/courses.py b/inginious/frontend/courses.py index e614f3cffa..351ce5edfa 100644 --- a/inginious/frontend/courses.py +++ b/inginious/frontend/courses.py @@ -9,7 +9,6 @@ import gettext import re from typing import List -from collections import OrderedDict from inginious.frontend.user_settings.course_user_setting import CourseUserSetting from inginious.common.tags import Tag @@ -17,81 +16,61 @@ from inginious.frontend.parsable_text import ParsableText from inginious.frontend.user_manager import UserInfo from inginious.frontend.task_dispensers.toc import TableOfContents - - -def _migrate_from_v_0_6(content, task_list): - if 'task_dispenser' not in content: - content["task_dispenser"] = "toc" - if 'toc' in content: - content['dispenser_data'] = {"toc": content["toc"]} - else: - ordered_tasks = OrderedDict(sorted(list(task_list.items()), - key=lambda t: (int(t[1]._data.get('order', -1)), t[1].get_id()))) - content['dispenser_data'] = {"toc": [{"id": "tasks-list", "title": _("List of exercises"), - "rank": 0, "tasks_list": list(ordered_tasks.keys())}], "config": {}} +from inginious.frontend.tasksets import _migrate_from_v_0_6 class Course(object): """ A course with some modification for users """ - def __init__(self, courseid, content, course_fs, task_factory, plugin_manager, task_dispensers, database): + def __init__(self, courseid, content, taskset_factory, task_factory, plugin_manager, database): self._id = courseid self._content = content - self._fs = course_fs + self._taskset_factory = taskset_factory self._task_factory = task_factory self._plugin_manager = plugin_manager - self._translations = {} - translations_fs = self._fs.from_subfolder("$i18n") - if translations_fs.exists(): - for f in translations_fs.list(folders=False, files=True, recursive=False): - lang = f[0:len(f) - 3] - if translations_fs.exists(lang + ".mo"): - self._translations[lang] = gettext.GNUTranslations(translations_fs.get_fd(lang + ".mo")) - else: - self._translations[lang] = gettext.NullTranslations() try: self._name = self._content['name'] + self._tasksetid = self._content['tasksetid'] + self._taskset = taskset_factory.get_taskset(self._tasksetid) except: - raise Exception("Course has an invalid name: " + self.get_id()) - - if self._content.get('nofrontend', False): - raise Exception("That course is not allowed to be displayed directly in the webapp") - - _migrate_from_v_0_6(content, self._task_factory.get_all_tasks(self)) - + raise Exception("Course has an invalid name or tasksetid: " + self.get_id()) + + _migrate_from_v_0_6(content, self._task_factory.get_all_tasks(self._taskset)) + + self._admins = self._content.get('admins', []) + self._description = self._content.get('description', '') + self._accessible = AccessibleTime(self._content.get("accessible", None)) + self._registration = AccessibleTime(self._content.get("registration", None)) + self._registration_password = self._content.get('registration_password', None) + self._registration_ac = self._content.get('registration_ac', None) + if self._registration_ac not in [None, "username", "binding", "email"]: + raise Exception("Course has an invalid value for registration_ac: " + self.get_id()) + self._registration_ac_accept = self._content.get('registration_ac_accept', True) + self._registration_ac_list = self._content.get('registration_ac_list', []) + self._groups_student_choice = self._content.get("groups_student_choice", False) + self._allow_unregister = self._content.get('allow_unregister', True) + self._allow_preview = self._content.get('allow_preview', False) + self._is_lti = self._content.get('is_lti', False) + self._lti_url = self._content.get('lti_url', '') + self._lti_keys = self._content.get('lti_keys', {}) + self._lti_send_back_grade = self._content.get('lti_send_back_grade', False) + self._tags = {key: Tag(key, tag_dict, self.gettext) for key, tag_dict in self._content.get("tags", {}).items()} + self._course_user_setting = {key: CourseUserSetting(key, + field_dict["description"], + field_dict["type"]) + for key, field_dict in self._content.get("fields", {}).items()} + task_dispensers = self._taskset_factory.get_task_dispensers() + task_dispenser_class = task_dispensers.get(self._content.get('task_dispenser', 'toc'), TableOfContents) + # Here we use a lambda to encourage the task dispenser to pass by the task_factory to fetch course tasks + # to avoid them to be cached along with the course object. Passing the task factory as argument + # would require to pass the course too, and have a useless reference back. try: - self._admins = self._content.get('admins', []) - self._tutors = self._content.get('tutors', []) - self._description = self._content.get('description', '') - self._accessible = AccessibleTime(self._content.get("accessible", None)) - self._registration = AccessibleTime(self._content.get("registration", None)) - self._registration_password = self._content.get('registration_password', None) - self._registration_ac = self._content.get('registration_ac', None) - if self._registration_ac not in [None, "username", "binding", "email"]: - raise Exception("Course has an invalid value for registration_ac: " + self.get_id()) - self._registration_ac_accept = self._content.get('registration_ac_accept', True) - self._registration_ac_list = self._content.get('registration_ac_list', []) - self._groups_student_choice = self._content.get("groups_student_choice", False) - self._allow_unregister = self._content.get('allow_unregister', True) - self._allow_preview = self._content.get('allow_preview', False) - self._is_lti = self._content.get('is_lti', False) - self._lti_url = self._content.get('lti_url', '') - self._lti_keys = self._content.get('lti_keys', {}) - self._lti_send_back_grade = self._content.get('lti_send_back_grade', False) - self._tags = {key: Tag(key, tag_dict, self.gettext) for key, tag_dict in self._content.get("tags", {}).items()} - self._course_user_setting = {key: CourseUserSetting(key, - field_dict["description"], - field_dict["type"]) - for key, field_dict in self._content.get("fields", {}).items()} - task_dispenser_class = task_dispensers.get(self._content.get('task_dispenser', 'toc'), TableOfContents) - # Here we use a lambda to encourage the task dispenser to pass by the task_factory to fetch course tasks - # to avoid them to be cached along with the course object. Passing the task factory as argument - # would require to pass the course too, and have a useless reference back. - self._task_dispenser = task_dispenser_class(lambda: self._task_factory.get_all_tasks(self), self._content.get("dispenser_data", ''), database, self.get_id()) - except: - raise Exception("Course has an invalid YAML spec: " + self.get_id()) + self._task_dispenser = task_dispenser_class(lambda: self._task_factory.get_all_tasks(self._taskset), + self._content.get("dispenser_data", ''), database, self.get_id()) + except Exception as e: + raise # Force some parameters if LTI is active if self.is_lti(): @@ -120,30 +99,21 @@ def get_id(self): """ Return the _id of this course """ return self._id - def get_fs(self): - """ Returns a FileSystemProvider which points to the folder of this course """ - return self._fs + def get_taskset(self): + return self._taskset def get_task(self, taskid): """ Returns a Task object """ - return self._task_factory.get_task(self, taskid) + return self._task_factory.get_task(self._taskset, taskid) def get_descriptor(self): """ Get (a copy) the description of the course """ return copy.deepcopy(self._content) - def get_staff(self): - """ Returns a list containing the usernames of all the staff users """ - return list(set(self.get_tutors() + self.get_admins())) - def get_admins(self): """ Returns a list containing the usernames of the administrators of this course """ return self._admins - def get_tutors(self): - """ Returns a list containing the usernames of the tutors assigned to this course """ - return self._tutors - def is_open_to_non_staff(self): """ Returns true if the course is accessible by users that are not administrator of this course """ return self.get_accessibility().is_open() @@ -170,7 +140,7 @@ def get_registration_accessibility(self): return self._registration def get_tasks(self, ordered=False): - return self._task_dispenser.get_ordered_tasks() if ordered else self._task_factory.get_all_tasks(self) + return self._task_dispenser.get_ordered_tasks() if ordered else self._task_factory.get_all_tasks(self._taskset) def get_access_control_method(self): """ Returns either None, "username", "binding", or "email", depending on the method used to verify that users can register to the course """ diff --git a/inginious/frontend/environment_types/generic_docker_oci_runtime.py b/inginious/frontend/environment_types/generic_docker_oci_runtime.py index 264b55cf2e..b293260182 100644 --- a/inginious/frontend/environment_types/generic_docker_oci_runtime.py +++ b/inginious/frontend/environment_types/generic_docker_oci_runtime.py @@ -52,7 +52,7 @@ def check_task_environment_parameters(self, data): return out def studio_env_template(self, templator, task, allow_html: bool): - return templator.render("course_admin/edit_tabs/env_generic_docker_oci.html", env_params=task.get("environment_parameters", {}), + return templator.render("taskset_admin/edit_tabs/env_generic_docker_oci.html", env_params=task.get("environment_parameters", {}), content_is_html=allow_html, env_id=self.id) def __init__(self, ssh_allowed=False): diff --git a/inginious/frontend/exceptions.py b/inginious/frontend/exceptions.py new file mode 100644 index 0000000000..33aef63b46 --- /dev/null +++ b/inginious/frontend/exceptions.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# +# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for +# more information about the licensing of this file. + +""" Some type of exceptions used by parts of INGInious """ + + +class CourseNotFoundException(Exception): + pass + + +class CourseAlreadyExistsException(Exception): + pass + + +class TasksetNotFoundException(Exception): + pass + + +class TasksetUnreadableException(Exception): + pass + + +class TasksetAlreadyExistsException(Exception): + pass + + +class ImportTasksetException(Exception): + pass diff --git a/inginious/frontend/flask/mapping.py b/inginious/frontend/flask/mapping.py index 99b720cf05..4ae54129a3 100644 --- a/inginious/frontend/flask/mapping.py +++ b/inginious/frontend/flask/mapping.py @@ -14,6 +14,7 @@ from inginious.frontend.pages.queue import QueuePage from inginious.frontend.pages.courselist import CourseListPage from inginious.frontend.pages.mycourses import MyCoursesPage +from inginious.frontend.pages.tasksets import TasksetsPage from inginious.frontend.pages.preferences.bindings import BindingsPage from inginious.frontend.pages.preferences.delete import DeletePage from inginious.frontend.pages.preferences.profile import ProfilePage @@ -27,7 +28,7 @@ from inginious.frontend.pages.lti import LTITaskPage, LTILaunchPage, LTIBindPage, LTIAssetPage, LTILoginPage from inginious.frontend.pages.group import GroupPage from inginious.frontend.pages.marketplace import MarketplacePage -from inginious.frontend.pages.marketplace_course import MarketplaceCoursePage +from inginious.frontend.pages.marketplace_taskset import MarketplaceTasksetPage from inginious.frontend.pages.api.auth_methods import APIAuthMethods from inginious.frontend.pages.api.authentication import APIAuthentication from inginious.frontend.pages.api.courses import APICourses @@ -42,12 +43,16 @@ from inginious.frontend.pages.course_admin.submissions import CourseSubmissionsPage from inginious.frontend.pages.course_admin.task_list import CourseTaskListPage from inginious.frontend.pages.course_admin.audience_edit import CourseEditAudience -from inginious.frontend.pages.course_admin.task_edit import CourseEditTask -from inginious.frontend.pages.course_admin.task_edit_file import CourseTaskFiles -from inginious.frontend.pages.course_admin.task_edit_file import CourseTaskFileUpload -from inginious.frontend.pages.course_admin.danger_zone import CourseDangerZonePage from inginious.frontend.pages.course_admin.statistics import CourseStatisticsPage -from inginious.frontend.pages.course_admin.search_user import CourseAdminSearchUserPage +from inginious.frontend.pages.course_admin.danger_zone import CourseDangerZonePage +from inginious.frontend.pages.taskset_admin.utils import TasksetRedirectPage +from inginious.frontend.pages.taskset_admin.settings import TasksetSettingsPage +from inginious.frontend.pages.taskset_admin.task_edit import EditTaskPage +from inginious.frontend.pages.taskset_admin.task_edit_file import CourseTaskFiles +from inginious.frontend.pages.taskset_admin.task_edit_file import CourseTaskFileUpload +from inginious.frontend.pages.taskset_admin.template import TasksetTemplatePage +from inginious.frontend.pages.taskset_admin.danger_zone import TasksetDangerZonePage +from inginious.frontend.pages.search_user import SearchUserPage class CookielessConverter(BaseConverter): @@ -78,8 +83,8 @@ def init_flask_mapping(flask_app): flask_app.add_url_rule('/register/', view_func=CourseRegisterPage.as_view('courseregisterpage')) flask_app.add_url_rule('/marketplace', view_func=MarketplacePage.as_view('marketplacepage')) - flask_app.add_url_rule('/marketplace/', - view_func=MarketplaceCoursePage.as_view('marketplacecoursepage')) + flask_app.add_url_rule('/marketplace/', + view_func=MarketplaceTasksetPage.as_view('marketplacetasksetpage')) flask_app.add_url_rule('/course/', view_func=CoursePage.as_view('coursepage')) flask_app.add_url_rule('/course//', view_func=TaskPage.as_view('taskpage')) flask_app.add_url_rule('/course///', @@ -93,6 +98,7 @@ def init_flask_mapping(flask_app): flask_app.add_url_rule('/pages/', view_func=INGIniousStaticPage.as_view('staticpage')) flask_app.add_url_rule('/courselist', view_func=CourseListPage.as_view('courselistpage')) flask_app.add_url_rule('/mycourses', view_func=MyCoursesPage.as_view('mycoursespage')) + flask_app.add_url_rule('/tasksets', view_func=TasksetsPage.as_view('tasksetspage')) flask_app.add_url_rule('/preferences', view_func=PrefRedirectPage.as_view('prefredirectpage')) flask_app.add_url_rule('/preferences/bindings', view_func=BindingsPage.as_view('bindingspage')) @@ -121,18 +127,29 @@ def init_flask_mapping(flask_app): view_func=CourseTaskListPage.as_view('coursetasklistpage')) flask_app.add_url_rule('/admin//edit/audience/', view_func=CourseEditAudience.as_view('courseditaudience')) - flask_app.add_url_rule('/admin//edit/task/', - view_func=CourseEditTask.as_view('coursedittask')) - flask_app.add_url_rule('/admin//edit/task//files', - view_func=CourseTaskFiles.as_view('coursetaskfiles')) - flask_app.add_url_rule('/admin//edit/task//dd_upload', - view_func=CourseTaskFileUpload.as_view('coursetaskfileupload')) + + flask_app.add_url_rule('/taskset/', + view_func=TasksetRedirectPage.as_view('tasksetredirectpage')) + flask_app.add_url_rule('/taskset//settings', + view_func=TasksetSettingsPage.as_view('tasksetsettingspage')) + flask_app.add_url_rule('/taskset//edit/', + view_func=EditTaskPage.as_view('tasksetedittask')) + flask_app.add_url_rule('/taskset//edit//files', + view_func=CourseTaskFiles.as_view('tasksettaskfiles')) + flask_app.add_url_rule('/taskset//edit//dd_upload', + view_func=CourseTaskFileUpload.as_view('tasksettaskfileupload')) + flask_app.add_url_rule('/taskset//template', + view_func=TasksetTemplatePage.as_view('tasksettemplatepage')) + flask_app.add_url_rule('/taskset//danger', + view_func=TasksetDangerZonePage.as_view('tasksetdangerzonepage')) + + flask_app.add_url_rule('/search_user/', + view_func=SearchUserPage.as_view('searchuserpage')) + flask_app.add_url_rule('/admin//danger', view_func=CourseDangerZonePage.as_view('coursedangerzonepage')) flask_app.add_url_rule('/admin//stats', view_func=CourseStatisticsPage.as_view('coursestatisticspage')) - flask_app.add_url_rule('/admin//search_user/', - view_func=CourseAdminSearchUserPage.as_view('courseadminsearchuserpage')) flask_app.add_url_rule('/api/v0/auth_methods', view_func=APIAuthMethods.as_view('apiauthmethods')) flask_app.add_url_rule('/api/v0/authentication', diff --git a/inginious/frontend/i18n/fr/LC_MESSAGES/messages.mo b/inginious/frontend/i18n/fr/LC_MESSAGES/messages.mo index ac38299236..c377ada81e 100644 Binary files a/inginious/frontend/i18n/fr/LC_MESSAGES/messages.mo and b/inginious/frontend/i18n/fr/LC_MESSAGES/messages.mo differ diff --git a/inginious/frontend/i18n/fr/LC_MESSAGES/messages.po b/inginious/frontend/i18n/fr/LC_MESSAGES/messages.po index 91d076c1b3..86b3a82113 100644 --- a/inginious/frontend/i18n/fr/LC_MESSAGES/messages.po +++ b/inginious/frontend/i18n/fr/LC_MESSAGES/messages.po @@ -6,22 +6,17 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2023-04-04 15:06+0200\n" +"POT-Creation-Date: 2023-08-22 13:53+0200\n" "PO-Revision-Date: 2023-04-07 13:26+0000\n" "Last-Translator: Ludovic Taffin \n" -"Language-Team: French \n" "Language: fr\n" +"Language-Team: French " +"\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" +"Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 4.14\n" -"Generated-By: Babel 2.11.0\n" - -#: inginious/frontend/courses.py:30 -msgid "List of exercises" -msgstr "Liste des exercices" +"Generated-By: Babel 2.12.1\n" #: inginious/frontend/parsable_text.py:37 msgid "[no content]" @@ -29,8 +24,7 @@ msgstr "[pas de contenu]" #: inginious/frontend/parsable_text.py:81 msgid "The feedback below will be hidden to the students until {}." -msgstr "" -"L'évaluation ci-dessous ne sera pas affichée aux étudiants avant le {}." +msgstr "L'évaluation ci-dessous ne sera pas affichée aux étudiants avant le {}." #: inginious/frontend/parsable_text.py:96 msgid "" @@ -44,7 +38,15 @@ msgstr "" msgid "Parsing failed:
{}
" msgstr "L'analyse a échoué:
{}
" -#: inginious/frontend/submission_manager.py:420 +#: inginious/frontend/submission_manager.py:91 +msgid "" +"Maximum submission size exceeded. Check feedback, stdout, stderr and " +"state." +msgstr "" +"Taille de soumission maximum excédée. Vérifiez les champs feedback, " +"stdout, stderr et state" + +#: inginious/frontend/submission_manager.py:432 msgid "Feedback is badly formatted." msgstr "Le feedback est mal formaté." @@ -68,33 +70,45 @@ msgstr "choix multiple" msgid "match" msgstr "correspondance" -#: inginious/frontend/tasks.py:86 +#: inginious/frontend/tasks.py:85 msgid "Environment type {0} is unknown" msgstr "Type d'environnement {} inconnu" -#: inginious/frontend/user_manager.py:436 +#: inginious/frontend/tasksets.py:24 +msgid "List of exercises" +msgstr "Liste des exercices" + +#: inginious/frontend/user_manager.py:446 msgid "Auth method not found." msgstr "Méthode d'authenfication non trouvée." #: inginious/frontend/pages/preferences/bindings.py:42 -#: inginious/frontend/user_manager.py:487 +#: inginious/frontend/user_manager.py:497 msgid "Incorrect authentication binding." msgstr "Méthode d'authentification incorrecte." -#: inginious/frontend/user_manager.py:497 +#: inginious/frontend/user_manager.py:507 msgid "You must set a password before removing all bindings." msgstr "" -"Vous devez spécifier un mot de passe avant de pouvoir supprimer toutes les " -"méthodes d'authentifications." +"Vous devez spécifier un mot de passe avant de pouvoir supprimer toutes " +"les méthodes d'authentifications." -#: inginious/frontend/user_manager.py:528 +#: inginious/frontend/user_manager.py:538 msgid "User could not be created." msgstr "L'utilisateur n'a pu être créé." #: inginious/frontend/environment_types/docker.py:12 -msgid "Standard container (Docker)" +msgid "Standard container + SSH" +msgstr "Conteneur standard (Docker) + SSH" + +#: inginious/frontend/environment_types/docker.py:12 +msgid "Standard container" msgstr "Conteneur standard (Docker)" +#: inginious/frontend/environment_types/kata.py:11 +msgid "Container running as root (Kata) + SSH" +msgstr "Conteneur s'exécutant en tant que root (Kata) + SSH" + #: inginious/frontend/environment_types/kata.py:11 msgid "Container running as root (Kata)" msgstr "Conteneur s'exécutant en tant que root (Kata)" @@ -103,6 +117,10 @@ msgstr "Conteneur s'exécutant en tant que root (Kata)" msgid "Multiple Choice Question solver" msgstr "Solveur intégré Questions à Choix Multiples" +#: inginious/frontend/environment_types/nvidia.py:11 +msgid "Container with GPUs (NVIDIA) + SSH" +msgstr "Conteneur avec GPUs (NVIDIA) + SSH" + #: inginious/frontend/environment_types/nvidia.py:11 msgid "Container with GPUs (NVIDIA)" msgstr "Conteneur avec GPUs (NVIDIA)" @@ -111,7 +129,7 @@ msgstr "Conteneur avec GPUs (NVIDIA)" msgid "Course not found." msgstr "Cours introuvable." -#: inginious/frontend/pages/course_register.py:23 +#: inginious/frontend/pages/course_register.py:24 msgid "This course doesn't exist." msgstr "Ce cours n'existe pas." @@ -161,8 +179,8 @@ msgstr "Une erreur s'est produite lors de la validation de la requête LTI" #: inginious/frontend/pages/lti.py:213 msgid "" "In order to send grade back to the TC, INGInious needs the parameters " -"lis_outcome_service_url and lis_outcome_result_id in the LTI basic-launch-" -"request. Please contact your administrator." +"lis_outcome_service_url and lis_outcome_result_id in the LTI basic-" +"launch-request. Please contact your administrator." msgstr "" "Afin d'envoyer la note à la plateforme hôte, les paramètres " "lis_outcome_service_urlet lis_outcome_result_id doivent être fournis à " @@ -173,12 +191,12 @@ msgid "Couldn't validate LTI request" msgstr "Impossible de valider la requête LTI" #: inginious/frontend/pages/marketplace.py:32 -msgid "You don't have superadmin rights on this course." -msgstr "Vous n'avez pas les droits de super-administrateur pour ce cours." +msgid "You don't have superadmin rights on this taskset." +msgstr "Vous n'avez pas les droits de super-administrateur pour ce jeu d'exercices." #: inginious/frontend/pages/marketplace.py:39 -#: inginious/frontend/pages/marketplace_course.py:33 -#: inginious/frontend/pages/marketplace_course.py:42 +#: inginious/frontend/pages/marketplace_taskset.py:33 +#: inginious/frontend/pages/marketplace_taskset.py:42 msgid "You're not allowed to do that" msgstr "Vous n'avez pas les droits nécessaires." @@ -188,16 +206,16 @@ msgid "User returned an invalid form." msgstr "L'utilisateur a soumis un formulaire invalide." #: inginious/frontend/pages/marketplace.py:75 -msgid "Couldn't clone course into your instance" -msgstr "Impossible de clôner ce cours sur votre instance" +msgid "Couldn't clone taskset into your instance" +msgstr "Impossible de copier ce jeu d'exercices sur votre instance" #: inginious/frontend/pages/marketplace.py:96 -msgid "An error occur while editing the course description" -msgstr "Une erreur a eu lieu lors de l'édition de la description du cours" +msgid "An error occur while editing the taskset description" +msgstr "Une erreur a eu lieu lors de l'édition de la description du jeu d'exercices" -#: inginious/frontend/pages/marketplace_course.py:25 -msgid "Course unavailable." -msgstr "Cours indisponible." +#: inginious/frontend/pages/marketplace_taskset.py:25 +msgid "Taskset unavailable." +msgstr "Jeu d'exercices indisponible." #: inginious/frontend/pages/register.py:30 #: inginious/frontend/pages/register.py:198 @@ -265,15 +283,16 @@ msgstr "" #: inginious/frontend/pages/register.py:116 msgid "" -"You are succesfully registered. An email has been sent to you for activation." +"You are succesfully registered. An email has been sent to you for " +"activation." msgstr "" -"Votre compte a été créé. Un email vous a été envoyé afin de l'activer avant " -"toute connexion." +"Votre compte a été créé. Un email vous a été envoyé afin de l'activer " +"avant toute connexion." #: inginious/frontend/pages/register.py:121 msgid "" -"Something went wrong while sending you activation email. Please contact the " -"administrator." +"Something went wrong while sending you activation email. Please contact " +"the administrator." msgstr "" "Une erreur s'est produite lors de l'envoi du mail d'activation. Veuillez " "contacter l'administrateur du site." @@ -290,13 +309,13 @@ msgstr "Récupération de votre mot de passe INGInious" msgid "" "Dear {realname},\n" "\n" -"Someone (probably you) asked to reset your INGInious password. If this was " -"you, please click on the following link :\n" +"Someone (probably you) asked to reset your INGInious password. If this " +"was you, please click on the following link :\n" msgstr "" "{realname},\n" "\n" -"Une personne (probablement vous) a demandé la réinitialisation de votre mot " -"de passe INGInious.S'il s'agit de vous, veuillez cliquer sur le lien " +"Une personne (probablement vous) a demandé la réinitialisation de votre " +"mot de passe INGInious.S'il s'agit de vous, veuillez cliquer sur le lien " "suivant: \n" #: inginious/frontend/pages/register.py:161 @@ -308,8 +327,8 @@ msgid "" "Something went wrong while sending you reset email. Please contact the " "administrator." msgstr "" -"Une erreur s'est produite lors de l'envoi du mail de réinitialisation du mot " -"de passe.Veuillez contacter l'administrateur du site." +"Une erreur s'est produite lors de l'envoi du mail de réinitialisation du " +"mot de passe.Veuillez contacter l'administrateur du site." #: inginious/frontend/pages/register.py:189 msgid "Invalid reset hash." @@ -323,114 +342,139 @@ msgstr "Votre mot de passe a bien été modifié." msgid "Auth method doesn't exist" msgstr "La méthode d'authentification n'existe pas." -#: inginious/frontend/pages/social.py:39 +#: inginious/frontend/pages/social.py:38 msgid "Auth method doesn't exist." msgstr "La méthode d'authentification n'existe pas." -#: inginious/frontend/pages/tasks.py:93 inginious/frontend/pages/tasks.py:264 +#: inginious/frontend/pages/tasks.py:93 inginious/frontend/pages/tasks.py:263 msgid "Submission doesn't exist." msgstr "La soumission n'existe pas." -#: inginious/frontend/pages/tasks.py:176 inginious/frontend/pages/tasks.py:184 -#: inginious/frontend/pages/tasks.py:202 inginious/frontend/pages/tasks.py:224 -#: inginious/frontend/pages/tasks.py:231 inginious/frontend/pages/tasks.py:248 +#: inginious/frontend/pages/tasks.py:175 inginious/frontend/pages/tasks.py:183 +#: inginious/frontend/pages/tasks.py:201 inginious/frontend/pages/tasks.py:223 +#: inginious/frontend/pages/tasks.py:230 inginious/frontend/pages/tasks.py:247 msgid "Error" msgstr "Erreur" -#: inginious/frontend/pages/tasks.py:176 +#: inginious/frontend/pages/tasks.py:175 msgid "You are not allowed to submit for this task." msgstr "Vous n'êtes pas autorisé à soumettre pour cet exercice." -#: inginious/frontend/pages/tasks.py:184 +#: inginious/frontend/pages/tasks.py:183 msgid "Your task has been regenerated. This current task is outdated." msgstr "Votre exercice a été regénéré. La version consultée est périmée." -#: inginious/frontend/pages/tasks.py:203 +#: inginious/frontend/pages/tasks.py:202 msgid "" -"Please answer to all the questions and verify the extensions of the files " -"you want to upload. Your responses were not tested." +"Please answer to all the questions and verify the extensions of the files" +" you want to upload. Your responses were not tested." msgstr "" "Veuillez répondre à toutes les questions et vérifier les extensions des " -"fichiers que vous souhaitez télécharger. Votre réponse n'a pas été soumise " -"pour correction." +"fichiers que vous souhaitez télécharger. Votre réponse n'a pas été " +"soumise pour correction." -#: inginious/frontend/pages/tasks.py:220 inginious/frontend/pages/tasks.py:329 +#: inginious/frontend/pages/tasks.py:219 inginious/frontend/pages/tasks.py:328 msgid "Your submission has been sent..." msgstr "Votre soumission a bien été envoyée..." -#: inginious/frontend/pages/course_admin/task_edit_file.py:209 -#: inginious/frontend/pages/course_admin/task_edit_file.py:230 -#: inginious/frontend/pages/course_admin/task_edit_file.py:234 -#: inginious/frontend/pages/tasks.py:231 inginious/frontend/pages/tasks.py:248 +#: inginious/frontend/pages/tasks.py:230 inginious/frontend/pages/tasks.py:247 +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:205 +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:226 +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:230 #: inginious/frontend/templates/internalerror.html:5 #: inginious/frontend/templates/task.html:325 msgid "Internal error" msgstr "Erreur interne" -#: inginious/frontend/pages/tasks.py:298 +#: inginious/frontend/pages/tasks.py:297 msgid "INGInious is currently grading your answers. (almost done)" msgstr "INGinious est en train de corriger vos réponses. (presque fini)" -#: inginious/frontend/pages/tasks.py:300 +#: inginious/frontend/pages/tasks.py:299 msgid "" -"INGInious is currently grading your answers. (Approx. wait time: {} " -"seconds)" +"INGInious is currently grading your answers. (Approx. wait time: " +"{} seconds)" msgstr "" "INGInious est en train de corriger vos réponses. (Temps d'attente " "approx. : {} secondes)" -#: inginious/frontend/pages/tasks.py:303 +#: inginious/frontend/pages/tasks.py:302 msgid "You are next in the waiting queue!" msgstr "Votre soumission est la suivante dans la file d'attente!" -#: inginious/frontend/pages/tasks.py:305 +#: inginious/frontend/pages/tasks.py:304 msgid "There is one task in front of you in the waiting queue." msgstr "Il y a une tâche devant vous dans la file d'attente." -#: inginious/frontend/pages/tasks.py:307 +#: inginious/frontend/pages/tasks.py:306 msgid "There are {} tasks in front of you in the waiting queue." msgstr "Il y a {} tâches devant vous dans la file d'attente." -#: inginious/frontend/pages/tasks.py:331 +#: inginious/frontend/pages/tasks.py:330 msgid "There are some errors in your answer. Your score is {score}%." msgstr "Il y a des erreurs dans votre réponse. Votre note est de {score}%." -#: inginious/frontend/pages/tasks.py:333 +#: inginious/frontend/pages/tasks.py:332 msgid "Your answer passed the tests! Your score is {score}%." msgstr "Votre réponse a passé les tests ! Votre note est de {score}%." -#: inginious/frontend/pages/tasks.py:335 +#: inginious/frontend/pages/tasks.py:334 msgid "Your submission timed out. Your score is {score}%." msgstr "" -"Votre soumission a pris trop de temps à exécuter. Votre note est de {score}%." +"Votre soumission a pris trop de temps à exécuter. Votre note est de " +"{score}%." -#: inginious/frontend/pages/tasks.py:337 +#: inginious/frontend/pages/tasks.py:336 msgid "Your submission made an overflow. Your score is {score}%." msgstr "" -"Votre soumission a nécessité l'allocation de trop de mémoire. Votre note est " -"de {score}%." +"Votre soumission a nécessité l'allocation de trop de mémoire. Votre note " +"est de {score}%." -#: inginious/frontend/pages/tasks.py:339 +#: inginious/frontend/pages/tasks.py:338 msgid "Your submission was killed." msgstr "Votre soumission a été annulée." -#: inginious/frontend/pages/tasks.py:341 +#: inginious/frontend/pages/tasks.py:340 msgid "" -"An internal error occurred. Please retry later. If the error persists, send " -"an email to the course administrator." +"An internal error occurred. Please retry later. If the error persists, " +"send an email to the course administrator." msgstr "" -"Une erreur interne s'est produite. Veuillez réessayer plus tard. Si l'erreur " -"persiste, contactez l'administrateur du cours." +"Une erreur interne s'est produite. Veuillez réessayer plus tard. Si " +"l'erreur persiste, contactez l'administrateur du cours." -#: inginious/frontend/pages/tasks.py:344 +#: inginious/frontend/pages/tasks.py:343 msgid "[Submission #{submissionid} ({submissionDate})]" msgstr "[Soumission #{submissionid} ({submissionDate})]" -#: inginious/frontend/pages/tasks.py:379 inginious/frontend/pages/tasks.py:381 +#: inginious/frontend/pages/tasks.py:378 inginious/frontend/pages/tasks.py:380 msgid " " msgstr " " -#: inginious/frontend/pages/utils.py:192 +#: inginious/frontend/pages/tasksets.py:43 +msgid "Course with id {} successfully instantiated from taskset {}" +msgstr "Le cours avec l'identifiant {} a été instantié à partir du jeu d'exercice {}" + +#: inginious/frontend/pages/tasksets.py:46 +msgid "You are not allowed to instantiate a course from this taskset." +msgstr "Vous n'êtes pas autorisé à instancier un cours à partir de ce jeu d'exercices." + +#: inginious/frontend/pages/tasksets.py:49 +msgid "A course with id {} already exists." +msgstr "Un cours avec l'identifiant {} existe déjà" + +#: inginious/frontend/pages/tasksets.py:52 +msgid "Couldn't instantiate course with id {} : " +msgstr "Impossible d'instantier un cours avec l'identifiant {} : " + +#: inginious/frontend/pages/tasksets.py:59 +msgid "Taskset created." +msgstr "Jeu d'exercices créé." + +#: inginious/frontend/pages/tasksets.py:62 +msgid "Failed to create the taskset." +msgstr "Une erreur s'est produite lors de la création du jeu d'exercices." + +#: inginious/frontend/pages/utils.py:197 msgid "" "An account using this email already exists and is not bound with this " "service. For security reasons, please log in via another method and bind " @@ -440,31 +484,31 @@ msgstr "" "Pour des raisons de sécurités, veuillez vous identifier via une autre " "méthode et lier votre compte depuis votre profil." -#: inginious/frontend/pages/utils.py:195 +#: inginious/frontend/pages/utils.py:200 msgid "" -"Couldn't fetch the required information from the service. Please check the " -"provided permissions (name, email) and contact your INGInious administrator " -"if the error persists." +"Couldn't fetch the required information from the service. Please check " +"the provided permissions (name, email) and contact your INGInious " +"administrator if the error persists." msgstr "" -"Impossible d'obtenir les informations requises depuis le service. Veuillez " -"vérifier les permissions (nom, email) octroyées et contacter votre " -"administrateur INGInious si l'erreur persiste." +"Impossible d'obtenir les informations requises depuis le service. " +"Veuillez vérifier les permissions (nom, email) octroyées et contacter " +"votre administrateur INGInious si l'erreur persiste." -#: inginious/frontend/pages/utils.py:221 +#: inginious/frontend/pages/utils.py:226 msgid "Invalid login/password" msgstr "Nom d'utilisateur/mot de passe invalide" -#: inginious/frontend/pages/utils.py:249 +#: inginious/frontend/pages/utils.py:254 #: inginious/frontend/templates/forbidden.html:5 #: inginious/frontend/templates/forbidden.html:9 msgid "Forbidden" msgstr "Accès interdit" -#: inginious/frontend/pages/utils.py:263 +#: inginious/frontend/pages/utils.py:268 msgid "You have not sufficient right to see this part." msgstr "Vous n'avez pas les droits suffisants pour voir cette partie." -#: inginious/frontend/pages/utils.py:312 +#: inginious/frontend/pages/utils.py:317 msgid "File doesn't exist." msgstr "Le fichier n'existe pas." @@ -503,6 +547,7 @@ msgid "This file doesn't exist." msgstr "Le fichier n'existe pas." #: inginious/frontend/pages/course_admin/danger_zone.py:164 +#: inginious/frontend/pages/taskset_admin/danger_zone.py:50 msgid "Operation aborted due to invalid token." msgstr "L'opération a échoué car le jeton est invalide." @@ -518,8 +563,8 @@ msgstr "Toutes les données du cours ont été supprimées." #: inginious/frontend/pages/course_admin/danger_zone.py:175 msgid "An error occurred while dumping course from database: {}" msgstr "" -"Une erreur a eu lieu lors de l'archivage du cours depuis la base de donnée : " -"{}" +"Une erreur a eu lieu lors de l'archivage du cours depuis la base de " +"donnée : {}" #: inginious/frontend/pages/course_admin/danger_zone.py:185 msgid "Course restored to date : {}." @@ -534,50 +579,51 @@ msgid "An error occurred while deleting the course data: {}" msgstr "Une erreur a eu lieu lors de la suppression des données du cours : {}" #: inginious/frontend/pages/course_admin/settings.py:34 +#: inginious/frontend/pages/taskset_admin/settings.py:48 msgid "Invalid name" msgstr "Nom invalide" #: inginious/frontend/pages/course_admin/settings.py:38 msgid "You cannot remove yourself from the administrators of this course" -msgstr "" -"Vous ne pouvez pas vous supprimer de la liste des administrateurs du cours" +msgstr "Vous ne pouvez pas vous supprimer de la liste des administrateurs du cours" -#: inginious/frontend/pages/course_admin/settings.py:55 +#: inginious/frontend/pages/course_admin/settings.py:52 msgid "Invalid accessibility dates" msgstr "Dates d'accessibilité invalides" -#: inginious/frontend/pages/course_admin/settings.py:70 +#: inginious/frontend/pages/course_admin/settings.py:67 msgid "Invalid registration dates" msgstr "Dates d'inscription invalides" -#: inginious/frontend/pages/course_admin/settings.py:78 +#: inginious/frontend/pages/course_admin/settings.py:75 msgid "Invalid ACL value" msgstr "Valeur de liste de contrôle d'accès invalide" -#: inginious/frontend/pages/course_admin/settings.py:92 +#: inginious/frontend/pages/course_admin/settings.py:89 msgid "LTI keys must be alphanumerical." msgstr "Les clés LTI doivent être alphanumériques." -#: inginious/frontend/pages/course_admin/settings.py:129 +#: inginious/frontend/pages/course_admin/settings.py:126 msgid "Some tag fields were missing." msgstr "Quelques champs d'étiquettes sont manquants." -#: inginious/frontend/pages/course_admin/settings.py:132 +#: inginious/frontend/pages/course_admin/settings.py:129 msgid "Invalid tag id: {}" msgstr "Identifiant d'étiquette invalide: {}" -#: inginious/frontend/pages/course_admin/settings.py:151 +#: inginious/frontend/pages/course_admin/settings.py:148 msgid "Invalid type value: {}" msgstr "Type de valeur invalide : {}" -#: inginious/frontend/pages/course_admin/settings.py:153 +#: inginious/frontend/pages/course_admin/settings.py:150 msgid "Invalid id: {}" msgstr "Identifiant invalide: {}" -#: inginious/frontend/pages/course_admin/settings.py:167 +#: inginious/frontend/pages/course_admin/settings.py:164 msgid "Some datas have the same id! The id must be unique." msgstr "" -"Plusieurs données ont le même identifiant ! L'identifiant doit être unique." +"Plusieurs données ont le même identifiant ! L'identifiant doit être " +"unique." #: inginious/frontend/pages/course_admin/statistics.py:192 #: inginious/frontend/pages/course_admin/submissions.py:138 @@ -627,8 +673,7 @@ msgstr "Une erreur s'est produite lors du traitement des données." #: inginious/frontend/pages/course_admin/student_list.py:353 msgid "Changes couldn't be applied for following students :" -msgstr "" -"Les changements n'ont pas pu être appliqués pour les étudiants suivants :" +msgstr "Les changements n'ont pas pu être appliqués pour les étudiants suivants :" #: inginious/frontend/pages/course_admin/student_list.py:360 msgid "Groups updated." @@ -645,11 +690,10 @@ msgstr "ObjectId inavlide." #: inginious/frontend/pages/course_admin/submissions.py:62 msgid "The following submission could not be prepared for download: {}" -msgstr "" -"La soumission suivante n'a pas pu être préparée pour le téléchargement: {}" +msgstr "La soumission suivante n'a pas pu être préparée pour le téléchargement: {}" #: inginious/frontend/pages/course_admin/submissions.py:67 -#: inginious/frontend/pages/course_admin/utils.py:43 +#: inginious/frontend/pages/course_admin/utils.py:38 msgid "You don't have admin rights on this course." msgstr "Vous n'avez pas les droits d'administrateur pour ce cours." @@ -661,154 +705,82 @@ msgstr "{0} soumissions vont être rejouées." msgid "The submission doesn't exist." msgstr "La soumission n'existe pas." -#: inginious/frontend/pages/course_admin/task_edit.py:34 -#: inginious/frontend/pages/course_admin/task_edit_file.py:24 -#: inginious/frontend/pages/course_admin/task_edit_file.py:45 -#: inginious/frontend/pages/course_admin/task_edit_file.py:264 -msgid "Invalid task id" -msgstr "Identifiant d'exercice invalide" - -#: inginious/frontend/pages/course_admin/task_edit.py:87 -msgid "Invalid course/task id" -msgstr "Identifiant de cours/exercice invalide" - -#: inginious/frontend/pages/course_admin/task_edit.py:117 -msgid "Invalid file type: {}" -msgstr "Type de fichier invalide : {}" - -#: inginious/frontend/pages/course_admin/task_edit.py:135 -msgid "The number of random inputs must be an integer!" -msgstr "Le nombre d'entrées aléatoires doit être un entier !" - -#: inginious/frontend/pages/course_admin/task_edit.py:137 -msgid "The number of random inputs must be positive!" -msgstr "Le nombre d'entrées aléatoires doit être positif !" - -#: inginious/frontend/pages/course_admin/task_edit.py:146 -msgid "Your browser returned an invalid form ({})" -msgstr "Votre navigateur a renvoyé un formaulaire invalide ({})" - -#: inginious/frontend/pages/course_admin/task_edit.py:152 -msgid "Error while reading course's informations" -msgstr "Une erreur s'est produite lors de la lecture des informations du cours" - -#: inginious/frontend/pages/course_admin/task_edit.py:176 -msgid "Invalid data: {}" -msgstr "Données invalides : {}" - -#: inginious/frontend/pages/course_admin/task_edit.py:182 -msgid "Cannot read zip file. Files were not modified" -msgstr "Impossible de lire le fichier ZIP. Les fichiers n'ont pas été modifiés" - -#: inginious/frontend/pages/course_admin/task_edit.py:189 -msgid "" -"There was a problem while extracting the zip archive. Some files may have " -"been modified" -msgstr "" -"Une erreur s'est produite lors de l'extraction de l'archive ZIP. Il se peut " -"que des fichiers aient été modifiés" - -#: inginious/frontend/pages/course_admin/task_edit_file.py:168 -#: inginious/frontend/pages/course_admin/task_edit_file.py:188 -#: inginious/frontend/pages/course_admin/task_edit_file.py:213 -msgid "Invalid new path" -msgstr "Nouveau chemin invalide" - -#: inginious/frontend/pages/course_admin/task_edit_file.py:174 -msgid "An error occurred while writing the file" -msgstr "Une erreur s'est produite lors de l'écriture du fichier" - -#: inginious/frontend/pages/course_admin/task_edit_file.py:219 -msgid "An error occurred while moving the files" -msgstr "Une erreur s'est produite lors du déplacement des fichiers" - -#: inginious/frontend/pages/course_admin/task_edit_file.py:240 -msgid "An error occurred while deleting the files" -msgstr "Une erreur s'est produite lors de la suppression des fichiers" - -#: inginious/frontend/pages/course_admin/task_edit_file.py:247 -msgid "This path doesn't exist." -msgstr "Le chemin n'existe pas." - #: inginious/frontend/pages/course_admin/task_list.py:36 +#: inginious/frontend/pages/taskset_admin/template.py:36 msgid "Invalid task dispenser" msgstr "Distribution d'exercice invalide" -#: inginious/frontend/pages/course_admin/task_list.py:45 -msgid "Invalid course structure: " -msgstr "Structure du cours invalide:" - -#: inginious/frontend/pages/course_admin/task_list.py:47 +#: inginious/frontend/pages/course_admin/task_list.py:43 +#: inginious/frontend/pages/course_admin/task_list.py:48 +#: inginious/frontend/pages/taskset_admin/template.py:43 +#: inginious/frontend/pages/taskset_admin/template.py:48 +#: inginious/frontend/pages/taskset_admin/template.py:53 msgid "Something wrong happened: " msgstr "Quelque choe de mal est survenu: " #: inginious/frontend/pages/course_admin/task_list.py:54 -msgid "Couldn't create task {} : " -msgstr "Impossible de supprimer l'exercice : {} " - -#: inginious/frontend/pages/course_admin/task_list.py:59 -msgid "Couldn't delete task {} : " -msgstr "Impossible de supprimer l'exercice : {} " - -#: inginious/frontend/pages/course_admin/task_list.py:64 msgid "Couldn't wipe task {} : " msgstr "Impossible de supprimer les données de la tâche : {} " -#: inginious/frontend/pages/course_admin/utils.py:40 -msgid "You don't have staff rights on this course." -msgstr "Vous n'avez pas les droits d'enseignant pour ce cours." +#: inginious/frontend/pages/course_admin/task_list.py:69 +msgid "Invalid course structure: " +msgstr "Structure du cours invalide:" -#: inginious/frontend/pages/course_admin/utils.py:50 +#: inginious/frontend/pages/course_admin/utils.py:45 msgid "This course is unreachable" msgstr "Ce cours n'est pas disponible." -#: inginious/frontend/pages/course_admin/utils.py:144 +#: inginious/frontend/pages/course_admin/utils.py:139 msgid "List not valid." msgstr "Liste invalide." -#: inginious/frontend/pages/course_admin/utils.py:343 -#: inginious/frontend/templates/course_admin/settings.html:18 -#: inginious/frontend/templates/course_admin/settings.html:25 +#: inginious/frontend/pages/course_admin/utils.py:338 +#: inginious/frontend/templates/course.html:53 +#: inginious/frontend/templates/course.html:71 +#: inginious/frontend/templates/course_user_settings.html:33 #: inginious/frontend/templates/course_user_settings.html:47 #: inginious/frontend/templates/course_user_settings.html:57 -msgid "Course settings" -msgstr "Paramètres du cours" +msgid "User settings" +msgstr "Paramètres utilisateur" -#: inginious/frontend/pages/course_admin/utils.py:345 -#: inginious/frontend/templates/course_admin/stats.html:27 -#: inginious/frontend/templates/course_admin/stats.html:35 +#: inginious/frontend/pages/course_admin/utils.py:340 +#: inginious/frontend/templates/course_admin/stats.html:28 +#: inginious/frontend/templates/course_admin/stats.html:36 #: inginious/frontend/templates/course_admin/submissions_query.html:211 msgid "Statistics" msgstr "Statistiques" -#: inginious/frontend/pages/course_admin/utils.py:346 -#: inginious/frontend/templates/course_admin/student_list.html:20 -#: inginious/frontend/templates/course_admin/student_list.html:28 +#: inginious/frontend/pages/course_admin/utils.py:341 +#: inginious/frontend/templates/course_admin/student_list.html:21 +#: inginious/frontend/templates/course_admin/student_list.html:29 msgid "User management" msgstr "Gestion des utilisateurs" -#: inginious/frontend/pages/course_admin/utils.py:349 -#: inginious/frontend/templates/course_admin/stats.html:49 -#: inginious/frontend/templates/course_admin/stats.html:248 -#: inginious/frontend/templates/course_admin/task_edit.html:19 -#: inginious/frontend/templates/course_admin/task_list.html:19 -#: inginious/frontend/templates/course_admin/task_list.html:25 +#: inginious/frontend/pages/course_admin/utils.py:344 +#: inginious/frontend/templates/course_admin/stats.html:50 +#: inginious/frontend/templates/course_admin/stats.html:249 +#: inginious/frontend/templates/course_admin/task_list.html:20 +#: inginious/frontend/templates/course_admin/task_list.html:26 msgid "Tasks" msgstr "Exercices" -#: inginious/frontend/pages/course_admin/utils.py:351 -#: inginious/frontend/templates/course_admin/stats.html:43 -#: inginious/frontend/templates/course_admin/stats.html:168 +#: inginious/frontend/pages/course_admin/utils.py:346 +#: inginious/frontend/templates/course_admin/stats.html:44 +#: inginious/frontend/templates/course_admin/stats.html:169 #: inginious/frontend/templates/course_admin/submissions.html:6 -#: inginious/frontend/templates/course_admin/submissions.html:18 -#: inginious/frontend/templates/course_admin/submissions.html:26 +#: inginious/frontend/templates/course_admin/submissions.html:19 +#: inginious/frontend/templates/course_admin/submissions.html:27 msgid "Submissions" msgstr "Soumissions" -#: inginious/frontend/pages/course_admin/utils.py:354 +#: inginious/frontend/pages/course_admin/utils.py:349 +#: inginious/frontend/pages/taskset_admin/utils.py:63 #: inginious/frontend/templates/course_admin/danger_zone.html:6 -#: inginious/frontend/templates/course_admin/danger_zone.html:18 -#: inginious/frontend/templates/course_admin/danger_zone.html:24 +#: inginious/frontend/templates/course_admin/danger_zone.html:19 +#: inginious/frontend/templates/course_admin/danger_zone.html:25 +#: inginious/frontend/templates/taskset_admin/danger_zone.html:6 +#: inginious/frontend/templates/taskset_admin/danger_zone.html:19 +#: inginious/frontend/templates/taskset_admin/danger_zone.html:25 msgid "Danger zone" msgstr "Zone dangereuse" @@ -882,13 +854,132 @@ msgstr "Méthodes d'authentification" msgid "Delete my account" msgstr "Supprimer mon compte" +#: inginious/frontend/pages/taskset_admin/danger_zone.py:54 +msgid "Wrong taskset id." +msgstr "Identifiant de jeu d'exercices incorrect." + +#: inginious/frontend/pages/taskset_admin/danger_zone.py:59 +msgid "One or more course(s) rely on the current taskset." +msgstr "Un ou plusieurs cours dépendent du jeu d'exercices actuel." + +#: inginious/frontend/pages/taskset_admin/danger_zone.py:63 +msgid "An error occurred while deleting the taskset data: {}" +msgstr "Une erreur a eu lieu lors de la suppression des données du jeu d'exercices : {}" + +#: inginious/frontend/pages/taskset_admin/settings.py:33 +msgid "Invalid taskid : {}" +msgstr "Identifiant d'éxercice invalide: {}" + +#: inginious/frontend/pages/taskset_admin/settings.py:42 +msgid "Couldn't create task {} : " +msgstr "Impossible de supprimer l'exercice : {} " + +#: inginious/frontend/pages/taskset_admin/settings.py:53 +msgid "You cannot remove yourself from the administrators of this taskset" +msgstr "Vous ne pouvez pas vous supprimer de la liste des administrateurs du jeu d'exercices" + +#: inginious/frontend/pages/taskset_admin/task_edit.py:34 +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:24 +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:45 +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:260 +msgid "Invalid task id" +msgstr "Identifiant d'exercice invalide" + +#: inginious/frontend/pages/taskset_admin/task_edit.py:87 +msgid "Invalid taskset/task id" +msgstr "Identifiant de (jeux d') exercice(s) invalide" + +#: inginious/frontend/pages/taskset_admin/task_edit.py:117 +msgid "Invalid file type: {}" +msgstr "Type de fichier invalide : {}" + +#: inginious/frontend/pages/taskset_admin/task_edit.py:135 +msgid "The number of random inputs must be an integer!" +msgstr "Le nombre d'entrées aléatoires doit être un entier !" + +#: inginious/frontend/pages/taskset_admin/task_edit.py:137 +msgid "The number of random inputs must be positive!" +msgstr "Le nombre d'entrées aléatoires doit être positif !" + +#: inginious/frontend/pages/taskset_admin/task_edit.py:146 +msgid "Your browser returned an invalid form ({})" +msgstr "Votre navigateur a renvoyé un formaulaire invalide ({})" + +#: inginious/frontend/pages/taskset_admin/task_edit.py:152 +msgid "Error while reading taskset data" +msgstr "Une erreur s'est produite lors de la lecture des informations du jeu d'exercices" + +#: inginious/frontend/pages/taskset_admin/task_edit.py:176 +msgid "Invalid data: {}" +msgstr "Données invalides : {}" + +#: inginious/frontend/pages/taskset_admin/task_edit.py:182 +msgid "Cannot read zip file. Files were not modified" +msgstr "Impossible de lire le fichier ZIP. Les fichiers n'ont pas été modifiés" + +#: inginious/frontend/pages/taskset_admin/task_edit.py:189 +msgid "" +"There was a problem while extracting the zip archive. Some files may have" +" been modified" +msgstr "" +"Une erreur s'est produite lors de l'extraction de l'archive ZIP. Il se " +"peut que des fichiers aient été modifiés" + +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:164 +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:184 +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:209 +msgid "Invalid new path" +msgstr "Nouveau chemin invalide" + +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:170 +msgid "An error occurred while writing the file" +msgstr "Une erreur s'est produite lors de l'écriture du fichier" + +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:215 +msgid "An error occurred while moving the files" +msgstr "Une erreur s'est produite lors du déplacement des fichiers" + +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:236 +msgid "An error occurred while deleting the files" +msgstr "Une erreur s'est produite lors de la suppression des fichiers" + +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:243 +msgid "This path doesn't exist." +msgstr "Le chemin n'existe pas." + +#: inginious/frontend/pages/taskset_admin/template.py:68 +msgid "Invalid taskset structure: " +msgstr "Structure du jeu d'exercices invalide:" + +#: inginious/frontend/pages/taskset_admin/utils.py:34 +msgid "You don't have admin rights on this taskset." +msgstr "Vous n'avez pas les droits d'administrateur pour ce jeu d'exercices." + +#: inginious/frontend/pages/taskset_admin/utils.py:43 +msgid "This taskset is unreachable" +msgstr "Ce jeu d'exercices n'est pas disponible." + +#: inginious/frontend/pages/taskset_admin/utils.py:61 +#: inginious/frontend/templates/course_admin/settings.html:6 +#: inginious/frontend/templates/taskset_admin/settings.html:6 +#: inginious/frontend/templates/taskset_admin/settings.html:49 +msgid "Settings" +msgstr "Paramètres" + +#: inginious/frontend/pages/taskset_admin/utils.py:62 +#: inginious/frontend/templates/taskset_admin/template.html:20 +#: inginious/frontend/templates/taskset_admin/template.html:26 +msgid "Course template" +msgstr "Modèle de cours" + #: inginious/frontend/plugins/auth/custom_auth_form.html:6 msgid "Login" msgstr "Connexion" #: inginious/frontend/plugins/auth/custom_auth_form.html:25 -#: inginious/frontend/templates/course_admin/edit_tabs/file_modals.html:26 -#: inginious/frontend/templates/course_admin/task_dispensers/util_task_edit_modal.html:22 +#: inginious/frontend/templates/task_dispensers_admin/util.html:67 +#: inginious/frontend/templates/task_dispensers_admin/util_task_edit_modal.html:22 +#: inginious/frontend/templates/taskset_admin/edit_tabs/file_modals.html:26 msgid "Close" msgstr "Fermer" @@ -898,20 +989,20 @@ msgstr "L'authentification a échoué" #: inginious/frontend/plugins/auth/custom_auth_form.html:31 #: inginious/frontend/templates/auth.html:21 -#: inginious/frontend/templates/course_admin/task_list.html:78 +#: inginious/frontend/templates/taskset_admin/settings.html:102 msgid "Username" msgstr "Nom d'utilisateur" #: inginious/frontend/plugins/auth/custom_auth_form.html:34 #: inginious/frontend/templates/auth.html:24 -#: inginious/frontend/templates/course_admin/task_list.html:79 #: inginious/frontend/templates/course_register.html:29 +#: inginious/frontend/templates/taskset_admin/settings.html:103 msgid "Password" msgstr "Mot de passe" #: inginious/frontend/plugins/auth/custom_auth_form.html:36 #: inginious/frontend/templates/auth.html:26 -#: inginious/frontend/templates/layout.html:179 +#: inginious/frontend/templates/layout.html:180 #: inginious/frontend/templates/signin_button.html:4 #: inginious/frontend/templates/signin_button.html:11 msgid "Sign in" @@ -953,28 +1044,32 @@ msgstr "Exercices à réaliser" #: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:11 #: inginious/frontend/templates/admin/admin_users.html:13 #: inginious/frontend/templates/course.html:103 -#: inginious/frontend/templates/course_admin/audience_edit.html:20 -#: inginious/frontend/templates/course_admin/danger_zone.html:18 -#: inginious/frontend/templates/course_admin/settings.html:18 -#: inginious/frontend/templates/course_admin/stats.html:27 -#: inginious/frontend/templates/course_admin/student_info.html:23 -#: inginious/frontend/templates/course_admin/student_list.html:20 -#: inginious/frontend/templates/course_admin/submission.html:32 -#: inginious/frontend/templates/course_admin/submissions.html:18 -#: inginious/frontend/templates/course_admin/task_edit.html:21 -#: inginious/frontend/templates/course_admin/task_list.html:19 +#: inginious/frontend/templates/course_admin/audience_edit.html:21 +#: inginious/frontend/templates/course_admin/danger_zone.html:19 +#: inginious/frontend/templates/course_admin/settings.html:19 +#: inginious/frontend/templates/course_admin/stats.html:28 +#: inginious/frontend/templates/course_admin/student_info.html:24 +#: inginious/frontend/templates/course_admin/student_list.html:21 +#: inginious/frontend/templates/course_admin/submission.html:33 +#: inginious/frontend/templates/course_admin/submissions.html:19 +#: inginious/frontend/templates/course_admin/task_list.html:20 #: inginious/frontend/templates/course_register.html:10 #: inginious/frontend/templates/course_user_settings.html:46 -#: inginious/frontend/templates/courselist.html:27 +#: inginious/frontend/templates/courselist.html:21 #: inginious/frontend/templates/group.html:61 #: inginious/frontend/templates/marketplace.html:26 -#: inginious/frontend/templates/marketplace_course.html:47 +#: inginious/frontend/templates/marketplace_taskset.html:47 #: inginious/frontend/templates/mycourses.html:11 #: inginious/frontend/templates/preferences/bindings.html:17 #: inginious/frontend/templates/preferences/delete.html:18 #: inginious/frontend/templates/preferences/profile.html:17 #: inginious/frontend/templates/queue.html:11 #: inginious/frontend/templates/task.html:266 +#: inginious/frontend/templates/taskset_admin/danger_zone.html:19 +#: inginious/frontend/templates/taskset_admin/settings.html:19 +#: inginious/frontend/templates/taskset_admin/task_edit.html:20 +#: inginious/frontend/templates/taskset_admin/template.html:20 +#: inginious/frontend/templates/tasksets.html:11 msgid "(current)" msgstr "(actuel)" @@ -984,8 +1079,8 @@ msgstr "Exercices à réaliser" #: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:20 msgid "" -"This page lists the coming tasks ordered on deadlines for courses you are " -"registered in." +"This page lists the coming tasks ordered on deadlines for courses you are" +" registered in." msgstr "" "Cette page liste les exercices à réaliser triés par ordre de date limite " "pour les cours auxquels vous êtes inscrits." @@ -996,42 +1091,42 @@ msgstr "" #: inginious/frontend/templates/course_unavailable.html:9 #: inginious/frontend/templates/course_user_settings.html:44 #: inginious/frontend/templates/courselist.html:5 -#: inginious/frontend/templates/courselist.html:26 -#: inginious/frontend/templates/courselist.html:33 +#: inginious/frontend/templates/courselist.html:20 +#: inginious/frontend/templates/courselist.html:27 #: inginious/frontend/templates/layout.html:149 -#: inginious/frontend/templates/layout.html:170 -#: inginious/frontend/templates/mycourses.html:25 +#: inginious/frontend/templates/layout.html:171 #: inginious/frontend/templates/task.html:263 +#: inginious/frontend/templates/tasksets.html:24 msgid "Course list" msgstr "Liste des cours" #: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:29 #: inginious/frontend/templates/course.html:21 #: inginious/frontend/templates/group.html:19 -#: inginious/frontend/templates/mycourses.html:29 +#: inginious/frontend/templates/mycourses.html:23 msgid "Last tried exercises" msgstr "Derniers exercices essayés" -#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:48 +#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:51 #: inginious/frontend/templates/course.html:40 #: inginious/frontend/templates/group.html:39 -#: inginious/frontend/templates/mycourses.html:48 +#: inginious/frontend/templates/mycourses.html:45 msgid "No submissions" msgstr "Pas de soumission" -#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:56 +#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:59 msgid "My Upcoming Tasks" msgstr "Mes exercices à réaliser" -#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:60 +#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:63 msgid "Switch time planner" msgstr "Changer de planificateur" -#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:71 +#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:74 msgid "Modify time planner" msgstr "Modifier le planificateur" -#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:77 +#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:80 msgid "" "This allows to display only the tasks whose deadline is in a certain " "temporal proximity." @@ -1039,64 +1134,79 @@ msgstr "" "Ceci vous permet de n'afficher que les exercices dont la date limite est " "dans une certaine proximité temporelle." -#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:78 +#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:81 msgid "Number of days:" msgstr "Nombre de jours :" -#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:86 +#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:89 #: inginious/frontend/templates/admin/admin_users.html:111 #: inginious/frontend/templates/admin/admin_users.html:131 #: inginious/frontend/templates/admin/admin_users.html:167 #: inginious/frontend/templates/admin/admin_users.html:187 -#: inginious/frontend/templates/course_admin/audience_edit.html:62 -#: inginious/frontend/templates/course_admin/danger_zone.html:85 -#: inginious/frontend/templates/course_admin/danger_zone.html:166 -#: inginious/frontend/templates/course_admin/student_info.html:106 -#: inginious/frontend/templates/course_admin/student_list.html:275 +#: inginious/frontend/templates/course_admin/audience_edit.html:63 +#: inginious/frontend/templates/course_admin/danger_zone.html:86 +#: inginious/frontend/templates/course_admin/danger_zone.html:167 +#: inginious/frontend/templates/course_admin/student_info.html:107 +#: inginious/frontend/templates/course_admin/student_list.html:276 #: inginious/frontend/templates/course_admin/student_list_table.html:86 -#: inginious/frontend/templates/course_admin/submissions.html:249 -#: inginious/frontend/templates/course_admin/task_dispensers/util_delete_modal.html:27 -#: inginious/frontend/templates/course_admin/task_list.html:56 -#: inginious/frontend/templates/course_admin/task_list.html:108 +#: inginious/frontend/templates/course_admin/submissions.html:250 +#: inginious/frontend/templates/course_admin/task_list.html:57 +#: inginious/frontend/templates/course_admin/task_list.html:100 #: inginious/frontend/templates/lti_bind.html:32 #: inginious/frontend/templates/preferences/delete.html:59 #: inginious/frontend/templates/queue.html:112 +#: inginious/frontend/templates/task_dispensers_admin/util_delete_modal.html:29 +#: inginious/frontend/templates/task_dispensers_admin/util_move_modal.html:17 +#: inginious/frontend/templates/taskset_admin/danger_zone.html:71 +#: inginious/frontend/templates/taskset_admin/settings.html:151 +#: inginious/frontend/templates/taskset_admin/settings.html:177 +#: inginious/frontend/templates/taskset_admin/template.html:57 +#: inginious/frontend/templates/taskset_admin/template.html:101 +#: inginious/frontend/templates/tasksets.html:101 #: inginious/frontend/templates/unregister_modal.html:19 msgid "Cancel" msgstr "Annuler" -#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:87 -#: inginious/frontend/templates/course_admin/settings.html:486 -#: inginious/frontend/templates/course_admin/task_edit.html:10 -#: inginious/frontend/templates/course_admin/task_edit.html:31 -#: inginious/frontend/templates/course_admin/task_edit.html:97 -#: inginious/frontend/templates/course_admin/task_list.html:57 -#: inginious/frontend/templates/course_admin/task_list.html:113 +#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:90 +#: inginious/frontend/templates/course_admin/settings.html:481 +#: inginious/frontend/templates/course_admin/task_list.html:58 +#: inginious/frontend/templates/course_admin/task_list.html:105 +#: inginious/frontend/templates/taskset_admin/settings.html:51 +#: inginious/frontend/templates/taskset_admin/task_edit.html:10 +#: inginious/frontend/templates/taskset_admin/task_edit.html:30 +#: inginious/frontend/templates/taskset_admin/task_edit.html:95 +#: inginious/frontend/templates/taskset_admin/template.html:58 +#: inginious/frontend/templates/taskset_admin/template.html:106 msgid "Save changes" msgstr "Appliquer les changements" -#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:102 +#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:105 msgid "You have no upcoming tasks" msgstr "Vous n'avez aucun exercice à réaliser" -#: inginious/frontend/task_dispensers/combinatory_test.py:22 +#: inginious/frontend/task_dispensers/combinatory_test.py:24 msgid "Combinatory test" msgstr "Test combinatoire" -#: inginious/frontend/task_dispensers/combinatory_test.py:45 +#: inginious/frontend/task_dispensers/combinatory_test.py:47 msgid "Amount of tasks to be displayed" msgstr "Nombre d'exercices à afficher" -#: inginious/frontend/task_dispensers/toc.py:40 +#: inginious/frontend/task_dispensers/toc.py:52 msgid "Table of contents" msgstr "Table des matières" -#: inginious/frontend/task_dispensers/toc.py:103 +#: inginious/frontend/task_dispensers/toc.py:115 msgid "Closed by default" msgstr "Fermé par défaut" +#: inginious/frontend/task_dispensers/toc.py:116 +msgid "Hidden if empty" +msgstr "Masqué si vide" + #: inginious/frontend/task_dispensers/util.py:49 -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/groups.html:4 +#: inginious/frontend/templates/task_dispensers_admin/config_items/groups.html:6 +#: inginious/frontend/templates/task_dispensers_admin/config_items/groups.html:10 msgid "Submission mode" msgstr "Mode de soumission" @@ -1105,7 +1215,8 @@ msgid "Weight" msgstr "Poids de la note" #: inginious/frontend/task_dispensers/util.py:97 -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/submission_storage.html:4 +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_storage.html:6 +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_storage.html:12 msgid "Submission storage" msgstr "Stockage des soumissions" @@ -1114,7 +1225,8 @@ msgid "Evaluation mode" msgstr "Mode d'évaluation" #: inginious/frontend/task_dispensers/util.py:145 -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/categories.html:4 +#: inginious/frontend/templates/task_dispensers_admin/config_items/categories.html:6 +#: inginious/frontend/templates/task_dispensers_admin/config_items/categories.html:9 msgid "Categories" msgstr "Catégories" @@ -1124,16 +1236,16 @@ msgid "Submission limit" msgstr "Limite de soumission" #: inginious/frontend/task_dispensers/util.py:193 -#: inginious/frontend/templates/course_admin/settings.html:43 -#: inginious/frontend/templates/course_admin/settings.html:140 -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/accessibility.html:4 +#: inginious/frontend/templates/course_admin/settings.html:44 +#: inginious/frontend/templates/course_admin/settings.html:135 +#: inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html:6 +#: inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html:15 msgid "Accessibility" msgstr "Accessibilité" #: inginious/frontend/task_dispensers/util.py:219 msgid "One section don't contain a sections list nor a tasks list" -msgstr "" -"Une section ne contient ni de liste de sections ni de liste d'exercices" +msgstr "Une section ne contient ni de liste de sections ni de liste d'exercices" #: inginious/frontend/task_dispensers/util.py:251 msgid "No title for one section" @@ -1164,17 +1276,12 @@ msgstr "Note actuelle" #: inginious/frontend/templates/course_user_settings.html:13 msgid "" "This course is currently invisible for students. You can change this by " -"modifying the \"accessibility\" option in the configuration of the course." +"modifying the \"accessibility\" option in the configuration of the " +"course." msgstr "" "Ce cours n'est pas affiché aux étudiants. Vous pouvez changer cela en " "modifiant l'option \"accessibilité\" dans les paramètres du cours." -#: inginious/frontend/templates/course.html:53 -#: inginious/frontend/templates/course.html:71 -#: inginious/frontend/templates/course_user_settings.html:33 -msgid "Course Settings" -msgstr "Paramètres du cours" - #: inginious/frontend/templates/course.html:56 #: inginious/frontend/templates/course_user_settings.html:18 msgid "Course administration" @@ -1213,14 +1320,21 @@ msgstr "" "problèmes." #: inginious/frontend/templates/course.html:99 +#: inginious/frontend/templates/course_admin/audience_edit.html:15 +#: inginious/frontend/templates/course_admin/danger_zone.html:15 +#: inginious/frontend/templates/course_admin/settings.html:15 +#: inginious/frontend/templates/course_admin/stats.html:22 +#: inginious/frontend/templates/course_admin/student_info.html:17 +#: inginious/frontend/templates/course_admin/student_list.html:15 +#: inginious/frontend/templates/course_admin/submission.html:19 +#: inginious/frontend/templates/course_admin/submissions.html:15 +#: inginious/frontend/templates/course_admin/task_list.html:16 #: inginious/frontend/templates/course_user_settings.html:42 -#: inginious/frontend/templates/courselist.html:17 #: inginious/frontend/templates/layout.html:154 -#: inginious/frontend/templates/marketplace.html:17 #: inginious/frontend/templates/mycourses.html:5 #: inginious/frontend/templates/mycourses.html:10 #: inginious/frontend/templates/mycourses.html:18 -#: inginious/frontend/templates/mycourses.html:55 +#: inginious/frontend/templates/mycourses.html:52 #: inginious/frontend/templates/task.html:261 msgid "My courses" msgstr "Mes cours" @@ -1238,7 +1352,7 @@ msgid "Register to course '{}'" msgstr "S'inscrire au cours '{}'" #: inginious/frontend/templates/course_register.html:11 -#: inginious/frontend/templates/layout.html:175 +#: inginious/frontend/templates/layout.html:176 #: inginious/frontend/templates/register.html:5 msgid "Register" msgstr "S'enregistrer" @@ -1274,8 +1388,8 @@ msgid "" "This course must be accessed on an external platform. Read the course " "description or contact the teaching team for more information." msgstr "" -"Ce cours est accessible via une plateforme externe. Veuillez vous référer à " -"la description du cours ou contacter l'équipe enseignante pour plus " +"Ce cours est accessible via une plateforme externe. Veuillez vous référer" +" à la description du cours ou contacter l'équipe enseignante pour plus " "d'information." #: inginious/frontend/templates/course_unavailable.html:25 @@ -1296,22 +1410,22 @@ msgstr "Cours publics" #: inginious/frontend/templates/courselist.html:12 msgid "" -"This page lists all the courses that are available now. If you are a course " -"administrator, go to your 'My courses' page to see all of them." +"This page lists all the courses that are available now. If you are a " +"course administrator, go to your 'My courses' page to see all of them." msgstr "" "Cette page liste tous les cours disponibles pour l'instant. Si vous êtes " -"administrateur de cours, rendez vous sur la page 'Mes cours' pour les voir " -"tous." +"administrateur de cours, rendez vous sur la page 'Mes cours' pour les " +"voir tous." -#: inginious/frontend/templates/courselist.html:62 +#: inginious/frontend/templates/courselist.html:56 msgid "External platform" msgstr "Plateforme externe" -#: inginious/frontend/templates/courselist.html:66 +#: inginious/frontend/templates/courselist.html:60 msgid "Auto-registration" msgstr "Auto-enregistrement" -#: inginious/frontend/templates/courselist.html:70 +#: inginious/frontend/templates/courselist.html:64 msgid "Password needed" msgstr "Mot de passe requis" @@ -1319,8 +1433,8 @@ msgstr "Mot de passe requis" msgid "Register for a group" msgstr "S'inscrire dans un groupe" -#: inginious/frontend/templates/course_admin/student_list.html:48 -#: inginious/frontend/templates/course_admin/student_list.html:208 +#: inginious/frontend/templates/course_admin/student_list.html:49 +#: inginious/frontend/templates/course_admin/student_list.html:209 #: inginious/frontend/templates/group.html:59 #: inginious/frontend/templates/group.html:68 msgid "Groups" @@ -1341,19 +1455,20 @@ msgstr "Envoyer un email" #: inginious/frontend/templates/group.html:114 msgid "" -"No set group in this course. If you think this is a mistake, please contact " -"the course administrator." +"No set group in this course. If you think this is a mistake, please " +"contact the course administrator." msgstr "" "Pas de groupe configuré pour ce cours. Si vous pensez qu'il s'agitd'une " "erreur, veuillez contacter l'administrateur du cours." #: inginious/frontend/templates/group.html:118 msgid "" -"Groups have been set up but you're not allowed to register in anyone. Please " -"contact the course administrator." +"Groups have been set up but you're not allowed to register in anyone. " +"Please contact the course administrator." msgstr "" -"Des groupes ont été configurés pour ce cours mais vous n'êtes pas autorisés " -"à vous y inscrire. Veuillez contacter l'administrateur du cours." +"Des groupes ont été configurés pour ce cours mais vous n'êtes pas " +"autorisés à vous y inscrire. Veuillez contacter l'administrateur du " +"cours." #: inginious/frontend/templates/group.html:121 #: inginious/frontend/templates/group.html:126 @@ -1362,9 +1477,9 @@ msgstr "Mon groupe" #: inginious/frontend/templates/group.html:123 msgid "" -"You're not registered to a group. Please consider registration in one of the " -"group below to take part in all course activities. If no more group is " -"available, please contact the course administrator." +"You're not registered to a group. Please consider registration in one of " +"the group below to take part in all course activities. If no more group " +"is available, please contact the course administrator." msgstr "" "Vous n'êtes pas inscrit dans un groupe. Veuillez vous enregistrer pour " "prendre part à toutes les activités du cours. S'il n'y a plus de groupe " @@ -1375,8 +1490,8 @@ msgid "" "You're not yet registered to a group. Please contact the course " "administrator for more information." msgstr "" -"Vous n'êtes pas inscrit dans un groupe. Veuillez contacter l'admnistrateur " -"du cours pour plus d'informations." +"Vous n'êtes pas inscrit dans un groupe. Veuillez contacter " +"l'admnistrateur du cours pour plus d'informations." #: inginious/frontend/templates/group.html:133 msgid "All groups" @@ -1399,8 +1514,8 @@ msgid "" "Internal error Something bad happened. The error has been logged, " "please contact your administrator if the error persists." msgstr "" -"Erreur interne Quelque chose de mal est arrivé. L'erreur a été notée, " -"veuillez contacter votre administrateur si l'erreur persiste." +"Erreur interne Quelque chose de mal est arrivé. L'erreur a été " +"notée, veuillez contacter votre administrateur si l'erreur persiste." #: inginious/frontend/templates/layout.html:19 #: inginious/frontend/templates/layout.html:76 @@ -1412,22 +1527,25 @@ msgstr "false" msgid "Open menu" msgstr "Ouvrir le menu" -#: inginious/frontend/templates/course_admin/audience_edit.html:16 -#: inginious/frontend/templates/course_admin/danger_zone.html:16 -#: inginious/frontend/templates/course_admin/settings.html:16 -#: inginious/frontend/templates/course_admin/stats.html:23 -#: inginious/frontend/templates/course_admin/student_info.html:18 -#: inginious/frontend/templates/course_admin/student_list.html:16 -#: inginious/frontend/templates/course_admin/submission.html:21 -#: inginious/frontend/templates/course_admin/submissions.html:16 -#: inginious/frontend/templates/course_admin/task_edit.html:17 -#: inginious/frontend/templates/course_admin/task_list.html:17 +#: inginious/frontend/templates/course_admin/audience_edit.html:17 +#: inginious/frontend/templates/course_admin/danger_zone.html:17 +#: inginious/frontend/templates/course_admin/settings.html:17 +#: inginious/frontend/templates/course_admin/stats.html:24 +#: inginious/frontend/templates/course_admin/student_info.html:19 +#: inginious/frontend/templates/course_admin/student_list.html:17 +#: inginious/frontend/templates/course_admin/submission.html:22 +#: inginious/frontend/templates/course_admin/submissions.html:17 +#: inginious/frontend/templates/course_admin/task_list.html:18 #: inginious/frontend/templates/layout.html:142 #: inginious/frontend/templates/task.html:128 +#: inginious/frontend/templates/taskset_admin/danger_zone.html:18 +#: inginious/frontend/templates/taskset_admin/settings.html:18 +#: inginious/frontend/templates/taskset_admin/task_edit.html:19 +#: inginious/frontend/templates/taskset_admin/template.html:19 msgid "Administration" msgstr "Administration" -#: inginious/frontend/templates/course_admin/stats.html:260 +#: inginious/frontend/templates/course_admin/stats.html:261 #: inginious/frontend/templates/layout.html:145 msgid "Users" msgstr "Utilisateurs" @@ -1436,46 +1554,55 @@ msgstr "Utilisateurs" #: inginious/frontend/templates/marketplace.html:5 #: inginious/frontend/templates/marketplace.html:25 #: inginious/frontend/templates/marketplace.html:32 -#: inginious/frontend/templates/marketplace_course.html:45 +#: inginious/frontend/templates/marketplace_taskset.html:45 msgid "Marketplace" msgstr "Magasin de cours" #: inginious/frontend/templates/layout.html:155 +#: inginious/frontend/templates/marketplace.html:17 +#: inginious/frontend/templates/tasksets.html:5 +#: inginious/frontend/templates/tasksets.html:10 +#: inginious/frontend/templates/tasksets.html:32 +msgid "My tasksets" +msgstr "Mes jeux d'exercices" + +#: inginious/frontend/templates/layout.html:156 #: inginious/frontend/templates/preferences/bindings.html:15 #: inginious/frontend/templates/preferences/delete.html:16 #: inginious/frontend/templates/preferences/profile.html:15 msgid "Preferences" msgstr "Préférences" -#: inginious/frontend/templates/layout.html:156 +#: inginious/frontend/templates/layout.html:157 msgid "Service status" msgstr "Etat du service" -#: inginious/frontend/templates/layout.html:157 +#: inginious/frontend/templates/layout.html:158 msgid "Log out" msgstr "Déconnexion" -#: inginious/frontend/templates/layout.html:161 +#: inginious/frontend/templates/layout.html:162 msgid "English" msgstr "Français" -#: inginious/frontend/templates/layout.html:195 +#: inginious/frontend/templates/layout.html:196 msgid "" -"Powered by INGInious v.{} , a free and open-source grading tool." +"Powered by INGInious v.{} , a free and open-source grading " +"tool." msgstr "" -"Propulsé par INGInious v.{}, un outil de correction libre." +"Propulsé par INGInious v.{}, un outil de correction libre." -#: inginious/frontend/templates/layout.html:197 +#: inginious/frontend/templates/layout.html:198 msgid "Running INGInious v.{}" msgstr "Fourni par INGInious v.{}" -#: inginious/frontend/templates/layout.html:199 +#: inginious/frontend/templates/layout.html:200 msgid "INGInious is distributed under AGPL license" msgstr "INGInious est distribué sous licence AGPL" -#: inginious/frontend/templates/layout.html:203 +#: inginious/frontend/templates/layout.html:204 msgid "Privacy Policy" msgstr "Politique de confidentialité" @@ -1536,8 +1663,8 @@ msgid "" "In order to use INGInious via this platform, please bind your existing " "account or create a new one." msgstr "" -"Afin d'utiliser INGInious via cette platforme, veuillez lier votre compte " -"existant ou en créer un nouveau." +"Afin d'utiliser INGInious via cette platforme, veuillez lier votre compte" +" existant ou en créer un nouveau." #: inginious/frontend/templates/lti_login.html:32 msgid "Bind your INGInious account" @@ -1549,11 +1676,11 @@ msgstr "Rafraîchir la page" #: inginious/frontend/templates/maintenance.html:9 msgid "" -"This INGInious instance is currently under maintenance. Please come " -"back in a few minutes!" +"This INGInious instance is currently under maintenance. Please " +"come back in a few minutes!" msgstr "" -"Cette instance d'INGInious est sous maintenance. Veuillez réessayer " -"dans quelques minutes !" +"Cette instance d'INGInious est sous maintenance. Veuillez " +"réessayer dans quelques minutes !" #: inginious/frontend/templates/marketplace.html:10 msgid "How to use" @@ -1561,124 +1688,104 @@ msgstr "Comment cela fonctionne" #: inginious/frontend/templates/marketplace.html:12 msgid "" -"This page list all the course that are publicly avalaible. When you click " -"import, it will create a new course on your INGInious instance. You will be " -"admin of this new course and it will contain all the tasks and the structure " -"of the imported course" +"This page list all the tasksets that are publicly avalaible. When you " +"click import, it will create a new taskset on your INGInious instance. " +"You will be admin of this new taskset and it will contain all the tasks " +"of the imported taskset." msgstr "" -"Cette page liste tous les cours disponibles publiquement. Lorsque vous " -"cliquer sur Importer, un nouveau cours sera créé sur votre instance " -"INGInious. Vous serez administrateur de ce nouveau cours qui contiendra la " -"structure et l'ensemble des exercices du cours importé." +"Cette page liste tous les jeux d'exercices disponibles publiquement. Lorsque vous " +"cliquez sur Importer, un nouveau jeu d'exercices sera créé sur votre instance " +"INGInious. Vous serez administrateur de ce nouveau jeu d'exercices qui contiendra " +"la structure et l'ensemble des exercices du jeu importé." #: inginious/frontend/templates/marketplace.html:56 -#: inginious/frontend/templates/marketplace_course.html:68 -msgid "Import course" -msgstr "Importer le cours" +#: inginious/frontend/templates/marketplace_taskset.html:68 +msgid "Import taskset" +msgstr "Importer le jeu d'exercices" #: inginious/frontend/templates/marketplace.html:74 -msgid "No public courses available" -msgstr "Pas de cours publiques disponible." +msgid "No public tasksets available" +msgstr "Pas de jeu d'exercice publique disponible." #: inginious/frontend/templates/marketplace.html:84 -msgid "Import this course" -msgstr "Importer ce cours" +msgid "Import this taskset" +msgstr "Importer ce jeu d'exercices" #: inginious/frontend/templates/marketplace.html:89 msgid "" -"This will create a new course with the ID bellow. This new course will " -"contain the tasks and structure of the selected course" +"This will create a new taskset with the ID bellow. This new taskset will " +"contain the tasks and structure of the selected taskset" msgstr "" -"Un nouveau cours sera créé avec l'identifiant ci-dessous. Celui-ci " -"contiendra la structure et les exercices du cours sélectionné." +"Un nouveau jeu d'exercices sera créé avec l'identifiant ci-dessous. Celui-ci " +"contiendra la structure et les exercices du jeu d'exercices sélectionné." #: inginious/frontend/templates/marketplace.html:90 msgid "Course ID" msgstr "Identifiant du cours" #: inginious/frontend/templates/marketplace.html:94 -msgid "Create course" -msgstr "Créer le cours" +msgid "Create taskset" +msgstr "Créer un nouveau jeu d'exercices" -#: inginious/frontend/templates/marketplace_course.html:7 +#: inginious/frontend/templates/marketplace_taskset.html:7 #: inginious/frontend/templates/task.html:13 msgid "Information" msgstr "Informations" -#: inginious/frontend/templates/marketplace_course.html:11 +#: inginious/frontend/templates/marketplace_taskset.html:11 msgid "Language(s)" msgstr "Langue(s)" -#: inginious/frontend/templates/marketplace_course.html:17 +#: inginious/frontend/templates/marketplace_taskset.html:17 msgid "License" msgstr "Licence" -#: inginious/frontend/templates/marketplace_course.html:23 +#: inginious/frontend/templates/marketplace_taskset.html:23 msgid "Maintainer(s)" msgstr "Gestionnaire(s)" -#: inginious/frontend/templates/marketplace_course.html:29 +#: inginious/frontend/templates/marketplace_taskset.html:29 #: inginious/frontend/templates/task.html:17 msgid "Author(s)" msgstr "Auteur(s)" -#: inginious/frontend/templates/marketplace_course.html:35 +#: inginious/frontend/templates/marketplace_taskset.html:35 msgid "Link" msgstr "Lien" -#: inginious/frontend/templates/marketplace_course.html:65 -#: inginious/frontend/templates/mycourses.html:110 -msgid "New course id" -msgstr "Nouvel identifiant de cours" +#: inginious/frontend/templates/marketplace_taskset.html:65 +#: inginious/frontend/templates/tasksets.html:75 +msgid "New taskset id" +msgstr "Nouvel identifiant de jeu d'exercices" #: inginious/frontend/templates/mycourses.html:20 msgid "" -"This page lists all the courses you are currently enrolled in. You can find " -"new courses on the course list page." +"This page lists all the courses you are currently enrolled in. You can " +"find new courses on the course list page." msgstr "" -"Cette page liste tous les cours auxquels vous êtes actuellement enregistré. " -"Vous pouvez trouver de nouveaux cours dans la Liste des cours" +"Cette page liste tous les cours auxquels vous êtes actuellement " +"enregistré. Vous pouvez trouver de nouveaux cours dans la Liste des cours" -#: inginious/frontend/templates/mycourses.html:73 +#: inginious/frontend/templates/mycourses.html:70 msgid "LTI course" msgstr "Cours LTI" -#: inginious/frontend/templates/mycourses.html:75 +#: inginious/frontend/templates/mycourses.html:72 msgid "Hidden course" msgstr "Cours masqué" -#: inginious/frontend/templates/mycourses.html:79 +#: inginious/frontend/templates/mycourses.html:76 msgid "Administrator" msgstr "Administrateur" -#: inginious/frontend/templates/mycourses.html:81 -msgid "Tutor" -msgstr "Tuteur" - -#: inginious/frontend/templates/mycourses.html:83 +#: inginious/frontend/templates/mycourses.html:78 msgid "Student" msgstr "Etudiant" -#: inginious/frontend/templates/mycourses.html:91 +#: inginious/frontend/templates/mycourses.html:86 msgid "You are not registered to any course" msgstr "Vous n'êtes inscrit à aucun cours" -#: inginious/frontend/templates/mycourses.html:99 -msgid "Course created." -msgstr "Cours créé." - -#: inginious/frontend/templates/mycourses.html:104 -msgid "Failed to create the course." -msgstr "Une erreur s'est produite lors de la création du cours." - -#: inginious/frontend/templates/mycourses.html:109 -msgid "Course" -msgstr "Cours" - -#: inginious/frontend/templates/mycourses.html:113 -msgid "Create new course" -msgstr "Créer un nouveau cours" - #: inginious/frontend/templates/notfound.html:5 #: inginious/frontend/templates/notfound.html:10 msgid "Error 404" @@ -1692,17 +1799,16 @@ msgstr "File d'attente" #: inginious/frontend/templates/queue.html:19 msgid "This page shows a snapshot of the job queue." -msgstr "" -"Cette page affiche un instantané de la file d'attente." +msgstr "Cette page affiche un instantané de la file d'attente." #: inginious/frontend/templates/queue.html:21 msgid "Running jobs" msgstr "Tâches en cours d'exécution" -#: inginious/frontend/templates/course_admin/subproblems/code.html:19 -#: inginious/frontend/templates/course_admin/subproblems/file.html:23 #: inginious/frontend/templates/queue.html:25 #: inginious/frontend/templates/queue.html:66 +#: inginious/frontend/templates/taskset_admin/subproblems/code.html:19 +#: inginious/frontend/templates/taskset_admin/subproblems/file.html:23 msgid "Type" msgstr "Type" @@ -1710,11 +1816,12 @@ msgstr "Type" msgid "Agent name" msgstr "Nom de l'agent" -#: inginious/frontend/templates/course_admin/edit_tabs/basic.html:5 -#: inginious/frontend/templates/course_admin/settings.html:62 -#: inginious/frontend/templates/course_admin/task_edit.html:124 +#: inginious/frontend/templates/course_admin/settings.html:63 #: inginious/frontend/templates/queue.html:27 #: inginious/frontend/templates/queue.html:67 +#: inginious/frontend/templates/taskset_admin/edit_tabs/basic.html:5 +#: inginious/frontend/templates/taskset_admin/settings.html:56 +#: inginious/frontend/templates/taskset_admin/task_edit.html:122 msgid "Name" msgstr "Nom" @@ -1806,11 +1913,11 @@ msgstr "Confirmez le mot de passe :" #: inginious/frontend/templates/preferences/profile.html:90 #: inginious/frontend/templates/register.html:48 msgid "" -"I have read the {a1start}Terms of service{a1end} and the {a2start}Privacy " -"policy{a2end}." +"I have read the {a1start}Terms of service{a1end} and the {a2start}Privacy" +" policy{a2end}." msgstr "" -"J'ai pris connaissance des {a1start}conditions d'utilisation{a1end} et de la " -"{a2start}politique de confidentialité{a2end}." +"J'ai pris connaissance des {a1start}conditions d'utilisation{a1end} et de" +" la {a2start}politique de confidentialité{a2end}." #: inginious/frontend/templates/register.html:52 msgid "Sign up" @@ -1830,18 +1937,18 @@ msgstr "Configurez le nouveau mot de passe" #: inginious/frontend/templates/signin_button.html:6 msgid "" -"Please register or sign in to see the complete list of courses and be able " -"to submit answers to problems." +"Please register or sign in to see the complete list of courses and be " +"able to submit answers to problems." msgstr "" -"Veuillez vous enregistrer ou vous connecter pour voir la liste complète des " -"cours et pouvoir soumettrez des réponses aux problèmes." +"Veuillez vous enregistrer ou vous connecter pour voir la liste complète " +"des cours et pouvoir soumettrez des réponses aux problèmes." #: inginious/frontend/templates/task.html:23 msgid "Contact" msgstr "Contact" -#: inginious/frontend/templates/course_admin/edit_tabs/basic.html:49 #: inginious/frontend/templates/task.html:24 +#: inginious/frontend/templates/taskset_admin/edit_tabs/basic.html:49 msgid "Contact link" msgstr "Lien de contact" @@ -1865,8 +1972,8 @@ msgstr "Etat" msgid "Not yet attempted" msgstr "Pas encore essayé" -#: inginious/frontend/templates/course_admin/student_info.html:73 -#: inginious/frontend/templates/course_admin/submissions.html:133 +#: inginious/frontend/templates/course_admin/student_info.html:74 +#: inginious/frontend/templates/course_admin/submissions.html:134 #: inginious/frontend/templates/task.html:52 msgid "Succeeded" msgstr "Réussi" @@ -1875,8 +1982,8 @@ msgstr "Réussi" msgid "Waiting for verification" msgstr "En attente de correction" -#: inginious/frontend/templates/course_admin/student_info.html:76 -#: inginious/frontend/templates/course_admin/submissions.html:137 +#: inginious/frontend/templates/course_admin/student_info.html:77 +#: inginious/frontend/templates/course_admin/submissions.html:138 #: inginious/frontend/templates/task.html:63 msgid "Failed" msgstr "Echoué" @@ -1890,8 +1997,9 @@ msgstr "Note" msgid "Attempts" msgstr "Nombre d'essais" -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/submission_limit.html:7 #: inginious/frontend/templates/task.html:86 +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_limit.html:8 +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_limit.html:20 msgid "No limitation" msgstr "Pas de limite" @@ -1907,8 +2015,8 @@ msgstr "{nb_submissions} soumissions" msgid "Category tags" msgstr "Étiquettes de catégories" -#: inginious/frontend/templates/course_admin/settings.html:49 -#: inginious/frontend/templates/course_admin/submission.html:98 +#: inginious/frontend/templates/course_admin/settings.html:50 +#: inginious/frontend/templates/course_admin/submission.html:99 #: inginious/frontend/templates/task.html:114 msgid "Tags" msgstr "Etiquettes" @@ -1926,19 +2034,20 @@ msgid "" "This task is currently invisible for students. You can change this by " "modifying the \"accessible\" option in the configuration of the task." msgstr "" -"Cet exercice n'est pas affiché aux étudiants. Vous pouvez changer cela en " -"modifiant l'option \"accessibilité\" dans les paramètres de l'exercice." +"Cet exercice n'est pas affiché aux étudiants. Vous pouvez changer cela en" +" modifiant l'option \"accessibilité\" dans les paramètres de l'exercice." -#: inginious/frontend/templates/course_admin/stats.html:81 -#: inginious/frontend/templates/course_admin/student_list.html:185 +#: inginious/frontend/templates/course_admin/stats.html:82 +#: inginious/frontend/templates/course_admin/student_list.html:186 #: inginious/frontend/templates/course_admin/student_list_table.html:59 -#: inginious/frontend/templates/course_admin/task_dispensers/task_buttons.html:18 #: inginious/frontend/templates/task.html:141 +#: inginious/frontend/templates/task_dispensers_admin/task_buttons.html:26 msgid "View submissions" msgstr "Voir les soumissions" -#: inginious/frontend/templates/course_admin/task_dispensers/task_buttons.html:14 #: inginious/frontend/templates/task.html:145 +#: inginious/frontend/templates/task_dispensers_admin/task_buttons.html:20 +#: inginious/frontend/templates/taskset_admin/settings.html:127 msgid "Edit task" msgstr "Editer l'exercice" @@ -1959,13 +2068,15 @@ msgstr "Groupe" msgid "For evaluation" msgstr "Pour évaluation" -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/evaluation_mode.html:15 #: inginious/frontend/templates/task.html:180 +#: inginious/frontend/templates/task_dispensers_admin/config_items/evaluation_mode.html:8 +#: inginious/frontend/templates/task_dispensers_admin/config_items/evaluation_mode.html:21 msgid "Last submission" msgstr "Dernière soumission" -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/evaluation_mode.html:9 #: inginious/frontend/templates/task.html:182 +#: inginious/frontend/templates/task_dispensers_admin/config_items/evaluation_mode.html:7 +#: inginious/frontend/templates/task_dispensers_admin/config_items/evaluation_mode.html:15 msgid "Best submission" msgstr "Meilleure soumission" @@ -2000,8 +2111,7 @@ msgstr "Exercice suivant" #: inginious/frontend/templates/task.html:315 msgid "Alternatively, you can also paste this command into your terminal:" -msgstr "" -"Vous pouvez également copier/coller cette commande dans votre terminal :" +msgstr "Vous pouvez également copier/coller cette commande dans votre terminal :" #: inginious/frontend/templates/task.html:317 msgid "Paste this command into your terminal:" @@ -2025,12 +2135,12 @@ msgstr "{} est trop lourd." #: inginious/frontend/templates/task.html:340 msgid "" -"The raw data from the container will be displayed here, helping you to debug " -"the task.
This box is only displayed because you are an administrator " -"of this course. It is not displayed to students." +"The raw data from the container will be displayed here, helping you to " +"debug the task.
This box is only displayed because you are an " +"administrator of this course. It is not displayed to students." msgstr "" -"Les données brutes du conteneur seront affichées ici, afin de vous aider à " -"déboguer l'exercice.
Ce dialogue est affiché car vous êtes " +"Les données brutes du conteneur seront affichées ici, afin de vous aider " +"à déboguer l'exercice.
Ce dialogue est affiché car vous êtes " "administrateur du cours. Il n'est pas affiché aux étudiants." #: inginious/frontend/templates/task.html:373 @@ -2079,13 +2189,50 @@ msgstr "Vous n'êtes pas autorisé à soumettre" msgid "Task unavailable" msgstr "Exercice non disponible" +#: inginious/frontend/templates/tasksets.html:19 +msgid "" +"This page lists all the tasksets you have access to. You can find " +"instantiated courses on the course list page." +msgstr "" +"Cette page liste tous les jeux d'exercices auxquels vous avez accès. " +"Vous pouvez trouver les cours instanciés dans la Liste des cours" + +#: inginious/frontend/templates/tasksets.html:56 +msgid "Edit taskset" +msgstr "Editer le jeu d'exercices" + +#: inginious/frontend/templates/tasksets.html:60 +#: inginious/frontend/templates/tasksets.html:102 +msgid "Instantiate" +msgstr "Instancier" + +#: inginious/frontend/templates/tasksets.html:67 +msgid "You don't own any taskset." +msgstr "Vous ne possédez aucun jeu d'exercices." + +#: inginious/frontend/templates/tasksets.html:74 +msgid "Course" +msgstr "Cours" + +#: inginious/frontend/templates/tasksets.html:78 +msgid "Create new taskset" +msgstr "Créer un nouvel exercice" + +#: inginious/frontend/templates/tasksets.html:88 +msgid "Instantiate a course" +msgstr "Instancier un cours" + +#: inginious/frontend/templates/tasksets.html:93 +#: inginious/frontend/templates/tasksets.html:96 +msgid "New courseid" +msgstr "Nouvel identifiant de cours" + #: inginious/frontend/templates/unregister_modal.html:9 msgid "Unregister from {}" msgstr "Se désinscrire de {}" #: inginious/frontend/templates/unregister_modal.html:15 -msgid "" -"This will keep your submissions saved but will remove you from your group." +msgid "This will keep your submissions saved but will remove you from your group." msgstr "Ceci conservera vos soumissions mais vous enlèvera de votre groupe." #: inginious/frontend/templates/course_admin/student_list_table.html:77 @@ -2109,7 +2256,7 @@ msgstr "membre" #: inginious/frontend/templates/admin/admin_users.html:33 #: inginious/frontend/templates/course_admin/student_list_table.html:16 -#: inginious/frontend/templates/course_admin/submissions.html:55 +#: inginious/frontend/templates/course_admin/submissions.html:56 msgid "username" msgstr "nom d'utilisateur" @@ -2119,11 +2266,11 @@ msgid "email address" msgstr "adresse email" #: inginious/frontend/templates/admin/admin_users.html:38 -#: inginious/frontend/templates/course_admin/stats.html:61 -#: inginious/frontend/templates/course_admin/student_info.html:40 -#: inginious/frontend/templates/course_admin/student_list.html:159 +#: inginious/frontend/templates/course_admin/stats.html:62 +#: inginious/frontend/templates/course_admin/student_info.html:41 +#: inginious/frontend/templates/course_admin/student_list.html:160 #: inginious/frontend/templates/course_admin/student_list_table.html:26 -#: inginious/frontend/templates/course_admin/submissions.html:78 +#: inginious/frontend/templates/course_admin/submissions.html:79 msgid "Download CSV" msgstr "Télécharger sous format CSV" @@ -2132,10 +2279,11 @@ msgid "Bindings" msgstr "Liaisons" #: inginious/frontend/templates/admin/admin_users.html:102 -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:19 -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:53 -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice_templates.html:31 -#: inginious/frontend/templates/course_admin/task_dispensers/section_menu.html:21 +#: inginious/frontend/templates/task_dispensers_admin/section_menu.html:21 +#: inginious/frontend/templates/task_dispensers_admin/util.html:131 +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:19 +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:53 +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice_templates.html:31 msgid "Delete" msgstr "Supprimer" @@ -2161,8 +2309,8 @@ msgid "Add user" msgstr "Ajouter un utilisateur" #: inginious/frontend/templates/admin/admin_users.html:168 -#: inginious/frontend/templates/course_admin/edit_tabs/subproblems.html:22 -#: inginious/frontend/templates/course_admin/task_dispensers/util.html:103 +#: inginious/frontend/templates/task_dispensers_admin/util.html:98 +#: inginious/frontend/templates/taskset_admin/edit_tabs/subproblems.html:22 msgid "Add" msgstr "Ajouter" @@ -2179,687 +2327,673 @@ msgstr "Supprimer l'accès" msgid "Identifier :" msgstr "Identifiant :" -#: inginious/frontend/templates/course_admin/audience_edit.html:18 -#: inginious/frontend/templates/course_admin/student_list.html:43 -#: inginious/frontend/templates/course_admin/student_list.html:78 -#: inginious/frontend/templates/course_admin/student_list.html:110 +#: inginious/frontend/templates/course_admin/audience_edit.html:19 +#: inginious/frontend/templates/course_admin/student_list.html:44 +#: inginious/frontend/templates/course_admin/student_list.html:79 +#: inginious/frontend/templates/course_admin/student_list.html:111 msgid "Audiences" msgstr "Publics" -#: inginious/frontend/templates/course_admin/audience_edit.html:20 -#: inginious/frontend/templates/course_admin/audience_edit.html:26 +#: inginious/frontend/templates/course_admin/audience_edit.html:21 +#: inginious/frontend/templates/course_admin/audience_edit.html:27 msgid "Edit audience {}" msgstr "Éditer le public {}" -#: inginious/frontend/templates/course_admin/audience_edit.html:44 -#: inginious/frontend/templates/course_admin/audience_edit.html:63 -#: inginious/frontend/templates/course_admin/student_list.html:67 -#: inginious/frontend/templates/course_admin/student_list.html:257 -#: inginious/frontend/templates/course_admin/student_list.html:276 +#: inginious/frontend/templates/course_admin/audience_edit.html:45 +#: inginious/frontend/templates/course_admin/audience_edit.html:64 +#: inginious/frontend/templates/course_admin/student_list.html:68 +#: inginious/frontend/templates/course_admin/student_list.html:258 +#: inginious/frontend/templates/course_admin/student_list.html:277 msgid "Add student" msgstr "Ajouter un étudiant" -#: inginious/frontend/templates/course_admin/audience_edit.html:51 +#: inginious/frontend/templates/course_admin/audience_edit.html:52 msgid "Choose student :" msgstr "Sélectionner l'étudiant :" -#: inginious/frontend/templates/course_admin/audience_edit.html:55 -#: inginious/frontend/templates/course_admin/settings.html:70 -#: inginious/frontend/templates/course_admin/settings.html:76 -#: inginious/frontend/templates/course_admin/student_list.html:65 +#: inginious/frontend/templates/course_admin/audience_edit.html:56 +#: inginious/frontend/templates/course_admin/settings.html:71 +#: inginious/frontend/templates/course_admin/student_list.html:66 +#: inginious/frontend/templates/taskset_admin/settings.html:64 msgid "Enter something here to search for a user" msgstr "Commencez à taper pour rechercher un utilisateur" -#: inginious/frontend/templates/course_admin/audience_edit.html:74 -#: inginious/frontend/templates/course_admin/task_edit.html:46 +#: inginious/frontend/templates/course_admin/audience_edit.html:75 +#: inginious/frontend/templates/taskset_admin/task_edit.html:44 msgid "Basic settings" msgstr "Paramètres de base" -#: inginious/frontend/templates/course_admin/audience_edit.html:78 -#: inginious/frontend/templates/course_admin/audience_edit.html:82 +#: inginious/frontend/templates/course_admin/audience_edit.html:79 +#: inginious/frontend/templates/course_admin/audience_edit.html:83 msgid "Audience description" msgstr "Description du public" -#: inginious/frontend/templates/course_admin/audience_edit.html:85 +#: inginious/frontend/templates/course_admin/audience_edit.html:86 msgid "Delete audience" msgstr "Supprimer le public" -#: inginious/frontend/templates/course_admin/audience_edit.html:92 +#: inginious/frontend/templates/course_admin/audience_edit.html:93 msgid "Tutor list" msgstr "Liste des tuteurs" -#: inginious/frontend/templates/course_admin/audience_edit.html:124 +#: inginious/frontend/templates/course_admin/audience_edit.html:125 msgid "Add tutor" msgstr "Ajouter un tuteur" -#: inginious/frontend/templates/course_admin/audience_edit.html:137 +#: inginious/frontend/templates/course_admin/audience_edit.html:138 msgid "Student list" msgstr "Liste des étudiants" -#: inginious/frontend/templates/course_admin/audience_edit.html:153 +#: inginious/frontend/templates/course_admin/audience_edit.html:154 msgid "Remove student" msgstr "Supprimer l'étudiant" -#: inginious/frontend/templates/course_admin/audience_edit.html:164 -#: inginious/frontend/templates/course_admin/student_list.html:435 +#: inginious/frontend/templates/course_admin/audience_edit.html:165 +#: inginious/frontend/templates/course_admin/student_list.html:436 msgid "Update" msgstr "Mettre à jour" -#: inginious/frontend/templates/course_admin/danger_zone.html:41 +#: inginious/frontend/templates/course_admin/danger_zone.html:42 msgid "Archive data" msgstr "Archiver les données" -#: inginious/frontend/templates/course_admin/danger_zone.html:44 -#: inginious/frontend/templates/course_admin/danger_zone.html:138 -#: inginious/frontend/templates/course_admin/danger_zone.html:151 -#: inginious/frontend/templates/course_admin/danger_zone.html:159 -#: inginious/frontend/templates/course_admin/danger_zone.html:167 +#: inginious/frontend/templates/course_admin/danger_zone.html:45 +#: inginious/frontend/templates/course_admin/danger_zone.html:139 +#: inginious/frontend/templates/course_admin/danger_zone.html:152 +#: inginious/frontend/templates/course_admin/danger_zone.html:160 +#: inginious/frontend/templates/course_admin/danger_zone.html:168 msgid "Delete course" msgstr "Supprimer le cours" -#: inginious/frontend/templates/course_admin/danger_zone.html:52 -#: inginious/frontend/templates/course_admin/danger_zone.html:63 +#: inginious/frontend/templates/course_admin/danger_zone.html:53 +#: inginious/frontend/templates/course_admin/danger_zone.html:64 msgid "Archive course data" msgstr "Archiver les données du cours" -#: inginious/frontend/templates/course_admin/danger_zone.html:55 +#: inginious/frontend/templates/course_admin/danger_zone.html:56 msgid "" "

This will reset and backup all course data (submissions, audiences, " "groups, user statistics) from the database.

To confirm your will, " "please type the course id below :

" msgstr "" "

Ceci remettra à zéro et sauvegardera toutes les données du cours " -"(soumissions, publics, groupes, statistiques) se trouvant dans la base de " -"données.

Pour confirmer, veuillez entrer l'identifiant du cours ci-" -"dessus :

" +"(soumissions, publics, groupes, statistiques) se trouvant dans la base de" +" données.

Pour confirmer, veuillez entrer l'identifiant du cours " +"ci-dessus :

" -#: inginious/frontend/templates/course_admin/danger_zone.html:75 +#: inginious/frontend/templates/course_admin/danger_zone.html:76 msgid "Restore backup from {}" msgstr "Restaurer la sauvegarde du {}" -#: inginious/frontend/templates/course_admin/danger_zone.html:79 +#: inginious/frontend/templates/course_admin/danger_zone.html:80 msgid "

This will restore your course data to {}. Are you sure ?

" msgstr "" -"

Ceci va restaurer les données du cours à la date du {}. Etes vous sûr ?" +"

Ceci va restaurer les données du cours à la date du {}. Etes vous sûr " +"?

" -#: inginious/frontend/templates/course_admin/danger_zone.html:86 -#: inginious/frontend/templates/course_admin/danger_zone.html:110 +#: inginious/frontend/templates/course_admin/danger_zone.html:87 +#: inginious/frontend/templates/course_admin/danger_zone.html:111 msgid "Restore backup" msgstr "Restaurer la sauvegarde" -#: inginious/frontend/templates/course_admin/danger_zone.html:94 +#: inginious/frontend/templates/course_admin/danger_zone.html:95 msgid "Backups" msgstr "Sauvegardes" -#: inginious/frontend/templates/course_admin/danger_zone.html:99 +#: inginious/frontend/templates/course_admin/danger_zone.html:100 msgid "backup date" msgstr "date de la sauvegarde" -#: inginious/frontend/templates/course_admin/danger_zone.html:100 +#: inginious/frontend/templates/course_admin/danger_zone.html:101 msgid "download" msgstr "télécharger" -#: inginious/frontend/templates/course_admin/danger_zone.html:114 +#: inginious/frontend/templates/course_admin/danger_zone.html:115 msgid "Download backup" msgstr "Télécharger la sauvegarde" -#: inginious/frontend/templates/course_admin/danger_zone.html:141 +#: inginious/frontend/templates/course_admin/danger_zone.html:142 msgid "" "

This will permanently remove the course and all its data " -"(including tasks and backups) from INGInious.

To confirm your will, " -"please type the course id below :

" +"(including backups) from INGInious.

To confirm your will, please " +"type the course id below :

" msgstr "" "

Ceci va supprimer définitement le cours et toutes ses données " -"d'INGInious (y compris les exercices et sauvegardes).

Pour confirmer, " -"veuillez entrer l'identifiant de cours ci-dessous :

" +"d'INGInious (y compris les sauvegardes).

Pour " +"confirmer, veuillez entrer l'identifiant de cours ci-dessous :

" -#: inginious/frontend/templates/course_admin/danger_zone.html:163 +#: inginious/frontend/templates/course_admin/danger_zone.html:164 msgid "" "

This will permanently remove the course and all its data " "(including tasks and backups) from INGInious. Are you really sure ?

" msgstr "" -"

Ceci va supprimer définitivement le cours et toutes ses données " -"d'INGInious(y compris les exercices et ssauvegardes). Êtes-vous vraiment " -"sûr ?

" +"

Ceci va supprimer définitivement le cours et toutes ses données" +" d'INGInious(y compris les exercices et ssauvegardes). Êtes-vous vraiment" +" sûr ?

" #: inginious/frontend/templates/course_admin/menu.html:15 -msgid "How to create a task?" -msgstr "Comment créer un exercice ?" - -#: inginious/frontend/templates/course_admin/menu.html:16 +#: inginious/frontend/templates/taskset_admin/menu.html:16 msgid "Documentation" msgstr "Documentation" -#: inginious/frontend/templates/course_admin/settings.html:6 -msgid "Settings" -msgstr "Paramètres" +#: inginious/frontend/templates/course_admin/settings.html:19 +#: inginious/frontend/templates/course_admin/settings.html:26 +msgid "Course settings" +msgstr "Paramètres du cours" -#: inginious/frontend/templates/course_admin/settings.html:32 +#: inginious/frontend/templates/course_admin/settings.html:33 +#: inginious/frontend/templates/taskset_admin/settings.html:39 msgid "Settings saved." msgstr "Paramètres sauvegardés." -#: inginious/frontend/templates/course_admin/settings.html:40 +#: inginious/frontend/templates/course_admin/settings.html:41 msgid "Base" msgstr "Base" -#: inginious/frontend/templates/course_admin/settings.html:46 +#: inginious/frontend/templates/course_admin/settings.html:47 msgid "LTI" msgstr "LTI" -#: inginious/frontend/templates/course_admin/settings.html:52 -msgid "User Settings" -msgstr "Paramètres utilisateur" +#: inginious/frontend/templates/course_admin/settings.html:53 +msgid "Course Settings" +msgstr "Paramètres du cours" -#: inginious/frontend/templates/course_admin/settings.html:68 +#: inginious/frontend/templates/course_admin/settings.html:69 +#: inginious/frontend/templates/taskset_admin/settings.html:62 msgid "Administrators" msgstr "Administrateurs" -#: inginious/frontend/templates/course_admin/settings.html:74 -msgid "Tutors" -msgstr "Tuteurs" - -#: inginious/frontend/templates/course_admin/settings.html:80 +#: inginious/frontend/templates/course_admin/settings.html:75 msgid "Description" msgstr "Description" -#: inginious/frontend/templates/course_admin/settings.html:86 +#: inginious/frontend/templates/course_admin/settings.html:81 msgid "Group attribution" msgstr "Attribution des groupes" -#: inginious/frontend/templates/course_admin/settings.html:93 +#: inginious/frontend/templates/course_admin/settings.html:88 msgid "Staff only" msgstr "Uniquement par l'équipe enseignante" -#: inginious/frontend/templates/course_admin/settings.html:100 +#: inginious/frontend/templates/course_admin/settings.html:95 msgid "Staff and students" msgstr "Par l'équipe enseignante et les étudiants" -#: inginious/frontend/templates/course_admin/settings.html:105 +#: inginious/frontend/templates/course_admin/settings.html:100 msgid "Enable LTI" msgstr "Activer LTI" -#: inginious/frontend/templates/course_admin/settings.html:112 +#: inginious/frontend/templates/course_admin/settings.html:107 msgid "" "Enable this option to allow your INGInious course to be used in external " -"Learning Management Systems (LMS) such as Moodle or edX. Note that this will " -"deactivate the group system." +"Learning Management Systems (LMS) such as Moodle or edX. Note that this " +"will deactivate the group system." msgstr "" -"Activez cette option pour permettre à votre cours INGInious d'être utilisé " -"par des Learning Management Systems (LMS) externes tels que Moodle ou edX. " -"Notez que cela désactivera le système de groupes." +"Activez cette option pour permettre à votre cours INGInious d'être " +"utilisé par des Learning Management Systems (LMS) externes tels que " +"Moodle ou edX. Notez que cela désactivera le système de groupes." -#: inginious/frontend/templates/course_admin/settings.html:121 +#: inginious/frontend/templates/course_admin/settings.html:116 msgid "Course preview" msgstr "Aperçu du cours" -#: inginious/frontend/templates/course_admin/settings.html:128 +#: inginious/frontend/templates/course_admin/settings.html:123 msgid "Allowed (read-only for non-registered users)" msgstr "Autorisé (lecture seule pour les utilisateurs anonymes)" -#: inginious/frontend/templates/course_admin/settings.html:135 +#: inginious/frontend/templates/course_admin/settings.html:130 msgid "Not allowed" msgstr "Interdit" -#: inginious/frontend/templates/course_admin/settings.html:148 +#: inginious/frontend/templates/course_admin/settings.html:143 msgid "Course hidden from students" msgstr "Cours masqué aux étudiants" -#: inginious/frontend/templates/course_admin/settings.html:155 +#: inginious/frontend/templates/course_admin/settings.html:150 msgid "Always accessible" msgstr "Toujours accessible" -#: inginious/frontend/templates/course_admin/settings.html:162 -#: inginious/frontend/templates/course_admin/settings.html:222 +#: inginious/frontend/templates/course_admin/settings.html:157 +#: inginious/frontend/templates/course_admin/settings.html:217 msgid "Custom:" msgstr "Personnalisé :" -#: inginious/frontend/templates/course_admin/settings.html:166 -#: inginious/frontend/templates/course_admin/settings.html:226 +#: inginious/frontend/templates/course_admin/settings.html:161 +#: inginious/frontend/templates/course_admin/settings.html:221 msgid "From" msgstr "De" -#: inginious/frontend/templates/course_admin/settings.html:179 -#: inginious/frontend/templates/course_admin/settings.html:238 +#: inginious/frontend/templates/course_admin/settings.html:174 +#: inginious/frontend/templates/course_admin/settings.html:233 msgid "To" msgstr "A" -#: inginious/frontend/templates/course_admin/settings.html:201 +#: inginious/frontend/templates/course_admin/settings.html:196 msgid "Registration" msgstr "Inscription" -#: inginious/frontend/templates/course_admin/settings.html:208 +#: inginious/frontend/templates/course_admin/settings.html:203 msgid "Closed" msgstr "Fermé" -#: inginious/frontend/templates/course_admin/settings.html:215 +#: inginious/frontend/templates/course_admin/settings.html:210 msgid "Always open" msgstr "Toujours ouvert" -#: inginious/frontend/templates/course_admin/settings.html:260 +#: inginious/frontend/templates/course_admin/settings.html:255 msgid "Allow auto-unregistration" msgstr "Permettre l'auto-désinscription" -#: inginious/frontend/templates/course_admin/settings.html:268 +#: inginious/frontend/templates/course_admin/settings.html:263 msgid "Yes" msgstr "Oui" -#: inginious/frontend/templates/course_admin/settings.html:275 +#: inginious/frontend/templates/course_admin/settings.html:270 msgid "No" msgstr "Non" -#: inginious/frontend/templates/course_admin/settings.html:280 +#: inginious/frontend/templates/course_admin/settings.html:275 msgid "Registration password" msgstr "Mot de passe d'inscription" -#: inginious/frontend/templates/course_admin/settings.html:283 +#: inginious/frontend/templates/course_admin/settings.html:278 msgid "" "Password needed for registration. Leave blank if you don't want to set a " "password." msgstr "" -"Mot de passe requis pour l'inscription. Laissez vide si vous ne souhaitez " -"pas configurer de mot de passe." +"Mot de passe requis pour l'inscription. Laissez vide si vous ne souhaitez" +" pas configurer de mot de passe." -#: inginious/frontend/templates/course_admin/settings.html:288 +#: inginious/frontend/templates/course_admin/settings.html:283 msgid "Access control" msgstr "Contrôle d'accès" -#: inginious/frontend/templates/course_admin/settings.html:295 +#: inginious/frontend/templates/course_admin/settings.html:290 msgid "No access control (everyone can register)" msgstr "Pas de contrôle d'accès (tout le monde peut s'inscrire)" -#: inginious/frontend/templates/course_admin/settings.html:302 +#: inginious/frontend/templates/course_admin/settings.html:297 msgid "Check by username" msgstr "Vérification par nom d'utilisateur" -#: inginious/frontend/templates/course_admin/settings.html:309 +#: inginious/frontend/templates/course_admin/settings.html:304 msgid "Check by authentication binding" msgstr "Vérification par méthode d'authentification" -#: inginious/frontend/templates/course_admin/settings.html:316 +#: inginious/frontend/templates/course_admin/settings.html:311 msgid "Check by email" msgstr "Vérification par email" -#: inginious/frontend/templates/course_admin/settings.html:321 +#: inginious/frontend/templates/course_admin/settings.html:316 msgid "Access control type" msgstr "Type de contrôle d'accès" -#: inginious/frontend/templates/course_admin/settings.html:328 +#: inginious/frontend/templates/course_admin/settings.html:323 msgid "Accept" msgstr "Accepter" -#: inginious/frontend/templates/course_admin/settings.html:335 +#: inginious/frontend/templates/course_admin/settings.html:330 msgid "Deny" msgstr "Refuser" -#: inginious/frontend/templates/course_admin/settings.html:340 +#: inginious/frontend/templates/course_admin/settings.html:335 msgid "Access control list" msgstr "Liste de contrôle d'accès" -#: inginious/frontend/templates/course_admin/settings.html:342 +#: inginious/frontend/templates/course_admin/settings.html:337 msgid "Only used if access control is activated. Separate users by new lines." msgstr "" "Utilisé uniquement si le contrôle d'accès est activé. Séparez les " "utilisateurs par de nouvelles lignes." -#: inginious/frontend/templates/course_admin/settings.html:348 -#: inginious/frontend/templates/course_admin/settings.html:350 +#: inginious/frontend/templates/course_admin/settings.html:343 +#: inginious/frontend/templates/course_admin/settings.html:345 msgid "External platform URL" msgstr "URL vers la plateforme externe" -#: inginious/frontend/templates/course_admin/settings.html:354 +#: inginious/frontend/templates/course_admin/settings.html:349 msgid "LTI keys" msgstr "Clés LTI" -#: inginious/frontend/templates/course_admin/settings.html:356 +#: inginious/frontend/templates/course_admin/settings.html:351 msgid "" -"LTI keys in the form name:key, separated by newlines. Please ensure keys are " -"long enough and random." +"LTI keys in the form name:key, separated by newlines. Please ensure keys " +"are long enough and random." msgstr "" -"Clés LTI sous la forme clé:secret, séparées par de nouvelles lignes. Assurez-" -"vous que les secrets soient assez long et aléatoires." +"Clés LTI sous la forme clé:secret, séparées par de nouvelles lignes. " +"Assurez-vous que les secrets soient assez long et aléatoires." -#: inginious/frontend/templates/course_admin/settings.html:361 +#: inginious/frontend/templates/course_admin/settings.html:356 msgid "Send back grades" msgstr "Envoyer les notes" -#: inginious/frontend/templates/course_admin/settings.html:368 +#: inginious/frontend/templates/course_admin/settings.html:363 msgid "" -"Enable this to send back grades to the calling Tool Consumer (which is, most " -"of the time, your LMS). INGInious will deactivate students' access to the " -"course and tasks from the web application when enabled. Tasks will be only " -"available from the LTI interface." +"Enable this to send back grades to the calling Tool Consumer (which is, " +"most of the time, your LMS). INGInious will deactivate students' access " +"to the course and tasks from the web application when enabled. Tasks will" +" be only available from the LTI interface." msgstr "" "Activez cette option pour envoyer les notes à la plateforme LMS hôte. " -"INGInious désactivera l'accès des étudiants au cours et aux tâches depuis " -"l'interface web classique. Les tâches seront seulement accessibles depuis " -"l'interface LTI." +"INGInious désactivera l'accès des étudiants au cours et aux tâches depuis" +" l'interface web classique. Les tâches seront seulement accessibles " +"depuis l'interface LTI." -#: inginious/frontend/templates/course_admin/settings.html:383 -#, fuzzy +#: inginious/frontend/templates/course_admin/settings.html:378 msgid "New tag" msgstr "Nouvelle étiquette" -#: inginious/frontend/templates/course_admin/settings.html:388 -#: inginious/frontend/templates/course_admin/settings.html:447 -#: inginious/frontend/templates/course_admin/submissions.html:66 +#: inginious/frontend/templates/course_admin/settings.html:383 +#: inginious/frontend/templates/course_admin/settings.html:442 +#: inginious/frontend/templates/course_admin/submissions.html:67 msgid "id" msgstr "identifiant" -#: inginious/frontend/templates/course_admin/settings.html:389 +#: inginious/frontend/templates/course_admin/settings.html:384 msgid "name" msgstr "nom" -#: inginious/frontend/templates/course_admin/settings.html:390 -#: inginious/frontend/templates/course_admin/settings.html:448 +#: inginious/frontend/templates/course_admin/settings.html:385 +#: inginious/frontend/templates/course_admin/settings.html:443 msgid "description" msgstr "description" -#: inginious/frontend/templates/course_admin/settings.html:391 +#: inginious/frontend/templates/course_admin/settings.html:386 msgid "show to students" msgstr "visible pour l'étudiant" -#: inginious/frontend/templates/course_admin/settings.html:392 -#: inginious/frontend/templates/course_admin/settings.html:449 +#: inginious/frontend/templates/course_admin/settings.html:387 +#: inginious/frontend/templates/course_admin/settings.html:444 msgid "type " msgstr "type " -#: inginious/frontend/templates/course_admin/settings.html:405 -#: inginious/frontend/templates/course_admin/settings.html:423 +#: inginious/frontend/templates/course_admin/settings.html:400 +#: inginious/frontend/templates/course_admin/settings.html:418 msgid "Skill" msgstr "Compétence" -#: inginious/frontend/templates/course_admin/settings.html:406 -#: inginious/frontend/templates/course_admin/settings.html:424 +#: inginious/frontend/templates/course_admin/settings.html:401 +#: inginious/frontend/templates/course_admin/settings.html:419 msgid "Misconception" msgstr "Idée fausse" -#: inginious/frontend/templates/course_admin/settings.html:412 +#: inginious/frontend/templates/course_admin/settings.html:407 msgid "Unknown name" msgstr "Nom inconnu" -#: inginious/frontend/templates/course_admin/settings.html:442 +#: inginious/frontend/templates/course_admin/settings.html:437 msgid "New field" msgstr "Nouveau champ" -#: inginious/frontend/templates/course_admin/stats.html:40 +#: inginious/frontend/templates/course_admin/stats.html:41 msgid "Student progress" msgstr "Progrès de l'étudiant" -#: inginious/frontend/templates/course_admin/stats.html:53 -#: inginious/frontend/templates/course_admin/submissions.html:60 +#: inginious/frontend/templates/course_admin/stats.html:54 +#: inginious/frontend/templates/course_admin/submissions.html:61 msgid "task name" msgstr "nom de l'exercice" -#: inginious/frontend/templates/course_admin/stats.html:54 +#: inginious/frontend/templates/course_admin/stats.html:55 msgid "# student viewed" msgstr "# étudiant ayant visionné" -#: inginious/frontend/templates/course_admin/stats.html:55 +#: inginious/frontend/templates/course_admin/stats.html:56 msgid "# student attempted" msgstr "# étudiants ayant essayé" -#: inginious/frontend/templates/course_admin/stats.html:56 +#: inginious/frontend/templates/course_admin/stats.html:57 msgid "# student succeeded" msgstr "# étudiants ayant réussi" -#: inginious/frontend/templates/course_admin/stats.html:57 +#: inginious/frontend/templates/course_admin/stats.html:58 msgid "# attempts" msgstr "# essais" -#: inginious/frontend/templates/course_admin/stats.html:64 -#: inginious/frontend/templates/course_admin/student_info.html:42 -#: inginious/frontend/templates/course_admin/student_info.html:82 -#: inginious/frontend/templates/course_admin/student_list.html:161 +#: inginious/frontend/templates/course_admin/stats.html:65 +#: inginious/frontend/templates/course_admin/student_info.html:43 +#: inginious/frontend/templates/course_admin/student_info.html:83 +#: inginious/frontend/templates/course_admin/student_list.html:162 msgid "View all submissions" msgstr "Voir toutes les soumissions" -#: inginious/frontend/templates/course_admin/stats.html:105 +#: inginious/frontend/templates/course_admin/stats.html:106 msgid "Global statistics" msgstr "Statistiques globaux" -#: inginious/frontend/templates/course_admin/stats.html:109 +#: inginious/frontend/templates/course_admin/stats.html:110 msgid "Submissions statistics" msgstr "Statistiques de soumission" -#: inginious/frontend/templates/course_admin/stats.html:127 +#: inginious/frontend/templates/course_admin/stats.html:128 msgid "Tag statistics" msgstr "Statistiques des étiquettes" -#: inginious/frontend/templates/course_admin/stats.html:132 +#: inginious/frontend/templates/course_admin/stats.html:133 msgid "Tag" msgstr "Marqueur" -#: inginious/frontend/templates/course_admin/stats.html:133 -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/submission_storage.html:9 +#: inginious/frontend/templates/course_admin/stats.html:134 +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_storage.html:7 +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_storage.html:16 msgid "All submissions" msgstr "Toutes les soumissions" -#: inginious/frontend/templates/course_admin/stats.html:134 +#: inginious/frontend/templates/course_admin/stats.html:135 msgid "Best submissions" msgstr "Meilleures soumissions" -#: inginious/frontend/templates/course_admin/stats.html:262 +#: inginious/frontend/templates/course_admin/stats.html:263 msgid "user" msgstr "Utilisateur" -#: inginious/frontend/templates/course_admin/stats.html:262 -#: inginious/frontend/templates/course_admin/student_info.html:36 +#: inginious/frontend/templates/course_admin/stats.html:263 +#: inginious/frontend/templates/course_admin/student_info.html:37 msgid "# submissions" msgstr "# soumissions" -#: inginious/frontend/templates/course_admin/stats.html:262 +#: inginious/frontend/templates/course_admin/stats.html:263 msgid "# valid submissions" msgstr "# soumissions valides" -#: inginious/frontend/templates/course_admin/student_info.html:20 -#: inginious/frontend/templates/course_admin/student_list.html:35 -#: inginious/frontend/templates/course_admin/student_list.html:396 -#: inginious/frontend/templates/course_admin/submission.html:26 +#: inginious/frontend/templates/course_admin/student_info.html:21 +#: inginious/frontend/templates/course_admin/student_list.html:36 +#: inginious/frontend/templates/course_admin/student_list.html:397 +#: inginious/frontend/templates/course_admin/submission.html:27 msgid "Students" msgstr "Étudiants" -#: inginious/frontend/templates/course_admin/student_info.html:29 +#: inginious/frontend/templates/course_admin/student_info.html:30 msgid "Statistics for student {realname} ({username})" msgstr "Statistiques pour l'étudiant {realname} ({username})" -#: inginious/frontend/templates/course_admin/student_info.html:34 +#: inginious/frontend/templates/course_admin/student_info.html:35 msgid "task" msgstr "exercice" -#: inginious/frontend/templates/course_admin/student_info.html:35 +#: inginious/frontend/templates/course_admin/student_info.html:36 msgid "status" msgstr "état" -#: inginious/frontend/templates/course_admin/student_info.html:62 +#: inginious/frontend/templates/course_admin/student_info.html:63 msgid "Not served by the task dispenser" msgstr "Non desservi par le distributeur de tâches" -#: inginious/frontend/templates/course_admin/student_info.html:68 +#: inginious/frontend/templates/course_admin/student_info.html:69 msgid "Not viewed" msgstr "Non consulté" -#: inginious/frontend/templates/course_admin/student_info.html:70 +#: inginious/frontend/templates/course_admin/student_info.html:71 msgid "Not attempted (viewed)" msgstr "Non essayé (consulté)" -#: inginious/frontend/templates/course_admin/student_info.html:72 -#: inginious/frontend/templates/course_admin/student_info.html:75 +#: inginious/frontend/templates/course_admin/student_info.html:73 +#: inginious/frontend/templates/course_admin/student_info.html:76 msgid "View evaluation submission" msgstr "Voir la soumission rendue pour évaluation" -#: inginious/frontend/templates/course_admin/student_info.html:85 -#, fuzzy +#: inginious/frontend/templates/course_admin/student_info.html:86 msgid "Reset state" msgstr "Remise à zéro de l'état" -#: inginious/frontend/templates/course_admin/student_info.html:98 +#: inginious/frontend/templates/course_admin/student_info.html:99 msgid "Reset State" msgstr "Remise à zéro de l'état" -#: inginious/frontend/templates/course_admin/student_info.html:102 +#: inginious/frontend/templates/course_admin/student_info.html:103 msgid "This will reset the state for {}. Are you sure ?" msgstr "Ceci va remettre à zéro l'état de soumission pour {}. Etes-vous sûr ?" -#: inginious/frontend/templates/course_admin/student_info.html:107 +#: inginious/frontend/templates/course_admin/student_info.html:108 #: inginious/frontend/templates/course_admin/submissions_query.html:251 msgid "Reset" msgstr "Reinitialiser" -#: inginious/frontend/templates/course_admin/student_list.html:39 +#: inginious/frontend/templates/course_admin/student_list.html:40 msgid "Teaching staff" msgstr "Équipe enseignante" -#: inginious/frontend/templates/course_admin/student_list.html:58 +#: inginious/frontend/templates/course_admin/student_list.html:59 msgid "Number of students" msgstr "Nombre d'étudiants" -#: inginious/frontend/templates/course_admin/student_list.html:81 +#: inginious/frontend/templates/course_admin/student_list.html:82 msgid "Upload audiences" msgstr "Uploader les audiences" -#: inginious/frontend/templates/course_admin/student_list.html:82 -#: inginious/frontend/templates/course_admin/student_list.html:212 +#: inginious/frontend/templates/course_admin/student_list.html:83 +#: inginious/frontend/templates/course_admin/student_list.html:213 msgid "Download structure" msgstr "Télécharger la structure" -#: inginious/frontend/templates/course_admin/student_list.html:104 -#, fuzzy +#: inginious/frontend/templates/course_admin/student_list.html:105 msgid "Upload audience creation" msgstr "Description du nouveau public" -#: inginious/frontend/templates/course_admin/student_list.html:108 -#: inginious/frontend/templates/course_admin/student_list.html:226 +#: inginious/frontend/templates/course_admin/student_list.html:109 +#: inginious/frontend/templates/course_admin/student_list.html:227 msgid "Note : Please refer to documentation for file format" -msgstr "" -"Note : veuillez vous référer à la documentation pour le format de fichier" +msgstr "Note : veuillez vous référer à la documentation pour le format de fichier" -#: inginious/frontend/templates/course_admin/student_list.html:115 -#, fuzzy +#: inginious/frontend/templates/course_admin/student_list.html:116 msgid "Upload (will erase current repartition)" -msgstr "Télécharger (cela écrasera les paramètres actuels)" +msgstr "Téléverser (cela écrasera les paramètres actuels)" -#: inginious/frontend/templates/course_admin/student_list.html:127 -#, fuzzy +#: inginious/frontend/templates/course_admin/student_list.html:128 msgid "Download audiences structure" msgstr "Télécharger la structure" -#: inginious/frontend/templates/course_admin/student_list.html:132 -#, fuzzy +#: inginious/frontend/templates/course_admin/student_list.html:133 msgid "Prefered field" -msgstr "\"Mettre à jour les champs additionnels\"" +msgstr "Champ préféré" -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:16 -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:24 -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:39 -#: inginious/frontend/templates/course_admin/student_list.html:141 -#: inginious/frontend/templates/course_admin/submissions.html:250 +#: inginious/frontend/templates/course_admin/student_list.html:142 +#: inginious/frontend/templates/course_admin/submissions.html:251 +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:16 +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:24 +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:39 msgid "Download" msgstr "Télécharger" -#: inginious/frontend/templates/course_admin/student_list.html:152 -#, fuzzy +#: inginious/frontend/templates/course_admin/student_list.html:153 msgid "audience" -msgstr "Public" +msgstr "public" -#: inginious/frontend/templates/course_admin/student_list.html:153 +#: inginious/frontend/templates/course_admin/student_list.html:154 msgid "# students" msgstr "# étudiants" -#: inginious/frontend/templates/course_admin/student_list.html:154 +#: inginious/frontend/templates/course_admin/student_list.html:155 #: inginious/frontend/templates/course_admin/student_list_table.html:18 msgid "# task tried" msgstr "# exercices essayés" -#: inginious/frontend/templates/course_admin/student_list.html:155 +#: inginious/frontend/templates/course_admin/student_list.html:156 #: inginious/frontend/templates/course_admin/student_list_table.html:19 msgid "# task done" msgstr "# exercices effectués" -#: inginious/frontend/templates/course_admin/student_list.html:167 +#: inginious/frontend/templates/course_admin/student_list.html:168 msgid "My audience(s)" msgstr "Mes publics" -#: inginious/frontend/templates/course_admin/student_list.html:167 +#: inginious/frontend/templates/course_admin/student_list.html:168 msgid "Other audience(s)" msgstr "Autres publics" -#: inginious/frontend/templates/course_admin/student_list.html:183 -#, fuzzy +#: inginious/frontend/templates/course_admin/student_list.html:184 msgid "Edit audience" msgstr "\"Editer le public\"" -#: inginious/frontend/templates/course_admin/student_list.html:200 +#: inginious/frontend/templates/course_admin/student_list.html:201 msgid "New audience description" msgstr "Description du nouveau public" -#: inginious/frontend/templates/course_admin/student_list.html:201 +#: inginious/frontend/templates/course_admin/student_list.html:202 msgid "New audience" msgstr "Nouveau public" -#: inginious/frontend/templates/course_admin/student_list.html:211 +#: inginious/frontend/templates/course_admin/student_list.html:212 msgid "Upload structure" msgstr "Télécharger la structure" -#: inginious/frontend/templates/course_admin/student_list.html:222 +#: inginious/frontend/templates/course_admin/student_list.html:223 msgid "Upload course structure" msgstr "Télécharger la structure du cours" -#: inginious/frontend/templates/course_admin/student_list.html:228 +#: inginious/frontend/templates/course_admin/student_list.html:229 msgid "Course structure" msgstr "Structure du cours" -#: inginious/frontend/templates/course_admin/student_list.html:233 +#: inginious/frontend/templates/course_admin/student_list.html:234 msgid "Upload (will erase current settings)" msgstr "Télécharger (cela écrasera les paramètres actuels)" -#: inginious/frontend/templates/course_admin/student_list.html:265 +#: inginious/frontend/templates/course_admin/student_list.html:266 msgid "Student username (will be registered) :" msgstr "Nom d'utilisateur de l'étudiant (sera incrit) :" -#: inginious/frontend/templates/course_admin/student_list.html:268 +#: inginious/frontend/templates/course_admin/student_list.html:269 msgid "Student username" msgstr "Nom d'utilisateur de l'étudiant" -#: inginious/frontend/templates/course_admin/student_list.html:286 +#: inginious/frontend/templates/course_admin/student_list.html:287 msgid "Ungrouped students" msgstr "Étudiants non groupés" -#: inginious/frontend/templates/course_admin/student_list.html:312 -#: inginious/frontend/templates/course_admin/student_list.html:333 -#, fuzzy +#: inginious/frontend/templates/course_admin/student_list.html:313 +#: inginious/frontend/templates/course_admin/student_list.html:334 msgid "Group description" -msgstr "description" +msgstr "Description du groupe" -#: inginious/frontend/templates/course_admin/student_list.html:312 -#: inginious/frontend/templates/course_admin/student_list.html:420 +#: inginious/frontend/templates/course_admin/student_list.html:313 +#: inginious/frontend/templates/course_admin/student_list.html:421 msgid "New group" msgstr "Nouveau groupe" -#: inginious/frontend/templates/course_admin/student_list.html:315 -#: inginious/frontend/templates/course_admin/student_list.html:336 +#: inginious/frontend/templates/course_admin/student_list.html:316 +#: inginious/frontend/templates/course_admin/student_list.html:337 msgid "Max group size :" msgstr "Taille max. du groupe :" -#: inginious/frontend/templates/course_admin/student_list.html:321 -#: inginious/frontend/templates/course_admin/student_list.html:342 -#, fuzzy +#: inginious/frontend/templates/course_admin/student_list.html:322 +#: inginious/frontend/templates/course_admin/student_list.html:343 msgid "Delete group" -msgstr "\"Supprimer le groupe\"" +msgstr "Supprimer le groupe" -#: inginious/frontend/templates/course_admin/student_list.html:353 +#: inginious/frontend/templates/course_admin/student_list.html:354 msgid "Restrict to audiences" msgstr "Restreindre l'accès aux publics" -#: inginious/frontend/templates/course_admin/student_list.html:365 +#: inginious/frontend/templates/course_admin/student_list.html:366 msgid "Add audience" msgstr "Ajouter un public" -#: inginious/frontend/templates/course_admin/student_list.html:425 +#: inginious/frontend/templates/course_admin/student_list.html:426 msgid "Clean groups" msgstr "Vider les groupes" -#: inginious/frontend/templates/course_admin/student_list.html:430 +#: inginious/frontend/templates/course_admin/student_list.html:431 msgid "Delete all groups" msgstr "Supprimer tous les groupes" @@ -2872,22 +3006,18 @@ msgid "current grade" msgstr "note actuelle" #: inginious/frontend/templates/course_admin/student_list_table.html:21 -#, fuzzy -msgid "" -"The current grade is computed over the tasks that are visible for users." +msgid "The current grade is computed over the tasks that are visible for users." msgstr "" -"\"La note actuelle est calculée sur base des exercices qui sont visibles " -"pour l'utilisateur.\"" +"La note actuelle est calculée sur base des exercices qui sont visibles " +"pour l'utilisateur." #: inginious/frontend/templates/course_admin/student_list_table.html:28 -#, fuzzy msgid "View all submissions" -msgstr "\"Voir toutes les soumissions \"" +msgstr "Voir toutes les soumissions" #: inginious/frontend/templates/course_admin/student_list_table.html:30 -#, fuzzy msgid "Unregister all" -msgstr "\"Désinscrire tous les étudiants\"" +msgstr "Désinscrire tous les étudiants" #: inginious/frontend/templates/course_admin/student_list_table.html:81 msgid "This will remove {} from the course. Are you sure ?" @@ -2897,11 +3027,11 @@ msgstr "Ceci désinscrira {} du cours. Êtes-vous sûr ?" msgid "Remove {}" msgstr "Désinscrire {}" -#: inginious/frontend/templates/course_admin/submission.html:43 +#: inginious/frontend/templates/course_admin/submission.html:44 msgid "Submission {}" msgstr "Soumission {}" -#: inginious/frontend/templates/course_admin/submission.html:45 +#: inginious/frontend/templates/course_admin/submission.html:46 msgid "" "This page show what was shown to the student when (s)he made his/her " "submission." @@ -2909,121 +3039,114 @@ msgstr "" "Cette page indique ce que l'étudiant a vu à la fin de l'évaluation de sa " "soumission." -#: inginious/frontend/templates/course_admin/submission.html:50 +#: inginious/frontend/templates/course_admin/submission.html:51 msgid "Click here to hide/display context informations" msgstr "Cliquez ici pour masquer/afficher l'énoncé général de l'exercice" -#: inginious/frontend/templates/course_admin/submission.html:56 +#: inginious/frontend/templates/course_admin/submission.html:57 msgid "Download full submission" msgstr "Télécharger la soumission complète" -#: inginious/frontend/templates/course_admin/submission.html:63 -#: inginious/frontend/templates/course_admin/submissions.html:183 +#: inginious/frontend/templates/course_admin/submission.html:64 +#: inginious/frontend/templates/course_admin/submissions.html:184 msgid "Replay submission" msgstr "Rejouer la soumission" -#: inginious/frontend/templates/course_admin/submission.html:69 +#: inginious/frontend/templates/course_admin/submission.html:70 msgid "Replay as {}" msgstr "Rejouer en tant que {}" -#: inginious/frontend/templates/course_admin/submission.html:75 +#: inginious/frontend/templates/course_admin/submission.html:76 msgid "Replay/debug as {}" msgstr "Rejouer/déboguer en tant que {}" -#: inginious/frontend/templates/course_admin/submission.html:135 +#: inginious/frontend/templates/course_admin/submission.html:136 msgid "This problem id is not defined in the task." msgstr "Cet identifiant de problème n'est pas défini pour l'exercice." -#: inginious/frontend/templates/course_admin/submission.html:137 +#: inginious/frontend/templates/course_admin/submission.html:138 msgid "This problem is not present in the submission." msgstr "Ce problème n'est pas présent dans la soumission." -#: inginious/frontend/templates/course_admin/submissions.html:43 -#, fuzzy +#: inginious/frontend/templates/course_admin/submissions.html:44 msgid "submissions selected" -msgstr "Stockage des soumissions" +msgstr "soumissions sélectionnées" -#: inginious/frontend/templates/course_admin/submissions.html:53 +#: inginious/frontend/templates/course_admin/submissions.html:54 msgid "student name" msgstr "nom de l'étudiant" -#: inginious/frontend/templates/course_admin/submissions.html:62 +#: inginious/frontend/templates/course_admin/submissions.html:63 msgid "taskid" msgstr "identifiant exercice" -#: inginious/frontend/templates/course_admin/submissions.html:68 +#: inginious/frontend/templates/course_admin/submissions.html:69 msgid "submitted on" msgstr "soumis le" -#: inginious/frontend/templates/course_admin/submissions.html:69 +#: inginious/frontend/templates/course_admin/submissions.html:70 msgid "replayed on" -msgstr "" +msgstr "rejoué le" -#: inginious/frontend/templates/course_admin/submissions.html:70 +#: inginious/frontend/templates/course_admin/submissions.html:71 msgid "result" msgstr "résultat" -#: inginious/frontend/templates/course_admin/submissions.html:72 +#: inginious/frontend/templates/course_admin/submissions.html:73 msgid "tags" msgstr "étiquettes" -#: inginious/frontend/templates/course_admin/submissions.html:80 +#: inginious/frontend/templates/course_admin/submissions.html:81 msgid "Download current selection" msgstr "Télécharger la sélection actuelle" -#: inginious/frontend/templates/course_admin/submissions.html:83 +#: inginious/frontend/templates/course_admin/submissions.html:84 msgid "Replay current selection" msgstr "Rejouer la sélection actuelle" -#: inginious/frontend/templates/course_admin/submissions.html:135 -#: inginious/frontend/templates/course_admin/submissions.html:267 +#: inginious/frontend/templates/course_admin/submissions.html:136 +#: inginious/frontend/templates/course_admin/submissions.html:268 msgid "Waiting" msgstr "En attente" -#: inginious/frontend/templates/course_admin/submissions.html:179 -#, fuzzy +#: inginious/frontend/templates/course_admin/submissions.html:180 msgid "View submission" -msgstr "Voir les soumissions" +msgstr "Voir la soumission" -#: inginious/frontend/templates/course_admin/submissions.html:180 -#, fuzzy +#: inginious/frontend/templates/course_admin/submissions.html:181 msgid "Download submission" -msgstr "\"Télécharger la soumission\"" +msgstr "Télécharger la soumission" -#: inginious/frontend/templates/course_admin/submissions.html:226 +#: inginious/frontend/templates/course_admin/submissions.html:227 msgid "Download format" msgstr "Format de téléchargement" -#: inginious/frontend/templates/course_admin/submissions.html:230 +#: inginious/frontend/templates/course_admin/submissions.html:231 msgid "Folder format" msgstr "Format de dossier" -#: inginious/frontend/templates/course_admin/submissions.html:233 -#, fuzzy +#: inginious/frontend/templates/course_admin/submissions.html:234 msgid "taskid/username" -msgstr "nom d'utilisateur" +msgstr "identifiant d'exercice/nom d'utilisateur" -#: inginious/frontend/templates/course_admin/submissions.html:233 -#, fuzzy +#: inginious/frontend/templates/course_admin/submissions.html:234 msgid "taskid/audience" -msgstr "Ajouter un public" +msgstr "identifiant d'exercice/public" -#: inginious/frontend/templates/course_admin/submissions.html:233 -#, fuzzy +#: inginious/frontend/templates/course_admin/submissions.html:234 msgid "username/taskid" -msgstr "nom d'utilisateur" +msgstr "nom d'utilisateur/identifiant d'exercice" -#: inginious/frontend/templates/course_admin/submissions.html:233 -#, fuzzy +#: inginious/frontend/templates/course_admin/submissions.html:234 msgid "audience/taskid" -msgstr "Publics" +msgstr "public/identifiant d'exercice" -#: inginious/frontend/templates/course_admin/submissions.html:240 +#: inginious/frontend/templates/course_admin/submissions.html:241 #: inginious/frontend/templates/course_admin/submissions_query.html:154 msgid "Only evaluation submissions" msgstr "Uniquement les soumissions pour évaluation" -#: inginious/frontend/templates/course_admin/submissions.html:244 +#: inginious/frontend/templates/course_admin/submissions.html:245 msgid "Simplified file tree" msgstr "Arborescence simplifiée" @@ -3068,9 +3191,8 @@ msgid "Select tasks" msgstr "Sélectionner des exercices" #: inginious/frontend/templates/course_admin/submissions_query.html:122 -#, fuzzy msgid "Select category" -msgstr "Sélectionner des étiquettes" +msgstr "Sélectionner la catégorie" #: inginious/frontend/templates/course_admin/submissions_query.html:149 msgid "Advanced query" @@ -3081,7 +3203,6 @@ msgid "Only timeouts and crashes" msgstr "Uniquement les délais dépassés et les erreurs" #: inginious/frontend/templates/course_admin/submissions_query.html:156 -#, fuzzy msgid "Show Tags" msgstr "Afficher les étiquettes" @@ -3169,660 +3290,747 @@ msgstr "Soumissions par page" msgid "Filter" msgstr "Filtrer" -#: inginious/frontend/templates/course_admin/task_edit.html:6 +#: inginious/frontend/templates/course_admin/task_list.html:30 +#: inginious/frontend/templates/course_admin/task_list.html:42 +#: inginious/frontend/templates/taskset_admin/template.html:30 +#: inginious/frontend/templates/taskset_admin/template.html:42 +msgid "Switch task dispenser" +msgstr "Changer de distributeur" + +#: inginious/frontend/templates/course_admin/task_list.html:48 +msgid "" +"This will wipe your current course structure. Tasks file won't be deleted" +" but you'll have to import them again." +msgstr "" +"Ceci va effacer l'actuelle structure de cours. Les fichiers des exercices" +" ne seront pas supprimés mais vous devrez les importer à nouveau." + +#: inginious/frontend/templates/course_admin/task_list.html:49 +#: inginious/frontend/templates/taskset_admin/template.html:49 +msgid "New task dispenser:" +msgstr "Nouveau distributeur d'exercice:" + +#: inginious/frontend/templates/course_admin/task_list.html:75 +#: inginious/frontend/templates/taskset_admin/template.html:75 +msgid "Changes saved." +msgstr "Modifications sauvegardées." + +#: inginious/frontend/templates/course_admin/task_list.html:85 +msgid "" +"This course currently includes legacy tasks with importable data to the " +"task dispenser settings. The taskset owner can clean the task files." +msgstr "Ce cours inclut des anciens exercices avec des données importables " +"vers les paramètres du distributeur d'exercices. Le propriétaire du " +"jeu d'exercices peut nettoyer les fichier d'exercices." + +#: inginious/frontend/templates/course_admin/task_list.html:87 +#: inginious/frontend/templates/taskset_admin/template.html:87 +msgid "Import settings" +msgstr "Importer les paramètres" + +#: inginious/frontend/templates/preferences/bindings.html:46 +msgid "Refresh fields" +msgstr "\"Mettre à jour les champs additionnels\"" + +#: inginious/frontend/templates/preferences/bindings.html:58 +msgid "Identifier" +msgstr "Identifiant" + +#: inginious/frontend/templates/preferences/bindings.html:60 +msgid "Additional fields" +msgstr "Champs additionnels" + +#: inginious/frontend/templates/preferences/bindings.html:74 +msgid "Add a new binding" +msgstr "Ajouter une nouvelle méthode" + +#: inginious/frontend/templates/preferences/bindings.html:78 +msgid "Select a authentication binding" +msgstr "Sélectionner une méthode d'authentification" + +#: inginious/frontend/templates/preferences/bindings.html:87 +msgid "Add new binding" +msgstr "Ajouter la méthode" + +#: inginious/frontend/templates/preferences/delete.html:41 +msgid "To confirm, type your email address :" +msgstr "Pour confirmer, tapez votre addresse mail:" + +#: inginious/frontend/templates/preferences/delete.html:56 +msgid "This will also delete all your data ! Are you really sure ?" +msgstr "" +"Ceci va supprimer toutes vos données. Etes vous vraiment certain ?" + +#: inginious/frontend/templates/preferences/profile.html:40 +msgid "Please set a new username" +msgstr "Veuillez choisir un nom d'utilisateur" + +#: inginious/frontend/templates/preferences/profile.html:54 +msgid "Username :" +msgstr "Nom d'utilisateur :" + +#: inginious/frontend/templates/preferences/profile.html:66 +msgid "Language :" +msgstr "Langue :" + +#: inginious/frontend/templates/preferences/profile.html:75 +msgid "Old password :" +msgstr "Mot de passe actuel :" + +#: inginious/frontend/templates/preferences/profile.html:79 +msgid "New password (min. 6 characters) :" +msgstr "Nouveau mot de passe (min. 6 caractères) :" + +#: inginious/frontend/templates/preferences/profile.html:83 +msgid "Confirm new password :" +msgstr "Confirmez le nouveau mot de passe :" + +#: inginious/frontend/templates/preferences/profile.html:94 +msgid "Save my profile" +msgstr "Sauvegarder mon profil" + +#: inginious/frontend/templates/task_dispensers/task_list.html:34 +msgid "deadline reached" +msgstr "date limite atteinte" + +#: inginious/frontend/templates/task_dispensers/task_list.html:39 +msgid "Weight : " +msgstr "Poids : " + +#: inginious/frontend/templates/task_dispensers_admin/combinatory_test.html:5 +msgid "Combinatory tests" +msgstr "Tests combinatoires" + +#: inginious/frontend/templates/task_dispensers_admin/combinatory_test.html:9 +msgid "" +"Only the specified amount of tasks to be displayed will be shown to the " +"user. There is no state: changing the amount of tasks per section, " +"section position or section title will affect the effective task set a " +"user will be displayed." +msgstr "" +"Seul le nombre spécifié d'exercices sera affiché à l'utilisateur. Ceci " +"est calculé sans état: modifier la quantité d'exercice par section, la " +"position de la section, ou le titre de la section affectera le jeu " +"d'exercices effectivement affiché à l'utilisateur." + +#: inginious/frontend/templates/task_dispensers_admin/combinatory_test.html:11 +msgid "" +"As an administrator, the only way to access all the tasks is through this" +" administration page." +msgstr "" +"En tant qu'administrateur, la seule manière d'accéder à tous les " +"exercices est via cette page d'administration." + +#: inginious/frontend/templates/task_dispensers_admin/combinatory_test.html:27 +#: inginious/frontend/templates/task_dispensers_admin/toc.html:17 +msgid "Add section" +msgstr "Ajouter une section" + +#: inginious/frontend/templates/task_dispensers_admin/combinatory_test.html:33 +#: inginious/frontend/templates/task_dispensers_admin/toc.html:23 +msgid "New section" +msgstr "Ajouter une section" + +#: inginious/frontend/templates/task_dispensers_admin/empty_section.html:26 +msgid "Drag tasks or section here." +msgstr "Glisser les exercices ou la section ici." + +#: inginious/frontend/templates/task_dispensers_admin/section_menu.html:10 +#: inginious/frontend/templates/task_dispensers_admin/util.html:80 +msgid "Add tasks" +msgstr "Ajouter des exercices" + +#: inginious/frontend/templates/task_dispensers_admin/section_menu.html:14 +msgid "Add subsection" +msgstr "Ajouter une sous-section" + +#: inginious/frontend/templates/task_dispensers_admin/section_menu.html:18 +msgid "Rename" +msgstr "Renommer" + +#: inginious/frontend/templates/task_dispensers_admin/task_buttons.html:6 +msgid "Task settings" +msgstr "Paramètres de l'exercice" + +#: inginious/frontend/templates/task_dispensers_admin/task_buttons.html:11 +msgid "View task" +msgstr "Voir l'exercice" + +#: inginious/frontend/templates/task_dispensers_admin/task_buttons.html:37 +#: inginious/frontend/templates/task_dispensers_admin/util_delete_modal.html:11 +#: inginious/frontend/templates/task_dispensers_admin/util_delete_modal.html:33 +#: inginious/frontend/templates/taskset_admin/settings.html:130 +#: inginious/frontend/templates/taskset_admin/settings.html:152 +msgid "Delete task" +msgstr "Supprimer l'exercice" + +#: inginious/frontend/templates/task_dispensers_admin/task_list.html:49 +msgid "No valid task with id:" +msgstr "Identifiant d'exercice invalide: {}" + +#: inginious/frontend/templates/task_dispensers_admin/task_list.html:54 +msgid "Delete invalid task" +msgstr "Supprimer l'exercice invalide" + +#: inginious/frontend/templates/task_dispensers_admin/util.html:51 +msgid "Edit selected tasks" +msgstr "Editer les exercices sélectionnés" + +#: inginious/frontend/templates/task_dispensers_admin/util.html:84 +msgid "Search..." +msgstr "Chercher..." + +#: inginious/frontend/templates/task_dispensers_admin/util.html:86 +msgid "No unassigned tasks in the taskset." +msgstr "Pas d'exercice non-assigné dans les fichiers du cours" + +#: inginious/frontend/templates/task_dispensers_admin/util.html:126 +msgid "Grouped actions" +msgstr "Actions groupées" + +#: inginious/frontend/templates/task_dispensers_admin/util.html:128 +msgid "Change selection" +msgstr "Modifier la sélection" + +#: inginious/frontend/templates/task_dispensers_admin/util.html:130 +msgid "Move to section..." +msgstr "Déplacer vers la section..." + +#: inginious/frontend/templates/task_dispensers_admin/util.html:133 +msgid "Compact view" +msgstr "Vue compacte" + +#: inginious/frontend/templates/task_dispensers_admin/util.html:137 +msgid "On setting: " +msgstr "Sur le paramètre: " + +#: inginious/frontend/templates/task_dispensers_admin/util_delete_modal.html:9 +#: inginious/frontend/templates/task_dispensers_admin/util_delete_modal.html:31 +msgid "Delete section" +msgstr "Supprimer la section" + +#: inginious/frontend/templates/task_dispensers_admin/util_delete_modal.html:13 +msgid "Delete selected tasks" +msgstr "Supprimer les exercices sélectionnés" + +#: inginious/frontend/templates/task_dispensers_admin/util_delete_modal.html:19 +msgid "Once saved, this will delete this section, all its subsections." +msgstr "" +"Une fois les modifications sauvegardées, cette section, ainsi que toutes " +"ses sous-sections, seront supprimées." + +#: inginious/frontend/templates/task_dispensers_admin/util_delete_modal.html:21 +msgid "Once saved, this will remove the task." +msgstr "" +"Une fois les modifications sauvegardées, cet exercice sera supprimé." + +#: inginious/frontend/templates/task_dispensers_admin/util_delete_modal.html:25 +msgid "Wipe all submissions" +msgstr "Effacer toutes les soumissions" + +#: inginious/frontend/templates/task_dispensers_admin/util_delete_modal.html:35 +msgid "Delete tasks" +msgstr "Supprimer les exercices" + +#: inginious/frontend/templates/task_dispensers_admin/util_move_modal.html:8 +msgid "Move selected tasks" +msgstr "Déplacer les exercices sélectionnés" + +#: inginious/frontend/templates/task_dispensers_admin/util_move_modal.html:12 +msgid "Please select the section to move the selected tasks into." +msgstr "Veuillez choisir la section dans laquelle déplacer les exercices sélectionnés." + +#: inginious/frontend/templates/task_dispensers_admin/util_move_modal.html:18 +msgid "Move tasks" +msgstr "Déplacer les exercices" + +#: inginious/frontend/templates/task_dispensers_admin/util_task_edit_modal.html:8 +msgid "Edit task {}" +msgstr "Éditer l'exercice \"{}\"" + +#: inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html:7 +#: inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html:21 +msgid "Never" +msgstr "Jamais" + +#: inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html:8 +#: inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html:27 +msgid "Always" +msgstr "Toujours" + +#: inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html:10 +#: inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html:34 +msgid "Custom, from: {} to: {}" +msgstr "Personnalisé, de: {} jusqu'à: {}" + +#: inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html:60 +msgid "Students can still submit after this date" +msgstr "Les étudiants peuvent toujours soumettre après cette date" + +#: inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html:60 +msgid "Soft Deadline" +msgstr "Date limite affichée" + +#: inginious/frontend/templates/task_dispensers_admin/config_items/categories.html:11 +msgid "Tag name, separated by commas" +msgstr "\"Identifiants d'étiquettes, séparés par des virgules\"" + +#: inginious/frontend/templates/task_dispensers_admin/config_items/evaluation_mode.html:6 +#: inginious/frontend/templates/task_dispensers_admin/config_items/evaluation_mode.html:10 +msgid "Evaluation submission" +msgstr "Soumission pour évaluation" + +#: inginious/frontend/templates/task_dispensers_admin/config_items/groups.html:7 +#: inginious/frontend/templates/task_dispensers_admin/config_items/groups.html:26 +msgid "Per group" +msgstr "Par groupe" + +#: inginious/frontend/templates/task_dispensers_admin/config_items/groups.html:8 +#: inginious/frontend/templates/task_dispensers_admin/config_items/groups.html:17 +msgid "Individually" +msgstr "Indivuellement" + +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_limit.html:6 +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_limit.html:17 +msgid "Submission limits" +msgstr "Limite de soumission" + +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_limit.html:10 +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_limit.html:26 +msgid "Hard limit: {nbr_submissions} submission(s)" +msgstr "Limite dure: {nbr_submissions} soumission(s)" + +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_limit.html:13 +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_limit.html:34 +msgid "Soft limit: {nbr_submissions} submission(s) every {nbr_hours} hour(s)" +msgstr "" +"Limite douce: {nbr_submissions} soumission(s) toutes les {nbr_hours} " +"heure(s)" + +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_storage.html:9 +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_storage.html:22 +msgid "Only the last {nbr_submissions} submissions" +msgstr "Seulement les {nbr_submissions} dernières soumissions" + +#: inginious/frontend/templates/task_dispensers_admin/config_items/weight.html:6 +#: inginious/frontend/templates/task_dispensers_admin/config_items/weight.html:8 +msgid "Grade weight (in comparison to other tasks)" +msgstr "Poids de la note (par rapport aux autres exercices)" + +#: inginious/frontend/templates/tasks/file.html:7 +msgid "Click here to download the file you submitted previously" +msgstr "Cliquez ici pour télécharger le fichier que vous aviez soumis" + +#: inginious/frontend/templates/tasks/file.html:22 +msgid "Max file size:" +msgstr "Taille fichier max. :" + +#: inginious/frontend/templates/tasks/file.html:23 +msgid "Allowed extensions:" +msgstr "Extensions autorisées :" + +#: inginious/frontend/templates/taskset_admin/danger_zone.html:15 +#: inginious/frontend/templates/taskset_admin/settings.html:15 +#: inginious/frontend/templates/taskset_admin/task_edit.html:16 +#: inginious/frontend/templates/taskset_admin/template.html:16 +msgid "Tasksets" +msgstr "Jeux d'exercices" + +#: inginious/frontend/templates/taskset_admin/danger_zone.html:40 +#: inginious/frontend/templates/taskset_admin/danger_zone.html:56 +#: inginious/frontend/templates/taskset_admin/danger_zone.html:64 +#: inginious/frontend/templates/taskset_admin/danger_zone.html:72 +msgid "Delete taskset" +msgstr "Supprimer le jeu d'exercices" + +#: inginious/frontend/templates/taskset_admin/danger_zone.html:44 +msgid "" +"This taskset is used by at least one course. Please delete the course(s) " +"before deleting the taskset." +msgstr "" +"Ce jeu d'exercices est utilisé par au moins un cours. Veuillez le(s) " +"supprimer avant de supprimer le jeu d'exercices." + +#: inginious/frontend/templates/taskset_admin/danger_zone.html:46 +msgid "" +"

This will permanently remove the taskset and all the tasks " +"files from INGInious.

To confirm your will, please type the taskset" +" id below :

" +msgstr "" +"

Ceci va supprimer définitement le jeu d'exercices et tous les " +"fichiers d'exercices d'INGInious.

Pour " +"confirmer, veuillez entrer l'identifiant du jeu d'exercices ci-dessous :

" + +#: inginious/frontend/templates/taskset_admin/danger_zone.html:68 +msgid "" +"

This will permanently remove the taskset and all the tasks " +"files from INGInious. Are you really sure ?

" +msgstr "" +"

Ceci va supprimer définitivement le jeu d'exercices et tous les fichiers " +"d'exercices d'INGInious. Êtes-vous vraiment certain ?

" + +#: inginious/frontend/templates/taskset_admin/menu.html:15 +msgid "How to create a task?" +msgstr "Comment créer un exercice ?" + +#: inginious/frontend/templates/taskset_admin/settings.html:19 +msgid "Taskset settings" +msgstr "Paramètres du jeu d'exercices" + +#: inginious/frontend/templates/taskset_admin/settings.html:26 +msgid "Edit taskset {}" +msgstr "Éditer le jeu d'exercices {}" + +#: inginious/frontend/templates/taskset_admin/settings.html:68 +msgid "Short description" +msgstr "Description courte" + +#: inginious/frontend/templates/taskset_admin/settings.html:74 +msgid "Instantiable by" +msgstr "Instanciable par" + +#: inginious/frontend/templates/taskset_admin/settings.html:77 +msgid "Taskset owners only" +msgstr "Propriétaires du jeu d'exercices uniquement" + +#: inginious/frontend/templates/taskset_admin/settings.html:80 +msgid "Everyone" +msgstr "Tout le monde" + +#: inginious/frontend/templates/taskset_admin/settings.html:93 +msgid "WebDAV access" +msgstr "Accès WebDAV" + +#: inginious/frontend/templates/taskset_admin/settings.html:98 +msgid "Use this URL to access your taskset folder using WebDAV:" +msgstr "Utilisez cette URL pour accéder au dossier du jeu d'exercices via WebDAV:" + +#: inginious/frontend/templates/taskset_admin/settings.html:101 +msgid "URL" +msgstr "URL" + +#: inginious/frontend/templates/taskset_admin/settings.html:113 +msgid "Task list" +msgstr "Liste des exercices" + +#: inginious/frontend/templates/taskset_admin/settings.html:114 +msgid "Add new task" +msgstr "Ajouter des exercices" + +#: inginious/frontend/templates/taskset_admin/settings.html:144 +msgid "Delete task {}" +msgstr "Supprimer l'exercice {}" + +#: inginious/frontend/templates/taskset_admin/settings.html:148 +msgid "" +"This will permanently remove the task {} and its files from " +"INGInious, making it unavailable from courses." +msgstr "" +"Ceci va définitivement supprimer l'exercice {} et ses fichiers" +" d'INGInious, le rendant indisponible depuis les cours." + +#: inginious/frontend/templates/taskset_admin/settings.html:165 +msgid "Create new task" +msgstr "Créer un nouvel exercice" + +#: inginious/frontend/templates/taskset_admin/settings.html:170 +#: inginious/frontend/templates/taskset_admin/settings.html:172 +msgid "New taskid" +msgstr "Nouvel identifiant d'exercice" + +#: inginious/frontend/templates/taskset_admin/settings.html:178 +msgid "Add task" +msgstr "Ajouter un exercice" + +#: inginious/frontend/templates/taskset_admin/task_edit.html:6 msgid "Edit {}" msgstr "Éditer {}" -#: inginious/frontend/templates/course_admin/task_edit.html:21 -#: inginious/frontend/templates/course_admin/task_edit.html:28 +#: inginious/frontend/templates/taskset_admin/task_edit.html:20 +#: inginious/frontend/templates/taskset_admin/task_edit.html:27 msgid "Edit task \"{}\"" msgstr "Éditer l'exercice \"{}\"" -#: inginious/frontend/templates/course_admin/task_dispensers/task_buttons.html:10 -#: inginious/frontend/templates/course_admin/task_edit.html:32 -msgid "View task" -msgstr "Voir l'exercice" - -#: inginious/frontend/templates/course_admin/task_edit.html:49 +#: inginious/frontend/templates/taskset_admin/task_edit.html:47 msgid "Environment" msgstr "Environnement" -#: inginious/frontend/templates/course_admin/task_edit.html:52 +#: inginious/frontend/templates/taskset_admin/task_edit.html:50 msgid "Subproblems" msgstr "Sous-problèmes" -#: inginious/frontend/templates/course_admin/task_edit.html:55 +#: inginious/frontend/templates/taskset_admin/task_edit.html:53 msgid "Task files" msgstr "Fichiers de l'exercice" -#: inginious/frontend/templates/course_admin/task_edit.html:79 +#: inginious/frontend/templates/taskset_admin/task_edit.html:77 msgid "File list" msgstr "Liste des fichiers" -#: inginious/frontend/templates/course_admin/task_edit.html:109 +#: inginious/frontend/templates/taskset_admin/task_edit.html:107 msgid "Problem id:" msgstr "Identifiant du problème :" -#: inginious/frontend/templates/course_admin/task_edit.html:126 -#, fuzzy +#: inginious/frontend/templates/taskset_admin/task_edit.html:124 msgid "A title for this question" -msgstr "Une section n'a pas de titre" +msgstr "Un titre pour la question" -#: inginious/frontend/templates/course_admin/task_edit.html:144 +#: inginious/frontend/templates/taskset_admin/task_edit.html:142 msgid "Are you sure that you want to delete this subproblem?" msgstr "Êtes-vous sûr de vouloir supprimer ce sous-problème ?" -#: inginious/frontend/templates/course_admin/task_list.html:29 -#: inginious/frontend/templates/course_admin/task_list.html:41 -msgid "Switch task dispenser" -msgstr "Changer de distributeur" - -#: inginious/frontend/templates/course_admin/task_list.html:47 +#: inginious/frontend/templates/taskset_admin/template.html:48 msgid "" -"This will wipe your current course structure. Tasks file won't be deleted " -"but you'll have to import them again." +"This will wipe your current taskset structure. Tasks file won't be " +"deleted but you'll have to import them again." msgstr "" -"Ceci va effacer l'actuelle structure de cours. Les fichiers des exercices ne " -"seront pas supprimés mais vous devrez les importer à nouveau." - -#: inginious/frontend/templates/course_admin/task_list.html:48 -msgid "New task dispenser:" -msgstr "Nouveau distributeur d'exercice:" - -#: inginious/frontend/templates/course_admin/task_list.html:69 -msgid "WebDAV access" -msgstr "Accès WebDAV" +"Ceci va effacer l'actuelle structure du jeu d'exercices. Les fichiers des exercices" +" ne seront pas supprimés mais vous devrez les importer à nouveau." -#: inginious/frontend/templates/course_admin/task_list.html:74 -msgid "Use this URL to access your course folder using WebDAV:" -msgstr "Utilisez cette URL pour accéder au dossier du cours via WebDAV:" - -#: inginious/frontend/templates/course_admin/task_list.html:77 -msgid "URL" -msgstr "URL" +#: inginious/frontend/templates/taskset_admin/template.html:85 +msgid "" +"This taskset currently includes legacy tasks with importable data to the " +"task dispenser settings. The taskset owner can clean the task files." +msgstr "" +"Ce jeu d'exercices contient des anciens exercices avec des données importables " +"vers les paramètres du distributeur d'exercices. Le propriétaire du jeu d'exercices" +" peut nettoyer les fichiers." -#: inginious/frontend/templates/course_admin/task_list.html:97 -#, fuzzy -msgid "Changes saved." -msgstr "Modifications sauvegardées." +#: inginious/frontend/templates/taskset_admin/template.html:88 +msgid "Clean task files" +msgstr "Nettoyer les fichiers d'exercices" -#: inginious/frontend/templates/course_admin/edit_tabs/basic.html:13 +#: inginious/frontend/templates/taskset_admin/edit_tabs/basic.html:13 msgid "Filetype" msgstr "Type de fichier" -#: inginious/frontend/templates/course_admin/edit_tabs/basic.html:30 -#: inginious/frontend/templates/course_admin/subproblems/code.html:5 -#: inginious/frontend/templates/course_admin/subproblems/file.html:5 -#: inginious/frontend/templates/course_admin/subproblems/match.html:5 -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:5 +#: inginious/frontend/templates/taskset_admin/edit_tabs/basic.html:30 +#: inginious/frontend/templates/taskset_admin/subproblems/code.html:5 +#: inginious/frontend/templates/taskset_admin/subproblems/file.html:5 +#: inginious/frontend/templates/taskset_admin/subproblems/match.html:5 +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:5 msgid "Context" msgstr "Énoncé" -#: inginious/frontend/templates/course_admin/edit_tabs/basic.html:36 +#: inginious/frontend/templates/taskset_admin/edit_tabs/basic.html:36 msgid "Author" msgstr "Auteur" -#: inginious/frontend/templates/course_admin/edit_tabs/basic.html:43 -#, fuzzy +#: inginious/frontend/templates/taskset_admin/edit_tabs/basic.html:43 msgid "Your name" -msgstr "nom d'utilisateur" +msgstr "Votre nom" -#: inginious/frontend/templates/course_admin/edit_tabs/basic.html:47 +#: inginious/frontend/templates/taskset_admin/edit_tabs/basic.html:47 msgid "Contact URL" msgstr "URL de contact" -#: inginious/frontend/templates/course_admin/edit_tabs/basic.html:53 +#: inginious/frontend/templates/taskset_admin/edit_tabs/basic.html:53 msgid "Random inputs" msgstr "Paramètres aléatoires" -#: inginious/frontend/templates/course_admin/edit_tabs/basic.html:60 +#: inginious/frontend/templates/taskset_admin/edit_tabs/basic.html:60 msgid "Regenerate random inputs for each reloading of the task page" msgstr "Regénère les entrées aléatoires à chaque chargement de la page" -#: inginious/frontend/templates/course_admin/edit_tabs/basic.html:60 +#: inginious/frontend/templates/taskset_admin/edit_tabs/basic.html:60 msgid "Regenerate input random" msgstr "Regénérer l'entrée aléatoire" -#: inginious/frontend/templates/course_admin/edit_tabs/env_generic_docker_oci.html:5 +#: inginious/frontend/templates/taskset_admin/edit_tabs/env_generic_docker_oci.html:5 msgid "Timeout limit (in seconds)" msgstr "Temps d'expiration (en secondes, temps CPU)" -#: inginious/frontend/templates/course_admin/edit_tabs/env_generic_docker_oci.html:11 +#: inginious/frontend/templates/taskset_admin/edit_tabs/env_generic_docker_oci.html:11 msgid "Hard timeout limit (in seconds) Default to 3*timeout" msgstr "" -"Temps d'éxpiration (en seconde, temps total) 3 fois le temps CPU par " -"défaut" +"Temps d'éxpiration (en seconde, temps total) 3 fois le temps CPU " +"par défaut" -#: inginious/frontend/templates/course_admin/edit_tabs/env_generic_docker_oci.html:18 +#: inginious/frontend/templates/taskset_admin/edit_tabs/env_generic_docker_oci.html:18 msgid "Memory limit (in megabytes)" msgstr "Limite de mémoire (en mégaoctets)" -#: inginious/frontend/templates/course_admin/edit_tabs/env_generic_docker_oci.html:24 +#: inginious/frontend/templates/taskset_admin/edit_tabs/env_generic_docker_oci.html:24 msgid "Allow internet access inside the grading container?" msgstr "Autoriser l'accès à Internet lors de l'exécution de la correction ?" -#: inginious/frontend/templates/course_admin/edit_tabs/env_generic_docker_oci.html:24 +#: inginious/frontend/templates/taskset_admin/edit_tabs/env_generic_docker_oci.html:24 msgid "It also adds and configures local interfaces with IPv4 and IPv6." msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/env_generic_docker_oci.html:34 -msgid "Allow ssh?" -msgstr "" - -#: inginious/frontend/templates/course_admin/edit_tabs/env_generic_docker_oci.html:45 +#: inginious/frontend/templates/taskset_admin/edit_tabs/env_generic_docker_oci.html:35 msgid "" "Custom command to be run in container (instead of running the run " "script)" msgstr "" -"Commande devant être lancée dans le conteneur (à la place du script " -"run)" +"Commande devant être lancée dans le conteneur (à la place du " +"script run)" -#: inginious/frontend/templates/course_admin/edit_tabs/env_generic_docker_oci.html:48 -#, fuzzy +#: inginious/frontend/templates/taskset_admin/edit_tabs/env_generic_docker_oci.html:38 msgid "Optional" msgstr "Optionnel ?" -#: inginious/frontend/templates/course_admin/edit_tabs/env_generic_docker_oci.html:53 +#: inginious/frontend/templates/taskset_admin/edit_tabs/env_generic_docker_oci.html:43 msgid "Are the task's responses written in HTML instead of restructuredText?" msgstr "Évaluation écrite en HTML ou restructuredText ?" -#: inginious/frontend/templates/course_admin/edit_tabs/environment.html:5 +#: inginious/frontend/templates/taskset_admin/edit_tabs/environment.html:5 msgid "Grading environment type" msgstr "Type d'environnement de correction" -#: inginious/frontend/templates/course_admin/edit_tabs/environment.html:20 +#: inginious/frontend/templates/taskset_admin/edit_tabs/environment.html:20 msgid "Grading environment" msgstr "Environnement de correction" -#: inginious/frontend/templates/course_admin/edit_tabs/file_modals.html:9 -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:60 +#: inginious/frontend/templates/taskset_admin/edit_tabs/file_modals.html:9 +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:60 msgid "Upload a file" msgstr "Télécharger un fichier" -#: inginious/frontend/templates/course_admin/edit_tabs/file_modals.html:16 +#: inginious/frontend/templates/taskset_admin/edit_tabs/file_modals.html:16 msgid "File:" msgstr "Fichier :" -#: inginious/frontend/templates/course_admin/edit_tabs/file_modals.html:20 +#: inginious/frontend/templates/taskset_admin/edit_tabs/file_modals.html:20 msgid "File path:" msgstr "Chemin du fichier :" -#: inginious/frontend/templates/course_admin/edit_tabs/file_modals.html:27 +#: inginious/frontend/templates/taskset_admin/edit_tabs/file_modals.html:27 msgid "Upload" msgstr "Télécharger" -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:15 +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:15 msgid "Path" msgstr "Chemin" -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:17 -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:44 +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:17 +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:44 msgid "Edit" msgstr "Éditer" -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:18 +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:18 msgid "Move" msgstr "Déplacer" -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:49 -#, fuzzy +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:49 msgid "Move/Rename" -msgstr "\"Déplacer/Renommer\"" +msgstr "Déplacer/Renommer" -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:59 +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:59 msgid "Create a new file" msgstr "Créer un nouveau fichier" -#: inginious/frontend/templates/course_admin/edit_tabs/subproblems.html:8 +#: inginious/frontend/templates/taskset_admin/edit_tabs/subproblems.html:8 msgid "New problem id" msgstr "Nouvel identifiant de problème" -#: inginious/frontend/templates/course_admin/edit_tabs/subproblems.html:13 +#: inginious/frontend/templates/taskset_admin/edit_tabs/subproblems.html:13 msgid "Problem type" msgstr "Type de problème" -#: inginious/frontend/templates/course_admin/subproblems/code.html:12 +#: inginious/frontend/templates/taskset_admin/subproblems/code.html:12 msgid "Language" msgstr "Langage" -#: inginious/frontend/templates/course_admin/subproblems/code.html:14 +#: inginious/frontend/templates/taskset_admin/subproblems/code.html:14 msgid "Language used in this question (c/cpp/java/python/oz/...)" msgstr "" -"Langage de programmation utilisé pour cette question (c/cpp/java/python/" -"oz/...)" +"Langage de programmation utilisé pour cette question " +"(c/cpp/java/python/oz/...)" -#: inginious/frontend/templates/course_admin/subproblems/code.html:23 +#: inginious/frontend/templates/taskset_admin/subproblems/code.html:23 msgid "Multiline code" msgstr "Code libre" -#: inginious/frontend/templates/course_admin/subproblems/code.html:25 +#: inginious/frontend/templates/taskset_admin/subproblems/code.html:25 msgid "Single-line code" msgstr "Ligne de code" -#: inginious/frontend/templates/course_admin/subproblems/code.html:31 +#: inginious/frontend/templates/taskset_admin/subproblems/code.html:31 msgid "Optional?" msgstr "Optionnel ?" -#: inginious/frontend/templates/course_admin/subproblems/code.html:41 +#: inginious/frontend/templates/taskset_admin/subproblems/code.html:41 msgid "Default value" msgstr "Valeur par défaut" -#: inginious/frontend/templates/course_admin/subproblems/file.html:11 -#, fuzzy +#: inginious/frontend/templates/taskset_admin/subproblems/file.html:11 msgid "Allowed file extensions (including the dot, separated by commas)" msgstr "" "Extensions de fichier autorisées (incluant le point, séparées par des " "espaces)" -#: inginious/frontend/templates/course_admin/subproblems/file.html:17 +#: inginious/frontend/templates/taskset_admin/subproblems/file.html:17 msgid "Maximum file size (in bytes)" msgstr "Taille de fichier max. (en octets)" -#: inginious/frontend/templates/course_admin/subproblems/file.html:26 +#: inginious/frontend/templates/taskset_admin/subproblems/file.html:26 msgid "File upload" msgstr "Téléchargement d'un fichier" -#: inginious/frontend/templates/course_admin/subproblems/match.html:11 +#: inginious/frontend/templates/taskset_admin/subproblems/match.html:11 msgid "Answer" msgstr "Réponse" -#: inginious/frontend/templates/course_admin/subproblems/match.html:13 -#, fuzzy +#: inginious/frontend/templates/taskset_admin/subproblems/match.html:13 msgid "Answer of this question" -msgstr "Veuillez répondre à toutes les questions." +msgstr "Réponse à cette question" -#: inginious/frontend/templates/course_admin/subproblems/match.html:17 -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:27 +#: inginious/frontend/templates/taskset_admin/subproblems/match.html:17 +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:27 msgid "Count error only at top of the page?" msgstr "Uniquement compter les erreurs de tout l'exercice ?" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:11 +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:11 msgid "Can select multiple answers" msgstr "Réponses multiples" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:19 +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:19 msgid "Unshuffle answers" -msgstr "" +msgstr "Ne pas mélanger les réponses" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:35 +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:35 msgid "Number of choices to display (0 = all choices)" msgstr "Nombre de choix à afficher (0 = tous)" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:41 +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:41 msgid "Success message" msgstr "Message lors de réussite" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:45 -#, fuzzy +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:45 msgid "An optional success message" -msgstr "Message lors de réussite" +msgstr "Message optionnel en cas de réussite" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:49 +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:49 msgid "Error message" msgstr "Message lors d'erreur" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:53 -#, fuzzy +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:53 msgid "An optional error message" -msgstr "Message lors d'erreur" +msgstr "Message optionnel en cas d'erreur" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:58 +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:58 msgid "Choices" msgstr "Choix" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:63 +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:63 msgid "Add new choice" msgstr "Ajouter un nouveau choix" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice_templates.html:8 +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice_templates.html:8 msgid "Content" msgstr "Réponse possible" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice_templates.html:15 +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice_templates.html:15 msgid "Feedback message (displayed when selected)" msgstr "Message d'évaluation affiché si sélectionné" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice_templates.html:19 +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice_templates.html:19 msgid "An optional feedback message" msgstr "Un message de retour optionnel" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice_templates.html:26 +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice_templates.html:26 msgid "Valid ?" msgstr "Correct ?" -#: inginious/frontend/templates/course_admin/task_dispensers/combinatory_test.html:7 -msgid "Combinatory tests" -msgstr "Tests combinatoires" - -#: inginious/frontend/templates/course_admin/task_dispensers/combinatory_test.html:11 -msgid "" -"Only the specified amount of tasks to be displayed will be shown to the " -"user. There is no state: changing the amount of tasks per section, section " -"position or section title will affect the effective task set a user will be " -"displayed." -msgstr "" -"Seul le nombre spécifié d'exercices sera affiché à l'utilisateur. Ceci est " -"calculé sans état: modifier la quantité d'exercice par section, la position " -"de la section, ou le titre de la section affectera le jeu d'exercices " -"effectivement affiché à l'utilisateur." - -#: inginious/frontend/templates/course_admin/task_dispensers/combinatory_test.html:13 -msgid "" -"As an administrator, the only way to access all the tasks is through this " -"administration page." -msgstr "" -"En tant qu'administrateur, la seule manière d'accéder à tous les exercices " -"est via cette page d'administration." - -#: inginious/frontend/templates/course_admin/task_dispensers/combinatory_test.html:27 -#: inginious/frontend/templates/course_admin/task_dispensers/toc.html:17 -msgid "Add section" -msgstr "Ajouter une section" - -#: inginious/frontend/templates/course_admin/task_dispensers/combinatory_test.html:33 -#: inginious/frontend/templates/course_admin/task_dispensers/toc.html:23 -#, fuzzy -msgid "New section" -msgstr "Ajouter une section" - -#: inginious/frontend/templates/course_admin/task_dispensers/empty_section.html:25 -msgid "Drag tasks or section here." -msgstr "Glisser les exercices ou la section ici." - -#: inginious/frontend/templates/course_admin/task_dispensers/section_menu.html:10 -#: inginious/frontend/templates/course_admin/task_dispensers/util.html:78 -msgid "Add tasks" -msgstr "Ajouter des exercices" - -#: inginious/frontend/templates/course_admin/task_dispensers/section_menu.html:14 -msgid "Add subsection" -msgstr "Ajouter une sous-section" - -#: inginious/frontend/templates/course_admin/task_dispensers/section_menu.html:18 -msgid "Rename" -msgstr "Renommer" - -#: inginious/frontend/templates/course_admin/task_dispensers/task_buttons.html:6 -#, fuzzy -msgid "Task parameters" -msgstr "nom de l'exercice" - -#: inginious/frontend/templates/course_admin/task_dispensers/task_buttons.html:23 -#: inginious/frontend/templates/course_admin/task_dispensers/util_delete_modal.html:11 -#: inginious/frontend/templates/course_admin/task_dispensers/util_delete_modal.html:33 -msgid "Delete task" -msgstr "Supprimer l'exercice" - -#: inginious/frontend/templates/course_admin/task_dispensers/task_list.html:35 -#, fuzzy -msgid "No valid task with id:" -msgstr "Identifiant d'étiquette invalide: {}" - -#: inginious/frontend/templates/course_admin/task_dispensers/task_list.html:39 -#: inginious/frontend/templates/course_admin/task_dispensers/util.html:123 -#, fuzzy -msgid "Delete invalid task" -msgstr "\"Supprimer l'exercice invalide \"" - -#: inginious/frontend/templates/course_admin/task_dispensers/util.html:82 -#: inginious/frontend/templates/course_admin/task_dispensers/util.html:85 -msgid "Create new task" -msgstr "Créer un nouvel exercice" - -#: inginious/frontend/templates/course_admin/task_dispensers/util.html:84 -#, fuzzy -msgid "New task id" -msgstr "\"Nouvel identifiant d'exercice\"" - -#: inginious/frontend/templates/course_admin/task_dispensers/util.html:87 -msgid "Import from course filesystem" -msgstr "Importer des fichiers du cours" - -#: inginious/frontend/templates/course_admin/task_dispensers/util.html:88 -msgid "Search..." -msgstr "Chercher..." - -#: inginious/frontend/templates/course_admin/task_dispensers/util.html:90 -msgid "No unassigned tasks in the filesystem of this course" -msgstr "Pas d'exercice non-assigné dans les fichiers du cours" - -#: inginious/frontend/templates/course_admin/task_dispensers/util.html:120 -#, fuzzy -msgid "New task with id: " -msgstr "\"Nouvel identifiant d'exercice\"" - -#: inginious/frontend/templates/course_admin/task_dispensers/util_delete_modal.html:9 -#: inginious/frontend/templates/course_admin/task_dispensers/util_delete_modal.html:30 -msgid "Delete section" -msgstr "Supprimer la section" - -#: inginious/frontend/templates/course_admin/task_dispensers/util_delete_modal.html:17 -msgid "" -"Once saved, this will permanently delete this section, all its " -"subsections and remove all the tasks and their files from INGInious." -msgstr "" -"Une fois les modifications sauvegardées, cette section, ainsi que toutes ses " -"sous-sections et exercices, seront définitivement supprimée " -"d'INGInious." - -#: inginious/frontend/templates/course_admin/task_dispensers/util_delete_modal.html:19 -msgid "" -"Once saved, this will permanently remove the task and its files from " -"INGInious." -msgstr "" -"Une fois les modifications sauvegardées, cet exercice, ainsi que les " -"fichiers associés, seront définitivement supprimé d'INGInious" - -#: inginious/frontend/templates/course_admin/task_dispensers/util_delete_modal.html:23 -msgid "Wipe all submissions" -msgstr "Effacer toutes les soumissions" - -#: inginious/frontend/templates/course_admin/task_dispensers/util_delete_modal.html:29 -#: inginious/frontend/templates/course_admin/task_dispensers/util_delete_modal.html:32 -msgid "Keep files" -msgstr "Conserver les fichiers" - -#: inginious/frontend/templates/course_admin/task_dispensers/util_task_edit_modal.html:8 -#, fuzzy -msgid "Edit task {}" -msgstr "Éditer l'exercice \"{}\"" - -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/accessibility.html:11 -msgid "Never" -msgstr "Jamais" - -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/accessibility.html:18 -msgid "Always" -msgstr "Toujours" - -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/accessibility.html:26 -msgid "Custom, from: {} to: {}" -msgstr "Personnalisé, de: {} jusqu'à: {}" - -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/accessibility.html:52 -msgid "Students can still submit after this date" -msgstr "Les étudiants peuvent toujours soumettre après cette date" - -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/accessibility.html:52 -msgid "Soft Deadline" -msgstr "Date limite affichée" - -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/categories.html:7 -#, fuzzy -msgid "Tag name, separated by commas" -msgstr "\"Identifiants d'étiquettes, séparés par des virgules\"" - -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/evaluation_mode.html:4 -msgid "Evaluation submission" -msgstr "Soumission pour évaluation" - -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/groups.html:12 -msgid "Individually" -msgstr "Indivuellement" - -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/groups.html:21 -msgid "Per group" -msgstr "Par groupe" - -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/submission_limit.html:3 -msgid "Submission limits" -msgstr "Limite de soumission" - -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/submission_limit.html:14 -msgid "Hard limit: {nbr_submissions} submission(s)" -msgstr "Limite dure: {nbr_submissions} soumission(s)" - -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/submission_limit.html:23 -msgid "Soft limit: {nbr_submissions} submission(s) every {nbr_hours} hour(s)" -msgstr "" -"Limite douce: {nbr_submissions} soumission(s) toutes les {nbr_hours} heure(s)" - -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/submission_storage.html:15 -msgid "Only the last {nbr_submissions} submissions" -msgstr "Seulement les {nbr_submissions} dernières soumissions" - -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/weight.html:4 -msgid "Grade weight (in comparison to other tasks)" -msgstr "Poids de la note (par rapport aux autres exercices)" - -#: inginious/frontend/templates/preferences/bindings.html:46 -#, fuzzy -msgid "Refresh fields" -msgstr "\"Mettre à jour les champs additionnels\"" - -#: inginious/frontend/templates/preferences/bindings.html:58 -msgid "Identifier" -msgstr "Identifiant" - -#: inginious/frontend/templates/preferences/bindings.html:60 -msgid "Additional fields" -msgstr "Champs additionnels" - -#: inginious/frontend/templates/preferences/bindings.html:74 -msgid "Add a new binding" -msgstr "Ajouter une nouvelle méthode" - -#: inginious/frontend/templates/preferences/bindings.html:78 -msgid "Select a authentication binding" -msgstr "Sélectionner une méthode d'authentification" - -#: inginious/frontend/templates/preferences/bindings.html:87 -msgid "Add new binding" -msgstr "Ajouter la méthode" - -#: inginious/frontend/templates/preferences/delete.html:41 -msgid "To confirm, type your email address :" -msgstr "Pour confirmer, tapez votre addresse mail:" - -#: inginious/frontend/templates/preferences/delete.html:56 -#, fuzzy -msgid "This will also delete all your data ! Are you really sure ?" -msgstr "" -"

Ceci va restaurer les données du cours à la date du {}. Etes vous sûr ?" - -#: inginious/frontend/templates/preferences/profile.html:40 -msgid "Please set a new username" -msgstr "Veuillez choisir un nom d'utilisateur" - -#: inginious/frontend/templates/preferences/profile.html:54 -msgid "Username :" -msgstr "Nom d'utilisateur :" - -#: inginious/frontend/templates/preferences/profile.html:66 -msgid "Language :" -msgstr "Langue :" - -#: inginious/frontend/templates/preferences/profile.html:75 -msgid "Old password :" -msgstr "Mot de passe actuel :" - -#: inginious/frontend/templates/preferences/profile.html:79 -msgid "New password (min. 6 characters) :" -msgstr "Nouveau mot de passe (min. 6 caractères) :" - -#: inginious/frontend/templates/preferences/profile.html:83 -msgid "Confirm new password :" -msgstr "Confirmez le nouveau mot de passe :" - -#: inginious/frontend/templates/preferences/profile.html:94 -msgid "Save my profile" -msgstr "Sauvegarder mon profil" - -#: inginious/frontend/templates/task_dispensers/task_list.html:34 -msgid "deadline reached" -msgstr "date limite atteinte" - -#: inginious/frontend/templates/task_dispensers/task_list.html:39 -msgid "Weight : " -msgstr "" - -#: inginious/frontend/templates/tasks/file.html:7 -msgid "Click here to download the file you submitted previously" -msgstr "Cliquez ici pour télécharger le fichier que vous aviez soumis" - -#: inginious/frontend/templates/tasks/file.html:22 -msgid "Max file size:" -msgstr "Taille fichier max. :" - -#: inginious/frontend/templates/tasks/file.html:23 -msgid "Allowed extensions:" -msgstr "Extensions autorisées :" - -#~ msgid "Docker container" -#~ msgstr "Conteneur Docker" - -#~ msgid "Cannot have a soft deadline after the hard one" -#~ msgstr "La date limite affichée doit précéder la date limite effective." - -#~ msgid "You are now activated. You can proceed to login." -#~ msgstr "Votre compte est maintenant activé. Vous pouvez vous connecter." - -#~ msgid "Grade weight must be a floating-point number" -#~ msgstr "Le poids de la note doit être un nombre réel" - -#~ msgid "Invalid submission limit!" -#~ msgstr "Limite de soumission invalide !" - -#~ msgid "Invalid task accessibility ({})" -#~ msgstr "Accessibilité invalide ({})" - -#~ msgid "INGInious | {course} - {task}" -#~ msgstr "INGInious | {course} - {task}" - -#~ msgid "" -#~ "Check out INGInious course {course} and beat my score of {score}%% on " -#~ "task {task} !" -#~ msgstr "" -#~ "Découvrez le cours {course} sur INGInious et battez mon score de {score}%" -#~ "% sur l'exercice {task} !" - -#~ msgid "No rank for one section" -#~ msgstr "Une section n'a pas de rang" - -#~ msgid "No id for one section" -#~ msgstr "Une section n'a pas d'identifiant" - -#~ msgid "Share my result" -#~ msgstr "Partager mon résultat" - -#~ msgid "Share my result on: " -#~ msgstr "Partager mon résultat sur: " - -#~ msgid "Registered student" -#~ msgstr "Etudiant enregistré" - -#~ msgid "New student" -#~ msgstr "Nouvel étudiant" - -#~ msgid "Category" -#~ msgstr "Catégorie" - -#~ msgid "" -#~ "This will permanently remove the task and its files from INGInious." -#~ msgstr "" -#~ "Ceci supprimera définitivement l'exercice et ses fichier " -#~ "d'INGInious." -#~ msgid "Accessible" -#~ msgstr "Accessible" diff --git a/inginious/frontend/i18n/messages.pot b/inginious/frontend/i18n/messages.pot index 55b61c40e5..e030e948e8 100644 --- a/inginious/frontend/i18n/messages.pot +++ b/inginious/frontend/i18n/messages.pot @@ -8,18 +8,14 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2023-04-04 15:06+0200\n" +"POT-Creation-Date: 2023-08-22 13:53+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.11.0\n" - -#: inginious/frontend/courses.py:30 -msgid "List of exercises" -msgstr "" +"Generated-By: Babel 2.12.1\n" #: inginious/frontend/parsable_text.py:37 msgid "[no content]" @@ -39,7 +35,13 @@ msgstr "" msgid "Parsing failed:

{}
" msgstr "" -#: inginious/frontend/submission_manager.py:420 +#: inginious/frontend/submission_manager.py:91 +msgid "" +"Maximum submission size exceeded. Check feedback, stdout, stderr and " +"state." +msgstr "" + +#: inginious/frontend/submission_manager.py:432 msgid "Feedback is badly formatted." msgstr "" @@ -63,29 +65,41 @@ msgstr "" msgid "match" msgstr "" -#: inginious/frontend/tasks.py:86 +#: inginious/frontend/tasks.py:85 msgid "Environment type {0} is unknown" msgstr "" -#: inginious/frontend/user_manager.py:436 +#: inginious/frontend/tasksets.py:24 +msgid "List of exercises" +msgstr "" + +#: inginious/frontend/user_manager.py:446 msgid "Auth method not found." msgstr "" #: inginious/frontend/pages/preferences/bindings.py:42 -#: inginious/frontend/user_manager.py:487 +#: inginious/frontend/user_manager.py:497 msgid "Incorrect authentication binding." msgstr "" -#: inginious/frontend/user_manager.py:497 +#: inginious/frontend/user_manager.py:507 msgid "You must set a password before removing all bindings." msgstr "" -#: inginious/frontend/user_manager.py:528 +#: inginious/frontend/user_manager.py:538 msgid "User could not be created." msgstr "" #: inginious/frontend/environment_types/docker.py:12 -msgid "Standard container (Docker)" +msgid "Standard container + SSH" +msgstr "" + +#: inginious/frontend/environment_types/docker.py:12 +msgid "Standard container" +msgstr "" + +#: inginious/frontend/environment_types/kata.py:11 +msgid "Container running as root (Kata) + SSH" msgstr "" #: inginious/frontend/environment_types/kata.py:11 @@ -96,6 +110,10 @@ msgstr "" msgid "Multiple Choice Question solver" msgstr "" +#: inginious/frontend/environment_types/nvidia.py:11 +msgid "Container with GPUs (NVIDIA) + SSH" +msgstr "" + #: inginious/frontend/environment_types/nvidia.py:11 msgid "Container with GPUs (NVIDIA)" msgstr "" @@ -104,7 +122,7 @@ msgstr "" msgid "Course not found." msgstr "" -#: inginious/frontend/pages/course_register.py:23 +#: inginious/frontend/pages/course_register.py:24 msgid "This course doesn't exist." msgstr "" @@ -161,12 +179,12 @@ msgid "Couldn't validate LTI request" msgstr "" #: inginious/frontend/pages/marketplace.py:32 -msgid "You don't have superadmin rights on this course." +msgid "You don't have superadmin rights on this taskset." msgstr "" #: inginious/frontend/pages/marketplace.py:39 -#: inginious/frontend/pages/marketplace_course.py:33 -#: inginious/frontend/pages/marketplace_course.py:42 +#: inginious/frontend/pages/marketplace_taskset.py:33 +#: inginious/frontend/pages/marketplace_taskset.py:42 msgid "You're not allowed to do that" msgstr "" @@ -176,15 +194,15 @@ msgid "User returned an invalid form." msgstr "" #: inginious/frontend/pages/marketplace.py:75 -msgid "Couldn't clone course into your instance" +msgid "Couldn't clone taskset into your instance" msgstr "" #: inginious/frontend/pages/marketplace.py:96 -msgid "An error occur while editing the course description" +msgid "An error occur while editing the taskset description" msgstr "" -#: inginious/frontend/pages/marketplace_course.py:25 -msgid "Course unavailable." +#: inginious/frontend/pages/marketplace_taskset.py:25 +msgid "Taskset unavailable." msgstr "" #: inginious/frontend/pages/register.py:30 @@ -296,132 +314,156 @@ msgstr "" msgid "Auth method doesn't exist" msgstr "" -#: inginious/frontend/pages/social.py:39 +#: inginious/frontend/pages/social.py:38 msgid "Auth method doesn't exist." msgstr "" -#: inginious/frontend/pages/tasks.py:93 inginious/frontend/pages/tasks.py:264 +#: inginious/frontend/pages/tasks.py:93 inginious/frontend/pages/tasks.py:263 msgid "Submission doesn't exist." msgstr "" -#: inginious/frontend/pages/tasks.py:176 inginious/frontend/pages/tasks.py:184 -#: inginious/frontend/pages/tasks.py:202 inginious/frontend/pages/tasks.py:224 -#: inginious/frontend/pages/tasks.py:231 inginious/frontend/pages/tasks.py:248 +#: inginious/frontend/pages/tasks.py:175 inginious/frontend/pages/tasks.py:183 +#: inginious/frontend/pages/tasks.py:201 inginious/frontend/pages/tasks.py:223 +#: inginious/frontend/pages/tasks.py:230 inginious/frontend/pages/tasks.py:247 msgid "Error" msgstr "" -#: inginious/frontend/pages/tasks.py:176 +#: inginious/frontend/pages/tasks.py:175 msgid "You are not allowed to submit for this task." msgstr "" -#: inginious/frontend/pages/tasks.py:184 +#: inginious/frontend/pages/tasks.py:183 msgid "Your task has been regenerated. This current task is outdated." msgstr "" -#: inginious/frontend/pages/tasks.py:203 +#: inginious/frontend/pages/tasks.py:202 msgid "" "Please answer to all the questions and verify the extensions of the files" " you want to upload. Your responses were not tested." msgstr "" -#: inginious/frontend/pages/tasks.py:220 inginious/frontend/pages/tasks.py:329 +#: inginious/frontend/pages/tasks.py:219 inginious/frontend/pages/tasks.py:328 msgid "Your submission has been sent..." msgstr "" -#: inginious/frontend/pages/course_admin/task_edit_file.py:209 -#: inginious/frontend/pages/course_admin/task_edit_file.py:230 -#: inginious/frontend/pages/course_admin/task_edit_file.py:234 -#: inginious/frontend/pages/tasks.py:231 inginious/frontend/pages/tasks.py:248 +#: inginious/frontend/pages/tasks.py:230 inginious/frontend/pages/tasks.py:247 +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:205 +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:226 +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:230 #: inginious/frontend/templates/internalerror.html:5 #: inginious/frontend/templates/task.html:325 msgid "Internal error" msgstr "" -#: inginious/frontend/pages/tasks.py:298 +#: inginious/frontend/pages/tasks.py:297 msgid "INGInious is currently grading your answers. (almost done)" msgstr "" -#: inginious/frontend/pages/tasks.py:300 +#: inginious/frontend/pages/tasks.py:299 msgid "" "INGInious is currently grading your answers. (Approx. wait time: " "{} seconds)" msgstr "" -#: inginious/frontend/pages/tasks.py:303 +#: inginious/frontend/pages/tasks.py:302 msgid "You are next in the waiting queue!" msgstr "" -#: inginious/frontend/pages/tasks.py:305 +#: inginious/frontend/pages/tasks.py:304 msgid "There is one task in front of you in the waiting queue." msgstr "" -#: inginious/frontend/pages/tasks.py:307 +#: inginious/frontend/pages/tasks.py:306 msgid "There are {} tasks in front of you in the waiting queue." msgstr "" -#: inginious/frontend/pages/tasks.py:331 +#: inginious/frontend/pages/tasks.py:330 msgid "There are some errors in your answer. Your score is {score}%." msgstr "" -#: inginious/frontend/pages/tasks.py:333 +#: inginious/frontend/pages/tasks.py:332 msgid "Your answer passed the tests! Your score is {score}%." msgstr "" -#: inginious/frontend/pages/tasks.py:335 +#: inginious/frontend/pages/tasks.py:334 msgid "Your submission timed out. Your score is {score}%." msgstr "" -#: inginious/frontend/pages/tasks.py:337 +#: inginious/frontend/pages/tasks.py:336 msgid "Your submission made an overflow. Your score is {score}%." msgstr "" -#: inginious/frontend/pages/tasks.py:339 +#: inginious/frontend/pages/tasks.py:338 msgid "Your submission was killed." msgstr "" -#: inginious/frontend/pages/tasks.py:341 +#: inginious/frontend/pages/tasks.py:340 msgid "" "An internal error occurred. Please retry later. If the error persists, " "send an email to the course administrator." msgstr "" -#: inginious/frontend/pages/tasks.py:344 +#: inginious/frontend/pages/tasks.py:343 msgid "[Submission #{submissionid} ({submissionDate})]" msgstr "" -#: inginious/frontend/pages/tasks.py:379 inginious/frontend/pages/tasks.py:381 +#: inginious/frontend/pages/tasks.py:378 inginious/frontend/pages/tasks.py:380 msgid " " msgstr "" -#: inginious/frontend/pages/utils.py:192 +#: inginious/frontend/pages/tasksets.py:43 +msgid "Course with id {} successfully instantiated from taskset {}" +msgstr "" + +#: inginious/frontend/pages/tasksets.py:46 +msgid "You are not allowed to instantiate a course from this taskset." +msgstr "" + +#: inginious/frontend/pages/tasksets.py:49 +msgid "A course with id {} already exists." +msgstr "" + +#: inginious/frontend/pages/tasksets.py:52 +msgid "Couldn't instantiate course with id {} : " +msgstr "" + +#: inginious/frontend/pages/tasksets.py:59 +msgid "Taskset created." +msgstr "" + +#: inginious/frontend/pages/tasksets.py:62 +msgid "Failed to create the taskset." +msgstr "" + +#: inginious/frontend/pages/utils.py:197 msgid "" "An account using this email already exists and is not bound with this " "service. For security reasons, please log in via another method and bind " "your account in your profile." msgstr "" -#: inginious/frontend/pages/utils.py:195 +#: inginious/frontend/pages/utils.py:200 msgid "" "Couldn't fetch the required information from the service. Please check " "the provided permissions (name, email) and contact your INGInious " "administrator if the error persists." msgstr "" -#: inginious/frontend/pages/utils.py:221 +#: inginious/frontend/pages/utils.py:226 msgid "Invalid login/password" msgstr "" -#: inginious/frontend/pages/utils.py:249 +#: inginious/frontend/pages/utils.py:254 #: inginious/frontend/templates/forbidden.html:5 #: inginious/frontend/templates/forbidden.html:9 msgid "Forbidden" msgstr "" -#: inginious/frontend/pages/utils.py:263 +#: inginious/frontend/pages/utils.py:268 msgid "You have not sufficient right to see this part." msgstr "" -#: inginious/frontend/pages/utils.py:312 +#: inginious/frontend/pages/utils.py:317 msgid "File doesn't exist." msgstr "" @@ -460,6 +502,7 @@ msgid "This file doesn't exist." msgstr "" #: inginious/frontend/pages/course_admin/danger_zone.py:164 +#: inginious/frontend/pages/taskset_admin/danger_zone.py:50 msgid "Operation aborted due to invalid token." msgstr "" @@ -489,6 +532,7 @@ msgid "An error occurred while deleting the course data: {}" msgstr "" #: inginious/frontend/pages/course_admin/settings.py:34 +#: inginious/frontend/pages/taskset_admin/settings.py:48 msgid "Invalid name" msgstr "" @@ -496,39 +540,39 @@ msgstr "" msgid "You cannot remove yourself from the administrators of this course" msgstr "" -#: inginious/frontend/pages/course_admin/settings.py:55 +#: inginious/frontend/pages/course_admin/settings.py:52 msgid "Invalid accessibility dates" msgstr "" -#: inginious/frontend/pages/course_admin/settings.py:70 +#: inginious/frontend/pages/course_admin/settings.py:67 msgid "Invalid registration dates" msgstr "" -#: inginious/frontend/pages/course_admin/settings.py:78 +#: inginious/frontend/pages/course_admin/settings.py:75 msgid "Invalid ACL value" msgstr "" -#: inginious/frontend/pages/course_admin/settings.py:92 +#: inginious/frontend/pages/course_admin/settings.py:89 msgid "LTI keys must be alphanumerical." msgstr "" -#: inginious/frontend/pages/course_admin/settings.py:129 +#: inginious/frontend/pages/course_admin/settings.py:126 msgid "Some tag fields were missing." msgstr "" -#: inginious/frontend/pages/course_admin/settings.py:132 +#: inginious/frontend/pages/course_admin/settings.py:129 msgid "Invalid tag id: {}" msgstr "" -#: inginious/frontend/pages/course_admin/settings.py:151 +#: inginious/frontend/pages/course_admin/settings.py:148 msgid "Invalid type value: {}" msgstr "" -#: inginious/frontend/pages/course_admin/settings.py:153 +#: inginious/frontend/pages/course_admin/settings.py:150 msgid "Invalid id: {}" msgstr "" -#: inginious/frontend/pages/course_admin/settings.py:167 +#: inginious/frontend/pages/course_admin/settings.py:164 msgid "Some datas have the same id! The id must be unique." msgstr "" @@ -600,7 +644,7 @@ msgid "The following submission could not be prepared for download: {}" msgstr "" #: inginious/frontend/pages/course_admin/submissions.py:67 -#: inginious/frontend/pages/course_admin/utils.py:43 +#: inginious/frontend/pages/course_admin/utils.py:38 msgid "You don't have admin rights on this course." msgstr "" @@ -612,152 +656,82 @@ msgstr "" msgid "The submission doesn't exist." msgstr "" -#: inginious/frontend/pages/course_admin/task_edit.py:34 -#: inginious/frontend/pages/course_admin/task_edit_file.py:24 -#: inginious/frontend/pages/course_admin/task_edit_file.py:45 -#: inginious/frontend/pages/course_admin/task_edit_file.py:264 -msgid "Invalid task id" -msgstr "" - -#: inginious/frontend/pages/course_admin/task_edit.py:87 -msgid "Invalid course/task id" -msgstr "" - -#: inginious/frontend/pages/course_admin/task_edit.py:117 -msgid "Invalid file type: {}" -msgstr "" - -#: inginious/frontend/pages/course_admin/task_edit.py:135 -msgid "The number of random inputs must be an integer!" -msgstr "" - -#: inginious/frontend/pages/course_admin/task_edit.py:137 -msgid "The number of random inputs must be positive!" -msgstr "" - -#: inginious/frontend/pages/course_admin/task_edit.py:146 -msgid "Your browser returned an invalid form ({})" -msgstr "" - -#: inginious/frontend/pages/course_admin/task_edit.py:152 -msgid "Error while reading course's informations" -msgstr "" - -#: inginious/frontend/pages/course_admin/task_edit.py:176 -msgid "Invalid data: {}" -msgstr "" - -#: inginious/frontend/pages/course_admin/task_edit.py:182 -msgid "Cannot read zip file. Files were not modified" -msgstr "" - -#: inginious/frontend/pages/course_admin/task_edit.py:189 -msgid "" -"There was a problem while extracting the zip archive. Some files may have" -" been modified" -msgstr "" - -#: inginious/frontend/pages/course_admin/task_edit_file.py:168 -#: inginious/frontend/pages/course_admin/task_edit_file.py:188 -#: inginious/frontend/pages/course_admin/task_edit_file.py:213 -msgid "Invalid new path" -msgstr "" - -#: inginious/frontend/pages/course_admin/task_edit_file.py:174 -msgid "An error occurred while writing the file" -msgstr "" - -#: inginious/frontend/pages/course_admin/task_edit_file.py:219 -msgid "An error occurred while moving the files" -msgstr "" - -#: inginious/frontend/pages/course_admin/task_edit_file.py:240 -msgid "An error occurred while deleting the files" -msgstr "" - -#: inginious/frontend/pages/course_admin/task_edit_file.py:247 -msgid "This path doesn't exist." -msgstr "" - #: inginious/frontend/pages/course_admin/task_list.py:36 +#: inginious/frontend/pages/taskset_admin/template.py:36 msgid "Invalid task dispenser" msgstr "" -#: inginious/frontend/pages/course_admin/task_list.py:45 -msgid "Invalid course structure: " -msgstr "" - -#: inginious/frontend/pages/course_admin/task_list.py:47 +#: inginious/frontend/pages/course_admin/task_list.py:43 +#: inginious/frontend/pages/course_admin/task_list.py:48 +#: inginious/frontend/pages/taskset_admin/template.py:43 +#: inginious/frontend/pages/taskset_admin/template.py:48 +#: inginious/frontend/pages/taskset_admin/template.py:53 msgid "Something wrong happened: " msgstr "" #: inginious/frontend/pages/course_admin/task_list.py:54 -msgid "Couldn't create task {} : " -msgstr "" - -#: inginious/frontend/pages/course_admin/task_list.py:59 -msgid "Couldn't delete task {} : " -msgstr "" - -#: inginious/frontend/pages/course_admin/task_list.py:64 msgid "Couldn't wipe task {} : " msgstr "" -#: inginious/frontend/pages/course_admin/utils.py:40 -msgid "You don't have staff rights on this course." +#: inginious/frontend/pages/course_admin/task_list.py:69 +msgid "Invalid course structure: " msgstr "" -#: inginious/frontend/pages/course_admin/utils.py:50 +#: inginious/frontend/pages/course_admin/utils.py:45 msgid "This course is unreachable" msgstr "" -#: inginious/frontend/pages/course_admin/utils.py:144 +#: inginious/frontend/pages/course_admin/utils.py:139 msgid "List not valid." msgstr "" -#: inginious/frontend/pages/course_admin/utils.py:343 -#: inginious/frontend/templates/course_admin/settings.html:18 -#: inginious/frontend/templates/course_admin/settings.html:25 +#: inginious/frontend/pages/course_admin/utils.py:338 +#: inginious/frontend/templates/course.html:53 +#: inginious/frontend/templates/course.html:71 +#: inginious/frontend/templates/course_user_settings.html:33 #: inginious/frontend/templates/course_user_settings.html:47 #: inginious/frontend/templates/course_user_settings.html:57 -msgid "Course settings" +msgid "User settings" msgstr "" -#: inginious/frontend/pages/course_admin/utils.py:345 -#: inginious/frontend/templates/course_admin/stats.html:27 -#: inginious/frontend/templates/course_admin/stats.html:35 +#: inginious/frontend/pages/course_admin/utils.py:340 +#: inginious/frontend/templates/course_admin/stats.html:28 +#: inginious/frontend/templates/course_admin/stats.html:36 #: inginious/frontend/templates/course_admin/submissions_query.html:211 msgid "Statistics" msgstr "" -#: inginious/frontend/pages/course_admin/utils.py:346 -#: inginious/frontend/templates/course_admin/student_list.html:20 -#: inginious/frontend/templates/course_admin/student_list.html:28 +#: inginious/frontend/pages/course_admin/utils.py:341 +#: inginious/frontend/templates/course_admin/student_list.html:21 +#: inginious/frontend/templates/course_admin/student_list.html:29 msgid "User management" msgstr "" -#: inginious/frontend/pages/course_admin/utils.py:349 -#: inginious/frontend/templates/course_admin/stats.html:49 -#: inginious/frontend/templates/course_admin/stats.html:248 -#: inginious/frontend/templates/course_admin/task_edit.html:19 -#: inginious/frontend/templates/course_admin/task_list.html:19 -#: inginious/frontend/templates/course_admin/task_list.html:25 +#: inginious/frontend/pages/course_admin/utils.py:344 +#: inginious/frontend/templates/course_admin/stats.html:50 +#: inginious/frontend/templates/course_admin/stats.html:249 +#: inginious/frontend/templates/course_admin/task_list.html:20 +#: inginious/frontend/templates/course_admin/task_list.html:26 msgid "Tasks" msgstr "" -#: inginious/frontend/pages/course_admin/utils.py:351 -#: inginious/frontend/templates/course_admin/stats.html:43 -#: inginious/frontend/templates/course_admin/stats.html:168 +#: inginious/frontend/pages/course_admin/utils.py:346 +#: inginious/frontend/templates/course_admin/stats.html:44 +#: inginious/frontend/templates/course_admin/stats.html:169 #: inginious/frontend/templates/course_admin/submissions.html:6 -#: inginious/frontend/templates/course_admin/submissions.html:18 -#: inginious/frontend/templates/course_admin/submissions.html:26 +#: inginious/frontend/templates/course_admin/submissions.html:19 +#: inginious/frontend/templates/course_admin/submissions.html:27 msgid "Submissions" msgstr "" -#: inginious/frontend/pages/course_admin/utils.py:354 +#: inginious/frontend/pages/course_admin/utils.py:349 +#: inginious/frontend/pages/taskset_admin/utils.py:63 #: inginious/frontend/templates/course_admin/danger_zone.html:6 -#: inginious/frontend/templates/course_admin/danger_zone.html:18 -#: inginious/frontend/templates/course_admin/danger_zone.html:24 +#: inginious/frontend/templates/course_admin/danger_zone.html:19 +#: inginious/frontend/templates/course_admin/danger_zone.html:25 +#: inginious/frontend/templates/taskset_admin/danger_zone.html:6 +#: inginious/frontend/templates/taskset_admin/danger_zone.html:19 +#: inginious/frontend/templates/taskset_admin/danger_zone.html:25 msgid "Danger zone" msgstr "" @@ -831,13 +805,130 @@ msgstr "" msgid "Delete my account" msgstr "" +#: inginious/frontend/pages/taskset_admin/danger_zone.py:54 +msgid "Wrong taskset id." +msgstr "" + +#: inginious/frontend/pages/taskset_admin/danger_zone.py:59 +msgid "One or more course(s) rely on the current taskset." +msgstr "" + +#: inginious/frontend/pages/taskset_admin/danger_zone.py:63 +msgid "An error occurred while deleting the taskset data: {}" +msgstr "" + +#: inginious/frontend/pages/taskset_admin/settings.py:33 +msgid "Invalid taskid : {}" +msgstr "" + +#: inginious/frontend/pages/taskset_admin/settings.py:42 +msgid "Couldn't create task {} : " +msgstr "" + +#: inginious/frontend/pages/taskset_admin/settings.py:53 +msgid "You cannot remove yourself from the administrators of this taskset" +msgstr "" + +#: inginious/frontend/pages/taskset_admin/task_edit.py:34 +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:24 +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:45 +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:260 +msgid "Invalid task id" +msgstr "" + +#: inginious/frontend/pages/taskset_admin/task_edit.py:87 +msgid "Invalid taskset/task id" +msgstr "" + +#: inginious/frontend/pages/taskset_admin/task_edit.py:117 +msgid "Invalid file type: {}" +msgstr "" + +#: inginious/frontend/pages/taskset_admin/task_edit.py:135 +msgid "The number of random inputs must be an integer!" +msgstr "" + +#: inginious/frontend/pages/taskset_admin/task_edit.py:137 +msgid "The number of random inputs must be positive!" +msgstr "" + +#: inginious/frontend/pages/taskset_admin/task_edit.py:146 +msgid "Your browser returned an invalid form ({})" +msgstr "" + +#: inginious/frontend/pages/taskset_admin/task_edit.py:152 +msgid "Error while reading taskset data" +msgstr "" + +#: inginious/frontend/pages/taskset_admin/task_edit.py:176 +msgid "Invalid data: {}" +msgstr "" + +#: inginious/frontend/pages/taskset_admin/task_edit.py:182 +msgid "Cannot read zip file. Files were not modified" +msgstr "" + +#: inginious/frontend/pages/taskset_admin/task_edit.py:189 +msgid "" +"There was a problem while extracting the zip archive. Some files may have" +" been modified" +msgstr "" + +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:164 +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:184 +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:209 +msgid "Invalid new path" +msgstr "" + +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:170 +msgid "An error occurred while writing the file" +msgstr "" + +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:215 +msgid "An error occurred while moving the files" +msgstr "" + +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:236 +msgid "An error occurred while deleting the files" +msgstr "" + +#: inginious/frontend/pages/taskset_admin/task_edit_file.py:243 +msgid "This path doesn't exist." +msgstr "" + +#: inginious/frontend/pages/taskset_admin/template.py:68 +msgid "Invalid taskset structure: " +msgstr "" + +#: inginious/frontend/pages/taskset_admin/utils.py:34 +msgid "You don't have admin rights on this taskset." +msgstr "" + +#: inginious/frontend/pages/taskset_admin/utils.py:43 +msgid "This taskset is unreachable" +msgstr "" + +#: inginious/frontend/pages/taskset_admin/utils.py:61 +#: inginious/frontend/templates/course_admin/settings.html:6 +#: inginious/frontend/templates/taskset_admin/settings.html:6 +#: inginious/frontend/templates/taskset_admin/settings.html:49 +msgid "Settings" +msgstr "" + +#: inginious/frontend/pages/taskset_admin/utils.py:62 +#: inginious/frontend/templates/taskset_admin/template.html:20 +#: inginious/frontend/templates/taskset_admin/template.html:26 +msgid "Course template" +msgstr "" + #: inginious/frontend/plugins/auth/custom_auth_form.html:6 msgid "Login" msgstr "" #: inginious/frontend/plugins/auth/custom_auth_form.html:25 -#: inginious/frontend/templates/course_admin/edit_tabs/file_modals.html:26 -#: inginious/frontend/templates/course_admin/task_dispensers/util_task_edit_modal.html:22 +#: inginious/frontend/templates/task_dispensers_admin/util.html:67 +#: inginious/frontend/templates/task_dispensers_admin/util_task_edit_modal.html:22 +#: inginious/frontend/templates/taskset_admin/edit_tabs/file_modals.html:26 msgid "Close" msgstr "" @@ -847,20 +938,20 @@ msgstr "" #: inginious/frontend/plugins/auth/custom_auth_form.html:31 #: inginious/frontend/templates/auth.html:21 -#: inginious/frontend/templates/course_admin/task_list.html:78 +#: inginious/frontend/templates/taskset_admin/settings.html:102 msgid "Username" msgstr "" #: inginious/frontend/plugins/auth/custom_auth_form.html:34 #: inginious/frontend/templates/auth.html:24 -#: inginious/frontend/templates/course_admin/task_list.html:79 #: inginious/frontend/templates/course_register.html:29 +#: inginious/frontend/templates/taskset_admin/settings.html:103 msgid "Password" msgstr "" #: inginious/frontend/plugins/auth/custom_auth_form.html:36 #: inginious/frontend/templates/auth.html:26 -#: inginious/frontend/templates/layout.html:179 +#: inginious/frontend/templates/layout.html:180 #: inginious/frontend/templates/signin_button.html:4 #: inginious/frontend/templates/signin_button.html:11 msgid "Sign in" @@ -902,28 +993,32 @@ msgstr "" #: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:11 #: inginious/frontend/templates/admin/admin_users.html:13 #: inginious/frontend/templates/course.html:103 -#: inginious/frontend/templates/course_admin/audience_edit.html:20 -#: inginious/frontend/templates/course_admin/danger_zone.html:18 -#: inginious/frontend/templates/course_admin/settings.html:18 -#: inginious/frontend/templates/course_admin/stats.html:27 -#: inginious/frontend/templates/course_admin/student_info.html:23 -#: inginious/frontend/templates/course_admin/student_list.html:20 -#: inginious/frontend/templates/course_admin/submission.html:32 -#: inginious/frontend/templates/course_admin/submissions.html:18 -#: inginious/frontend/templates/course_admin/task_edit.html:21 -#: inginious/frontend/templates/course_admin/task_list.html:19 +#: inginious/frontend/templates/course_admin/audience_edit.html:21 +#: inginious/frontend/templates/course_admin/danger_zone.html:19 +#: inginious/frontend/templates/course_admin/settings.html:19 +#: inginious/frontend/templates/course_admin/stats.html:28 +#: inginious/frontend/templates/course_admin/student_info.html:24 +#: inginious/frontend/templates/course_admin/student_list.html:21 +#: inginious/frontend/templates/course_admin/submission.html:33 +#: inginious/frontend/templates/course_admin/submissions.html:19 +#: inginious/frontend/templates/course_admin/task_list.html:20 #: inginious/frontend/templates/course_register.html:10 #: inginious/frontend/templates/course_user_settings.html:46 -#: inginious/frontend/templates/courselist.html:27 +#: inginious/frontend/templates/courselist.html:21 #: inginious/frontend/templates/group.html:61 #: inginious/frontend/templates/marketplace.html:26 -#: inginious/frontend/templates/marketplace_course.html:47 +#: inginious/frontend/templates/marketplace_taskset.html:47 #: inginious/frontend/templates/mycourses.html:11 #: inginious/frontend/templates/preferences/bindings.html:17 #: inginious/frontend/templates/preferences/delete.html:18 #: inginious/frontend/templates/preferences/profile.html:17 #: inginious/frontend/templates/queue.html:11 #: inginious/frontend/templates/task.html:266 +#: inginious/frontend/templates/taskset_admin/danger_zone.html:19 +#: inginious/frontend/templates/taskset_admin/settings.html:19 +#: inginious/frontend/templates/taskset_admin/task_edit.html:20 +#: inginious/frontend/templates/taskset_admin/template.html:20 +#: inginious/frontend/templates/tasksets.html:11 msgid "(current)" msgstr "" @@ -943,105 +1038,120 @@ msgstr "" #: inginious/frontend/templates/course_unavailable.html:9 #: inginious/frontend/templates/course_user_settings.html:44 #: inginious/frontend/templates/courselist.html:5 -#: inginious/frontend/templates/courselist.html:26 -#: inginious/frontend/templates/courselist.html:33 +#: inginious/frontend/templates/courselist.html:20 +#: inginious/frontend/templates/courselist.html:27 #: inginious/frontend/templates/layout.html:149 -#: inginious/frontend/templates/layout.html:170 -#: inginious/frontend/templates/mycourses.html:25 +#: inginious/frontend/templates/layout.html:171 #: inginious/frontend/templates/task.html:263 +#: inginious/frontend/templates/tasksets.html:24 msgid "Course list" msgstr "" #: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:29 #: inginious/frontend/templates/course.html:21 #: inginious/frontend/templates/group.html:19 -#: inginious/frontend/templates/mycourses.html:29 +#: inginious/frontend/templates/mycourses.html:23 msgid "Last tried exercises" msgstr "" -#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:48 +#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:51 #: inginious/frontend/templates/course.html:40 #: inginious/frontend/templates/group.html:39 -#: inginious/frontend/templates/mycourses.html:48 +#: inginious/frontend/templates/mycourses.html:45 msgid "No submissions" msgstr "" -#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:56 +#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:59 msgid "My Upcoming Tasks" msgstr "" -#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:60 +#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:63 msgid "Switch time planner" msgstr "" -#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:71 +#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:74 msgid "Modify time planner" msgstr "" -#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:77 +#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:80 msgid "" "This allows to display only the tasks whose deadline is in a certain " "temporal proximity." msgstr "" -#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:78 +#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:81 msgid "Number of days:" msgstr "" -#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:86 +#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:89 #: inginious/frontend/templates/admin/admin_users.html:111 #: inginious/frontend/templates/admin/admin_users.html:131 #: inginious/frontend/templates/admin/admin_users.html:167 #: inginious/frontend/templates/admin/admin_users.html:187 -#: inginious/frontend/templates/course_admin/audience_edit.html:62 -#: inginious/frontend/templates/course_admin/danger_zone.html:85 -#: inginious/frontend/templates/course_admin/danger_zone.html:166 -#: inginious/frontend/templates/course_admin/student_info.html:106 -#: inginious/frontend/templates/course_admin/student_list.html:275 +#: inginious/frontend/templates/course_admin/audience_edit.html:63 +#: inginious/frontend/templates/course_admin/danger_zone.html:86 +#: inginious/frontend/templates/course_admin/danger_zone.html:167 +#: inginious/frontend/templates/course_admin/student_info.html:107 +#: inginious/frontend/templates/course_admin/student_list.html:276 #: inginious/frontend/templates/course_admin/student_list_table.html:86 -#: inginious/frontend/templates/course_admin/submissions.html:249 -#: inginious/frontend/templates/course_admin/task_dispensers/util_delete_modal.html:27 -#: inginious/frontend/templates/course_admin/task_list.html:56 -#: inginious/frontend/templates/course_admin/task_list.html:108 +#: inginious/frontend/templates/course_admin/submissions.html:250 +#: inginious/frontend/templates/course_admin/task_list.html:57 +#: inginious/frontend/templates/course_admin/task_list.html:100 #: inginious/frontend/templates/lti_bind.html:32 #: inginious/frontend/templates/preferences/delete.html:59 #: inginious/frontend/templates/queue.html:112 +#: inginious/frontend/templates/task_dispensers_admin/util_delete_modal.html:29 +#: inginious/frontend/templates/task_dispensers_admin/util_move_modal.html:17 +#: inginious/frontend/templates/taskset_admin/danger_zone.html:71 +#: inginious/frontend/templates/taskset_admin/settings.html:151 +#: inginious/frontend/templates/taskset_admin/settings.html:177 +#: inginious/frontend/templates/taskset_admin/template.html:57 +#: inginious/frontend/templates/taskset_admin/template.html:101 +#: inginious/frontend/templates/tasksets.html:101 #: inginious/frontend/templates/unregister_modal.html:19 msgid "Cancel" msgstr "" -#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:87 -#: inginious/frontend/templates/course_admin/settings.html:486 -#: inginious/frontend/templates/course_admin/task_edit.html:10 -#: inginious/frontend/templates/course_admin/task_edit.html:31 -#: inginious/frontend/templates/course_admin/task_edit.html:97 -#: inginious/frontend/templates/course_admin/task_list.html:57 -#: inginious/frontend/templates/course_admin/task_list.html:113 +#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:90 +#: inginious/frontend/templates/course_admin/settings.html:481 +#: inginious/frontend/templates/course_admin/task_list.html:58 +#: inginious/frontend/templates/course_admin/task_list.html:105 +#: inginious/frontend/templates/taskset_admin/settings.html:51 +#: inginious/frontend/templates/taskset_admin/task_edit.html:10 +#: inginious/frontend/templates/taskset_admin/task_edit.html:30 +#: inginious/frontend/templates/taskset_admin/task_edit.html:95 +#: inginious/frontend/templates/taskset_admin/template.html:58 +#: inginious/frontend/templates/taskset_admin/template.html:106 msgid "Save changes" msgstr "" -#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:102 +#: inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html:105 msgid "You have no upcoming tasks" msgstr "" -#: inginious/frontend/task_dispensers/combinatory_test.py:22 +#: inginious/frontend/task_dispensers/combinatory_test.py:24 msgid "Combinatory test" msgstr "" -#: inginious/frontend/task_dispensers/combinatory_test.py:45 +#: inginious/frontend/task_dispensers/combinatory_test.py:47 msgid "Amount of tasks to be displayed" msgstr "" -#: inginious/frontend/task_dispensers/toc.py:40 +#: inginious/frontend/task_dispensers/toc.py:52 msgid "Table of contents" msgstr "" -#: inginious/frontend/task_dispensers/toc.py:103 +#: inginious/frontend/task_dispensers/toc.py:115 msgid "Closed by default" msgstr "" +#: inginious/frontend/task_dispensers/toc.py:116 +msgid "Hidden if empty" +msgstr "" + #: inginious/frontend/task_dispensers/util.py:49 -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/groups.html:4 +#: inginious/frontend/templates/task_dispensers_admin/config_items/groups.html:6 +#: inginious/frontend/templates/task_dispensers_admin/config_items/groups.html:10 msgid "Submission mode" msgstr "" @@ -1050,7 +1160,8 @@ msgid "Weight" msgstr "" #: inginious/frontend/task_dispensers/util.py:97 -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/submission_storage.html:4 +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_storage.html:6 +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_storage.html:12 msgid "Submission storage" msgstr "" @@ -1059,7 +1170,8 @@ msgid "Evaluation mode" msgstr "" #: inginious/frontend/task_dispensers/util.py:145 -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/categories.html:4 +#: inginious/frontend/templates/task_dispensers_admin/config_items/categories.html:6 +#: inginious/frontend/templates/task_dispensers_admin/config_items/categories.html:9 msgid "Categories" msgstr "" @@ -1069,9 +1181,10 @@ msgid "Submission limit" msgstr "" #: inginious/frontend/task_dispensers/util.py:193 -#: inginious/frontend/templates/course_admin/settings.html:43 -#: inginious/frontend/templates/course_admin/settings.html:140 -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/accessibility.html:4 +#: inginious/frontend/templates/course_admin/settings.html:44 +#: inginious/frontend/templates/course_admin/settings.html:135 +#: inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html:6 +#: inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html:15 msgid "Accessibility" msgstr "" @@ -1112,12 +1225,6 @@ msgid "" "course." msgstr "" -#: inginious/frontend/templates/course.html:53 -#: inginious/frontend/templates/course.html:71 -#: inginious/frontend/templates/course_user_settings.html:33 -msgid "Course Settings" -msgstr "" - #: inginious/frontend/templates/course.html:56 #: inginious/frontend/templates/course_user_settings.html:18 msgid "Course administration" @@ -1154,14 +1261,21 @@ msgid "Please enroll in the course to be able to submit answers to problems." msgstr "" #: inginious/frontend/templates/course.html:99 +#: inginious/frontend/templates/course_admin/audience_edit.html:15 +#: inginious/frontend/templates/course_admin/danger_zone.html:15 +#: inginious/frontend/templates/course_admin/settings.html:15 +#: inginious/frontend/templates/course_admin/stats.html:22 +#: inginious/frontend/templates/course_admin/student_info.html:17 +#: inginious/frontend/templates/course_admin/student_list.html:15 +#: inginious/frontend/templates/course_admin/submission.html:19 +#: inginious/frontend/templates/course_admin/submissions.html:15 +#: inginious/frontend/templates/course_admin/task_list.html:16 #: inginious/frontend/templates/course_user_settings.html:42 -#: inginious/frontend/templates/courselist.html:17 #: inginious/frontend/templates/layout.html:154 -#: inginious/frontend/templates/marketplace.html:17 #: inginious/frontend/templates/mycourses.html:5 #: inginious/frontend/templates/mycourses.html:10 #: inginious/frontend/templates/mycourses.html:18 -#: inginious/frontend/templates/mycourses.html:55 +#: inginious/frontend/templates/mycourses.html:52 #: inginious/frontend/templates/task.html:261 msgid "My courses" msgstr "" @@ -1179,7 +1293,7 @@ msgid "Register to course '{}'" msgstr "" #: inginious/frontend/templates/course_register.html:11 -#: inginious/frontend/templates/layout.html:175 +#: inginious/frontend/templates/layout.html:176 #: inginious/frontend/templates/register.html:5 msgid "Register" msgstr "" @@ -1238,15 +1352,15 @@ msgid "" "course administrator, go to your 'My courses' page to see all of them." msgstr "" -#: inginious/frontend/templates/courselist.html:62 +#: inginious/frontend/templates/courselist.html:56 msgid "External platform" msgstr "" -#: inginious/frontend/templates/courselist.html:66 +#: inginious/frontend/templates/courselist.html:60 msgid "Auto-registration" msgstr "" -#: inginious/frontend/templates/courselist.html:70 +#: inginious/frontend/templates/courselist.html:64 msgid "Password needed" msgstr "" @@ -1254,8 +1368,8 @@ msgstr "" msgid "Register for a group" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:48 -#: inginious/frontend/templates/course_admin/student_list.html:208 +#: inginious/frontend/templates/course_admin/student_list.html:49 +#: inginious/frontend/templates/course_admin/student_list.html:209 #: inginious/frontend/templates/group.html:59 #: inginious/frontend/templates/group.html:68 msgid "Groups" @@ -1336,22 +1450,25 @@ msgstr "" msgid "Open menu" msgstr "" -#: inginious/frontend/templates/course_admin/audience_edit.html:16 -#: inginious/frontend/templates/course_admin/danger_zone.html:16 -#: inginious/frontend/templates/course_admin/settings.html:16 -#: inginious/frontend/templates/course_admin/stats.html:23 -#: inginious/frontend/templates/course_admin/student_info.html:18 -#: inginious/frontend/templates/course_admin/student_list.html:16 -#: inginious/frontend/templates/course_admin/submission.html:21 -#: inginious/frontend/templates/course_admin/submissions.html:16 -#: inginious/frontend/templates/course_admin/task_edit.html:17 -#: inginious/frontend/templates/course_admin/task_list.html:17 +#: inginious/frontend/templates/course_admin/audience_edit.html:17 +#: inginious/frontend/templates/course_admin/danger_zone.html:17 +#: inginious/frontend/templates/course_admin/settings.html:17 +#: inginious/frontend/templates/course_admin/stats.html:24 +#: inginious/frontend/templates/course_admin/student_info.html:19 +#: inginious/frontend/templates/course_admin/student_list.html:17 +#: inginious/frontend/templates/course_admin/submission.html:22 +#: inginious/frontend/templates/course_admin/submissions.html:17 +#: inginious/frontend/templates/course_admin/task_list.html:18 #: inginious/frontend/templates/layout.html:142 #: inginious/frontend/templates/task.html:128 +#: inginious/frontend/templates/taskset_admin/danger_zone.html:18 +#: inginious/frontend/templates/taskset_admin/settings.html:18 +#: inginious/frontend/templates/taskset_admin/task_edit.html:19 +#: inginious/frontend/templates/taskset_admin/template.html:19 msgid "Administration" msgstr "" -#: inginious/frontend/templates/course_admin/stats.html:260 +#: inginious/frontend/templates/course_admin/stats.html:261 #: inginious/frontend/templates/layout.html:145 msgid "Users" msgstr "" @@ -1360,45 +1477,53 @@ msgstr "" #: inginious/frontend/templates/marketplace.html:5 #: inginious/frontend/templates/marketplace.html:25 #: inginious/frontend/templates/marketplace.html:32 -#: inginious/frontend/templates/marketplace_course.html:45 +#: inginious/frontend/templates/marketplace_taskset.html:45 msgid "Marketplace" msgstr "" #: inginious/frontend/templates/layout.html:155 +#: inginious/frontend/templates/marketplace.html:17 +#: inginious/frontend/templates/tasksets.html:5 +#: inginious/frontend/templates/tasksets.html:10 +#: inginious/frontend/templates/tasksets.html:32 +msgid "My tasksets" +msgstr "" + +#: inginious/frontend/templates/layout.html:156 #: inginious/frontend/templates/preferences/bindings.html:15 #: inginious/frontend/templates/preferences/delete.html:16 #: inginious/frontend/templates/preferences/profile.html:15 msgid "Preferences" msgstr "" -#: inginious/frontend/templates/layout.html:156 +#: inginious/frontend/templates/layout.html:157 msgid "Service status" msgstr "" -#: inginious/frontend/templates/layout.html:157 +#: inginious/frontend/templates/layout.html:158 msgid "Log out" msgstr "" -#: inginious/frontend/templates/layout.html:161 +#: inginious/frontend/templates/layout.html:162 msgid "English" msgstr "" -#: inginious/frontend/templates/layout.html:195 +#: inginious/frontend/templates/layout.html:196 msgid "" "Powered by INGInious v.{} , a free and open-source grading " "tool." msgstr "" -#: inginious/frontend/templates/layout.html:197 +#: inginious/frontend/templates/layout.html:198 msgid "Running INGInious v.{}" msgstr "" -#: inginious/frontend/templates/layout.html:199 +#: inginious/frontend/templates/layout.html:200 msgid "INGInious is distributed under AGPL license" msgstr "" -#: inginious/frontend/templates/layout.html:203 +#: inginious/frontend/templates/layout.html:204 msgid "Privacy Policy" msgstr "" @@ -1478,29 +1603,29 @@ msgstr "" #: inginious/frontend/templates/marketplace.html:12 msgid "" -"This page list all the course that are publicly avalaible. When you click" -" import, it will create a new course on your INGInious instance. You will" -" be admin of this new course and it will contain all the tasks and the " -"structure of the imported course" +"This page list all the tasksets that are publicly avalaible. When you " +"click import, it will create a new taskset on your INGInious instance. " +"You will be admin of this new taskset and it will contain all the tasks " +"of the imported taskset." msgstr "" #: inginious/frontend/templates/marketplace.html:56 -#: inginious/frontend/templates/marketplace_course.html:68 -msgid "Import course" +#: inginious/frontend/templates/marketplace_taskset.html:68 +msgid "Import taskset" msgstr "" #: inginious/frontend/templates/marketplace.html:74 -msgid "No public courses available" +msgid "No public tasksets available" msgstr "" #: inginious/frontend/templates/marketplace.html:84 -msgid "Import this course" +msgid "Import this taskset" msgstr "" #: inginious/frontend/templates/marketplace.html:89 msgid "" -"This will create a new course with the ID bellow. This new course will " -"contain the tasks and structure of the selected course" +"This will create a new taskset with the ID bellow. This new taskset will " +"contain the tasks and structure of the selected taskset" msgstr "" #: inginious/frontend/templates/marketplace.html:90 @@ -1508,38 +1633,38 @@ msgid "Course ID" msgstr "" #: inginious/frontend/templates/marketplace.html:94 -msgid "Create course" +msgid "Create taskset" msgstr "" -#: inginious/frontend/templates/marketplace_course.html:7 +#: inginious/frontend/templates/marketplace_taskset.html:7 #: inginious/frontend/templates/task.html:13 msgid "Information" msgstr "" -#: inginious/frontend/templates/marketplace_course.html:11 +#: inginious/frontend/templates/marketplace_taskset.html:11 msgid "Language(s)" msgstr "" -#: inginious/frontend/templates/marketplace_course.html:17 +#: inginious/frontend/templates/marketplace_taskset.html:17 msgid "License" msgstr "" -#: inginious/frontend/templates/marketplace_course.html:23 +#: inginious/frontend/templates/marketplace_taskset.html:23 msgid "Maintainer(s)" msgstr "" -#: inginious/frontend/templates/marketplace_course.html:29 +#: inginious/frontend/templates/marketplace_taskset.html:29 #: inginious/frontend/templates/task.html:17 msgid "Author(s)" msgstr "" -#: inginious/frontend/templates/marketplace_course.html:35 +#: inginious/frontend/templates/marketplace_taskset.html:35 msgid "Link" msgstr "" -#: inginious/frontend/templates/marketplace_course.html:65 -#: inginious/frontend/templates/mycourses.html:110 -msgid "New course id" +#: inginious/frontend/templates/marketplace_taskset.html:65 +#: inginious/frontend/templates/tasksets.html:75 +msgid "New taskset id" msgstr "" #: inginious/frontend/templates/mycourses.html:20 @@ -1548,46 +1673,26 @@ msgid "" "find new courses on the course list page." msgstr "" -#: inginious/frontend/templates/mycourses.html:73 +#: inginious/frontend/templates/mycourses.html:70 msgid "LTI course" msgstr "" -#: inginious/frontend/templates/mycourses.html:75 +#: inginious/frontend/templates/mycourses.html:72 msgid "Hidden course" msgstr "" -#: inginious/frontend/templates/mycourses.html:79 +#: inginious/frontend/templates/mycourses.html:76 msgid "Administrator" msgstr "" -#: inginious/frontend/templates/mycourses.html:81 -msgid "Tutor" -msgstr "" - -#: inginious/frontend/templates/mycourses.html:83 +#: inginious/frontend/templates/mycourses.html:78 msgid "Student" msgstr "" -#: inginious/frontend/templates/mycourses.html:91 +#: inginious/frontend/templates/mycourses.html:86 msgid "You are not registered to any course" msgstr "" -#: inginious/frontend/templates/mycourses.html:99 -msgid "Course created." -msgstr "" - -#: inginious/frontend/templates/mycourses.html:104 -msgid "Failed to create the course." -msgstr "" - -#: inginious/frontend/templates/mycourses.html:109 -msgid "Course" -msgstr "" - -#: inginious/frontend/templates/mycourses.html:113 -msgid "Create new course" -msgstr "" - #: inginious/frontend/templates/notfound.html:5 #: inginious/frontend/templates/notfound.html:10 msgid "Error 404" @@ -1607,10 +1712,10 @@ msgstr "" msgid "Running jobs" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/code.html:19 -#: inginious/frontend/templates/course_admin/subproblems/file.html:23 #: inginious/frontend/templates/queue.html:25 #: inginious/frontend/templates/queue.html:66 +#: inginious/frontend/templates/taskset_admin/subproblems/code.html:19 +#: inginious/frontend/templates/taskset_admin/subproblems/file.html:23 msgid "Type" msgstr "" @@ -1618,11 +1723,12 @@ msgstr "" msgid "Agent name" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/basic.html:5 -#: inginious/frontend/templates/course_admin/settings.html:62 -#: inginious/frontend/templates/course_admin/task_edit.html:124 +#: inginious/frontend/templates/course_admin/settings.html:63 #: inginious/frontend/templates/queue.html:27 #: inginious/frontend/templates/queue.html:67 +#: inginious/frontend/templates/taskset_admin/edit_tabs/basic.html:5 +#: inginious/frontend/templates/taskset_admin/settings.html:56 +#: inginious/frontend/templates/taskset_admin/task_edit.html:122 msgid "Name" msgstr "" @@ -1744,8 +1850,8 @@ msgstr "" msgid "Contact" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/basic.html:49 #: inginious/frontend/templates/task.html:24 +#: inginious/frontend/templates/taskset_admin/edit_tabs/basic.html:49 msgid "Contact link" msgstr "" @@ -1769,8 +1875,8 @@ msgstr "" msgid "Not yet attempted" msgstr "" -#: inginious/frontend/templates/course_admin/student_info.html:73 -#: inginious/frontend/templates/course_admin/submissions.html:133 +#: inginious/frontend/templates/course_admin/student_info.html:74 +#: inginious/frontend/templates/course_admin/submissions.html:134 #: inginious/frontend/templates/task.html:52 msgid "Succeeded" msgstr "" @@ -1779,8 +1885,8 @@ msgstr "" msgid "Waiting for verification" msgstr "" -#: inginious/frontend/templates/course_admin/student_info.html:76 -#: inginious/frontend/templates/course_admin/submissions.html:137 +#: inginious/frontend/templates/course_admin/student_info.html:77 +#: inginious/frontend/templates/course_admin/submissions.html:138 #: inginious/frontend/templates/task.html:63 msgid "Failed" msgstr "" @@ -1794,8 +1900,9 @@ msgstr "" msgid "Attempts" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/submission_limit.html:7 #: inginious/frontend/templates/task.html:86 +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_limit.html:8 +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_limit.html:20 msgid "No limitation" msgstr "" @@ -1811,8 +1918,8 @@ msgstr "" msgid "Category tags" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:49 -#: inginious/frontend/templates/course_admin/submission.html:98 +#: inginious/frontend/templates/course_admin/settings.html:50 +#: inginious/frontend/templates/course_admin/submission.html:99 #: inginious/frontend/templates/task.html:114 msgid "Tags" msgstr "" @@ -1829,16 +1936,17 @@ msgid "" "modifying the \"accessible\" option in the configuration of the task." msgstr "" -#: inginious/frontend/templates/course_admin/stats.html:81 -#: inginious/frontend/templates/course_admin/student_list.html:185 +#: inginious/frontend/templates/course_admin/stats.html:82 +#: inginious/frontend/templates/course_admin/student_list.html:186 #: inginious/frontend/templates/course_admin/student_list_table.html:59 -#: inginious/frontend/templates/course_admin/task_dispensers/task_buttons.html:18 #: inginious/frontend/templates/task.html:141 +#: inginious/frontend/templates/task_dispensers_admin/task_buttons.html:26 msgid "View submissions" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/task_buttons.html:14 #: inginious/frontend/templates/task.html:145 +#: inginious/frontend/templates/task_dispensers_admin/task_buttons.html:20 +#: inginious/frontend/templates/taskset_admin/settings.html:127 msgid "Edit task" msgstr "" @@ -1859,13 +1967,15 @@ msgstr "" msgid "For evaluation" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/evaluation_mode.html:15 #: inginious/frontend/templates/task.html:180 +#: inginious/frontend/templates/task_dispensers_admin/config_items/evaluation_mode.html:8 +#: inginious/frontend/templates/task_dispensers_admin/config_items/evaluation_mode.html:21 msgid "Last submission" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/evaluation_mode.html:9 #: inginious/frontend/templates/task.html:182 +#: inginious/frontend/templates/task_dispensers_admin/config_items/evaluation_mode.html:7 +#: inginious/frontend/templates/task_dispensers_admin/config_items/evaluation_mode.html:15 msgid "Best submission" msgstr "" @@ -1975,6 +2085,42 @@ msgstr "" msgid "Task unavailable" msgstr "" +#: inginious/frontend/templates/tasksets.html:19 +msgid "" +"This page lists all the tasksets you have access to. You can find " +"instantiated courses on the course list page." +msgstr "" + +#: inginious/frontend/templates/tasksets.html:56 +msgid "Edit taskset" +msgstr "" + +#: inginious/frontend/templates/tasksets.html:60 +#: inginious/frontend/templates/tasksets.html:102 +msgid "Instantiate" +msgstr "" + +#: inginious/frontend/templates/tasksets.html:67 +msgid "You don't own any taskset." +msgstr "" + +#: inginious/frontend/templates/tasksets.html:74 +msgid "Course" +msgstr "" + +#: inginious/frontend/templates/tasksets.html:78 +msgid "Create new taskset" +msgstr "" + +#: inginious/frontend/templates/tasksets.html:88 +msgid "Instantiate a course" +msgstr "" + +#: inginious/frontend/templates/tasksets.html:93 +#: inginious/frontend/templates/tasksets.html:96 +msgid "New courseid" +msgstr "" + #: inginious/frontend/templates/unregister_modal.html:9 msgid "Unregister from {}" msgstr "" @@ -2004,7 +2150,7 @@ msgstr "" #: inginious/frontend/templates/admin/admin_users.html:33 #: inginious/frontend/templates/course_admin/student_list_table.html:16 -#: inginious/frontend/templates/course_admin/submissions.html:55 +#: inginious/frontend/templates/course_admin/submissions.html:56 msgid "username" msgstr "" @@ -2014,11 +2160,11 @@ msgid "email address" msgstr "" #: inginious/frontend/templates/admin/admin_users.html:38 -#: inginious/frontend/templates/course_admin/stats.html:61 -#: inginious/frontend/templates/course_admin/student_info.html:40 -#: inginious/frontend/templates/course_admin/student_list.html:159 +#: inginious/frontend/templates/course_admin/stats.html:62 +#: inginious/frontend/templates/course_admin/student_info.html:41 +#: inginious/frontend/templates/course_admin/student_list.html:160 #: inginious/frontend/templates/course_admin/student_list_table.html:26 -#: inginious/frontend/templates/course_admin/submissions.html:78 +#: inginious/frontend/templates/course_admin/submissions.html:79 msgid "Download CSV" msgstr "" @@ -2027,10 +2173,11 @@ msgid "Bindings" msgstr "" #: inginious/frontend/templates/admin/admin_users.html:102 -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:19 -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:53 -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice_templates.html:31 -#: inginious/frontend/templates/course_admin/task_dispensers/section_menu.html:21 +#: inginious/frontend/templates/task_dispensers_admin/section_menu.html:21 +#: inginious/frontend/templates/task_dispensers_admin/util.html:131 +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:19 +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:53 +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice_templates.html:31 msgid "Delete" msgstr "" @@ -2056,8 +2203,8 @@ msgid "Add user" msgstr "" #: inginious/frontend/templates/admin/admin_users.html:168 -#: inginious/frontend/templates/course_admin/edit_tabs/subproblems.html:22 -#: inginious/frontend/templates/course_admin/task_dispensers/util.html:103 +#: inginious/frontend/templates/task_dispensers_admin/util.html:98 +#: inginious/frontend/templates/taskset_admin/edit_tabs/subproblems.html:22 msgid "Add" msgstr "" @@ -2074,330 +2221,326 @@ msgstr "" msgid "Identifier :" msgstr "" -#: inginious/frontend/templates/course_admin/audience_edit.html:18 -#: inginious/frontend/templates/course_admin/student_list.html:43 -#: inginious/frontend/templates/course_admin/student_list.html:78 -#: inginious/frontend/templates/course_admin/student_list.html:110 +#: inginious/frontend/templates/course_admin/audience_edit.html:19 +#: inginious/frontend/templates/course_admin/student_list.html:44 +#: inginious/frontend/templates/course_admin/student_list.html:79 +#: inginious/frontend/templates/course_admin/student_list.html:111 msgid "Audiences" msgstr "" -#: inginious/frontend/templates/course_admin/audience_edit.html:20 -#: inginious/frontend/templates/course_admin/audience_edit.html:26 +#: inginious/frontend/templates/course_admin/audience_edit.html:21 +#: inginious/frontend/templates/course_admin/audience_edit.html:27 msgid "Edit audience {}" msgstr "" -#: inginious/frontend/templates/course_admin/audience_edit.html:44 -#: inginious/frontend/templates/course_admin/audience_edit.html:63 -#: inginious/frontend/templates/course_admin/student_list.html:67 -#: inginious/frontend/templates/course_admin/student_list.html:257 -#: inginious/frontend/templates/course_admin/student_list.html:276 +#: inginious/frontend/templates/course_admin/audience_edit.html:45 +#: inginious/frontend/templates/course_admin/audience_edit.html:64 +#: inginious/frontend/templates/course_admin/student_list.html:68 +#: inginious/frontend/templates/course_admin/student_list.html:258 +#: inginious/frontend/templates/course_admin/student_list.html:277 msgid "Add student" msgstr "" -#: inginious/frontend/templates/course_admin/audience_edit.html:51 +#: inginious/frontend/templates/course_admin/audience_edit.html:52 msgid "Choose student :" msgstr "" -#: inginious/frontend/templates/course_admin/audience_edit.html:55 -#: inginious/frontend/templates/course_admin/settings.html:70 -#: inginious/frontend/templates/course_admin/settings.html:76 -#: inginious/frontend/templates/course_admin/student_list.html:65 +#: inginious/frontend/templates/course_admin/audience_edit.html:56 +#: inginious/frontend/templates/course_admin/settings.html:71 +#: inginious/frontend/templates/course_admin/student_list.html:66 +#: inginious/frontend/templates/taskset_admin/settings.html:64 msgid "Enter something here to search for a user" msgstr "" -#: inginious/frontend/templates/course_admin/audience_edit.html:74 -#: inginious/frontend/templates/course_admin/task_edit.html:46 +#: inginious/frontend/templates/course_admin/audience_edit.html:75 +#: inginious/frontend/templates/taskset_admin/task_edit.html:44 msgid "Basic settings" msgstr "" -#: inginious/frontend/templates/course_admin/audience_edit.html:78 -#: inginious/frontend/templates/course_admin/audience_edit.html:82 +#: inginious/frontend/templates/course_admin/audience_edit.html:79 +#: inginious/frontend/templates/course_admin/audience_edit.html:83 msgid "Audience description" msgstr "" -#: inginious/frontend/templates/course_admin/audience_edit.html:85 +#: inginious/frontend/templates/course_admin/audience_edit.html:86 msgid "Delete audience" msgstr "" -#: inginious/frontend/templates/course_admin/audience_edit.html:92 +#: inginious/frontend/templates/course_admin/audience_edit.html:93 msgid "Tutor list" msgstr "" -#: inginious/frontend/templates/course_admin/audience_edit.html:124 +#: inginious/frontend/templates/course_admin/audience_edit.html:125 msgid "Add tutor" msgstr "" -#: inginious/frontend/templates/course_admin/audience_edit.html:137 +#: inginious/frontend/templates/course_admin/audience_edit.html:138 msgid "Student list" msgstr "" -#: inginious/frontend/templates/course_admin/audience_edit.html:153 +#: inginious/frontend/templates/course_admin/audience_edit.html:154 msgid "Remove student" msgstr "" -#: inginious/frontend/templates/course_admin/audience_edit.html:164 -#: inginious/frontend/templates/course_admin/student_list.html:435 +#: inginious/frontend/templates/course_admin/audience_edit.html:165 +#: inginious/frontend/templates/course_admin/student_list.html:436 msgid "Update" msgstr "" -#: inginious/frontend/templates/course_admin/danger_zone.html:41 +#: inginious/frontend/templates/course_admin/danger_zone.html:42 msgid "Archive data" msgstr "" -#: inginious/frontend/templates/course_admin/danger_zone.html:44 -#: inginious/frontend/templates/course_admin/danger_zone.html:138 -#: inginious/frontend/templates/course_admin/danger_zone.html:151 -#: inginious/frontend/templates/course_admin/danger_zone.html:159 -#: inginious/frontend/templates/course_admin/danger_zone.html:167 +#: inginious/frontend/templates/course_admin/danger_zone.html:45 +#: inginious/frontend/templates/course_admin/danger_zone.html:139 +#: inginious/frontend/templates/course_admin/danger_zone.html:152 +#: inginious/frontend/templates/course_admin/danger_zone.html:160 +#: inginious/frontend/templates/course_admin/danger_zone.html:168 msgid "Delete course" msgstr "" -#: inginious/frontend/templates/course_admin/danger_zone.html:52 -#: inginious/frontend/templates/course_admin/danger_zone.html:63 +#: inginious/frontend/templates/course_admin/danger_zone.html:53 +#: inginious/frontend/templates/course_admin/danger_zone.html:64 msgid "Archive course data" msgstr "" -#: inginious/frontend/templates/course_admin/danger_zone.html:55 +#: inginious/frontend/templates/course_admin/danger_zone.html:56 msgid "" "

This will reset and backup all course data (submissions, audiences, " "groups, user statistics) from the database.

To confirm your will, " "please type the course id below :

" msgstr "" -#: inginious/frontend/templates/course_admin/danger_zone.html:75 +#: inginious/frontend/templates/course_admin/danger_zone.html:76 msgid "Restore backup from {}" msgstr "" -#: inginious/frontend/templates/course_admin/danger_zone.html:79 +#: inginious/frontend/templates/course_admin/danger_zone.html:80 msgid "

This will restore your course data to {}. Are you sure ?

" msgstr "" -#: inginious/frontend/templates/course_admin/danger_zone.html:86 -#: inginious/frontend/templates/course_admin/danger_zone.html:110 +#: inginious/frontend/templates/course_admin/danger_zone.html:87 +#: inginious/frontend/templates/course_admin/danger_zone.html:111 msgid "Restore backup" msgstr "" -#: inginious/frontend/templates/course_admin/danger_zone.html:94 +#: inginious/frontend/templates/course_admin/danger_zone.html:95 msgid "Backups" msgstr "" -#: inginious/frontend/templates/course_admin/danger_zone.html:99 +#: inginious/frontend/templates/course_admin/danger_zone.html:100 msgid "backup date" msgstr "" -#: inginious/frontend/templates/course_admin/danger_zone.html:100 +#: inginious/frontend/templates/course_admin/danger_zone.html:101 msgid "download" msgstr "" -#: inginious/frontend/templates/course_admin/danger_zone.html:114 +#: inginious/frontend/templates/course_admin/danger_zone.html:115 msgid "Download backup" msgstr "" -#: inginious/frontend/templates/course_admin/danger_zone.html:141 +#: inginious/frontend/templates/course_admin/danger_zone.html:142 msgid "" "

This will permanently remove the course and all its data " -"(including tasks and backups) from INGInious.

To confirm your will," -" please type the course id below :

" +"(including backups) from INGInious.

To confirm your will, please " +"type the course id below :

" msgstr "" -#: inginious/frontend/templates/course_admin/danger_zone.html:163 +#: inginious/frontend/templates/course_admin/danger_zone.html:164 msgid "" "

This will permanently remove the course and all its data " "(including tasks and backups) from INGInious. Are you really sure ?

" msgstr "" #: inginious/frontend/templates/course_admin/menu.html:15 -msgid "How to create a task?" -msgstr "" - -#: inginious/frontend/templates/course_admin/menu.html:16 +#: inginious/frontend/templates/taskset_admin/menu.html:16 msgid "Documentation" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:6 -msgid "Settings" +#: inginious/frontend/templates/course_admin/settings.html:19 +#: inginious/frontend/templates/course_admin/settings.html:26 +msgid "Course settings" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:32 +#: inginious/frontend/templates/course_admin/settings.html:33 +#: inginious/frontend/templates/taskset_admin/settings.html:39 msgid "Settings saved." msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:40 +#: inginious/frontend/templates/course_admin/settings.html:41 msgid "Base" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:46 +#: inginious/frontend/templates/course_admin/settings.html:47 msgid "LTI" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:52 -msgid "User Settings" +#: inginious/frontend/templates/course_admin/settings.html:53 +msgid "Course Settings" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:68 +#: inginious/frontend/templates/course_admin/settings.html:69 +#: inginious/frontend/templates/taskset_admin/settings.html:62 msgid "Administrators" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:74 -msgid "Tutors" -msgstr "" - -#: inginious/frontend/templates/course_admin/settings.html:80 +#: inginious/frontend/templates/course_admin/settings.html:75 msgid "Description" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:86 +#: inginious/frontend/templates/course_admin/settings.html:81 msgid "Group attribution" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:93 +#: inginious/frontend/templates/course_admin/settings.html:88 msgid "Staff only" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:100 +#: inginious/frontend/templates/course_admin/settings.html:95 msgid "Staff and students" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:105 +#: inginious/frontend/templates/course_admin/settings.html:100 msgid "Enable LTI" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:112 +#: inginious/frontend/templates/course_admin/settings.html:107 msgid "" "Enable this option to allow your INGInious course to be used in external " "Learning Management Systems (LMS) such as Moodle or edX. Note that this " "will deactivate the group system." msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:121 +#: inginious/frontend/templates/course_admin/settings.html:116 msgid "Course preview" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:128 +#: inginious/frontend/templates/course_admin/settings.html:123 msgid "Allowed (read-only for non-registered users)" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:135 +#: inginious/frontend/templates/course_admin/settings.html:130 msgid "Not allowed" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:148 +#: inginious/frontend/templates/course_admin/settings.html:143 msgid "Course hidden from students" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:155 +#: inginious/frontend/templates/course_admin/settings.html:150 msgid "Always accessible" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:162 -#: inginious/frontend/templates/course_admin/settings.html:222 +#: inginious/frontend/templates/course_admin/settings.html:157 +#: inginious/frontend/templates/course_admin/settings.html:217 msgid "Custom:" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:166 -#: inginious/frontend/templates/course_admin/settings.html:226 +#: inginious/frontend/templates/course_admin/settings.html:161 +#: inginious/frontend/templates/course_admin/settings.html:221 msgid "From" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:179 -#: inginious/frontend/templates/course_admin/settings.html:238 +#: inginious/frontend/templates/course_admin/settings.html:174 +#: inginious/frontend/templates/course_admin/settings.html:233 msgid "To" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:201 +#: inginious/frontend/templates/course_admin/settings.html:196 msgid "Registration" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:208 +#: inginious/frontend/templates/course_admin/settings.html:203 msgid "Closed" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:215 +#: inginious/frontend/templates/course_admin/settings.html:210 msgid "Always open" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:260 +#: inginious/frontend/templates/course_admin/settings.html:255 msgid "Allow auto-unregistration" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:268 +#: inginious/frontend/templates/course_admin/settings.html:263 msgid "Yes" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:275 +#: inginious/frontend/templates/course_admin/settings.html:270 msgid "No" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:280 +#: inginious/frontend/templates/course_admin/settings.html:275 msgid "Registration password" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:283 +#: inginious/frontend/templates/course_admin/settings.html:278 msgid "" "Password needed for registration. Leave blank if you don't want to set a " "password." msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:288 +#: inginious/frontend/templates/course_admin/settings.html:283 msgid "Access control" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:295 +#: inginious/frontend/templates/course_admin/settings.html:290 msgid "No access control (everyone can register)" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:302 +#: inginious/frontend/templates/course_admin/settings.html:297 msgid "Check by username" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:309 +#: inginious/frontend/templates/course_admin/settings.html:304 msgid "Check by authentication binding" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:316 +#: inginious/frontend/templates/course_admin/settings.html:311 msgid "Check by email" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:321 +#: inginious/frontend/templates/course_admin/settings.html:316 msgid "Access control type" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:328 +#: inginious/frontend/templates/course_admin/settings.html:323 msgid "Accept" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:335 +#: inginious/frontend/templates/course_admin/settings.html:330 msgid "Deny" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:340 +#: inginious/frontend/templates/course_admin/settings.html:335 msgid "Access control list" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:342 +#: inginious/frontend/templates/course_admin/settings.html:337 msgid "Only used if access control is activated. Separate users by new lines." msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:348 -#: inginious/frontend/templates/course_admin/settings.html:350 +#: inginious/frontend/templates/course_admin/settings.html:343 +#: inginious/frontend/templates/course_admin/settings.html:345 msgid "External platform URL" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:354 +#: inginious/frontend/templates/course_admin/settings.html:349 msgid "LTI keys" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:356 +#: inginious/frontend/templates/course_admin/settings.html:351 msgid "" "LTI keys in the form name:key, separated by newlines. Please ensure keys " "are long enough and random." msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:361 +#: inginious/frontend/templates/course_admin/settings.html:356 msgid "Send back grades" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:368 +#: inginious/frontend/templates/course_admin/settings.html:363 msgid "" "Enable this to send back grades to the calling Tool Consumer (which is, " "most of the time, your LMS). INGInious will deactivate students' access " @@ -2405,320 +2548,321 @@ msgid "" " be only available from the LTI interface." msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:383 +#: inginious/frontend/templates/course_admin/settings.html:378 msgid "New tag" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:388 -#: inginious/frontend/templates/course_admin/settings.html:447 -#: inginious/frontend/templates/course_admin/submissions.html:66 +#: inginious/frontend/templates/course_admin/settings.html:383 +#: inginious/frontend/templates/course_admin/settings.html:442 +#: inginious/frontend/templates/course_admin/submissions.html:67 msgid "id" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:389 +#: inginious/frontend/templates/course_admin/settings.html:384 msgid "name" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:390 -#: inginious/frontend/templates/course_admin/settings.html:448 +#: inginious/frontend/templates/course_admin/settings.html:385 +#: inginious/frontend/templates/course_admin/settings.html:443 msgid "description" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:391 +#: inginious/frontend/templates/course_admin/settings.html:386 msgid "show to students" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:392 -#: inginious/frontend/templates/course_admin/settings.html:449 +#: inginious/frontend/templates/course_admin/settings.html:387 +#: inginious/frontend/templates/course_admin/settings.html:444 msgid "type " msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:405 -#: inginious/frontend/templates/course_admin/settings.html:423 +#: inginious/frontend/templates/course_admin/settings.html:400 +#: inginious/frontend/templates/course_admin/settings.html:418 msgid "Skill" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:406 -#: inginious/frontend/templates/course_admin/settings.html:424 +#: inginious/frontend/templates/course_admin/settings.html:401 +#: inginious/frontend/templates/course_admin/settings.html:419 msgid "Misconception" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:412 +#: inginious/frontend/templates/course_admin/settings.html:407 msgid "Unknown name" msgstr "" -#: inginious/frontend/templates/course_admin/settings.html:442 +#: inginious/frontend/templates/course_admin/settings.html:437 msgid "New field" msgstr "" -#: inginious/frontend/templates/course_admin/stats.html:40 +#: inginious/frontend/templates/course_admin/stats.html:41 msgid "Student progress" msgstr "" -#: inginious/frontend/templates/course_admin/stats.html:53 -#: inginious/frontend/templates/course_admin/submissions.html:60 +#: inginious/frontend/templates/course_admin/stats.html:54 +#: inginious/frontend/templates/course_admin/submissions.html:61 msgid "task name" msgstr "" -#: inginious/frontend/templates/course_admin/stats.html:54 +#: inginious/frontend/templates/course_admin/stats.html:55 msgid "# student viewed" msgstr "" -#: inginious/frontend/templates/course_admin/stats.html:55 +#: inginious/frontend/templates/course_admin/stats.html:56 msgid "# student attempted" msgstr "" -#: inginious/frontend/templates/course_admin/stats.html:56 +#: inginious/frontend/templates/course_admin/stats.html:57 msgid "# student succeeded" msgstr "" -#: inginious/frontend/templates/course_admin/stats.html:57 +#: inginious/frontend/templates/course_admin/stats.html:58 msgid "# attempts" msgstr "" -#: inginious/frontend/templates/course_admin/stats.html:64 -#: inginious/frontend/templates/course_admin/student_info.html:42 -#: inginious/frontend/templates/course_admin/student_info.html:82 -#: inginious/frontend/templates/course_admin/student_list.html:161 +#: inginious/frontend/templates/course_admin/stats.html:65 +#: inginious/frontend/templates/course_admin/student_info.html:43 +#: inginious/frontend/templates/course_admin/student_info.html:83 +#: inginious/frontend/templates/course_admin/student_list.html:162 msgid "View all submissions" msgstr "" -#: inginious/frontend/templates/course_admin/stats.html:105 +#: inginious/frontend/templates/course_admin/stats.html:106 msgid "Global statistics" msgstr "" -#: inginious/frontend/templates/course_admin/stats.html:109 +#: inginious/frontend/templates/course_admin/stats.html:110 msgid "Submissions statistics" msgstr "" -#: inginious/frontend/templates/course_admin/stats.html:127 +#: inginious/frontend/templates/course_admin/stats.html:128 msgid "Tag statistics" msgstr "" -#: inginious/frontend/templates/course_admin/stats.html:132 +#: inginious/frontend/templates/course_admin/stats.html:133 msgid "Tag" msgstr "" -#: inginious/frontend/templates/course_admin/stats.html:133 -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/submission_storage.html:9 +#: inginious/frontend/templates/course_admin/stats.html:134 +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_storage.html:7 +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_storage.html:16 msgid "All submissions" msgstr "" -#: inginious/frontend/templates/course_admin/stats.html:134 +#: inginious/frontend/templates/course_admin/stats.html:135 msgid "Best submissions" msgstr "" -#: inginious/frontend/templates/course_admin/stats.html:262 +#: inginious/frontend/templates/course_admin/stats.html:263 msgid "user" msgstr "" -#: inginious/frontend/templates/course_admin/stats.html:262 -#: inginious/frontend/templates/course_admin/student_info.html:36 +#: inginious/frontend/templates/course_admin/stats.html:263 +#: inginious/frontend/templates/course_admin/student_info.html:37 msgid "# submissions" msgstr "" -#: inginious/frontend/templates/course_admin/stats.html:262 +#: inginious/frontend/templates/course_admin/stats.html:263 msgid "# valid submissions" msgstr "" -#: inginious/frontend/templates/course_admin/student_info.html:20 -#: inginious/frontend/templates/course_admin/student_list.html:35 -#: inginious/frontend/templates/course_admin/student_list.html:396 -#: inginious/frontend/templates/course_admin/submission.html:26 +#: inginious/frontend/templates/course_admin/student_info.html:21 +#: inginious/frontend/templates/course_admin/student_list.html:36 +#: inginious/frontend/templates/course_admin/student_list.html:397 +#: inginious/frontend/templates/course_admin/submission.html:27 msgid "Students" msgstr "" -#: inginious/frontend/templates/course_admin/student_info.html:29 +#: inginious/frontend/templates/course_admin/student_info.html:30 msgid "Statistics for student {realname} ({username})" msgstr "" -#: inginious/frontend/templates/course_admin/student_info.html:34 +#: inginious/frontend/templates/course_admin/student_info.html:35 msgid "task" msgstr "" -#: inginious/frontend/templates/course_admin/student_info.html:35 +#: inginious/frontend/templates/course_admin/student_info.html:36 msgid "status" msgstr "" -#: inginious/frontend/templates/course_admin/student_info.html:62 +#: inginious/frontend/templates/course_admin/student_info.html:63 msgid "Not served by the task dispenser" msgstr "" -#: inginious/frontend/templates/course_admin/student_info.html:68 +#: inginious/frontend/templates/course_admin/student_info.html:69 msgid "Not viewed" msgstr "" -#: inginious/frontend/templates/course_admin/student_info.html:70 +#: inginious/frontend/templates/course_admin/student_info.html:71 msgid "Not attempted (viewed)" msgstr "" -#: inginious/frontend/templates/course_admin/student_info.html:72 -#: inginious/frontend/templates/course_admin/student_info.html:75 +#: inginious/frontend/templates/course_admin/student_info.html:73 +#: inginious/frontend/templates/course_admin/student_info.html:76 msgid "View evaluation submission" msgstr "" -#: inginious/frontend/templates/course_admin/student_info.html:85 +#: inginious/frontend/templates/course_admin/student_info.html:86 msgid "Reset state" msgstr "" -#: inginious/frontend/templates/course_admin/student_info.html:98 +#: inginious/frontend/templates/course_admin/student_info.html:99 msgid "Reset State" msgstr "" -#: inginious/frontend/templates/course_admin/student_info.html:102 +#: inginious/frontend/templates/course_admin/student_info.html:103 msgid "This will reset the state for {}. Are you sure ?" msgstr "" -#: inginious/frontend/templates/course_admin/student_info.html:107 +#: inginious/frontend/templates/course_admin/student_info.html:108 #: inginious/frontend/templates/course_admin/submissions_query.html:251 msgid "Reset" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:39 +#: inginious/frontend/templates/course_admin/student_list.html:40 msgid "Teaching staff" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:58 +#: inginious/frontend/templates/course_admin/student_list.html:59 msgid "Number of students" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:81 +#: inginious/frontend/templates/course_admin/student_list.html:82 msgid "Upload audiences" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:82 -#: inginious/frontend/templates/course_admin/student_list.html:212 +#: inginious/frontend/templates/course_admin/student_list.html:83 +#: inginious/frontend/templates/course_admin/student_list.html:213 msgid "Download structure" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:104 +#: inginious/frontend/templates/course_admin/student_list.html:105 msgid "Upload audience creation" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:108 -#: inginious/frontend/templates/course_admin/student_list.html:226 +#: inginious/frontend/templates/course_admin/student_list.html:109 +#: inginious/frontend/templates/course_admin/student_list.html:227 msgid "Note : Please refer to documentation for file format" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:115 +#: inginious/frontend/templates/course_admin/student_list.html:116 msgid "Upload (will erase current repartition)" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:127 +#: inginious/frontend/templates/course_admin/student_list.html:128 msgid "Download audiences structure" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:132 +#: inginious/frontend/templates/course_admin/student_list.html:133 msgid "Prefered field" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:16 -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:24 -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:39 -#: inginious/frontend/templates/course_admin/student_list.html:141 -#: inginious/frontend/templates/course_admin/submissions.html:250 +#: inginious/frontend/templates/course_admin/student_list.html:142 +#: inginious/frontend/templates/course_admin/submissions.html:251 +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:16 +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:24 +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:39 msgid "Download" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:152 +#: inginious/frontend/templates/course_admin/student_list.html:153 msgid "audience" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:153 +#: inginious/frontend/templates/course_admin/student_list.html:154 msgid "# students" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:154 +#: inginious/frontend/templates/course_admin/student_list.html:155 #: inginious/frontend/templates/course_admin/student_list_table.html:18 msgid "# task tried" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:155 +#: inginious/frontend/templates/course_admin/student_list.html:156 #: inginious/frontend/templates/course_admin/student_list_table.html:19 msgid "# task done" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:167 +#: inginious/frontend/templates/course_admin/student_list.html:168 msgid "My audience(s)" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:167 +#: inginious/frontend/templates/course_admin/student_list.html:168 msgid "Other audience(s)" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:183 +#: inginious/frontend/templates/course_admin/student_list.html:184 msgid "Edit audience" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:200 +#: inginious/frontend/templates/course_admin/student_list.html:201 msgid "New audience description" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:201 +#: inginious/frontend/templates/course_admin/student_list.html:202 msgid "New audience" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:211 +#: inginious/frontend/templates/course_admin/student_list.html:212 msgid "Upload structure" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:222 +#: inginious/frontend/templates/course_admin/student_list.html:223 msgid "Upload course structure" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:228 +#: inginious/frontend/templates/course_admin/student_list.html:229 msgid "Course structure" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:233 +#: inginious/frontend/templates/course_admin/student_list.html:234 msgid "Upload (will erase current settings)" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:265 +#: inginious/frontend/templates/course_admin/student_list.html:266 msgid "Student username (will be registered) :" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:268 +#: inginious/frontend/templates/course_admin/student_list.html:269 msgid "Student username" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:286 +#: inginious/frontend/templates/course_admin/student_list.html:287 msgid "Ungrouped students" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:312 -#: inginious/frontend/templates/course_admin/student_list.html:333 +#: inginious/frontend/templates/course_admin/student_list.html:313 +#: inginious/frontend/templates/course_admin/student_list.html:334 msgid "Group description" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:312 -#: inginious/frontend/templates/course_admin/student_list.html:420 +#: inginious/frontend/templates/course_admin/student_list.html:313 +#: inginious/frontend/templates/course_admin/student_list.html:421 msgid "New group" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:315 -#: inginious/frontend/templates/course_admin/student_list.html:336 +#: inginious/frontend/templates/course_admin/student_list.html:316 +#: inginious/frontend/templates/course_admin/student_list.html:337 msgid "Max group size :" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:321 -#: inginious/frontend/templates/course_admin/student_list.html:342 +#: inginious/frontend/templates/course_admin/student_list.html:322 +#: inginious/frontend/templates/course_admin/student_list.html:343 msgid "Delete group" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:353 +#: inginious/frontend/templates/course_admin/student_list.html:354 msgid "Restrict to audiences" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:365 +#: inginious/frontend/templates/course_admin/student_list.html:366 msgid "Add audience" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:425 +#: inginious/frontend/templates/course_admin/student_list.html:426 msgid "Clean groups" msgstr "" -#: inginious/frontend/templates/course_admin/student_list.html:430 +#: inginious/frontend/templates/course_admin/student_list.html:431 msgid "Delete all groups" msgstr "" @@ -2750,124 +2894,124 @@ msgstr "" msgid "Remove {}" msgstr "" -#: inginious/frontend/templates/course_admin/submission.html:43 +#: inginious/frontend/templates/course_admin/submission.html:44 msgid "Submission {}" msgstr "" -#: inginious/frontend/templates/course_admin/submission.html:45 +#: inginious/frontend/templates/course_admin/submission.html:46 msgid "" "This page show what was shown to the student when (s)he made his/her " "submission." msgstr "" -#: inginious/frontend/templates/course_admin/submission.html:50 +#: inginious/frontend/templates/course_admin/submission.html:51 msgid "Click here to hide/display context informations" msgstr "" -#: inginious/frontend/templates/course_admin/submission.html:56 +#: inginious/frontend/templates/course_admin/submission.html:57 msgid "Download full submission" msgstr "" -#: inginious/frontend/templates/course_admin/submission.html:63 -#: inginious/frontend/templates/course_admin/submissions.html:183 +#: inginious/frontend/templates/course_admin/submission.html:64 +#: inginious/frontend/templates/course_admin/submissions.html:184 msgid "Replay submission" msgstr "" -#: inginious/frontend/templates/course_admin/submission.html:69 +#: inginious/frontend/templates/course_admin/submission.html:70 msgid "Replay as {}" msgstr "" -#: inginious/frontend/templates/course_admin/submission.html:75 +#: inginious/frontend/templates/course_admin/submission.html:76 msgid "Replay/debug as {}" msgstr "" -#: inginious/frontend/templates/course_admin/submission.html:135 +#: inginious/frontend/templates/course_admin/submission.html:136 msgid "This problem id is not defined in the task." msgstr "" -#: inginious/frontend/templates/course_admin/submission.html:137 +#: inginious/frontend/templates/course_admin/submission.html:138 msgid "This problem is not present in the submission." msgstr "" -#: inginious/frontend/templates/course_admin/submissions.html:43 +#: inginious/frontend/templates/course_admin/submissions.html:44 msgid "submissions selected" msgstr "" -#: inginious/frontend/templates/course_admin/submissions.html:53 +#: inginious/frontend/templates/course_admin/submissions.html:54 msgid "student name" msgstr "" -#: inginious/frontend/templates/course_admin/submissions.html:62 +#: inginious/frontend/templates/course_admin/submissions.html:63 msgid "taskid" msgstr "" -#: inginious/frontend/templates/course_admin/submissions.html:68 +#: inginious/frontend/templates/course_admin/submissions.html:69 msgid "submitted on" msgstr "" -#: inginious/frontend/templates/course_admin/submissions.html:69 +#: inginious/frontend/templates/course_admin/submissions.html:70 msgid "replayed on" msgstr "" -#: inginious/frontend/templates/course_admin/submissions.html:70 +#: inginious/frontend/templates/course_admin/submissions.html:71 msgid "result" msgstr "" -#: inginious/frontend/templates/course_admin/submissions.html:72 +#: inginious/frontend/templates/course_admin/submissions.html:73 msgid "tags" msgstr "" -#: inginious/frontend/templates/course_admin/submissions.html:80 +#: inginious/frontend/templates/course_admin/submissions.html:81 msgid "Download current selection" msgstr "" -#: inginious/frontend/templates/course_admin/submissions.html:83 +#: inginious/frontend/templates/course_admin/submissions.html:84 msgid "Replay current selection" msgstr "" -#: inginious/frontend/templates/course_admin/submissions.html:135 -#: inginious/frontend/templates/course_admin/submissions.html:267 +#: inginious/frontend/templates/course_admin/submissions.html:136 +#: inginious/frontend/templates/course_admin/submissions.html:268 msgid "Waiting" msgstr "" -#: inginious/frontend/templates/course_admin/submissions.html:179 +#: inginious/frontend/templates/course_admin/submissions.html:180 msgid "View submission" msgstr "" -#: inginious/frontend/templates/course_admin/submissions.html:180 +#: inginious/frontend/templates/course_admin/submissions.html:181 msgid "Download submission" msgstr "" -#: inginious/frontend/templates/course_admin/submissions.html:226 +#: inginious/frontend/templates/course_admin/submissions.html:227 msgid "Download format" msgstr "" -#: inginious/frontend/templates/course_admin/submissions.html:230 +#: inginious/frontend/templates/course_admin/submissions.html:231 msgid "Folder format" msgstr "" -#: inginious/frontend/templates/course_admin/submissions.html:233 +#: inginious/frontend/templates/course_admin/submissions.html:234 msgid "taskid/username" msgstr "" -#: inginious/frontend/templates/course_admin/submissions.html:233 +#: inginious/frontend/templates/course_admin/submissions.html:234 msgid "taskid/audience" msgstr "" -#: inginious/frontend/templates/course_admin/submissions.html:233 +#: inginious/frontend/templates/course_admin/submissions.html:234 msgid "username/taskid" msgstr "" -#: inginious/frontend/templates/course_admin/submissions.html:233 +#: inginious/frontend/templates/course_admin/submissions.html:234 msgid "audience/taskid" msgstr "" -#: inginious/frontend/templates/course_admin/submissions.html:240 +#: inginious/frontend/templates/course_admin/submissions.html:241 #: inginious/frontend/templates/course_admin/submissions_query.html:154 msgid "Only evaluation submissions" msgstr "" -#: inginious/frontend/templates/course_admin/submissions.html:244 +#: inginious/frontend/templates/course_admin/submissions.html:245 msgid "Simplified file tree" msgstr "" @@ -3009,559 +3153,708 @@ msgstr "" msgid "Filter" msgstr "" -#: inginious/frontend/templates/course_admin/task_edit.html:6 -msgid "Edit {}" +#: inginious/frontend/templates/course_admin/task_list.html:30 +#: inginious/frontend/templates/course_admin/task_list.html:42 +#: inginious/frontend/templates/taskset_admin/template.html:30 +#: inginious/frontend/templates/taskset_admin/template.html:42 +msgid "Switch task dispenser" msgstr "" -#: inginious/frontend/templates/course_admin/task_edit.html:21 -#: inginious/frontend/templates/course_admin/task_edit.html:28 -msgid "Edit task \"{}\"" +#: inginious/frontend/templates/course_admin/task_list.html:48 +msgid "" +"This will wipe your current course structure. Tasks file won't be deleted" +" but you'll have to import them again." msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/task_buttons.html:10 -#: inginious/frontend/templates/course_admin/task_edit.html:32 -msgid "View task" +#: inginious/frontend/templates/course_admin/task_list.html:49 +#: inginious/frontend/templates/taskset_admin/template.html:49 +msgid "New task dispenser:" msgstr "" -#: inginious/frontend/templates/course_admin/task_edit.html:49 -msgid "Environment" +#: inginious/frontend/templates/course_admin/task_list.html:75 +#: inginious/frontend/templates/taskset_admin/template.html:75 +msgid "Changes saved." msgstr "" -#: inginious/frontend/templates/course_admin/task_edit.html:52 -msgid "Subproblems" +#: inginious/frontend/templates/course_admin/task_list.html:85 +msgid "" +"This course currently includes legacy tasks with importable data to the " +"task dispenser settings. The taskset owner can clean the task files." msgstr "" -#: inginious/frontend/templates/course_admin/task_edit.html:55 -msgid "Task files" +#: inginious/frontend/templates/course_admin/task_list.html:87 +#: inginious/frontend/templates/taskset_admin/template.html:87 +msgid "Import settings" msgstr "" -#: inginious/frontend/templates/course_admin/task_edit.html:79 -msgid "File list" +#: inginious/frontend/templates/preferences/bindings.html:46 +msgid "Refresh fields" msgstr "" -#: inginious/frontend/templates/course_admin/task_edit.html:109 -msgid "Problem id:" +#: inginious/frontend/templates/preferences/bindings.html:58 +msgid "Identifier" msgstr "" -#: inginious/frontend/templates/course_admin/task_edit.html:126 -msgid "A title for this question" +#: inginious/frontend/templates/preferences/bindings.html:60 +msgid "Additional fields" msgstr "" -#: inginious/frontend/templates/course_admin/task_edit.html:144 -msgid "Are you sure that you want to delete this subproblem?" +#: inginious/frontend/templates/preferences/bindings.html:74 +msgid "Add a new binding" msgstr "" -#: inginious/frontend/templates/course_admin/task_list.html:29 -#: inginious/frontend/templates/course_admin/task_list.html:41 -msgid "Switch task dispenser" +#: inginious/frontend/templates/preferences/bindings.html:78 +msgid "Select a authentication binding" msgstr "" -#: inginious/frontend/templates/course_admin/task_list.html:47 -msgid "" -"This will wipe your current course structure. Tasks file won't be deleted" -" but you'll have to import them again." +#: inginious/frontend/templates/preferences/bindings.html:87 +msgid "Add new binding" msgstr "" -#: inginious/frontend/templates/course_admin/task_list.html:48 -msgid "New task dispenser:" +#: inginious/frontend/templates/preferences/delete.html:41 +msgid "To confirm, type your email address :" msgstr "" -#: inginious/frontend/templates/course_admin/task_list.html:69 -msgid "WebDAV access" +#: inginious/frontend/templates/preferences/delete.html:56 +msgid "This will also delete all your data ! Are you really sure ?" msgstr "" -#: inginious/frontend/templates/course_admin/task_list.html:74 -msgid "Use this URL to access your course folder using WebDAV:" +#: inginious/frontend/templates/preferences/profile.html:40 +msgid "Please set a new username" msgstr "" -#: inginious/frontend/templates/course_admin/task_list.html:77 -msgid "URL" +#: inginious/frontend/templates/preferences/profile.html:54 +msgid "Username :" msgstr "" -#: inginious/frontend/templates/course_admin/task_list.html:97 -msgid "Changes saved." +#: inginious/frontend/templates/preferences/profile.html:66 +msgid "Language :" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/basic.html:13 -msgid "Filetype" +#: inginious/frontend/templates/preferences/profile.html:75 +msgid "Old password :" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/basic.html:30 -#: inginious/frontend/templates/course_admin/subproblems/code.html:5 -#: inginious/frontend/templates/course_admin/subproblems/file.html:5 -#: inginious/frontend/templates/course_admin/subproblems/match.html:5 -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:5 -msgid "Context" +#: inginious/frontend/templates/preferences/profile.html:79 +msgid "New password (min. 6 characters) :" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/basic.html:36 -msgid "Author" +#: inginious/frontend/templates/preferences/profile.html:83 +msgid "Confirm new password :" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/basic.html:43 -msgid "Your name" +#: inginious/frontend/templates/preferences/profile.html:94 +msgid "Save my profile" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/basic.html:47 -msgid "Contact URL" +#: inginious/frontend/templates/task_dispensers/task_list.html:34 +msgid "deadline reached" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/basic.html:53 -msgid "Random inputs" +#: inginious/frontend/templates/task_dispensers/task_list.html:39 +msgid "Weight : " msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/basic.html:60 -msgid "Regenerate random inputs for each reloading of the task page" +#: inginious/frontend/templates/task_dispensers_admin/combinatory_test.html:5 +msgid "Combinatory tests" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/basic.html:60 -msgid "Regenerate input random" +#: inginious/frontend/templates/task_dispensers_admin/combinatory_test.html:9 +msgid "" +"Only the specified amount of tasks to be displayed will be shown to the " +"user. There is no state: changing the amount of tasks per section, " +"section position or section title will affect the effective task set a " +"user will be displayed." msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/env_generic_docker_oci.html:5 -msgid "Timeout limit (in seconds)" +#: inginious/frontend/templates/task_dispensers_admin/combinatory_test.html:11 +msgid "" +"As an administrator, the only way to access all the tasks is through this" +" administration page." msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/env_generic_docker_oci.html:11 -msgid "Hard timeout limit (in seconds) Default to 3*timeout" +#: inginious/frontend/templates/task_dispensers_admin/combinatory_test.html:27 +#: inginious/frontend/templates/task_dispensers_admin/toc.html:17 +msgid "Add section" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/env_generic_docker_oci.html:18 -msgid "Memory limit (in megabytes)" +#: inginious/frontend/templates/task_dispensers_admin/combinatory_test.html:33 +#: inginious/frontend/templates/task_dispensers_admin/toc.html:23 +msgid "New section" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/env_generic_docker_oci.html:24 -msgid "Allow internet access inside the grading container?" +#: inginious/frontend/templates/task_dispensers_admin/empty_section.html:26 +msgid "Drag tasks or section here." msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/env_generic_docker_oci.html:24 -msgid "It also adds and configures local interfaces with IPv4 and IPv6." +#: inginious/frontend/templates/task_dispensers_admin/section_menu.html:10 +#: inginious/frontend/templates/task_dispensers_admin/util.html:80 +msgid "Add tasks" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/env_generic_docker_oci.html:34 -msgid "Allow ssh?" +#: inginious/frontend/templates/task_dispensers_admin/section_menu.html:14 +msgid "Add subsection" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/env_generic_docker_oci.html:45 -msgid "" -"Custom command to be run in container (instead of running the run " -"script)" +#: inginious/frontend/templates/task_dispensers_admin/section_menu.html:18 +msgid "Rename" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/env_generic_docker_oci.html:48 -msgid "Optional" +#: inginious/frontend/templates/task_dispensers_admin/task_buttons.html:6 +msgid "Task settings" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/env_generic_docker_oci.html:53 -msgid "Are the task's responses written in HTML instead of restructuredText?" +#: inginious/frontend/templates/task_dispensers_admin/task_buttons.html:11 +msgid "View task" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/environment.html:5 -msgid "Grading environment type" +#: inginious/frontend/templates/task_dispensers_admin/task_buttons.html:37 +#: inginious/frontend/templates/task_dispensers_admin/util_delete_modal.html:11 +#: inginious/frontend/templates/task_dispensers_admin/util_delete_modal.html:33 +#: inginious/frontend/templates/taskset_admin/settings.html:130 +#: inginious/frontend/templates/taskset_admin/settings.html:152 +msgid "Delete task" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/environment.html:20 -msgid "Grading environment" +#: inginious/frontend/templates/task_dispensers_admin/task_list.html:49 +msgid "No valid task with id:" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/file_modals.html:9 -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:60 -msgid "Upload a file" +#: inginious/frontend/templates/task_dispensers_admin/task_list.html:54 +msgid "Delete invalid task" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/file_modals.html:16 -msgid "File:" +#: inginious/frontend/templates/task_dispensers_admin/util.html:51 +msgid "Edit selected tasks" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/file_modals.html:20 -msgid "File path:" +#: inginious/frontend/templates/task_dispensers_admin/util.html:84 +msgid "Search..." msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/file_modals.html:27 -msgid "Upload" +#: inginious/frontend/templates/task_dispensers_admin/util.html:86 +msgid "No unassigned tasks in the taskset." msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:15 -msgid "Path" +#: inginious/frontend/templates/task_dispensers_admin/util.html:126 +msgid "Grouped actions" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:17 -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:44 -msgid "Edit" +#: inginious/frontend/templates/task_dispensers_admin/util.html:128 +msgid "Change selection" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:18 -msgid "Move" +#: inginious/frontend/templates/task_dispensers_admin/util.html:130 +msgid "Move to section..." msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:49 -msgid "Move/Rename" +#: inginious/frontend/templates/task_dispensers_admin/util.html:133 +msgid "Compact view" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/files.html:59 -msgid "Create a new file" +#: inginious/frontend/templates/task_dispensers_admin/util.html:137 +msgid "On setting: " msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/subproblems.html:8 -msgid "New problem id" +#: inginious/frontend/templates/task_dispensers_admin/util_delete_modal.html:9 +#: inginious/frontend/templates/task_dispensers_admin/util_delete_modal.html:31 +msgid "Delete section" msgstr "" -#: inginious/frontend/templates/course_admin/edit_tabs/subproblems.html:13 -msgid "Problem type" +#: inginious/frontend/templates/task_dispensers_admin/util_delete_modal.html:13 +msgid "Delete selected tasks" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/code.html:12 -msgid "Language" +#: inginious/frontend/templates/task_dispensers_admin/util_delete_modal.html:19 +msgid "Once saved, this will delete this section, all its subsections." msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/code.html:14 -msgid "Language used in this question (c/cpp/java/python/oz/...)" +#: inginious/frontend/templates/task_dispensers_admin/util_delete_modal.html:21 +msgid "Once saved, this will remove the task." msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/code.html:23 -msgid "Multiline code" +#: inginious/frontend/templates/task_dispensers_admin/util_delete_modal.html:25 +msgid "Wipe all submissions" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/code.html:25 -msgid "Single-line code" +#: inginious/frontend/templates/task_dispensers_admin/util_delete_modal.html:35 +msgid "Delete tasks" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/code.html:31 -msgid "Optional?" +#: inginious/frontend/templates/task_dispensers_admin/util_move_modal.html:8 +msgid "Move selected tasks" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/code.html:41 -msgid "Default value" +#: inginious/frontend/templates/task_dispensers_admin/util_move_modal.html:12 +msgid "Please select the section to move the selected tasks into." msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/file.html:11 -msgid "Allowed file extensions (including the dot, separated by commas)" +#: inginious/frontend/templates/task_dispensers_admin/util_move_modal.html:18 +msgid "Move tasks" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/file.html:17 -msgid "Maximum file size (in bytes)" +#: inginious/frontend/templates/task_dispensers_admin/util_task_edit_modal.html:8 +msgid "Edit task {}" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/file.html:26 -msgid "File upload" +#: inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html:7 +#: inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html:21 +msgid "Never" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/match.html:11 -msgid "Answer" +#: inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html:8 +#: inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html:27 +msgid "Always" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/match.html:13 -msgid "Answer of this question" +#: inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html:10 +#: inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html:34 +msgid "Custom, from: {} to: {}" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/match.html:17 -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:27 -msgid "Count error only at top of the page?" +#: inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html:60 +msgid "Students can still submit after this date" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:11 -msgid "Can select multiple answers" +#: inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html:60 +msgid "Soft Deadline" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:19 -msgid "Unshuffle answers" +#: inginious/frontend/templates/task_dispensers_admin/config_items/categories.html:11 +msgid "Tag name, separated by commas" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:35 -msgid "Number of choices to display (0 = all choices)" +#: inginious/frontend/templates/task_dispensers_admin/config_items/evaluation_mode.html:6 +#: inginious/frontend/templates/task_dispensers_admin/config_items/evaluation_mode.html:10 +msgid "Evaluation submission" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:41 -msgid "Success message" +#: inginious/frontend/templates/task_dispensers_admin/config_items/groups.html:7 +#: inginious/frontend/templates/task_dispensers_admin/config_items/groups.html:26 +msgid "Per group" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:45 -msgid "An optional success message" +#: inginious/frontend/templates/task_dispensers_admin/config_items/groups.html:8 +#: inginious/frontend/templates/task_dispensers_admin/config_items/groups.html:17 +msgid "Individually" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:49 -msgid "Error message" +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_limit.html:6 +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_limit.html:17 +msgid "Submission limits" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:53 -msgid "An optional error message" +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_limit.html:10 +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_limit.html:26 +msgid "Hard limit: {nbr_submissions} submission(s)" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:58 -msgid "Choices" +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_limit.html:13 +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_limit.html:34 +msgid "Soft limit: {nbr_submissions} submission(s) every {nbr_hours} hour(s)" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice.html:63 -msgid "Add new choice" +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_storage.html:9 +#: inginious/frontend/templates/task_dispensers_admin/config_items/submission_storage.html:22 +msgid "Only the last {nbr_submissions} submissions" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice_templates.html:8 -msgid "Content" +#: inginious/frontend/templates/task_dispensers_admin/config_items/weight.html:6 +#: inginious/frontend/templates/task_dispensers_admin/config_items/weight.html:8 +msgid "Grade weight (in comparison to other tasks)" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice_templates.html:15 -msgid "Feedback message (displayed when selected)" +#: inginious/frontend/templates/tasks/file.html:7 +msgid "Click here to download the file you submitted previously" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice_templates.html:19 -msgid "An optional feedback message" +#: inginious/frontend/templates/tasks/file.html:22 +msgid "Max file size:" msgstr "" -#: inginious/frontend/templates/course_admin/subproblems/multiple_choice_templates.html:26 -msgid "Valid ?" +#: inginious/frontend/templates/tasks/file.html:23 +msgid "Allowed extensions:" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/combinatory_test.html:7 -msgid "Combinatory tests" +#: inginious/frontend/templates/taskset_admin/danger_zone.html:15 +#: inginious/frontend/templates/taskset_admin/settings.html:15 +#: inginious/frontend/templates/taskset_admin/task_edit.html:16 +#: inginious/frontend/templates/taskset_admin/template.html:16 +msgid "Tasksets" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/danger_zone.html:40 +#: inginious/frontend/templates/taskset_admin/danger_zone.html:56 +#: inginious/frontend/templates/taskset_admin/danger_zone.html:64 +#: inginious/frontend/templates/taskset_admin/danger_zone.html:72 +msgid "Delete taskset" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/combinatory_test.html:11 +#: inginious/frontend/templates/taskset_admin/danger_zone.html:44 msgid "" -"Only the specified amount of tasks to be displayed will be shown to the " -"user. There is no state: changing the amount of tasks per section, " -"section position or section title will affect the effective task set a " -"user will be displayed." +"This taskset is used by at least one course. Please delete the course(s) " +"before deleting the taskset." msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/combinatory_test.html:13 +#: inginious/frontend/templates/taskset_admin/danger_zone.html:46 msgid "" -"As an administrator, the only way to access all the tasks is through this" -" administration page." +"

This will permanently remove the taskset and all the tasks " +"files from INGInious.

To confirm your will, please type the taskset" +" id below :

" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/combinatory_test.html:27 -#: inginious/frontend/templates/course_admin/task_dispensers/toc.html:17 -msgid "Add section" +#: inginious/frontend/templates/taskset_admin/danger_zone.html:68 +msgid "" +"

This will permanently remove the taskset and all the tasks " +"files from INGInious. Are you really sure ?

" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/combinatory_test.html:33 -#: inginious/frontend/templates/course_admin/task_dispensers/toc.html:23 -msgid "New section" +#: inginious/frontend/templates/taskset_admin/menu.html:15 +msgid "How to create a task?" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/empty_section.html:25 -msgid "Drag tasks or section here." +#: inginious/frontend/templates/taskset_admin/settings.html:19 +msgid "Taskset settings" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/section_menu.html:10 -#: inginious/frontend/templates/course_admin/task_dispensers/util.html:78 -msgid "Add tasks" +#: inginious/frontend/templates/taskset_admin/settings.html:26 +msgid "Edit taskset {}" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/section_menu.html:14 -msgid "Add subsection" +#: inginious/frontend/templates/taskset_admin/settings.html:68 +msgid "Short description" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/section_menu.html:18 -msgid "Rename" +#: inginious/frontend/templates/taskset_admin/settings.html:74 +msgid "Instantiable by" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/task_buttons.html:6 -msgid "Task parameters" +#: inginious/frontend/templates/taskset_admin/settings.html:77 +msgid "Taskset owners only" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/task_buttons.html:23 -#: inginious/frontend/templates/course_admin/task_dispensers/util_delete_modal.html:11 -#: inginious/frontend/templates/course_admin/task_dispensers/util_delete_modal.html:33 -msgid "Delete task" +#: inginious/frontend/templates/taskset_admin/settings.html:80 +msgid "Everyone" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/task_list.html:35 -msgid "No valid task with id:" +#: inginious/frontend/templates/taskset_admin/settings.html:93 +msgid "WebDAV access" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/task_list.html:39 -#: inginious/frontend/templates/course_admin/task_dispensers/util.html:123 -msgid "Delete invalid task" +#: inginious/frontend/templates/taskset_admin/settings.html:98 +msgid "Use this URL to access your taskset folder using WebDAV:" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/settings.html:101 +msgid "URL" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/settings.html:113 +msgid "Task list" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/settings.html:114 +msgid "Add new task" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/util.html:82 -#: inginious/frontend/templates/course_admin/task_dispensers/util.html:85 +#: inginious/frontend/templates/taskset_admin/settings.html:144 +msgid "Delete task {}" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/settings.html:148 +msgid "" +"This will permanently remove the task {} and its files from " +"INGInious, making it unavailable from courses." +msgstr "" + +#: inginious/frontend/templates/taskset_admin/settings.html:165 msgid "Create new task" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/util.html:84 -msgid "New task id" +#: inginious/frontend/templates/taskset_admin/settings.html:170 +#: inginious/frontend/templates/taskset_admin/settings.html:172 +msgid "New taskid" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/util.html:87 -msgid "Import from course filesystem" +#: inginious/frontend/templates/taskset_admin/settings.html:178 +msgid "Add task" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/util.html:88 -msgid "Search..." +#: inginious/frontend/templates/taskset_admin/task_edit.html:6 +msgid "Edit {}" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/util.html:90 -msgid "No unassigned tasks in the filesystem of this course" +#: inginious/frontend/templates/taskset_admin/task_edit.html:20 +#: inginious/frontend/templates/taskset_admin/task_edit.html:27 +msgid "Edit task \"{}\"" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/util.html:120 -msgid "New task with id: " +#: inginious/frontend/templates/taskset_admin/task_edit.html:47 +msgid "Environment" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/util_delete_modal.html:9 -#: inginious/frontend/templates/course_admin/task_dispensers/util_delete_modal.html:30 -msgid "Delete section" +#: inginious/frontend/templates/taskset_admin/task_edit.html:50 +msgid "Subproblems" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/task_edit.html:53 +msgid "Task files" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/task_edit.html:77 +msgid "File list" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/task_edit.html:107 +msgid "Problem id:" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/task_edit.html:124 +msgid "A title for this question" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/task_edit.html:142 +msgid "Are you sure that you want to delete this subproblem?" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/util_delete_modal.html:17 +#: inginious/frontend/templates/taskset_admin/template.html:48 msgid "" -"Once saved, this will permanently delete this section, all its " -"subsections and remove all the tasks and their files from INGInious." +"This will wipe your current taskset structure. Tasks file won't be " +"deleted but you'll have to import them again." msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/util_delete_modal.html:19 +#: inginious/frontend/templates/taskset_admin/template.html:85 msgid "" -"Once saved, this will permanently remove the task and its files " -"from INGInious." +"This taskset currently includes legacy tasks with importable data to the " +"task dispenser settings. The taskset owner can clean the task files." msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/util_delete_modal.html:23 -msgid "Wipe all submissions" +#: inginious/frontend/templates/taskset_admin/template.html:88 +msgid "Clean task files" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/util_delete_modal.html:29 -#: inginious/frontend/templates/course_admin/task_dispensers/util_delete_modal.html:32 -msgid "Keep files" +#: inginious/frontend/templates/taskset_admin/edit_tabs/basic.html:13 +msgid "Filetype" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/util_task_edit_modal.html:8 -msgid "Edit task {}" +#: inginious/frontend/templates/taskset_admin/edit_tabs/basic.html:30 +#: inginious/frontend/templates/taskset_admin/subproblems/code.html:5 +#: inginious/frontend/templates/taskset_admin/subproblems/file.html:5 +#: inginious/frontend/templates/taskset_admin/subproblems/match.html:5 +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:5 +msgid "Context" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/accessibility.html:11 -msgid "Never" +#: inginious/frontend/templates/taskset_admin/edit_tabs/basic.html:36 +msgid "Author" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/accessibility.html:18 -msgid "Always" +#: inginious/frontend/templates/taskset_admin/edit_tabs/basic.html:43 +msgid "Your name" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/accessibility.html:26 -msgid "Custom, from: {} to: {}" +#: inginious/frontend/templates/taskset_admin/edit_tabs/basic.html:47 +msgid "Contact URL" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/accessibility.html:52 -msgid "Students can still submit after this date" +#: inginious/frontend/templates/taskset_admin/edit_tabs/basic.html:53 +msgid "Random inputs" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/accessibility.html:52 -msgid "Soft Deadline" +#: inginious/frontend/templates/taskset_admin/edit_tabs/basic.html:60 +msgid "Regenerate random inputs for each reloading of the task page" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/categories.html:7 -msgid "Tag name, separated by commas" +#: inginious/frontend/templates/taskset_admin/edit_tabs/basic.html:60 +msgid "Regenerate input random" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/evaluation_mode.html:4 -msgid "Evaluation submission" +#: inginious/frontend/templates/taskset_admin/edit_tabs/env_generic_docker_oci.html:5 +msgid "Timeout limit (in seconds)" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/groups.html:12 -msgid "Individually" +#: inginious/frontend/templates/taskset_admin/edit_tabs/env_generic_docker_oci.html:11 +msgid "Hard timeout limit (in seconds) Default to 3*timeout" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/groups.html:21 -msgid "Per group" +#: inginious/frontend/templates/taskset_admin/edit_tabs/env_generic_docker_oci.html:18 +msgid "Memory limit (in megabytes)" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/submission_limit.html:3 -msgid "Submission limits" +#: inginious/frontend/templates/taskset_admin/edit_tabs/env_generic_docker_oci.html:24 +msgid "Allow internet access inside the grading container?" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/submission_limit.html:14 -msgid "Hard limit: {nbr_submissions} submission(s)" +#: inginious/frontend/templates/taskset_admin/edit_tabs/env_generic_docker_oci.html:24 +msgid "It also adds and configures local interfaces with IPv4 and IPv6." msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/submission_limit.html:23 -msgid "Soft limit: {nbr_submissions} submission(s) every {nbr_hours} hour(s)" +#: inginious/frontend/templates/taskset_admin/edit_tabs/env_generic_docker_oci.html:35 +msgid "" +"Custom command to be run in container (instead of running the run " +"script)" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/submission_storage.html:15 -msgid "Only the last {nbr_submissions} submissions" +#: inginious/frontend/templates/taskset_admin/edit_tabs/env_generic_docker_oci.html:38 +msgid "Optional" msgstr "" -#: inginious/frontend/templates/course_admin/task_dispensers/config_items/weight.html:4 -msgid "Grade weight (in comparison to other tasks)" +#: inginious/frontend/templates/taskset_admin/edit_tabs/env_generic_docker_oci.html:43 +msgid "Are the task's responses written in HTML instead of restructuredText?" msgstr "" -#: inginious/frontend/templates/preferences/bindings.html:46 -msgid "Refresh fields" +#: inginious/frontend/templates/taskset_admin/edit_tabs/environment.html:5 +msgid "Grading environment type" msgstr "" -#: inginious/frontend/templates/preferences/bindings.html:58 -msgid "Identifier" +#: inginious/frontend/templates/taskset_admin/edit_tabs/environment.html:20 +msgid "Grading environment" msgstr "" -#: inginious/frontend/templates/preferences/bindings.html:60 -msgid "Additional fields" +#: inginious/frontend/templates/taskset_admin/edit_tabs/file_modals.html:9 +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:60 +msgid "Upload a file" msgstr "" -#: inginious/frontend/templates/preferences/bindings.html:74 -msgid "Add a new binding" +#: inginious/frontend/templates/taskset_admin/edit_tabs/file_modals.html:16 +msgid "File:" msgstr "" -#: inginious/frontend/templates/preferences/bindings.html:78 -msgid "Select a authentication binding" +#: inginious/frontend/templates/taskset_admin/edit_tabs/file_modals.html:20 +msgid "File path:" msgstr "" -#: inginious/frontend/templates/preferences/bindings.html:87 -msgid "Add new binding" +#: inginious/frontend/templates/taskset_admin/edit_tabs/file_modals.html:27 +msgid "Upload" msgstr "" -#: inginious/frontend/templates/preferences/delete.html:41 -msgid "To confirm, type your email address :" +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:15 +msgid "Path" msgstr "" -#: inginious/frontend/templates/preferences/delete.html:56 -msgid "This will also delete all your data ! Are you really sure ?" +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:17 +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:44 +msgid "Edit" msgstr "" -#: inginious/frontend/templates/preferences/profile.html:40 -msgid "Please set a new username" +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:18 +msgid "Move" msgstr "" -#: inginious/frontend/templates/preferences/profile.html:54 -msgid "Username :" +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:49 +msgid "Move/Rename" msgstr "" -#: inginious/frontend/templates/preferences/profile.html:66 -msgid "Language :" +#: inginious/frontend/templates/taskset_admin/edit_tabs/files.html:59 +msgid "Create a new file" msgstr "" -#: inginious/frontend/templates/preferences/profile.html:75 -msgid "Old password :" +#: inginious/frontend/templates/taskset_admin/edit_tabs/subproblems.html:8 +msgid "New problem id" msgstr "" -#: inginious/frontend/templates/preferences/profile.html:79 -msgid "New password (min. 6 characters) :" +#: inginious/frontend/templates/taskset_admin/edit_tabs/subproblems.html:13 +msgid "Problem type" msgstr "" -#: inginious/frontend/templates/preferences/profile.html:83 -msgid "Confirm new password :" +#: inginious/frontend/templates/taskset_admin/subproblems/code.html:12 +msgid "Language" msgstr "" -#: inginious/frontend/templates/preferences/profile.html:94 -msgid "Save my profile" +#: inginious/frontend/templates/taskset_admin/subproblems/code.html:14 +msgid "Language used in this question (c/cpp/java/python/oz/...)" msgstr "" -#: inginious/frontend/templates/task_dispensers/task_list.html:34 -msgid "deadline reached" +#: inginious/frontend/templates/taskset_admin/subproblems/code.html:23 +msgid "Multiline code" msgstr "" -#: inginious/frontend/templates/task_dispensers/task_list.html:39 -msgid "Weight : " +#: inginious/frontend/templates/taskset_admin/subproblems/code.html:25 +msgid "Single-line code" msgstr "" -#: inginious/frontend/templates/tasks/file.html:7 -msgid "Click here to download the file you submitted previously" +#: inginious/frontend/templates/taskset_admin/subproblems/code.html:31 +msgid "Optional?" msgstr "" -#: inginious/frontend/templates/tasks/file.html:22 -msgid "Max file size:" +#: inginious/frontend/templates/taskset_admin/subproblems/code.html:41 +msgid "Default value" msgstr "" -#: inginious/frontend/templates/tasks/file.html:23 -msgid "Allowed extensions:" +#: inginious/frontend/templates/taskset_admin/subproblems/file.html:11 +msgid "Allowed file extensions (including the dot, separated by commas)" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/subproblems/file.html:17 +msgid "Maximum file size (in bytes)" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/subproblems/file.html:26 +msgid "File upload" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/subproblems/match.html:11 +msgid "Answer" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/subproblems/match.html:13 +msgid "Answer of this question" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/subproblems/match.html:17 +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:27 +msgid "Count error only at top of the page?" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:11 +msgid "Can select multiple answers" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:19 +msgid "Unshuffle answers" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:35 +msgid "Number of choices to display (0 = all choices)" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:41 +msgid "Success message" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:45 +msgid "An optional success message" msgstr "" + +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:49 +msgid "Error message" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:53 +msgid "An optional error message" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:58 +msgid "Choices" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice.html:63 +msgid "Add new choice" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice_templates.html:8 +msgid "Content" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice_templates.html:15 +msgid "Feedback message (displayed when selected)" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice_templates.html:19 +msgid "An optional feedback message" +msgstr "" + +#: inginious/frontend/templates/taskset_admin/subproblems/multiple_choice_templates.html:26 +msgid "Valid ?" +msgstr "" + diff --git a/inginious/frontend/log.py b/inginious/frontend/log.py new file mode 100644 index 0000000000..9a70106a2a --- /dev/null +++ b/inginious/frontend/log.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# +# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for +# more information about the licensing of this file. + +import logging + +def get_course_logger(coursename): + """ + :param coursename: the course id + :return: a logger object associated to a specific course + """ + return logging.getLogger("inginious.course."+coursename) + +def get_taskset_logger(coursename): + """ + :param coursename: the course id + :return: a logger object associated to a specific course + """ + return logging.getLogger("inginious.course."+coursename) \ No newline at end of file diff --git a/inginious/frontend/lti_outcome_manager.py b/inginious/frontend/lti_outcome_manager.py index 13d081eeb4..c2fee3c01c 100644 --- a/inginious/frontend/lti_outcome_manager.py +++ b/inginious/frontend/lti_outcome_manager.py @@ -47,7 +47,7 @@ def run(self): consumer_secret = course.lti_keys()[consumer_key] - grade = self._user_manager.get_task_cache(username, task.get_course_id(), task.get_id())["grade"] + grade = self._user_manager.get_task_cache(username, courseid, task.get_id())["grade"] grade = grade / 100.0 if grade > 1: grade = 1 diff --git a/inginious/frontend/marketplace_courses.py b/inginious/frontend/marketplace_tasksets.py similarity index 70% rename from inginious/frontend/marketplace_courses.py rename to inginious/frontend/marketplace_tasksets.py index b6b1a0e230..069e51795a 100644 --- a/inginious/frontend/marketplace_courses.py +++ b/inginious/frontend/marketplace_tasksets.py @@ -3,18 +3,18 @@ # This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for # more information about the licensing of this file. -""" A class for the course from the marketplace """ +""" A class for the tasksets from the marketplace """ import logging import requests from inginious import MARKETPLACE_URL from inginious.common.base import loads_json_or_yaml -from inginious.common.exceptions import CourseNotFoundException +from inginious.frontend.exceptions import CourseNotFoundException from inginious.frontend.parsable_text import ParsableText -class MarketplaceCourse(object): - """ A class for the course from the marketplace """ +class MarketplaceTaskset(object): + """ A class for the tasksets from the marketplace """ def __init__(self, structure): self._id = structure["id"] self._languages = structure["languages"] @@ -28,27 +28,27 @@ def __init__(self, structure): self._link = structure["link"] def get_id(self): - """ Return the id of this course """ + """ Return the id of this taskset """ return self._id def get_languages(self): - """ Return the languages of this course """ + """ Return the languages of this taskset """ return self._languages def get_license(self): - """ Return the license of this course """ + """ Return the license of this taskset """ return self._license def get_maintainers(self): - """ Return the maintainers of this course """ + """ Return the maintainers of this taskset """ return self._maintainers def get_authors(self): - """ Return the authors of this course """ + """ Return the authors of this taskset """ return self._authors def get_name(self, language): - """ Return the name of this course """ + """ Return the name of this taskset """ if language in self._name: return self._name[language] elif self._default_language in self._name: @@ -57,7 +57,7 @@ def get_name(self, language): return list(self._name.keys())[0] def get_short_desc(self, language): - """Returns the short course description """ + """Returns the short taskset description """ if language in self._short_desc: return self._short_desc[language] elif self._default_language in self._short_desc: @@ -66,7 +66,7 @@ def get_short_desc(self, language): return list(self._short_desc.keys())[0] def get_description(self, language): - """Returns the course description """ + """Returns the taskset description """ if language in self._description: return ParsableText(self._description[language], "rst") elif self._default_language in self._description: @@ -75,22 +75,22 @@ def get_description(self, language): return ParsableText(list(self._short_desc.keys())[0], "rst") def get_link(self): - """ Return the name of this course """ + """ Return the name of this taskset """ return self._link -def get_all_marketplace_courses(): +def get_all_marketplace_tasksets(): r = requests.get(MARKETPLACE_URL) marketplace_file = loads_json_or_yaml("marketplace.json", r.content) try: - return {course["id"]: MarketplaceCourse(course) for course in marketplace_file} + return {taskset["id"]: MarketplaceTaskset(taskset) for taskset in marketplace_file} except: logging.getLogger("inginious.webapp.marketplace").info("Could not load marketplace") return {} -def get_marketplace_course(courseid): - courses = get_all_marketplace_courses() - if courseid not in courses: - raise CourseNotFoundException("Marketplace course not found") - return courses[courseid] +def get_marketplace_taskset(tasksetid): + tasksets = get_all_marketplace_tasksets() + if tasksetid not in tasksets: + raise CourseNotFoundException("Marketplace taskset not found") + return tasksets[tasksetid] diff --git a/inginious/frontend/pages/api/courses.py b/inginious/frontend/pages/api/courses.py index a8d3028acd..e3c08d2095 100644 --- a/inginious/frontend/pages/api/courses.py +++ b/inginious/frontend/pages/api/courses.py @@ -45,10 +45,10 @@ def API_GET(self, courseid): # pylint: disable=arguments-differ output = [] if courseid is None: - courses = self.course_factory.get_all_courses() + courses = self.taskset_factory.get_all_courses() else: try: - courses = {courseid: self.course_factory.get_course(courseid)} + courses = {courseid: self.taskset_factory.get_course(courseid)} except: raise APINotFound("Course not found") diff --git a/inginious/frontend/pages/api/submissions.py b/inginious/frontend/pages/api/submissions.py index 37ef1d771c..5b38f3e22e 100644 --- a/inginious/frontend/pages/api/submissions.py +++ b/inginious/frontend/pages/api/submissions.py @@ -12,13 +12,13 @@ from inginious.frontend.pages.api._api_page import APIAuthenticatedPage, APINotFound, APIForbidden, APIInvalidArguments, APIError -def _get_submissions(course_factory, submission_manager, user_manager, translations, courseid, taskid, with_input, submissionid=None): +def _get_submissions(taskset_factory, submission_manager, user_manager, translations, courseid, taskid, with_input, submissionid=None): """ Helper for the GET methods of the two following classes """ try: - course = course_factory.get_course(courseid) + course = taskset_factory.get_course(courseid) except: raise APINotFound("Course not found") @@ -31,7 +31,7 @@ def _get_submissions(course_factory, submission_manager, user_manager, translati raise APINotFound("Task not found") if submissionid is None: - submissions = submission_manager.get_user_submissions(task) + submissions = submission_manager.get_user_submissions(course, task) else: try: submissions = [submission_manager.get_submission(submissionid)] @@ -45,7 +45,7 @@ def _get_submissions(course_factory, submission_manager, user_manager, translati for submission in submissions: submission = submission_manager.get_feedback_from_submission( submission, - show_everything=user_manager.has_staff_rights_on_course(course, user_manager.session_username()), + show_everything=user_manager.has_admin_rights_on_course(course, user_manager.session_username()), translation=translations.get(user_manager.session_language(), gettext.NullTranslations()) ) data = { @@ -111,7 +111,7 @@ def API_GET(self, courseid, taskid, submissionid): # pylint: disable=arguments- """ with_input = "input" in flask.request.args - return _get_submissions(self.course_factory, self.submission_manager, self.user_manager, self.app.l10n_manager.translations, courseid, taskid, with_input, submissionid) + return _get_submissions(self.taskset_factory, self.submission_manager, self.user_manager, self.app.l10n_manager.translations, courseid, taskid, with_input, submissionid) class APISubmissions(APIAuthenticatedPage): @@ -152,7 +152,7 @@ def API_GET(self, courseid, taskid): # pylint: disable=arguments-differ """ with_input = "input" in flask.request.args - return _get_submissions(self.course_factory, self.submission_manager, self.user_manager, self.app.l10n_manager.translations, courseid, taskid, with_input) + return _get_submissions(self.taskset_factory, self.submission_manager, self.user_manager, self.app.l10n_manager.translations, courseid, taskid, with_input) def API_POST(self, courseid, taskid): # pylint: disable=arguments-differ """ @@ -168,7 +168,7 @@ def API_POST(self, courseid, taskid): # pylint: disable=arguments-differ """ try: - course = self.course_factory.get_course(courseid) + course = self.taskset_factory.get_course(courseid) except: raise APINotFound("Course not found") @@ -185,7 +185,7 @@ def API_POST(self, courseid, taskid): # pylint: disable=arguments-differ self.user_manager.user_saw_task(username, courseid, taskid) # Verify rights - if not self.user_manager.task_can_user_submit(task, username, False): + if not self.user_manager.task_can_user_submit(course, task, username, False): raise APIForbidden("You are not allowed to submit for this task") user_input = flask.request.form.copy() @@ -209,7 +209,7 @@ def API_POST(self, courseid, taskid): # pylint: disable=arguments-differ # Start the submission try: - submissionid, _ = self.submission_manager.add_job(task, user_input, course.get_task_dispenser(), debug) + submissionid, _ = self.submission_manager.add_job(course, task, user_input, course.get_task_dispenser(), debug) return 200, {"submissionid": str(submissionid)} except Exception as ex: raise APIError(500, str(ex)) diff --git a/inginious/frontend/pages/api/tasks.py b/inginious/frontend/pages/api/tasks.py index e86e726964..021c281164 100644 --- a/inginious/frontend/pages/api/tasks.py +++ b/inginious/frontend/pages/api/tasks.py @@ -61,7 +61,7 @@ def API_GET(self, courseid, taskid): # pylint: disable=arguments-differ """ try: - course = self.course_factory.get_course(courseid) + course = self.taskset_factory.get_course(courseid) except: raise APINotFound("Course not found") @@ -78,7 +78,7 @@ def API_GET(self, courseid, taskid): # pylint: disable=arguments-differ output = [] for taskid, task in tasks.items(): - task_cache = self.user_manager.get_task_cache(self.user_manager.session_username(), task.get_course_id(), task.get_id()) + task_cache = self.user_manager.get_task_cache(self.user_manager.session_username(), courseid, task.get_id()) data = { "id": taskid, diff --git a/inginious/frontend/pages/course_admin/audience_edit.py b/inginious/frontend/pages/course_admin/audience_edit.py index f6a1c4d062..7f419cfbae 100644 --- a/inginious/frontend/pages/course_admin/audience_edit.py +++ b/inginious/frontend/pages/course_admin/audience_edit.py @@ -20,7 +20,7 @@ class CourseEditAudience(INGIniousAdminPage): def get_user_lists(self, course, audienceid=''): """ Get the available student and tutor lists for audience edition""" - tutor_list = course.get_staff() + tutor_list = course.get_admins() student_list = self.user_manager.get_course_registered_users(course, False) users_info = self.user_manager.get_users_info(student_list + tutor_list) @@ -55,13 +55,13 @@ def display_page(self, course, audienceid, msg='', error=False): def GET_AUTH(self, courseid, audienceid): # pylint: disable=arguments-differ """ Edit a audience """ - course, __ = self.get_course_and_check_rights(courseid, allow_all_staff=True) + course, __ = self.get_course_and_check_rights(courseid) return self.display_page(course, audienceid) def POST_AUTH(self, courseid, audienceid=''): # pylint: disable=arguments-differ """ Edit a audience """ - course, __ = self.get_course_and_check_rights(courseid, allow_all_staff=True) + course, __ = self.get_course_and_check_rights(courseid) msg='' error = False diff --git a/inginious/frontend/pages/course_admin/danger_zone.py b/inginious/frontend/pages/course_admin/danger_zone.py index fd250ae2f8..59356df40c 100644 --- a/inginious/frontend/pages/course_admin/danger_zone.py +++ b/inginious/frontend/pages/course_admin/danger_zone.py @@ -22,7 +22,7 @@ class CourseDangerZonePage(INGIniousAdminPage): """ Course administration page: list of audiences """ - _logger = logging.getLogger("inginious.webapp.danger_zone") + _logger = logging.getLogger("inginious.webapp.course.danger_zone") def wipe_course(self, courseid): submissions = self.database.submissions.find({"courseid": courseid}) @@ -135,7 +135,7 @@ def delete_course(self, courseid): def GET_AUTH(self, courseid): # pylint: disable=arguments-differ """ GET request """ - course, __ = self.get_course_and_check_rights(courseid, allow_all_staff=False) + course, __ = self.get_course_and_check_rights(courseid) data = flask.request.args @@ -154,7 +154,7 @@ def GET_AUTH(self, courseid): # pylint: disable=arguments-differ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ """ POST request """ - course, __ = self.get_course_and_check_rights(courseid, allow_all_staff=False) + course, __ = self.get_course_and_check_rights(courseid) msg = "" error = False diff --git a/inginious/frontend/pages/course_admin/settings.py b/inginious/frontend/pages/course_admin/settings.py index e5f8e95035..fa230129fd 100644 --- a/inginious/frontend/pages/course_admin/settings.py +++ b/inginious/frontend/pages/course_admin/settings.py @@ -17,12 +17,12 @@ class CourseSettingsPage(INGIniousAdminPage): def GET_AUTH(self, courseid): # pylint: disable=arguments-differ """ GET request """ - course, __ = self.get_course_and_check_rights(courseid, allow_all_staff=False) + course, __ = self.get_course_and_check_rights(courseid) return self.page(course) def POST_AUTH(self, courseid): # pylint: disable=arguments-differ """ POST request """ - course, __ = self.get_course_and_check_rights(courseid, allow_all_staff=False) + course, __ = self.get_course_and_check_rights(courseid) errors = [] course_content = {} @@ -36,9 +36,6 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ course_content['admins'] = list(map(str.strip, data['admins'].split(','))) if data['admins'].strip() else [] if not self.user_manager.user_is_superadmin() and self.user_manager.session_username() not in course_content['admins']: errors.append(_('You cannot remove yourself from the administrators of this course')) - course_content['tutors'] = list(map(str.strip, data['tutors'].split(','))) if data['tutors'].strip() else [] - if len(course_content['tutors']) == 1 and course_content['tutors'][0].strip() == "": - course_content['tutors'] = [] course_content['groups_student_choice'] = True if data["groups_student_choice"] == "true" else False @@ -104,7 +101,7 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ if len(errors) == 0: self.course_factory.update_course_descriptor_content(courseid, course_content) errors = None - course, __ = self.get_course_and_check_rights(courseid, allow_all_staff=False) # don't forget to reload the modified course + course, __ = self.get_course_and_check_rights(courseid) # don't forget to reload the modified course return self.page(course, errors, errors is None) diff --git a/inginious/frontend/pages/course_admin/statistics.py b/inginious/frontend/pages/course_admin/statistics.py index 6fefc77c58..dd9e8e68ba 100644 --- a/inginious/frontend/pages/course_admin/statistics.py +++ b/inginious/frontend/pages/course_admin/statistics.py @@ -143,7 +143,7 @@ def _progress_stats(self, course): result[entry["_id"]]["succeeded"] = entry["succeeded"] return result - def _global_stats(self, tasks, filter, limit, best_submissions_list, pond_stat): + def _global_stats(self, course, tasks, filter, limit, best_submissions_list, pond_stat): submissions = self.database.submissions.find(filter) if limit is not None: submissions.limit(limit) @@ -152,7 +152,7 @@ def _global_stats(self, tasks, filter, limit, best_submissions_list, pond_stat): for d in data: d["best"] = d["_id"] in best_submissions_list # mark best submissions - return compute_statistics(tasks, data, pond_stat) + return compute_statistics(course, tasks, data, pond_stat) def GET_AUTH(self, courseid): # pylint: disable=arguments-differ """ GET request """ @@ -216,7 +216,7 @@ def page(self, course, params): stats_users = self._users_stats(filter, limit) stats_graph = self._graph_stats(daterange, filter, limit) stats_progress = self._progress_stats(course) - stats_global = self._global_stats(tasks, filter, limit, best_submissions_list, params.get('stat', 'normal') == 'pond_stat') + stats_global = self._global_stats(course, tasks, filter, limit, best_submissions_list, params.get('stat', 'normal') == 'pond_stat') if "progress_csv" in flask.request.args: return make_csv(stats_progress) @@ -229,7 +229,7 @@ def page(self, course, params): display_hour=display_hours, msgs=msgs) -def compute_statistics(tasks, data, ponderation): +def compute_statistics(course, tasks, data, ponderation): """ Compute statistics about submissions and tags. This function returns a tuple of lists following the format describe below: @@ -244,7 +244,7 @@ def compute_statistics(tasks, data, ponderation): task = tasks.get(submission["taskid"], None) if task: username = "".join(submission["username"]) - tags_of_course = [tag for key, tag in task.get_course().get_tags().items() if tag.get_type() in [0,1]] + tags_of_course = [tag for key, tag in course.get_tags().items() if tag.get_type() in [0,1]] for tag in tags_of_course: super_dict.setdefault(tag, {}) super_dict[tag].setdefault(username, {}) diff --git a/inginious/frontend/pages/course_admin/student_list.py b/inginious/frontend/pages/course_admin/student_list.py index f917f8f966..15f6b0dcd2 100644 --- a/inginious/frontend/pages/course_admin/student_list.py +++ b/inginious/frontend/pages/course_admin/student_list.py @@ -57,7 +57,7 @@ def GET_AUTH(self, courseid): # pylint: disable=arguments-differ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ """ POST request """ - course, __ = self.get_course_and_check_rights(courseid, None, True) + course, __ = self.get_course_and_check_rights(courseid) data = flask.request.form.copy() data["delete"] = flask.request.form.getlist("delete") data["groupfile"] = flask.request.files.get("groupfile") @@ -109,7 +109,7 @@ def get_student_list_params(self, course): self.user_manager.get_users_info(self.user_manager.get_course_registered_users(course, False)).items()), key=lambda k: k[1].realname if k[1] is not None else "") - users = OrderedDict(sorted(list(self.user_manager.get_users_info(course.get_staff()).items()), + users = OrderedDict(sorted(list(self.user_manager.get_users_info(course.get_admins()).items()), key=lambda k: k[1].realname if k[1] is not None else "") + users) user_data = OrderedDict([(username, { @@ -271,7 +271,7 @@ def post_audiences(self, course, data, active_tab, msg, error): # update list of students and tutors of the course. new_students = list(set(stud_list).union(set(course_students))) - new_tutors = list(set(course.get_tutors()).union(set(course_tutors))) + new_tutors = list(set(course.get_admins()).union(set(course_tutors))) self.database.courses.update_one({"_id": courseid}, {"$set": {"students": new_students, "tutors": new_tutors}}) @@ -288,7 +288,7 @@ def post_audiences(self, course, data, active_tab, msg, error): {"$set": {"students": audience["students"], "tutors": audience["tutors"]}}) - active_tab = "tab_audiences" + active_tab = "tab_audiences" except Exception as e: msg["audiences"] = _('An error occurred while parsing the data.') error["audiences"] = True @@ -459,7 +459,7 @@ def update_audience(self, course, audienceid, new_data): audience = self.database.audiences.find_one({"_id": ObjectId(audienceid), "courseid": course.get_id()}) # Check tutors - new_data["tutors"] = [tutor for tutor in new_data["tutors"] if tutor in course.get_staff()] + new_data["tutors"] = [tutor for tutor in new_data["tutors"] if tutor in course.get_admins()] students, errored_students = [], [] diff --git a/inginious/frontend/pages/course_admin/submission.py b/inginious/frontend/pages/course_admin/submission.py index 8097d4e13f..9fe0a4b0d7 100644 --- a/inginious/frontend/pages/course_admin/submission.py +++ b/inginious/frontend/pages/course_admin/submission.py @@ -42,12 +42,12 @@ def POST_AUTH(self, submissionid): # pylint: disable=arguments-differ webinput = flask.request.form if "replay" in webinput and is_admin: - self.submission_manager.replay_job(task, submission, course.get_task_dispenser()) + self.submission_manager.replay_job(course, task, submission, course.get_task_dispenser()) elif "replay-copy" in webinput: # Authorized for tutors - self.submission_manager.replay_job(task, submission, course.get_task_dispenser(), True) + self.submission_manager.replay_job(course, task, submission, course.get_task_dispenser(), True) return redirect(self.app.get_homepath() + "/course/" + course.get_id() + "/" + task.get_id()) elif "replay-debug" in webinput and is_admin: - self.submission_manager.replay_job(task, submission, course.get_task_dispenser(), True, "ssh") + self.submission_manager.replay_job(course, task, submission, course.get_task_dispenser(), True, "ssh") return redirect(self.app.get_homepath() + "/course/" + course.get_id() + "/" + task.get_id()) return self.page(course, task, submission) diff --git a/inginious/frontend/pages/course_admin/submissions.py b/inginious/frontend/pages/course_admin/submissions.py index b73f21f0e4..6f20e3ad99 100644 --- a/inginious/frontend/pages/course_admin/submissions.py +++ b/inginious/frontend/pages/course_admin/submissions.py @@ -34,7 +34,7 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ if submission is None: raise NotFound(description=_("This submission doesn't exist.")) - self.submission_manager.replay_job(course.get_task(submission["taskid"]), submission, course.get_task_dispenser()) + self.submission_manager.replay_job(course, course.get_task(submission["taskid"]), submission, course.get_task_dispenser()) return Response(response=json.dumps({"status": "waiting"}), content_type='application/json') elif "csv" in user_input or "download" in user_input or "replay" in user_input: @@ -68,7 +68,7 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ tasks = course.get_tasks() for submission in data: - self.submission_manager.replay_job(tasks[submission["taskid"]], submission, course.get_task_dispenser()) + self.submission_manager.replay_job(course, tasks[submission["taskid"]], submission, course.get_task_dispenser()) msgs.append(_("{0} selected submissions were set for replay.").format(str(len(data)))) return self.page(course, params, msgs=msgs) diff --git a/inginious/frontend/pages/course_admin/task_list.py b/inginious/frontend/pages/course_admin/task_list.py index 75a971bcd5..71e85acaa6 100644 --- a/inginious/frontend/pages/course_admin/task_list.py +++ b/inginious/frontend/pages/course_admin/task_list.py @@ -17,12 +17,12 @@ class CourseTaskListPage(INGIniousAdminPage): def GET_AUTH(self, courseid): # pylint: disable=arguments-differ """ GET request """ - course, __ = self.get_course_and_check_rights(courseid, allow_all_staff=True) + course, __ = self.get_course_and_check_rights(courseid) return self.page(course) def POST_AUTH(self, courseid): # pylint: disable=arguments-differ """ POST request """ - course, __ = self.get_course_and_check_rights(courseid, allow_all_staff=False) + course, __ = self.get_course_and_check_rights(courseid) errors = [] user_input = flask.request.form @@ -34,29 +34,19 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ self.course_factory.update_course_descriptor_element(courseid, 'dispenser_data', {}) else: errors.append(_("Invalid task dispenser")) + elif "migrate_tasks" in user_input: + task_dispenser = course.get_task_dispenser() + try: + data = task_dispenser.import_legacy_tasks() + self.update_dispenser(course, data) + except Exception as e: + errors.append(_("Something wrong happened: ") + str(e)) else: try: - task_dispenser = course.get_task_dispenser() - data, msg = task_dispenser.check_dispenser_data(user_input["course_structure"]) - if data: - self.course_factory.update_course_descriptor_element(courseid, 'task_dispenser', task_dispenser.get_id()) - self.course_factory.update_course_descriptor_element(courseid, 'dispenser_data', data) - else: - errors.append(_("Invalid course structure: ") + msg) + self.update_dispenser(course, json.loads(user_input["dispenser_structure"])) except Exception as e: errors.append(_("Something wrong happened: ") + str(e)) - for taskid in json.loads(user_input.get("new_tasks", "[]")): - try: - self.task_factory.create_task(course, taskid, { - "name": taskid, "accessible": False, "problems": {}, "environment_type": "mcq"}) - except Exception as ex: - errors.append(_("Couldn't create task {} : ").format(taskid) + str(ex)) - for taskid in json.loads(user_input.get("deleted_tasks", "[]")): - try: - self.task_factory.delete_task(courseid, taskid) - except Exception as ex: - errors.append(_("Couldn't delete task {} : ").format(taskid) + str(ex)) for taskid in json.loads(user_input.get("wiped_tasks", "[]")): try: self.wipe_task(courseid, taskid) @@ -64,9 +54,20 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ errors.append(_("Couldn't wipe task {} : ").format(taskid) + str(ex)) # don't forget to reload the modified course - course, __ = self.get_course_and_check_rights(courseid, allow_all_staff=False) + course, __ = self.get_course_and_check_rights(courseid) return self.page(course, errors, not errors) + def update_dispenser(self, course, dispenser_data): + """ Update the task dispenser based on dispenser_data """ + task_dispenser = course.get_task_dispenser() + data, msg = task_dispenser.check_dispenser_data(dispenser_data) + if data: + self.course_factory.update_course_descriptor_element(course.get_id(), 'task_dispenser', + task_dispenser.get_id()) + self.course_factory.update_course_descriptor_element(course.get_id(), 'dispenser_data', data) + else: + raise Exception(_("Invalid course structure: ") + msg) + def submission_url_generator(self, taskid): """ Generates a submission url """ return "?format=taskid%2Fusername&tasks=" + taskid @@ -88,16 +89,18 @@ def page(self, course, errors=None, validated=False): """ Get all data and display the page """ # Load tasks and verify exceptions - files = self.task_factory.get_readable_tasks(course) + files = self.task_factory.get_readable_tasks(course.get_taskset()) tasks = {} if errors is None: errors = [] + + tasks_errors = {} for taskid in files: try: tasks[taskid] = course.get_task(taskid) - except Exception as inst: - errors.append({"taskid": taskid, "error": str(inst)}) + except Exception as ex: + tasks_errors[taskid] = str(ex) tasks_data = natsorted([(taskid, {"name": tasks[taskid].get_name(self.user_manager.session_language()), "url": self.submission_url_generator(taskid)}) for taskid in tasks], @@ -108,5 +111,5 @@ def page(self, course, errors=None, validated=False): return self.template_helper.render("course_admin/task_list.html", course=course, task_dispensers=task_dispensers, tasks=tasks_data, errors=errors, - validated=validated, webdav_host=self.webdav_host) + tasks_errors=tasks_errors, validated=validated) diff --git a/inginious/frontend/pages/course_admin/utils.py b/inginious/frontend/pages/course_admin/utils.py index f2c014775f..b8675400d7 100644 --- a/inginious/frontend/pages/course_admin/utils.py +++ b/inginious/frontend/pages/course_admin/utils.py @@ -24,23 +24,18 @@ class INGIniousAdminPage(INGIniousAuthPage): An improved version of INGIniousAuthPage that checks rights for the administration """ - def get_course_and_check_rights(self, courseid, taskid=None, allow_all_staff=True): + def get_course_and_check_rights(self, courseid, taskid=None): """ Returns the course with id ``courseid`` and the task with id ``taskid``, and verify the rights of the user. Raise app.forbidden() when there is no such course of if the users has not enough rights. :param courseid: the course on which to check rights :param taskid: If not None, returns also the task with id ``taskid`` - :param allow_all_staff: allow admins AND tutors to see the page. If false, all only admins. :returns (Course, Task) """ try: course = self.course_factory.get_course(courseid) - if allow_all_staff: - if not self.user_manager.has_staff_rights_on_course(course): - raise Forbidden(description=_("You don't have staff rights on this course.")) - else: - if not self.user_manager.has_admin_rights_on_course(course): - raise Forbidden(description=_("You don't have admin rights on this course.")) + if not self.user_manager.has_admin_rights_on_course(course): + raise Forbidden(description=_("You don't have admin rights on this course.")) if taskid is None: return course, None @@ -366,10 +361,7 @@ class CourseRedirectPage(INGIniousAdminPage): def GET_AUTH(self, courseid): # pylint: disable=arguments-differ """ GET request """ course, __ = self.get_course_and_check_rights(courseid) - if self.user_manager.session_username() in course.get_tutors(): - return redirect(self.app.get_homepath() + '/admin/{}/tasks'.format(courseid)) - else: - return redirect(self.app.get_homepath() + '/admin/{}/settings'.format(courseid)) + return redirect(self.app.get_homepath() + '/admin/{}/settings'.format(courseid)) def POST_AUTH(self, courseid): # pylint: disable=arguments-differ """ POST request """ diff --git a/inginious/frontend/pages/course_register.py b/inginious/frontend/pages/course_register.py index f5e8025610..04d759f77b 100644 --- a/inginious/frontend/pages/course_register.py +++ b/inginious/frontend/pages/course_register.py @@ -8,7 +8,8 @@ from flask import redirect from werkzeug.exceptions import NotFound -from inginious.common.exceptions import InvalidNameException, CourseNotFoundException, CourseUnreadableException +from inginious.common.exceptions import InvalidNameException +from inginious.frontend.exceptions import CourseNotFoundException from inginious.frontend.pages.utils import INGIniousAuthPage @@ -19,7 +20,7 @@ class CourseRegisterPage(INGIniousAuthPage): def basic_checks(self, courseid): try: course = self.course_factory.get_course(courseid) - except (InvalidNameException, CourseNotFoundException, CourseUnreadableException) as e: + except (InvalidNameException, CourseNotFoundException) as e: raise NotFound(description=_("This course doesn't exist.")) username = self.user_manager.session_username() diff --git a/inginious/frontend/pages/course_user_settings.py b/inginious/frontend/pages/course_user_settings.py index 6577f8c68c..24a011e774 100644 --- a/inginious/frontend/pages/course_user_settings.py +++ b/inginious/frontend/pages/course_user_settings.py @@ -43,7 +43,7 @@ def POST_AUTH(self, courseid): self.database.users.update_one({"username": username}, {"$set": {"course_settings." + courseid: course_user_settings}}) - return self.show_page(courseid, course_user_settings, ("success", "Course settings successfully updated.")) + return self.show_page(courseid, course_user_settings, ("success", "User settings successfully updated.")) def show_page(self, courseid, course_user_settings, feedback): """ diff --git a/inginious/frontend/pages/group.py b/inginious/frontend/pages/group.py index e351b5665d..faf9c2db75 100644 --- a/inginious/frontend/pages/group.py +++ b/inginious/frontend/pages/group.py @@ -22,13 +22,13 @@ class GroupPage(INGIniousAuthPage): def GET_AUTH(self, courseid): # pylint: disable=arguments-differ """ GET request """ - course = self.course_factory.get_course(courseid) + course = self.taskset_factory.get_course(courseid) username = self.user_manager.session_username() error = False msg = "" data = flask.request.args - if self.user_manager.has_staff_rights_on_course(course): + if self.user_manager.has_admin_rights_on_course(course): raise Forbidden(description=_("You can't access this page as a member of the staff.")) elif not (self.user_manager.course_is_open_to_user(course, lti=False) and self.user_manager.course_is_user_registered(course, username)): diff --git a/inginious/frontend/pages/lti.py b/inginious/frontend/pages/lti.py index ee8a454b5f..02d953ab65 100644 --- a/inginious/frontend/pages/lti.py +++ b/inginious/frontend/pages/lti.py @@ -10,7 +10,7 @@ from inginious.frontend.pages.utils import INGIniousPage, INGIniousAuthPage from itsdangerous import want_bytes -from inginious.common import exceptions +from inginious.frontend import exceptions from inginious.frontend.lti_tool_provider import LTIWebPyToolProvider from inginious.frontend.pages.tasks import BaseTaskPage diff --git a/inginious/frontend/pages/marketplace.py b/inginious/frontend/pages/marketplace.py index 8240f6ca25..a92640a802 100644 --- a/inginious/frontend/pages/marketplace.py +++ b/inginious/frontend/pages/marketplace.py @@ -10,9 +10,9 @@ from werkzeug.exceptions import Forbidden from inginious.common.base import id_checker -from inginious.common.exceptions import ImportCourseException -from inginious.common.log import get_course_logger -from inginious.frontend.marketplace_courses import get_all_marketplace_courses, get_marketplace_course +from inginious.frontend.exceptions import ImportTasksetException +from inginious.frontend.log import get_taskset_logger +from inginious.frontend.marketplace_tasksets import get_all_marketplace_tasksets, get_marketplace_taskset from inginious.frontend.pages.utils import INGIniousAuthPage if sys.platform == 'win32': @@ -29,7 +29,7 @@ def GET_AUTH(self): # pylint: disable=arguments-differ """ GET request """ # Change to teacher privilege when created if not self.user_manager.user_is_superadmin(): - raise Forbidden(description=_("You don't have superadmin rights on this course.")) + raise Forbidden(description=_("You don't have superadmin rights on this taskset.")) return self.show_page() def POST_AUTH(self): # pylint: disable=arguments-differ @@ -40,42 +40,42 @@ def POST_AUTH(self): # pylint: disable=arguments-differ user_input = flask.request.form errors = [] - if "new_courseid" in user_input: - new_courseid = user_input["new_courseid"] + if "new_tasksetid" in user_input: + new_tasksetid = user_input["new_tasksetid"] try: - course = get_marketplace_course(user_input["courseid"]) - import_course(course, new_courseid, self.user_manager.session_username(), self.course_factory) - except ImportCourseException as e: + taskset = get_marketplace_taskset(user_input["tasksetid"]) + import_taskset(taskset, new_tasksetid, self.user_manager.session_username(), self.taskset_factory) + except ImportTasksetException as e: errors.append(str(e)) except: errors.append(_("User returned an invalid form.")) if not errors: - return redirect(self.app.get_homepath() + "/admin/{}".format(new_courseid)) + return redirect(self.app.get_homepath() + "/taskset/{}".format(new_tasksetid)) return self.show_page(errors) def show_page(self, errors=None): - """ Prepares and shows the course marketplace """ + """ Prepares and shows the taskset marketplace """ if errors is None: errors = [] - courses = get_all_marketplace_courses() - return self.template_helper.render("marketplace.html", courses=courses, errors=errors) + tasksets = get_all_marketplace_tasksets() + return self.template_helper.render("marketplace.html", tasksets=tasksets, errors=errors) -def import_course(course, new_courseid, username, course_factory): - if not id_checker(new_courseid): - raise ImportCourseException("Course with invalid name: " + new_courseid) - course_fs = course_factory.get_course_fs(new_courseid) +def import_taskset(taskset, new_tasksetid, username, taskset_factory): + if not id_checker(new_tasksetid): + raise ImportTasksetException("Course with invalid name: " + new_tasksetid) + taskset_fs = taskset_factory.get_taskset_fs(new_tasksetid) - if course_fs.exists("course.yaml") or course_fs.exists("course.json"): - raise ImportCourseException("Course with id " + new_courseid + " already exists.") + if taskset_fs.exists("taskset.yaml") or taskset_fs.exists("course.yaml") or taskset_fs.exists("course.json"): + raise ImportTasksetException("Course with id " + new_tasksetid + " already exists.") try: - git.clone(course.get_link(), course_fs.prefix) + git.clone(taskset.get_link(), taskset_fs.prefix) except: - raise ImportCourseException(_("Couldn't clone course into your instance")) + raise ImportTasksetException(_("Couldn't clone taskset into your instance")) try: - old_descriptor = course_factory.get_course_descriptor_content(new_courseid) + old_descriptor = taskset_factory.get_taskset_descriptor_content(new_tasksetid) except: old_descriptor ={} @@ -85,15 +85,15 @@ def import_course(course, new_courseid, username, course_factory): "accessible": False, "tags": old_descriptor.get("tags", {})} if "name" in old_descriptor: - new_descriptor["name"] = old_descriptor["name"] + " - " + new_courseid + new_descriptor["name"] = old_descriptor["name"] + " - " + new_tasksetid else: - new_descriptor["name"] = new_courseid + new_descriptor["name"] = new_tasksetid if "toc" in old_descriptor: new_descriptor["toc"] = old_descriptor["toc"] - course_factory.update_course_descriptor_content(new_courseid, new_descriptor) + taskset_factory.update_taskset_descriptor_content(new_tasksetid, new_descriptor) except: - course_factory.delete_course(new_courseid) - raise ImportCourseException(_("An error occur while editing the course description")) + taskset_factory.delete_taskset(new_tasksetid) + raise ImportTasksetException(_("An error occur while editing the taskset description")) - get_course_logger(new_courseid).info("Course %s cloned from the marketplace.", new_courseid) + get_taskset_logger(new_tasksetid).info("Course %s cloned from the marketplace.", new_tasksetid) diff --git a/inginious/frontend/pages/marketplace_course.py b/inginious/frontend/pages/marketplace_course.py deleted file mode 100644 index 9a3872acf9..0000000000 --- a/inginious/frontend/pages/marketplace_course.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for -# more information about the licensing of this file. - -""" Course page """ -import flask -from flask import redirect -from werkzeug.exceptions import Forbidden - -from inginious.common.exceptions import ImportCourseException -from inginious.frontend.marketplace_courses import get_marketplace_course -from inginious.frontend.pages.marketplace import import_course -from inginious.frontend.pages.utils import INGIniousAuthPage - - -class MarketplaceCoursePage(INGIniousAuthPage): - """ Course marketplace """ - - def get_course(self, courseid): - """ Return the course """ - try: - course = get_marketplace_course(courseid) - except: - raise Forbidden(description=_("Course unavailable.")) - - return course - - def GET_AUTH(self, courseid): # pylint: disable=arguments-differ - """ GET request """ - # Change to teacher privilege when created - if not self.user_manager.user_is_superadmin(): - raise Forbidden(description=_("You're not allowed to do that")) - - course = self.get_course(courseid) - return self.show_page(course) - - def POST_AUTH(self, courseid): # pylint: disable=arguments-differ - """ POST request """ - # Change to teacher privilege when created - if not self.user_manager.user_is_superadmin(): - raise Forbidden(description=_("You're not allowed to do that")) - - course = self.get_course(courseid) - user_input = flask.request.form - errors = [] - if "new_courseid" in user_input: - new_courseid = user_input["new_courseid"] - try: - import_course(course, new_courseid, self.user_manager.session_username(), self.course_factory) - except ImportCourseException as e: - errors.append(str(e)) - if not errors: - return redirect(self.app.get_homepath() + "/admin/{}".format(new_courseid)) - return self.show_page(course, errors) - - def show_page(self, course, errors=None): - """ Prepares and shows the course marketplace """ - if errors is None: - errors = [] - - return self.template_helper.render("marketplace_course.html", course=course, errors=errors) diff --git a/inginious/frontend/pages/marketplace_taskset.py b/inginious/frontend/pages/marketplace_taskset.py new file mode 100644 index 0000000000..a8904c83ce --- /dev/null +++ b/inginious/frontend/pages/marketplace_taskset.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# +# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for +# more information about the licensing of this file. + +""" Course page """ +import flask +from flask import redirect +from werkzeug.exceptions import Forbidden + +from inginious.frontend.exceptions import ImportTasksetException +from inginious.frontend.marketplace_tasksets import get_marketplace_taskset +from inginious.frontend.pages.marketplace import import_taskset +from inginious.frontend.pages.utils import INGIniousAuthPage + + +class MarketplaceTasksetPage(INGIniousAuthPage): + """ Course marketplace """ + + def get_taskset(self, tasksetid): + """ Return the taskset """ + try: + taskset = get_marketplace_taskset(tasksetid) + except: + raise Forbidden(description=_("Taskset unavailable.")) + + return taskset + + def GET_AUTH(self, tasksetid): # pylint: disable=arguments-differ + """ GET request """ + # Change to teacher privilege when created + if not self.user_manager.user_is_superadmin(): + raise Forbidden(description=_("You're not allowed to do that")) + + taskset = self.get_taskset(tasksetid) + return self.show_page(taskset) + + def POST_AUTH(self, tasksetid): # pylint: disable=arguments-differ + """ POST request """ + # Change to teacher privilege when created + if not self.user_manager.user_is_superadmin(): + raise Forbidden(description=_("You're not allowed to do that")) + + taskset = self.get_taskset(tasksetid) + user_input = flask.request.form + errors = [] + if "new_tasksetid" in user_input: + new_tasksetid = user_input["new_tasksetid"] + try: + import_taskset(taskset, new_tasksetid, self.user_manager.session_username(), self.taskset_factory) + except ImportTasksetException as e: + errors.append(str(e)) + if not errors: + return redirect(self.app.get_homepath() + "/taskset/{}".format(new_tasksetid)) + return self.show_page(taskset, errors) + + def show_page(self, taskset, errors=None): + """ Prepares and shows the taskset marketplace """ + if errors is None: + errors = [] + + return self.template_helper.render("marketplace_taskset.html", taskset=taskset, errors=errors) diff --git a/inginious/frontend/pages/mycourses.py b/inginious/frontend/pages/mycourses.py index b9e4f57ff2..60caefe47f 100644 --- a/inginious/frontend/pages/mycourses.py +++ b/inginious/frontend/pages/mycourses.py @@ -14,26 +14,6 @@ class MyCoursesPage(INGIniousAuthPage): """ Index page """ def GET_AUTH(self): # pylint: disable=arguments-differ - """ Display main course list page """ - return self.show_page(None) - - def POST_AUTH(self): # pylint: disable=arguments-differ - """ Parse course registration or course creation and display the course list page """ - - user_input = flask.request.form - success = None - - if "new_courseid" in user_input and self.user_manager.user_is_superadmin(): - try: - courseid = user_input["new_courseid"] - self.course_factory.create_course(courseid, {"name": courseid, "accessible": False}) - success = True - except: - success = False - - return self.show_page(success) - - def show_page(self, success): """ Display main course list page """ username = self.user_manager.session_username() user_info = self.user_manager.get_user_info(username) @@ -64,5 +44,4 @@ def show_page(self, success): return self.template_helper.render("mycourses.html", open_courses=open_courses, registrable_courses=registerable_courses, - submissions=except_free_last_submissions, - success=success) + submissions=except_free_last_submissions) diff --git a/inginious/frontend/pages/preferences/profile.py b/inginious/frontend/pages/preferences/profile.py index 0d9b888707..5a31af1a50 100644 --- a/inginious/frontend/pages/preferences/profile.py +++ b/inginious/frontend/pages/preferences/profile.py @@ -82,6 +82,29 @@ def save_profile(self, userdata, data): else: self.user_manager.set_session_language(language) + if data["theme"] != userdata.get('theme', 'default'): + theme = data["theme"] if data["theme"] in self.app.available_themes else "default" + result = self.database.users.find_one_and_update({"username": self.user_manager.session_username()}, + {"$set": {"theme": theme}}, + return_document=ReturnDocument.AFTER) + if not result: + error = True + msg = _("Incorrect username.") + return result, msg, error + else: + self.user_manager.set_session_theme(theme) + + if data["codemirror_theme"] != userdata.get('codemirror_theme', 'default'): + codemirror_theme = data["codemirror_theme"] if data["codemirror_theme"] in self.app.available_codemirror_themes else "default" + result = self.database.users.find_one_and_update({"username": self.user_manager.session_username()}, + {"$set": {"codemirror_theme": codemirror_theme}}, + return_document=ReturnDocument.AFTER) + if not result: + error = True + msg = _("Incorrect username.") + return result, msg, error + else: + self.user_manager.set_session_codemirror_theme(codemirror_theme) # Checks if updating name if len(data["realname"]) > 0: result = self.database.users.find_one_and_update({"username": self.user_manager.session_username()}, diff --git a/inginious/frontend/pages/course_admin/search_user.py b/inginious/frontend/pages/search_user.py similarity index 84% rename from inginious/frontend/pages/course_admin/search_user.py rename to inginious/frontend/pages/search_user.py index 9475f3967e..84d3b85e11 100644 --- a/inginious/frontend/pages/course_admin/search_user.py +++ b/inginious/frontend/pages/search_user.py @@ -9,13 +9,11 @@ from inginious.frontend.pages.course_admin.utils import INGIniousAdminPage -class CourseAdminSearchUserPage(INGIniousAdminPage): +class SearchUserPage(INGIniousAdminPage): """ Return users based on their username or realname """ - def GET_AUTH(self, courseid, request): # pylint: disable=arguments-differ + def GET_AUTH(self, request): # pylint: disable=arguments-differ """ GET request """ - # check rights - self.get_course_and_check_rights(courseid, allow_all_staff=True) request = re.escape(request) # escape for safety. Maybe this is not needed... users = list(self.database.users.find({"$and":[{ "activate": { "$exists": False } }, diff --git a/inginious/frontend/pages/tasks.py b/inginious/frontend/pages/tasks.py index 3415000e43..164cf8b6e7 100644 --- a/inginious/frontend/pages/tasks.py +++ b/inginious/frontend/pages/tasks.py @@ -19,7 +19,8 @@ from bson.objectid import ObjectId from pymongo import ReturnDocument -from inginious.common.exceptions import TaskNotFoundException, CourseNotFoundException +from inginious.common.exceptions import TaskNotFoundException +from inginious.frontend.exceptions import CourseNotFoundException from inginious.frontend.pages.course import handle_course_unavailable from inginious.frontend.pages.utils import INGIniousPage, INGIniousAuthPage @@ -62,11 +63,11 @@ def GET(self, courseid, taskid, is_LTI): if not self.user_manager.course_is_open_to_user(course, username, is_LTI): return handle_course_unavailable(self.cp.app.get_homepath(), self.template_helper, self.user_manager, course) - is_staff = self.user_manager.has_staff_rights_on_course(course, username) + is_staff = self.user_manager.has_admin_rights_on_course(course, username) try: task = course.get_task(taskid) - if not self.user_manager.task_is_visible_by_user(task, username, is_LTI): + if not self.user_manager.task_is_visible_by_user(course, task, username, is_LTI): return self.template_helper.render("task_unavailable.html") except TaskNotFoundException: raise NotFound() @@ -83,7 +84,6 @@ def GET(self, courseid, taskid, is_LTI): self.user_manager.user_saw_task(username, courseid, taskid) - is_staff = self.user_manager.has_staff_rights_on_course(course, username) userinput = flask.request.args if "submissionid" in userinput and "questionid" in userinput: @@ -111,7 +111,7 @@ def GET(self, courseid, taskid, is_LTI): user_task = self.database.user_tasks.find_one_and_update( { - "courseid": task.get_course_id(), + "courseid": courseid, "taskid": task.get_id(), "username": self.user_manager.session_username() }, @@ -126,19 +126,19 @@ def GET(self, courseid, taskid, is_LTI): students = [self.user_manager.session_username()] if course.get_task_dispenser().get_group_submission(taskid) and not self.user_manager.has_admin_rights_on_course(course, username): - group = self.database.groups.find_one({"courseid": task.get_course_id(), + group = self.database.groups.find_one({"courseid": courseid, "students": self.user_manager.session_username()}) if group is not None: students = group["students"] # we don't care for the other case, as the student won't be able to submit. - submissions = self.submission_manager.get_user_submissions(task) if self.user_manager.session_logged_in() else [] + submissions = self.submission_manager.get_user_submissions(course, task) if self.user_manager.session_logged_in() else [] user_info = self.user_manager.get_user_info(username) # Visible tags course_tags = course.get_tags() visible_tags = [tags for _,tags in course_tags.items() if - tags.is_visible_for_student() or self.user_manager.has_staff_rights_on_course(course)] + tags.is_visible_for_student() or self.user_manager.has_admin_rights_on_course(course)] # Problem dict pdict = {problem.get_id(): problem.get_type() for problem in task.get_problems()} @@ -160,11 +160,10 @@ def POST(self, courseid, taskid, isLTI): if not self.user_manager.course_is_open_to_user(course, username, isLTI): return handle_course_unavailable(self.cp.app.get_homepath(), self.template_helper, self.user_manager, course) - is_staff = self.user_manager.has_staff_rights_on_course(course, username) is_admin = self.user_manager.has_admin_rights_on_course(course, username) task = course.get_task(taskid) - if not self.user_manager.task_is_visible_by_user(task, username, isLTI): + if not self.user_manager.task_is_visible_by_user(course, task, username, isLTI): return self.template_helper.render("task_unavailable.html") self.user_manager.user_saw_task(username, courseid, taskid) @@ -172,11 +171,11 @@ def POST(self, courseid, taskid, isLTI): userinput = flask.request.form if "@action" in userinput and userinput["@action"] == "submit": # Verify rights - if not self.user_manager.task_can_user_submit(task, username, lti=isLTI): + if not self.user_manager.task_can_user_submit(course, task, username, lti=isLTI): return json.dumps({"status": "error", "title": _("Error"), "text": _("You are not allowed to submit for this task.")}) # Retrieve input random and check still valid - random_input = self.database.user_tasks.find_one({"courseid": task.get_course_id(), "taskid": task.get_id(), "username": username}, { "random": 1 }) + random_input = self.database.user_tasks.find_one({"courseid": courseid, "taskid": task.get_id(), "username": username}, { "random": 1 }) random_input = random_input["random"] if "random" in random_input else [] for i in range(0, len(random_input)): s = "@random_" + str(i) @@ -214,7 +213,7 @@ def POST(self, courseid, taskid, isLTI): # Start the submission try: - submissionid, oldsubids = self.submission_manager.add_job(task, userinput, course.get_task_dispenser(), debug) + submissionid, oldsubids = self.submission_manager.add_job(course, task, userinput, course.get_task_dispenser(), debug) return Response(content_type='application/json', response=json.dumps({ "status": "ok", "submissionid": str(submissionid), "remove": oldsubids, "text": _("Your submission has been sent...") @@ -225,18 +224,18 @@ def POST(self, courseid, taskid, isLTI): })) elif "@action" in userinput and userinput["@action"] == "check" and "submissionid" in userinput: - result = self.submission_manager.get_submission(userinput['submissionid'], user_check=not is_staff) + result = self.submission_manager.get_submission(userinput['submissionid'], user_check=not is_admin) if result is None: return Response(content_type='application/json', response=json.dumps({ 'status': "error", "title": _("Error"), "text": _("Internal error") })) - elif self.submission_manager.is_done(result, user_check=not is_staff): + elif self.submission_manager.is_done(result, user_check=not is_admin): result = self.submission_manager.get_input_from_submission(result) - result = self.submission_manager.get_feedback_from_submission(result, show_everything=is_staff) + result = self.submission_manager.get_feedback_from_submission(result, show_everything=is_admin) # user_task always exists as we called user_saw_task before user_task = self.database.user_tasks.find_one({ - "courseid":task.get_course_id(), + "courseid": courseid, "taskid": task.get_id(), "username": {"$in": result["username"]} }) @@ -257,9 +256,9 @@ def POST(self, courseid, taskid, isLTI): )) elif "@action" in userinput and userinput["@action"] == "load_submission_input" and "submissionid" in userinput: - submission = self.submission_manager.get_submission(userinput["submissionid"], user_check=not is_staff) + submission = self.submission_manager.get_submission(userinput["submissionid"], user_check=not is_admin) submission = self.submission_manager.get_input_from_submission(submission) - submission = self.submission_manager.get_feedback_from_submission(submission, show_everything=is_staff) + submission = self.submission_manager.get_feedback_from_submission(submission, show_everything=is_admin) if not submission: raise NotFound(description=_("Submission doesn't exist.")) @@ -404,11 +403,11 @@ def GET(self, courseid, taskid, path): # pylint: disable=arguments-differ path_norm = posixpath.normpath(urllib.parse.unquote(path)) if taskid == "$common": - public_folder = course.get_fs().from_subfolder("$common").from_subfolder("public") + public_folder = course.get_taskset().get_fs().from_subfolder("$common").from_subfolder("public") else: task = course.get_task(taskid) - if not self.user_manager.task_is_visible_by_user(task): # ignore LTI check here + if not self.user_manager.task_is_visible_by_user(course, task): # ignore LTI check here return self.template_helper.render("task_unavailable.html") public_folder = task.get_fs().from_subfolder("public") diff --git a/inginious/frontend/pages/taskset_admin/danger_zone.py b/inginious/frontend/pages/taskset_admin/danger_zone.py new file mode 100644 index 0000000000..c189991817 --- /dev/null +++ b/inginious/frontend/pages/taskset_admin/danger_zone.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# +# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for +# more information about the licensing of this file. + +import glob +import logging +import os +import random +import flask +from flask import redirect, Response + + +from inginious.frontend.pages.taskset_admin.utils import INGIniousAdminPage +from inginious.frontend.user_manager import UserManager + + +class TasksetDangerZonePage(INGIniousAdminPage): + """ Course administration page: list of audiences """ + _logger = logging.getLogger("inginious.webapp.taskset.danger_zone") + + def delete_taskset(self, tasksetid): + """ Erase all taskset data """ + + # Deletes the taskset from the factory (entire folder) + self.taskset_factory.delete_taskset(tasksetid) + + # Removes backup + filepath = os.path.join(self.backup_dir, tasksetid) + if os.path.exists(os.path.dirname(filepath)): + for backup in glob.glob(os.path.join(filepath, '*.zip')): + os.remove(backup) + + self._logger.info("Taskset %s files erased.", tasksetid) + + def GET_AUTH(self, tasksetid): # pylint: disable=arguments-differ + """ GET request """ + taskset, __ = self.get_taskset_and_check_rights(tasksetid) + return self.page(taskset) + + def POST_AUTH(self, tasksetid): # pylint: disable=arguments-differ + """ POST request """ + taskset, __ = self.get_taskset_and_check_rights(tasksetid) + + msg = "" + error = False + + data = flask.request.form + if not data.get("token", "") == self.user_manager.session_token(): + msg = _("Operation aborted due to invalid token.") + error = True + elif "deleteall" in data: + if not data.get("tasksetid", "") == tasksetid: + msg = _("Wrong taskset id.") + error = True + else: + try: + if self.database.courses.find_one({"tasksetid": tasksetid}): + raise Exception(_("One or more course(s) rely on the current taskset.")) + self.delete_taskset(tasksetid) + return redirect(self.app.get_homepath() + '/tasksets') + except Exception as ex: + msg = _("An error occurred while deleting the taskset data: {}").format(str(ex)) + error = True + + return self.page(taskset, msg, error) + + def page(self, taskset, msg="", error=False): + """ Get all data and display the page """ + thehash = UserManager.hash_password(str(random.getrandbits(256))) + self.user_manager.set_session_token(thehash) + + has_deployed_courses = self.database.courses.find_one({"tasksetid": taskset.get_id()}) is not None + + return self.template_helper.render("taskset_admin/danger_zone.html", taskset=taskset, + has_deployed_courses=has_deployed_courses, thehash=thehash, + msg=msg, error=error) diff --git a/inginious/frontend/pages/taskset_admin/settings.py b/inginious/frontend/pages/taskset_admin/settings.py new file mode 100644 index 0000000000..a55dacab23 --- /dev/null +++ b/inginious/frontend/pages/taskset_admin/settings.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# +# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for +# more information about the licensing of this file. + +import re +import flask + +from inginious.frontend.pages.taskset_admin.utils import INGIniousAdminPage + + +class TasksetSettingsPage(INGIniousAdminPage): + """ Couse settings """ + + def GET_AUTH(self, tasksetid): # pylint: disable=arguments-differ + """ GET request """ + taskset, __ = self.get_taskset_and_check_rights(tasksetid) + return self.page(taskset) + + def POST_AUTH(self, tasksetid): # pylint: disable=arguments-differ + """ POST request """ + taskset, __ = self.get_taskset_and_check_rights(tasksetid) + + errors = [] + + data = flask.request.form + + if "delete" in data: + taskid = data["delete"] + try: + self.task_factory.get_task(taskset, taskid) + except: + errors.append(_("Invalid taskid : {}").format(taskid)) + + self.task_factory.delete_task(tasksetid, taskid) + elif "add" in data: + taskid = data["taskid"] + try: + self.task_factory.create_task(taskset, taskid, { + "name": taskid, "problems": {}, "environment_type": "mcq"}) + except Exception as ex: + errors.append(_("Couldn't create task {} : ").format(taskid) + str(ex)) + + else: + taskset_content = self.taskset_factory.get_taskset_descriptor_content(tasksetid) + taskset_content['name'] = data['name'] + if taskset_content['name'] == "": + errors.append(_('Invalid name')) + taskset_content['description'] = data['description'] + taskset_content['public'] = data['public'] == 'true' + taskset_content['admins'] = list(map(str.strip, data['admins'].split(','))) if data['admins'].strip() else [] + if not self.user_manager.user_is_superadmin() and self.user_manager.session_username() not in taskset_content['admins']: + errors.append(_('You cannot remove yourself from the administrators of this taskset')) + + if len(errors) == 0: + self.taskset_factory.update_taskset_descriptor_content(tasksetid, taskset_content) + taskset, __ = self.get_taskset_and_check_rights(tasksetid) # don't forget to reload the modified taskset + + return self.page(taskset, errors, len(errors) == 0) + + def page(self, taskset, errors=[], saved=False): + """ Get all data and display the page """ + return self.template_helper.render("taskset_admin/settings.html", taskset=taskset, errors=errors, saved=saved, + webdav_host=self.webdav_host) diff --git a/inginious/frontend/pages/course_admin/task_edit.py b/inginious/frontend/pages/taskset_admin/task_edit.py similarity index 82% rename from inginious/frontend/pages/course_admin/task_edit.py rename to inginious/frontend/pages/taskset_admin/task_edit.py index f68da25e6a..43d2f43237 100644 --- a/inginious/frontend/pages/course_admin/task_edit.py +++ b/inginious/frontend/pages/taskset_admin/task_edit.py @@ -16,27 +16,27 @@ from werkzeug.exceptions import NotFound from inginious.frontend.tasks import _migrate_from_v_0_6 -from inginious.frontend.pages.course_admin.utils import INGIniousAdminPage +from inginious.frontend.pages.taskset_admin.utils import INGIniousAdminPage from inginious.common.base import dict_from_prefix, id_checker from inginious.common.exceptions import TaskNotFoundException -from inginious.frontend.pages.course_admin.task_edit_file import CourseTaskFiles +from inginious.frontend.pages.taskset_admin.task_edit_file import CourseTaskFiles from inginious.frontend.tasks import Task -class CourseEditTask(INGIniousAdminPage): +class EditTaskPage(INGIniousAdminPage): """ Edit a task """ _logger = logging.getLogger("inginious.webapp.task_edit") - def GET_AUTH(self, courseid, taskid): # pylint: disable=arguments-differ + def GET_AUTH(self, tasksetid, taskid): # pylint: disable=arguments-differ """ Edit a task """ if not id_checker(taskid): raise NotFound(description=_("Invalid task id")) - course, __ = self.get_course_and_check_rights(courseid, allow_all_staff=False) + taskset, __ = self.get_taskset_and_check_rights(tasksetid) try: - task_data = self.task_factory.get_task_descriptor_content(courseid, taskid) + task_data = self.task_factory.get_task_descriptor_content(taskset.get_id(), taskid) except TaskNotFoundException: raise NotFound() @@ -48,22 +48,22 @@ def GET_AUTH(self, courseid, taskid): # pylint: disable=arguments-differ current_filetype = None try: - current_filetype = self.task_factory.get_task_descriptor_extension(courseid, taskid) + current_filetype = self.task_factory.get_task_descriptor_extension(taskset.get_id(), taskid) except: pass available_filetypes = self.task_factory.get_available_task_file_extensions() - additional_tabs = self.plugin_manager.call_hook('task_editor_tab', course=course, taskid=taskid, + additional_tabs = self.plugin_manager.call_hook('task_editor_tab', taskset=taskset, taskid=taskid, task_data=task_data, template_helper=self.template_helper) - return self.template_helper.render("course_admin/task_edit.html", course=course, taskid=taskid, + return self.template_helper.render("taskset_admin/task_edit.html", taskset=taskset, taskid=taskid, problem_types=self.task_factory.get_problem_types(), task_data=task_data, environment_types=environment_types, environments=environments, problemdata=json.dumps(task_data.get('problems', {})), contains_is_html=self.contains_is_html(task_data), current_filetype=current_filetype, available_filetypes=available_filetypes, - file_list=CourseTaskFiles.get_task_filelist(self.task_factory, courseid, taskid), + file_list=CourseTaskFiles.get_task_filelist(self.task_factory, taskset, taskid), additional_tabs=additional_tabs) @classmethod @@ -81,12 +81,12 @@ def parse_problem(self, problem_content): del problem_content["@order"] return self.task_factory.get_problem_types().get(problem_content["type"]).parse_problem(problem_content) - def POST_AUTH(self, courseid, taskid): # pylint: disable=arguments-differ + def POST_AUTH(self, tasksetid, taskid): # pylint: disable=arguments-differ """ Edit a task """ - if not id_checker(taskid) or not id_checker(courseid): - raise NotFound(description=_("Invalid course/task id")) + if not id_checker(taskid) or not id_checker(tasksetid): + raise NotFound(description=_("Invalid taskset/task id")) - __, __ = self.get_course_and_check_rights(courseid, allow_all_staff=False) + __, __ = self.get_taskset_and_check_rights(tasksetid) data = flask.request.form.copy() data["task_file"] = flask.request.files.get("task_file") @@ -145,24 +145,24 @@ def POST_AUTH(self, courseid, taskid): # pylint: disable=arguments-differ except Exception as message: return json.dumps({"status": "error", "message": _("Your browser returned an invalid form ({})").format(message)}) - # Get the course + # Get the taskset try: - course = self.course_factory.get_course(courseid) + taskset = self.taskset_factory.get_taskset(tasksetid) except: - return json.dumps({"status": "error", "message": _("Error while reading course's informations")}) + return json.dumps({"status": "error", "message": _("Error while reading taskset data")}) # Get original data try: - orig_data = self.task_factory.get_task_descriptor_content(courseid, taskid) + orig_data = self.task_factory.get_task_descriptor_content(taskset.get_id(), taskid) data["order"] = orig_data["order"] except: pass - task_fs = self.task_factory.get_task_fs(courseid, taskid) + task_fs = self.task_factory.get_task_fs(taskset.get_id(), taskid) task_fs.ensure_exists() # Call plugins and return the first error - plugin_results = self.plugin_manager.call_hook('task_editor_submit', course=course, taskid=taskid, + plugin_results = self.plugin_manager.call_hook('task_editor_submit', taskset=taskset, taskid=taskid, task_data=data, task_fs=task_fs) # Retrieve the first non-null element @@ -171,7 +171,7 @@ def POST_AUTH(self, courseid, taskid): # pylint: disable=arguments-differ return error try: - Task(course, taskid, data, self.course_factory.get_fs(), self.plugin_manager, self.task_factory.get_problem_types()) + Task(taskset, taskid, data, self.plugin_manager, self.task_factory.get_problem_types()) except Exception as message: return json.dumps({"status": "error", "message": _("Invalid data: {}").format(str(message))}) @@ -189,7 +189,7 @@ def POST_AUTH(self, courseid, taskid): # pylint: disable=arguments-differ {"status": "error", "message": _("There was a problem while extracting the zip archive. Some files may have been modified")}) task_fs.copy_to(tmpdirname) - self.task_factory.delete_all_possible_task_files(courseid, taskid) - self.task_factory.update_task_descriptor_content(courseid, taskid, data, force_extension=file_ext) + self.task_factory.delete_all_possible_task_files(tasksetid, taskid) + self.task_factory.update_task_descriptor_content(tasksetid, taskid, data, force_extension=file_ext) return json.dumps({"status": "ok"}) diff --git a/inginious/frontend/pages/course_admin/task_edit_file.py b/inginious/frontend/pages/taskset_admin/task_edit_file.py similarity index 60% rename from inginious/frontend/pages/course_admin/task_edit_file.py rename to inginious/frontend/pages/taskset_admin/task_edit_file.py index 4d14482bfb..59ecedfdd0 100644 --- a/inginious/frontend/pages/course_admin/task_edit_file.py +++ b/inginious/frontend/pages/taskset_admin/task_edit_file.py @@ -12,64 +12,65 @@ from werkzeug.exceptions import NotFound from inginious.common.base import id_checker -from inginious.frontend.pages.course_admin.utils import INGIniousAdminPage +from inginious.frontend.pages.taskset_admin.utils import INGIniousAdminPage class CourseTaskFiles(INGIniousAdminPage): """ Edit a task """ - def GET_AUTH(self, courseid, taskid): # pylint: disable=arguments-differ + def GET_AUTH(self, tasksetid, taskid): # pylint: disable=arguments-differ """ Edit a task """ if not id_checker(taskid): raise NotFound(description=_("Invalid task id")) - self.get_course_and_check_rights(courseid, allow_all_staff=False) + self.get_taskset_and_check_rights(tasksetid) user_input = flask.request.args if user_input.get("action") == "download" and user_input.get('path') is not None: - return self.action_download(courseid, taskid, user_input.get('path')) + return self.action_download(tasksetid, taskid, user_input.get('path')) elif user_input.get("action") == "delete" and user_input.get('path') is not None: - return self.action_delete(courseid, taskid, user_input.get('path')) + return self.action_delete(tasksetid, taskid, user_input.get('path')) elif user_input.get("action") == "rename" and user_input.get('path') is not None and user_input.get('new_path') is not None: - return self.action_rename(courseid, taskid, user_input.get('path'), user_input.get('new_path')) + return self.action_rename(tasksetid, taskid, user_input.get('path'), user_input.get('new_path')) elif user_input.get("action") == "create" and user_input.get('path') is not None: - return self.action_create(courseid, taskid, user_input.get('path')) + return self.action_create(tasksetid, taskid, user_input.get('path')) elif user_input.get("action") == "edit" and user_input.get('path') is not None: - return self.action_edit(courseid, taskid, user_input.get('path')) + return self.action_edit(tasksetid, taskid, user_input.get('path')) else: - return self.show_tab_file(courseid, taskid) + return self.show_tab_file(tasksetid, taskid) - def POST_AUTH(self, courseid, taskid): # pylint: disable=arguments-differ + def POST_AUTH(self, tasksetid, taskid): # pylint: disable=arguments-differ """ Upload or modify a file """ if not id_checker(taskid): raise NotFound(description=_("Invalid task id")) - self.get_course_and_check_rights(courseid, allow_all_staff=False) + self.get_taskset_and_check_rights(tasksetid) user_input = flask.request.form.copy() user_input["file"] = flask.request.files.get("file") if user_input.get("action") == "upload" and user_input.get('path') is not None and user_input.get('file') is not None: - return self.action_upload(courseid, taskid, user_input.get('path'), user_input.get('file')) + return self.action_upload(tasksetid, taskid, user_input.get('path'), user_input.get('file')) elif user_input.get("action") == "edit_save" and user_input.get('path') is not None and user_input.get('content') is not None: - return self.action_edit_save(courseid, taskid, user_input.get('path'), user_input.get('content')) + return self.action_edit_save(tasksetid, taskid, user_input.get('path'), user_input.get('content')) else: - return self.show_tab_file(courseid, taskid) + return self.show_tab_file(tasksetid, taskid) - def show_tab_file(self, courseid, taskid, error=None): + def show_tab_file(self, tasksetid, taskid, error=None): """ Return the file tab """ - return self.template_helper.render("course_admin/edit_tabs/files.html", - course=self.course_factory.get_course(courseid), + taskset = self.taskset_factory.get_taskset(tasksetid) + return self.template_helper.render("taskset_admin/edit_tabs/files.html", + taskset=taskset, taskid=taskid, - file_list=self.get_task_filelist(self.task_factory, courseid, taskid), + file_list=self.get_task_filelist(self.task_factory, taskset, taskid), error=error) @classmethod - def get_task_filelist(cls, task_factory, courseid, taskid): + def get_task_filelist(cls, task_factory, taskset, taskid): """ Returns a flattened version of all the files inside the task directory, excluding the files task.* and hidden files. It returns a list of tuples, of the type (Integer Level, Boolean IsDirectory, String Name, String CompleteName) """ - task_fs = task_factory.get_task_fs(courseid, taskid) + task_fs = task_factory.get_task_fs(taskset.get_id(), taskid) if not task_fs.exists(): return [] @@ -105,9 +106,9 @@ def recur_print(current, level, current_name): recur_print(tmp_out, 0, '') return recur_print.flattened - def verify_path(self, courseid, taskid, path, new_path=False): + def verify_path(self, tasksetid, taskid, path, new_path=False): """ Return the real wanted path (relative to the INGInious root) or None if the path is not valid/allowed """ - task_fs = self.task_factory.get_task_fs(courseid, taskid) + task_fs = self.task_factory.get_task_fs(tasksetid, taskid) # verify that the dir exists if not task_fs.exists(): return None @@ -128,53 +129,48 @@ def verify_path(self, courseid, taskid, path, new_path=False): self.task_factory.get_available_task_file_extensions(): return None - # do not allow hidden dir/files - if path != ".": - for i in path.split(os.path.sep): - if i.startswith("."): - return None return path - def action_edit(self, courseid, taskid, path): + def action_edit(self, tasksetid, taskid, path): """ Edit a file """ - wanted_path = self.verify_path(courseid, taskid, path) + wanted_path = self.verify_path(tasksetid, taskid, path) if wanted_path is None: return "Internal error" try: - content = self.task_factory.get_task_fs(courseid, taskid).get(wanted_path).decode("utf-8") + content = self.task_factory.get_task_fs(tasksetid, taskid).get(wanted_path).decode("utf-8") return json.dumps({"content": content}) except: return json.dumps({"error": "not-readable"}) - def action_edit_save(self, courseid, taskid, path, content): + def action_edit_save(self, tasksetid, taskid, path, content): """ Save an edited file """ - wanted_path = self.verify_path(courseid, taskid, path) + wanted_path = self.verify_path(tasksetid, taskid, path) if wanted_path is None: return json.dumps({"error": True}) try: - self.task_factory.get_task_fs(courseid, taskid).put(wanted_path, content.encode("utf-8")) + self.task_factory.get_task_fs(tasksetid, taskid).put(wanted_path, content.encode("utf-8")) return json.dumps({"ok": True}) except: return json.dumps({"error": True}) - def action_upload(self, courseid, taskid, path, fileobj): + def action_upload(self, tasksetid, taskid, path, fileobj): """ Upload a file """ # the path is given by the user. Let's normalize it path = path.strip() if not path.startswith("/"): path = "/" + path - wanted_path = self.verify_path(courseid, taskid, path, True) + wanted_path = self.verify_path(tasksetid, taskid, path, True) if wanted_path is None: - return self.show_tab_file(courseid, taskid, _("Invalid new path")) + return self.show_tab_file(tasksetid, taskid, _("Invalid new path")) - task_fs = self.task_factory.get_task_fs(courseid, taskid) + task_fs = self.task_factory.get_task_fs(tasksetid, taskid) try: task_fs.put(wanted_path, fileobj.read()) except: - return self.show_tab_file(courseid, taskid, _("An error occurred while writing the file")) - return self.show_tab_file(courseid, taskid) + return self.show_tab_file(tasksetid, taskid, _("An error occurred while writing the file")) + return self.show_tab_file(tasksetid, taskid) - def action_create(self, courseid, taskid, path): + def action_create(self, tasksetid, taskid, path): """ Delete a file or a directory """ # the path is given by the user. Let's normalize it path = path.strip() @@ -183,18 +179,18 @@ def action_create(self, courseid, taskid, path): want_directory = path.endswith("/") - wanted_path = self.verify_path(courseid, taskid, path, True) + wanted_path = self.verify_path(tasksetid, taskid, path, True) if wanted_path is None: - return self.show_tab_file(courseid, taskid, _("Invalid new path")) + return self.show_tab_file(tasksetid, taskid, _("Invalid new path")) - task_fs = self.task_factory.get_task_fs(courseid, taskid) + task_fs = self.task_factory.get_task_fs(tasksetid, taskid) if want_directory: task_fs.from_subfolder(wanted_path).ensure_exists() else: task_fs.put(wanted_path, b"") - return self.show_tab_file(courseid, taskid) + return self.show_tab_file(tasksetid, taskid) - def action_rename(self, courseid, taskid, path, new_path): + def action_rename(self, tasksetid, taskid, path, new_path): """ Delete a file or a directory """ # normalize path = path.strip() @@ -204,49 +200,49 @@ def action_rename(self, courseid, taskid, path, new_path): if not new_path.startswith("/"): new_path = "/" + new_path - old_path = self.verify_path(courseid, taskid, path) + old_path = self.verify_path(tasksetid, taskid, path) if old_path is None: - return self.show_tab_file(courseid, taskid, _("Internal error")) + return self.show_tab_file(tasksetid, taskid, _("Internal error")) - wanted_path = self.verify_path(courseid, taskid, new_path, True) + wanted_path = self.verify_path(tasksetid, taskid, new_path, True) if wanted_path is None: - return self.show_tab_file(courseid, taskid, _("Invalid new path")) + return self.show_tab_file(tasksetid, taskid, _("Invalid new path")) try: - self.task_factory.get_task_fs(courseid, taskid).move(old_path, wanted_path) - return self.show_tab_file(courseid, taskid) + self.task_factory.get_task_fs(tasksetid, taskid).move(old_path, wanted_path) + return self.show_tab_file(tasksetid, taskid) except: - return self.show_tab_file(courseid, taskid, _("An error occurred while moving the files")) + return self.show_tab_file(tasksetid, taskid, _("An error occurred while moving the files")) - def action_delete(self, courseid, taskid, path): + def action_delete(self, tasksetid, taskid, path): """ Delete a file or a directory """ # normalize path = path.strip() if not path.startswith("/"): path = "/" + path - wanted_path = self.verify_path(courseid, taskid, path) + wanted_path = self.verify_path(tasksetid, taskid, path) if wanted_path is None: - return self.show_tab_file(courseid, taskid, _("Internal error")) + return self.show_tab_file(tasksetid, taskid, _("Internal error")) # special case: cannot delete current directory of the task if "/" == wanted_path: - return self.show_tab_file(courseid, taskid, _("Internal error")) + return self.show_tab_file(tasksetid, taskid, _("Internal error")) try: - self.task_factory.get_task_fs(courseid, taskid).delete(wanted_path) - return self.show_tab_file(courseid, taskid) + self.task_factory.get_task_fs(tasksetid, taskid).delete(wanted_path) + return self.show_tab_file(tasksetid, taskid) except: - return self.show_tab_file(courseid, taskid, _("An error occurred while deleting the files")) + return self.show_tab_file(tasksetid, taskid, _("An error occurred while deleting the files")) - def action_download(self, courseid, taskid, path): + def action_download(self, tasksetid, taskid, path): """ Download a file or a directory """ - wanted_path = self.verify_path(courseid, taskid, path) + wanted_path = self.verify_path(tasksetid, taskid, path) if wanted_path is None: raise NotFound(description=_("This path doesn't exist.")) - task_fs = self.task_factory.get_task_fs(courseid, taskid) + task_fs = self.task_factory.get_task_fs(tasksetid, taskid) (method, mimetype_or_none, file_or_url) = task_fs.distribute(wanted_path) if method == "local": @@ -259,11 +255,11 @@ def action_download(self, courseid, taskid, path): class CourseTaskFileUpload(CourseTaskFiles): - def POST_AUTH(self, courseid, taskid): + def POST_AUTH(self, tasksetid, taskid): if not id_checker(taskid): raise NotFound(description=_("Invalid task id")) - self.get_course_and_check_rights(courseid, allow_all_staff=False) + self.get_taskset_and_check_rights(tasksetid) user_input = flask.request.form.copy() user_input["file"] = flask.request.files.get("file") @@ -271,6 +267,6 @@ def POST_AUTH(self, courseid, taskid): file = user_input.get('file') name = user_input.get('name') filename = "/"+name - wanted_path = self.verify_path(courseid, taskid, filename, True) - self.action_upload(courseid, taskid, wanted_path, file) - return json.dumps("success") \ No newline at end of file + wanted_path = self.verify_path(tasksetid, taskid, filename, True) + self.action_upload(tasksetid, taskid, wanted_path, file) + return json.dumps("success") diff --git a/inginious/frontend/pages/taskset_admin/template.py b/inginious/frontend/pages/taskset_admin/template.py new file mode 100644 index 0000000000..cc27bca01c --- /dev/null +++ b/inginious/frontend/pages/taskset_admin/template.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +# +# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for +# more information about the licensing of this file. +import bson +import json +import logging +import flask +from collections import OrderedDict +from natsort import natsorted + +from inginious.frontend.pages.taskset_admin.utils import INGIniousAdminPage + + +class TasksetTemplatePage(INGIniousAdminPage): + """ List information about all tasks """ + + def GET_AUTH(self, tasksetid): # pylint: disable=arguments-differ + """ GET request """ + taskset, __ = self.get_taskset_and_check_rights(tasksetid) + return self.page(taskset) + + def POST_AUTH(self, tasksetid): # pylint: disable=arguments-differ + """ POST request """ + taskset, __ = self.get_taskset_and_check_rights(tasksetid) + + errors = [] + user_input = flask.request.form + if "task_dispenser" in user_input: + selected_task_dispenser = user_input.get("task_dispenser", "toc") + task_dispenser_class = self.taskset_factory.get_task_dispensers().get(selected_task_dispenser, None) + if task_dispenser_class: + self.taskset_factory.update_taskset_descriptor_element(tasksetid, 'task_dispenser', task_dispenser_class.get_id()) + self.taskset_factory.update_taskset_descriptor_element(tasksetid, 'dispenser_data', {}) + else: + errors.append(_("Invalid task dispenser")) + elif "migrate_tasks" in user_input: + task_dispenser = taskset.get_task_dispenser() + try: + data = task_dispenser.import_legacy_tasks() + self.update_dispenser(taskset, data) + except Exception as e: + errors.append(_("Something wrong happened: ") + str(e)) + elif "clean_tasks" in user_input: + try: + self.clean_task_files(taskset) + except Exception as e: + errors.append(_("Something wrong happened: ") + str(e)) + else: + try: + self.update_dispenser(taskset, json.loads(user_input["dispenser_structure"])) + except Exception as e: + errors.append(_("Something wrong happened: ") + str(e)) + + # don't forget to reload the modified taskset + taskset, __ = self.get_taskset_and_check_rights(tasksetid) + return self.page(taskset, errors, not errors) + + def update_dispenser(self, taskset, dispenser_data): + """ Update the task dispenser based on dispenser_data """ + task_dispenser = taskset.get_task_dispenser() + data, msg = task_dispenser.check_dispenser_data(dispenser_data) + if data: + self.taskset_factory.update_taskset_descriptor_element(taskset.get_id(), 'task_dispenser', + task_dispenser.get_id()) + self.taskset_factory.update_taskset_descriptor_element(taskset.get_id(), 'dispenser_data', data) + else: + raise Exception(_("Invalid taskset structure: ") + msg) + + def clean_task_files(self, taskset): + task_dispenser = taskset.get_task_dispenser() + legacy_fields = task_dispenser.legacy_fields.keys() + for taskid in taskset.get_tasks(): + descriptor = self.task_factory.get_task_descriptor_content(taskset.get_id(), taskid) + for field in legacy_fields: + descriptor.pop(field, None) + self.task_factory.update_task_descriptor_content(taskset.get_id(), taskid, descriptor) + + def submission_url_generator(self, taskid): + """ Generates a submission url """ + return "?format=taskid%2Fusername&tasks=" + taskid + + def page(self, taskset, errors=None, validated=False): + """ Get all data and display the page """ + + # Load tasks and verify exceptions + files = self.task_factory.get_readable_tasks(taskset) + + tasks = {} + if errors is None: + errors = [] + + tasks_errors = {} + for taskid in files: + try: + tasks[taskid] = taskset.get_task(taskid) + except Exception as ex: + tasks_errors[taskid] = str(ex) + + tasks_data = natsorted([(taskid, {"name": tasks[taskid].get_name(self.user_manager.session_language()), + "url": self.submission_url_generator(taskid)}) for taskid in tasks], + key=lambda x: x[1]["name"]) + tasks_data = OrderedDict(tasks_data) + + task_dispensers = self.taskset_factory.get_task_dispensers() + + return self.template_helper.render("taskset_admin/template.html", taskset=taskset, + task_dispensers=task_dispensers, tasks=tasks_data, errors=errors, + tasks_errors=tasks_errors, validated=validated) + diff --git a/inginious/frontend/pages/taskset_admin/utils.py b/inginious/frontend/pages/taskset_admin/utils.py new file mode 100644 index 0000000000..7b2938d0c9 --- /dev/null +++ b/inginious/frontend/pages/taskset_admin/utils.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# +# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for +# more information about the licensing of this file. + +""" Utilities for administration pages """ + +from collections import OrderedDict +from datetime import datetime + +from flask import redirect, Response +from werkzeug.exceptions import Forbidden + +from inginious.common.base import id_checker +from inginious.frontend.pages.utils import INGIniousAuthPage + + +class INGIniousAdminPage(INGIniousAuthPage): + """ + An improved version of INGIniousAuthPage that checks rights for the administration + """ + + def get_taskset_and_check_rights(self, tasksetid, taskid=None): + """ Returns the course with id ``courseid`` and the task with id ``taskid``, and verify the rights of the user. + Raise app.forbidden() when there is no such course of if the users has not enough rights. + :param courseid: the course on which to check rights + :param taskid: If not None, returns also the task with id ``taskid`` + :returns (Course, Task) + """ + + try: + taskset = self.taskset_factory.get_taskset(tasksetid) + if not self.user_manager.session_username() in taskset.get_admins() and not self.user_manager.user_is_superadmin(): + raise Forbidden(description=_("You don't have admin rights on this taskset.")) + + if taskid is None: + return taskset, None + else: + return taskset, taskset.get_task(taskid) + except Forbidden as f: + raise + except: + raise Forbidden(description=_("This taskset is unreachable")) + + +class TasksetRedirectPage(INGIniousAdminPage): + """ Redirect to /settings """ + + def GET_AUTH(self, tasksetid): # pylint: disable=arguments-differ + """ GET request """ + taskset, __ = self.get_taskset_and_check_rights(tasksetid) + return redirect(self.app.get_homepath() + '/taskset/{}/settings'.format(tasksetid)) + + def POST_AUTH(self, courseid): # pylint: disable=arguments-differ + """ POST request """ + return self.GET_AUTH(courseid) + + +def get_menu(taskset, current, renderer, user_manager): + """ Returns the HTML of the menu used in the administration. ```current``` is the current page of section """ + default_entries = [("settings", "  " + _("Settings")), + ("template", "  " + _("Course template")), + ("danger", "  " + _("Danger zone"))] + + return renderer("taskset_admin/menu.html", taskset=taskset, entries=default_entries, current=current) \ No newline at end of file diff --git a/inginious/frontend/pages/tasksets.py b/inginious/frontend/pages/tasksets.py new file mode 100644 index 0000000000..27a03a80a7 --- /dev/null +++ b/inginious/frontend/pages/tasksets.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# +# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for +# more information about the licensing of this file. + +""" Index page """ +import flask +from collections import OrderedDict + +from inginious.frontend.pages.utils import INGIniousAuthPage +from inginious.frontend.exceptions import CourseAlreadyExistsException + + +class TasksetsPage(INGIniousAuthPage): + """ Index page """ + + def GET_AUTH(self): # pylint: disable=arguments-differ + """ Display main course list page """ + return self.show_page(False, []) + + def POST_AUTH(self): # pylint: disable=arguments-differ + """ Parse taskset creation or course instantiation and display the tasksets list page """ + + user_input = flask.request.form + success = None + + messages = [] + + if "instantiate" in user_input: + tasksetid = user_input["tasksetid"] + courseid = user_input["courseid"] + + try: + taskset = self.taskset_factory.get_taskset(tasksetid) + if self.user_manager.session_username() in taskset.get_admins() or taskset.is_public() or self.user_manager.user_is_superadmin(): + task_dispenser = taskset.get_task_dispenser() + self.course_factory.create_course(courseid, { + "name": courseid, "accessible": False, "tasksetid": taskset.get_id(), + "admins": [self.user_manager.session_username()], "students": [], + "task_dispenser": task_dispenser.get_id(), "dispenser_data": task_dispenser.get_dispenser_data() + }) + success = True + messages.append(_("Course with id {} successfully instantiated from taskset {}").format(courseid, tasksetid)) + else: + success = False + messages.append(_("You are not allowed to instantiate a course from this taskset.")) + except CourseAlreadyExistsException as e: + success = False + messages.append(_("A course with id {} already exists.").format(courseid)) + except Exception as e: + success = False + messages.append(_("Couldn't instantiate course with id {} : ").format(courseid) + str(e)) + + elif "new_tasksetid" in user_input and self.user_manager.user_is_superadmin(): + try: + tasksetid = user_input["new_tasksetid"] + self.taskset_factory.create_taskset(tasksetid, {"name": tasksetid, "admins": [], "description": ""}) + success = True + messages.append(_("Taskset created.")) + except: + success = False + messages.append( _("Failed to create the taskset.")) + + return self.show_page(success, messages) + + def show_page(self, success, messages): + """ Display main course list page """ + all_tasksets = self.taskset_factory.get_all_tasksets() + all_tasksets = {tasksetid: taskset for tasksetid, taskset in all_tasksets.items() if + self.user_manager.user_is_superadmin() or self.user_manager.session_username() in taskset.get_admins() or taskset.is_public()} + + tasksets = OrderedDict(sorted(iter(all_tasksets.items()), key=lambda x: x[1].get_name(self.user_manager.session_language()))) + + return self.template_helper.render("tasksets.html", tasksets=tasksets, success=success, messages=messages) diff --git a/inginious/frontend/pages/utils.py b/inginious/frontend/pages/utils.py index 1118a30987..40684b3e54 100644 --- a/inginious/frontend/pages/utils.py +++ b/inginious/frontend/pages/utils.py @@ -25,7 +25,7 @@ from inginious.frontend.parsable_text import ParsableText from pymongo.database import Database -from inginious.frontend.course_factory import CourseFactory +from inginious.frontend.taskset_factory import TasksetFactory from inginious.frontend.task_factory import TaskFactory from inginious.frontend.lti_outcome_manager import LTIOutcomeManager @@ -71,8 +71,13 @@ def plugin_manager(self) -> PluginManager: return self.app.plugin_manager @property - def course_factory(self) -> CourseFactory: + def taskset_factory(self) -> TasksetFactory: """ Returns the course factory singleton """ + return self.app.taskset_factory + + @property + def course_factory(self) -> TaskFactory: + """ Returns the task factory singleton """ return self.app.course_factory @property @@ -323,7 +328,7 @@ def show_page(self, page): return self.template_helper.render("static.html", pagetitle=title, content=content) -def generate_user_selection_box(user_manager: UserManager, render_func, current_users: List[str], course_id: str, +def generate_user_selection_box(user_manager: UserManager, render_func, current_users: List[str], name: str, id: str, placeholder: str = None, single=False): """ Returns the HTML for a user selection box. @@ -331,7 +336,7 @@ def generate_user_selection_box(user_manager: UserManager, render_func, current_ The box will return, when submitted using a form, a list of usernames separated by commas, under the given name. - NB: this function is available in the templates directly as "$user_selection_box(current_users, course_id, name, id)". + NB: this function is available in the templates directly as "$user_selection_box(current_users, name, id)". You must ignore the first argument (template_helper) in the templates. :param user_manager: UserManager instance @@ -345,7 +350,7 @@ def generate_user_selection_box(user_manager: UserManager, render_func, current_ """ current_users = [{"realname": y.realname if y is not None else x, "username": x} for x, y in user_manager.get_users_info(current_users).items()] - return render_func("course_admin/user_selection_box.html", current_users=current_users, course_id=course_id, + return render_func("user_selection_box.html", current_users=current_users, name=name, id=id, placeholder=placeholder, single=single) @@ -354,8 +359,8 @@ def register_utils(database, user_manager, template_helper: TemplateHelper): Registers utils in the template helper """ template_helper.add_to_template_globals("user_selection_box", - lambda current_users, course_id, name, id, placeholder=None, single=False: + lambda current_users, name, id, placeholder=None, single=False: generate_user_selection_box(user_manager, template_helper.render, - current_users, course_id, name, id, placeholder, + current_users, name, id, placeholder, single) ) diff --git a/inginious/frontend/parsable_text.py b/inginious/frontend/parsable_text.py index 8dceffeec1..495050fe50 100644 --- a/inginious/frontend/parsable_text.py +++ b/inginious/frontend/parsable_text.py @@ -35,6 +35,8 @@ def run(self): if not self.content: translation = _get_inginious_translation() self.content = [translation.gettext("[no content]")] + + self.options.setdefault('classes', []).append('theme:{}'.format(flask.current_app.user_manager.session_codemirror_theme())) return super(EmptiableCodeBlock, self).run() @@ -327,3 +329,4 @@ class GenAdm(CustomBaseAdmonition): directives.register_directive("warning", _gen_admonition_cls(nodes.warning)) directives.register_directive("hidden-until", HiddenUntilDirective) directives.register_directive("code-block", EmptiableCodeBlock) +directives.register_directive("code", EmptiableCodeBlock) diff --git a/inginious/frontend/plugins/auth/facebook_auth.py b/inginious/frontend/plugins/auth/facebook_auth.py index 96baa57bdd..ad0408328a 100644 --- a/inginious/frontend/plugins/auth/facebook_auth.py +++ b/inginious/frontend/plugins/auth/facebook_auth.py @@ -59,7 +59,7 @@ def get_imlink(self): return '' -def init(plugin_manager, course_factory, client, conf): +def init(plugin_manager, taskset_factory, client, conf): if conf.get("debug", False): os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' diff --git a/inginious/frontend/plugins/auth/github_auth.py b/inginious/frontend/plugins/auth/github_auth.py index 945207c16d..487c57409c 100644 --- a/inginious/frontend/plugins/auth/github_auth.py +++ b/inginious/frontend/plugins/auth/github_auth.py @@ -58,7 +58,7 @@ def get_imlink(self): return '' -def init(plugin_manager, course_factory, client, conf): +def init(plugin_manager, taskset_factory, client, conf): if conf.get("debug", False): os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' diff --git a/inginious/frontend/plugins/auth/google_auth.py b/inginious/frontend/plugins/auth/google_auth.py index 50087b63ba..a947b8759c 100644 --- a/inginious/frontend/plugins/auth/google_auth.py +++ b/inginious/frontend/plugins/auth/google_auth.py @@ -67,7 +67,7 @@ def get_imlink(self): 'user-select: none; width: 50px; height:50px;" >' -def init(plugin_manager, course_factory, client, conf): +def init(plugin_manager, taskset_factory, client, conf): if conf.get("debug", False): os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' diff --git a/inginious/frontend/plugins/auth/linkedin_auth.py b/inginious/frontend/plugins/auth/linkedin_auth.py index 681216b844..5a249a846e 100644 --- a/inginious/frontend/plugins/auth/linkedin_auth.py +++ b/inginious/frontend/plugins/auth/linkedin_auth.py @@ -62,7 +62,7 @@ def get_imlink(self): return '' -def init(plugin_manager, course_factory, client, conf): +def init(plugin_manager, taskset_factory, client, conf): if conf.get("debug", False): os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' diff --git a/inginious/frontend/plugins/auth/saml2_auth.py b/inginious/frontend/plugins/auth/saml2_auth.py index d1bb4f5d9a..23e2629625 100644 --- a/inginious/frontend/plugins/auth/saml2_auth.py +++ b/inginious/frontend/plugins/auth/saml2_auth.py @@ -165,7 +165,7 @@ def GET(self, id): raise InternalServerError(description=', '.join(errors)) -def init(plugin_manager, course_factory, client, conf): +def init(plugin_manager, taskset_factory, client, conf): plugin_manager.add_page('/auth//metadata', MetadataPage.as_view('metadatapage_' + conf.get("id"))) plugin_manager.register_auth_method(SAMLAuthMethod(conf.get("id"), conf.get('name', 'SAML'), conf.get('imlink', ''), conf)) diff --git a/inginious/frontend/plugins/auth/twitter_auth.py b/inginious/frontend/plugins/auth/twitter_auth.py index 9af22787c3..40fd6a9862 100644 --- a/inginious/frontend/plugins/auth/twitter_auth.py +++ b/inginious/frontend/plugins/auth/twitter_auth.py @@ -68,7 +68,7 @@ def get_imlink(self): return '' -def init(plugin_manager, course_factory, client, conf): +def init(plugin_manager, taskset_factory, client, conf): if conf.get("debug", False): os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' diff --git a/inginious/frontend/plugins/contests/__init__.py b/inginious/frontend/plugins/contests/__init__.py index 8da40e3117..8fe079c1a9 100644 --- a/inginious/frontend/plugins/contests/__init__.py +++ b/inginious/frontend/plugins/contests/__init__.py @@ -95,7 +95,7 @@ class ContestScoreboard(INGIniousAuthPage): """ Displays the scoreboard of the contest """ def GET_AUTH(self, courseid): # pylint: disable=arguments-differ - course = self.course_factory.get_course(courseid) + course = self.taskset_factory.get_course(courseid) task_dispenser = course.get_task_dispenser() if not task_dispenser.get_id() == Contest.get_id(): raise NotFound() @@ -191,13 +191,13 @@ class ContestAdmin(INGIniousAdminPage): def save_contest_data(self, course, contest_data): """ Saves updated contest data for the course """ - course_content = self.course_factory.get_course_descriptor_content(course.get_id()) + course_content = self.taskset_factory.get_course_descriptor_content(course.get_id()) course_content["dispenser_data"]["contest_settings"] = contest_data - self.course_factory.update_course_descriptor_content(course.get_id(), course_content) + self.taskset_factory.update_course_descriptor_content(course.get_id(), course_content) def GET_AUTH(self, courseid): # pylint: disable=arguments-differ """ GET request: simply display the form """ - course, __ = self.get_course_and_check_rights(courseid, allow_all_staff=False) + course, __ = self.get_course_and_check_rights(courseid) task_dispenser = course.get_task_dispenser() if not task_dispenser.get_id() == Contest.get_id(): raise NotFound() @@ -207,7 +207,7 @@ def GET_AUTH(self, courseid): # pylint: disable=arguments-differ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ """ POST request: update the settings """ - course, __ = self.get_course_and_check_rights(courseid, allow_all_staff=False) + course, __ = self.get_course_and_check_rights(courseid) task_dispenser = course.get_task_dispenser() if not task_dispenser.get_id() == Contest.get_id(): raise NotFound() @@ -259,7 +259,7 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ data=contest_data, errors=errors, saved=False) -def init(plugin_manager, course_factory, client, config): # pylint: disable=unused-argument +def init(plugin_manager, taskset_factory, client, config): # pylint: disable=unused-argument """ Init the contest plugin. Available configuration: @@ -275,4 +275,4 @@ def init(plugin_manager, course_factory, client, config): # pylint: disable=unu plugin_manager.add_hook('course_admin_menu', add_admin_menu) plugin_manager.add_hook('header_html', additional_headers) plugin_manager.add_hook('course_menu', course_menu) - course_factory.add_task_dispenser(Contest) + taskset_factory.add_task_dispenser(Contest) diff --git a/inginious/frontend/plugins/ltibestsubmission/__init__.py b/inginious/frontend/plugins/ltibestsubmission/__init__.py index 0d6abf595b..acf1991ae9 100644 --- a/inginious/frontend/plugins/ltibestsubmission/__init__.py +++ b/inginious/frontend/plugins/ltibestsubmission/__init__.py @@ -46,7 +46,7 @@ def GET_AUTH(self): # attach the input to the submission best_sub = self.submission_manager.get_input_from_submission(best_sub) - task = self.course_factory.get_task(courseid, taskid) + task = self.taskset_factory.get_task(courseid, taskid) question_answer_list = [] for problem in task.get_problems(): answer = best_sub["input"][problem.get_id()] diff --git a/inginious/frontend/plugins/scoreboard/__init__.py b/inginious/frontend/plugins/scoreboard/__init__.py index fd57ad948e..5c07d91b78 100644 --- a/inginious/frontend/plugins/scoreboard/__init__.py +++ b/inginious/frontend/plugins/scoreboard/__init__.py @@ -17,7 +17,7 @@ class ScoreBoardCourse(INGIniousAuthPage): def GET_AUTH(self, courseid): # pylint: disable=arguments-differ """ GET request """ - course = self.course_factory.get_course(courseid) + course = self.taskset_factory.get_course(courseid) scoreboards = course.get_descriptor().get('scoreboard', []) try: @@ -46,7 +46,7 @@ class ScoreBoard(INGIniousAuthPage): def GET_AUTH(self, courseid, scoreboardid): # pylint: disable=arguments-differ """ GET request """ - course = self.course_factory.get_course(courseid) + course = self.taskset_factory.get_course(courseid) scoreboards = course.get_descriptor().get('scoreboard', []) try: diff --git a/inginious/frontend/plugins/simple_grader/__init__.py b/inginious/frontend/plugins/simple_grader/__init__.py index 4f1d23ec0f..6d99d211c4 100644 --- a/inginious/frontend/plugins/simple_grader/__init__.py +++ b/inginious/frontend/plugins/simple_grader/__init__.py @@ -14,7 +14,7 @@ from inginious.frontend.pages.utils import INGIniousPage -def init(plugin_manager, course_factory, client, config): +def init(plugin_manager, taskset_factory, client, config): """ Init the external grader plugin. This simple grader allows only anonymous requests, and submissions are not stored in database. @@ -32,7 +32,7 @@ def init(plugin_manager, course_factory, client, config): Different types of request are available : see documentation """ courseid = config.get('courseid', 'external') - course = course_factory.get_course(courseid) + course = taskset_factory.get_course(courseid) page_pattern = config.get('page_pattern', '/external') return_fields = re.compile(config.get('return_fields', '^(result|text|problems)$')) @@ -92,7 +92,7 @@ def POST(self): if post_input.get("async") is None: # New sync job try: - result, grade, problems, tests, custom, state, archive, stdout, stderr = client_sync.new_job(0, task, task_input, "Plugin - Simple Grader") + result, grade, problems, tests, custom, state, archive, stdout, stderr = client_sync.new_job(0, course.get_taskset(), task, task_input, "Plugin - Simple Grader") job_return = {"result":result, "grade": grade, "problems": problems, "tests": tests, "custom": custom, "state": state, "archive": archive, "stdout": stdout, "stderr": stderr} except: response.response = [json.dumps({"status": "error", "status_message": "An internal error occurred"})] @@ -102,7 +102,7 @@ def POST(self): return response else: # New async job - jobid = client_buffer.new_job(task, task_input, "Plugin - Simple Grader") + jobid = client_buffer.new_job(0, course.get_taskset(), task, task_input, "Plugin - Simple Grader") response.response = [json.dumps({"status": "done", "jobid": str(jobid)})] return response elif "jobid" in post_input: diff --git a/inginious/frontend/plugins/task_editor_hook_example/__init__.py b/inginious/frontend/plugins/task_editor_hook_example/__init__.py index a52ae55eb6..5a72bb3dbc 100644 --- a/inginious/frontend/plugins/task_editor_hook_example/__init__.py +++ b/inginious/frontend/plugins/task_editor_hook_example/__init__.py @@ -29,7 +29,7 @@ def on_task_editor_submit(course, taskid, task_data, task_fs): return json.dumps({"status": "error", "message": "You must provide a task hint in Example tab 2"}) -def init(plugin_manager, course_factory, client, config): +def init(plugin_manager, taskset_factory, client, config): plugin_manager.add_hook('task_editor_tab', example_task_editor_tab) plugin_manager.add_hook('task_editor_tab', example_task_editor_tab_2) diff --git a/inginious/frontend/plugins/upcoming_tasks/__init__.py b/inginious/frontend/plugins/upcoming_tasks/__init__.py index d1e662b047..4a4b294b6e 100644 --- a/inginious/frontend/plugins/upcoming_tasks/__init__.py +++ b/inginious/frontend/plugins/upcoming_tasks/__init__.py @@ -53,7 +53,7 @@ def time_planner_conversion(self, string_time_planner): def page(self, time_planner): """ General main method called for GET and POST """ username = self.user_manager.session_username() - all_courses = self.course_factory.get_all_courses() + all_courses = self.taskset_factory.get_all_courses() time_planner = self.time_planner_conversion(time_planner) # Get the courses id diff --git a/inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html b/inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html index 3707b8e27c..4e9148a755 100644 --- a/inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html +++ b/inginious/frontend/plugins/upcoming_tasks/templates/coming_tasks.html @@ -7,7 +7,7 @@ {% block navbar %}