예제 #1
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))
예제 #2
0
파일: assignment.py 프로젝트: ubc/compair
    def post(self, course_uuid, assignment_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        assignment_criteria = assignment.assignment_criteria
        require(EDIT, assignment,
            title="Assignment Not Saved",
            message="Sorry, your role in this course does not allow you to save assignments.")

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

        params = existing_assignment_parser.parse_args()

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

        old_file = assignment.file

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

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

        assignment.enable_self_evaluation = params.get('enable_self_evaluation', assignment.enable_self_evaluation)
        assignment.self_eval_instructions = params.get('self_eval_instructions', None)
        assignment.self_eval_start = params.get('self_eval_start', None)
        if assignment.self_eval_start is not None:
            assignment.self_eval_start = datetime.datetime.strptime(
                assignment.self_eval_start,
                '%Y-%m-%dT%H:%M:%S.%fZ')
        assignment.self_eval_end = params.get('self_eval_end', None)
        if assignment.self_eval_end is not None:
            assignment.self_eval_end = datetime.datetime.strptime(
                params.get('self_eval_end', assignment.self_eval_end),
                '%Y-%m-%dT%H:%M:%S.%fZ')

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

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

        if assignment.student_answer_count == 0:
            assignment.enable_group_answers = params.get('enable_group_answers')
        elif assignment.enable_group_answers != params.get('enable_group_answers'):
            abort(400, title="Assignment Not Saved",
                message='Group answer settings selection cannot be changed for this assignment because there are already submitted answers.')

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

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

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

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

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

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

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

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

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

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

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

        model_changes = get_model_changes(assignment)

        db.session.commit()

        on_assignment_modified.send(
            self,
            event_name=on_assignment_modified.name,
            user=current_user,
            course_id=course.id,
            assignment=assignment,
            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,
                assignment=assignment,
                data={'assignment_id': assignment.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={'assignment_id': assignment.id, 'file_id': attachment.id})

        # need to reorder after update
        assignment_criteria.reorder()

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

        return marshal(assignment, dataformat.get_assignment())
예제 #3
0
    def post(self, course_uuid, assignment_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        assignment_criteria = assignment.assignment_criteria
        require(EDIT, assignment,
            title="Assignment Not Saved",
            message="Sorry, your role in this course does not allow you to save assignments.")

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

        params = existing_assignment_parser.parse_args()

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

        old_file = assignment.file

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

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

        assignment.enable_self_evaluation = params.get('enable_self_evaluation', assignment.enable_self_evaluation)
        assignment.self_eval_instructions = params.get('self_eval_instructions', None)
        assignment.self_eval_start = params.get('self_eval_start', None)
        if assignment.self_eval_start is not None:
            assignment.self_eval_start = datetime.datetime.strptime(
                assignment.self_eval_start,
                '%Y-%m-%dT%H:%M:%S.%fZ')
        assignment.self_eval_end = params.get('self_eval_end', None)
        if assignment.self_eval_end is not None:
            assignment.self_eval_end = datetime.datetime.strptime(
                params.get('self_eval_end', assignment.self_eval_end),
                '%Y-%m-%dT%H:%M:%S.%fZ')

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

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

        if assignment.student_answer_count == 0:
            assignment.enable_group_answers = params.get('enable_group_answers')
        elif assignment.enable_group_answers != params.get('enable_group_answers'):
            abort(400, title="Assignment Not Saved",
                message='Group answer settings selection cannot be changed for this assignment because there are already submitted answers.')

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

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

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

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

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

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

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

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

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

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

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

        model_changes = get_model_changes(assignment)

        db.session.commit()

        on_assignment_modified.send(
            self,
            event_name=on_assignment_modified.name,
            user=current_user,
            course_id=course.id,
            assignment=assignment,
            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,
                assignment=assignment,
                data={'assignment_id': assignment.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={'assignment_id': assignment.id, 'file_id': attachment.id})

        # need to reorder after update
        assignment_criteria.reorder()

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

        return marshal(assignment, dataformat.get_assignment())
예제 #4
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))