From 841ccc9ea1b775c04cf08a1643ff52cf88e47a4e Mon Sep 17 00:00:00 2001 From: Safa Alfulaij <safaalfulaij@hotmail.com> Date: Sun, 28 Mar 2021 08:20:28 +0300 Subject: [PATCH 1/6] Update views.py --- rest_framework_json_api/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework_json_api/views.py b/rest_framework_json_api/views.py index 84ec509e..c54ff40f 100644 --- a/rest_framework_json_api/views.py +++ b/rest_framework_json_api/views.py @@ -63,7 +63,7 @@ def get_prefetch_related(self, include): def get_queryset(self, *args, **kwargs): qs = super(PreloadIncludesMixin, self).get_queryset(*args, **kwargs) - included_resources = get_included_resources(self.request) + included_resources = get_included_resources(self.request, self.get_serializer_class()) for included in included_resources + ["__all__"]: select_related = self.get_select_related(included) @@ -82,7 +82,7 @@ def get_queryset(self, *args, **kwargs): """ This mixin adds automatic prefetching for OneToOne and ManyToMany fields. """ qs = super(AutoPrefetchMixin, self).get_queryset(*args, **kwargs) - included_resources = get_included_resources(self.request) + included_resources = get_included_resources(self.request, self.get_serializer_class()) for included in included_resources + ["__all__"]: # If include was not defined, trying to resolve it automatically From 1adaac1369415247188cd56237d71a8c5719d553 Mon Sep 17 00:00:00 2001 From: Safa Alfulaij <safaalfulaij@hotmail.com> Date: Sun, 28 Mar 2021 08:22:59 +0300 Subject: [PATCH 2/6] Update serializers.py --- rest_framework_json_api/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework_json_api/serializers.py b/rest_framework_json_api/serializers.py index a73a5d47..5ca773d0 100644 --- a/rest_framework_json_api/serializers.py +++ b/rest_framework_json_api/serializers.py @@ -137,7 +137,7 @@ def validate_path(serializer_class, field_path, path): validate_path(this_included_serializer, new_included_field_path, path) if request and view: - included_resources = get_included_resources(request) + included_resources = get_included_resources(request, self) for included_field_name in included_resources: included_field_path = included_field_name.split(".") if "related_field" in view.kwargs: From baa96444fc70ddf4e0ce63fe451cf9b9a3bc42e8 Mon Sep 17 00:00:00 2001 From: Safa AlFulaij <safa1996alfulaij@gmail.com> Date: Sat, 3 Apr 2021 08:46:30 +0300 Subject: [PATCH 3/6] Fix related serializer and add test --- example/tests/test_performance.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/example/tests/test_performance.py b/example/tests/test_performance.py index e42afada..fbef000c 100644 --- a/example/tests/test_performance.py +++ b/example/tests/test_performance.py @@ -1,7 +1,7 @@ from django.utils import timezone from rest_framework.test import APITestCase -from example.factories import CommentFactory +from example.factories import CommentFactory, EntryFactory from example.models import Author, Blog, Comment, Entry @@ -36,6 +36,7 @@ def setUp(self): ) self.comment = Comment.objects.create(entry=self.first_entry) CommentFactory.create_batch(50) + EntryFactory.create_batch(50) def test_query_count_no_includes(self): """We expect a simple list view to issue only two queries. @@ -49,7 +50,7 @@ def test_query_count_no_includes(self): self.assertEqual(len(response.data["results"]), 25) def test_query_count_include_author(self): - """We expect a list view with an include have three queries: + """We expect a list view with an include have five queries: 1. Primary resource COUNT query 2. Primary resource SELECT @@ -70,3 +71,14 @@ def test_query_select_related_entry(self): with self.assertNumQueries(2): response = self.client.get("/comments?include=writer&page[size]=25") self.assertEqual(len(response.data["results"]), 25) + + def test_query_prefetch_related_resources(self): + """We expect a list view with related_resources have three queries: + + 1. Primary resource COUNT query + 2. Primary resource SELECT + 3. Comments prefetched + """ + with self.assertNumQueries(3): + response = self.client.get("/entries?fields[entry]=comments&page[size]=25") + self.assertEqual(len(response.data["results"]), 25) From b7ea55390341e0a52021d639bbc6501d060486e0 Mon Sep 17 00:00:00 2001 From: Safa AlFulaij <safa1996alfulaij@gmail.com> Date: Sat, 3 Apr 2021 20:57:24 +0300 Subject: [PATCH 4/6] Fix resource type and format code --- example/tests/test_performance.py | 4 +++- rest_framework_json_api/views.py | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/example/tests/test_performance.py b/example/tests/test_performance.py index fbef000c..a04a6287 100644 --- a/example/tests/test_performance.py +++ b/example/tests/test_performance.py @@ -80,5 +80,7 @@ def test_query_prefetch_related_resources(self): 3. Comments prefetched """ with self.assertNumQueries(3): - response = self.client.get("/entries?fields[entry]=comments&page[size]=25") + response = self.client.get( + "/entries?fields[entries]=comments&page[size]=25" + ) self.assertEqual(len(response.data["results"]), 25) diff --git a/rest_framework_json_api/views.py b/rest_framework_json_api/views.py index c54ff40f..8369cec9 100644 --- a/rest_framework_json_api/views.py +++ b/rest_framework_json_api/views.py @@ -63,7 +63,9 @@ def get_prefetch_related(self, include): def get_queryset(self, *args, **kwargs): qs = super(PreloadIncludesMixin, self).get_queryset(*args, **kwargs) - included_resources = get_included_resources(self.request, self.get_serializer_class()) + included_resources = get_included_resources( + self.request, self.get_serializer_class() + ) for included in included_resources + ["__all__"]: select_related = self.get_select_related(included) @@ -82,7 +84,9 @@ def get_queryset(self, *args, **kwargs): """ This mixin adds automatic prefetching for OneToOne and ManyToMany fields. """ qs = super(AutoPrefetchMixin, self).get_queryset(*args, **kwargs) - included_resources = get_included_resources(self.request, self.get_serializer_class()) + included_resources = get_included_resources( + self.request, self.get_serializer_class() + ) for included in included_resources + ["__all__"]: # If include was not defined, trying to resolve it automatically From d7981712ac872afb96560f7bda18dd9d32afb78d Mon Sep 17 00:00:00 2001 From: Oliver Sauder <os@esite.ch> Date: Sun, 2 May 2021 22:50:02 +0400 Subject: [PATCH 5/6] Add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32799366..c62b5383 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ any parts of the framework not mentioned in the documentation should generally b OrderViewSet.as_view({'get': 'retrieve_related'}), name='order-related'), ``` +* Ensure default `included_resources` are considered when calculating prefetches. ### Deprecated From 74d2a17e8800bfcd87b8169885f63eaf74eeb34e Mon Sep 17 00:00:00 2001 From: Oliver Sauder <os@esite.ch> Date: Sun, 2 May 2021 22:50:37 +0400 Subject: [PATCH 6/6] Rename test for clarity --- example/tests/test_performance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/tests/test_performance.py b/example/tests/test_performance.py index a04a6287..cae11ed4 100644 --- a/example/tests/test_performance.py +++ b/example/tests/test_performance.py @@ -72,8 +72,8 @@ def test_query_select_related_entry(self): response = self.client.get("/comments?include=writer&page[size]=25") self.assertEqual(len(response.data["results"]), 25) - def test_query_prefetch_related_resources(self): - """We expect a list view with related_resources have three queries: + def test_query_prefetch_uses_included_resources(self): + """We expect a list view with `included_resources` to have three queries: 1. Primary resource COUNT query 2. Primary resource SELECT