Beispiel #1
0
class ILSGatewayConfig(Document):
    enabled = BooleanProperty(default=False)
    domain = StringProperty()
    url = StringProperty(default="http://ilsgateway.com/api/v0_1")
    username = StringProperty()
    password = StringProperty()
    steady_sync = BooleanProperty(default=False)
    all_stock_data = BooleanProperty(default=False)

    @classmethod
    def for_domain(cls, name):
        try:
            mapping = DocDomainMapping.objects.get(domain_name=name,
                                                   doc_type='ILSGatewayConfig')
            return cls.get(docid=mapping.doc_id)
        except DocDomainMapping.DoesNotExist:
            return None

    @classmethod
    def get_all_configs(cls):
        mappings = DocDomainMapping.objects.filter(doc_type='ILSGatewayConfig')
        configs = [cls.get(docid=mapping.doc_id) for mapping in mappings]
        return configs

    @classmethod
    def get_all_enabled_domains(cls):
        configs = cls.get_all_configs()
        return [
            c.domain for c in filter(lambda config: config.enabled, configs)
        ]

    @classmethod
    def get_all_steady_sync_configs(cls):
        return [
            config for config in cls.get_all_configs() if config.steady_sync
        ]

    @property
    def is_configured(self):
        return True if self.enabled and self.url and self.password and self.username else False

    def save(self, **params):
        super(ILSGatewayConfig, self).save(**params)
        try:
            DocDomainMapping.objects.get(doc_id=self._id,
                                         domain_name=self.domain,
                                         doc_type="ILSGatewayConfig")
        except DocDomainMapping.DoesNotExist:
            DocDomainMapping.objects.create(doc_id=self._id,
                                            domain_name=self.domain,
                                            doc_type='ILSGatewayConfig')
            add_to_module_map(self.domain, 'custom.ilsgateway')
Beispiel #2
0
class ReportMeta(DocumentSchema):
    # `True` if this report was initially constructed by the report builder.
    created_by_builder = BooleanProperty(default=False)
    report_builder_version = StringProperty(default="")
    # `True` if this report was ever edited in the advanced JSON UIs (after June 7, 2016)
    edited_manually = BooleanProperty(default=False)
    last_modified = DateTimeProperty()
    builder_report_type = StringProperty(
        choices=['chart', 'list', 'table', 'worker', 'map'])
    builder_source_type = StringProperty(
        choices=REPORT_BUILDER_DATA_SOURCE_TYPE_VALUES)

    # If this is a linked report, this is the ID of the report this pulls from
    master_id = StringProperty()
Beispiel #3
0
class CallCenterProperties(DocumentSchema):
    enabled = BooleanProperty(default=False)
    use_fixtures = BooleanProperty(default=True)

    case_owner_id = StringProperty()
    use_user_location_as_owner = BooleanProperty(default=False)
    user_location_ancestor_level = IntegerProperty(default=0)

    case_type = StringProperty()

    def fixtures_are_active(self):
        return self.enabled and self.use_fixtures

    def config_is_valid(self):
        return (self.use_user_location_as_owner or self.case_owner_id) and self.case_type
Beispiel #4
0
class LicenseAgreement(DocumentSchema):
    signed = BooleanProperty(default=False)
    type = StringProperty()
    date = DateTimeProperty()
    user_id = StringProperty()
    user_ip = StringProperty()
    version = StringProperty()
Beispiel #5
0
class CustomDataField(JsonObject):
    slug = StringProperty()
    is_required = BooleanProperty()
    label = StringProperty()
    choices = StringListProperty()
    regex = StringProperty()
    regex_msg = StringProperty()
Beispiel #6
0
class CDotWeeklySchedule(OldDocument):
    """Weekly schedule where each day has a username"""
    schedule_id = StringProperty(default=make_uuid)

    sunday = StringProperty()
    monday = StringProperty()
    tuesday = StringProperty()
    wednesday = StringProperty()
    thursday = StringProperty()
    friday = StringProperty()
    saturday = StringProperty()

    comment = StringProperty()

    deprecated = BooleanProperty(default=False)

    started = OldDateTimeProperty(default=datetime.utcnow, required=True)
    ended = OldDateTimeProperty()

    created_by = StringProperty()  # user id
    edited_by = StringProperty()  # user id

    @property
    def is_current(self):
        now = datetime.utcnow()
        return self.started <= now and (self.ended is None or self.ended > now)

    class Meta:
        app_label = 'pact'
Beispiel #7
0
class Dhis2Connection(Document):
    domain = StringProperty()
    server_url = StringProperty()
    username = StringProperty()
    password = StringProperty()
    skip_cert_verify = BooleanProperty(default=False)

    @classmethod
    def wrap(cls, data):
        data.pop('log_level', None)
        return super(Dhis2Connection, cls).wrap(data)

    def save(self, *args, **kwargs):
        # Save to SQL
        model, created = SQLDhis2Connection.objects.update_or_create(
            domain=self.domain,
            defaults={
                'server_url': self.server_url,
                'username': self.username,
                'password': self.password,
                'skip_cert_verify': self.skip_cert_verify,
            }
        )

        # Save to couch
        super().save(*args, **kwargs)
Beispiel #8
0
class Deployment(DocumentSchema, UpdatableSchema):
    city = StringProperty()
    countries = StringListProperty()
    region = StringProperty(
    )  # e.g. US, LAC, SA, Sub-saharn Africa, East Africa, West Africa, Southeast Asia)
    description = StringProperty()
    public = BooleanProperty(default=False)
Beispiel #9
0
class ApplicationAccess(QuickCachedDocumentMixin, Document):
    """
    This is used to control which users/groups can access which applications on cloudcare.
    """
    domain = StringProperty()
    app_groups = SchemaListProperty(AppGroup, default=[])
    restrict = BooleanProperty(default=False)
Beispiel #10
0
class StudySettings(DocumentSchema):
    is_ws_enabled = BooleanProperty()
    url = StringProperty()
    username = StringProperty()
    password = StringProperty()
    protocol_id = StringProperty()
    metadata = StringProperty()  # Required when web service is not enabled
