Beispiel #1
0
    def post(self, user_uuid):
        user = User.get_by_uuid_or_404(user_uuid)
        # anyone who passes checking below should be an instructor or admin
        require(
            EDIT,
            user,
            title="Notifications Not Updated",
            message=
            "Sorry, your system role does not allow you to update notification settings for this user."
        )

        if not user.email:
            abort(
                400,
                title="Notifications Not Updated",
                message=
                "Sorry, you cannot update notification settings since this user does not have an email address in ComPAIR."
            )

        params = update_notification_settings_parser.parse_args()

        email_notification_method = params.get("email_notification_method")
        check_valid_email_notification_method(email_notification_method)
        user.email_notification_method = EmailNotificationMethod(
            email_notification_method)

        db.session.commit()
        on_user_notifications_update.send(
            self,
            event_name=on_user_notifications_update.name,
            user=current_user)
        return marshal(user,
                       dataformat.get_user(is_user_access_restricted(user)))
Beispiel #2
0
    def delete(self, course_uuid, user_uuid):
        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(
                course_id=course.id,
                user_id=user.id
            ) \
            .first_or_404()
        require(
            EDIT,
            user_course,
            title="Group Not Saved",
            message=
            "Sorry, your role in this course does not allow you to save groups."
        )

        user_course.group_name = None
        db.session.commit()

        on_course_group_user_delete.send(
            current_app._get_current_object(),
            event_name=on_course_group_user_delete.name,
            user=current_user,
            course_id=course.id,
            data={'user_id': user.id})

        return {'user_id': user.uuid, 'course_id': course.uuid}
Beispiel #3
0
    def post(self, user_uuid):
        user = User.get_by_uuid_or_404(user_uuid)
        # anyone who passes checking below should be an instructor or admin
        require(EDIT, user,
            title="Password Not Saved",
            message="Sorry, your system role does not allow you to update passwords for this user.")

        if not user.uses_compair_login:
            abort(400, title="Password Not Saved",
                message="Sorry, you cannot update the password since this user does not use the ComPAIR account login method.")

        params = update_password_parser.parse_args()
        oldpassword = params.get('oldpassword')

        if current_user.id == user.id and not oldpassword:
            abort(400, title="Password Not Saved", message="Sorry, the old password is required. Please enter the old password and try saving again.")
        elif current_user.id == user.id and not user.verify_password(oldpassword):
            abort(400, title="Password Not Saved", message="Sorry, the old password is not correct. Please double-check the old password and try saving again.")
        elif len(params.get('newpassword')) < 4:
            abort(400, title="Password Not Saved", message="The new password must be at least 4 characters long.")

        user.password = params.get('newpassword')
        db.session.commit()
        on_user_password_update.send(
            self,
            event_name=on_user_password_update.name,
            user=current_user)

        return marshal_user_data(user)
Beispiel #4
0
    def delete(self, course_uuid, user_uuid):
        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_or_404()
        require(EDIT, user_course)

        user_course.course_role = CourseRole.dropped
        result = {
            'user_id': user.uuid,
            'fullname': user.fullname,
            'course_role': CourseRole.dropped.value
        }

        db.session.add(user_course)

        on_classlist_unenrol.send(
            self,
            event_name=on_classlist_unenrol.name,
            user=current_user,
            course_id=course.id,
            data={'user_id': user.id})

        db.session.commit()
        return result
Beispiel #5
0
    def delete(self, user_uuid, lti_user_uuid):
        """
        unlink lti user from compair user
        """

        user = User.get_by_uuid_or_404(user_uuid)
        lti_user = LTIUser.get_by_uuid_or_404(lti_user_uuid)

        require(
            MANAGE,
            User,
            title="User's LTI Account Links Unavailable",
            message=
            "Sorry, your system role does not allow you to remove LTI account links for this user."
        )

        lti_user.compair_user_id = None
        db.session.commit()

        on_user_lti_user_unlink.send(self,
                                     event_name=on_user_lti_user_unlink.name,
                                     user=current_user,
                                     data={
                                         'user_id': user.id,
                                         'lti_user_id': lti_user.id
                                     })

        return {'success': True}
Beispiel #6
0
    def get(self, user_uuid):

        user = User.get_by_uuid_or_404(user_uuid)

        require(
            MANAGE,
            User,
            title="User's LTI Account Links Unavailable",
            message=
            "Sorry, your system role does not allow you to view LTI account links for this user."
        )

        lti_users = user.lti_user_links \
                        .order_by(
                            LTIUser.lti_consumer_id,
                            LTIUser.user_id
                            ) \
                        .all()

        on_user_lti_users_get.send(self,
                                   event_name=on_user_lti_users_get.name,
                                   user=current_user,
                                   data={'user_id': user.id})

        return {"objects": marshal(lti_users, dataformat.get_lti_user())}
Beispiel #7
0
    def delete(self, user_uuid, third_party_user_uuid):

        user = User.get_by_uuid_or_404(user_uuid)
        third_party_user = ThirdPartyUser.get_by_uuid_or_404(
            third_party_user_uuid)

        require(
            MANAGE,
            User,
            title="User's Third Party Logins Unavailable",
            message=
            "Sorry, your system role does not allow you to delete third party connections for this user."
        )

        on_user_third_party_user_delete.send(
            self,
            event_name=on_user_third_party_user_delete.name,
            user=current_user,
            data={
                'user_id': user.id,
                'third_party_type': third_party_user.third_party_type,
                'unique_identifier': third_party_user.unique_identifier
            })

        # TODO: consider adding soft delete to thrid_party_user in the future
        ThirdPartyUser.query.filter(
            ThirdPartyUser.uuid == third_party_user_uuid).delete()
        db.session.commit()

        return {'success': True}
Beispiel #8
0
    def get(self, user_uuid):

        user = User.get_by_uuid_or_404(user_uuid)

        require(
            MANAGE,
            User,
            title="User's Third Party Logins Unavailable",
            message=
            "Sorry, your system role does not allow you to view third party logins for this user."
        )

        third_party_users = ThirdPartyUser.query \
                                .filter(ThirdPartyUser.user_id == user.id) \
                                .order_by(
                                    ThirdPartyUser.third_party_type,
                                    ThirdPartyUser.unique_identifier
                                    ) \
                                .all()

        on_user_third_party_users_get.send(
            self,
            event_name=on_user_third_party_users_get.name,
            user=current_user,
            data={'user_id': user.id})

        return {
            "objects":
            marshal(third_party_users, dataformat.get_third_party_user())
        }
Beispiel #9
0
class TestUsersModel(ComPAIRTestCase):
    user = User()

    def setUp(self):
        self.user.firstname = "John"
        self.user.lastname = "Smith"

    def test_fullname(self):
        self.assertEqual(self.user.fullname, "John Smith")

    def test_fullname_sortable(self):
        self.assertEqual(self.user.fullname_sortable, "Smith, John")

    def test_avatar(self):
        # role != student
        self.user.email = "*****@*****.**"
        self.assertEqual(self.user.avatar, '0bc83cb571cd1c50ba6f3e8a78ef1346')

        self.user.email = " [email protected] "
        self.assertEqual(self.user.avatar, '0bc83cb571cd1c50ba6f3e8a78ef1346',
                         'Email with leading and trailing whilespace')
        self.user.email = "*****@*****.**"
        self.assertEqual(self.user.avatar, '0bc83cb571cd1c50ba6f3e8a78ef1346',
                         'Email with upper case letters')

        self.user.system_role = SystemRole.student
        self.user.uuid = str(base64.urlsafe_b64encode(
            uuid.uuid4().bytes)).replace('=', '')
        self.assertNotEqual(self.user.avatar,
                            '0bc83cb571cd1c50ba6f3e8a78ef1346',
                            'Student based on uuid')

    def test_set_password(self):
        self.user.password = '******'
        self.assertTrue(self.user.verify_password('123456'))
Beispiel #10
0
    def post(self, user_uuid):
        user = User.get_by_uuid_or_404(user_uuid)
        # anyone who passes checking below should be an admin or current user
        if not allow(MANAGE, user) and not user.id == current_user.id and not \
                (allow(EDIT, user) and current_app.config.get('EXPOSE_EMAIL_TO_INSTRUCTOR', False)):
            abort(
                403,
                title="Notifications Not Updated",
                message=
                "Sorry, your system role does not allow you to update notification settings for this user."
            )

        if not user.email:
            abort(
                400,
                title="Notifications Not Updated",
                message=
                "Sorry, you cannot update notification settings since this user does not have an email address in ComPAIR."
            )

        params = update_notification_settings_parser.parse_args()

        email_notification_method = params.get("email_notification_method")
        check_valid_email_notification_method(email_notification_method)
        user.email_notification_method = EmailNotificationMethod(
            email_notification_method)

        db.session.commit()
        on_user_notifications_update.send(
            self,
            event_name=on_user_notifications_update.name,
            user=current_user)

        return marshal_user_data(user)
Beispiel #11
0
    def post(self, course_uuid, user_uuid, group_name):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        user = User.get_by_uuid_or_404(user_uuid)

        user_course = UserCourse.query.filter(
            and_(
                UserCourse.course_id == course.id,
                UserCourse.user_id == user.id,
                UserCourse.course_role != CourseRole.dropped,
            )
        ).first_or_404()

        require(EDIT, user_course)

        user_course.group_name = group_name
        db.session.commit()

        on_course_group_user_create.send(
            current_app._get_current_object(),
            event_name=on_course_group_user_create.name,
            user=current_user,
            course_id=course.id,
            data={"user_id": user.id},
        )

        return {"group_name": group_name}
Beispiel #12
0
    def post(self, user_uuid):
        if not current_app.config.get('IMPERSONATION_ENABLED', False):
            abort(404,
                  title="Impersonation not supported",
                  message="Impersonation function not enabled")

        original_user = User.query.get(current_user.id)
        impersonate_as_uuid = user_uuid
        impersonate_as_user = User.get_by_uuid_or_404(impersonate_as_uuid)
        if not impersonation.can_impersonate(impersonate_as_user.get_id()):
            abort(400,
                  title="Impersonation Failed",
                  message="Cannot perform impersonation")

        impersonation_success = impersonation.start_impersonation(
            impersonate_as_user.get_id())

        # if successful, from this point on, current_user is no longer the original user

        if not impersonation_success:
            abort(400,
                  title="Impersonation Failed",
                  message="Cannot perform impersonation")

        on_impersonation_started.send(
            self,
            event_name=on_impersonation_started.name,
            user=original_user,
            data={'impersonate_as': impersonate_as_user.id})
        # user is impersonating. treat as restrict_user when calling dataformat
        return {
            'impersonate_as':
            marshal(impersonate_as_user, dataformat.get_user(True))
        }
Beispiel #13
0
    def post(self, user_uuid):
        user = User.get_by_uuid_or_404(user_uuid)
        # anyone who passes checking below should be an instructor or admin
        require(EDIT, user,
            title="Password Not Saved",
            message="Sorry, your system role does not allow you to update passwords for this user.")

        if not user.uses_compair_login:
            abort(400, title="Password Not Saved",
                message="Sorry, you cannot update the password since this user does not use the ComPAIR account login method.")

        params = update_password_parser.parse_args()
        oldpassword = params.get('oldpassword')

        if current_user.id == user.id and not oldpassword:
            abort(400, title="Password Not Saved", message="Sorry, the old password is required. Please enter the old password and try saving again.")
        elif current_user.id == user.id and not user.verify_password(oldpassword):
            abort(400, title="Password Not Saved", message="Sorry, the old password is not correct. Please double-check the old password and try saving again.")
        elif len(params.get('newpassword')) < 4:
            abort(400, title="Password Not Saved", message="The new password must be at least 4 characters long.")

        user.password = params.get('newpassword')
        db.session.commit()
        on_user_password_update.send(
            self,
            event_name=on_user_password_update.name,
            user=current_user)

        return marshal_user_data(user)
