예제 #1
0
class MediumResource(FootprintResource):
    content = PickledDictField(attribute='limited_content', null=True, blank=True, default=lambda:{}, readonly=True)
    style_attributes = fields.ToManyField(StyleAttributeResource, 'style_attributes', full=True, null=True)

    class Meta(FootprintResource.Meta):
        always_return_data = True
        resource_name = 'medium'
        queryset = LayerStyle.objects.all()
예제 #2
0
class BuiltFormExampleResource(FootprintResource):
    content = PickledDictField(attribute='content',
                               null=True,
                               blank=True,
                               default=lambda: {})

    class Meta(FootprintResource.Meta):
        always_return_data = True
        queryset = BuiltFormExample.objects.all()
        resource_name = 'built_form_example'
예제 #3
0
class PresentationMediumResource(FootprintResource):
    """
        The through class between Presentation and Medium, a list of which are loaded by a PresentationResource instance to give the user access to the corresponding Medium and also the important db_entity method, which returns the selected DbEntity interest of the PresentationMedium's db_entity_key
    """

    # The db_entity--We don't expose the DbEntityInterest to the client
    db_entity = fields.ToOneField(DbEntityResource,
                                  attribute='db_entity',
                                  null=False)
    # Return the full Medium
    medium = fields.ToOneField(MediumResource,
                               attribute='medium',
                               null=False,
                               full=True)
    # The configuration of items not directly related to the Medium, such as graph labels. These are usually also
    # editable by the user.
    configuration = PickledDictField(attribute='configuration',
                                     null=True,
                                     blank=True,
                                     default=lambda: {})

    visible_attributes = ListField(attribute='visible_attributes',
                                   null=True,
                                   blank=True)

    creator = fields.ToOneField(UserResource,
                                'creator',
                                full=True,
                                null=True,
                                readonly=True)
    updater = fields.ToOneField(UserResource,
                                'updater',
                                full=True,
                                null=True,
                                readonly=True)

    def dehydrate_medium_context(self, bundle):
        # Remove data that isn't needed by the API
        return remove_keys(['attributes'])

    def hydrate(self, bundle):
        """
            Set the user who created the Layer
        :param bundle:
        :return:
        """
        if not bundle.obj.id:
            bundle.obj.creator = self.resolve_user(bundle.request.GET)
        bundle.obj.updater = self.resolve_user(bundle.request.GET)
        return super(PresentationMediumResource, self).hydrate(bundle)

    def full_hydrate(self, bundle):
        super(PresentationMediumResource, self).full_hydrate(bundle)
        if not bundle.data.get(
                'id'
        ) and bundle.obj.db_entity_interest.db_entity.origin_instance:
            # If cloning, copy the medium_context.attributes
            config_entity = bundle.obj.db_entity_interest.config_entity
            origin_db_entity = bundle.obj.db_entity_interest.db_entity.origin_instance
            presentation_medium = PresentationMedium.objects.get(
                presentation__config_entity=config_entity,
                db_entity_key=origin_db_entity.key)
            bundle.data['medium']['attributes'] = presentation_medium.medium[
                'attributes']
        return bundle

    class Meta(FootprintResource.Meta):
        resource_name = 'presentation_medium'
        always_return_data = True
        queryset = PresentationMedium.objects.all()
        excludes = ['rendered_medium']