Beispiel #11
0
class ExportColumn(DocumentSchema):
    """
    A column configuration, for export
    """
    index = StringProperty()
    display = StringProperty()
    # signature: transform(val, doc) -> val
    transform = SerializableFunctionProperty(default=None)
    tag = StringProperty()
    is_sensitive = BooleanProperty(default=False)
    show = BooleanProperty(default=False)

    @classmethod
    def wrap(self, data):
        if 'is_sensitive' not in data and data.get('transform', None):
            data['is_sensitive'] = True

        if 'doc_type' in data and \
           self.__name__ == ExportColumn.__name__ and \
           self.__name__ != data['doc_type']:
            if data['doc_type'] in column_types:
                return column_types[data['doc_type']].wrap(data)
            else:
                raise ResourceNotFound('Unknown column type: %s', data)
        else:
            return super(ExportColumn, self).wrap(data)

    def get_display(self):
        return u'{primary}{extra}'.format(
            primary=self.display,
            extra=" [sensitive]" if self.is_sensitive else ''
        )

    def to_config_format(self, selected=True):
        return {
            "index": self.index,
            "display": self.display,
            "transform": self.transform.dumps() if self.transform else None,
            "is_sensitive": self.is_sensitive,
            "selected": selected,
            "tag": self.tag,
            "show": self.show,
            "doc_type": self.doc_type,
            "options": [],
            "allOptions": None,
        }
Beispiel #12
0
class FormRepeater(Repeater):
    """
    Record that forms should be repeated to a new url

    """

    payload_generator_classes = (FormRepeaterXMLPayloadGenerator,
                                 FormRepeaterJsonPayloadGenerator)

    include_app_id_param = BooleanProperty(default=True)
    white_listed_form_xmlns = StringListProperty(
        default=[])  # empty value means all form xmlns are accepted
    friendly_name = _("Forward Forms")

    @memoized
    def payload_doc(self, repeat_record):
        return FormAccessors(repeat_record.domain).get_form(
            repeat_record.payload_id)

    @property
    def form_class_name(self):
        """
        FormRepeater and its subclasses use the same form for editing
        """
        return 'FormRepeater'

    def allowed_to_forward(self, payload):
        return (payload.xmlns != DEVICE_LOG_XMLNS
                and (not self.white_listed_form_xmlns
                     or payload.xmlns in self.white_listed_form_xmlns))

    def get_url(self, repeat_record):
        url = super(FormRepeater, self).get_url(repeat_record)
        if not self.include_app_id_param:
            return url
        else:
            # adapted from http://stackoverflow.com/a/2506477/10840
            url_parts = list(urlparse(url))
            query = parse_qsl(url_parts[4])
            try:
                query.append(
                    ("app_id", self.payload_doc(repeat_record).app_id))
            except (XFormNotFound, ResourceNotFound):
                return None
            url_parts[4] = urlencode(query)
            return urlunparse(url_parts)

    def get_headers(self, repeat_record):
        headers = super(FormRepeater, self).get_headers(repeat_record)
        headers.update({
            "received-on":
            self.payload_doc(repeat_record).received_on.isoformat() + "Z"
        })
        return headers

    def __str__(self):
        return "forwarding forms to: %s" % self.url
Beispiel #13
0
class DataSourceBuildInformation(DocumentSchema):
    """
    A class to encapsulate meta information about the process through which
    its DataSourceConfiguration was configured and built.
    """
    # Either the case type or the form xmlns that this data source is based on.
    source_id = StringProperty()
    # The app that the form belongs to, or the app that was used to infer the case properties.
    app_id = StringProperty()
    # The version of the app at the time of the data source's configuration.
    app_version = IntegerProperty()
    # True if the data source has been built, that is, if the corresponding SQL table has been populated.
    finished = BooleanProperty(default=False)
    # Start time of the most recent build SQL table celery task.
    initiated = DateTimeProperty()
    # same as previous attributes but used for rebuilding tables in place
    finished_in_place = BooleanProperty(default=False)
    initiated_in_place = DateTimeProperty()
Beispiel #14
0
class Dhis2Connection(Document):
    domain = StringProperty()
    server_url = StringProperty()
    username = StringProperty()
    password = StringProperty()
    skip_cert_verify = BooleanProperty(default=False)

    @classmethod
    def wrap(cls, data):
        data.pop('log_level', None)
        return super(Dhis2Connection, cls).wrap(data)
Beispiel #15
0
class WisePillDeviceEvent(Document):
    """
    One DeviceEvent is created each time a device sends data that is
    forwarded to the CommCareHQ WisePill API (/wisepill/device/).
    """
    domain = StringProperty()
    data = StringProperty()
    received_on = DateTimeProperty()
    # Document _id of the case representing the device that sent this data in
    case_id = StringProperty()
    processed = BooleanProperty()

    @property
    @memoized
    def data_as_dict(self):
        """
        Convert 'a=b,c=d' to {'a': 'b', 'c': 'd'}
        """
        result = {}
        if isinstance(self.data, str):
            items = self.data.strip().split(',')
            for item in items:
                parts = item.partition('=')
                key = parts[0].strip().upper()
                value = parts[2].strip()
                if value:
                    result[key] = value
        return result

    @property
    def serial_number(self):
        return self.data_as_dict.get('SN', None)

    @property
    def timestamp(self):
        raw = self.data_as_dict.get('T', None)
        if isinstance(raw, str) and len(raw) == 12:
            return "20%s-%s-%s %s:%s:%s" % (
                raw[4:6],
                raw[2:4],
                raw[0:2],
                raw[6:8],
                raw[8:10],
                raw[10:12],
            )
        else:
            return None

    @classmethod
    def get_all_ids(cls):
        result = cls.view('wisepill/device_event', include_docs=False)
        return [row['id'] for row in result]
Beispiel #16
0
class VerifiedNumber(Document):
    """
    There should only be one VerifiedNumber entry per (owner_doc_type, owner_id), and
    each VerifiedNumber.phone_number should be unique across all entries.
    """
    domain = StringProperty()
    owner_doc_type = StringProperty()
    owner_id = StringProperty()
    phone_number = StringProperty()
    backend_id = StringProperty(
    )  # the name of a MobileBackend (can be domain-level or system-level)
    ivr_backend_id = StringProperty()  # points to a MobileBackend
    verified = BooleanProperty()
    contact_last_modified = DateTimeProperty()
Beispiel #17
0
class SavedBasicExport(BlobMixin, Document):
    """
    A cache of an export that lives in couch.
    Doesn't do anything smart, just works off an index
    """
    configuration = SchemaProperty(ExportConfiguration)
    last_updated = DateTimeProperty()
    last_accessed = DateTimeProperty()
    is_safe = BooleanProperty(default=False)
    _blobdb_type_code = CODES.basic_export

    @property
    def size(self):
        try:
            return self.blobs[self.get_attachment_name()].content_length
        except KeyError:
            return 0

    def has_file(self):
        return self.get_attachment_name() in self.blobs

    def get_attachment_name(self):
        # obfuscate this because couch doesn't like attachments that start with underscores
        return hashlib.md5(
            six.text_type(
                self.configuration.filename).encode('utf-8')).hexdigest()

    def set_payload(self, payload):
        # According to @esoergel this code is slated for removal in the near
        # future, so I didn't think it was worth it to try to pass the domain
        # in here.
        self.put_attachment(payload,
                            self.get_attachment_name(),
                            domain=UNKNOWN_DOMAIN)

    def get_payload(self, stream=False):
        return self.fetch_attachment(self.get_attachment_name(),
                                     stream=stream,
                                     return_bytes=True)

    @classmethod
    def by_index(cls, index):
        return SavedBasicExport.view(
            "couchexport/saved_exports",
            key=json.dumps(index),
            include_docs=True,
            reduce=False,
        ).all()
