예제 #1
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
예제 #2
0
파일: answer.py 프로젝트: ubc/acj-versus
    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}
예제 #3
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())
예제 #4
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]}
예제 #5
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}
예제 #6
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}
예제 #7
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}
예제 #8
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}
예제 #9
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))
예제 #10
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 }
예제 #11
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 }
예제 #12
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}
예제 #13
0
파일: answer.py 프로젝트: ubc/acj-versus
    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))
예제 #14
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}
예제 #15
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}
예제 #16
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())
예제 #17
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
예제 #18
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 }
예제 #19
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())
예제 #20
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}
예제 #21
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())
예제 #22
0
파일: classlist.py 프로젝트: ubc/acj-versus
    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
예제 #23
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}
예제 #24
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())
예제 #25
0
파일: course.py 프로젝트: ubc/acj-versus
    def get(self, course_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        require(READ, course,
            title="Course Unavailable",
            message="Courses can be seen only by those enrolled in them. Please double-check your enrollment in this 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())
예제 #26
0
    def get(self, course_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        require(READ, UserCourse(course_id=course.id))
        restrict_user = not allow(READ, USER_IDENTITY)

        # expire current_user from the session. When loading classlist from database, if the
        # user is already in the session, e.g. instructor for the course, the User.user_courses
        # is not loaded from the query below, but from session. In this case, if user has more
        # than one course, User.user_courses will return multiple row. Thus violating the
        # course_role constrain.
        db.session.expire(current_user)

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

        if not restrict_user:
            user_ids = [_user.id for (_user, _course_role, _group_name) in users]
            third_party_auths = ThirdPartyUser.query \
                .filter(and_(
                    ThirdPartyUser.user_id.in_(user_ids),
                    ThirdPartyUser.third_party_type == ThirdPartyType.cas
                )) \
                .all()

        class_list = []
        for (_user, _course_role, _group_name) in users:
            _user.course_role = _course_role
            _user.group_name = _group_name

            if not restrict_user:
                third_party_auth = next(
                    (third_party_auth for third_party_auth in third_party_auths if third_party_auth.user_id == _user.id),
                    None
                )
                _user.cas_username = third_party_auth.unique_identifier if third_party_auth else None

            class_list.append(_user)

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

        return {'objects': marshal(class_list, dataformat.get_users_in_course(restrict_user=restrict_user))}
예제 #27
0
파일: answer.py 프로젝트: ubc/acj-versus
    def get(self, course_uuid, assignment_uuid):
        """
        Get answers submitted to the assignment submitted by current user

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

        require(READ, Answer(user_id=current_user.id),
            title="Answers Unavailable",
            message="Sorry, your role in this course does not allow you to view answers for this assignment.")
        restrict_user = not allow(MANAGE, assignment)

        params = user_answer_list_parser.parse_args()

        query = Answer.query \
            .options(joinedload('comments')) \
            .options(joinedload('file')) \
            .options(joinedload('user')) \
            .options(joinedload('group')) \
            .options(joinedload('score')) \
            .filter_by(
                active=True,
                assignment_id=assignment.id,
                course_id=course.id,
                draft=params.get('draft')
            )

        # get group and individual answers for user if applicable
        group = current_user.get_course_group(course.id)
        if group:
            query = query.filter(or_(
                Answer.user_id == current_user.id,
                Answer.group_id == group.id
            ))
        # get just individual answers for user
        else:
            query = query.filter(Answer.user_id == current_user.id)

        answers = query.all()

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

        return {"objects": marshal(answers, dataformat.get_answer(restrict_user))}
예제 #28
0
    def get(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(READ, assignment_comment)

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

        return marshal(assignment_comment, dataformat.get_assignment_comment())
예제 #29
0
파일: course.py 프로젝트: ubc/acj-versus
    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())
예제 #30
0
    def delete(self, course_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        require(DELETE, course)

        course.active = False
        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}
예제 #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}
예제 #32
0
파일: lti_course.py 프로젝트: ubc/compair
    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}
예제 #33
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))
예제 #34
0
파일: group.py 프로젝트: vishnu-meera/ACJ
    def post(self, course_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        new_group = Group(course_id=course.id)

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

        params = new_group_parser.parse_args()

        new_group.name = params.get("name")

        # check if group name is unique
        group_name_exists = Group.query \
            .filter(
                Group.course_id == course.id,
                Group.name == new_group.name
            ) \
            .first()

        if group_name_exists:
            abort(400, title="Group Not Added",
                message="Sorry, the group name you have entered already exists. Please choose a different name.")

        db.session.add(new_group)
        db.session.commit()

        on_group_create.send(
            current_app._get_current_object(),
            event_name=on_group_create.name,
            user=current_user,
            course_id=course.id,
            data=marshal(new_group, dataformat.get_group())
        )

        return marshal(new_group, dataformat.get_group())
예제 #35
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="Feedback Unavailable",
            message="Sorry, your role in this course does not allow you to view this feedback.")

        restrict_user = not allow(MANAGE, assignment)

        restrict_user = not allow(MANAGE, assignment)

        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(restrict_user))
예제 #36
0
    def get(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(
            READ,
            assignment_comment,
            title="Help Comment Unavailable",
            message=
            "Sorry, your role in this course does not allow you to view help comments."
        )

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

        return marshal(assignment_comment, dataformat.get_assignment_comment())
예제 #37
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))
예제 #38
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="Assignment Status Unavailable",
            message="Assignment status can be seen only by those enrolled in the course. Please double-check your enrollment in this course.")

        answer_count = Answer.query \
            .filter_by(
                user_id=current_user.id,
                assignment_id=assignment.id,
                active=True,
                practice=False,
                draft=False
            ) \
            .count()

        feedback_count = AnswerComment.query \
            .join("answer") \
            .filter(and_(
                AnswerComment.active == True,
                AnswerComment.draft == False,
                Answer.user_id == current_user.id,
                Answer.assignment_id == assignment.id,
                Answer.active == True,
                Answer.practice == False,
                Answer.draft == False
            )) \
            .count()

        drafts = Answer.query \
            .options(load_only('id')) \
            .filter_by(
                user_id=current_user.id,
                assignment_id=assignment.id,
                active=True,
                practice=False,
                draft=True,
                saved=True
            ) \
            .all()

        comparison_count = assignment.completed_comparison_count_for_user(current_user.id)
        other_student_answers = assignment.student_answer_count - answer_count
        comparison_available = comparison_count < other_student_answers * (other_student_answers - 1) / 2

        status = {
            'answers': {
                'answered': answer_count > 0,
                'feedback': feedback_count,
                'count': answer_count,
                'has_draft': len(drafts) > 0,
                'draft_ids': [draft.uuid for draft in drafts]
            },
            'comparisons': {
                'available': comparison_available,
                'count': comparison_count,
                'left': max(0, assignment.total_comparisons_required - comparison_count)
            }
        }

        if assignment.enable_self_evaluation:
            self_evaluations = AnswerComment.query \
                .join("answer") \
                .filter(and_(
                    AnswerComment.user_id == current_user.id,
                    AnswerComment.active == True,
                    AnswerComment.comment_type == AnswerCommentType.self_evaluation,
                    AnswerComment.draft == False,
                    Answer.assignment_id == assignment.id,
                    Answer.active == True,
                    Answer.practice == False,
                    Answer.draft == False
                )) \
                .count()
            status['comparisons']['self_evaluation_completed'] = self_evaluations > 0

        on_assignment_get_status.send(
            self,
            event_name=on_assignment_get_status.name,
            user=current_user,
            course_id=course.id,
            data=status)

        return {"status": status}
예제 #39
0
    def post(self, course_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        # check permission first before reading parser arguments
        new_assignment = Assignment(course_id=course.id)
        require(CREATE, new_assignment,
            title="Assignment Not Saved",
            message="Sorry, your role in this course does not allow you to save assignments.")

        params = new_assignment_parser.parse_args()

        new_assignment.user_id = current_user.id
        new_assignment.name = params.get("name")
        new_assignment.description = params.get("description")
        new_assignment.peer_feedback_prompt = params.get("peer_feedback_prompt")
        new_assignment.answer_start = dateutil.parser.parse(params.get('answer_start'))
        new_assignment.answer_end = dateutil.parser.parse(params.get('answer_end'))
        new_assignment.educators_can_compare = params.get("educators_can_compare")
        new_assignment.rank_display_limit = params.get("rank_display_limit", None)
        if new_assignment.rank_display_limit != None and new_assignment.rank_display_limit <= 0:
            new_assignment.rank_display_limit = None

        # make sure that file attachment exists
        file_uuid = params.get('file_id')
        if file_uuid:
            attachment = File.get_by_uuid_or_404(file_uuid)
            new_assignment.file_id = attachment.id
        else:
            new_assignment.file_id = None

        new_assignment.compare_start = params.get('compare_start', None)
        if new_assignment.compare_start is not None:
            new_assignment.compare_start = dateutil.parser.parse(params.get('compare_start', None))

        new_assignment.compare_end = params.get('compare_end', None)
        if new_assignment.compare_end is not None:
            new_assignment.compare_end = dateutil.parser.parse(params.get('compare_end', None))

        # validate answer + comparison period start & end times
        valid, error_message = Assignment.validate_periods(course.start_date, course.end_date,
             new_assignment.answer_start, new_assignment.answer_end,
             new_assignment.compare_start, new_assignment.compare_end)
        if not valid:
            abort(400, title="Assignment Not Saved", message=error_message)

        new_assignment.students_can_reply = params.get('students_can_reply', False)
        new_assignment.number_of_comparisons = params.get('number_of_comparisons')
        new_assignment.enable_self_evaluation = params.get('enable_self_evaluation')

        new_assignment.answer_grade_weight = params.get('answer_grade_weight')
        new_assignment.comparison_grade_weight = params.get('comparison_grade_weight')
        new_assignment.self_evaluation_grade_weight = params.get('self_evaluation_grade_weight')

        pairing_algorithm = params.get("pairing_algorithm", PairingAlgorithm.random)
        check_valid_pairing_algorithm(pairing_algorithm)
        new_assignment.pairing_algorithm = PairingAlgorithm(pairing_algorithm)

        criterion_uuids = [c.get('id') for c in params.criteria]
        criterion_data = {c.get('id'): c.get('weight', 1) for c in params.criteria}
        if len(criterion_data) == 0:
            msg = "Please add at least one criterion to the assignment and save again."
            abort(400, title="Assignment Not Saved", message=msg)

        criteria = Criterion.query \
            .filter(Criterion.uuid.in_(criterion_uuids)) \
            .all()

        if len(criterion_uuids) != len(criteria):
            abort(400, title="Assignment Not Saved", message="Please double-check the criteria you selected and save agaiin.")

        # add criteria to assignment in order
        for criterion_uuid in criterion_uuids:
            criterion = next(criterion for criterion in criteria if criterion.uuid == criterion_uuid)
            new_assignment.assignment_criteria.append(AssignmentCriterion(
                criterion=criterion,
                weight=criterion_data.get(criterion.uuid)
            ))

        db.session.add(new_assignment)
        db.session.commit()

        # need to reorder after insert
        new_assignment.assignment_criteria.reorder()

        # update course grades
        course.calculate_grades()

        on_assignment_create.send(
            self,
            event_name=on_assignment_create.name,
            user=current_user,
            course_id=course.id,
            assignment=new_assignment,
            data=marshal(new_assignment, dataformat.get_assignment(False)))

        return marshal(new_assignment, dataformat.get_assignment())
예제 #40
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)
        assignment_criteria = assignment.assignment_criteria
        require(EDIT, assignment,
            title="Assignment Not Saved",
            message="Sorry, your role in this course does not allow you to save 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 Saved", message="Sorry, you cannot edit the default demo assignments.")

        params = existing_assignment_parser.parse_args()

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

        # make sure that file attachment exists
        file_uuid = params.get('file_id')
        if file_uuid:
            attachment = File.get_by_uuid_or_404(file_uuid)
            assignment.file_id = attachment.id
        else:
            assignment.file_id = None

        # modify assignment according to new values, preserve original values if values not passed
        assignment.name = params.get("name", assignment.name)
        assignment.description = params.get("description", assignment.description)
        assignment.peer_feedback_prompt = params.get("peer_feedback_prompt", assignment.peer_feedback_prompt)
        assignment.answer_start = datetime.datetime.strptime(
            params.get('answer_start', assignment.answer_start),
            '%Y-%m-%dT%H:%M:%S.%fZ')
        assignment.answer_end = datetime.datetime.strptime(
            params.get('answer_end', assignment.answer_end),
            '%Y-%m-%dT%H:%M:%S.%fZ')
        # if nothing in request, assume user don't want comparison date
        assignment.compare_start = params.get('compare_start', None)
        if assignment.compare_start is not None:
            assignment.compare_start = datetime.datetime.strptime(
                assignment.compare_start,
                '%Y-%m-%dT%H:%M:%S.%fZ')
        assignment.compare_end = params.get('compare_end', None)
        if assignment.compare_end is not None:
            assignment.compare_end = datetime.datetime.strptime(
                params.get('compare_end', assignment.compare_end),
                '%Y-%m-%dT%H:%M:%S.%fZ')

        # validate answer + comparison period start & end times
        valid, error_message = Assignment.validate_periods(course.start_date, course.end_date,
             assignment.answer_start, assignment.answer_end, assignment.compare_start, assignment.compare_end)
        if not valid:
            abort(400, title="Assignment Not Saved", message=error_message)

        assignment.students_can_reply = params.get('students_can_reply', False)
        assignment.number_of_comparisons = params.get(
            'number_of_comparisons', assignment.number_of_comparisons)
        assignment.enable_self_evaluation = params.get(
            'enable_self_evaluation', assignment.enable_self_evaluation)

        assignment.answer_grade_weight = params.get(
            'answer_grade_weight', assignment.answer_grade_weight)
        assignment.comparison_grade_weight = params.get(
            'comparison_grade_weight', assignment.comparison_grade_weight)
        assignment.self_evaluation_grade_weight = params.get(
            'self_evaluation_grade_weight', assignment.self_evaluation_grade_weight)

        pairing_algorithm = params.get("pairing_algorithm")
        check_valid_pairing_algorithm(pairing_algorithm)
        if not assignment.compared:
            assignment.pairing_algorithm = PairingAlgorithm(pairing_algorithm)
        elif assignment.pairing_algorithm != PairingAlgorithm(pairing_algorithm):
            msg = 'The answer pair selection algorithm cannot be changed for this assignment ' + \
                    'because it has already been used in one or more comparisons.'
            abort(403, title="Assignment Not Saved", message=msg)

        assignment.educators_can_compare = params.get("educators_can_compare")

        assignment.rank_display_limit = params.get("rank_display_limit", None)
        if assignment.rank_display_limit != None and assignment.rank_display_limit <= 0:
            assignment.rank_display_limit = None

        criterion_uuids = [c.get('id') for c in params.criteria]
        criterion_data = {c.get('id'): c.get('weight', 1) for c in params.criteria}
        if assignment.compared:
            active_uuids = [c.uuid for c in assignment.criteria]
            active_data = {c.uuid: c.weight for c in assignment.criteria}
            if set(criterion_uuids) != set(active_uuids):
                msg = 'The criteria cannot be changed for this assignment ' + \
                      'because they have already been used in one or more comparisons.'
                abort(403, title="Assignment Not Saved", message=msg)

            for criterion in assignment.criteria:
                if criterion_data.get(criterion.uuid) != criterion.weight:
                    msg = 'The criteria weights cannot be changed for this assignment ' + \
                        'because they have already been used in one or more comparisons.'
                    abort(403, title="Assignment Not Saved", message=msg)
        else:
            # assignment not compared yet, can change criteria
            if len(criterion_uuids) == 0:
                msg = "Please add at least one criterion to the assignment and save again."
                abort(403, title="Assignment Not Saved", message=msg)

            existing_uuids = [c.criterion_uuid for c in assignment_criteria]
            # disable old ones
            for c in assignment_criteria:
                c.active = c.criterion_uuid in criterion_uuids
                if c.active:
                    c.weight = criterion_data.get(c.criterion_uuid)

            # add the new ones
            new_uuids = []
            for criterion_uuid in criterion_uuids:
                if criterion_uuid not in existing_uuids:
                    new_uuids.append(criterion_uuid)

            if len(new_uuids) > 0:
                new_criteria = Criterion.query.filter(Criterion.uuid.in_(new_uuids)).all()
                for criterion in new_criteria:
                    assignment_criteria.append(AssignmentCriterion(
                        criterion=criterion,
                        weight=criterion_data.get(criterion.uuid)
                    ))

        # ensure criteria are in order
        for index, criterion_uuid in enumerate(criterion_uuids):
            assignment_criterion = next(assignment_criterion \
                for assignment_criterion in assignment_criteria \
                if assignment_criterion.criterion_uuid == criterion_uuid)

            assignment_criteria.remove(assignment_criterion)
            assignment_criteria.insert(index, assignment_criterion)

        model_changes = get_model_changes(assignment)

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

        db.session.commit()

        # need to reorder after update
        assignment_criteria.reorder()

        # update assignment and course grades if needed
        if model_changes and (model_changes.get('answer_grade_weight') or
                 model_changes.get('comparison_grade_weight') or
                 model_changes.get('self_evaluation_grade_weight') or
                 model_changes.get('enable_self_evaluation')):
            assignment.calculate_grades()
            course.calculate_grades()

        return marshal(assignment, dataformat.get_assignment())
예제 #41
0
    def post(self, course_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment(course_id=course.id)
        require(MANAGE, assignment,
            title="Report Not Run",
            message="Sorry, your system role does not allow you to run reports.")

        params = report_parser.parse_args()
        group_name = params.get('group_name', None)
        report_type = params.get('type')

        assignments = []
        assignment_uuid = params.get('assignment', None)
        if assignment_uuid:
            assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
            assignments = [assignment]
        else:
            assignments = Assignment.query \
                .filter_by(
                    course_id=course.id,
                    active=True
                ) \
                .all()

        if group_name:
            # ensure that group_name is valid
            group_exists = UserCourse.query \
                .filter(
                    UserCourse.group_name == group_name,
                    UserCourse.course_id == course.id,
                    UserCourse.course_role != CourseRole.dropped
                ) \
                .first()
            if group_exists == None:
                abort(400, title="Report Not Run", message="Please try again with a group from the list of groups provided.")

        if report_type == "participation_stat":
            data = participation_stat_report(course, assignments, group_name, assignment_uuid is None)

            title = [
                'Assignment', 'User UUID', 'Last Name', 'First Name', 'Answer Submitted', 'Answer ID',
                'Evaluations Submitted', 'Evaluations Required', 'Evaluation Requirements Met',
                'Replies Submitted']
            titles = [title]

        elif report_type == "participation":
            user_titles = ['Last Name', 'First Name', 'Student No']
            data = participation_report(course, assignments, group_name)

            title_row1 = [""] * len(user_titles)
            title_row2 = user_titles

            for assignment in assignments:
                assignment_criteria = AssignmentCriterion.query \
                    .filter_by(
                        assignment_id=assignment.id,
                        active=True
                    ) \
                    .order_by(AssignmentCriterion.position) \
                    .all()

                title_row1 += [assignment.name] + [""] * len(assignment_criteria)
                title_row2.append('Percentage score for answer overall')
                for assignment_criterion in assignment_criteria:
                    title_row2.append('Percentage score for "' + assignment_criterion.criterion.name + '"')
                title_row2.append("Evaluations Submitted (" + str(assignment.total_comparisons_required) + ' required)')
                if assignment.enable_self_evaluation:
                    title_row1 += [""]
                    title_row2.append("Self Evaluation Submitted")
            titles = [title_row1, title_row2]

        elif report_type == "peer_feedback":
            titles1 = [
                "",
                "Feedback Author", "", "",
                "Answer Author", "", "",
                "", ""
            ]
            titles2 = [
                "Assignment",
                "Last Name", "First Name", "Student No",
                "Last Name", "First Name", "Student No",
                "Feedback Type", "Feedback"
            ]
            data = peer_feedback_report(course, assignments, group_name)
            titles = [titles1, titles2]
        else:
            abort(400, title="Report Not Run", message="Please try again with a report type from the list of report types provided.")

        name = name_generator(course, report_type, group_name)
        tmp_name = os.path.join(current_app.config['REPORT_FOLDER'], name)

        with open(tmp_name, 'wb') as report:
            out = csv.writer(report)
            for t in titles:
                out.writerow(t)
            for s in data:
                out.writerow(s)

        on_export_report.send(
            self,
            event_name=on_export_report.name,
            user=current_user,
            course_id=course.id,
            data={'type': report_type, 'filename': name})

        return {'file': 'report/' + name}
예제 #42
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))
예제 #43
0
    def post(self, course_uuid):
        """
        Duplicate a course
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        require(
            EDIT,
            course,
            title="Course Not Duplicated",
            message=
            "Sorry, your role in this course does not allow you to duplicate it."
        )

        params = duplicate_course_parser.parse_args()

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

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

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

        assignments = [
            assignment for assignment in course.assignments
            if assignment.active
        ]
        assignments_copy_data = params.get("assignments")

        if len(assignments) != len(assignments_copy_data):
            abort(
                400,
                title="Course Not Saved",
                message=
                "The course is missing assignments. Please reload the page and try duplicating again."
            )

        for assignment_copy_data in assignments_copy_data:
            if assignment_copy_data.get('answer_start'):
                assignment_copy_data[
                    'answer_start'] = datetime.datetime.strptime(
                        assignment_copy_data.get('answer_start'),
                        '%Y-%m-%dT%H:%M:%S.%fZ')

            if assignment_copy_data.get('answer_end'):
                assignment_copy_data[
                    'answer_end'] = datetime.datetime.strptime(
                        assignment_copy_data.get('answer_end'),
                        '%Y-%m-%dT%H:%M:%S.%fZ')

            if assignment_copy_data.get('compare_start'):
                assignment_copy_data[
                    'compare_start'] = datetime.datetime.strptime(
                        assignment_copy_data.get('compare_start'),
                        '%Y-%m-%dT%H:%M:%S.%fZ')

            if assignment_copy_data.get('compare_end'):
                assignment_copy_data[
                    'compare_end'] = datetime.datetime.strptime(
                        assignment_copy_data.get('compare_end'),
                        '%Y-%m-%dT%H:%M:%S.%fZ')

            if 'enable_self_evaluation' not in assignment_copy_data:
                assignment_copy_data['enable_self_evaluation'] = False

            if assignment_copy_data.get('self_eval_start'):
                assignment_copy_data[
                    'self_eval_start'] = datetime.datetime.strptime(
                        assignment_copy_data.get('self_eval_start'),
                        '%Y-%m-%dT%H:%M:%S.%fZ')

            if assignment_copy_data.get('self_eval_end'):
                assignment_copy_data[
                    'self_eval_end'] = datetime.datetime.strptime(
                        assignment_copy_data.get('self_eval_end'),
                        '%Y-%m-%dT%H:%M:%S.%fZ')

            valid, error_message = Assignment.validate_periods(
                start_date, end_date, assignment_copy_data.get('answer_start'),
                assignment_copy_data.get('answer_end'),
                assignment_copy_data.get('compare_start'),
                assignment_copy_data.get('compare_end'),
                assignment_copy_data.get('self_eval_start'),
                assignment_copy_data.get('self_eval_end'))
            if not valid:
                error_message = error_message.replace(
                    ".", "") + " for assignment " + text_type(
                        assignment_copy_data.get('name', '')) + "."
                abort(400, title="Course Not Saved", message=error_message)

        # duplicate course
        duplicate_course = Course(name=params.get("name"),
                                  year=params.get("year"),
                                  term=params.get("term"),
                                  sandbox=params.get("sandbox"),
                                  start_date=start_date,
                                  end_date=end_date)
        db.session.add(duplicate_course)

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

        # duplicate assignments
        for assignment in assignments:
            # this should never be null due
            assignment_copy_data = next(
                (assignment_copy_data
                 for assignment_copy_data in assignments_copy_data
                 if assignment_copy_data.get('id') == assignment.uuid), None)

            if not assignment_copy_data:
                abort(400,
                      title="Course Not Saved",
                      message="Missing information for assignment " +
                      assignment.name + ". Please try duplicating again.")

            duplicate_assignment = Assignment(
                course=duplicate_course,
                user_id=current_user.id,
                file=assignment.file,
                name=assignment.name,
                description=assignment.description,
                answer_start=assignment_copy_data.get('answer_start'),
                answer_end=assignment_copy_data.get('answer_end'),
                compare_start=assignment_copy_data.get('compare_start'),
                compare_end=assignment_copy_data.get('compare_end'),
                self_eval_start=assignment_copy_data.get('self_eval_start')
                if assignment_copy_data.get('enable_self_evaluation',
                                            False) else None,
                self_eval_end=assignment_copy_data.get('self_eval_end')
                if assignment_copy_data.get('enable_self_evaluation',
                                            False) else None,
                self_eval_instructions=assignment.self_eval_instructions
                if assignment_copy_data.get('enable_self_evaluation',
                                            False) else None,
                answer_grade_weight=assignment.answer_grade_weight,
                comparison_grade_weight=assignment.comparison_grade_weight,
                self_evaluation_grade_weight=assignment.
                self_evaluation_grade_weight,
                number_of_comparisons=assignment.number_of_comparisons,
                students_can_reply=assignment.students_can_reply,
                enable_self_evaluation=assignment_copy_data.get(
                    'enable_self_evaluation', False),
                enable_group_answers=assignment.enable_group_answers,
                pairing_algorithm=assignment.pairing_algorithm,
                scoring_algorithm=assignment.scoring_algorithm,
                peer_feedback_prompt=assignment.peer_feedback_prompt,
                educators_can_compare=assignment.educators_can_compare,
                rank_display_limit=assignment.rank_display_limit,
            )
            db.session.add(duplicate_assignment)

            # duplicate assignment criteria
            for assignment_criterion in assignment.assignment_criteria:
                if not assignment_criterion.active:
                    continue

                duplicate_assignment_criterion = AssignmentCriterion(
                    assignment=duplicate_assignment,
                    criterion_id=assignment_criterion.criterion_id)
                db.session.add(duplicate_assignment_criterion)

            # duplicate assignment comparisons examples
            for comparison_example in assignment.comparison_examples:
                answer1 = comparison_example.answer1
                answer2 = comparison_example.answer2

                # duplicate assignment comparisons example answers
                duplicate_answer1 = Answer(assignment=duplicate_assignment,
                                           user_id=current_user.id,
                                           file=answer1.file,
                                           content=answer1.content,
                                           practice=answer1.practice,
                                           active=answer1.active,
                                           draft=answer1.draft)
                db.session.add(duplicate_answer1)

                # duplicate assignment comparisons example answers
                duplicate_answer2 = Answer(assignment=duplicate_assignment,
                                           user_id=current_user.id,
                                           file=answer2.file,
                                           content=answer2.content,
                                           practice=answer2.practice,
                                           active=answer2.active,
                                           draft=answer2.draft)
                db.session.add(duplicate_answer2)

                duplicate_comparison_example = ComparisonExample(
                    assignment=duplicate_assignment,
                    answer1=duplicate_answer1,
                    answer2=duplicate_answer2)
                db.session.add(duplicate_comparison_example)

        db.session.commit()

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

        return marshal(duplicate_course, dataformat.get_course())
예제 #44
0
파일: classlist.py 프로젝트: ubc/compair
    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
예제 #45
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}
예제 #46
0
    def post(self, course_uuid, assignment_uuid, answer_uuid, answer_comment_uuid):
        """
        Update 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(EDIT, answer_comment,
            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)

        was_draft = answer_comment.draft

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

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

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

        eval_comment_types = [
            AnswerCommentType.evaluation.value,
            AnswerCommentType.self_evaluation.value
        ]

        comment_type = params.get("comment_type", AnswerCommentType.private.value)
        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.")

        # do not allow changing a self-eval into a comment or vise-versa
        if (answer_comment.comment_type.value in eval_comment_types or comment_type in eval_comment_types) and answer_comment.comment_type.value != comment_type:
            abort(400, title="Feedback Not Saved", message="Feedback type cannot be changed. 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.")

        # only update draft param if currently a draft
        if answer_comment.draft:
            answer_comment.draft = params.get('draft', answer_comment.draft)

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

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

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

        on_answer_comment_modified.send(
            self,
            event_name=on_answer_comment_modified.name,
            user=current_user,
            course_id=course.id,
            answer_comment=answer_comment,
            evaluation_number=params.get("evaluation_number"),
            was_draft=was_draft,
            data=model_changes)

        # 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)

        return marshal(answer_comment, dataformat.get_answer_comment(restrict_user))
예제 #47
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))
예제 #48
0
    def get(self, course_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        require(READ, course,
            title="Assignment Status Unavailable",
            message="Assignment status can be seen only by those enrolled in the course. Please double-check your enrollment in this course.")

        assignments = course.assignments \
            .filter_by(active=True) \
            .all()
        assignment_ids = [assignment.id for assignment in assignments]

        answer_counts = Answer.query \
            .with_entities(
                Answer.assignment_id,
                func.count(Answer.assignment_id).label('answer_count')
            ) \
            .filter_by(
                user_id=current_user.id,
                active=True,
                practice=False,
                draft=False
            ) \
            .filter(Answer.assignment_id.in_(assignment_ids)) \
            .group_by(Answer.assignment_id) \
            .all()


        feedback_counts = AnswerComment.query \
            .join("answer") \
            .with_entities(
                Answer.assignment_id,
                func.count(Answer.assignment_id).label('feedback_count')
            ) \
            .filter(and_(
                AnswerComment.active == True,
                AnswerComment.draft == False,
                Answer.user_id == current_user.id,
                Answer.active == True,
                Answer.practice == False,
                Answer.draft == False,
                Answer.assignment_id.in_(assignment_ids)
            )) \
            .group_by(Answer.assignment_id) \
            .all()

        # get self evaluation status for assignments with self evaluations enabled
        self_evaluations = AnswerComment.query \
            .join("answer") \
            .with_entities(
                Answer.assignment_id,
                func.count(Answer.assignment_id).label('self_evaluation_count')
            ) \
            .filter(and_(
                AnswerComment.user_id == current_user.id,
                AnswerComment.active == True,
                AnswerComment.comment_type == AnswerCommentType.self_evaluation,
                AnswerComment.draft == False,
                Answer.active == True,
                Answer.practice == False,
                Answer.draft == False,
                Answer.assignment_id.in_(assignment_ids)
            )) \
            .group_by(Answer.assignment_id) \
            .all()

        drafts = Answer.query \
            .options(load_only('id', 'assignment_id')) \
            .filter_by(
                user_id=current_user.id,
                active=True,
                practice=False,
                draft=True,
                saved=True
            ) \
            .all()

        statuses = {}
        for assignment in assignments:
            answer_count = next(
                (result.answer_count for result in answer_counts if result.assignment_id == assignment.id),
                0
            )
            feedback_count = next(
                (result.feedback_count for result in feedback_counts if result.assignment_id == assignment.id),
                0
            )
            assignment_drafts = [draft for draft in drafts if draft.assignment_id == assignment.id]
            comparison_count = assignment.completed_comparison_count_for_user(current_user.id)
            other_student_answers = assignment.student_answer_count - answer_count
            comparison_available = comparison_count < other_student_answers * (other_student_answers - 1) / 2

            statuses[assignment.uuid] = {
                'answers': {
                    'answered': answer_count > 0,
                    'feedback': feedback_count,
                    'count': answer_count,
                    'has_draft': len(assignment_drafts) > 0,
                    'draft_ids': [draft.uuid for draft in assignment_drafts]
                },
                'comparisons': {
                    'available': comparison_available,
                    'count': comparison_count,
                    'left': max(0, assignment.total_comparisons_required - comparison_count)
                }
            }

            if assignment.enable_self_evaluation:
                self_evaluation_count = next(
                    (result.self_evaluation_count for result in self_evaluations if result.assignment_id == assignment.id),
                    0
                )
                statuses[assignment.uuid]['comparisons']['self_evaluation_completed'] = self_evaluation_count > 0

        on_assignment_list_get_status.send(
            self,
            event_name=on_assignment_list_get_status.name,
            user=current_user,
            course_id=course.id,
            data=statuses)

        return {"statuses": statuses}
예제 #49
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="Comparisons Unavailable",
            message="Sorry, your role in this course does not allow you to view comparisons for this assignment.")

        restrict_user = is_user_access_restricted(current_user)

        # get comparisons for current user
        comparisons = Comparison.query \
            .filter(and_(
                Comparison.completed == True,
                Comparison.assignment_id == assignment.id,
                Comparison.user_id == current_user.id
            )) \
            .all()

        # get all self-evaluations and evaluation comments for current user
        answer_comments = AnswerComment.query \
            .join("answer") \
            .filter(and_(
                AnswerComment.active == True,
                AnswerComment.comment_type.in_([AnswerCommentType.self_evaluation, AnswerCommentType.evaluation]),
                AnswerComment.draft == False,
                Answer.active == True,
                Answer.draft == False,
                Answer.assignment_id == assignment.id,
                AnswerComment.user_id == current_user.id
            )) \
            .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
            ]

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

        comparison_set = {
            'comparisons': [comparison for comparison in comparisons if
                comparison.user_id == current_user.id
            ],
            'self_evaluations': [comment for comment in answer_comments if
                comment.user_id == current_user.id and
                comment.comment_type == AnswerCommentType.self_evaluation
            ]
        }

        return marshal(comparison_set, dataformat.get_comparison_set(restrict_user, with_user=False))
예제 #50
0
파일: classlist.py 프로젝트: ubc/compair
    def post(self, course_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        require(
            EDIT,
            UserCourse(course_id=course.id),
            title="Enrollment Not Updated",
            message=
            "Sorry, your role in this course does not allow you to update enrollment."
        )

        params = update_users_course_role_parser.parse_args()

        role_name = params.get('course_role')
        course_roles = [
            CourseRole.dropped.value, CourseRole.student.value,
            CourseRole.teaching_assistant.value, CourseRole.instructor.value
        ]
        if role_name not in course_roles:
            abort(
                400,
                title="Enrollment Not Updated",
                message=
                "Please try again with a course role from the list of roles provided."
            )
        course_role = CourseRole(role_name)

        if len(params.get('ids')) == 0:
            abort(
                400,
                title="Enrollment Not Updated",
                message=
                "Please select at least one user below and then try to update the enrollment again."
            )

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

        if len(params.get('ids')) != len(user_courses):
            abort(
                400,
                title="Enrollment Not Updated",
                message=
                "One or more users selected are not enrolled in the course yet."
            )

        if len(user_courses
               ) == 1 and user_courses[0].user_id == current_user.id:
            if course_role == CourseRole.dropped:
                abort(
                    400,
                    title="Enrollment Not Updated",
                    message=
                    "Sorry, you cannot drop yourself from the course. Please select only other users and try again."
                )
            else:
                abort(
                    400,
                    title="Enrollment Not Updated",
                    message=
                    "Sorry, you cannot change your own course role. Please select only other users and try again."
                )

        for user_course in user_courses:
            if current_app.config.get('DEMO_INSTALLATION', False):
                from data.fixtures import DemoDataFixture
                if course.id == DemoDataFixture.DEFAULT_COURSE_ID and user.id in DemoDataFixture.DEFAULT_COURSE_USERS:
                    abort(
                        400,
                        title="Enrollment Not Updated",
                        message=
                        "Sorry, you cannot update course role for the default users in the default demo course."
                    )

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

        db.session.commit()

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

        return {'course_role': role_name}
예제 #51
0
파일: classlist.py 프로젝트: ubc/compair
    def get(self, course_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        require(
            READ,
            UserCourse(course_id=course.id),
            title="Class List Unavailable",
            message=
            "Sorry, your role in this course does not allow you to view the class list."
        )
        restrict_user = not can(READ, USER_IDENTITY)

        # expire current_user from the session. When loading classlist from database, if the
        # user is already in the session, e.g. instructor for the course, the User.user_courses
        # is not loaded from the query below, but from session. In this case, if user has more
        # than one course, User.user_courses will return multiple row. Thus violating the
        # course_role constrain.
        db.session.expire(current_user)

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

        if not restrict_user:
            user_ids = [_user.id for (_user, _user_course) in users]
            third_party_auths = ThirdPartyUser.query \
                .filter(and_(
                    ThirdPartyUser.user_id.in_(user_ids),
                    or_(
                        ThirdPartyUser.third_party_type == ThirdPartyType.cas,
                        ThirdPartyUser.third_party_type == ThirdPartyType.saml,
                    )
                )) \
                .all()

        class_list = []
        for (_user, _user_course) in users:
            _user.course_role = _user_course.course_role
            _user.group = _user_course.group
            _user.group_uuid = _user.group.uuid if _user.group else None
            _user.group_name = _user.group.name if _user.group else None

            if not restrict_user:
                cas_auth = next(
                    (auth
                     for auth in third_party_auths if auth.user_id == _user.id
                     and auth.third_party_type == ThirdPartyType.cas), None)
                _user.cas_username = cas_auth.unique_identifier if cas_auth else None
                saml_auth = next(
                    (auth
                     for auth in third_party_auths if auth.user_id == _user.id
                     and auth.third_party_type == ThirdPartyType.saml), None)
                _user.saml_username = saml_auth.unique_identifier if saml_auth else None

            class_list.append(_user)

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

        if can(MANAGE, User):
            return {
                'objects':
                marshal(class_list, dataformat.get_full_users_in_course())
            }
        else:
            return {
                'objects':
                marshal(
                    class_list,
                    dataformat.get_users_in_course(
                        restrict_user=restrict_user))
            }
예제 #52
0
    def get(self, course_uuid, assignment_uuid, **kwargs):
        """
        :query string ids: a comma separated comment uuids to query
        :query string answer_ids: a comma separated answer uuids for answer filter
        :query string assignment_id: filter the answer comments with a assignment uuid
        :query string user_ids: a comma separated user uuids that own the comments
        :query string self_evaluation: indicate whether the result should include self-evaluation comments or self-evaluation only.
                Possible values: true, false or only. Default true.
        :query string evaluation: indicate whether the result should include evaluation comments or evaluation only.
                Possible values: true, false or only. Default true.
        :query string draft: indicate whether the result should include drafts for current user or not.
                Possible values: true, false or only. Default false.
        :reqheader Accept: the response content type depends on :mailheader:`Accept` header
        :resheader Content-Type: this depends on :mailheader:`Accept` header of request
        :statuscode 200: no error
        :statuscode 404: answers don't exist

        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)

        restrict_user = not allow(MANAGE, assignment)

        params = answer_comment_list_parser.parse_args()
        answer_uuids = []
        if 'answer_uuid' in kwargs:
            answer_uuids.append(kwargs['answer_uuid'])
        elif 'answer_ids' in params and params['answer_ids']:
            answer_uuids.extend(params['answer_ids'].split(','))

        if not answer_uuids and not params['ids'] and not params['assignment_id'] and not params['user_ids']:
            abort(404, title="Feedback Unavailable", message="There was a problem getting the feedback for this answer. Please try again.")

        conditions = []

        answers = Answer.query \
            .filter(
                Answer.assignment_id == assignment.id,
                Answer.active == True,
                Answer.draft == False,
                Answer.uuid.in_(answer_uuids)
            ) \
            .all() if answer_uuids else []
        if answer_uuids and not answers:
            # non-existing answer ids.
            abort(404, title="Feedback Unavailable", message="There was a problem getting the feedback for this answer. Please try again.")

        group = current_user.get_course_group(course.id)
        course_role = current_user.get_course_role(course.id)

        # build query condition for each answer
        for answer in answers:
            clauses = [AnswerComment.answer_id == answer.id]

            # student can only see the comments for themselves or public ones.
            # since the owner of the answer can access all comments. We only filter
            # on non-owners
            answer_owner = answer.user_id == current_user.id or (group and group.id == answer.group_id)
            if course_role == CourseRole.student and not answer_owner:
                # public comments or comments owned by current user
                clauses.append(or_(
                    AnswerComment.comment_type == AnswerCommentType.public,
                    AnswerComment.user_id == current_user.id
                ))

            conditions.append(and_(*clauses))

        query = AnswerComment.query \
            .filter(
                AnswerComment.assignment_id == assignment.id,
                AnswerComment.active==True,
                or_(*conditions)
            )

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

        if params['self_evaluation'] == 'false':
            # do not include self-evaluation
            query = query.filter(AnswerComment.comment_type != AnswerCommentType.self_evaluation)
        elif params['self_evaluation'] == 'only':
            # only self_evaluation
            query = query.filter(AnswerComment.comment_type == AnswerCommentType.self_evaluation)

        if params['evaluation'] == 'false':
            # do not include evalulation comments
            query = query.filter(AnswerComment.comment_type != AnswerCommentType.evaluation)
        elif params['evaluation'] == 'only':
            # only evaluation
            query = query.filter(AnswerComment.comment_type == AnswerCommentType.evaluation)

        if params['draft'] == 'true':
            # with draft (current_user)
            query = query.filter(or_(
                AnswerComment.draft == False,
                and_(
                    AnswerComment.draft == True,
                    AnswerComment.user_id == current_user.id
                )
            ))
        elif params['draft'] == 'only':
            # only draft (current_user)
            query = query.filter(and_(
                AnswerComment.draft == True,
                AnswerComment.user_id == current_user.id
            ))
        else:
            # do not include draft. Default
            query = query.filter(AnswerComment.draft == False)

        if params['user_ids']:
            user_ids = params['user_ids'].split(',')
            query = query \
                .join(User, AnswerComment.user_id == User.id) \
                .filter(User.uuid.in_(user_ids))

        answer_comments = query.order_by(AnswerComment.created.desc()).all()

        # checking the permission
        for answer_comment in answer_comments:
            require(READ, answer_comment.answer,
                title="Feedback Unavailable",
                message="Sorry, your role in this course does not allow you to view feedback for this answer.")

        on_answer_comment_list_get.send(
            self,
            event_name=on_answer_comment_list_get.name,
            user=current_user,
            data={'answer_ids': ','.join([str(answer.id) for answer in answers])})

        return marshal(answer_comments, dataformat.get_answer_comment(restrict_user))
예제 #53
0
파일: classlist.py 프로젝트: ubc/compair
    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
        }
