Skip to content

Commit bba4f6e

Browse files
author
Tim Csitkovics
committed
Do not lazy load forward relations
If the forward relations are included, the renderer will break, so this optimization only works if it is not included. fixup! Do not lazy load forward relations fixup! Do not lazy load forward relations fixup! Do not lazy load forward relations fixup! Do not lazy load forward relations fixup! Do not lazy load forward relations Include test case Small refactoring
1 parent 96c533b commit bba4f6e

File tree

2 files changed

+63
-27
lines changed

2 files changed

+63
-27
lines changed

example/tests/test_serializers.py

+35-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
from collections import namedtuple
2+
13
import pytest
24
from django.test import TestCase
35
from django.urls import reverse
46
from django.utils import timezone
57

6-
from rest_framework_json_api.serializers import ResourceIdentifierObjectSerializer
8+
from rest_framework_json_api.serializers import ResourceIdentifierObjectSerializer, ModelSerializer
79
from rest_framework_json_api.utils import format_resource_type
810

911
from example.models import Author, Blog, Entry
@@ -18,8 +20,8 @@ def setUp(self):
1820
blog=self.blog,
1921
headline='headline',
2022
body_text='body_text',
21-
pub_date=timezone.now(),
22-
mod_date=timezone.now(),
23+
pub_date=timezone.now().date(),
24+
mod_date=timezone.now().date(),
2325
n_comments=0,
2426
n_pingbacks=0,
2527
rating=3
@@ -30,6 +32,36 @@ def setUp(self):
3032
Author.objects.create(name=name, email='{}@example.org'.format(name))
3133
)
3234

35+
def test_forward_relationship_not_loaded_when_not_included(self):
36+
MockRequest = namedtuple('Request', ['query_params'])
37+
request_without_includes = MockRequest({})
38+
to_representation_was_called = False
39+
40+
class BlogSerializer(ModelSerializer):
41+
class Meta:
42+
model = Blog
43+
fields = '__all__'
44+
45+
def to_representation(self, instance):
46+
nonlocal to_representation_was_called
47+
to_representation_was_called = True
48+
return super().to_representation(instance)
49+
50+
class EntrySerializer(ModelSerializer):
51+
blog = BlogSerializer()
52+
53+
class Meta:
54+
model = Entry
55+
fields = '__all__'
56+
57+
included_serializers = {
58+
'blog': BlogSerializer,
59+
}
60+
61+
serializer = EntrySerializer(context={'request': request_without_includes})
62+
serializer.to_representation(self.entry)
63+
self.assertFalse(to_representation_was_called)
64+
3365
def test_data_in_correct_format_when_instantiated_with_blog_object(self):
3466
serializer = ResourceIdentifierObjectSerializer(instance=self.blog)
3567

rest_framework_json_api/serializers.py

+28-24
Original file line numberDiff line numberDiff line change
@@ -182,35 +182,39 @@ def to_representation(self, instance):
182182

183183
for field in readable_fields:
184184
try:
185-
186-
if isinstance(field, ModelSerializer) and hasattr(field, field.source + "_id"):
187-
attribute = getattr(instance, field.source + "_id")
188-
if attribute is None:
189-
ret[field.field_name] = None
190-
continue
191-
resource_type = get_resource_type_from_instance(field)
192-
if resource_type:
193-
ret[field.field_name] = OrderedDict([("type", resource_type),
194-
("id", attribute)])
195-
continue
196-
197-
attribute = field.get_attribute(instance)
185+
field_representation = self._get_field_representation(field, instance)
186+
ret[field.field_name] = field_representation
198187
except SkipField:
199188
continue
200189

201-
# We skip `to_representation` for `None` values so that fields do
202-
# not have to explicitly deal with that case.
203-
#
204-
# For related fields with `use_pk_only_optimization` we need to
205-
# resolve the pk value.
206-
check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
207-
if check_for_none is None:
208-
ret[field.field_name] = None
209-
else:
210-
ret[field.field_name] = field.to_representation(attribute)
211-
212190
return ret
213191

192+
def _get_field_representation(self, field, instance):
193+
request = self.context.get('request', None)
194+
is_included = field.source in get_included_resources(request)
195+
if not is_included and isinstance(field, ModelSerializer) and hasattr(instance, f'{field.source}_id'):
196+
attribute = getattr(instance, f'{field.source}_id')
197+
198+
if attribute is None:
199+
return None
200+
201+
resource_type = get_resource_type_from_serializer(field)
202+
if resource_type:
203+
return OrderedDict([('type', resource_type), ('id', attribute)])
204+
205+
attribute = field.get_attribute(instance)
206+
207+
# We skip `to_representation` for `None` values so that fields do
208+
# not have to explicitly deal with that case.
209+
#
210+
# For related fields with `use_pk_only_optimization` we need to
211+
# resolve the pk value.
212+
check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
213+
if check_for_none is None:
214+
return None
215+
else:
216+
return field.to_representation(attribute)
217+
214218

215219
class PolymorphicSerializerMetaclass(SerializerMetaclass):
216220
"""

0 commit comments

Comments
 (0)