class Product(JsonObject): name = StringProperty() units = StringProperty() sms_code = StringProperty() description = StringProperty() is_active = BooleanProperty() program = ObjectProperty(item_type=Program)
class SMSUser(JsonObject): id = IntegerProperty() name = StringProperty() role = StringProperty() is_active = StringProperty() supply_point = ObjectProperty(item_type=SupplyPoint) email = StringProperty() phone_numbers = ListProperty() backend = StringProperty() family_name = StringProperty() to = StringProperty() language = StringProperty()
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 ConditionalFormQuestionResponse(JsonObject): question = ObjectProperty(FormQuestionResponse) condition = ObjectProperty(FormActionCondition)
class PercentageColumn(ReportColumn): type = TypeProperty('percent') numerator = ObjectProperty(FieldColumn, required=True) denominator = ObjectProperty(FieldColumn, required=True) format = StringProperty( choices=['percent', 'fraction', 'both', 'numeric_percent', 'decimal'], default='percent') def get_column_config(self, data_source_config, lang): # todo: better checks that fields are not expand num_config = self.numerator.get_column_config(data_source_config, lang) denom_config = self.denominator.get_column_config( data_source_config, lang) return ColumnConfig( columns=[ AggregateColumn( header=self.get_header(lang), aggregate_fn=lambda n, d: { 'num': n, 'denom': d }, format_fn=self.get_format_fn(), columns=[ c.view for c in num_config.columns + denom_config.columns ], slug=self.column_id, data_slug=self.column_id, ) ], warnings=num_config.warnings + denom_config.warnings, ) def get_format_fn(self): NO_DATA_TEXT = '--' CANT_CALCULATE_TEXT = '?' class NoData(Exception): pass class BadData(Exception): pass def trap_errors(fn): def inner(*args, **kwargs): try: return fn(*args, **kwargs) except BadData: return CANT_CALCULATE_TEXT except NoData: return NO_DATA_TEXT return inner def _raw(data): if data['denom']: try: return round(float(data['num']) / float(data['denom']), 3) except (ValueError, TypeError): raise BadData() else: raise NoData() def _raw_pct(data, round_type=float): return round_type(_raw(data) * 100) @trap_errors def _clean_raw(data): return _raw(data) @trap_errors def _numeric_pct(data): return _raw_pct(data, round_type=int) @trap_errors def _pct(data): return '{0:.0f}%'.format(_raw_pct(data)) _fraction = lambda data: '{num}/{denom}'.format(**data) return { 'percent': _pct, 'fraction': _fraction, 'both': lambda data: '{} ({})'.format(_pct(data), _fraction(data)), 'numeric_percent': _numeric_pct, 'decimal': _clean_raw, }[self.format] def get_column_ids(self): # override this to include the columns for the numerator and denominator as well return [ self.column_id, self.numerator.column_id, self.denominator.column_id ] def get_fields(self, data_source_config=None, lang=None): return self.numerator.get_fields() + self.denominator.get_fields() def aggregations(self, data_source_config, lang): num_aggs = self.numerator.aggregations(data_source_config, lang) denom_aggs = self.denominator.aggregations(data_source_config, lang) return num_aggs + denom_aggs def get_es_data(self, row, data_source_config, lang, from_aggregation=True): num = self.numerator denom = self.denominator num_data = num.get_es_data(row, data_source_config, lang, from_aggregation) denom_data = denom.get_es_data(row, data_source_config, lang, from_aggregation) return { num.column_id: num_data[num.column_id], denom.column_id: denom_data[denom.column_id] }