Beispiel #18
0
class ApplicationAccess(QuickCachedDocumentMixin, Document):
    """
    This is used to control which users/groups can access which applications on cloudcare.
    """
    domain = StringProperty()
    app_groups = SchemaListProperty(AppGroup, default=[])
    restrict = BooleanProperty(default=False)

    @classmethod
    def get_by_domain(cls, domain):
        from corehq.apps.cloudcare.dbaccessors import get_application_access_for_domain
        self = get_application_access_for_domain(domain)
        return self or cls(domain=domain)

    def clear_caches(self):
        from corehq.apps.cloudcare.dbaccessors import get_application_access_for_domain
        get_application_access_for_domain.clear(self.domain)
        super(ApplicationAccess, self).clear_caches()

    def user_can_access_app(self, user, app):
        user_id = user['_id']
        app_id = app['_id']
        if not self.restrict or user['doc_type'] == 'WebUser':
            return True
        app_group = None
        for app_group in self.app_groups:
            if app_group.app_id in (app_id, app['copy_of'] or ()):
                break
        if app_group:
            return Group.user_in_group(user_id, app_group.group_id)
        else:
            return False

    @classmethod
    def get_template_json(cls, domain, apps):
        app_ids = dict([(app['_id'], app) for app in apps])
        self = ApplicationAccess.get_by_domain(domain)
        j = self.to_json()
        merged_access_list = []
        for a in j['app_groups']:
            app_id = a['app_id']
            if app_id in app_ids:
                merged_access_list.append(a)
                del app_ids[app_id]
        for app in app_ids.values():
            merged_access_list.append({'app_id': app['_id'], 'group_id': None})
        j['app_groups'] = merged_access_list
        return j
Beispiel #19
0
class RegistrationRequest(Document):
    tos_confirmed = BooleanProperty(default=False)
    request_time = DateTimeProperty()
    request_ip = StringProperty()
    activation_guid = StringProperty()
    confirm_time = DateTimeProperty()
    confirm_ip = StringProperty()
    domain = StringProperty()
    new_user_username = StringProperty()
    requesting_user_username = StringProperty()

    @property
    @memoized
    def project(self):
        return Domain.get_by_name(self.domain)

    @classmethod
    def get_by_guid(cls, guid):
        result = cls.view("registration/requests_by_guid",
                          key=guid,
                          reduce=False,
                          include_docs=True).first()
        return result

    @classmethod
    def get_requests_today(cls):
        today = datetime.datetime.utcnow()
        yesterday = today - datetime.timedelta(1)
        result = cls.view("registration/requests_by_time",
                          startkey=yesterday.isoformat(),
                          endkey=today.isoformat(),
                          reduce=True).all()
        if not result:
            return 0
        return result[0]['value']

    @classmethod
    def get_request_for_username(cls, username):
        result = cls.view("registration/requests_by_username",
                          key=username,
                          reduce=False,
                          include_docs=True).first()
        return result
Beispiel #20
0
class InternalProperties(DocumentSchema, UpdatableSchema):
    """
    Project properties that should only be visible/editable by superusers
    """
    sf_contract_id = StringProperty()
    sf_account_id = StringProperty()
    commcare_edition = StringProperty(
        choices=['', "plus", "community", "standard", "pro", "advanced", "enterprise"],
        default="community"
    )
    initiative = StringListProperty()
    workshop_region = StringProperty()
    project_state = StringProperty(choices=["", "POC", "transition", "at-scale"], default="")
    self_started = BooleanProperty(default=True)
    area = StringProperty()
    sub_area = StringProperty()
    using_adm = BooleanProperty()
    using_call_center = BooleanProperty()
    custom_eula = BooleanProperty()
    can_use_data = BooleanProperty(default=True)
    notes = StringProperty()
    organization_name = StringProperty()
    platform = StringListProperty()
    project_manager = StringProperty()
    phone_model = StringProperty()
    goal_time_period = IntegerProperty()
    goal_followup_rate = DecimalProperty()
    # intentionally different from and commtrack_enabled so that FMs can change
    commtrack_domain = BooleanProperty()
    performance_threshold = IntegerProperty()
    experienced_threshold = IntegerProperty()
    amplifies_workers = StringProperty(
        choices=[AMPLIFIES_YES, AMPLIFIES_NO, AMPLIFIES_NOT_SET],
        default=AMPLIFIES_NOT_SET
    )
    amplifies_project = StringProperty(
        choices=[AMPLIFIES_YES, AMPLIFIES_NO, AMPLIFIES_NOT_SET],
        default=AMPLIFIES_NOT_SET
    )
    business_unit = StringProperty(choices=BUSINESS_UNITS + [""], default="")
    data_access_threshold = IntegerProperty()
    partner_technical_competency = IntegerProperty()
    support_prioritization = IntegerProperty()
    gs_continued_involvement = StringProperty()
    technical_complexity = StringProperty()
    app_design_comments = StringProperty()
    training_materials = StringProperty()
    partner_comments = StringProperty()
    partner_contact = StringProperty()
    dimagi_contact = StringProperty()
Beispiel #21
0
class ComputedDocumentMixin(DocumentSchema):
    """
        Use this mixin for things like CommCareCase or XFormInstance documents that take advantage
        of indicator definitions.

        computed_ is namespaced and may look like the following for indicators:
        computed_: {
            mvp_indicators: {
                indicator_slug: {
                    version: 1,
                    value: "foo"
                }
            }
        }
    """
    computed_ = DictProperty()
    computed_modified_on_ = DateTimeProperty()

    # a flag for the indicator pillows so that there aren't any Document Update Conflicts
    initial_processing_complete = BooleanProperty()
