Esempio n. 1
0
    def __init__(self, courseid, content, course_fs, task_factory, hook_manager):
        super(WebAppCourse, self).__init__(courseid, content, course_fs, task_factory, hook_manager)

        try:
            self._name = self._content['name']
        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")

        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_list = self._content.get('registration_ac_list', [])
            self._groups_student_choice = self._content.get("groups_student_choice", False)
            self._use_classrooms = self._content.get('use_classrooms', True)
            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_keys = self._content.get('lti_keys', {})
            self._lti_send_back_grade = self._content.get('lti_send_back_grade', False)
        except:
            raise Exception("Course has an invalid YAML spec: " + self.get_id())

        # Force some parameters if LTI is active
        if self.is_lti():
            self._accessible = AccessibleTime(True)
            self._registration = AccessibleTime(False)
            self._registration_password = None
            self._registration_ac = None
            self._registration_ac_list = []
            self._groups_student_choice = False
            self._use_classrooms = True
            self._allow_unregister = False
        else:
            self._lti_keys = {}
            self._lti_send_back_grade = False
            
        # Caches for tag lists
        self._all_tags_cache = None
        self._all_tags_cache_list = {}
        self._all_tags_cache_list_admin = {}
        self._organisational_tags_to_task = {}
        self.update_all_tags_cache()
Esempio n. 2
0
    def __init__(self, course, taskid, content, task_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,
                                         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", {}))
Esempio n. 3
0
def course_accessibility(course, default_value, course_factory, database,
                         user_manager):
    descriptor = course.get_descriptor()
    if descriptor.get("exam_active", False):
        # Check for SEB
        if not check_key(descriptor.get("seb_hash", "")):
            return AccessibleTime(False)

        # Check for exam finalization
        courseid = course.get_id()
        username = user_manager.session_username()

        if get_user_status(courseid, username, database, user_manager):
            return AccessibleTime(False)

    return default_value
Esempio n. 4
0
    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)

        # Env type
        env_type_obj = get_env_type(self._environment_type)
        if env_type_obj is None:
            raise Exception(_("Environment type {0} is unknown").format(self._environment_type))
        # Ensure that the content of the dictionary is ok
        self._environment_parameters = env_type_obj.check_task_environment_parameters(self._environment_parameters)

        # Name and context
        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", [])
Esempio n. 5
0
    def __init__(self, courseid, content, course_fs, task_factory, hook_manager):
        super(WebAppCourse, self).__init__(courseid, content, course_fs, task_factory, hook_manager)

        try:
            self._name = self._content['name']
        except:
            raise Exception("Course has an invalid description: " + self.get_id())

        if self._content.get('nofrontend', False):
            raise Exception("That course is not allowed to be displayed directly in the webapp")

        try:
            self._admins = self._content.get('admins', [])
            self._tutors = self._content.get('tutors', [])
            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_list = self._content.get('registration_ac_list', [])
            self._groups_student_choice = self._content.get("groups_student_choice", False)
            self._use_classrooms = self._content.get('use_classrooms', True)
            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_keys = self._content.get('lti_keys', {})
            self._lti_send_back_grade = self._content.get('lti_send_back_grade', False)
        except:
            raise Exception("Course has an invalid description: " + self.get_id())

        # Force some parameters if LTI is active
        if self.is_lti():
            self._accessible = AccessibleTime(True)
            self._registration = AccessibleTime(False)
            self._registration_password = None
            self._registration_ac = None
            self._registration_ac_list = []
            self._groups_student_choice = False
            self._use_classrooms = True
            self._allow_unregister = True
            self._allow_preview = False
        else:
            self._lti_keys = {}
            self._lti_send_back_grade = False
            
        # Caches for tag lists
        self._all_tags_cache = None
        self._all_tags_cache_list = {}
        self._all_tags_cache_list_admin = {}
        self.update_all_tags_cache()
Esempio n. 6
0
    def __init__(self, courseid, content, course_fs, task_factory, hook_manager):
        super(WebAppCourse, self).__init__(courseid, content, course_fs, task_factory, hook_manager)

        try:
            self._name = self._content['name']
        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")

        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_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()}
        except:
            raise Exception("Course has an invalid YAML spec: " + self.get_id())

        # Force some parameters if LTI is active
        if self.is_lti():
            self._accessible = AccessibleTime(True)
            self._registration = AccessibleTime(False)
            self._registration_password = None
            self._registration_ac = None
            self._registration_ac_list = []
            self._groups_student_choice = False
            self._allow_unregister = False
        else:
            self._lti_keys = {}
            self._lti_url = ''
            self._lti_send_back_grade = False

        self.course_page = "course"
        self.admin_page = "admin"
        self.has_student = True