Beispiel #14
0
    def post(self, course_uuid, user_uuid, group_name):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        user = User.get_by_uuid_or_404(user_uuid)

        user_course = UserCourse.query \
            .filter(and_(
                UserCourse.course_id == course.id,
                UserCourse.user_id == user.id,
                UserCourse.course_role != CourseRole.dropped
            )) \
            .first_or_404()

        require(
            EDIT,
            user_course,
            title="Group Not Saved",
            message=
            "Sorry, your role in this course does not allow you to save groups."
        )

        user_course.group_name = group_name
        db.session.commit()

        on_course_group_user_create.send(
            current_app._get_current_object(),
            event_name=on_course_group_user_create.name,
            user=current_user,
            course_id=course.id,
            data={'user_id': user.id})

        return {'group_name': group_name}
Beispiel #15
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
Beispiel #16
0
    def get(self, user_uuid):
        user = User.get_by_uuid_or_404(user_uuid)

        on_user_get.send(self,
                         event_name=on_user_get.name,
                         user=current_user,
                         data={'id': user.id})
        return marshal_user_data(user)
Beispiel #17
0
    def get(self, user_uuid):
        user = User.get_by_uuid_or_404(user_uuid)

        on_user_get.send(self,
                         event_name=on_user_get.name,
                         user=current_user,
                         data={'id': user.id})
        return marshal(user,
                       dataformat.get_user(is_user_access_restricted(user)))
Beispiel #18
0
    def get(self, user_uuid):
        user = User.get_by_uuid_or_404(user_uuid)

        on_user_get.send(
            self,
            event_name=on_user_get.name,
            user=current_user,
            data={'id': user.id}
        )
        return marshal_user_data(user)
Beispiel #19
0
    def get(self, user_uuid):
        user = User.get_by_uuid_or_404(user_uuid)

        on_user_get.send(
            self,
            event_name=on_user_get.name,
            user=current_user,
            data={'id': user.id}
        )
        return marshal(user, dataformat.get_user(is_user_access_restricted(user)))
Beispiel #20
0
    def get(self, user_uuid):
        user = User.get_by_uuid_or_404(user_uuid)
        available = allow(EDIT, user)

        on_user_edit_button_get.send(
            self,
            event_name=on_user_edit_button_get.name,
            user=current_user,
            data={'user_id': user.id, 'available': available})

        return {'available': available}
Beispiel #21
0
    def get(self, user_uuid):
        user = User.get_by_uuid_or_404(user_uuid)
        available = can(EDIT, user)

        on_user_edit_button_get.send(
            self,
            event_name=on_user_edit_button_get.name,
            user=current_user,
            data={'user_id': user.id, 'available': available})

        return {'available': available}
Beispiel #22
0
    def post(self, course_uuid, assignment_uuid, answer_uuid):
        """
        Create comment for an answer
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        answer = Answer.get_active_by_uuid_or_404(answer_uuid)
        require(CREATE, AnswerComment(course_id=course.id))

        answer_comment = AnswerComment(answer_id=answer.id)

        params = new_answer_comment_parser.parse_args()
        answer_comment.draft = params.get("draft")
        answer_comment.content = params.get("content")
        # require content not empty if not a draft
        if not answer_comment.content and not answer_comment.draft:
            return {"error": "The comment content is empty!"}, 400

        if params.get("user_id") and current_user.system_role == SystemRole.sys_admin:
            user = User.get_by_uuid_or_404(params.get("user_id"))
            answer_comment.user_id = user.id
        else:
            answer_comment.user_id = current_user.id

        comment_types = [
            AnswerCommentType.public.value,
            AnswerCommentType.private.value,
            AnswerCommentType.evaluation.value,
            AnswerCommentType.self_evaluation.value,
        ]

        comment_type = params.get("comment_type")
        if comment_type not in comment_types:
            abort(400)
        answer_comment.comment_type = AnswerCommentType(comment_type)

        db.session.add(answer_comment)
        db.session.commit()

        # update course & assignment grade for user if self-evaluation is completed
        if not answer_comment.draft and answer_comment.comment_type == AnswerCommentType.self_evaluation:
            assignment.calculate_grade(answer_comment.user)
            course.calculate_grade(answer_comment.user)

        on_answer_comment_create.send(
            self,
            event_name=on_answer_comment_create.name,
            user=current_user,
            course_id=course.id,
            answer_comment=answer_comment,
            data=marshal(answer_comment, dataformat.get_answer_comment(False)),
        )

        return marshal(answer_comment, dataformat.get_answer_comment())
Beispiel #23
0
    def delete(self, course_uuid, user_uuid):
        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(course_id=course.id, user_id=user.id).first_or_404()
        require(EDIT, user_course)

        user_course.group_name = None
        db.session.commit()

        on_course_group_user_delete.send(
            current_app._get_current_object(),
            event_name=on_course_group_user_delete.name,
            user=current_user,
            course_id=course.id,
            data={"user_id": user.id},
        )

        return {"user_id": user.uuid, "course_id": course.uuid}
Beispiel #24
0
    def delete(self, course_uuid, user_uuid):
        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_or_404()
        require(
            EDIT,
            user_course,
            title="Enrollment Not Updated",
            message=
            "Sorry, your role in this course does not allow you to update enrollment."
        )

        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.course_role = CourseRole.dropped
        db.session.add(user_course)

        on_classlist_unenrol.send(self,
                                  event_name=on_classlist_unenrol.name,
                                  user=current_user,
                                  course_id=course.id,
                                  data={'user_id': user.id})

        db.session.commit()

        return {
            'user_id': user.uuid,
            'fullname': user.fullname,
            'fullname_sortable': user.fullname_sortable,
            'course_role': CourseRole.dropped.value
        }
Beispiel #25
0
    def delete(self, user_uuid, third_party_user_uuid):

        user = User.get_by_uuid_or_404(user_uuid)
        third_party_user = ThirdPartyUser.get_by_uuid_or_404(third_party_user_uuid)

        require(MANAGE, User,
            title="User's Third Party Logins Unavailable",
            message="Sorry, your system role does not allow you to delete third party connections for this user.")

        on_user_third_party_user_delete.send(
            self,
            event_name=on_user_third_party_user_delete.name,
            user=current_user,
            data={'user_id': user.id, 'third_party_type': third_party_user.third_party_type, 'unique_identifier': third_party_user.unique_identifier})

        # TODO: consider adding soft delete to thrid_party_user in the future
        ThirdPartyUser.query.filter(ThirdPartyUser.uuid == third_party_user_uuid).delete()
        db.session.commit()

        return { 'success': True }
Beispiel #26
0
    def post(self, course_uuid, group_uuid, user_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        group = Group.get_active_by_uuid_or_404(group_uuid)
        user = User.get_by_uuid_or_404(user_uuid)

        user_course = UserCourse.query \
            .filter(and_(
                UserCourse.course_id == course.id,
                UserCourse.user_id == user.id,
                UserCourse.course_role != CourseRole.dropped
            )) \
            .first_or_404()

        require(
            EDIT,
            user_course,
            title="Group Not Saved",
            message=
            "Sorry, your role in this course does not allow you to save groups."
        )

        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. This user is already assigned to a different group."
            )

        user_course.group_id = group.id
        db.session.commit()

        on_group_user_create.send(current_app._get_current_object(),
                                  event_name=on_group_user_create.name,
                                  user=current_user,
                                  course_id=course.id,
                                  data={'user_id': user.id})

        return marshal(group, dataformat.get_group())
Beispiel #27
0
    def delete(self, user_uuid, lti_user_uuid):
        """
        unlink lti user from compair user
        """

        user = User.get_by_uuid_or_404(user_uuid)
        lti_user = LTIUser.get_by_uuid_or_404(lti_user_uuid)

        require(MANAGE, User,
            title="User's LTI Account Links Unavailable",
            message="Sorry, your system role does not allow you to remove LTI account links for this user.")

        lti_user.compair_user_id = None
        db.session.commit()

        on_user_lti_user_unlink.send(
            self,
            event_name=on_user_lti_user_unlink.name,
            user=current_user,
            data={'user_id': user.id, 'lti_user_id': lti_user.id})

        return { 'success': True }
Beispiel #28
0
    def get(self, user_uuid):

        user = User.get_by_uuid_or_404(user_uuid)

        require(MANAGE, User,
            title="User's LTI Account Links Unavailable",
            message="Sorry, your system role does not allow you to view LTI account links for this user.")

        lti_users = user.lti_user_links \
                        .order_by(
                            LTIUser.lti_consumer_id,
                            LTIUser.user_id
                            ) \
                        .all()

        on_user_lti_users_get.send(
            self,
            event_name=on_user_lti_users_get.name,
            user=current_user,
            data={'user_id': user.id})

        return {"objects": marshal(lti_users, dataformat.get_lti_user())}
Beispiel #29
0
    def post(self, user_uuid):
        user = User.get_by_uuid_or_404(user_uuid)
        # anyone who passes checking below should be an instructor or admin
        require(EDIT, user)

        if user.uses_compair_login:
            params = update_password_parser.parse_args()
            oldpassword = params.get('oldpassword')
            # if it is not current user changing own password, it must be an instructor or admin
            # because of above check
            if current_user.id != user.id or (oldpassword and user.verify_password(oldpassword)):
                user.password = params.get('newpassword')
                db.session.commit()
                on_user_password_update.send(
                    self,
                    event_name=on_user_password_update.name,
                    user=current_user)
                return marshal(user, dataformat.get_user(False))
            else:
                return {"error": "The old password is incorrect or you do not have permission to change password."}, 403
        else:
            return {"error": "Cannot update password. User does not use ComPAIR account login authentication method."}, 400
Beispiel #30
0
    def get(self, user_uuid):

        user = User.get_by_uuid_or_404(user_uuid)

        require(MANAGE, User,
            title="User's Third Party Logins Unavailable",
            message="Sorry, your system role does not allow you to view third party logins for this user.")

        third_party_users = ThirdPartyUser.query \
                                .filter(ThirdPartyUser.user_id == user.id) \
                                .order_by(
                                    ThirdPartyUser.third_party_type,
                                    ThirdPartyUser.unique_identifier
                                    ) \
                                .all()

        on_user_third_party_users_get.send(
            self,
            event_name=on_user_third_party_users_get.name,
            user=current_user,
            data={'user_id': user.id})

        return {"objects": marshal(third_party_users, dataformat.get_third_party_user())}
Beispiel #31
0
    def delete(self, course_uuid, user_uuid):
        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(
                course_id=course.id,
                user_id=user.id
            ) \
            .first_or_404()

        require(
            EDIT,
            user_course,
            title="Group Not Saved",
            message=
            "Sorry, your role in this course does not allow you to save groups."
        )

        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."
            )

        user_course.group_id = None
        db.session.commit()

        on_group_user_delete.send(current_app._get_current_object(),
                                  event_name=on_group_user_delete.name,
                                  user=current_user,
                                  course_id=course.id,
                                  data={'user_id': user.id})

        return {'success': True}
Beispiel #32
0
    def post(self, user_uuid):
        user = User.get_by_uuid_or_404(user_uuid)
        # anyone who passes checking below should be an admin or current user
        if not allow(MANAGE, user) and not user.id == current_user.id and not \
                (allow(EDIT, user) and current_app.config.get('EXPOSE_EMAIL_TO_INSTRUCTOR', False)):
            abort(403, title="Notifications Not Updated", message="Sorry, your system role does not allow you to update notification settings for this user.")

        if not user.email:
            abort(400, title="Notifications Not Updated",
                message="Sorry, you cannot update notification settings since this user does not have an email address in ComPAIR.")

        params = update_notification_settings_parser.parse_args()

        email_notification_method = params.get("email_notification_method")
        check_valid_email_notification_method(email_notification_method)
        user.email_notification_method = EmailNotificationMethod(email_notification_method)

        db.session.commit()
        on_user_notifications_update.send(
            self,
            event_name=on_user_notifications_update.name,
            user=current_user)

        return marshal_user_data(user)
Beispiel #33
0
    def delete(self, course_uuid, user_uuid):
        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_or_404()
        require(EDIT, user_course,
            title="Enrollment Not Updated",
            message="Sorry, your role in this course does not allow you to update enrollment.")

        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.course_role = CourseRole.dropped
        db.session.add(user_course)

        on_classlist_unenrol.send(
            self,
            event_name=on_classlist_unenrol.name,
            user=current_user,
            course_id=course.id,
            data={'user_id': user.id})

        db.session.commit()

        return {
            'user_id': user.uuid,
            'fullname': user.fullname,
            'fullname_sortable': user.fullname_sortable,
            'course_role': CourseRole.dropped.value
        }
Beispiel #34
0
    def post(self, user_uuid):
        if not current_app.config.get('IMPERSONATION_ENABLED', False):
            abort(404, title="Impersonation not supported", message="Impersonation function not enabled")

        original_user = User.query.get(current_user.id)
        impersonate_as_uuid = user_uuid
        impersonate_as_user = User.get_by_uuid_or_404(impersonate_as_uuid)
        if not impersonation.can_impersonate(impersonate_as_user.get_id()):
            abort(400, title="Impersonation Failed", message="Cannot perform impersonation")

        impersonation_success = impersonation.start_impersonation(impersonate_as_user.get_id())

        # if successful, from this point on, current_user is no longer the original user

        if not impersonation_success:
            abort(400, title="Impersonation Failed", message="Cannot perform impersonation")

        on_impersonation_started.send(
            self,
            event_name=on_impersonation_started.name,
            user=original_user,
            data={ 'impersonate_as': impersonate_as_user.id })
        # user is impersonating. treat as restrict_user when calling dataformat
        return { 'impersonate_as' : marshal(impersonate_as_user, dataformat.get_user(True)) }
Beispiel #35
0
    def post(self, user_uuid):
        user = User.get_by_uuid_or_404(user_uuid)

        if is_user_access_restricted(user):
            abort(403, title="User Not Saved", message="Sorry, your role does not allow you to save this user.")

        params = existing_user_parser.parse_args()

        # make sure the user id in the url and the id matches
        if params['id'] != user_uuid:
            abort(400, title="User Not Saved",
                message="The user's ID does not match the URL, which is required in order to save the user.")

        # only update username if user uses compair login method
        if user.uses_compair_login:
            username = params.get("username")
            if 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=username).first()
            if username_exists and username_exists.id != user.id:
                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.")

            user.username = username
        elif allow(MANAGE, user):
            #admins can optionally set username for users without a username
            username = params.get("username")
            if username:
                username_exists = User.query.filter_by(username=username).first()
                if username_exists and username_exists.id != user.id:
                    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.")
            user.username = username
        else:
            user.username = None

        if allow(MANAGE, user):
            system_role = params.get("system_role", user.system_role.value)
            check_valid_system_role(system_role)
            user.system_role = SystemRole(system_role)

        if allow(MANAGE, user) or user.id == current_user.id or current_app.config.get('EXPOSE_EMAIL_TO_INSTRUCTOR', False):
            if current_user.system_role != SystemRole.student or current_app.config.get('ALLOW_STUDENT_CHANGE_EMAIL'):
                user.email = params.get("email", user.email)

            email_notification_method = params.get("email_notification_method")
            check_valid_email_notification_method(email_notification_method)
            user.email_notification_method = EmailNotificationMethod(email_notification_method)

        elif params.get("email") or params.get("email_notification_method"):
            abort(400, title="User Not Saved", message="your role does not allow you to change email settings for this user.")

        if current_user.system_role != SystemRole.student or current_app.config.get('ALLOW_STUDENT_CHANGE_STUDENT_NUMBER'):
            # only students should have student numbers
            if user.system_role == SystemRole.student:
                student_number = params.get("student_number", user.student_number)
                student_number_exists = User.query.filter_by(student_number=student_number).first()
                if student_number is not None and student_number_exists and student_number_exists.id != user.id:
                    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.")
                else:
                    user.student_number = student_number
            else:
                user.student_number = None

        if current_user.system_role != SystemRole.student or current_app.config.get('ALLOW_STUDENT_CHANGE_NAME'):
            user.firstname = params.get("firstname", user.firstname)
            user.lastname = params.get("lastname", user.lastname)

        if current_user.system_role != SystemRole.student or current_app.config.get('ALLOW_STUDENT_CHANGE_DISPLAY_NAME'):
            user.displayname = params.get("displayname", user.displayname)

        model_changes = get_model_changes(user)

        try:
            db.session.commit()
            on_user_modified.send(
                self,
                event_name=on_user_modified.name,
                user=current_user,
                data={'id': user.id, 'changes': model_changes})
        except exc.IntegrityError:
            db.session.rollback()
            abort(409, title="User Not Saved", message="Sorry, this ID already exists and IDs must be unique in ComPAIR. Please try addding another user.")

        return marshal_user_data(user)
Beispiel #36
0
    def post(self, course_uuid, assignment_uuid, answer_uuid):
        """
        Create comment for an answer
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        answer = Answer.get_active_by_uuid_or_404(answer_uuid)
        require(CREATE, AnswerComment(course_id=course.id),
            title="Feedback Not Saved",
            message="Sorry, your role in this course does not allow you to save feedback for this answer.")

        restrict_user = not allow(MANAGE, assignment)

        restrict_user = not allow(MANAGE, assignment)

        answer_comment = AnswerComment(answer_id=answer.id)

        params = new_answer_comment_parser.parse_args()
        answer_comment.draft = params.get('draft')
        answer_comment.content = params.get("content")
        # require content not empty if not a draft
        if not answer_comment.content and not answer_comment.draft:
            abort(400, title="Feedback Not Saved", message="Please provide content in the text editor and try saving again.")

        if params.get('user_id') and current_user.system_role == SystemRole.sys_admin:
            user = User.get_by_uuid_or_404(params.get('user_id'))
            answer_comment.user_id = user.id
        else:
            answer_comment.user_id = current_user.id

        comment_types = [
            AnswerCommentType.public.value,
            AnswerCommentType.private.value,
            AnswerCommentType.evaluation.value,
            AnswerCommentType.self_evaluation.value
        ]

        comment_type = params.get("comment_type")
        if comment_type not in comment_types:
            abort(400, title="Feedback Not Saved", message="This feedback type is not recognized. Please contact support for assistance.")
        answer_comment.comment_type = AnswerCommentType(comment_type)

        if answer_comment.comment_type == AnswerCommentType.self_evaluation and not assignment.self_eval_grace and not allow(MANAGE, assignment):
            abort(403, title="Self-Evaluation Not Saved", message="Sorry, the self-evaluation deadline has passed and therefore cannot be submitted.")

        answer_comment.update_attempt(
            params.get('attempt_uuid'),
            params.get('attempt_started', None),
            params.get('attempt_ended', None)
        )

        db.session.add(answer_comment)
        db.session.commit()

        # update course & assignment grade for user if self-evaluation is completed
        if not answer_comment.draft and answer_comment.comment_type == AnswerCommentType.self_evaluation:
            assignment.calculate_grade(answer_comment.user)
            course.calculate_grade(answer_comment.user)

        on_answer_comment_create.send(
            self,
            event_name=on_answer_comment_create.name,
            user=current_user,
            course_id=course.id,
            answer_comment=answer_comment,
            evaluation_number=params.get("evaluation_number"),
            data=marshal(answer_comment, dataformat.get_answer_comment(restrict_user)))

        return marshal(answer_comment, dataformat.get_answer_comment(restrict_user))
