def wrapper(request, *args, **kwargs):
            # Dynamic resources based on a ConfigEntity instance need to pass the config_entity__id so that we can properly construct the dynamic resource
            if hasattr(self.Meta, 'abstract') and self.Meta.abstract:
                wrapped_view = self.subclass_resource_if_needed(view, request)
                # During the subclassing process we may have removed parameters needed only to resolve
                # the dynamic Resource class. We don't want those parameters to be used by the dynamic
                # resource for filtering
                filter = getattr(request, '_filters', request.GET)

                # request.GET is used for filtering along with kwargs['GET']. They are combined (see get_object_list)
                encoding = request.GET.encoding
                request._original_params = request.GET.copy()

                # Make sure the values are singular so we don't
                # get weird array values back
                request.GET = http.QueryDict(
                    '&'.join(
                        map_dict(lambda key, value: '%s=%s' % (key, value[-1] if isinstance(value, list) else value), filter)
                    ),
                    encoding=encoding
                )
            else:
                wrapped_view = super(ModelResource, self).wrap_view(view)

            return wrapped_view(request, *args, **kwargs)
Beispiel #2
0
            def dehydrate(self, bundle):
                bundle = super(JoinFeatureResource, self).dehydrate(bundle)
                def map_related_to_field_and_label(field_name, related_model):
                    """
                        Creates a mapping from the _id attribute of ForeignKeys to
                        to the related attribute (e.g. built_form_id to built_form).
                        It puts the related attribute in the main dict with its resource_uri
                        and puts a label representation in the __labels dict so that
                        the related attribute can map from its id to a label. This
                        is similar to what the regular FeatureResource does but it is
                        more manual since we only have the _id attribute and must
                        resolve the related instance ourselves
                    """
                    client_field_name = self.client_field_name(field_name)
                    related_id = getattr(bundle.obj, client_field_name) if hasattr(bundle.obj, client_field_name) else None
                    if related_id:
                        related_model_instance = related_model.objects.get(id=related_id)
                        related_field_name = field_name.replace('_id', '')
                        return {
                            # Get the resource class of the related model so we can make a uri
                            related_field_name: FootprintResource.resolve_resource_class(related_model)().get_resource_uri(related_model_instance),
                            # The label representation
                            '__labels': {related_field_name: LayerSelection.resolve_instance_label(related_model_instance)}
                        }
                    return None

                related_field_lookup = self.related_field_lookup
                if related_field_lookup:
                    # For join queries, we need to manually add related models--not the joins,
                    # rather related models on the main feature, such as a BuiltForm reference
                    bundle.data = deep_merge(bundle.data, *compact(map_dict(
                        map_related_to_field_and_label,
                        related_field_lookup
                    )))
                return bundle
Beispiel #3
0
        def wrapper(request, *args, **kwargs):
            # Dynamic resources based on a ConfigEntity instance need to pass the config_entity__id so that we can properly construct the dynamic resource
            if hasattr(self.Meta, 'abstract') and self.Meta.abstract:
                wrapped_view = self.subclass_resource_if_needed(view, request)
                # During the subclassing process we may have removed parameters needed only to resolve
                # the dynamic Resource class. We don't want those parameters to be used by the dynamic
                # resource for filtering
                filter = getattr(request, '_filters', request.GET)

                # request.GET is used for filtering along with kwargs['GET']. They are combined (see get_object_list)
                encoding = request.GET.encoding
                request._original_params = request.GET.copy()

                # Make sure the values are singular so we don't
                # get weird array values back
                request.GET = http.QueryDict(
                    '&'.join(
                        map_dict(lambda key, value: '%s=%s' % (key, value[-1] if isinstance(value, list) else value), filter)
                    ),
                    encoding=encoding
                )
            else:
                wrapped_view = super(ModelResource, self).wrap_view(view)

            return wrapped_view(request, *args, **kwargs)
