def add_udfs_to_web_detail_fields(apps, schema_editor):
    Instance = apps.get_model("treemap", "Instance")
    UserDefinedFieldDefinition = apps.get_model("treemap", "UserDefinedFieldDefinition")

    for instance in Instance.objects.all():
        scalar_udfs = UserDefinedFieldDefinition.objects \
            .filter(iscollection=False, instance=instance) \
            .order_by('name')

        # Nothing to add to the configs
        if not scalar_udfs:
            continue

        if 'web_detail_fields' not in instance.config:
            instance.config['web_detail_fields'] = deepcopy(DEFAULT_WEB_DETAIL_FIELDS)

        for udf in scalar_udfs:
            for group in instance.config['web_detail_fields']:
                if 'model' in group and group['model'] == to_object_name(udf.model_type):
                    field_keys = group.get('field_keys')

                    udf_full_name = to_object_name(udf.model_type) + '.udf:' + udf.name
                    if 'field_keys' in group and udf_full_name not in field_keys:
                        field_keys.append(udf_full_name)

        instance.save()
Exemple #2
0
    def get_udfc_search_fields(self, user):
        from treemap.models import InstanceUser
        from treemap.udf import UDFModel
        from treemap.util import to_object_name, leaf_models_of_class
        from treemap.lib.perms import udf_write_level, READ, WRITE

        try:
            iu = self.instanceuser_set.get(user__pk=user.pk)
        except InstanceUser.DoesNotExist:
            iu = None

        data = DotDict({'models': set(), 'udfc': {}})
        for clz in (leaf_models_of_class(UDFModel)):
            model_name = clz.__name__
            items = (
                (k, v) for k, v
                in getattr(clz, 'collection_udf_settings', {}).iteritems())
            for k, v in items:
                udfds = (u for u in udf_defs(self, model_name) if u.name == k)
                for udfd in udfds:
                    if udf_write_level(iu, udfd) in (READ, WRITE):
                        nest_path = ('udfc.%s.models.%s' %
                                     (to_object_name(k),
                                      to_object_name(model_name)))
                        data[nest_path] = {
                            'udfd': udfd,
                            'fields': udfd.datatype_dict[0]['choices']
                        }
                        p = 'udfc.%s.' % to_object_name(k)
                        data[p + 'action_verb'] = v['action_verb']
                        data[p + 'range_field_key'] = v['range_field_key']
                        data[p + 'action_field_key'] = v['action_field_key']
                        data['models'] |= {clz}

        return data
Exemple #3
0
    def _get_udfc_search_fields(self):
        from treemap.util import to_object_name

        empty_udfc = {to_object_name(n_k):
                      {to_object_name(m_k): {'fields': [], 'udfd': None}
                       for m_k in UDFC_MODELS}
                      for n_k in UDFC_NAMES}

        udfds = []
        for model_name in UDFC_MODELS:
            for udfd in udf_defs(self, model_name):
                if udfd.name in UDFC_NAMES:
                    udfds.append(udfd)

        udfc = deepcopy(empty_udfc)

        for udfd in udfds:
            udfd_info = {
                'udfd': udfd,
                'fields': udfd.datatype_dict[0]['choices']
            }
            name_dict = udfc[to_object_name(udfd.name)]
            name_dict[to_object_name(udfd.model_type)] = udfd_info

        return udfc
Exemple #4
0
    def get_udfc_search_fields(self, user):
        from treemap.models import InstanceUser
        from treemap.udf import UDFModel
        from treemap.util import to_object_name, leaf_models_of_class
        from treemap.lib.perms import udf_write_level, READ, WRITE

        try:
            iu = self.instanceuser_set.get(user__pk=user.pk)
        except InstanceUser.DoesNotExist:
            iu = None

        data = DotDict({"models": set(), "udfc": {}})
        for clz in leaf_models_of_class(UDFModel):
            model_name = clz.__name__
            for k, v in clz.collection_udf_settings.items():
                udfds = (u for u in udf_defs(self, model_name) if u.name == k)
                for udfd in udfds:
                    if udf_write_level(iu, udfd) in (READ, WRITE):
                        _base_nest_path = "udfc.%s." % (to_object_name(k))
                        ids_nest_path = "%sids.%s" % (_base_nest_path, to_object_name(model_name))
                        models_nest_path = "%smodels.%s" % (_base_nest_path, to_object_name(model_name))
                        data[ids_nest_path] = udfd.pk
                        data[models_nest_path] = {"udfd": udfd, "fields": udfd.datatype_dict[0]["choices"]}
                        p = "udfc.%s." % to_object_name(k)
                        data[p + "action_verb"] = v["action_verb"]
                        data[p + "range_field_key"] = v["range_field_key"]
                        data[p + "action_field_key"] = v["action_field_key"]
                        data["models"] |= {clz}

        return data
