1
1
from rest_framework .exceptions import ValidationError
2
+ from rest_framework .fields import MISSING_ERROR_MESSAGE
2
3
from rest_framework .relations import *
3
- from rest_framework_json_api .utils import format_relation_name , get_related_resource_type , \
4
- get_resource_type_from_queryset , get_resource_type_from_instance
5
4
from django .utils .translation import ugettext_lazy as _
6
5
6
+ from rest_framework_json_api .exceptions import Conflict
7
+ from rest_framework_json_api .utils import format_relation_name , Hyperlink , \
8
+ get_resource_type_from_queryset , get_resource_type_from_instance
9
+
7
10
8
11
class HyperlinkedRelatedField (HyperlinkedRelatedField ):
9
12
"""
@@ -40,23 +43,26 @@ def to_internal_value(self, data):
40
43
41
44
42
45
class ResourceRelatedField (PrimaryKeyRelatedField ):
43
- lookup_field = 'pk'
44
- view_name = None
45
-
46
-
47
-
46
+ self_link_view_name = None
47
+ related_link_view_name = None
48
+ related_link_lookup_field = 'pk'
48
49
49
50
default_error_messages = {
50
51
'required' : _ ('This field is required.' ),
51
52
'does_not_exist' : _ ('Invalid pk "{pk_value}" - object does not exist.' ),
52
- 'incorrect_type' : _ ('Incorrect type. Expected pk value , received {data_type}.' ),
53
+ 'incorrect_type' : _ ('Incorrect type. Expected resource identifier object , received {data_type}.' ),
53
54
'incorrect_relation_type' : _ ('Incorrect relation type. Expected {relation_type}, received {received_type}.' ),
54
55
'no_match' : _ ('Invalid hyperlink - No URL match.' ),
55
56
}
56
57
57
- def __init__ (self , view_name = None , ** kwargs ):
58
- self .lookup_field = kwargs .pop ('lookup_field' , self .lookup_field )
59
- self .lookup_url_kwarg = kwargs .pop ('lookup_url_kwarg' , self .lookup_field )
58
+ def __init__ (self , self_link_view_name = None , related_link_view_name = None , ** kwargs ):
59
+ if self_link_view_name is not None :
60
+ self .self_link_view_name = self_link_view_name
61
+ if related_link_view_name is not None :
62
+ self .related_link_view_name = related_link_view_name
63
+
64
+ self .related_link_lookup_field = kwargs .pop ('related_link_lookup_field' , self .related_link_lookup_field )
65
+ self .related_link_url_kwarg = kwargs .pop ('related_link_url_kwarg' , self .related_link_lookup_field )
60
66
61
67
# We include this simply for dependency injection in tests.
62
68
# We can't add it as a class attributes or it would expect an
@@ -65,30 +71,80 @@ def __init__(self, view_name=None, **kwargs):
65
71
66
72
super (ResourceRelatedField , self ).__init__ (** kwargs )
67
73
68
- def get_url (self , obj , view_name , request ):
74
+ def use_pk_only_optimization (self ):
75
+ # We need the real object to determine its type...
76
+ return False
77
+
78
+ def conflict (self , key , ** kwargs ):
69
79
"""
70
- Given an object, return the URL that hyperlinks to the object.
80
+ A helper method that simply raises a validation error.
81
+ """
82
+ try :
83
+ msg = self .error_messages [key ]
84
+ except KeyError :
85
+ class_name = self .__class__ .__name__
86
+ msg = MISSING_ERROR_MESSAGE .format (class_name = class_name , key = key )
87
+ raise AssertionError (msg )
88
+ message_string = msg .format (** kwargs )
89
+ raise Conflict (message_string )
90
+
91
+ def get_url (self , name , view_name , kwargs , request ):
92
+ """
93
+ Given a name, view name and kwargs, return the URL that hyperlinks to the object.
71
94
72
95
May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
73
96
attributes are not configured to correctly match the URL conf.
74
97
"""
75
- # Unsaved objects will not yet have a valid URL.
76
- if hasattr (obj , 'pk' ) and obj .pk is None :
98
+
99
+ # Return None if the view name is not supplied
100
+ if not view_name :
101
+ return None
102
+
103
+ # Return the hyperlink, or error if incorrectly configured.
104
+ try :
105
+ url = self .reverse (view_name , kwargs = kwargs , request = request )
106
+ except NoReverseMatch :
107
+ msg = (
108
+ 'Could not resolve URL for hyperlinked relationship using '
109
+ 'view name "%s".'
110
+ )
111
+ raise ImproperlyConfigured (msg % view_name )
112
+
113
+ if url is None :
77
114
return None
78
115
79
- lookup_value = getattr (obj , self .lookup_field )
80
- kwargs = {self .lookup_url_kwarg : lookup_value }
81
- return self .reverse (view_name , kwargs = kwargs , request = request )
116
+ return Hyperlink (url , name )
117
+
118
+ def get_links (self ):
119
+ request = self .context .get ('request' , None )
120
+ view = self .context .get ('view' , None )
121
+ return_data = OrderedDict ()
122
+ self_kwargs = view .kwargs .copy ()
123
+ self_kwargs .update ({'related_field' : self .field_name if self .field_name else self .parent .field_name })
124
+ self_link = self .get_url ('self' , self .self_link_view_name , self_kwargs , request )
125
+
126
+ related_kwargs = {self .related_link_url_kwarg : view .kwargs [self .related_link_lookup_field ]}
127
+ related_link = self .get_url ('related' , self .related_link_view_name , related_kwargs , request )
128
+
129
+ if self_link :
130
+ return_data .update ({'self' : self_link })
131
+ if related_link :
132
+ return_data .update ({'related' : related_link })
133
+ return return_data
82
134
83
135
def to_internal_value (self , data ):
84
136
expected_relation_type = get_resource_type_from_queryset (self .queryset )
137
+ if not isinstance (data , dict ):
138
+ self .fail ('incorrect_type' , data_type = type (data ).__name__ )
85
139
if data ['type' ] != expected_relation_type :
86
- self .fail ('incorrect_relation_type' , relation_type = expected_relation_type , received_type = data ['type' ])
140
+ self .conflict ('incorrect_relation_type' , relation_type = expected_relation_type , received_type = data ['type' ])
87
141
return super (ResourceRelatedField , self ).to_internal_value (data ['id' ])
88
142
89
143
def to_representation (self , value ):
90
- return {
91
- 'type' : format_relation_name (get_resource_type_from_instance (value )),
92
- 'id' : str (value .pk )
93
- }
144
+ if self .pk_field is not None :
145
+ pk = self .pk_field .to_representation (value .pk )
146
+ else :
147
+ pk = value .pk
148
+
149
+ return OrderedDict ([('type' , format_relation_name (get_resource_type_from_instance (value ))), ('id' , str (pk ))])
94
150
0 commit comments