Example #1
0
def test_build_json_resource_obj():
    resource = {"username": "******", "version": "1.0.0"}

    serializer = ResourceSerializer(data={"username": "******"})
    serializer.is_valid()
    resource_instance = serializer.save()

    output = {
        "type": "user",
        "id": "1",
        "attributes": {
            "username": "******"
        },
        "meta": {
            "version": "1.0.0"
        },
    }

    assert (JSONRenderer.build_json_resource_obj(
        get_serializer_fields(serializer),
        resource,
        resource_instance,
        "user",
        serializer,
    ) == output)
Example #2
0
    def build_serializer_schema(self,
                                serializer,
                                resource_name,
                                SwaggerType,
                                ChildSwaggerType,
                                use_references,
                                is_request=None):
        fields = json_api_utils.get_serializer_fields(serializer)
        is_post = self.method.lower() == 'post'

        id_field = self.extract_id_field(fields, serializer)
        if id_field is None and not (is_request and is_post):
            logging.warning(
                '{view}.{serializer} does not contain id field as every resource should'
                .format(view=self.view.__class__.__name__,
                        serializer=serializer.__class__.__name__))

        attributes, req_attrs = self.extract_attributes(
            id_field, fields, ChildSwaggerType, use_references, is_request)
        relationships, req_rels = self.extract_relationships(
            fields, ChildSwaggerType, use_references, is_request)
        links = self.extract_links(fields, ChildSwaggerType,
                                   use_references) if not is_request else None

        schema_fields = filter_none(
            OrderedDict(
                type=self.build_type_schema(resource_name),
                id=self.probe_field_inspectors(id_field, ChildSwaggerType,
                                               use_references)
                if id_field and not (self.strip_read_fields_from_request
                                     and is_request and is_post) else None,
                attributes=openapi.Schema(type=openapi.TYPE_OBJECT,
                                          properties=attributes,
                                          required=req_attrs)
                if attributes else None,
                relationships=openapi.Schema(type=openapi.TYPE_OBJECT,
                                             properties=relationships,
                                             required=req_rels)
                if relationships else None,
                links=openapi.Schema(type=openapi.TYPE_OBJECT,
                                     properties=links) if links else None))

        required_properties = None
        if self.is_request_or_unknown(is_request):
            required_properties = filter_none([
                'type',
                'id' if 'id' in schema_fields else None,
                'attributes' if req_attrs else None,
                'relationships' if req_rels else None,
            ])

        return SwaggerType(type=openapi.TYPE_OBJECT,
                           properties=schema_fields,
                           required=required_properties)
Example #3
0
    def build_json_resource_schema(self,
                                   serializer,
                                   resource_name,
                                   SwaggerType,
                                   ChildSwaggerType,
                                   use_references,
                                   is_request=None):
        fields = json_api_utils.get_serializer_fields(serializer)

        id_ = fields.get('id')
        if id_ is None and self.method.lower() == 'get':
            logging.warning(
                '{view}.{serializer} does not contain id field as every resource should'
                .format(view=self.view.__class__.__name__,
                        serializer=serializer.__class__.__name__))
        if id_ is not None and id_.read_only and is_request:
            id_ = None

        attributes, req_attributes = self.extract_attributes(
            fields, ChildSwaggerType, use_references, is_request)
        relationships, req_relationships = self.extract_relationships(
            fields, ChildSwaggerType, use_references, is_request)
        links = self.extract_links(fields, ChildSwaggerType,
                                   use_references) if not is_request else None

        schema_fields = filter_none(
            OrderedDict(
                type=SwaggerType(type=openapi.TYPE_STRING,
                                 pattern=resource_name),
                id=self.probe_field_inspectors(
                    id_, ChildSwaggerType, use_references) if id_ else None,
                attributes=SwaggerType(type=openapi.TYPE_OBJECT,
                                       properties=attributes,
                                       required=req_attributes)
                if attributes else None,
                relationships=SwaggerType(type=openapi.TYPE_OBJECT,
                                          properties=relationships,
                                          required=req_relationships)
                if relationships else None,
                links=SwaggerType(type=openapi.TYPE_OBJECT, properties=links)
                if links else None))

        if self.is_request_or_unknown(is_request):
            required_properties = ['id', 'type'
                                   ] if 'id' in schema_fields else ['type']
        else:
            required_properties = None

        return SwaggerType(type=openapi.TYPE_OBJECT,
                           properties=schema_fields,
                           required=required_properties)
