Esempio n. 1
0
 def _get_start_stop_datetime(self, form_config):
     """
     Returns a start datetime for the Visit and the Encounter, and a
     stop_datetime for the Visit
     """
     if form_config.openmrs_start_datetime:
         cc_start_datetime_str = form_config.openmrs_start_datetime._get_commcare_value(self.info)
         if cc_start_datetime_str is None:
             raise ConfigurationError(
                 'A form config for form XMLNS "{}" uses "openmrs_start_datetime" to get the start of '
                 'the visit but no value was found in the form.'.format(form_config.xmlns)
             )
         try:
             cc_start_datetime = string_to_utc_datetime(cc_start_datetime_str)
         except ValueError:
             raise ConfigurationError(
                 'A form config for form XMLNS "{}" uses "openmrs_start_datetime" to get the start of '
                 'the visit but an invalid value was found in the form.'.format(form_config.xmlns)
             )
         cc_stop_datetime = cc_start_datetime + timedelta(days=1) - timedelta(seconds=1)
         # We need to use openmrs_start_datetime.serialize()
         # for both values because they could be either
         # OpenMRS datetimes or OpenMRS dates, and their data
         # types must match.
         start_datetime = form_config.openmrs_start_datetime.serialize(cc_start_datetime)
         stop_datetime = form_config.openmrs_start_datetime.serialize(cc_stop_datetime)
     else:
         cc_start_datetime = string_to_utc_datetime(self.form_json['form']['meta']['timeEnd'])
         cc_stop_datetime = cc_start_datetime + timedelta(days=1) - timedelta(seconds=1)
         start_datetime = to_omrs_datetime(cc_start_datetime)
         stop_datetime = to_omrs_datetime(cc_stop_datetime)
     return start_datetime, stop_datetime
Esempio n. 2
0
def validate_parent_ref(parent_ref, parent_resource_type):
    """
    Validates that ``parent_ref`` is a relative reference with an
    expected resource type. e.g. "Patient/12345"
    """
    try:
        resource_type_name, resource_id = parent_ref.split('/')
    except (AttributeError, ValueError):
        raise ConfigurationError(f'Unexpected reference format {parent_ref!r}')
    if resource_type_name != parent_resource_type.name:
        raise ConfigurationError(
            'Resource type does not match expected parent resource type')
Esempio n. 3
0
 def save(self, *args, **kwargs):
     if (self.case_property and
             self.case_property.case_type != self.resource_type.case_type):
         raise ConfigurationError(
             "Invalid FHIRResourceProperty: case_property case type "
             f"'{self.case_property.case_type}' does not match "
             f"resource_type case type '{self.resource_type.case_type}'.")
     if ((self.case_property or self.jsonpath or self.value_map)
             and self.value_source_config):
         raise ConfigurationError(
             "Invalid FHIRResourceProperty: Unable to set "
             "'value_source_config' when 'case_property', 'jsonpath' or "
             "'value_map' are set.")
     super().save(*args, **kwargs)
Esempio n. 4
0
def get_case_properties(patient, importer):
    """
    Returns case name and dictionary of case properties to update

    Raises ConfigurationError if a value cannot be deserialized using
    the data types given in a column mapping.
    """
    name_columns = importer.name_columns.split(' ')
    case_name = ' '.join([patient[column] for column in name_columns])
    errors = []
    fields_to_update = {}
    tz = importer.get_timezone()
    for mapping in importer.column_map:
        value = patient[mapping.column]
        try:
            fields_to_update[mapping.property] = deserialize(
                mapping, value, tz)
        except (TypeError, ValueError) as err:
            errors.append(
                f'Unable to deserialize value {repr(value)} '
                f'in column "{mapping.column}" for case property '
                f'"{mapping.property}". OpenMRS data type is given as '
                f'"{mapping.data_type}". CommCare data type is given as '
                f'"{mapping.commcare_data_type}": {err}')
    if errors:
        raise ConfigurationError(f'Errors importing from {importer}:\n' +
                                 '\n'.join(errors))
    return case_name, fields_to_update