Beispiel #4
0
    def update_summary_results(self, query_result):
        """
            Updates the summary results with the given QuerySet or results list
        :param self:
        :param query_result:
        :return:
        """
        if isinstance(query_result, QuerySet):
            # Find aggregate and normal field names
            aggregate_names = query_result.aggregate_names if hasattr(query_result, 'aggregate_names') else []
            self.summary_fields = (query_result.field_names if hasattr(query_result, 'field_names') else []) + aggregate_names
            # Find aggregate and normal field titles
            aggregate_titles = map_dict(lambda key, value: self.cleanup_title(key), query_result.query.aggregates) if hasattr(query_result.query, 'aggregates') else []
            titles = map(lambda tup: self.cleanup_title(tup[1]), query_result.query.select) + aggregate_titles
            # Create a lookup from field name to title
            self.summary_field_title_lookup = dual_map_to_dict(lambda key, value: [key, value], self.summary_fields, titles)
            self.summary_query_sql = str(query_result.query)
        elif len(query_result) > 0:
            # For single row aggregates. TODO figure out who to extract the names from the query
            self.summary_fields = query_result[0].keys()
            self.summary_field_title_lookup = map_to_dict(lambda key: [key, key], self.summary_fields)
            self.summary_query_sql = str(query_result.query)

        self.summary_results = list(query_result)
        self.save()
            def dehydrate(self, bundle):
                bundle = super(JoinFeatureResource, self).dehydrate(bundle)
                def map_related_to_field_and_label(field_name, related_model):
                    """
                        Creates a mapping from the _id attribute of ForeignKeys to
                        to the related attribute (e.g. built_form_id to built_form).
                        It puts the related attribute in the main dict with its resource_uri
                        and puts a label representation in the __labels dict so that
                        the related attribute can map from its id to a label. This
                        is similar to what the regular FeatureResource does but it is
                        more manual since we only have the _id attribute and must
                        resolve the related instance ourselves
                    """
                    client_field_name = self.client_field_name(field_name)
                    related_id = getattr(bundle.obj, client_field_name) if hasattr(bundle.obj, client_field_name) else None
                    if related_id:
                        related_model_instance = related_model.objects.get(id=related_id)
                        related_field_name = field_name.replace('_id', '')
                        return {
                            # Get the resource class of the related model so we can make a uri
                            related_field_name: FootprintResource.resolve_resource_class(related_model)().get_resource_uri(related_model_instance),
                            # The label representation
                            '__labels': {related_field_name: LayerSelection.resolve_instance_label(related_model_instance)}
                        }
                    return None

                related_field_lookup = self.related_field_lookup
                if related_field_lookup:
                    # For join queries, we need to manually add related models--not the joins,
                    # rather related models on the main feature, such as a BuiltForm reference
                    bundle.data = deep_merge(bundle.data, *compact(map_dict(
                        map_related_to_field_and_label,
                        related_field_lookup
                    )))
                return bundle
Beispiel #6
0
 def get(self,
         resource_name,
         ids=None,
         user=None,
         query_params=None,
         **kwargs):
     """
         Get all or the instances specified by ids
     :param resource_name:
     :param ids:
     :param user:
     :return:
     """
     if query_params:
         # The test framework takes care of the username unless query_params are specified,
         # apparently
         query_params.update(username=user.username)
     return self.api_client.get(
         '%s/%s/%s%s' %
         (self.base_uri, resource_name,
          ('set/%s' % ';'.join(ids)) if ids else '', ('?%s' % '&'.join(
              map_dict(lambda k, v: '%s=%s' %
                       (k, v), query_params))) if query_params else ''),
         format='json',
         authentication=self.get_credentials(user),
         **kwargs)
Beispiel #7
0
 def parse_queryset_inner_argument(self, argument):
     """
         The inner arguments of the queryset command are either a simple column/alias path or a dict in the
         case of aggregate functions, e.g. dict(Avg='book__rating') to indicate Avg('book__rating')
     :param argument:
     :return:
     """
     if isinstance(argument, dict):
         return map_dict(lambda key, value: self.resolve_models_class(key)(value), argument)[0]
     return argument
