Esempio n. 1
0
class CaseDisplaySettings(DocumentSchema):
    case_details = DictProperty(
        verbose_name="Mapping of case type to definitions of properties "
        "to display above the fold on case details")
    form_details = DictProperty(
        verbose_name="Mapping of form xmlns to definitions of properties "
        "to display for individual forms")
Esempio n. 2
0
class Dhis2CaseConfig(DocumentSchema):
    """
    A Dhis2CaseConfig maps a case type to a tracked entity type.
    """
    case_type = StringProperty()

    # The ID of the Tracked Entity type. e.g. the ID of "Person"
    te_type_id = StringProperty()

    # CommCare case indices to export as DHIS2 relationships
    relationships_to_export = SchemaListProperty(RelationshipConfig)

    # The case property to store the ID of the corresponding Tracked
    # Entity instance. If this is not set, MOTECH will search for a
    # matching Tracked Entity on every payload.
    tei_id = DictProperty()

    # The corresponding Org Unit of the case's location
    org_unit_id = DictProperty()

    # Attribute Type ID to case property / constant
    attributes = DictProperty()

    # Events for this Tracked Entity:
    form_configs = ListProperty(Dhis2FormConfig)

    finder_config = SchemaProperty(FinderConfig)
Esempio n. 3
0
class BaseExpressionRepeater(Repeater):
    """Uses a UCR dict expression to send a generic json response
    """
    class Meta:
        app_label = 'repeaters'

    configured_filter = DictProperty()
    configured_expression = DictProperty()
    url_template = StringProperty()
    payload_generator_classes = (ExpressionPayloadGenerator, )

    @property
    @memoized
    def parsed_filter(self):
        return FilterFactory.from_spec(self.configured_filter,
                                       FactoryContext.empty())

    @property
    @memoized
    def parsed_expression(self):
        return ExpressionFactory.from_spec(self.configured_expression,
                                           FactoryContext.empty())

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

    def allowed_to_forward(self, payload):
        payload_json = payload.to_json()
        return self.parsed_filter(payload_json,
                                  EvaluationContext(payload_json))

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

    def get_url(self, repeat_record):
        base_url = super().get_url(repeat_record)
        if self.url_template:
            return base_url + self.generator.get_url(
                repeat_record,
                self.url_template,
                self.payload_doc(repeat_record),
            )
        return base_url

    @classmethod
    def _migration_get_sql_model_class(cls):
        return SQLBaseExpressionRepeater

    @classmethod
    def _migration_get_fields(cls):
        return super()._migration_get_fields() + [
            "configured_filter", "configured_expression"
        ]
Esempio n. 4
0
class NavigationEventAudit(AuditEvent):
    """
    Audit event to track happenings within the system, ie, view access
    """
    request_path = StringProperty()
    ip_address = StringProperty()
    user_agent = StringProperty()

    view = StringProperty()  # the fully qualifid view name
    view_kwargs = DictProperty()
    headers = DictProperty()  # the request.META?
    # in the future possibly save some disk space by storing user agent and IP stuff in a separte session document?
    session_key = StringProperty()

    status_code = IntegerProperty()

    extra = DictProperty()

    @property
    def summary(self):
        return "%s from %s" % (self.request_path, self.ip_address)

    class Meta(object):
        app_label = 'auditcare'

    @cached_property
    def domain(self):
        from corehq.apps.domain.utils import get_domain_from_url
        return get_domain_from_url(self.request_path)

    @classmethod
    def audit_view(cls, request, user, view_func, view_kwargs, extra={}):
        """Creates an instance of a Access log."""
        try:
            audit = cls.create_audit(cls, user)
            audit.description += "View"
            if len(list(request.GET)) > 0:
                params = "&".join(f"{x}={request.GET[x]}"
                                  for x in request.GET.keys())
                audit.request_path = f"{request.path}?{params}"
            else:
                audit.request_path = request.path
            audit.ip_address = get_ip(request)
            audit.user_agent = request.META.get('HTTP_USER_AGENT', '<unknown>')
            audit.view = "%s.%s" % (view_func.__module__, view_func.__name__)
            for k in STANDARD_HEADER_KEYS:
                header_item = request.META.get(k, None)
                if header_item is not None:
                    audit.headers[k] = header_item
            # it's a bit verbose to go to that extreme, TODO: need to have
            # targeted fields in the META, but due to server differences, it's
            # hard to make it universal.
            #audit.headers = request.META
            audit.session_key = request.session.session_key
            audit.extra = extra
            audit.view_kwargs = view_kwargs
            return audit
        except Exception:
            log.exception("NavigationEventAudit.audit_view error")
