1
1
import inflection
2
+
3
+ from django .db .models .query import QuerySet
2
4
from django .utils .translation import ugettext_lazy as _
5
+ from django .utils import six
3
6
from rest_framework .exceptions import ParseError
4
7
from rest_framework .serializers import *
5
8
6
9
from rest_framework_json_api .relations import ResourceRelatedField
10
+ from rest_framework_json_api .exceptions import Conflict
7
11
from rest_framework_json_api .utils import (
8
12
get_resource_type_from_model , get_resource_type_from_instance ,
9
13
get_resource_type_from_serializer , get_included_serializers , get_included_resources )
10
14
11
15
12
16
class ResourceIdentifierObjectSerializer (BaseSerializer ):
13
17
default_error_messages = {
14
- 'incorrect_model_type' : _ ('Incorrect model type. Expected {model_type}, received {received_type}.' ),
18
+ 'incorrect_model_type' : _ ('Incorrect model type. Expected {model_type}, '
19
+ 'received {received_type}.' ),
15
20
'does_not_exist' : _ ('Invalid pk "{pk_value}" - object does not exist.' ),
16
21
'incorrect_type' : _ ('Incorrect type. Expected pk value, received {data_type}.' ),
17
22
}
@@ -21,7 +26,8 @@ class ResourceIdentifierObjectSerializer(BaseSerializer):
21
26
def __init__ (self , * args , ** kwargs ):
22
27
self .model_class = kwargs .pop ('model_class' , self .model_class )
23
28
if 'instance' not in kwargs and not self .model_class :
24
- raise RuntimeError ('ResourceIdentifierObjectsSerializer must be initialized with a model class.' )
29
+ raise RuntimeError (
30
+ 'ResourceIdentifierObjectsSerializer must be initialized with a model class.' )
25
31
super (ResourceIdentifierObjectSerializer , self ).__init__ (* args , ** kwargs )
26
32
27
33
def to_representation (self , instance ):
@@ -32,7 +38,8 @@ def to_representation(self, instance):
32
38
33
39
def to_internal_value (self , data ):
34
40
if data ['type' ] != get_resource_type_from_model (self .model_class ):
35
- self .fail ('incorrect_model_type' , model_type = self .model_class , received_type = data ['type' ])
41
+ self .fail (
42
+ 'incorrect_model_type' , model_type = self .model_class , received_type = data ['type' ])
36
43
pk = data ['id' ]
37
44
try :
38
45
return self .model_class .objects .get (pk = pk )
@@ -48,15 +55,18 @@ def __init__(self, *args, **kwargs):
48
55
request = context .get ('request' ) if context else None
49
56
50
57
if request :
51
- sparse_fieldset_query_param = 'fields[{}]' .format (get_resource_type_from_serializer (self ))
58
+ sparse_fieldset_query_param = 'fields[{}]' .format (
59
+ get_resource_type_from_serializer (self ))
52
60
try :
53
- param_name = next (key for key in request .query_params if sparse_fieldset_query_param in key )
61
+ param_name = next (
62
+ key for key in request .query_params if sparse_fieldset_query_param in key )
54
63
except StopIteration :
55
64
pass
56
65
else :
57
66
fieldset = request .query_params .get (param_name ).split (',' )
58
- # iterate over a *copy* of self.fields' underlying OrderedDict, because we may modify the
59
- # original during the iteration. self.fields is a `rest_framework.utils.serializer_helpers.BindingDict`
67
+ # Iterate over a *copy* of self.fields' underlying OrderedDict, because we may
68
+ # modify the original during the iteration.
69
+ # self.fields is a `rest_framework.utils.serializer_helpers.BindingDict`
60
70
for field_name , field in self .fields .fields .copy ().items ():
61
71
if field_name == api_settings .URL_FIELD_NAME : # leave self link there
62
72
continue
@@ -100,7 +110,8 @@ def validate_path(serializer_class, field_path, path):
100
110
super (IncludedResourcesValidationMixin , self ).__init__ (* args , ** kwargs )
101
111
102
112
103
- class HyperlinkedModelSerializer (IncludedResourcesValidationMixin , SparseFieldsetsMixin , HyperlinkedModelSerializer ):
113
+ class HyperlinkedModelSerializer (IncludedResourcesValidationMixin , SparseFieldsetsMixin ,
114
+ HyperlinkedModelSerializer ):
104
115
"""
105
116
A type of `ModelSerializer` that uses hyperlinked relationships instead
106
117
of primary key relationships. Specifically:
@@ -151,3 +162,122 @@ def get_field_names(self, declared_fields, info):
151
162
declared [field_name ] = field
152
163
fields = super (ModelSerializer , self ).get_field_names (declared , info )
153
164
return list (fields ) + list (getattr (self .Meta , 'meta_fields' , list ()))
165
+
166
+
167
+ class PolymorphicSerializerMetaclass (SerializerMetaclass ):
168
+ """
169
+ This metaclass ensures that the `polymorphic_serializers` is correctly defined on a
170
+ `PolymorphicSerializer` class and make a cache of model/serializer/type mappings.
171
+ """
172
+
173
+ def __new__ (cls , name , bases , attrs ):
174
+ new_class = super (PolymorphicSerializerMetaclass , cls ).__new__ (cls , name , bases , attrs )
175
+
176
+ # Ensure initialization is only performed for subclasses of PolymorphicModelSerializer
177
+ # (excluding PolymorphicModelSerializer class itself).
178
+ parents = [b for b in bases if isinstance (b , PolymorphicSerializerMetaclass )]
179
+ if not parents :
180
+ return new_class
181
+
182
+ polymorphic_serializers = getattr (new_class , 'polymorphic_serializers' , None )
183
+ if not polymorphic_serializers :
184
+ raise NotImplementedError (
185
+ "A PolymorphicModelSerializer must define a `polymorphic_serializers` attribute." )
186
+ serializer_to_model = {
187
+ serializer : serializer .Meta .model for serializer in polymorphic_serializers }
188
+ model_to_serializer = {
189
+ serializer .Meta .model : serializer for serializer in polymorphic_serializers }
190
+ type_to_model = {
191
+ get_resource_type_from_model (model ): model for model in model_to_serializer .keys ()}
192
+ setattr (new_class , '_poly_serializer_model_map' , serializer_to_model )
193
+ setattr (new_class , '_poly_model_serializer_map' , model_to_serializer )
194
+ setattr (new_class , '_poly_type_model_map' , type_to_model )
195
+ return new_class
196
+
197
+
198
+ @six .add_metaclass (PolymorphicSerializerMetaclass )
199
+ class PolymorphicModelSerializer (ModelSerializer ):
200
+ """
201
+ A serializer for polymorphic models.
202
+ Useful for "lazy" parent models. Leaves should be represented with a regular serializer.
203
+ """
204
+ def get_fields (self ):
205
+ """
206
+ Return an exhaustive list of the polymorphic serializer fields.
207
+ """
208
+ if self .instance is not None :
209
+ if not isinstance (self .instance , QuerySet ):
210
+ serializer_class = self .get_polymorphic_serializer_for_instance (self .instance )
211
+ return serializer_class (self .instance , context = self .context ).get_fields ()
212
+ else :
213
+ raise Exception ("Cannot get fields from a polymorphic serializer given a queryset" )
214
+ return super (PolymorphicModelSerializer , self ).get_fields ()
215
+
216
+ def get_polymorphic_serializer_for_instance (self , instance ):
217
+ """
218
+ Return the polymorphic serializer associated with the given instance/model.
219
+ Raise `NotImplementedError` if no serializer is found for the given model. This usually
220
+ means that a serializer is missing in the class's `polymorphic_serializers` attribute.
221
+ """
222
+ try :
223
+ return self ._poly_model_serializer_map [instance ._meta .model ]
224
+ except KeyError :
225
+ raise NotImplementedError (
226
+ "No polymorphic serializer has been found for model {}" .format (
227
+ instance ._meta .model .__name__ ))
228
+
229
+ def get_polymorphic_model_for_serializer (self , serializer ):
230
+ """
231
+ Return the polymorphic model associated with the given serializer.
232
+ Raise `NotImplementedError` if no model is found for the given serializer. This usually
233
+ means that a serializer is missing in the class's `polymorphic_serializers` attribute.
234
+ """
235
+ try :
236
+ return self ._poly_serializer_model_map [serializer ]
237
+ except KeyError :
238
+ raise NotImplementedError (
239
+ "No polymorphic model has been found for serializer {}" .format (serializer .__name__ ))
240
+
241
+ def get_polymorphic_model_for_type (self , obj_type ):
242
+ """
243
+ Return the polymorphic model associated with the given type.
244
+ Raise `NotImplementedError` if no model is found for the given type. This usually
245
+ means that a serializer is missing in the class's `polymorphic_serializers` attribute.
246
+ """
247
+ try :
248
+ return self ._poly_type_model_map [obj_type ]
249
+ except KeyError :
250
+ raise NotImplementedError (
251
+ "No polymorphic model has been found for type {}" .format (obj_type ))
252
+
253
+ def get_polymorphic_serializer_for_type (self , obj_type ):
254
+ """
255
+ Return the polymorphic serializer associated with the given type.
256
+ Raise `NotImplementedError` if no serializer is found for the given type. This usually
257
+ means that a serializer is missing in the class's `polymorphic_serializers` attribute.
258
+ """
259
+ return self .get_polymorphic_serializer_for_instance (
260
+ self .get_polymorphic_model_for_type (obj_type ))
261
+
262
+ def to_representation (self , instance ):
263
+ """
264
+ Retrieve the appropriate polymorphic serializer and use this to handle representation.
265
+ """
266
+ serializer_class = self .get_polymorphic_serializer_for_instance (instance )
267
+ return serializer_class (instance , context = self .context ).to_representation (instance )
268
+
269
+ def to_internal_value (self , data ):
270
+ """
271
+ Ensure that the given type is one of the expected polymorphic types, then retrieve the
272
+ appropriate polymorphic serializer and use this to handle internal value.
273
+ """
274
+ received_type = data .get ('type' )
275
+ expected_types = self ._poly_type_model_map .keys ()
276
+ if received_type not in expected_types :
277
+ raise Conflict (
278
+ 'Incorrect relation type. Expected on of {expected_types}, '
279
+ 'received {received_type}.' .format (
280
+ expected_types = ', ' .join (expected_types ), received_type = received_type ))
281
+ serializer_class = self .get_polymorphic_serializer_for_type (received_type )
282
+ self .__class__ = serializer_class
283
+ return serializer_class (data , context = self .context ).to_internal_value (data )
0 commit comments