예제 #4
0
class DbEntityResource(
    FootprintResource,
    TagResourceMixin,
    TimestampResourceMixin,
    CloneableResourceMixin,
    PermissionResourceMixin,
    CategoryResourceMixin):
    hosts = fields.ListField('hosts', null=True)

    def get_object_list(self, request):
        return self.permission_get_object_list(request, super(DbEntityResource, self).get_object_list(request))

    config_entity = fields.ToOneField(ConfigEntityResource, 'config_entity', full=False)


    # This gets sent by the client and is used to set the url.
    # It is marked readonly so that tastypie doesn't try to find a matching
    # DbEntity attribute using it. I don't know how to tell tastypie to just map this
    # value to url
    upload_id = fields.CharField(null=True, readonly=True)

    # FeatureClassConfiguration isn't a model class, so we just pickle it
    feature_class_configuration = PickledDictField(attribute='feature_class_configuration_as_dict', null=True)

    layer = fields.ToOneField('footprint.main.resources.layer_resources.LayerResource', 'layer', null=True, readonly=True)

    def hydrate_feature_class_configuration(self, bundle):
        if bundle.obj.id > 0:
            del bundle.data['feature_class_configuration']
        return bundle

    # Describes the structure of the Feature class
    # TODO this should replace feature_fields of the DbEntityResource once we can model
    # Tastypie's schema object as a non-model class an make this a toOne relationship
    # That way the schema info will only be downloaded when requested
    #feature_schema = PickledDictField(attribute='feature_class', null=True, readonly=True)
    #def dehydrate_feature_schema(self, bundle):
    #    return FeatureResource().dynamic_resource_subclass(db_entity=bundle.obj)().build_schema()

    # FeatureBehavior is a settable property of DbEntity, since the relationship is actually defined
    # from FeatureBehavior to DbEntity.
    feature_behavior = ToOneField(FeatureBehaviorResource, attribute='feature_behavior', null=True)

    _content_type_ids = None
    _perm_ids = None

    def lookup_kwargs_with_identifiers(self, bundle, kwargs):
        """
            Override to remove feature_behavior from the lookup_kwargs,
            since it is actually defined in reverse--feature_behavior has a db_entity
        """

        return remove_keys(
            super(DbEntityResource, self).lookup_kwargs_with_identifiers(bundle, kwargs),
            ['feature_behavior'])


    def full_hydrate(self, bundle):
        hydrated_bundle = super(DbEntityResource, self).full_hydrate(bundle)
        # If new, Ensure the db_entity schema matches that of the config_entity
        # This happens after all hydration since it depends on two different fields
        if not hydrated_bundle.obj.id:
            hydrated_bundle.obj.schema = hydrated_bundle.obj._config_entity.schema()

        return hydrated_bundle

    def hydrate(self, bundle):
        if not bundle.data.get('id'):
            bundle.obj.creator = self.resolve_user(bundle.request.GET)
            # Update the key if this is a new instance but the key already is in use
            while DbEntity.objects.filter(key=bundle.data['key']).count() > 0:
                bundle.data['key'] = increment_key(bundle.data['key'])
            # Set this field to 0 so we can track post save progress and know when
            # the DbEntity is completely ready
            bundle.obj.setup_percent_complete = 0
            bundle.obj.key = bundle.data['key']

        bundle.obj.updater = self.resolve_user(bundle.request.GET)
        return bundle

    def dehydrate_url(self, bundle):
        # Use the upload_id to create a source url for the db_entity
        if bundle.data['url'].startswith('postgres'):
            # Never show a postgres url
            return 'Preconfigured Layer'
        else:
            return bundle.data['url']

    def hydrate_url(self, bundle):
        # Use the upload_id to create a source url for the db_entity
        if bundle.data.get('upload_id', False):
            bundle.data['url'] = 'file:///tmp/%s' % bundle.data['upload_id']
        return bundle

    def dehydrate_feature_class_configuration(self, bundle):
        return remove_keys(bundle.data['feature_class_configuration'], ['class_attrs', 'related_class_lookup']) if\
            bundle.data['feature_class_configuration'] else None

    class Meta(FootprintResource.Meta):
        always_return_data = True
        queryset = DbEntity.objects.filter(deleted=False, setup_percent_complete=100)
        excludes=['table', 'query', 'hosts', 'group_by']
        resource_name= 'db_entity'
        filtering = {
            "id": ALL,
        }
