1
0
mirror of https://github.com/django/django.git synced 2024-12-22 09:05:43 +00:00

Fixed #35996 -- Fixed database serialization crash when serializing a many-to-many field that had a prefetch.

This commit is contained in:
Erica Pisani 2024-12-18 09:03:02 +01:00 committed by Sarah Boyce
parent a8b70aeffd
commit 20f9f61805
3 changed files with 49 additions and 18 deletions

View File

@ -74,7 +74,11 @@ class Serializer(base.Serializer):
return value.natural_key() return value.natural_key()
def queryset_iterator(obj, field): 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: else:
@ -82,12 +86,9 @@ class Serializer(base.Serializer):
return self._value_from_field(value, value._meta.pk) return self._value_from_field(value, value._meta.pk)
def queryset_iterator(obj, field): def queryset_iterator(obj, field):
return ( query_set = getattr(obj, field.name).select_related(None).only("pk")
getattr(obj, field.name) chunk_size = 2000 if query_set._prefetch_related_lookups else None
.select_related(None) return query_set.iterator(chunk_size=chunk_size)
.only("pk")
.iterator()
)
m2m_iter = getattr(obj, "_prefetched_objects_cache", {}).get( m2m_iter = getattr(obj, "_prefetched_objects_cache", {}).get(
field.name, field.name,

View File

@ -148,7 +148,11 @@ class Serializer(base.Serializer):
self.xml.endElement("object") self.xml.endElement("object")
def queryset_iterator(obj, field): 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: else:
@ -156,12 +160,9 @@ class Serializer(base.Serializer):
self.xml.addQuickElement("object", attrs={"pk": str(value.pk)}) self.xml.addQuickElement("object", attrs={"pk": str(value.pk)})
def queryset_iterator(obj, field): def queryset_iterator(obj, field):
return ( query_set = getattr(obj, field.name).select_related(None).only("pk")
getattr(obj, field.name) chunk_size = 2000 if query_set._prefetch_related_lookups else None
.select_related(None) return query_set.iterator(chunk_size=chunk_size)
.only("pk")
.iterator()
)
m2m_iter = getattr(obj, "_prefetched_objects_cache", {}).get( m2m_iter = getattr(obj, "_prefetched_objects_cache", {}).get(
field.name, field.name,

View File

@ -7,6 +7,7 @@ from django.core import serializers
from django.core.serializers import SerializerDoesNotExist from django.core.serializers import SerializerDoesNotExist
from django.core.serializers.base import ProgressBar from django.core.serializers.base import ProgressBar
from django.db import connection, transaction from django.db import connection, transaction
from django.db.models import Prefetch
from django.http import HttpResponse from django.http import HttpResponse
from django.test import SimpleTestCase, override_settings, skipUnlessDBFeature from django.test import SimpleTestCase, override_settings, skipUnlessDBFeature
from django.test.utils import Approximate from django.test.utils import Approximate
@ -18,6 +19,7 @@ from .models import (
AuthorProfile, AuthorProfile,
BaseModel, BaseModel,
Category, Category,
CategoryMetaData,
Child, Child,
ComplexModel, ComplexModel,
Movie, Movie,
@ -275,18 +277,45 @@ class SerializersTestBase:
serializers.serialize(self.serializer_name, [mv]) serializers.serialize(self.serializer_name, [mv])
def test_serialize_prefetch_related_m2m(self): def test_serialize_prefetch_related_m2m(self):
# One query for the Article table and one for each prefetched m2m # One query for the Article table, one for each prefetched m2m
# field. # field, and one extra one for the nested prefetch for the Topics
with self.assertNumQueries(4): # that have a relationship to the Category.
with self.assertNumQueries(5):
serializers.serialize( serializers.serialize(
self.serializer_name, 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 # One query for the Article table, and three m2m queries for each
# article. # article.
with self.assertNumQueries(7): with self.assertNumQueries(7):
serializers.serialize(self.serializer_name, Article.objects.all()) 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): def test_serialize_with_null_pk(self):
""" """
Serialized data with no primary key results Serialized data with no primary key results