Example #4
0
def test_can_override_methods():
    """
    Make sure extract_attributes and extract_relationships can be overriden.
    """

    resource = {"username": "******", "version": "1.0.0"}
    serializer = ResourceSerializer(data={"username": "******"})
    serializer.is_valid()
    resource_instance = serializer.save()

    output = {
        "type": "user",
        "id": "1",
        "attributes": {
            "username": "******"
        },
        "meta": {
            "version": "1.0.0"
        },
    }

    class CustomRenderer(JSONRenderer):
        extract_attributes_was_overriden = False
        extract_relationships_was_overriden = False

        @classmethod
        def extract_attributes(cls, fields, resource):
            cls.extract_attributes_was_overriden = True
            return super(CustomRenderer,
                         cls).extract_attributes(fields, resource)

        @classmethod
        def extract_relationships(cls, fields, resource, resource_instance):
            cls.extract_relationships_was_overriden = True
            return super(CustomRenderer,
                         cls).extract_relationships(fields, resource,
                                                    resource_instance)

    assert (CustomRenderer.build_json_resource_obj(
        get_serializer_fields(serializer),
        resource,
        resource_instance,
        "user",
        serializer,
    ) == output)
    assert CustomRenderer.extract_attributes_was_overriden
    assert CustomRenderer.extract_relationships_was_overriden
    def render(self, data, accepted_media_type=None, renderer_context=None):

        renderer_context = renderer_context or {}

        view = renderer_context.get("view", None)
        request = renderer_context.get("request", None)

        # Get the resource name.
        resource_name = utils.get_resource_name(renderer_context)

        # If this is an error response, skip the rest.
        if resource_name == "errors":
            return self.render_errors(data, accepted_media_type,
                                      renderer_context)

        # if response.status_code is 204 then the data to be rendered must
        # be None
        response = renderer_context.get("response", None)
        if response is not None and response.status_code == 204:
            return super().render(None, accepted_media_type, renderer_context)

        from rest_framework_json_api.views import RelationshipView

        if isinstance(view, RelationshipView):
            return self.render_relationship_view(data, accepted_media_type,
                                                 renderer_context)

        # If `resource_name` is set to None then render default as the dev
        # wants to build the output format manually.
        if resource_name is None or resource_name is False:
            return super().render(data, accepted_media_type, renderer_context)

        json_api_data = data
        # initialize json_api_meta with pagination meta or an empty dict
        json_api_meta = data.get("meta", {}) if isinstance(data, dict) else {}
        included_cache = defaultdict(dict)

        if data and "results" in data:
            serializer_data = data["results"]
        else:
            serializer_data = data

        serializer = getattr(serializer_data, "serializer", None)

        included_resources = utils.get_included_resources(request, serializer)

        if serializer is not None:

            # Extract root meta for any type of serializer
            json_api_meta.update(
                self.extract_root_meta(serializer, serializer_data))

            if getattr(serializer, "many", False):
                json_api_data = list()

                for position in range(len(serializer_data)):
                    resource = serializer_data[
                        position]  # Get current resource
                    resource_instance = serializer.instance[
                        position]  # Get current instance

                    if isinstance(
                            serializer.child,
                            rest_framework_json_api.serializers.
                            PolymorphicModelSerializer,
                    ):
                        resource_serializer_class = (
                            serializer.child.
                            get_polymorphic_serializer_for_instance(
                                resource_instance)(
                                    context=serializer.child.context))
                    else:
                        resource_serializer_class = serializer.child

                    fields = utils.get_serializer_fields(
                        resource_serializer_class)
                    force_type_resolution = getattr(
                        resource_serializer_class,
                        "_poly_force_type_resolution", False)

                    json_resource_obj = self.build_json_resource_obj(
                        fields,
                        resource,
                        resource_instance,
                        resource_name,
                        serializer,
                        force_type_resolution,
                    )
                    json_api_data.append(json_resource_obj)

                    self.extract_included(
                        fields,
                        resource,
                        resource_instance,
                        included_resources,
                        included_cache,
                    )
            else:
                fields = utils.get_serializer_fields(serializer)
                force_type_resolution = getattr(serializer,
                                                "_poly_force_type_resolution",
                                                False)

                resource_instance = serializer.instance
                json_api_data = self.build_json_resource_obj(
                    fields,
                    serializer_data,
                    resource_instance,
                    resource_name,
                    serializer,
                    force_type_resolution,
                )

                self.extract_included(
                    fields,
                    serializer_data,
                    resource_instance,
                    included_resources,
                    included_cache,
                )

        # Make sure we render data in a specific order
        render_data = OrderedDict()

        if isinstance(data, dict) and data.get("links"):
            render_data["links"] = data.get("links")

        # format the api root link list
        if view.__class__ and view.__class__.__name__ == "APIRoot":
            render_data["data"] = None
            render_data["links"] = json_api_data
        else:
            render_data["data"] = json_api_data

        if included_cache:
            if isinstance(json_api_data, list):
                objects = json_api_data
            else:
                objects = [json_api_data]

            for object in objects:
                obj_type = object.get("type")
                obj_id = object.get("id")
                if obj_type in included_cache and obj_id in included_cache[
                        obj_type]:
                    del included_cache[obj_type][obj_id]
                if not included_cache[obj_type]:
                    del included_cache[obj_type]

        if included_cache:
            render_data["included"] = list()
            for included_type in sorted(included_cache.keys()):
                for included_id in sorted(
                        included_cache[included_type].keys()):
                    render_data["included"].append(
                        included_cache[included_type][included_id])

        if json_api_meta:
            render_data["meta"] = utils.format_field_names(json_api_meta)

        return super().render(render_data, accepted_media_type,
                              renderer_context)
    def extract_included(cls, fields, resource, resource_instance,
                         included_resources, included_cache):
        """
        Adds related data to the top level included key when the request includes
        ?include=example,example_field2
        """
        # this function may be called with an empty record (example: Browsable Interface)
        if not resource_instance:
            return

        current_serializer = fields.serializer
        context = current_serializer.context
        included_serializers = getattr(current_serializer,
                                       "included_serializers", dict())
        included_resources = copy.copy(included_resources)
        included_resources = [
            inflection.underscore(value) for value in included_resources
        ]

        for field_name, field in iter(fields.items()):
            # Skip URL field
            if field_name == api_settings.URL_FIELD_NAME:
                continue

            # Skip fields without relations
            if not utils.is_relationship_field(field):
                continue

            try:
                included_resources.remove(field_name)
            except ValueError:
                # Skip fields not in requested included resources
                # If no child field, directly continue with the next field
                if field_name not in [
                        node.split(".")[0] for node in included_resources
                ]:
                    continue

            relation_instance = cls.extract_relation_instance(
                field, resource_instance)
            if isinstance(relation_instance, Manager):
                relation_instance = relation_instance.all()

            serializer_data = resource.get(field_name)

            if isinstance(field, relations.ManyRelatedField):
                serializer_class = included_serializers[field_name]
                field = serializer_class(relation_instance,
                                         many=True,
                                         context=context)
                serializer_data = field.data

            if isinstance(field, relations.RelatedField):
                if relation_instance is None or not serializer_data:
                    continue

                many = field._kwargs.get("child_relation", None) is not None

                if isinstance(field, ResourceRelatedField) and not many:
                    already_included = (
                        serializer_data["type"] in included_cache
                        and serializer_data["id"]
                        in included_cache[serializer_data["type"]])

                    if already_included:
                        continue

                serializer_class = included_serializers[field_name]
                field = serializer_class(relation_instance,
                                         many=many,
                                         context=context)
                serializer_data = field.data

            new_included_resources = [
                key.replace("%s." % field_name, "", 1)
                for key in included_resources
                if field_name == key.split(".")[0]
            ]

            if isinstance(field, ListSerializer):
                serializer = field.child
                relation_type = utils.get_resource_type_from_serializer(
                    serializer)
                relation_queryset = list(relation_instance)

                if serializer_data:
                    for position in range(len(serializer_data)):
                        serializer_resource = serializer_data[position]
                        nested_resource_instance = relation_queryset[position]
                        resource_type = (relation_type or
                                         utils.get_resource_type_from_instance(
                                             nested_resource_instance))
                        serializer_fields = utils.get_serializer_fields(
                            serializer.__class__(nested_resource_instance,
                                                 context=serializer.context))
                        new_item = cls.build_json_resource_obj(
                            serializer_fields,
                            serializer_resource,
                            nested_resource_instance,
                            resource_type,
                            serializer,
                            getattr(serializer, "_poly_force_type_resolution",
                                    False),
                        )
                        included_cache[new_item["type"]][
                            new_item["id"]] = new_item

                        cls.extract_included(
                            serializer_fields,
                            serializer_resource,
                            nested_resource_instance,
                            new_included_resources,
                            included_cache,
                        )

            if isinstance(field, Serializer):
                relation_type = utils.get_resource_type_from_serializer(field)

                # Get the serializer fields
                serializer_fields = utils.get_serializer_fields(field)
                if serializer_data:
                    new_item = cls.build_json_resource_obj(
                        serializer_fields,
                        serializer_data,
                        relation_instance,
                        relation_type,
                        field,
                        getattr(field, "_poly_force_type_resolution", False),
                    )
                    included_cache[new_item["type"]][new_item["id"]] = new_item

                    cls.extract_included(
                        serializer_fields,
                        serializer_data,
                        relation_instance,
                        new_included_resources,
                        included_cache,
                    )
    def extract_included(cls, fields, resource, resource_instance, included_resources,
                         included_cache):
        """
        Adds related data to the top level included key when the request includes
        ?include=example,example_field2
        """
        # this function may be called with an empty record (example: Browsable Interface)
        if not resource_instance:
            return

        current_serializer = fields.serializer
        context = current_serializer.context
        included_serializers = utils.get_included_serializers(current_serializer)
        included_resources = copy.copy(included_resources)
        included_resources = [inflection.underscore(value) for value in included_resources]

        for field_name, field in six.iteritems(fields):
            # Skip URL field
            if field_name == api_settings.URL_FIELD_NAME:
                continue

            # Skip fields without relations or serialized data
            if not isinstance(
                    field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer)
            ):
                continue

            try:
                included_resources.remove(field_name)
            except ValueError:
                # Skip fields not in requested included resources
                # If no child field, directly continue with the next field
                if field_name not in [node.split('.')[0] for node in included_resources]:
                    continue

            relation_instance = cls.extract_relation_instance(
                field, resource_instance
            )
            if isinstance(relation_instance, Manager):
                relation_instance = relation_instance.all()

            serializer_data = resource.get(field_name)

            if isinstance(field, relations.ManyRelatedField):
                serializer_class = included_serializers[field_name]
                context['parent'] = resource_instance
                field = serializer_class(relation_instance, many=True, context=context)
                serializer_data = field.data

            if isinstance(field, relations.RelatedField):
                if relation_instance is None or not serializer_data:
                    continue

                many = field._kwargs.get('child_relation', None) is not None

                if isinstance(field, ResourceRelatedField) and not many:
                    already_included = serializer_data['type'] in included_cache and \
                        serializer_data['id'] in included_cache[serializer_data['type']]

                    if already_included:
                        continue

                serializer_class = included_serializers[field_name]
                context['parent'] = resource_instance
                field = serializer_class(relation_instance, many=many, context=context)
                serializer_data = field.data

            new_included_resources = [key.replace('%s.' % field_name, '', 1)
                                      for key in included_resources
                                      if field_name == key.split('.')[0]]

            if isinstance(field, ListSerializer):
                serializer = field.child
                relation_type = utils.get_resource_type_from_serializer(serializer)
                relation_queryset = list(relation_instance)

                if serializer_data:
                    for position in range(len(serializer_data)):
                        serializer_resource = serializer_data[position]
                        nested_resource_instance = relation_queryset[position]
                        resource_type = (
                            relation_type or
                            utils.get_resource_type_from_instance(nested_resource_instance)
                        )
                        serializer_fields = utils.get_serializer_fields(
                            serializer.__class__(
                                nested_resource_instance, context=serializer.context
                            )
                        )
                        new_item = cls.build_json_resource_obj(
                            serializer_fields,
                            serializer_resource,
                            nested_resource_instance,
                            resource_type,
                            getattr(serializer, '_poly_force_type_resolution', False)
                        )

                        # NEW: Add meta to included resource
                        meta = cls.extract_meta(serializer.__class__, serializer_resource)
                        if meta:
                            new_item.update({'meta': utils._format_object(meta)})

                        included_cache[new_item['type']][new_item['id']] = \
                            utils._format_object(new_item)
                        cls.extract_included(
                            serializer_fields,
                            serializer_resource,
                            nested_resource_instance,
                            new_included_resources,
                            included_cache,
                        )

            if isinstance(field, Serializer):
                relation_type = utils.get_resource_type_from_serializer(field)

                # Get the serializer fields
                serializer_fields = utils.get_serializer_fields(field)
                new_item = cls.build_json_resource_obj(
                    serializer_fields,
                    serializer_data,
                    relation_instance,
                    relation_type,
                    getattr(field, '_poly_force_type_resolution', False)
                )
                # NEW: Add meta to included resource
                meta = cls.extract_meta(serializer_class, serializer_data)
                if meta:
                    new_item.update({'meta': utils._format_object(meta)})

                included_cache[new_item['type']][new_item['id']] = utils._format_object(
                    new_item
                )
                cls.extract_included(
                    serializer_fields,
                    serializer_data,
                    relation_instance,
                    new_included_resources,
                    included_cache,
                )