Esempio n. 7
0
class WebAppCourse(Course):
    """ A course with some modification for users """

    def __init__(self, courseid, content, course_fs, task_factory, hook_manager):
        super(WebAppCourse, self).__init__(courseid, content, course_fs, task_factory, hook_manager)

        try:
            self._name = self._content['name']
        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")

        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_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()}
        except:
            raise Exception("Course has an invalid YAML spec: " + self.get_id())

        # Force some parameters if LTI is active
        if self.is_lti():
            self._accessible = AccessibleTime(True)
            self._registration = AccessibleTime(False)
            self._registration_password = None
            self._registration_ac = None
            self._registration_ac_list = []
            self._groups_student_choice = False
            self._allow_unregister = False
        else:
            self._lti_keys = {}
            self._lti_url = ''
            self._lti_send_back_grade = False

        self.course_page = "course"
        self.admin_page = "admin"
        self.has_student = True

    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()

    def is_registration_possible(self, user_info):
        """ Returns true if users can register for this course """
        return self.get_accessibility().is_open() and self._registration.is_open() and self.is_user_accepted_by_access_control(user_info)

    def is_password_needed_for_registration(self):
        """ Returns true if a password is needed for registration """
        return self._registration_password is not None

    def get_registration_password(self):
        """ Returns the password needed for registration (None if there is no password) """
        return self._registration_password

    def get_accessibility(self, plugin_override=True):
        """ Return the AccessibleTime object associated with the accessibility of this course """
        vals = self._hook_manager.call_hook('course_accessibility', course=self, default=self._accessible)
        return vals[0] if len(vals) and plugin_override else self._accessible

    def get_registration_accessibility(self):
        """ Return the AccessibleTime object associated with the registration """
        return self._registration

    def get_tasks(self):
        return OrderedDict(sorted(list(Course.get_tasks(self).items()), key=lambda t: (t[1].get_order(), t[1].get_id())))

    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 """
        return self._registration_ac

    def get_access_control_list(self):
        """ Returns the list of all users allowed by the AC list """
        return self._registration_ac_list

    def can_students_choose_group(self):
        """ Returns True if the students can choose their groups """
        return self._groups_student_choice

    def is_lti(self):
        """ True if the current course is in LTI mode """
        return self._is_lti

    def lti_keys(self):
        """ {name: key} for the LTI customers """
        return self._lti_keys if self._is_lti else {}

    def lti_url(self):
        """ Returns the URL to the external platform the course is hosted on """
        return self._lti_url

    def lti_send_back_grade(self):
        """ True if the current course should send back grade to the LTI Tool Consumer """
        return self._is_lti and self._lti_send_back_grade

    def is_user_accepted_by_access_control(self, user_info):
        """ Returns True if the user is allowed by the ACL """
        if self.get_access_control_method() is None:
            return True
        elif not user_info:
            return False
        elif self.get_access_control_method() == "username":
            return user_info["username"] in self.get_access_control_list()
        elif self.get_access_control_method() == "email":
            return user_info["email"] in self.get_access_control_list()
        elif self.get_access_control_method() == "binding":
            return set(user_info["bindings"].keys()).intersection(set(self.get_access_control_list()))
        return False

    def allow_preview(self):
        return self._allow_preview

    def allow_unregister(self, plugin_override=True):
        """ Returns True if students can unregister from course """
        vals = self._hook_manager.call_hook('course_allow_unregister', course=self, default=self._allow_unregister)
        return vals[0] if len(vals) and plugin_override else self._allow_unregister

    def get_name(self, language):
        """ Return the name of this course """
        return self.gettext(language, self._name) if self._name else ""

    def get_description(self, language):
        """Returns the course description """
        description = self.gettext(language, self._description) if self._description else ''
        return ParsableText(description, "rst", translation=self.get_translation_obj(language))

    def get_tags(self):
        return self._tags

    def get_admin_menu(self, plugin_manager, user_manager):
        """ Return the element to display in the admin menu of the course """
        default_entries = []
        if user_manager.has_admin_rights_on_course(self):
            default_entries += [("settings", "<i class='fa fa-cog fa-fw'></i>&nbsp; " + _("Course settings"))]

        default_entries += [("stats", "<i class='fa fa-area-chart fa-fw'></i>&nbsp; " + _("Stats")),
                            ("students", "<i class='fa fa-user fa-fw'></i>&nbsp; " + _("Students")),
                            ("audiences", "<i class='fa fa-group fa-fw'></i>&nbsp; " + _("Audiences"))]

        if not self.is_lti():
            default_entries += [("groups", "<i class='fa fa-group fa-fw'></i>&nbsp; " + _("Groups"))]

        default_entries += [("tasks", "<i class='fa fa-tasks fa-fw'></i>&nbsp; " + _("Tasks")),
                            ("tags", "<i class='fa fa-tags fa-fw'></i>&nbsp;" + _("Tags")),
                            ("submissions", "<i class='fa fa-search fa-fw'></i>&nbsp; " + _("View submissions")),
                            ("download", "<i class='fa fa-download fa-fw'></i>&nbsp; " + _("Download submissions"))]

        if user_manager.has_admin_rights_on_course(self):
            default_entries += [("replay", "<i class='fa fa-refresh fa-fw'></i>&nbsp; " + _("Replay submissions")),
                                ("danger", "<i class='fa fa-bomb fa-fw'></i>&nbsp; " + _("Danger zone"))]

        # Hook should return a tuple (link,name) where link is the relative link from the index of the course administration.
        additional_entries = [entry for entry in plugin_manager.call_hook('course_admin_menu', course=self) if
                              entry is not None]

        return default_entries + additional_entries
Esempio n. 8
0
    def POST_AUTH(self, courseid):  # pylint: disable=arguments-differ
        """ POST request """
        course, __ = self.get_course_and_check_rights(courseid, allow_all_staff=False)

        errors = []
        course_content = {}
        try:
            data = web.input()
            course_content = self.course_factory.get_course_descriptor_content(courseid)
            course_content['name'] = data['name']
            if course_content['name'] == "":
                errors.append(_('Invalid name'))
            course_content['description'] = data['description']
            course_content['admins'] = list(map(str.strip, data['admins'].split(',')))
            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 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

            if course_content.get('use_classrooms', True) != (data['use_classrooms'] == "true"):
                self.database.aggregations.delete_many({"courseid": course.get_id()})

            course_content['use_classrooms'] = True if data["use_classrooms"] == "true" else False

            if data["accessible"] == "custom":
                course_content['accessible'] = "{}/{}".format(data["accessible_start"], data["accessible_end"])
            elif data["accessible"] == "true":
                course_content['accessible'] = True
            else:
                course_content['accessible'] = False

            try:
                AccessibleTime(course_content['accessible'])
            except:
                errors.append(_('Invalid accessibility dates'))

            course_content['allow_unregister'] = True if data["allow_unregister"] == "true" else False
            course_content['allow_preview'] = True if data["allow_preview"] == "true" else False

            if data["registration"] == "custom":
                course_content['registration'] = "{}/{}".format(data["registration_start"], data["registration_end"])
            elif data["registration"] == "true":
                course_content['registration'] = True
            else:
                course_content['registration'] = False

            try:
                AccessibleTime(course_content['registration'])
            except:
                errors.append(_('Invalid registration dates'))

            course_content['registration_password'] = data['registration_password']
            if course_content['registration_password'] == "":
                course_content['registration_password'] = None

            course_content['registration_ac'] = data['registration_ac']
            if course_content['registration_ac'] not in ["None", "username", "binding", "email"]:
                errors.append(_('Invalid ACL value'))
            if course_content['registration_ac'] == "None":
                course_content['registration_ac'] = None
            course_content['registration_ac_list'] = data['registration_ac_list'].split("\n")

            course_content['is_lti'] = 'lti' in data and data['lti'] == "true"
            course_content['lti_keys'] = dict([x.split(":") for x in data['lti_keys'].split("\n") if x])

            for lti_key in course_content['lti_keys'].keys():
                if not re.match("^[a-zA-Z0-9]*$", lti_key):
                    errors.append(_("LTI keys must be alphanumerical."))

            course_content['lti_send_back_grade'] = 'lti_send_back_grade' in data and data['lti_send_back_grade'] == "true"
        except:
            errors.append(_('User returned an invalid form.'))

        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

        return self.page(course, errors, errors is None)
Esempio n. 9
0
class WebAppCourse(Course):
    """ A course with some modification for users """
    def __init__(self, courseid, content, course_fs, task_factory,
                 hook_manager):
        super(WebAppCourse, self).__init__(courseid, content, course_fs,
                                           task_factory, hook_manager)

        try:
            self._name = self._content['name']
        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"
            )

        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_list = self._content.get(
                'registration_ac_list', [])
            self._groups_student_choice = self._content.get(
                "groups_student_choice", False)
            self._use_classrooms = self._content.get('use_classrooms', True)
            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_keys = self._content.get('lti_keys', {})
            self._lti_send_back_grade = self._content.get(
                'lti_send_back_grade', False)
        except:
            raise Exception("Course has an invalid YAML spec: " +
                            self.get_id())

        # Force some parameters if LTI is active
        if self.is_lti():
            self._accessible = AccessibleTime(True)
            self._registration = AccessibleTime(False)
            self._registration_password = None
            self._registration_ac = None
            self._registration_ac_list = []
            self._groups_student_choice = False
            self._use_classrooms = True
            self._allow_unregister = False
        else:
            self._lti_keys = {}
            self._lti_send_back_grade = False

        # Caches for tag lists
        self._all_tags_cache = None
        self._all_tags_cache_list = {}
        self._all_tags_cache_list_admin = {}
        self._organisational_tags_to_task = {}
        self.update_all_tags_cache()

    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()

    def is_registration_possible(self, user_info):
        """ Returns true if users can register for this course """
        return self.get_accessibility().is_open(
        ) and self._registration.is_open(
        ) and self.is_user_accepted_by_access_control(user_info)

    def is_password_needed_for_registration(self):
        """ Returns true if a password is needed for registration """
        return self._registration_password is not None

    def get_registration_password(self):
        """ Returns the password needed for registration (None if there is no password) """
        return self._registration_password

    def get_accessibility(self, plugin_override=True):
        """ Return the AccessibleTime object associated with the accessibility of this course """
        vals = self._hook_manager.call_hook('course_accessibility',
                                            course=self,
                                            default=self._accessible)
        return vals[0] if len(vals) and plugin_override else self._accessible

    def get_registration_accessibility(self):
        """ Return the AccessibleTime object associated with the registration """
        return self._registration

    def get_tasks(self):
        return OrderedDict(
            sorted(list(Course.get_tasks(self).items()),
                   key=lambda t: (t[1].get_order(), t[1].get_id())))

    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 """
        return self._registration_ac

    def get_access_control_list(self):
        """ Returns the list of all users allowed by the AC list """
        return self._registration_ac_list

    def can_students_choose_group(self):
        """ Returns True if the students can choose their groups """
        return self._groups_student_choice

    def use_classrooms(self):
        """ Returns True if classrooms are used """
        return self._use_classrooms

    def is_lti(self):
        """ True if the current course is in LTI mode """
        return self._is_lti

    def lti_keys(self):
        """ {name: key} for the LTI customers """
        return self._lti_keys if self._is_lti else {}

    def lti_send_back_grade(self):
        """ True if the current course should send back grade to the LTI Tool Consumer """
        return self._is_lti and self._lti_send_back_grade

    def is_user_accepted_by_access_control(self, user_info):
        """ Returns True if the user is allowed by the ACL """
        if self.get_access_control_method() is None:
            return True
        elif not user_info:
            return False
        elif self.get_access_control_method() == "username":
            return user_info["username"] in self.get_access_control_list()
        elif self.get_access_control_method() == "email":
            return user_info["email"] in self.get_access_control_list()
        elif self.get_access_control_method() == "binding":
            return set(user_info["bindings"].keys()).intersection(
                set(self.get_access_control_list()))
        return False

    def allow_preview(self):
        return self._allow_preview

    def allow_unregister(self, plugin_override=True):
        """ Returns True if students can unregister from course """
        vals = self._hook_manager.call_hook('course_allow_unregister',
                                            course=self,
                                            default=self._allow_unregister)
        return vals[0] if len(
            vals) and plugin_override else self._allow_unregister

    def get_name(self, language):
        """ Return the name of this course """
        return self.gettext(language, self._name) if self._name else ""

    def get_description(self, language):
        """Returns the course description """
        description = self.gettext(
            language, self._description) if self._description else ''
        return ParsableText(
            description, "rst",
            self._translations.get(language, gettext.NullTranslations()))

    def get_all_tags(self):
        """ 
        Return a tuple of lists ([common_tags], [anti_tags], [organisational_tags]) all tags of all tasks of this course 
        Since this is an heavy procedure, we use a cache to cache results. Cache should be updated when a task is modified.
        """

        if self._all_tags_cache != None:
            return self._all_tags_cache

        tag_list_common = set()
        tag_list_misconception = set()
        tag_list_org = set()

        tasks = self.get_tasks()
        for id, task in tasks.items():
            for tag in task.get_tags()[0]:
                tag_list_common.add(tag)
            for tag in task.get_tags()[1]:
                tag_list_misconception.add(tag)
            for tag in task.get_tags()[2]:
                tag_list_org.add(tag)

        tag_list_common = natsorted(tag_list_common,
                                    key=lambda y: y.get_name().lower())
        tag_list_misconception = natsorted(tag_list_misconception,
                                           key=lambda y: y.get_name().lower())
        tag_list_org = natsorted(tag_list_org,
                                 key=lambda y: y.get_name().lower())

        self._all_tags_cache = (list(tag_list_common),
                                list(tag_list_misconception),
                                list(tag_list_org))
        return self._all_tags_cache

    def get_all_tags_names_as_list(self, admin=False, language="en"):
        """ Computes and cache two list containing all tags name sorted by natural order on name """

        if admin:
            if self._all_tags_cache_list_admin != {} and language in self._all_tags_cache_list_admin:
                return self._all_tags_cache_list_admin[language]  #Cache hit
        else:
            if self._all_tags_cache_list != {} and language in self._all_tags_cache_list:
                return self._all_tags_cache_list[language]  #Cache hit

        #Cache miss, computes everything
        s_stud = set()
        s_admin = set()
        (common, _, org) = self.get_all_tags()
        for tag in common + org:
            # Is tag_name_with_translation correct by doing that like that ?
            tag_name_with_translation = self.gettext(
                language, tag.get_name()) if tag.get_name() else ""
            s_admin.add(tag_name_with_translation)
            if tag.is_visible_for_student():
                s_stud.add(tag_name_with_translation)
        self._all_tags_cache_list_admin[language] = natsorted(
            s_admin, key=lambda y: y.lower())
        self._all_tags_cache_list[language] = natsorted(
            s_stud, key=lambda y: y.lower())

        if admin:
            return self._all_tags_cache_list_admin[language]
        return self._all_tags_cache_list[language]

    def get_organisational_tags_to_task(self):
        """ This build a dict for fast retrive tasks id based on organisational tags. The form of the dict is:
        
            { 'org_tag_1': ['task_id', 'task_id', ...], 
              'org_tag_2' : ['task_id', 'task_id', ...], 
              ... }
         """
        if self._organisational_tags_to_task != {}:
            return self._organisational_tags_to_task

        for taskid, task in self.get_tasks().items():
            for tag in task.get_tags()[2]:
                self._organisational_tags_to_task.setdefault(
                    tag.get_name(), []).append(taskid)

        return self._organisational_tags_to_task

    def update_all_tags_cache(self):
        """ Force the cache refreshing """

        self._all_tags_cache = None
        self._all_tags_cache_list = {}
        self._all_tags_cache_list_admin = {}
        self._organisational_tags_to_task = {}

        self.get_all_tags()
        self.get_all_tags_names_as_list()
        self.get_organisational_tags_to_task()