Beispiel #37
0
    def get(self, course_uuid, assignment_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        require(READ, Comparison(course_id=course.id),
            title="Comparisons Unavailable",
            message="Sorry, your role in this course does not allow you to view all comparisons for this assignment.")

        restrict_user = is_user_access_restricted(current_user)
        params = assignment_users_comparison_list_parser.parse_args()

        # only get users who have at least made one comparison
        # each paginated item is a user (with a set of comparisons and self-evaluations)
        user_query = User.query \
            .join(UserCourse, and_(
                User.id == UserCourse.user_id,
                UserCourse.course_id == course.id
            )) \
            .join(Comparison, and_(
                Comparison.user_id == User.id,
                Comparison.assignment_id == assignment.id
            )) \
            .filter(and_(
                UserCourse.course_role != CourseRole.dropped,
                Comparison.completed == True
            )) \
            .group_by(User) \
            .order_by(User.lastname, User.firstname)

        self_evaluation_total = AnswerComment.query \
            .join("answer") \
            .with_entities(
                func.count(Answer.assignment_id).label('self_evaluation_count')
            ) \
            .filter(and_(
                AnswerComment.active == True,
                AnswerComment.comment_type == AnswerCommentType.self_evaluation,
                AnswerComment.draft == False,
                Answer.active == True,
                Answer.practice == False,
                Answer.draft == False,
                Answer.assignment_id == assignment.id
            ))

        comparison_total = Comparison.query \
            .with_entities(
                func.count(Comparison.assignment_id).label('comparison_count')
            ) \
            .filter(and_(
                Comparison.completed == True,
                Comparison.assignment_id == assignment.id
            ))

        if params['author']:
            user = User.get_by_uuid_or_404(params['author'])

            user_query = user_query.filter(User.id == user.id)
            self_evaluation_total = self_evaluation_total.filter(AnswerComment.user_id == user.id)
            comparison_total = comparison_total.filter(Comparison.user_id == user.id)
        elif params['group']:
            group = Group.get_active_by_uuid_or_404(params['group'])
            user_query = user_query.filter(UserCourse.group_id == group.id)

            self_evaluation_total = self_evaluation_total \
                .join(UserCourse, and_(
                    AnswerComment.user_id == UserCourse.user_id,
                    UserCourse.course_id == course.id
                )) \
                .filter(UserCourse.group_id == group.id)

            comparison_total = comparison_total \
                .join(UserCourse, and_(
                    Comparison.user_id == UserCourse.user_id,
                    UserCourse.course_id == course.id
                )) \
                .filter(UserCourse.group_id == group.id)

        page = user_query.paginate(params['page'], params['perPage'])
        self_evaluation_total = self_evaluation_total.scalar()
        comparison_total = comparison_total.scalar()

        comparison_sets = []
        if page.total:
            user_ids = [user.id for user in page.items]

            # get all comparisons that group of users created
            comparisons = Comparison.query \
                .filter(and_(
                    Comparison.completed == True,
                    Comparison.assignment_id == assignment.id,
                    Comparison.user_id.in_(user_ids)
                )) \
                .all()

            # retrieve the answer comments
            user_comparison_answers = {}
            for comparison in comparisons:
                user_answers = user_comparison_answers.setdefault(comparison.user_id, set())
                user_answers.add(comparison.answer1_id)
                user_answers.add(comparison.answer2_id)

            conditions = []
            for user_id, answer_set in user_comparison_answers.items():
                conditions.append(and_(
                        AnswerComment.comment_type == AnswerCommentType.evaluation,
                        AnswerComment.user_id == user_id,
                        AnswerComment.answer_id.in_(list(answer_set)),
                        AnswerComment.assignment_id == assignment.id
                ))
                conditions.append(and_(
                    AnswerComment.comment_type == AnswerCommentType.self_evaluation,
                    AnswerComment.user_id == user_id,
                    AnswerComment.assignment_id == assignment.id
                ))

            answer_comments = AnswerComment.query \
                .filter(or_(*conditions)) \
                .filter_by(draft=False) \
                .all()

            # add comparison answer evaluation comments to comparison object
            for comparison in comparisons:
                comparison.answer1_feedback = [comment for comment in answer_comments if
                    comment.user_id == comparison.user_id and
                    comment.answer_id == comparison.answer1_id and
                    comment.comment_type == AnswerCommentType.evaluation
                ]

                comparison.answer2_feedback = [comment for comment in answer_comments if
                    comment.user_id == comparison.user_id and
                    comment.answer_id == comparison.answer2_id and
                    comment.comment_type == AnswerCommentType.evaluation
                ]

            for user in page.items:
                comparison_sets.append({
                    'user': user,
                    'comparisons': [comparison for comparison in comparisons if
                        comparison.user_id == user.id
                    ],
                    'self_evaluations': [comment for comment in answer_comments if
                        comment.user_id == user.id and
                        comment.comment_type == AnswerCommentType.self_evaluation
                    ]
                })


        on_assignment_users_comparisons_get.send(
            self,
            event_name=on_assignment_users_comparisons_get.name,
            user=current_user,
            course_id=course.id,
            data={'assignment_id': assignment.id}
        )

        return {"objects": marshal(comparison_sets, dataformat.get_comparison_set(restrict_user, with_user=True)),
                "comparison_total": comparison_total, "self_evaluation_total": self_evaluation_total,
                "page": page.page, "pages": page.pages, "total": page.total, "per_page": page.per_page}
Beispiel #38
0
    def post(self, course_uuid, assignment_uuid, answer_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)

        if not assignment.answer_grace and not allow(MANAGE, assignment):
            return {'error': answer_deadline_message}, 403

        answer = Answer.get_active_by_uuid_or_404(answer_uuid)
        require(EDIT, answer)
        restrict_user = not allow(MANAGE, assignment)

        params = existing_answer_parser.parse_args()
        # make sure the answer id in the url and the id matches
        if params['id'] != answer_uuid:
            return {"error": "Answer id does not match the URL."}, 400

        # modify answer according to new values, preserve original values if values not passed
        answer.content = params.get("content")

        user_uuid = params.get("user_id")
        # we allow instructor and TA to submit multiple answers for other users in the class
        if user_uuid and user_uuid != answer.user_uuid:
            if not allow(MANAGE, answer) or not answer.draft:
                return {"error": "Only instructors and teaching assistants can submit an answer on behalf of another user."}, 400
            user = User.get_by_uuid_or_404(user_uuid)
            answer.user_id = user.id

            user_course = UserCourse.query \
                .filter_by(
                    course_id=course.id,
                    user_id=answer.user_id
                ) \
                .one_or_none()

            if user_course.course_role.value not in [CourseRole.instructor.value, CourseRole.teaching_assistant.value]:
                # check if there is a previous answer submitted for the student
                prev_answer = Answer.query \
                    .filter(Answer.id != answer.id) \
                    .filter_by(
                        assignment_id=assignment.id,
                        user_id=answer.user_id,
                        active=True
                    ) \
                    .first()
                if prev_answer:
                    return {"error": "An answer has already been submitted."}, 400

        # can only change draft status while a draft
        if answer.draft:
            answer.draft = params.get("draft")
        uploaded = params.get('uploadFile')

        file_uuid = params.get('file_id')
        if file_uuid:
            answer.file = File.get_by_uuid_or_404(file_uuid)
        else:
            answer.file_id = None

        # non-drafts must have content
        if not answer.draft and not answer.content and not file_uuid:
            return {"error": "The answer content is empty!"}, 400

        db.session.add(answer)
        db.session.commit()

        on_answer_modified.send(
            self,
            event_name=on_answer_modified.name,
            user=current_user,
            course_id=course.id,
            answer=answer,
            assignment=assignment,
            data=get_model_changes(answer))

        # update course & assignment grade for user if answer is fully submitted
        if not answer.draft:
            assignment.calculate_grade(answer.user)
            course.calculate_grade(answer.user)

        return marshal(answer, dataformat.get_answer(restrict_user))
Beispiel #39
0
    def post(self, course_uuid, assignment_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)

        if not assignment.answer_grace and not allow(MANAGE, assignment):
            abort(403, title="Answer Not Submitted", message="Sorry, the answer deadline has passed. No answers can be submitted after the deadline unless the instructor submits the answer for you.")

        require(CREATE, Answer(course_id=course.id),
            title="Answer Not Submitted",
            message="Answers can be submitted only by those enrolled in the course. Please double-check your enrollment in this course.")
        restrict_user = not allow(MANAGE, assignment)

        answer = Answer(assignment_id=assignment.id)

        params = new_answer_parser.parse_args()
        answer.content = params.get("content")
        answer.draft = params.get("draft")

        file_uuid = params.get('file_id')
        attachment = None
        if file_uuid:
            attachment = File.get_by_uuid_or_404(file_uuid)
            answer.file_id = attachment.id
        else:
            answer.file_id = None

        # non-drafts must have content
        if not answer.draft and not answer.content and not file_uuid:
            abort(400, title="Answer Not Submitted", message="Please provide content in the text editor or upload a file and try submitting again.")

        user_uuid = params.get("user_id")
        group_uuid = params.get("group_id")
        # we allow instructor and TA to submit multiple answers for other users in the class
        if user_uuid and not allow(MANAGE, Answer(course_id=course.id)):
            abort(400, title="Answer Not Submitted", message="Only instructors and teaching assistants can submit an answer on behalf of another.")
        if group_uuid and not assignment.enable_group_answers:
            abort(400, title="Answer Not Submitted", message="Group answers are not allowed for this assignment.")
        if group_uuid and not allow(MANAGE, Answer(course_id=course.id)):
            abort(400, title="Answer Not Submitted", message="Only instructors and teaching assistants can submit an answer on behalf of a group.")
        if group_uuid and user_uuid:
            abort(400, title="Answer Not Submitted", message="You cannot submit an answer for a user and a group at the same time.")

        user = User.get_by_uuid_or_404(user_uuid) if user_uuid else current_user
        group = Group.get_active_by_uuid_or_404(group_uuid) if group_uuid else None

        if restrict_user and assignment.enable_group_answers and not group:
            group = current_user.get_course_group(course.id)
            if group == None:
                abort(400, title="Answer Not Submitted",
                    message="You are currently not in any group for this course. Please contact your instructor to be added to a group.")

        check_for_existing_answers = False
        if group and assignment.enable_group_answers:
            if group.course_id != course.id:
                abort(400, title="Answer Not Submitted",
                    message="Group answers can be submitted to courses they belong in.")

            answer.user_id = None
            answer.group_id = group.id
            answer.comparable = True
            check_for_existing_answers = True
        else:
            answer.user_id = user.id
            answer.group_id = None

            course_role = User.get_user_course_role(answer.user_id, course.id)

            # only system admin can add answers for themselves to a class without being enrolled in it
            # required for managing comparison examples as system admin
            if (not course_role or course_role == CourseRole.dropped) and current_user.system_role != SystemRole.sys_admin:
                abort(400, title="Answer Not Submitted", message="Answers can be submitted only by those enrolled in the course. Please double-check your enrollment in this course.")

            if course_role == CourseRole.student and assignment.enable_group_answers:
                abort(400, title="Answer Not Submitted", message="Students can only submit group answers for this assignment.")

            # we allow instructor and TA to submit multiple answers for their own,
            # but not for student. Each student can only have one answer.
            if course_role and course_role == CourseRole.student:
                check_for_existing_answers = True
                answer.comparable = True
            else:
                # instructor / TA / Sys Admin can mark the answer as non-comparable, unless the answer is for a student
                answer.comparable = params.get("comparable")

        if check_for_existing_answers:
            # check for answers with user_id or group_id
            prev_answers = Answer.query \
                .filter_by(
                    assignment_id=assignment.id,
                    user_id=answer.user_id,
                    group_id=answer.group_id,
                    active=True
                ) \
                .all()

            # check if there is a previous answer submitted for the student
            non_draft_answers = [prev_answer for prev_answer in prev_answers if not prev_answer.draft]
            if len(non_draft_answers) > 0:
                abort(400, title="Answer Not Submitted", message="An answer has already been submitted for this assignment by you or on your behalf.")

            # check if there is a previous draft answer submitted for the student (soft-delete if present)
            draft_answers = [prev_answer for prev_answer in prev_answers if prev_answer.draft]
            for draft_answer in draft_answers:
                draft_answer.active = False

        # set submission date if answer is being submitted for the first time
        if not answer.draft and not answer.submission_date:
            answer.submission_date = datetime.datetime.utcnow()

        answer.update_attempt(
            params.get('attempt_uuid'),
            params.get('attempt_started', None),
            params.get('attempt_ended', None)
        )

        db.session.add(answer)
        db.session.commit()

        on_answer_create.send(
            self,
            event_name=on_answer_create.name,
            user=current_user,
            course_id=course.id,
            answer=answer,
            data=marshal(answer, dataformat.get_answer(restrict_user)))

        if attachment:
            on_attach_file.send(
                self,
                event_name=on_attach_file.name,
                user=current_user,
                course_id=course.id,
                file=attachment,
                data={'answer_id': answer.id, 'file_id': attachment.id})

        # update course & assignment grade for user if answer is fully submitted
        if not answer.draft:
            if answer.user:
                assignment.calculate_grade(answer.user)
                course.calculate_grade(answer.user)
            elif answer.group:
                assignment.calculate_group_grade(answer.group)
                course.calculate_group_grade(answer.group)

        return marshal(answer, dataformat.get_answer(restrict_user))
Beispiel #40
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
        }
