6
6
from rest_framework import parsers
7
7
from rest_framework .exceptions import ParseError
8
8
9
- from . import exceptions , renderers , serializers , utils
9
+ from . import exceptions , renderers , utils
10
+ from .serializers import PolymorphicModelSerializer , ResourceIdentifierObjectSerializer
10
11
11
12
12
13
class JSONParser (parsers .JSONParser ):
@@ -74,6 +75,29 @@ def parse_metadata(result):
74
75
def parse (self , stream , media_type = None , parser_context = None ):
75
76
"""
76
77
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.
77
101
"""
78
102
result = super (JSONParser , self ).parse (
79
103
stream , media_type = media_type , parser_context = parser_context
@@ -84,32 +108,39 @@ def parse(self, stream, media_type=None, parser_context=None):
84
108
85
109
data = result .get ('data' )
86
110
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
105
139
return data
106
140
107
- request = parser_context .get ('request' )
108
-
141
+ def parse_resource (self , data , meta_source , resource_name , method , serializer_class ):
109
142
# 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' ):
113
144
if isinstance (resource_name , six .string_types ):
114
145
if data .get ('type' ) != resource_name :
115
146
raise exceptions .Conflict (
@@ -126,17 +157,20 @@ def parse(self, stream, media_type=None, parser_context=None):
126
157
"(one of [{resource_types}])." .format (
127
158
data_type = data .get ('type' ),
128
159
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. " )
131
162
132
163
# Construct the return data
133
- serializer_class = getattr (view , 'serializer_class' , None )
134
164
parsed_data = {'id' : data .get ('id' )} if 'id' in data else {}
135
165
# `type` field needs to be allowed in none polymorphic serializers
136
166
if serializer_class is not None :
137
- if issubclass (serializer_class , serializers . PolymorphicModelSerializer ):
167
+ if issubclass (serializer_class , PolymorphicModelSerializer ):
138
168
parsed_data ['type' ] = data .get ('type' )
139
169
parsed_data .update (self .parse_attributes (data ))
140
170
parsed_data .update (self .parse_relationships (data ))
141
- parsed_data .update (self .parse_metadata (result ))
171
+ parsed_data .update (self .parse_metadata (meta_source ))
142
172
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