diff options
| author | Erica Pisani <erica.pisani@floatcard.com> | 2024-12-18 09:03:02 +0100 |
|---|---|---|
| committer | Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> | 2024-12-18 10:39:20 +0100 |
| commit | 20f9f6180531279785ceabed6ab2f29ab634d8dc (patch) | |
| tree | 63439d6393edb4d802903b9160fa8fef25063df3 | |
| parent | a8b70aeffd41d082d4bd8f99c777f37b6a58da66 (diff) | |
Fixed #35996 -- Fixed database serialization crash when serializing a many-to-many field that had a prefetch.
| -rw-r--r-- | django/core/serializers/python.py | 15 | ||||
| -rw-r--r-- | django/core/serializers/xml_serializer.py | 15 | ||||
| -rw-r--r-- | tests/serializers/tests.py | 37 |
3 files changed, 49 insertions, 18 deletions
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 |