Beispiel #41
0
    def get(self, course_uuid, assignment_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        require(READ, Comparison(course_id=course.id),
            title="Comparisons Unavailable",
            message="Sorry, your role in this course does not allow you to view all comparisons for this assignment.")

        restrict_user = is_user_access_restricted(current_user)
        params = assignment_users_comparison_list_parser.parse_args()

        # only get users who have at least made one comparison
        # each paginated item is a user (with a set of comparisons and self-evaluations)
        user_query = User.query \
            .join(UserCourse, and_(
                User.id == UserCourse.user_id,
                UserCourse.course_id == course.id
            )) \
            .join(Comparison, and_(
                Comparison.user_id == User.id,
                Comparison.assignment_id == assignment.id
            )) \
            .filter(and_(
                UserCourse.course_role != CourseRole.dropped,
                Comparison.completed == True
            )) \
            .group_by(User) \
            .order_by(User.lastname, User.firstname)

        self_evaluation_total = AnswerComment.query \
            .join("answer") \
            .with_entities(
                func.count(Answer.assignment_id).label('self_evaluation_count')
            ) \
            .filter(and_(
                AnswerComment.active == True,
                AnswerComment.comment_type == AnswerCommentType.self_evaluation,
                AnswerComment.draft == False,
                Answer.active == True,
                Answer.practice == False,
                Answer.draft == False,
                Answer.assignment_id == assignment.id
            ))

        comparison_total = Comparison.query \
            .with_entities(
                func.count(Comparison.assignment_id).label('comparison_count')
            ) \
            .filter(and_(
                Comparison.completed == True,
                Comparison.assignment_id == assignment.id
            ))

        if params['author']:
            user = User.get_by_uuid_or_404(params['author'])

            user_query = user_query.filter(User.id == user.id)
            self_evaluation_total = self_evaluation_total.filter(AnswerComment.user_id == user.id)
            comparison_total = comparison_total.filter(Comparison.user_id == user.id)
        elif params['group']:
            user_query = user_query.filter(UserCourse.group_name == params['group'])

            self_evaluation_total = self_evaluation_total \
                .join(UserCourse, and_(
                    AnswerComment.user_id == UserCourse.user_id,
                    UserCourse.course_id == course.id
                )) \
                .filter(UserCourse.group_name == params['group'])

            comparison_total = comparison_total \
                .join(UserCourse, and_(
                    Comparison.user_id == UserCourse.user_id,
                    UserCourse.course_id == course.id
                )) \
                .filter(UserCourse.group_name == params['group'])

        page = user_query.paginate(params['page'], params['perPage'])
        self_evaluation_total = self_evaluation_total.scalar()
        comparison_total = comparison_total.scalar()

        comparison_sets = []
        if page.total:
            user_ids = [user.id for user in page.items]

            # get all comparisons that group of users created
            comparisons = Comparison.query \
                .filter(and_(
                    Comparison.completed == True,
                    Comparison.assignment_id == assignment.id,
                    Comparison.user_id.in_(user_ids)
                )) \
                .all()

            # retrieve the answer comments
            user_comparison_answers = {}
            for comparison in comparisons:
                user_answers = user_comparison_answers.setdefault(comparison.user_id, set())
                user_answers.add(comparison.answer1_id)
                user_answers.add(comparison.answer2_id)

            conditions = []
            for user_id, answer_set in user_comparison_answers.items():
                conditions.append(and_(
                        AnswerComment.comment_type == AnswerCommentType.evaluation,
                        AnswerComment.user_id == user_id,
                        AnswerComment.answer_id.in_(list(answer_set)),
                        AnswerComment.assignment_id == assignment.id
                ))
                conditions.append(and_(
                    AnswerComment.comment_type == AnswerCommentType.self_evaluation,
                    AnswerComment.user_id == user_id,
                    AnswerComment.assignment_id == assignment.id
                ))

            answer_comments = AnswerComment.query \
                .filter(or_(*conditions)) \
                .filter_by(draft=False) \
                .all()

            # add comparison answer evaluation comments to comparison object
            for comparison in comparisons:
                comparison.answer1_feedback = [comment for comment in answer_comments if
                    comment.user_id == comparison.user_id and
                    comment.answer_id == comparison.answer1_id and
                    comment.comment_type == AnswerCommentType.evaluation
                ]

                comparison.answer2_feedback = [comment for comment in answer_comments if
                    comment.user_id == comparison.user_id and
                    comment.answer_id == comparison.answer2_id and
                    comment.comment_type == AnswerCommentType.evaluation
                ]

            for user in page.items:
                comparison_sets.append({
                    'user': user,
                    'comparisons': [comparison for comparison in comparisons if
                        comparison.user_id == user.id
                    ],
                    'self_evaluations': [comment for comment in answer_comments if
                        comment.user_id == user.id and
                        comment.comment_type == AnswerCommentType.self_evaluation
                    ]
                })


        on_assignment_users_comparisons_get.send(
            self,
            event_name=on_assignment_users_comparisons_get.name,
            user=current_user,
            course_id=course.id,
            data={'assignment_id': assignment.id}
        )

        return {"objects": marshal(comparison_sets, dataformat.get_comparison_set(restrict_user, with_user=True)),
                "comparison_total": comparison_total, "self_evaluation_total": self_evaluation_total,
                "page": page.page, "pages": page.pages, "total": page.total, "per_page": page.per_page}
Beispiel #42
0
    def get(self, course_uuid, assignment_uuid):
        """
        Return a list of answers for a assignment based on search criteria. The
        list of the answers are paginated. If there is any answers from instructor
        or TA, their answers will be on top of the list (unless they are comparable).

        :param course_uuid: course uuid
        :param assignment_uuid: assignment uuid
        :return: list of answers
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)

        require(
            READ,
            assignment,
            title="Answers Unavailable",
            message=
            "Answers are visible only to those enrolled in the course. Please double-check your enrollment in this course."
        )
        restrict_user = not allow(MANAGE, assignment)

        params = answer_list_parser.parse_args()

        # if assingment has no rank display limit set, restricted users can't force to retreive by rank
        if params[
                'orderBy'] == 'score' and restrict_user and not assignment.rank_display_limit:
            abort(
                400,
                title="Answers Unavailable",
                message=
                "Sorry, you cannot cannot see answers by rank for this assignment."
            )

        if restrict_user and not assignment.after_comparing:
            # only the answer from student himself/herself should be returned
            params['author'] = current_user.uuid

        # this query could be further optimized by reduction the selected columns
        query = Answer.query \
            .options(joinedload('file')) \
            .options(joinedload('user')) \
            .options(joinedload('group')) \
            .options(joinedload('score')) \
            .options(undefer_group('counts')) \
            .outerjoin(UserCourse, and_(
                Answer.user_id == UserCourse.user_id,
                UserCourse.course_id == course.id
            )) \
            .add_columns(
                and_(
                    UserCourse != None,
                    UserCourse.course_role.__eq__(CourseRole.instructor),
                    not Answer.comparable
                ).label("instructor_role"),
                and_(
                    UserCourse != None,
                    UserCourse.course_role.__eq__(CourseRole.teaching_assistant),
                    not Answer.comparable
                ).label("ta_role")
            ) \
            .filter(and_(
                Answer.assignment_id == assignment.id,
                Answer.active == True,
                Answer.practice == False,
                Answer.draft == False,
                or_(
                    and_(UserCourse.course_role != CourseRole.dropped, Answer.user_id != None),
                    Answer.group_id != None
                )
            )) \
            .order_by(desc('instructor_role'), desc('ta_role'))

        if params['author']:
            user = User.get_by_uuid_or_404(params['author'])
            group = user.get_course_group(course.id)
            if group:
                query = query.filter(
                    or_(Answer.user_id == user.id,
                        Answer.group_id == group.id))
            else:
                query = query.filter(Answer.user_id == user.id)
        elif params['group']:
            group = Group.get_active_by_uuid_or_404(params['group'])
            query = query.filter(
                or_(UserCourse.group_id == group.id,
                    Answer.group_id == group.id))

        if params['ids']:
            query = query.filter(Answer.uuid.in_(params['ids'].split(',')))

        if params['top']:
            query = query.filter(Answer.top_answer == True)

        if params['orderBy'] == 'score':
            # use outer join to include comparable answers that are not yet compared (for non-restricted users)
            query = query.outerjoin(AnswerScore) \
                .filter(Answer.comparable == True) \
                .order_by(AnswerScore.score.desc(), Answer.submission_date.desc(), Answer.created.desc())

            if restrict_user:
                # when orderd by rank, students won't see answers that are not compared (i.e. no score/rank)
                query = query.filter(AnswerScore.score.isnot(None))

            # limit answers up to rank if rank_display_limit is set and current_user is restricted (student)
            if assignment.rank_display_limit and restrict_user:
                score_for_rank = AnswerScore.get_score_for_rank(
                    assignment.id, assignment.rank_display_limit)

                # display answers with score >= score_for_rank
                if score_for_rank != None:
                    # will get all answers with a score greater than or equal to the score for a given rank
                    # the '- 0.00001' fixes floating point precision problems
                    query = query.filter(
                        AnswerScore.score >= score_for_rank - 0.00001)

        else:
            # when ordered by date, non-comparable answers should be on top of the list
            query = query.order_by(Answer.comparable,
                                   Answer.submission_date.desc(),
                                   Answer.created.desc())

        page = query.paginate(params['page'],
                              params['perPage'],
                              error_out=False)
        # remove label entities from results
        page.items = [
            answer for (answer, instructor_role, ta_role) in page.items
        ]

        on_answer_list_get.send(self,
                                event_name=on_answer_list_get.name,
                                user=current_user,
                                course_id=course.id,
                                data={'assignment_id': assignment.id})

        # only include score/rank info if:
        # - requesters are non-restricted users (i.e. instructors / TAs); or,
        # - retrieving answers ordered by score/rank
        include_score = (not restrict_user) or \
            (params['orderBy'] == 'score' and assignment.rank_display_limit)

        return {
            "objects":
            marshal(
                page.items,
                dataformat.get_answer(restrict_user,
                                      include_score=include_score)),
            "page":
            page.page,
            "pages":
            page.pages,
            "total":
            page.total,
            "per_page":
            page.per_page
        }
Beispiel #43
0
    def post(self, course_uuid, assignment_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)

        if not assignment.answer_grace and not allow(MANAGE, assignment):
            abort(
                403,
                title="Answer Not Submitted",
                message=
                "Sorry, the answer deadline has passed. No answers can be submitted after the deadline unless the instructor submits the answer for you."
            )

        require(
            CREATE,
            Answer(course_id=course.id),
            title="Answer Not Submitted",
            message=
            "Answers can be submitted only by those enrolled in the course. Please double-check your enrollment in this course."
        )
        restrict_user = not allow(MANAGE, assignment)

        answer = Answer(assignment_id=assignment.id)

        params = new_answer_parser.parse_args()
        answer.content = params.get("content")
        answer.draft = params.get("draft")

        file_uuid = params.get('file_id')
        attachment = None
        if file_uuid:
            attachment = File.get_by_uuid_or_404(file_uuid)
            answer.file_id = attachment.id
        else:
            answer.file_id = None

        # non-drafts must have content
        if not answer.draft and not answer.content and not file_uuid:
            abort(
                400,
                title="Answer Not Submitted",
                message=
                "Please provide content in the text editor or upload a file and try submitting again."
            )

        user_uuid = params.get("user_id")
        group_uuid = params.get("group_id")
        # we allow instructor and TA to submit multiple answers for other users in the class
        if user_uuid and not allow(MANAGE, Answer(course_id=course.id)):
            abort(
                400,
                title="Answer Not Submitted",
                message=
                "Only instructors and teaching assistants can submit an answer on behalf of another."
            )
        if group_uuid and not assignment.enable_group_answers:
            abort(400,
                  title="Answer Not Submitted",
                  message="Group answers are not allowed for this assignment.")
        if group_uuid and not allow(MANAGE, Answer(course_id=course.id)):
            abort(
                400,
                title="Answer Not Submitted",
                message=
                "Only instructors and teaching assistants can submit an answer on behalf of a group."
            )
        if group_uuid and user_uuid:
            abort(
                400,
                title="Answer Not Submitted",
                message=
                "You cannot submit an answer for a user and a group at the same time."
            )

        user = User.get_by_uuid_or_404(
            user_uuid) if user_uuid else current_user
        group = Group.get_active_by_uuid_or_404(
            group_uuid) if group_uuid else None

        if restrict_user and assignment.enable_group_answers and not group:
            group = current_user.get_course_group(course.id)
            if group == None:
                abort(
                    400,
                    title="Answer Not Submitted",
                    message=
                    "You are currently not in any group for this course. Please contact your instructor to be added to a group."
                )

        check_for_existing_answers = False
        if group and assignment.enable_group_answers:
            if group.course_id != course.id:
                abort(
                    400,
                    title="Answer Not Submitted",
                    message=
                    "Group answers can be submitted to courses they belong in."
                )

            answer.user_id = None
            answer.group_id = group.id
            answer.comparable = True
            check_for_existing_answers = True
        else:
            answer.user_id = user.id
            answer.group_id = None

            course_role = User.get_user_course_role(answer.user_id, course.id)

            # only system admin can add answers for themselves to a class without being enrolled in it
            # required for managing comparison examples as system admin
            if (not course_role or course_role == CourseRole.dropped
                ) and current_user.system_role != SystemRole.sys_admin:
                abort(
                    400,
                    title="Answer Not Submitted",
                    message=
                    "Answers can be submitted only by those enrolled in the course. Please double-check your enrollment in this course."
                )

            if course_role == CourseRole.student and assignment.enable_group_answers:
                abort(
                    400,
                    title="Answer Not Submitted",
                    message=
                    "Students can only submit group answers for this assignment."
                )

            # we allow instructor and TA to submit multiple answers for their own,
            # but not for student. Each student can only have one answer.
            if course_role and course_role == CourseRole.student:
                check_for_existing_answers = True
                answer.comparable = True
            else:
                # instructor / TA / Sys Admin can mark the answer as non-comparable, unless the answer is for a student
                answer.comparable = params.get("comparable")

        if check_for_existing_answers:
            # check for answers with user_id or group_id
            prev_answers = Answer.query \
                .filter_by(
                    assignment_id=assignment.id,
                    user_id=answer.user_id,
                    group_id=answer.group_id,
                    active=True
                ) \
                .all()

            # check if there is a previous answer submitted for the student
            non_draft_answers = [
                prev_answer for prev_answer in prev_answers
                if not prev_answer.draft
            ]
            if len(non_draft_answers) > 0:
                abort(
                    400,
                    title="Answer Not Submitted",
                    message=
                    "An answer has already been submitted for this assignment by you or on your behalf."
                )

            # check if there is a previous draft answer submitted for the student (soft-delete if present)
            draft_answers = [
                prev_answer for prev_answer in prev_answers
                if prev_answer.draft
            ]
            for draft_answer in draft_answers:
                draft_answer.active = False

        # set submission date if answer is being submitted for the first time
        if not answer.draft and not answer.submission_date:
            answer.submission_date = datetime.datetime.utcnow()

        answer.update_attempt(params.get('attempt_uuid'),
                              params.get('attempt_started', None),
                              params.get('attempt_ended', None))

        db.session.add(answer)
        db.session.commit()

        on_answer_create.send(self,
                              event_name=on_answer_create.name,
                              user=current_user,
                              course_id=course.id,
                              answer=answer,
                              data=marshal(
                                  answer,
                                  dataformat.get_answer(restrict_user)))

        if attachment:
            on_attach_file.send(self,
                                event_name=on_attach_file.name,
                                user=current_user,
                                course_id=course.id,
                                file=attachment,
                                data={
                                    'answer_id': answer.id,
                                    'file_id': attachment.id
                                })

        # update course & assignment grade for user if answer is fully submitted
        if not answer.draft:
            if answer.user:
                assignment.calculate_grade(answer.user)
                course.calculate_grade(answer.user)
            elif answer.group:
                assignment.calculate_group_grade(answer.group)
                course.calculate_group_grade(answer.group)

        return marshal(answer, dataformat.get_answer(restrict_user))
Beispiel #44
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)
Beispiel #45
0
    def post(self, user_uuid):
        user = User.get_by_uuid_or_404(user_uuid)

        if is_user_access_restricted(user):
            abort(
                403,
                title="User Not Saved",
                message="Sorry, your role does not allow you to save this user."
            )

        params = existing_user_parser.parse_args()

        # make sure the user id in the url and the id matches
        if params['id'] != user_uuid:
            abort(
                400,
                title="User Not Saved",
                message=
                "The user's ID does not match the URL, which is required in order to save the user."
            )

        # only update username if user uses compair login method
        if user.uses_compair_login:
            username = params.get("username")
            if 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=username).first()
            if username_exists and username_exists.id != user.id:
                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."
                )

            user.username = username
        elif allow(MANAGE, user):
            #admins can optionally set username for users without a username
            username = params.get("username")
            if username:
                username_exists = User.query.filter_by(
                    username=username).first()
                if username_exists and username_exists.id != user.id:
                    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."
                    )
            user.username = username
        else:
            user.username = None

        if allow(MANAGE, user):
            system_role = params.get("system_role", user.system_role.value)
            check_valid_system_role(system_role)
            user.system_role = SystemRole(system_role)

        if allow(MANAGE,
                 user) or user.id == current_user.id or current_app.config.get(
                     'EXPOSE_EMAIL_TO_INSTRUCTOR', False):
            if current_user.system_role != SystemRole.student or current_app.config.get(
                    'ALLOW_STUDENT_CHANGE_EMAIL'):
                user.email = params.get("email", user.email)

            email_notification_method = params.get("email_notification_method")
            check_valid_email_notification_method(email_notification_method)
            user.email_notification_method = EmailNotificationMethod(
                email_notification_method)

        elif params.get("email") or params.get("email_notification_method"):
            abort(
                400,
                title="User Not Saved",
                message=
                "your role does not allow you to change email settings for this user."
            )

        if current_user.system_role != SystemRole.student or current_app.config.get(
                'ALLOW_STUDENT_CHANGE_STUDENT_NUMBER'):
            # only students should have student numbers
            if user.system_role == SystemRole.student:
                student_number = params.get("student_number",
                                            user.student_number)
                student_number_exists = User.query.filter_by(
                    student_number=student_number).first()
                if student_number is not None and student_number_exists and student_number_exists.id != user.id:
                    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."
                    )
                else:
                    user.student_number = student_number
            else:
                user.student_number = None

        if current_user.system_role != SystemRole.student or current_app.config.get(
                'ALLOW_STUDENT_CHANGE_NAME'):
            user.firstname = params.get("firstname", user.firstname)
            user.lastname = params.get("lastname", user.lastname)

        if current_user.system_role != SystemRole.student or current_app.config.get(
                'ALLOW_STUDENT_CHANGE_DISPLAY_NAME'):
            user.displayname = params.get("displayname", user.displayname)

        model_changes = get_model_changes(user)

        try:
            db.session.commit()
            on_user_modified.send(self,
                                  event_name=on_user_modified.name,
                                  user=current_user,
                                  data={
                                      'id': user.id,
                                      'changes': model_changes
                                  })
        except exc.IntegrityError:
            db.session.rollback()
            abort(
                409,
                title="User Not Saved",
                message=
                "Sorry, this ID already exists and IDs must be unique in ComPAIR. Please try addding another user."
            )

        return marshal_user_data(user)
Beispiel #46
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())
Beispiel #47
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())
Beispiel #48
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)
Beispiel #49
0
    def get(self, course_uuid, assignment_uuid):
        """
        Return a list of answers for a assignment based on search criteria. The
        list of the answers are paginated. If there is any answers from instructor
        or TA, their answers will be on top of the list (unless they are comparable).

        :param course_uuid: course uuid
        :param assignment_uuid: assignment uuid
        :return: list of answers
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)

        require(READ, assignment,
            title="Answers Unavailable",
            message="Answers are visible only to those enrolled in the course. Please double-check your enrollment in this course.")
        restrict_user = not allow(MANAGE, assignment)

        params = answer_list_parser.parse_args()

        # if assingment has no rank display limit set, restricted users can't force to retreive by rank
        if params['orderBy'] == 'score' and restrict_user and not assignment.rank_display_limit:
            abort(400, title="Answers Unavailable", message="Sorry, you cannot cannot see answers by rank for this assignment.")

        if restrict_user and not assignment.after_comparing:
            # only the answer from student himself/herself should be returned
            params['author'] = current_user.uuid

        # this query could be further optimized by reduction the selected columns
        query = Answer.query \
            .options(joinedload('file')) \
            .options(joinedload('user')) \
            .options(joinedload('group')) \
            .options(joinedload('score')) \
            .options(undefer_group('counts')) \
            .outerjoin(UserCourse, and_(
                Answer.user_id == UserCourse.user_id,
                UserCourse.course_id == course.id
            )) \
            .add_columns(
                and_(
                    UserCourse != None,
                    UserCourse.course_role.__eq__(CourseRole.instructor),
                    not Answer.comparable
                ).label("instructor_role"),
                and_(
                    UserCourse != None,
                    UserCourse.course_role.__eq__(CourseRole.teaching_assistant),
                    not Answer.comparable
                ).label("ta_role")
            ) \
            .filter(and_(
                Answer.assignment_id == assignment.id,
                Answer.active == True,
                Answer.practice == False,
                Answer.draft == False,
                or_(
                    and_(UserCourse.course_role != CourseRole.dropped, Answer.user_id != None),
                    Answer.group_id != None
                )
            )) \
            .order_by(desc('instructor_role'), desc('ta_role'))

        if params['author']:
            user = User.get_by_uuid_or_404(params['author'])
            group = user.get_course_group(course.id)
            if group:
                query = query.filter(or_(
                    Answer.user_id == user.id,
                    Answer.group_id == group.id
                ))
            else:
                query = query.filter(Answer.user_id == user.id)
        elif params['group']:
            group = Group.get_active_by_uuid_or_404(params['group'])
            query = query.filter(or_(
                UserCourse.group_id == group.id,
                Answer.group_id == group.id
            ))

        if params['ids']:
            query = query.filter(Answer.uuid.in_(params['ids'].split(',')))

        if params['top']:
            query = query.filter(Answer.top_answer == True)

        if params['orderBy'] == 'score':
            # use outer join to include comparable answers that are not yet compared (for non-restricted users)
            query = query.outerjoin(AnswerScore) \
                .filter(Answer.comparable == True) \
                .order_by(AnswerScore.score.desc(), Answer.submission_date.desc(), Answer.created.desc())

            if restrict_user:
                # when orderd by rank, students won't see answers that are not compared (i.e. no score/rank)
                query = query.filter(AnswerScore.score.isnot(None))

            # limit answers up to rank if rank_display_limit is set and current_user is restricted (student)
            if assignment.rank_display_limit and restrict_user:
                score_for_rank = AnswerScore.get_score_for_rank(assignment.id, assignment.rank_display_limit)

                # display answers with score >= score_for_rank
                if score_for_rank != None:
                    # will get all answers with a score greater than or equal to the score for a given rank
                    # the '- 0.00001' fixes floating point precision problems
                    query = query.filter(AnswerScore.score >= score_for_rank - 0.00001)

        else:
            # when ordered by date, non-comparable answers should be on top of the list
            query = query.order_by(Answer.comparable, Answer.submission_date.desc(), Answer.created.desc())

        page = query.paginate(params['page'], params['perPage'], error_out=False)
        # remove label entities from results
        page.items = [answer for (answer, instructor_role, ta_role) in page.items]

        on_answer_list_get.send(
            self,
            event_name=on_answer_list_get.name,
            user=current_user,
            course_id=course.id,
            data={'assignment_id': assignment.id})

        # only include score/rank info if:
        # - requesters are non-restricted users (i.e. instructors / TAs); or,
        # - retrieving answers ordered by score/rank
        include_score = (not restrict_user) or \
            (params['orderBy'] == 'score' and assignment.rank_display_limit)

        return {"objects": marshal(page.items, dataformat.get_answer(restrict_user, include_score=include_score)),
                "page": page.page, "pages": page.pages,
                "total": page.total, "per_page": page.per_page}
