Esempio n. 1
0
    def _submit_all_comparisons_for_assignment(self, assignment, user_id):
        submit_count = 0

        for comparison_example in assignment.comparison_examples:
            comparisons = Comparison.create_new_comparison_set(assignment.id, user_id, False)
            self.assertEqual(comparisons[0].answer1_id, comparison_example.answer1_id)
            self.assertEqual(comparisons[0].answer2_id, comparison_example.answer2_id)
            for comparison in comparisons:
                comparison.completed = True
                comparison.winner_id = min([comparisons[0].answer1_id, comparisons[0].answer2_id])
                db.session.add(comparison)
            submit_count += 1
            db.session.commit()

        for i in range(assignment.number_of_comparisons):
            comparisons = Comparison.create_new_comparison_set(assignment.id, user_id, False)
            for comparison in comparisons:
                comparison.completed = True
                comparison.winner_id = min([comparisons[0].answer1_id, comparisons[0].answer2_id])
                db.session.add(comparison)
            submit_count += 1
            db.session.commit()

            Comparison.calculate_scores(assignment.id)
        return submit_count
Esempio n. 2
0
    def _submit_all_possible_comparisons_for_user(self, user_id):
        example_winner_ids = []
        example_loser_ids = []

        for comparison_example in self.data.comparisons_examples:
            if comparison_example.assignment_id == self.assignment.id:
                comparisons = Comparison.create_new_comparison_set(self.assignment.id, user_id, False)
                self.assertEqual(comparisons[0].answer1_id, comparison_example.answer1_id)
                self.assertEqual(comparisons[0].answer2_id, comparison_example.answer2_id)
                min_id = min([comparisons[0].answer1_id, comparisons[0].answer2_id])
                max_id = max([comparisons[0].answer1_id, comparisons[0].answer2_id])
                example_winner_ids.append(min_id)
                example_loser_ids.append(max_id)
                for comparison in comparisons:
                    comparison.completed = True
                    comparison.winner_id = min_id
                    db.session.add(comparison)
                db.session.commit()

        # self.login(username)
        # calculate number of comparisons to do before user has compared all the pairs it can
        num_eligible_answers = 0  # need to minus one to exclude the logged in user's own answer
        for answer in self.data.get_student_answers():
            if answer.assignment_id == self.assignment.id and answer.user_id != user_id:
                num_eligible_answers += 1
        # n - 1 possible pairs before all answers have been compared
        num_possible_comparisons = num_eligible_answers - 1
        winner_ids = []
        loser_ids = []
        for i in range(num_possible_comparisons):
            comparisons = Comparison.create_new_comparison_set(self.assignment.id, user_id, False)
            answer1_id = comparisons[0].answer1_id
            answer2_id = comparisons[0].answer2_id
            min_id = min([answer1_id, answer2_id])
            max_id = max([answer1_id, answer2_id])
            winner_ids.append(min_id)
            loser_ids.append(max_id)
            for comparison in comparisons:
                comparison.completed = True
                comparison.winner_id = min_id
                db.session.add(comparison)
            db.session.commit()

            Comparison.calculate_scores(self.assignment.id)
        return {
            'comparisons': {
                'winners': winner_ids, 'losers': loser_ids
            },
            'comparison_examples': {
                'winners': example_winner_ids, 'losers': example_loser_ids
            }
        }
Esempio n. 3
0
def recalculate(assignment_id):
    if not assignment_id:
        raise RuntimeError("Assignment with ID {} is not found.".format(assignment_id))

    assignment = Assignment.query.filter_by(id=assignment_id).first()
    if not assignment:
        raise RuntimeError("Assignment with ID {} is not found.".format(assignment_id))

    if prompt_bool("""All current answer scores and answer criterion scores will be overwritten.
Final scores may differ slightly due to floating point rounding (especially if recalculating on different systems).
Are you sure?"""):
        print ('Recalculating scores...')
        Comparison.calculate_scores(assignment.id)
        print ('Recalculate scores successful.')
Esempio n. 4
0
    def add_comparisons(self):
        for assignment in self.assignments:
            for student in self.students:
                for i in range(assignment.total_comparisons_required):
                    comparisons = Comparison.create_new_comparison_set(assignment.id, student.id, False)
                    for comparison in comparisons:
                        comparison.completed = True
                        comparison.winner_id = min([comparisons[0].answer1_id, comparisons[0].answer2_id])
                        db.session.add(comparison)
        db.session.commit()

        return self