Exemple #5
0
def _is_valid_models_list_for_model(models, model_name, ModelClass, instance):
    """Validates everything in models are valid filters for model_name"""
    def collection_udf_set_for_model(Model):
        if not issubclass(ModelClass, UDFModel):
            return {}
        if hasattr(Model, 'instance'):
            fake_model = Model(instance=instance)
        else:
            fake_model = Model()
        return set(fake_model.collection_udfs_search_names())

    # MapFeature is valid for all models
    models = models - MAP_FEATURE_RELATED_NAMES

    object_name = to_object_name(model_name)
    models = models - {object_name}

    if model_name == 'Plot':
        related_models = PLOT_RELATED_MODELS
    else:
        related_models = {ModelClass}

    for Model in related_models:
        models = models - {to_object_name(Model.__name__)}
        if issubclass(Model, UDFModel):
            models = models - collection_udf_set_for_model(Model)

    return len(models) == 0
Exemple #6
0
    def remove_map_feature_types(self, remove=None, keep=None):
        from treemap.util import to_object_name
        if keep and remove:
            raise Exception('Invalid use of remove_map_features API: '
                            'pass arguments "keep" or "remove" but not both')
        elif keep:
            remaining_types = [name for name in self.map_feature_types
                               if name in keep]
        else:
            remaining_types = [class_name for class_name
                               in self.map_feature_types
                               if class_name not in remove]

        for class_name in self.map_feature_types:
            if class_name not in remaining_types:
                if class_name in self.search_config:
                    del self.search_config[class_name]
                if 'missing' in self.search_config:
                    self.search_config['missing'] = [
                        o for o in self.search_config['missing']
                        if not o.get('identifier', '').startswith(
                            to_object_name(class_name))]
                # TODO: delete from mobile_api_fields
                # non-plot mobile_api_fields are not currently
                # supported, but when they are added, they should
                # also be removed here.
        self.map_feature_types = remaining_types
        self.save()
Exemple #7
0
def context_dict_for_resource(request, resource, **kwargs):
    context = context_dict_for_map_feature(request, resource, **kwargs)
    instance = request.instance

    # Give them 2 for adding the resource and answering its questions
    total_progress_items = 3
    completed_progress_items = 2

    context['external_link'] = None

    photos = resource.photos()
    context['photos'] = [context_dict_for_photo(request, photo)
                         for photo in photos]

    has_photos = len(photos) > 0

    if has_photos:
        completed_progress_items += 1

    context['upload_photo_endpoint'] = reverse(
        'add_photo_to_map_feature',
        kwargs={'instance_url_name': instance.url_name,
                'feature_id': resource.pk})

    context['progress_percent'] = int(100 * (
        completed_progress_items / total_progress_items) + .5)

    context['progress_messages'] = []
    if not has_photos:
        context['progress_messages'].append(_('Add a photo'))

    audits = _map_feature_audits(request.user, request.instance, resource)

    _add_audits_to_context(audits, context)

    _add_share_context(context, request, photos)

    object_name_alias = to_object_name(context['feature'].__class__.__name__)
    # some features that were originally written to support plot and tree
    # have grown to support other resource types, but they expect a context
    # entry for their type, not just for 'feature'.
    # For example:
    # * Plot detail expects 'plot' and 'tree'
    # * Foo detail would expect 'foo'
    context[object_name_alias] = context['feature']

    if isinstance(resource, PolygonalMapFeature):
        context['contained_plots'] = resource.contained_plots()
        area = resource.calculate_area()
        __, display_area = get_display_value(instance,
                                             'greenInfrastructure', 'area',
                                             area, digits=0)
        display_units = get_unit_abbreviation(
            get_units(instance, 'greenInfrastructure', 'area'))
        context['area'] = area
        context['display_area'] = display_area
        context['area_units'] = display_units

    return context
