コード例 #1
0
ファイル: specs.py プロジェクト: soitun/commcare-hq
class ExpressionIndicatorSpec(IndicatorSpecBase):
    type = TypeProperty('expression')
    datatype = DataTypeProperty(required=True)
    is_nullable = BooleanProperty(default=True)
    is_primary_key = BooleanProperty(default=False)
    create_index = BooleanProperty(default=False)
    expression = DefaultProperty(required=True)
    transform = DictProperty(required=False)

    def parsed_expression(self, context):
        from corehq.apps.userreports.expressions.factory import ExpressionFactory
        expression = ExpressionFactory.from_spec(self.expression, context)
        datatype_transform = transform_for_datatype(self.datatype)
        if self.transform:
            generic_transform = TransformFactory.get_transform(
                self.transform).get_transform_function()
            inner_getter = TransformedGetter(expression, generic_transform)
        else:
            inner_getter = expression
        return TransformedGetter(inner_getter, datatype_transform)

    def readable_output(self, context):
        from corehq.apps.userreports.expressions.factory import ExpressionFactory
        expression_object = ExpressionFactory.from_spec(
            self.expression, context)
        return str(expression_object)
コード例 #2
0
ファイル: specs.py プロジェクト: ekush/commcare-hq
class RawIndicatorSpec(PropertyReferenceIndicatorSpecBase):
    type = TypeProperty('raw')
    datatype = DataTypeProperty(required=True)
    is_nullable = BooleanProperty(default=True)
    is_primary_key = BooleanProperty(default=False)

    @property
    def getter(self):
        transform = transform_from_datatype(self.datatype)
        getter = getter_from_property_reference(self)
        return TransformedGetter(getter, transform)
コード例 #3
0
ファイル: api.py プロジェクト: ekush/commcare-hq
class ILSUser(JsonObject):
    username = StringProperty()
    first_name = StringProperty()
    last_name = StringProperty()
    email = StringProperty()
    password = StringProperty()
    is_staff = BooleanProperty(default=False)
    is_active = BooleanProperty()
    is_superuser = BooleanProperty(default=False)
    last_login = StringProperty()
    date_joined = StringProperty()
    location = DecimalProperty()
    supply_point = IntegerProperty()
コード例 #4
0
class CaseProperty(JsonObject):
    name = StringProperty()
    forms = ListProperty(CaseFormMeta)
    short_details = ListProperty(CaseDetailMeta)
    long_details = ListProperty(CaseDetailMeta)
    has_errors = BooleanProperty()
    description = StringProperty()

    def get_form(self, form_id):
        try:
            form = next(form for form in self.forms if form.form_id == form_id)
        except StopIteration:
            form = CaseFormMeta(form_id=form_id)
            self.forms.append(form)
        return form

    def add_load(self, form_id, question):
        form = self.get_form(form_id)
        form.load_questions.append(ConditionalFormQuestionResponse(
            question=question,
            condition=None
        ))

    def add_save(self, form_id, question, condition=None):
        form = self.get_form(form_id)
        form.save_questions.append(ConditionalFormQuestionResponse(
            question=question,
            condition=(condition if condition and condition.type == 'if' else None)
        ))

    def add_detail(self, type_, module_id, header, format):
        {
            "short": self.short_details,
            "long": self.long_details,
        }[type_].append(CaseDetailMeta(module_id=module_id, header=header, format=format))
コード例 #5
0
class ReportFilter(JsonObject):
    """
    This is a spec class that is just used for validation on a ReportConfiguration object.

    These get converted to FilterSpecs (below) by the FilterFactory.
    """
    # todo: this class is silly and can likely be removed.
    type = StringProperty(required=True)
    slug = StringProperty(required=True)
    field = StringProperty(required=True)
    display = DefaultProperty()
    compare_as_string = BooleanProperty(default=False)

    _class_map = {
        'quarter': QuarterFilterValue,
        'date': DateFilterValue,
        'numeric': NumericFilterValue,
        'pre': PreFilterValue,
        'choice_list': ChoiceListFilterValue,
        'dynamic_choice_list': ChoiceListFilterValue,
        'multi_field_dynamic_choice_list': MultiFieldChoiceListFilterValue,
        'location_drilldown': LocationDrilldownFilterValue,
    }

    def create_filter_value(self, value):
        return self._class_map[self.type](self, value)
