Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 91bb86d

Browse files
author
Tim Csitkovics
committedAug 31, 2018
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 Make compatible with Python 2.X fixup! Make compatible with Python 2.X Fix travis Use isort Implement suggested changes Make import backward compatible Make travis happy
1 parent a12de94 commit 91bb86d

File tree

4 files changed

+105
-28
lines changed

4 files changed

+105
-28
lines changed
 

‎AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ Oliver Sauder <os@esite.ch>
1515
Raphael Cohen <raphael.cohen.utt@gmail.com>
1616
Roberto Barreda <roberto.barreda@gmail.com>
1717
santiavenda <santiavenda2@gmail.com>
18+
Tim Selman <timcbaoth@gmail.com>
1819
Yaniv Peer <yanivpeer@gmail.com>

‎CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
* Add optional [jsonapi-style](http://jsonapi.org/format/) sort filter backend. See [usage docs](docs/usage.md#filter-backends)
88
* For naming consistency, renamed new `JsonApi`-prefix pagination classes to `JSONAPI`-prefix.
99
* Deprecates `JsonApiPageNumberPagination` and `JsonApiLimitOffsetPagination`
10+
* Fix for performance improvement for related `ModelSerializer`
11+
1012

1113
v2.5.0 - Released July 11, 2018
1214

‎example/tests/test_serializers.py

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,39 @@
22
from django.test import TestCase
33
from django.urls import reverse
44
from django.utils import timezone
5-
6-
from rest_framework_json_api.serializers import ResourceIdentifierObjectSerializer
5+
from rest_framework.request import Request
6+
from rest_framework.test import APIRequestFactory
7+
8+
from rest_framework_json_api.serializers import (
9+
DateField,
10+
ModelSerializer,
11+
ResourceIdentifierObjectSerializer
12+
)
713
from rest_framework_json_api.utils import format_resource_type
814

915
from example.models import Author, Blog, Entry
16+
from example.serializers import BlogSerializer
17+
18+
try:
19+
from unittest import mock
20+
except ImportError:
21+
import mock
1022

23+
request_factory = APIRequestFactory()
1124
pytestmark = pytest.mark.django_db
1225

1326

1427
class TestResourceIdentifierObjectSerializer(TestCase):
1528
def setUp(self):
1629
self.blog = Blog.objects.create(name='Some Blog', tagline="It's a blog")
30+
now = timezone.now()
31+
1732
self.entry = Entry.objects.create(
1833
blog=self.blog,
1934
headline='headline',
2035
body_text='body_text',
21-
pub_date=timezone.now(),
22-
mod_date=timezone.now(),
36+
pub_date=now.date(),
37+
mod_date=now.date(),
2338
n_comments=0,
2439
n_pingbacks=0,
2540
rating=3
@@ -30,6 +45,59 @@ def setUp(self):
3045
Author.objects.create(name=name, email='{}@example.org'.format(name))
3146
)
3247

48+
def test_forward_relationship_not_loaded_when_not_included(self):
49+
to_representation_method = 'example.serializers.BlogSerializer.to_representation'
50+
with mock.patch(to_representation_method) as mocked_serializer:
51+
class EntrySerializer(ModelSerializer):
52+
blog = BlogSerializer()
53+
54+
class Meta:
55+
model = Entry
56+
fields = '__all__'
57+
58+
request_without_includes = Request(request_factory.get('/'))
59+
serializer = EntrySerializer(context={'request': request_without_includes})
60+
serializer.to_representation(self.entry)
61+
62+
mocked_serializer.assert_not_called()
63+
64+
def test_forward_relationship_optimization_correct_representation(self):
65+
class EntrySerializer(ModelSerializer):
66+
blog = BlogSerializer()
67+
68+
class Meta:
69+
model = Entry
70+
fields = '__all__'
71+
72+
request_without_includes = Request(request_factory.get('/'))
73+
serializer = EntrySerializer(context={'request': request_without_includes})
74+
result = serializer.to_representation(self.entry)
75+
76+
# Remove non deterministic fields
77+
result.pop('created_at')
78+
result.pop('modified_at')
79+
80+
expected = dict(
81+
[
82+
('id', 1),
83+
('blog', dict([('type', 'blogs'), ('id', 1)])),
84+
('headline', 'headline'),
85+
('body_text', 'body_text'),
86+
('pub_date', DateField().to_representation(self.entry.pub_date)),
87+
('mod_date', DateField().to_representation(self.entry.mod_date)),
88+
('n_comments', 0),
89+
('n_pingbacks', 0),
90+
('rating', 3),
91+
('authors',
92+
[
93+
dict([('type', 'authors'), ('id', '1')]),
94+
dict([('type', 'authors'), ('id', '2')]),
95+
dict([('type', 'authors'), ('id', '3')]),
96+
dict([('type', 'authors'), ('id', '4')]),
97+
dict([('type', 'authors'), ('id', '5')])])])
98+
99+
self.assertDictEqual(expected, result)
100+
33101
def test_data_in_correct_format_when_instantiated_with_blog_object(self):
34102
serializer = ResourceIdentifierObjectSerializer(instance=self.blog)
35103

‎rest_framework_json_api/serializers.py

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -182,35 +182,41 @@ 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')
194+
is_included = field.source in get_included_resources(request)
195+
if not is_included and \
196+
isinstance(field, ModelSerializer) and \
197+
hasattr(instance, field.source + '_id'):
198+
attribute = getattr(instance, field.source + '_id')
199+
200+
if attribute is None:
201+
return None
202+
203+
resource_type = get_resource_type_from_serializer(field)
204+
if resource_type:
205+
return OrderedDict([('type', resource_type), ('id', attribute)])
206+
207+
attribute = field.get_attribute(instance)
208+
209+
# We skip `to_representation` for `None` values so that fields do
210+
# not have to explicitly deal with that case.
211+
#
212+
# For related fields with `use_pk_only_optimization` we need to
213+
# resolve the pk value.
214+
check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
215+
if check_for_none is None:
216+
return None
217+
else:
218+
return field.to_representation(attribute)
219+
214220

215221
class PolymorphicSerializerMetaclass(SerializerMetaclass):
216222
"""

0 commit comments

Comments
 (0)
Please sign in to comment.