From 277990fdf7b66dc41242a3eabc70a2c3443e31a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Wr=C3=B3bel?= Date: Thu, 13 Oct 2016 00:58:40 +0200 Subject: [PATCH] Overall improvements to parser. - Less exceptions caused by data type assumptions - Better handling of list data - Does not special-case RelationshipView --- rest_framework_json_api/parsers.py | 92 ++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/rest_framework_json_api/parsers.py b/rest_framework_json_api/parsers.py index 86a61e74..a9091d0e 100644 --- a/rest_framework_json_api/parsers.py +++ b/rest_framework_json_api/parsers.py @@ -6,7 +6,8 @@ from rest_framework import parsers from rest_framework.exceptions import ParseError -from . import exceptions, renderers, serializers, utils +from . import exceptions, renderers, utils +from .serializers import PolymorphicModelSerializer, ResourceIdentifierObjectSerializer class JSONParser(parsers.JSONParser): @@ -74,6 +75,29 @@ def parse_metadata(result): def parse(self, stream, media_type=None, parser_context=None): """ Parses the incoming bytestream as JSON and returns the resulting data + + There are two basic object types in JSON-API. + + 1. Resource Identifier Object + + They only have 'id' and 'type' keys (optionally also 'meta'). The 'type' + should be passed to the views for processing. These objects are used in + 'relationships' keys and also as the actual 'data' in Relationship URLs. + + 2. Resource Objects + + They use the keys as above plus optional 'attributes' and + 'relationships'. Attributes and relationships should be flattened before + sending to views and the 'type' key should be removed. + + We support requests with list data. In JSON-API list data can be found + in Relationship URLs where we would expect Resource Identifier Objects, + but we will also allow lists of Resource Objects as the users might want + to implement bulk operations in their custom views. + + In addition True, False and None will be accepted as data and passed to + views. In JSON-API None is a valid data for 1-to-1 Relationship URLs and + indicates that the relationship should be cleared. """ result = super(JSONParser, self).parse( stream, media_type=media_type, parser_context=parser_context @@ -84,32 +108,39 @@ def parse(self, stream, media_type=None, parser_context=None): data = result.get('data') view = parser_context['view'] - - from rest_framework_json_api.views import RelationshipView - if isinstance(view, RelationshipView): - # We skip parsing the object as JSONAPI Resource Identifier Object and not a regular - # Resource Object - if isinstance(data, list): - for resource_identifier_object in data: - if not ( - resource_identifier_object.get('id') and - resource_identifier_object.get('type') - ): - raise ParseError( - 'Received data contains one or more malformed JSONAPI ' - 'Resource Identifier Object(s)' - ) - elif not (data.get('id') and data.get('type')): - raise ParseError('Received data is not a valid JSONAPI Resource Identifier Object') - + resource_name = utils.get_resource_name(parser_context, expand_polymorphic_types=True) + method = parser_context.get('request').method + serializer_class = getattr(view, 'serializer_class', None) + in_relationship_view = serializer_class == ResourceIdentifierObjectSerializer + + if isinstance(data, list): + for item in data: + if not isinstance(item, dict): + err = "Items in data array must be objects with 'id' and 'type' members." + raise ParseError(err) + + if in_relationship_view: + for identifier in data: + self.verify_resource_identifier(identifier) + return data + else: + return list( + self.parse_resource(d, d, resource_name, method, serializer_class) + for d in data + ) + elif isinstance(data, dict): + if in_relationship_view: + self.verify_resource_identifier(data) + return data + else: + return self.parse_resource(data, result, resource_name, method, serializer_class) + else: + # None, True, False, numbers and strings return data - request = parser_context.get('request') - + def parse_resource(self, data, meta_source, resource_name, method, serializer_class): # Check for inconsistencies - if request.method in ('PUT', 'POST', 'PATCH'): - resource_name = utils.get_resource_name( - parser_context, expand_polymorphic_types=True) + if method in ('PUT', 'POST', 'PATCH'): if isinstance(resource_name, six.string_types): if data.get('type') != resource_name: raise exceptions.Conflict( @@ -126,17 +157,20 @@ def parse(self, stream, media_type=None, parser_context=None): "(one of [{resource_types}]).".format( data_type=data.get('type'), resource_types=", ".join(resource_name))) - if not data.get('id') and request.method in ('PATCH', 'PUT'): - raise ParseError("The resource identifier object must contain an 'id' member") + if not data.get('id') and method in ('PATCH', 'PUT'): + raise ParseError("The resource object must contain an 'id' member.") # Construct the return data - serializer_class = getattr(view, 'serializer_class', None) parsed_data = {'id': data.get('id')} if 'id' in data else {} # `type` field needs to be allowed in none polymorphic serializers if serializer_class is not None: - if issubclass(serializer_class, serializers.PolymorphicModelSerializer): + if issubclass(serializer_class, PolymorphicModelSerializer): parsed_data['type'] = data.get('type') parsed_data.update(self.parse_attributes(data)) parsed_data.update(self.parse_relationships(data)) - parsed_data.update(self.parse_metadata(result)) + parsed_data.update(self.parse_metadata(meta_source)) return parsed_data + + def verify_resource_identifier(self, data): + if not data.get('id') or not data.get('type'): + raise ParseError('Received data is not a valid JSONAPI Resource Identifier Object(s).') pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy