def green_infrastructure(request, instance): json_data = json_from_request(request) new_data = {} increment_universal_rev = False for identifier, value in json_data.iteritems(): model, field_name = dotted_split(identifier, 2, maxsplit=1) if field_name.startswith('config.map_feature_types') or \ field_name.startswith('config.map_feature_config'): if not instance.feature_enabled('green_infrastructure'): raise PermissionDenied("The Green Infrastructure module is " "not enabled for this tree map") increment_universal_rev = True if field_name.startswith('config.map_feature_types'): __, __, mft_name = dotted_split(field_name, 3, maxsplit=2) if value: instance.add_map_feature_types([mft_name]) else: instance.remove_map_feature_types([mft_name]) else: new_data[identifier] = value error_dict = {} _validate_and_set_individual_values(new_data, instance, error_dict) _cross_validate_values(new_data, instance, error_dict) if error_dict: raise ValidationError(package_field_errors('instance', ValidationError(error_dict))) if increment_universal_rev: instance.update_universal_rev() instance.save() return {'ok': True}
def green_infrastructure(request, instance): json_data = json_from_request(request) new_data = {} increment_universal_rev = False for identifier, value in json_data.iteritems(): model, field_name = dotted_split(identifier, 2, maxsplit=1) if field_name.startswith('config.map_feature_types') or \ field_name.startswith('config.map_feature_config'): if not instance.feature_enabled('green_infrastructure'): raise PermissionDenied("The Green Infrastructure module is " "not enabled for this tree map") increment_universal_rev = True if field_name.startswith('config.map_feature_types'): __, __, mft_name = dotted_split(field_name, 3, maxsplit=2) if value: instance.add_map_feature_types([mft_name]) else: instance.remove_map_feature_types([mft_name]) else: new_data[identifier] = value error_dict = {} _validate_and_set_individual_values(new_data, instance, error_dict) _cross_validate_values(new_data, instance, error_dict) if error_dict: raise ValidationError( package_field_errors('instance', ValidationError(error_dict))) if increment_universal_rev: instance.update_universal_rev() instance.save() return {'ok': True}
def _validate_and_set_individual_values(json_data, instance, error_dict): errors = None INVALID_KEY_MESSAGE = _("An invalid key was sent in the request") for identifier, value in json_data.iteritems(): if not '.' in identifier: error_dict[identifier] = [INVALID_KEY_MESSAGE] __, field_name = dotted_split(identifier, 2, maxsplit=1) if not identifier.startswith('instance.config.'): error_dict[field_name] = [INVALID_KEY_MESSAGE] continue if field_name == 'config.annual_rainfall_inches': errors = _annual_rainfall_validator(value) if errors is None: _set_annual_rainfall(value, instance) elif field_name.startswith('config.map_feature_config'): errors = _map_feature_config_validator(field_name, value, instance) if errors is None: _set_map_feature_config(field_name, value, instance) elif field_name.startswith('config.terms'): errors = _terminology_validator(field_name, value, instance) if errors is None: set_attr_on_json_field(instance, field_name, value) else: errors = [INVALID_KEY_MESSAGE] if errors is not None: error_dict[field_name] = errors
def _update_instance_fields(json_data, instance, validation_fn=None): error_dict = {} for identifier, value in json_data.iteritems(): model, field_name = dotted_split(identifier, 2, maxsplit=1) obj = instance if validation_fn: errors = validation_fn(field_name, value, model) if errors is not None: error_dict[field_name] = errors if is_json_field_reference(field_name): set_attr_on_json_field(obj, field_name, value) else: setattr(obj, field_name, value) if error_dict: validation_error = ValidationError(error_dict) else: try: validate_is_public(instance) instance.save() return {'ok': True} except ValidationError, ve: validation_error = ve
def validate_and_save_field_perm(role, field_perm): for model_field_name, perm_type in field_perm.iteritems(): model_name, field_name = dotted_split(model_field_name, 2) validate_model_name(model_name, valid_field_model_names) field_perm = field_perms.get((role, model_field_name), None) create = field_perm is None if create: field_perm = FieldPermission.objects.create( field_name=field_name, model_name=model_name, role=role, instance=role.instance) perm_type = int(perm_type) if create or field_perm.permission_level != perm_type: valid_levels = [level for __, level in options_for_permission(field_perm)] if perm_type in valid_levels: field_perm.permission_level = perm_type field_perm.save() else: raise Exception('Invalid field type ' '(allowed %s, given %s)' % (valid_levels, perm_type)) notifications = instance.config.get('udf_notifications', []) if field_perm.full_name in notifications: remove_udf_notification(instance, field_perm.full_name)
def test_annual_rainfall_unit_conversion(self): rainfall_unit_key, rainfall_unit = get_value_display_attr( self.instance, 'greenInfrastructure', 'rainfall', 'units') self.assertEqual(rainfall_unit, 'in') __, __, unit_key = dotted_split(rainfall_unit_key, 3, maxsplit=2) self.assertEqual(rainfall_unit, 'in') config_key = 'annual_rainfall_inches' request_key = 'instance.config.annual_rainfall_inches' updates = {request_key: 10} json_updates = json.dumps(updates) request = make_request(method='PUT', body=json_updates, user=self.commander) green_infrastructure(request, self.instance) self.assertEqual(self.instance.config[config_key], 10.0) self.instance.config[unit_key] = 'cm' config_key = 'annual_rainfall_inches' request_key = 'instance.config.{}'.format(config_key) updates = {request_key: 25.4} json_updates = json.dumps(updates) request = make_request(method='PUT', body=json_updates, user=self.commander) green_infrastructure(request, self.instance) self.assertEqual(self.instance.config[config_key], 10.0)
def _update_instance_fields(json_data, instance, validation_fn=None, should_update_universal_rev=False): error_dict = {} for identifier, value in json_data.iteritems(): model, field_name = dotted_split(identifier, 2, maxsplit=1) obj = instance if validation_fn: errors = validation_fn(field_name, value, model) if errors is not None: error_dict[field_name] = errors if is_json_field_reference(field_name): set_attr_on_json_field(obj, field_name, value) else: setattr(obj, field_name, value) if error_dict: validation_error = ValidationError(error_dict) else: try: validate_is_public(instance) instance.save() if should_update_universal_rev: instance.update_universal_rev() return {'ok': True} except ValidationError, ve: validation_error = ve
def update_benefits(request, instance): conv = instance.eco_benefits_conversion or _get_default_conversions() valid_fields = ('currency_symbol', 'electricity_kwh_to_currency', 'natural_gas_kbtu_to_currency', 'h20_gal_to_currency', 'co2_lb_to_currency', 'o3_lb_to_currency', 'nox_lb_to_currency', 'pm10_lb_to_currency', 'sox_lb_to_currency', 'voc_lb_to_currency') valid_fields = [ "benefitCurrencyConversion." + field for field in valid_fields ] updated_values = json_from_request(request) for field, value in updated_values.iteritems(): if field in valid_fields: field_part = dotted_split(field, 2)[1] setattr(conv, field_part, value) else: raise Exception( 'invalid field specified %s for benefit conversion' % field) try: conv.save() except ValidationError as e: raise ValidationError( package_field_errors('benefitCurrencyConversion', e)) instance.eco_benefits_conversion = conv instance.update_eco_rev() instance.save() return {'ok': True}
def identifier_model_name(identifier): """ Takes an identifier like "model.field" and returns the model's display name """ object_name, __ = dotted_split(identifier, 2, maxsplit=1) return display_name(to_model_name(object_name))
def validate_and_save_field_perm(role, field_perm): for model_field_name, perm_type in field_perm.iteritems(): model_name, field_name = dotted_split(model_field_name, 2) validate_model_name(model_name, valid_field_model_names) field_perm = field_perms.get((role, model_field_name), None) create = field_perm is None if create: field_perm = FieldPermission.objects.create( field_name=field_name, model_name=model_name, role=role, instance=role.instance) perm_type = int(perm_type) if create or field_perm.permission_level != perm_type: valid_levels = [ level for __, level in options_for_permission(field_perm) ] if perm_type in valid_levels: field_perm.permission_level = perm_type field_perm.save() else: raise Exception('Invalid field type ' '(allowed %s, given %s)' % (valid_levels, perm_type)) notifications = instance.config.get('udf_notifications', []) if field_perm.full_name in notifications: remove_udf_notification(instance, field_perm.full_name)
def convert_filter_units(instance, filter_dict): """ Convert the values in a filter dictionary from display units to database units. Mutates the `filter_dict` argument and returns it. """ for field_name, value in filter_dict.iteritems(): if field_name not in ['tree.diameter', 'tree.height', 'tree.canopy_height', 'plot.width', 'plot.length', 'bioswale.drainage_area', 'rainBarrel.capacity', 'rainGarden.drainage_area']: continue model_name, field = dotted_split(field_name, 2, maxsplit=1) if isinstance(value, dict): factor = 1 / storage_to_instance_units_factor(instance, model_name, field) for k in ['MIN', 'MAX', 'IS']: if k in value: try: if isinstance(value[k], dict): float_val = float(value[k]['VALUE']) value[k]['VALUE'] = factor * float_val else: float_val = float(value[k]) value[k] = factor * float_val except ValueError: # If the value is non-numeric we can just leave is as # is and let the filter logic handle it. pass return filter_dict
def _split_key(key): format_string = 'Keys must be in the form of "model.field", not "%s"' return dotted_split(key, 2, failure_format_string=format_string, cls=ParseException)
def _get_json_as_dotdict(model, field_path): field, json_path = dotted_split(field_path, 2, maxsplit=1) if not hasattr(model, field): raise ValueError('Field %s not found' % field_path) dotdict = getattr(model, field) if type(dotdict) is not DotDict: raise ValueError('Field %s does not contain JSON' % field_path) return dotdict, json_path
def _set_map_feature_config(field_name, value, instance): __, __, model_name, setting = dotted_split(field_name, 4, maxsplit=3) Classes = _get_replaceable_models(instance) Cls = next(Cls for Cls in Classes if Cls.__name__ == model_name) if setting == 'diversion_rate': value = float(value) elif setting == 'should_show_eco': value = bool(value) Cls.set_config_property(instance, setting, value, save=False)
def _cross_validate_values(json_data, instance, error_dict): errors = None for identifier, value in json_data.iteritems(): __, field_name = dotted_split(identifier, 2, maxsplit=1) if field_name in error_dict: continue if field_name.startswith('config.map_feature_config'): errors = _map_feature_cross_validator(field_name, value, instance) if errors is not None: error_dict[field_name] = errors
def get_search_query_value(column, display, identifier, related_class, search_query): if not search_query: return '' search_map = json.loads(search_query) or {} model_name, field_name = tuple(dotted_split( identifier, 2, maxsplit=1)) search_field = '{}.{}'.format(model_name, column) pred = search_map.get(search_field) if not pred: return '' query_value = pred[FOREIGN_KEY_PREDICATE] related_model = related_class.objects.get(id=query_value) return getattr(related_model, display)
def _terminology_validator(field_name, value, instance): acceptable_terms = REPLACEABLE_TERMS.keys() + [ Cls.__name__ for Cls in _get_replaceable_models(instance) ] __, terms, term, form = dotted_split(field_name, 4, maxsplit=3) field_name_valid = (terms == 'terms' and term in acceptable_terms and form in ('singular', 'plural')) if not field_name_valid: return [_("An invalid key was sent in the request")] if term == 'Resource' and len(value) > 20: return [_('Please limit replacement text to 20 characters.')] return None
def _terminology_validator(field_name, value, instance): acceptable_terms = REPLACEABLE_TERMS.keys() + [ Cls.__name__ for Cls in _get_replaceable_models(instance)] __, terms, term, form = dotted_split(field_name, 4, maxsplit=3) field_name_valid = (terms == 'terms' and term in acceptable_terms and form in ('singular', 'plural')) if not field_name_valid: return [_("An invalid key was sent in the request")] if term == 'Resource' and len(value) > 20: return [_('Please limit replacement text to 20 characters.')] return None
def test_model_assignment(self): self.instance.add_map_feature_types(['Bioswale']) permissions = [ 'Plot.add_plot', 'Plot.delete_plot', 'Tree.add_tree', 'Tree.delete_tree', 'TreePhoto.add_treephoto', 'TreePhoto.delete_treephoto', 'Bioswale.add_bioswale', 'Bioswale.delete_bioswale', 'MapFeaturePhoto.add_bioswalephoto', 'MapFeaturePhoto.delete_bioswalephoto' ] self.request_updates(dict(zip(permissions, [True] * len(permissions)))) for existing in permissions: __, codename = dotted_split(existing, 2, maxsplit=1) permission = Permission.objects.get(codename=codename) self.assert_assignment(permission, self.new_role)
def _map_feature_cross_validator(field_name, value, instance): __, __, model_name, setting = dotted_split(field_name, 4, maxsplit=3) Classes = _get_replaceable_models(instance) Cls = next(Cls for Cls in Classes if Cls.__name__ == model_name) config = Cls.get_config(instance) if setting == 'should_show_eco': value = config.get(setting) if value: rainfall_inches = instance.annual_rainfall_inches if rainfall_inches is None or rainfall_inches == '': return [_("Ecobenefits cannot be calculated unless " "annual rainfall is given")] diversion_rate = config.get('diversion_rate') if diversion_rate is None or diversion_rate == '': return [_("Ecobenefits cannot be calculated unless " "a runoff coefficient is given")]
def _parse_predicate_key(key, mapping): format_string = 'Keys must be in the form of "model.field", not "%s"' model, field = dotted_split(key, 2, failure_format_string=format_string, cls=ParseException) if _is_udf(model): _, mapping_model, _ = model.split(':') field = 'id' else: mapping_model = model if mapping_model not in mapping: raise ParseException( 'Valid models are: %s or a collection UDF, not "%s"' % (mapping.keys(), model)) return model, mapping[mapping_model] + field
def validate_and_save_model_perm(role, model_perm): unassign = [] for model_perm_name, should_be_assigned in model_perm.iteritems(): model_name, codename = dotted_split(model_perm_name, 2) validate_model_name(model_name, set(valid_perm_models_by_name.keys())) Model = valid_perm_models_by_name[model_name] permission = get_and_validate_permission(codename, Model) validate_permission_assignment(codename, should_be_assigned, Model) if should_be_assigned: RolePermissionModel.objects.get_or_create( role=role, permission=permission) else: unassign.append({'role': role, 'permission': permission}) if unassign: unassign_q = reduce(lambda q1, q2: q1 | q2, [Q(**rpm) for rpm in unassign]) RolePermissionModel.objects.filter(unassign_q).delete()
def update_user(request, user): new_values = json_from_request(request) or {} for key in new_values: try: model, field = dotted_split(key, 2, cls=ValueError) if model != 'user': raise ValidationError( 'All fields should be prefixed with "user."') if field not in USER_PROFILE_FIELDS: raise ValidationError(field + ' is not an updatable field') except ValueError: raise ValidationError('All fields should be prefixed with "user."') setattr(user, field, new_values[key]) try: user.save() return {"ok": True} except ValidationError as ve: raise ValidationError(package_field_errors('user', ve))
def _parse_predicate_key(key, mapping): format_string = 'Keys must be in the form of "model.field", not "%s"' model, field = dotted_split(key, 2, failure_format_string=format_string, cls=ParseException) if _is_udf(model): __, mapping_model, __ = model.split(':') field = 'id' else: mapping_model = model if mapping_model not in mapping: raise ParseException( 'Valid models are: %s or a collection UDF, not "%s"' % (mapping.keys(), model)) return model, mapping[mapping_model] + field
def validate_and_save_model_perm(role, model_perm): unassign = [] for model_perm_name, should_be_assigned in model_perm.iteritems(): model_name, codename = dotted_split(model_perm_name, 2) validate_model_name( model_name, set(valid_perm_models_by_name.keys())) Model = valid_perm_models_by_name[model_name] permission = get_and_validate_permission(codename, Model) validate_permission_assignment(codename, should_be_assigned, Model) if should_be_assigned: RolePermissionModel.objects.get_or_create( role=role, permission=permission) else: unassign.append({'role': role, 'permission': permission}) if unassign: unassign_q = reduce(lambda q1, q2: q1 | q2, [Q(**rpm) for rpm in unassign]) RolePermissionModel.objects.filter(unassign_q).delete()
def _map_feature_config_validator(field_name, value, instance): acceptable_terms = [ Cls.__name__ for Cls in _get_replaceable_models(instance) ] __, __, term, key = dotted_split(field_name, 4, maxsplit=3) field_name_valid = (term in acceptable_terms and key in ('should_show_eco', 'diversion_rate')) if not field_name_valid: return [_("An invalid key was sent in the request")] plural = get_attr_from_json_field(instance, 'config.terms.{}.plural'.format(term)) if key == 'should_show_eco': if term == 'RainBarrel': return [ _("Showing Ecobenefits is not applicable to {RainBarrels}"). format(RainBarrels=plural) ] elif key == 'diversion_rate': if term == 'RainBarrel': return [ _("The runoff coefficient is not applicable to {RainBarrels}"). format(RainBarrels=plural) ] error_message = ("Please enter a number between 0 and 1 " "for the runoff coefficient") if value is None or value == '': return [_(error_message)] try: float_value = float(value) except ValueError: return [_(error_message)] if not 0.0 <= float_value <= 1.0: return [_(error_message)] return None
def test_model_assignment(self): self.instance.add_map_feature_types(['Bioswale']) permissions = [ 'Plot.add_plot', 'Plot.delete_plot', 'Tree.add_tree', 'Tree.delete_tree', 'TreePhoto.add_treephoto', 'TreePhoto.delete_treephoto', 'Bioswale.add_bioswale', 'Bioswale.delete_bioswale', 'MapFeaturePhoto.add_bioswalephoto', 'MapFeaturePhoto.delete_bioswalephoto'] self.request_updates( dict(zip(permissions, [True] * len(permissions)))) for existing in permissions: __, codename = dotted_split(existing, 2, maxsplit=1) permission = Permission.objects.get(codename=codename) self.assert_assignment(permission, self.new_role)
def _map_feature_cross_validator(field_name, value, instance): __, __, model_name, setting = dotted_split(field_name, 4, maxsplit=3) Classes = _get_replaceable_models(instance) Cls = next(Cls for Cls in Classes if Cls.__name__ == model_name) config = Cls.get_config(instance) if setting == 'should_show_eco': value = config.get(setting) if value: rainfall_inches = instance.annual_rainfall_inches if rainfall_inches is None or rainfall_inches == '': return [ _("Ecobenefits cannot be calculated unless " "annual rainfall is given") ] diversion_rate = config.get('diversion_rate') if diversion_rate is None or diversion_rate == '': return [ _("Ecobenefits cannot be calculated unless " "a runoff coefficient is given") ]
def update_benefits(request, instance): conv = instance.eco_benefits_conversion or _get_default_conversions() valid_fields = ('currency_symbol', 'electricity_kwh_to_currency', 'natural_gas_kbtu_to_currency', 'h20_gal_to_currency', 'co2_lb_to_currency', 'o3_lb_to_currency', 'nox_lb_to_currency', 'pm10_lb_to_currency', 'sox_lb_to_currency', 'voc_lb_to_currency') valid_fields = ["benefitCurrencyConversion." + field for field in valid_fields] updated_values = json_from_request(request) for field, value in updated_values.iteritems(): if field in valid_fields: field_part = dotted_split(field, 2)[1] setattr(conv, field_part, value) else: raise Exception( 'invalid field specified %s for benefit conversion' % field) try: conv.save() except ValidationError as e: raise ValidationError( package_field_errors('benefitCurrencyConversion', e)) instance.eco_benefits_conversion = conv instance.update_eco_rev() instance.save() return {'ok': True}
def _map_feature_config_validator(field_name, value, instance): acceptable_terms = [ Cls.__name__ for Cls in _get_replaceable_models(instance)] __, __, term, key = dotted_split(field_name, 4, maxsplit=3) field_name_valid = (term in acceptable_terms and key in ('should_show_eco', 'diversion_rate')) if not field_name_valid: return [_("An invalid key was sent in the request")] plural = get_attr_from_json_field(instance, 'config.terms.{}.plural' .format(term)) if key == 'should_show_eco': if term == 'RainBarrel': return [_("Showing Ecobenefits is not applicable to {RainBarrels}") .format(RainBarrels=plural)] elif key == 'diversion_rate': if term == 'RainBarrel': return [_( "The runoff coefficient is not applicable to {RainBarrels}") .format(RainBarrels=plural)] error_message = ("Please enter a number between 0 and 1 " "for the runoff coefficient") if value is None or value == '': return [_(error_message)] try: float_value = float(value) except ValueError: return [_(error_message)] if not 0.0 <= float_value <= 1.0: return [_(error_message)] return None
def update_map_feature(request_dict, user, feature): """ Update a map feature. Expects JSON in the request body to be: {'model.field', ...} Where model is either 'tree', 'plot', or another map feature type and field is any field on the model. UDF fields should be prefixed with 'udf:'. This method can be used to create a new map feature by passing in an empty MapFeature object (i.e. Plot(instance=instance)) """ feature_object_names = [ to_object_name(ft) for ft in feature.instance.map_feature_types ] if isinstance(feature, Convertible): # We're going to always work in display units here feature.convert_to_display_units() def set_attr_on_model(model, attr, val): field_classname = \ model._meta.get_field_by_name(attr)[0].__class__.__name__ if field_classname.endswith('PointField'): srid = val.get('srid', 3857) val = Point(val['x'], val['y'], srid=srid) val.transform(3857) elif field_classname.endswith('MultiPolygonField'): srid = val.get('srid', 4326) val = MultiPolygon(Polygon(val['polygon'], srid=srid), srid=srid) val.transform(3857) if attr == 'mapfeature_ptr': if model.mapfeature_ptr_id != value: raise Exception('You may not change the mapfeature_ptr_id') elif attr == 'id': if val != model.pk: raise Exception("Can't update id attribute") elif attr.startswith('udf:'): udf_name = attr[4:] if udf_name in [ field.name for field in model.get_user_defined_fields() ]: model.udfs[udf_name] = val else: raise KeyError('Invalid UDF %s' % attr) elif attr in model.fields(): model.apply_change(attr, val) else: raise Exception('Malformed request - invalid field %s' % attr) def save_and_return_errors(thing, user): try: if isinstance(thing, Convertible): thing.convert_to_database_units() thing.save_with_user(user) return {} except ValidationError as e: return package_field_errors(thing._model_name, e) tree = None rev_updates = ['universal_rev'] old_geom = feature.geom for (identifier, value) in request_dict.iteritems(): split_template = 'Malformed request - invalid field %s' object_name, field = dotted_split(identifier, 2, failure_format_string=split_template) if (object_name not in feature_object_names + ['tree']): raise Exception(split_template % identifier) tree_udfc_names = [ fdef.canonical_name for fdef in udf_defs(feature.instance, 'Tree') if fdef.iscollection ] if ((field in tree_udfc_names and feature.safe_get_current_tree() is None and value == [])): continue elif object_name in feature_object_names: model = feature elif object_name == 'tree' and feature.feature_type == 'Plot': # Get the tree or spawn a new one if needed tree = (tree or feature.safe_get_current_tree() or Tree(instance=feature.instance)) # We always edit in display units tree.convert_to_display_units() model = tree if field == 'species' and value: value = get_object_or_404(Species, instance=feature.instance, pk=value) elif field == 'plot' and value == unicode(feature.pk): value = feature else: raise Exception('Malformed request - invalid model %s' % object_name) set_attr_on_model(model, field, value) field_class = model._meta.get_field_by_name(field)[0] if isinstance(field_class, GeometryField): rev_updates.append('geo_rev') rev_updates.append('eco_rev') elif identifier in ['tree.species', 'tree.diameter']: rev_updates.append('eco_rev') errors = {} if feature.fields_were_updated(): errors.update(save_and_return_errors(feature, user)) if tree and tree.fields_were_updated(): tree.plot = feature errors.update(save_and_return_errors(tree, user)) if errors: # It simplifies the templates and client-side logic if the geometry # field errors are returned under the generic name if feature.geom_field_name in errors: errors['mapFeature.geom'] = errors[feature.geom_field_name] raise ValidationError(errors) if old_geom is not None and feature.geom != old_geom: update_hide_at_zoom_after_move(feature, user, old_geom) feature.instance.update_revs(*rev_updates) return feature, tree
def update_map_feature(request_dict, user, feature): """ Update a map feature. Expects JSON in the request body to be: {'model.field', ...} Where model is either 'tree', 'plot', or another map feature type and field is any field on the model. UDF fields should be prefixed with 'udf:'. This method can be used to create a new map feature by passing in an empty MapFeature object (i.e. Plot(instance=instance)) """ feature_object_names = [to_object_name(ft) for ft in feature.instance.map_feature_types] if isinstance(feature, Convertible): # We're going to always work in display units here feature.convert_to_display_units() def set_attr_on_model(model, attr, val): field_classname = \ model._meta.get_field_by_name(attr)[0].__class__.__name__ if field_classname.endswith('PointField'): srid = val.get('srid', 3857) val = Point(val['x'], val['y'], srid=srid) val.transform(3857) elif field_classname.endswith('MultiPolygonField'): srid = val.get('srid', 4326) val = MultiPolygon(Polygon(val['polygon'], srid=srid), srid=srid) val.transform(3857) if attr == 'mapfeature_ptr': if model.mapfeature_ptr_id != value: raise Exception( 'You may not change the mapfeature_ptr_id') elif attr == 'id': if val != model.pk: raise Exception("Can't update id attribute") elif attr.startswith('udf:'): udf_name = attr[4:] if udf_name in [field.name for field in model.get_user_defined_fields()]: model.udfs[udf_name] = val else: raise KeyError('Invalid UDF %s' % attr) elif attr in model.fields(): model.apply_change(attr, val) else: raise Exception('Malformed request - invalid field %s' % attr) def save_and_return_errors(thing, user): try: if isinstance(thing, Convertible): thing.convert_to_database_units() thing.save_with_user(user) return {} except ValidationError as e: return package_field_errors(thing._model_name, e) tree = None rev_updates = ['universal_rev'] old_geom = feature.geom for (identifier, value) in request_dict.iteritems(): split_template = 'Malformed request - invalid field %s' object_name, field = dotted_split(identifier, 2, failure_format_string=split_template) if (object_name not in feature_object_names + ['tree']): raise Exception(split_template % identifier) tree_udfc_names = [fdef.canonical_name for fdef in udf_defs(feature.instance, 'Tree') if fdef.iscollection] if ((field in tree_udfc_names and feature.safe_get_current_tree() is None and value == [])): continue elif object_name in feature_object_names: model = feature elif object_name == 'tree' and feature.feature_type == 'Plot': # Get the tree or spawn a new one if needed tree = (tree or feature.safe_get_current_tree() or Tree(instance=feature.instance)) # We always edit in display units tree.convert_to_display_units() model = tree if field == 'species' and value: value = get_object_or_404(Species, instance=feature.instance, pk=value) elif field == 'plot' and value == unicode(feature.pk): value = feature else: raise Exception( 'Malformed request - invalid model %s' % object_name) set_attr_on_model(model, field, value) field_class = model._meta.get_field_by_name(field)[0] if isinstance(field_class, GeometryField): rev_updates.append('geo_rev') rev_updates.append('eco_rev') elif identifier in ['tree.species', 'tree.diameter']: rev_updates.append('eco_rev') errors = {} if feature.fields_were_updated(): errors.update(save_and_return_errors(feature, user)) if tree and tree.fields_were_updated(): tree.plot = feature errors.update(save_and_return_errors(tree, user)) if errors: # It simplifies the templates and client-side logic if the geometry # field errors are returned under the generic name if feature.geom_field_name in errors: errors['mapFeature.geom'] = errors[feature.geom_field_name] raise ValidationError(errors) if old_geom is not None and feature.geom != old_geom: update_hide_at_zoom_after_move(feature, user, old_geom) feature.instance.update_revs(*rev_updates) return feature, tree
def update_map_feature(request_dict, user, feature): """ Update a map feature. Expects JSON in the request body to be: {'model.field', ...} Where model is either 'tree', 'plot', or another map feature type and field is any field on the model. UDF fields should be prefixed with 'udf:'. This method can be used to create a new map feature by passing in an empty MapFeature object (i.e. Plot(instance=instance)) """ feature_object_names = [to_object_name(ft) for ft in feature.instance.map_feature_types] if isinstance(feature, Convertible): # We're going to always work in display units here feature.convert_to_display_units() def set_attr_on_model(model, attr, val): field_classname = model._meta.get_field_by_name(attr)[0].__class__.__name__ if field_classname.endswith("PointField"): srid = val.get("srid", 3857) val = Point(val["x"], val["y"], srid=srid) val.transform(3857) elif field_classname.endswith("MultiPolygonField"): srid = val.get("srid", 4326) val = MultiPolygon(Polygon(val["polygon"], srid=srid), srid=srid) val.transform(3857) if attr == "mapfeature_ptr": if model.mapfeature_ptr_id != value: raise Exception("You may not change the mapfeature_ptr_id") elif attr == "id": if val != model.pk: raise Exception("Can't update id attribute") elif attr.startswith("udf:"): udf_name = attr[4:] if udf_name in [field.name for field in model.get_user_defined_fields()]: model.udfs[udf_name] = val else: raise KeyError("Invalid UDF %s" % attr) elif attr in model.fields(): model.apply_change(attr, val) else: raise Exception("Malformed request - invalid field %s" % attr) def save_and_return_errors(thing, user): try: if isinstance(thing, Convertible): thing.convert_to_database_units() thing.save_with_user(user) return {} except ValidationError as e: return package_field_errors(thing._model_name, e) old_location = feature.geom tree = None for (identifier, value) in request_dict.iteritems(): split_template = "Malformed request - invalid field %s" object_name, field = dotted_split(identifier, 2, failure_format_string=split_template) if object_name not in feature_object_names + ["tree"]: raise Exception(split_template % identifier) tree_udfc_names = [fdef.canonical_name for fdef in udf_defs(feature.instance, "Tree") if fdef.iscollection] if field in tree_udfc_names and feature.safe_get_current_tree() is None and value == []: continue elif object_name in feature_object_names: model = feature elif object_name == "tree" and feature.feature_type == "Plot": # Get the tree or spawn a new one if needed tree = tree or feature.safe_get_current_tree() or Tree(instance=feature.instance) # We always edit in display units tree.convert_to_display_units() model = tree if field == "species" and value: value = get_object_or_404(Species, instance=feature.instance, pk=value) elif field == "plot" and value == unicode(feature.pk): value = feature else: raise Exception("Malformed request - invalid model %s" % object_name) set_attr_on_model(model, field, value) errors = {} if feature.fields_were_updated(): errors.update(save_and_return_errors(feature, user)) if tree and tree.fields_were_updated(): tree.plot = feature errors.update(save_and_return_errors(tree, user)) if errors: # It simplifies the templates and client-side logic if the geometry # field errors are returned under the generic name if feature.geom_field_name in errors: errors["mapFeature.geom"] = errors[feature.geom_field_name] raise ValidationError(errors) if old_location is None or not feature.geom.equals_exact(old_location): feature.instance.update_geo_rev() return feature, tree
def render(self, context): explanation = _resolve_variable(self.explanation, context) label, identifier = self.resolve_label_and_identifier(context) user = _resolve_variable(self.user, context) instance = _resolve_variable(self.instance, context) field_template = get_template(_resolve_variable( self.field_template, context)).template if not isinstance(identifier, basestring)\ or not _identifier_regex.match(identifier): raise template.TemplateSyntaxError( 'expected a string with the format "object_name.property" ' 'to follow "from" %s' % identifier) model_name_or_object_name, field_name = dotted_split(identifier, 2, maxsplit=1) model = self.get_model(context, model_name_or_object_name, instance) object_name = to_object_name(model_name_or_object_name) identifier = "%s.%s" % (object_name, field_name) def _field_value(model, field_name, data_type): udf_field_name = field_name.replace('udf:', '') val = None if field_name in [f.name for f in model._meta.get_fields()]: try: val = getattr(model, field_name) except (ObjectDoesNotExist, AttributeError): pass elif _is_udf(model, udf_field_name): if udf_field_name in model.udfs: val = model.udfs[udf_field_name] # multichoices place a json serialized data-value # on the dom element and client-side javascript # processes it into a view table and edit widget if data_type == 'multichoice': val = json.dumps(val) elif data_type == 'multichoice': val = '[]' else: raise ValueError('Could not find field: %s' % field_name) return val if is_json_field_reference(field_name): field_value = get_attr_from_json_field(model, field_name) choices = None is_visible = is_editable = True data_type = "string" else: add_blank = (ADD_BLANK_ALWAYS if self.treat_multichoice_as_choice else ADD_BLANK_IF_CHOICE_FIELD) data_type, label, explanation, choices = field_type_label_choices( model, field_name, label, explanation=explanation, add_blank=add_blank) field_value = _field_value(model, field_name, data_type) if user is not None and hasattr(model, 'field_is_visible'): is_visible = model.field_is_visible(user, field_name) is_editable = model.field_is_editable(user, field_name) else: # This tag can be used without specifying a user. In that case # we assume that the content is visible and upstream code is # responsible for only showing the content to the appropriate # user is_visible = True is_editable = True digits = units = '' if hasattr(model, 'instance'): digits = get_digits_if_formattable( model.instance, object_name, field_name) units = get_units_if_convertible( model.instance, object_name, field_name) if units != '': units = get_unit_abbreviation(units) if data_type == 'foreign_key': # rendered clientside display_val = '' elif field_value is None: display_val = None elif data_type in ['date', 'datetime']: fmt = (model.instance.short_date_format if model.instance else settings.SHORT_DATE_FORMAT) display_val = dateformat.format(field_value, fmt) elif is_convertible_or_formattable(object_name, field_name): display_val = format_value( model.instance, object_name, field_name, field_value) if units != '': display_val += (' %s' % units) elif data_type == 'bool': display_val = _('Yes') if field_value else _('No') elif data_type == 'multichoice': # this is rendered clientside from data attributes so # there's no meaningful intermediate value to send # without rendering the same markup server-side. display_val = None elif choices: display_vals = [choice['display_value'] for choice in choices if choice['value'] == field_value] display_val = display_vals[0] if display_vals else field_value else: display_val = unicode(field_value) context['field'] = { 'label': label, 'explanation': explanation, 'identifier': identifier, 'value': field_value, 'display_value': display_val, 'units': units, 'digits': digits, 'data_type': data_type, 'is_visible': is_visible, 'is_editable': is_editable, 'choices': choices, } self.get_additional_context( context['field'], model, field_name, context.get('q', '')) return field_template.render(context)
def render(self, context): label, identifier = self.resolve_label_and_identifier(context) user = _resolve_variable(self.user, context) instance = _resolve_variable(self.instance, context) field_template = get_template(_resolve_variable( self.field_template, context)) if not isinstance(identifier, basestring)\ or not _identifier_regex.match(identifier): raise template.TemplateSyntaxError( 'expected a string with the format "object_name.property" ' 'to follow "from" %s' % identifier) model_name_or_object_name, field_name = dotted_split(identifier, 2, maxsplit=1) model = self.get_model(context, model_name_or_object_name, instance) object_name = to_object_name(model_name_or_object_name) identifier = "%s.%s" % (object_name, field_name) def _field_value(model, field_name): udf_field_name = field_name.replace('udf:', '') if field_name in model._meta.get_all_field_names(): try: val = getattr(model, field_name) except ObjectDoesNotExist: val = None elif _is_udf(model, udf_field_name): val = model.udfs[udf_field_name] else: raise ValueError('Could not find field: %s' % field_name) return val if is_json_field_reference(field_name): field_value = get_attr_from_json_field(model, field_name) choices = None is_visible = is_editable = True data_type = "string" else: field_value = _field_value(model, field_name) data_type, label, choices = field_type_label_choices( model, field_name, label) if user is not None and hasattr(model, 'field_is_visible'): is_visible = model.field_is_visible(user, field_name) is_editable = model.field_is_editable(user, field_name) else: # This tag can be used without specifying a user. In that case # we assume that the content is visible and upstream code is # responsible for only showing the content to the appropriate # user is_visible = True is_editable = True digits = units = '' if hasattr(model, 'instance'): digits = get_digits_if_formattable( model.instance, object_name, field_name) units = get_units_if_convertible( model.instance, object_name, field_name) if field_value is None: display_val = None elif data_type == 'date' and model.instance: display_val = dateformat.format(field_value, model.instance.short_date_format) elif data_type == 'date': display_val = dateformat.format(field_value, settings.SHORT_DATE_FORMAT) elif is_convertible_or_formattable(object_name, field_name): display_val = format_value( model.instance, object_name, field_name, field_value) if units != '': display_val += (' %s' % units) elif data_type == 'bool': display_val = trans('Yes') if field_value else trans('No') else: display_val = unicode(field_value) context['field'] = { 'label': label, 'identifier': identifier, 'value': field_value, 'display_value': display_val, 'units': units, 'digits': digits, 'data_type': data_type, 'is_visible': is_visible, 'is_editable': is_editable, 'choices': choices } self.get_additional_context(context['field']) return field_template.render(context)
def render(self, context): explanation = _resolve_variable(self.explanation, context) label, identifier = self.resolve_label_and_identifier(context) user = _resolve_variable(self.user, context) instance = _resolve_variable(self.instance, context) field_template = get_template(_resolve_variable( self.field_template, context)).template if not isinstance(identifier, basestring)\ or not _identifier_regex.match(identifier): raise template.TemplateSyntaxError( 'expected a string with the format "object_name.property" ' 'to follow "from" %s' % identifier) model_name_or_object_name, field_name = dotted_split(identifier, 2, maxsplit=1) model = self.get_model(context, model_name_or_object_name, instance) object_name = to_object_name(model_name_or_object_name) identifier = "%s.%s" % (object_name, field_name) def _field_value(model, field_name, data_type): udf_field_name = field_name.replace('udf:', '') val = None if field_name in [f.name for f in model._meta.get_fields()]: try: val = getattr(model, field_name) except (ObjectDoesNotExist, AttributeError): pass elif _is_udf(model, udf_field_name): if udf_field_name in model.udfs: val = model.udfs[udf_field_name] # multichoices place a json serialized data-value # on the dom element and client-side javascript # processes it into a view table and edit widget if data_type == 'multichoice': val = json.dumps(val) elif data_type == 'multichoice': val = '[]' else: raise ValueError('Could not find field: %s' % field_name) return val if is_json_field_reference(field_name): field_value = get_attr_from_json_field(model, field_name) choices = None is_visible = is_editable = True data_type = "string" else: add_blank = (ADD_BLANK_ALWAYS if self.treat_multichoice_as_choice else ADD_BLANK_IF_CHOICE_FIELD) data_type, label, explanation, choices = field_type_label_choices( model, field_name, label, explanation=explanation, add_blank=add_blank) field_value = _field_value(model, field_name, data_type) if user is not None and hasattr(model, 'field_is_visible'): is_visible = model.field_is_visible(user, field_name) is_editable = model.field_is_editable(user, field_name) else: # This tag can be used without specifying a user. In that case # we assume that the content is visible and upstream code is # responsible for only showing the content to the appropriate # user is_visible = True is_editable = True digits = units = '' if hasattr(model, 'instance'): digits = get_digits_if_formattable( model.instance, object_name, field_name) units = get_units_if_convertible( model.instance, object_name, field_name) if units != '': units = get_unit_abbreviation(units) if data_type == 'foreign_key': # rendered clientside display_val = '' elif field_value is None: display_val = None elif data_type in ['date', 'datetime']: fmt = (model.instance.short_date_format if model.instance else settings.SHORT_DATE_FORMAT) display_val = dateformat.format(field_value, fmt) elif is_convertible_or_formattable(object_name, field_name): display_val = format_value( model.instance, object_name, field_name, field_value) if units != '': display_val += (' %s' % units) elif data_type == 'bool': display_val = _('Yes') if field_value else _('No') elif data_type == 'multichoice': # this is rendered clientside from data attributes so # there's no meaningful intermediate value to send # without rendering the same markup server-side. display_val = None elif choices: display_vals = [choice['display_value'] for choice in choices if choice['value'] == field_value] display_val = display_vals[0] if display_vals else field_value elif data_type == 'float': display_val = num_format(field_value) else: display_val = unicode(field_value) context['field'] = { 'label': label, 'explanation': explanation, 'identifier': identifier, 'value': field_value, 'display_value': display_val, 'units': units, 'digits': digits, 'data_type': data_type, 'is_visible': is_visible, 'is_editable': is_editable, 'choices': choices, } self.get_additional_context( context['field'], model, field_name, context.get('q', '')) return field_template.render(context)