Example #1
0
    def to_python(self, value):
        """
            Resolve the model from the class name and id
        """
        if value is not None:
            try:
                value = dbsafe_decode(value, self.compress)
            except:
                # If the value is a definite pickle; and an error is raised in
                # de-pickling it should be allowed to propogate.
                if isinstance(value, PickledObject):
                    raise
            else:
                # If the value was encoded (not from cache, convert it here back to our
                # desired format)
                if value:
                    if isinstance(value, _ObjectWrapper):
                        unwrapped_value = value._obj
                    else:
                        unwrapped_value = value

                    return map_dict_to_dict(
                        lambda key, inner_dict: [
                            key,
                            map_dict_to_dict(
                                lambda inner_key, model_dict: [
                                    inner_key,
                                    self.model_from_class_name_and_pk(
                                        model_dict)
                                ], inner_dict)
                        ], unwrapped_value)
        # Value from cache, leave alone
        return value
Example #2
0
    def to_python(self, value):
        """
            Resolve the model from the class name and id
        """
        if value is not None:
            try:
                value = dbsafe_decode(value, self.compress)
            except:
                # If the value is a definite pickle; and an error is raised in
                # de-pickling it should be allowed to propogate.
                if isinstance(value, PickledObject):
                    raise
            else:
                # If the value was encoded (not from cache, convert it here back to our
                # desired format)
                if value:
                    if isinstance(value, _ObjectWrapper):
                        unwrapped_value = value._obj
                    else:
                        unwrapped_value = value

                    return map_dict_to_dict(lambda key, inner_dict:
                        [key, map_dict_to_dict(lambda inner_key, model_dict:
                            [inner_key,
                             self.model_from_class_name_and_pk(model_dict)],
                            inner_dict
                        )],
                        unwrapped_value)
        # Value from cache, leave alone
        return value
    def create_result_map(self, related_models=[], map_path_segments={}):
        """
            Given the field_paths of the queryset, returns a ResultMap instance.
            ResultMap.result_fields is a list of field_paths minus specifically omitted ones--
            the parent id and geometry column.
            ResultMap.title_lookup is a lookup from the field_path to a title appropriate for the user.
            The generated title uses '.' in place of '__'
            ResultMap.value_map is a lookup from the field_path to a property path that describes
            how to convert the value to a more human readable form. This is used to convert
            instances to a readable label and dates, etc, to a more readable format.
            :param: related_models: pass the related_models represented in the query results so that unneeded
            paraent reference fields can be removed from the result fields
            :param: map_path_segments: An optional dict that matches segments of the field_paths. The value
            corresponding the key is the name to convert it to for the title. If the value is None it will
            be eliminated from the path when it is rejoined with '.'
        """

        result_paths = self.model_result_paths(related_models)
        # Get a mapping of the each result field_path to its field class path along
        # with the related model of that field, if the field is a ForeignKey or AutoField
        result_field_path_lookup = self.model_result_field_path_field_lookup(
            related_models, True)
        join_models = map(lambda model: full_module_path(model.__base__),
                          related_models)
        return ResultMap(
            # Replace '__' with '_x_'. We can't use __ because it confuses tastypie
            result_fields=map(lambda path: string.replace(path, '__', '_x_'),
                              result_paths),
            # Create a lookup from field name to title
            # The only processing we do to the title is to remove the middle path
            title_lookup=map_to_dict(
                lambda path: [
                    # Replace '__' with '_x_'. We can't use __ because it confuses tastypie
                    string.replace(path, '__', '_x_'),
                    # match each segment to map_path_segments or failing that return the segment
                    # remove segments that map to None
                    '__'.join(
                        compact(
                            map(
                                lambda segment: map_path_segments.get(
                                    segment, segment), path.split('__'))))
                ],
                result_paths),
            field_lookup=map_dict_to_dict(
                lambda field_path, tup: [field_path, tup[0]],
                result_field_path_lookup),
            related_model_lookup=compact_dict(
                map_dict_to_dict(lambda field_path, tup: [field_path, tup[1]],
                                 result_field_path_lookup)),
            join_models=join_models,
        )