예제 #5
0
class LayerSelectionResource(DynamicResource):
    """
        An abstract resource class that is subclassed by the resources.py wrapper to match a particular layer_id
    """

    # Writable Fields

    query_strings = PickledDictField(
        attribute='query_strings',
        null=True,
        blank=True,
        default=lambda: dict(
            aggregates_string=None, filter_string=None, group_by_string=None))

    bounds = CustomGeometryApiField(attribute='bounds',
                                    null=True,
                                    blank=True,
                                    default=lambda: {})

    filter = PickledDictField(attribute='filter', null=True, blank=True)
    group_bys = PickledDictField(attribute='group_bys', null=True, blank=True)
    joins = fields.ListField(attribute='joins', null=True, blank=True)
    aggregates = PickledDictField(attribute='aggregates',
                                  null=True,
                                  blank=True)

    # Readonly Fields

    # Unique id for the Client across all LayerSelections in the system--a combination of the layer id and user id
    unique_id = fields.CharField(attribute='unique_id',
                                 null=False,
                                 readonly=True)

    user = fields.ToOneField(UserResource, 'user', readonly=True, full=False)

    # The number of features in selected_features. The LayerSelection doesn't need to include Features
    # or their ids. Features will be downloaded separately via a FeatureResource request with the
    # LayerSelection unique_id as a parameter
    features_count = fields.IntegerField(attribute='features_count',
                                         readonly=True,
                                         default=0)

    # Profiles result_fields, a result title lookup, and result mapping
    result_map = PickledDictField(attribute='result_map',
                                  null=True,
                                  readonly=True)

    summary_results = fields.ListField(attribute='summary_results',
                                       null=True,
                                       blank=True,
                                       readonly=True,
                                       default=[])
    summary_fields = fields.ListField(attribute='summary_fields',
                                      null=True,
                                      blank=True,
                                      readonly=True,
                                      default=[])
    summary_field_title_lookup = PickledDictField(
        attribute='summary_field_title_lookup',
        null=True,
        blank=True,
        readonly=True)

    query_sql = fields.CharField(attribute='query_sql',
                                 null=True,
                                 readonly=True)
    summary_query_sql = fields.CharField(attribute='summary_query_sql',
                                         null=True,
                                         readonly=True)

    # The layer instance is not a LayerSelection field, but a property of the LayerSelection subclass

    @using_bundle_cache
    def selection_layer_queryset(bundle):
        return bundle.obj.__class__.layer

    selection_layer = fields.ToOneField(LayerResource,
                                        attribute=selection_layer_queryset,
                                        readonly=True,
                                        full=False)

    selection_extent = CustomGeometryApiField(attribute='selection_extent',
                                              null=True,
                                              blank=True,
                                              default=lambda: {},
                                              readonly=True)

    # TODO remove
    filter_by_selection = fields.BooleanField(attribute='filter_by_selection',
                                              default=False)
    # TODO unused
    selection_options = PickledDictField(
        attribute='selection_options',
        null=True,
        blank=True,
        default=lambda: dict(constrain_to_bounds=True,
                             constrain_to_previous_results=False))

    def full_hydrate(self, bundle, for_list=False):
        """
            Clear the previous bounds or query if the other is sent
        :param bundle:
        :return:
        """

        # Remove the computed properties. Some or all will be set
        bundle.obj.summary_results = None
        bundle.obj.summary_fields = None
        bundle.obj.summary_field_title_lookup = None

        # Call super to populate the bundle.obj
        bundle = super(LayerSelectionResource, self).full_hydrate(bundle)

        # Default these to None
        for attr in ['filter', 'aggregates', 'group_bys', 'joins', 'bounds']:
            if not bundle.data.get(attr, None):
                setattr(bundle.obj, attr, None)

        # Update the features and related derived fields to the queryset
        bundle.obj.sync_to_query()
        return bundle

    def query_data_specified(self, data):
        return data.get('query', None)

    def create_subclass(self, params, **kwargs):
        """
            Subclasses the LayerSelectionResource instance's class for the given config_entity and layer.
        :param params Must contain a 'config_entity__id' and 'layer__id'
        :return:
        """

        layer = self.resolve_layer(params)
        config_entity = self.resolve_config_entity(params)

        logger.debug(
            "Resolving LayerSelection for config_entity %s, layer %s" %
            (config_entity.key, layer.db_entity.key))
        layer_selection_class = get_or_create_layer_selection_class_for_layer(
            layer, config_entity)
        # Have the LayerPublisher create the LayerSelection instance for the user if needed
        update_or_create_layer_selections_for_layer(
            layer, users=[self.resolve_user(params)])

        if not layer_selection_class:
            raise Exception(
                "Layer with db_entity_key %s has no feature_class. Its LayerSelections should not be requested"
                % layer.db_entity_key)
        return get_dynamic_resource_class(self.__class__,
                                          layer_selection_class)

    def search_params(self, params):
        """
        :param params
        :return:
        """
        user = get_user_model().objects.get(username=params['username'])
        return dict(user__id=user.id)

    def resolve_config_entity(self, params):
        """
            If the ConfigEntity param is specified it gets precedence. Otherwise
            use the Layer param
        :param params:
        :return:
        """
        if params.get('config_entity__id'):
            return ConfigEntity.objects.get_subclass(
                id=params['config_entity__id'])
        else:
            return Layer.objects.get(id=params['layer__id']).config_entity

    def create_layer_from_layer_selection(self, params):
        """
            Used to create a new Layer from the current LayerSelection features
        :param params:
        :return:
        """
        # Resolve the source layer from the layer_selection__id
        source_layer = self.resolve_layer(params)
        config_entity = source_layer.config_entity
        db_entity = source_layer.db_entity_interest.db_enitty
        feature_class = FeatureClassCreator(config_entity,
                                            db_entity).dynamic_model_class()
        layer = Layer.objects.get(presentation__config_entity=config_entity,
                                  db_entity_key=db_entity.key)
        layer_selection = get_or_create_layer_selection_class_for_layer(
            layer, config_entity, False).objects.all()[0]
        # TODO no need to do geojson here
        feature_dict = dict(type="Feature")
        feature_dicts = map(
            lambda feature:
            deep_merge(feature_dict,
                       {"geometry": geojson.loads(feature.wkb_geometry.json)}),
            layer_selection.selected_features or feature_class.objects.all())
        json = dict({"type": "FeatureCollection", "features": feature_dicts})
        db_entity_configuration = update_or_create_db_entity(
            config_entity,
            **dict(class_scope=FutureScenario,
                   name='Import From Selection Test',
                   key='import_selection_test',
                   url='file://notusingthis'))
        self.make_geojson_db_entity(config_entity,
                                    db_entity_configuration,
                                    data=json)

    class Meta(DynamicResource.Meta):
        abstract = True
        filtering = {
            # There is only one instance per user_id. This should always be specified for GETs
            "user": ALL_WITH_RELATIONS,
            "id": ALL
        }
        always_return_data = True
        # We don't want to deliver this, the user only sees and manipulates the bounds
        excludes = ['geometry']
        resource_name = 'layer_selection'