Beispiel #8
0
 def wrapper(*args, **kwargs):
     """
         Wraps the function to include the logging. Logging includes the args and kwargs in the log message
     :param args:
     :param kwargs:
     :return:
     """
     args_str = '\n\targs %s' % ', '.join(map(lambda arg: '\n\t\t%s' % unicode(arg), args)) if args else ''
     kwargs_str = '\n\tkwargs %s' % ', '.join(map_dict(lambda key, value: '\n\t\t%s' % '='.join([unicode(key), unicode(value)]), kwargs)) if kwargs else ''
     log.log(level, 'Calling %s%s%s' % (logmsg, args_str, kwargs_str))
     return func(*args, **kwargs)
Beispiel #9
0
 def parse_queryset_inner_argument(self, argument):
     """
         The inner arguments of the queryset command are either a simple column/alias path or a dict in the
         case of aggregate functions, e.g. dict(Avg='book__rating') to indicate Avg('book__rating')
     :param argument:
     :return:
     """
     if isinstance(argument, dict):
         return map_dict(
             lambda key, value: self.resolve_models_class(key)(value),
             argument)[0]
     return argument
    def default_remote_db_entities(self):
        # The Behavior keyspace
        behavior_key = BehaviorKey.Fab.ricate
        # Used to load Behaviors defined elsewhere
        get_behavior = lambda key: Behavior.objects.get(key=behavior_key(key))

        mapbox_layers = {
            'Aerial Photo': {
                "id": "elevan.kib62d92",
                "key": "mapbox_aerial"
            },
            'Simple Streets': {
                "id": "elevan.e53fa071",
                "key": "mapbox_streets"
            }
        }

        mapbox_base_url = "https://api.mapbox.com/v4/{id}/{{z}}/{{x}}/{{y}}.png?access_token={api_key}"

        mapbox_setups = map_dict(
            lambda name, attrs: DbEntity(
                key="mapbox_" + attrs['key'],
                name="Mapbox: " + name,
                url=mapbox_base_url.format(id=attrs['id'],
                                           api_key=settings.MAPBOX_API_KEY),
                hosts=["a", "b", "c", "d"],
                schema=self.config_entity,
                no_feature_class_configuration=True,
                feature_behavior=FeatureBehavior(behavior=get_behavior(
                    'remote_imagery')),
                _categories=[
                    Category(key=DbEntityCategoryKey.KEY_CLASSIFICATION,
                             value=DbEntityCategoryKey.BASEMAPS)
                ]), mapbox_layers)

        cloudmade_setups = [
            DbEntity(key='osm_default',
                     name='OpenStreetMap: Base Layer',
                     url="http://a.tile.openstreetmap.org/{z}/{x}/{y}.png",
                     schema=self.config_entity,
                     no_feature_class_configuration=True,
                     feature_behavior=FeatureBehavior(
                         behavior=get_behavior('remote_imagery')),
                     _categories=[
                         Category(key=DbEntityCategoryKey.KEY_CLASSIFICATION,
                                  value=DbEntityCategoryKey.BASEMAPS)
                     ])
        ]
        return mapbox_setups + cloudmade_setups
Beispiel #11
0
def build_style_string(style, separator=' '):
    """
        Creates key value pairs as a string for the given Style with the given separator
        between the paris
    :param style:
    :param separator: Default ' '
    :return:
    """

    style_string = separator.join(
        map_dict(
            lambda key, value: '{key}: {value};'.format(key=dasherize(key), value=map_value(value)),
            compact_dict(style)))

    return style_string
def build_style_string(style, separator=' '):
    """
        Creates key value pairs as a string for the given Style with the given separator
        between the paris
    :param style:
    :param separator: Default ' '
    :return:
    """

    style_string = separator.join(
        map_dict(
            lambda key, value: '{key}: {value};'.format(key=dasherize(key), value=map_value(value)),
            compact_dict(style)))

    return style_string
