Exemple #1
0
    def get(self):
        require(READ, USER_IDENTITY,
            title="User List Unavailable",
            message="Sorry, your system role does not allow you to view the list of users.")

        params = user_list_parser.parse_args()

        query = User.query
        if params['search']:
            # match each word of search
            for word in params['search'].strip().split(' '):
                if word != '':
                    search = '%'+word+'%'
                    query = query.filter(or_(
                        User.firstname.like(search),
                        User.lastname.like(search),
                        User.displayname.like(search)
                    ))

        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(User.lastname.asc(), User.firstname.asc())

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

        on_user_list_get.send(
            self,
            event_name=on_user_list_get.name,
            user=current_user)

        return {"objects": marshal(page.items, dataformat.get_user(False)), "page": page.page,
                "pages": page.pages, "total": page.total, "per_page": page.per_page}
Exemple #2
0
    def get(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)

        answer = Answer.get_active_by_uuid_or_404(
            answer_uuid, joinedloads=['file', 'user', 'group', 'score'])
        require(
            READ,
            answer,
            title="Answer Unavailable",
            message=
            "Sorry, your role in this course does not allow you to view this answer."
        )
        restrict_user = not allow(MANAGE, assignment)

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

        # don't include score/rank unless the user is non-restricted
        include_score = not restrict_user

        return marshal(
            answer,
            dataformat.get_answer(restrict_user, include_score=include_score))
Exemple #3
0
    def get(self, course_uuid, group_name):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        user_course = UserCourse(course_id=course.id)
        require(READ, user_course)

        members = User.query \
            .join(UserCourse, UserCourse.user_id == User.id) \
            .filter(and_(
                UserCourse.course_id == course.id,
                UserCourse.course_role != CourseRole.dropped,
                UserCourse.group_name == group_name
            )) \
            .all()

        if len(members) == 0:
            abort(404)

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

        return {'students': [{'id': u.uuid, 'name': u.fullname} for u in members]}
Exemple #4
0
    def delete(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)
        answer = Answer.get_active_by_uuid_or_404(answer_uuid)
        require(DELETE, answer)

        answer.active = False
        if answer.file:
            answer.file.active = False
        db.session.commit()

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

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

        return {'id': answer.uuid}
Exemple #5
0
    def get(self):
        require(MANAGE, LTIConsumer,
            title="Consumers Unavailable",
            message="Sorry, your system role does not allow you to view LTI consumers.")

        params = consumer_list_parser.parse_args()

        query = LTIConsumer.query

        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(LTIConsumer.created)

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

        on_consumer_list_get.send(
            self,
            event_name=on_consumer_list_get.name,
            user=current_user)

        return {'objects': marshal(page.items, dataformat.get_lti_consumer()),
            "page": page.page, "pages": page.pages, "total": page.total, "per_page": page.per_page}
Exemple #6
0
    def post(self):
        params = new_consumer_parser.parse_args()

        consumer = LTIConsumer()
        require(CREATE, consumer,
            title="Consumer Not Saved",
            message="Sorry, your system role does not allow you to save LTI consumers.")

        consumer.oauth_consumer_key = params.get("oauth_consumer_key")
        consumer.oauth_consumer_secret = params.get("oauth_consumer_secret")
        consumer.global_unique_identifier_param = params.get("global_unique_identifier_param")
        consumer.student_number_param = params.get("student_number_param")

        try:
            db.session.add(consumer)
            db.session.commit()
            on_consumer_create.send(
                self,
                event_name=on_consumer_create.name,
                user=current_user,
                consumer=consumer,
                data={'consumer': marshal(consumer, dataformat.get_lti_consumer())}
            )
        except exc.IntegrityError:
            db.session.rollback()
            abort(409, title="Consumer Not Saved", message="An LTI consumer with the same consumer key already exists. Please double-check the consumer key and try saving again.")

        return marshal(consumer, dataformat.get_lti_consumer(include_sensitive=True))
Exemple #7
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}
Exemple #8
0
    def post(self, consumer_uuid):
        consumer = LTIConsumer.get_by_uuid_or_404(consumer_uuid)
        require(EDIT, consumer,
            title="Consumer Not Saved",
            message="Sorry, your system role does not allow you to save LTI consumers.")

        params = existing_consumer_parser.parse_args()

        # make sure the course id in the url and the course id in the params match
        if params['id'] != consumer_uuid:
            abort(400, title="Consumer Not Saved", message="The LTI consumer ID does not match the URL, which is required in order to save the LTI consumer.")

        consumer.oauth_consumer_key = params.get("oauth_consumer_key")
        consumer.oauth_consumer_secret = params.get("oauth_consumer_secret")
        consumer.global_unique_identifier_param = params.get("global_unique_identifier_param")
        consumer.student_number_param = params.get("student_number_param")
        consumer.active = params.get("active")

        try:
            db.session.commit()
            on_consumer_update.send(
                self,
                event_name=on_consumer_update.name,
                user=current_user,
                consumer=consumer
            )
        except exc.IntegrityError:
            db.session.rollback()
            abort(409, title="Consumer Not Saved", message="An LTI consumer with the same consumer key already exists. Please double-check the consumer key and try saving again.")

        return marshal(consumer, dataformat.get_lti_consumer(include_sensitive=True))