Esempio n. 5
0
class Dhis2FormConfig(DocumentSchema):
    xmlns = StringProperty(required=True)
    program_id = StringProperty(required=True)
    enrollment_date = DictProperty(required=False)
    incident_date = DictProperty(required=False)
    program_stage_id = DictProperty(required=False)
    program_status = DictProperty(
        required=False, default={"value": DHIS2_PROGRAM_STATUS_ACTIVE})
    org_unit_id = DictProperty(
        required=False,
        default={"form_user_ancestor_location_field": LOCATION_DHIS_ID})
    event_date = DictProperty(required=True,
                              default={
                                  "form_question": "/metadata/received_on",
                                  "external_data_type": DHIS2_DATA_TYPE_DATE,
                              })
    event_status = DictProperty(
        required=False, default={"value": DHIS2_EVENT_STATUS_COMPLETED})
    completed_date = DictProperty(required=False)
    datavalue_maps = SchemaListProperty(FormDataValueMap)
    event_location = DictProperty(required=False, default={})

    @classmethod
    def wrap(cls, data):
        if isinstance(data.get('org_unit_id'), str):
            # Convert org_unit_id from a string to a ConstantValue
            data['org_unit_id'] = {'value': data['org_unit_id']}
        if isinstance(data.get('event_status'), str):
            data['event_status'] = {'value': data['event_status']}
        if isinstance(data.get('program_status'), str):
            data['program_status'] = {'value': data['program_status']}
        return super(Dhis2FormConfig, cls).wrap(data)
Esempio n. 6
0
class NavigationEventAudit(AuditEvent):
    """
    Audit event to track happenings within the system, ie, view access
    """
    request_path = StringProperty()
    ip_address = StringProperty()
    user_agent = StringProperty()

    view = StringProperty()  #the fully qualifid view name
    view_kwargs = DictProperty()
    headers = DictProperty()  #the request.META?
    session_key = StringProperty(
    )  #in the future possibly save some disk space by storing user agent and IP stuff in a separte session document?

    status_code = IntegerProperty()

    extra = DictProperty()

    @property
    def summary(self):
        return "%s from %s" % (self.request_path, self.ip_address)

    class Meta:
        app_label = 'auditcare'

    @classmethod
    def audit_view(cls, request, user, view_func, view_kwargs, extra={}):
        """Creates an instance of a Access log."""
        try:
            audit = cls.create_audit(cls, user)
            audit.description += "View"
            if len(request.GET.keys()) > 0:
                audit.request_path = "%s?%s" % (request.path, '&'.join([
                    "%s=%s" % (x, request.GET[x]) for x in request.GET.keys()
                ]))
            else:
                audit.request_path = request.path
            audit.ip_address = utils.get_ip(request)
            audit.user_agent = request.META.get('HTTP_USER_AGENT', '<unknown>')
            audit.view = "%s.%s" % (view_func.__module__, view_func.func_name)
            for k in STANDARD_HEADER_KEYS:
                header_item = request.META.get(k, None)
                if header_item is not None:
                    audit.headers[k] = header_item
            #audit.headers = request.META #it's a bit verbose to go to that extreme, TODO: need to have targeted fields in the META, but due to server differences, it's hard to make it universal.
            audit.session_key = request.session.session_key
            audit.extra = extra
            audit.view_kwargs = view_kwargs
            audit.save()
            return audit
        except Exception, ex:
            log.error("NavigationEventAudit.audit_view error: %s", ex)
