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)
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)
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()
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))
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)
class Product(JsonObject): name = StringProperty() units = StringProperty() sms_code = StringProperty() description = StringProperty() is_active = BooleanProperty() program = ObjectProperty(item_type=Program)
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) ])
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)))
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
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)
class DynamicChoiceListFilterSpec(FilterSpec): type = TypeProperty('dynamic_choice_list') show_all = BooleanProperty(default=True) datatype = DataTypeProperty(default='string') @property def choices(self): return []
class DocInfo(JsonObject): id = StringProperty() domain = StringProperty() type = StringProperty() display = StringProperty() link = StringProperty() type_display = StringProperty() is_deleted = BooleanProperty()
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()
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 []
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]
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)
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()
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]
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')
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)
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]
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
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']
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"
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]
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 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]
class DateFilterSpec(FilterSpec): compare_as_string = BooleanProperty(default=False)
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)