class MapItemsExpressionSpec(JsonObject): type = TypeProperty('map_items') items_expression = DefaultProperty(required=True) map_expression = DictProperty(required=True) def configure(self, items_expression, map_expression): self._items_expression = items_expression self._map_expression = map_expression def __call__(self, doc, context=None): items = _evaluate_items_expression(self._items_expression, doc, context) return map(lambda i: self._map_expression(i, context), items)
class MonthEndDateExpressionSpec(JsonObject): type = TypeProperty('month_end_date') date_expression = DefaultProperty(required=True) def configure(self, date_expression): self._date_expression = date_expression def __call__(self, item, context=None): date_val = transform_date(self._date_expression(item, context)) if date_val is not None: first_week_day, last_day = calendar.monthrange( date_val.year, date_val.month) return datetime.date(date_val.year, date_val.month, last_day) return None
class AddHoursExpressionSpec(JsonObject): """ Below is a simple example that demonstrates the structure. The expression below will add 12 hours to a property called "visit_date". The date_expression and count_expression can be any valid expressions, or simply constants. .. code:: json { "type": "add_hours", "date_expression": { "type": "property_name", "property_name": "visit_date", }, "count_expression": 12 } """ type = TypeProperty('add_hours') date_expression = DefaultProperty(required=True) count_expression = DefaultProperty(required=True) def configure(self, date_expression, count_expression): self._date_expression = date_expression self._count_expression = count_expression def __call__(self, item, context=None): date_val = transform_datetime(self._date_expression(item, context)) int_val = transform_int(self._count_expression(item, context)) if date_val is not None and int_val is not None: return date_val + datetime.timedelta(hours=int_val) return None def __str__(self): return "add_day({date}, {count})".format( date=self._date_expression, count=self._count_expression)
class ConstantGetterSpec(JsonObject): type = TypeProperty('constant') constant = DefaultProperty() @classmethod def wrap(self, obj): if 'constant' not in obj: raise BadSpecError('"constant" property is required!') return super(ConstantGetterSpec, self).wrap(obj) def __call__(self, item, context=None): return self.constant def __str__(self): return '{}'.format(self.constant)
class MonthStartDateExpressionSpec(JsonObject): type = TypeProperty('month_start_date') date_expression = DefaultProperty(required=True) def configure(self, date_expression): self._date_expression = date_expression def __call__(self, item, context=None): date_val = transform_date(self._date_expression(item, context)) if date_val is not None: return datetime.date(date_val.year, date_val.month, 1) return None def __str__(self): return "first_day({})".format(self._date_expression)
class DiffDaysExpressionSpec(JsonObject): """ ``diff_days`` returns number of days between dates specified by ``from_date_expression`` and ``to_date_expression``. The from_date_expression and to_date_expression can be any valid expressions, or simply constants. .. code:: json { "type": "diff_days", "from_date_expression": { "type": "property_name", "property_name": "dob", }, "to_date_expression": "2016-02-01" } """ type = TypeProperty('diff_days') from_date_expression = DefaultProperty(required=True) to_date_expression = DefaultProperty(required=True) def configure(self, from_date_expression, to_date_expression): self._from_date_expression = from_date_expression self._to_date_expression = to_date_expression def __call__(self, item, context=None): from_date_val = transform_date(self._from_date_expression(item, context)) to_date_val = transform_date(self._to_date_expression(item, context)) if from_date_val is not None and to_date_val is not None: return (to_date_val - from_date_val).days return None def __str__(self): return "({}) - ({})".format(self._to_date_expression, self._from_date_expression)
class ReduceItemsExpressionSpec(JsonObject): type = TypeProperty('reduce_items') items_expression = DefaultProperty(required=True) aggregation_fn = StringProperty(required=True) def configure(self, items_expression): self._items_expression = items_expression if self.aggregation_fn not in SUPPORTED_UCR_AGGREGATIONS: raise BadSpecError( _("aggregation_fn '{}' is not valid. Valid options are: {} "). format(self.aggregation_fn, SUPPORTED_UCR_AGGREGATIONS)) def __call__(self, doc, context=None): items = _evaluate_items_expression(self._items_expression, doc, context) return aggregate_items(items, self.aggregation_fn)
class PropertyNameGetterSpec(JsonObject): type = TypeProperty('property_name') property_name = DefaultProperty(required=True) datatype = DataTypeProperty(required=False) def configure(self, property_name_expression): self._property_name_expression = property_name_expression def __call__(self, item, context=None): raw_value = item.get(self._property_name_expression(item, context)) if isinstance(item, dict) else None return transform_from_datatype(self.datatype)(raw_value) def __str__(self): value = self.property_name if self.datatype: "({datatype}){value}".format(datatype=self.datatype, value=value) return value
class ArrayIndexExpressionSpec(NoPropertyTypeCoercionMixIn, JsonObject): """ This expression returns ``doc["siblings"][0]``: .. code:: json { "type": "array_index", "array_expression": { "type": "property_name", "property_name": "siblings" }, "index_expression": { "type": "constant", "constant": 0 } } It will return nothing if the siblings property is not a list, the index isn't a number, or the indexed item doesn't exist. """ type = TypeProperty('array_index') array_expression = DictProperty(required=True) index_expression = DefaultProperty(required=True) def configure(self, array_expression, index_expression): self._array_expression = array_expression self._index_expression = index_expression def __call__(self, item, context=None): array_value = self._array_expression(item, context) if not isinstance(array_value, list): return None index_value = self._index_expression(item, context) if not isinstance(index_value, int): return None try: return array_value[index_value] except IndexError: return None def __str__(self): return "{}[{}]".format(str(self._array_expression), str(self._index_expression))
class FlattenExpressionSpec(JsonObject): type = TypeProperty('flatten') items_expression = DefaultProperty(required=True) def configure(self, items_expression): self._items_expression = items_expression def __call__(self, doc, context=None): items = _evaluate_items_expression(self._items_expression, doc, context) # all items should be iterable, if not return empty list for item in items: if not isinstance(item, list): return [] try: return (list(itertools.chain(*items))) except TypeError: return []
class ICDSUserLocation(JsonObject): """Heavily cached expression to reduce queries to Couch """ type = TypeProperty('icds_user_location') user_id_expression = DefaultProperty(required=True) def configure(self, user_id_expression): self._user_id_expression = user_id_expression def __call__(self, item, context=None): user_id = self._user_id_expression(item, context) if not user_id: return None return _get_user_location_id(user_id) def __str__(self): return "User's location id"
class MapItemsExpressionSpec(JsonObject): type = TypeProperty('map_items') items_expression = DefaultProperty(required=True) map_expression = DictProperty(required=True) def configure(self, items_expression, map_expression): self._items_expression = items_expression self._map_expression = map_expression def __call__(self, doc, context=None): items = _evaluate_items_expression(self._items_expression, doc, context) return [self._map_expression(i, context) for i in items] def __str__(self): return "map:\n{items}\nto:\n{map}\n".format( items=add_tabbed_text(str(self._items_expression)), map=add_tabbed_text(str(self._map_expression)))
class FilterItemsExpressionSpec(JsonObject): type = TypeProperty('filter_items') items_expression = DefaultProperty(required=True) filter_expression = DictProperty(required=True) def configure(self, items_expression, filter_expression): self._items_expression = items_expression self._filter_expression = filter_expression def __call__(self, doc, context=None): items = _evaluate_items_expression(self._items_expression, doc, context) values = [] for item in items: if self._filter_expression(item, context): values.append(item) return values
class Constant(JsonObject): """ Defines a readonly constant in the form whose value can be used to conditionally change form behavior. **Authors**: Gagandeep Singh """ _allow_dynamic_properties = False label = StringProperty(required=True, validators=[validators.validate_label]) text_ref = StringProperty() # Text for reference use only text_translation_id = StringProperty( ) # Raw id of text translation ('languages.Translation') value = DefaultProperty(required=True) show_on_form = BooleanProperty(required=True, default=False) include_in_answers = BooleanProperty( required=True, default=False) # Include this field in answers part of the form @property def translation(self): return Translation.objects.with_id( self.text_translation_id) if self.text_translation_id else None def __str__(self): return "<{}: {}>".format(self.__class__.__name__, self.label) def __init__(self, _obj=None, **kwargs): super(Constant, self).__init__(_obj=_obj, **kwargs) if self.show_on_form: if self.text_translation_id is None: raise VariableDefinitionError( "Text translation required for this constant since 'show_on_form' is true." ) def to_json(self): if self.text_translation_id: self.text_ref = self.translation.sentence return super(Constant, self).to_json()
class SortItemsExpressionSpec(JsonObject): ASC, DESC = "ASC", "DESC" type = TypeProperty('sort_items') items_expression = DefaultProperty(required=True) sort_expression = DictProperty(required=True) order = StringProperty(choices=[ASC, DESC], default=ASC) def configure(self, items_expression, sort_expression): self._items_expression = items_expression self._sort_expression = sort_expression def __call__(self, doc, context=None): items = _evaluate_items_expression(self._items_expression, doc, context) try: return sorted(items, key=lambda i: self._sort_expression(i, context), reverse=True if self.order == self.DESC else False) except TypeError: return []
class SubcasesExpressionSpec(JsonObject): type = TypeProperty('get_subcases') case_id_expression = DefaultProperty(required=True) def configure(self, case_id_expression): self._case_id_expression = case_id_expression def __call__(self, item, context=None): case_id = self._case_id_expression(item, context) if not case_id: return [] assert context.root_doc['domain'] return self._get_subcases(case_id, context) @ucr_context_cache(vary_on=('case_id',)) def _get_subcases(self, case_id, context): domain = context.root_doc['domain'] return [c.to_json() for c in CaseAccessors(domain).get_reverse_indexed_cases([case_id])] def __str__(self): return "get subcases for {}".format(str(self._case_id_expression))
class ArrayIndexExpressionSpec(JsonObject): type = TypeProperty('array_index') array_expression = DictProperty(required=True) index_expression = DefaultProperty(required=True) def configure(self, array_expression, index_expression): self._array_expression = array_expression self._index_expression = index_expression def __call__(self, item, context=None): array_value = self._array_expression(item, context) if not isinstance(array_value, list): return None index_value = self._index_expression(item, context) if not isinstance(index_value, int): return None try: return array_value[index_value] except IndexError: return None
class FlattenExpressionSpec(NoPropertyTypeCoercionMixIn, JsonObject): """ ``flatten`` takes list of list of objects specified by ``items_expression`` and returns one list of all objects. ``items_expression`` is any valid expression that returns a list of lists. It this doesn't evaluate to a list of lists an empty list is returned. It may be necessary to specify a ``datatype`` of ``array`` if the expression could return a single element. .. code:: json { "type": "flatten", "items_expression": {}, } """ type = TypeProperty('flatten') items_expression = DefaultProperty(required=True) def configure(self, items_expression): self._items_expression = items_expression def __call__(self, doc, context=None): items = _evaluate_items_expression(self._items_expression, doc, context) # all items should be iterable, if not return empty list for item in items: if not isinstance(item, list): return [] try: return (list(itertools.chain(*items))) except TypeError: return [] def __str__(self): return "flatten:\n{items}\n".format( items=add_tabbed_text(str(self._items_expression)))
class SplitStringExpressionSpec(JsonObject): type = TypeProperty('split_string') string_expression = DictProperty(required=True) index_expression = DefaultProperty(required=True) delimiter = StringProperty(required=False) def configure(self, string_expression, index_expression): self._string_expression = string_expression self._index_expression = index_expression def __call__(self, item, context=None): string_value = self._string_expression(item, context) if not isinstance(string_value, basestring): return None index_value = self._index_expression(item, context) if not isinstance(index_value, int): return None try: return string_value.split(self.delimiter)[index_value] except IndexError: return None
class GetAppVersion(JsonObject): type = TypeProperty('icds_get_app_version') app_version_string = DefaultProperty(required=True) def configure(self, app_version_string): self._app_version_string = app_version_string def get_version_from_app_object(self, item, app_version_from_app_string): domain_name = item.get('domain') form_accessor = FormAccessors(domain_name) form_id = item.get('form').get('meta').get('instanceID') form = form_accessor.get_form(form_id) app_build = get_build_by_version(domain_name, form.app_id, app_version_from_app_string) if app_build is None: return app_version_from_app_string elif app_build.get('value').get('doc_type') == 'LinkedApplication': return app_build.get('value').get('upstream_version') else: return app_build.get('value').get('version') def get_version_from_appversion_string(self, item, context): app_version_string = self._app_version_string(item, context) return get_version_from_appversion_text(app_version_string) def __call__(self, item, context=None): app_version = self.get_version_from_appversion_string(item, context) if settings.SERVER_ENVIRONMENT == 'india': app_version = self.get_version_from_app_object(item, app_version) return app_version def __str__(self): return "Application Version"
class FilterItemsExpressionSpec(NoPropertyTypeCoercionMixIn, JsonObject): type = TypeProperty('filter_items') items_expression = DefaultProperty(required=True) filter_expression = DictProperty(required=True) def configure(self, items_expression, filter_expression): self._items_expression = items_expression self._filter_expression = filter_expression def __call__(self, doc, context=None): items = _evaluate_items_expression(self._items_expression, doc, context) values = [] for item in items: if self._filter_expression(item, context): values.append(item) return values def __str__(self): return "filter:\n{items}\non:\n{filter}\n".format( items=add_tabbed_text(str(self._items_expression)), filter=add_tabbed_text(str(self._filter_expression)))
class SubcasesExpressionSpec(JsonObject): type = TypeProperty('get_subcases') case_id_expression = DefaultProperty(required=True) def configure(self, case_id_expression): self._case_id_expression = case_id_expression def __call__(self, item, context=None): case_id = self._case_id_expression(item, context) if not case_id: return [] assert context.root_doc['domain'] return self._get_subcases(case_id, context) def _get_subcases(self, case_id, context): domain = context.root_doc['domain'] cache_key = (self.__class__.__name__, case_id) if context.get_cache_value(cache_key) is not None: return context.get_cache_value(cache_key) subcases = [c.to_json() for c in CaseAccessors(domain).get_reverse_indexed_cases([case_id])] context.set_cache_value(cache_key, subcases) return subcases
class BooleanChoiceQuestion(JsonObject): type = TypeProperty('icds_boolean') boolean_property = DefaultProperty(required=True) true_values = ListProperty(required=True) false_values = ListProperty(required=True) nullable = BooleanProperty(default=True)
class FormsInDateExpressionSpec(NoPropertyTypeCoercionMixIn, JsonObject): type = TypeProperty('icds_get_case_forms_in_date') case_id_expression = DefaultProperty(required=True) xmlns = ListProperty(required=False) from_date_expression = DictProperty(required=False) to_date_expression = DictProperty(required=False) count = BooleanProperty(default=False) def configure(self, case_id_expression, from_date_expression=None, to_date_expression=None): self._case_id_expression = case_id_expression self._from_date_expression = from_date_expression self._to_date_expression = to_date_expression def __call__(self, item, context=None): case_id = self._case_id_expression(item, context) if self._from_date_expression: from_date = self._from_date_expression(item, context) else: from_date = None if self._to_date_expression: to_date = self._to_date_expression(item, context) else: to_date = None if not case_id: return [] assert context.root_doc['domain'] return self._get_forms(case_id, from_date, to_date, context) def _get_forms(self, case_id, from_date, to_date, context): domain = context.root_doc['domain'] xmlns_tuple = tuple(self.xmlns) cache_key = (self.__class__.__name__, case_id, self.count, from_date, to_date, xmlns_tuple) if context.get_cache_value(cache_key) is not None: return context.get_cache_value(cache_key) xform_ids = FormsInDateExpressionSpec._get_case_form_ids( case_id, context) # TODO(Emord) this will eventually break down when cases have a lot of # forms associated with them. perhaps change to intersecting two sets xforms = FormsInDateExpressionSpec._get_filtered_forms_from_es( case_id, xform_ids, context) if self.xmlns: xforms = [x for x in xforms if x['xmlns'] in xmlns_tuple] if from_date: xforms = [x for x in xforms if x['timeEnd'] >= from_date] if to_date: xforms = [x for x in xforms if x['timeEnd'] <= to_date] if self.count: count = len(xforms) context.set_cache_value(cache_key, count) return count if not ICDS_UCR_ELASTICSEARCH_DOC_LOADING.enabled( case_id, NAMESPACE_OTHER): form_ids = [x['_id'] for x in xforms] xforms = FormAccessors(domain).get_forms(form_ids) xforms = FormsInDateExpressionSpec._get_form_json_list( case_id, xforms, context, domain) context.set_cache_value(cache_key, xforms) return xforms @staticmethod def _get_filtered_forms_from_es(case_id, xform_ids, context): es_toggle_enabled = ICDS_UCR_ELASTICSEARCH_DOC_LOADING.enabled( case_id, NAMESPACE_OTHER) cache_key = (FormsInDateExpressionSpec.__name__, 'es_helper', case_id, tuple(xform_ids), es_toggle_enabled) if context.get_cache_value(cache_key) is not None: return context.get_cache_value(cache_key) source = True if es_toggle_enabled else [ 'form.meta.timeEnd', 'xmlns', '_id' ] forms = FormsInDateExpressionSpec._bulk_get_forms_from_elasticsearch( xform_ids) context.set_cache_value(cache_key, forms) return forms @staticmethod def _get_case_form_ids(case_id, context): cache_key = (FormsInDateExpressionSpec.__name__, 'helper', case_id) if context.get_cache_value(cache_key) is not None: return context.get_cache_value(cache_key) domain = context.root_doc['domain'] if case_id != context.root_doc.get( '_id') or 'xform_ids' not in context.root_doc: xform_ids = CaseAccessors(domain).get_case_xform_ids(case_id) else: xform_ids = context.root_doc.get('xform_ids') context.set_cache_value(cache_key, xform_ids) return xform_ids @staticmethod def _get_form_json_list(case_id, xforms, context, domain): domain_filtered_forms = [f for f in xforms if f.domain == domain] return [ FormsInDateExpressionSpec._get_form_json(f, context) for f in domain_filtered_forms ] @staticmethod def _get_form_json(form, context): cached_form = FormsInDateExpressionSpec._get_cached_form_json( form, context) if cached_form is not None: return cached_form form_json = form.to_json() FormsInDateExpressionSpec._set_cached_form_json( form, form_json, context) return form_json @staticmethod def _bulk_get_form_json_from_es(forms): form_ids = [form.form_id for form in forms] es_forms = FormsInDateExpressionSpec._bulk_get_forms_from_elasticsearch( form_ids) return {f['_id']: f for f in es_forms} @staticmethod def _get_cached_form_json(form, context): return context.get_cache_value( FormsInDateExpressionSpec._get_form_json_cache_key(form)) @staticmethod def _set_cached_form_json(form, form_json, context): context.set_cache_value( FormsInDateExpressionSpec._get_form_json_cache_key(form), form_json) @staticmethod def _get_form_json_cache_key(form): return (XFORM_CACHE_KEY_PREFIX, form.form_id) @staticmethod def _bulk_get_forms_from_elasticsearch(form_ids): forms = mget_query('forms', form_ids) return list( filter(None, [ FormsInDateExpressionSpec. _transform_time_end_and_filter_bad_data(f) for f in forms ])) @staticmethod def _transform_time_end_and_filter_bad_data(xform): if not xform.get('xmlns', None): return None try: time = xform['form']['meta']['timeEnd'] except KeyError: return None xform['timeEnd'] = datetime.strptime(time, '%Y-%m-%dT%H:%M:%S.%fZ').date() return xform def __str__(self): value = "case_forms[{case_id}]".format( case_id=self._case_id_expression) if self.from_date_expression or self.to_date_expression: value = "{value}[date={start} to {end}]".format( value=value, start=self._from_date_expression, end=self._to_date_expression) if self.xmlns: value = "{value}[xmlns=\n{xmlns}\n]".format( value=value, xmlns=add_tabbed_text("\n".join(self.xmlns))) if self.count: value = "count({value})".format(value=value) return value
class GetCaseHistoryByDateSpec(JsonObject): type = TypeProperty('icds_get_case_history_by_date') case_id_expression = DefaultProperty(required=False) start_date = DefaultProperty(required=False) end_date = DefaultProperty(required=False) filter = DefaultProperty(required=False)
class SortItemsExpressionSpec(NoPropertyTypeCoercionMixIn, JsonObject): """ ``sort_items`` returns a sorted list of items based on sort value of each item.The sort value of an item is specified by ``sort_expression``. By default, list will be in ascending order. Order can be changed by adding optional ``order`` expression with one of ``DESC`` (for descending) or ``ASC`` (for ascending) If a sort-value of an item is ``None``, the item will appear in the start of list. If sort-values of any two items can't be compared, an empty list is returned. ``items_expression`` can be any valid expression that returns a list. If this doesn't evaluate to a list an empty list is returned. It may be necessary to specify a ``datatype`` of ``array`` if the expression could return a single element. ``sort_expression`` can be any valid expression relative to the items in above list, that returns a value to be used as sort value. .. code:: json { "type": "sort_items", "items_expression": { "datatype": "array", "type": "property_path", "property_path": ["form", "child_repeat"] }, "sort_expression": { "type": "property_path", "property_path": ["age"] } } """ ASC, DESC = "ASC", "DESC" type = TypeProperty('sort_items') items_expression = DefaultProperty(required=True) sort_expression = DictProperty(required=True) order = StringProperty(choices=[ASC, DESC], default=ASC) def configure(self, items_expression, sort_expression): self._items_expression = items_expression self._sort_expression = sort_expression def __call__(self, doc, context=None): items = _evaluate_items_expression(self._items_expression, doc, context) try: def sort_key(item): value = self._sort_expression(item, context) # swap 0 and 1 to sort nulls last instead of first return (0 if value is None else 1), value return sorted(items, key=sort_key, reverse=True if self.order == self.DESC else False) except TypeError: return [] def __str__(self): return "sort:\n{items}\n{order} on:\n{sort}".format( items=add_tabbed_text(str(self._items_expression)), order=self.order, sort=add_tabbed_text(str(self._sort_expression)))
class ConstantColumnProperties(jsonobject.JsonObject): constant = DefaultProperty(required=True)
class FormsInDateExpressionSpec(JsonObject): type = TypeProperty('icds_get_case_forms_in_date') case_id_expression = DefaultProperty(required=True) xmlns = ListProperty(required=False) from_date_expression = DictProperty(required=False) to_date_expression = DictProperty(required=False) count = BooleanProperty(default=False) def configure(self, case_id_expression, from_date_expression=None, to_date_expression=None): self._case_id_expression = case_id_expression self._from_date_expression = from_date_expression self._to_date_expression = to_date_expression def __call__(self, item, context=None): case_id = self._case_id_expression(item, context) if self._from_date_expression: from_date = self._from_date_expression(item, context) else: from_date = None if self._to_date_expression: to_date = self._to_date_expression(item, context) else: to_date = None if not case_id: return [] assert context.root_doc['domain'] return self._get_forms(case_id, from_date, to_date, context) def _get_forms(self, case_id, from_date, to_date, context): domain = context.root_doc['domain'] xmlns_tuple = tuple(self.xmlns) cache_key = (self.__class__.__name__, case_id, self.count, from_date, to_date, xmlns_tuple) if context.get_cache_value(cache_key) is not None: return context.get_cache_value(cache_key) xform_ids = self._get_case_form_ids(case_id, context) # TODO(Emord) this will eventually break down when cases have a lot of # forms associated with them. perhaps change to intersecting two sets xforms = self._get_filtered_forms_from_es(case_id, xform_ids, context) if self.xmlns: xforms = [x for x in xforms if x['xmlns'] in xmlns_tuple] if from_date: xforms = [x for x in xforms if x['timeEnd'] >= from_date] if to_date: xforms = [x for x in xforms if x['timeEnd'] <= to_date] if self.count: count = len(xforms) context.set_cache_value(cache_key, count) return count form_ids = [x['_id'] for x in xforms] xforms = FormAccessors(domain).get_forms(form_ids) xforms = [ self._get_form_json(f, context) for f in xforms if f.domain == domain ] context.set_cache_value(cache_key, xforms) return xforms def _get_filtered_forms_from_es(self, case_id, xform_ids, context): cache_key = (self.__class__.__name__, 'es_helper', case_id, tuple(xform_ids)) if context.get_cache_value(cache_key) is not None: return context.get_cache_value(cache_key) def _transform_time_end(xform): xform = xform.get('_source', {}) if not xform.get('xmlns', None): return None try: time = xform['form']['meta']['timeEnd'] except KeyError: return None xform['timeEnd'] = datetime.strptime( time, '%Y-%m-%dT%H:%M:%S.%fZ').date() return xform forms = mget_query('forms', xform_ids, ['form.meta.timeEnd', 'xmlns', '_id']) forms = filter(None, map(_transform_time_end, forms)) context.set_cache_value(cache_key, forms) return forms def _get_case_form_ids(self, case_id, context): cache_key = (self.__class__.__name__, 'helper', case_id) if context.get_cache_value(cache_key) is not None: return context.get_cache_value(cache_key) domain = context.root_doc['domain'] xform_ids = CaseAccessors(domain).get_case_xform_ids(case_id) context.set_cache_value(cache_key, xform_ids) return xform_ids def _get_form_json(self, form, context): cache_key = (XFORM_CACHE_KEY_PREFIX, form.get_id) if context.get_cache_value(cache_key) is not None: return context.get_cache_value(cache_key) form_json = form.to_json() context.set_cache_value(cache_key, form_json) return form_json
class AWCOwnerId(JsonObject): """ This class and subclasses are intended to allow us to use one expression for all case data sources and return the correct owner id of a case. It must also appropriately handle the case where some cases are in the original ICDS hierarchy (AWC owner id on every case) and the new hierarchy. REACH is proposing changing the case structure to rely on extension cases that do not include an owner on each case. The owner(s) will be determined by assignment cases that are extensions of the household. assignment (AWC) +------>household<------+ assignment (Village) ^ | + person ^ | + child_health/ccs_record """ type = TypeProperty('icds_awc_owner_id') case_id_expression = DefaultProperty(required=True) index_identifier = 'owner_awc' def configure(self, case_id_expression): self._case_id_expression = case_id_expression def __call__(self, item, context=None): case_id = self._case_id_expression(item, context) if not case_id: return None if item['owner_id'] and item['owner_id'] != UNOWNED_EXTENSION_OWNER_ID: return item['owner_id'] return self._owner_from_extension(item, context, case_id) def _owner_from_extension(self, item, context, case_id): if item['owner_id'] == UNOWNED_EXTENSION_OWNER_ID: accessor = CaseAccessors(context.root_doc['domain']) indices = {case_id} last_indices = set() loop_counter = 0 # only allow 10 iterations at most in case there are loops while indices != last_indices and loop_counter < 10: last_indices |= indices indices |= set(accessor.get_indexed_case_ids(indices)) loop_counter += 1 cases = accessor.get_cases(list(indices)) cases_with_owners = [ case for case in cases if case.owner_id and case.owner_id != UNOWNED_EXTENSION_OWNER_ID ] if len(cases_with_owners) != 0: # This shouldn't happen in this world, but will feasibly # occur depending on our migration path from parent/child -> # extension cases. Once a migration path is decided revisit # alerting in this case return cases_with_owners[0].owner_id household_cases = [ case for case in cases if case.type == 'household' ] assert len(household_cases) == 1 household_case = household_cases[0] subcases = household_case.get_subcases( index_identifier=self.index_identifier) cases_with_owners = [ case for case in subcases if case.owner_id and case.owner_id != UNOWNED_EXTENSION_OWNER_ID ] assert len(cases_with_owners) == 1 assert cases_with_owners[0].type == 'assignment' return cases_with_owners[0].owner_id
class SwitchExpressionSpec(JsonObject): """ This expression returns the value of the expression for the case that matches the switch on expression. Note that case values may only be strings at this time. .. code:: json { "type": "switch", "switch_on": { "type": "property_name", "property_name": "district" }, "cases": { "north": { "type": "constant", "constant": 4000 }, "south": { "type": "constant", "constant": 2500 }, "east": { "type": "constant", "constant": 3300 }, "west": { "type": "constant", "constant": 65 }, }, "default": { "type": "constant", "constant": 0 } } """ type = TypeProperty('switch') switch_on = DefaultProperty(required=True) cases = DefaultProperty(required=True) default = DefaultProperty(required=True) def configure(self, switch_on_expression, case_expressions, default_expression): self._switch_on_expression = switch_on_expression self._case_expressions = case_expressions self._default_expression = default_expression def __call__(self, item, context=None): switch_value = self._switch_on_expression(item, context) for c in self.cases: if switch_value == c: return self._case_expressions[c](item, context) return self._default_expression(item, context) def __str__(self): map_text = ", ".join([ "{}:{}".format(c, str(self._case_expressions[c])) for c in self.cases ]) return "switch:{expression}:\n{map}\ndefault:\n{default}".format( expression=str(self._switch_on_expression), map=add_tabbed_text(map_text), default=add_tabbed_text((str(self._default_expression))))