Example #8
0
    def render(self, data, accepted_media_type=None, renderer_context=None):

        renderer_context = renderer_context or {}

        view = renderer_context.get("view", None)
        request = renderer_context.get("request", None)

        # Get the resource name.
        resource_name = utils.get_resource_name(renderer_context)

        # If this is an error response, skip the rest.
        if resource_name == 'errors':
            return self.render_errors(data, accepted_media_type, renderer_context)

        # if response.status_code is 204 then the data to be rendered must
        # be None
        response = renderer_context.get('response', None)
        if response is not None and response.status_code == 204:
            return super(JSONRenderer, self).render(
                None, accepted_media_type, renderer_context
            )

        from rest_framework_json_api.views import RelationshipView
        if isinstance(view, RelationshipView):
            return self.render_relationship_view(data, accepted_media_type, renderer_context)

        # If `resource_name` is set to None then render default as the dev
        # wants to build the output format manually.
        if resource_name is None or resource_name is False:
            return super(JSONRenderer, self).render(
                data, accepted_media_type, renderer_context
            )

        json_api_data = data
        # initialize json_api_meta with pagination meta or an empty dict
        json_api_meta = data.get('meta', {}) if isinstance(data, dict) else {}
        included_cache = defaultdict(dict)

        if data and 'results' in data:
            serializer_data = data["results"]
        else:
            serializer_data = data

        serializer = getattr(serializer_data, 'serializer', None)

        included_resources = utils.get_included_resources(request, serializer)

        if serializer is not None:

            # Extract root meta for any type of serializer
            json_api_meta.update(self.extract_root_meta(serializer, serializer_data))

            if getattr(serializer, 'many', False):
                json_api_data = list()

                for position in range(len(serializer_data)):
                    resource = serializer_data[position]  # Get current resource
                    resource_instance = serializer.instance[position]  # Get current instance

                    if isinstance(serializer.child, rest_framework_json_api.
                                  serializers.PolymorphicModelSerializer):
                        resource_serializer_class = serializer.child.\
                            get_polymorphic_serializer_for_instance(resource_instance)()
                    else:
                        resource_serializer_class = serializer.child

                    fields = utils.get_serializer_fields(resource_serializer_class)
                    force_type_resolution = getattr(
                        resource_serializer_class, '_poly_force_type_resolution', False)

                    json_resource_obj = self.build_json_resource_obj(
                        fields, resource, resource_instance, resource_name, force_type_resolution
                    )
                    meta = self.extract_meta(serializer, resource)
                    if meta:
                        json_resource_obj.update({'meta': utils.format_keys(meta)})
                    json_api_data.append(json_resource_obj)

                    self.extract_included(
                        fields, resource, resource_instance, included_resources, included_cache
                    )
            else:
                fields = utils.get_serializer_fields(serializer)
                force_type_resolution = getattr(serializer, '_poly_force_type_resolution', False)

                resource_instance = serializer.instance
                json_api_data = self.build_json_resource_obj(
                    fields, serializer_data, resource_instance, resource_name, force_type_resolution
                )

                meta = self.extract_meta(serializer, serializer_data)
                if meta:
                    json_api_data.update({'meta': utils.format_keys(meta)})

                self.extract_included(
                    fields, serializer_data, resource_instance, included_resources, included_cache
                )

        # Make sure we render data in a specific order
        render_data = OrderedDict()

        if isinstance(data, dict) and data.get('links'):
            render_data['links'] = data.get('links')

        # format the api root link list
        if view.__class__ and view.__class__.__name__ == 'APIRoot':
            render_data['data'] = None
            render_data['links'] = json_api_data
        else:
            render_data['data'] = json_api_data

        if included_cache:
            render_data['included'] = list()
            for included_type in sorted(included_cache.keys()):
                for included_id in sorted(included_cache[included_type].keys()):
                    render_data['included'].append(included_cache[included_type][included_id])

        if json_api_meta:
            render_data['meta'] = utils.format_keys(json_api_meta)

        return super(JSONRenderer, self).render(
            render_data, accepted_media_type, renderer_context
        )