コード例 #6
0
ファイル: api.py プロジェクト: ekush/commcare-hq
class Product(JsonObject):
    name = StringProperty()
    units = StringProperty()
    sms_code = StringProperty()
    description = StringProperty()
    is_active = BooleanProperty()
    program = ObjectProperty(item_type=Program)
コード例 #7
0
ファイル: specs.py プロジェクト: sophiamcg/commcare-hq
class LocationColumn(ReportColumn):
    type = TypeProperty('location')
    field = StringProperty(required=True)
    sortable = BooleanProperty(default=False)

    def format_data(self, data):
        column_name = self.column_id
        for row in data:
            try:
                row[column_name] = '{g.latitude} {g.longitude} {g.altitude} {g.accuracy}'.format(
                    g=GeoPointProperty().wrap(row[column_name]))
            except BadValueError:
                row[column_name] = u'{} ({})'.format(row[column_name],
                                                     _('Invalid Location'))

    def get_column_config(self, data_source_config, lang):
        return ColumnConfig(columns=[
            DatabaseColumn(header=self.get_header(lang),
                           agg_column=SimpleColumn(self.field,
                                                   alias=self.column_id),
                           sortable=self.sortable,
                           data_slug=self.column_id,
                           format_fn=self.get_format_fn(),
                           help_text=self.description)
        ])
コード例 #8
0
ファイル: specs.py プロジェクト: sophiamcg/commcare-hq
class ReportColumn(BaseReportColumn):
    transform = DictProperty()
    calculate_total = BooleanProperty(default=False)

    def format_data(self, data):
        """
        Subclasses can apply formatting to the entire dataset.
        """
        pass

    def get_format_fn(self):
        """
        A function that gets applied to the data just in time before the report is rendered.
        """
        if self.transform:
            return TransformFactory.get_transform(
                self.transform).get_transform_function()
        return None

    def get_query_column_ids(self):
        """
        Gets column IDs associated with a query. These could be different from
        the normal column_ids if the same column ends up in multiple columns in
        the query (e.g. an aggregate date splitting into year and month)
        """
        raise InvalidQueryColumn(
            _("You can't query on columns of type {}".format(self.type)))
コード例 #9
0
class CaseTypeMeta(JsonObject):
    name = StringProperty(required=True)
    relationships = DictProperty()  # relationship name -> case type
    properties = ListProperty(CaseProperty)  # property -> CaseProperty
    opened_by = DictProperty(ConditionList)  # form_ids -> [FormActionCondition, ...]
    closed_by = DictProperty(ConditionList)  # form_ids -> [FormActionCondition, ...]
    error = StringProperty()
    has_errors = BooleanProperty()

    def get_property(self, name, allow_parent=False):
        if not allow_parent:
            assert '/' not in name, "Add parent properties to the correct case type"
        try:
            prop = next(prop for prop in self.properties if prop.name == name)
        except StopIteration:
            prop = CaseProperty(name=name)
            self.properties.append(prop)
        return prop

    def add_opener(self, form_id, condition):
        openers = self.opened_by.get(form_id, ConditionList())
        if condition.type == 'if':
            # only add optional conditions
            openers.conditions.append(condition)
        self.opened_by[form_id] = openers

    def add_closer(self, form_id, condition):
        closers = self.closed_by.get(form_id, ConditionList())
        if condition.type == 'if':
            # only add optional conditions
            closers.conditions.append(condition)
        self.closed_by[form_id] = closers
コード例 #10
0
ファイル: api.py プロジェクト: ekush/commcare-hq
class EWSUser(JsonObject):
    username = StringProperty()
    first_name = StringProperty()
    last_name = StringProperty()
    email = StringProperty()
    password = StringProperty()
    is_staff = BooleanProperty()
    is_active = BooleanProperty()
    is_superuser = BooleanProperty()
    last_login = StringProperty()
    date_joined = StringProperty()
    location = IntegerProperty()
    supply_point = IntegerProperty()
    sms_notifications = BooleanProperty()
    organization = StringProperty()
    groups = ListProperty(item_type=Group)
    contact = ObjectProperty(item_type=SMSUser)