예제 #6
0
    def dynamic_resource_subclass(self,
                                  layer_selection=None,
                                  db_entity=None,
                                  feature_class=None,
                                  config_entity=None,
                                  metadata=None,
                                  params=None,
                                  **kwargs):
        """
            Creates the dynamic Feature Resource class by passing in a layer_selection, db_entity, or feature_class
        :param layer_selection: Required if db_entity or metadata aren't present
        :param db_entity: Required if layer_selection or metadata aren't present
        :param metadata: Required along with config_entity if layer_selection or db_entity aren't present
        :param kwargs:
        :return:
        """
        feature_class_configuration = None
        if layer_selection:
            # Coming in relative to a LayerSelection, which puts us in the context of the LayerSelection's
            # feature query for this Feature subclass
            layer = layer_selection.layer
            # If we pass in a ConfigEntity it means we want to scope the Feature class to its scope.
            # The ConfigEntity defaults to that of the Layer, but we can override it to be a lower
            # scope to make sure that we have access to lower DbEntities of performing joins
            config_entity = config_entity.subclassed if config_entity else layer.config_entity.subclassed
            logger.debug(
                "Resolving FeatureResource subclass for layer_selection: {0}, config_entity: {1}"
                .format(layer_selection.unique_id, config_entity.id))
            # Resolve the dynamic Feature class with the given config_entity so that we can access all DbEntities
            # of the ConfigEntity for joins
            feature_class = config_entity.db_entity_feature_class(
                layer.db_entity.key)
        elif db_entity:
            # Coming in relative to a DbEntity, meaning we don't care about a particular LayerSelection's
            # feature query for this Feature subclass
            config_entity = db_entity.config_entity
            logger.debug(
                "Resolving FeatureResource subclass for db_entity: {0}, config_entity: {1}"
                .format(db_entity.id, config_entity.id))
            # Resolve the dynamic Feature class with the given config_entity so that we can access all DbEntities
            # of the ConfigEntity for joins
            feature_class = config_entity.db_entity_feature_class(
                db_entity.key)
        elif metadata:
            # Coming in with metadata, meaning this is and uploaded or ArcGis table with no DbEntity yet
            # We need to construct a FeatureClass from the metadata
            logger.debug(
                "Resolving FeatureResource subclass for metadata: {0}, config_entity: {1}"
                .format(metadata, config_entity.id))
            feature_class_creator = FeatureClassCreator(config_entity)
            feature_class_configuration = feature_class_creator.feature_class_configuration_from_metadata(
                metadata['schema'])
            feature_class = FeatureClassCreator(
                config_entity,
                feature_class_configuration).dynamic_model_class()

        if not feature_class_configuration:
            # If we didn't already ensure all dynamic model classes have been created
            # This only need to run once to get all dynamic feature subclasses into memory,
            # in case they are needed by an association, join, or something similar
            feature_class_creator = FeatureClassCreator.from_dynamic_model_class(
                feature_class)
            feature_class_creator.ensure_dynamic_models()

        logger.debug("Resolving resource for Feature subclass: {0}".format(
            feature_class))

        # Resolve the FeatureResource subclass based on the given Feature subclass
        # If self is already a subclass, just return self
        # Else, return a preconfigured subclass or one dynamically created. The latter will probably be the only way in the future.
        # If not already subclassed
        is_singleton_feature = issubclass(self.__class__,
                                          SingletonFeatureResourceMixin)
        is_template_feature = self.__class__ == TemplateFeatureResource
        if self.__class__ in [
                FeatureResource, TemplateFeatureResource,
                FeatureCategoryAttributeResource,
                FeatureQuantitativeAttributeResource
        ]:
            if is_singleton_feature or params.get('is_feature_attribute'):
                queryset = feature_class.objects.none()
            elif kwargs.get('method', None) == 'PATCH':
                # It's possible to PATCH with an active join query.
                # But we don't want to use a join query when patching
                queryset = feature_class.objects.all()
            else:
                # Get the queryset stored by the layer_selection or an empty query if we don't have a layer_selection
                queryset = layer_selection.selected_features_or_values if\
                    layer_selection else \
                    feature_class.objects.none()

                if layer_selection and not (is_singleton_feature or kwargs.get(
                        'query_may_be_empty')) and queryset.count() == 0:
                    raise Exception(
                        "Unexpected empty queryset for layer_selection features: %s"
                        % queryset.query)
            is_values_queryset = isinstance(queryset, ValuesQuerySet)

            #returns queryset ordered by the table id
            queryset = queryset.order_by('id')

            if is_values_queryset:
                join_feature_class = layer_selection.create_join_feature_class(
                ) if is_values_queryset else feature_class
                logger.info("Created join_feature_class: %s" %
                            join_feature_class)
                # Force the queryset to our new class so that Tastypie can map the dict results to it
                queryset.model = join_feature_class

                return self.__class__.resolve_resource_class(
                    join_feature_class,
                    queryset=queryset,
                    base_resource_class=self.join_feature_resource_class(
                        join_feature_class),
                    additional_fields_dict=dict(
                        # Pass these to the feature resource to help it resolve
                        # field mappings and add related fields (just need for join_feature_class)
                        # Use the layer_selection if it exists since it might have filtered or extra query fields
                        result_field_lookup=(layer_selection
                                             or db_entity).result_field_lookup
                        if not metadata else {},
                        related_field_lookup=(
                            layer_selection or db_entity).related_field_lookup
                        if not metadata else {},
                        # We use these in the FeatureResource to create a unique id for each join Feature
                        join_model_attributes=layer_selection
                        and layer_selection.result_map.join_model_attributes),
                    is_join_query=True,
                    limit_fields=layer_selection.result_map['result_fields'])
            else:
                abstract_feature_resource_class = self.__class__
                resource_class = abstract_feature_resource_class.resolve_resource_class(
                    feature_class,
                    queryset=queryset,
                    # Give FeatureResource a reference to the layer_selection
                    additional_fields_dict=merge(
                        dict(
                            # Pass this to the feature resource to help it resolve field mappings
                            result_field_lookup=(layer_selection or db_entity).
                            result_field_lookup if not metadata else {}),
                        dict(
                            # Not sure why it doesn't work to just stick this on the TemplateFeatureResource
                            feature_fields=ListField(
                                attribute='feature_fields',
                                null=True,
                                blank=True,
                                readonly=True),
                            feature_field_title_lookup=PickledDictField(
                                attribute='feature_field_title_lookup',
                                null=True,
                                blank=True,
                                readonly=True),
                        ) if is_template_feature else dict()),
                    for_template=is_template_feature)
                return resource_class
        return self