Beispiel #50
0
    def post(self, course_uuid, assignment_uuid, answer_uuid):
        """
        Create comment for an answer
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        answer = Answer.get_active_by_uuid_or_404(answer_uuid)
        require(
            CREATE,
            AnswerComment(course_id=course.id),
            title="Reply Not Saved",
            message=
            "Sorry, your role in this course does not allow you to save replies for this answer."
        )

        answer_comment = AnswerComment(answer_id=answer.id)

        params = new_answer_comment_parser.parse_args()
        answer_comment.draft = params.get('draft')
        answer_comment.content = params.get("content")
        # require content not empty if not a draft
        if not answer_comment.content and not answer_comment.draft:
            abort(
                400,
                title="Reply Not Saved",
                message=
                "Please provide content in the text editor to reply and try saving again."
            )

        if params.get('user_id'
                      ) and current_user.system_role == SystemRole.sys_admin:
            user = User.get_by_uuid_or_404(params.get('user_id'))
            answer_comment.user_id = user.id
        else:
            answer_comment.user_id = current_user.id

        comment_types = [
            AnswerCommentType.public.value, AnswerCommentType.private.value,
            AnswerCommentType.evaluation.value,
            AnswerCommentType.self_evaluation.value
        ]

        comment_type = params.get("comment_type")
        if comment_type not in comment_types:
            abort(
                400,
                title="Reply Not Saved",
                message=
                "This reply type is not recognized. Please contact support for assistance."
            )
        answer_comment.comment_type = AnswerCommentType(comment_type)

        db.session.add(answer_comment)
        db.session.commit()

        # update course & assignment grade for user if self-evaluation is completed
        if not answer_comment.draft and answer_comment.comment_type == AnswerCommentType.self_evaluation:
            assignment.calculate_grade(answer_comment.user)
            course.calculate_grade(answer_comment.user)

        on_answer_comment_create.send(
            self,
            event_name=on_answer_comment_create.name,
            user=current_user,
            course_id=course.id,
            answer_comment=answer_comment,
            data=marshal(answer_comment, dataformat.get_answer_comment(False)))

        return marshal(answer_comment, dataformat.get_answer_comment())
Beispiel #51
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))
    }
Beispiel #52
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))
    }
Beispiel #53
0
    def get(self, course_uuid, assignment_uuid):
        """
        Return a list of answers for a assignment based on search criteria. The
        list of the answers are paginated. If there is any answers from instructor
        or TA, their answers will be on top of the list.

        :param course_uuid: course uuid
        :param assignment_uuid: assignment uuid
        :return: list of answers
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)

        require(READ, assignment)
        restrict_user = not allow(MANAGE, assignment)

        params = answer_list_parser.parse_args()

        if restrict_user and not assignment.after_comparing:
            # only the answer from student himself/herself should be returned
            params['author'] = current_user.uuid

        # this query could be further optimized by reduction the selected columns
        query = Answer.query \
            .options(joinedload('file')) \
            .options(joinedload('user')) \
            .options(joinedload('scores')) \
            .options(undefer_group('counts')) \
            .join(UserCourse, and_(
                Answer.user_id == UserCourse.user_id,
                UserCourse.course_id == course.id
            )) \
            .add_columns(
                UserCourse.course_role.__eq__(CourseRole.instructor).label("instructor_role"),
                UserCourse.course_role.__eq__(CourseRole.teaching_assistant).label("ta_role")
            ) \
            .filter(and_(
                Answer.assignment_id == assignment.id,
                Answer.active == True,
                Answer.practice == False,
                Answer.draft == False,
                UserCourse.course_role != CourseRole.dropped
            )) \
            .order_by(desc('instructor_role'), desc('ta_role'))

        if params['author']:
            user = User.get_by_uuid_or_404(params['author'])
            query = query.filter(Answer.user_id == user.id)
        elif params['group']:
            query = query.filter(UserCourse.group_name == params['group'])

        if params['ids']:
            query = query.filter(Answer.uuid.in_(params['ids'].split(',')))

        if params['top']:
            query = query.filter(Answer.top_answer == True)

        if params['orderBy']:
            criterion = Criterion.get_active_by_uuid_or_404(params['orderBy'])

            # order answer ids by one criterion and pagination, in case there are multiple criteria in assignment
            # does not include answers without a score (Note: ta and instructor never have a score)
            query = query.join(Score) \
                .filter(Score.criterion_id == criterion.id) \
                .order_by(Score.score.desc(), Answer.created.desc())

            # limit answers up to rank if rank_display_limit is set and current_user is restricted (student)
            if assignment.rank_display_limit and restrict_user:
                score_for_rank = Score.get_score_for_rank(
                    assignment.id, criterion.id, assignment.rank_display_limit)

                # display answers with score >= score_for_rank
                if score_for_rank != None:
                    # will get all answer with a score greater than or equal to the score for a given rank
                    # the '- 0.00001' fixes floating point precision problems
                    query = query.filter(Score.score >= score_for_rank - 0.00001)

        else:
            query = query.order_by(Answer.created.desc())

        page = query.paginate(params['page'], params['perPage'])
        # remove label entities from results
        page.items = [answer for (answer, instructor_role, ta_role) in page.items]

        on_answer_list_get.send(
            self,
            event_name=on_answer_list_get.name,
            user=current_user,
            course_id=course.id,
            data={'assignment_id': assignment.id})

        return {"objects": marshal(page.items, dataformat.get_answer(restrict_user)),
                "page": page.page, "pages": page.pages,
                "total": page.total, "per_page": page.per_page}