コード例 #11
0
class DynamicChoiceListFilterSpec(FilterSpec):
    type = TypeProperty('dynamic_choice_list')
    show_all = BooleanProperty(default=True)
    datatype = DataTypeProperty(default='string')

    @property
    def choices(self):
        return []
コード例 #12
0
ファイル: doc_info.py プロジェクト: soitun/commcare-hq
class DocInfo(JsonObject):
    id = StringProperty()
    domain = StringProperty()
    type = StringProperty()

    display = StringProperty()
    link = StringProperty()
    type_display = StringProperty()
    is_deleted = BooleanProperty()
コード例 #13
0
ファイル: api.py プロジェクト: ekush/commcare-hq
class Location(JsonObject):
    id = IntegerProperty()
    name = StringProperty()
    type = StringProperty()
    parent_id = IntegerProperty()
    latitude = StringProperty()
    longitude = StringProperty()
    code = StringProperty()
    groups = ListProperty()
    supervised_by = IntegerProperty()
    supply_points = ListProperty(item_type=SupplyPoint)
    is_active = BooleanProperty()
コード例 #14
0
class DynamicChoiceListFilterSpec(FilterSpec):
    type = TypeProperty('dynamic_choice_list')
    show_all = BooleanProperty(default=True)
    datatype = DataTypeProperty(default='string')
    choice_provider = DictProperty()

    def get_choice_provider_spec(self):
        return self.choice_provider or {'type': DATA_SOURCE_COLUMN}

    @property
    def choices(self):
        return []
コード例 #15
0
ファイル: specs.py プロジェクト: yonglehou/commcare-hq
class FieldColumn(ReportColumn):
    type = TypeProperty('field')
    field = StringProperty(required=True)
    aggregation = StringProperty(
        choices=SQLAGG_COLUMN_MAP.keys(),
        required=True,
    )
    format = StringProperty(default='default', choices=[
        'default',
        'percent_of_total',
    ])
    sortable = BooleanProperty(default=False)

    @classmethod
    def wrap(cls, obj):
        # lazy migrations for legacy data.
        # todo: remove once all reports are on new format
        # 1. set column_id to alias, or field if no alias found
        _add_column_id_if_missing(obj)
        # 2. if aggregation='expand' convert to ExpandedColumn
        if obj.get('aggregation') == 'expand':
            del obj['aggregation']
            obj['type'] = 'expanded'
            return ExpandedColumn.wrap(obj)

        return super(FieldColumn, cls).wrap(obj)

    def format_data(self, data):
        if self.format == 'percent_of_total':
            column_name = self.column_id
            total = sum(row[column_name] for row in data)
            for row in data:
                row[column_name] = '{:.0%}'.format(
                    float(row[column_name]) / total
                )

    def get_sql_column_config(self, data_source_config, lang):
        return SqlColumnConfig(columns=[
            DatabaseColumn(
                header=self.get_header(lang),
                agg_column=SQLAGG_COLUMN_MAP[self.aggregation](self.field, alias=self.column_id),
                sortable=self.sortable,
                data_slug=self.column_id,
                format_fn=self.get_format_fn(),
                help_text=self.description
            )
        ])

    def get_query_column_ids(self):
        return [self.column_id]
コード例 #16
0
class ReportFilter(JsonObject):
    type = StringProperty(required=True)
    slug = StringProperty(required=True)
    field = StringProperty(required=True)
    display = DefaultProperty()
    compare_as_string = BooleanProperty(default=False)

    def create_filter_value(self, value):
        return {
            'date': DateFilterValue,
            'numeric': NumericFilterValue,
            'choice_list': ChoiceListFilterValue,
            'dynamic_choice_list': ChoiceListFilterValue,
        }[self.type](self, value)
コード例 #17
0
ファイル: api.py プロジェクト: ekush/commcare-hq
class SupplyPoint(JsonObject):
    id = IntegerProperty()
    active = BooleanProperty()
    code = StringProperty()
    groups = ListProperty()
    last_reported = StringProperty()
    name = StringProperty()
    primary_reporter = IntegerProperty()
    supervised_by = IntegerProperty()
    supplied_by = IntegerProperty()
    type = StringProperty()
    location_id = IntegerProperty()
    products = ListProperty()
    incharges = ListProperty()
