66from rest_framework import parsers
77from rest_framework .exceptions import ParseError
88
9- from . import exceptions , renderers , serializers , utils
9+ from . import exceptions , renderers , utils
10+ from .serializers import PolymorphicModelSerializer , ResourceIdentifierObjectSerializer
1011
1112
1213class JSONParser (parsers .JSONParser ):
@@ -74,6 +75,29 @@ def parse_metadata(result):
7475 def parse (self , stream , media_type = None , parser_context = None ):
7576 """
7677 Parses the incoming bytestream as JSON and returns the resulting data
78+
79+ There are two basic object types in JSON-API.
80+
81+ 1. Resource Identifier Object
82+
83+ They only have 'id' and 'type' keys (optionally also 'meta'). The 'type'
84+ should be passed to the views for processing. These objects are used in
85+ 'relationships' keys and also as the actual 'data' in Relationship URLs.
86+
87+ 2. Resource Objects
88+
89+ They use the keys as above plus optional 'attributes' and
90+ 'relationships'. Attributes and relationships should be flattened before
91+ sending to views and the 'type' key should be removed.
92+
93+ We support requests with list data. In JSON-API list data can be found
94+ in Relationship URLs where we would expect Resource Identifier Objects,
95+ but we will also allow lists of Resource Objects as the users might want
96+ to implement bulk operations in their custom views.
97+
98+ In addition True, False and None will be accepted as data and passed to
99+ views. In JSON-API None is a valid data for 1-to-1 Relationship URLs and
100+ indicates that the relationship should be cleared.
77101 """
78102 result = super (JSONParser , self ).parse (
79103 stream , media_type = media_type , parser_context = parser_context
@@ -84,32 +108,39 @@ def parse(self, stream, media_type=None, parser_context=None):
84108
85109 data = result .get ('data' )
86110 view = parser_context ['view' ]
87-
88- from rest_framework_json_api .views import RelationshipView
89- if isinstance (view , RelationshipView ):
90- # We skip parsing the object as JSONAPI Resource Identifier Object and not a regular
91- # Resource Object
92- if isinstance (data , list ):
93- for resource_identifier_object in data :
94- if not (
95- resource_identifier_object .get ('id' ) and
96- resource_identifier_object .get ('type' )
97- ):
98- raise ParseError (
99- 'Received data contains one or more malformed JSONAPI '
100- 'Resource Identifier Object(s)'
101- )
102- elif not (data .get ('id' ) and data .get ('type' )):
103- raise ParseError ('Received data is not a valid JSONAPI Resource Identifier Object' )
104-
111+ resource_name = utils .get_resource_name (parser_context , expand_polymorphic_types = True )
112+ method = parser_context .get ('request' ).method
113+ serializer_class = getattr (view , 'serializer_class' , None )
114+ in_relationship_view = serializer_class == ResourceIdentifierObjectSerializer
115+
116+ if isinstance (data , list ):
117+ for item in data :
118+ if not isinstance (item , dict ):
119+ err = "Items in data array must be objects with 'id' and 'type' members."
120+ raise ParseError (err )
121+
122+ if in_relationship_view :
123+ for identifier in data :
124+ self .verify_resource_identifier (identifier )
125+ return data
126+ else :
127+ return list (
128+ self .parse_resource (d , d , resource_name , method , serializer_class )
129+ for d in data
130+ )
131+ elif isinstance (data , dict ):
132+ if in_relationship_view :
133+ self .verify_resource_identifier (data )
134+ return data
135+ else :
136+ return self .parse_resource (data , result , resource_name , method , serializer_class )
137+ else :
138+ # None, True, False, numbers and strings
105139 return data
106140
107- request = parser_context .get ('request' )
108-
141+ def parse_resource (self , data , meta_source , resource_name , method , serializer_class ):
109142 # Check for inconsistencies
110- if request .method in ('PUT' , 'POST' , 'PATCH' ):
111- resource_name = utils .get_resource_name (
112- parser_context , expand_polymorphic_types = True )
143+ if method in ('PUT' , 'POST' , 'PATCH' ):
113144 if isinstance (resource_name , six .string_types ):
114145 if data .get ('type' ) != resource_name :
115146 raise exceptions .Conflict (
@@ -126,17 +157,20 @@ def parse(self, stream, media_type=None, parser_context=None):
126157 "(one of [{resource_types}])." .format (
127158 data_type = data .get ('type' ),
128159 resource_types = ", " .join (resource_name )))
129- if not data .get ('id' ) and request . method in ('PATCH' , 'PUT' ):
130- raise ParseError ("The resource identifier object must contain an 'id' member" )
160+ if not data .get ('id' ) and method in ('PATCH' , 'PUT' ):
161+ raise ParseError ("The resource object must contain an 'id' member. " )
131162
132163 # Construct the return data
133- serializer_class = getattr (view , 'serializer_class' , None )
134164 parsed_data = {'id' : data .get ('id' )} if 'id' in data else {}
135165 # `type` field needs to be allowed in none polymorphic serializers
136166 if serializer_class is not None :
137- if issubclass (serializer_class , serializers . PolymorphicModelSerializer ):
167+ if issubclass (serializer_class , PolymorphicModelSerializer ):
138168 parsed_data ['type' ] = data .get ('type' )
139169 parsed_data .update (self .parse_attributes (data ))
140170 parsed_data .update (self .parse_relationships (data ))
141- parsed_data .update (self .parse_metadata (result ))
171+ parsed_data .update (self .parse_metadata (meta_source ))
142172 return parsed_data
173+
174+ def verify_resource_identifier (self , data ):
175+ if not data .get ('id' ) or not data .get ('type' ):
176+ raise ParseError ('Received data is not a valid JSONAPI Resource Identifier Object(s).' )
0 commit comments