Beispiel #54
0
    def get(self, user_uuid):
        user = User.get_by_uuid_or_404(user_uuid)

        require(
            MANAGE,
            User,
            title="User's Courses Unavailable",
            message=
            "Sorry, your system role does not allow you to view courses for this user."
        )

        params = user_id_course_list_parser.parse_args()

        query = Course.query \
            .with_entities(Course, UserCourse) \
            .options(joinedload(UserCourse.group)) \
            .join(UserCourse, and_(
                UserCourse.course_id == Course.id,
                UserCourse.user_id == user.id,
            )) \
            .filter(and_(
                Course.active == True,
                UserCourse.course_role != CourseRole.dropped
            ))

        if params['search']:
            search_terms = params['search'].split()
            for search_term in search_terms:
                if search_term != "":
                    search = '%' + search_term + '%'
                    query = query.filter(
                        or_(Course.name.like(search), Course.year.like(search),
                            Course.term.like(search)))

        if params['includeSandbox'] != None:
            query = query.filter(Course.sandbox == params['includeSandbox'])

        if params['orderBy']:
            if params['reverse']:
                query = query.order_by(desc(params['orderBy']))
            else:
                query = query.order_by(asc(params['orderBy']))
        query = query.order_by(Course.start_date_order.desc(), Course.name)

        page = query.paginate(params['page'], params['perPage'])

        # fix results
        courses = []
        for (_course, _user_course) in page.items:
            _course.course_role = _user_course.course_role
            _course.group = _user_course.group
            _course.group_uuid = _course.group.uuid if _course.group else None
            courses.append(_course)
        page.items = courses

        on_user_course_get.send(self,
                                event_name=on_user_course_get.name,
                                user=user)

        return {
            "objects": marshal(page.items, dataformat.get_user_courses()),
            "page": page.page,
            "pages": page.pages,
            "total": page.total,
            "per_page": page.per_page
        }