Esempio n. 10
0
    def __init__(self, courseid, content, course_fs, task_factory,
                 plugin_manager):

        self._id = courseid
        self._content = content
        self._fs = course_fs
        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']
        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"
            )

        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_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()
            }
            if 'toc' in self._content:
                self._toc = SectionsList(self._content['toc'])
            else:
                tasks = self._task_factory.get_all_tasks(self)
                ordered_task_list = OrderedDict(
                    sorted(list(tasks.items()),
                           key=lambda t:
                           (t[1].get_old_order(), t[1].get_id())))
                indexed_task_list = {
                    taskid: rank
                    for rank, taskid in enumerate(ordered_task_list.keys())
                }
                self._toc = SectionsList([{
                    "id": "tasks-list",
                    "title": _("List of exercises"),
                    "rank": 0,
                    "tasks_list": indexed_task_list
                }])
        except:
            raise Exception("Course has an invalid YAML spec: " +
                            self.get_id())

        # Force some parameters if LTI is active
        if self.is_lti():
            self._accessible = AccessibleTime(True)
            self._registration = AccessibleTime(False)
            self._registration_password = None
            self._registration_ac = None
            self._registration_ac_list = []
            self._groups_student_choice = False
            self._allow_unregister = False
        else:
            self._lti_keys = {}
            self._lti_url = ''
            self._lti_send_back_grade = False