Esempio n. 5
0
    def add_comparisons_for_user(self,
                                 assignment,
                                 student,
                                 with_comments=False,
                                 with_self_eval=False):
        answers = set()
        for i in range(assignment.total_comparisons_required):
            comparison = Comparison.create_new_comparison(
                assignment.id, student.id, False)
            comparison.completed = True
            comparison.winner = WinningAnswer.answer1 if comparison.answer1_id < comparison.answer2_id else WinningAnswer.answer2
            for comparison_criterion in comparison.comparison_criteria:
                comparison_criterion.winner = comparison.winner
            db.session.add(comparison)
            db.session.commit()
            self.comparisons.append(comparison)

        if with_comments:
            for answer in answers:
                answer_comment = AnswerCommentFactory(
                    user=student,
                    answer=answer,
                    comment_type=AnswerCommentType.evaluation)
                self.answer_comments.append(answer_comment)
            db.session.commit()

        if with_self_eval:
            student_answer = next(answer for answer in self.answers
                                  if answer.user_id == student.id
                                  and answer.assignment_id == assignment.id)
            if student_answer:
                self_evaluation = AnswerCommentFactory(
                    user=student,
                    answer=student_answer,
                    comment_type=AnswerCommentType.self_evaluation)
                self.self_evaluations.append(self_evaluation)
                db.session.commit()

        return self
Esempio n. 6
0
    def add_comparisons_for_user(self, assignment, student, with_comments=False, with_self_eval=False):
        answers = set()
        for i in range(assignment.total_comparisons_required):
            comparison = Comparison.create_new_comparison(assignment.id, student.id, False)
            comparison.completed = True
            comparison.winner = WinningAnswer.answer1 if comparison.answer1_id < comparison.answer2_id else WinningAnswer.answer2
            for comparison_criterion in comparison.comparison_criteria:
                comparison_criterion.winner = comparison.winner
            db.session.add(comparison)
            db.session.commit()
            self.comparisons.append(comparison)

        if with_comments:
            for answer in answers:
                answer_comment = AnswerCommentFactory(
                    user=student,
                    answer=answer,
                    comment_type=AnswerCommentType.evaluation
                )
                self.answer_comments.append(answer_comment)
            db.session.commit()

        if with_self_eval:
            student_answer = next(
                answer for answer in self.answers if answer.user_id == student.id and answer.assignment_id == assignment.id
            )
            if student_answer:
                self_evaluation = AnswerCommentFactory(
                    user=student,
                    answer=student_answer,
                    comment_type=AnswerCommentType.self_evaluation
                )
                self.self_evaluations.append(self_evaluation)
                db.session.commit()

        return self
