def post_with_raise(self, uri, *args, **kwargs): response = self.post(uri, *args, **kwargs) try: response.raise_for_status() except HTTPError as err: err_request, err_response = parse_request_exception(err) logger.error('Request: ', err_request) logger.error('Response: ', err_response) raise return response
def search_patients(requests, search_string): try: # Finding the patient is the first request sent to the server. If there is a mistake in the server details, # or the server is offline, this is where we will discover it. response = requests.get('/ws/rest/v1/patient', { 'q': search_string, 'v': 'full' }) response.raise_for_status() except HTTPError as err: # raise_for_status() raised an HTTPError. err_request, err_response = parse_request_exception(err) logger.error('Error encountered searching patients') logger.error('Request: ', err_request) logger.error('Response: ', err_response) http_error_msg = ( 'An error was when encountered searching patients: {}. Check in Data Forwarding that the server URL ' 'includes the path to the API, and that the password is correct'. format(err) ) # This message will be shown in the Repeat Records report, and needs to be useful to an administrator raise HTTPError(http_error_msg, response=err.response) except Exception as err: # get() failed -- probably a connection failure. logger.error('Error encountered searching patients: ', str(err)) raise err.__class__( 'Unable to send request to OpenMRS server: {}. Please check the server address in Data Forwarding and ' 'that the server is online.'.format(err)) return response.json()
def import_patients_with_importer(importer_json): importer = OpenmrsImporter.wrap(importer_json) password = b64_aes_decrypt(importer.password) requests = Requests(importer.domain, importer.server_url, importer.username, password) if importer.location_type_name: try: location_type = LocationType.objects.get( domain=importer.domain, name=importer.location_type_name) except LocationType.DoesNotExist: logger.error( f'No organization level named "{importer.location_type_name}" ' f'found in project space "{importer.domain}".') return if importer.location_id: location = SQLLocation.objects.filter(domain=importer.domain).get( importer.location_id) locations = location.get_descendants.filter( location_type=location_type) else: locations = SQLLocation.objects.filter(domain=importer.domain, location_type=location_type) for location in locations: # Assign cases to the first user in the location, not to the location itself owner = get_one_commcare_user_at_location(importer.domain, location.location_id) if not owner: logger.error( f'Project space "{importer.domain}" at location ' f'"{location.name}" has no user to own cases imported ' f'from OpenMRS Importer "{importer}"') continue import_patients_of_owner(requests, importer, importer.domain, owner, location) elif importer.owner_id: try: owner = CommCareUser.get(importer.owner_id) except ResourceNotFound: logger.error( f'Project space "{importer.domain}" has no user to own cases ' f'imported from OpenMRS Importer "{importer}"') return import_patients_of_owner(requests, importer, importer.domain, owner) else: logger.error( f'Error importing patients for project space "{importer.domain}" from ' f'OpenMRS Importer "{importer}": Unable to determine the owner of ' 'imported cases without either owner_id or location_type_name')
def import_patients_to_domain(domain_name, force=False): """ Iterates OpenmrsImporters of a domain, and imports patients Who owns the imported cases? If `importer.owner_id` is set, then the server will be queried once. All patients, regardless of their location, will be assigned to the mobile worker whose ID is `importer.owner_id`. If `importer.location_type_name` is set, then check whether the OpenmrsImporter's location is set with `importer.location_id`. If `importer.location_id` is given, then the server will be queried for each location among its descendants whose type is `importer.location_type_name`. The request's query parameters will include that descendant location's OpenMRS UUID. Imported cases will be owned by the first mobile worker in that location. If `importer.location_id` is given, then the server will be queried for each location in the project space whose type is `importer.location_type_name`. As when `importer.location_id` is specified, the request's query parameters will include that location's OpenMRS UUID, and imported cases will be owned by the first mobile worker in that location. ..NOTE:: As you can see from the description above, if `importer.owner_id` is set then `importer.location_id` is not used. :param domain_name: The name of the domain :param force: Import regardless of the configured import frequency / today's date """ today = datetime.today() for importer in get_openmrs_importers_by_domain(domain_name): if not force and importer.import_frequency == IMPORT_FREQUENCY_WEEKLY and today.weekday( ) != 1: continue # Import on Tuesdays if not force and importer.import_frequency == IMPORT_FREQUENCY_MONTHLY and today.day != 1: continue # Import on the first of the month # TODO: ^^^ Make those configurable password = b64_aes_decrypt(importer.password) requests = Requests(domain_name, importer.server_url, importer.username, password) if importer.location_type_name: try: location_type = LocationType.objects.get( domain=domain_name, name=importer.location_type_name) except LocationType.DoesNotExist: logger.error( 'No organization level named "{location_type}" found in project space "{domain}".' .format(location_type=importer.location_type_name, domain=domain_name)) continue if importer.location_id: location = SQLLocation.objects.filter(domain=domain_name).get( importer.location_id) locations = location.get_descendants.filter( location_type=location_type) else: locations = SQLLocation.objects.filter( domain=domain_name, location_type=location_type) for location in locations: # Assign cases to the first user in the location, not to the location itself owner = get_one_commcare_user_at_location( domain_name, location.location_id) if not owner: logger.error( 'Project space "{domain}" at location "{location}" has no user to own cases imported from ' 'OpenMRS Importer "{importer}"'.format( domain=domain_name, location=location.name, importer=importer)) continue import_patients_of_owner(requests, importer, domain_name, owner, location) elif importer.owner_id: try: owner = CommCareUser.get(importer.owner_id) except ResourceNotFound: logger.error( 'Project space "{domain}" has no user to own cases imported from OpenMRS Importer ' '"{importer}"'.format(domain=domain_name, importer=importer)) continue import_patients_of_owner(requests, importer, domain_name, owner) else: logger.error( 'Error importing patients for project space "{domain}" from OpenMRS Importer "{importer}": Unable ' 'to determine the owner of imported cases without either owner_id or location_type_name' .format(domain=domain_name, importer=importer)) continue
def send_openmrs_data(requests, domain, form_json, openmrs_config, case_trigger_infos, form_question_values): """ Updates an OpenMRS patient and (optionally) creates visits. This involves several requests to the `OpenMRS REST Web Services`_. If any of those requests fail, we want to roll back previous changes to avoid inconsistencies in OpenMRS. To do this we define a workflow of tasks we want to do. Each workflow task has a rollback task. If a task fails, all previous tasks are rolled back in reverse order. :return: A response-like object that can be used by Repeater.handle_response .. _OpenMRS REST Web Services: https://wiki.openmrs.org/display/docs/REST+Web+Services+API+For+Clients """ errors = [] for info in case_trigger_infos: assert isinstance(info, CaseTriggerInfo) patient = get_patient(requests, domain, info, openmrs_config) if patient is None: errors.append('Warning: CommCare case "{}" was not found in OpenMRS'.format(info.case_id)) continue # case_trigger_infos are info about all of the cases # created/updated by the form. Execute a separate workflow to # update each patient. workflow = [ # Update name first. If the current name in OpenMRS fails # validation, other API requests will be rejected. UpdatePersonNameTask(requests, info, openmrs_config, patient['person']), # Update identifiers second. If a current identifier fails # validation, other API requests will be rejected. SyncPatientIdentifiersTask(requests, info, openmrs_config, patient), # Now we should be able to update the rest. UpdatePersonPropertiesTask(requests, info, openmrs_config, patient['person']), SyncPersonAttributesTask( requests, info, openmrs_config, patient['person']['uuid'], patient['person']['attributes'] ), ] if patient['person']['preferredAddress']: workflow.append( UpdatePersonAddressTask(requests, info, openmrs_config, patient['person']) ) else: workflow.append( CreatePersonAddressTask(requests, info, openmrs_config, patient['person']) ) workflow.append( CreateVisitsEncountersObsTask( requests, domain, info, form_json, form_question_values, openmrs_config, patient['person']['uuid'] ), ) errors.extend( execute_workflow(workflow) ) if errors: logger.error('Errors encountered sending OpenMRS data: %s', errors) # If the form included multiple patients, some workflows may # have succeeded, but don't say everything was OK if any # workflows failed. (Of course most forms will only involve one # case, so one workflow.) return OpenmrsResponse(400, 'Bad Request', pformat_json([str(e) for e in errors])) else: return OpenmrsResponse(200, 'OK', '')
def send_openmrs_data(requests, domain, form_json, openmrs_config, case_trigger_infos, form_question_values): """ Updates an OpenMRS patient and (optionally) creates visits. This involves several requests to the `OpenMRS REST Web Services`_. If any of those requests fail, we want to roll back previous changes to avoid inconsistencies in OpenMRS. To do this we define a workflow of tasks we want to do. Each workflow task has a rollback task. If a task fails, all previous tasks are rolled back in reverse order. :return: A response-like object that can be used by Repeater.handle_response .. _OpenMRS REST Web Services: https://wiki.openmrs.org/display/docs/REST+Web+Services+API+For+Clients """ errors = [] for info in case_trigger_infos: assert isinstance(info, CaseTriggerInfo) patient = get_patient(requests, domain, info, openmrs_config) if patient is None: errors.append('Warning: CommCare case "{}" was not found in OpenMRS'.format(info.case_id)) continue # case_trigger_infos are info about all of the cases # created/updated by the form. Execute a separate workflow to # update each patient. workflow = [ # Update name first. If the current name in OpenMRS fails # validation, other API requests will be rejected. UpdatePersonNameTask(requests, info, openmrs_config, patient['person']), # Update identifiers second. If a current identifier fails # validation, other API requests will be rejected. SyncPatientIdentifiersTask(requests, info, openmrs_config, patient), # Now we should be able to update the rest. UpdatePersonPropertiesTask(requests, info, openmrs_config, patient['person']), SyncPersonAttributesTask( requests, info, openmrs_config, patient['person']['uuid'], patient['person']['attributes'] ), ] if patient['person']['preferredAddress']: workflow.append( UpdatePersonAddressTask(requests, info, openmrs_config, patient['person']) ) else: workflow.append( CreatePersonAddressTask(requests, info, openmrs_config, patient['person']) ) workflow.append( CreateVisitsEncountersObsTask( requests, domain, info, form_json, form_question_values, openmrs_config, patient['person']['uuid'] ), ) errors.extend( execute_workflow(workflow) ) if errors: logger.error('Errors encountered sending OpenMRS data: %s', errors) # If the form included multiple patients, some workflows may # have succeeded, but don't say everything was OK if any # workflows failed. (Of course most forms will only involve one # case, so one workflow.) return OpenmrsResponse(400, 'Bad Request', pformat_json([str(e) for e in errors])) else: return OpenmrsResponse(200, 'OK', '')
def import_patients_to_domain(domain_name, force=False): """ Iterates OpenmrsImporters of a domain, and imports patients Who owns the imported cases? If `importer.owner_id` is set, then the server will be queried once. All patients, regardless of their location, will be assigned to the mobile worker whose ID is `importer.owner_id`. If `importer.location_type_name` is set, then check whether the OpenmrsImporter's location is set with `importer.location_id`. If `importer.location_id` is given, then the server will be queried for each location among its descendants whose type is `importer.location_type_name`. The request's query parameters will include that descendant location's OpenMRS UUID. Imported cases will be owned by the first mobile worker in that location. If `importer.location_id` is given, then the server will be queried for each location in the project space whose type is `importer.location_type_name`. As when `importer.location_id` is specified, the request's query parameters will include that location's OpenMRS UUID, and imported cases will be owned by the first mobile worker in that location. ..NOTE:: As you can see from the description above, if `importer.owner_id` is set then `importer.location_id` is not used. :param domain_name: The name of the domain :param force: Import regardless of the configured import frequency / today's date """ today = datetime.today() for importer in get_openmrs_importers_by_domain(domain_name): if not force and importer.import_frequency == IMPORT_FREQUENCY_WEEKLY and today.weekday() != 1: continue # Import on Tuesdays if not force and importer.import_frequency == IMPORT_FREQUENCY_MONTHLY and today.day != 1: continue # Import on the first of the month # TODO: ^^^ Make those configurable password = b64_aes_decrypt(importer.password) requests = Requests(domain_name, importer.server_url, importer.username, password) if importer.location_type_name: try: location_type = LocationType.objects.get(domain=domain_name, name=importer.location_type_name) except LocationType.DoesNotExist: logger.error( 'No organization level named "{location_type}" found in project space "{domain}".'.format( location_type=importer.location_type_name, domain=domain_name) ) continue if importer.location_id: location = SQLLocation.objects.filter(domain=domain_name).get(importer.location_id) locations = location.get_descendants.filter(location_type=location_type) else: locations = SQLLocation.objects.filter(domain=domain_name, location_type=location_type) for location in locations: # Assign cases to the first user in the location, not to the location itself owner = get_one_commcare_user_at_location(domain_name, location.location_id) if not owner: logger.error( 'Project space "{domain}" at location "{location}" has no user to own cases imported from ' 'OpenMRS Importer "{importer}"'.format( domain=domain_name, location=location.name, importer=importer) ) continue import_patients_of_owner(requests, importer, domain_name, owner, location) elif importer.owner_id: try: owner = CommCareUser.get(importer.owner_id) except ResourceNotFound: logger.error( 'Project space "{domain}" has no user to own cases imported from OpenMRS Importer ' '"{importer}"'.format( domain=domain_name, importer=importer) ) continue import_patients_of_owner(requests, importer, domain_name, owner) else: logger.error( 'Error importing patients for project space "{domain}" from OpenMRS Importer "{importer}": Unable ' 'to determine the owner of imported cases without either owner_id or location_type_name'.format( domain=domain_name, importer=importer) ) continue