Esempio n. 11
0
    def __init__(self, courseid, content, course_fs, task_factory, plugin_manager, task_dispensers):
        self._id = courseid
        self._content = content
        self._fs = course_fs
        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']
        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))

        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_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()}
            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", ''))
        except:
            raise Exception("Course has an invalid YAML spec: " + self.get_id())

        # Force some parameters if LTI is active
        if self.is_lti():
            self._accessible = AccessibleTime(True)
            self._registration = AccessibleTime(False)
            self._registration_password = None
            self._registration_ac = None
            self._registration_ac_list = []
            self._groups_student_choice = False
            self._allow_unregister = False
        else:
            self._lti_keys = {}
            self._lti_url = ''
            self._lti_send_back_grade = False

        # Build the regex for the ACL, allowing for fast matching. Only used internally.
        self._registration_ac_regex = self._build_ac_regex(self._registration_ac_list)
Esempio n. 12
0
class WebAppCourse(Course):
    """ A course with some modification for users """
    def __init__(self, courseid, content, course_fs, task_factory,
                 hook_manager):
        super(WebAppCourse, self).__init__(courseid, content, course_fs,
                                           task_factory, hook_manager)

        try:
            self._name = self._content['name']
        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"
            )

        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_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_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()
            }
        except:
            raise Exception("Course has an invalid YAML spec: " +
                            self.get_id())

        # Force some parameters if LTI is active
        if self.is_lti():
            self._accessible = AccessibleTime(True)
            self._registration = AccessibleTime(False)
            self._registration_password = None
            self._registration_ac = None
            self._registration_ac_list = []
            self._groups_student_choice = False
            self._allow_unregister = False
        else:
            self._lti_keys = {}
            self._lti_send_back_grade = False

    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()

    def is_registration_possible(self, user_info):
        """ Returns true if users can register for this course """
        return self.get_accessibility().is_open(
        ) and self._registration.is_open(
        ) and self.is_user_accepted_by_access_control(user_info)

    def is_password_needed_for_registration(self):
        """ Returns true if a password is needed for registration """
        return self._registration_password is not None

    def get_registration_password(self):
        """ Returns the password needed for registration (None if there is no password) """
        return self._registration_password

    def get_accessibility(self, plugin_override=True):
        """ Return the AccessibleTime object associated with the accessibility of this course """
        vals = self._hook_manager.call_hook('course_accessibility',
                                            course=self,
                                            default=self._accessible)
        return vals[0] if len(vals) and plugin_override else self._accessible

    def get_registration_accessibility(self):
        """ Return the AccessibleTime object associated with the registration """
        return self._registration

    def get_tasks(self):
        return OrderedDict(
            sorted(list(Course.get_tasks(self).items()),
                   key=lambda t: (t[1].get_order(), t[1].get_id())))

    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 """
        return self._registration_ac

    def get_access_control_list(self):
        """ Returns the list of all users allowed by the AC list """
        return self._registration_ac_list

    def can_students_choose_group(self):
        """ Returns True if the students can choose their groups """
        return self._groups_student_choice

    def is_lti(self):
        """ True if the current course is in LTI mode """
        return self._is_lti

    def lti_keys(self):
        """ {name: key} for the LTI customers """
        return self._lti_keys if self._is_lti else {}

    def lti_send_back_grade(self):
        """ True if the current course should send back grade to the LTI Tool Consumer """
        return self._is_lti and self._lti_send_back_grade

    def is_user_accepted_by_access_control(self, user_info):
        """ Returns True if the user is allowed by the ACL """
        if self.get_access_control_method() is None:
            return True
        elif not user_info:
            return False
        elif self.get_access_control_method() == "username":
            return user_info["username"] in self.get_access_control_list()
        elif self.get_access_control_method() == "email":
            return user_info["email"] in self.get_access_control_list()
        elif self.get_access_control_method() == "binding":
            return set(user_info["bindings"].keys()).intersection(
                set(self.get_access_control_list()))
        return False

    def allow_preview(self):
        return self._allow_preview

    def allow_unregister(self, plugin_override=True):
        """ Returns True if students can unregister from course """
        vals = self._hook_manager.call_hook('course_allow_unregister',
                                            course=self,
                                            default=self._allow_unregister)
        return vals[0] if len(
            vals) and plugin_override else self._allow_unregister

    def get_name(self, language):
        """ Return the name of this course """
        return self.gettext(language, self._name) if self._name else ""

    def get_description(self, language):
        """Returns the course description """
        description = self.gettext(
            language, self._description) if self._description else ''
        return ParsableText(description,
                            "rst",
                            translation=self.get_translation_obj(language))

    def get_tags(self):
        return self._tags
Esempio n. 13
0
def task_accessibility(course, task, default):  # pylint: disable=unused-argument
    contest_data = get_contest_data(course)
    if contest_data['enabled']:
        return AccessibleTime(contest_data['start'] + '/')
    else:
        return default
Esempio n. 14
0
class WebAppCourse(Course):
    """ A course with some modification for users """

    def __init__(self, courseid, content, course_fs, task_factory, hook_manager):
        super(WebAppCourse, self).__init__(courseid, content, course_fs, task_factory, hook_manager)

        try:
            self._name = self._content['name']
        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")

        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_list = self._content.get('registration_ac_list', [])
            self._groups_student_choice = self._content.get("groups_student_choice", False)
            self._use_classrooms = self._content.get('use_classrooms', True)
            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_keys = self._content.get('lti_keys', {})
            self._lti_send_back_grade = self._content.get('lti_send_back_grade', False)
        except:
            raise Exception("Course has an invalid YAML spec: " + self.get_id())

        # Force some parameters if LTI is active
        if self.is_lti():
            self._accessible = AccessibleTime(True)
            self._registration = AccessibleTime(False)
            self._registration_password = None
            self._registration_ac = None
            self._registration_ac_list = []
            self._groups_student_choice = False
            self._use_classrooms = True
            self._allow_unregister = False
        else:
            self._lti_keys = {}
            self._lti_send_back_grade = False
            
        # Caches for tag lists
        self._all_tags_cache = None
        self._all_tags_cache_list = {}
        self._all_tags_cache_list_admin = {}
        self._organisational_tags_to_task = {}
        self.update_all_tags_cache()

    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()

    def is_registration_possible(self, user_info):
        """ Returns true if users can register for this course """
        return self.get_accessibility().is_open() and self._registration.is_open() and self.is_user_accepted_by_access_control(user_info)

    def is_password_needed_for_registration(self):
        """ Returns true if a password is needed for registration """
        return self._registration_password is not None

    def get_registration_password(self):
        """ Returns the password needed for registration (None if there is no password) """
        return self._registration_password

    def get_accessibility(self, plugin_override=True):
        """ Return the AccessibleTime object associated with the accessibility of this course """
        vals = self._hook_manager.call_hook('course_accessibility', course=self, default=self._accessible)
        return vals[0] if len(vals) and plugin_override else self._accessible

    def get_registration_accessibility(self):
        """ Return the AccessibleTime object associated with the registration """
        return self._registration

    def get_tasks(self):
        return OrderedDict(sorted(list(Course.get_tasks(self).items()), key=lambda t: (t[1].get_order(), t[1].get_id())))

    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 """
        return self._registration_ac

    def get_access_control_list(self):
        """ Returns the list of all users allowed by the AC list """
        return self._registration_ac_list

    def can_students_choose_group(self):
        """ Returns True if the students can choose their groups """
        return self._groups_student_choice

    def use_classrooms(self):
        """ Returns True if classrooms are used """
        return self._use_classrooms

    def is_lti(self):
        """ True if the current course is in LTI mode """
        return self._is_lti

    def lti_keys(self):
        """ {name: key} for the LTI customers """
        return self._lti_keys if self._is_lti else {}

    def lti_send_back_grade(self):
        """ True if the current course should send back grade to the LTI Tool Consumer """
        return self._is_lti and self._lti_send_back_grade

    def is_user_accepted_by_access_control(self, user_info):
        """ Returns True if the user is allowed by the ACL """
        if self.get_access_control_method() is None:
            return True
        elif not user_info:
            return False
        elif self.get_access_control_method() == "username":
            return user_info["username"] in self.get_access_control_list()
        elif self.get_access_control_method() == "email":
            return user_info["email"] in self.get_access_control_list()
        elif self.get_access_control_method() == "binding":
            return set(user_info["bindings"].keys()).intersection(set(self.get_access_control_list()))
        return False

    def allow_preview(self):
        return self._allow_preview

    def allow_unregister(self, plugin_override=True):
        """ Returns True if students can unregister from course """
        vals = self._hook_manager.call_hook('course_allow_unregister', course=self, default=self._allow_unregister)
        return vals[0] if len(vals) and plugin_override else self._allow_unregister

    def get_name(self, language):
        """ Return the name of this course """
        return self.gettext(language, self._name) if self._name else ""

    def get_description(self, language):
        """Returns the course description """
        description = self.gettext(language, self._description) if self._description else ''
        return ParsableText(description, "rst", self._translations.get(language, gettext.NullTranslations()))
        
    def get_all_tags(self):
        """ 
        Return a tuple of lists ([common_tags], [anti_tags], [organisational_tags]) all tags of all tasks of this course 
        Since this is an heavy procedure, we use a cache to cache results. Cache should be updated when a task is modified.
        """
        
        if self._all_tags_cache != None:
            return self._all_tags_cache
    
        tag_list_common = set()
        tag_list_misconception = set()
        tag_list_org = set()

        tasks = self.get_tasks()
        for id, task in tasks.items():
            for tag in task.get_tags()[0]:
                tag_list_common.add(tag)
            for tag in task.get_tags()[1]:
                tag_list_misconception.add(tag)
            for tag in task.get_tags()[2]:
                tag_list_org.add(tag)
        
        tag_list_common = natsorted(tag_list_common, key=lambda y: y.get_name().lower())
        tag_list_misconception = natsorted(tag_list_misconception, key=lambda y: y.get_name().lower())
        tag_list_org = natsorted(tag_list_org, key=lambda y: y.get_name().lower())
             
        self._all_tags_cache = (list(tag_list_common), list(tag_list_misconception), list(tag_list_org))
        return self._all_tags_cache
        
    def get_all_tags_names_as_list(self, admin=False, language="en"):
        """ Computes and cache two list containing all tags name sorted by natural order on name """

        if admin:
            if self._all_tags_cache_list_admin != {} and language in self._all_tags_cache_list_admin:
                return self._all_tags_cache_list_admin[language] #Cache hit
        else:
            if self._all_tags_cache_list != {} and language in self._all_tags_cache_list:
                return self._all_tags_cache_list[language] #Cache hit
                        
        #Cache miss, computes everything
        s_stud = set()
        s_admin = set()
        (common, _, org) = self.get_all_tags()
        for tag in common + org:
            # Is tag_name_with_translation correct by doing that like that ?
            tag_name_with_translation = self.gettext(language, tag.get_name()) if tag.get_name() else ""
            s_admin.add(tag_name_with_translation) 
            if tag.is_visible_for_student():
                s_stud.add(tag_name_with_translation) 
        self._all_tags_cache_list_admin[language] = natsorted(s_admin, key=lambda y: y.lower())
        self._all_tags_cache_list[language] = natsorted(s_stud, key=lambda y: y.lower())
        
        if admin:
            return self._all_tags_cache_list_admin[language]
        return self._all_tags_cache_list[language]
        
    def get_organisational_tags_to_task(self):
        """ This build a dict for fast retrive tasks id based on organisational tags. The form of the dict is:
        
            { 'org_tag_1': ['task_id', 'task_id', ...], 
              'org_tag_2' : ['task_id', 'task_id', ...], 
              ... }
         """
        if self._organisational_tags_to_task != {}:
            return self._organisational_tags_to_task

        for taskid, task in self.get_tasks().items():
            for tag in task.get_tags()[2]:
                self._organisational_tags_to_task.setdefault(tag.get_name(), []).append(taskid)

        return self._organisational_tags_to_task
        
    def update_all_tags_cache(self):
        """ Force the cache refreshing """
        
        self._all_tags_cache = None
        self._all_tags_cache_list = {}
        self._all_tags_cache_list_admin = {}
        self._organisational_tags_to_task = {}
            
        self.get_all_tags()
        self.get_all_tags_names_as_list()
        self.get_organisational_tags_to_task()