Example #9
0
    def render(self, data, accepted_media_type=None, renderer_context=None):

        view = renderer_context.get('view', None)
        request = renderer_context.get('request', None)

        # Get the resource name.
        resource_name = utils.get_resource_name(renderer_context)

        # If this is an error response, skip the rest.
        if resource_name == 'errors':
            return self.render_errors(data, accepted_media_type,
                                      renderer_context)

        from rest_framework_json_api.views import RelationshipView
        if isinstance(view, RelationshipView):
            return self.render_relationship_view(data, accepted_media_type,
                                                 renderer_context)

        # If `resource_name` is set to None then render default as the dev
        # wants to build the output format manually.
        if resource_name is None or resource_name is False:
            return super().render(data, accepted_media_type, renderer_context)

        json_api_data = data
        json_api_included = list()
        # initialize json_api_meta with pagination meta or an empty dict
        json_api_meta = data.get('meta', {}) if isinstance(data, dict) else {}

        if data and 'results' in data:
            serializer_data = data['results']
        else:
            serializer_data = data

        serializer = getattr(serializer_data, 'serializer', None)

        included_resources = utils.get_included_resources(request, serializer)

        if serializer is not None:

            # Get the serializer fields
            fields = utils.get_serializer_fields(serializer)

            # Extract root meta for any type of serializer
            json_api_meta.update(
                self.extract_root_meta(serializer, serializer_data))

            if getattr(serializer, 'many', False):
                json_api_data = list()

                for position in range(len(serializer_data)):
                    resource = serializer_data[
                        position]  # Get current resource
                    resource_instance = serializer.instance[
                        position]  # Get current instance

                    json_resource_obj = self.build_json_resource_obj(
                        fields, resource, resource_instance, resource_name)
                    meta = self.extract_meta(serializer, resource)
                    if meta:
                        json_resource_obj.update(
                            {'meta': api_utils.format_keys(meta)})
                    json_api_data.append(json_resource_obj)

                    included = self.extract_included(fields, resource,
                                                     resource_instance,
                                                     included_resources)
                    if included:
                        json_api_included.extend(included)
            else:
                resource_instance = serializer.instance
                json_api_data = self.build_json_resource_obj(
                    fields, serializer_data, resource_instance, resource_name)

                meta = self.extract_meta(serializer, serializer_data)
                if meta:
                    json_api_data.update({'meta': api_utils.format_keys(meta)})

                included = self.extract_included(fields, serializer_data,
                                                 resource_instance,
                                                 included_resources)
                if included:
                    json_api_included.extend(included)

        # Make sure we render data in a specific order
        render_data = OrderedDict()

        if isinstance(data, dict) and data.get('links'):
            render_data['links'] = data.get('links')

        from rest_framework.routers import APIRootView
        # format the api root link list
        if isinstance(view, APIRootView):
            render_data['data'] = None
            render_data['links'] = json_api_data
        else:
            render_data['data'] = json_api_data

        if len(json_api_included) > 0:
            # Iterate through compound documents to remove duplicates
            seen = set()
            unique_compound_documents = list()
            for included_dict in json_api_included:
                type_tuple = tuple(
                    (included_dict['type'], included_dict['id']))
                if type_tuple not in seen:
                    seen.add(type_tuple)
                    unique_compound_documents.append(included_dict)

            # Sort the items by type then by id
            render_data['included'] = sorted(unique_compound_documents,
                                             key=lambda item:
                                             (item['type'], item['id']))

        if json_api_meta:
            render_data['meta'] = api_utils.format_keys(json_api_meta)

        return super().render(render_data, accepted_media_type,
                              renderer_context)