Exemple #9
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,
            assignment,
            title="Help Comments Unavailable",
            message=
            "Help comments can be seen only by those enrolled in the course. Please double-check your enrollment in this course."
        )
        restrict_user = not allow(MANAGE, assignment)

        assignment_comments = AssignmentComment.query \
            .filter_by(
                course_id=course.id,
                assignment_id=assignment.id,
                active=True
            ) \
            .order_by(AssignmentComment.created.asc()).all()

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

        return {
            "objects":
            marshal(assignment_comments,
                    dataformat.get_assignment_comment(restrict_user))
        }
Exemple #10
0
    def delete(self, course_uuid, assignment_uuid, assignment_comment_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        assignment_comment = AssignmentComment.get_active_by_uuid_or_404(
            assignment_comment_uuid)
        require(
            DELETE,
            assignment_comment,
            title="Help Comment Not Deleted",
            message=
            "Sorry, your role in this course does not allow you to delete help comments."
        )

        data = marshal(assignment_comment,
                       dataformat.get_assignment_comment(False))
        assignment_comment.active = False
        db.session.commit()

        on_assignment_comment_delete.send(
            self,
            event_name=on_assignment_comment_delete.name,
            user=current_user,
            course_id=course.id,
            assignment_comment=assignment_comment,
            data=data)

        return {'id': assignment_comment.uuid}
Exemple #11
0
    def get(self, course_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        require(
            READ,
            course,
            title="Instructors Unavailable",
            message=
            "Instructors can only be seen here by those enrolled in the course. Please double-check your enrollment in this course."
        )

        instructors = UserCourse.query \
            .filter(and_(
                UserCourse.course_id==course.id,
                UserCourse.course_role.in_([CourseRole.teaching_assistant, CourseRole.instructor])
            )) \
            .all()
        instructor_uuids = {
            u.user_uuid: u.course_role.value
            for u in instructors
        }

        on_classlist_instructor_label.send(
            self,
            event_name=on_classlist_instructor_label.name,
            user=current_user,
            course_id=course.id)

        return {'instructors': instructor_uuids}
Exemple #12
0
    def post(self, course_uuid, assignment_uuid, answer_uuid):
        """
        Mark an answer as being a top answer
        :param course_uuid:
        :param assignment_uuid:
        :param answer_uuid:
        :return: marked 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(MANAGE, answer)

        params = top_answer_parser.parse_args()
        answer.top_answer = params.get('top_answer')
        db.session.add(answer)

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

        db.session.commit()
        return marshal(answer, dataformat.get_answer(restrict_user=False))
Exemple #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)
Exemple #14
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
Exemple #15
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,
            ComparisonExample(course_id=course.id),
            title="Comparison Example Unavailable",
            message=
            "Sorry, your role in this course does not allow you to view practice answers."
        )

        # Get all comparison examples for this assignment
        comparison_examples = ComparisonExample.query \
            .filter_by(
                active=True,
                assignment_id=assignment.id
            ) \
            .all()

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

        return {
            "objects":
            marshal(comparison_examples, dataformat.get_comparison_example())
        }
Exemple #16
0
    def post(self, course_uuid, assignment_uuid, answer_uuid):
        """
        Mark an answer as being a top answer
        :param course_uuid:
        :param assignment_uuid:
        :param answer_uuid:
        :return: marked 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(MANAGE, answer,
            title="Answer Not Added",
            message="Your role in this course does not allow you to add to the list of instructor-picked answers.")

        params = top_answer_parser.parse_args()
        answer.top_answer = params.get('top_answer')
        db.session.add(answer)

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

        db.session.commit()

        return marshal(answer, dataformat.get_answer(restrict_user=False))
Exemple #17
0
    def delete(self, course_uuid, assignment_uuid, comparison_example_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        comparison_example = ComparisonExample.get_active_by_uuid_or_404(
            comparison_example_uuid)
        require(
            DELETE,
            comparison_example,
            title="Comparison Example Not Deleted",
            message=
            "Sorry, your role in this course does not allow you to delete practice answers."
        )

        formatted_comparison_example = marshal(
            comparison_example,
            dataformat.get_comparison_example(with_answers=False))

        comparison_example.active = False
        db.session.add(comparison_example)
        db.session.commit()

        on_comparison_example_delete.send(
            self,
            event_name=on_comparison_example_delete.name,
            user=current_user,
            course_id=course.id,
            data=formatted_comparison_example)

        return {'id': comparison_example.uuid}
Exemple #18
0
    def delete(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)
        answer = Answer.get_active_by_uuid_or_404(answer_uuid)
        require(DELETE, answer,
            title="Answer Not Deleted",
            message="Sorry, your role in this course does not allow you to delete this answer.")

        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 Deleted", message="Sorry, you cannot delete the default student demo answers.")

        answer.active = False
        db.session.commit()

        # update course & assignment grade for user if answer was 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)

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

        return {'id': answer.uuid}
Exemple #19
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}
Exemple #20
0
    def delete(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(DELETE, assignment,
            title="Assignment Not Deleted",
            message="Sorry, your role in this course does not allow you to delete assignments.")

        if current_app.config.get('DEMO_INSTALLATION', False):
            from data.fixtures import DemoDataFixture
            if assignment.id in DemoDataFixture.DEFAULT_ASSIGNMENT_IDS:
                abort(400, title="Assignment Not Deleted", message="Sorry, you cannot remove the default demo assignments.")

        formatted_assignment = marshal(assignment, dataformat.get_assignment(False))
        # delete file when assignment is deleted
        assignment.active = False
        assignment.clear_lti_links()
        db.session.commit()

        # update course grades
        course.calculate_grades()

        on_assignment_delete.send(
            self,
            event_name=on_assignment_delete.name,
            user=current_user,
            course_id=course.id,
            assignment=assignment,
            data=formatted_assignment)

        return {'id': assignment.uuid}
Exemple #21
0
    def get(self):
        require(READ, USER_IDENTITY,
            title="User List Unavailable",
            message="Sorry, your system role does not allow you to view the list of users.")

        params = user_list_parser.parse_args()

        query = User.query
        if params['search']:
            # match each word of search
            for word in params['search'].strip().split(' '):
                if word != '':
                    search = '%'+word+'%'
                    query = query.filter(or_(
                        User.firstname.like(search),
                        User.lastname.like(search),
                        User.displayname.like(search)
                    ))

        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(User.lastname.asc(), User.firstname.asc())

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

        on_user_list_get.send(
            self,
            event_name=on_user_list_get.name,
            user=current_user)

        return {"objects": marshal(page.items, dataformat.get_user(False)), "page": page.page,
                "pages": page.pages, "total": page.total, "per_page": page.per_page}
Exemple #22
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)
Exemple #23
0
    def delete(self, course_uuid, assignment_uuid, answer_uuid, answer_comment_uuid):
        """
        Delete an answer comment
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        answer_comment = AnswerComment.get_active_by_uuid_or_404(answer_comment_uuid)
        require(DELETE, answer_comment)

        data = marshal(answer_comment, dataformat.get_answer_comment(False))
        answer_comment.active = False
        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_delete.send(
            self,
            event_name=on_answer_comment_delete.name,
            user=current_user,
            course_id=course.id,
            answer_comment=answer_comment,
            data=data,
        )

        return {"id": answer_comment.uuid}
Exemple #24
0
    def get(self, course_uuid):
        """
        refresh the course membership if able
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        require(EDIT, course)

        if not course.lti_linked:
            return {"error": "Course not linked to lti context"}, 400

        valid_membership_contexts = [
            lti_context
            for lti_context in course.lti_contexts
            if lti_context.ext_ims_lis_memberships_url and lti_context.ext_ims_lis_memberships_id
        ]

        pending = 0
        enabled = len(valid_membership_contexts) > 0
        if enabled:
            lti_context_ids = [lti_context.id for lti_context in valid_membership_contexts]

            pending = (
                LTIMembership.query.join(LTIUser)
                .filter(and_(LTIUser.compair_user_id == None, LTIMembership.lti_context_id.in_(lti_context_ids)))
                .count()
            )

        status = {"enabled": enabled, "pending": pending}

        on_lti_course_membership_status_get.send(
            self, event_name=on_lti_course_membership_status_get.name, user=current_user, data={"course_id": course.id}
        )

        return {"status": status}
Exemple #25
0
    def post(self, course_uuid):
        """
        refresh the course membership if able
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        require(EDIT, course)

        if not course.lti_linked:
            return {"error": "Course not linked to lti context"}, 400

        try:
            LTIMembership.update_membership_for_course(course)
        except MembershipNoValidContextsException as err:
            return {"error": "LTI membership service is not supported for this course"}, 400
        except MembershipNoResultsException as err:
            return {"error": "LTI membership service did not return any users"}, 400
        except MembershipInvalidRequestException as err:
            return (
                {
                    "error": "LTI membership request was invalid. Please relaunch the ComPAIR course from the LTI consumer and try again"
                },
                400,
            )

        on_lti_course_membership_update.send(
            self, event_name=on_lti_course_membership_update.name, user=current_user, data={"course_id": course.id}
        )

        return {"imported": True}
Exemple #26
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
Exemple #27
0
    def post(self, course_uuid, assignment_uuid, comparison_example_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        comparison_example = ComparisonExample.get_active_by_uuid_or_404(comparison_example_uuid)
        require(EDIT, comparison_example)

        params = existing_comparison_example_parser.parse_args()
        answer1_uuid = params.get("answer1_id")
        answer2_uuid = params.get("answer2_id")

        if answer1_uuid:
            answer1 = Answer.get_active_by_uuid_or_404(answer1_uuid)
            answer1.practice = True
            comparison_example.answer1 = answer1
        else:
            return {"error": "Comparison examples must have 2 answers"}, 400

        if answer2_uuid:
            answer2 = Answer.get_active_by_uuid_or_404(answer2_uuid)
            answer2.practice = True
            comparison_example.answer2 = answer2
        else:
            return {"error": "Comparison examples must have 2 answers"}, 400

        on_comparison_example_modified.send(
            self,
            event_name=on_comparison_example_modified.name,
            user=current_user,
            course_id=course.id,
            data=get_model_changes(comparison_example))

        db.session.add(comparison_example)
        db.session.commit()

        return marshal(comparison_example, dataformat.get_comparison_example())
Exemple #28
0
    def post(self, course_uuid, assignment_uuid, assignment_comment_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        assignment_comment = AssignmentComment.get_active_by_uuid_or_404(assignment_comment_uuid)
        require(EDIT, assignment_comment)

        params = existing_assignment_comment_parser.parse_args()
        # make sure the comment id in the rul and the id matches
        if params['id'] != assignment_comment_uuid:
            return {"error": "Comment id does not match URL."}, 400

        # modify comment according to new values, preserve original values if values not passed
        if not params.get("content"):
            return {"error": "The comment content is empty!"}, 400

        assignment_comment.content = params.get("content")
        db.session.add(assignment_comment)

        on_assignment_comment_modified.send(
            self,
            event_name=on_assignment_comment_modified.name,
            user=current_user,
            course_id=course.id,
            assignment_comment=assignment_comment,
            data=get_model_changes(assignment_comment))

        db.session.commit()
        return marshal(assignment_comment, dataformat.get_assignment_comment())
Exemple #29
0
    def post(self):
        params = new_consumer_parser.parse_args()

        consumer = LTIConsumer()
        require(CREATE, consumer,
            title="Consumer Not Saved",
            message="Sorry, your system role does not allow you to save LTI consumers.")

        consumer.oauth_consumer_key = params.get("oauth_consumer_key")
        consumer.oauth_consumer_secret = params.get("oauth_consumer_secret")
        consumer.global_unique_identifier_param = params.get("global_unique_identifier_param")
        consumer.student_number_param = params.get("student_number_param")
        consumer.custom_param_regex_sanitizer = params.get("custom_param_regex_sanitizer")

        try:
            db.session.add(consumer)
            db.session.commit()
            on_consumer_create.send(
                self,
                event_name=on_consumer_create.name,
                user=current_user,
                consumer=consumer,
                data={'consumer': marshal(consumer, dataformat.get_lti_consumer())}
            )
        except exc.IntegrityError:
            db.session.rollback()
            abort(409, title="Consumer Not Saved", message="An LTI consumer with the same consumer key already exists. Please double-check the consumer key and try saving again.")

        return marshal(consumer, dataformat.get_lti_consumer(include_sensitive=True))
Exemple #30
0
    def get(self, course_uuid, assignment_uuid, answer_uuid,
            answer_comment_uuid):
        """
        Get an answer comment
        """
        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)
        answer_comment = AnswerComment.get_active_by_uuid_or_404(
            answer_comment_uuid)
        require(
            READ,
            answer_comment,
            title="Reply Unavailable",
            message=
            "Sorry, your role in this course does not allow you to view this reply."
        )

        on_answer_comment_get.send(self,
                                   event_name=on_answer_comment_get.name,
                                   user=current_user,
                                   course_id=course.id,
                                   data={
                                       'assignment_id': assignment.id,
                                       'answer_id': answer.id,
                                       'answer_comment_id': answer_comment.id
                                   })

        return marshal(answer_comment, dataformat.get_answer_comment())
Exemple #31
0
    def post(self, consumer_uuid):
        consumer = LTIConsumer.get_by_uuid_or_404(consumer_uuid)
        require(EDIT, consumer,
            title="Consumer Not Saved",
            message="Sorry, your system role does not allow you to save LTI consumers.")

        params = existing_consumer_parser.parse_args()

        # make sure the course id in the url and the course id in the params match
        if params['id'] != consumer_uuid:
            abort(400, title="Consumer Not Saved", message="The LTI consumer ID does not match the URL, which is required in order to save the LTI consumer.")

        consumer.oauth_consumer_key = params.get("oauth_consumer_key")
        consumer.oauth_consumer_secret = params.get("oauth_consumer_secret")
        consumer.global_unique_identifier_param = params.get("global_unique_identifier_param")
        consumer.student_number_param = params.get("student_number_param")
        consumer.custom_param_regex_sanitizer = params.get("custom_param_regex_sanitizer")
        consumer.active = params.get("active")

        try:
            db.session.commit()
            on_consumer_update.send(
                self,
                event_name=on_consumer_update.name,
                user=current_user,
                consumer=consumer
            )
        except exc.IntegrityError:
            db.session.rollback()
            abort(409, title="Consumer Not Saved", message="An LTI consumer with the same consumer key already exists. Please double-check the consumer key and try saving again.")

        return marshal(consumer, dataformat.get_lti_consumer(include_sensitive=True))
Exemple #32
0
    def delete(self, course_uuid, lti_context_uuid):
        """
        unlink lti context from course
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        lti_context = LTIContext.get_by_uuid_or_404(lti_context_uuid)
        require(DELETE, lti_context,
            title="Course Not Unlinked",
            message="Sorry, your system role does not allow you to unlink LTI courses.")

        if lti_context.compair_course_id != course.id:
            abort(400, title="Course Not Unlinked", message="Sorry, The LTI context is already not linked to the course.")

        lti_context.compair_course_id = None
        db.session.commit()

        # automatically refresh membership if it was enabled for the removed context
        if lti_context.membership_enabled:
            update_lti_course_membership.delay(course.id)

        on_lti_course_unlink.send(
            self,
            event_name=on_lti_course_unlink.name,
            user=current_user,
            data={ 'course_id': course.id, 'lti_context_id': lti_context.id })

        return { 'success': True }
Exemple #33
0
    def delete(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(DELETE, assignment,
            title="Assignment Not Deleted",
            message="Sorry, your role in this course does not allow you to delete assignments.")

        if current_app.config.get('DEMO_INSTALLATION', False):
            from data.fixtures import DemoDataFixture
            if assignment.id in DemoDataFixture.DEFAULT_ASSIGNMENT_IDS:
                abort(400, title="Assignment Not Deleted", message="Sorry, you cannot remove the default demo assignments.")

        formatted_assignment = marshal(assignment, dataformat.get_assignment(False))
        # delete file when assignment is deleted
        assignment.active = False
        assignment.clear_lti_links()
        db.session.commit()

        # update course grades
        course.calculate_grades()

        on_assignment_delete.send(
            self,
            event_name=on_assignment_delete.name,
            user=current_user,
            course_id=course.id,
            assignment=assignment,
            data=formatted_assignment)

        return {'id': assignment.uuid}
Exemple #34
0
    def get(self, course_uuid, group_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        group = Group.get_active_by_uuid_or_404(group_uuid)

        require(
            READ,
            UserCourse(course_id=course.id),
            title="Group Members Unavailable",
            message=
            "Group membership can be seen only by those enrolled in the course. Please double-check your enrollment in this course."
        )

        members = User.query \
            .join(UserCourse, UserCourse.user_id == User.id) \
            .filter(and_(
                UserCourse.course_id == course.id,
                UserCourse.course_role != CourseRole.dropped,
                UserCourse.group_id == group.id
            )) \
            .order_by(User.lastname, User.firstname) \
            .all()

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

        return {
            'objects': [{
                'id': u.uuid,
                'name': u.fullname_sortable
            } for u in members]
        }
Exemple #35
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)
        require(CREATE, AssignmentComment(course_id=course.id))

        new_assignment_comment = AssignmentComment(assignment_id=assignment.id)

        params = new_assignment_comment_parser.parse_args()

        new_assignment_comment.content = params.get("content")
        if not new_assignment_comment.content:
            return {"error": "The comment content is empty!"}, 400

        new_assignment_comment.user_id = current_user.id

        db.session.add(new_assignment_comment)
        db.session.commit()

        on_assignment_comment_create.send(
            self,
            event_name=on_assignment_comment_create.name,
            user=current_user,
            course_id=course.id,
            assignment_comment=new_assignment_comment,
            data=marshal(new_assignment_comment, dataformat.get_assignment_comment(False)))

        return marshal(new_assignment_comment, dataformat.get_assignment_comment())
Exemple #36
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())
        }
Exemple #37
0
    def delete(self, course_uuid, assignment_uuid, answer_uuid,
               answer_comment_uuid):
        """
        Delete an answer comment
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        answer_comment = AnswerComment.get_active_by_uuid_or_404(
            answer_comment_uuid)
        require(
            DELETE,
            answer_comment,
            title="Reply Not Deleted",
            message=
            "Sorry, your role in this course does not allow you to delete replies for this answer."
        )

        data = marshal(answer_comment, dataformat.get_answer_comment(False))
        answer_comment.active = False
        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_delete.send(self,
                                      event_name=on_answer_comment_delete.name,
                                      user=current_user,
                                      course_id=course.id,
                                      answer_comment=answer_comment,
                                      data=data)

        return {'id': answer_comment.uuid}
Exemple #38
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)))
Exemple #39
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}
Exemple #40
0
    def post(self, course_uuid):
        """
        link current session's lti context with a course
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        require(EDIT, course,
            title="Course Not Linked",
            message="Sorry, you do not have permission to link this course since you are not enrolled as an instructor in the course.")

        if not sess.get('LTI'):
            abort(400, title="Course Not Linked",
                message="Sorry, your LTI session has expired. Please log in via LTI and try linking again.")

        if not sess.get('lti_context'):
            abort(400, title="Course Not Linked",
                message="Sorry, your LTI link settings have no course context. Please edit your LTI link settings and try linking again.")

        lti_context = LTIContext.query.get_or_404(sess.get('lti_context'))
        lti_context.compair_course_id = course.id
        db.session.commit()

        # automatically fetch membership if enabled for context
        if lti_context.membership_enabled:
            update_lti_course_membership.delay(course.id)

        on_lti_course_link_create.send(
            self,
            event_name=on_lti_course_link_create.name,
            user=current_user,
            data={ 'course_id': course.id, 'lti_context_id': lti_context.id })

        return { 'success': True }
Exemple #41
0
    def post(self, course_uuid):
        """
        refresh the course membership if able
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        require(EDIT, course,
            title="Membership Not Updated",
            message="Sorry, your role in this course does not allow you to update membership.")

        if not course.lti_linked:
            abort(400, title="Membership Not Updated",
                message="Sorry, your LTI link settings have no course context. Please edit your LTI link settings and try linking again.")

        try:
            LTIMembership.update_membership_for_course(course)
        except MembershipNoValidContextsException as err:
            abort(400, title="Membership Not Updated",
                message="The LTI link does not support the membership extension. Please edit your LTI link settings or contact your system administrator and try again.")
        except MembershipNoResultsException as err:
            abort(400, title="Membership Not Updated",
                message="The membership service did not return any users. Please check your LTI course and try again.")
        except MembershipInvalidRequestException as err:
            abort(400, title="Membership Not Updated",
                message="The membership request was invalid. Please relaunch the LTI link and try again.")

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

        return { 'imported': True }
Exemple #42
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())}
Exemple #43
0
    def post(self, course_uuid):
        """
        link current session's lti context with a course
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        require(EDIT, course)

        if not sess.get("LTI"):
            return {"error": "Your LTI session has expired."}, 404

        if not sess.get("lti_context"):
            return {"error": "Your LTI session has no context."}, 404

        lti_context = LTIContext.query.get_or_404(sess.get("lti_context"))
        lti_context.compair_course_id = course.id
        db.session.commit()

        # automatically fetch membership if enabled for context
        if lti_context.ext_ims_lis_memberships_url and lti_context.ext_ims_lis_memberships_id:
            update_lti_course_membership.delay(course.id)

        on_lti_course_link.send(
            self,
            event_name=on_lti_course_link.name,
            user=current_user,
            data={"course_id": course.id, "lti_context_id": lti_context.id},
        )

        return {"success": True}
Exemple #44
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}
Exemple #45
0
    def post(self, course_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        require(
            EDIT,
            course,
            title="Course Not Saved",
            message=
            "Sorry, your role in this course does not allow you to save changes to it."
        )

        if current_app.config.get('DEMO_INSTALLATION', False):
            from data.fixtures import DemoDataFixture
            if course.id == DemoDataFixture.DEFAULT_COURSE_ID:
                abort(
                    400,
                    title="Course Not Updated",
                    message="Sorry, you cannot edit the default demo course.")

        params = existing_course_parser.parse_args()

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

        # modify course according to new values, preserve original values if values not passed
        course.name = params.get("name", course.name)
        course.year = params.get("year", course.year)
        course.term = params.get("term", course.term)
        course.sandbox = params.get("sandbox", course.sandbox)

        course.start_date = params.get("start_date")
        if course.start_date is not None:
            course.start_date = datetime.datetime.strptime(
                course.start_date, '%Y-%m-%dT%H:%M:%S.%fZ')

        course.end_date = params.get("end_date", None)
        if course.end_date is not None:
            course.end_date = datetime.datetime.strptime(
                course.end_date, '%Y-%m-%dT%H:%M:%S.%fZ')

        if course.start_date and course.end_date and course.start_date > course.end_date:
            abort(400,
                  title="Course Not Saved",
                  message="Course end time must be after course start time.")

        model_changes = get_model_changes(course)
        db.session.commit()

        on_course_modified.send(self,
                                event_name=on_course_modified.name,
                                user=current_user,
                                course=course,
                                data=model_changes)

        return marshal(course, dataformat.get_course())
Exemple #46
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}
Exemple #47
0
    def delete(self, course_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        require(
            DELETE,
            course,
            title="Course Not Deleted",
            message=
            "Sorry, your role in this course does not allow you to delete it.")

        if current_app.config.get('DEMO_INSTALLATION', False):
            from data.fixtures import DemoDataFixture
            if course.id == DemoDataFixture.DEFAULT_COURSE_ID:
                abort(
                    400,
                    title="Course Not Deleted",
                    message="Sorry, you cannot remove the default demo course."
                )

        course.active = False
        course.clear_lti_links()
        db.session.commit()

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

        return {'id': course.uuid}
Exemple #48
0
    def delete(self, course_uuid, assignment_uuid, answer_uuid, answer_comment_uuid):
        """
        Delete an answer comment
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        answer_comment = AnswerComment.get_active_by_uuid_or_404(answer_comment_uuid)
        require(DELETE, answer_comment,
            title="Feedback Not Deleted",
            message="Sorry, your role in this course does not allow you to delete feedback for this answer.")

        data = marshal(answer_comment, dataformat.get_answer_comment(False))
        answer_comment.active = False
        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_delete.send(
            self,
            event_name=on_answer_comment_delete.name,
            user=current_user,
            course_id=course.id,
            answer_comment=answer_comment,
            data=data)

        return {'id': answer_comment.uuid}
Exemple #49
0
    def get(self):
        require(MANAGE, LTIConsumer,
            title="Consumers Unavailable",
            message="Sorry, your system role does not allow you to view LTI consumers.")

        params = consumer_list_parser.parse_args()

        query = LTIConsumer.query

        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(LTIConsumer.created)

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

        on_consumer_list_get.send(
            self,
            event_name=on_consumer_list_get.name,
            user=current_user)

        return {'objects': marshal(page.items, dataformat.get_lti_consumer()),
            "page": page.page, "pages": page.pages, "total": page.total, "per_page": page.per_page}
Exemple #50
0
    def get(self, course_uuid, assignment_uuid):
        # course unused, but we need to call it to check if it's a valid course
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)

        # permission checks
        require(
            MANAGE,
            assignment,
            title="Unable to download attachments",
            message=
            "Sorry, your system role does not allow downloading all attachments"
        )

        # grab answers so we can see how many has files
        answers = self.getStudentAnswersByAssignment(assignment)
        fileIds = []
        fileAuthors = {}
        for answer in answers:
            if not answer.file_id:
                continue
            # answer has an attachment
            fileIds.append(answer.file_id)
            # the user who uploaded the file can be different from the answer
            # author (e.g. instructor can upload on behalf of student), so
            # we need to use the answer author instead of file uploader
            author = answer.user_fullname
            if answer.user_student_number:
                author += self.DELIM + answer.user_student_number
            fileAuthors[answer.file_id] = author

        if not fileIds:
            return {'msg': 'Assignment has no attachments'}

        # grab files so we can get the real file location
        files = self.getFilesByIds(fileIds)

        # zip up the tmp dir and save it to the report dir
        zipName = '{} [attachments-{}].zip'.format(assignment.name,
                                                   assignment.uuid)
        zipPath = '{}/{}'.format(current_app.config['REPORT_FOLDER'], zipName)
        # we're using compression level 6 as a compromise between speed &
        # compression (this is Z_DEFAULT_COMPRESSION in zlib)
        with zipfile.ZipFile(zipPath, 'w', zipfile.ZIP_DEFLATED, True,
                             6) as zipFile:
            for srcFile in files:
                srcFilePath = '{}/{}'.format(
                    current_app.config['ATTACHMENT_UPLOAD_FOLDER'],
                    srcFile.name)
                # filename should be 'full name - student number - uuid.ext'
                # student number is omitted if user doesn't have one
                srcFileName = fileAuthors[
                    srcFile.id] + self.DELIM + srcFile.name
                #current_app.logger.debug("writing " + srcFileName)
                zipFile.write(srcFilePath, srcFileName)
        #current_app.logger.debug("Writing zip file")

        return {'file': 'report/' + zipName, 'filename': zipName}
Exemple #51
0
    def post(self):
        """
        Create new course
        """
        require(CREATE, Course,
            title="Course Not Saved",
            message="Sorry, your role in the system does not allow you to save courses.")
        params = new_course_parser.parse_args()

        new_course = Course(
            name=params.get("name"),
            year=params.get("year"),
            term=params.get("term"),
            sandbox=params.get("sandbox"),
            start_date=params.get('start_date'),
            end_date=params.get('end_date', None)
        )
        if new_course.start_date is not None:
            new_course.start_date = datetime.datetime.strptime(
                new_course.start_date,
                '%Y-%m-%dT%H:%M:%S.%fZ')

        if new_course.end_date is not None:
            new_course.end_date = datetime.datetime.strptime(
                new_course.end_date,
                '%Y-%m-%dT%H:%M:%S.%fZ')

        if new_course.start_date and new_course.end_date and new_course.start_date > new_course.end_date:
            abort(400, title="Course Not Saved", message="Course end time must be after course start time.")

        try:
            # create the course
            db.session.add(new_course)
            # also need to enrol the user as an instructor
            new_user_course = UserCourse(
                course=new_course,
                user_id=current_user.id,
                course_role=CourseRole.instructor
            )
            db.session.add(new_user_course)

            db.session.commit()

        except exc.SQLAlchemyError as e:
            db.session.rollback()
            current_app.logger.error("Failed to add new course. " + str(e))
            raise

        on_course_create.send(
            self,
            event_name=on_course_create.name,
            user=current_user,
            course=new_course,
            data=marshal(new_course, dataformat.get_course()))

        return marshal(new_course, dataformat.get_course())
Exemple #52
0
    def get(self, course_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        require(READ, course)

        on_course_get.send(
            self,
            event_name=on_course_get.name,
            user=current_user,
            data={'id': course.id})
        return marshal(course, dataformat.get_course())
Exemple #53
0
    def file_retrieve(file_type, file_name):
        file_dirs = {
            'attachment': app.config['ATTACHMENT_UPLOAD_FOLDER'],
            'report': app.config['REPORT_FOLDER']
        }
        file_path = '{}/{}'.format(file_dirs[file_type], file_name)
        params = attachment_download_parser.parse_args()

        if file_type == 'attachment':
            attachment = File.get_by_file_name_or_404(
                file_name,
                joinedloads=['answers', 'assignments']
            )

            for answer in attachment.answers:
                require(READ, answer,
                    title="Attachment Unavailable",
                    message="Sorry, your role does not allow you to view the attachment.")

            for assignment in attachment.assignments:
                require(READ, assignment,
                    title="Attachment Unavailable",
                    message="Sorry, your role does not allow you to view the attachment.")

            # If attachment is in Kaltura, redirect the user
            if attachment.kaltura_media and KalturaAPI.enabled():
                entry_id = attachment.kaltura_media.entry_id
                download_url = attachment.kaltura_media.download_url
                if entry_id:
                    # Short-lived session of 60 seconds for user to start the media download
                    kaltura_url = KalturaAPI.get_direct_access_url(entry_id, download_url, 60)
                    return redirect(kaltura_url)

        if not os.path.exists(file_path):
            return make_response('invalid file name', 404)

        # TODO: add bouncer for reports
        mimetype, encoding = mimetypes.guess_type(file_name)
        attachment_filename = None
        as_attachment = False

        if file_type == 'attachment' and mimetype != "application/pdf":
            attachment_filename = params.get('name') #optionally set the download file name
            as_attachment = True

        on_get_file.send(
            current_app._get_current_object(),
            event_name=on_get_file.name,
            user=current_user,
            file_type=file_type,
            file_name=file_name,
            data={'file_path': file_path, 'mimetype': mimetype})

        return send_file(file_path, mimetype=mimetype,
            attachment_filename=attachment_filename, as_attachment=as_attachment)
Exemple #54
0
    def post(self, course_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        require(EDIT, UserCourse(course_id=course.id))

        params = update_users_course_role_parser.parse_args()

        role_name = params.get('course_role')
        course_roles = [
            CourseRole.dropped.value,
            CourseRole.student.value,
            CourseRole.teaching_assistant.value,
            CourseRole.instructor.value
        ]
        if role_name not in course_roles:
            abort(404)
        course_role = CourseRole(role_name)

        if len(params.get('ids')) == 0:
            return {"error": "Please select at least one user below"}, 400

        user_courses = UserCourse.query \
            .join(User, UserCourse.user_id == User.id) \
            .filter(and_(
                UserCourse.course_id == course.id,
                User.uuid.in_(params.get('ids')),
                UserCourse.course_role != CourseRole.dropped
            )) \
            .all()

        if len(params.get('ids')) != len(user_courses):
            return {"error": "One or more users are not enrolled in the course"}, 400

        if len(user_courses) == 1 and user_courses[0].user_id == current_user.id:
            if course_role == CourseRole.dropped:
                return {"error": "You cannot drop yourself from the course. Please select other users"}, 400
            else:
                return {"error": "You cannot change your own course role. Please select other users"}, 400

        for user_course in user_courses:
            # skip current user
            if user_course.user_id == current_user.id:
                continue
            # update user's role'
            user_course.course_role = course_role

        db.session.commit()

        on_classlist_update_users_course_roles.send(
            current_app._get_current_object(),
            event_name=on_classlist_update_users_course_roles.name,
            user=current_user,
            course_id=course.id,
            data={'user_uuids': params.get('ids'), 'course_role': role_name})

        return {'course_role': role_name}
Exemple #55
0
    def post(self, course_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        user_course = UserCourse(course_id=course.id)
        require(EDIT, user_course,
            title="Class List Not Imported",
            message="Sorry, your role in this course does not allow you to import or otherwise change the class list.")

        if current_app.config.get('DEMO_INSTALLATION', False):
            from data.fixtures import DemoDataFixture
            if course.id == DemoDataFixture.DEFAULT_COURSE_ID:
                abort(400, title="Class List Not Imported", message="Sorry, you cannot import users for the default demo course.")

        params = import_classlist_parser.parse_args()
        import_type = params.get('import_type')

        if import_type not in [ThirdPartyType.cas.value, ThirdPartyType.saml.value, None]:
            abort(400, title="Class List Not Imported", message="Please select a way for students to log in and try importing again.")
        elif import_type == ThirdPartyType.cas.value and not current_app.config.get('CAS_LOGIN_ENABLED'):
            abort(400, title="Class List Not Imported", message="Please select another way for students to log in and try importing again. Students are not able to use CWL logins based on the current settings.")
        elif import_type == ThirdPartyType.saml.value and not current_app.config.get('SAML_LOGIN_ENABLED'):
            abort(400, title="Class List Not Imported", message="Please select another way for students to log in and try importing again. Students are not able to use CWL logins based on the current settings.")
        elif import_type is None and not current_app.config.get('APP_LOGIN_ENABLED'):
            abort(400, title="Class List Not Imported", message="Please select another way for students to log in and try importing again. Students are not able to use the ComPAIR logins based on the current settings.")

        uploaded_file = request.files['file']
        results = {'success': 0, 'invalids': []}

        if not uploaded_file:
            abort(400, title="Class List Not Imported", message="No file was found to upload. Please try uploading again.")
        elif not allowed_file(uploaded_file.filename, current_app.config['UPLOAD_ALLOWED_EXTENSIONS']):
            abort(400, title="Class List Not Imported", message="Sorry, only CSV files can be imported. Please try again with a CSV file.")

        unique = str(uuid.uuid4())
        filename = unique + secure_filename(uploaded_file.filename)
        tmp_name = os.path.join(current_app.config['UPLOAD_FOLDER'], filename)
        uploaded_file.save(tmp_name)
        current_app.logger.debug("Importing for course " + str(course.id) + " with " + filename)
        with open(tmp_name, 'rb') as csvfile:
            spamreader = csv.reader(csvfile)
            users = []
            for row in spamreader:
                if row:
                    users.append(row)

            if len(users) > 0:
                results = import_users(import_type, course, users)

            on_classlist_upload.send(
                self,
                event_name=on_classlist_upload.name,
                user=current_user,
                course_id=course.id)
        os.remove(tmp_name)
        current_app.logger.debug("Class Import for course " + str(course.id) + " is successful. Removed file.")
        return results