Exemple #8
0
def render_map_feature_add(request, instance, type):
    if type in instance.map_feature_types[1:]:
        app = MapFeature.get_subclass(type).__module__.split('.')[0]
        try:
            template = '%s/%s_add.html' % (app, type)
        except:
            template = 'treemap/resource_add.html'
        return render(request, template, {'object_name': to_object_name(type)})
    else:
        raise_non_instance_404(type)
def render_map_feature_add(request, instance, type):
    if type in instance.map_feature_types[1:]:
        app = MapFeature.get_subclass(type).__module__.split(".")[0]
        try:
            template = "%s/%s_add.html" % (app, type)
            get_template(template)
        except:
            template = "treemap/resource_add.html"
        return render_to_response(template, {"object_name": to_object_name(type)}, RequestContext(request))
    else:
        raise_non_instance_404(type)
Exemple #10
0
def _get_fields(instance, model_name):
    Model = safe_get_model_class(model_name)
    mobj = Model(instance=instance)
    udfs = {udf.canonical_name
            for udf in udf_defs(instance, model_name)
            if not udf.iscollection}
    concrete_fields = {
        f.name for f in mobj._meta.get_fields(include_parents=False)
        if _should_show_field(Model, f.name) and not f.is_relation}
    model_fields = concrete_fields | udfs
    model_fields = {'%s.%s' % (to_object_name(model_name), f)
                    for f in model_fields}
    return model_fields
Exemple #11
0
def render_map_feature_add(request, instance, type):
    if type in instance.map_feature_types[1:]:
        app = MapFeature.get_subclass(type).__module__.split('.')[0]
        try:
            template = '%s/%s_add.html' % (app, type)
            get_template(template)
        except:
            template = 'treemap/resource_add.html'
        return render_to_response(template,
                                  {'object_name': to_object_name(type)},
                                  RequestContext(request))
    else:
        raise Http404('Instance does not support feature type ' + type)
Exemple #12
0
def get_udfc_search_fields(instance, user):
    from treemap.models import InstanceUser
    from treemap.udf import UDFModel
    from treemap.util import to_object_name, leaf_models_of_class
    from treemap.lib.perms import udf_write_level, READ, WRITE

    try:
        iu = instance.instanceuser_set.get(user__pk=user.pk)
    except InstanceUser.DoesNotExist:
        iu = None

    data = DotDict({'models': set(), 'udfc': {}})
    for clz in leaf_models_of_class(UDFModel):
        model_name = clz.__name__
        if model_name not in ['Tree'] + instance.map_feature_types:
            continue
        for k, v in clz.collection_udf_settings.items():
            udfds = (u for u in udf_defs(instance, model_name) if u.name == k)
            for udfd in udfds:
                if udf_write_level(iu, udfd) in (READ, WRITE):
                    _base_nest_path = 'udfc.%s.' % (to_object_name(k))
                    ids_nest_path = ('%sids.%s'
                                     % (_base_nest_path,
                                        to_object_name(model_name)))
                    models_nest_path = ('%smodels.%s' %
                                        (_base_nest_path,
                                         to_object_name(model_name)))
                    data[ids_nest_path] = udfd.pk
                    data[models_nest_path] = {
                        'udfd': udfd,
                        'fields': udfd.datatype_dict[0]['choices']
                    }
                    p = 'udfc.%s.' % to_object_name(k)
                    data[p + 'action_verb'] = v['action_verb']
                    data[p + 'range_field_key'] = v['range_field_key']
                    data[p + 'action_field_key'] = v['action_field_key']
                    data['models'] |= {clz}

    return data
