def post(self, course_uuid, assignment_uuid, comparison_example_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) comparison_example = ComparisonExample.get_active_by_uuid_or_404(comparison_example_uuid) require(EDIT, comparison_example) params = existing_comparison_example_parser.parse_args() answer1_uuid = params.get("answer1_id") answer2_uuid = params.get("answer2_id") if answer1_uuid: answer1 = Answer.get_active_by_uuid_or_404(answer1_uuid) answer1.practice = True comparison_example.answer1 = answer1 else: return {"error": "Comparison examples must have 2 answers"}, 400 if answer2_uuid: answer2 = Answer.get_active_by_uuid_or_404(answer2_uuid) answer2.practice = True comparison_example.answer2 = answer2 else: return {"error": "Comparison examples must have 2 answers"}, 400 on_comparison_example_modified.send( self, event_name=on_comparison_example_modified.name, user=current_user, course_id=course.id, data=get_model_changes(comparison_example)) db.session.add(comparison_example) db.session.commit() return marshal(comparison_example, dataformat.get_comparison_example())
def post(self, course_uuid, assignment_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) require( CREATE, ComparisonExample(assignment=Assignment(course_id=course.id)), title="Comparison Example Not Saved", message= "Sorry, your role in this course does not allow you to save practice answers." ) new_comparison_example = ComparisonExample(assignment_id=assignment.id) params = new_comparison_example_parser.parse_args() answer1_uuid = params.get("answer1_id") answer2_uuid = params.get("answer2_id") if answer1_uuid: answer1 = Answer.get_active_by_uuid_or_404(answer1_uuid) answer1.practice = True new_comparison_example.answer1 = answer1 else: abort( 400, title="Comparison Example Not Saved", message= "Please add two answers with content to the practice answers and try again." ) if answer2_uuid: answer2 = Answer.get_active_by_uuid_or_404(answer2_uuid) answer2.practice = True new_comparison_example.answer2 = answer2 else: abort( 400, title="Comparison Example Not Saved", message= "Please add two answers with content to the practice answers and try again." ) on_comparison_example_create.send( self, event_name=on_comparison_example_create.name, user=current_user, course_id=course.id, data=marshal( new_comparison_example, dataformat.get_comparison_example(with_answers=False))) db.session.add(new_comparison_example) db.session.commit() return marshal(new_comparison_example, dataformat.get_comparison_example())
def post(self, course_uuid, assignment_uuid, comparison_example_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) comparison_example = ComparisonExample.get_active_by_uuid_or_404( comparison_example_uuid) require( EDIT, comparison_example, title="Comparison Example Not Saved", message= "Sorry, your role in this course does not allow you to save practice answers." ) params = existing_comparison_example_parser.parse_args() answer1_uuid = params.get("answer1_id") answer2_uuid = params.get("answer2_id") if answer1_uuid: answer1 = Answer.get_active_by_uuid_or_404(answer1_uuid) answer1.practice = True comparison_example.answer1 = answer1 else: abort( 400, title="Comparison Example Not Saved", message= "Please add two answers with content to the practice answers and try again." ) if answer2_uuid: answer2 = Answer.get_active_by_uuid_or_404(answer2_uuid) answer2.practice = True comparison_example.answer2 = answer2 else: abort( 400, title="Comparison Example Not Saved", message= "Please add two answers with content to the practice answers and try again." ) model_changes = get_model_changes(comparison_example) db.session.add(comparison_example) db.session.commit() on_comparison_example_modified.send( self, event_name=on_comparison_example_modified.name, user=current_user, course_id=course.id, data=model_changes) return marshal(comparison_example, dataformat.get_comparison_example())
def get(self, course_uuid, assignment_uuid, answer_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404( answer_uuid, joinedloads=['file', 'user', 'group', 'score']) require( READ, answer, title="Answer Unavailable", message= "Sorry, your role in this course does not allow you to view this answer." ) restrict_user = not allow(MANAGE, assignment) on_answer_get.send(self, event_name=on_answer_get.name, user=current_user, course_id=course.id, data={ 'assignment_id': assignment.id, 'answer_id': answer.id }) # don't include score/rank unless the user is non-restricted include_score = not restrict_user return marshal( answer, dataformat.get_answer(restrict_user, include_score=include_score))
def post(self, course_uuid, assignment_uuid, answer_uuid): """ Mark an answer as being a top answer :param course_uuid: :param assignment_uuid: :param answer_uuid: :return: marked answer """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) require(MANAGE, answer, title="Answer Not Added", message="Your role in this course does not allow you to add to the list of instructor-picked answers.") params = top_answer_parser.parse_args() answer.top_answer = params.get('top_answer') db.session.add(answer) on_set_top_answer.send( self, event_name=on_set_top_answer.name, user=current_user, course_id=course.id, assignment_id=assignment.id, data={'answer_id': answer.id, 'top_answer': answer.top_answer}) db.session.commit() return marshal(answer, dataformat.get_answer(restrict_user=False))
def delete(self, course_uuid, assignment_uuid, answer_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) require(DELETE, answer, title="Answer Not Deleted", message="Sorry, your role in this course does not allow you to delete this answer.") if current_app.config.get('DEMO_INSTALLATION', False): from data.fixtures import DemoDataFixture if assignment.id in DemoDataFixture.DEFAULT_ASSIGNMENT_IDS and answer.user_id in DemoDataFixture.DEFAULT_STUDENT_IDS: abort(400, title="Answer Not Deleted", message="Sorry, you cannot delete the default student demo answers.") answer.active = False db.session.commit() # update course & assignment grade for user if answer was fully submitted if not answer.draft: if answer.user: assignment.calculate_grade(answer.user) course.calculate_grade(answer.user) elif answer.group: assignment.calculate_group_grade(answer.group) course.calculate_group_grade(answer.group) on_answer_delete.send( self, event_name=on_answer_delete.name, user=current_user, course_id=course.id, answer=answer, data={'assignment_id': assignment.id, 'answer_id': answer.id}) return {'id': answer.uuid}
def delete(self, course_uuid, assignment_uuid, answer_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) require(DELETE, answer) answer.active = False if answer.file: answer.file.active = False db.session.commit() # update course & assignment grade for user if answer was fully submitted if not answer.draft: assignment.calculate_grade(answer.user) course.calculate_grade(answer.user) on_answer_delete.send( self, event_name=on_answer_delete.name, user=current_user, course_id=course.id, answer=answer, data={'assignment_id': assignment.id, 'answer_id': answer.id}) return {'id': answer.uuid}
def post(self, course_uuid, assignment_uuid, answer_uuid): """ Mark an answer as being a top answer :param course_uuid: :param assignment_uuid: :param answer_uuid: :return: marked answer """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) require(MANAGE, answer) params = top_answer_parser.parse_args() answer.top_answer = params.get('top_answer') db.session.add(answer) on_set_top_answer.send( self, event_name=on_set_top_answer.name, user=current_user, course_id=course.id, assignment_id=assignment.id, data={'answer_id': answer.id, 'top_answer': answer.top_answer}) db.session.commit() return marshal(answer, dataformat.get_answer(restrict_user=False))
def get(self, course_uuid, assignment_uuid, answer_uuid, answer_comment_uuid): """ Get an answer comment """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) answer_comment = AnswerComment.get_active_by_uuid_or_404( answer_comment_uuid) require( READ, answer_comment, title="Reply Unavailable", message= "Sorry, your role in this course does not allow you to view this reply." ) on_answer_comment_get.send(self, event_name=on_answer_comment_get.name, user=current_user, course_id=course.id, data={ 'assignment_id': assignment.id, 'answer_id': answer.id, 'answer_comment_id': answer_comment.id }) return marshal(answer_comment, dataformat.get_answer_comment())
def post(self, course_uuid, assignment_uuid, answer_uuid): """ Create comment for an answer """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) require(CREATE, AnswerComment(course_id=course.id)) answer_comment = AnswerComment(answer_id=answer.id) params = new_answer_comment_parser.parse_args() answer_comment.draft = params.get("draft") answer_comment.content = params.get("content") # require content not empty if not a draft if not answer_comment.content and not answer_comment.draft: return {"error": "The comment content is empty!"}, 400 if params.get("user_id") and current_user.system_role == SystemRole.sys_admin: user = User.get_by_uuid_or_404(params.get("user_id")) answer_comment.user_id = user.id else: answer_comment.user_id = current_user.id comment_types = [ AnswerCommentType.public.value, AnswerCommentType.private.value, AnswerCommentType.evaluation.value, AnswerCommentType.self_evaluation.value, ] comment_type = params.get("comment_type") if comment_type not in comment_types: abort(400) answer_comment.comment_type = AnswerCommentType(comment_type) db.session.add(answer_comment) db.session.commit() # update course & assignment grade for user if self-evaluation is completed if not answer_comment.draft and answer_comment.comment_type == AnswerCommentType.self_evaluation: assignment.calculate_grade(answer_comment.user) course.calculate_grade(answer_comment.user) on_answer_comment_create.send( self, event_name=on_answer_comment_create.name, user=current_user, course_id=course.id, answer_comment=answer_comment, data=marshal(answer_comment, dataformat.get_answer_comment(False)), ) return marshal(answer_comment, dataformat.get_answer_comment())
def post(self, course_uuid, assignment_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) require(CREATE, ComparisonExample(assignment=Assignment(course_id=course.id)), title="Comparison Example Not Saved", message="Sorry, your role in this course does not allow you to save practice answers.") new_comparison_example = ComparisonExample(assignment_id=assignment.id) params = new_comparison_example_parser.parse_args() answer1_uuid = params.get("answer1_id") answer2_uuid = params.get("answer2_id") if answer1_uuid: answer1 = Answer.get_active_by_uuid_or_404(answer1_uuid) answer1.practice = True new_comparison_example.answer1 = answer1 else: abort(400, title="Comparison Example Not Saved", message="Please add two answers with content to the practice answers and try again.") if answer2_uuid: answer2 = Answer.get_active_by_uuid_or_404(answer2_uuid) answer2.practice = True new_comparison_example.answer2 = answer2 else: abort(400, title="Comparison Example Not Saved", message="Please add two answers with content to the practice answers and try again.") on_comparison_example_create.send( self, event_name=on_comparison_example_create.name, user=current_user, course_id=course.id, data=marshal(new_comparison_example, dataformat.get_comparison_example(with_answers=False))) db.session.add(new_comparison_example) db.session.commit() return marshal(new_comparison_example, dataformat.get_comparison_example())
def post(self, course_uuid, assignment_uuid, comparison_example_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) comparison_example = ComparisonExample.get_active_by_uuid_or_404(comparison_example_uuid) require(EDIT, comparison_example, title="Comparison Example Not Saved", message="Sorry, your role in this course does not allow you to save practice answers.") params = existing_comparison_example_parser.parse_args() answer1_uuid = params.get("answer1_id") answer2_uuid = params.get("answer2_id") if answer1_uuid: answer1 = Answer.get_active_by_uuid_or_404(answer1_uuid) answer1.practice = True comparison_example.answer1 = answer1 else: abort(400, title="Comparison Example Not Saved", message="Please add two answers with content to the practice answers and try again.") if answer2_uuid: answer2 = Answer.get_active_by_uuid_or_404(answer2_uuid) answer2.practice = True comparison_example.answer2 = answer2 else: abort(400, title="Comparison Example Not Saved", message="Please add two answers with content to the practice answers and try again.") model_changes = get_model_changes(comparison_example) db.session.add(comparison_example) db.session.commit() on_comparison_example_modified.send( self, event_name=on_comparison_example_modified.name, user=current_user, course_id=course.id, data=model_changes) return marshal(comparison_example, dataformat.get_comparison_example())
def post(self, course_uuid, assignment_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) require(CREATE, ComparisonExample(assignment=Assignment(course_id=course.id))) new_comparison_example = ComparisonExample(assignment_id=assignment.id) params = new_comparison_example_parser.parse_args() answer1_uuid = params.get("answer1_id") answer2_uuid = params.get("answer2_id") if answer1_uuid: answer1 = Answer.get_active_by_uuid_or_404(answer1_uuid) answer1.practice = True new_comparison_example.answer1 = answer1 else: return {"error": "Comparison examples must have 2 answers"}, 400 if answer2_uuid: answer2 = Answer.get_active_by_uuid_or_404(answer2_uuid) answer2.practice = True new_comparison_example.answer2 = answer2 else: return {"error": "Comparison examples must have 2 answers"}, 400 on_comparison_example_create.send( self, event_name=on_comparison_example_create.name, user=current_user, course_id=course.id, data=marshal(new_comparison_example, dataformat.get_comparison_example(with_answers=False))) db.session.add(new_comparison_example) db.session.commit() return marshal(new_comparison_example, dataformat.get_comparison_example())
def get(self, course_uuid, assignment_uuid, answer_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404( answer_uuid, joinedloads=['file', 'user', 'scores'] ) require(READ, answer) restrict_user = not allow(MANAGE, assignment) on_answer_get.send( self, event_name=on_answer_get.name, user=current_user, course_id=course.id, data={'assignment_id': assignment.id, 'answer_id': answer.id}) return marshal(answer, dataformat.get_answer(restrict_user))
def get(self, course_uuid, assignment_uuid, answer_uuid, answer_comment_uuid): """ Get an answer comment """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) answer_comment = AnswerComment.get_active_by_uuid_or_404(answer_comment_uuid) require(READ, answer_comment) on_answer_comment_get.send( self, event_name=on_answer_comment_get.name, user=current_user, course_id=course.id, data={"assignment_id": assignment.id, "answer_id": answer.id, "answer_comment_id": answer_comment.id}, ) return marshal(answer_comment, dataformat.get_answer_comment())
def post(self, course_uuid, assignment_uuid, answer_uuid): """ Mark an answer as inappropriate or incomplete to instructors :param course_uuid: :param assignment_uuid: :param answer_uuid: :return: marked answer """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) require(READ, answer, title="Answer Not Flagged", message="Sorry, your role in this course does not allow you to flag answers.") restrict_user = not allow(MANAGE, answer) # anyone can flag an answer, but only the original flagger or someone who can manage # the answer can unflag it if answer.flagged and answer.flagger_user_id != current_user.id and \ not allow(MANAGE, answer): abort(400, title="Answer Not Updated", message="Sorry, your role in this course does not allow you to unflag answers.") params = flag_parser.parse_args() answer.flagged = params['flagged'] answer.flagger_user_id = current_user.id db.session.add(answer) db.session.commit() on_answer_flag.send( self, event_name=on_answer_flag.name, user=current_user, course_id=course.id, assignment_id=assignment.id, answer=answer, data={'answer_id': answer.id, 'flag': answer.flagged}) return marshal(answer, dataformat.get_answer(restrict_user))
def post(self, course_uuid, assignment_uuid, answer_uuid): """ Mark an answer as inappropriate or incomplete to instructors :param course_uuid: :param assignment_uuid: :param answer_uuid: :return: marked answer """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) require(READ, answer) restrict_user = not allow(MANAGE, answer) # anyone can flag an answer, but only the original flagger or someone who can manage # the answer can unflag it if answer.flagged and answer.flagger_user_id != current_user.id and \ not allow(MANAGE, answer): return {"error": "You do not have permission to unflag this answer."}, 400 params = flag_parser.parse_args() answer.flagged = params['flagged'] answer.flagger_user_id = current_user.id db.session.add(answer) db.session.commit() on_answer_flag.send( self, event_name=on_answer_flag.name, user=current_user, course_id=course.id, assignment_id=assignment.id, answer=answer, data={'answer_id': answer.id, 'flag': answer.flagged}) return marshal(answer, dataformat.get_answer(restrict_user))
def get(self, course_uuid, assignment_uuid, answer_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404( answer_uuid, joinedloads=['file', 'user', 'group', 'score'] ) require(READ, answer, title="Answer Unavailable", message="Sorry, your role in this course does not allow you to view this answer.") restrict_user = not allow(MANAGE, assignment) on_answer_get.send( self, event_name=on_answer_get.name, user=current_user, course_id=course.id, data={'assignment_id': assignment.id, 'answer_id': answer.id}) # don't include score/rank unless the user is non-restricted include_score = not restrict_user return marshal(answer, dataformat.get_answer(restrict_user, include_score=include_score))
def get(self, course_uuid, assignment_uuid, answer_uuid, answer_comment_uuid): """ Get an answer comment """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) answer_comment = AnswerComment.get_active_by_uuid_or_404(answer_comment_uuid) require(READ, answer_comment, title="Feedback Unavailable", message="Sorry, your role in this course does not allow you to view this feedback.") restrict_user = not allow(MANAGE, assignment) restrict_user = not allow(MANAGE, assignment) on_answer_comment_get.send( self, event_name=on_answer_comment_get.name, user=current_user, course_id=course.id, data={'assignment_id': assignment.id, 'answer_id': answer.id, 'answer_comment_id': answer_comment.id}) return marshal(answer_comment, dataformat.get_answer_comment(restrict_user))
def post(self, course_uuid, assignment_uuid, answer_uuid, answer_comment_uuid): """ Update an answer comment """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) answer_comment = AnswerComment.get_active_by_uuid_or_404( answer_comment_uuid) require( EDIT, answer_comment, title="Reply Not Saved", message= "Sorry, your role in this course does not allow you to save replies for this answer." ) was_draft = answer_comment.draft params = existing_answer_comment_parser.parse_args() # make sure the answer comment id in the url and the id matches if params['id'] != answer_comment_uuid: abort( 400, title="Reply Not Saved", message= "The reply's ID does not match the URL, which is required in order to save the reply." ) # modify answer comment according to new values, preserve original values if values not passed answer_comment.content = params.get("content") comment_types = [ AnswerCommentType.public.value, AnswerCommentType.private.value, AnswerCommentType.evaluation.value, AnswerCommentType.self_evaluation.value ] comment_type = params.get("comment_type", AnswerCommentType.private.value) if comment_type not in comment_types: abort( 400, title="Reply Not Saved", message= "This reply type is not recognized. Please contact support for assistance." ) answer_comment.comment_type = AnswerCommentType(comment_type) # only update draft param if currently a draft if answer_comment.draft: answer_comment.draft = params.get('draft', answer_comment.draft) # require content not empty if not a draft if not answer_comment.content and not answer_comment.draft: abort( 400, title="Reply Not Saved", message= "Please provide content in the text editor to reply and try saving again." ) db.session.add(answer_comment) db.session.commit() on_answer_comment_modified.send( self, event_name=on_answer_comment_modified.name, user=current_user, course_id=course.id, answer_comment=answer_comment, was_draft=was_draft, data=get_model_changes(answer_comment)) # update course & assignment grade for user if self-evaluation is completed if not answer_comment.draft and answer_comment.comment_type == AnswerCommentType.self_evaluation: assignment.calculate_grade(answer_comment.user) course.calculate_grade(answer_comment.user) return marshal(answer_comment, dataformat.get_answer_comment())
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): return {'error': answer_deadline_message}, 403 answer = Answer.get_active_by_uuid_or_404(answer_uuid) require(EDIT, answer) restrict_user = not allow(MANAGE, assignment) params = existing_answer_parser.parse_args() # make sure the answer id in the url and the id matches if params['id'] != answer_uuid: return {"error": "Answer id does not match the URL."}, 400 # modify answer according to new values, preserve original values if values not passed answer.content = params.get("content") user_uuid = params.get("user_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: if not allow(MANAGE, answer) or not answer.draft: return {"error": "Only instructors and teaching assistants can submit an answer on behalf of another user."}, 400 user = User.get_by_uuid_or_404(user_uuid) answer.user_id = user.id user_course = UserCourse.query \ .filter_by( course_id=course.id, user_id=answer.user_id ) \ .one_or_none() if user_course.course_role.value not in [CourseRole.instructor.value, CourseRole.teaching_assistant.value]: # check if there is a previous answer submitted for the student prev_answer = Answer.query \ .filter(Answer.id != answer.id) \ .filter_by( assignment_id=assignment.id, user_id=answer.user_id, active=True ) \ .first() if prev_answer: return {"error": "An answer has already been submitted."}, 400 # can only change draft status while a draft if answer.draft: answer.draft = params.get("draft") uploaded = params.get('uploadFile') file_uuid = params.get('file_id') if file_uuid: answer.file = File.get_by_uuid_or_404(file_uuid) else: answer.file_id = None # non-drafts must have content if not answer.draft and not answer.content and not file_uuid: return {"error": "The answer content is empty!"}, 400 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, assignment=assignment, data=get_model_changes(answer)) # update course & assignment grade for user if answer is fully submitted if not answer.draft: assignment.calculate_grade(answer.user) course.calculate_grade(answer.user) return marshal(answer, dataformat.get_answer(restrict_user))
def post(self, course_uuid, assignment_uuid, answer_uuid): """ Create comment for an answer """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) require( CREATE, AnswerComment(course_id=course.id), title="Reply Not Saved", message= "Sorry, your role in this course does not allow you to save replies for this answer." ) answer_comment = AnswerComment(answer_id=answer.id) params = new_answer_comment_parser.parse_args() answer_comment.draft = params.get('draft') answer_comment.content = params.get("content") # require content not empty if not a draft if not answer_comment.content and not answer_comment.draft: abort( 400, title="Reply Not Saved", message= "Please provide content in the text editor to reply and try saving again." ) if params.get('user_id' ) and current_user.system_role == SystemRole.sys_admin: user = User.get_by_uuid_or_404(params.get('user_id')) answer_comment.user_id = user.id else: answer_comment.user_id = current_user.id comment_types = [ AnswerCommentType.public.value, AnswerCommentType.private.value, AnswerCommentType.evaluation.value, AnswerCommentType.self_evaluation.value ] comment_type = params.get("comment_type") if comment_type not in comment_types: abort( 400, title="Reply Not Saved", message= "This reply type is not recognized. Please contact support for assistance." ) answer_comment.comment_type = AnswerCommentType(comment_type) db.session.add(answer_comment) db.session.commit() # update course & assignment grade for user if self-evaluation is completed if not answer_comment.draft and answer_comment.comment_type == AnswerCommentType.self_evaluation: assignment.calculate_grade(answer_comment.user) course.calculate_grade(answer_comment.user) on_answer_comment_create.send( self, event_name=on_answer_comment_create.name, user=current_user, course_id=course.id, answer_comment=answer_comment, data=marshal(answer_comment, dataformat.get_answer_comment(False))) return marshal(answer_comment, dataformat.get_answer_comment())
def post(self, course_uuid, assignment_uuid, answer_uuid, answer_comment_uuid): """ Update an answer comment """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) answer_comment = AnswerComment.get_active_by_uuid_or_404(answer_comment_uuid) require(EDIT, answer_comment, title="Feedback Not Saved", message="Sorry, your role in this course does not allow you to save feedback for this answer.") restrict_user = not allow(MANAGE, assignment) restrict_user = not allow(MANAGE, assignment) was_draft = answer_comment.draft params = existing_answer_comment_parser.parse_args() # make sure the answer comment id in the url and the id matches if params['id'] != answer_comment_uuid: abort(400, title="Feedback Not Saved", message="The feedback's ID does not match the URL, which is required in order to save the feedback.") # modify answer comment according to new values, preserve original values if values not passed answer_comment.content = params.get("content") comment_types = [ AnswerCommentType.public.value, AnswerCommentType.private.value, AnswerCommentType.evaluation.value, AnswerCommentType.self_evaluation.value ] eval_comment_types = [ AnswerCommentType.evaluation.value, AnswerCommentType.self_evaluation.value ] comment_type = params.get("comment_type", AnswerCommentType.private.value) if comment_type not in comment_types: abort(400, title="Feedback Not Saved", message="This feedback type is not recognized. Please contact support for assistance.") # do not allow changing a self-eval into a comment or vise-versa if (answer_comment.comment_type.value in eval_comment_types or comment_type in eval_comment_types) and answer_comment.comment_type.value != comment_type: abort(400, title="Feedback Not Saved", message="Feedback type cannot be changed. Please contact support for assistance.") answer_comment.comment_type = AnswerCommentType(comment_type) if answer_comment.comment_type == AnswerCommentType.self_evaluation and not assignment.self_eval_grace and not allow(MANAGE, assignment): abort(403, title="Self-Evaluation Not Saved", message="Sorry, the self-evaluation deadline has passed and therefore cannot be submitted.") # only update draft param if currently a draft if answer_comment.draft: answer_comment.draft = params.get('draft', answer_comment.draft) # require content not empty if not a draft if not answer_comment.content and not answer_comment.draft: abort(400, title="Feedback Not Saved", message="Please provide content in the text editor and try saving again.") answer_comment.update_attempt( params.get('attempt_uuid'), params.get('attempt_started', None), params.get('attempt_ended', None) ) model_changes = get_model_changes(answer_comment) db.session.add(answer_comment) db.session.commit() on_answer_comment_modified.send( self, event_name=on_answer_comment_modified.name, user=current_user, course_id=course.id, answer_comment=answer_comment, evaluation_number=params.get("evaluation_number"), was_draft=was_draft, data=model_changes) # update course & assignment grade for user if self-evaluation is completed if not answer_comment.draft and answer_comment.comment_type == AnswerCommentType.self_evaluation: assignment.calculate_grade(answer_comment.user) course.calculate_grade(answer_comment.user) return marshal(answer_comment, dataformat.get_answer_comment(restrict_user))
def post(self, course_uuid, assignment_uuid, answer_uuid): """ Create comment for an answer """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) require(CREATE, AnswerComment(course_id=course.id), title="Feedback Not Saved", message="Sorry, your role in this course does not allow you to save feedback for this answer.") restrict_user = not allow(MANAGE, assignment) restrict_user = not allow(MANAGE, assignment) answer_comment = AnswerComment(answer_id=answer.id) params = new_answer_comment_parser.parse_args() answer_comment.draft = params.get('draft') answer_comment.content = params.get("content") # require content not empty if not a draft if not answer_comment.content and not answer_comment.draft: abort(400, title="Feedback Not Saved", message="Please provide content in the text editor and try saving again.") if params.get('user_id') and current_user.system_role == SystemRole.sys_admin: user = User.get_by_uuid_or_404(params.get('user_id')) answer_comment.user_id = user.id else: answer_comment.user_id = current_user.id comment_types = [ AnswerCommentType.public.value, AnswerCommentType.private.value, AnswerCommentType.evaluation.value, AnswerCommentType.self_evaluation.value ] comment_type = params.get("comment_type") if comment_type not in comment_types: abort(400, title="Feedback Not Saved", message="This feedback type is not recognized. Please contact support for assistance.") answer_comment.comment_type = AnswerCommentType(comment_type) if answer_comment.comment_type == AnswerCommentType.self_evaluation and not assignment.self_eval_grace and not allow(MANAGE, assignment): abort(403, title="Self-Evaluation Not Saved", message="Sorry, the self-evaluation deadline has passed and therefore cannot be submitted.") answer_comment.update_attempt( params.get('attempt_uuid'), params.get('attempt_started', None), params.get('attempt_ended', None) ) db.session.add(answer_comment) db.session.commit() # update course & assignment grade for user if self-evaluation is completed if not answer_comment.draft and answer_comment.comment_type == AnswerCommentType.self_evaluation: assignment.calculate_grade(answer_comment.user) course.calculate_grade(answer_comment.user) on_answer_comment_create.send( self, event_name=on_answer_comment_create.name, user=current_user, course_id=course.id, answer_comment=answer_comment, evaluation_number=params.get("evaluation_number"), data=marshal(answer_comment, dataformat.get_answer_comment(restrict_user))) return marshal(answer_comment, dataformat.get_answer_comment(restrict_user))
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))
def post(self, course_uuid, assignment_uuid, answer_uuid, answer_comment_uuid): """ Create an answer comment """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) answer_comment = AnswerComment.get_active_by_uuid_or_404(answer_comment_uuid) require(EDIT, answer_comment) params = existing_answer_comment_parser.parse_args() # make sure the answer comment id in the url and the id matches if params["id"] != answer_comment_uuid: return {"error": "Comment id does not match URL."}, 400 # modify answer comment according to new values, preserve original values if values not passed answer_comment.content = params.get("content") comment_types = [ AnswerCommentType.public.value, AnswerCommentType.private.value, AnswerCommentType.evaluation.value, AnswerCommentType.self_evaluation.value, ] comment_type = params.get("comment_type", AnswerCommentType.private.value) if comment_type not in comment_types: abort(400) answer_comment.comment_type = AnswerCommentType(comment_type) # only update draft param if currently a draft if answer_comment.draft: answer_comment.draft = params.get("draft", answer_comment.draft) # require content not empty if not a draft if not answer_comment.content and not answer_comment.draft: return {"error": "The comment content is empty!"}, 400 db.session.add(answer_comment) if answer_comment.comment_type == AnswerCommentType.evaluation: on_answer_comment_modified.send( self, event_name=on_answer_comment_modified.name, user=current_user, course_id=course.id, answer_comment=answer_comment, data=get_model_changes(answer_comment), ) else: on_answer_comment_modified.send( self, event_name=on_answer_comment_modified.name, user=current_user, course_id=course.id, answer_comment=answer_comment, data=get_model_changes(answer_comment), ) db.session.commit() # update course & assignment grade for user if self-evaluation is completed if not answer_comment.draft and answer_comment.comment_type == AnswerCommentType.self_evaluation: assignment.calculate_grade(answer_comment.user) course.calculate_grade(answer_comment.user) return marshal(answer_comment, dataformat.get_answer_comment())
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))
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) 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 Saved", 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") # we allow instructor and TA to submit multiple answers for other users in the class if user_uuid and user_uuid != answer.user_uuid: if not allow(MANAGE, answer) or not answer.draft: abort(400, title="Answer Not Saved", message="Only instructors and teaching assistants can update an answer on behalf of another.") user = User.get_by_uuid_or_404(user_uuid) answer.user_id = user.id user_course = UserCourse.query \ .filter_by( course_id=course.id, user_id=answer.user_id ) \ .one_or_none() if user_course.course_role.value not in [CourseRole.instructor.value, CourseRole.teaching_assistant.value]: # check if there is a previous answer submitted for the student prev_answer = Answer.query \ .filter(Answer.id != answer.id) \ .filter_by( assignment_id=assignment.id, user_id=answer.user_id, active=True ) \ .first() if prev_answer: abort(400, title="Answer Not Saved", message="An answer has already been submitted for this assignment by you or on your behalf.") # can only change draft status while a draft if answer.draft: answer.draft = params.get("draft") uploaded = params.get('uploadFile') file_uuid = params.get('file_id') 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.") 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, assignment=assignment, data=get_model_changes(answer)) # update course & assignment grade for user if answer is fully submitted if not answer.draft: assignment.calculate_grade(answer.user) course.calculate_grade(answer.user) return marshal(answer, dataformat.get_answer(restrict_user))