예제 #1
0
    def get(self, course_uuid, group_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        group = Group.get_active_by_uuid_or_404(group_uuid)

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

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

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

        return {
            'objects': [{
                'id': u.uuid,
                'name': u.fullname_sortable
            } for u in members]
        }
예제 #2
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())
예제 #3
0
파일: group.py 프로젝트: vishnu-meera/ACJ
    def delete(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(DELETE, group,
            title="Group Not Deleted",
            message="Sorry, your role in this course does not allow you to delete groups.")

        # check if group has submitted any answers
        group_answer_exists = Answer.query \
            .filter_by(
                group_id=group.id,
                practice=False,
                active=True,
                draft=False
            ) \
            .first()

        if group_answer_exists:
            abort(400, title="Group Not Deleted",
                message="Sorry, you cannot remove groups that have submitted answers.")

        group.active = False

        # remove members from the group
        user_courses = UserCourse.query \
            .filter_by(group_id=group.id) \
            .all()
        for user_course in user_courses:
            user_course.group_id = None

        db.session.commit()

        on_group_delete.send(
            current_app._get_current_object(),
            event_name=on_group_delete.name,
            user=current_user,
            course_id=course.id
        )

        return {'id': group.uuid }
예제 #4
0
    def post(self, course_uuid, group_uuid, user_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        group = Group.get_active_by_uuid_or_404(group_uuid)
        user = User.get_by_uuid_or_404(user_uuid)

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

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

        if course.groups_locked and user_course.group_id != None and user_course.group_id != group.id:
            abort(
                400,
                title="Group Not Saved",
                message=
                "The course groups are locked. This user is already assigned to a different group."
            )

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

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

        return marshal(group, dataformat.get_group())
예제 #5
0
파일: group.py 프로젝트: vishnu-meera/ACJ
    def get(self, course_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        require(READ, Group(course_id=course.id),
            title="Groups Unavailable",
            message="Groups can be seen only by those enrolled in the course. Please double-check your enrollment in this course.")

        groups = Group.query \
            .filter_by(
                course_id=course.id,
                active=True
            ) \
            .order_by(Group.name) \
            .all()

        on_group_get.send(
            current_app._get_current_object(),
            event_name=on_group_get.name,
            user=current_user,
            course_id=course.id
        )

        return {'objects': marshal(groups, dataformat.get_group()) }
예제 #6
0
파일: group.py 프로젝트: vishnu-meera/ACJ
    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, group,
            title="Group Not Saved",
            message="Sorry, your role in this course does not allow you to save groups.")

        params = existing_group_parser.parse_args()

        # check if group name is unique
        group_name_exists = Group.query \
            .filter(
                Group.id != group.id,
                Group.course_id == group.course_id,
                Group.name == params.get("name", group.name)
            ) \
            .first()

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

        group.name = params.get("name", group.name)

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

        on_group_edit.send(
            current_app._get_current_object(),
            event_name=on_group_edit.name,
            user=current_user,
            course_id=course.id,
            data=model_changes
        )

        return marshal(group, dataformat.get_group())
예제 #7
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))
예제 #8
0
파일: classlist.py 프로젝트: ubc/compair
def import_users(import_type, course, users):
    invalids = []  # invalid entries - eg. invalid # of columns
    count = 0  # store number of successful enrolments

    imported_users = []
    set_user_passwords = []

    # store unique user identifiers - eg. student number - throws error if duplicate in file
    import_usernames = []
    import_student_numbers = []

    # store unique user identifiers - eg. student number - throws error if duplicate in file
    existing_system_usernames = _get_existing_users_by_identifier(
        import_type, users)
    existing_system_student_numbers = _get_existing_users_by_student_number(
        import_type, users)

    groups = course.groups.all()
    groups_by_name = {}
    for group in groups:
        groups_by_name[group.name] = group

    # create / update users in file
    for user_row in users:
        if len(user_row) < 1:
            continue  # skip empty row
        user = _parse_user_row(import_type, user_row)

        # validate unique identifier
        username = user.get('username')
        password = user.get(
            'password'
        )  #always None for CAS/SAML import, can be None for existing users on ComPAIR import
        student_number = user.get('student_number')

        u = existing_system_usernames.get(username, None)

        if not username:
            invalids.append({
                'user': User(username=username),
                'message': 'The username is required.'
            })
            continue
        elif username in import_usernames:
            invalids.append({
                'user':
                User(username=username),
                'message':
                'This username already exists in the file.'
            })
            continue

        if u:
            # overwrite password if user has not logged in yet
            if u.last_online == None and not password in [None, '*']:
                set_user_passwords.append((u, password))
        else:
            u = User(username=None,
                     password=None,
                     student_number=user.get('student_number'),
                     firstname=user.get('firstname'),
                     lastname=user.get('lastname'),
                     email=user.get('email'))
            if import_type == ThirdPartyType.cas.value or import_type == ThirdPartyType.saml.value:
                # CAS/SAML login
                u.third_party_auths.append(
                    ThirdPartyUser(
                        unique_identifier=username,
                        third_party_type=ThirdPartyType(import_type)))
            else:
                # ComPAIR login
                u.username = username
                if password in [None, '*']:
                    invalids.append({
                        'user': u,
                        'message': 'The password is required.'
                    })
                    continue
                elif len(password) < 4:
                    invalids.append({
                        'user':
                        u,
                        'message':
                        'The password must be at least 4 characters long.'
                    })
                    continue
                else:
                    set_user_passwords.append((u, password))

            # validate student number (if not None)
            if student_number:
                # invalid if already showed up in file
                if student_number in import_student_numbers:
                    u.username = username
                    invalids.append({
                        'user':
                        u,
                        'message':
                        'This student number already exists in the file.'
                    })
                    continue
                # invalid if student number already exists in the system
                elif student_number in existing_system_student_numbers:
                    u.username = username
                    invalids.append({
                        'user':
                        u,
                        'message':
                        'This student number already exists in the system.'
                    })
                    continue

            u.system_role = SystemRole.student
            u.displayname = user.get('displayname') if user.get(
                'displayname') else display_name_generator()
            db.session.add(u)

        import_usernames.append(username)
        if student_number:
            import_student_numbers.append(student_number)
        imported_users.append((u, user.get('group')))
    db.session.commit()

    enroled = UserCourse.query \
        .filter_by(course_id=course.id) \
        .all()

    enroled = {e.user_id: e for e in enroled}

    students = UserCourse.query \
        .filter_by(
            course_id=course.id,
            course_role=CourseRole.student
        ) \
        .all()
    students = {s.user_id: s for s in students}

    # enrol valid users in file
    for user, group_name in imported_users:
        enrol = enroled.get(user.id,
                            UserCourse(course_id=course.id, user_id=user.id))
        enrol.group = None
        if group_name:
            group = groups_by_name.get(group_name)
            # add new groups if needed
            if not group:
                group = Group(course=course, name=group_name)
                groups_by_name[group_name] = group
                db.session.add(group)
            enrol.group = group

        # do not overwrite instructor or teaching assistant roles
        if enrol.course_role not in [
                CourseRole.instructor, CourseRole.teaching_assistant
        ]:
            enrol.course_role = CourseRole.student
            if user.id in students:
                del students[user.id]
            count += 1
        db.session.add(enrol)

    db.session.commit()

    # unenrol users not in file anymore
    for user_id in students:
        enrolment = students.get(user_id)
        # skip users that are already dropped
        if enrolment.course_role == CourseRole.dropped:
            continue
        enrolment.course_role = CourseRole.dropped
        enrolment.group_id = None
        db.session.add(enrolment)
    db.session.commit()

    # wait until user ids are generated before starting background jobs
    # perform password update in chunks of 100
    chunk_size = 100
    chunks = [
        set_user_passwords[index:index + chunk_size]
        for index in range(0, len(set_user_passwords), chunk_size)
    ]
    for chunk in chunks:
        set_passwords.delay({user.id: password for (user, password) in chunk})

    on_classlist_upload.send(current_app._get_current_object(),
                             event_name=on_classlist_upload.name,
                             user=current_user,
                             course_id=course.id)

    return {
        'success': count,
        'invalids': marshal(invalids,
                            dataformat.get_import_users_results(False))
    }