Beispiel #55
0
    def post(self, course_uuid, assignment_uuid, answer_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)

        if not assignment.answer_grace and not allow(MANAGE, assignment):
            abort(403, title="Answer Not Submitted", message="Sorry, the answer deadline has passed. No answers can be submitted after the deadline unless the instructor submits the answer for you.")

        answer = Answer.get_active_by_uuid_or_404(answer_uuid)

        old_file = answer.file

        require(EDIT, answer,
            title="Answer Not Saved",
            message="Sorry, your role in this course does not allow you to save this answer.")
        restrict_user = not allow(MANAGE, assignment)

        if current_app.config.get('DEMO_INSTALLATION', False):
            from data.fixtures import DemoDataFixture
            if assignment.id in DemoDataFixture.DEFAULT_ASSIGNMENT_IDS and answer.user_id in DemoDataFixture.DEFAULT_STUDENT_IDS:
                abort(400, title="Answer Not Saved", message="Sorry, you cannot edit the default student demo answers.")

        params = existing_answer_parser.parse_args()
        # make sure the answer id in the url and the id matches
        if params['id'] != answer_uuid:
            abort(400, title="Answer Not Submitted", message="The answer's ID does not match the URL, which is required in order to save the answer.")

        # modify answer according to new values, preserve original values if values not passed
        answer.content = params.get("content")

        user_uuid = params.get("user_id")
        group_uuid = params.get("group_id")
        # we allow instructor and TA to submit multiple answers for other users in the class
        if user_uuid and user_uuid != answer.user_uuid and not allow(MANAGE, answer):
            abort(400, title="Answer Not Submitted", message="Only instructors and teaching assistants can submit an answer on behalf of another.")
        if group_uuid and not assignment.enable_group_answers:
            abort(400, title="Answer Not Submitted", message="Group answers are not allowed for this assignment.")
        if group_uuid and group_uuid != answer.group_uuid and not allow(MANAGE, answer):
            abort(400, title="Answer Not Submitted", message="Only instructors and teaching assistants can submit an answer on behalf of a group.")
        if group_uuid and user_uuid:
            abort(400, title="Answer Not Submitted", message="You cannot submit an answer for a user and a group at the same time.")

        user = User.get_by_uuid_or_404(user_uuid) if user_uuid else answer.user
        group = Group.get_active_by_uuid_or_404(group_uuid) if group_uuid else answer.group

        check_for_existing_answers = False
        if group and assignment.enable_group_answers:
            if group.course_id != course.id:
                abort(400, title="Answer Not Submitted",
                    message="Group answers can be submitted to courses they belong in.")

            answer.user_id = None
            answer.group_id = group.id
            answer.comparable = True
            check_for_existing_answers = True
        else:
            answer.user_id = user.id
            answer.group_id = None

            course_role = User.get_user_course_role(answer.user_id, course.id)

            # only system admin can add answers for themselves to a class without being enrolled in it
            # required for managing comparison examples as system admin
            if (not course_role or course_role == CourseRole.dropped) and current_user.system_role != SystemRole.sys_admin:
                abort(400, title="Answer Not Submitted", message="Answers can be submitted only by those enrolled in the course. Please double-check your enrollment in this course.")

            if course_role == CourseRole.student and assignment.enable_group_answers:
                abort(400, title="Answer Not Submitted", message="Students can only submit group answers for this assignment.")

            # we allow instructor and TA to submit multiple answers for their own,
            # but not for student. Each student can only have one answer.
            if course_role and course_role == CourseRole.student:
                check_for_existing_answers = True
                answer.comparable = True
            else:
                # instructor / TA / Sys Admin can mark the answer as non-comparable, unless the answer is for a student
                answer.comparable = params.get("comparable")

        if check_for_existing_answers:
            # check for answers with user_id or group_id
            prev_answers = Answer.query \
                .filter_by(
                    assignment_id=assignment.id,
                    user_id=answer.user_id,
                    group_id=answer.group_id,
                    active=True
                ) \
                .filter(Answer.id != answer.id) \
                .all()

            # check if there is a previous answer submitted for the student
            non_draft_answers = [prev_answer for prev_answer in prev_answers if not prev_answer.draft]
            if len(non_draft_answers) > 0:
                abort(400, title="Answer Not Submitted", message="An answer has already been submitted for this assignment by you or on your behalf.")

            # check if there is a previous draft answer submitted for the student (soft-delete if present)
            draft_answers = [prev_answer for prev_answer in prev_answers if prev_answer.draft]
            for draft_answer in draft_answers:
                draft_answer.active = False

        # can only change draft status while a draft
        if answer.draft:
            answer.draft = params.get("draft")

        file_uuid = params.get('file_id')
        attachment=None
        if file_uuid:
            attachment = File.get_by_uuid_or_404(file_uuid)
            answer.file_id = attachment.id
        else:
            answer.file_id = None

        # non-drafts must have content
        if not answer.draft and not answer.content and not file_uuid:
            abort(400, title="Answer Not Submitted", message="Please provide content in the text editor or upload a file and try submitting again.")

        # set submission date if answer is being submitted for the first time
        if not answer.draft and not answer.submission_date:
            answer.submission_date = datetime.datetime.utcnow()

        answer.update_attempt(
            params.get('attempt_uuid'),
            params.get('attempt_started', None),
            params.get('attempt_ended', None)
        )

        model_changes = get_model_changes(answer)
        db.session.add(answer)
        db.session.commit()

        on_answer_modified.send(
            self,
            event_name=on_answer_modified.name,
            user=current_user,
            course_id=course.id,
            answer=answer,
            data=model_changes)

        if old_file and (not attachment or old_file.id != attachment.id):
            on_detach_file.send(
                self,
                event_name=on_detach_file.name,
                user=current_user,
                course_id=course.id,
                file=old_file,
                answer=answer,
                data={'answer_id': answer.id, 'file_id': old_file.id})

        if attachment and (not old_file or old_file.id != attachment.id):
            on_attach_file.send(
                self,
                event_name=on_attach_file.name,
                user=current_user,
                course_id=course.id,
                file=attachment,
                data={'answer_id': answer.id, 'file_id': attachment.id})

        # update course & assignment grade for user if answer is fully submitted
        if not answer.draft:
            if answer.user:
                assignment.calculate_grade(answer.user)
                course.calculate_grade(answer.user)
            elif answer.group:
                assignment.calculate_group_grade(answer.group)
                course.calculate_group_grade(answer.group)

        return marshal(answer, dataformat.get_answer(restrict_user))