Beispiel #22
0
class SavedBasicExport(BlobMixin, Document):
    """
    A cache of an export that lives in couch.
    Doesn't do anything smart, just works off an index
    """
    configuration = SchemaProperty(ExportConfiguration)
    last_updated = DateTimeProperty()
    last_accessed = DateTimeProperty()
    is_safe = BooleanProperty(default=False)

    @property
    def size(self):
        try:
            return self.blobs[self.get_attachment_name()].content_length
        except KeyError:
            return 0

    def has_file(self):
        return self.get_attachment_name() in self.blobs

    def get_attachment_name(self):
        # obfuscate this because couch doesn't like attachments that start with underscores
        return hashlib.md5(
            six.text_type(
                self.configuration.filename).encode('utf-8')).hexdigest()

    def set_payload(self, payload):
        self.put_attachment(payload, self.get_attachment_name())

    def get_payload(self, stream=False):
        return self.fetch_attachment(self.get_attachment_name(), stream=stream)

    @classmethod
    def by_index(cls, index):
        return SavedBasicExport.view(
            "couchexport/saved_exports",
            key=json.dumps(index),
            include_docs=True,
            reduce=False,
        ).all()
Beispiel #23
0
class PatientFinder(DocumentSchema):
    """
    Subclasses of the PatientFinder class implement particular
    strategies for finding OpenMRS patients that suit a particular
    project. (WeightedPropertyPatientFinder was first subclass to be
    written. A future project with stronger emphasis on patient names
    might use Levenshtein distance, for example.)

    Subclasses must implement the `find_patients()` method.
    """

    # Whether to create a new patient if no patients are found
    create_missing = BooleanProperty(default=False)

    @classmethod
    def wrap(cls, data):

        if cls is PatientFinder:
            return {sub._doc_type: sub
                    for sub in recurse_subclasses(cls)
                    }[data['doc_type']].wrap(data)
        else:
            return super(PatientFinder, cls).wrap(data)

    def find_patients(self, requests, case, case_config):
        """
        Given a case, search OpenMRS for possible matches. Return the
        best results. Subclasses must define "best". If just one result
        is returned, it will be chosen.

        NOTE:: False positives can result in overwriting one patient
               with the data of another. It is definitely better to
               return no results or multiple results than to return a
               single invalid result. Returned results should be
               logged.
        """
        raise NotImplementedError
Beispiel #24
0
class CallCenterProperties(DocumentSchema):
    enabled = BooleanProperty(default=False)
    use_fixtures = BooleanProperty(default=True)

    case_owner_id = StringProperty()
    use_user_location_as_owner = BooleanProperty(default=False)
    user_location_ancestor_level = IntegerProperty(default=0)

    case_type = StringProperty()

    form_datasource_enabled = BooleanProperty(default=True)
    case_datasource_enabled = BooleanProperty(default=True)
    case_actions_datasource_enabled = BooleanProperty(default=True)

    def fixtures_are_active(self):
        return self.enabled and self.use_fixtures

    def config_is_valid(self):
        return (self.use_user_location_as_owner
                or self.case_owner_id) and self.case_type

    def update_from_app_config(self, config):
        """Update datasources enabled based on app config.

        Follows similar logic to CallCenterIndicators
        :returns: True if changes were made
        """
        pre = (self.form_datasource_enabled, self.case_datasource_enabled,
               self.case_actions_datasource_enabled)
        self.form_datasource_enabled = config.forms_submitted.enabled or bool(
            config.custom_form)
        self.case_datasource_enabled = (config.cases_total.enabled
                                        or config.cases_opened.enabled
                                        or config.cases_closed.enabled)
        self.case_actions_datasource_enabled = config.cases_active.enabled
        post = (self.form_datasource_enabled, self.case_datasource_enabled,
                self.case_actions_datasource_enabled)
        return pre != post
Beispiel #25
0
class FHIRRepeater(CaseRepeater):
    class Meta:
        app_label = 'repeaters'

    friendly_name = _('Forward Cases to a FHIR API')
    payload_generator_classes = (FormDictPayloadGenerator, )
    include_app_id_param = False
    _has_config = False

    fhir_version = StringProperty(default=FHIR_VERSION_4_0_1)
    patient_registration_enabled = BooleanProperty(default=True)
    patient_search_enabled = BooleanProperty(default=False)

    @memoized
    def payload_doc(self, repeat_record):
        return FormAccessors(repeat_record.domain).get_form(
            repeat_record.payload_id)

    @property
    def form_class_name(self):
        # The class name used to determine which edit form to use
        return self.__class__.__name__

    @classmethod
    def available_for_domain(cls, domain):
        return (domain_has_privilege(domain, DATA_FORWARDING)
                and FHIR_INTEGRATION.enabled(domain))

    def allowed_to_forward(self, payload):
        # When we update a case's external_id to their ID on a remote
        # FHIR service, the form is submitted with XMLNS_FHIR. This
        # check makes sure that we don't send the update back to FHIR.
        return payload.xmlns != XMLNS_FHIR

    def send_request(self, repeat_record, payload):
        """
        Generates FHIR resources from ``payload``, and sends them as a
        FHIR transaction bundle. If there are patients that need to be
        registered, that is done first.

        Returns an HTTP response-like object. If the payload has nothing
        to send, returns True.
        """
        requests = self.connection_settings.get_requests(
            repeat_record.payload_id)
        infos, resource_types = self.get_infos_resource_types(
            payload,
            self.fhir_version,
        )
        try:
            resources = get_info_resource_list(infos, resource_types)
            resources = register_patients(
                requests,
                resources,
                self.patient_registration_enabled,
                self.patient_search_enabled,
                self._id,
            )
            response = send_resources(
                requests,
                resources,
                self.fhir_version,
                self._id,
            )
        except Exception as err:
            requests.notify_exception(str(err))
            return RepeaterResponse(400, 'Bad Request', pformat_json(str(err)))
        return response

    def get_infos_resource_types(
        self,
        form_json: dict,
        fhir_version: str,
    ) -> Tuple[List[CaseTriggerInfo], Dict[str, FHIRResourceType]]:

        form_question_values = get_form_question_values(form_json)
        case_blocks = extract_case_blocks(form_json)
        cases_by_id = _get_cases_by_id(self.domain, case_blocks)
        resource_types_by_case_type = _get_resource_types_by_case_type(
            self.domain,
            fhir_version,
            cases_by_id.values(),
        )

        case_trigger_info_list = []
        for case_block in case_blocks:
            try:
                case = cases_by_id[case_block['@case_id']]
            except KeyError:
                form_id = form_json[TAG_FORM][TAG_META]['instanceID']
                raise CaseNotFound(
                    f"Form {form_id!r} touches case {case_block['@case_id']!r} "
                    "but that case is not found.")
            try:
                resource_type = resource_types_by_case_type[case.type]
            except KeyError:
                # The case type is not mapped to a FHIR resource type.
                # This case is not meant to be represented as a FHIR
                # resource.
                continue
            case_trigger_info_list.append(
                get_case_trigger_info(
                    case,
                    resource_type,
                    case_block,
                    form_question_values,
                ))
