summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorErica Pisani <erica.pisani@floatcard.com>2024-12-18 09:03:02 +0100
committerSarah Boyce <42296566+sarahboyce@users.noreply.github.com>2024-12-18 10:39:20 +0100
commit20f9f6180531279785ceabed6ab2f29ab634d8dc (patch)
tree63439d6393edb4d802903b9160fa8fef25063df3
parenta8b70aeffd41d082d4bd8f99c777f37b6a58da66 (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.py15
-rw-r--r--django/core/serializers/xml_serializer.py15
-rw-r--r--tests/serializers/tests.py37
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