Example #4
0
 def get_db_prep_value(self, value, connection=None, prepared=False):
     """
         Simplify the model instance to an id and class name to avoid
         expensive pickling
     """
     mapped_dict = map_dict_to_dict(lambda key, inner_dict:
         [key, map_dict_to_dict(lambda inner_key, model_instance:
             [inner_key, dict(
                 class_name=resolvable_model_name(model_instance.__class__),
                 pk=self.pk_of_model(model_instance)
             )] if model_instance else None, # Should never be null, but sometimes i
             inner_dict
         )],
         value)
     return super(SelectionModelsPickledObjectField, self).get_db_prep_value(mapped_dict, connection, prepared)
    def dehydrate(self, bundle):
        """
            Override to add in the __label attributes. These are the human readable
            version of attributes that represent objects or more appropriate formatting,
            such as for dates
        :param bundle:
        :return:
        """

        # This is a dynamic field create at the time of creating the FeatureResource subclass
        result_field_lookup = self.result_field_lookup

        # Map the fields that need special human-readable representation
        # to labels. We iterate through the result_field_lookup that
        # the LayerSelection generated for this.
        # Not the conditional statement is for the "corner" case
        # of patching Features with joins specified. Patches always
        # ignore the JoinFeature class and use the regular Feature class,
        # however the result_field_lookup still contains join fields--ignore them
        bundle.data['__labels'] = compact_dict(map_dict_to_dict(
            lambda field_name, field_lambda: [
                field_name,
                field_lambda(getattr(bundle.obj, self.client_field_name(field_name)))
            ] if '__' not in field_name else None,
            result_field_lookup
        ))
        return super(FeatureResource, self).dehydrate(bundle)
            def full_dehydrate(self, bundle, for_list=False):
                # Convert the dict to the unmanaged join_feature_class instance
                # Since our JoinFeatureClass uses the client field names, we must map them back to server names
                # To do the lookup below
                field_name_lookup = map_to_dict(
                    lambda field: [string.replace(field.name, self.FIELD_RELATION_CLIENT, self.FIELD_RELATION_SERVER), True],
                    join_feature_class._meta.fields)

                # Map mapping fields from __ to _x_ so Tastypie|Django isn't confused by them
                dct = map_dict_to_dict(
                    lambda key, value: [self.client_field_name(key), value] if
                        field_name_lookup.get(key) else
                        None,
                    bundle.obj)
                obj = join_feature_class()
                for key, value in dct.items():
                    setattr(obj, key, value)
                # The object needs a pk to create its resource_uri
                setattr(obj, 'pk', obj.id)
                new_bundle = self.build_bundle(obj=obj, request=bundle.request)
                # This isn't automatically included like for the normal FeatureResource
                # The unique id must be unique across all joined Feature classes
                new_bundle.data['the_unique_id'] = '_'.join(
                    [obj.the_unique_id] +\
                    map(lambda attr: str(getattr(obj, attr)), self.join_model_attributes)
                )
                return super(JoinFeatureResource, self).full_dehydrate(new_bundle, for_list)
Example #7
0
def unbundle(bundle):
    if isinstance(bundle, Bundle):
        return map_dict_to_dict(lambda attr, value: [attr, unbundle(value)], bundle.data)
    elif is_list_or_tuple(bundle):
        return map(lambda value: unbundle(value), bundle)
    else:
        return bundle
Example #8
0
            def full_dehydrate(self, bundle, for_list=False):
                # Convert the dict to the unmanaged join_feature_class instance
                # Since our JoinFeatureClass uses the client field names, we must map them back to server names
                # To do the lookup below
                field_name_lookup = map_to_dict(
                    lambda field: [
                        string.replace(field.name, self.FIELD_RELATION_CLIENT,
                                       self.FIELD_RELATION_SERVER), True
                    ], join_feature_class._meta.fields)

                # Map mapping fields from __ to _x_ so Tastypie|Django isn't confused by them
                dct = map_dict_to_dict(
                    lambda key, value: [self.client_field_name(key), value]
                    if field_name_lookup.get(key) else None, bundle.obj)
                obj = join_feature_class()
                for key, value in dct.items():
                    setattr(obj, key, value)
                # The object needs a pk to create its resource_uri
                setattr(obj, 'pk', obj.id)
                new_bundle = self.build_bundle(obj=obj, request=bundle.request)
                # This isn't automatically included like for the normal FeatureResource
                # The unique id must be unique across all joined Feature classes
                new_bundle.data['the_unique_id'] = '_'.join(
                    [obj.the_unique_id] +\
                    map(lambda attr: str(getattr(obj, attr)), self.join_model_attributes)
                )
                return super(JoinFeatureResource,
                             self).full_dehydrate(new_bundle, for_list)