Esempio n. 15
0
    def __init__(self, course, taskid, content, filesystem, plugin_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)

        content = _migrate_from_v_0_6(content)

        self._course = course
        self._taskid = taskid
        self._fs = filesystem
        self._plugin_manager = plugin_manager
        self._data = content

        if "problems" not in self._data:
            raise Exception("Tasks must have some problems descriptions")

        # i18n
        self._translations = {}
        self._course_fs = self._fs.from_subfolder(course.get_id())
        self._course_fs.ensure_exists()
        self._task_fs = self._course_fs.from_subfolder(taskid)
        self._task_fs.ensure_exists()

        self._translations_fs = self._task_fs.from_subfolder("$i18n")

        if not self._translations_fs.exists():
            self._translations_fs = self._task_fs.from_subfolder("student").from_subfolder("$i18n")
        if not self._translations_fs.exists():
            self._translations_fs = self._course_fs.from_subfolder("$common").from_subfolder("$i18n")
        if not self._translations_fs.exists():
            self._translations_fs = self._course_fs.from_subfolder("$common").from_subfolder(
                "student").from_subfolder("$i18n")

        if self._translations_fs.exists():
            for f in self._translations_fs.list(folders=False, files=True, recursive=False):
                lang = f[0:len(f) - 3]
                if self._translations_fs.exists(lang + ".mo"):
                    self._translations[lang] = gettext.GNUTranslations(self._translations_fs.get_fd(lang + ".mo"))
                else:
                    self._translations[lang] = gettext.NullTranslations()

        # Check all problems
        self._problems = []
        for problemid in self._data['problems']:
            self._problems.append(
                self._create_task_problem(problemid, self._data['problems'][problemid], task_problem_types))

        # Env type
        self._environment_id = self._data.get('environment_id', 'default')
        self._environment_type = self._data.get('environment_type', 'unknown')
        self._environment_parameters = self._data.get("environment_parameters", {})

        env_type_obj = get_env_type(self._environment_type)
        if env_type_obj is None:
            raise Exception(_("Environment type {0} is unknown").format(self._environment_type))
        # Ensure that the content of the dictionary is ok
        self._environment_parameters = env_type_obj.check_task_environment_parameters(self._environment_parameters)

        # Name and context
        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", [])
