def update_task_descriptor_content(self, courseid, taskid, content, force_extension=None): """ Update the task descriptor with the dict in content :param course: the course id of the course :param taskid: the task id of the task :param content: the content to put in the task file :param force_extension: If None, save it the same format. Else, save with the given extension :raise InvalidNameException, TaskNotFoundException, TaskUnreadableException """ if not id_checker(courseid): raise InvalidNameException("Course with invalid name: " + courseid) if not id_checker(taskid): raise InvalidNameException("Task with invalid name: " + taskid) if force_extension is None: path_to_descriptor, _, descriptor_manager = self._get_task_descriptor_info(courseid, taskid) elif force_extension in self.get_available_task_file_extensions(): path_to_descriptor = os.path.join(self._tasks_directory, courseid, taskid, "task." + force_extension) descriptor_manager = self._task_file_managers[force_extension] else: raise TaskReaderNotFoundException() try: with codecs.open(path_to_descriptor, 'w', 'utf-8') as fd: fd.write(descriptor_manager.dump(content)) except: raise TaskNotFoundException()
def get_directory_path(self, courseid, taskid): """ :param courseid: the course id of the course :param taskid: the task id of the task :raise InvalidNameException :return: The path to the directory that contains the files related to the task """ if not id_checker(courseid): raise InvalidNameException("Course with invalid name: " + courseid) if not id_checker(taskid): raise InvalidNameException("Task with invalid name: " + taskid) return os.path.join(self._tasks_directory, courseid, taskid)
def get_task_fs(self, courseid, taskid): """ :param courseid: the course id of the course :param taskid: the task id of the task :raise InvalidNameException :return: A FileSystemProvider to the folder containing the task files """ if not id_checker(courseid): raise InvalidNameException("Course with invalid name: " + courseid) if not id_checker(taskid): raise InvalidNameException("Task with invalid name: " + taskid) return self._filesystem.from_subfolder(courseid).from_subfolder(taskid)
def delete_all_possible_task_files(self, courseid, taskid): """ Deletes all possibles task files in directory, to allow to change the format """ if not id_checker(courseid): raise InvalidNameException("Course with invalid name: " + courseid) if not id_checker(taskid): raise InvalidNameException("Task with invalid name: " + taskid) task_fs = self.get_task_fs(courseid, taskid) for ext in self.get_available_task_file_extensions(): try: task_fs.delete("task." + ext) except: pass
def get_task_descriptor_extension(self, courseid, taskid): """ :param courseid: the course id of the course :param taskid: the task id of the task :raise InvalidNameException, TaskNotFoundException :return: the current extension of the task descriptor """ if not id_checker(courseid): raise InvalidNameException("Course with invalid name: " + courseid) if not id_checker(taskid): raise InvalidNameException("Task with invalid name: " + taskid) _, descriptor_ext, _2 = self._get_task_descriptor_info(courseid, taskid) return descriptor_ext
def get_task_descriptor_extension(self, courseid, taskid): """ :param courseid: the course id of the course :param taskid: the task id of the task :raise InvalidNameException, TaskNotFoundException :return: the current extension of the task descriptor """ if not id_checker(courseid): raise InvalidNameException("Course with invalid name: " + courseid) if not id_checker(taskid): raise InvalidNameException("Task with invalid name: " + taskid) descriptor_path = self._get_task_descriptor_info(courseid, taskid)[0] return splitext(descriptor_path)[1]
def GET_AUTH(self, courseid, taskid): # pylint: disable=arguments-differ """ Edit a task """ if not id_checker(taskid): raise Exception("Invalid task id") course, __ = self.get_course_and_check_rights(courseid, allow_all_staff=False) try: task_data = self.task_factory.get_task_descriptor_content( courseid, taskid) except: task_data = None if task_data is None: task_data = {} environments = self.containers current_filetype = None try: current_filetype = self.task_factory.get_task_descriptor_extension( courseid, taskid) except: pass available_filetypes = self.task_factory.get_available_task_file_extensions( ) return self.template_helper.get_renderer().course_admin.task_edit( course, taskid, self.task_factory.get_problem_types(), task_data, environments, json.dumps(task_data.get('problems', {})), self.contains_is_html(task_data), current_filetype, available_filetypes, AccessibleTime, CourseTaskFiles.get_task_filelist(self.task_factory, courseid, taskid))
def __init__(self, course, taskid, content, directory_path, task_problem_types=None): # We load the descriptor of the task here to allow plugins to modify settings of the task before it is read by the Task constructor if not id_checker(taskid): raise Exception("Task with invalid id: " + course.get_id() + "/" + taskid) task_problem_types = task_problem_types or { "code": DisplayableCodeProblem, "code-file": DisplayableCodeFileProblem, "code-single-line": DisplayableCodeSingleLineProblem, "multiple-choice": DisplayableMultipleChoiceProblem, "match": DisplayableMatchProblem} super(FrontendTask, self).__init__(course, taskid, content, directory_path, task_problem_types) self._name = self._data.get('name', 'Task {}'.format(self.get_id())) self._context = ParsableText(self._data.get('context', ""), "rst") # Authors if isinstance(self._data.get('author'), basestring): # verify if author is a string self._author = [self._data['author']] elif isinstance(self._data.get('author'), list): # verify if author is a list for author in self._data['author']: if not isinstance(author, basestring): # authors must be strings raise Exception("This task has an invalid author") self._author = self._data['author'] else: self._author = []
def get_temporal_task_file_content(self, course, taskid): """ :param course: a Course object :param taskid: the task id of the task :raise InvalidNameException, TaskUnreadableException :return: the content of the temporary task file """ if not id_checker(taskid): raise InvalidNameException("Task with invalid name: " + taskid) task_file_manager = None task_fs = self.get_task_fs(course.get_id(), taskid) for file_extension, file_manager in self._task_file_managers.items(): if task_fs.get("task_temp." + file_extension): task_file_manager = file_manager temporal_task_file_content = {} try: temporal_task_file_content["data"] = task_file_manager.load( task_fs.get("task_temp.yaml")) except Exception as e: raise TaskUnreadableException(str(e)) return temporal_task_file_content
def __init__(self, course, taskid, content, directory_path, hook_manager, task_problem_types=None): # We load the descriptor of the task here to allow plugins to modify settings of the task before it is read by the Task constructor if not id_checker(taskid): raise Exception("Task with invalid id: " + course.get_id() + "/" + taskid) task_problem_types = task_problem_types or { "code": DisplayableCodeProblem, "code-file": DisplayableCodeFileProblem, "code-single-line": DisplayableCodeSingleLineProblem, "multiple-choice": DisplayableMultipleChoiceProblem, "match": DisplayableMatchProblem} super(FrontendTask, self).__init__(course, taskid, content, directory_path, hook_manager, task_problem_types) self._name = self._data.get('name', 'Task {}'.format(self.get_id())) self._context = ParsableText(self._data.get('context', ""), "rst") # Authors if isinstance(self._data.get('author'), str): # verify if author is a string self._author = [self._data['author']] elif isinstance(self._data.get('author'), list): # verify if author is a list for author in self._data['author']: if not isinstance(author, str): # authors must be strings raise Exception("This task has an invalid author") self._author = self._data['author'] else: self._author = [] # Submission storage self._stored_submissions = int(self._data.get("stored_submissions", 0)) # Default download self._evaluate = self._data.get("evaluate", "best")
def get_input(self): """ Loads web input, initialise default values and check/sanitise some inputs from users """ user_input = web.input( user=[], task=[], aggregation=[], org_tags=[], grade_min='', grade_max='', sort_by="submitted_on", order='0', # "0" for pymongo.DESCENDING, anything else for pymongo.ASCENDING limit='', filter_tags=[], filter_tags_presence=[], date_after='', date_before='', stat='with_stat', ) # Sanitise inputs for item in itertools.chain(user_input.user, user_input.task, user_input.aggregation): if not id_checker(item): raise web.notfound() if user_input.sort_by not in self._allowed_sort: raise web.notfound() digits = [user_input.grade_min, user_input.grade_max, user_input.order, user_input.limit] for d in digits: if d != '' and not d.isdigit(): raise web.notfound() return user_input
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) if course_fs.exists("course.yaml") or course_fs.exists("course.json"): raise ImportCourseException("Course with id " + new_courseid + " already exists.") try: git.clone(course.get_link(), course_fs.prefix) except: raise ImportCourseException(_("Couldn't clone course into your instance")) try: old_descriptor = course_factory.get_course_descriptor_content(new_courseid) except: old_descriptor ={} try: new_descriptor = {"description": old_descriptor.get("description", ""), 'admins': [username], "accessible": False, "tags": old_descriptor.get("tags", {})} if "name" in old_descriptor: new_descriptor["name"] = old_descriptor["name"] + " - " + new_courseid else: new_descriptor["name"] = new_courseid if "toc" in old_descriptor: new_descriptor["toc"] = old_descriptor["toc"] course_factory.update_course_descriptor_content(new_courseid, new_descriptor) except: course_factory.delete_course(new_courseid) raise ImportCourseException(_("An error occur while editing the course description")) get_course_logger(new_courseid).info("Course %s cloned from the marketplace.", new_courseid)
def GET_AUTH(self, courseid, 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) 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')) elif user_input.get("action") == "delete" and user_input.get( 'path') is not None: return self.action_delete(courseid, 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')) elif user_input.get("action") == "create" and user_input.get( 'path') is not None: return self.action_create(courseid, 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')) else: return self.show_tab_file(courseid, taskid)
def update_temporal_task_file(self, course, taskid, data): """ :param course: a Course object :param taskid: the task id of the task :param data: a Dict with the temporary data of the task to be stored :raise InvalidNameException, TaskReaderNotFoundException, TaskNotFoundException Create or Update a temporary task file that is used to store the task data that is required for some plugins """ if not id_checker(taskid): raise InvalidNameException("Task with invalid name: " + taskid) task_fs = self.get_task_fs(course.get_id(), taskid) task_file_manager = None try: for file_extension, file_manager in self._task_file_managers.items( ): if file_extension is "yaml": task_file_manager = file_manager except: raise TaskReaderNotFoundException() if task_file_manager: temporal_task_file_content = task_file_manager.dump(data) try: task_fs.put("task_temp.yaml", temporal_task_file_content) except: raise TaskNotFoundException()
def create_course(self, courseid, init_content): """ :param courseid: the course id of the course :param init_content: initial descriptor content :raise InvalidNameException or CourseAlreadyExistsException Create a new course folder and set initial descriptor content, folder can already exist """ if not id_checker(courseid): raise InvalidNameException("Course with invalid name: " + courseid) course_directory = os.path.join(self._tasks_directory, courseid) if not os.path.exists(course_directory): os.mkdir(course_directory) base_file = os.path.join(course_directory, "course") if not os.path.isfile(base_file + ".yaml") and not os.path.isfile(base_file + ".json"): write_yaml(os.path.join(course_directory, "course.yaml"), init_content) else: raise CourseAlreadyExistsException("Course with id " + courseid + " already exists.") get_course_logger(courseid).info("Course %s created in the factory.", courseid)
def _update_cache(self, course, taskid): """ Updates the cache :param course: a Course object :param taskid: a (valid) task id :raise InvalidNameException, TaskNotFoundException, TaskUnreadableException """ if not id_checker(taskid): raise InvalidNameException("Task with invalid name: " + taskid) task_fs = self.get_task_fs(course.get_id(), taskid) descriptor_name, descriptor_reader = self._get_task_descriptor_info(course.get_id(), taskid) try: task_content = descriptor_reader.load(task_fs.get(descriptor_name)) except Exception as e: raise TaskUnreadableException(str(e)) last_modif = {descriptor_name: task_fs.get_last_modification_time(descriptor_name)} translations_fs = task_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"): last_modif["$i18n/" + lang + ".mo"] = translations_fs.get_last_modification_time(lang + ".mo") self._cache[(course.get_id(), taskid)] = ( self._task_class(course, taskid, task_content, task_fs, self._hook_manager, self._task_problem_types), last_modif )
def get_input(self): """ Loads web input, initialise default values and check/sanitise some inputs from users """ user_input = web.input( user=[], task=[], aggregation=[], org_tags=[], grade_min='', grade_max='', sort_by="submitted_on", order='0', # "0" for pymongo.DESCENDING, anything else for pymongo.ASCENDING limit='', filter_tags=[], filter_tags_presence=[], date_after='', date_before='', stat='with_stat', ) # Sanitise inputs for item in itertools.chain(user_input.task, user_input.aggregation): if not id_checker(item): raise web.notfound() if user_input.sort_by not in self._allowed_sort: raise web.notfound() digits = [user_input.grade_min, user_input.grade_max, user_input.order, user_input.limit] for d in digits: if d != '' and not d.isdigit(): raise web.notfound() return user_input
def create_course(self, courseid, init_content): """ :param courseid: the course id of the course :param init_content: initial descriptor content :raise InvalidNameException or CourseAlreadyExistsException Create a new course folder and set initial descriptor content, folder can already exist """ 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) self._hook_manager.call_hook('course_created', courseid=courseid, new_content=init_content)
def _cache_update_needed(self, course, taskid): """ :param course: a Course object :param taskid: a (valid) task id :raise InvalidNameException, TaskNotFoundException :return: True if an update of the cache is needed, False else """ if not id_checker(taskid): raise InvalidNameException("Task with invalid name: " + taskid) task_fs = self.get_task_fs(course.get_id(), taskid) if (course.get_id(), taskid) not in self._cache: return True try: descriptor_name = self._get_task_descriptor_info(course.get_id(), taskid)[0] last_update = {descriptor_name: task_fs.get_last_modification_time(descriptor_name)} translations_fs = task_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"): last_update["$i18n/" + lang + ".mo"] = translations_fs.get_last_modification_time(lang + ".mo") except: raise TaskNotFoundException() last_modif = self._cache[(course.get_id(), taskid)][1] for filename, mftime in last_update.items(): if filename not in last_modif or last_modif[filename] < mftime: return True return False
def GET_AUTH(self, courseid, taskid): # pylint: disable=arguments-differ """ Edit a task """ if not id_checker(taskid): raise Exception("Invalid task id") self.get_course_and_check_rights(courseid, allow_all_staff=False) tmp = courseid try: self.task_factory.get_task_descriptor_content(courseid, taskid) except: try: courseid = self.bank_name self.task_factory.get_task_descriptor_content(courseid, taskid) except: courseid = tmp request = web.input() if request.get("action") == "download" and request.get( 'path') is not None: return self.action_download(courseid, taskid, request.get('path')) elif request.get("action") == "delete" and request.get( 'path') is not None: return self.action_testcase_delete(courseid, taskid, request.get('path')) elif request.get("action") == "rename" and request.get( 'path') is not None and request.get('new_path') is not None: return self.action_rename(courseid, taskid, request.get('path'), request.get('new_path')) elif request.get("action") == "create" and request.get( 'path') is not None: return self.action_create(courseid, taskid, request.get('path')) elif request.get("action") == "edit" and request.get( 'path') is not None: return self.action_edit(courseid, taskid, request.get('path')) else: return self.show_tab_file(courseid, taskid)
def POST_AUTH(self, courseid, taskid): # pylint: disable=arguments-differ """ Upload or modify a file """ if not id_checker(taskid): raise Exception("Invalid task id") self.get_course_and_check_rights(courseid, allow_all_staff=False) tmp = courseid try: self.task_factory.get_task_descriptor_content(courseid, taskid) except: try: courseid = self.bank_name self.task_factory.get_task_descriptor_content(courseid, taskid) except: courseid = tmp request = web.input(testcase_input={}, testcase_output={}, testcase_feedback={}) if request.get("action") == "upload" and request.get( 'testcase_desc') is not None and request.get( 'testcase_input') is not None and request.get( 'testcase_output') is not None: output_value = request.get( 'testcase_output').value if not isinstance( request.get('testcase_output'), str) else 1 return self.action_upload_testcase( courseid, taskid, request.get('testcase_desc'), request.get('testcase_input'), request.get('testcase_output'), request.get('testcase_feedback', {}), output_value) elif request.get("action") == "edit_save" and request.get( 'path') is not None and request.get('content') is not None: return self.action_edit_save(courseid, taskid, request.get('path'), request.get('content')) else: return self.show_tab_file(courseid, taskid)
def _cache_update_needed(self, course, taskid): """ :param course: a Course object :param taskid: a (valid) task id :raise InvalidNameException, TaskNotFoundException :return: True if an update of the cache is needed, False else """ if not id_checker(taskid): raise InvalidNameException("Task with invalid name: " + taskid) task_fs = self.get_task_fs(course.get_id(), taskid) if (course.get_id(), taskid) not in self._cache: return True try: last_update, __, __ = self._get_last_updates( course, taskid, task_fs, False) except: raise TaskNotFoundException() last_modif = self._cache[(course.get_id(), taskid)][1] for filename, mftime in last_update.items(): if filename not in last_modif or last_modif[filename] < mftime: return True return False
def delete_task(self, courseid, taskid): """ Erase the content of the task folder :param courseid: the course id of the course :param taskid: the task id of the task :raise: InvalidNameException or CourseNotFoundException """ if not id_checker(courseid): raise InvalidNameException("Course with invalid name: " + courseid) if not id_checker(taskid): raise InvalidNameException("Task with invalid name: " + taskid) task_fs = self.get_task_fs(courseid, taskid) if task_fs.exists(): task_fs.delete() get_course_logger(courseid).info("Task %s erased from the factory.", taskid)
def get_course_fs(self, courseid): """ :param courseid: :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 __init__(self, task, problemid, content): if not id_checker(problemid): raise Exception("Invalid problem _id: " + problemid) self._id = problemid self._task = task self._name = content['name'] if "name" in content else "" self._original_content = content
def __init__(self, structure): Section.__init__(self, structure) if not all(id_checker(id) for id in structure["tasks_list"]): raise InvalidTocException( _("One task id contains non alphanumerical characters")) self._task_list = [ task for task, _ in sorted(structure["tasks_list"].items(), key=lambda x: x[1]) ]
def __init__(self, task, problemid, content, translations=None): if not id_checker(problemid): raise Exception("Invalid problem _id: " + problemid) self._translations = translations self._id = problemid self._task = task self._name = content['name'] if "name" in content else "" self._original_content = content
def _create_task_problem(self, problemid, problem_content, task_problem_types): """Creates a new instance of the right class for a given problem.""" # Basic checks if not id_checker(problemid): raise Exception("Invalid problem _id: " + problemid) if problem_content.get('type', "") not in task_problem_types: raise Exception("Invalid type for problem " + problemid) return task_problem_types.get(problem_content.get('type', ""))(self, problemid, problem_content)
def get_task_descriptor_content(self, courseid, taskid): """ :param courseid: the course id of the course :param taskid: the task id of the task :raise InvalidNameException, TaskNotFoundException, TaskUnreadableException :return: the content of the task descriptor, as a dict """ if not id_checker(courseid): raise InvalidNameException("Course with invalid name: " + courseid) if not id_checker(taskid): raise InvalidNameException("Task with invalid name: " + taskid) descriptor_path, descriptor_manager = self._get_task_descriptor_info(courseid, taskid) try: task_content = descriptor_manager.load(self.get_task_fs(courseid, taskid).get(descriptor_path)) except Exception as e: raise TaskUnreadableException(str(e)) return task_content
def __init__(self, course, taskid, content, task_fs, translations_fs, hook_manager, task_problem_types): # We load the descriptor of the task here to allow plugins to modify settings of the task before it is read by the Task constructor if not id_checker(taskid): raise Exception("Task with invalid id: " + course.get_id() + "/" + taskid) super(WebAppTask, self).__init__(course, taskid, content, task_fs, translations_fs, hook_manager, task_problem_types) self._name = self._data.get('name', 'Task {}'.format(self.get_id())) self._context = self._data.get('context', "") # Authors if isinstance(self._data.get('author'), str): # verify if author is a string self._author = self._data['author'] else: self._author = "" if isinstance(self._data.get('contact_url'), str): self._contact_url = self._data['contact_url'] else: self._contact_url = "" # Submission storage self._stored_submissions = int(self._data.get("stored_submissions", 0)) # Default download self._evaluate = self._data.get("evaluate", "best") # Grade weight self._weight = float(self._data.get("weight", 1.0)) # _accessible self._accessible = AccessibleTime(self._data.get("accessible", None)) # Group task self._groups = bool(self._data.get("groups", False)) # Submission limits self._submission_limit = self._data.get("submission_limit", { "amount": -1, "period": -1 }) # Input random self._input_random = int(self._data.get("input_random", 0)) # Regenerate input random self._regenerate_input_random = bool( self._data.get("regenerate_input_random", False)) # Category tags self._categories = self._data.get("categories", [])
def get_task_descriptor_content(self, courseid, taskid): """ :param courseid: the course id of the course :param taskid: the task id of the task :raise InvalidNameException, TaskNotFoundException, TaskUnreadableException :return: the content of the task descriptor, as a dict """ if not id_checker(courseid): raise InvalidNameException("Course with invalid name: " + courseid) if not id_checker(taskid): raise InvalidNameException("Task with invalid name: " + taskid) path_to_descriptor, _, descriptor_manager = self._get_task_descriptor_info(courseid, taskid) try: with codecs.open(path_to_descriptor, 'r', 'utf-8') as fd: task_content = descriptor_manager.load(fd.read()) except Exception as e: raise TaskUnreadableException(str(e)) return task_content
def get_task_descriptor_content(self, courseid, taskid): """ :param courseid: the course id of the course :param taskid: the task id of the task :raise InvalidNameException, TaskNotFoundException, TaskUnreadableException :return: the content of the task descriptor, as a dict """ if not id_checker(courseid): raise InvalidNameException("Course with invalid name: " + courseid) if not id_checker(taskid): raise InvalidNameException("Task with invalid name: " + taskid) path_to_descriptor, descriptor_ext, descriptor_manager = self._get_task_descriptor_info(courseid, taskid) try: with codecs.open(path_to_descriptor, 'r', 'utf-8') as fd: task_content = descriptor_manager.load(fd.read()) except Exception as e: raise TaskUnreadableException(str(e)) return task_content
def __init__(self, course, taskid, content, task_fs, hook_manager, task_problem_types=None): # We load the descriptor of the task here to allow plugins to modify settings of the task before it is read by the Task constructor if not id_checker(taskid): raise Exception("Task with invalid id: " + course.get_id() + "/" + taskid) task_problem_types = task_problem_types or { "code": DisplayableCodeProblem, "code-file": DisplayableCodeFileProblem, "code-single-line": DisplayableCodeSingleLineProblem, "multiple-choice": DisplayableMultipleChoiceProblem, "match": DisplayableMatchProblem } super(WebAppTask, self).__init__(course, taskid, content, task_fs, hook_manager, task_problem_types) self._name = self._data.get('name', 'Task {}'.format(self.get_id())) self._context = self._data.get('context', "") # Authors if isinstance(self._data.get('author'), str): # verify if author is a string self._author = self._data['author'] else: self._author = "" # Submission storage self._stored_submissions = int(self._data.get("stored_submissions", 0)) # Default download self._evaluate = self._data.get("evaluate", "best") # Grade weight self._weight = float(self._data.get("weight", 1.0)) # _accessible self._accessible = AccessibleTime(self._data.get("accessible", None)) # Group task self._groups = bool(self._data.get("groups", False)) # Submission limits self._submission_limit = self._data.get("submission_limit", { "amount": -1, "period": -1 }) # Tags self._tags = Tag.create_tags_from_dict(self._data.get("tags", {}))
def _create_task_problem(self, problemid, problem_content, task_problem_types): """Creates a new instance of the right class for a given problem.""" # Basic checks if not id_checker(problemid): raise Exception("Invalid problem _id: " + problemid) if problem_content.get('type', "") not in task_problem_types: raise Exception("Invalid type for problem " + problemid) return task_problem_types.get(problem_content.get('type', ""))(self, problemid, problem_content, self._translations)
def __init__(self, problemid, content, translations, taskfs): if not id_checker(problemid): raise Exception("Invalid problem _id: " + problemid) self._id = problemid self._translations = translations self._name = content['name'] if "name" in content else "" self._original_content = content self._task_fs = taskfs
def __init__(self, task, problemid, content): if not id_checker(problemid): raise Exception("Invalid problem _id: " + problemid) self._id = problemid self._task = task self._name = content['name'] if "name" in content else "" self._header = content['header'] if "header" in content else "" self._original_content = content
def _create_box(self, boxid, box_content): """ Create adequate box """ if not id_checker(boxid) and not boxid == "": raise Exception("Invalid box _id " + boxid) if "type" not in box_content: raise Exception("Box " + boxid + " does not have a type") try: return self._box_types[box_content["type"]](self, boxid, box_content) except: raise Exception("Unknow box type " + box_content["type"] + "for box _id " + boxid)
def _create_box(self, boxid, box_content): """ Create adequate box """ if not id_checker(boxid) and not boxid == "": raise Exception("Invalid box _id " + boxid) if "type" not in box_content: raise Exception("Box " + boxid + " does not have a type") if box_content["type"] not in self._box_types: raise Exception("Unknown box type " + box_content["type"] + " for box _id " + boxid) return self._box_types[box_content["type"]](self, boxid, box_content)
def __init__(self, task, problemid, content, translations=None): if not id_checker(problemid): raise Exception("Invalid problem _id: " + problemid) self._translations = translations self._id = problemid self._task = task self._name = content['name'] if "name" in content else "" self._header = content['header'] if "header" in content else "" self._original_content = content
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_descriptor_info(self, courseid, taskid): """ :param courseid: the course id of the course :param taskid: the task id of the task :raise InvalidNameException, TaskNotFoundException :return: a tuple, containing: (the path to the descriptor of the task, extension of the descriptor, task file manager for the descriptor) """ if not id_checker(courseid): raise InvalidNameException("Course with invalid name: " + courseid) if not id_checker(taskid): raise InvalidNameException("Task with invalid name: " + taskid) base_file = os.path.join(self._tasks_directory, courseid, taskid, "task") for ext, task_file_manager in self._task_file_managers.iteritems(): if os.path.isfile(base_file + "." + ext): return (base_file + "." + ext, ext, task_file_manager) raise TaskNotFoundException()
def get_task(self, course, taskid): """ :param course: a Course object :param taskid: the task id of the task :raise InvalidNameException, TaskNotFoundException, TaskUnreadableException :return: an object representing the task, of the type given in the constructor """ if not id_checker(taskid): raise InvalidNameException("Task with invalid name: " + taskid) if self._cache_update_needed(course, taskid): self._update_cache(course, taskid) return self._cache[(course.get_id(), taskid)][0]
def POST_AUTH(self, courseid, taskid): if not id_checker(taskid): raise Exception("Invalid task id") self.get_course_and_check_rights(courseid, allow_all_staff=False) request = web.input(file={}) if request.get('file') is not None: file = request.get('file') name = request.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")
def POST(self, courseid, taskid): """ Upload or modify a file """ if not id_checker(taskid): raise Exception("Invalid task id") self.get_course_and_check_rights(courseid, allow_all_staff=False) request = web.input(file={}) if request.get("action") == "upload" and request.get('path') is not None and request.get('file') is not None: return self.action_upload(courseid, taskid, request.get('path'), request.get('file')) elif request.get("action") == "edit_save" and request.get('path') is not None and request.get('content') is not None: return self.action_edit_save(courseid, taskid, request.get('path'), request.get('content')) else: return self.show_tab_file(courseid, taskid)
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 _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) base_file = os.path.join(self._tasks_directory, courseid, "course") if os.path.isfile(base_file + ".yaml"): return base_file + ".yaml" elif os.path.isfile(base_file + ".json"): return base_file + ".json" else: raise CourseNotFoundException()
def GET(self, courseid, taskid): """ Edit a task """ if not id_checker(taskid): raise Exception("Invalid task id") course, _ = self.get_course_and_check_rights(courseid, allow_all_staff=False) try: task_data = self.task_factory.get_task_descriptor_content(courseid, taskid) except: task_data = None if task_data is None: task_data = {} environments = self.containers current_filetype = None try: current_filetype = self.task_factory.get_task_descriptor_extension(courseid, taskid) except: pass available_filetypes = self.task_factory.get_available_task_file_extensions() # custom problem-type: for pid in task_data.get("problems", {}): problem = task_data["problems"][pid] if (problem["type"] == "code" and "boxes" in problem) or problem["type"] not in ( "code", "code-single-line", "code-file", "match", "multiple-choice"): problem_copy = copy.deepcopy(problem) for i in ["name", "header"]: if i in problem_copy: del problem_copy[i] problem["custom"] = inginious.common.custom_yaml.dump(problem_copy) return self.template_helper.get_renderer().course_admin.edit_task( course, taskid, task_data, environments, json.dumps( task_data.get( 'problems', {})), self.contains_is_html(task_data), current_filetype, available_filetypes, AccessibleTime, CourseTaskFiles.get_task_filelist(self.task_factory, courseid, taskid))
def delete_course(self, courseid): """ :param courseid: the course id of the course :raise InvalidNameException or CourseNotFoundException Erase the content of the course folder """ if not id_checker(courseid): raise InvalidNameException("Course with invalid name: " + courseid) course_fs = self.get_course_fs(courseid) if not course_fs.exists(): raise CourseNotFoundException() course_fs.delete() get_course_logger(courseid).info("Course %s erased from the factory.", courseid)
def create_course(self, courseid, init_content): """ :param courseid: the course id of the course :param init_content: initial descriptor content :raise InvalidNameException or CourseAlreadyExistsException Create a new course folder and set initial descriptor content, folder can already exist """ 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)
def __init__(self, course, taskid, content, task_fs, translations_fs, hook_manager, task_problem_types): # We load the descriptor of the task here to allow plugins to modify settings of the task before it is read by the Task constructor if not id_checker(taskid): raise Exception("Task with invalid id: " + course.get_id() + "/" + taskid) super(WebAppTask, self).__init__(course, taskid, content, task_fs, translations_fs, hook_manager, task_problem_types) self._name = self._data.get('name', 'Task {}'.format(self.get_id())) self._context = self._data.get('context', "") # Authors if isinstance(self._data.get('author'), str): # verify if author is a string self._author = self._data['author'] else: self._author = "" # Submission storage self._stored_submissions = int(self._data.get("stored_submissions", 0)) # Default download self._evaluate = self._data.get("evaluate", "best") # Grade weight self._weight = float(self._data.get("weight", 1.0)) # _accessible self._accessible = AccessibleTime(self._data.get("accessible", None)) # Group task self._groups = bool(self._data.get("groups", False)) # Submission limits self._submission_limit = self._data.get("submission_limit", {"amount": -1, "period": -1}) # Input random self._input_random = int(self._data.get("input_random", 0)) # Regenerate input random self._regenerate_input_random = bool(self._data.get("regenerate_input_random", False)) # Tags self._tags = Tag.create_tags_from_dict(self._data.get("tags", {}))
def GET(self, courseid, taskid): """ Edit a task """ if not id_checker(taskid): raise Exception("Invalid task id") self.get_course_and_check_rights(courseid, allow_all_staff=False) request = web.input() if request.get("action") == "download" and request.get('path') is not None: return self.action_download(courseid, taskid, request.get('path')) elif request.get("action") == "delete" and request.get('path') is not None: return self.action_delete(courseid, taskid, request.get('path')) elif request.get("action") == "rename" and request.get('path') is not None and request.get('new_path') is not None: return self.action_rename(courseid, taskid, request.get('path'), request.get('new_path')) elif request.get("action") == "create" and request.get('path') is not None: return self.action_create(courseid, taskid, request.get('path')) elif request.get("action") == "edit" and request.get('path') is not None: return self.action_edit(courseid, taskid, request.get('path')) else: return self.show_tab_file(courseid, taskid)
def GET_AUTH(self, courseid, taskid): # pylint: disable=arguments-differ """ Edit a task """ if not id_checker(taskid): raise Exception("Invalid task id") course, __ = self.get_course_and_check_rights(courseid, allow_all_staff=False) try: task_data = self.task_factory.get_task_descriptor_content(courseid, taskid) except: task_data = None if task_data is None: task_data = {} environments = self.containers current_filetype = None try: current_filetype = self.task_factory.get_task_descriptor_extension(courseid, 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, task_data=task_data, template_helper=self.template_helper) return self.template_helper.get_renderer().course_admin.task_edit( course, taskid, self.task_factory.get_problem_types(), task_data, environments, task_data.get('problems',{}), self.contains_is_html(task_data), current_filetype, available_filetypes, AccessibleTime, CourseTaskFiles.get_task_filelist(self.task_factory, courseid, taskid), additional_tabs )
def POST_AUTH(self, courseid, taskid): # pylint: disable=arguments-differ """ Edit a task """ if not id_checker(taskid) or not id_checker(courseid): raise Exception("Invalid course/task id") course, __ = self.get_course_and_check_rights(courseid, allow_all_staff=False) data = web.input(task_file={}) # Delete task ? if "delete" in data: self.task_factory.delete_task(courseid, taskid) if data.get("wipe", False): self.wipe_task(courseid, taskid) raise web.seeother(self.app.get_homepath() + "/admin/"+courseid+"/tasks") # Else, parse content try: try: task_zip = data.get("task_file").file except: task_zip = None del data["task_file"] problems = self.dict_from_prefix("problem", data) limits = self.dict_from_prefix("limits", data) #Tags tags = self.dict_from_prefix("tags", data) if tags is None: tags = {} tags = OrderedDict(sorted(tags.items(), key=lambda item: item[0])) # Sort by key # Repair tags for k in tags: tags[k]["visible"] = ("visible" in tags[k]) # Since unckecked checkboxes are not present here, we manually add them to avoid later errors tags[k]["type"] = int(tags[k]["type"]) if not "id" in tags[k]: tags[k]["id"] = "" # Since textinput is disabled when the tag is organisational, the id field is missing. We add it to avoid Keys Errors if tags[k]["type"] == 2: tags[k]["id"] = "" # Force no id if organisational tag # Remove uncompleted tags (tags with no name or no id) for k in list(tags.keys()): if (tags[k]["id"] == "" and tags[k]["type"] != 2) or tags[k]["name"] == "": del tags[k] # Find duplicate ids. Return an error if some tags use the same id. for k in tags: if tags[k]["type"] != 2: # Ignore organisational tags since they have no id. count = 0 id = str(tags[k]["id"]) if (" " in id): return json.dumps({"status": "error", "message": _("You can not use spaces in the tag id field.")}) if not id_checker(id): return json.dumps({"status": "error", "message": _("Invalid tag id: {}").format(id)}) for k2 in tags: if tags[k2]["type"] != 2 and tags[k2]["id"] == id: count = count+1 if count > 1: return json.dumps({"status": "error", "message": _("Some tags have the same id! The id of a tag must be unique.")}) data = {key: val for key, val in data.items() if not key.startswith("problem") and not key.startswith("limits") and not key.startswith("tags") and not key.startswith("/")} del data["@action"] # Determines the task filetype if data["@filetype"] not in self.task_factory.get_available_task_file_extensions(): return json.dumps({"status": "error", "message": _("Invalid file type: {}").format(str(data["@filetype"]))}) file_ext = data["@filetype"] del data["@filetype"] # Parse and order the problems (also deletes @order from the result) if problems is None: data["problems"] = OrderedDict([]) else: data["problems"] = OrderedDict([(key, self.parse_problem(val)) for key, val in sorted(iter(problems.items()), key=lambda x: int(x[1]['@order']))]) # Task limits data["limits"] = limits data["tags"] = OrderedDict(sorted(tags.items(), key=lambda x: x[1]['type'])) if "hard_time" in data["limits"] and data["limits"]["hard_time"] == "": del data["limits"]["hard_time"] # Weight try: data["weight"] = float(data["weight"]) except: return json.dumps({"status": "error", "message": _("Grade weight must be a floating-point number")}) # Groups if "groups" in data: data["groups"] = True if data["groups"] == "true" else False # Submision storage if "store_all" in data: try: stored_submissions = data["stored_submissions"] data["stored_submissions"] = 0 if data["store_all"] == "true" else int(stored_submissions) except: return json.dumps( {"status": "error", "message": _("The number of stored submission must be positive!")}) if data["store_all"] == "false" and data["stored_submissions"] <= 0: return json.dumps({"status": "error", "message": _("The number of stored submission must be positive!")}) del data['store_all'] # Submission limits if "submission_limit" in data: if data["submission_limit"] == "none": result = {"amount": -1, "period": -1} elif data["submission_limit"] == "hard": try: result = {"amount": int(data["submission_limit_hard"]), "period": -1} except: return json.dumps({"status": "error", "message": _("Invalid submission limit!")}) else: try: result = {"amount": int(data["submission_limit_soft_0"]), "period": int(data["submission_limit_soft_1"])} except: return json.dumps({"status": "error", "message": _("Invalid submission limit!")}) del data["submission_limit_hard"] del data["submission_limit_soft_0"] del data["submission_limit_soft_1"] data["submission_limit"] = result # Accessible if data["accessible"] == "custom": data["accessible"] = "{}/{}/{}".format(data["accessible_start"], data["accessible_soft_end"], data["accessible_end"]) elif data["accessible"] == "true": data["accessible"] = True else: data["accessible"] = False del data["accessible_start"] del data["accessible_end"] del data["accessible_soft_end"] # Checkboxes if data.get("responseIsHTML"): data["responseIsHTML"] = True # Network grading data["network_grading"] = "network_grading" in data except Exception as message: return json.dumps({"status": "error", "message": _("Your browser returned an invalid form ({})").format(message)}) # Get the course try: course = self.course_factory.get_course(courseid) except: return json.dumps({"status": "error", "message": _("Error while reading course's informations")}) # Get original data try: orig_data = self.task_factory.get_task_descriptor_content(courseid, taskid) data["order"] = orig_data["order"] except: pass task_fs = self.task_factory.get_task_fs(courseid, 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, task_data=data, task_fs=task_fs) # Retrieve the first non-null element error = next(filter(None, plugin_results), None) if error is not None: return error try: WebAppTask(course, taskid, data, task_fs, None, self.plugin_manager, self.task_factory.get_problem_types()) except Exception as message: return json.dumps({"status": "error", "message": _("Invalid data: {}").format(str(message))}) if task_zip: try: zipfile = ZipFile(task_zip) except Exception: return json.dumps({"status": "error", "message": _("Cannot read zip file. Files were not modified")}) with tempfile.TemporaryDirectory() as tmpdirname: try: zipfile.extractall(tmpdirname) except Exception: return json.dumps( {"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) course.update_all_tags_cache() return json.dumps({"status": "ok"})
def test_id_checker_invalid_3(self): assert id_checker("test/test") is False
def test_id_checker_invalid_1(self): assert id_checker("a@a") is False
def POST(self, courseid, taskid): """ Edit a task """ if not id_checker(taskid) or not id_checker(courseid): raise Exception("Invalid course/task id") course, _ = self.get_course_and_check_rights(courseid, allow_all_staff=False) # Parse content try: data = web.input(task_file={}) try: task_zip = data.get("task_file").file except: task_zip = None del data["task_file"] problems = self.dict_from_prefix("problem", data) limits = self.dict_from_prefix("limits", data) data = {key: val for key, val in data.iteritems() if not key.startswith("problem") and not key.startswith("limits")} del data["@action"] if data["@filetype"] not in self.task_factory.get_available_task_file_extensions(): return json.dumps({"status": "error", "message": "Invalid file type: {}".format(str(data["@filetype"]))}) file_ext = data["@filetype"] del data["@filetype"] if problems is None: return json.dumps({"status": "error", "message": "You cannot create a task without subproblems"}) # Order the problems (this line also deletes @order from the result) data["problems"] = OrderedDict([(key, self.parse_problem(val)) for key, val in sorted(problems.iteritems(), key=lambda x: int(x[1]['@order']))]) data["limits"] = limits if "hard_time" in data["limits"] and data["limits"]["hard_time"] == "": del data["limits"]["hard_time"] # Weight try: data["weight"] = float(data["weight"]) except: return json.dumps({"status": "error", "message": "Grade weight must be a floating-point number"}) # Groups if "groups" in data: data["groups"] = True if data["groups"] == "true" else False # Accessible if data["accessible"] == "custom": data["accessible"] = "{}/{}".format(data["accessible_start"], data["accessible_end"]) elif data["accessible"] == "true": data["accessible"] = True else: data["accessible"] = False del data["accessible_start"] del data["accessible_end"] # Checkboxes if data.get("responseIsHTML"): data["responseIsHTML"] = True # Network grading data["network_grading"] = "network_grading" in data except Exception as message: return json.dumps({"status": "error", "message": "Your browser returned an invalid form ({})".format(str(message))}) # Get the course try: course = self.course_factory.get_course(courseid) except: return json.dumps({"status": "error", "message": "Error while reading course's informations"}) # Get original data try: orig_data = self.task_factory.get_task_descriptor_content(courseid, taskid) data["order"] = orig_data["order"] except: pass directory_path = self.task_factory.get_directory_path(courseid, taskid) try: WebAppTask(course, taskid, data, directory_path) except Exception as message: return json.dumps({"status": "error", "message": "Invalid data: {}".format(str(message))}) if not os.path.exists(directory_path): os.mkdir(directory_path) if task_zip: try: zipfile = ZipFile(task_zip) except Exception as message: return json.dumps({"status": "error", "message": "Cannot read zip file. Files were not modified"}) try: zipfile.extractall(directory_path) except Exception as message: return json.dumps( {"status": "error", "message": "There was a problem while extracting the zip archive. Some files may have been modified"}) self.task_factory.delete_all_possible_task_files(courseid, taskid) self.task_factory.update_task_descriptor_content(courseid, taskid, data, force_extension=file_ext) return json.dumps({"status": "ok"})
def test_id_checker_invalid_2(self): assert id_checker("") is False
def _validate_list(self, usernames): """ Prevent MongoDB injections by verifying arrays sent to it """ for i in usernames: if not id_checker(i): raise web.notfound()