Example #10
0
    def extract_included(cls, fields, resource, resource_instance,
                         included_resources):
        # this function may be called with an empty record (example: Browsable Interface)
        if not resource_instance:
            return

        included_data = list()
        current_serializer = fields.serializer
        context = current_serializer.context
        included_serializers = utils.get_included_serializers(
            current_serializer)
        included_resources = copy.copy(included_resources)
        included_resources = [
            inflection.underscore(value) for value in included_resources
        ]

        for field_name, field in six.iteritems(fields):
            # Skip URL field
            if field_name == api_settings.URL_FIELD_NAME:
                continue

            # Skip fields without relations or serialized data
            if not isinstance(field,
                              (relations.RelatedField,
                               relations.ManyRelatedField, BaseSerializer)):
                continue

            try:
                included_resources.remove(field_name)
            except ValueError:
                # Skip fields not in requested included resources
                # If no child field, directly continue with the next field
                if field_name not in [
                        node.split('.')[0] for node in included_resources
                ]:
                    continue

            try:
                relation_instance = getattr(resource_instance, field_name)
            except AttributeError:
                try:
                    # For ManyRelatedFields if `related_name` is not set we need to access `foo_set` from `source`
                    relation_instance = getattr(resource_instance,
                                                field.child_relation.source)
                except AttributeError:
                    if not hasattr(current_serializer, field.source):
                        continue
                    serializer_method = getattr(current_serializer,
                                                field.source)
                    relation_instance = serializer_method(resource_instance)

            if isinstance(relation_instance, Manager):
                relation_instance = relation_instance.all()

            new_included_resources = [
                key.replace('%s.' % field_name, '', 1)
                for key in included_resources
                if field_name == key.split('.')[0]
            ]
            serializer_data = resource.get(field_name)

            if isinstance(field, relations.ManyRelatedField):
                serializer_class = included_serializers[field_name]
                field = serializer_class(relation_instance,
                                         many=True,
                                         context=context)
                serializer_data = field.data

            if isinstance(field, relations.RelatedField):
                if relation_instance is None:
                    continue

                many = field._kwargs.get('child_relation', None) is not None
                serializer_class = included_serializers[field_name]
                field = serializer_class(relation_instance,
                                         many=many,
                                         context=context)
                serializer_data = field.data

            if isinstance(field, ListSerializer):
                serializer = field.child
                relation_type = utils.get_resource_type_from_serializer(
                    serializer)
                relation_queryset = list(relation_instance)

                # Get the serializer fields
                serializer_fields = utils.get_serializer_fields(serializer)
                if serializer_data:
                    for position in range(len(serializer_data)):
                        serializer_resource = serializer_data[position]
                        nested_resource_instance = relation_queryset[position]
                        resource_type = (relation_type or
                                         utils.get_resource_type_from_instance(
                                             nested_resource_instance))
                        included_data.append(
                            cls.build_json_resource_obj(
                                serializer_fields, serializer_resource,
                                nested_resource_instance, resource_type))
                        included_data.extend(
                            cls.extract_included(serializer_fields,
                                                 serializer_resource,
                                                 nested_resource_instance,
                                                 new_included_resources))

            if isinstance(field, Serializer):

                relation_type = utils.get_resource_type_from_serializer(field)

                # Get the serializer fields
                serializer_fields = utils.get_serializer_fields(field)
                if serializer_data:
                    included_data.append(
                        cls.build_json_resource_obj(serializer_fields,
                                                    serializer_data,
                                                    relation_instance,
                                                    relation_type))
                    included_data.extend(
                        cls.extract_included(serializer_fields,
                                             serializer_data,
                                             relation_instance,
                                             new_included_resources))

        return api_utils.format_keys(included_data)