Example #9
0
    def dehydrate(self, bundle):
        """
            Override to add in the __label attributes. These are the human readable
            version of attributes that represent objects or more appropriate formatting,
            such as for dates
        :param bundle:
        :return:
        """

        # This is a dynamic field create at the time of creating the FeatureResource subclass
        result_field_lookup = self.result_field_lookup

        # Map the fields that need special human-readable representation
        # to labels. We iterate through the result_field_lookup that
        # the LayerSelection generated for this.
        # Not the conditional statement is for the "corner" case
        # of patching Features with joins specified. Patches always
        # ignore the JoinFeature class and use the regular Feature class,
        # however the result_field_lookup still contains join fields--ignore them
        bundle.data['__labels'] = compact_dict(
            map_dict_to_dict(
                lambda field_name, field_lambda: [
                    field_name,
                    field_lambda(
                        getattr(bundle.obj, self.client_field_name(field_name))
                    )
                ] if '__' not in field_name else None, result_field_lookup))
        return super(FeatureResource, self).dehydrate(bundle)
Example #10
0
    def create_result_map(self, related_models=[], map_path_segments={}):
        """
            Given the field_paths of the queryset, returns a ResultMap instance.
            ResultMap.result_fields is a list of field_paths minus specifically omitted ones--
            the parent id and geometry column.
            ResultMap.title_lookup is a lookup from the field_path to a title appropriate for the user.
            The generated title uses '.' in place of '__'
            ResultMap.value_map is a lookup from the field_path to a property path that describes
            how to convert the value to a more human readable form. This is used to convert
            instances to a readable label and dates, etc, to a more readable format.
            :param: related_models: pass the related_models represented in the query results so that unneeded
            paraent reference fields can be removed from the result fields
            :param: map_path_segments: An optional dict that matches segments of the field_paths. The value
            corresponding the key is the name to convert it to for the title. If the value is None it will
            be eliminated from the path when it is rejoined with '.'
        """

        result_paths = self.model_result_paths(related_models)
        # Get a mapping of the each result field_path to its field class path along
        # with the related model of that field, if the field is a ForeignKey or AutoField
        result_field_path_lookup = self.model_result_field_path_field_lookup(related_models, True)
        join_models = map(lambda model: full_module_path(model.__base__), related_models)
        return ResultMap(
            # Replace '__' with '_x_'. We can't use __ because it confuses tastypie
            result_fields=map(lambda path: string.replace(path, '__', '_x_'), result_paths),
            # Create a lookup from field name to title
            # The only processing we do to the title is to remove the middle path
            title_lookup=map_to_dict(
                lambda path: [
                    # Replace '__' with '_x_'. We can't use __ because it confuses tastypie
                    string.replace(path, '__', '_x_'),
                    # match each segment to map_path_segments or failing that return the segment
                    # remove segments that map to None
                    '__'.join(compact(
                        map(
                            lambda segment: map_path_segments.get(segment, segment),
                            path.split('__')
                        )
                    ))
                ],
                result_paths
            ),
            field_lookup=map_dict_to_dict(lambda field_path, tup: [field_path, tup[0]], result_field_path_lookup),
            related_model_lookup=compact_dict(map_dict_to_dict(lambda field_path, tup: [field_path, tup[1]], result_field_path_lookup)),
            join_models=join_models,

        )