def create_layer_style_for_related_field(related_field_path, related_model, color_lookup, color_lookup_field, visible):
    """
        Creates the CSS context_dict to use in a LayerStyle instance for the given ForeignKey model field
    :param related_field_path: If the field is a many-to-many, specify this, e.g. 'built_form__id'.
    :param related_model - Model object having items with a color_lookup_field
    :param color_lookup: A dict that maps a field of the ForeignKey model class to a color
    :param color_lookup_field: The field of the ForeignKey model that matches the keys of the color_lookup
    :return: A complete default context_dict for the give model field
    """

    def create_style_value_context(lookup_field_value, color):
        if color:
            try:
                foreign_key = related_model.objects.get(**{color_lookup_field: lookup_field_value}).id
                return StyleValueContext(
                    value=foreign_key,
                    symbol='=',
                    style=Style(
                        polygon_fill=color,
                        polygon_opacity=0.8,
                        line_color='#d2d2d5',
                        line_opacity=0.7,
                        line_width=.5
                    )
                )
            except:
                return None
        return None

    return dict(
        geometry_type=GeometryTypeKey.POLYGON,
        style_attributes=[
            dict(
                attribute=related_field_path,
                opacity=1,
                visible=visible,

                style_type=StyleTypeKey.CATEGORICAL,
                style_value_contexts=[null_style_value_context()] +
                    sorted(compact(map_dict(
                        create_style_value_context,
                        color_lookup
                    )), key=lambda style_value_context: style_value_context.value)
            )
        ]
    )
Beispiel #14
0
 def wrapper(*args, **kwargs):
     """
         Wraps the function to include the logging. Logging includes the args and kwargs in the log message
     :param args:
     :param kwargs:
     :return:
     """
     args_str = '\n\targs %s' % ', '.join(
         map(lambda arg: '\n\t\t%s' % unicode(arg),
             args)) if args else ''
     kwargs_str = '\n\tkwargs %s' % ', '.join(
         map_dict(
             lambda key, value: '\n\t\t%s' % '='.join(
                 [unicode(key), unicode(value)]),
             kwargs)) if kwargs else ''
     log.log(level, 'Calling %s%s%s' % (logmsg, args_str, kwargs_str))
     return func(*args, **kwargs)
def create_layer_style_for_related_field(related_field_path, related_model, color_lookup, color_lookup_field, visible):
    """
        Creates the CSS context_dict to use in a LayerStyle instance for the given ForeignKey model field
    :param related_field_path: If the field is a many-to-many, specify this, e.g. 'built_form__id'.
    :param related_model - Model object having items with a color_lookup_field
    :param color_lookup: A dict that maps a field of the ForeignKey model class to a color
    :param color_lookup_field: The field of the ForeignKey model that matches the keys of the color_lookup
    :return: A complete default context_dict for the give model field
    """

    def create_style_value_context(lookup_field_value, color):
        if color:
            try:
                foreign_key = related_model.objects.get(**{color_lookup_field: lookup_field_value}).id
                return StyleValueContext(
                    value=foreign_key,
                    symbol='=',
                    style=Style(
                        polygon_fill=color,
                        polygon_opacity=0.8,
                        line_color='#d2d2d5',
                        line_opacity=0.7,
                        line_width=.5
                    )
                )
            except:
                return None
        return None

    return dict(
        geometry_type=GeometryTypeKey.POLYGON,
        style_attributes=[
            dict(
                attribute=related_field_path,
                opacity=1,
                visible=visible,

                style_type=StyleTypeKey.CATEGORICAL,
                style_value_contexts=[null_style_value_context()] +
                    sorted(compact(map_dict(
                        create_style_value_context,
                        color_lookup
                    )), key=lambda style_value_context: style_value_context.value)
            )
        ]
    )
Beispiel #16
0
 def get(self, resource_name, ids=None, user=None, query_params=None, **kwargs):
     """
         Get all or the instances specified by ids
     :param resource_name:
     :param ids:
     :param user:
     :return:
     """
     if query_params:
         # The test framework takes care of the username unless query_params are specified,
         # apparently
         query_params.update(username=user.username)
     return self.api_client.get(
         '%s/%s/%s%s' % (
             self.base_uri,
             resource_name,
             ('set/%s' % ';'.join(ids)) if ids else '',
             ('?%s' % '&'.join(map_dict(lambda k, v: '%s=%s' % (k,v), query_params))) if query_params else ''),
         format='json',
         authentication=self.get_credentials(user),
         **kwargs)
    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
        )
Beispiel #18
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
        )