Example #11
0
    def extract_included(cls, request, fields, resource, resource_instance,
                         included_resources):
        # this function may be called with an empty record (example: Browsable Interface)
        if not resource_instance:
            return

        included_data = list()
        current_serializer = fields.serializer
        context = current_serializer.context
        included_serializers = utils.get_included_serializers(
            current_serializer)
        included_resources = copy.copy(included_resources)
        included_resources = [
            inflection.underscore(value) for value in included_resources
        ]

        for field_name, field in six.iteritems(fields):
            # Skip URL field
            if field_name == api_settings.URL_FIELD_NAME:
                continue

            # Skip fields without relations or serialized data
            if not isinstance(field,
                              (relations.RelatedField,
                               relations.ManyRelatedField, BaseSerializer)):
                continue

            try:
                included_resources.remove(field_name)
            except ValueError:
                # Skip fields not in requested included resources
                # If no child field, directly continue with the next field
                if field_name not in [
                        node.split('.')[0] for node in included_resources
                ]:
                    continue

            try:
                relation_instance = getattr(resource_instance, field_name)
            except AttributeError:
                try:
                    # For ManyRelatedFields if `related_name` is not set we need to access `foo_set` from `source`
                    relation_instance = getattr(resource_instance,
                                                field.child_relation.source)
                except AttributeError:
                    if not hasattr(current_serializer, field.source):
                        continue
                    serializer_method = getattr(current_serializer,
                                                field.source)
                    relation_instance = serializer_method(resource_instance)

            if isinstance(relation_instance, Manager):
                relation_instance = relation_instance.all()

            new_included_resources = [
                key.replace('%s.' % field_name, '', 1)
                for key in included_resources
                if field_name == key.split('.')[0]
            ]
            serializer_data = resource.get(field_name)

            if isinstance(field, RemoteResourceField):
                user_id = getattr(request.user, 'id', None)
                roles = request.user.roles
                pk = serializer_data.get('id')

                include = ",".join(new_included_resources)
                try:
                    remote_resource = event_client.get_remote_resource_data(
                        field_name,
                        pk=pk,
                        user_id=user_id,
                        include=include,
                        page_size=1000,
                        roles=roles)

                    body = ujson.loads(remote_resource['body'])

                    if 400 <= remote_resource['status'] < 600:
                        raise RemoteResourceIncludeError(
                            field_name, body["errors"][0])
                except RequestTimeout:
                    raise RemoteResourceIncludeTimeoutError(field_name)

                included_data.append(body['data'])

                if body.get('included'):
                    included_data.extend(body['included'])

                # We continue here since RemoteResourceField inherits
                # form ResourceRelatedField which is a RelatedField
                continue

            if isinstance(field, relations.ManyRelatedField):
                serializer_class = included_serializers[field_name]
                field = serializer_class(relation_instance,
                                         many=True,
                                         context=context)
                serializer_data = field.data

            if isinstance(field, relations.RelatedField):
                if relation_instance is None:
                    continue

                many = field._kwargs.get('child_relation', None) is not None
                serializer_class = included_serializers[field_name]
                field = serializer_class(relation_instance,
                                         many=many,
                                         context=context)
                serializer_data = field.data

            if isinstance(field, ListSerializer):
                serializer = field.child
                relation_type = utils.get_resource_type_from_serializer(
                    serializer)
                relation_queryset = list(relation_instance)

                # Get the serializer fields
                serializer_fields = utils.get_serializer_fields(serializer)
                if serializer_data:
                    for position in range(len(serializer_data)):
                        serializer_resource = serializer_data[position]
                        nested_resource_instance = relation_queryset[position]
                        resource_type = (relation_type or
                                         utils.get_resource_type_from_instance(
                                             nested_resource_instance))
                        included_data.append(
                            cls.build_json_resource_obj(
                                serializer_fields, serializer_resource,
                                nested_resource_instance, resource_type))
                        included_data.extend(
                            cls.extract_included(request, serializer_fields,
                                                 serializer_resource,
                                                 nested_resource_instance,
                                                 new_included_resources))

            if isinstance(field, Serializer):

                relation_type = utils.get_resource_type_from_serializer(field)

                # Get the serializer fields
                serializer_fields = utils.get_serializer_fields(field)
                if serializer_data:
                    included_data.append(
                        cls.build_json_resource_obj(serializer_fields,
                                                    serializer_data,
                                                    relation_instance,
                                                    relation_type))
                    included_data.extend(
                        cls.extract_included(request, serializer_fields,
                                             serializer_data,
                                             relation_instance,
                                             new_included_resources))

        return key_formatter()(included_data)
    def render(self, data, accepted_media_type=None, renderer_context=None):

        renderer_context = renderer_context or {}

        view = renderer_context.get("view", None)
        request = renderer_context.get("request", None)

        # Get the resource name.
        resource_name = utils.get_resource_name(renderer_context)

        # If this is an error response, skip the rest.
        if resource_name == 'errors':
            return self.render_errors(data, accepted_media_type, renderer_context)

        # if response.status_code is 204 then the data to be rendered must
        # be None
        response = renderer_context.get('response', None)
        if response is not None and response.status_code == 204:
            return super(JSONRenderer, self).render(
                None, accepted_media_type, renderer_context
            )

        from rest_framework_json_api.views import RelationshipView
        if isinstance(view, RelationshipView):
            return self.render_relationship_view(data, accepted_media_type, renderer_context)

        # If `resource_name` is set to None then render default as the dev
        # wants to build the output format manually.
        if resource_name is None or resource_name is False:
            return super(JSONRenderer, self).render(
                data, accepted_media_type, renderer_context
            )

        json_api_data = data
        # initialize json_api_meta with pagination meta or an empty dict
        json_api_meta = data.get('meta', {}) if isinstance(data, dict) else {}
        included_cache = defaultdict(dict)

        if data and 'results' in data:
            serializer_data = data["results"]
        else:
            serializer_data = data

        serializer = getattr(serializer_data, 'serializer', None)

        included_resources = utils.get_included_resources(request, serializer)

        if serializer is not None:

            # Extract root meta for any type of serializer
            json_api_meta.update(self.extract_root_meta(serializer, serializer_data))

            if getattr(serializer, 'many', False):
                json_api_data = list()

                for position in range(len(serializer_data)):
                    resource = serializer_data[position]  # Get current resource
                    resource_instance = serializer.instance[position]  # Get current instance

                    if isinstance(serializer.child, rest_framework_json_api.
                                  serializers.PolymorphicModelSerializer):
                        resource_serializer_class = serializer.child.\
                            get_polymorphic_serializer_for_instance(resource_instance)(
                                context=serializer.child.context
                            )
                    else:
                        resource_serializer_class = serializer.child

                    fields = utils.get_serializer_fields(resource_serializer_class)
                    force_type_resolution = getattr(
                        resource_serializer_class, '_poly_force_type_resolution', False)

                    json_resource_obj = self.build_json_resource_obj(
                        fields, resource, resource_instance, resource_name, force_type_resolution
                    )
                    meta = self.extract_meta(serializer, resource)
                    if meta:
                        json_resource_obj.update({'meta': utils._format_object(meta)})
                    json_api_data.append(json_resource_obj)

                    self.extract_included(
                        fields, resource, resource_instance, included_resources, included_cache
                    )
            else:
                fields = utils.get_serializer_fields(serializer)
                force_type_resolution = getattr(serializer, '_poly_force_type_resolution', False)

                resource_instance = serializer.instance
                json_api_data = self.build_json_resource_obj(
                    fields, serializer_data, resource_instance, resource_name, force_type_resolution
                )

                meta = self.extract_meta(serializer, serializer_data)
                if meta:
                    json_api_data.update({'meta': utils._format_object(meta)})

                self.extract_included(
                    fields, serializer_data, resource_instance, included_resources, included_cache
                )

        # Make sure we render data in a specific order
        render_data = OrderedDict()

        if isinstance(data, dict) and data.get('links'):
            render_data['links'] = data.get('links')

        # format the api root link list
        if view.__class__ and view.__class__.__name__ == 'APIRoot':
            render_data['data'] = None
            render_data['links'] = json_api_data
        else:
            render_data['data'] = json_api_data

        if included_cache:
            if isinstance(json_api_data, list):
                objects = json_api_data
            else:
                objects = [json_api_data]

            for object in objects:
                obj_type = object.get('type')
                obj_id = object.get('id')
                if obj_type in included_cache and \
                   obj_id in included_cache[obj_type]:
                    del included_cache[obj_type][obj_id]
                if not included_cache[obj_type]:
                    del included_cache[obj_type]

        if included_cache:
            render_data['included'] = list()
            for included_type in sorted(included_cache.keys()):
                for included_id in sorted(included_cache[included_type].keys()):
                    render_data['included'].append(included_cache[included_type][included_id])

        if json_api_meta:
            render_data['meta'] = utils._format_object(json_api_meta)

        return super(JSONRenderer, self).render(
            render_data, accepted_media_type, renderer_context
        )
    def extract_included(cls, fields, resource, resource_instance, included_resources,
                         included_cache):
        """
        Adds related data to the top level included key when the request includes
        ?include=example,example_field2
        """
        # this function may be called with an empty record (example: Browsable Interface)
        if not resource_instance:
            return

        current_serializer = fields.serializer
        context = current_serializer.context
        included_serializers = utils.get_included_serializers(current_serializer)
        included_resources = copy.copy(included_resources)
        included_resources = [inflection.underscore(value) for value in included_resources]

        for field_name, field in six.iteritems(fields):
            # Skip URL field
            if field_name == api_settings.URL_FIELD_NAME:
                continue

            # Skip fields without relations or serialized data
            if not isinstance(
                    field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer)
            ):
                continue

            try:
                included_resources.remove(field_name)
            except ValueError:
                # Skip fields not in requested included resources
                # If no child field, directly continue with the next field
                if field_name not in [node.split('.')[0] for node in included_resources]:
                    continue

            relation_instance = cls.extract_relation_instance(
                field_name, field, resource_instance, current_serializer
            )
            if isinstance(relation_instance, Manager):
                relation_instance = relation_instance.all()

            serializer_data = resource.get(field_name)

            if isinstance(field, relations.ManyRelatedField):
                serializer_class = included_serializers[field_name]
                field = serializer_class(relation_instance, many=True, context=context)
                serializer_data = field.data

            if isinstance(field, relations.RelatedField):
                if relation_instance is None or not serializer_data:
                    continue

                many = field._kwargs.get('child_relation', None) is not None

                if isinstance(field, ResourceRelatedField) and not many:
                    already_included = serializer_data['type'] in included_cache and \
                        serializer_data['id'] in included_cache[serializer_data['type']]

                    if already_included:
                        continue

                serializer_class = included_serializers[field_name]
                field = serializer_class(relation_instance, many=many, context=context)
                serializer_data = field.data

            new_included_resources = [key.replace('%s.' % field_name, '', 1)
                                      for key in included_resources
                                      if field_name == key.split('.')[0]]

            if isinstance(field, ListSerializer):
                serializer = field.child
                relation_type = utils.get_resource_type_from_serializer(serializer)
                relation_queryset = list(relation_instance)

                if serializer_data:
                    for position in range(len(serializer_data)):
                        serializer_resource = serializer_data[position]
                        nested_resource_instance = relation_queryset[position]
                        resource_type = (
                            relation_type or
                            utils.get_resource_type_from_instance(nested_resource_instance)
                        )
                        serializer_fields = utils.get_serializer_fields(
                            serializer.__class__(
                                nested_resource_instance, context=serializer.context
                            )
                        )
                        new_item = cls.build_json_resource_obj(
                            serializer_fields,
                            serializer_resource,
                            nested_resource_instance,
                            resource_type,
                            getattr(serializer, '_poly_force_type_resolution', False)
                        )
                        included_cache[new_item['type']][new_item['id']] = \
                            utils._format_object(new_item)
                        cls.extract_included(
                            serializer_fields,
                            serializer_resource,
                            nested_resource_instance,
                            new_included_resources,
                            included_cache,
                        )

            if isinstance(field, Serializer):
                relation_type = utils.get_resource_type_from_serializer(field)

                # Get the serializer fields
                serializer_fields = utils.get_serializer_fields(field)
                if serializer_data:
                    new_item = cls.build_json_resource_obj(
                        serializer_fields,
                        serializer_data,
                        relation_instance,
                        relation_type,
                        getattr(field, '_poly_force_type_resolution', False)
                    )
                    included_cache[new_item['type']][new_item['id']] = utils._format_object(
                        new_item
                    )
                    cls.extract_included(
                        serializer_fields,
                        serializer_data,
                        relation_instance,
                        new_included_resources,
                        included_cache,
                    )