Esempio n. 7
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}
Esempio n. 8
0
    def get(self, course_uuid, assignment_uuid):
        """
        Get (or create if needed) a comparison set for assignment.
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        require(READ, assignment)
        require(CREATE, Comparison)
        restrict_user = not allow(MANAGE, assignment)

        if not assignment.compare_grace:
            return {'error': comparison_deadline_message}, 403
        elif not restrict_user and not assignment.educators_can_compare:
            return {'error': educators_can_not_compare_message}, 403

        # check if user has comparisons they have not completed yet
        comparisons = Comparison.query \
            .filter_by(
                assignment_id=assignment.id,
                user_id=current_user.id,
                completed=False
            ) \
            .all()

        new_pair = False

        if len(comparisons) > 0:
            on_comparison_get.send(
                self,
                event_name=on_comparison_get.name,
                user=current_user,
                course_id=course.id,
                data=marshal(comparisons, dataformat.get_comparison(restrict_user)))
        else:
            # if there aren't incomplete comparisons, assign a new one
            try:
                comparisons = Comparison.create_new_comparison_set(assignment.id, current_user.id,
                    skip_comparison_examples=allow(MANAGE, assignment))
                new_pair = True

                on_comparison_create.send(
                    self,
                    event_name=on_comparison_create.name,
                    user=current_user,
                    course_id=course.id,
                    data=marshal(comparisons, dataformat.get_comparison(restrict_user)))

            except InsufficientObjectsForPairException:
                return {"error": "Not enough answers are available for a comparison."}, 400
            except UserComparedAllObjectsException:
                return {"error": "You have compared all the currently available answers."}, 400
            except UnknownPairGeneratorException:
                return {"error": "Generating scored pairs failed, this really shouldn't happen."}, 500

        comparison_count = assignment.completed_comparison_count_for_user(current_user.id)

        return {
            'objects': marshal(comparisons, dataformat.get_comparison(restrict_user)),
            'new_pair': new_pair,
            'current': comparison_count+1
        }
Esempio n. 9
0
    def post(self, course_uuid, assignment_uuid):
        """
        Stores comparison set into the database.
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        require(READ, assignment)
        require(CREATE, Comparison)
        restrict_user = not allow(MANAGE, assignment)

        if not assignment.compare_grace:
            return {'error': comparison_deadline_message}, 403
        elif not restrict_user and not assignment.educators_can_compare:
            return {'error': educators_can_not_compare_message}, 403

        comparisons = Comparison.query \
            .filter_by(
                assignment_id=assignment.id,
                user_id=current_user.id,
                completed=False
            ) \
            .all()

        params = update_comparison_parser.parse_args()
        completed = True

        # check if there are any comparisons to update
        if len(comparisons) == 0:
            return {"error": "There are no comparisons open for evaluation."}, 400

        is_comparison_example = comparisons[0].comparison_example_id != None

        # check if number of comparisons submitted matches number of comparisons needed
        if len(comparisons) != len(params['comparisons']):
            return {"error": "Not all criteria were evaluated."}, 400

        # check if each comparison has a criterion Id and a winner id
        for comparison_to_update in params['comparisons']:
            # check if saving a draft
            if 'draft' in comparison_to_update and comparison_to_update['draft']:
                completed = False

            # ensure criterion param is present
            if 'criterion_id' not in comparison_to_update:
                return {"error": "Missing criterion_id in evaluation."}, 400

            # set default values for cotnent and winner
            comparison_to_update.setdefault('content', None)
            winner_uuid = comparison_to_update.setdefault('winner_id', None)

            # if winner isn't set for any comparisons, then the comparison set isn't complete yet
            if winner_uuid == None:
                completed = False

            # check that we're using criteria that were assigned to the course and that we didn't
            # get duplicate criteria in comparisons
            known_criterion = False
            for comparison in comparisons:
                if comparison_to_update['criterion_id'] == comparison.criterion_uuid:
                    known_criterion = True

                    # check that the winner id matches one of the answer pairs
                    if winner_uuid not in [comparison.answer1_uuid, comparison.answer2_uuid, None]:
                        return {"error": "Selected answer does not match the available answers in comparison."}, 400

                    break
            if not known_criterion:
                return {"error": "Unknown criterion submitted!"}, 400


        # update comparisons
        for comparison in comparisons:
            comparison.completed = completed

            for comparison_to_update in params['comparisons']:
                if comparison_to_update['criterion_id'] != comparison.criterion_uuid:
                    continue

                if comparison_to_update['winner_id'] == comparison.answer1_uuid:
                    comparison.winner_id = comparison.answer1_id
                elif comparison_to_update['winner_id'] == comparison.answer2_uuid:
                    comparison.winner_id = comparison.answer2_id
                else:
                    comparison.winner_id = None
                comparison.content = comparison_to_update['content']

        db.session.commit()

        # update answer scores
        if completed and not is_comparison_example:
            current_app.logger.debug("Doing scoring")
            Comparison.update_scores_1vs1(comparisons)
            #Comparison.calculate_scores(assignment.id)

        # update course & assignment grade for user if comparison is completed
        if completed:
            assignment.calculate_grade(current_user)
            course.calculate_grade(current_user)

        on_comparison_update.send(
            self,
            event_name=on_comparison_update.name,
            user=current_user,
            course_id=course.id,
            assignment=assignment,
            comparisons=comparisons,
            is_comparison_example=is_comparison_example,
            data=marshal(comparisons, dataformat.get_comparison(restrict_user)))

        return {'objects': marshal(comparisons, dataformat.get_comparison(restrict_user))}
