class FrontendCourse(Course): """ A course with some modification for users """ _task_class = FrontendTask def __init__(self, courseid): Course.__init__(self, courseid) if self._content.get('nofrontend', False): raise Exception("That course is not allowed to be displayed directly in the frontend") if "name" in self._content and "admins" in self._content and isinstance(self._content["admins"], list): self._name = self._content['name'] self._admins = self._content['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", "realname", "email"]: raise Exception("Course has an invalid value for registration_ac: " + courseid) self._registration_ac_list = self._content.get('registration_ac_list', []) self._groups = self._content.get("groups", False) self._groups_student_choice = self._content.get("groups_student_choice", False) else: raise Exception("Course has an invalid description: " + courseid) def get_name(self): """ Return the name of this course """ return self._name def get_staff(self, with_superadmin=True): """ Returns a list containing the usernames of all the staff users """ return list(set(self.get_tutors() + self.get_admins(with_superadmin))) def get_admins(self, with_superadmin=True): """ Returns a list containing the usernames of the administrators of this course """ if with_superadmin: return list(set(self._admins + INGIniousConfiguration.get('superadmins', []))) else: 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._accessible.is_open() def is_open_to_user(self, username, check_group=False): """ Returns true if the course is open to this user """ return (self._accessible.is_open() and self.is_user_registered(username, check_group)) or username in self.get_staff() def is_registration_possible(self, username): """ Returns true if users can register for this course """ return self._accessible.is_open() and self._registration.is_open() and self.is_user_accepted_by_access_control(username) 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 register_user(self, username, password=None, force=False): """ Register a user to the course. Returns True if the registration succeeded, False else. """ if not force: if not self.is_registration_possible(username): return False if self.is_password_needed_for_registration() and self._registration_password != password: return False if self.is_open_to_user(username): return False # already registered? get_database().registration.insert({"username": username, "courseid": self.get_id(), "date": datetime.now()}) return True def unregister_user(self, username): """ Unregister a user from this course """ get_database().registration.remove({"username": username, "courseid": self.get_id()}) if self.is_group_course(): get_database().groups.update({"course_id": self.get_id(), "users": username}, {"$pull":{"users": username}}) def is_user_registered(self, username, check_group=False): """ Returns True if the user is registered """ has_group = (not check_group) or \ (get_database().groups.find_one({"users": username, "course_id": self.get_id()}) is not None) return (get_database().registration.find_one({"username": username, "courseid": self.get_id()}) is not None)\ and has_group or username in self.get_staff() def get_registered_users(self, with_admins=True): """ Get all the usernames that are registered to this course (in no particular order)""" l = [entry['username'] for entry in list(get_database().registration.find({"courseid": self.get_id()}, {"username": True, "_id": False}))] if with_admins: return list(set(l + self.get_staff())) else: return l def get_accessibility(self): """ Return the AccessibleTime object associated with the accessibility of this course """ return self._accessible def get_registration_accessibility(self): """ Return the AccessibleTime object associated with the registration """ return self._registration def get_user_completion_percentage(self, username=None): """ Returns the percentage (integer) of completion of this course by the current user (or username if it is not None)""" if username is None: import frontend.user as User username = User.get_username() cache = UserData(username).get_course_data(self.get_id()) if cache is None: return 0 if cache["total_tasks"] == 0: return 100 return int(cache["task_succeeded"] * 100 / cache["total_tasks"]) def get_user_grade(self, username=None): """ Return the grade (a floating-point number between 0 and 100) of the user (if username is None, it uses the currently logged-in user) """ if username is None: import frontend.user as User username = User.get_username() cache = UserData(username).get_course_data(self.get_id()) if cache is None: return 0 total_weight = 0 grade = 0 for task_id, task in self.get_tasks().iteritems(): if task.is_visible_by_user(username): total_weight += task.get_grading_weight() grade += cache["task_grades"].get(task_id, 0.0) * task.get_grading_weight() if total_weight == 0: return 0 return grade / total_weight def get_user_last_submissions(self, limit=5, one_per_task=False): """ Returns a given number (default 5) of submissions of task from this course """ from frontend.submission_manager import get_user_last_submissions as extern_get_user_last_submissions task_ids = [] for task_id in self.get_tasks(): task_ids.append(task_id) return extern_get_user_last_submissions({"courseid": self.get_id(), "taskid": {"$in": task_ids}}, limit, one_per_task) def get_tasks(self): return OrderedDict(sorted(Course.get_tasks(self).items(), key=lambda t: t[1].get_order())) def get_access_control_method(self): """ Returns either None, "username", "realname", 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 get_user_group(self, username): """ Returns the group whose username belongs to """ return get_database().groups.find_one({"course_id": self.get_id(), "users": username}) def is_group_course(self): """ Returns True if the course submissions are made by groups """ return self._groups def can_students_choose_group(self): """ Returns True if the students can choose their groups """ return self._groups_student_choice def is_user_accepted_by_access_control(self, username): """ Returns True if the user is allowed by the ACL """ if self.get_access_control_method() is None: return True elif self.get_access_control_method() == "username": return username in self.get_access_control_list() elif self.get_access_control_method() == "realname": return UserData(username).get_data()["realname"] in self.get_access_control_list() elif self.get_access_control_method() == "email": return UserData(username).get_data()["email"] in self.get_access_control_list() return False
class FrontendTask(common.tasks.Task): """ A task that stores additionnal context informations """ # Redefine _problem_types with displayable ones _problem_types = { "code": DisplayableCodeProblem, "code-file": DisplayableCodeFileProblem, "code-single-line": DisplayableCodeSingleLineProblem, "multiple-choice": DisplayableMultipleChoiceProblem, "match": DisplayableMatchProblem} def __init__(self, course, taskid, init_data=None): # We load the descriptor of the task here to allow plugins to modify settings of the task before it is read by the Task constructor if not id_checker(taskid): raise Exception("Task with invalid id: " + course.get_id() + "/" + taskid) if init_data is None: try: init_data = get_task_file_manager(course.get_id(), taskid).read() except Exception as inst: raise Exception("Error while reading task file: " + self._course.get_id() + "/" + self._taskid + " :\n" + str(inst)) PluginManager.get_instance().call_hook('modify_task_data', course=course, taskid=taskid, data=init_data) # Now init the task common.tasks.Task.__init__(self, course, taskid, init_data) self._name = self._data.get('name', 'Task {}'.format(taskid)) self._context = ParsableText(self._data.get('context', ""), "HTML" if self._data.get("contextIsHTML", False) else "rst") # Authors if isinstance(self._data.get('author'), basestring): # verify if author is a string self._author = [self._data['author']] elif isinstance(self._data.get('author'), list): # verify if author is a list for author in self._data['author']: if not isinstance(author, basestring): # authors must be strings raise Exception("This task has an invalid author") self._author = self._data['author'] else: self._author = [] # Grade weight self._weight = float(self._data.get("weight", 1.0)) # _accessible self._accessible = AccessibleTime(self._data.get("accessible", None)) # Order self._order = int(self._data.get('order', -1)) def get_name(self): """ Returns the name of this task """ return self._name def get_context(self): """ Get the context(description) of this task """ return self._context def get_authors(self): """ Return the list of this task's authors """ return self._author def get_order(self): """ Get the position of this task in the course """ return self._order def get_grading_weight(self): """ Get the relative weight of this task in the grading """ return self._weight 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_admin() and self._accessible.after_start() def is_visible_by_user(self, username=None): """ Returns true if the task is visible by the user """ if username is None: import frontend.user as User username = User.get_username() return (self.get_course().is_open_to_user(username) and self._accessible.after_start()) or username in self.get_course().get_admins() def can_user_submit(self, username=None): """ returns true if the user can submit his work for this task """ if username is None: import frontend.user as User username = User.get_username() return (self.get_course().is_open_to_user(username) and self._accessible.is_open()) or username in self.get_course().get_admins() def get_deadline(self): """ Returns a string containing the deadline for this task """ if self._accessible.is_always_accessible(): return "No deadline" elif self._accessible.is_never_accessible(): return "It's too late" else: return self._accessible.get_end_date().strftime("%d/%m/%Y %H:%M:%S") def get_user_status(self): """ Returns "succeeded" if the current user solved this task, "failed" if he failed, and "notattempted" if he did not try it yet """ import frontend.user as User # insert here to avoid initialisation of session task_cache = User.get_data().get_task_data(self.get_course_id(), self.get_id()) if task_cache is None: return "notviewed" if task_cache["tried"] == 0: return "notattempted" return "succeeded" if task_cache["succeeded"] else "failed" def get_user_grade(self): """ Returns the grade (a floating-point number between 0 and 100) of the student """ import frontend.user as User # insert here to avoid initialisation of session task_cache = User.get_data().get_task_data(self.get_course_id(), self.get_id()) if task_cache is None: return 0.0 return task_cache.get("grade", 0.0) def adapt_input_for_backend(self, input_data): """ Adapt the input from web.py for the backend """ for problem in self._problems: input_data = problem.adapt_input_for_backend(input_data) return input_data