def context_dict_for_resource(request, resource, **kwargs):
    context = context_dict_for_map_feature(request, resource, **kwargs)
    instance = request.instance

    # Give them 2 for adding the resource and answering its questions
    total_progress_items = 3
    completed_progress_items = 2

    photos = resource.photos()
    context["photos"] = [context_dict_for_photo(request, photo) for photo in photos]

    has_photos = len(photos) > 0

    if has_photos:
        completed_progress_items += 1

    context["upload_photo_endpoint"] = reverse(
        "add_photo_to_map_feature", kwargs={"instance_url_name": instance.url_name, "feature_id": resource.pk}
    )

    context["progress_percent"] = int(100 * (completed_progress_items / total_progress_items) + 0.5)

    context["progress_messages"] = []
    if not has_photos:
        context["progress_messages"].append(_("Add a photo"))

    audits = _map_feature_audits(request.user, request.instance, resource)

    _add_audits_to_context(audits, context)

    _add_share_context(context, request, photos)

    object_name_alias = to_object_name(context["feature"].__class__.__name__)
    # some features that were originally written to support plot and tree
    # have grown to support other resource types, but they expect a context
    # entry for their type, not just for 'feature'.
    # For example:
    # * Plot detail expects 'plot' and 'tree'
    # * Foo detail would expect 'foo'
    context[object_name_alias] = context["feature"]

    if isinstance(resource, PolygonalMapFeature):
        context["contained_plots"] = resource.contained_plots()

    return context
Exemple #14
0
    def _mutate_convertable_fields(self, f):
        from treemap.util import to_object_name
        # note that `to_object_name` is a helper function we use
        # for lowerCamelCase, but `._meta.object_name` is a django
        # internal that is represented as UpperCamelCase.
        model = to_object_name(self._meta.object_name)
        for field in self._meta.get_all_field_names():
            if self.instance and is_convertible(model, field):
                value = getattr(self, field)

                try:
                    value = float(value)
                except Exception:
                    # These will be caught later in the cleaning process
                    pass

                converted_value = f(self.instance, model, field, value)

                setattr(self, field, converted_value)
Exemple #15
0
    def _mutate_convertable_fields(self, f):
        from treemap.util import to_object_name
        # note that `to_object_name` is a helper function we use
        # for lowerCamelCase, but `._meta.object_name` is a django
        # internal that is represented as UpperCamelCase.
        model = to_object_name(self._meta.object_name)
        for field in self._meta.get_all_field_names():
            if self.instance and is_convertible(model, field):
                value = getattr(self, field)

                try:
                    value = float(value)
                except Exception:
                    # These will be caught later in the cleaning process
                    pass

                converted_value = f(self.instance, model, field, value)

                setattr(self, field, converted_value)
Exemple #16
0
    def remove_map_feature_types(self, remove=None, keep=None):
        """
        remove_map_feature_types(self, remove=None, keep=None)

        Either remove the map feature types in the remove argument,
        or remove all but the types in the keep argument.
        The types to remove or keep is a list of map feature class names.

        Then remove map feature config for all removed map features,
        and save the instance!!!
        """
        from treemap.util import to_object_name
        if keep and remove:
            raise Exception('Invalid use of remove_map_features API: '
                            'pass arguments "keep" or "remove" but not both')
        elif keep:
            remaining_types = [
                name for name in self.map_feature_types if name in keep
            ]
        else:
            remaining_types = [
                class_name for class_name in self.map_feature_types
                if class_name not in remove
            ]

        for class_name in self.map_feature_types:
            if class_name not in remaining_types:
                if class_name in self.search_config:
                    del self.search_config[class_name]
                if 'missing' in self.search_config:
                    self.search_config['missing'] = [
                        o for o in self.search_config['missing']
                        if not o.get('identifier', '').startswith(
                            to_object_name(class_name))
                    ]
                # TODO: delete from mobile_api_fields
                # non-plot mobile_api_fields are not currently
                # supported, but when they are added, they should
                # also be removed here.
        self._map_feature_types = remaining_types
        self.save()
