def view(request_id): """ This function is for testing purposes of the view a request back until backend functionality is implemented. :return: redirect to view request page """ try: current_request = Requests.query.filter_by(id=request_id).one() assert current_request.agency.is_active except NoResultFound: print("Request with id '{}' does not exist.".format(request_id)) sentry.captureException() return abort(404) except AssertionError: print("Request belongs to inactive agency.") sentry.captureException() return abort(404) holidays = sorted(get_holidays_date_list( datetime.utcnow().year, (datetime.utcnow() + rd(years=DEFAULT_YEARS_HOLIDAY_LIST)).year) ) active_users = [] assigned_users = [] if current_user.is_agency: for agency_user in current_request.agency.active_users: if not agency_user in current_request.agency.administrators and (agency_user != current_user): # populate list of assigned users that can be removed from a request if agency_user in current_request.agency_users: assigned_users.append(agency_user) # append to list of active users that can be added to a request else: active_users.append(agency_user) permissions = { 'acknowledge': permission.ACKNOWLEDGE, 'deny': permission.DENY, 'extend': permission.EXTEND, 'close': permission.CLOSE, 're_open': permission.RE_OPEN, 'add_file': permission.ADD_FILE, 'edit_file_privacy': permission.EDIT_FILE_PRIVACY, 'delete_file': permission.DELETE_FILE, 'add_note': permission.ADD_NOTE, 'edit_note_privacy': permission.EDIT_NOTE_PRIVACY, 'delete_note': permission.DELETE_NOTE, 'add_link': permission.ADD_LINK, 'edit_link_privacy': permission.EDIT_LINK_PRIVACY, 'delete_link': permission.DELETE_LINK, 'add_instructions': permission.ADD_OFFLINE_INSTRUCTIONS, 'edit_instructions_privacy': permission.EDIT_OFFLINE_INSTRUCTIONS_PRIVACY, 'delete_instructions': permission.DELETE_OFFLINE_INSTRUCTIONS, 'generate_letter': permission.GENERATE_LETTER, 'add_user': permission.ADD_USER_TO_REQUEST, 'edit_user': permission.EDIT_USER_REQUEST_PERMISSIONS, 'remove_user': permission.REMOVE_USER_FROM_REQUEST, 'edit_title': permission.EDIT_TITLE, 'edit_title_privacy': permission.CHANGE_PRIVACY_TITLE, 'edit_agency_request_summary': permission.EDIT_AGENCY_REQUEST_SUMMARY, 'edit_agency_request_summary_privacy': permission.CHANGE_PRIVACY_AGENCY_REQUEST_SUMMARY, 'edit_requester_info': permission.EDIT_REQUESTER_INFO } # Build permissions dictionary for checking on the front-end. for key, val in permissions.items(): if current_user.is_anonymous or not current_request.user_requests.filter_by( user_guid=current_user.guid).first(): permissions[key] = False else: permissions[key] = is_allowed(current_user, request_id, val) if not current_user.is_anonymous else False # Build dictionary of current permissions for all assigned users. assigned_user_permissions = {} for u in assigned_users: assigned_user_permissions[u.guid] = UserRequests.query.filter_by( request_id=request_id, user_guid=u.guid).one().get_permission_choice_indices() point_of_contact = get_current_point_of_contact(request_id) if point_of_contact: current_point_of_contact = {'user_guid': point_of_contact.user_guid} else: current_point_of_contact = {'user_guid': ''} # Determine if the Agency Request Summary should be shown. show_agency_request_summary = False if current_user in current_request.agency_users \ or current_request.agency_request_summary \ and (current_request.requester == current_user and current_request.status == request_status.CLOSED and not current_request.privacy['agency_request_summary'] or current_request.status == request_status.CLOSED and current_request.agency_request_summary_release_date and current_request.agency_request_summary_release_date < datetime.utcnow() and not current_request.privacy['agency_request_summary']): show_agency_request_summary = True # Determine if the title should be shown. show_title = (current_user in current_request.agency_users or current_request.requester == current_user or not current_request.privacy['title']) # Determine if "Generate Letter" functionality is enabled for the agency. if 'letters' in current_request.agency.agency_features: generate_letters_enabled = current_request.agency.agency_features['letters']['generate_letters'] else: generate_letters_enabled = False # Determine if custom request forms are enabled if 'enabled' in current_request.agency.agency_features['custom_request_forms']: custom_request_forms_enabled = current_request.agency.agency_features['custom_request_forms']['enabled'] else: custom_request_forms_enabled = False # Determine if custom request form panels should be expanded by default if 'expand_by_default' in current_request.agency.agency_features['custom_request_forms']: expand_by_default = current_request.agency.agency_features['custom_request_forms']['expand_by_default'] else: expand_by_default = False # Determine if request description should be hidden when custom forms are enabled if 'description_hidden_by_default' in current_request.agency.agency_features['custom_request_forms']: description_hidden_by_default = current_request.agency.agency_features['custom_request_forms']['description_hidden_by_default'] else: description_hidden_by_default = False return render_template( 'request/view_request.html', request=current_request, status=request_status, agency_users=current_request.agency_users, edit_requester_form=EditRequesterForm(current_request.requester), contact_agency_form=ContactAgencyForm(current_request), deny_request_form=DenyRequestForm(current_request.agency.ein), close_request_form=CloseRequestForm(current_request.agency.ein), reopen_request_form=ReopenRequestForm(current_request.agency.ein), remove_user_request_form=RemoveUserRequestForm(assigned_users), add_user_request_form=AddUserRequestForm(active_users), edit_user_request_form=EditUserRequestForm(assigned_users), generate_acknowledgment_letter_form=GenerateAcknowledgmentLetterForm(current_request.agency.ein), generate_denial_letter_form=GenerateDenialLetterForm(current_request.agency.ein), generate_closing_letter_form=GenerateClosingLetterForm(current_request.agency.ein), generate_extension_letter_form=GenerateExtensionLetterForm(current_request.agency.ein), generate_envelope_form=GenerateEnvelopeForm(current_request.agency_ein, current_request.requester), generate_response_letter_form=GenerateResponseLetterForm(current_request.agency.ein), assigned_user_permissions=assigned_user_permissions, current_point_of_contact=current_point_of_contact, holidays=holidays, assigned_users=assigned_users, active_users=active_users, permissions=permissions, show_agency_request_summary=show_agency_request_summary, show_title=show_title, is_requester=(current_request.requester == current_user), permissions_length=len(permission.ALL), generate_letters_enabled=generate_letters_enabled, custom_request_forms_enabled = custom_request_forms_enabled, expand_by_default=expand_by_default, description_hidden_by_default=description_hidden_by_default )
def patch(response_id): """ Edit a response's fields and send a notification email. Expects a request body containing field names and updated values. Ex: { 'privacy': 'release_public', 'title': 'new title' 'filename': 'uploaded_file_name.ext' # REQUIRED for updates to Files metadata } Ex (for delete): { 'deleted': true, 'confirmation': string checked against 'DELETE' if the strings do not match, the 'deleted' field will not be updated } :return: on success: { 'old': { original attributes and their values } 'new': { updated attributes and their values } } """ resp = Responses.query.filter_by(id=response_id, deleted=False).one() if current_user.is_anonymous or not resp.is_editable: return abort(403) patch_form = dict(flask_request.form) privacy = patch_form.pop('privacy', None) if privacy: # Check permissions for editing the privacy if required. permission_for_edit_type_privacy = { Files: permission.EDIT_FILE_PRIVACY, Notes: permission.EDIT_NOTE_PRIVACY, Instructions: permission.EDIT_OFFLINE_INSTRUCTIONS_PRIVACY, Links: permission.EDIT_LINK_PRIVACY } if not is_allowed(current_user, resp.request_id, permission_for_edit_type_privacy[type(resp)]): return abort(403) delete = patch_form.pop('deleted', None) if delete: confirmation = patch_form.pop('confirmation', None) if not confirmation: return abort(403) permission_for_delete_type = { Files: permission.DELETE_FILE, Notes: permission.DELETE_NOTE, Instructions: permission.DELETE_OFFLINE_INSTRUCTIONS, Links: permission.DELETE_LINK } if not is_allowed(current_user, resp.request_id, permission_for_delete_type[type(resp)]): return abort(403) if patch_form: # Mapping of Response types to permission values permission_for_type = { Files: permission.EDIT_FILE, Notes: permission.EDIT_NOTE, Instructions: permission.EDIT_OFFLINE_INSTRUCTIONS, Links: permission.EDIT_LINK } # If the current user does not have the permission to edit the response type, return 403 if not is_allowed(current_user, resp.request_id, permission_for_type[type(resp)]): return abort(403) editor_for_type = { Files: RespFileEditor, Notes: RespNoteEditor, Instructions: RespInstructionsEditor, Links: RespLinkEditor, } editor = editor_for_type[type(resp)](current_user, resp, flask_request) if editor.errors: http_response = {"errors": editor.errors} else: if editor.no_change: # TODO: unittest http_response = { "message": "No changes detected." } else: http_response = { "old": editor.data_old, "new": editor.data_new } return jsonify(http_response), 200
def get_request_responses(): """ Returns a set of responses (id, type, and template), ordered by date descending, and starting from a specified index. Request parameters: - start: (int) starting index - request_id: FOIL request id - with_template: (default: False) include html (rows and modals) for each response """ start = int(flask_request.args['start']) current_request = Requests.query.filter_by(id=flask_request.args['request_id']).one() if current_user in current_request.agency_users: # If the user is an agency user assigned to the request, all responses can be retrieved. responses = Responses.query.filter( Responses.request_id == current_request.id, ~Responses.id.in_([cm.method_id for cm in CommunicationMethods.query.all()]), Responses.type != response_type.EMAIL, Responses.deleted == False ).order_by( desc(Responses.date_modified) ).all() elif current_user == current_request.requester: # If the user is the requester, then only responses that are "Release and Private" or "Release and Public" # can be retrieved. responses = Responses.query.filter( Responses.request_id == current_request.id, ~Responses.id.in_([cm.method_id for cm in CommunicationMethods.query.all()]), Responses.type != response_type.EMAIL, Responses.deleted == False, Responses.privacy.in_([response_privacy.RELEASE_AND_PRIVATE, response_privacy.RELEASE_AND_PUBLIC]) ).order_by( desc(Responses.date_modified) ).all() else: # If the user is not an agency user assigned to the request or the requester, then only responses that are # "Release and Public" whose release date is not in the future can be retrieved. responses = Responses.query.filter( Responses.request_id == current_request.id, ~Responses.id.in_([cm.method_id for cm in CommunicationMethods.query.all()]), Responses.type != response_type.EMAIL, Responses.deleted == False, Responses.privacy.in_([response_privacy.RELEASE_AND_PUBLIC]), Responses.release_date.isnot(None), Responses.release_date < datetime.utcnow() ).order_by( desc(Responses.date_modified) ).all() total = len(responses) responses = responses[start: start + RESPONSES_INCREMENT] template_path = 'request/responses/' response_jsons = [] row_count = 0 for response in responses: json = { 'id': response.id, 'type': response.type } if eval_request_bool(flask_request.args.get('with_template')): row_count += 1 row = render_template( template_path + 'row.html', response=response, row_num=start + row_count, response_type=response_type, determination_type=determination_type, show_preview=not (response.type == response_type.DETERMINATION and (response.dtype == determination_type.ACKNOWLEDGMENT or response.dtype == determination_type.REOPENING)) ) modal = render_template( template_path + 'modal.html', response=response, requires_workflow=response.type in response_type.EMAIL_WORKFLOW_TYPES, modal_body=render_template( "{}modal_body/{}.html".format( template_path, response.type ), response=response, privacies=[response_privacy.RELEASE_AND_PUBLIC, response_privacy.RELEASE_AND_PRIVATE, response_privacy.PRIVATE], determination_type=determination_type, request_status=request_status, edit_response_privacy_permission=is_allowed(user=current_user, request_id=response.request_id, permission=get_permission( permission_type='privacy', response_type=type( response))), edit_response_permission=is_allowed(user=current_user, request_id=response.request_id, permission=get_permission(permission_type='edit', response_type=type( response))), delete_response_permission=is_allowed(user=current_user, request_id=response.request_id, permission=get_permission(permission_type='delete', response_type=type(response))), is_editable=response.is_editable, current_request=current_request ), response_type=response_type, determination_type=determination_type, request_status=request_status, edit_response_permission=is_allowed(user=current_user, request_id=response.request_id, permission=get_permission(permission_type='edit', response_type=type(response))), delete_response_permission=is_allowed(user=current_user, request_id=response.request_id, permission=get_permission(permission_type='delete', response_type=type(response))), edit_response_privacy_permission=is_allowed(user=current_user, request_id=response.request_id, permission=get_permission( permission_type='privacy', response_type=type( response))), is_editable=response.is_editable, current_request=current_request ) json['template'] = row + modal response_jsons.append(json) return jsonify(responses=response_jsons, total=total)
def view(request_id): """ This function is for testing purposes of the view a request back until backend functionality is implemented. :return: redirect to view request page """ try: current_request = Requests.query.filter_by(id=request_id).one() assert current_request.agency.is_active except NoResultFound: print("Request with id '{}' does not exist.".format(request_id)) sentry.captureException() return abort(404) except AssertionError: print("Request belongs to inactive agency.") sentry.captureException() return abort(404) holidays = sorted( get_holidays_date_list(datetime.utcnow().year, (datetime.utcnow() + rd(years=DEFAULT_YEARS_HOLIDAY_LIST)).year)) active_users = [] assigned_users = [] if current_user.is_agency: for agency_user in current_request.agency.active_users: if not agency_user in current_request.agency.administrators and ( agency_user != current_user): # populate list of assigned users that can be removed from a request if agency_user in current_request.agency_users: assigned_users.append(agency_user) # append to list of active users that can be added to a request else: active_users.append(agency_user) permissions = { 'acknowledge': permission.ACKNOWLEDGE, 'deny': permission.DENY, 'extend': permission.EXTEND, 'close': permission.CLOSE, 're_open': permission.RE_OPEN, 'add_file': permission.ADD_FILE, 'edit_file_privacy': permission.EDIT_FILE_PRIVACY, 'delete_file': permission.DELETE_FILE, 'add_note': permission.ADD_NOTE, 'edit_note_privacy': permission.EDIT_NOTE_PRIVACY, 'delete_note': permission.DELETE_NOTE, 'add_link': permission.ADD_LINK, 'edit_link_privacy': permission.EDIT_LINK_PRIVACY, 'delete_link': permission.DELETE_LINK, 'add_instructions': permission.ADD_OFFLINE_INSTRUCTIONS, 'edit_instructions_privacy': permission.EDIT_OFFLINE_INSTRUCTIONS_PRIVACY, 'delete_instructions': permission.DELETE_OFFLINE_INSTRUCTIONS, 'generate_letter': permission.GENERATE_LETTER, 'add_user': permission.ADD_USER_TO_REQUEST, 'edit_user': permission.EDIT_USER_REQUEST_PERMISSIONS, 'remove_user': permission.REMOVE_USER_FROM_REQUEST, 'edit_title': permission.EDIT_TITLE, 'edit_title_privacy': permission.CHANGE_PRIVACY_TITLE, 'edit_agency_request_summary': permission.EDIT_AGENCY_REQUEST_SUMMARY, 'edit_agency_request_summary_privacy': permission.CHANGE_PRIVACY_AGENCY_REQUEST_SUMMARY, 'edit_requester_info': permission.EDIT_REQUESTER_INFO } # Build permissions dictionary for checking on the front-end. for key, val in permissions.items(): if current_user.is_anonymous or not current_request.user_requests.filter_by( user_guid=current_user.guid, auth_user_type=current_user.auth_user_type).first(): permissions[key] = False else: permissions[key] = is_allowed( current_user, request_id, val) if not current_user.is_anonymous else False # Build dictionary of current permissions for all assigned users. assigned_user_permissions = {} for u in assigned_users: assigned_user_permissions[u.guid] = UserRequests.query.filter_by( request_id=request_id, user_guid=u.guid).one().get_permission_choice_indices() point_of_contact = get_current_point_of_contact(request_id) if point_of_contact: current_point_of_contact = {'user_guid': point_of_contact.user_guid} else: current_point_of_contact = {'user_guid': ''} # Determine if the Agency Request Summary should be shown. show_agency_request_summary = False if current_user in current_request.agency_users \ or current_request.agency_request_summary \ and (current_request.requester == current_user and current_request.status == request_status.CLOSED and not current_request.privacy['agency_request_summary'] or current_request.status == request_status.CLOSED and current_request.agency_request_summary_release_date and current_request.agency_request_summary_release_date < datetime.utcnow() and not current_request.privacy['agency_request_summary']): show_agency_request_summary = True # Determine if the title should be shown. show_title = (current_user in current_request.agency_users or current_request.requester == current_user or not current_request.privacy['title']) # Determine if "Generate Letter" functionality is enabled for the agency. if 'letters' in current_request.agency.agency_features: generate_letters_enabled = current_request.agency.agency_features[ 'letters']['generate_letters'] else: generate_letters_enabled = False return render_template( 'request/view_request.html', request=current_request, status=request_status, agency_users=current_request.agency_users, edit_requester_form=EditRequesterForm(current_request.requester), contact_agency_form=ContactAgencyForm(current_request), deny_request_form=DenyRequestForm(current_request.agency.ein), close_request_form=CloseRequestForm(current_request.agency.ein), remove_user_request_form=RemoveUserRequestForm(assigned_users), add_user_request_form=AddUserRequestForm(active_users), edit_user_request_form=EditUserRequestForm(assigned_users), generate_acknowledgment_letter_form=GenerateAcknowledgmentLetterForm( current_request.agency.ein), generate_denial_letter_form=GenerateDenialLetterForm( current_request.agency.ein), generate_closing_letter_form=GenerateClosingLetterForm( current_request.agency.ein), generate_extension_letter_form=GenerateExtensionLetterForm( current_request.agency.ein), generate_envelope_form=GenerateEnvelopeForm(current_request.agency_ein, current_request.requester), generate_response_letter_form=GenerateResponseLetterForm( current_request.agency.ein), assigned_user_permissions=assigned_user_permissions, current_point_of_contact=current_point_of_contact, holidays=holidays, assigned_users=assigned_users, active_users=active_users, permissions=permissions, show_agency_request_summary=show_agency_request_summary, show_title=show_title, is_requester=(current_request.requester == current_user), permissions_length=len(permission.ALL), generate_letters_enabled=generate_letters_enabled)
def post(request_id): """ Create a new upload. Handles chunked files through the Content-Range header. For filesize validation and more upload logic, see: /static/js/upload/fileupload.js Optional request body parameters: - update (bool) save the uploaded file to the 'updated' directory (this indicates the file is meant to replace a previously uploaded file) - response_id (int) the id of a response associated with the file this upload is replacing - REQUIRED if 'update' is 'true' - ignored if 'update' is 'false' :returns: { "name": file name, "size": file size } """ files = request.files file_ = files[next(files.keys())] filename = secure_filename(file_.filename) is_update = eval_request_bool(request.form.get('update')) agency_ein = Requests.query.filter_by(id=request_id).one().agency.ein if is_allowed(user=current_user, request_id=request_id, permission=permission.ADD_FILE) or \ is_allowed(user=current_user, request_id=request_id, permission=permission.EDIT_FILE): response_id = request.form.get('response_id') if is_update else None if upload_exists(request_id, filename, response_id): response = { "files": [{ "name": filename, "error": "A file with this name has already " "been uploaded for this request." # TODO: "link": <link-to-existing-file> ? would be nice }] } else: upload_path = os.path.join( current_app.config['UPLOAD_QUARANTINE_DIRECTORY'], request_id) if not os.path.exists(upload_path): os.mkdir(upload_path) filepath = os.path.join(upload_path, filename) key = get_upload_key(request_id, filename, is_update) try: if CONTENT_RANGE_HEADER in request.headers: start, size = parse_content_range( request.headers[CONTENT_RANGE_HEADER]) # Only validate mime type on first chunk valid_file_type = True file_type = None if start == 0: valid_file_type, file_type = is_valid_file_type(file_) if current_user.is_agency_active(agency_ein): valid_file_type = True if os.path.exists(filepath): # remove existing file (upload 'restarted' for same file) os.remove(filepath) if valid_file_type: redis.set(key, upload_status.PROCESSING) with open(filepath, 'ab') as fp: fp.seek(start) fp.write(file_.stream.read()) # scan if last chunk written if os.path.getsize(filepath) == size: scan_and_complete_upload.delay(request_id, filepath, is_update, response_id) else: valid_file_type, file_type = is_valid_file_type(file_) if current_user.is_agency_active(agency_ein): valid_file_type = True if valid_file_type: redis.set(key, upload_status.PROCESSING) file_.save(filepath) scan_and_complete_upload.delay(request_id, filepath, is_update, response_id) if not valid_file_type: response = { "files": [{ "name": filename, "error": "The file type '{}' is not allowed.".format( file_type) }] } else: response = { "files": [{ "name": filename, "original_name": file_.filename, "size": os.path.getsize(filepath), }] } except Exception as e: sentry.captureException() redis.set(key, upload_status.ERROR) current_app.logger.exception("Upload for file '{}' failed: {}".format(filename, e)) response = { "files": [{ "name": filename, "error": "There was a problem uploading this file." }] } return jsonify(response), 200
def delete(r_id_type, r_id, filecode): """ Removes an uploaded file. :param r_id_type: "response" or "request" :param r_id: the Response or Request identifier :param filecode: the encoded name of the uploaded file (base64 without padding) Optional request body parameters: - quarantined_only (bool) only delete the file if it is quarantined (beware: takes precedence over 'updated_only') - updated_only (bool) only delete the file if it is in the 'updated' directory :returns: On success: { "deleted": filename } On failure: { "error": error message } """ filename = secure_filename(b64decode_lenient(filecode)) if r_id_type not in ["request", "response"]: response = {"error": "Invalid ID type."} else: try: if r_id_type == "response": response = Responses.query.filter_by(id=r_id, deleted=False) r_id = response.request_id path = '' quarantined_only = eval_request_bool(request.form.get('quarantined_only')) has_add_edit = (is_allowed(user=current_user, request_id=r_id, permission=permission.ADD_FILE) or is_allowed(user=current_user, request_id=r_id, permission=permission.EDIT_FILE)) if quarantined_only and has_add_edit: path = os.path.join( current_app.config['UPLOAD_QUARANTINE_DIRECTORY'], r_id ) elif eval_request_bool(request.form.get('updated_only')) and \ is_allowed(user=current_user, request_id=r_id, permission=permission.EDIT_FILE): path = os.path.join( current_app.config['UPLOAD_DIRECTORY'], r_id, UPDATED_FILE_DIRNAME ) else: path_for_status = { upload_status.PROCESSING: current_app.config['UPLOAD_QUARANTINE_DIRECTORY'], upload_status.SCANNING: current_app.config['UPLOAD_QUARANTINE_DIRECTORY'], upload_status.READY: current_app.config['UPLOAD_DIRECTORY'] } status = redis.get(get_upload_key(r_id, filename)) if status is not None: dest_path = path_for_status[status.decode("utf-8")] if (dest_path == current_app.config['UPLOAD_QUARANTINE_DIRECTORY'] and has_add_edit) or ( dest_path == current_app.config['UPLOAD_DIRECTORY'] and is_allowed(user=current_user, request_id=r_id, permission=permission.ADD_FILE) ): path = os.path.join( dest_path, r_id ) filepath = os.path.join(path, filename) found = False if path != '': if quarantined_only: if os.path.exists(filepath): os.remove(filepath) found = True else: if fu.exists(filepath): fu.remove(filepath) found = True if found: response = {"deleted": filename} else: response = {"error": "Upload not found."} except Exception as e: sentry.captureException() current_app.logger.exception("Error on DELETE /upload/: {}".format(e)) response = {"error": "Failed to delete '{}'".format(filename)} return jsonify(response), 200
def get_request_responses(): """ Returns a set of responses (id, type, and template), ordered by date descending, and starting from a specified index. Request parameters: - start: (int) starting index - request_id: FOIL request id - with_template: (default: False) include html (rows and modals) for each response """ start = int(flask_request.args['start']) current_request = Requests.query.filter_by( id=flask_request.args['request_id']).one() responses = Responses.query.filter( Responses.request_id == current_request.id, Responses.type != response_type.EMAIL, Responses.deleted == False).order_by(desc( Responses.date_modified)).all()[start:start + RESPONSES_INCREMENT] template_path = 'request/responses/' response_jsons = [] row_count = 0 for response in responses: # If a user is anonymous or a public user who is not the requester AND the date for Release and Public is in # the future, do not generate response row if (current_user in response.request.agency_users) or ( current_user == response.request.requester and response.privacy != response_privacy.PRIVATE) or ( response.privacy == response_privacy.RELEASE_AND_PUBLIC and response.release_date and response.release_date < datetime.utcnow()): json = {'id': response.id, 'type': response.type} if eval_request_bool(flask_request.args.get('with_template')): row_count += 1 row = render_template( template_path + 'row.html', response=response, row_num=start + row_count, response_type=response_type, determination_type=determination_type, show_preview=not ( response.type == response_type.DETERMINATION and (response.dtype == determination_type.ACKNOWLEDGMENT or response.dtype == determination_type.REOPENING))) modal = render_template( template_path + 'modal.html', response=response, requires_workflow=response.type in response_type.EMAIL_WORKFLOW_TYPES, modal_body=render_template( "{}modal_body/{}.html".format(template_path, response.type), response=response, privacies=[ response_privacy.RELEASE_AND_PUBLIC, response_privacy.RELEASE_AND_PRIVATE, response_privacy.PRIVATE ], determination_type=determination_type, request_status=request_status, edit_response_privacy_permission=is_allowed( user=current_user, request_id=response.request_id, permission=get_permission( permission_type='privacy', response_type=type(response))), edit_response_permission=is_allowed( user=current_user, request_id=response.request_id, permission=get_permission( permission_type='edit', response_type=type(response))), delete_response_permission=is_allowed( user=current_user, request_id=response.request_id, permission=get_permission( permission_type='delete', response_type=type(response))), is_editable=response.is_editable, current_request=current_request), response_type=response_type, determination_type=determination_type, request_status=request_status, edit_response_permission=is_allowed( user=current_user, request_id=response.request_id, permission=get_permission( permission_type='edit', response_type=type(response))), delete_response_permission=is_allowed( user=current_user, request_id=response.request_id, permission=get_permission( permission_type='delete', response_type=type(response))), edit_response_privacy_permission=is_allowed( user=current_user, request_id=response.request_id, permission=get_permission( permission_type='privacy', response_type=type(response))), is_editable=response.is_editable, current_request=current_request) json['template'] = row + modal response_jsons.append(json) return jsonify(responses=response_jsons)
def patch(response_id): """ Edit a response's fields and send a notification email. Expects a request body containing field names and updated values. Ex: { 'privacy': 'release_public', 'title': 'new title' 'filename': 'uploaded_file_name.ext' # REQUIRED for updates to Files metadata } Ex (for delete): { 'deleted': true, 'confirmation': string checked against 'DELETE' if the strings do not match, the 'deleted' field will not be updated } :return: on success: { 'old': { original attributes and their values } 'new': { updated attributes and their values } } """ resp = Responses.query.filter_by(id=response_id, deleted=False).one() if current_user.is_anonymous or not resp.is_editable: return abort(403) patch_form = dict(flask_request.form) privacy = patch_form.pop('privacy', None) if privacy: # Check permissions for editing the privacy if required. permission_for_edit_type_privacy = { Files: permission.EDIT_FILE_PRIVACY, Notes: permission.EDIT_NOTE_PRIVACY, Instructions: permission.EDIT_OFFLINE_INSTRUCTIONS_PRIVACY, Links: permission.EDIT_LINK_PRIVACY } if not is_allowed(current_user, resp.request_id, permission_for_edit_type_privacy[type(resp)]): return abort(403) delete = patch_form.pop('deleted', None) if delete: confirmation = patch_form.pop('confirmation', None) if not confirmation: return abort(403) permission_for_delete_type = { Files: permission.DELETE_FILE, Notes: permission.DELETE_NOTE, Instructions: permission.DELETE_OFFLINE_INSTRUCTIONS, Links: permission.DELETE_LINK } if not is_allowed(current_user, resp.request_id, permission_for_delete_type[type(resp)]): return abort(403) if patch_form: # Mapping of Response types to permission values permission_for_type = { Files: permission.EDIT_FILE, Notes: permission.EDIT_NOTE, Instructions: permission.EDIT_OFFLINE_INSTRUCTIONS, Links: permission.EDIT_LINK } # If the current user does not have the permission to edit the response type, return 403 if not is_allowed(current_user, resp.request_id, permission_for_type[type(resp)]): return abort(403) editor_for_type = { Files: RespFileEditor, Notes: RespNoteEditor, Instructions: RespInstructionsEditor, Links: RespLinkEditor, } editor = editor_for_type[type(resp)](current_user, resp, flask_request) if editor.errors: http_response = {"errors": editor.errors} else: if editor.no_change: # TODO: unittest http_response = {"message": "No changes detected."} else: http_response = {"old": editor.data_old, "new": editor.data_new} return jsonify(http_response), 200