Esempio n. 7
0
class PatientFinder(DocumentSchema):
    """
    The ``PatientFinder`` base class was developed as a way to
    handle situations where patient cases are created in CommCare
    instead of being imported from OpenMRS.

    When patients are imported from OpenMRS, they will come with at
    least one identifier that MOTECH can use to match the case in
    CommCare with the corresponding patient in OpenMRS. But if the case
    is registered in CommCare then we may not have an ID, or the ID
    could be wrong. We need to search for a corresponding OpenMRS
    patient.

    Different projects may focus on different kinds of case properties,
    so it was felt that a base class would allow some flexibility.

    The ``PatientFinder.wrap()`` method allows you to wrap documents of
    subclasses.

    The ``PatientFinder.find_patients()`` method must be implemented by
    subclasses. It returns a list of zero, one, or many patients. If it
    returns one patient, the OpenmrsRepeater.find_or_create_patient()
    will accept that patient as a true match.

    .. NOTE:: The consequences of a false positive (a Type II error) are
              severe: A real patient will have their valid values
              overwritten by those of someone else. So ``PatientFinder``
              subclasses should be written and configured to skew
              towards false negatives (Type I errors). In other words,
              it is much better not to choose a patient than to choose
              the wrong patient.

    """

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

    @classmethod
    def wrap(cls, data):
        if 'create_missing' in data and isinstance(data['create_missing'], bool):
            data['create_missing'] = {
                'external_data_type': OPENMRS_DATA_TYPE_BOOLEAN,
                'value': str(data['create_missing'])
            }

        if cls is PatientFinder:
            subclass = {
                sub._doc_type: sub for sub in recurse_subclasses(cls)
            }.get(data['doc_type'])
            return subclass.wrap(data) if subclass else None
        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.
        """
        raise NotImplementedError
Esempio n. 8
0
class CommCareCaseAttachment(LooselyEqualDocumentSchema, CaseAttachmentMixin,
                             UnicodeMixIn):
    identifier = StringProperty()
    attachment_src = StringProperty()
    attachment_from = StringProperty()
    attachment_name = StringProperty()
    server_mime = StringProperty()  # Server detected MIME
    server_md5 = StringProperty()  # Couch detected hash

    attachment_size = IntegerProperty()  # file size
    attachment_properties = DictProperty(
    )  # width, height, other relevant metadata

    @property
    def content_type(self):
        return self.server_mime

    @classmethod
    def from_case_index_update(cls, attachment):
        if attachment.attachment_src:
            guessed = mimetypes.guess_type(attachment.attachment_src)
            if len(guessed) > 0 and guessed[0] is not None:
                mime_type = guessed[0]
            else:
                mime_type = None

            ret = cls(identifier=attachment.identifier,
                      attachment_src=attachment.attachment_src,
                      attachment_from=attachment.attachment_from,
                      attachment_name=attachment.attachment_name,
                      server_mime=mime_type)
        else:
            ret = cls(identifier=attachment.identifier)
        return ret
Esempio n. 9
0
class FixtureItemField(DocumentSchema):
    """
        "field_value": "Delhi_IN_HIN",
        "properties": {"lang": "hin"}
    """
    field_value = StringProperty()
    properties = DictProperty()
Esempio n. 10
0
class CasePropertyMap(CaseProperty):
    """
    Maps case property values to OpenMRS values or concept UUIDs
    """
    # Example "person_attribute" value::
    #
    #     {
    #       "00000000-771d-0000-0000-000000000000": {
    #         "doc_type": "CasePropertyMap",
    #         "case_property": "pill"
    #         "value_map": {
    #           "red": "00ff0000-771d-0000-0000-000000000000",
    #           "blue": "000000ff-771d-0000-0000-000000000000",
    #         }
    #       }
    #     }
    #
    value_map = DictProperty()

    def serialize(self, value):
        # Don't bother serializing. self.value_map does that already.
        #
        # Using `.get()` because it's OK if some CommCare answers are
        # not mapped to OpenMRS concepts, e.g. when only the "yes" value
        # of a yes-no question in CommCare is mapped to a concept in
        # OpenMRS.
        return self.value_map.get(value)

    def deserialize(self, external_value):
        reverse_map = {v: k for k, v in self.value_map.items()}
        return reverse_map.get(external_value)
Esempio n. 11
0
class CasePropertyMap(CaseProperty):
    """
    Maps case property values to OpenMRS values or concept UUIDs
    """
    # Example "person_attribute" value::
    #
    #     {
    #       "00000000-771d-0000-0000-000000000000": {
    #         "doc_type": "CasePropertyMap",
    #         "case_property": "pill"
    #         "value_map": {
    #           "red": "00ff0000-771d-0000-0000-000000000000",
    #           "blue": "000000ff-771d-0000-0000-000000000000",
    #         }
    #       }
    #     }
    #
    value_map = DictProperty()

    def get_value(self, case_trigger_info):
        value = super(CasePropertyMap, self).get_value(case_trigger_info)
        try:
            return self.value_map[value]
        except KeyError:
            # We don't care if some CommCare answers are not mapped to OpenMRS concepts, e.g. when only the "yes"
            # value of a yes-no question in CommCare is mapped to a concept in OpenMRS.
            return None
Esempio n. 12
0
class FixtureReportResult(Document, QueryMixin):
    domain = StringProperty()
    location_id = StringProperty()
    start_date = DateProperty()
    end_date = DateProperty()
    report_slug = StringProperty()
    rows = DictProperty()
    name = StringProperty()

    class Meta(object):
        app_label = "m4change"

    @classmethod
    def by_composite_key(cls, domain, location_id, start_date, end_date, report_slug):
        try:
            return cls.view("m4change/fixture_by_composite_key",
                             key=[domain, location_id, start_date, end_date, report_slug],
                             include_docs=True).one(except_all=True)
        except (NoResultFound, ResourceNotFound, MultipleResultsFound):
            return None

    @classmethod
    def all_by_composite_key(cls, domain, location_id, start_date, end_date, report_slug):
        return cls.view("m4change/fixture_by_composite_key",
                        startkey=[domain, location_id, start_date, end_date, report_slug],
                        endkey=[domain, location_id, start_date, end_date, report_slug],
                        include_docs=True).all()

    @classmethod
    def by_domain(cls, domain):
        return cls.view("m4change/fixture_by_composite_key", startkey=[domain], endkey=[domain, {}], include_docs=True).all()

    @classmethod
    def get_report_results_by_key(cls, domain, location_id, start_date, end_date):
        return cls.view("m4change/fixture_by_composite_key",
                        startkey=[domain, location_id, start_date, end_date],
                        endkey=[domain, location_id, start_date, end_date, {}],
                        include_docs=True).all()

    @classmethod
    def _validate_params(cls, params):
        for param in params:
            if param is None or len(param) == 0:
                return False
        return True

    @classmethod
    def save_result(cls, domain, location_id, start_date, end_date, report_slug, rows, name):
        if not cls._validate_params([domain, location_id, report_slug]) \
                or not isinstance(rows, dict) or len(rows) == 0 \
                or not isinstance(start_date, date) or not isinstance(end_date, date):
            return
        FixtureReportResult(domain=domain, location_id=location_id, start_date=start_date, end_date=end_date,
                            report_slug=report_slug, rows=rows, name=name).save()
Esempio n. 13
0
class FormQuestionMap(FormQuestion):
    """
    Maps form question values to OpenMRS values or concept UUIDs
    """
    value_map = DictProperty()

    def get_value(self, case_trigger_info):
        value = super(FormQuestionMap, self).get_value(case_trigger_info)
        try:
            return self.value_map[value]
        except KeyError:
            return None
Esempio n. 14
0
class AuxMedia(DocumentSchema):
    """
    Additional metadata companion for couch models
    that you want to add arbitrary attachments to
    """
    uploaded_date = DateTimeProperty()
    uploaded_by = StringProperty()
    uploaded_filename = StringProperty()  # the uploaded filename info
    checksum = StringProperty()
    attachment_id = StringProperty()  # the actual attachment id in blobs
    media_meta = DictProperty()
    notes = StringProperty()
Esempio n. 15
0
class ModelAuditEvent(models.Model):
    object_type = StringProperty() # String of ContentType/Model, verbose_name='Case linking content type', blank=True, null=True)
    object_uuid = StringProperty() #('object_uuid', max_length=32, db_index=True, blank=True, null=True)

    properties = StringListProperty() #models.ManyToManyField(FieldAccess, blank=True, null=True)
    property_data = DictProperty() #models.TextField() #json of the actual fields accessed

    user = StringProperty() # The User's username accessing this
    accessed = DateTimeProperty(default=getdate)

    class Meta:
        app_label = 'auditcare'
Esempio n. 16
0
class FormQuestionConcept(FormQuestion):
    """
    Maps form question values to OpenMRS concepts
    """
    value_concepts = DictProperty()

    def get_value(self, case_trigger_info):
        value = super(FormQuestionConcept, self).get_value(case_trigger_info)
        try:
            return self.value_concepts[value]
        except KeyError:
            return None
Esempio n. 17
0
class FormQuestionMap(FormQuestion):
    """
    Maps form question values to OpenMRS values or concept UUIDs
    """
    value_map = DictProperty()

    def serialize(self, value):
        return self.value_map.get(value)

    def deserialize(self, external_value):
        reverse_map = {v: k for k, v in self.value_map.items()}
        return reverse_map.get(external_value)
Esempio n. 18
0
class TranslationMixin(Document):
    translations = DictProperty()

    def set_translation(self, lang, key, value):
        if lang not in self.translations:
            self.translations[lang] = {}
        if value is not None:
            self.translations[lang][key] = value
        else:
            del self.translations[lang][key]

    def set_translations(self, lang, translations):
        self.translations[lang] = translations
Esempio n. 19
0
class CasePropertyConcept(CaseProperty):
    """
    Maps case property values to OpenMRS concepts
    """
    value_concepts = DictProperty()

    def get_value(self, case_trigger_info):
        value = super(CasePropertyConcept, self).get_value(case_trigger_info)
        try:
            return self.value_concepts[value]
        except KeyError:
            # We don't care if some CommCare answers are not mapped to OpenMRS concepts, e.g. when only the "yes"
            # value of a yes-no question in CommCare is mapped to a concept in OpenMRS.
            return None
Esempio n. 20
0
class CommCareCaseAttachment(LooselyEqualDocumentSchema, UnicodeMixIn):
    identifier = StringProperty()
    attachment_src = StringProperty()
    attachment_from = StringProperty()
    attachment_name = StringProperty()
    server_mime = StringProperty()  # Server detected MIME
    server_md5 = StringProperty()  # Couch detected hash

    attachment_size = IntegerProperty()  # file size
    attachment_properties = DictProperty(
    )  # width, height, other relevant metadata

    @property
    def is_image(self):
        if self.server_mime is None:
            return None
        return True if self.server_mime.startswith('image/') else False

    @property
    def is_present(self):
        """
        Helper method to see if this is a delete vs. update
        """
        if self.identifier and (self.attachment_src == self.attachment_from is
                                None):
            return False
        else:
            return True

    @property
    def attachment_key(self):
        return self.identifier

    @classmethod
    def from_case_index_update(cls, attachment):
        if attachment.attachment_src:
            guessed = mimetypes.guess_type(attachment.attachment_src)
            if len(guessed) > 0 and guessed[0] is not None:
                mime_type = guessed[0]
            else:
                mime_type = None

            ret = cls(identifier=attachment.identifier,
                      attachment_src=attachment.attachment_src,
                      attachment_from=attachment.attachment_from,
                      attachment_name=attachment.attachment_name,
                      server_mime=mime_type)
        else:
            ret = cls(identifier=attachment.identifier)
        return ret
Esempio n. 21
0
class OpenmrsFormConfig(DocumentSchema):
    xmlns = StringProperty()

    # Used to determine the start of a visit and an encounter. The end
    # of a visit is set to one day (specifically 23:59:59) later. If not
    # given, the value defaults to when the form was completed according
    # to the device, /meta/timeEnd.
    openmrs_start_datetime = DictProperty(required=False)

    openmrs_visit_type = StringProperty()
    openmrs_encounter_type = StringProperty()
    openmrs_form = StringProperty()
    openmrs_observations = ListProperty(ObservationMapping)
    bahmni_diagnoses = ListProperty(ObservationMapping)
Esempio n. 22
0
class OpenmrsImporter(Document):
    """
    Import cases from an OpenMRS instance using a report
    """
    domain = StringProperty()
    server_url = StringProperty()  # e.g. "http://www.example.com/openmrs"
    username = StringProperty()
    password = StringProperty()

    # If a domain has multiple OpenmrsImporter instances, for which CommCare location is this one authoritative?
    location_id = StringProperty()

    # How often should cases be imported
    import_frequency = StringProperty(choices=IMPORT_FREQUENCY_CHOICES,
                                      default=IMPORT_FREQUENCY_MONTHLY)

    log_level = IntegerProperty()

    # OpenMRS UUID of the report of patients to be imported
    report_uuid = StringProperty()

    # Can include template params, e.g. {"endDate": "{{ today }}"}
    # Available template params: "today", "location"
    report_params = DictProperty()

    # The case type of imported cases
    case_type = StringProperty()

    # The ID of the owner of imported cases, if all imported cases are to have the same owner. To assign imported
    # cases to different owners, see `location_type` below.
    owner_id = StringProperty()

    # If report_params includes "{{ location }}" then location_type_name is used to determine which locations to
    # pull the report for. Those locations will need an "openmrs_uuid" param set. Imported cases will be owned by
    # the first mobile worker assigned to that location. If this OpenmrsImporter.location_id is set, only
    # sub-locations will be returned
    location_type_name = StringProperty()

    # external_id should always be the OpenMRS UUID of the patient (and not, for example, a national ID number)
    # because it is immutable. external_id_column is the column that contains the UUID
    external_id_column = StringProperty()

    # Space-separated column(s) to be concatenated to create the case name (e.g. "givenName familyName")
    name_columns = StringProperty()

    column_map = ListProperty(ColumnMapping)

    def __str__(self):
        return self.server_url
Esempio n. 23
0
class ObservationMapping(DocumentSchema):
    """
    Maps OpenMRS Observations to value sources.

    e.g.::

        {
          "concept": "123456":
          "value": {
            "form_question": "/data/trimester"
            "value_map": {
              "first": "123456",
              "second": "123456",
              "third": "123456"
            },
            "direction": "out"
          }
        }

    """
    # If no concept is specified, this ObservationMapping is used for
    # setting a case property or creating an extension case for any
    # concept
    concept = StringProperty(required=True, default=ALL_CONCEPTS)
    value = DictProperty()

    # Import Observations as case updates from Atom feed. (Case type is
    # OpenmrsRepeater.white_listed_case_types[0]; Atom feed integration
    # requires len(OpenmrsRepeater.white_listed_case_types) == 1.)
    case_property = StringProperty(required=False)

    # Use indexed_case_mapping to create an extension case or a child
    # case instead of setting a case property. Used for referrals.
    indexed_case_mapping = SchemaProperty(
        IndexedCaseMapping, required=False, default=None, exclude_if_none=True
    )

    def __eq__(self, other):
        return (
            isinstance(other, self.__class__)
            and other.concept == self.concept
            and other.value == self.value
            and other.case_property == self.case_property
        )
Esempio n. 24
0
class CommCareCaseAttachment(LooselyEqualDocumentSchema, IsImageMixin):
    identifier = StringProperty()
    attachment_src = StringProperty()
    attachment_from = StringProperty()
    attachment_name = StringProperty()
    server_mime = StringProperty()  # Server detected MIME
    server_md5 = StringProperty()  # Couch detected hash

    attachment_size = IntegerProperty()  # file size
    attachment_properties = DictProperty(
    )  # width, height, other relevant metadata

    @property
    def content_type(self):
        return self.server_mime

    @property
    def is_present(self):
        """
        Helper method to see if this is a delete vs. update

        NOTE this is related to but reversed logic from
        `casexml.apps.case.xml.parser.CaseAttachment.is_delete`.
        """
        return self.attachment_src or self.attachment_from

    @classmethod
    def from_case_index_update(cls, attachment):
        if attachment.attachment_src or attachment.attachment_from:
            guessed = mimetypes.guess_type(attachment.attachment_src)
            if len(guessed) > 0 and guessed[0] is not None:
                mime_type = guessed[0]
            else:
                mime_type = None

            ret = cls(identifier=attachment.identifier,
                      attachment_src=attachment.attachment_src,
                      attachment_from=attachment.attachment_from,
                      attachment_name=attachment.attachment_name,
                      server_mime=mime_type)
        else:
            ret = cls(identifier=attachment.identifier)
        return ret
Esempio n. 25
0
class HQMediaMapItem(DocumentSchema):

    multimedia_id = StringProperty()
    media_type = StringProperty()
    output_size = DictProperty()
    version = IntegerProperty()
    unique_id = StringProperty()

    @property
    def url(self):
        return reverse("hqmedia_download",
                       args=[self.media_type, self.multimedia_id
                             ]) if self.multimedia_id else ""

    @classmethod
    def gen_unique_id(cls, m_id, path):
        return hashlib.md5(
            b"%s: %s" %
            (path.encode('utf-8'), m_id.encode('utf-8'))).hexdigest()
Esempio n. 26
0
class CustomReportConfiguration(JsonObject):
    """
    For statically defined reports based off of custom data sources
    """
    domains = ListProperty()
    report_id = StringProperty()
    data_source_table = StringProperty()
    config = DictProperty()

    @classmethod
    def get_doc_id(cls, domain, report_id):
        return '{}{}-{}'.format(CUSTOM_PREFIX, domain, report_id)

    @classmethod
    def all(cls):
        for path in settings.CUSTOM_UCR_REPORTS:
            with open(path) as f:
                wrapped = cls.wrap(json.load(f))
                for domain in wrapped.domains:
                    doc = copy(wrapped.config)
                    doc['domain'] = domain
                    doc['_id'] = cls.get_doc_id(domain, wrapped.report_id)
                    doc['config_id'] = CustomDataSourceConfiguration.get_doc_id(domain, wrapped.data_source_table)
                    yield ReportConfiguration.wrap(doc)

    @classmethod
    def by_domain(cls, domain):
        """
        Returns a list of ReportConfiguration objects, NOT CustomReportConfigurations.
        """
        return [ds for ds in cls.all() if ds.domain == domain]

    @classmethod
    def by_id(cls, config_id):
        """
        Returns a ReportConfiguration object, NOT CustomReportConfigurations.
        """
        for ds in cls.all():
            if ds.get_id == config_id:
                return ds
        raise BadSpecError(_('The report configuration referenced by this report could '
                             'not be found.'))
Esempio n. 27
0
class CustomDataSourceConfiguration(JsonObject):
    """
    For custom data sources maintained in the repository
    """
    _datasource_id_prefix = CUSTOM_PREFIX
    domains = ListProperty()
    config = DictProperty()

    @classmethod
    def get_doc_id(cls, domain, table_id):
        return '{}{}-{}'.format(cls._datasource_id_prefix, domain, table_id)

    @classmethod
    def all(cls):
        for path in settings.CUSTOM_DATA_SOURCES:
            with open(path) as f:
                wrapped = cls.wrap(json.load(f))
                for domain in wrapped.domains:
                    doc = copy(wrapped.config)
                    doc['domain'] = domain
                    doc['_id'] = cls.get_doc_id(domain, doc['table_id'])
                    yield DataSourceConfiguration.wrap(doc)

    @classmethod
    def by_domain(cls, domain):
        """
        Returns a list of DataSourceConfiguration objects,
        NOT CustomDataSourceConfigurations.
        """
        return [ds for ds in cls.all() if ds.domain == domain]

    @classmethod
    def by_id(cls, config_id):
        """
        Returns a DataSourceConfiguration object,
        NOT a CustomDataSourceConfiguration.
        """
        for ds in cls.all():
            if ds.get_id == config_id:
                return ds
        raise BadSpecError(_('The data source referenced by this report could '
                             'not be found.'))
Esempio n. 28
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()
Esempio n. 29
0
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)
Esempio n. 30
0
class BlobMixin(Document):

    class Meta(object):
        abstract = True

    # TODO evaluate all uses of `external_blobs`
    external_blobs = DictProperty(BlobMetaRef)

    # When true, fallback to couch on fetch and delete if blob is not
    # found in blobdb. Set this to True on subclasses that are in the
    # process of being migrated. When this is false (the default) the
    # methods on this mixin will not touch couchdb.
    _migrating_blobs_from_couch = False

    _atomic_blobs = None

    @classmethod
    def wrap(cls, data):
        if data.get("external_blobs"):
            doc_id = safe_id(data["_id"])
            dbname = _get_couchdb_name(cls)
            normalize = BlobMetaRef._normalize_json
            blobs = {}
            normalized = False
            for key, value in data["external_blobs"].items():
                if value["doc_type"] == "BlobMetaRef":
                    blobs[key] = value
                else:
                    blobs[key] = normalize(dbname, data['_id'], value)
                    normalized = True
            if normalized:
                data = data.copy()
                data["external_blobs"] = blobs
        return super(BlobMixin, cls).wrap(data)

    @classproperty
    def _blobdb_type_code(cls):
        """Blob DB type code

        This is an abstract attribute that must be set on non-abstract
        subclasses of `BlobMixin`. Its value should be one of the codes
        in `corehq.blobs.CODES`.
        """
        raise NotImplementedError(
            "abstract class attribute %s._blobdb_type_code is missing" %
            cls.__name__
        )

    @property
    def blobs(self):
        """Get a dictionary of BlobMetaRef objects keyed by attachment name

        Includes CouchDB attachments if `_migrating_blobs_from_couch` is true.
        The returned value should not be mutated.
        """
        if not self._migrating_blobs_from_couch or not self._attachments:
            return self.external_blobs
        value = {name: BlobMetaRef._from_attachment(info)
            for name, info in self._attachments.items()}
        value.update(self.external_blobs)
        return value

    @document_method
    def put_attachment(self, content, name=None, content_type=None,
                       content_length=None, domain=None, type_code=None):
        """Put attachment in blob database

        See `get_short_identifier()` for restrictions on the upper bound
        for number of attachments per object.

        :param content: String or file object.
        """
        db = get_blob_db()

        if name is None:
            name = getattr(content, "name", None)
        if name is None:
            raise InvalidAttachment("cannot save attachment without name")
        if self._id is None:
            raise ResourceNotFound("cannot put attachment on unidentified document")
        if hasattr(self, "domain"):
            if domain is not None and self.domain != domain:
                raise ValueError("domain mismatch: %s != %s" % (self.domain, domain))
            domain = self.domain
        elif domain is None:
            raise ValueError("domain attribute or argument is required")
        old_meta = self.blobs.get(name)

        if isinstance(content, str):
            content = BytesIO(content.encode("utf-8"))
        elif isinstance(content, bytes):
            content = BytesIO(content)

        # do we need to worry about BlobDB reading beyond content_length?
        meta = db.put(
            content,
            domain=domain or self.domain,
            parent_id=self._id,
            name=name,
            type_code=(self._blobdb_type_code if type_code is None else type_code),
            content_type=content_type,
        )
        self.external_blobs[name] = BlobMetaRef(
            key=meta.key,
            blobmeta_id=meta.id,
            content_type=content_type,
            content_length=meta.content_length,
        )
        if self._migrating_blobs_from_couch and self._attachments:
            self._attachments.pop(name, None)
        if self._atomic_blobs is None:
            self.save()
            if old_meta and old_meta.key:
                db.delete(key=old_meta.key)
        elif old_meta and old_meta.key:
            self._atomic_blobs[name].append(old_meta.key)
        return True

    @document_method
    def fetch_attachment(self, name, stream=False):
        """Get named attachment

        :param stream: When true, return a file-like object that can be
        read at least once (streamers should not expect to seek within
        or read the contents of the returned file more than once).
        """
        db = get_blob_db()
        try:
            try:
                key = self.external_blobs[name].key
            except KeyError:
                if self._migrating_blobs_from_couch:
                    return super(BlobMixin, self) \
                        .fetch_attachment(name, stream=stream)
                raise NotFound(name)
            blob = db.get(key=key)
        except NotFound:
            raise ResourceNotFound(
                "{model} {model_id} attachment: {name!r}".format(
                    model=type(self).__name__,
                    model_id=self._id,
                    name=name,
                ))
        if stream:
            return blob

        with blob:
            return blob.read()

    def has_attachment(self, name):
        return name in self.blobs

    def delete_attachment(self, name):
        if self._migrating_blobs_from_couch and self._attachments:
            deleted = bool(self._attachments.pop(name, None))
        else:
            deleted = False
        meta = self.external_blobs.pop(name, None)
        if meta is not None:
            if self._atomic_blobs is None:
                deleted = get_blob_db().delete(key=meta.key) or deleted
            else:
                self._atomic_blobs[name].append(meta.key)
                deleted = True
        if self._atomic_blobs is None:
            self.save()
        return deleted

    @document_method
    def atomic_blobs(self, save=None):
        """Return a context manager to atomically save doc + blobs

        Usage::

            with doc.atomic_blobs():
                doc.put_attachment(...)
            # doc and blob are now saved

        Blobs saved inside the context manager will be deleted if an
        exception is raised inside the context body.

        :param save: A function to be called instead of `self.save()`
        """
        @contextmanager
        def atomic_blobs_context():
            if self._id is None:
                self._id = uuid.uuid4().hex
            old_external_blobs = dict(self.external_blobs)
            if self._migrating_blobs_from_couch:
                if self._attachments:
                    old_attachments = dict(self._attachments)
                else:
                    old_attachments = None
            atomicity = self._atomic_blobs
            self._atomic_blobs = new_deleted = defaultdict(list)
            db = get_blob_db()
            success = False
            try:
                yield
                (self.save if save is None else save)()
                success = True
            except:
                typ, exc, tb = sys.exc_info()
                # delete new blobs that were not saved
                for name, meta in self.external_blobs.items():
                    old_meta = old_external_blobs.get(name)
                    if old_meta is None or meta.key != old_meta.key:
                        db.delete(key=meta.key)
                self.external_blobs = old_external_blobs
                if self._migrating_blobs_from_couch:
                    self._attachments = old_attachments
                six.reraise(typ, exc, tb)
            finally:
                self._atomic_blobs = atomicity
            if success:
                # delete replaced blobs
                deleted = set()
                blobs = self.blobs
                for name, meta in list(old_external_blobs.items()):
                    if name not in blobs or meta.key != blobs[name].key:
                        db.delete(key=meta.key)
                        deleted.add(meta.key)
                # delete newly created blobs that were overwritten or deleted
                for key in chain.from_iterable(new_deleted.values()):
                    if key not in deleted:
                        db.delete(key=key)
        return atomic_blobs_context()