Esempio n. 10
0
    def get(self, course_uuid, assignment_uuid):
        """
        Get (or create if needed) a comparison set for assignment.
        """
        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="Assignments and their comparisons can only be seen here by those enrolled in the course. Please double-check your enrollment in this course.")
        require(CREATE, Comparison,
            title="Comparisons Unavailable",
            message="Comparisons can only be seen here by those enrolled in the course. Please double-check your enrollment in this course.")
        restrict_user = not allow(MANAGE, assignment)

        comparison_count = assignment.completed_comparison_count_for_user(current_user.id)

        if not assignment.compare_grace:
            abort(403, title="Comparisons Unavailable",
                message="Sorry, the comparison deadline has passed. No comparisons can be done after the deadline.")
        elif not restrict_user and not assignment.educators_can_compare:
            abort(403, title="Comparisons Unavailable",
                message="Only students can currently compare answers for this assignment. To change these settings to include instructors and teaching assistants, edit the assignment.")
        elif restrict_user and comparison_count >= assignment.total_comparisons_required:
            abort(400, title="Comparisons Completed",
                message="More comparisons aren't available, since you've finished your comparisons for this assignment. Good job!")

        # check if user has a comparison they have not completed yet
        comparison = Comparison.query \
            .options(joinedload('comparison_criteria')) \
            .filter_by(
                assignment_id=assignment.id,
                user_id=current_user.id,
                completed=False
            ) \
            .first()

        if comparison:
            on_comparison_get.send(
                self,
                event_name=on_comparison_get.name,
                user=current_user,
                course_id=course.id,
                data=marshal(comparison, dataformat.get_comparison(restrict_user)))
        else:
            # if there isn't an incomplete comparison, assign a new one
            try:
                comparison = Comparison.create_new_comparison(assignment.id, current_user.id,
                    skip_comparison_examples=allow(MANAGE, assignment))

                on_comparison_create.send(
                    self,
                    event_name=on_comparison_create.name,
                    user=current_user,
                    course_id=course.id,
                    data=marshal(comparison, dataformat.get_comparison(restrict_user)))

            except InsufficientObjectsForPairException:
                abort(400, title="Comparisons Unavailable", message="Not enough answers are available for you to do comparisons right now. Please check back later for more answers.")
            except UserComparedAllObjectsException:
                abort(400, title="Comparisons Unavailable", message="You have compared all the currently available answer pairs. Please check back later for more answers.")
            except UnknownPairGeneratorException:
                abort(500, title="Comparisons Unavailable", message="Generating scored pairs failed, this really shouldn't happen.")


        # get evaluation comments for answers by current user
        answer_comments = AnswerComment.query \
            .join("answer") \
            .filter(and_(
                # both draft and completed comments are allowed
                AnswerComment.active == True,
                AnswerComment.comment_type == AnswerCommentType.evaluation,
                Answer.id.in_([comparison.answer1_id, comparison.answer2_id]),
                AnswerComment.user_id == current_user.id
            )) \
            .order_by(AnswerComment.draft, AnswerComment.created) \
            .all()

        comparison.answer1_feedback = [c for c in answer_comments if c.answer_id == comparison.answer1_id]
        comparison.answer2_feedback = [c for c in answer_comments if c.answer_id == comparison.answer2_id]

        return {
            'comparison': marshal(comparison, dataformat.get_comparison(restrict_user,
                include_answer_author=False, include_score=False, with_feedback=True)),
            'current': comparison_count+1
        }
