diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index 57edebbb70..807d4b3977 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -74,7 +74,11 @@ class Serializer(base.Serializer): return value.natural_key() def queryset_iterator(obj, field): - return getattr(obj, field.name).iterator() + attr = getattr(obj, field.name) + chunk_size = ( + 2000 if getattr(attr, "prefetch_cache_name", None) else None + ) + return attr.iterator(chunk_size) else: @@ -82,12 +86,9 @@ class Serializer(base.Serializer): return self._value_from_field(value, value._meta.pk) def queryset_iterator(obj, field): - return ( - getattr(obj, field.name) - .select_related(None) - .only("pk") - .iterator() - ) + query_set = getattr(obj, field.name).select_related(None).only("pk") + chunk_size = 2000 if query_set._prefetch_related_lookups else None + return query_set.iterator(chunk_size=chunk_size) m2m_iter = getattr(obj, "_prefetched_objects_cache", {}).get( field.name, diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py index 5818bfaa84..3530d443b2 100644 --- a/django/core/serializers/xml_serializer.py +++ b/django/core/serializers/xml_serializer.py @@ -148,7 +148,11 @@ class Serializer(base.Serializer): self.xml.endElement("object") def queryset_iterator(obj, field): - return getattr(obj, field.name).iterator() + attr = getattr(obj, field.name) + chunk_size = ( + 2000 if getattr(attr, "prefetch_cache_name", None) else None + ) + return attr.iterator(chunk_size) else: @@ -156,12 +160,9 @@ class Serializer(base.Serializer): self.xml.addQuickElement("object", attrs={"pk": str(value.pk)}) def queryset_iterator(obj, field): - return ( - getattr(obj, field.name) - .select_related(None) - .only("pk") - .iterator() - ) + query_set = getattr(obj, field.name).select_related(None).only("pk") + chunk_size = 2000 if query_set._prefetch_related_lookups else None + return query_set.iterator(chunk_size=chunk_size) m2m_iter = getattr(obj, "_prefetched_objects_cache", {}).get( field.name, diff --git a/tests/serializers/tests.py b/tests/serializers/tests.py index 420246db0b..9e6bb762c9 100644 --- a/tests/serializers/tests.py +++ b/tests/serializers/tests.py @@ -7,6 +7,7 @@ from django.core import serializers from django.core.serializers import SerializerDoesNotExist from django.core.serializers.base import ProgressBar from django.db import connection, transaction +from django.db.models import Prefetch from django.http import HttpResponse from django.test import SimpleTestCase, override_settings, skipUnlessDBFeature from django.test.utils import Approximate @@ -18,6 +19,7 @@ from .models import ( AuthorProfile, BaseModel, Category, + CategoryMetaData, Child, ComplexModel, Movie, @@ -275,18 +277,45 @@ class SerializersTestBase: serializers.serialize(self.serializer_name, [mv]) def test_serialize_prefetch_related_m2m(self): - # One query for the Article table and one for each prefetched m2m - # field. - with self.assertNumQueries(4): + # One query for the Article table, one for each prefetched m2m + # field, and one extra one for the nested prefetch for the Topics + # that have a relationship to the Category. + with self.assertNumQueries(5): serializers.serialize( self.serializer_name, - Article.objects.prefetch_related("categories", "meta_data", "topics"), + Article.objects.prefetch_related( + "meta_data", + "topics", + Prefetch( + "categories", + queryset=Category.objects.prefetch_related("topic_set"), + ), + ), ) # One query for the Article table, and three m2m queries for each # article. with self.assertNumQueries(7): serializers.serialize(self.serializer_name, Article.objects.all()) + def test_serialize_prefetch_related_m2m_with_natural_keys(self): + # One query for the Article table, one for each prefetched m2m + # field, and a query to get the categories for each Article (two in + # total). + with self.assertNumQueries(5): + serializers.serialize( + self.serializer_name, + Article.objects.prefetch_related( + Prefetch( + "meta_data", + queryset=CategoryMetaData.objects.prefetch_related( + "category_set" + ), + ), + "topics", + ), + use_natural_foreign_keys=True, + ) + def test_serialize_with_null_pk(self): """ Serialized data with no primary key results