コード例 #18
0
ファイル: specs.py プロジェクト: yonglehou/commcare-hq
class ReportColumn(JsonObject):
    type = StringProperty(required=True)
    column_id = StringProperty(required=True)
    display = DefaultProperty()
    description = StringProperty()
    transform = DictProperty()
    calculate_total = BooleanProperty(default=False)

    @classmethod
    def wrap(cls, obj):
        if 'display' not in obj and 'column_id' in obj:
            obj['display'] = obj['column_id']
        return super(ReportColumn, cls).wrap(obj)

    def format_data(self, data):
        """
        Subclasses can apply formatting to the entire dataset.
        """
        pass

    def get_sql_column_config(self, data_source_config, lang):
        raise NotImplementedError('subclasses must override this')

    def get_format_fn(self):
        """
        A function that gets applied to the data just in time before the report is rendered.
        """
        if self.transform:
            return TransformFactory.get_transform(self.transform).get_transform_function()
        return None

    def get_query_column_ids(self):
        """
        Gets column IDs associated with a query. These could be different from
        the normal column_ids if the same column ends up in multiple columns in
        the query (e.g. an aggregate date splitting into year and month)
        """
        raise InvalidQueryColumn(_("You can't query on columns of type {}".format(self.type)))

    def get_header(self, lang):
        return localize(self.display, lang)

    def get_column_ids(self):
        """
        Used as an abstraction layer for columns that can contain more than one data column
        (for example, PercentageColumns).
        """
        return [self.column_id]
コード例 #19
0
ファイル: specs.py プロジェクト: sophiamcg/commcare-hq
class BaseReportColumn(JsonObject):
    type = StringProperty(required=True)
    column_id = StringProperty(required=True)
    display = DefaultProperty()
    description = StringProperty()
    visible = BooleanProperty(default=True)

    @classmethod
    def wrap(cls, obj):
        if 'display' not in obj and 'column_id' in obj:
            obj['display'] = obj['column_id']
        return super(BaseReportColumn, cls).wrap(obj)

    def get_header(self, lang):
        return localize(self.display, lang)

    def get_column_ids(self):
        """
        Used as an abstraction layer for columns that can contain more than one data column
        (for example, PercentageColumns).
        """
        return [self.column_id]

    def get_column_config(self, data_source_config, lang):
        raise NotImplementedError('subclasses must override this')

    def aggregations(self, data_source_config, lang):
        """
        Returns a list of aggregations to be used in an ES query
        """
        raise NotImplementedError('subclasses must override this')

    def get_es_data(self,
                    row,
                    data_source_config,
                    lang,
                    from_aggregation=True):
        """
        Returns a dictionary of the data of this column from an ES query
        """
        raise NotImplementedError('subclasses must override this')

    def get_fields(self, data_source_config=None, lang=None):
        """
        Get database fields associated with this column. Could be one, or more
        if a column is a function of two values in the DB (e.g. PercentageColumn)
        """
        raise NotImplementedError('subclasses must override this')
コード例 #20
0
class MultibarChartSpec(ChartSpec):
    type = TypeProperty('multibar')
    aggregation_column = StringProperty()
    x_axis_column = StringProperty(required=True)
    y_axis_columns = ListProperty(GraphDisplayColumn)
    is_stacked = BooleanProperty(default=False)

    @classmethod
    def wrap(cls, obj):
        def _convert_columns_to_properly_dicts(cols):
            for column in cols:
                if isinstance(column, str):
                    yield {'column_id': column, 'display': column}
                else:
                    yield column
        obj['y_axis_columns'] = list(_convert_columns_to_properly_dicts(obj.get('y_axis_columns', [])))
        return super(MultibarChartSpec, cls).wrap(obj)