Exemple #17
0
def save_audit(migration_rules, migration_event,
               relic_ids, model_dict, audit_obj, instance):

    fields = model_dict['fields']

    # the migrator uses downcase names
    model_name = to_object_name(fields['model'])

    model_id = relic_ids[model_name][fields['model_id']]

    if model_id == models.UNBOUND_MODEL_ID:
        print("cannot save this audit. "
              "The underlying model '%s' was discarded."
              % model_name)
        return None

    audit_obj.model_id = model_id

    if ((fields['field'] == 'id' and
         fields['current_value'] == fields['model_id'])):
        audit_obj.current_value = model_id

    # after the initial save, `created` can be updated without
    # getting clobbered by `auto_now_add`.
    # save the object, then set the created time.
    audit_obj.save()
    created = fields['created']
    assert created != '' and not created is None
    audit_obj.created = dateutil.parser.parse(created)
    audit_obj.save()

    OTM1ModelRelic.objects.create(
        instance=instance,
        migration_event=migration_event,
        otm1_model_id=model_dict['pk'],
        otm2_model_name='audit',
        otm2_model_id=audit_obj.pk)
    return audit_obj
Exemple #18
0
def _add_scalar_udf_to_field_configs(udf, instance):
    save_instance = False

    for prop in ('mobile_api_fields', 'web_detail_fields'):
        attr = getattr(instance, prop)
        for group in attr:
            if (('model' in group and
                 group['model'] == to_object_name(udf.model_type))):
                field_keys = group.get('field_keys')

                if 'field_keys' in group and udf.full_name not in field_keys:
                    field_keys.append(udf.full_name)
                    save_instance = True
                    # The first time a udf is configured,
                    # getattr(instance, prop) returns a deepcopy of
                    # the default for prop.
                    # Mutating the deepcopy does not set prop on config
                    # to refer to that deepcopy, so we must do the
                    # setattr here.
                    setattr(instance, prop, attr)

    if save_instance:
        instance.save()
Exemple #19
0
 def geom_field_name(cls):
     return "%s.geom" % to_object_name(cls.map_feature_type)
Exemple #20
0
 def full_name(self):
     return to_object_name(self.model_type) + '.' + self.canonical_name
Exemple #21
0
 def full_name(self):
     return to_object_name(self.model_type) + '.' + self.canonical_name
Exemple #22
0
    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)
Exemple #23
0
 def geom_field_name(cls):
     return "%s.geom" % to_object_name(cls.map_feature_type)
Exemple #24
0
 def collection_udfs_search_names(self):
     object_name = to_object_name(self.__class__.__name__)
     return [
         'udf:%s:%s' % (object_name, udf.pk) for udf in self.collection_udfs
     ]
Exemple #25
0
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 split_model_or_raise(identifier):
        parts = identifier.split('.', 1)

        if (len(parts) != 2 or
                parts[0] not in feature_object_names + ['tree']):
            raise Exception(
                'Malformed request - invalid field %s' % identifier)
        else:
            return parts

    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_validation_errors(thing._model_name, e)

    tree = None

    for (identifier, value) in request_dict.iteritems():
        object_name, field = split_model_or_raise(identifier)

        if 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.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:
        raise ValidationError(errors)

    # Refresh feature.instance in case geo_rev_hash was updated
    feature.instance = Instance.objects.get(id=feature.instance.id)

    return feature, tree