Esempio n. 16
0
class Course(object):
    """ A course with some modification for users """

    def __init__(self, courseid, content, course_fs, task_factory, plugin_manager, task_dispensers):
        self._id = courseid
        self._content = content
        self._fs = course_fs
        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']
        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))

        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_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()}
            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", ''))
        except:
            raise Exception("Course has an invalid YAML spec: " + self.get_id())

        # Force some parameters if LTI is active
        if self.is_lti():
            self._accessible = AccessibleTime(True)
            self._registration = AccessibleTime(False)
            self._registration_password = None
            self._registration_ac = None
            self._registration_ac_list = []
            self._groups_student_choice = False
            self._allow_unregister = False
        else:
            self._lti_keys = {}
            self._lti_url = ''
            self._lti_send_back_grade = False

        # Build the regex for the ACL, allowing for fast matching. Only used internally.
        self._registration_ac_regex = self._build_ac_regex(self._registration_ac_list)

    def get_translation_obj(self, language):
        return self._translations.get(language, gettext.NullTranslations())

    def gettext(self, language, *args, **kwargs):
        return self.get_translation_obj(language).gettext(*args, **kwargs)

    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_task(self, taskid):
        """ Returns a Task object """
        return self._task_factory.get_task(self, 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()

    def is_registration_possible(self, user_info: UserInfo):
        """ Returns true if users can register for this course """
        return self.get_accessibility().is_open() and self._registration.is_open() and self.is_user_accepted_by_access_control(user_info)

    def is_password_needed_for_registration(self):
        """ Returns true if a password is needed for registration """
        return self._registration_password is not None

    def get_registration_password(self):
        """ Returns the password needed for registration (None if there is no password) """
        return self._registration_password

    def get_accessibility(self, plugin_override=True):
        """ Return the AccessibleTime object associated with the accessibility of this course """
        vals = self._plugin_manager.call_hook('course_accessibility', course=self, default=self._accessible)
        return vals[0] if len(vals) and plugin_override else self._accessible

    def get_registration_accessibility(self):
        """ Return the AccessibleTime object associated with the registration """
        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)

    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 """
        return self._registration_ac

    def get_access_control_list(self) -> List[str]:
        """ Returns the list of all users/emails/binding methods/... (see get_access_control_method) allowed by the AC list """
        return self._registration_ac_list

    def can_students_choose_group(self):
        """ Returns True if the students can choose their groups """
        return self._groups_student_choice

    def is_lti(self):
        """ True if the current course is in LTI mode """
        return self._is_lti

    def lti_keys(self):
        """ {name: key} for the LTI customers """
        return self._lti_keys if self._is_lti else {}

    def lti_url(self):
        """ Returns the URL to the external platform the course is hosted on """
        return self._lti_url

    def lti_send_back_grade(self):
        """ True if the current course should send back grade to the LTI Tool Consumer """
        return self._is_lti and self._lti_send_back_grade

    def is_user_accepted_by_access_control(self, user_info: UserInfo):
        """ Returns True if the user is allowed by the ACL """
        if self.get_access_control_method() is None:
            return True

        keys_per_access_control_method = {
            "username": (lambda: [user_info.username]),
            "email": (lambda: [user_info.email]),
            "binding": (lambda: user_info.bindings.keys())
        }

        if not user_info or self.get_access_control_method() not in keys_per_access_control_method:
            return False

        # check that at least one key matches in the list
        keys = keys_per_access_control_method[self.get_access_control_method()]()
        return any(self._registration_ac_regex.match(key) for key in keys)

    def allow_preview(self):
        return self._allow_preview

    def allow_unregister(self, plugin_override=True):
        """ Returns True if students can unregister from course """
        vals = self._plugin_manager.call_hook('course_allow_unregister', course=self, default=self._allow_unregister)
        return vals[0] if len(vals) and plugin_override else self._allow_unregister

    def get_name(self, language):
        """ Return the name of this course """
        return self.gettext(language, self._name) if self._name else ""

    def get_description(self, language):
        """Returns the course description """
        description = self.gettext(language, self._description) if self._description else ''
        return ParsableText(description, "rst", translation=self.get_translation_obj(language))

    def get_tags(self):
        return self._tags

    def get_task_dispenser(self):
        """
       :return: the structure of the course
       """
        return self._task_dispenser

    def _build_ac_regex(self, list_ac):
        """ Build a regex for the AC list, allowing for fast matching. The regex is only used internally """
        return re.compile('|'.join(re.escape(x).replace("\\*", ".*") for x in list_ac))
Esempio n. 17
0
class WebAppTask(Task):
    """ A task that stores additional context information, specific to the web app """
    def __init__(self, course, taskid, content, task_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,
                                         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_grading_weight(self):
        """ Get the relative weight of this task in the grading """
        return self._weight

    def get_accessible_time(self, plugin_override=True):
        """  Get the accessible time of this task """
        vals = self._hook_manager.call_hook('task_accessibility',
                                            course=self.get_course(),
                                            task=self,
                                            default=self._accessible)
        return vals[0] if len(vals) and plugin_override else self._accessible

    def is_visible_by_students(self):
        """ Returns true if the task is accessible by all students that are not administrator of the course """
        return self.get_course().is_open_to_non_staff(
        ) and self.get_accessible_time().after_start()

    def get_deadline(self):
        """ Returns a string containing the deadline for this task """
        if self.get_accessible_time().is_always_accessible():
            return _("No deadline")
        elif self.get_accessible_time().is_never_accessible():
            return _("It's too late")
        else:
            return self.get_accessible_time().get_end_date().strftime(
                "%d/%m/%Y %H:%M:%S")

    def is_group_task(self):
        """ Indicates if the task submission mode is per groups """
        return self._groups

    def get_submission_limit(self):
        """ Returns the submission limits et for the task"""
        return self._submission_limit

    def get_name(self, language):
        """ Returns the name of this task """
        return self.gettext(language, self._name) if self._name else ""

    def get_name_or_id(self, language=None):
        """ Return the name or id  this task """
        name_or_id = self.get_name(language)
        if name_or_id == "":
            name_or_id = self.get_id()
        return name_or_id

    def get_context(self, language):
        """ Get the context(description) of this task """
        context = self.gettext(language,
                               self._context) if self._context else ""
        vals = self._hook_manager.call_hook('task_context',
                                            course=self.get_course(),
                                            task=self,
                                            default=context)
        return ParsableText(vals[0], "rst", self._translations.get(language, gettext.NullTranslations())) if len(vals) else \
            ParsableText(context, "rst", self._translations.get(language, gettext.NullTranslations()))

    def get_authors(self, language):
        """ Return the list of this task's authors """
        return self.gettext(language, self._author) if self._author else ""

    def adapt_input_for_backend(self, input_data):
        """ Adapt the input from web.py for the inginious.backend """
        for problem in self._problems:
            input_data = problem.adapt_input_for_backend(input_data)
        return input_data

    def get_stored_submissions(self):
        """ Indicates if only the last submission must be stored for the task """
        return self._stored_submissions

    def get_evaluate(self):
        """ Indicates the default download for the task """
        return self._evaluate

    def get_tags(self):
        """ Get the tuple of list of the task """
        return self._tags

    def get_number_input_random(self):
        """ Return the number of random inputs """
        return self._input_random

    def regenerate_input_random(self):
        """ Indicates if random inputs should be regenerated """
        return self._regenerate_input_random

    def can_submit_after_deadline(self):
        return self._data.get("allow_late_submission",
                              False) and self._accessible.after_deadline()
