7
7
from django .core .exceptions import ImproperlyConfigured
8
8
from django .urls import NoReverseMatch
9
9
from django .utils .translation import ugettext_lazy as _
10
- from rest_framework .fields import MISSING_ERROR_MESSAGE
11
- from rest_framework .relations import MANY_RELATION_KWARGS , PrimaryKeyRelatedField
10
+ from rest_framework .fields import MISSING_ERROR_MESSAGE , SkipField
11
+ from rest_framework .relations import MANY_RELATION_KWARGS
12
+ from rest_framework .relations import ManyRelatedField as DRFManyRelatedField
13
+ from rest_framework .relations import PrimaryKeyRelatedField , RelatedField
12
14
from rest_framework .reverse import reverse
13
15
from rest_framework .serializers import Serializer
14
16
29
31
]
30
32
31
33
32
- class ResourceRelatedField (PrimaryKeyRelatedField ):
33
- _skip_polymorphic_optimization = True
34
+ class SkipDataMixin (object ):
35
+ """
36
+ This workaround skips "data" rendering for relationships
37
+ in order to save some sql queries and improve performance
38
+ """
39
+
40
+ def __init__ (self , * args , ** kwargs ):
41
+ super (SkipDataMixin , self ).__init__ (* args , ** kwargs )
42
+
43
+ def get_attribute (self , instance ):
44
+ raise SkipField
45
+
46
+ def to_representation (self , * args ):
47
+ raise NotImplementedError
48
+
49
+
50
+ class ManyRelatedFieldWithNoData (SkipDataMixin , DRFManyRelatedField ):
51
+ pass
52
+
53
+
54
+ class HyperLinkedMixin (object ):
34
55
self_link_view_name = None
35
56
related_link_view_name = None
36
57
related_link_lookup_field = 'pk'
37
58
38
- default_error_messages = {
39
- 'required' : _ ('This field is required.' ),
40
- 'does_not_exist' : _ ('Invalid pk "{pk_value}" - object does not exist.' ),
41
- 'incorrect_type' : _ (
42
- 'Incorrect type. Expected resource identifier object, received {data_type}.'
43
- ),
44
- 'incorrect_relation_type' : _ (
45
- 'Incorrect relation type. Expected {relation_type}, received {received_type}.'
46
- ),
47
- 'missing_type' : _ ('Invalid resource identifier object: missing \' type\' attribute' ),
48
- 'missing_id' : _ ('Invalid resource identifier object: missing \' id\' attribute' ),
49
- 'no_match' : _ ('Invalid hyperlink - No URL match.' ),
50
- }
51
-
52
59
def __init__ (self , self_link_view_name = None , related_link_view_name = None , ** kwargs ):
53
60
if self_link_view_name is not None :
54
61
self .self_link_view_name = self_link_view_name
@@ -62,34 +69,12 @@ def __init__(self, self_link_view_name=None, related_link_view_name=None, **kwar
62
69
'related_link_url_kwarg' , self .related_link_lookup_field
63
70
)
64
71
65
- # check for a model class that was passed in for the relation type
66
- model = kwargs .pop ('model' , None )
67
- if model :
68
- self .model = model
69
-
70
72
# We include this simply for dependency injection in tests.
71
73
# We can't add it as a class attributes or it would expect an
72
74
# implicit `self` argument to be passed.
73
75
self .reverse = reverse
74
76
75
- super (ResourceRelatedField , self ).__init__ (** kwargs )
76
-
77
- def use_pk_only_optimization (self ):
78
- # We need the real object to determine its type...
79
- return self .get_resource_type_from_included_serializer () is not None
80
-
81
- def conflict (self , key , ** kwargs ):
82
- """
83
- A helper method that simply raises a validation error.
84
- """
85
- try :
86
- msg = self .error_messages [key ]
87
- except KeyError :
88
- class_name = self .__class__ .__name__
89
- msg = MISSING_ERROR_MESSAGE .format (class_name = class_name , key = key )
90
- raise AssertionError (msg )
91
- message_string = msg .format (** kwargs )
92
- raise Conflict (message_string )
77
+ super (HyperLinkedMixin , self ).__init__ (** kwargs )
93
78
94
79
def get_url (self , name , view_name , kwargs , request ):
95
80
"""
@@ -140,6 +125,78 @@ def get_links(self, obj=None, lookup_field='pk'):
140
125
return_data .update ({'related' : related_link })
141
126
return return_data
142
127
128
+
129
+ class HyperLinkedRelatedField (HyperLinkedMixin , SkipDataMixin , RelatedField ):
130
+
131
+ @classmethod
132
+ def many_init (cls , * args , ** kwargs ):
133
+ """
134
+ This method handles creating a parent `ManyRelatedField` instance
135
+ when the `many=True` keyword argument is passed.
136
+
137
+ Typically you won't need to override this method.
138
+
139
+ Note that we're over-cautious in passing most arguments to both parent
140
+ and child classes in order to try to cover the general case. If you're
141
+ overriding this method you'll probably want something much simpler, eg:
142
+
143
+ @classmethod
144
+ def many_init(cls, *args, **kwargs):
145
+ kwargs['child'] = cls()
146
+ return CustomManyRelatedField(*args, **kwargs)
147
+ """
148
+ list_kwargs = {'child_relation' : cls (* args , ** kwargs )}
149
+ for key in kwargs :
150
+ if key in MANY_RELATION_KWARGS :
151
+ list_kwargs [key ] = kwargs [key ]
152
+ return ManyRelatedFieldWithNoData (** list_kwargs )
153
+
154
+
155
+ class ResourceRelatedField (HyperLinkedMixin , PrimaryKeyRelatedField ):
156
+ _skip_polymorphic_optimization = True
157
+ self_link_view_name = None
158
+ related_link_view_name = None
159
+ related_link_lookup_field = 'pk'
160
+
161
+ default_error_messages = {
162
+ 'required' : _ ('This field is required.' ),
163
+ 'does_not_exist' : _ ('Invalid pk "{pk_value}" - object does not exist.' ),
164
+ 'incorrect_type' : _ (
165
+ 'Incorrect type. Expected resource identifier object, received {data_type}.'
166
+ ),
167
+ 'incorrect_relation_type' : _ (
168
+ 'Incorrect relation type. Expected {relation_type}, received {received_type}.'
169
+ ),
170
+ 'missing_type' : _ ('Invalid resource identifier object: missing \' type\' attribute' ),
171
+ 'missing_id' : _ ('Invalid resource identifier object: missing \' id\' attribute' ),
172
+ 'no_match' : _ ('Invalid hyperlink - No URL match.' ),
173
+ }
174
+
175
+ def __init__ (self , ** kwargs ):
176
+ # check for a model class that was passed in for the relation type
177
+ model = kwargs .pop ('model' , None )
178
+ if model :
179
+ self .model = model
180
+
181
+ super (ResourceRelatedField , self ).__init__ (** kwargs )
182
+
183
+ def use_pk_only_optimization (self ):
184
+ # We need the real object to determine its type...
185
+ return self .get_resource_type_from_included_serializer () is not None
186
+
187
+ def conflict (self , key , ** kwargs ):
188
+ """
189
+ A helper method that simply raises a validation error.
190
+ """
191
+ try :
192
+ msg = self .error_messages [key ]
193
+ except KeyError :
194
+ class_name = self .__class__ .__name__
195
+ msg = MISSING_ERROR_MESSAGE .format (class_name = class_name , key = key )
196
+ raise AssertionError (msg )
197
+ message_string = msg .format (** kwargs )
198
+ raise Conflict (message_string )
199
+
143
200
def to_internal_value (self , data ):
144
201
if isinstance (data , six .text_type ):
145
202
try :
@@ -323,3 +380,7 @@ def to_representation(self, value):
323
380
base = super (SerializerMethodResourceRelatedField , self )
324
381
return [base .to_representation (x ) for x in value ]
325
382
return super (SerializerMethodResourceRelatedField , self ).to_representation (value )
383
+
384
+
385
+ class SerializerMethodHyperLinkedRelatedField (SkipDataMixin , SerializerMethodResourceRelatedField ):
386
+ pass
0 commit comments