Skip to content

Commit b884535

Browse files
Anton-Shutiksliverc
authored andcommitted
Use REST framework serializer functionality to extract includes (#632)
1 parent 01e92be commit b884535

File tree

3 files changed

+27
-25
lines changed

3 files changed

+27
-25
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ any parts of the framework not mentioned in the documentation should generally b
1818

1919
* Allow to define `select_related` per include using [select_for_includes](https://django-rest-framework-json-api.readthedocs.io/en/stable/usage.html#performance-improvements)
2020
* Reduce number of queries to calculate includes by using `select_related` when possible
21+
* Use REST framework serializer functionality to extract includes. This adds support like using
22+
dotted notations in source attribute in `ResourceRelatedField`.
2123

2224
### Fixed
2325

example/tests/unit/test_renderers.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010

1111
# serializers
1212
class RelatedModelSerializer(serializers.ModelSerializer):
13+
blog = serializers.ReadOnlyField(source='entry.blog')
14+
1315
class Meta:
1416
model = Comment
15-
fields = ('id',)
17+
fields = ('id', 'blog')
1618

1719

1820
class DummyTestSerializer(serializers.ModelSerializer):
@@ -137,3 +139,13 @@ class EmptyRelationshipViewSet(views.ReadOnlyModelViewSet):
137139
assert 'relationships' in result['data']
138140
assert 'bio' in result['data']['relationships']
139141
assert result['data']['relationships']['bio'] == {'data': None}
142+
143+
144+
@pytest.mark.django_db
145+
def test_extract_relation_instance(comment):
146+
serializer = RelatedModelSerializer(instance=comment)
147+
148+
got = JSONRenderer.extract_relation_instance(
149+
field=serializer.fields['blog'], resource_instance=comment
150+
)
151+
assert got == comment.entry.blog

rest_framework_json_api/renderers.py

+12-24
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from django.db.models import Manager
99
from django.utils import encoding, six
1010
from rest_framework import relations, renderers
11+
from rest_framework.fields import SkipField, get_attribute
12+
from rest_framework.relations import PKOnlyObject
1113
from rest_framework.serializers import BaseSerializer, ListSerializer, Serializer
1214
from rest_framework.settings import api_settings
1315

@@ -297,34 +299,20 @@ def extract_relationships(cls, fields, resource, resource_instance):
297299
return utils._format_object(data)
298300

299301
@classmethod
300-
def extract_relation_instance(cls, field_name, field, resource_instance, serializer):
302+
def extract_relation_instance(cls, field, resource_instance):
301303
"""
302304
Determines what instance represents given relation and extracts it.
303305
304-
Relation instance is determined by given field_name or source configured on
305-
field. As fallback is a serializer method called with name of field's source.
306+
Relation instance is determined exactly same way as it determined
307+
in parent serializer
306308
"""
307-
relation_instance = None
308-
309309
try:
310-
relation_instance = getattr(resource_instance, field_name)
311-
except AttributeError:
312-
try:
313-
# For ManyRelatedFields if `related_name` is not set
314-
# we need to access `foo_set` from `source`
315-
relation_instance = getattr(resource_instance, field.child_relation.source)
316-
except AttributeError:
317-
if hasattr(serializer, field.source):
318-
serializer_method = getattr(serializer, field.source)
319-
relation_instance = serializer_method(resource_instance)
320-
else:
321-
# case when source is a simple remap on resource_instance
322-
try:
323-
relation_instance = getattr(resource_instance, field.source)
324-
except AttributeError:
325-
pass
326-
327-
return relation_instance
310+
res = field.get_attribute(resource_instance)
311+
if isinstance(res, PKOnlyObject):
312+
return get_attribute(resource_instance, field.source_attrs)
313+
return res
314+
except SkipField:
315+
return None
328316

329317
@classmethod
330318
def extract_included(cls, fields, resource, resource_instance, included_resources,
@@ -363,7 +351,7 @@ def extract_included(cls, fields, resource, resource_instance, included_resource
363351
continue
364352

365353
relation_instance = cls.extract_relation_instance(
366-
field_name, field, resource_instance, current_serializer
354+
field, resource_instance
367355
)
368356
if isinstance(relation_instance, Manager):
369357
relation_instance = relation_instance.all()

0 commit comments

Comments
 (0)