5
5
from rest_framework .exceptions import ParseError
6
6
7
7
from . import utils , renderers , exceptions
8
+ from .serializers import ResourceIdentifierObjectSerializer
8
9
9
10
10
11
class JSONParser (parsers .JSONParser ):
@@ -57,6 +58,29 @@ def parse_metadata(result):
57
58
def parse (self , stream , media_type = None , parser_context = None ):
58
59
"""
59
60
Parses the incoming bytestream as JSON and returns the resulting data
61
+
62
+ There are two basic object types in JSON-API.
63
+
64
+ 1. Resource Identifier Object
65
+
66
+ They only have 'id' and 'type' keys (optionally also 'meta'). The 'type'
67
+ should be passed to the views for processing. These objects are used in
68
+ 'relationships' keys and also as the actual 'data' in Relationship URLs.
69
+
70
+ 2. Resource Objects
71
+
72
+ They use the keys as above plus optional 'attributes' and
73
+ 'relationships'. Attributes and relationships should be flattened before
74
+ sending to views and the 'type' key should be removed.
75
+
76
+ We support requests with list data. In JSON-API list data can be found
77
+ in Relationship URLs where we would expect Resource Identifier Objects,
78
+ but we will also allow lists of Resource Objects as the users might want
79
+ to implement bulk operations in their custom views.
80
+
81
+ In addition True, False and None will be accepted as data and passed to
82
+ views. In JSON-API None is a valid data for 1-to-1 Relationship URLs and
83
+ indicates that the relationship should be cleared.
60
84
"""
61
85
result = super (JSONParser , self ).parse (stream , media_type = media_type , parser_context = parser_context )
62
86
@@ -65,38 +89,52 @@ def parse(self, stream, media_type=None, parser_context=None):
65
89
66
90
data = result .get ('data' )
67
91
68
- from rest_framework_json_api .views import RelationshipView
69
- if isinstance (parser_context ['view' ], RelationshipView ):
70
- # We skip parsing the object as JSONAPI Resource Identifier Object and not a regular Resource Object
71
- if isinstance (data , list ):
72
- for resource_identifier_object in data :
73
- if not (resource_identifier_object .get ('id' ) and resource_identifier_object .get ('type' )):
74
- raise ParseError (
75
- 'Received data contains one or more malformed JSONAPI Resource Identifier Object(s)'
76
- )
77
- elif not (data .get ('id' ) and data .get ('type' )):
78
- raise ParseError ('Received data is not a valid JSONAPI Resource Identifier Object' )
79
-
92
+ resource_name = utils .get_resource_name (parser_context )
93
+ method = parser_context .get ('request' ).method
94
+ in_relationship_view = (
95
+ parser_context ['view' ].serializer_class == ResourceIdentifierObjectSerializer
96
+ )
97
+
98
+ if isinstance (data , list ):
99
+ for item in data :
100
+ if not isinstance (item , dict ):
101
+ err = "Items in data array must be objects with 'id' and 'type' members."
102
+ raise ParseError (err )
103
+
104
+ if in_relationship_view :
105
+ for identifier in data :
106
+ self .verify_resource_identifier (identifier )
107
+ return data
108
+ else :
109
+ return [self .parse_resource (d , d , resource_name , method ) for d in data ]
110
+ elif isinstance (data , dict ):
111
+ if in_relationship_view :
112
+ self .verify_resource_identifier (data )
113
+ return data
114
+ else :
115
+ return self .parse_resource (data , result , resource_name , method )
116
+ else :
117
+ # None, True, False, numbers and strings
80
118
return data
81
119
82
- request = parser_context .get ('request' )
83
-
120
+ def parse_resource (self , data , meta_source , resource_name , method ):
84
121
# Check for inconsistencies
85
- resource_name = utils .get_resource_name (parser_context )
86
- if data .get ('type' ) != resource_name and request .method in ('PUT' , 'POST' , 'PATCH' ):
122
+ if data .get ('type' ) != resource_name and method in ('PUT' , 'POST' , 'PATCH' ):
87
123
raise exceptions .Conflict (
88
124
"The resource object's type ({data_type}) is not the type "
89
- "that constitute the collection represented by the endpoint ({resource_type})." .format (
90
- data_type = data .get ('type' ),
91
- resource_type = resource_name
92
- )
125
+ "that constitute the collection represented by the endpoint ({resource_type})." \
126
+ .format (data_type = data .get ('type' ), resource_type = resource_name )
93
127
)
94
- if not data .get ('id' ) and request . method in ('PATCH' , 'PUT' ):
95
- raise ParseError ("The resource identifier object must contain an 'id' member" )
128
+ if not data .get ('id' ) and method in ('PATCH' , 'PUT' ):
129
+ raise ParseError ("The resource object must contain an 'id' member. " )
96
130
97
131
# Construct the return data
98
132
parsed_data = {'id' : data .get ('id' )} if 'id' in data else {}
99
133
parsed_data .update (self .parse_attributes (data ))
100
134
parsed_data .update (self .parse_relationships (data ))
101
- parsed_data .update (self .parse_metadata (result ))
135
+ parsed_data .update (self .parse_metadata (meta_source ))
102
136
return parsed_data
137
+
138
+ def verify_resource_identifier (self , data ):
139
+ if not data .get ('id' ) or not data .get ('type' ):
140
+ raise ParseError ('Received data is not a valid JSONAPI Resource Identifier Object(s).' )
0 commit comments