コード例 #21
0
class _CaseExpressionColumn(ReportColumn):
    """ Wraps a SQLAlchemy "case" expression:

    http://docs.sqlalchemy.org/en/latest/core/sqlelement.html#sqlalchemy.sql.expression.case
    """
    type = None
    whens = DictProperty()
    else_ = StringProperty()
    sortable = BooleanProperty(default=False)

    @classmethod
    def restricted_to_static(cls):
        # The conditional expressions used here don't have sufficient safety checks,
        # so this column type is only available for static reports.  To release this,
        # we should require that conditions be expressed using a PreFilterValue type
        # syntax, as attempted in commit 02833e28b7aaf5e0a71741244841ad9910ffb1e5
        return True

    _agg_column_type = None

    def get_column_config(self, data_source_config, lang):
        if not self.type and self._agg_column_type:
            raise NotImplementedError(
                "subclasses must define a type and column_type")
        return ColumnConfig(columns=[
            DatabaseColumn(
                header=self.get_header(lang),
                agg_column=self._agg_column_type(
                    whens=self.get_whens(),
                    else_=self.else_,
                    alias=self.column_id,
                ),
                sortable=self.sortable,
                data_slug=self.column_id,
                format_fn=self.get_format_fn(),
                help_text=self.description,
                visible=self.visible,
            )
        ], )

    def get_whens(self):
        return self.whens

    def get_query_column_ids(self):
        return [self.column_id]
コード例 #22
0
class FilterSpec(JsonObject):
    """
    This is the spec for a report filter - a thing that should show up as a UI filter element
    in a report (like a date picker or a select list).
    """
    type = StringProperty(
        required=True,
        choices=['date', 'numeric', 'choice_list', 'dynamic_choice_list'])
    slug = StringProperty(
        required=True)  # this shows up as the ID in the filter HTML
    field = StringProperty(
        required=True)  # this is the actual column that is queried
    display = DefaultProperty()
    required = BooleanProperty(default=False)
    datatype = DataTypeProperty(default='string')

    def get_display(self):
        return self.display or self.slug
コード例 #23
0
class FormQuestion(JsonObject):
    label = StringProperty()
    translations = DictProperty(exclude_if_none=True)
    tag = StringProperty()
    type = StringProperty(choices=list(VELLUM_TYPES))
    value = StringProperty()
    repeat = StringProperty()
    group = StringProperty()
    options = ListProperty(FormQuestionOption)
    calculate = StringProperty()
    relevant = StringProperty()
    required = BooleanProperty()
    comment = StringProperty()
    setvalue = StringProperty()
    data_source = DictProperty(exclude_if_none=True)

    @property
    def icon(self):
        try:
            return VELLUM_TYPES[self.type]['icon']
        except KeyError:
            return 'fa fa-question-circle'

    @property
    def relative_value(self):
        if self.group:
            prefix = self.group + '/'
            if self.value.startswith(prefix):
                return self.value[len(prefix):]
        return '/'.join(self.value.split('/')[2:])

    @property
    def option_values(self):
        return [o.value for o in self.options]

    @property
    def editable(self):
        if not self.type:
            return True
        vtype = VELLUM_TYPES[self.type]
        if 'editable' not in vtype:
            return False
        return vtype['editable']
コード例 #24
0
class OPMCase(CommCareCase):
    opened_on = DateTimeProperty(datetime(2010, 1, 1))
    block_name = StringProperty("Sahora")
    type = StringProperty("pregnancy")
    closed = BooleanProperty(default=False)
    closed_on = DateTimeProperty()
    awc_name = StringProperty("Atri")
    owner_id = StringProperty("Sahora")

    def __init__(self, forms=None, **kwargs):
        super(OPMCase, self).__init__(**kwargs)
        self._id = "z640804p375ps5u2yx7"
        self._fake_forms = forms if forms is not None else []

    def get_forms(self):
        return self._fake_forms

    class Meta:
        # This is necessary otherwise tests get sad
        app_label = "opm"
コード例 #25
0
class _CaseExpressionColumn(ReportColumn):
    """ Wraps a SQLAlchemy "case" expression:

    http://docs.sqlalchemy.org/en/latest/core/sqlelement.html#sqlalchemy.sql.expression.case
    """
    type = None
    whens = ListProperty(
        ListProperty)  # List of (expression, bind1, bind2, ... value) tuples
    else_ = StringProperty()
    sortable = BooleanProperty(default=False)

    _agg_column_type = None

    def get_column_config(self, data_source_config, lang):
        if not self.type and self._agg_column_type:
            raise NotImplementedError(
                "subclasses must define a type and column_type")
        return ColumnConfig(columns=[
            DatabaseColumn(
                header=self.get_header(lang),
                agg_column=self._agg_column_type(
                    whens=self.get_whens(),
                    else_=self.else_,
                    alias=self.column_id,
                ),
                sortable=self.sortable,
                data_slug=self.column_id,
                format_fn=self.get_format_fn(),
                help_text=self.description,
                visible=self.visible,
            )
        ], )

    def get_whens(self):
        return self.whens

    def get_query_column_ids(self):
        return [self.column_id]