Esempio n. 11
0
    def post(self, course_uuid, assignment_uuid):
        """
        Stores comparison set into the database.
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        require(READ, assignment,
            title="Comparison Not Saved",
            message="Comparisons can only be saved by those enrolled in the course. Please double-check your enrollment in this course.")
        require(EDIT, Comparison,
            title="Comparison Not Saved",
            message="Comparisons can only be saved by those enrolled in the course. Please double-check your enrollment in this course.")
        restrict_user = not allow(MANAGE, assignment)

        if not assignment.compare_grace:
            abort(403, title="Comparison Not Saved",
                message="Sorry, the comparison deadline has passed. No comparisons can be done after the deadline.")
        elif not restrict_user and not assignment.educators_can_compare:
            abort(403, title="Comparison Not Saved",
                message="Only students can save answer comparisons for this assignment. To change these settings to include instructors and teaching assistants, edit the assignment.")

        comparison = Comparison.query \
            .options(joinedload('comparison_criteria')) \
            .filter_by(
                assignment_id=assignment.id,
                user_id=current_user.id,
                completed=False
            ) \
            .first()

        params = update_comparison_parser.parse_args()
        completed = True

        # check if there are any comparisons to update
        if not comparison:
            abort(400, title="Comparison Not Saved", message="There are no comparisons open for evaluation.")

        is_comparison_example = comparison.comparison_example_id != None

        # check if number of comparison criteria submitted matches number of comparison criteria needed
        if len(comparison.comparison_criteria) != len(params['comparison_criteria']):
            abort(400, title="Comparison Not Saved", message="Please double-check that all criteria were evaluated and try saving again.")

        if params.get('draft'):
            completed = False

        # check if each comparison has a criterion Id and a winner id
        for comparison_criterion_update in params['comparison_criteria']:
            # ensure criterion param is present
            if 'criterion_id' not in comparison_criterion_update:
                abort(400, title="Comparison Not Saved", message="Sorry, the assignment is missing criteria. Please reload the page and try again.")

            # set default values for cotnent and winner
            comparison_criterion_update.setdefault('content', None)
            winner = comparison_criterion_update.setdefault('winner', None)

            # if winner isn't set for any comparison criterion, then the comparison isn't complete yet
            if winner == None:
                completed = False

            # check that we're using criteria that were assigned to the course and that we didn't
            # get duplicate criteria in comparison criteria
            known_criterion = False
            for comparison_criterion in comparison.comparison_criteria:
                if comparison_criterion_update['criterion_id'] == comparison_criterion.criterion_uuid:
                    known_criterion = True

                    # check that the winner id matches one of the answer pairs
                    if winner not in [None, WinningAnswer.answer1.value, WinningAnswer.answer2.value]:
                        abort(400, title="Comparison Not Saved", message="Please select an answer from the two answers provided for each criterion and try saving again.")

                    break
            if not known_criterion:
                abort(400, title="Comparison Not Saved", message="You are attempting to submit a comparison of an unknown criterion. Please remove the unknown criterion and try again.")


        # update comparison criterion
        comparison.completed = completed
        comparison.winner = None

        assignment_criteria = assignment.assignment_criteria
        answer1_weight = 0
        answer2_weight = 0

        for comparison_criterion in comparison.comparison_criteria:
            for comparison_criterion_update in params['comparison_criteria']:
                if comparison_criterion_update['criterion_id'] != comparison_criterion.criterion_uuid:
                    continue

                winner = WinningAnswer(comparison_criterion_update['winner']) if comparison_criterion_update['winner'] != None else None

                comparison_criterion.winner = winner
                comparison_criterion.content = comparison_criterion_update['content']

                if completed:
                    weight = next((
                        assignment_criterion.weight for assignment_criterion in assignment_criteria \
                        if assignment_criterion.criterion_uuid == comparison_criterion.criterion_uuid
                    ), 0)
                    if winner == WinningAnswer.answer1:
                        answer1_weight += weight
                    elif winner == WinningAnswer.answer2:
                        answer2_weight += weight

        if completed:
            if answer1_weight > answer2_weight:
                comparison.winner = WinningAnswer.answer1
            elif answer1_weight < answer2_weight:
                comparison.winner = WinningAnswer.answer2
            else:
                comparison.winner = WinningAnswer.draw
        else:
            # ensure that the comparison is 'touched' when saving a draft
            comparison.modified = datetime.datetime.utcnow()

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

        db.session.commit()

        # update answer scores
        if completed and not is_comparison_example:
            current_app.logger.debug("Doing scoring")
            Comparison.update_scores_1vs1(comparison)
            #Comparison.calculate_scores(assignment.id)

        # update course & assignment grade for user if comparison is completed
        if completed:
            assignment.calculate_grade(current_user)
            course.calculate_grade(current_user)

        on_comparison_update.send(
            self,
            event_name=on_comparison_update.name,
            user=current_user,
            course_id=course.id,
            assignment=assignment,
            comparison=comparison,
            is_comparison_example=is_comparison_example,
            data=marshal(comparison, dataformat.get_comparison(restrict_user)))

        return {'comparison': marshal(comparison, dataformat.get_comparison(restrict_user, include_answer_author=False, include_score=False))}
Esempio n. 12
0
    def get(self, course_uuid, assignment_uuid):
        """
        Get (or create if needed) a comparison set for assignment.
        """
        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=
            "Assignments and their comparisons can only be seen here by those enrolled in the course. Please double-check your enrollment in this course."
        )
        require(
            CREATE,
            Comparison,
            title="Comparisons Unavailable",
            message=
            "Comparisons can only be seen here by those enrolled in the course. Please double-check your enrollment in this course."
        )
        restrict_user = not allow(MANAGE, assignment)

        comparison_count = assignment.completed_comparison_count_for_user(
            current_user.id)

        if not assignment.compare_grace:
            abort(
                403,
                title="Comparisons Unavailable",
                message=
                "Sorry, the comparison deadline has passed. No comparisons can be done after the deadline."
            )
        elif not restrict_user and not assignment.educators_can_compare:
            abort(
                403,
                title="Comparisons Unavailable",
                message=
                "Only students can currently compare answers for this assignment. To change these settings to include instructors and teaching assistants, edit the assignment."
            )
        elif restrict_user and comparison_count >= assignment.total_comparisons_required:
            abort(
                400,
                title="Comparisons Completed",
                message=
                "More comparisons aren't available, since you've finished your comparisons for this assignment. Good job!"
            )

        # check if user has a comparison they have not completed yet
        new_pair = False
        comparison = Comparison.query \
            .options(joinedload('comparison_criteria')) \
            .filter_by(
                assignment_id=assignment.id,
                user_id=current_user.id,
                completed=False
            ) \
            .first()

        if comparison:
            on_comparison_get.send(
                self,
                event_name=on_comparison_get.name,
                user=current_user,
                course_id=course.id,
                data=marshal(comparison,
                             dataformat.get_comparison(restrict_user)))
        else:
            # if there isn't an incomplete comparison, assign a new one
            try:
                comparison = Comparison.create_new_comparison(
                    assignment.id,
                    current_user.id,
                    skip_comparison_examples=allow(MANAGE, assignment))
                new_pair = True

                on_comparison_create.send(
                    self,
                    event_name=on_comparison_create.name,
                    user=current_user,
                    course_id=course.id,
                    data=marshal(comparison,
                                 dataformat.get_comparison(restrict_user)))

            except InsufficientObjectsForPairException:
                abort(
                    400,
                    title="Comparisons Unavailable",
                    message=
                    "Not enough answers are available for you to do comparisons right now. Please check back later for more answers."
                )
            except UserComparedAllObjectsException:
                abort(
                    400,
                    title="Comparisons Unavailable",
                    message=
                    "You have compared all the currently available answer pairs. Please check back later for more answers."
                )
            except UnknownPairGeneratorException:
                abort(
                    500,
                    title="Comparisons Unavailable",
                    message=
                    "Generating scored pairs failed, this really shouldn't happen."
                )

        # get evaluation comments for answers by current user
        answer_comments = AnswerComment.query \
            .join("answer") \
            .filter(and_(
                # both draft and completed comments are allowed
                AnswerComment.active == True,
                AnswerComment.comment_type == AnswerCommentType.evaluation,
                Answer.id.in_([comparison.answer1_id, comparison.answer2_id]),
                AnswerComment.user_id == current_user.id
            )) \
            .order_by(AnswerComment.draft, AnswerComment.created) \
            .all()

        comparison.answer1_feedback = [
            c for c in answer_comments if c.answer_id == comparison.answer1_id
        ]
        comparison.answer2_feedback = [
            c for c in answer_comments if c.answer_id == comparison.answer2_id
        ]

        return {
            'comparison':
            marshal(
                comparison,
                dataformat.get_comparison(restrict_user,
                                          include_answer_author=False,
                                          include_score=False,
                                          with_feedback=True)),
            'new_pair':
            new_pair,
            'current':
            comparison_count + 1
        }
Esempio n. 13
0
    def post(self, course_uuid, assignment_uuid):
        """
        Stores comparison set into the database.
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        require(
            READ,
            assignment,
            title="Comparison Not Saved",
            message=
            "Comparisons can only be saved by those enrolled in the course. Please double-check your enrollment in this course."
        )
        require(
            EDIT,
            Comparison,
            title="Comparison Not Saved",
            message=
            "Comparisons can only be saved by those enrolled in the course. Please double-check your enrollment in this course."
        )
        restrict_user = not allow(MANAGE, assignment)

        if not assignment.compare_grace:
            abort(
                403,
                title="Comparison Not Saved",
                message=
                "Sorry, the comparison deadline has passed. No comparisons can be done after the deadline."
            )
        elif not restrict_user and not assignment.educators_can_compare:
            abort(
                403,
                title="Comparison Not Saved",
                message=
                "Only students can save answer comparisons for this assignment. To change these settings to include instructors and teaching assistants, edit the assignment."
            )

        comparison = Comparison.query \
            .options(joinedload('comparison_criteria')) \
            .filter_by(
                assignment_id=assignment.id,
                user_id=current_user.id,
                completed=False
            ) \
            .first()

        params = update_comparison_parser.parse_args()
        completed = True

        # check if there are any comparisons to update
        if not comparison:
            abort(400,
                  title="Comparison Not Saved",
                  message="There are no comparisons open for evaluation.")

        is_comparison_example = comparison.comparison_example_id != None

        # check if number of comparison criteria submitted matches number of comparison criteria needed
        if len(comparison.comparison_criteria) != len(
                params['comparison_criteria']):
            abort(
                400,
                title="Comparison Not Saved",
                message=
                "Please double-check that all criteria were evaluated and try saving again."
            )

        if params.get('draft'):
            completed = False

        # check if each comparison has a criterion Id and a winner id
        for comparison_criterion_update in params['comparison_criteria']:
            # ensure criterion param is present
            if 'criterion_id' not in comparison_criterion_update:
                abort(
                    400,
                    title="Comparison Not Saved",
                    message=
                    "Sorry, the assignment is missing criteria. Please reload the page and try again."
                )

            # set default values for cotnent and winner
            comparison_criterion_update.setdefault('content', None)
            winner = comparison_criterion_update.setdefault('winner', None)

            # if winner isn't set for any comparison criterion, then the comparison isn't complete yet
            if winner == None:
                completed = False

            # check that we're using criteria that were assigned to the course and that we didn't
            # get duplicate criteria in comparison criteria
            known_criterion = False
            for comparison_criterion in comparison.comparison_criteria:
                if comparison_criterion_update[
                        'criterion_id'] == comparison_criterion.criterion_uuid:
                    known_criterion = True

                    # check that the winner id matches one of the answer pairs
                    if winner not in [
                            None, WinningAnswer.answer1.value,
                            WinningAnswer.answer2.value
                    ]:
                        abort(
                            400,
                            title="Comparison Not Saved",
                            message=
                            "Please select an answer from the two answers provided for each criterion and try saving again."
                        )

                    break
            if not known_criterion:
                abort(
                    400,
                    title="Comparison Not Saved",
                    message=
                    "You are attempting to submit a comparison of an unknown criterion. Please remove the unknown criterion and try again."
                )

        # update comparison criterion
        comparison.completed = completed
        comparison.winner = None

        assignment_criteria = assignment.assignment_criteria
        answer1_weight = 0
        answer2_weight = 0

        for comparison_criterion in comparison.comparison_criteria:
            for comparison_criterion_update in params['comparison_criteria']:
                if comparison_criterion_update[
                        'criterion_id'] != comparison_criterion.criterion_uuid:
                    continue

                winner = WinningAnswer(
                    comparison_criterion_update['winner']
                ) if comparison_criterion_update['winner'] != None else None

                comparison_criterion.winner = winner
                comparison_criterion.content = comparison_criterion_update[
                    'content']

                if completed:
                    weight = next((
                        assignment_criterion.weight for assignment_criterion in assignment_criteria \
                        if assignment_criterion.criterion_uuid == comparison_criterion.criterion_uuid
                    ), 0)
                    if winner == WinningAnswer.answer1:
                        answer1_weight += weight
                    elif winner == WinningAnswer.answer2:
                        answer2_weight += weight

        if completed:
            if answer1_weight > answer2_weight:
                comparison.winner = WinningAnswer.answer1
            elif answer1_weight < answer2_weight:
                comparison.winner = WinningAnswer.answer2
            else:
                comparison.winner = WinningAnswer.draw
        else:
            # ensure that the comparison is 'touched' when saving a draft
            comparison.modified = datetime.datetime.utcnow()

        db.session.commit()

        # update answer scores
        if completed and not is_comparison_example:
            current_app.logger.debug("Doing scoring")
            Comparison.update_scores_1vs1(comparison)
            #Comparison.calculate_scores(assignment.id)

        # update course & assignment grade for user if comparison is completed
        if completed:
            assignment.calculate_grade(current_user)
            course.calculate_grade(current_user)

        on_comparison_update.send(
            self,
            event_name=on_comparison_update.name,
            user=current_user,
            course_id=course.id,
            assignment=assignment,
            comparison=comparison,
            is_comparison_example=is_comparison_example,
            data=marshal(comparison, dataformat.get_comparison(restrict_user)))

        return {
            'comparison':
            marshal(
                comparison,
                dataformat.get_comparison(restrict_user,
                                          include_answer_author=False,
                                          include_score=False))
        }
