Skip to content

Commit fddb06b

Browse files
committed
Basic support of write operations on polymorphic relations
1 parent 5c63425 commit fddb06b

File tree

3 files changed

+63
-2
lines changed

3 files changed

+63
-2
lines changed

example/serializers.py

+3
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ class Meta:
115115

116116

117117
class CompanySerializer(serializers.ModelSerializer):
118+
current_project = relations.PolymorphicResourceRelatedField(ProjectSerializer, queryset=models.Project.objects.all())
119+
future_projects = relations.PolymorphicResourceRelatedField(ProjectSerializer, queryset=models.Project.objects.all(), many=True)
120+
118121
included_serializers = {
119122
'current_project': ProjectSerializer,
120123
'future_projects': ProjectSerializer,

example/tests/integration/test_polymorphism.py

+18
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,21 @@ def test_polymorphism_on_polymorphic_model_list_post(client):
7171
assert content["data"]["type"] == "artProjects"
7272
assert content['data']['attributes']['topic'] == test_topic
7373
assert content['data']['attributes']['artist'] == test_artist
74+
75+
76+
def test_polymorphism_relations_update(single_company, research_project_factory, client):
77+
response = client.get(reverse("company-detail", kwargs={'pk': single_company.pk}))
78+
content = load_json(response.content)
79+
assert content["data"]["relationships"]["currentProject"]["data"]["type"] == "artProjects"
80+
81+
research_project = research_project_factory()
82+
content["data"]["relationships"]["currentProject"]["data"] = {
83+
"type": "researchProjects",
84+
"id": research_project.pk
85+
}
86+
response = client.put(reverse("company-detail", kwargs={'pk': single_company.pk}),
87+
data=json.dumps(content), content_type='application/vnd.api+json')
88+
assert response.status_code is 200
89+
content = load_json(response.content)
90+
assert content["data"]["relationships"]["currentProject"]["data"]["type"] == "researchProjects"
91+
assert int(content["data"]["relationships"]["currentProject"]["data"]["id"]) is research_project.pk

rest_framework_json_api/relations.py

+42-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313

1414
class ResourceRelatedField(PrimaryKeyRelatedField):
15+
_skip_polymorphic_optimization = True
1516
self_link_view_name = None
1617
related_link_view_name = None
1718
related_link_lookup_field = 'pk'
@@ -21,6 +22,7 @@ class ResourceRelatedField(PrimaryKeyRelatedField):
2122
'does_not_exist': _('Invalid pk "{pk_value}" - object does not exist.'),
2223
'incorrect_type': _('Incorrect type. Expected resource identifier object, received {data_type}.'),
2324
'incorrect_relation_type': _('Incorrect relation type. Expected {relation_type}, received {received_type}.'),
25+
# 'incorrect_poly_relation_type': _('Incorrect relation type. Expected one of {relation_type}, received {received_type}.'),
2426
'missing_type': _('Invalid resource identifier object: missing \'type\' attribute'),
2527
'missing_id': _('Invalid resource identifier object: missing \'id\' attribute'),
2628
'no_match': _('Invalid hyperlink - No URL match.'),
@@ -135,7 +137,8 @@ def to_internal_value(self, data):
135137
self.fail('missing_id')
136138

137139
if data['type'] != expected_relation_type:
138-
self.conflict('incorrect_relation_type', relation_type=expected_relation_type, received_type=data['type'])
140+
self.conflict('incorrect_relation_type', relation_type=expected_relation_type,
141+
received_type=data['type'])
139142

140143
return super(ResourceRelatedField, self).to_internal_value(data['id'])
141144

@@ -150,7 +153,8 @@ def to_representation(self, value):
150153
resource_type = None
151154
root = getattr(self.parent, 'parent', self.parent)
152155
field_name = self.field_name if self.field_name else self.parent.field_name
153-
if getattr(root, 'included_serializers', None) is not None and not self.is_polymorphic:
156+
if getattr(root, 'included_serializers', None) is not None and \
157+
self._skip_polymorphic_optimization:
154158
includes = get_included_serializers(root)
155159
if field_name in includes.keys():
156160
resource_type = get_resource_type_from_serializer(includes[field_name])
@@ -177,6 +181,42 @@ def get_choices(self, cutoff=None):
177181
])
178182

179183

184+
class PolymorphicResourceRelatedField(ResourceRelatedField):
185+
186+
_skip_polymorphic_optimization = False
187+
default_error_messages = dict(ResourceRelatedField.default_error_messages, **{
188+
'incorrect_relation_type': _('Incorrect relation type. Expected one of {relation_type}, '
189+
'received {received_type}.'),
190+
})
191+
192+
def __init__(self, polymorphic_serializer, *args, **kwargs):
193+
self.polymorphic_serializer = polymorphic_serializer
194+
super(PolymorphicResourceRelatedField, self).__init__(*args, **kwargs)
195+
196+
def to_internal_value(self, data):
197+
if isinstance(data, six.text_type):
198+
try:
199+
data = json.loads(data)
200+
except ValueError:
201+
# show a useful error if they send a `pk` instead of resource object
202+
self.fail('incorrect_type', data_type=type(data).__name__)
203+
if not isinstance(data, dict):
204+
self.fail('incorrect_type', data_type=type(data).__name__)
205+
206+
if 'type' not in data:
207+
self.fail('missing_type')
208+
209+
if 'id' not in data:
210+
self.fail('missing_id')
211+
212+
expected_relation_types = get_resource_type_from_serializer(self.polymorphic_serializer)
213+
214+
if data['type'] not in expected_relation_types:
215+
self.conflict('incorrect_relation_type', relation_type=", ".join(
216+
expected_relation_types), received_type=data['type'])
217+
218+
return super(ResourceRelatedField, self).to_internal_value(data['id'])
219+
180220

181221
class SerializerMethodResourceRelatedField(ResourceRelatedField):
182222
"""

0 commit comments

Comments
 (0)