Exemple #26
0
def context_dict_for_resource(request, resource, **kwargs):
    context = context_dict_for_map_feature(request, resource, **kwargs)
    instance = request.instance

    # Give them 2 for adding the resource and answering its questions
    total_progress_items = 3
    completed_progress_items = 2

    context['external_link'] = None

    photos = resource.photos()
    context['photos'] = [
        context_dict_for_photo(request, photo) for photo in photos
    ]

    has_photos = len(photos) > 0

    if has_photos:
        completed_progress_items += 1

    context['upload_photo_endpoint'] = reverse('add_photo_to_map_feature',
                                               kwargs={
                                                   'instance_url_name':
                                                   instance.url_name,
                                                   'feature_id': resource.pk
                                               })

    context['progress_percent'] = int(
        100 * (completed_progress_items / total_progress_items) + .5)

    context['progress_messages'] = []
    if not has_photos:
        context['progress_messages'].append(_('Add a photo'))

    audits = _map_feature_audits(request.user, request.instance, resource)

    _add_audits_to_context(audits, context)

    _add_share_context(context, request, photos)

    object_name_alias = to_object_name(context['feature'].__class__.__name__)
    # some features that were originally written to support plot and tree
    # have grown to support other resource types, but they expect a context
    # entry for their type, not just for 'feature'.
    # For example:
    # * Plot detail expects 'plot' and 'tree'
    # * Foo detail would expect 'foo'
    context[object_name_alias] = context['feature']

    if isinstance(resource, PolygonalMapFeature):
        context['contained_plots'] = resource.contained_plots()
        area = resource.calculate_area()
        __, display_area = get_display_value(instance,
                                             'greenInfrastructure',
                                             'area',
                                             area,
                                             digits=0)
        display_units = get_unit_abbreviation(
            get_units(instance, 'greenInfrastructure', 'area'))
        context['area'] = area
        context['display_area'] = display_area
        context['area_units'] = display_units

    return context
Exemple #27
0
    def advanced_search_fields(self):
        # TODO pull from the config once users have a way to set search fields

        from treemap.util import to_object_name

        # if we come to support more udfc searches, we can add them here.
        udfc_models = ['Tree', 'Plot']
        udfc_names = ['Stewardship', 'Alerts']

        # must inflate object so that template can look for keys
        empty_udfc = {to_object_name(n_k):
                      {to_object_name(m_k): {'fields': [], 'udfd': None}
                       for m_k in udfc_models}
                      for n_k in udfc_names}

        if not self.feature_enabled('advanced_search_filters'):
            return {'standard': [], 'missing': [], 'display': [],
                    'udfc': empty_udfc}

        from treemap.models import MapFeature  # prevent circular import
        fields = {
            'standard': [
                {'identifier': 'tree.diameter', 'search_type': 'RANGE'},
                {'identifier': 'tree.date_planted', 'search_type': 'RANGE'}
            ],
            'display': [
                {'model': 'Tree', 'label': 'Show trees'},
                {'model': 'EmptyPlot',
                 'label': 'Show empty planting sites'}
            ],
            'missing': [
                {'identifier': 'species.id',
                 'label': 'Show missing species',
                 'search_type': 'ISNULL',
                 'value': 'true'},
                {'identifier': 'tree.diameter',
                 'label': 'Show missing trunk diameter',
                 'search_type': 'ISNULL',
                 'value': 'true'},
                {'identifier': 'mapFeaturePhoto.id',
                 'label': 'Show missing photos',
                 'search_type': 'ISNULL',
                 'value': 'true'}
            ],
        }

        def make_display_filter(feature_name):
            Feature = MapFeature.get_subclass(feature_name)
            if hasattr(Feature, 'display_name_plural'):
                plural = Feature.display_name_plural
            else:
                plural = Feature.display_name + 's'
            return {
                'label': 'Show %s' % plural.lower(),
                'model': feature_name
            }

        fields['display'] += [make_display_filter(feature_name)
                              for feature_name in self.map_feature_types
                              if feature_name != 'Plot']

        # It makes styling easier if every field has an identifier
        num = 0
        for filters in fields.itervalues():
            for field in filters:
                field['id'] = "%s_%s" % (field.get('identifier', ''), num)
                num += 1

        # prevent circular import
        from treemap.udf import UserDefinedFieldDefinition

        udfds = UserDefinedFieldDefinition.objects.filter(
            instance=self,
            name__in=udfc_names,
            model_type__in=udfc_models)

        udfc = deepcopy(empty_udfc)

        for udfd in udfds:
            udfd_info = {
                'udfd': udfd,
                'fields': udfd.datatype_dict[0]['choices']
            }
            name_dict = udfc[to_object_name(udfd.name)]
            name_dict[to_object_name(udfd.model_type)] = udfd_info

        fields['udfc'] = udfc

        return fields
