Skip to content

Commit 9e395a2

Browse files
slivercmblayman
authored andcommitted
Added nested included serializer support for remapped relations (#347)
* Added nested included serializer support for remapped relations * Added docstring to extract_relation_instance
1 parent 6140662 commit 9e395a2

File tree

5 files changed

+66
-16
lines changed

5 files changed

+66
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ v2.3.0
77
* Fix for apps that don't use `django.contrib.contenttypes`.
88
* Fix `resource_name` support for POST requests and nested serializers
99
* Enforcing flake8 linting
10+
* Added nested included serializer support for remapped relations
1011

1112
v2.2.0
1213

example/serializers.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,25 @@ class Meta:
111111
fields = ('name', 'email', 'bio', 'entries')
112112

113113

114+
class WriterSerializer(serializers.ModelSerializer):
115+
included_serializers = {
116+
'bio': AuthorBioSerializer
117+
}
118+
119+
class Meta:
120+
model = Author
121+
fields = ('name', 'email', 'bio')
122+
resource_name = 'writers'
123+
124+
114125
class CommentSerializer(serializers.ModelSerializer):
126+
# testing remapping of related name
127+
writer = relations.ResourceRelatedField(source='author', read_only=True)
128+
115129
included_serializers = {
116130
'entry': EntrySerializer,
117-
'author': AuthorSerializer
131+
'author': AuthorSerializer,
132+
'writer': WriterSerializer
118133
}
119134

120135
class Meta:

example/tests/integration/test_includes.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,15 @@ def test_missing_field_not_included(author_bio_factory, author_factory, client):
8282

8383
def test_deep_included_data_on_list(multiple_entries, client):
8484
response = client.get(reverse("entry-list") + '?include=comments,comments.author,'
85-
'comments.author.bio&page_size=5')
85+
'comments.author.bio,comments.writer&page_size=5')
8686
included = load_json(response.content).get('included')
8787

8888
assert len(load_json(response.content)['data']) == len(multiple_entries), (
8989
'Incorrect entry count'
9090
)
9191
assert [x.get('type') for x in included] == [
92-
'authorBios', 'authorBios', 'authors', 'authors', 'comments', 'comments'
92+
'authorBios', 'authorBios', 'authors', 'authors',
93+
'comments', 'comments', 'writers', 'writers'
9394
], 'List included types are incorrect'
9495

9596
comment_count = len([resource for resource in included if resource["type"] == "comments"])
@@ -106,6 +107,13 @@ def test_deep_included_data_on_list(multiple_entries, client):
106107
author__bio__isnull=False).count() for entry in multiple_entries])
107108
assert author_bio_count == expected_author_bio_count, 'List author bio count is incorrect'
108109

110+
writer_count = len(
111+
[resource for resource in included if resource["type"] == "writers"]
112+
)
113+
expected_writer_count = sum(
114+
[entry.comments.filter(author__isnull=False).count() for entry in multiple_entries])
115+
assert writer_count == expected_writer_count, 'List writer count is incorrect'
116+
109117
# Also include entry authors
110118
response = client.get(reverse("entry-list") + '?include=authors,comments,comments.author,'
111119
'comments.author.bio&page_size=5')

example/tests/test_serializers.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ def test_model_serializer_with_implicit_fields(self, comment, client):
102102
"id": str(comment.author.pk)
103103
}
104104
},
105+
"writer": {
106+
"data": {
107+
"type": "writers",
108+
"id": str(comment.author.pk)
109+
}
110+
},
105111
}
106112
}
107113
}

rest_framework_json_api/renderers.py

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,36 @@ def extract_relationships(cls, fields, resource, resource_instance):
272272

273273
return utils.format_keys(data)
274274

275+
@classmethod
276+
def extract_relation_instance(cls, field_name, field, resource_instance, serializer):
277+
"""
278+
Determines what instance represents given relation and extracts it.
279+
280+
Relation instance is determined by given field_name or source configured on
281+
field. As fallback is a serializer method called with name of field's source.
282+
"""
283+
relation_instance = None
284+
285+
try:
286+
relation_instance = getattr(resource_instance, field_name)
287+
except AttributeError:
288+
try:
289+
# For ManyRelatedFields if `related_name` is not set
290+
# we need to access `foo_set` from `source`
291+
relation_instance = getattr(resource_instance, field.child_relation.source)
292+
except AttributeError:
293+
if hasattr(serializer, field.source):
294+
serializer_method = getattr(serializer, field.source)
295+
relation_instance = serializer_method(resource_instance)
296+
else:
297+
# case when source is a simple remap on resource_instance
298+
try:
299+
relation_instance = getattr(resource_instance, field.source)
300+
except AttributeError:
301+
pass
302+
303+
return relation_instance
304+
275305
@classmethod
276306
def extract_included(cls, fields, resource, resource_instance, included_resources):
277307
# this function may be called with an empty record (example: Browsable Interface)
@@ -304,19 +334,9 @@ def extract_included(cls, fields, resource, resource_instance, included_resource
304334
if field_name not in [node.split('.')[0] for node in included_resources]:
305335
continue
306336

307-
try:
308-
relation_instance = getattr(resource_instance, field_name)
309-
except AttributeError:
310-
try:
311-
# For ManyRelatedFields if `related_name` is not set we need to access `foo_set`
312-
# from `source`
313-
relation_instance = getattr(resource_instance, field.child_relation.source)
314-
except AttributeError:
315-
if not hasattr(current_serializer, field.source):
316-
continue
317-
serializer_method = getattr(current_serializer, field.source)
318-
relation_instance = serializer_method(resource_instance)
319-
337+
relation_instance = cls.extract_relation_instance(
338+
field_name, field, resource_instance, current_serializer
339+
)
320340
if isinstance(relation_instance, Manager):
321341
relation_instance = relation_instance.all()
322342

0 commit comments

Comments
 (0)