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