def duplicate_file(original_file, new_model_name, new_model_id): try: duplicated_file = File( user_id=current_user.id, name='', alias=original_file.alias ) db.session.add(duplicated_file) db.session.commit() # use uuid generated by file model for name duplicate_name = duplicated_file.uuid + '.' + original_file.extension # create new file with name shutil.copy( os.path.join(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], original_file.name), os.path.join(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], duplicate_name) ) current_app.logger.debug( "copied file id:" + str(original_file.id) + " from " + os.path.join(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], original_file.name) + " to " + os.path.join(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], duplicate_name)) # update file record with name duplicated_file.name = duplicate_name db.session.commit() except Exception as e: db.session.rollback() raise e return duplicated_file
def post(self, upload_token_id): if not KalturaAPI.enabled(): abort(400, title="File Not Uploaded", message="Please use a valid upload method to attach files. You are not able to upload with this method based on the current settings.") kaltura_media = KalturaAPI.complete_upload_for_token(upload_token_id) on_save_kaltura_file.send( self, event_name=on_save_kaltura_file.name, user=current_user, data={'upload_token_id': upload_token_id}) try: db_file = File( user_id=current_user.id, name='', alias=kaltura_media.file_name, kaltura_media=kaltura_media ) db.session.add(db_file) db.session.commit() # use uuid generated by file model for name name = db_file.uuid + '.' + kaltura_media.extension # update file record with name db_file.name = name db.session.commit() except Exception as e: db.session.rollback() raise e return {'file': marshal(db_file, dataformat.get_file())}
def post(self): uploaded_file = request.files.get('file') if not uploaded_file: abort( 400, title="File Not Uploaded", message= "Sorry, no file was found to upload. Please try uploading again." ) elif not allowed_file( uploaded_file.filename, current_app.config['ATTACHMENT_ALLOWED_EXTENSIONS']): extensions = [ extension.upper() for extension in list( current_app.config['ATTACHMENT_ALLOWED_EXTENSIONS']) ] extensions.sort() extensions = ", ".join(extensions) abort( 400, title="File Not Uploaded", message= "Please try again with an approved file type, which includes: " + extensions + ".") on_save_file.send(self, event_name=on_save_file.name, user=current_user, data={'file': uploaded_file.filename}) try: db_file = File(user_id=current_user.id, name='', alias=uploaded_file.filename) db.session.add(db_file) db.session.commit() # use uuid generated by file model for name name = db_file.uuid + '.' + uploaded_file.filename.lower().rsplit( '.', 1)[1] # create new file with name full_path = os.path.join( current_app.config['ATTACHMENT_UPLOAD_FOLDER'], name) uploaded_file.save(full_path) current_app.logger.debug("Saved attachment {}/{}".format( current_app.config['ATTACHMENT_UPLOAD_FOLDER'], name)) # update file record with name db_file.name = name db.session.commit() except Exception as e: db.session.rollback() raise e return {'file': marshal(db_file, dataformat.get_file())}
def file_retrieve(file_type, file_name): file_dirs = { 'attachment': app.config['ATTACHMENT_UPLOAD_FOLDER'], 'report': app.config['REPORT_FOLDER'] } file_path = '{}/{}'.format(file_dirs[file_type], file_name) params = attachment_download_parser.parse_args() if file_type == 'attachment': attachment = File.get_by_file_name_or_404( file_name, joinedloads=['answers', 'assignments'] ) for answer in attachment.answers: require(READ, answer, title="Attachment Unavailable", message="Sorry, your role does not allow you to view the attachment.") for assignment in attachment.assignments: require(READ, assignment, title="Attachment Unavailable", message="Sorry, your role does not allow you to view the attachment.") # If attachment is in Kaltura, redirect the user if attachment.kaltura_media and KalturaAPI.enabled(): entry_id = attachment.kaltura_media.entry_id download_url = attachment.kaltura_media.download_url if entry_id: # Short-lived session of 60 seconds for user to start the media download kaltura_url = KalturaAPI.get_direct_access_url(entry_id, download_url, 60) return redirect(kaltura_url) if not os.path.exists(file_path): return make_response('invalid file name', 404) # TODO: add bouncer for reports mimetype, encoding = mimetypes.guess_type(file_name) attachment_filename = None as_attachment = False if file_type == 'attachment' and mimetype != "application/pdf": attachment_filename = params.get('name') #optionally set the download file name as_attachment = True on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=current_user, file_type=file_type, file_name=file_name, data={'file_path': file_path, 'mimetype': mimetype}) return send_file(file_path, mimetype=mimetype, attachment_filename=attachment_filename, as_attachment=as_attachment)
def post(self): uploaded_file = request.files.get('file') if not uploaded_file: abort(400, title="File Not Uploaded", message="Sorry, no file was found to upload. Please try uploading again.") elif not allowed_file(uploaded_file.filename, current_app.config['ATTACHMENT_ALLOWED_EXTENSIONS']): extensions = [extension.upper() for extension in list(current_app.config['ATTACHMENT_ALLOWED_EXTENSIONS'])] extensions.sort() extensions = ", ".join(extensions) abort(400, title="File Not Uploaded", message="Please try again with an approved file type, which includes: "+extensions+".") on_save_file.send( self, event_name=on_save_file.name, user=current_user, data={'file': uploaded_file.filename}) try: db_file = File( user_id=current_user.id, name='', alias=uploaded_file.filename ) db.session.add(db_file) db.session.commit() # use uuid generated by file model for name name = db_file.uuid + '.' + uploaded_file.filename.lower().rsplit('.', 1)[1] # create new file with name full_path = os.path.join(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], name) uploaded_file.save(full_path) current_app.logger.debug("Saved attachment {}/{}".format(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], name)) # update file record with name db_file.name = name db.session.commit() except Exception as e: db.session.rollback() raise e return {'file': marshal(db_file, dataformat.get_file())}
def post(self): uploaded_file = request.files['file'] on_save_file.send( self, event_name=on_save_file.name, user=current_user, data={'file': uploaded_file.filename}) if uploaded_file and allowed_file(uploaded_file.filename, current_app.config['ATTACHMENT_ALLOWED_EXTENSIONS']): try: db_file = File( user_id=current_user.id, name='', alias=uploaded_file.filename ) db.session.add(db_file) db.session.commit() # use uuid generated by file model for name name = db_file.uuid + '.' + uploaded_file.filename.rsplit('.', 1)[1] # create new file with name full_path = os.path.join(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], name) uploaded_file.save(full_path) current_app.logger.debug("Saved attachment {}/{}".format(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], name)) # update file record with name db_file.name = name db.session.commit() except Exception as e: db.session.rollback() raise e return {'file': marshal(db_file, dataformat.get_file())} return False
def delete(self, file_uuid): uploaded_file = File.get_active_by_uuid_or_404(file_uuid) require(DELETE, uploaded_file) for assignment in uploaded_file.assignments.all(): assignment.file_id = None for answer in uploaded_file.answers.all(): answer.file_id = None uploaded_file.active = False db.session.commit() on_file_delete.send( self, event_name=on_file_delete.name, user=current_user, data={'file_id': uploaded_file.id}) return {'id': uploaded_file.uuid}
def post(self, course_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) # check permission first before reading parser arguments new_assignment = Assignment(course_id=course.id) require(CREATE, new_assignment, title="Assignment Not Saved", message="Sorry, your role in this course does not allow you to save assignments.") params = new_assignment_parser.parse_args() new_assignment.user_id = current_user.id new_assignment.name = params.get("name") new_assignment.description = params.get("description") new_assignment.peer_feedback_prompt = params.get("peer_feedback_prompt") new_assignment.answer_start = dateutil.parser.parse(params.get('answer_start')) new_assignment.answer_end = dateutil.parser.parse(params.get('answer_end')) new_assignment.educators_can_compare = params.get("educators_can_compare") new_assignment.rank_display_limit = params.get("rank_display_limit", None) if new_assignment.rank_display_limit != None and new_assignment.rank_display_limit <= 0: new_assignment.rank_display_limit = None # make sure that file attachment exists file_uuid = params.get('file_id') if file_uuid: attachment = File.get_by_uuid_or_404(file_uuid) new_assignment.file_id = attachment.id else: new_assignment.file_id = None new_assignment.compare_start = params.get('compare_start', None) if new_assignment.compare_start is not None: new_assignment.compare_start = dateutil.parser.parse(params.get('compare_start', None)) new_assignment.compare_end = params.get('compare_end', None) if new_assignment.compare_end is not None: new_assignment.compare_end = dateutil.parser.parse(params.get('compare_end', None)) # validate answer + comparison period start & end times valid, error_message = Assignment.validate_periods(course.start_date, course.end_date, new_assignment.answer_start, new_assignment.answer_end, new_assignment.compare_start, new_assignment.compare_end) if not valid: abort(400, title="Assignment Not Saved", message=error_message) new_assignment.students_can_reply = params.get('students_can_reply', False) new_assignment.number_of_comparisons = params.get('number_of_comparisons') new_assignment.enable_self_evaluation = params.get('enable_self_evaluation') new_assignment.answer_grade_weight = params.get('answer_grade_weight') new_assignment.comparison_grade_weight = params.get('comparison_grade_weight') new_assignment.self_evaluation_grade_weight = params.get('self_evaluation_grade_weight') pairing_algorithm = params.get("pairing_algorithm", PairingAlgorithm.random) check_valid_pairing_algorithm(pairing_algorithm) new_assignment.pairing_algorithm = PairingAlgorithm(pairing_algorithm) criterion_uuids = [c.get('id') for c in params.criteria] criterion_data = {c.get('id'): c.get('weight', 1) for c in params.criteria} if len(criterion_data) == 0: msg = "Please add at least one criterion to the assignment and save again." abort(400, title="Assignment Not Saved", message=msg) criteria = Criterion.query \ .filter(Criterion.uuid.in_(criterion_uuids)) \ .all() if len(criterion_uuids) != len(criteria): abort(400, title="Assignment Not Saved", message="Please double-check the criteria you selected and save agaiin.") # add criteria to assignment in order for criterion_uuid in criterion_uuids: criterion = next(criterion for criterion in criteria if criterion.uuid == criterion_uuid) new_assignment.assignment_criteria.append(AssignmentCriterion( criterion=criterion, weight=criterion_data.get(criterion.uuid) )) db.session.add(new_assignment) db.session.commit() # need to reorder after insert new_assignment.assignment_criteria.reorder() # update course grades course.calculate_grades() on_assignment_create.send( self, event_name=on_assignment_create.name, user=current_user, course_id=course.id, assignment=new_assignment, data=marshal(new_assignment, dataformat.get_assignment(False))) return marshal(new_assignment, dataformat.get_assignment())
def post(self, course_uuid, assignment_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) assignment_criteria = assignment.assignment_criteria require(EDIT, assignment, title="Assignment Not Saved", message="Sorry, your role in this course does not allow you to save assignments.") if current_app.config.get('DEMO_INSTALLATION', False): from data.fixtures import DemoDataFixture if assignment.id in DemoDataFixture.DEFAULT_ASSIGNMENT_IDS: abort(400, title="Assignment Not Saved", message="Sorry, you cannot edit the default demo assignments.") params = existing_assignment_parser.parse_args() # make sure the assignment id in the url and the id matches if params['id'] != assignment_uuid: abort(400, title="Assignment Not Saved", message="The assignment's ID does not match the URL, which is required in order to update the assignment.") # make sure that file attachment exists file_uuid = params.get('file_id') if file_uuid: attachment = File.get_by_uuid_or_404(file_uuid) assignment.file_id = attachment.id else: assignment.file_id = None # modify assignment according to new values, preserve original values if values not passed assignment.name = params.get("name", assignment.name) assignment.description = params.get("description", assignment.description) assignment.peer_feedback_prompt = params.get("peer_feedback_prompt", assignment.peer_feedback_prompt) assignment.answer_start = datetime.datetime.strptime( params.get('answer_start', assignment.answer_start), '%Y-%m-%dT%H:%M:%S.%fZ') assignment.answer_end = datetime.datetime.strptime( params.get('answer_end', assignment.answer_end), '%Y-%m-%dT%H:%M:%S.%fZ') # if nothing in request, assume user don't want comparison date assignment.compare_start = params.get('compare_start', None) if assignment.compare_start is not None: assignment.compare_start = datetime.datetime.strptime( assignment.compare_start, '%Y-%m-%dT%H:%M:%S.%fZ') assignment.compare_end = params.get('compare_end', None) if assignment.compare_end is not None: assignment.compare_end = datetime.datetime.strptime( params.get('compare_end', assignment.compare_end), '%Y-%m-%dT%H:%M:%S.%fZ') # validate answer + comparison period start & end times valid, error_message = Assignment.validate_periods(course.start_date, course.end_date, assignment.answer_start, assignment.answer_end, assignment.compare_start, assignment.compare_end) if not valid: abort(400, title="Assignment Not Saved", message=error_message) assignment.students_can_reply = params.get('students_can_reply', False) assignment.number_of_comparisons = params.get( 'number_of_comparisons', assignment.number_of_comparisons) assignment.enable_self_evaluation = params.get( 'enable_self_evaluation', assignment.enable_self_evaluation) assignment.answer_grade_weight = params.get( 'answer_grade_weight', assignment.answer_grade_weight) assignment.comparison_grade_weight = params.get( 'comparison_grade_weight', assignment.comparison_grade_weight) assignment.self_evaluation_grade_weight = params.get( 'self_evaluation_grade_weight', assignment.self_evaluation_grade_weight) pairing_algorithm = params.get("pairing_algorithm") check_valid_pairing_algorithm(pairing_algorithm) if not assignment.compared: assignment.pairing_algorithm = PairingAlgorithm(pairing_algorithm) elif assignment.pairing_algorithm != PairingAlgorithm(pairing_algorithm): msg = 'The answer pair selection algorithm cannot be changed for this assignment ' + \ 'because it has already been used in one or more comparisons.' abort(403, title="Assignment Not Saved", message=msg) assignment.educators_can_compare = params.get("educators_can_compare") assignment.rank_display_limit = params.get("rank_display_limit", None) if assignment.rank_display_limit != None and assignment.rank_display_limit <= 0: assignment.rank_display_limit = None criterion_uuids = [c.get('id') for c in params.criteria] criterion_data = {c.get('id'): c.get('weight', 1) for c in params.criteria} if assignment.compared: active_uuids = [c.uuid for c in assignment.criteria] active_data = {c.uuid: c.weight for c in assignment.criteria} if set(criterion_uuids) != set(active_uuids): msg = 'The criteria cannot be changed for this assignment ' + \ 'because they have already been used in one or more comparisons.' abort(403, title="Assignment Not Saved", message=msg) for criterion in assignment.criteria: if criterion_data.get(criterion.uuid) != criterion.weight: msg = 'The criteria weights cannot be changed for this assignment ' + \ 'because they have already been used in one or more comparisons.' abort(403, title="Assignment Not Saved", message=msg) else: # assignment not compared yet, can change criteria if len(criterion_uuids) == 0: msg = "Please add at least one criterion to the assignment and save again." abort(403, title="Assignment Not Saved", message=msg) existing_uuids = [c.criterion_uuid for c in assignment_criteria] # disable old ones for c in assignment_criteria: c.active = c.criterion_uuid in criterion_uuids if c.active: c.weight = criterion_data.get(c.criterion_uuid) # add the new ones new_uuids = [] for criterion_uuid in criterion_uuids: if criterion_uuid not in existing_uuids: new_uuids.append(criterion_uuid) if len(new_uuids) > 0: new_criteria = Criterion.query.filter(Criterion.uuid.in_(new_uuids)).all() for criterion in new_criteria: assignment_criteria.append(AssignmentCriterion( criterion=criterion, weight=criterion_data.get(criterion.uuid) )) # ensure criteria are in order for index, criterion_uuid in enumerate(criterion_uuids): assignment_criterion = next(assignment_criterion \ for assignment_criterion in assignment_criteria \ if assignment_criterion.criterion_uuid == criterion_uuid) assignment_criteria.remove(assignment_criterion) assignment_criteria.insert(index, assignment_criterion) model_changes = get_model_changes(assignment) on_assignment_modified.send( self, event_name=on_assignment_modified.name, user=current_user, course_id=course.id, assignment=assignment, data=model_changes) db.session.commit() # need to reorder after update assignment_criteria.reorder() # update assignment and course grades if needed if model_changes and (model_changes.get('answer_grade_weight') or model_changes.get('comparison_grade_weight') or model_changes.get('self_evaluation_grade_weight') or model_changes.get('enable_self_evaluation')): assignment.calculate_grades() course.calculate_grades() return marshal(assignment, dataformat.get_assignment())
def post(self, course_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) # check permission first before reading parser arguments new_assignment = Assignment(course_id=course.id) require(CREATE, new_assignment, title="Assignment Not Saved", message="Sorry, your role in this course does not allow you to save assignments.") params = new_assignment_parser.parse_args() new_assignment.user_id = current_user.id new_assignment.name = params.get("name") new_assignment.description = params.get("description") new_assignment.peer_feedback_prompt = params.get("peer_feedback_prompt") new_assignment.answer_start = dateutil.parser.parse(params.get('answer_start')) new_assignment.answer_end = dateutil.parser.parse(params.get('answer_end')) new_assignment.educators_can_compare = params.get("educators_can_compare") new_assignment.rank_display_limit = params.get("rank_display_limit", None) if new_assignment.rank_display_limit != None and new_assignment.rank_display_limit <= 0: new_assignment.rank_display_limit = None # make sure that file attachment exists file_uuid = params.get('file_id') attachment = None if file_uuid: attachment = File.get_by_uuid_or_404(file_uuid) new_assignment.file_id = attachment.id else: new_assignment.file_id = None new_assignment.compare_start = params.get('compare_start', None) if new_assignment.compare_start is not None: new_assignment.compare_start = dateutil.parser.parse(params.get('compare_start', None)) new_assignment.compare_end = params.get('compare_end', None) if new_assignment.compare_end is not None: new_assignment.compare_end = dateutil.parser.parse(params.get('compare_end', None)) new_assignment.enable_self_evaluation = params.get('enable_self_evaluation', False) new_assignment.self_eval_instructions = params.get('self_eval_instructions', None) new_assignment.self_eval_start = params.get('self_eval_start', None) if new_assignment.self_eval_start is not None: new_assignment.self_eval_start = dateutil.parser.parse(new_assignment.self_eval_start) new_assignment.self_eval_end = params.get('self_eval_end', None) if new_assignment.self_eval_end is not None: new_assignment.self_eval_end = dateutil.parser.parse(new_assignment.self_eval_end) # validate answer + comparison period start & end times valid, error_message = Assignment.validate_periods(course.start_date, course.end_date, new_assignment.answer_start, new_assignment.answer_end, new_assignment.compare_start, new_assignment.compare_end, new_assignment.self_eval_start, new_assignment.self_eval_end) if not valid: abort(400, title="Assignment Not Saved", message=error_message) new_assignment.students_can_reply = params.get('students_can_reply', False) new_assignment.number_of_comparisons = params.get('number_of_comparisons') new_assignment.enable_group_answers = params.get('enable_group_answers') new_assignment.answer_grade_weight = params.get('answer_grade_weight') new_assignment.comparison_grade_weight = params.get('comparison_grade_weight') new_assignment.self_evaluation_grade_weight = params.get('self_evaluation_grade_weight') pairing_algorithm = params.get("pairing_algorithm", PairingAlgorithm.random) check_valid_pairing_algorithm(pairing_algorithm) new_assignment.pairing_algorithm = PairingAlgorithm(pairing_algorithm) criterion_uuids = [c.get('id') for c in params.criteria] criterion_data = {c.get('id'): c.get('weight', 1) for c in params.criteria} if len(criterion_data) == 0: msg = "Please add at least one criterion to the assignment and save again." abort(400, title="Assignment Not Saved", message=msg) criteria = Criterion.query \ .filter(Criterion.uuid.in_(criterion_uuids)) \ .all() if len(criterion_uuids) != len(criteria): abort(400, title="Assignment Not Saved", message="Please double-check the criteria you selected and save again.") # add criteria to assignment in order for criterion_uuid in criterion_uuids: criterion = next(criterion for criterion in criteria if criterion.uuid == criterion_uuid) new_assignment.assignment_criteria.append(AssignmentCriterion( criterion=criterion, weight=criterion_data.get(criterion.uuid) )) db.session.add(new_assignment) db.session.commit() # need to reorder after insert new_assignment.assignment_criteria.reorder() # update course grades course.calculate_grades() on_assignment_create.send( self, event_name=on_assignment_create.name, user=current_user, course_id=course.id, assignment=new_assignment, data=marshal(new_assignment, dataformat.get_assignment(False))) if new_assignment.file_id: on_attach_file.send( self, event_name=on_attach_file.name, user=current_user, course_id=course.id, file=attachment, data={'assignment_id': new_assignment.id, 'file_id': new_assignment.file_id}) return marshal(new_assignment, dataformat.get_assignment())
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())
def post(self, course_uuid, assignment_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) if not assignment.answer_grace and not allow(MANAGE, assignment): return {'error': answer_deadline_message}, 403 require(CREATE, Answer(course_id=course.id)) restrict_user = not allow(MANAGE, assignment) answer = Answer(assignment_id=assignment.id) params = new_answer_parser.parse_args() answer.content = params.get("content") answer.draft = params.get("draft") file_uuid = params.get('file_id') if file_uuid: uploaded_file = File.get_by_uuid_or_404(file_uuid) answer.file_id = uploaded_file.id 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 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 not allow(MANAGE, Answer(course_id=course.id)): return {"error": "Only instructors and teaching assistants can submit an answer on behalf of another user."}, 400 if user_uuid: user = User.get_by_uuid_or_404(user_uuid) answer.user_id = user.id else: answer.user_id = current_user.id user_course = UserCourse.query \ .filter_by( course_id=course.id, user_id=answer.user_id ) \ .one_or_none() # we allow instructor and TA to submit multiple answers for their own, # but not for student. Each student can only have one answer. instructors_and_tas = [CourseRole.instructor.value, CourseRole.teaching_assistant.value] if user_course == None: # 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 current_user.id != answer.user_id or current_user.system_role != SystemRole.sys_admin: return {"error": "You are not enrolled in the course."}, 400 elif user_course.course_role.value not in instructors_and_tas: # check if there is a previous answer submitted for the student prev_answer = Answer.query. \ 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 db.session.add(answer) db.session.commit() on_answer_create.send( self, event_name=on_answer_create.name, user=current_user, course_id=course.id, data=marshal(answer, dataformat.get_answer(restrict_user))) # 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): 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))
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): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) if not assignment.answer_grace and not allow(MANAGE, assignment): abort( 403, title="Answer Not Submitted", message= "Sorry, the answer deadline has passed. No answers can be submitted after the deadline unless the instructor submits the answer for you." ) require( CREATE, Answer(course_id=course.id), title="Answer Not Submitted", message= "Answers can be submitted only by those enrolled in the course. Please double-check your enrollment in this course." ) restrict_user = not allow(MANAGE, assignment) answer = Answer(assignment_id=assignment.id) params = new_answer_parser.parse_args() answer.content = params.get("content") answer.draft = params.get("draft") file_uuid = params.get('file_id') attachment = None if file_uuid: attachment = File.get_by_uuid_or_404(file_uuid) answer.file_id = attachment.id else: answer.file_id = None # non-drafts must have content if not answer.draft and not answer.content and not file_uuid: abort( 400, title="Answer Not Submitted", message= "Please provide content in the text editor or upload a file and try submitting again." ) user_uuid = params.get("user_id") group_uuid = params.get("group_id") # we allow instructor and TA to submit multiple answers for other users in the class if user_uuid and not allow(MANAGE, Answer(course_id=course.id)): abort( 400, title="Answer Not Submitted", message= "Only instructors and teaching assistants can submit an answer on behalf of another." ) if group_uuid and not assignment.enable_group_answers: abort(400, title="Answer Not Submitted", message="Group answers are not allowed for this assignment.") if group_uuid and not allow(MANAGE, Answer(course_id=course.id)): abort( 400, title="Answer Not Submitted", message= "Only instructors and teaching assistants can submit an answer on behalf of a group." ) if group_uuid and user_uuid: abort( 400, title="Answer Not Submitted", message= "You cannot submit an answer for a user and a group at the same time." ) user = User.get_by_uuid_or_404( user_uuid) if user_uuid else current_user group = Group.get_active_by_uuid_or_404( group_uuid) if group_uuid else None if restrict_user and assignment.enable_group_answers and not group: group = current_user.get_course_group(course.id) if group == None: abort( 400, title="Answer Not Submitted", message= "You are currently not in any group for this course. Please contact your instructor to be added to a group." ) check_for_existing_answers = False if group and assignment.enable_group_answers: if group.course_id != course.id: abort( 400, title="Answer Not Submitted", message= "Group answers can be submitted to courses they belong in." ) answer.user_id = None answer.group_id = group.id answer.comparable = True check_for_existing_answers = True else: answer.user_id = user.id answer.group_id = None course_role = User.get_user_course_role(answer.user_id, course.id) # only system admin can add answers for themselves to a class without being enrolled in it # required for managing comparison examples as system admin if (not course_role or course_role == CourseRole.dropped ) and current_user.system_role != SystemRole.sys_admin: abort( 400, title="Answer Not Submitted", message= "Answers can be submitted only by those enrolled in the course. Please double-check your enrollment in this course." ) if course_role == CourseRole.student and assignment.enable_group_answers: abort( 400, title="Answer Not Submitted", message= "Students can only submit group answers for this assignment." ) # we allow instructor and TA to submit multiple answers for their own, # but not for student. Each student can only have one answer. if course_role and course_role == CourseRole.student: check_for_existing_answers = True answer.comparable = True else: # instructor / TA / Sys Admin can mark the answer as non-comparable, unless the answer is for a student answer.comparable = params.get("comparable") if check_for_existing_answers: # check for answers with user_id or group_id prev_answers = Answer.query \ .filter_by( assignment_id=assignment.id, user_id=answer.user_id, group_id=answer.group_id, active=True ) \ .all() # check if there is a previous answer submitted for the student non_draft_answers = [ prev_answer for prev_answer in prev_answers if not prev_answer.draft ] if len(non_draft_answers) > 0: abort( 400, title="Answer Not Submitted", message= "An answer has already been submitted for this assignment by you or on your behalf." ) # check if there is a previous draft answer submitted for the student (soft-delete if present) draft_answers = [ prev_answer for prev_answer in prev_answers if prev_answer.draft ] for draft_answer in draft_answers: draft_answer.active = False # set submission date if answer is being submitted for the first time if not answer.draft and not answer.submission_date: answer.submission_date = datetime.datetime.utcnow() answer.update_attempt(params.get('attempt_uuid'), params.get('attempt_started', None), params.get('attempt_ended', None)) db.session.add(answer) db.session.commit() on_answer_create.send(self, event_name=on_answer_create.name, user=current_user, course_id=course.id, answer=answer, data=marshal( answer, dataformat.get_answer(restrict_user))) if attachment: on_attach_file.send(self, event_name=on_attach_file.name, user=current_user, course_id=course.id, file=attachment, data={ 'answer_id': answer.id, 'file_id': attachment.id }) # update course & assignment grade for user if answer is fully submitted if not answer.draft: if answer.user: assignment.calculate_grade(answer.user) course.calculate_grade(answer.user) elif answer.group: assignment.calculate_group_grade(answer.group) course.calculate_group_grade(answer.group) return marshal(answer, dataformat.get_answer(restrict_user))
def post(self, course_uuid, assignment_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) if not assignment.answer_grace and not allow(MANAGE, assignment): abort(403, title="Answer Not Submitted", message="Sorry, the answer deadline has passed. No answers can be submitted after the deadline unless the instructor submits the answer for you.") require(CREATE, Answer(course_id=course.id), title="Answer Not Submitted", message="Answers can be submitted only by those enrolled in the course. Please double-check your enrollment in this course.") restrict_user = not allow(MANAGE, assignment) answer = Answer(assignment_id=assignment.id) params = new_answer_parser.parse_args() answer.content = params.get("content") answer.draft = params.get("draft") file_uuid = params.get('file_id') attachment = None if file_uuid: attachment = File.get_by_uuid_or_404(file_uuid) answer.file_id = attachment.id else: answer.file_id = None # non-drafts must have content if not answer.draft and not answer.content and not file_uuid: abort(400, title="Answer Not Submitted", message="Please provide content in the text editor or upload a file and try submitting again.") user_uuid = params.get("user_id") group_uuid = params.get("group_id") # we allow instructor and TA to submit multiple answers for other users in the class if user_uuid and not allow(MANAGE, Answer(course_id=course.id)): abort(400, title="Answer Not Submitted", message="Only instructors and teaching assistants can submit an answer on behalf of another.") if group_uuid and not assignment.enable_group_answers: abort(400, title="Answer Not Submitted", message="Group answers are not allowed for this assignment.") if group_uuid and not allow(MANAGE, Answer(course_id=course.id)): abort(400, title="Answer Not Submitted", message="Only instructors and teaching assistants can submit an answer on behalf of a group.") if group_uuid and user_uuid: abort(400, title="Answer Not Submitted", message="You cannot submit an answer for a user and a group at the same time.") user = User.get_by_uuid_or_404(user_uuid) if user_uuid else current_user group = Group.get_active_by_uuid_or_404(group_uuid) if group_uuid else None if restrict_user and assignment.enable_group_answers and not group: group = current_user.get_course_group(course.id) if group == None: abort(400, title="Answer Not Submitted", message="You are currently not in any group for this course. Please contact your instructor to be added to a group.") check_for_existing_answers = False if group and assignment.enable_group_answers: if group.course_id != course.id: abort(400, title="Answer Not Submitted", message="Group answers can be submitted to courses they belong in.") answer.user_id = None answer.group_id = group.id answer.comparable = True check_for_existing_answers = True else: answer.user_id = user.id answer.group_id = None course_role = User.get_user_course_role(answer.user_id, course.id) # only system admin can add answers for themselves to a class without being enrolled in it # required for managing comparison examples as system admin if (not course_role or course_role == CourseRole.dropped) and current_user.system_role != SystemRole.sys_admin: abort(400, title="Answer Not Submitted", message="Answers can be submitted only by those enrolled in the course. Please double-check your enrollment in this course.") if course_role == CourseRole.student and assignment.enable_group_answers: abort(400, title="Answer Not Submitted", message="Students can only submit group answers for this assignment.") # we allow instructor and TA to submit multiple answers for their own, # but not for student. Each student can only have one answer. if course_role and course_role == CourseRole.student: check_for_existing_answers = True answer.comparable = True else: # instructor / TA / Sys Admin can mark the answer as non-comparable, unless the answer is for a student answer.comparable = params.get("comparable") if check_for_existing_answers: # check for answers with user_id or group_id prev_answers = Answer.query \ .filter_by( assignment_id=assignment.id, user_id=answer.user_id, group_id=answer.group_id, active=True ) \ .all() # check if there is a previous answer submitted for the student non_draft_answers = [prev_answer for prev_answer in prev_answers if not prev_answer.draft] if len(non_draft_answers) > 0: abort(400, title="Answer Not Submitted", message="An answer has already been submitted for this assignment by you or on your behalf.") # check if there is a previous draft answer submitted for the student (soft-delete if present) draft_answers = [prev_answer for prev_answer in prev_answers if prev_answer.draft] for draft_answer in draft_answers: draft_answer.active = False # set submission date if answer is being submitted for the first time if not answer.draft and not answer.submission_date: answer.submission_date = datetime.datetime.utcnow() answer.update_attempt( params.get('attempt_uuid'), params.get('attempt_started', None), params.get('attempt_ended', None) ) db.session.add(answer) db.session.commit() on_answer_create.send( self, event_name=on_answer_create.name, user=current_user, course_id=course.id, answer=answer, data=marshal(answer, dataformat.get_answer(restrict_user))) if attachment: on_attach_file.send( self, event_name=on_attach_file.name, user=current_user, course_id=course.id, file=attachment, data={'answer_id': answer.id, 'file_id': attachment.id}) # update course & assignment grade for user if answer is fully submitted if not answer.draft: if answer.user: assignment.calculate_grade(answer.user) course.calculate_grade(answer.user) elif answer.group: assignment.calculate_group_grade(answer.group) course.calculate_group_grade(answer.group) return marshal(answer, dataformat.get_answer(restrict_user))
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): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) require(EDIT, assignment) params = existing_assignment_parser.parse_args() # make sure the assignment id in the url and the id matches if params['id'] != assignment_uuid: return {"error": "Assignment id does not match URL."}, 400 # make sure that file attachment exists file_uuid = params.get('file_id') if file_uuid: uploaded_file = File.get_by_uuid_or_404(file_uuid) assignment.file_id = uploaded_file.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.answer_start = datetime.datetime.strptime( params.get('answer_start', assignment.answer_start), '%Y-%m-%dT%H:%M:%S.%fZ') assignment.answer_end = datetime.datetime.strptime( params.get('answer_end', assignment.answer_end), '%Y-%m-%dT%H:%M:%S.%fZ') # if nothing in request, assume user don't want comparison date assignment.compare_start = params.get('compare_start', None) if assignment.compare_start is not None: assignment.compare_start = datetime.datetime.strptime( assignment.compare_start, '%Y-%m-%dT%H:%M:%S.%fZ') assignment.compare_end = params.get('compare_end', None) if assignment.compare_end is not None: assignment.compare_end = datetime.datetime.strptime( params.get('compare_end', assignment.compare_end), '%Y-%m-%dT%H:%M:%S.%fZ') assignment.students_can_reply = params.get('students_can_reply', False) assignment.number_of_comparisons = params.get( 'number_of_comparisons', assignment.number_of_comparisons) assignment.enable_self_evaluation = params.get( 'enable_self_evaluation', assignment.enable_self_evaluation) assignment.answer_grade_weight = params.get( 'answer_grade_weight', assignment.answer_grade_weight) assignment.comparison_grade_weight = params.get( 'comparison_grade_weight', assignment.comparison_grade_weight) assignment.self_evaluation_grade_weight = params.get( 'self_evaluation_grade_weight', assignment.self_evaluation_grade_weight) pairing_algorithm = params.get("pairing_algorithm") check_valid_pairing_algorithm(pairing_algorithm) if not assignment.compared: assignment.pairing_algorithm = PairingAlgorithm(pairing_algorithm) elif assignment.pairing_algorithm != PairingAlgorithm(pairing_algorithm): msg = 'The pair selection algorithm cannot be changed in the assignment ' + \ 'because it has already been used in an evaluation.' return {"error": msg}, 403 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['id'] for c in params.criteria] if assignment.compared: active_uuids = [c.uuid for c in assignment.criteria] if set(criterion_uuids) != set(active_uuids): msg = 'The criteria cannot be changed in the assignment ' + \ 'because they have already been used in an evaluation.' return {"error": msg}, 403 else: # assignment not comapred yet, can change criteria if len(criterion_uuids) == 0: msg = 'You must add at least one criterion to the assignment ' return {"error": msg}, 403 existing_uuids = [c.criterion_uuid for c in assignment.assignment_criteria] # disable old ones for c in assignment.assignment_criteria: c.active = c.criterion_uuid in criterion_uuids # 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.assignment_criteria.append(AssignmentCriterion( criterion=criterion )) # ensure criteria are in order for index, criterion_uuid in enumerate(criterion_uuids): assignment_criterion = next(assignment_criterion \ for assignment_criterion in assignment.assignment_criteria \ if assignment_criterion.criterion_uuid == criterion_uuid) assignment.assignment_criteria.remove(assignment_criterion) assignment.assignment_criteria.insert(index, assignment_criterion) model_changes = get_model_changes(assignment) on_assignment_modified.send( self, event_name=on_assignment_modified.name, user=current_user, course_id=course.id, assignment=assignment, data=model_changes) db.session.commit() # need to reorder after update assignment.assignment_criteria.reorder() # update assignment and course grades if needed if model_changes and (model_changes.get('answer_grade_weight') or model_changes.get('comparison_grade_weight') or model_changes.get('self_evaluation_grade_weight') or model_changes.get('enable_self_evaluation')): assignment.calculate_grades() course.calculate_grades() return marshal(assignment, dataformat.get_assignment())
def post(self, course_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) # check permission first before reading parser arguments new_assignment = Assignment(course_id=course.id) require(CREATE, new_assignment) params = new_assignment_parser.parse_args() new_assignment.user_id = current_user.id new_assignment.name = params.get("name") new_assignment.description = params.get("description") new_assignment.answer_start = dateutil.parser.parse(params.get('answer_start')) new_assignment.answer_end = dateutil.parser.parse(params.get('answer_end')) new_assignment.educators_can_compare = params.get("educators_can_compare") new_assignment.rank_display_limit = params.get("rank_display_limit", None) if new_assignment.rank_display_limit != None and new_assignment.rank_display_limit <= 0: new_assignment.rank_display_limit = None # make sure that file attachment exists file_uuid = params.get('file_id') if file_uuid: uploaded_file = File.get_by_uuid_or_404(file_uuid) new_assignment.file_id = uploaded_file.id else: new_assignment.file_id = None new_assignment.compare_start = params.get('compare_start', None) if new_assignment.compare_start is not None: new_assignment.compare_start = dateutil.parser.parse(params.get('compare_start', None)) new_assignment.compare_end = params.get('compare_end', None) if new_assignment.compare_end is not None: new_assignment.compare_end = dateutil.parser.parse(params.get('compare_end', None)) new_assignment.students_can_reply = params.get('students_can_reply', False) new_assignment.number_of_comparisons = params.get('number_of_comparisons') new_assignment.enable_self_evaluation = params.get('enable_self_evaluation') new_assignment.answer_grade_weight = params.get('answer_grade_weight') new_assignment.comparison_grade_weight = params.get('comparison_grade_weight') new_assignment.self_evaluation_grade_weight = params.get('self_evaluation_grade_weight') pairing_algorithm = params.get("pairing_algorithm", PairingAlgorithm.random) check_valid_pairing_algorithm(pairing_algorithm) new_assignment.pairing_algorithm = PairingAlgorithm(pairing_algorithm) criterion_uuids = [c['id'] for c in params.criteria] if len(criterion_uuids) == 0: msg = 'You must add at least one criterion to the assignment' return {"error": msg}, 400 criteria = Criterion.query \ .filter(Criterion.uuid.in_(criterion_uuids)) \ .all() if len(criterion_uuids) != len(criteria): msg = 'You select an invalid criterion' return {"error": msg}, 400 # add criteria to assignment in order for criterion_uuid in criterion_uuids: criterion = next(criterion for criterion in criteria if criterion.uuid == criterion_uuid) new_assignment.assignment_criteria.append(AssignmentCriterion( criterion=criterion )) db.session.add(new_assignment) db.session.commit() # need to reorder after insert new_assignment.assignment_criteria.reorder() # update course grades course.calculate_grades() on_assignment_create.send( self, event_name=on_assignment_create.name, user=current_user, course_id=course.id, assignment=new_assignment, data=marshal(new_assignment, dataformat.get_assignment(False))) return marshal(new_assignment, dataformat.get_assignment())
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): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) if not assignment.answer_grace and not allow(MANAGE, assignment): abort(403, title="Answer Not Submitted", message="Sorry, the answer deadline has passed. No answers can be submitted after the deadline unless the instructor submits the answer for you.") require(CREATE, Answer(course_id=course.id), title="Answer Not Submitted", message="Answers can be submitted only by those enrolled in the course. Please double-check your enrollment in this course.") restrict_user = not allow(MANAGE, assignment) answer = Answer(assignment_id=assignment.id) params = new_answer_parser.parse_args() answer.content = params.get("content") answer.draft = params.get("draft") file_uuid = params.get('file_id') if file_uuid: attachment = File.get_by_uuid_or_404(file_uuid) answer.file_id = attachment.id else: answer.file_id = None # non-drafts must have content if not answer.draft and not answer.content and not file_uuid: abort(400, title="Answer Not Submitted", message="Please provide content in the text editor or upload a file and try submitting again.") user_uuid = params.get("user_id") # we allow instructor and TA to submit multiple answers for other users in the class if user_uuid and not allow(MANAGE, Answer(course_id=course.id)): abort(400, title="Answer Not Submitted", message="Only instructors and teaching assistants can submit an answer on behalf of another.") if user_uuid: user = User.get_by_uuid_or_404(user_uuid) answer.user_id = user.id else: answer.user_id = current_user.id user_course = UserCourse.query \ .filter_by( course_id=course.id, user_id=answer.user_id ) \ .one_or_none() # we allow instructor and TA to submit multiple answers for their own, # but not for student. Each student can only have one answer. instructors_and_tas = [CourseRole.instructor.value, CourseRole.teaching_assistant.value] if user_course == None: # 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 current_user.id != answer.user_id or 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.") elif user_course.course_role.value not in instructors_and_tas: # check if there is a previous answer submitted for the student prev_answer = Answer.query. \ filter_by( assignment_id=assignment.id, user_id=answer.user_id, active=True ). \ first() if prev_answer: abort(400, title="Answer Not Submitted", message="An answer has already been submitted for this assignment by you or on your behalf.") db.session.add(answer) db.session.commit() on_answer_create.send( self, event_name=on_answer_create.name, user=current_user, course_id=course.id, data=marshal(answer, dataformat.get_answer(restrict_user))) # 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))