class ReportMeta(DocumentSchema): # `True` if this report was initially constructed by the report builder. created_by_builder = BooleanProperty(default=False) # `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'])
class RepeatRecordAttempt(DocumentSchema): cancelled = BooleanProperty(default=False) datetime = DateTimeProperty() failure_reason = StringProperty() success_response = StringProperty() next_check = DateTimeProperty() succeeded = BooleanProperty(default=False) info = StringProperty() # extra information about this attempt @property def message(self): return self.success_response if self.succeeded else self.failure_reason @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 @property def created_at(self): # Used by .../case/partials/repeat_records.html return self.datetime
class CustomDataField(JsonObject): slug = StringProperty() is_required = BooleanProperty() label = StringProperty() choices = StringListProperty() regex = StringProperty() regex_msg = StringProperty() is_multiple_choice = BooleanProperty(default=False)
class CustomDataField(JsonObject): slug = StringProperty() is_required = BooleanProperty() label = StringProperty() choices = StringListProperty() is_multiple_choice = BooleanProperty(default=False) # Currently only relevant for location fields index_in_fixture = BooleanProperty(default=False)
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 [config for config in configs if config.enabled] ] @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) 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') class Meta(object): app_label = 'ewsghana'
class MVPChildCasesByAgeIndicatorDefinition(MVPActiveCasesIndicatorDefinition): """ Returns the number of child cases that were active within the datespan provided and have a date of birth that is less than the age provided by days in age. """ max_age_in_days = IntegerProperty() min_age_in_days = IntegerProperty(default=0) show_active_only = BooleanProperty(default=True) is_dob_in_datespan = BooleanProperty(default=False) _admin_crud_class = MVPChildCasesByAgeCRUDManager def get_value_by_status(self, status, user_id, datespan): cases = self._get_cases_by_status(status, user_id, datespan) return self._filter_by_age(cases, datespan) def _filter_by_age(self, results, datespan): valid_case_ids = [] datespan = self._apply_datespan_shifts(datespan) for item in results: if item.get('value'): try: date_of_birth = dateutil.parser.parse(item['value']) valid_id = False if self.is_dob_in_datespan: if datespan.startdate <= date_of_birth <= datespan.enddate: valid_id = True else: td = datespan.enddate - date_of_birth if self.min_age_in_days <= td.days < self.max_age_in_days: valid_id = True if valid_id: valid_case_ids.append(item['id']) except Exception: logging.error("date of birth could not be parsed") return valid_case_ids def get_value(self, user_ids, datespan=None, is_debug=False): if self.show_active_only: return super(MVPChildCasesByAgeIndicatorDefinition, self).get_value(user_ids, datespan=datespan, is_debug=is_debug) else: results = self.get_raw_results(user_ids, datespan) all_cases = self._filter_by_age(results, datespan) value = len(all_cases) if is_debug: return value, list(all_cases) return value @classmethod def get_nice_name(cls): return "MVP Child Cases"
class RepeatRecordAttempt(DocumentSchema): cancelled = BooleanProperty(default=False) datetime = DateTimeProperty() failure_reason = StringProperty() success_response = StringProperty() next_check = DateTimeProperty() succeeded = BooleanProperty(default=False) info = StringProperty() # extra information about this attempt @property def message(self): return self.success_response if self.succeeded else self.failure_reason
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()
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')
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
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)
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'
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)
class LicenseAgreement(DocumentSchema): signed = BooleanProperty(default=False) type = StringProperty() date = DateTimeProperty() user_id = StringProperty() user_ip = StringProperty() version = StringProperty()
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
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)
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, }
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()
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
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)
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]
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()
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()
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
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
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()
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()
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()
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
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