Beispiel #56
0
    def post(self, course_uuid, assignment_uuid, answer_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)

        if not assignment.answer_grace and not allow(MANAGE, assignment):
            abort(
                403,
                title="Answer Not Submitted",
                message=
                "Sorry, the answer deadline has passed. No answers can be submitted after the deadline unless the instructor submits the answer for you."
            )

        answer = Answer.get_active_by_uuid_or_404(answer_uuid)

        old_file = answer.file

        require(
            EDIT,
            answer,
            title="Answer Not Saved",
            message=
            "Sorry, your role in this course does not allow you to save this answer."
        )
        restrict_user = not allow(MANAGE, assignment)

        if current_app.config.get('DEMO_INSTALLATION', False):
            from data.fixtures import DemoDataFixture
            if assignment.id in DemoDataFixture.DEFAULT_ASSIGNMENT_IDS and answer.user_id in DemoDataFixture.DEFAULT_STUDENT_IDS:
                abort(
                    400,
                    title="Answer Not Saved",
                    message=
                    "Sorry, you cannot edit the default student demo answers.")

        params = existing_answer_parser.parse_args()
        # make sure the answer id in the url and the id matches
        if params['id'] != answer_uuid:
            abort(
                400,
                title="Answer Not Submitted",
                message=
                "The answer's ID does not match the URL, which is required in order to save the answer."
            )

        # modify answer according to new values, preserve original values if values not passed
        answer.content = params.get("content")

        user_uuid = params.get("user_id")
        group_uuid = params.get("group_id")
        # we allow instructor and TA to submit multiple answers for other users in the class
        if user_uuid and user_uuid != answer.user_uuid and not allow(
                MANAGE, answer):
            abort(
                400,
                title="Answer Not Submitted",
                message=
                "Only instructors and teaching assistants can submit an answer on behalf of another."
            )
        if group_uuid and not assignment.enable_group_answers:
            abort(400,
                  title="Answer Not Submitted",
                  message="Group answers are not allowed for this assignment.")
        if group_uuid and group_uuid != answer.group_uuid and not allow(
                MANAGE, answer):
            abort(
                400,
                title="Answer Not Submitted",
                message=
                "Only instructors and teaching assistants can submit an answer on behalf of a group."
            )
        if group_uuid and user_uuid:
            abort(
                400,
                title="Answer Not Submitted",
                message=
                "You cannot submit an answer for a user and a group at the same time."
            )

        user = User.get_by_uuid_or_404(user_uuid) if user_uuid else answer.user
        group = Group.get_active_by_uuid_or_404(
            group_uuid) if group_uuid else answer.group

        check_for_existing_answers = False
        if group and assignment.enable_group_answers:
            if group.course_id != course.id:
                abort(
                    400,
                    title="Answer Not Submitted",
                    message=
                    "Group answers can be submitted to courses they belong in."
                )

            answer.user_id = None
            answer.group_id = group.id
            answer.comparable = True
            check_for_existing_answers = True
        else:
            answer.user_id = user.id
            answer.group_id = None

            course_role = User.get_user_course_role(answer.user_id, course.id)

            # only system admin can add answers for themselves to a class without being enrolled in it
            # required for managing comparison examples as system admin
            if (not course_role or course_role == CourseRole.dropped
                ) and current_user.system_role != SystemRole.sys_admin:
                abort(
                    400,
                    title="Answer Not Submitted",
                    message=
                    "Answers can be submitted only by those enrolled in the course. Please double-check your enrollment in this course."
                )

            if course_role == CourseRole.student and assignment.enable_group_answers:
                abort(
                    400,
                    title="Answer Not Submitted",
                    message=
                    "Students can only submit group answers for this assignment."
                )

            # we allow instructor and TA to submit multiple answers for their own,
            # but not for student. Each student can only have one answer.
            if course_role and course_role == CourseRole.student:
                check_for_existing_answers = True
                answer.comparable = True
            else:
                # instructor / TA / Sys Admin can mark the answer as non-comparable, unless the answer is for a student
                answer.comparable = params.get("comparable")

        if check_for_existing_answers:
            # check for answers with user_id or group_id
            prev_answers = Answer.query \
                .filter_by(
                    assignment_id=assignment.id,
                    user_id=answer.user_id,
                    group_id=answer.group_id,
                    active=True
                ) \
                .filter(Answer.id != answer.id) \
                .all()

            # check if there is a previous answer submitted for the student
            non_draft_answers = [
                prev_answer for prev_answer in prev_answers
                if not prev_answer.draft
            ]
            if len(non_draft_answers) > 0:
                abort(
                    400,
                    title="Answer Not Submitted",
                    message=
                    "An answer has already been submitted for this assignment by you or on your behalf."
                )

            # check if there is a previous draft answer submitted for the student (soft-delete if present)
            draft_answers = [
                prev_answer for prev_answer in prev_answers
                if prev_answer.draft
            ]
            for draft_answer in draft_answers:
                draft_answer.active = False

        # can only change draft status while a draft
        if answer.draft:
            answer.draft = params.get("draft")

        file_uuid = params.get('file_id')
        attachment = None
        if file_uuid:
            attachment = File.get_by_uuid_or_404(file_uuid)
            answer.file_id = attachment.id
        else:
            answer.file_id = None

        # non-drafts must have content
        if not answer.draft and not answer.content and not file_uuid:
            abort(
                400,
                title="Answer Not Submitted",
                message=
                "Please provide content in the text editor or upload a file and try submitting again."
            )

        # set submission date if answer is being submitted for the first time
        if not answer.draft and not answer.submission_date:
            answer.submission_date = datetime.datetime.utcnow()

        answer.update_attempt(params.get('attempt_uuid'),
                              params.get('attempt_started', None),
                              params.get('attempt_ended', None))

        model_changes = get_model_changes(answer)
        db.session.add(answer)
        db.session.commit()

        on_answer_modified.send(self,
                                event_name=on_answer_modified.name,
                                user=current_user,
                                course_id=course.id,
                                answer=answer,
                                data=model_changes)

        if old_file and (not attachment or old_file.id != attachment.id):
            on_detach_file.send(self,
                                event_name=on_detach_file.name,
                                user=current_user,
                                course_id=course.id,
                                file=old_file,
                                answer=answer,
                                data={
                                    'answer_id': answer.id,
                                    'file_id': old_file.id
                                })

        if attachment and (not old_file or old_file.id != attachment.id):
            on_attach_file.send(self,
                                event_name=on_attach_file.name,
                                user=current_user,
                                course_id=course.id,
                                file=attachment,
                                data={
                                    'answer_id': answer.id,
                                    'file_id': attachment.id
                                })

        # update course & assignment grade for user if answer is fully submitted
        if not answer.draft:
            if answer.user:
                assignment.calculate_grade(answer.user)
                course.calculate_grade(answer.user)
            elif answer.group:
                assignment.calculate_group_grade(answer.group)
                course.calculate_group_grade(answer.group)

        return marshal(answer, dataformat.get_answer(restrict_user))
Beispiel #57
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
        }
Beispiel #58
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_full_user()))

        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_full_user())
Beispiel #59
0
    def get(self, user_uuid):
        user = User.get_by_uuid_or_404(user_uuid)

        require(MANAGE, User,
            title="User's Courses Unavailable",
            message="Sorry, your system role does not allow you to view courses for this user.")

        params = user_id_course_list_parser.parse_args()

        query = Course.query \
            .with_entities(Course, UserCourse) \
            .options(joinedload(UserCourse.group)) \
            .join(UserCourse, and_(
                UserCourse.course_id == Course.id,
                UserCourse.user_id == user.id,
            )) \
            .filter(and_(
                Course.active == True,
                UserCourse.course_role != CourseRole.dropped
            ))

        if params['search']:
            search_terms = params['search'].split()
            for search_term in search_terms:
                if search_term != "":
                    search = '%'+search_term+'%'
                    query = query.filter(or_(
                        Course.name.like(search),
                        Course.year.like(search),
                        Course.term.like(search)
                    ))

        if params['includeSandbox'] != None:
            query = query.filter(
                Course.sandbox == params['includeSandbox']
            )

        if params['orderBy']:
            if params['reverse']:
                query = query.order_by(desc(params['orderBy']))
            else:
                query = query.order_by(asc(params['orderBy']))
        query = query.order_by(Course.start_date_order.desc(), Course.name)

        page = query.paginate(params['page'], params['perPage'])

        # fix results
        courses = []
        for (_course, _user_course) in page.items:
            _course.course_role = _user_course.course_role
            _course.group = _user_course.group
            _course.group_uuid = _course.group.uuid if _course.group else None
            courses.append(_course)
        page.items = courses

        on_user_course_get.send(
            self,
            event_name=on_user_course_get.name,
            user=user)

        return {"objects": marshal(page.items, dataformat.get_user_courses()),
                "page": page.page, "pages": page.pages,
                "total": page.total, "per_page": page.per_page}