From 84999ff17b7c4fb6615eb272914d8013ca2e8a1e Mon Sep 17 00:00:00 2001 From: Anton-Shutik Date: Mon, 18 Mar 2019 15:57:49 +0300 Subject: [PATCH 01/11] Added SelectAndPrefetchForIncludesMixin, deprecated PrefetchForIncludesHelperMixin --- rest_framework_json_api/views.py | 54 ++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/rest_framework_json_api/views.py b/rest_framework_json_api/views.py index 4b4c6ce3..0b6a3570 100644 --- a/rest_framework_json_api/views.py +++ b/rest_framework_json_api/views.py @@ -1,3 +1,4 @@ +import warnings from django.core.exceptions import ImproperlyConfigured from django.db.models import Model @@ -31,6 +32,13 @@ class PrefetchForIncludesHelperMixin(object): + + def __init__(self, *args, **kwargs): + warnings.warn("PrefetchForIncludesHelperMixin is deprecated. " + "Use SelectAndPrefetchForIncludesMixin instead", + DeprecationWarning) + super(PrefetchForIncludesHelperMixin, self).__init__(*args, **kwargs) + def get_queryset(self): """ This viewset provides a helper attribute to prefetch related models @@ -62,10 +70,52 @@ class MyViewSet(viewsets.ModelViewSet): return qs +class SelectAndPrefetchForIncludesMixin(object): + """ + This mixin provides a helper attributes to select or prefetch related models + based on the include specified in the URL. + + __all__ can be used to specify a prefetch which should be done regardless of the include + + .. code:: python + + # When MyViewSet is called with ?include=author it will prefetch author and authorbio + class MyViewSet(viewsets.ModelViewSet): + queryset = Book.objects.all() + prefetch_for_includes = { + '__all__': [], + 'category.section': ['category'] + } + select_for_includes = { + '__all__': [], + 'author': ['author', 'author__authorbio'], + } + """ + def get_queryset(self): + qs = super(SelectAndPrefetchForIncludesMixin, self).get_queryset() + + includes = self.request.GET.get('include', '').split(',') + ['__all__'] + + if hasattr(self, 'select_for_includes'): + selects = [self.select_for_includes.get(inc) for inc in includes] + qs = qs.select_related(*selects) + + if hasattr(self, 'prefetch_for_includes'): + prefetches = [self.prefetch_for_includes.get(inc) for inc in includes] + qs = qs.prefetch_related(*prefetches) + + return qs + + class AutoPrefetchMixin(object): def get_queryset(self, *args, **kwargs): """ This mixin adds automatic prefetching for OneToOne and ManyToMany fields. """ qs = super(AutoPrefetchMixin, self).get_queryset(*args, **kwargs) + + # Prefetch includes handled by another mixin, let's do not mix them + if hasattr(self, 'prefetch_for_includes'): + return qs + included_resources = get_included_resources(self.request) for included in included_resources: @@ -187,14 +237,14 @@ def get_related_instance(self): class ModelViewSet(AutoPrefetchMixin, - PrefetchForIncludesHelperMixin, + SelectAndPrefetchForIncludesMixin, RelatedMixin, viewsets.ModelViewSet): pass class ReadOnlyModelViewSet(AutoPrefetchMixin, - PrefetchForIncludesHelperMixin, + SelectAndPrefetchForIncludesMixin, RelatedMixin, viewsets.ReadOnlyModelViewSet): pass From d2d0b94f1850b26d96d231934100b8bf1e6ca599 Mon Sep 17 00:00:00 2001 From: Anton-Shutik Date: Mon, 25 Mar 2019 14:02:14 +0300 Subject: [PATCH 02/11] Renamed SelectAndPrefetchForIncludesMixin with PreloadIncludesMixin --- rest_framework_json_api/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rest_framework_json_api/views.py b/rest_framework_json_api/views.py index 0b6a3570..a87e414b 100644 --- a/rest_framework_json_api/views.py +++ b/rest_framework_json_api/views.py @@ -70,7 +70,7 @@ class MyViewSet(viewsets.ModelViewSet): return qs -class SelectAndPrefetchForIncludesMixin(object): +class PreloadIncludesMixin(object): """ This mixin provides a helper attributes to select or prefetch related models based on the include specified in the URL. @@ -92,7 +92,7 @@ class MyViewSet(viewsets.ModelViewSet): } """ def get_queryset(self): - qs = super(SelectAndPrefetchForIncludesMixin, self).get_queryset() + qs = super(PreloadIncludesMixin, self).get_queryset() includes = self.request.GET.get('include', '').split(',') + ['__all__'] @@ -237,14 +237,14 @@ def get_related_instance(self): class ModelViewSet(AutoPrefetchMixin, - SelectAndPrefetchForIncludesMixin, + PreloadIncludesMixin, RelatedMixin, viewsets.ModelViewSet): pass class ReadOnlyModelViewSet(AutoPrefetchMixin, - SelectAndPrefetchForIncludesMixin, + PreloadIncludesMixin, RelatedMixin, viewsets.ReadOnlyModelViewSet): pass From 9ae38485ebbf3677a2fff6b5a5eaa1ad01606ef4 Mon Sep 17 00:00:00 2001 From: Anton-Shutik Date: Mon, 25 Mar 2019 14:24:45 +0300 Subject: [PATCH 03/11] Removed check for "prefetch_for_includes" --- rest_framework_json_api/views.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/rest_framework_json_api/views.py b/rest_framework_json_api/views.py index a87e414b..5cd2e089 100644 --- a/rest_framework_json_api/views.py +++ b/rest_framework_json_api/views.py @@ -111,11 +111,6 @@ class AutoPrefetchMixin(object): def get_queryset(self, *args, **kwargs): """ This mixin adds automatic prefetching for OneToOne and ManyToMany fields. """ qs = super(AutoPrefetchMixin, self).get_queryset(*args, **kwargs) - - # Prefetch includes handled by another mixin, let's do not mix them - if hasattr(self, 'prefetch_for_includes'): - return qs - included_resources = get_included_resources(self.request) for included in included_resources: From 0ac3ef9aa5f9e05de6bcc593bb94c9c6d749e920 Mon Sep 17 00:00:00 2001 From: Anton-Shutik Date: Tue, 26 Mar 2019 19:07:52 +0300 Subject: [PATCH 04/11] Renamed mixins --- rest_framework_json_api/views.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/rest_framework_json_api/views.py b/rest_framework_json_api/views.py index 5cd2e089..1a997aae 100644 --- a/rest_framework_json_api/views.py +++ b/rest_framework_json_api/views.py @@ -35,7 +35,7 @@ class PrefetchForIncludesHelperMixin(object): def __init__(self, *args, **kwargs): warnings.warn("PrefetchForIncludesHelperMixin is deprecated. " - "Use SelectAndPrefetchForIncludesMixin instead", + "Use PreloadIncludesMixin instead", DeprecationWarning) super(PrefetchForIncludesHelperMixin, self).__init__(*args, **kwargs) @@ -107,10 +107,10 @@ def get_queryset(self): return qs -class AutoPrefetchMixin(object): +class AutoPreloadMixin(object): def get_queryset(self, *args, **kwargs): """ This mixin adds automatic prefetching for OneToOne and ManyToMany fields. """ - qs = super(AutoPrefetchMixin, self).get_queryset(*args, **kwargs) + qs = super(AutoPreloadMixin, self).get_queryset(*args, **kwargs) included_resources = get_included_resources(self.request) for included in included_resources: @@ -154,6 +154,15 @@ def get_queryset(self, *args, **kwargs): return qs +class AutoPrefetchMixin(AutoPreloadMixin): + + def __init__(self, *args, **kwargs): + warnings.warn("AutoPrefetchMixin is deprecated. " + "Use AutoPreloadMixin instead", + DeprecationWarning) + super(AutoPrefetchMixin, self).__init__(*args, **kwargs) + + class RelatedMixin(object): """ This mixin handles all related entities, whose Serializers are declared in "related_serializers" @@ -231,14 +240,14 @@ def get_related_instance(self): raise NotFound -class ModelViewSet(AutoPrefetchMixin, +class ModelViewSet(AutoPreloadMixin, PreloadIncludesMixin, RelatedMixin, viewsets.ModelViewSet): pass -class ReadOnlyModelViewSet(AutoPrefetchMixin, +class ReadOnlyModelViewSet(AutoPreloadMixin, PreloadIncludesMixin, RelatedMixin, viewsets.ReadOnlyModelViewSet): From 580e9a9b8d860f76b08b7b1828f65bcc9c2a79d2 Mon Sep 17 00:00:00 2001 From: Anton-Shutik Date: Fri, 17 May 2019 11:30:48 +0300 Subject: [PATCH 05/11] Merged PreloadIncludesMixin into AutoPreloadMixin, test --- example/tests/test_performance.py | 10 ++++++++ example/views.py | 3 +++ rest_framework_json_api/views.py | 38 +++++++++++++++---------------- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/example/tests/test_performance.py b/example/tests/test_performance.py index 7da69bc9..50903498 100644 --- a/example/tests/test_performance.py +++ b/example/tests/test_performance.py @@ -56,3 +56,13 @@ def test_query_count_include_author(self): with self.assertNumQueries(5): response = self.client.get('/comments?include=author&page[size]=25') self.assertEqual(len(response.data['results']), 25) + + def test_query_select_related_entry(self): + """ We expect a list view with an include have three queries: + + 1. Primary resource COUNT query + 2. Primary resource SELECT + SELECT RELATED writer(author) and bio + """ + with self.assertNumQueries(2): + response = self.client.get('/comments?include=writer&page[size]=25') + self.assertEqual(len(response.data['results']), 25) diff --git a/example/views.py b/example/views.py index de6579bd..4d363032 100644 --- a/example/views.py +++ b/example/views.py @@ -184,6 +184,9 @@ class AuthorViewSet(ModelViewSet): class CommentViewSet(ModelViewSet): queryset = Comment.objects.all() serializer_class = CommentSerializer + select_for_includes = { + 'writer': ['author__bio'] + } prefetch_for_includes = { '__all__': [], 'author': ['author__bio', 'author__entries'], diff --git a/rest_framework_json_api/views.py b/rest_framework_json_api/views.py index 1a997aae..3f1a4acf 100644 --- a/rest_framework_json_api/views.py +++ b/rest_framework_json_api/views.py @@ -35,7 +35,7 @@ class PrefetchForIncludesHelperMixin(object): def __init__(self, *args, **kwargs): warnings.warn("PrefetchForIncludesHelperMixin is deprecated. " - "Use PreloadIncludesMixin instead", + "Use AutoPreloadMixin instead", DeprecationWarning) super(PrefetchForIncludesHelperMixin, self).__init__(*args, **kwargs) @@ -70,7 +70,7 @@ class MyViewSet(viewsets.ModelViewSet): return qs -class PreloadIncludesMixin(object): +class AutoPreloadMixin(object): """ This mixin provides a helper attributes to select or prefetch related models based on the include specified in the URL. @@ -91,29 +91,31 @@ class MyViewSet(viewsets.ModelViewSet): 'author': ['author', 'author__authorbio'], } """ - def get_queryset(self): - qs = super(PreloadIncludesMixin, self).get_queryset() - - includes = self.request.GET.get('include', '').split(',') + ['__all__'] - - if hasattr(self, 'select_for_includes'): - selects = [self.select_for_includes.get(inc) for inc in includes] - qs = qs.select_related(*selects) - if hasattr(self, 'prefetch_for_includes'): - prefetches = [self.prefetch_for_includes.get(inc) for inc in includes] - qs = qs.prefetch_related(*prefetches) - - return qs + def get_select_related(self, include): + return getattr(self, 'select_for_includes', {}).get(include, None) + def get_prefetch_related(self, include): + return getattr(self, 'prefetch_for_includes', {}).get(include, None) -class AutoPreloadMixin(object): def get_queryset(self, *args, **kwargs): """ This mixin adds automatic prefetching for OneToOne and ManyToMany fields. """ qs = super(AutoPreloadMixin, self).get_queryset(*args, **kwargs) included_resources = get_included_resources(self.request) - for included in included_resources: + for included in included_resources + ['__all__']: + # Custom defined "select_related" and "prefetch_related" is a priority + select_related = self.get_select_related(included) + if select_related is not None: + qs = qs.select_related(*select_related) + continue + + prefetch_related = self.get_prefetch_related(included) + if prefetch_related is not None: + qs = qs.prefetch_related(*prefetch_related) + continue + + # If include was not defined, trying to resolve it automatically included_model = None levels = included.split('.') level_model = qs.model @@ -241,14 +243,12 @@ def get_related_instance(self): class ModelViewSet(AutoPreloadMixin, - PreloadIncludesMixin, RelatedMixin, viewsets.ModelViewSet): pass class ReadOnlyModelViewSet(AutoPreloadMixin, - PreloadIncludesMixin, RelatedMixin, viewsets.ReadOnlyModelViewSet): pass From 6c9eac9afbcc1877f3eadc28cd02319094ce6670 Mon Sep 17 00:00:00 2001 From: Anton-Shutik Date: Fri, 17 May 2019 14:27:39 +0300 Subject: [PATCH 06/11] Use select_related if possible --- example/tests/test_performance.py | 2 +- rest_framework_json_api/views.py | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/example/tests/test_performance.py b/example/tests/test_performance.py index 50903498..ac8b5956 100644 --- a/example/tests/test_performance.py +++ b/example/tests/test_performance.py @@ -58,7 +58,7 @@ def test_query_count_include_author(self): self.assertEqual(len(response.data['results']), 25) def test_query_select_related_entry(self): - """ We expect a list view with an include have three queries: + """ We expect a list view with an include have two queries: 1. Primary resource COUNT query 2. Primary resource SELECT + SELECT RELATED writer(author) and bio diff --git a/rest_framework_json_api/views.py b/rest_framework_json_api/views.py index 3f1a4acf..d107ed7b 100644 --- a/rest_framework_json_api/views.py +++ b/rest_framework_json_api/views.py @@ -119,6 +119,8 @@ def get_queryset(self, *args, **kwargs): included_model = None levels = included.split('.') level_model = qs.model + # Suppose we can do select_related by default + can_select_related = True for level in levels: if not hasattr(level_model, level): break @@ -126,16 +128,20 @@ def get_queryset(self, *args, **kwargs): field_class = field.__class__ is_forward_relation = ( - issubclass(field_class, ForwardManyToOneDescriptor) or - issubclass(field_class, ManyToManyDescriptor) + issubclass(field_class, (ForwardManyToOneDescriptor, ManyToManyDescriptor)) ) is_reverse_relation = ( - issubclass(field_class, ReverseManyToOneDescriptor) or - issubclass(field_class, ReverseOneToOneDescriptor) + issubclass(field_class, (ReverseManyToOneDescriptor, ReverseOneToOneDescriptor)) ) if not (is_forward_relation or is_reverse_relation): break + # Figuring out if relation should be select related rather than prefetch_related + # If at least one relation in the chain is not "selectable" then use "prefetch" + can_select_related &= ( + issubclass(field_class, (ForwardManyToOneDescriptor, ReverseOneToOneDescriptor)) + ) + if level == levels[-1]: included_model = field else: @@ -151,7 +157,10 @@ def get_queryset(self, *args, **kwargs): level_model = model_field.model if included_model is not None: - qs = qs.prefetch_related(included.replace('.', '__')) + if can_select_related: + qs = qs.select_related(included.replace('.', '__')) + else: + qs = qs.prefetch_related(included.replace('.', '__')) return qs From 5ab68a9df57a7bcd9f2f9d1edb21328998dd1fed Mon Sep 17 00:00:00 2001 From: Anton-Shutik Date: Fri, 17 May 2019 15:26:50 +0300 Subject: [PATCH 07/11] Polymorphic FK should be explicitly overridden --- example/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/example/views.py b/example/views.py index 4d363032..891957ff 100644 --- a/example/views.py +++ b/example/views.py @@ -203,6 +203,9 @@ def get_queryset(self, *args, **kwargs): class CompanyViewset(ModelViewSet): queryset = Company.objects.all() serializer_class = CompanySerializer + prefetch_for_includes = { + 'current_project': ['current_project'], + } class ProjectViewset(ModelViewSet): From 3422186281adbb5c8b85a06c301f4e6fe44acebd Mon Sep 17 00:00:00 2001 From: Anton-Shutik Date: Fri, 17 May 2019 16:07:27 +0300 Subject: [PATCH 08/11] Updated docs --- docs/usage.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 0531daa3..fd32e1da 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -823,7 +823,9 @@ class QuestSerializer(serializers.ModelSerializer): Be aware that using included resources without any form of prefetching **WILL HURT PERFORMANCE** as it will introduce m\*(n+1) queries. -A viewset helper was designed to allow for greater flexibility and it is automatically available when subclassing +A viewset helper was designed to allow for greater flexibility and it is automatically available when subclassing. +You can also define your custom queryset for `select` or `prefetch` related for each `include` that comes from the url. +It has a priority over automatically added preloads. `rest_framework_json_api.views.ModelViewSet`: ```python from rest_framework_json_api import views @@ -831,9 +833,12 @@ from rest_framework_json_api import views # When MyViewSet is called with ?include=author it will dynamically prefetch author and author.bio class MyViewSet(views.ModelViewSet): queryset = Book.objects.all() + select_for_includes = { + 'author': ['author__bio'], + } prefetch_for_includes = { '__all__': [], - 'author': ['author', 'author__bio'], + 'all_authors': [Prefetch('all_authors', queryset=Author.objects.select_related('bio'))], 'category.section': ['category'] } ``` From b73c08b7fe313ced6807cd23588555dea52f2de6 Mon Sep 17 00:00:00 2001 From: Anton-Shutik Date: Fri, 17 May 2019 16:11:44 +0300 Subject: [PATCH 09/11] Updated change log --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf1b1723..11c06bc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ any parts of the framework not mentioned in the documentation should generally b ### Fixed +* Added `select_for_includes` handling. [PR](https://github.com/django-json-api/django-rest-framework-json-api/pull/600). [Docs](https://django-rest-framework-json-api.readthedocs.io/en/stable/usage.html#performance-improvements). * Avoid exception when trying to include skipped relationship * Don't swallow `filter[]` params when there are several * Fix DeprecationWarning regarding collections.abc import in Python 3.7 From 36cb9988c2a317310314f9eb1924ba89773f476f Mon Sep 17 00:00:00 2001 From: Anton-Shutik Date: Fri, 24 May 2019 15:40:22 +0300 Subject: [PATCH 10/11] Refactored AutoPrefetchRelated mixin --- example/tests/test_performance.py | 2 +- example/views.py | 4 ++-- rest_framework_json_api/views.py | 25 +++++++++++++++++-------- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/example/tests/test_performance.py b/example/tests/test_performance.py index ac8b5956..45c71aeb 100644 --- a/example/tests/test_performance.py +++ b/example/tests/test_performance.py @@ -53,7 +53,7 @@ def test_query_count_include_author(self): 4. Author types prefetched 5. Entries prefetched """ - with self.assertNumQueries(5): + with self.assertNumQueries(4): response = self.client.get('/comments?include=author&page[size]=25') self.assertEqual(len(response.data['results']), 25) diff --git a/example/views.py b/example/views.py index 891957ff..73a9136e 100644 --- a/example/views.py +++ b/example/views.py @@ -12,7 +12,7 @@ from rest_framework_json_api.filters import OrderingFilter, QueryParameterValidationFilter from rest_framework_json_api.pagination import JsonApiPageNumberPagination from rest_framework_json_api.utils import format_drf_errors -from rest_framework_json_api.views import ModelViewSet, RelationshipView +from rest_framework_json_api.views import ModelViewSet, RelationshipView, PreloadIncludesMixin from example.models import Author, Blog, Comment, Company, Entry, Project, ProjectType from example.serializers import ( @@ -200,7 +200,7 @@ def get_queryset(self, *args, **kwargs): return super(CommentViewSet, self).get_queryset() -class CompanyViewset(ModelViewSet): +class CompanyViewset(PreloadIncludesMixin, viewsets.ModelViewSet): queryset = Company.objects.all() serializer_class = CompanySerializer prefetch_for_includes = { diff --git a/rest_framework_json_api/views.py b/rest_framework_json_api/views.py index d107ed7b..a7a125f9 100644 --- a/rest_framework_json_api/views.py +++ b/rest_framework_json_api/views.py @@ -35,7 +35,7 @@ class PrefetchForIncludesHelperMixin(object): def __init__(self, *args, **kwargs): warnings.warn("PrefetchForIncludesHelperMixin is deprecated. " - "Use AutoPreloadMixin instead", + "Use PreloadIncludesMixin instead", DeprecationWarning) super(PrefetchForIncludesHelperMixin, self).__init__(*args, **kwargs) @@ -70,7 +70,7 @@ class MyViewSet(viewsets.ModelViewSet): return qs -class AutoPreloadMixin(object): +class PreloadIncludesMixin(object): """ This mixin provides a helper attributes to select or prefetch related models based on the include specified in the URL. @@ -99,22 +99,30 @@ def get_prefetch_related(self, include): return getattr(self, 'prefetch_for_includes', {}).get(include, None) def get_queryset(self, *args, **kwargs): - """ This mixin adds automatic prefetching for OneToOne and ManyToMany fields. """ - qs = super(AutoPreloadMixin, self).get_queryset(*args, **kwargs) - included_resources = get_included_resources(self.request) + qs = super(PreloadIncludesMixin, self).get_queryset(*args, **kwargs) + included_resources = get_included_resources(self.request) for included in included_resources + ['__all__']: - # Custom defined "select_related" and "prefetch_related" is a priority + select_related = self.get_select_related(included) if select_related is not None: qs = qs.select_related(*select_related) - continue prefetch_related = self.get_prefetch_related(included) if prefetch_related is not None: qs = qs.prefetch_related(*prefetch_related) - continue + return qs + + +class AutoPreloadMixin(object): + + def get_queryset(self, *args, **kwargs): + """ This mixin adds automatic prefetching for OneToOne and ManyToMany fields. """ + qs = super(AutoPreloadMixin, self).get_queryset(*args, **kwargs) + included_resources = get_included_resources(self.request) + + for included in included_resources + ['__all__']: # If include was not defined, trying to resolve it automatically included_model = None levels = included.split('.') @@ -252,6 +260,7 @@ def get_related_instance(self): class ModelViewSet(AutoPreloadMixin, + PreloadIncludesMixin, RelatedMixin, viewsets.ModelViewSet): pass From db5251de4f141b68932b9f3e58a7b886515710d5 Mon Sep 17 00:00:00 2001 From: Oliver Sauder Date: Tue, 28 May 2019 16:31:01 +0200 Subject: [PATCH 11/11] Clarify changelog and documentation --- CHANGELOG.md | 13 +++++++++++-- docs/usage.md | 13 +++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11c06bc2..c5d5e074 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,13 +14,22 @@ any parts of the framework not mentioned in the documentation should generally b * Add support for Django 2.2 +### Changed + +* 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) +* Reduce number of queries to calculate includes by using `select_related` when possible + ### Fixed -* Added `select_for_includes` handling. [PR](https://github.com/django-json-api/django-rest-framework-json-api/pull/600). [Docs](https://django-rest-framework-json-api.readthedocs.io/en/stable/usage.html#performance-improvements). * Avoid exception when trying to include skipped relationship * Don't swallow `filter[]` params when there are several * Fix DeprecationWarning regarding collections.abc import in Python 3.7 -* Allow OPTIONS request to be used on RelationshipView. +* Allow OPTIONS request to be used on RelationshipView + +### Deprecated + +* Deprecate `PrefetchForIncludesHelperMixin` use `PreloadIncludesMixin` instead +* Deprecate `AutoPrefetchMixin` use `AutoPreloadMixin` instead ## [2.7.0] - 2019-01-14 diff --git a/docs/usage.md b/docs/usage.md index fd32e1da..c06d276c 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -823,9 +823,10 @@ class QuestSerializer(serializers.ModelSerializer): Be aware that using included resources without any form of prefetching **WILL HURT PERFORMANCE** as it will introduce m\*(n+1) queries. -A viewset helper was designed to allow for greater flexibility and it is automatically available when subclassing. -You can also define your custom queryset for `select` or `prefetch` related for each `include` that comes from the url. -It has a priority over automatically added preloads. +A viewset helper was therefore designed to automatically preload data when possible. Such is automatically available when subclassing `ModelViewSet`. + +It also allows to define custom `select_related` and `prefetch_related` for each requested `include` when needed in special cases: + `rest_framework_json_api.views.ModelViewSet`: ```python from rest_framework_json_api import views @@ -853,7 +854,7 @@ class MyReadOnlyViewSet(views.ReadOnlyModelViewSet): The special keyword `__all__` can be used to specify a prefetch which should be done regardless of the include, similar to making the prefetch yourself on the QuerySet. -Using the helper to prefetch, rather than attempting to minimise queries via select_related might give you better performance depending on the characteristics of your data and database. +Using the helper to prefetch, rather than attempting to minimise queries via `select_related` might give you better performance depending on the characteristics of your data and database. For example: @@ -866,11 +867,11 @@ a) 1 query via selected_related, e.g. SELECT * FROM books LEFT JOIN author LEFT b) 4 small queries via prefetch_related. If you have 1M books, 50k authors, 10k categories, 10k copyrightholders -in the select_related scenario, you've just created a in-memory table +in the `select_related` scenario, you've just created a in-memory table with 1e18 rows which will likely exhaust any available memory and slow your database to crawl. -The prefetch_related case will issue 4 queries, but they will be small and fast queries. +The `prefetch_related` case will issue 4 queries, but they will be small and fast queries.