Example #11
0
def unbundle(bundle):
    if isinstance(bundle, Bundle):
        return map_dict_to_dict(
            lambda attr, value: [attr, unbundle(value)], bundle.data)
    elif is_list_or_tuple(bundle):
        return map(lambda value: unbundle(value), bundle)
    else:
        return bundle
 def related_descriptors(self):
     """
         Returns the existing related field descriptors that were created by create_related_fields and assigned
         to the dynamic feature class
     :return: A dict keyed by field name, valued by the ManyToMany field or equivalent
     """
     dynamic_model_class = self.dynamic_model_class()
     return map_dict_to_dict(
         lambda field_name, related_field_configuration: [field_name, getattr(dynamic_model_class, field_name)],
         self.configuration.related_fields or {})
 def create_related_fields(self):
     """
         Create ForeignKey and ManyFields for each self.configuration.related_fields
     :return:
     """
     return map_dict_to_dict(
         lambda field_name, related_field_configuration: self.create_related_field(
             field_name,
             related_field_configuration),
         self.configuration.related_fields or {})
 def related_descriptors(self):
     """
         Returns the existing related field descriptors that were created by create_related_fields and assigned
         to the dynamic feature class
     :return: A dict keyed by field name, valued by the ManyToMany field or equivalent
     """
     dynamic_model_class = self.dynamic_model_class()
     return map_dict_to_dict(
         lambda field_name, related_field_configuration: [field_name, getattr(dynamic_model_class, field_name)],
         self.configuration.related_fields or {})
 def create_related_fields(self):
     """
         Create ForeignKey and ManyFields for each self.configuration.related_fields
     :return:
     """
     return map_dict_to_dict(
         lambda field_name, related_field_configuration: self.create_related_field(
             field_name,
             related_field_configuration),
         self.configuration.related_fields or {})
Example #16
0
 def owned_db_entities(self, **query_kwargs):
     """
         Returns non-adopted DbEntity instances
     """
     return map(
         lambda db_entity_interest: db_entity_interest.db_entity,
         self.owned_db_entity_interests(
             # add a db_entity__ prefix since the kwargs are desiged for a DbEntity, not the DbEntityInterest
             **map_dict_to_dict(lambda key, value: ['db_entity__%s' % key, value], query_kwargs)
         )
     )
Example #17
0
 def owned_db_entities(self, **query_kwargs):
     """
         Returns non-adopted DbEntity instances
     """
     return map(
         lambda db_entity_interest: db_entity_interest.db_entity,
         self.owned_db_entity_interests(
             # add a db_entity__ prefix since the kwargs are desiged for a DbEntity, not the DbEntityInterest
             **map_dict_to_dict(
                 lambda key, value: ['db_entity__%s' % key, value],
                 query_kwargs)))
Example #18
0
 def get_db_prep_value(self, value, connection=None, prepared=False):
     """
         Simplify the model instance to an id and class name to avoid
         expensive pickling
     """
     mapped_dict = map_dict_to_dict(
         lambda key, inner_dict: [
             key,
             map_dict_to_dict(
                 lambda inner_key, model_instance: [
                     inner_key,
                     dict(class_name=resolvable_model_name(model_instance.
                                                           __class__),
                          pk=self.pk_of_model(model_instance))
                 ] if model_instance else
                 None,  # Should never be null, but sometimes i
                 inner_dict)
         ],
         value)
     return super(SelectionModelsPickledObjectField,
                  self).get_db_prep_value(mapped_dict, connection, prepared)
Example #19
0
    def result_field_lookup(self):
        """
            Returns a lookup that maps some field names to a lambda that describes
            how to create a human readable form of the field value. This is used by the FeatureResource
        :return:
        """

        if not self.result_map:
            # If a query has never been stored, we need to sync to an empty query
            self.sync_to_query()

        return compact_dict(
            map_dict_to_dict(self.field_map, self.result_map.field_lookup))
    def result_field_lookup(self):
        """
            Returns a lookup that maps some field names to a lambda that describes
            how to create a human readable form of the field value. This is used by the FeatureResource
        :return:
        """

        if not self.result_map:
            # If a query has never been stored, we need to sync to an empty query
            self.sync_to_query()

        return compact_dict(map_dict_to_dict(
            self.field_map,
            self.result_map.field_lookup))
