def patch(self, nr_id, action): """ Roll back a Name Request to a usable state in case of a frontend error. :param nr_id: :param action: :return: """ try: if not full_access_to_name_request(request): return { "message": "You do not have access to this NameRequest." }, 403 # Find the existing name request nr_model = Request.query.get(nr_id) # Creates a new NameRequestService, validates the app config, and sets request_data to the NameRequestService instance self.initialize() nr_svc = self.nr_service nr_svc.nr_num = nr_model.nrNum nr_svc.nr_id = nr_model.id # This could be moved out, but it's fine here for now def validate_patch_request(data): # TODO: Validate the data payload # Use the NR model state as the default, as the state change may not be included in the PATCH request is_valid = False msg = '' # This handles updates if the NR state is 'patchable' if NameRequestRollbackActions.has_value(action): is_valid = True else: msg = 'Invalid rollback action' return is_valid, msg is_valid_patch, validation_msg = validate_patch_request( self.request_data) validation_msg = validation_msg if not len( validation_msg) > 0 else 'Invalid request for PATCH' if not is_valid_patch: raise InvalidInputError(message=validation_msg) # This handles updates if the NR state is 'patchable' nr_model = self.handle_patch_rollback(nr_model, action) current_app.logger.debug(nr_model.json()) response_data = nr_model.json() # Add the list of valid Name Request actions for the given state to the response response_data['actions'] = nr_svc.current_state_actions return jsonify(response_data), 200 except NameRequestException as err: return handle_exception(err, err.message, 500) except Exception as err: return handle_exception(err, repr(err), 500)
def put(self, nr_id): """ NOT used for Existing Name Request updates that only change the Name Request. Use 'patch' instead. State changes handled include state changes to [DRAFT, COND_RESERVE, RESERVED, COND_RESERVE to CONDITIONAL, RESERVED to APPROVED] :param nr_id: :return: """ try: # Find the existing name request nr_model = Request.query.get(nr_id) # Creates a new NameRequestService, validates the app config, and sets request_data to the NameRequestService instance self.initialize() nr_svc = self.nr_service nr_svc.nr_num = nr_model.nrNum nr_svc.nr_id = nr_model.id valid_update_states = [ State.DRAFT, State.COND_RESERVE, State.RESERVED, State.PENDING_PAYMENT ] # This could be moved out, but it's fine here for now def validate_put_request(data): is_valid = False msg = '' if data.get('stateCd') in valid_update_states: is_valid = True return is_valid, msg is_valid_put, validation_msg = validate_put_request( self.request_data) validation_msg = validation_msg if not len( validation_msg) > 0 else 'Invalid request for PUT' if not is_valid_put: raise InvalidInputError(message=validation_msg) if nr_model.stateCd in valid_update_states: nr_model = self.update_nr(nr_model, nr_model.stateCd, self.handle_nr_update) # Record the event EventRecorder.record(nr_svc.user, Event.PUT, nr_model, nr_svc.request_data) current_app.logger.debug(nr_model.json()) response_data = nr_model.json() # Add the list of valid Name Request actions for the given state to the response response_data['actions'] = nr_svc.current_state_actions return jsonify(response_data), 200 except NameRequestException as err: return handle_exception(err, err.message, 500) except Exception as err: return handle_exception(err, repr(err), 500)
def patch(self, nr_id, nr_action: str): """ Update a specific set of fields and/or a provided action. Fields excluded from the payload will not be updated. The following data format is expected when providing a data payload: { 'stateCd': 'CANCELLED' } # Fields to update We use this to: - Edit a subset of NR fields - Cancel an NR - Change the state of an NR :param nr_id: :param nr_action: One of [CHECKOUT, CHECKIN, EDIT, CANCEL, RESEND] :return: """ try: if not full_access_to_name_request(request): return { "message": "You do not have access to this NameRequest." }, 403 nr_action = str(nr_action).upper( ) # Convert to upper-case, just so we can support lower case action strings nr_action = NameRequestPatchActions[nr_action].value \ if NameRequestPatchActions.has_value(nr_action) \ else NameRequestPatchActions.EDIT.value # Find the existing name request nr_model = Request.query.get(nr_id) def initialize(_self): _self.validate_config(current_app) request_json = request.get_json() if nr_action: _self.nr_action = nr_action if nr_action is NameRequestPatchActions.CHECKOUT.value: # Make sure the NR isn't already checked out checked_out_by_different_user = nr_model.checkedOutBy is not None and nr_model.checkedOutBy != request_json.get( 'checkedOutBy', None) if checked_out_by_different_user: raise NameRequestIsInProgressError() # set the user id of the request to name_request_service_account service_account_user = User.find_by_username( 'name_request_service_account') nr_model.userId = service_account_user.id # The request payload will be empty when making this call, add them to the request _self.request_data = { # Doesn't have to be a UUID but this is easy and works for a pretty unique token 'checkedOutBy': str(uuid4()), 'checkedOutDt': datetime.now() } # Set the request data to the service _self.nr_service.request_data = self.request_data elif nr_action is NameRequestPatchActions.CHECKIN.value: # The request payload will be empty when making this call, add them to the request _self.request_data = { 'checkedOutBy': None, 'checkedOutDt': None } # Set the request data to the service _self.nr_service.request_data = self.request_data else: super().initialize() initialize(self) nr_svc = self.nr_service nr_svc.nr_num = nr_model.nrNum nr_svc.nr_id = nr_model.id # This could be moved out, but it's fine here for now def validate_patch_request(data): # Use the NR model state as the default, as the state change may not be included in the PATCH request request_state = data.get('stateCd', nr_model.stateCd) is_valid = False msg = '' # Handles updates if the NR state is 'patchable' if request_state in request_editable_states: is_valid = True elif request_state in contact_editable_states: is_valid = True else: msg = 'Invalid state change requested - the Name Request state cannot be changed to [' + data.get( 'stateCd', '') + ']' # Check the action, make sure it's valid if not NameRequestPatchActions.has_value(nr_action): is_valid = False msg = 'Invalid Name Request PATCH action, please use one of [' + ', '.join( [action.value for action in NameRequestPatchActions]) + ']' return is_valid, msg is_valid_patch, validation_msg = validate_patch_request( self.request_data) validation_msg = validation_msg if not len( validation_msg) > 0 else 'Invalid request for PATCH' if not is_valid_patch: raise InvalidInputError(message=validation_msg) def handle_patch_actions(action, model): return { NameRequestPatchActions.CHECKOUT.value: self.handle_patch_checkout, NameRequestPatchActions.CHECKIN.value: self.handle_patch_checkin, NameRequestPatchActions.EDIT.value: self.handle_patch_edit, NameRequestPatchActions.CANCEL.value: self.handle_patch_cancel, NameRequestPatchActions.RESEND.value: self.handle_patch_resend, NameRequestPatchActions.REQUEST_REFUND.value: self.handle_patch_request_refund }.get(action)(model) # This handles updates if the NR state is 'patchable' nr_model = handle_patch_actions(nr_action, nr_model) current_app.logger.debug(nr_model.json()) response_data = nr_model.json() # Don't return the whole response object if we're checking in or checking out if nr_action == NameRequestPatchActions.CHECKOUT.value: response_data = { 'id': nr_id, 'checkedOutBy': response_data.get('checkedOutBy'), 'checkedOutDt': response_data.get('checkedOutDt'), 'state': response_data.get('state', ''), 'stateCd': response_data.get('stateCd', ''), 'actions': nr_svc.current_state_actions } return jsonify(response_data), 200 if nr_action == NameRequestPatchActions.CHECKIN.value: response_data = { 'id': nr_id, 'state': response_data.get('state', ''), 'stateCd': response_data.get('stateCd', ''), 'actions': nr_svc.current_state_actions } return jsonify(response_data), 200 # Add the list of valid Name Request actions for the given state to the response if (nr_action == NameRequestPatchActions.REQUEST_REFUND.value): response_data['actions'] = [] else: response_data['actions'] = nr_svc.current_state_actions return jsonify(response_data), 200 except NameRequestIsInProgressError as err: # Might as well use the Mozilla WebDAV HTTP Locked status, it's pretty close return handle_exception(err, err.message, 423) except NameRequestException as err: return handle_exception(err, err.message, 500) except Exception as err: return handle_exception(err, repr(err), 500)
def patch(self, nr_id, nr_action): """ Update a specific set of fields and/or a provided action. Fields excluded from the payload will not be updated. The following data format is expected when providing a data payload: { 'stateCd': 'CANCELLED' } # Fields to update We use this to: - Edit a subset of NR fields - Cancel an NR - Change the state of an NR to [CANCELLED, INPROGRESS, HOLD, APPROVED, REJECTED - Apply the following actions to an NR [EDIT, UPGRADE, CANCEL, REFUND, REAPPLY, RESEND] :param nr_id: :param nr_action: One of [EDIT, UPGRADE, CANCEL, REFUND, REAPPLY, RESEND] :return: """ try: # Find the existing name request nr_model = Request.query.get(nr_id) # Creates a new NameRequestService, validates the app config, and sets request_data to the NameRequestService instance self.initialize() nr_svc = self.nr_service nr_action = str(nr_action).upper( ) # Convert to upper-case, just so we can support lower case action strings nr_action = NameRequestActions[nr_action].value \ if NameRequestActions.has_value(nr_action) \ else NameRequestActions.EDIT.value nr_svc.nr_num = nr_model.nrNum nr_svc.nr_id = nr_model.id # This could be moved out, but it's fine here for now def validate_patch_request(data): # Use the NR model state as the default, as the state change may not be included in the PATCH request request_state = data.get('stateCd', nr_model.stateCd) is_valid = False msg = '' # Handles updates if the NR state is 'patchable' if request_state in request_editable_states: is_valid = True elif request_state in contact_editable_states: is_valid = True else: msg = 'Invalid state change requested - the Name Request state cannot be changed to [' + data.get( 'stateCd', '') + ']' return is_valid, msg is_valid_patch, validation_msg = validate_patch_request( self.request_data) validation_msg = validation_msg if not len( validation_msg) > 0 else 'Invalid request for PATCH' if not is_valid_patch: raise InvalidInputError(message=validation_msg) def handle_patch_actions(action, model): return { NameRequestActions.EDIT.value: self.handle_patch_edit, NameRequestActions.UPGRADE.value: self.handle_patch_upgrade, NameRequestActions.CANCEL.value: self.handle_patch_cancel, NameRequestActions.REFUND.value: self.handle_patch_refund, # TODO: This is a frontend only action throw an error! # NameRequestActions.RECEIPT.value: self.patch_receipt, NameRequestActions.REAPPLY.value: self.handle_patch_reapply, NameRequestActions.RESEND.value: self.handle_patch_resend }.get(action)(model) # This handles updates if the NR state is 'patchable' nr_model = handle_patch_actions(nr_action, nr_model) current_app.logger.debug(nr_model.json()) response_data = nr_model.json() # Add the list of valid Name Request actions for the given state to the response response_data['actions'] = nr_svc.current_state_actions return jsonify(response_data), 200 except NameRequestException as err: return handle_exception(err, err.message, 500) except Exception as err: return handle_exception(err, repr(err), 500)
def get(self): try: filters = [] # Validate the request if len(request.args) == 0: raise InvalidInputError( message='No query parameters were specified in the request' ) nr_num_query_str = get_query_param_str('nrNum') email_address_query_str = get_query_param_str('emailAddress') phone_number_query_str = get_query_param_str('phoneNumber') if not nr_num_query_str: raise InvalidInputError(message='An nrNum must be provided') else: if not email_address_query_str and not phone_number_query_str: raise InvalidInputError( message= 'Either an emailAddress or phoneNumber must be provided' ) # Continue nr_num = parse_nr_num(nr_num_query_str) email_address = email_address_query_str phone_number = get_query_param_str('phoneNumber') # Filter on addresses # address_line = get_query_param_str('addrLine1') if nr_num: filters.append(func.lower(Request.nrNum) == nr_num.lower()) if phone_number: strip_phone_number_chars_regex = r"[^0-9]" filters.append( Request.applicants.any( func.regexp_replace( Applicant.phoneNumber, strip_phone_number_chars_regex, '', 'g').contains( re.sub(strip_phone_number_chars_regex, '', phone_number)))) if email_address: filters.append( Request.applicants.any( func.lower(Applicant.emailAddress).startswith( email_address.lower()))) ''' Filter on addresses if address_line: filters.append( Request.applicants.any( func.lower(Applicant.addrLine1).startswith(address_line.lower()) ) ) ''' criteria = RequestQueryCriteria(nr_num=nr_num, filters=filters) results = Request.find_by_criteria(criteria) if not results: results = [] except InvalidInputError as err: return handle_exception(err, err.message, 400) except Exception as err: return handle_exception(err, 'Error retrieving the NR from the db.', 500) if nr_num and len(results) == 1: nr_model = results[0] if nr_model.requestTypeCd and (not nr_model.entity_type_cd or not nr_model.request_action_cd): # If requestTypeCd is set, but a request_entity (entity_type_cd) and a request_action (request_action_cd) # are not, use get_mapped_entity_and_action_code to map the values from the requestTypeCd entity_type, request_action = get_mapped_entity_and_action_code( nr_model.requestTypeCd) nr_model.entity_type_cd = entity_type nr_model.request_action_cd = request_action response_data = nr_model.json() # Add the list of valid Name Request actions for the given state to the response response_data['actions'] = get_nr_state_actions( results[0].stateCd, results[0]) return jsonify(response_data), 200 elif len(results) > 0: # We won't add the list of valid Name Request actions for the given state to the response if we're sending back a list # If the user / client accessing this data needs the Name Request actions, GET the individual record using NameRequest.get # This method, NameRequests.get is for Existing NR Search return jsonify(list(map(lambda result: result.json(), results))), 200 # We won't add the list of valid Name Request actions for the given state to the response if we're sending back a list # If the user / client accessing this data needs the Name Request actions, GET the individual record using NameRequest.get # This method, NameRequests.get is for Existing NR Search return jsonify(results), 200
def get(self): try: if not full_access_to_name_request(request): return {"message": "You do not have access to this NameRequest."}, 403 filters = [] nr_num_query_str = request.headers['Bcreg-Nr'] or request.headers['Bcreg-Nrl'] email_address_query_str = request.headers['Bcreg-User-Email'] phone_number_query_str = request.headers['Bcreg-User-Phone'] if not nr_num_query_str: raise InvalidInputError(message='An nrNum must be provided') else: if not email_address_query_str and not phone_number_query_str: raise InvalidInputError(message='Either an emailAddress or phoneNumber must be provided') # Continue nr_num = parse_nr_num(nr_num_query_str) email_address = email_address_query_str phone_number = phone_number_query_str # Filter on addresses # address_line = get_query_param_str('addrLine1') if nr_num: filters.append(func.lower(Request.nrNum) == nr_num.lower()) if phone_number: strip_phone_number_chars_regex = r"[^0-9]" filters.append( Request.applicants.any( func.regexp_replace(Applicant.phoneNumber, strip_phone_number_chars_regex, '', 'g').contains(re.sub(strip_phone_number_chars_regex, '', phone_number)) ) ) if email_address: filters.append( Request.applicants.any( func.lower(Applicant.emailAddress).startswith(email_address.lower()) ) ) ''' Filter on addresses if address_line: filters.append( Request.applicants.any( func.lower(Applicant.addrLine1).startswith(address_line.lower()) ) ) ''' criteria = RequestQueryCriteria( nr_num=nr_num, filters=filters ) results = Request.find_by_criteria(criteria) if not results: results = [] except InvalidInputError as err: return handle_exception(err, err.message, 400) except Exception as err: return handle_exception(err, 'Error retrieving the NR from the db.', 500) if nr_num and len(results) == 1: nr_model = results[0] if nr_model.requestTypeCd and (not nr_model.entity_type_cd or not nr_model.request_action_cd): # If requestTypeCd is set, but a request_entity (entity_type_cd) and a request_action (request_action_cd) # are not, use get_mapped_entity_and_action_code to map the values from the requestTypeCd entity_type, request_action = get_mapped_entity_and_action_code(nr_model.requestTypeCd) nr_model.entity_type_cd = entity_type nr_model.request_action_cd = request_action response_data = nr_model.json() # If draft, get the wait time and oldest queued request if nr_model.stateCd == 'DRAFT': service = WaitTimeStatsService() wait_time_response = service.get_waiting_time_dict() response_data.update(wait_time_response) # Add the list of valid Name Request actions for the given state to the response response_data['actions'] = get_nr_state_actions(results[0].stateCd, results[0]) return jsonify(response_data), 200 elif len(results) > 0: # We won't add the list of valid Name Request actions for the given state to the response if we're sending back a list # If the user / client accessing this data needs the Name Request actions, GET the individual record using NameRequest.get # This method, NameRequests.get is for Existing NR Search return jsonify(list(map(lambda result: result.json(), results))), 200 # We won't add the list of valid Name Request actions for the given state to the response if we're sending back a list # If the user / client accessing this data needs the Name Request actions, GET the individual record using NameRequest.get # This method, NameRequests.get is for Existing NR Search return jsonify(results), 200
def parse_nr_num(nr_num_str): nr_num = normalize_nr_num(nr_num_str) if nr_num_str else None if nr_num_str and not nr_num: raise InvalidInputError(message='Invalid NR number format provided') return nr_num