Esempio n. 5
0
def get_case_block_for_indexed_case(mapping, external_data, parent_case_id,
                                    parent_case_type, default_owner_id):
    relationship = mapping.indexed_case_mapping.relationship
    case_block_kwargs = {
        "index": {
            mapping.indexed_case_mapping.identifier:
            IndexAttrs(
                parent_case_type,
                parent_case_id,
                relationship,
            )
        },
        "update": {}
    }
    for value_source in mapping.indexed_case_mapping.case_properties:
        value = value_source.get_import_value(external_data)
        if value_source.case_property in CASE_BLOCK_ARGS:
            case_block_kwargs[value_source.case_property] = value
        else:
            case_block_kwargs["update"][value_source.case_property] = value

    case_id = uuid.uuid4().hex
    case_type = mapping.indexed_case_mapping.case_type
    case_block_kwargs.setdefault("owner_id", default_owner_id)
    if not case_block_kwargs["owner_id"]:
        raise ConfigurationError(
            _(f'Unable to determine mobile worker to own new "{case_type}" '
              f'{relationship} case or parent case "{parent_case_id}"'))
    case_block = CaseBlock(create=True,
                           case_id=case_id,
                           case_type=case_type,
                           **case_block_kwargs)
    return case_block
Esempio n. 6
0
def import_patients_of_owner(requests,
                             importer,
                             domain_name,
                             owner_id,
                             location=None):
    try:
        openmrs_patients = get_openmrs_patients(requests, importer, location)
    except RequestException as err:
        requests.notify_exception(
            f'Unable to import patients for project space "{domain_name}" '
            f'using {importer}: Error calling API: {err}')
        return
    except (KeyError, IndexError, TypeError, ValueError) as err:
        requests.notify_exception(
            f'Unable to import patients for project space "{domain_name}" '
            f'using {importer}: Unexpected response format: {err}')
        return
    case_blocks = []
    for i, patient in enumerate(openmrs_patients):
        try:
            patient_id = str(patient[importer.external_id_column])
        except KeyError:
            raise ConfigurationError(
                f'Error importing patients for project space "{importer.domain}" '
                f'from OpenMRS Importer "{importer}": External ID column '
                f'"{importer.external_id_column}" not found in patient data.')
        case, error = importer_util.lookup_case(EXTERNAL_ID, patient_id,
                                                domain_name,
                                                importer.case_type)
        if error is None:
            case_block = get_updatepatient_caseblock(case, patient, importer)
            case_blocks.append(RowAndCase(i, case_block))
        elif error == LookupErrors.NotFound:
            case_block = get_addpatient_caseblock(patient, importer, owner_id)
            case_blocks.append(RowAndCase(i, case_block))
        elif error == LookupErrors.MultipleResults:
            raise ConfigurationError(
                f'Error importing patients for project space "{importer.domain}" '
                f'from OpenMRS Importer "{importer}": {importer.case_type}'
                f'.{EXTERNAL_ID} "{patient_id}" is not unique.')

    submit_case_blocks(
        [cb.case.as_text() for cb in case_blocks],
        domain_name,
        device_id=f'{OPENMRS_IMPORTER_DEVICE_ID_PREFIX}{importer.get_id}',
        xmlns=XMLNS_OPENMRS,
    )