Example #21
0
        def feature(bundle):
            # Get the current version of the Feature instance
            feature_instance = feature_class.objects.get(id=bundle.obj.field_dict['id'])
            # Update it (without saving). This doesn't take care of our related fields
            feature_instance.__dict__.update(**bundle.obj.field_dict)
            # Take care of our related fields by settings the Revisionable mixin's _version_field_dict
            # We instruct the related Resource field to check this _version_field_dict
            # to grab the versioned value
            feature_instance._version_field_dict = map_dict_to_dict(
                lambda key, value: [key, get_versioned_version(bundle.obj, feature_class, key)],
                feature_class.dynamic_model_class_creator.related_descriptors())

            # Set the magic version property so that we grab the right meta data
            feature_instance._version = bundle.obj
            return feature_instance
        def feature(bundle):
            # Get the current version of the Feature instance
            feature_instance = feature_class.objects.get(id=bundle.obj.field_dict['id'])
            # Update it (without saving). This doesn't take care of our related fields
            feature_instance.__dict__.update(**bundle.obj.field_dict)
            # Take care of our related fields by settings the Revisionable mixin's _version_field_dict
            # We instruct the related Resource field to check this _version_field_dict
            # to grab the versioned value
            feature_instance._version_field_dict = map_dict_to_dict(
                lambda key, value: [key, get_versioned_version(bundle.obj, feature_class, key)],
                feature_class.dynamic_model_class_creator.related_descriptors())

            # Set the magic version property so that we grab the right meta data
            feature_instance._version = bundle.obj
            return feature_instance
Example #23
0
    def related_field_lookup(self):
        """
            Only for join queries
            Looks for any field of type AutoField and returns a key value of the related field,
            except for the id field. This is not for related_models being joined, but rather
            for models related to the feature tables, like BuiltForm
            For example built_form_id=AutoField maps to built_form_id=lambda built_form_id:
            :param The JoinFeature class
        :return:
        """
        def related_field_map(field_path, related_model_class_path):
            """
                This helps join feature_classes resolve related models,
                such as BuiltForms. The join feature class can't do this
                through the query since we have to use a values() query.
                Thus the query only contains built_form_id and not built_form with a
                reference to the built_form instance. The API needs to be
                able to return a property built_form=uri so we tell it how do
                do that here.
            :param field_path:
            :param related_model_class_path:
            :return: a key and value, the key is the escaped path (using _x_ for __) with the
             final _id removed and the value is
            the related model class. Ex: built_form_id results in
            [built_form, BuiltForm] for the main model or geographies_[scope_id]_end_state_feature__built_form_id in
            [geographies_[scope_id]_x_end_state_feature_x_built_form, BuiltForm]
            """

            # For static classes you module name resolution. For dynamic classes rely on current truth
            # that dynamic classes are all created in the main app, since module name resolution relies
            # on static module files
            logger.debug("Resolving %s" % related_model_class_path if\
                'dynamic_subclassing' not in related_model_class_path else\
                'main.%s' % related_model_class_path.split('.')[-1])

            related_model_class = resolve_module_attr(related_model_class_path)

            escaped_field_path = field_path.replace(r'_id$',
                                                    '').replace('__', '_x_')
            return [escaped_field_path, related_model_class]

        return compact_dict(
            map_dict_to_dict(related_field_map,
                             self.result_map.related_model_lookup))
    def related_field_lookup(self):
        """
            Only for join queries
            Looks for any field of type AutoField and returns a key value of the related field,
            except for the id field. This is not for related_models being joined, but rather
            for models related to the feature tables, like BuiltForm
            For example built_form_id=AutoField maps to built_form_id=lambda built_form_id:
            :param The JoinFeature class
        :return:
        """
        def related_field_map(field_path, related_model_class_path):
            """
                This helps join feature_classes resolve related models,
                such as BuiltForms. The join feature class can't do this
                through the query since we have to use a values() query.
                Thus the query only contains built_form_id and not built_form with a
                reference to the built_form instance. The API needs to be
                able to return a property built_form=uri so we tell it how do
                do that here.
            :param field_path:
            :param related_model_class_path:
            :return: a key and value, the key is the escaped path (using _x_ for __) with the
             final _id removed and the value is
            the related model class. Ex: built_form_id results in
            [built_form, BuiltForm] for the main model or geographies_[scope_id]_end_state_feature__built_form_id in
            [geographies_[scope_id]_x_end_state_feature_x_built_form, BuiltForm]
            """

            # For static classes you module name resolution. For dynamic classes rely on current truth
            # that dynamic classes are all created in the main app, since module name resolution relies
            # on static module files
            logger.debug("Resolving %s" % related_model_class_path if\
                'dynamic_subclassing' not in related_model_class_path else\
                'main.%s' % related_model_class_path.split('.')[-1])

            related_model_class = resolve_module_attr(related_model_class_path)

            escaped_field_path = field_path.replace(r'_id$', '').replace('__', '_x_')
            return [escaped_field_path, related_model_class]

        return compact_dict(map_dict_to_dict(
            related_field_map,
            self.result_map.related_model_lookup))