Beispiel #26
0
class EWSGhanaConfig(Document):
    enabled = BooleanProperty(default=False)
    domain = StringProperty()
    url = StringProperty(default="http://ewsghana.com/api/v0_1")
    username = StringProperty()
    password = StringProperty()
    steady_sync = BooleanProperty(default=False)
    all_stock_data = BooleanProperty(default=False)

    @classmethod
    def for_domain(cls, name):
        try:
            mapping = DocDomainMapping.objects.get(domain_name=name,
                                                   doc_type='EWSGhanaConfig')
            return cls.get(docid=mapping.doc_id)
        except DocDomainMapping.DoesNotExist:
            return None

    @classmethod
    def get_all_configs(cls):
        mappings = DocDomainMapping.objects.filter(doc_type='EWSGhanaConfig')
        configs = [cls.get(docid=mapping.doc_id) for mapping in mappings]
        return configs

    @classmethod
    def get_all_steady_sync_configs(cls):
        return [
            config for config in cls.get_all_configs() if config.steady_sync
        ]

    @classmethod
    def get_all_enabled_domains(cls):
        configs = cls.get_all_configs()
        return [
            c.domain for c in filter(lambda config: config.enabled, configs)
        ]

    @property
    def is_configured(self):
        return True if self.enabled and self.url and self.password and self.username else False

    def save(self, **params):
        super(EWSGhanaConfig, self).save(**params)

        self.update_toggle()

        try:
            DocDomainMapping.objects.get(doc_id=self._id,
                                         domain_name=self.domain,
                                         doc_type="EWSGhanaConfig")
        except DocDomainMapping.DoesNotExist:
            DocDomainMapping.objects.create(doc_id=self._id,
                                            domain_name=self.domain,
                                            doc_type='EWSGhanaConfig')
            add_to_module_map(self.domain, 'custom.ewsghana')

    def update_toggle(self):
        """
        This turns on the special stock handler when EWS is enabled.
        """

        if self.enabled:
            STOCK_AND_RECEIPT_SMS_HANDLER.set(self.domain, True,
                                              NAMESPACE_DOMAIN)

    class Meta:
        app_label = 'ewsghana'
Beispiel #27
0
class Repeater(QuickCachedDocumentMixin, Document):
    """
    Represents the configuration of a repeater. Will specify the URL to forward to and
    other properties of the configuration.
    """
    base_doc = 'Repeater'

    domain = StringProperty()
    url = StringProperty()
    format = StringProperty()

    auth_type = StringProperty(choices=(BASIC_AUTH, DIGEST_AUTH, OAUTH1), required=False)
    username = StringProperty()
    password = StringProperty()
    skip_cert_verify = BooleanProperty(default=False)
    friendly_name = _("Data")
    paused = BooleanProperty(default=False)

    payload_generator_classes = ()

    @classmethod
    def get_custom_url(cls, domain):
        return None

    @classmethod
    def available_for_domain(cls, domain):
        """Returns whether this repeater can be used by a particular domain
        """
        return True

    def get_pending_record_count(self):
        return get_pending_repeat_record_count(self.domain, self._id)

    def get_failure_record_count(self):
        return get_failure_repeat_record_count(self.domain, self._id)

    def get_success_record_count(self):
        return get_success_repeat_record_count(self.domain, self._id)

    def get_cancelled_record_count(self):
        return get_cancelled_repeat_record_count(self.domain, self._id)

    def _format_or_default_format(self):
        from corehq.motech.repeaters.repeater_generators import RegisterGenerator
        return self.format or RegisterGenerator.default_format_by_repeater(self.__class__)

    def _get_payload_generator(self, payload_format):
        from corehq.motech.repeaters.repeater_generators import RegisterGenerator
        gen = RegisterGenerator.generator_class_by_repeater_format(self.__class__, payload_format)
        return gen(self)

    @property
    @memoized
    def generator(self):
        return self._get_payload_generator(self._format_or_default_format())

    def payload_doc(self, repeat_record):
        raise NotImplementedError

    @memoized
    def get_payload(self, repeat_record):
        return self.generator.get_payload(repeat_record, self.payload_doc(repeat_record))

    def get_attempt_info(self, repeat_record):
        return None

    def register(self, payload, next_check=None):
        if not self.allowed_to_forward(payload):
            return

        now = datetime.utcnow()
        repeat_record = RepeatRecord(
            repeater_id=self.get_id,
            repeater_type=self.doc_type,
            domain=self.domain,
            registered_on=now,
            next_check=next_check or now,
            payload_id=payload.get_id
        )
        repeat_record.save()
        return repeat_record

    def allowed_to_forward(self, payload):
        """
        Return True/False depending on whether the payload meets forawrding criteria or not
        """
        return True

    def clear_caches(self):
        super(Repeater, self).clear_caches()
        # Also expire for cases repeater is fetched using Repeater class.
        # The quick cache called in clear_cache also check on relies of doc class
        # so in case the class is set as Repeater it is not expired like in edit forms.
        # So expire it explicitly here with Repeater class as well.
        Repeater.get.clear(Repeater, self._id)
        if self.__class__ == Repeater:
            cls = self.get_class_from_doc_type(self.doc_type)
        else:
            cls = self.__class__
        # clear cls.by_domain (i.e. filtered by doc type)
        Repeater.by_domain.clear(cls, self.domain)
        # clear Repeater.by_domain (i.e. not filtered by doc type)
        Repeater.by_domain.clear(Repeater, self.domain)

    @classmethod
    @quickcache(['cls.__name__', 'domain'], timeout=5 * 60, memoize_timeout=10)
    def by_domain(cls, domain):
        key = [domain]
        if cls.__name__ in get_all_repeater_types():
            key.append(cls.__name__)
        elif cls.__name__ == Repeater.__name__:
            # In this case the wrap function delegates to the
            # appropriate sub-repeater types.
            pass
        else:
            # Any repeater type can be posted to the API, and the installed apps
            # determine whether we actually know about it.
            # But if we do not know about it, then may as well return nothing now
            return []

        raw_docs = cls.view('repeaters/repeaters',
            startkey=key,
            endkey=key + [{}],
            include_docs=True,
            reduce=False,
            wrap_doc=False
        )

        return [cls.wrap(repeater_doc['doc']) for repeater_doc in raw_docs
                if cls.get_class_from_doc_type(repeater_doc['doc']['doc_type'])]

    @classmethod
    def wrap(cls, data):
        if cls.__name__ == Repeater.__name__:
            cls_ = cls.get_class_from_doc_type(data['doc_type'])
            if cls_:
                return cls_.wrap(data)
            else:
                raise ResourceNotFound('Unknown repeater type: %s' % data)
        else:
            return super(Repeater, cls).wrap(data)

    @staticmethod
    def get_class_from_doc_type(doc_type):
        doc_type = doc_type.replace(DELETED, '')
        repeater_types = get_all_repeater_types()
        if doc_type in repeater_types:
            return repeater_types[doc_type]
        else:
            return None

    def retire(self):
        if DELETED not in self['doc_type']:
            self['doc_type'] += DELETED
        if DELETED not in self['base_doc']:
            self['base_doc'] += DELETED
        self.paused = False
        self.save()

    def pause(self):
        self.paused = True
        self.save()

    def resume(self):
        self.paused = False
        self.save()

    def get_url(self, repeat_record):
        # to be overridden
        return self.url

    def allow_retries(self, response):
        """Whether to requeue the repeater when it fails
        """
        return True

    def get_headers(self, repeat_record):
        # to be overridden
        return self.generator.get_headers()

    @property
    def plaintext_password(self):
        if self.password.startswith('${algo}$'.format(algo=ALGO_AES)):
            ciphertext = self.password.split('$', 2)[2]
            return b64_aes_decrypt(ciphertext)
        return self.password

    def get_auth(self):
        if self.auth_type == BASIC_AUTH:
            return HTTPBasicAuth(self.username, self.plaintext_password)
        elif self.auth_type == DIGEST_AUTH:
            return HTTPDigestAuth(self.username, self.plaintext_password)
        return None

    @property
    def verify(self):
        return not self.skip_cert_verify

    def send_request(self, repeat_record, payload):
        headers = self.get_headers(repeat_record)
        auth = self.get_auth()
        url = self.get_url(repeat_record)
        return simple_post(payload, url, headers=headers, timeout=POST_TIMEOUT, auth=auth, verify=self.verify)

    def fire_for_record(self, repeat_record):
        payload = self.get_payload(repeat_record)
        try:
            response = self.send_request(repeat_record, payload)
        except (Timeout, ConnectionError) as error:
            log_repeater_timeout_in_datadog(self.domain)
            return self.handle_response(RequestConnectionError(error), repeat_record)
        except Exception as e:
            return self.handle_response(e, repeat_record)
        else:
            return self.handle_response(response, repeat_record)

    def handle_response(self, result, repeat_record):
        """
        route the result to the success, failure, or exception handlers

        result may be either a response object or an exception
        """
        if isinstance(result, Exception):
            attempt = repeat_record.handle_exception(result)
            self.generator.handle_exception(result, repeat_record)
        elif 200 <= result.status_code < 300:
            attempt = repeat_record.handle_success(result)
            self.generator.handle_success(result, self.payload_doc(repeat_record), repeat_record)
        else:
            attempt = repeat_record.handle_failure(result)
            self.generator.handle_failure(result, self.payload_doc(repeat_record), repeat_record)
        return attempt

    @property
    def form_class_name(self):
        """
        Return the name of the class whose edit form this class uses.

        (Most classes that extend CaseRepeater, and all classes that
        extend FormRepeater, use the same form.)
        """
        return self.__class__.__name__