Esempio n. 7
0
def update_patient(repeater, patient_uuid):
    """
    Fetch patient from OpenMRS, submit case update for all mapped case
    properties.

    .. NOTE:: OpenMRS UUID must be saved to "external_id" case property

    """
    if len(repeater.white_listed_case_types) != 1:
        raise ConfigurationError(
            _(f'{repeater.domain}: {repeater}: Error in settings: Unable to update '
              f'patients from OpenMRS unless only one case type is specified.')
        )
    case_type = repeater.white_listed_case_types[0]
    try:
        patient = get_patient_by_uuid(repeater.requests, patient_uuid)
    except (RequestException, ValueError) as err:
        raise OpenmrsException(
            _(f'{repeater.domain}: {repeater}: Error fetching Patient '
              f'{patient_uuid!r}: {err}')) from err

    case, error = importer_util.lookup_case(
        EXTERNAL_ID,
        patient_uuid,
        repeater.domain,
        case_type=case_type,
    )
    if error == LookupErrors.NotFound:
        if not repeater.openmrs_config.case_config.import_creates_cases:
            # We can't create cases via the Atom feed, just update them.
            # Nothing to do here.
            return
        default_owner: Optional[CommCareUser] = repeater.first_user
        case_block = get_addpatient_caseblock(case_type, default_owner,
                                              patient, repeater)
    elif error == LookupErrors.MultipleResults:
        # Multiple cases have been matched to the same patient.
        # Could be caused by:
        # * The cases were given the same identifier value. It could
        #   be user error, or case config assumed identifier was
        #   unique but it wasn't.
        # * PatientFinder matched badly.
        # * Race condition where a patient was previously added to
        #   both CommCare and OpenMRS.
        raise DuplicateCaseMatch(
            _(f'{repeater.domain}: {repeater}: More than one case found '
              f'matching unique OpenMRS UUID. case external_id: "{patient_uuid}"'
              ))
    else:
        case_block = get_updatepatient_caseblock(case, patient, repeater)

    if case_block:
        submit_case_blocks(
            [case_block.as_text()],
            repeater.domain,
            xmlns=XMLNS_OPENMRS,
            device_id=OPENMRS_ATOM_FEED_DEVICE_ID + repeater.get_id,
        )
Esempio n. 8
0
 def get_owner(self):
     if not self.owner_id:
         raise ConfigurationError('Owner ID missing')
     try:
         if self.owner_type == OWNER_TYPE_GROUP:
             return Group.get(self.owner_id)
         elif self.owner_type == OWNER_TYPE_LOCATION:
             return SQLLocation.objects.get(location_id=self.owner_id)
         elif self.owner_type == OWNER_TYPE_USER:
             user = CouchUser.get_by_user_id(self.owner_id)
             if user:
                 return user
             else:
                 raise ResourceNotFound()
         else:
             raise ConfigurationError(f'Unknown owner type {self.owner_type!r}')
     except (ResourceNotFound, SQLLocation.DoesNotExist):
         raise ConfigurationError(f'{self.owner_type.capitalize()} '
                                  f'{self.owner_id!r} does not exist')
Esempio n. 9
0
 def validate_resource(self, fhir_resource):
     schema = self.get_json_schema()
     resolver = RefResolver(base_uri=f'file://{self._schema_file}',
                            referrer=schema)
     try:
         validate(fhir_resource, schema, resolver=resolver)
     except JSONValidationError as err:
         raise ConfigurationError(
             f'Validation failed for resource {fhir_resource!r}: {err}'
         ) from err
Esempio n. 10
0
    def get_auth(self):
        if not self.last_token:
            raise ConfigurationError(
                _('OAuth1 authentication workflow has not been followed for '
                  f'Connection "{self.connection_settings}"'))

        resource_owner_key = self.last_token['oauth_token']
        resource_owner_secret = self.last_token['oauth_token_secret']
        return OAuth1(self.client_id,
                      client_secret=self.client_secret,
                      resource_owner_key=resource_owner_key,
                      resource_owner_secret=resource_owner_secret)
Esempio n. 11
0
 def get_external_value(self, external_data):
     if self.jsonpath is not None:
         try:
             jsonpath = parse_jsonpath(self.jsonpath)
         except Exception as err:
             raise JsonpathError from err
         matches = jsonpath.find(external_data)
         values = [m.value for m in matches]
         if not values:
             return None
         elif len(values) == 1:
             return values[0]
         else:
             return values
     raise ConfigurationError(f"{self} is not configured to parse external data")