Example #25
0
    def resolve_resource_class(cls,
                               model_class,
                               related_descriptors=None,
                               queryset=None,
                               base_resource_class=None,
                               object_class=None,
                               no_cache=False,
                               use_version_fields=False,
                               only_abstract_resources=True,
                               additional_fields_dict=None,
                               is_join_query=False,
                               limit_fields=None,
                               for_template=False):
        """
            Match the model_class to an existing resource class by iterating through subclasses of self.__class__
            If no match occurs generate one if db_entity is specified, else None
        :param model_class:
        :param related_descriptors: Optionally provided a dictionary of related descriptors of the model_class to use to create related resource fields
        in the case of dynamic resource creation. If unspecified, related_descriptors are found using the base resource class's resolve_related_descriptors method
        :param no_cache: Don't use cached resources. Specifying queryset also prevents the cache being consulted
        :param use_version_fields: Special case for versioned models. Instructs the resource class creation
         to create dynamic fields to look in the model_class's Revisionable._version_field_dict to resolve
         the value of a field before looking at the database (current object) version of the field.
        :param additional_fields_dict. Optional dict that gives the resource class additional fields
        with values. This is not for related fields, rather special cases like passing the layer_selection
        to the FeatureResource.
        :param for_template: Currently just used for Features, but this indicates a template version of the
        resource is being sought
        :param is_join_query: True if the is a join query
        table or similar. This means that the queryset isn't actually valid yet
        :return: The matching or created resource
        """
        # Never seek a cached resource if a queryset or no_cache is specified
        allow_caching = queryset is None and not no_cache

        cls.logger.info("Resolving Resource Class for model_class %s with query %s and allow_caching %s and only_abstract_resources %s and base_resource_class %s",
            model_class.__name__, queryset, allow_caching, only_abstract_resources, base_resource_class)
        if not base_resource_class and allow_caching:
            # Try to get a cached resource if a base_resource_class is present and we allow caching
            # of this resource. We never allow caching if the queryset changes from request to request
            # Instead we subclass the best matching existing resource class
            resources = FootprintResource.match_existing_resources(model_class)
            if len(resources) > 1:
                logging.warn("Too many resources for model class: %s. Resources; %s" % (model_class, resources))
            if (len(resources) > 0):
                cls.logger.info("Found existing resource class %s for model class %s" % (resources[0], model_class))
                # Done, return the existing resource
                return resources[0]

        # If we don't allow caching or no resource was found
        if only_abstract_resources:
            # See if we can match an abstract resource to be our base.
            # We look for the best matching resource who's model class matches or is a superclass or our model class.
            base_resource_classes = FootprintResource.match_existing_resources(model_class, only_abstract_resources=True)
            cls.logger.info("Matching abstract base resource classes: %s" % base_resource_classes)
            base_resource_class = base_resource_classes[0] if len(base_resource_classes)==1 else base_resource_class
        else:
            # Fist try to find a concrete one that we already created
            concrete_base_resource_classes = FootprintResource.match_existing_resources(model_class, allow_abstract_resources=False)
            if len(concrete_base_resource_classes)==1:
                # Found a concrete resource class that was already created for this model
                # If this isn't a join query we can return this resource class
                base_resource_class = concrete_base_resource_classes[0]
                if not is_join_query:
                    return base_resource_class
            else:
                # Try to find an abstract one
                all_base_resource_classes = FootprintResource.match_existing_resources(model_class, only_abstract_resources=True)
                cls.logger.info("Matching abstract base resource classes: %s" % all_base_resource_classes)
                base_resource_class = all_base_resource_classes[0] if len(all_base_resource_classes)==1 else base_resource_class

        if is_join_query:
            # Join queries must pass in the limited fields from the layer_selection.result_map.result_fields
            limit_api_fields = limit_fields
        else:
            # Non-join queries find their fields from the model_class
            limit_api_fields = limited_api_fields(model_class, for_template=for_template)

        resource_class = base_resource_class or cls
        if base_resource_class:
            cls.logger.info("Resolved base_resource_class: %s" % base_resource_class)
        else:
            cls.logger.info("Could not resolve a base_resource_class. Using resource class %s" % cls)
        related_descriptors = related_descriptors or \
                              resource_class.resolve_related_descriptors(model_class) or \
                              {}

        cls.logger.info("Creating a resource subclass of %s for model_class %s with the following related descriptors %s" % (
            resource_class,
            model_class,
            ', '.join(map_dict(
                lambda related_field_name, related_descriptor: related_field_name,
                related_descriptors
            ))))
        related_fields = merge(additional_fields_dict or {}, map_dict_to_dict(
            lambda related_field_name, relationship_dict: cls.related_resource_field(
                related_field_name,
                merge(
                    dict(
                        # This is passed in, but might be overriden in the relationship_dict
                        use_version_fields=use_version_fields
                    ),
                    # Already a relationship dict
                    relationship_dict if isinstance(relationship_dict, dict) else dict(),
                    # Not a relationship dict, just a descriptor, wrap it
                    dict(descriptor=relationship_dict) if not isinstance(relationship_dict, dict) else dict(),
                ),
                # Always allow cached related resource classes.
                # I think this is always safe
                no_cache=False),
            related_descriptors))
        meta = merge(
            dict(fields=limit_api_fields) if limit_api_fields else
                dict(excludes=(resource_class.Meta.excludes if hasattr(
                  resource_class.Meta, 'excludes') else []) + cls.dynamic_excludes(
                model_class)),
            dict(queryset=queryset, object_class=object_class) if queryset else dict())
        return get_dynamic_resource_class(
            resource_class,
            model_class,
            # Created related fields from the related_descriptors
            fields=related_fields,
            # Optionally set the fields and queryset on the Meta class
            meta_fields=meta
        )
    def resolve_resource_class(cls,
                               model_class,
                               related_descriptors=None,
                               queryset=None,
                               base_resource_class=None,
                               object_class=None,
                               no_cache=False,
                               use_version_fields=False,
                               only_abstract_resources=True,
                               additional_fields_dict=None,
                               is_join_query=False,
                               limit_fields=None,
                               for_template=False):
        """
            Match the model_class to an existing resource class by iterating through subclasses of self.__class__
            If no match occurs generate one if db_entity is specified, else None
        :param model_class:
        :param related_descriptors: Optionally provided a dictionary of related descriptors of the model_class to use to create related resource fields
        in the case of dynamic resource creation. If unspecified, related_descriptors are found using the base resource class's resolve_related_descriptors method
        :param no_cache: Don't use cached resources. Specifying queryset also prevents the cache being consulted
        :param use_version_fields: Special case for versioned models. Instructs the resource class creation
         to create dynamic fields to look in the model_class's Revisionable._version_field_dict to resolve
         the value of a field before looking at the database (current object) version of the field.
        :param additional_fields_dict. Optional dict that gives the resource class additional fields
        with values. This is not for related fields, rather special cases like passing the layer_selection
        to the FeatureResource.
        :param for_template: Currently just used for Features, but this indicates a template version of the
        resource is being sought
        :param is_join_query: True if the is a join query
        table or similar. This means that the queryset isn't actually valid yet
        :return: The matching or created resource
        """
        # Never seek a cached resource if a queryset or no_cache is specified
        allow_caching = queryset is None and not no_cache

        cls.logger.info("Resolving Resource Class for model_class %s with query %s and allow_caching %s and only_abstract_resources %s and base_resource_class %s",
            model_class.__name__, queryset, allow_caching, only_abstract_resources, base_resource_class)
        if not base_resource_class and allow_caching:
            # Try to get a cached resource if a base_resource_class is present and we allow caching
            # of this resource. We never allow caching if the queryset changes from request to request
            # Instead we subclass the best matching existing resource class
            resources = FootprintResource.match_existing_resources(model_class)
            if len(resources) > 1:
                logging.warn("Too many resources for model class: %s. Resources; %s" % (model_class, resources))
            if (len(resources) > 0):
                cls.logger.info("Found existing resource class %s for model class %s" % (resources[0], model_class))
                # Done, return the existing resource
                return resources[0]

        # If we don't allow caching or no resource was found
        if only_abstract_resources:
            # See if we can match an abstract resource to be our base.
            # We look for the best matching resource who's model class matches or is a superclass or our model class.
            base_resource_classes = FootprintResource.match_existing_resources(model_class, only_abstract_resources=True)
            cls.logger.info("Matching abstract base resource classes: %s" % base_resource_classes)
            base_resource_class = base_resource_classes[0] if len(base_resource_classes)==1 else base_resource_class
        else:
            # Fist try to find a concrete one that we already created
            concrete_base_resource_classes = FootprintResource.match_existing_resources(model_class, allow_abstract_resources=False)
            if len(concrete_base_resource_classes)==1:
                # Found a concrete resource class that was already created for this model
                # If this isn't a join query we can return this resource class
                base_resource_class = concrete_base_resource_classes[0]
                if not is_join_query:
                    return base_resource_class
            else:
                # Try to find an abstract one
                all_base_resource_classes = FootprintResource.match_existing_resources(model_class, only_abstract_resources=True)
                cls.logger.info("Matching abstract base resource classes: %s" % all_base_resource_classes)
                base_resource_class = all_base_resource_classes[0] if len(all_base_resource_classes)==1 else base_resource_class

        if is_join_query:
            # Join queries must pass in the limited fields from the layer_selection.result_map.result_fields
            limit_api_fields = limit_fields
        else:
            # Non-join queries find their fields from the model_class
            limit_api_fields = limited_api_fields(model_class, for_template=for_template)

        resource_class = base_resource_class or cls
        if base_resource_class:
            cls.logger.info("Resolved base_resource_class: %s" % base_resource_class)
        else:
            cls.logger.info("Could not resolve a base_resource_class. Using resource class %s" % cls)
        related_descriptors = related_descriptors or \
                              resource_class.resolve_related_descriptors(model_class) or \
                              {}

        cls.logger.info("Creating a resource subclass of %s for model_class %s with the following related descriptors %s" % (
            resource_class,
            model_class,
            ', '.join(map_dict(
                lambda related_field_name, related_descriptor: related_field_name,
                related_descriptors
            ))))
        related_fields = merge(additional_fields_dict or {}, map_dict_to_dict(
            lambda related_field_name, relationship_dict: cls.related_resource_field(
                related_field_name,
                merge(
                    dict(
                        # This is passed in, but might be overriden in the relationship_dict
                        use_version_fields=use_version_fields
                    ),
                    # Already a relationship dict
                    relationship_dict if isinstance(relationship_dict, dict) else dict(),
                    # Not a relationship dict, just a descriptor, wrap it
                    dict(descriptor=relationship_dict) if not isinstance(relationship_dict, dict) else dict(),
                ),
                # Always allow cached related resource classes.
                # I think this is always safe
                no_cache=False),
            related_descriptors))
        meta = merge(
            dict(fields=limit_api_fields) if limit_api_fields else
                dict(excludes=(resource_class.Meta.excludes if hasattr(
                  resource_class.Meta, 'excludes') else []) + cls.dynamic_excludes(
                model_class)),
            dict(queryset=queryset, object_class=object_class) if queryset else dict())
        return get_dynamic_resource_class(
            resource_class,
            model_class,
            # Created related fields from the related_descriptors
            fields=related_fields,
            # Optionally set the fields and queryset on the Meta class
            meta_fields=meta
        )