예제 #54
0
    def post(self, course_uuid, group_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        group = Group.get_active_by_uuid_or_404(group_uuid)

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

        params = user_list_parser.parse_args()

        if len(params.get('ids')) == 0:
            abort(
                400,
                title="Group Not Saved",
                message=
                "Please select at least one user below and then try to apply the group again."
            )

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

        if len(params.get('ids')) != len(user_courses):
            abort(
                400,
                title="Group Not Saved",
                message=
                "One or more users selected are not enrolled in the course yet."
            )

        for user_course in user_courses:
            if course.groups_locked and user_course.group_id != None and user_course.group_id != group.id:
                abort(
                    400,
                    title="Group Not Saved",
                    message=
                    "The course groups are locked. One or more users are already assigned to a different group."
                )

        for user_course in user_courses:
            user_course.group_id = group.id

        db.session.commit()

        on_group_user_list_create.send(
            current_app._get_current_object(),
            event_name=on_group_user_list_create.name,
            user=current_user,
            course_id=course.id,
            data={
                'user_ids':
                [user_course.user_id for user_course in user_courses]
            })

        return marshal(group, dataformat.get_group())
예제 #55
0
    def post(self, course_uuid):
        # delete multiple (DELETE request cannot use params in angular)
        course = Course.get_active_by_uuid_or_404(course_uuid)

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

        params = user_list_parser.parse_args()

        if len(params.get('ids')) == 0:
            abort(
                400,
                title="Group Not Saved",
                message=
                "Please select at least one user below and then try to apply the group again."
            )

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

        if len(params.get('ids')) != len(user_courses):
            abort(
                400,
                title="Group Not Saved",
                message=
                "One or more users selected are not enrolled in the course yet."
            )

        for user_course in user_courses:
            if course.groups_locked and user_course.group_id != None:
                abort(
                    400,
                    title="Group Not Saved",
                    message=
                    "The course groups are locked. You may not remove users from the group they are already assigned to."
                )

        for user_course in user_courses:
            user_course.group_id = None

        db.session.commit()

        on_group_user_list_delete.send(
            current_app._get_current_object(),
            event_name=on_group_user_list_delete.name,
            user=current_user,
            course_id=course.id,
            data={
                'user_ids':
                [user_course.user_id for user_course in user_courses]
            })

        return {'success': True}
예제 #56
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())
예제 #57
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))
예제 #58
0
    def post(self, course_uuid, assignment_uuid, answer_uuid,
             answer_comment_uuid):
        """
        Update 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(
            EDIT,
            answer_comment,
            title="Reply Not Saved",
            message=
            "Sorry, your role in this course does not allow you to save replies for this answer."
        )

        was_draft = answer_comment.draft

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

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

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

        comment_type = params.get("comment_type",
                                  AnswerCommentType.private.value)
        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)
        # only update draft param if currently a draft
        if answer_comment.draft:
            answer_comment.draft = params.get('draft', answer_comment.draft)

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

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

        on_answer_comment_modified.send(
            self,
            event_name=on_answer_comment_modified.name,
            user=current_user,
            course_id=course.id,
            answer_comment=answer_comment,
            was_draft=was_draft,
            data=get_model_changes(answer_comment))

        # 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)

        return marshal(answer_comment, dataformat.get_answer_comment())
예제 #59
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
        }
예제 #60
0
파일: report.py 프로젝트: vishnu-meera/ACJ
    def post(self, course_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment(course_id=course.id)
        require(
            MANAGE,
            assignment,
            title="Report Not Run",
            message="Sorry, your system role does not allow you to run reports."
        )

        params = report_parser.parse_args()
        group_uuid = params.get('group_id', None)
        report_type = params.get('type')

        group = Group.get_active_by_uuid_or_404(
            group_uuid) if group_uuid else None

        assignments = []
        assignment_uuid = params.get('assignment', None)
        if assignment_uuid:
            assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
            assignments = [assignment]
        else:
            assignments = Assignment.query \
                .filter_by(
                    course_id=course.id,
                    active=True
                ) \
                .all()

        if report_type == "participation_stat":
            data = participation_stat_report(course, assignments, group,
                                             assignment_uuid is None)

            title = [
                'Assignment', 'Last Name', 'First Name', 'Student Number',
                'User UUID', 'Answer', 'Answer ID', 'Answer Deleted',
                'Answer Submission Date', 'Answer Last Modified',
                'Answer Score (Normalized)', 'Overall Rank',
                'Comparisons Submitted', 'Comparisons Required',
                'Comparison Requirements Met', 'Self-Evaluation Submitted',
                'Feedback Submitted (During Comparisons)',
                'Feedback Submitted (Outside Comparisons)'
            ]
            titles = [title]

        elif report_type == "participation":
            user_titles = ['Last Name', 'First Name', 'Student Number']
            data = participation_report(course, assignments, group)

            title_row1 = [""] * len(user_titles)
            title_row2 = user_titles

            for assignment in assignments:
                title_row1 += [assignment.name]
                title_row2.append('Participation Grade')
                title_row1 += [""]
                title_row2.append('Answer')
                title_row1 += [""]
                title_row2.append('Attachment')
                title_row1 += [""]
                title_row2.append('Answer Score (Normalized)')
                title_row1 += [""]
                title_row2.append("Comparisons Submitted (" +
                                  str(assignment.total_comparisons_required) +
                                  ' required)')
                if assignment.enable_self_evaluation:
                    title_row1 += [""]
                    title_row2.append("Self-Evaluation Submitted")
                title_row1 += [""]
                title_row2.append("Feedback Submitted (During Comparisons)")
                title_row1 += [""]
                title_row2.append("Feedback Submitted (Outside Comparisons)")
            titles = [title_row1, title_row2]

        elif report_type == "peer_feedback":
            titles1 = [
                "", "Feedback Author", "", "", "Answer Author", "", "", "", ""
            ]
            titles2 = [
                "Assignment", "Last Name", "First Name", "Student Number",
                "Last Name", "First Name", "Student Number", "Feedback Type",
                "Feedback", "Feedback Character Count"
            ]
            data = peer_feedback_report(course, assignments, group)
            titles = [titles1, titles2]

        else:
            abort(
                400,
                title="Report Not Run",
                message=
                "Please try again with a report type from the list of report types provided."
            )

        name = name_generator(course, report_type, group)
        tmp_name = os.path.join(current_app.config['REPORT_FOLDER'], name)

        with open(tmp_name, 'wb') as report:
            out = csv.writer(report)
            for t in titles:
                out.writerow(t)
            for s in data:
                out.writerow(s)

        on_export_report.send(self,
                              event_name=on_export_report.name,
                              user=current_user,
                              course_id=course.id,
                              data={
                                  'type': report_type,
                                  'filename': name
                              })

        return {'file': 'report/' + name}