def post(self, course_uuid, user_uuid): """ Enrol or update a user enrolment in the course The payload for the request has to contain course_role. e.g. {"couse_role":"Student"} :param course_uuid: :param user_uuid: :return: """ course = Course.get_active_by_uuid_or_404(course_uuid) user = User.get_by_uuid_or_404(user_uuid) user_course = UserCourse.query \ .filter_by( user_id=user.id, course_id=course.id ) \ .first() if not user_course: user_course = UserCourse( user_id=user.id, course_id=course.id ) require(EDIT, user_course) params = new_course_user_parser.parse_args() role_name = params.get('course_role') course_roles = [ CourseRole.dropped.value, CourseRole.student.value, CourseRole.teaching_assistant.value, CourseRole.instructor.value ] if role_name not in course_roles: abort(404) course_role = CourseRole(role_name) if user_course.course_role != course_role: user_course.course_role = course_role db.session.add(user_course) db.session.commit() result = { 'user_id': user.uuid, 'fullname': user.fullname, 'course_role': course_role.value } on_classlist_enrol.send( self, event_name=on_classlist_enrol.name, user=current_user, course_id=course.id, data={'user_id': user.id}) return result
def get(self, course_uuid, group_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) group = Group.get_active_by_uuid_or_404(group_uuid) require( READ, UserCourse(course_id=course.id), title="Group Members Unavailable", message= "Group membership can be seen only by those enrolled in the course. Please double-check your enrollment in this course." ) members = User.query \ .join(UserCourse, UserCourse.user_id == User.id) \ .filter(and_( UserCourse.course_id == course.id, UserCourse.course_role != CourseRole.dropped, UserCourse.group_id == group.id )) \ .order_by(User.lastname, User.firstname) \ .all() on_group_user_list_get.send(current_app._get_current_object(), event_name=on_group_user_list_get.name, user=current_user, course_id=course.id, data={'group_id': group.id}) return { 'objects': [{ 'id': u.uuid, 'name': u.fullname_sortable } for u in members] }
def _update_enrollment_for_course(cls, course_id, lti_members): from compair.models import UserCourse user_courses = UserCourse.query \ .filter_by(course_id=course_id) \ .all() new_user_courses = [] for lti_member in lti_members: if lti_member.compair_user_id != None: user_course = next( (user_course for user_course in user_courses if user_course.user_id == lti_member.compair_user_id), None ) # add new user_course if doesn't exist if user_course == None: user_course = UserCourse( course_id=course_id, user_id=lti_member.compair_user_id, course_role=lti_member.course_role ) new_user_courses.append(user_course) # update user_course role else: user_course.course_role=lti_member.course_role # update user profile if needed lti_member.lti_user.update_user_profile() db.session.add_all(new_user_courses) db.session.commit() # set user_course to dropped role if missing from membership results and not current user for user_course in user_courses: # never unenrol current_user if current_user and current_user.is_authenticated and user_course.user_id == current_user.id: continue lti_member = next( (lti_member for lti_member in lti_members if user_course.user_id == lti_member.compair_user_id), None ) if lti_member == None: user_course.course_role = CourseRole.dropped db.session.commit()
def _update_enrollment_for_course(cls, course_id, lti_members): from compair.models import UserCourse user_courses = UserCourse.query \ .filter_by(course_id=course_id) \ .all() new_user_courses = [] for lti_member in lti_members: if lti_member.compair_user_id != None: user_course = next( (user_course for user_course in user_courses if user_course.user_id == lti_member.compair_user_id), None) # add new user_course if doesn't exist if user_course == None: user_course = UserCourse( course_id=course_id, user_id=lti_member.compair_user_id, course_role=lti_member.course_role) new_user_courses.append(user_course) # update user_course role else: user_course.course_role = lti_member.course_role # update user profile if needed lti_member.lti_user.update_user_profile() db.session.add_all(new_user_courses) db.session.commit() # set user_course to dropped role if missing from membership results and not current user for user_course in user_courses: # never unenrol current_user if current_user and current_user.is_authenticated and user_course.user_id == current_user.id: continue lti_member = next( (lti_member for lti_member in lti_members if user_course.user_id == lti_member.compair_user_id), None) if lti_member == None: user_course.course_role = CourseRole.dropped db.session.commit()
def post(self): """ Create new course """ require( CREATE, Course, title="Course Not Saved", message= "Sorry, your role in the system does not allow you to save courses." ) params = new_course_parser.parse_args() new_course = Course(name=params.get("name"), year=params.get("year"), term=params.get("term"), sandbox=params.get("sandbox"), start_date=params.get('start_date'), end_date=params.get('end_date', None)) if new_course.start_date is not None: new_course.start_date = datetime.datetime.strptime( new_course.start_date, '%Y-%m-%dT%H:%M:%S.%fZ') if new_course.end_date is not None: new_course.end_date = datetime.datetime.strptime( new_course.end_date, '%Y-%m-%dT%H:%M:%S.%fZ') if new_course.start_date and new_course.end_date and new_course.start_date > new_course.end_date: abort(400, title="Course Not Saved", message="Course end time must be after course start time.") try: # create the course db.session.add(new_course) # also need to enrol the user as an instructor new_user_course = UserCourse(course=new_course, user_id=current_user.id, course_role=CourseRole.instructor) db.session.add(new_user_course) db.session.commit() except exc.SQLAlchemyError as e: db.session.rollback() current_app.logger.error("Failed to add new course. " + str(e)) raise on_course_create.send(self, event_name=on_course_create.name, user=current_user, course=new_course, data=marshal(new_course, dataformat.get_course())) return marshal(new_course, dataformat.get_course())
def post(self, course_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) require( EDIT, UserCourse(course_id=course.id), title="Group Not Saved", message= "Sorry, your role in this course does not allow you to save groups." ) params = user_list_parser.parse_args() if len(params.get('ids')) == 0: abort( 400, title="Group Not Saved", message= "Please select at least one user below and then try to apply the group again." ) user_courses = UserCourse.query \ .join(User, UserCourse.user_id == User.id) \ .filter(and_( UserCourse.course_id == course.id, User.uuid.in_(params.get('ids')), UserCourse.course_role != CourseRole.dropped )) \ .all() if len(params.get('ids')) != len(user_courses): abort( 400, title="Group Not Saved", message= "One or more users selected are not enrolled in the course yet." ) for user_course in user_courses: user_course.group_name = None db.session.commit() on_course_group_user_list_delete.send( current_app._get_current_object(), event_name=on_course_group_user_list_delete.name, user=current_user, course_id=course.id, data={'user_uuids': params.get('ids')}) return {'course_id': course.uuid}
def get(self, course_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) require( READ, course, title="Instructors Unavailable", message= "Instructors can only be seen here by those enrolled in the course. Please double-check your enrollment in this course." ) restrict_user = not can(MANAGE, course) instructors = User.query \ .with_entities(User, UserCourse) \ .join(UserCourse, UserCourse.user_id == User.id) \ .filter( UserCourse.course_id == course.id, or_( UserCourse.course_role == CourseRole.instructor, UserCourse.course_role == CourseRole.teaching_assistant ) ) \ .order_by(User.lastname, User.firstname) \ .all() users = [] user_course = UserCourse(course_id=course.id) for u in instructors: if can(READ, user_course): users.append({ 'id': u.User.uuid, 'name': u.User.fullname_sortable, 'group_id': u.UserCourse.group_id, 'role': u.UserCourse.course_role.value }) else: users.append({ 'id': u.User.uuid, 'name': u.User.displayname, 'group_id': u.UserCourse.group_id, 'role': u.UserCourse.course_role.value }) on_classlist_instructor.send(self, event_name=on_classlist_instructor.name, user=current_user, course_id=course.id) return {'objects': users}
def get(self, course_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) require( READ, course, title="Students Unavailable", message= "Students can only be seen here by those enrolled in the course. Please double-check your enrollment in this course." ) restrict_user = not allow(MANAGE, course) students = User.query \ .with_entities(User, UserCourse.group_name) \ .join(UserCourse, UserCourse.user_id == User.id) \ .filter( UserCourse.course_id == course.id, UserCourse.course_role == CourseRole.student ) \ .order_by(User.lastname, User.firstname) \ .all() users = [] user_course = UserCourse(course_id=course.id) for u in students: if allow(READ, user_course): users.append({ 'id': u.User.uuid, 'name': u.User.fullname_sortable, 'group_name': u.group_name }) else: name = u.User.displayname if u.User.id == current_user.id: name += ' (You)' users.append({ 'id': u.User.uuid, 'name': name, 'group_name': u.group_name }) on_classlist_student.send(self, event_name=on_classlist_student.name, user=current_user, course_id=course.id) return {'objects': users}
def post(self, course_uuid, user_uuid): """ Enrol or update a user enrolment in the course The payload for the request has to contain course_role. e.g. {"couse_role":"Student"} :param course_uuid: :param user_uuid: :return: """ course = Course.get_active_by_uuid_or_404(course_uuid) user = User.get_by_uuid_or_404(user_uuid) if current_app.config.get('DEMO_INSTALLATION', False): from data.fixtures import DemoDataFixture if course.id == DemoDataFixture.DEFAULT_COURSE_ID and user.id in DemoDataFixture.DEFAULT_COURSE_USERS: abort( 400, title="Enrollment Not Updated", message= "Sorry, you cannot update course role for the default users in the default demo course." ) user_course = UserCourse.query \ .filter_by( user_id=user.id, course_id=course.id ) \ .first() if not user_course: user_course = UserCourse(user_id=user.id, course_id=course.id) require( EDIT, user_course, title="Enrollment Not Updated", message= "Sorry, your role in this course does not allow you to update enrollment." ) params = new_course_user_parser.parse_args() role_name = params.get('course_role') course_roles = [ CourseRole.dropped.value, CourseRole.student.value, CourseRole.teaching_assistant.value, CourseRole.instructor.value ] if role_name not in course_roles: abort( 400, title="Enrollment Not Updated", message= "Please try again with a course role from the list of roles provided." ) course_role = CourseRole(role_name) if user_course.course_role != course_role: user_course.course_role = course_role db.session.add(user_course) db.session.commit() on_classlist_enrol.send(self, event_name=on_classlist_enrol.name, user=current_user, course_id=course.id, data={'user_id': user.id}) return { 'user_id': user.uuid, 'fullname': user.fullname, 'fullname_sortable': user.fullname_sortable, 'course_role': course_role.value }
def post(self, course_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) user_course = UserCourse(course_id=course.id) require( EDIT, user_course, title="Class List Not Imported", message= "Sorry, your role in this course does not allow you to import or otherwise change the class list." ) if current_app.config.get('DEMO_INSTALLATION', False): from data.fixtures import DemoDataFixture if course.id == DemoDataFixture.DEFAULT_COURSE_ID: abort( 400, title="Class List Not Imported", message= "Sorry, you cannot import users for the default demo course." ) params = import_classlist_parser.parse_args() import_type = params.get('import_type') if import_type not in [ ThirdPartyType.cas.value, ThirdPartyType.saml.value, None ]: abort( 400, title="Class List Not Imported", message= "Please select a way for students to log in and try importing again." ) elif import_type == ThirdPartyType.cas.value and not current_app.config.get( 'CAS_LOGIN_ENABLED'): abort( 400, title="Class List Not Imported", message= "Please select another way for students to log in and try importing again. Students are not able to use CWL logins based on the current settings." ) elif import_type == ThirdPartyType.saml.value and not current_app.config.get( 'SAML_LOGIN_ENABLED'): abort( 400, title="Class List Not Imported", message= "Please select another way for students to log in and try importing again. Students are not able to use CWL logins based on the current settings." ) elif import_type is None and not current_app.config.get( 'APP_LOGIN_ENABLED'): abort( 400, title="Class List Not Imported", message= "Please select another way for students to log in and try importing again. Students are not able to use the ComPAIR logins based on the current settings." ) uploaded_file = request.files['file'] results = {'success': 0, 'invalids': []} if not uploaded_file: abort(400, title="Class List Not Imported", message= "No file was found to upload. Please try uploading again.") elif not allowed_file(uploaded_file.filename, current_app.config['UPLOAD_ALLOWED_EXTENSIONS']): abort( 400, title="Class List Not Imported", message= "Sorry, only CSV files can be imported. Please try again with a CSV file." ) unique = str(uuid.uuid4()) filename = unique + secure_filename(uploaded_file.filename) tmp_name = os.path.join(current_app.config['UPLOAD_FOLDER'], filename) uploaded_file.save(tmp_name) current_app.logger.debug("Importing for course " + str(course.id) + " with " + filename) with open(tmp_name, 'rb') as csvfile: spamreader = csv.reader(csvfile) users = [] for row in spamreader: if row: users.append(row) if len(users) > 0: results = import_users(import_type, course, users) on_classlist_upload.send(self, event_name=on_classlist_upload.name, user=current_user, course_id=course.id) os.remove(tmp_name) current_app.logger.debug("Class Import for course " + str(course.id) + " is successful. Removed file.") return results
def get(self, course_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) require( READ, UserCourse(course_id=course.id), title="Class List Unavailable", message= "Sorry, your role in this course does not allow you to view the class list." ) restrict_user = not can(READ, USER_IDENTITY) # expire current_user from the session. When loading classlist from database, if the # user is already in the session, e.g. instructor for the course, the User.user_courses # is not loaded from the query below, but from session. In this case, if user has more # than one course, User.user_courses will return multiple row. Thus violating the # course_role constrain. db.session.expire(current_user) users = User.query \ .with_entities(User, UserCourse) \ .options(joinedload(UserCourse.group)) \ .join(UserCourse, and_( UserCourse.user_id == User.id, UserCourse.course_id == course.id )) \ .filter( UserCourse.course_role != CourseRole.dropped ) \ .order_by(User.lastname, User.firstname) \ .all() if not restrict_user: user_ids = [_user.id for (_user, _user_course) in users] third_party_auths = ThirdPartyUser.query \ .filter(and_( ThirdPartyUser.user_id.in_(user_ids), or_( ThirdPartyUser.third_party_type == ThirdPartyType.cas, ThirdPartyUser.third_party_type == ThirdPartyType.saml, ) )) \ .all() class_list = [] for (_user, _user_course) in users: _user.course_role = _user_course.course_role _user.group = _user_course.group _user.group_uuid = _user.group.uuid if _user.group else None _user.group_name = _user.group.name if _user.group else None if not restrict_user: cas_auth = next( (auth for auth in third_party_auths if auth.user_id == _user.id and auth.third_party_type == ThirdPartyType.cas), None) _user.cas_username = cas_auth.unique_identifier if cas_auth else None saml_auth = next( (auth for auth in third_party_auths if auth.user_id == _user.id and auth.third_party_type == ThirdPartyType.saml), None) _user.saml_username = saml_auth.unique_identifier if saml_auth else None class_list.append(_user) on_classlist_get.send(self, event_name=on_classlist_get.name, user=current_user, course_id=course.id) if can(MANAGE, User): return { 'objects': marshal(class_list, dataformat.get_full_users_in_course()) } else: return { 'objects': marshal( class_list, dataformat.get_users_in_course( restrict_user=restrict_user)) }
def import_users(import_type, course, users): invalids = [] # invalid entries - eg. invalid # of columns count = 0 # store number of successful enrolments imported_users = [] set_user_passwords = [] # store unique user identifiers - eg. student number - throws error if duplicate in file import_usernames = [] import_student_numbers = [] # store unique user identifiers - eg. student number - throws error if duplicate in file existing_system_usernames = _get_existing_users_by_identifier( import_type, users) existing_system_student_numbers = _get_existing_users_by_student_number( import_type, users) groups = course.groups.all() groups_by_name = {} for group in groups: groups_by_name[group.name] = group # create / update users in file for user_row in users: if len(user_row) < 1: continue # skip empty row user = _parse_user_row(import_type, user_row) # validate unique identifier username = user.get('username') password = user.get( 'password' ) #always None for CAS/SAML import, can be None for existing users on ComPAIR import student_number = user.get('student_number') u = existing_system_usernames.get(username, None) if not username: invalids.append({ 'user': User(username=username), 'message': 'The username is required.' }) continue elif username in import_usernames: invalids.append({ 'user': User(username=username), 'message': 'This username already exists in the file.' }) continue if u: # overwrite password if user has not logged in yet if u.last_online == None and not password in [None, '*']: set_user_passwords.append((u, password)) else: u = User(username=None, password=None, student_number=user.get('student_number'), firstname=user.get('firstname'), lastname=user.get('lastname'), email=user.get('email')) if import_type == ThirdPartyType.cas.value or import_type == ThirdPartyType.saml.value: # CAS/SAML login u.third_party_auths.append( ThirdPartyUser( unique_identifier=username, third_party_type=ThirdPartyType(import_type))) else: # ComPAIR login u.username = username if password in [None, '*']: invalids.append({ 'user': u, 'message': 'The password is required.' }) continue elif len(password) < 4: invalids.append({ 'user': u, 'message': 'The password must be at least 4 characters long.' }) continue else: set_user_passwords.append((u, password)) # validate student number (if not None) if student_number: # invalid if already showed up in file if student_number in import_student_numbers: u.username = username invalids.append({ 'user': u, 'message': 'This student number already exists in the file.' }) continue # invalid if student number already exists in the system elif student_number in existing_system_student_numbers: u.username = username invalids.append({ 'user': u, 'message': 'This student number already exists in the system.' }) continue u.system_role = SystemRole.student u.displayname = user.get('displayname') if user.get( 'displayname') else display_name_generator() db.session.add(u) import_usernames.append(username) if student_number: import_student_numbers.append(student_number) imported_users.append((u, user.get('group'))) db.session.commit() enroled = UserCourse.query \ .filter_by(course_id=course.id) \ .all() enroled = {e.user_id: e for e in enroled} students = UserCourse.query \ .filter_by( course_id=course.id, course_role=CourseRole.student ) \ .all() students = {s.user_id: s for s in students} # enrol valid users in file for user, group_name in imported_users: enrol = enroled.get(user.id, UserCourse(course_id=course.id, user_id=user.id)) enrol.group = None if group_name: group = groups_by_name.get(group_name) # add new groups if needed if not group: group = Group(course=course, name=group_name) groups_by_name[group_name] = group db.session.add(group) enrol.group = group # do not overwrite instructor or teaching assistant roles if enrol.course_role not in [ CourseRole.instructor, CourseRole.teaching_assistant ]: enrol.course_role = CourseRole.student if user.id in students: del students[user.id] count += 1 db.session.add(enrol) db.session.commit() # unenrol users not in file anymore for user_id in students: enrolment = students.get(user_id) # skip users that are already dropped if enrolment.course_role == CourseRole.dropped: continue enrolment.course_role = CourseRole.dropped enrolment.group_id = None db.session.add(enrolment) db.session.commit() # wait until user ids are generated before starting background jobs # perform password update in chunks of 100 chunk_size = 100 chunks = [ set_user_passwords[index:index + chunk_size] for index in range(0, len(set_user_passwords), chunk_size) ] for chunk in chunks: set_passwords.delay({user.id: password for (user, password) in chunk}) on_classlist_upload.send(current_app._get_current_object(), event_name=on_classlist_upload.name, user=current_user, course_id=course.id) return { 'success': count, 'invalids': marshal(invalids, dataformat.get_import_users_results(False)) }
def post(self, course_uuid, user_uuid): """ Enrol or update a user enrolment in the course The payload for the request has to contain course_role. e.g. {"couse_role":"Student"} :param course_uuid: :param user_uuid: :return: """ course = Course.get_active_by_uuid_or_404(course_uuid) user = User.get_by_uuid_or_404(user_uuid) if current_app.config.get('DEMO_INSTALLATION', False): from data.fixtures import DemoDataFixture if course.id == DemoDataFixture.DEFAULT_COURSE_ID and user.id in DemoDataFixture.DEFAULT_COURSE_USERS: abort(400, title="Enrollment Not Updated", message="Sorry, you cannot update course role for the default users in the default demo course.") user_course = UserCourse.query \ .filter_by( user_id=user.id, course_id=course.id ) \ .first() if not user_course: user_course = UserCourse( user_id=user.id, course_id=course.id ) require(EDIT, user_course, title="Enrollment Not Updated", message="Sorry, your role in this course does not allow you to update enrollment.") params = new_course_user_parser.parse_args() role_name = params.get('course_role') course_roles = [ CourseRole.dropped.value, CourseRole.student.value, CourseRole.teaching_assistant.value, CourseRole.instructor.value ] if role_name not in course_roles: abort(400, title="Enrollment Not Updated", message="Please try again with a course role from the list of roles provided.") course_role = CourseRole(role_name) if user_course.course_role != course_role: user_course.course_role = course_role db.session.add(user_course) db.session.commit() on_classlist_enrol.send( self, event_name=on_classlist_enrol.name, user=current_user, course_id=course.id, data={'user_id': user.id}) return { 'user_id': user.uuid, 'fullname': user.fullname, 'fullname_sortable': user.fullname_sortable, 'course_role': course_role.value }
def post(self, course_uuid): # delete multiple (DELETE request cannot use params in angular) course = Course.get_active_by_uuid_or_404(course_uuid) require( EDIT, UserCourse(course_id=course.id), title="Group Not Saved", message= "Sorry, your role in this course does not allow you to save groups." ) params = user_list_parser.parse_args() if len(params.get('ids')) == 0: abort( 400, title="Group Not Saved", message= "Please select at least one user below and then try to apply the group again." ) user_courses = UserCourse.query \ .join(User, UserCourse.user_id == User.id) \ .filter(and_( UserCourse.course_id == course.id, User.uuid.in_(params.get('ids')), UserCourse.course_role != CourseRole.dropped )) \ .all() if len(params.get('ids')) != len(user_courses): abort( 400, title="Group Not Saved", message= "One or more users selected are not enrolled in the course yet." ) for user_course in user_courses: if course.groups_locked and user_course.group_id != None: abort( 400, title="Group Not Saved", message= "The course groups are locked. You may not remove users from the group they are already assigned to." ) for user_course in user_courses: user_course.group_id = None db.session.commit() on_group_user_list_delete.send( current_app._get_current_object(), event_name=on_group_user_list_delete.name, user=current_user, course_id=course.id, data={ 'user_ids': [user_course.user_id for user_course in user_courses] }) return {'success': True}
def post(self, course_uuid, group_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) group = Group.get_active_by_uuid_or_404(group_uuid) require( EDIT, UserCourse(course_id=course.id), title="Group Not Saved", message= "Sorry, your role in this course does not allow you to save groups." ) params = user_list_parser.parse_args() if len(params.get('ids')) == 0: abort( 400, title="Group Not Saved", message= "Please select at least one user below and then try to apply the group again." ) user_courses = UserCourse.query \ .join(User, UserCourse.user_id == User.id) \ .filter(and_( UserCourse.course_id == course.id, User.uuid.in_(params.get('ids')), UserCourse.course_role != CourseRole.dropped )) \ .all() if len(params.get('ids')) != len(user_courses): abort( 400, title="Group Not Saved", message= "One or more users selected are not enrolled in the course yet." ) for user_course in user_courses: if course.groups_locked and user_course.group_id != None and user_course.group_id != group.id: abort( 400, title="Group Not Saved", message= "The course groups are locked. One or more users are already assigned to a different group." ) for user_course in user_courses: user_course.group_id = group.id db.session.commit() on_group_user_list_create.send( current_app._get_current_object(), event_name=on_group_user_list_create.name, user=current_user, course_id=course.id, data={ 'user_ids': [user_course.user_id for user_course in user_courses] }) return marshal(group, dataformat.get_group())
def post(self, course_uuid): """ Duplicate a course """ course = Course.get_active_by_uuid_or_404(course_uuid) require( EDIT, course, title="Course Not Duplicated", message= "Sorry, your role in this course does not allow you to duplicate it." ) params = duplicate_course_parser.parse_args() start_date = datetime.datetime.strptime( params.get("start_date"), '%Y-%m-%dT%H:%M:%S.%fZ') if params.get("start_date") else None end_date = datetime.datetime.strptime( params.get("end_date"), '%Y-%m-%dT%H:%M:%S.%fZ') if params.get("end_date") else None if start_date is None: abort(400, title="Course Not Saved", message="Course start time is required.") elif start_date and end_date and start_date > end_date: abort(400, title="Course Not Saved", message="Course end time must be after course start time.") assignments = [ assignment for assignment in course.assignments if assignment.active ] assignments_copy_data = params.get("assignments") if len(assignments) != len(assignments_copy_data): abort( 400, title="Course Not Saved", message= "The course is missing assignments. Please reload the page and try duplicating again." ) for assignment_copy_data in assignments_copy_data: if assignment_copy_data.get('answer_start'): assignment_copy_data[ 'answer_start'] = datetime.datetime.strptime( assignment_copy_data.get('answer_start'), '%Y-%m-%dT%H:%M:%S.%fZ') if assignment_copy_data.get('answer_end'): assignment_copy_data[ 'answer_end'] = datetime.datetime.strptime( assignment_copy_data.get('answer_end'), '%Y-%m-%dT%H:%M:%S.%fZ') if assignment_copy_data.get('compare_start'): assignment_copy_data[ 'compare_start'] = datetime.datetime.strptime( assignment_copy_data.get('compare_start'), '%Y-%m-%dT%H:%M:%S.%fZ') if assignment_copy_data.get('compare_end'): assignment_copy_data[ 'compare_end'] = datetime.datetime.strptime( assignment_copy_data.get('compare_end'), '%Y-%m-%dT%H:%M:%S.%fZ') if 'enable_self_evaluation' not in assignment_copy_data: assignment_copy_data['enable_self_evaluation'] = False if assignment_copy_data.get('self_eval_start'): assignment_copy_data[ 'self_eval_start'] = datetime.datetime.strptime( assignment_copy_data.get('self_eval_start'), '%Y-%m-%dT%H:%M:%S.%fZ') if assignment_copy_data.get('self_eval_end'): assignment_copy_data[ 'self_eval_end'] = datetime.datetime.strptime( assignment_copy_data.get('self_eval_end'), '%Y-%m-%dT%H:%M:%S.%fZ') valid, error_message = Assignment.validate_periods( start_date, end_date, assignment_copy_data.get('answer_start'), assignment_copy_data.get('answer_end'), assignment_copy_data.get('compare_start'), assignment_copy_data.get('compare_end'), assignment_copy_data.get('self_eval_start'), assignment_copy_data.get('self_eval_end')) if not valid: error_message = error_message.replace( ".", "") + " for assignment " + text_type( assignment_copy_data.get('name', '')) + "." abort(400, title="Course Not Saved", message=error_message) # duplicate course duplicate_course = Course(name=params.get("name"), year=params.get("year"), term=params.get("term"), sandbox=params.get("sandbox"), start_date=start_date, end_date=end_date) db.session.add(duplicate_course) # also need to enrol the user as an instructor new_user_course = UserCourse(course=duplicate_course, user_id=current_user.id, course_role=CourseRole.instructor) db.session.add(new_user_course) # duplicate assignments for assignment in assignments: # this should never be null due assignment_copy_data = next( (assignment_copy_data for assignment_copy_data in assignments_copy_data if assignment_copy_data.get('id') == assignment.uuid), None) if not assignment_copy_data: abort(400, title="Course Not Saved", message="Missing information for assignment " + assignment.name + ". Please try duplicating again.") duplicate_assignment = Assignment( course=duplicate_course, user_id=current_user.id, file=assignment.file, name=assignment.name, description=assignment.description, answer_start=assignment_copy_data.get('answer_start'), answer_end=assignment_copy_data.get('answer_end'), compare_start=assignment_copy_data.get('compare_start'), compare_end=assignment_copy_data.get('compare_end'), self_eval_start=assignment_copy_data.get('self_eval_start') if assignment_copy_data.get('enable_self_evaluation', False) else None, self_eval_end=assignment_copy_data.get('self_eval_end') if assignment_copy_data.get('enable_self_evaluation', False) else None, self_eval_instructions=assignment.self_eval_instructions if assignment_copy_data.get('enable_self_evaluation', False) else None, answer_grade_weight=assignment.answer_grade_weight, comparison_grade_weight=assignment.comparison_grade_weight, self_evaluation_grade_weight=assignment. self_evaluation_grade_weight, number_of_comparisons=assignment.number_of_comparisons, students_can_reply=assignment.students_can_reply, enable_self_evaluation=assignment_copy_data.get( 'enable_self_evaluation', False), enable_group_answers=assignment.enable_group_answers, pairing_algorithm=assignment.pairing_algorithm, scoring_algorithm=assignment.scoring_algorithm, peer_feedback_prompt=assignment.peer_feedback_prompt, educators_can_compare=assignment.educators_can_compare, rank_display_limit=assignment.rank_display_limit, ) db.session.add(duplicate_assignment) # duplicate assignment criteria for assignment_criterion in assignment.assignment_criteria: if not assignment_criterion.active: continue duplicate_assignment_criterion = AssignmentCriterion( assignment=duplicate_assignment, criterion_id=assignment_criterion.criterion_id) db.session.add(duplicate_assignment_criterion) # duplicate assignment comparisons examples for comparison_example in assignment.comparison_examples: answer1 = comparison_example.answer1 answer2 = comparison_example.answer2 # duplicate assignment comparisons example answers duplicate_answer1 = Answer(assignment=duplicate_assignment, user_id=current_user.id, file=answer1.file, content=answer1.content, practice=answer1.practice, active=answer1.active, draft=answer1.draft) db.session.add(duplicate_answer1) # duplicate assignment comparisons example answers duplicate_answer2 = Answer(assignment=duplicate_assignment, user_id=current_user.id, file=answer2.file, content=answer2.content, practice=answer2.practice, active=answer2.active, draft=answer2.draft) db.session.add(duplicate_answer2) duplicate_comparison_example = ComparisonExample( assignment=duplicate_assignment, answer1=duplicate_answer1, answer2=duplicate_answer2) db.session.add(duplicate_comparison_example) db.session.commit() on_course_duplicate.send(self, event_name=on_course_duplicate.name, user=current_user, course=duplicate_course, data=marshal(course, dataformat.get_course())) return marshal(duplicate_course, dataformat.get_course())
def post(self): # login_required when oauth_create_user_link not set if not sess.get('oauth_create_user_link'): if not current_app.login_manager._login_disabled and \ not current_user.is_authenticated: return current_app.login_manager.unauthorized() user = User() params = new_user_parser.parse_args() user.student_number = params.get("student_number", None) user.email = params.get("email") user.firstname = params.get("firstname") user.lastname = params.get("lastname") user.displayname = params.get("displayname") email_notification_method = params.get("email_notification_method") check_valid_email_notification_method(email_notification_method) user.email_notification_method = EmailNotificationMethod( email_notification_method) # if creating a cas user, do not set username or password if sess.get('oauth_create_user_link') and sess.get('LTI') and sess.get( 'CAS_CREATE'): user.username = None user.password = None else: # else enforce required password and unique username user.password = params.get("password") if user.password == None: abort( 400, title="User Not Saved", message= "A password is required. Please enter a password and try saving again." ) user.username = params.get("username") if user.username == None: abort( 400, title="User Not Saved", message= "A username is required. Please enter a username and try saving again." ) username_exists = User.query.filter_by( username=user.username).first() if username_exists: abort( 409, title="User Not Saved", message= "Sorry, this username already exists and usernames must be unique in ComPAIR. Please enter another username and try saving again." ) student_number_exists = User.query.filter_by( student_number=user.student_number).first() # if student_number is not left blank and it exists -> 409 error if user.student_number is not None and student_number_exists: abort( 409, title="User Not Saved", message= "Sorry, this student number already exists and student numbers must be unique in ComPAIR. Please enter another number and try saving again." ) # handle oauth_create_user_link setup for third party logins if sess.get('oauth_create_user_link'): login_method = None if sess.get('LTI'): lti_user = LTIUser.query.get_or_404(sess['lti_user']) lti_user.compair_user = user user.system_role = lti_user.system_role login_method = 'LTI' if sess.get('lti_context') and sess.get( 'lti_user_resource_link'): lti_context = LTIContext.query.get_or_404( sess['lti_context']) lti_user_resource_link = LTIUserResourceLink.query.get_or_404( sess['lti_user_resource_link']) if lti_context.is_linked_to_course(): # create new enrollment new_user_course = UserCourse( user=user, course_id=lti_context.compair_course_id, course_role=lti_user_resource_link.course_role) db.session.add(new_user_course) if sess.get('CAS_CREATE'): thirdpartyuser = ThirdPartyUser( third_party_type=ThirdPartyType.cas, unique_identifier=sess.get('CAS_UNIQUE_IDENTIFIER'), params=sess.get('CAS_PARAMS'), user=user) login_method = ThirdPartyType.cas.value db.session.add(thirdpartyuser) else: system_role = params.get("system_role") check_valid_system_role(system_role) user.system_role = SystemRole(system_role) require( CREATE, user, title="User Not Saved", message="Sorry, your role does not allow you to save users.") # only students can have student numbers if user.system_role != SystemRole.student: user.student_number = None try: db.session.add(user) db.session.commit() if current_user.is_authenticated: on_user_create.send(self, event_name=on_user_create.name, user=current_user, data=marshal(user, dataformat.get_user(False))) else: on_user_create.send(self, event_name=on_user_create.name, data=marshal(user, dataformat.get_user(False))) except exc.IntegrityError: db.session.rollback() current_app.logger.error("Failed to add new user. Duplicate.") abort( 409, title="User Not Saved", message= "Sorry, this ID already exists and IDs must be unique in ComPAIR. Please try addding another user." ) # handle oauth_create_user_link teardown for third party logins if sess.get('oauth_create_user_link'): authenticate(user, login_method=login_method) sess.pop('oauth_create_user_link') if sess.get('CAS_CREATE'): sess.pop('CAS_CREATE') sess.pop('CAS_UNIQUE_IDENTIFIER') sess['CAS_LOGIN'] = True return marshal(user, dataformat.get_user())
def post(self): if not current_app.config.get('DEMO_INSTALLATION', False): abort( 404, title="Demo Accounts Unavailable", message= "Sorry, the system settings do now allow the use of demo accounts." ) params = new_user_demo_parser.parse_args() user = User() user.password = "******" system_role = params.get("system_role") check_valid_system_role(system_role) user.system_role = SystemRole(system_role) user_count = User.query \ .filter_by(system_role=user.system_role) \ .count() user_count += 1 # username while True: if user.system_role == SystemRole.sys_admin: user.username = "******" + str(user_count) elif user.system_role == SystemRole.instructor: user.username = "******" + str(user_count) else: user.username = "******" + str(user_count) username_exists = User.query.filter_by( username=user.username).first() if not username_exists: break else: user_count += 1 if user.system_role == SystemRole.sys_admin: user.firstname = "Admin" user.lastname = str(user_count) user.displayname = "Admin " + str(user_count) elif user.system_role == SystemRole.instructor: user.firstname = "Instructor" user.lastname = str(user_count) user.displayname = "Instructor " + str(user_count) # create new enrollment new_user_course = UserCourse(user=user, course_id=1, course_role=CourseRole.instructor) db.session.add(new_user_course) else: user.firstname = "Student" user.lastname = str(user_count) user.displayname = display_name_generator() while True: user.student_number = random_generator(8, string.digits) student_number_exists = User.query.filter_by( student_number=user.student_number).first() if not student_number_exists: break # create new enrollment new_user_course = UserCourse(user=user, course_id=1, course_role=CourseRole.student) db.session.add(new_user_course) try: db.session.add(user) db.session.commit() on_user_demo_create.send(self, event_name=on_user_demo_create.name, user=current_user, data=marshal(user, dataformat.get_user(False))) except exc.IntegrityError: db.session.rollback() current_app.logger.error("Failed to add new user. Duplicate.") return { 'error': 'A user with the same identifier already exists.' }, 400 authenticate(user, login_method="Demo") return marshal(user, dataformat.get_user())
def post(self, course_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) require( EDIT, UserCourse(course_id=course.id), title="Enrollment Not Updated", message= "Sorry, your role in this course does not allow you to update enrollment." ) params = update_users_course_role_parser.parse_args() role_name = params.get('course_role') course_roles = [ CourseRole.dropped.value, CourseRole.student.value, CourseRole.teaching_assistant.value, CourseRole.instructor.value ] if role_name not in course_roles: abort( 400, title="Enrollment Not Updated", message= "Please try again with a course role from the list of roles provided." ) course_role = CourseRole(role_name) if len(params.get('ids')) == 0: abort( 400, title="Enrollment Not Updated", message= "Please select at least one user below and then try to update the enrollment again." ) user_courses = UserCourse.query \ .join(User, UserCourse.user_id == User.id) \ .filter(and_( UserCourse.course_id == course.id, User.uuid.in_(params.get('ids')), UserCourse.course_role != CourseRole.dropped )) \ .all() if len(params.get('ids')) != len(user_courses): abort( 400, title="Enrollment Not Updated", message= "One or more users selected are not enrolled in the course yet." ) if len(user_courses ) == 1 and user_courses[0].user_id == current_user.id: if course_role == CourseRole.dropped: abort( 400, title="Enrollment Not Updated", message= "Sorry, you cannot drop yourself from the course. Please select only other users and try again." ) else: abort( 400, title="Enrollment Not Updated", message= "Sorry, you cannot change your own course role. Please select only other users and try again." ) for user_course in user_courses: if current_app.config.get('DEMO_INSTALLATION', False): from data.fixtures import DemoDataFixture if course.id == DemoDataFixture.DEFAULT_COURSE_ID and user.id in DemoDataFixture.DEFAULT_COURSE_USERS: abort( 400, title="Enrollment Not Updated", message= "Sorry, you cannot update course role for the default users in the default demo course." ) # skip current user if user_course.user_id == current_user.id: continue # update user's role user_course.course_role = course_role db.session.commit() on_classlist_update_users_course_roles.send( current_app._get_current_object(), event_name=on_classlist_update_users_course_roles.name, user=current_user, course_id=course.id, data={ 'user_uuids': params.get('ids'), 'course_role': role_name }) return {'course_role': role_name}
def post(self): # login_required when lti_create_user_link not set if not sess.get( 'lti_create_user_link') and not current_user.is_authenticated: return current_app.login_manager.unauthorized() user = User() params = new_user_parser.parse_args() user.student_number = params.get("student_number", None) user.email = params.get("email") user.firstname = params.get("firstname") user.lastname = params.get("lastname") user.displayname = params.get("displayname") email_notification_method = params.get("email_notification_method") check_valid_email_notification_method(email_notification_method) user.email_notification_method = EmailNotificationMethod( email_notification_method) if not current_app.config.get('APP_LOGIN_ENABLED'): # if APP_LOGIN_ENABLED is not enabled, allow blank username and password user.username = None user.password = None else: # else enforce required password and unique username user.password = params.get("password") if user.password == None: abort( 400, title="User Not Saved", message= "A password is required. Please enter a password and try saving again." ) elif len(params.get("password")) < 4: abort( 400, title="User Not Saved", message="The password must be at least 4 characters long.") user.username = params.get("username") if user.username == None: abort( 400, title="User Not Saved", message= "A username is required. Please enter a username and try saving again." ) username_exists = User.query.filter_by( username=user.username).first() if username_exists: abort( 409, title="User Not Saved", message= "Sorry, this username already exists and usernames must be unique in ComPAIR. Please enter another username and try saving again." ) student_number_exists = User.query.filter_by( student_number=user.student_number).first() # if student_number is not left blank and it exists -> 409 error if user.student_number is not None and student_number_exists: abort( 409, title="User Not Saved", message= "Sorry, this student number already exists and student numbers must be unique in ComPAIR. Please enter another number and try saving again." ) # handle lti_create_user_link setup for third party logins if sess.get('lti_create_user_link') and sess.get('LTI'): lti_user = LTIUser.query.get_or_404(sess['lti_user']) lti_user.compair_user = user user.system_role = lti_user.system_role lti_user.update_user_profile() if sess.get('lti_context') and sess.get('lti_user_resource_link'): lti_context = LTIContext.query.get_or_404(sess['lti_context']) lti_user_resource_link = LTIUserResourceLink.query.get_or_404( sess['lti_user_resource_link']) if lti_context.is_linked_to_course(): # create new enrollment new_user_course = UserCourse( user=user, course_id=lti_context.compair_course_id, course_role=lti_user_resource_link.course_role) db.session.add(new_user_course) else: system_role = params.get("system_role") check_valid_system_role(system_role) user.system_role = SystemRole(system_role) require( CREATE, user, title="User Not Saved", message="Sorry, your role does not allow you to save users.") # only students can have student numbers if user.system_role != SystemRole.student: user.student_number = None try: db.session.add(user) db.session.commit() if current_user.is_authenticated: on_user_create.send(self, event_name=on_user_create.name, user=current_user, data=marshal(user, dataformat.get_full_user())) else: on_user_create.send(self, event_name=on_user_create.name, data=marshal(user, dataformat.get_full_user())) except exc.IntegrityError: db.session.rollback() current_app.logger.error("Failed to add new user. Duplicate.") abort( 409, title="User Not Saved", message= "Sorry, this ID already exists and IDs must be unique in ComPAIR. Please try addding another user." ) # handle lti_create_user_link teardown for third party logins if sess.get('lti_create_user_link'): authenticate(user, login_method='LTI') sess.pop('lti_create_user_link') return marshal_user_data(user)