Esempio n. 18
0
    def POST_AUTH(self, courseid, 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"))

        course, __ = self.get_course_and_check_rights(courseid,
                                                      allow_all_staff=False)
        data = flask.request.form.copy()
        data["task_file"] = flask.request.files.get("task_file")

        # Delete task ?
        if "delete" in data:
            toc = course.get_task_dispenser().get_dispenser_data()
            toc.remove_task(taskid)
            self.course_factory.update_course_descriptor_element(
                courseid, 'toc', toc.to_structure())
            self.task_factory.delete_task(courseid, taskid)
            if data.get("wipe", False):
                self.wipe_task(courseid, taskid)
            return redirect(self.app.get_homepath() + "/admin/" + courseid +
                            "/tasks")

        # Else, parse content
        try:
            try:
                task_zip = data.get("task_file").read()
            except:
                task_zip = None
            del data["task_file"]

            problems = dict_from_prefix("problem", data)
            environment_type = data.get("environment_type", "")
            environment_parameters = dict_from_prefix("envparams", data).get(
                environment_type, {})
            environment_id = dict_from_prefix("environment_id",
                                              data).get(environment_type, "")

            data = {
                key: val
                for key, val in data.items()
                if not key.startswith("problem") and not key.startswith(
                    "envparams") and not key.startswith("environment_id")
                and not key.startswith("/") and not key == "@action"
            }

            data[
                "environment_id"] = environment_id  # we do this after having removed all the environment_id[something] entries

            # 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']))
                ])

            # Categories
            course_tags = course.get_tags()
            data['categories'] = [
                cat for cat in map(str.strip, data['categories'].split(','))
                if cat
            ]
            for category in data['categories']:
                if category not in course_tags:
                    return json.dumps({
                        "status": "error",
                        "message": _("Unknown category tag.")
                    })

            # Task environment parameters
            data["environment_parameters"] = environment_parameters

            # 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

            # Submission 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"]
            try:
                AccessibleTime(data["accessible"])
            except Exception as message:
                return json.dumps({
                    "status":
                    "error",
                    "message":
                    _("Invalid task accessibility ({})").format(message)
                })

            # 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:
            Task(course, taskid, data, self.course_factory.get_fs(),
                 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)

        return json.dumps({"status": "ok"})
