Example #1
0
    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
Example #2
0
    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]
        }
Example #3
0
    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()
Example #4
0
    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()
Example #5
0
    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())
Example #6
0
    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}
Example #7
0
    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}
Example #8
0
    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}
Example #9
0
    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
        }
Example #10
0
    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
Example #11
0
    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))
            }
Example #12
0
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))
    }
Example #13
0
    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
        }
Example #14
0
    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}
Example #15
0
    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())
Example #16
0
    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())
Example #17
0
    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())
Example #18
0
    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())
Example #19
0
    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}
Example #20
0
    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)