コード例 #26
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)
コード例 #27
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
コード例 #28
0
ファイル: specs.py プロジェクト: sophiamcg/commcare-hq
class FieldColumn(ReportColumn):
    type = TypeProperty('field')
    field = StringProperty(required=True)
    aggregation = StringProperty(
        choices=list(SQLAGG_COLUMN_MAP),
        required=True,
    )
    format = StringProperty(default='default',
                            choices=[
                                'default',
                                'percent_of_total',
                            ])
    sortable = BooleanProperty(default=False)
    width = StringProperty(default=None, required=False)
    css_class = StringProperty(default=None, required=False)

    @classmethod
    def wrap(cls, obj):
        # lazy migrations for legacy data.
        # todo: remove once all reports are on new format
        # 1. set column_id to alias, or field if no alias found
        _add_column_id_if_missing(obj)
        # 2. if aggregation='expand' convert to ExpandedColumn
        if obj.get('aggregation') == 'expand':
            del obj['aggregation']
            obj['type'] = 'expanded'
            return ExpandedColumn.wrap(obj)

        return super(FieldColumn, cls).wrap(obj)

    def format_data(self, data):
        if self.format == 'percent_of_total':
            column_name = self.column_id
            total = sum(row[column_name] for row in data)
            for row in data:
                row[column_name] = '{:.0%}'.format(
                    float(row[column_name]) / total)

    def get_column_config(self, data_source_config, lang):
        return ColumnConfig(columns=[
            DatabaseColumn(
                header=self.get_header(lang),
                agg_column=SQLAGG_COLUMN_MAP[self.aggregation](
                    self.field, alias=self.column_id),
                sortable=self.sortable,
                data_slug=self.column_id,
                format_fn=self.get_format_fn(),
                help_text=self.description,
                visible=self.visible,
                width=self.width,
                css_class=self.css_class,
            )
        ])

    def get_fields(self, data_source_config=None, lang=None):
        return [self.field]

    def _data_source_col_config(self, data_source_config):
        return filter(lambda c: c['column_id'] == self.field,
                      data_source_config.configured_indicators)[0]

    def _column_data_type(self, data_source_config):
        return self._data_source_col_config(data_source_config).get('datatype')

    def _use_terms_aggregation_for_max_min(self, data_source_config):
        return (self.aggregation in ['max', 'min']
                and self._column_data_type(data_source_config)
                and self._column_data_type(data_source_config)
                not in ['integer', 'decimal'])

    def aggregations(self, data_source_config, lang):
        # SQL supports max and min on strings so hack it into ES
        if self._use_terms_aggregation_for_max_min(data_source_config):
            aggregation = aggregations.TermsAggregation(self.column_id,
                                                        self.field,
                                                        size=1)
            order = "desc" if self.aggregation == 'max' else 'asc'
            aggregation = aggregation.order('_term', order=order)
        else:
            aggregation = ES_AGG_MAP[self.aggregation](self.column_id,
                                                       self.field)
        return [aggregation] if aggregation else []

    def get_es_data(self,
                    row,
                    data_source_config,
                    lang,
                    from_aggregation=True):
        if not from_aggregation:
            value = row[self.field]
        elif self.aggregation == 'simple':
            value = row['past_bucket_values'][self.field]
        elif self._use_terms_aggregation_for_max_min(data_source_config):
            buckets = row[self.column_id].get('buckets', [])
            if buckets:
                value = buckets[0]['key']
            else:
                value = ''
        else:
            value = int(row[self.column_id]['value'])

        return {self.column_id: value}

    def get_query_column_ids(self):
        return [self.column_id]
コード例 #29
0
class DateFilterSpec(FilterSpec):
    compare_as_string = BooleanProperty(default=False)
コード例 #30
0
class LocationDrilldownFilterSpec(FilterSpec):
    type = TypeProperty('location_drilldown')
    include_descendants = BooleanProperty(default=False)
    # default to some random high number '99'
    max_drilldown_levels = IntegerProperty(default=99)
    ancestor_expression = DictProperty(default={}, required=False)