예제 #9
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())
예제 #10
0
    def get(self, course_uuid, assignment_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        require(READ, Comparison(course_id=course.id),
            title="Comparisons Unavailable",
            message="Sorry, your role in this course does not allow you to view all comparisons for this assignment.")

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

        return {"objects": marshal(comparison_sets, dataformat.get_comparison_set(restrict_user, with_user=True)),
                "comparison_total": comparison_total, "self_evaluation_total": self_evaluation_total,
                "page": page.page, "pages": page.pages, "total": page.total, "per_page": page.per_page}
예제 #11
0
파일: answer.py 프로젝트: ubc/acj-versus
    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))
예제 #12
0
파일: report.py 프로젝트: ubc/acj-versus
    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}
예제 #13
0
파일: assignment.py 프로젝트: ubc/compair
    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()

        # get users who have at least made one comparison or finished self-eval.
        # 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
            )) \
            .outerjoin(Comparison, and_(
                Comparison.user_id == User.id,
                Comparison.assignment_id == assignment.id
            )) \
            .outerjoin(AnswerComment, and_(
                AnswerComment.user_id == User.id,
                AnswerComment.assignment_id == assignment.id,
                AnswerComment.active == True,
                AnswerComment.comment_type == AnswerCommentType.self_evaluation,
            )) \
            .filter(and_(
                or_(
                    AnswerComment.draft == False,
                    Comparison.completed == True
                ),
                UserCourse.course_role != CourseRole.dropped \
            )) \
            .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,
                # self-eval on deleted answer should also be counted. so NOT checking the active flag
                # Answer.active == True,
                Answer.practice == False,
                Answer.draft == False,
                Answer.assignment_id == assignment.id
            ))

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

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

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

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

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

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

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

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

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

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

            answer_comments = AnswerComment.query \
                .filter_by(assignment_id=assignment.id) \
                .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}
예제 #14
0
파일: answer.py 프로젝트: ubc/acj-versus
    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}
예제 #15
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))
예제 #16
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}
예제 #17
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
        }
예제 #18
0
파일: answer.py 프로젝트: ubc/acj-versus
    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))