Esempio n. 1
0
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)
Esempio n. 2
0
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
Esempio n. 3
0
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)
Esempio n. 4
0
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)
Esempio n. 5
0
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)
Esempio n. 6
0
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)
Esempio n. 7
0
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)
Esempio n. 8
0
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
Esempio n. 9
0
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))
Esempio n. 10
0
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 []
Esempio n. 11
0
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"
Esempio n. 12
0
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)))
Esempio n. 13
0
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
Esempio n. 14
0
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()
Esempio n. 15
0
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 []
Esempio n. 16
0
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))
Esempio n. 17
0
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
Esempio n. 18
0
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)))
Esempio n. 19
0
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
Esempio n. 20
0
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"
Esempio n. 21
0
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)))
Esempio n. 22
0
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
Esempio n. 23
0
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)
Esempio n. 24
0
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
Esempio n. 25
0
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)
Esempio n. 26
0
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)))
Esempio n. 27
0
class ConstantColumnProperties(jsonobject.JsonObject):
    constant = DefaultProperty(required=True)
Esempio n. 28
0
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
Esempio n. 29
0
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
Esempio n. 30
0
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))))