Beispiel #28
0
class RepeatRecord(Document):
    """
    An record of a particular instance of something that needs to be forwarded
    with a link to the proper repeater object
    """

    domain = StringProperty()
    repeater_id = StringProperty()
    repeater_type = StringProperty()
    payload_id = StringProperty()

    overall_tries = IntegerProperty(default=0)
    max_possible_tries = IntegerProperty(default=3)

    attempts = ListProperty(RepeatRecordAttempt)

    cancelled = BooleanProperty(default=False)
    registered_on = DateTimeProperty()
    last_checked = DateTimeProperty()
    failure_reason = StringProperty()
    next_check = DateTimeProperty()
    succeeded = BooleanProperty(default=False)

    @property
    def record_id(self):
        return self._id

    @classmethod
    def wrap(cls, data):
        should_bootstrap_attempts = ('attempts' not in data)

        self = super(RepeatRecord, cls).wrap(data)

        if should_bootstrap_attempts and self.last_checked:
            assert not self.attempts
            self.attempts = [RepeatRecordAttempt(
                cancelled=self.cancelled,
                datetime=self.last_checked,
                failure_reason=self.failure_reason,
                success_response=None,
                next_check=self.next_check,
                succeeded=self.succeeded,
            )]
        return self

    @property
    @memoized
    def repeater(self):
        try:
            return Repeater.get(self.repeater_id)
        except ResourceNotFound:
            return None

    @property
    def url(self):
        warnings.warn("RepeatRecord.url is deprecated. Use Repeater.get_url instead", DeprecationWarning)
        if self.repeater:
            return self.repeater.get_url(self)

    @property
    def state(self):
        state = RECORD_PENDING_STATE
        if self.succeeded:
            state = RECORD_SUCCESS_STATE
        elif self.cancelled:
            state = RECORD_CANCELLED_STATE
        elif self.failure_reason:
            state = RECORD_FAILURE_STATE
        return state

    @classmethod
    def all(cls, domain=None, due_before=None, limit=None):
        json_now = json_format_datetime(due_before or datetime.utcnow())
        repeat_records = RepeatRecord.view("repeaters/repeat_records_by_next_check",
            startkey=[domain],
            endkey=[domain, json_now, {}],
            include_docs=True,
            reduce=False,
            limit=limit,
        )
        return repeat_records

    @classmethod
    def count(cls, domain=None):
        results = RepeatRecord.view("repeaters/repeat_records_by_next_check",
            startkey=[domain],
            endkey=[domain, {}],
            reduce=True,
        ).one()
        return results['value'] if results else 0

    def add_attempt(self, attempt):
        self.attempts.append(attempt)
        self.last_checked = attempt.datetime
        self.next_check = attempt.next_check
        self.succeeded = attempt.succeeded
        self.cancelled = attempt.cancelled
        self.failure_reason = attempt.failure_reason

    def get_numbered_attempts(self):
        for i, attempt in enumerate(self.attempts):
            yield i + 1, attempt

    def postpone_by(self, duration):
        self.last_checked = datetime.utcnow()
        self.next_check = self.last_checked + duration
        self.save()

    def make_set_next_try_attempt(self, failure_reason):
        # we use an exponential back-off to avoid submitting to bad urls
        # too frequently.
        assert self.succeeded is False
        assert self.next_check is not None
        window = timedelta(minutes=0)
        if self.last_checked:
            window = self.next_check - self.last_checked
            window += (window // 2)  # window *= 1.5
        if window < MIN_RETRY_WAIT:
            window = MIN_RETRY_WAIT
        elif window > MAX_RETRY_WAIT:
            window = MAX_RETRY_WAIT

        now = datetime.utcnow()
        return RepeatRecordAttempt(
            cancelled=False,
            datetime=now,
            failure_reason=failure_reason,
            success_response=None,
            next_check=now + window,
            succeeded=False,
        )

    def try_now(self):
        # try when we haven't succeeded and either we've
        # never checked, or it's time to check again
        return not self.succeeded

    def get_payload(self):
        return self.repeater.get_payload(self)

    def get_attempt_info(self):
        return self.repeater.get_attempt_info(self)

    def handle_payload_exception(self, exception):
        now = datetime.utcnow()
        return RepeatRecordAttempt(
            cancelled=True,
            datetime=now,
            failure_reason=six.text_type(exception),
            success_response=None,
            next_check=None,
            succeeded=False,
        )

    def fire(self, force_send=False):
        if self.try_now() or force_send:
            self.overall_tries += 1
            try:
                attempt = self.repeater.fire_for_record(self)
            except Exception as e:
                log_repeater_error_in_datadog(self.domain, status_code=None,
                                              repeater_type=self.repeater_type)
                attempt = self.handle_payload_exception(e)
                raise
            finally:
                # pycharm warns attempt might not be defined.
                # that'll only happen if fire_for_record raise a non-Exception exception (e.g. SIGINT)
                # or handle_payload_exception raises an exception. I'm okay with that. -DMR
                self.add_attempt(attempt)
                self.save()

    @staticmethod
    def _format_response(response):
        return '{}: {}.\n{}'.format(
            response.status_code, response.reason, getattr(response, 'content', None))

    def handle_success(self, response):
        """Do something with the response if the repeater succeeds
        """
        now = datetime.utcnow()
        log_repeater_success_in_datadog(
            self.domain,
            response.status_code if response else None,
            self.repeater_type
        )
        return RepeatRecordAttempt(
            cancelled=False,
            datetime=now,
            failure_reason=None,
            success_response=self._format_response(response) if response else None,
            next_check=None,
            succeeded=True,
            info=self.get_attempt_info(),
        )

    def handle_failure(self, response):
        """Do something with the response if the repeater fails
        """
        return self._make_failure_attempt(self._format_response(response), response)

    def handle_exception(self, exception):
        """handle internal exceptions
        """
        return self._make_failure_attempt(six.text_type(exception), None)

    def _make_failure_attempt(self, reason, response):
        log_repeater_error_in_datadog(self.domain, response.status_code if response else None,
                                      self.repeater_type)

        if self.repeater.allow_retries(response) and self.overall_tries < self.max_possible_tries:
            return self.make_set_next_try_attempt(reason)
        else:
            now = datetime.utcnow()
            return RepeatRecordAttempt(
                cancelled=True,
                datetime=now,
                failure_reason=reason,
                success_response=None,
                next_check=None,
                succeeded=False,
                info=self.get_attempt_info(),
            )

    def cancel(self):
        self.next_check = None
        self.cancelled = True

    def requeue(self):
        self.cancelled = False
        self.succeeded = False
        self.failure_reason = ''
        self.overall_tries = 0
        self.next_check = datetime.utcnow()
class OpenmrsCaseConfig(DocumentSchema):

    # "patient_identifiers": {
    #     "e2b966d0-1d5f-11e0-b929-000c29ad1d07": {
    #         "case_property": "nid"
    #     },
    #     "uuid": {
    #         "case_property": "openmrs_uuid",
    #     }
    # }
    patient_identifiers = DictProperty()

    # The patient_identifiers that are considered reliable
    # "match_on_ids": ["uuid", "e2b966d0-1d5f-11e0-b929-000c29ad1d07",
    match_on_ids = ListProperty()

    # "person_properties": {
    #     "gender": {
    #         "case_property": "gender"
    #     },
    #     "birthdate": {
    #         "case_property": "dob"
    #     }
    # }
    person_properties = DictProperty()

    # "patient_finder": {
    #     "doc_type": "WeightedPropertyPatientFinder",
    #     "searchable_properties": ["nid", "family_name"],
    #     "property_weights": [
    #         {"case_property": "nid", "weight": 0.9},
    #         // if "match_type" is not given it defaults to "exact"
    #         {"case_property": "family_name", "weight": 0.4},
    #         {
    #             "case_property": "given_name",
    #             "weight": 0.3,
    #             "match_type": "levenshtein",
    #             // levenshtein function takes edit_distance / len
    #             "match_params": [0.2]
    #             // i.e. 0.2 (20%) is one edit for every 5 characters
    #             // e.g. "Riyaz" matches "Riaz" but not "Riazz"
    #         },
    #         {"case_property": "city", "weight": 0.2},
    #         {
    #             "case_property": "dob",
    #             "weight": 0.3,
    #             "match_type": "days_diff",
    #             // days_diff matches based on days difference from given date
    #             "match_params": [364]
    #         }
    #     ]
    # }
    patient_finder = PatientFinder(required=False)

    # "person_preferred_name": {
    #     "givenName": {
    #         "case_property": "given_name"
    #     },
    #     "middleName": {
    #         "case_property": "middle_name"
    #     },
    #     "familyName": {
    #         "case_property": "family_name"
    #     }
    # }
    person_preferred_name = DictProperty()

    # "person_preferred_address": {
    #     "address1": {
    #         "case_property": "address_1"
    #     },
    #     "address2": {
    #         "case_property": "address_2"
    #     },
    #     "cityVillage": {
    #         "case_property": "city"
    #     }
    # }
    person_preferred_address = DictProperty()

    # "person_attributes": {
    #     "c1f4239f-3f10-11e4-adec-0800271c1b75": {
    #         "case_property": "caste"
    #     },
    #     "c1f455e7-3f10-11e4-adec-0800271c1b75": {
    #         "case_property": "class",
    #         "value_map": {
    #             "sc": "c1fcd1c6-3f10-11e4-adec-0800271c1b75",
    #             "general": "c1fc20ab-3f10-11e4-adec-0800271c1b75",
    #             "obc": "c1fb51cc-3f10-11e4-adec-0800271c1b75",
    #             "other_caste": "c207073d-3f10-11e4-adec-0800271c1b75",
    #             "st": "c20478b6-3f10-11e4-adec-0800271c1b75"
    #         }
    #     }
    # }
    person_attributes = DictProperty()

    # Create cases when importing via the Atom feed
    import_creates_cases = BooleanProperty(default=True)
    # If we ever need to disable updating cases, ``import_updates_cases``
    # could be added here. Similarly, we could replace
    # ``patient_finder.create_missing`` with ``export_creates_patients``
    # and ``export_updates_patients``

    @classmethod
    def wrap(cls, data):
        if 'id_matchers' in data:
            # Convert legacy id_matchers to patient_identifiers. e.g.
            #     [{'doc_type': 'IdMatcher'
            #       'identifier_type_id': 'e2b966d0-1d5f-11e0-b929-000c29ad1d07',
            #       'case_property': 'nid'}]
            # to
            #     {'e2b966d0-1d5f-11e0-b929-000c29ad1d07': {'doc_type': 'CaseProperty', 'case_property': 'nid'}},
            patient_identifiers = {
                m['identifier_type_id']: {
                    'doc_type': 'CaseProperty',
                    'case_property': m['case_property']
                }
                for m in data['id_matchers']
            }
            data['patient_identifiers'] = patient_identifiers
            data['match_on_ids'] = list(patient_identifiers)
            data.pop('id_matchers')
        # Set default data types for known properties
        for property_, value_source in chain(
                data.get('person_properties', {}).items(),
                data.get('person_preferred_name', {}).items(),
                data.get('person_preferred_address', {}).items(),
        ):
            data_type = OPENMRS_PROPERTIES[property_]
            value_source.setdefault('external_data_type', data_type)
        return super(OpenmrsCaseConfig, cls).wrap(data)
Beispiel #30
0
class OpenmrsRepeater(CaseRepeater):
    """
    ``OpenmrsRepeater`` is responsible for updating OpenMRS patients
    with changes made to cases in CommCare. It is also responsible for
    creating OpenMRS "visits", "encounters" and "observations" when a
    corresponding visit form is submitted in CommCare.

    The ``OpenmrsRepeater`` class is different from most repeater
    classes in three details:

    1. It has a case type and it updates the OpenMRS equivalent of cases
       like the ``CaseRepeater`` class, but it reads forms like the
       ``FormRepeater`` class. So it subclasses ``CaseRepeater`` but its
       payload format is ``form_json``.

    2. It makes many API calls for each payload.

    3. It can have a location.

    """
    class Meta(object):
        app_label = 'repeaters'

    include_app_id_param = False
    friendly_name = _("Forward to OpenMRS")
    payload_generator_classes = (FormRepeaterJsonPayloadGenerator, )

    location_id = StringProperty(default='')
    openmrs_config = SchemaProperty(OpenmrsConfig)

    _has_config = True

    # self.white_listed_case_types must have exactly one case type set
    # for Atom feed integration to add cases for OpenMRS patients.
    # self.location_id must be set to determine their case owner. The
    # owner is set to the first CommCareUser instance found at that
    # location.
    atom_feed_enabled = BooleanProperty(default=False)
    atom_feed_status = SchemaDictProperty(AtomFeedStatus)

    def __init__(self, *args, **kwargs):
        super(OpenmrsRepeater, self).__init__(*args, **kwargs)

    def __eq__(self, other):
        return (isinstance(other, self.__class__)
                and self.get_id == other.get_id)

    def __str__(self):
        return Repeater.__str__(self)

    @classmethod
    def wrap(cls, data):
        if 'atom_feed_last_polled_at' in data:
            data['atom_feed_status'] = {
                ATOM_FEED_NAME_PATIENT: {
                    'last_polled_at': data.pop('atom_feed_last_polled_at'),
                    'last_page': data.pop('atom_feed_last_page', None),
                }
            }
        return super(OpenmrsRepeater, cls).wrap(data)

    @cached_property
    def requests(self):
        # Used by atom_feed module and views that don't have a payload
        # associated with the request
        return self.get_requests()

    def get_requests(self, payload_id=None):
        return Requests(
            self.domain,
            self.url,
            self.username,
            self.plaintext_password,
            verify=self.verify,
            notify_addresses=self.notify_addresses,
            payload_id=payload_id,
        )

    @cached_property
    def first_user(self):
        return get_one_commcare_user_at_location(self.domain, self.location_id)

    @memoized
    def payload_doc(self, repeat_record):
        return FormAccessors(repeat_record.domain).get_form(
            repeat_record.payload_id)

    @property
    def form_class_name(self):
        """
        The class name used to determine which edit form to use
        """
        return self.__class__.__name__

    @classmethod
    def available_for_domain(cls, domain):
        return OPENMRS_INTEGRATION.enabled(domain)

    def allowed_to_forward(self, payload):
        """
        Forward the payload if ...

        * it did not come from OpenMRS, and
        * CaseRepeater says it's OK for the case types and users of any
          of the payload's cases, and
        * this repeater forwards to the right OpenMRS server for any of
          the payload's cases.

        :param payload: An XFormInstance (not a case)

        """
        if payload.xmlns == XMLNS_OPENMRS:
            # payload came from OpenMRS. Don't send it back.
            return False

        case_blocks = extract_case_blocks(payload)
        case_ids = [case_block['@case_id'] for case_block in case_blocks]
        cases = CaseAccessors(payload.domain).get_cases(case_ids, ordered=True)
        if not any(
                CaseRepeater.allowed_to_forward(self, case) for case in cases):
            # If none of the case updates in the payload are allowed to
            # be forwarded, drop it.
            return False

        if not self.location_id:
            # If this repeater  does not have a location, all payloads
            # should go to it.
            return True

        repeaters = [
            repeater for case in cases
            for repeater in get_case_location_ancestor_repeaters(case)
        ]
        # If this repeater points to the wrong OpenMRS server for this
        # payload then let the right repeater handle it.
        return self in repeaters

    def get_payload(self, repeat_record):
        payload = super(OpenmrsRepeater, self).get_payload(repeat_record)
        return json.loads(payload)

    def send_request(self, repeat_record, payload):
        value_source_configs: Iterable[JsonDict] = chain(
            self.openmrs_config.case_config.patient_identifiers.values(),
            self.openmrs_config.case_config.person_properties.values(),
            self.openmrs_config.case_config.person_preferred_name.values(),
            self.openmrs_config.case_config.person_preferred_address.values(),
            self.openmrs_config.case_config.person_attributes.values(),
        )
        case_trigger_infos = get_relevant_case_updates_from_form_json(
            self.domain,
            payload,
            case_types=self.white_listed_case_types,
            extra_fields=[
                conf["case_property"] for conf in value_source_configs
                if "case_property" in conf
            ],
            form_question_values=get_form_question_values(payload),
        )
        requests = self.get_requests(payload_id=repeat_record.payload_id)
        try:
            response = send_openmrs_data(
                requests,
                self.domain,
                payload,
                self.openmrs_config,
                case_trigger_infos,
            )
        except Exception as err:
            requests.notify_exception(str(err))
            return OpenmrsResponse(400, 'Bad Request', pformat_json(str(err)))
        return response