From 79382aa69965bcf1b850bc8fbf0e7358828e108b Mon Sep 17 00:00:00 2001 From: sebastiandev Date: Fri, 1 Apr 2016 16:55:42 -0300 Subject: [PATCH 1/2] refactored render method to allow results with mixed types (Serializers). Abstracted some logic into methods and some tweaks needed as well in utils to get resource_name for a particular serializer and model --- rest_framework_json_api/renderers.py | 101 ++++++++++++++++--------- rest_framework_json_api/serializers.py | 2 + rest_framework_json_api/utils.py | 34 ++++++--- 3 files changed, 90 insertions(+), 47 deletions(-) diff --git a/rest_framework_json_api/renderers.py b/rest_framework_json_api/renderers.py index 877ccf8e..6f77a330 100644 --- a/rest_framework_json_api/renderers.py +++ b/rest_framework_json_api/renderers.py @@ -82,6 +82,9 @@ def extract_relationships(fields, resource, resource_instance): if not isinstance(field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer)): continue + if field_name == 'polymorphic_ctype': + continue + source = field.source try: relation_instance_or_manager = getattr(resource_instance, source) @@ -233,6 +236,7 @@ def extract_included(fields, resource, resource_instance, included_resources): current_serializer = fields.serializer context = current_serializer.context included_serializers = utils.get_included_serializers(current_serializer) + included_resources = copy.copy(included_resources) for field_name, field in six.iteritems(fields): @@ -340,10 +344,8 @@ def extract_meta(serializer, resource): @staticmethod def extract_root_meta(serializer, resource, meta): if getattr(serializer, 'get_root_meta', None): - root_meta = serializer.get_root_meta(resource) - if root_meta: - assert isinstance(root_meta, dict), 'get_root_meta must return a dict' - meta.update(root_meta) + meta.update(serializer.get_root_meta(resource) or {}) + return meta @staticmethod @@ -384,7 +386,6 @@ def render_errors(self, data, accepted_media_type=None, renderer_context=None): ) def render(self, data, accepted_media_type=None, renderer_context=None): - view = renderer_context.get("view", None) request = renderer_context.get("request", None) @@ -428,42 +429,24 @@ def render(self, data, accepted_media_type=None, renderer_context=None): # If detail view then json api spec expects dict, otherwise a list # - http://jsonapi.org/format/#document-top-level # The `results` key may be missing if unpaginated or an OPTIONS request + resource_name = self.check_resource_name(resource_name, serializer_data.serializer, view) + json_api_data = self.render_serializer(serializer_data.serializer, serializer_data, resource_name, + included_resources, json_api_meta, json_api_included) - resource_serializer = serializer_data.serializer - - # Get the serializer fields - fields = utils.get_serializer_fields(resource_serializer) + else: + if hasattr(data, 'serializer'): + resource_name = self.check_resource_name(resource_name, data.serializer, view) + json_api_data = self.render_serializer_many(data.serializer, data, resource_name, included_resources, + json_api_meta, json_api_included) - json_api_data = list() - for position in range(len(serializer_data)): - resource = serializer_data[position] # Get current resource - resource_instance = resource_serializer.instance[position] # Get current instance + elif isinstance(serializer_data, (list, tuple)) and hasattr(serializer_data[0], 'serializer'): + json_api_data = list() - json_resource_obj = self.build_json_resource_obj(fields, resource, resource_instance, resource_name) - meta = self.extract_meta(resource_serializer, resource) - if meta: - json_resource_obj.update({'meta': utils.format_keys(meta)}) - json_api_meta = self.extract_root_meta(resource_serializer, resource, json_api_meta) - json_api_data.append(json_resource_obj) + for r in serializer_data: + resource_name = self.check_resource_name(resource_name, r.serializer.child, view) + json_api_data.extend(self.render_serializer(r.serializer, r, resource_name, included_resources, + json_api_meta, json_api_included)) - included = self.extract_included(fields, resource, resource_instance, included_resources) - if included: - json_api_included.extend(included) - else: - # Check if data contains a serializer - if hasattr(data, 'serializer'): - fields = utils.get_serializer_fields(data.serializer) - resource_instance = data.serializer.instance - json_api_data = self.build_json_resource_obj(fields, data, resource_instance, resource_name) - - meta = self.extract_meta(data.serializer, data) - if meta: - json_api_data.update({'meta': utils.format_keys(meta)}) - json_api_meta = self.extract_root_meta(data.serializer, data, json_api_meta) - - included = self.extract_included(fields, data, resource_instance, included_resources) - if included: - json_api_included.extend(included) else: json_api_data = data @@ -499,3 +482,47 @@ def render(self, data, accepted_media_type=None, renderer_context=None): return super(JSONRenderer, self).render( render_data, accepted_media_type, renderer_context ) + + def check_resource_name(self, resource_name, serializer, view): + res_name = resource_name + + if res_name == view.__class__.__name__: + resource_from_serializer = utils.get_resource_name_from_serializer_or_model(serializer, view) + res_name = resource_from_serializer or res_name + + return res_name + + def render_serializer(self, serializer, data, resource_name, included_resources, json_api_meta, json_api_included): + fields = utils.get_serializer_fields(serializer) + rendered_data = list() + + for position in range(len(data)): + resource = data[position] # Get current resource + resource_instance = serializer.instance[position] # Get current instance + + rendered_data.append(self.render_resource(serializer, fields, resource, resource_instance, resource_name, + included_resources, json_api_meta, json_api_included)) + + return rendered_data + + def render_serializer_many(self, serializer, data, resource_name, included_resources, json_api_meta, json_api_included): + fields = utils.get_serializer_fields(serializer) + resource_instance = serializer.instance + return self.render_resource(serializer, fields, data, resource_instance, resource_name, included_resources, + json_api_meta, json_api_included) + + def render_resource(self, serializer, fields, resource, resource_instance, resource_name, included_resources, + json_api_meta, json_api_included): + data = self.build_json_resource_obj(fields, resource, resource_instance, resource_name) + + meta = self.extract_meta(serializer, resource) + if meta: + data.update({'meta': utils.format_keys(meta)}) + + self.extract_root_meta(serializer, resource, json_api_meta) + + included = self.extract_included(fields, resource, resource_instance, included_resources) + if included: + json_api_included.extend(included) + + return data diff --git a/rest_framework_json_api/serializers.py b/rest_framework_json_api/serializers.py index 953c4437..a559ba17 100644 --- a/rest_framework_json_api/serializers.py +++ b/rest_framework_json_api/serializers.py @@ -54,11 +54,13 @@ def __init__(self, *args, **kwargs): pass else: fieldset = request.query_params.get(param_name).split(',') + # iterate over a *copy* of self.fields' underlying OrderedDict, because we may modify the # original during the iteration. self.fields is a `rest_framework.utils.serializer_helpers.BindingDict` for field_name, field in self.fields.fields.copy().items(): if field_name == api_settings.URL_FIELD_NAME: # leave self link there continue + if field_name not in fieldset: self.fields.pop(field_name) diff --git a/rest_framework_json_api/utils.py b/rest_framework_json_api/utils.py index d8a4e67a..06f86326 100644 --- a/rest_framework_json_api/utils.py +++ b/rest_framework_json_api/utils.py @@ -47,20 +47,34 @@ def get_resource_name(context): except AttributeError: try: serializer = view.get_serializer_class() - return get_resource_type_from_serializer(serializer) + resource_name = get_resource_name_from_serializer_or_model(serializer, view) except AttributeError: - try: - resource_name = get_resource_type_from_model(view.model) - except AttributeError: - resource_name = view.__class__.__name__ + resource_name = get_resource_name_from_model_or_relationship(view) + + return resource_name - if not isinstance(resource_name, six.string_types): - # The resource name is not a string - return as is - return resource_name - # the name was calculated automatically from the view > pluralize and format - resource_name = format_relation_name(resource_name) +def get_resource_name_from_serializer_or_model(serializer, view): + try: + return get_resource_type_from_serializer(serializer) + except AttributeError: + resource_name = get_resource_name_from_model_or_relationship(view) + + return resource_name + + +def get_resource_name_from_model_or_relationship(view): + try: + resource_name = get_resource_type_from_model(view.model) + except AttributeError: + resource_name = view.__class__.__name__ + + if not isinstance(resource_name, six.string_types): + # The resource name is not a string - return as is + return resource_name + # the name was calculated automatically from the view > pluralize and format + resource_name = format_relation_name(resource_name) return resource_name From 2b4683e6ccb601ad7b6766c86da038bb3b9deaa8 Mon Sep 17 00:00:00 2001 From: seba Date: Wed, 20 Apr 2016 15:34:17 -0300 Subject: [PATCH 2/2] fixed single data results, serializer with many and mixed result types --- rest_framework_json_api/renderers.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/rest_framework_json_api/renderers.py b/rest_framework_json_api/renderers.py index 6f77a330..15d1b9ea 100644 --- a/rest_framework_json_api/renderers.py +++ b/rest_framework_json_api/renderers.py @@ -422,6 +422,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None): else: serializer_data = data + # A serializer with many if hasattr(serializer_data, 'serializer') and getattr(serializer_data.serializer, 'many', False): # The below is not true for non-paginated responses # and isinstance(data, dict): @@ -430,21 +431,27 @@ def render(self, data, accepted_media_type=None, renderer_context=None): # - http://jsonapi.org/format/#document-top-level # The `results` key may be missing if unpaginated or an OPTIONS request resource_name = self.check_resource_name(resource_name, serializer_data.serializer, view) - json_api_data = self.render_serializer(serializer_data.serializer, serializer_data, resource_name, - included_resources, json_api_meta, json_api_included) + d_serializer = serializer_data.serializer.child if hasattr(serializer_data.serializer, 'child') else serializer_data.serializer + + json_api_data = self.render_serializer_many(d_serializer, serializer_data, resource_name, + included_resources, json_api_meta, json_api_included) else: + # A single item if hasattr(data, 'serializer'): resource_name = self.check_resource_name(resource_name, data.serializer, view) - json_api_data = self.render_serializer_many(data.serializer, data, resource_name, included_resources, - json_api_meta, json_api_included) + json_api_data = self.render_serializer(data.serializer, data, resource_name, included_resources, + json_api_meta, json_api_included) + + # A list of mixed items elif isinstance(serializer_data, (list, tuple)) and hasattr(serializer_data[0], 'serializer'): json_api_data = list() for r in serializer_data: - resource_name = self.check_resource_name(resource_name, r.serializer.child, view) - json_api_data.extend(self.render_serializer(r.serializer, r, resource_name, included_resources, + resource_name = self.check_resource_name(resource_name, r.serializer, view) + r_serializer = r.serializer.child if hasattr(r.serializer, 'child') else r.serializer + json_api_data.append(self.render_serializer(r_serializer, r, resource_name, included_resources, json_api_meta, json_api_included)) else: @@ -492,7 +499,7 @@ def check_resource_name(self, resource_name, serializer, view): return res_name - def render_serializer(self, serializer, data, resource_name, included_resources, json_api_meta, json_api_included): + def render_serializer_many(self, serializer, data, resource_name, included_resources, json_api_meta, json_api_included): fields = utils.get_serializer_fields(serializer) rendered_data = list() @@ -505,7 +512,7 @@ def render_serializer(self, serializer, data, resource_name, included_resources, return rendered_data - def render_serializer_many(self, serializer, data, resource_name, included_resources, json_api_meta, json_api_included): + def render_serializer(self, serializer, data, resource_name, included_resources, json_api_meta, json_api_included): fields = utils.get_serializer_fields(serializer) resource_instance = serializer.instance return self.render_resource(serializer, fields, data, resource_instance, resource_name, included_resources,