Exemple #28
0
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
Exemple #29
0
 def search_slug(self):
     return to_object_name(self.__class__.__name__)
Exemple #30
0
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 split_model_or_raise(identifier):
        parts = identifier.split('.', 1)

        if (len(parts) != 2 or
                parts[0] not in feature_object_names + ['tree']):
            raise Exception(
                'Malformed request - invalid field %s' % identifier)
        else:
            return parts

    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_validation_errors(thing._model_name, e)

    tree = None

    for (identifier, value) in request_dict.iteritems():
        object_name, field = split_model_or_raise(identifier)

        if 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.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:
        raise ValidationError(errors)

    # Refresh feature.instance in case geo_rev_hash was updated
    feature.instance = Instance.objects.get(id=feature.instance.id)

    return feature, tree
Exemple #31
0
    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)
Exemple #32
0
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
Exemple #33
0
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
Exemple #34
0
 def collection_udfs_search_names(self):
     object_name = to_object_name(self.__class__.__name__)
     return ['udf:%s:%s' % (object_name, udf.pk)
             for udf in self.collection_udfs]
Exemple #35
0
    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 = identifier.split(".", 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)
Exemple #36
0
    def advanced_search_fields(self):
        # TODO pull from the config once users have a way to set search fields

        from treemap.util import to_object_name

        # if we come to support more udfc searches, we can add them here.
        udfc_models = ['Tree', 'Plot']
        udfc_names = ['Stewardship', 'Alerts']

        # must inflate object so that template can look for keys
        empty_udfc = {
            to_object_name(n_k): {
                to_object_name(m_k): {
                    'fields': [],
                    'udfd': None
                }
                for m_k in udfc_models
            }
            for n_k in udfc_names
        }

        if not self.feature_enabled('advanced_search_filters'):
            return {
                'standard': [],
                'missing': [],
                'display': [],
                'udfc': empty_udfc
            }

        from treemap.models import MapFeature  # prevent circular import
        fields = {
            'standard': [{
                'identifier': 'tree.diameter',
                'search_type': 'RANGE'
            }, {
                'identifier': 'tree.date_planted',
                'search_type': 'RANGE'
            }],
            'display': [{
                'model': 'Tree',
                'label': 'Show trees'
            }, {
                'model': 'EmptyPlot',
                'label': 'Show empty planting sites'
            }],
            'missing': [{
                'identifier': 'species.id',
                'label': 'Show missing species',
                'search_type': 'ISNULL',
                'value': 'true'
            }, {
                'identifier': 'tree.diameter',
                'label': 'Show missing trunk diameter',
                'search_type': 'ISNULL',
                'value': 'true'
            }, {
                'identifier': 'mapFeaturePhoto.id',
                'label': 'Show missing photos',
                'search_type': 'ISNULL',
                'value': 'true'
            }],
        }

        def make_display_filter(feature_name):
            Feature = MapFeature.get_subclass(feature_name)
            if hasattr(Feature, 'display_name_plural'):
                plural = Feature.display_name_plural
            else:
                plural = Feature.display_name + 's'
            return {'label': 'Show %s' % plural.lower(), 'model': feature_name}

        fields['display'] += [
            make_display_filter(feature_name)
            for feature_name in self.map_feature_types
            if feature_name != 'Plot'
        ]

        # It makes styling easier if every field has an identifier
        num = 0
        for filters in fields.itervalues():
            for field in filters:
                field['id'] = "%s_%s" % (field.get('identifier', ''), num)
                num += 1

        # prevent circular import
        from treemap.udf import UserDefinedFieldDefinition

        udfds = UserDefinedFieldDefinition.objects.filter(
            instance=self, name__in=udfc_names, model_type__in=udfc_models)

        udfc = deepcopy(empty_udfc)

        for udfd in udfds:
            udfd_info = {
                'udfd': udfd,
                'fields': udfd.datatype_dict[0]['choices']
            }
            name_dict = udfc[to_object_name(udfd.name)]
            name_dict[to_object_name(udfd.model_type)] = udfd_info

        fields['udfc'] = udfc

        return fields
Exemple #37
0
    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)