Esempio n. 19
0
class Course(object):
    """ A course with some modification for users """
    def __init__(self, courseid, content, course_fs, task_factory,
                 plugin_manager):

        self._id = courseid
        self._content = content
        self._fs = course_fs
        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']
        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"
            )

        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_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()
            }
            if 'toc' in self._content:
                self._toc = SectionsList(self._content['toc'])
            else:
                tasks = self._task_factory.get_all_tasks(self)
                ordered_task_list = OrderedDict(
                    sorted(list(tasks.items()),
                           key=lambda t:
                           (t[1].get_old_order(), t[1].get_id())))
                indexed_task_list = {
                    taskid: rank
                    for rank, taskid in enumerate(ordered_task_list.keys())
                }
                self._toc = SectionsList([{
                    "id": "tasks-list",
                    "title": _("List of exercises"),
                    "rank": 0,
                    "tasks_list": indexed_task_list
                }])
        except:
            raise Exception("Course has an invalid YAML spec: " +
                            self.get_id())

        # Force some parameters if LTI is active
        if self.is_lti():
            self._accessible = AccessibleTime(True)
            self._registration = AccessibleTime(False)
            self._registration_password = None
            self._registration_ac = None
            self._registration_ac_list = []
            self._groups_student_choice = False
            self._allow_unregister = False
        else:
            self._lti_keys = {}
            self._lti_url = ''
            self._lti_send_back_grade = False

    def get_translation_obj(self, language):
        return self._translations.get(language, gettext.NullTranslations())

    def gettext(self, language, *args, **kwargs):
        return self.get_translation_obj(language).gettext(*args, **kwargs)

    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_task(self, taskid):
        """ Returns a Task object """
        return self._task_factory.get_task(self, 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()

    def is_registration_possible(self, user_info):
        """ Returns true if users can register for this course """
        return self.get_accessibility().is_open(
        ) and self._registration.is_open(
        ) and self.is_user_accepted_by_access_control(user_info)

    def is_password_needed_for_registration(self):
        """ Returns true if a password is needed for registration """
        return self._registration_password is not None

    def get_registration_password(self):
        """ Returns the password needed for registration (None if there is no password) """
        return self._registration_password

    def get_accessibility(self, plugin_override=True):
        """ Return the AccessibleTime object associated with the accessibility of this course """
        vals = self._plugin_manager.call_hook('course_accessibility',
                                              course=self,
                                              default=self._accessible)
        return vals[0] if len(vals) and plugin_override else self._accessible

    def get_registration_accessibility(self):
        """ Return the AccessibleTime object associated with the registration """
        return self._registration

    def get_tasks(self):
        tasks = self._task_factory.get_all_tasks(self)
        return OrderedDict(
            sorted(list(tasks.items()),
                   key=lambda t: (t[1].get_order(), t[1].get_id())))

    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 """
        return self._registration_ac

    def get_access_control_list(self):
        """ Returns the list of all users allowed by the AC list """
        return self._registration_ac_list

    def can_students_choose_group(self):
        """ Returns True if the students can choose their groups """
        return self._groups_student_choice

    def is_lti(self):
        """ True if the current course is in LTI mode """
        return self._is_lti

    def lti_keys(self):
        """ {name: key} for the LTI customers """
        return self._lti_keys if self._is_lti else {}

    def lti_url(self):
        """ Returns the URL to the external platform the course is hosted on """
        return self._lti_url

    def lti_send_back_grade(self):
        """ True if the current course should send back grade to the LTI Tool Consumer """
        return self._is_lti and self._lti_send_back_grade

    def is_user_accepted_by_access_control(self, user_info):
        """ Returns True if the user is allowed by the ACL """
        if self.get_access_control_method() is None:
            return True
        elif not user_info:
            return False
        elif self.get_access_control_method() == "username":
            return user_info["username"] in self.get_access_control_list()
        elif self.get_access_control_method() == "email":
            return user_info["email"] in self.get_access_control_list()
        elif self.get_access_control_method() == "binding":
            return set(user_info["bindings"].keys()).intersection(
                set(self.get_access_control_list()))
        return False

    def allow_preview(self):
        return self._allow_preview

    def allow_unregister(self, plugin_override=True):
        """ Returns True if students can unregister from course """
        vals = self._plugin_manager.call_hook('course_allow_unregister',
                                              course=self,
                                              default=self._allow_unregister)
        return vals[0] if len(
            vals) and plugin_override else self._allow_unregister

    def get_name(self, language):
        """ Return the name of this course """
        return self.gettext(language, self._name) if self._name else ""

    def get_description(self, language):
        """Returns the course description """
        description = self.gettext(
            language, self._description) if self._description else ''
        return ParsableText(description,
                            "rst",
                            translation=self.get_translation_obj(language))

    def get_tags(self):
        return self._tags

    def get_toc(self):
        """
       :return: the structure of the course
       """
        return self._toc