Esempio n. 14
0
    def get(self, course_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        require(READ, 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()

        # 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
            )
            assignment_drafts = [draft for draft in drafts if draft.assignment_id == assignment.id]
            comparison_count = assignment.completed_comparison_count_for_user(current_user.id)
            comparison_availble = Comparison.comparison_avialble_for_user(course.id, assignment.id, current_user.id)

            statuses[assignment.uuid] = {
                'answers': {
                    'answered': answer_count > 0,
                    'count': answer_count,
                    'has_draft': len(assignment_drafts) > 0,
                    'draft_ids': [draft.uuid for draft in assignment_drafts]
                },
                'comparisons': {
                    'available': comparison_availble,
                    '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}
Esempio n. 15
0
    def get(self, course_uuid, assignment_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        require(READ, assignment)

        answer_count = Answer.query \
            .filter_by(
                user_id=current_user.id,
                assignment_id=assignment.id,
                active=True,
                practice=False,
                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)
        comparison_availble = Comparison.comparison_avialble_for_user(course.id, assignment.id, current_user.id)

        status = {
            'answers': {
                'answered': answer_count > 0,
                'count': answer_count,
                'has_draft': len(drafts) > 0,
                'draft_ids': [draft.uuid for draft in drafts]
            },
            'comparisons': {
                'available': comparison_availble,
                '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}
Esempio n. 16
0
    def _submit_all_possible_comparisons_for_user(self, user_id):
        example_winner_ids = []
        example_loser_ids = []

        for comparison_example in self.data.comparisons_examples:
            if comparison_example.assignment_id == self.assignment.id:
                comparison = Comparison.create_new_comparison(
                    self.assignment.id, user_id, False)
                self.assertEqual(comparison.answer1_id,
                                 comparison_example.answer1_id)
                self.assertEqual(comparison.answer2_id,
                                 comparison_example.answer2_id)
                min_id = min([comparison.answer1_id, comparison.answer2_id])
                max_id = max([comparison.answer1_id, comparison.answer2_id])
                example_winner_ids.append(min_id)
                example_loser_ids.append(max_id)

                comparison.completed = True
                comparison.winner = WinningAnswer.answer1 if comparison.answer1_id < comparison.answer2_id else WinningAnswer.answer2
                for comparison_criterion in comparison.comparison_criteria:
                    comparison_criterion.winner = comparison.winner
                db.session.add(comparison)

                db.session.commit()

        # self.login(username)
        # calculate number of comparisons to do before user has compared all the pairs it can
        num_eligible_answers = 0  # need to minus one to exclude the logged in user's own answer
        for answer in self.data.get_student_answers():
            if answer.assignment_id == self.assignment.id and answer.user_id != user_id:
                num_eligible_answers += 1
        # n(n-1)/2 possible pairs before all answers have been compared
        num_possible_comparisons = int(num_eligible_answers *
                                       (num_eligible_answers - 1) / 2)
        winner_ids = []
        loser_ids = []
        for i in range(num_possible_comparisons):
            comparison = Comparison.create_new_comparison(
                self.assignment.id, user_id, False)
            min_id = min([comparison.answer1_id, comparison.answer2_id])
            max_id = max([comparison.answer1_id, comparison.answer2_id])
            winner_ids.append(min_id)
            loser_ids.append(max_id)

            comparison.completed = True
            comparison.winner = WinningAnswer.answer1 if comparison.answer1_id < comparison.answer2_id else WinningAnswer.answer2
            for comparison_criterion in comparison.comparison_criteria:
                comparison_criterion.winner = comparison.winner
            db.session.add(comparison)

            db.session.commit()

            Comparison.calculate_scores(self.assignment.id)
        return {
            'comparisons': {
                'winners': winner_ids,
                'losers': loser_ids
            },
            'comparison_examples': {
                'winners': example_winner_ids,
                'losers': example_loser_ids
            }
        }