Example #14
0
    def render(self, data, accepted_media_type=None, renderer_context=None):
        view = renderer_context.get("view", None)
        request = renderer_context.get("request", None)

        # Get the resource name.
        resource_name = utils.get_resource_name(renderer_context)

        # If this is an error response, skip the rest.
        if resource_name == 'errors':
            return self.render_errors(data, accepted_media_type,
                                      renderer_context)

        # if response.status_code is 204 then the data to be rendered must
        # be None
        response = renderer_context.get('response', None)
        if response is not None and response.status_code == 204:
            return super(JSONRenderer, self).render(None, accepted_media_type,
                                                    renderer_context)

        from rest_framework_json_api.views import RelationshipView
        if isinstance(view, RelationshipView):
            return self.render_relationship_view(data, accepted_media_type,
                                                 renderer_context)

        # If `resource_name` is set to None then render default as the dev
        # wants to build the output format manually.
        if resource_name is None or resource_name is False:
            return super(JSONRenderer, self).render(data, accepted_media_type,
                                                    renderer_context)

        json_api_data = data
        json_api_included = list()
        # initialize json_api_meta with pagination meta or an empty dict
        json_api_meta = data.get('meta', {}) if isinstance(data, dict) else {}
        if json_api_meta:
            meta_pagination = json_api_meta.pop('pagination')
            meta_links = json_api_meta.pop('links')
            meta_pagination.update(meta_links)
            json_api_meta['pagination'] = meta_pagination
        json_api_meta['include'] = []
        json_api_meta['custom'] = []

        if data and 'results' in data:
            serializer_data = data["results"]
        else:
            serializer_data = data

        serializer = getattr(serializer_data, 'serializer', None)

        included_resources = utils.get_included_resources(request, serializer)

        if serializer is not None:

            # Get the serializer fields
            fields = utils.get_serializer_fields(serializer)

            # Determine if resource name must be resolved on each instance (polymorphic serializer)
            force_type_resolution = getattr(serializer,
                                            '_poly_force_type_resolution',
                                            False)

            # Extract root meta for any type of serializer
            json_api_meta.update(
                self.extract_root_meta(serializer, serializer_data))

            if getattr(serializer, 'many', False):
                json_api_data = list()

                for position in range(len(serializer_data)):
                    resource = serializer_data[
                        position]  # Get current resource
                    resource_instance = serializer.instance[
                        position]  # Get current instance

                    json_resource_obj = self.build_json_resource_obj(
                        fields, resource, resource_instance, resource_name,
                        force_type_resolution)
                    meta = self.extract_meta(serializer, resource)
                    if meta:
                        json_resource_obj.update(
                            {'meta': utils.format_keys(meta)})
                    json_api_data.append(json_resource_obj)

                    included = self.extract_included(fields, resource,
                                                     resource_instance,
                                                     included_resources)
                    if included:
                        json_api_included.extend(included)
            else:
                resource_instance = serializer.instance
                json_api_data = self.build_json_resource_obj(
                    fields, serializer_data, resource_instance, resource_name,
                    force_type_resolution)

                meta = self.extract_meta(serializer, serializer_data)
                if meta:
                    json_api_data.update({'meta': utils.format_keys(meta)})

                included = self.extract_included(fields, serializer_data,
                                                 resource_instance,
                                                 included_resources)
                if included:
                    json_api_included.extend(included)

        # Make sure we render data in a specific order
        render_data = OrderedDict()

        if isinstance(data, dict) and data.get('links'):
            render_data['links'] = data.get('links')

        # format the api root link list
        if view.__class__ and view.__class__.__name__ == 'APIRoot':
            render_data['data'] = None
            render_data['links'] = json_api_data
        else:
            render_data['data'] = json_api_data

        if len(json_api_included) > 0:
            # Iterate through compound documents to remove duplicates
            seen = set()
            unique_compound_documents = list()
            for included_dict in json_api_included:
                type_tuple = tuple(
                    (included_dict['type'], included_dict['id']))
                if type_tuple not in seen:
                    seen.add(type_tuple)
                    unique_compound_documents.append(included_dict)

            # Sort the items by type then by id
            render_data['included'] = sorted(unique_compound_documents,
                                             key=lambda item:
                                             (item['type'], item['id']))

        if json_api_meta:
            render_data['meta'] = utils.format_keys(json_api_meta)

        return super(renderers.JSONRenderer,
                     self).render(render_data, accepted_media_type,
                                  renderer_context)