Esempio n. 12
0
    def get_value_source(self) -> ValueSource:
        """
        Returns a ValueSource for building FHIR resources.
        """
        if self.value_source_config:
            return as_value_source(self.value_source_config)

        if not (self.case_property and self.jsonpath):
            raise ConfigurationError(
                'Unable to set FHIR resource property value without case '
                'property and JSONPath.')
        value_source_config = {
            'case_property': self.case_property.name,
            'jsonpath': self.jsonpath,
        }
        if self.value_map:
            value_source_config['value_map'] = self.value_map
        return as_value_source(value_source_config)
Esempio n. 13
0
    def set_external_value(self, external_data: dict, info: CaseTriggerInfo):
        """
        Builds ``external_data`` by reference.

        Currently implemented for dicts using JSONPath but could be
        implemented for other types as long as they are mutable.
        """
        if self.jsonpath is None:
            raise ConfigurationError(f"{self} is not configured to navigate "
                                     "external data")
        value = self.get_value(info)
        if value is None:
            # Don't set external value if CommCare has no value
            return
        try:
            jsonpath = parse_jsonpath(self.jsonpath)
        except Exception as err:
            raise JsonpathError from err
        jsonpath.update_or_create(external_data, value)
Esempio n. 14
0
def get_addpatient_caseblock(
    case_type: str,
    default_owner: Optional[CommCareUser],
    patient: dict,
    repeater: OpenmrsRepeater,
) -> CaseBlock:

    case_block_kwargs = get_case_block_kwargs_from_patient(patient, repeater)
    if default_owner:
        case_block_kwargs.setdefault("owner_id", default_owner.user_id)
    if not case_block_kwargs.get("owner_id"):
        raise ConfigurationError(
            _(f'No users found at location "{repeater.location_id}" to own '
              'patients added from OpenMRS Atom feed.'))
    case_id = uuid.uuid4().hex
    return CaseBlock.deprecated_init(create=True,
                                     case_id=case_id,
                                     case_type=case_type,
                                     external_id=patient['uuid'],
                                     **case_block_kwargs)
Esempio n. 15
0
    def get_json_schema(self) -> dict:
        """
        Returns the JSON schema of this resource type.

        >>> resource_type = FHIRResourceType(
        ...     case_type=CaseType(name='mother'),
        ...     name='Patient',
        ... )
        >>> schema = resource_type.get_json_schema()
        >>> schema['$ref']
        '#/definitions/Patient'

        """
        try:
            with open(self._schema_file, 'r') as file:
                return json.load(file)
        except FileNotFoundError:
            raise ConfigurationError(
                f'Unknown resource type {self.name!r} for FHIR version '
                f'{self.fhir_version}')
Esempio n. 16
0
def create_parent_indices(
    importer: FHIRImportConfig,
    child_cases: List[ParentInfo],
):
    """
    Creates parent-child relationships on imported cases.

    If ``ResourceTypeRelationship.related_resource_is_parent`` is
    ``True`` then this function will add an ``index`` on the child case
    to its parent case.
    """
    if not child_cases:
        return

    case_blocks = []
    for child_case_id, parent_ref, parent_resource_type in child_cases:
        resource_type_name, external_id = parent_ref.split('/')
        parent_case = get_case_by_external_id(
            parent_resource_type.domain,
            external_id,
            parent_resource_type.case_type.name,
        )
        if not parent_case:
            raise ConfigurationError(
                f'Case not found with external_id {external_id!r}')

        case_blocks.append(
            CaseBlock(
                child_case_id,
                index={'parent': (parent_case.type, parent_case.case_id)},
            ))
    submit_case_blocks(
        [cb.as_text() for cb in case_blocks],
        importer.domain,
        xmlns=XMLNS_FHIR,
        device_id=f'FHIRImportConfig-{importer.pk}',
    )