1
0
mirror of https://github.com/django/django.git synced 2025-01-03 06:55:47 +00:00

Fixed #34620 -- Fixed serialization crash on m2m fields without natural keys when base querysets use select_related().

Regression in 19e0587ee5.

Thanks Martin Svoboda for the report.
This commit is contained in:
Mariusz Felisiak 2023-06-04 20:49:07 +02:00 committed by GitHub
parent 1136aa5005
commit f9936deed1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 38 additions and 10 deletions

View File

@ -79,7 +79,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 getattr(obj, field.name).only("pk").iterator() return (
getattr(obj, field.name).select_related().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

@ -155,7 +155,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 getattr(obj, field.name).only("pk").iterator() return (
getattr(obj, field.name).select_related().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

@ -43,3 +43,7 @@ Bugfixes
* Fixed a regression in Django 4.2 that caused a crash of querysets on SQLite * Fixed a regression in Django 4.2 that caused a crash of querysets on SQLite
when filtering on ``DecimalField`` against values outside of the defined when filtering on ``DecimalField`` against values outside of the defined
range (:ticket:`34590`). range (:ticket:`34590`).
* Fixed a regression in Django 4.2 that caused a serialization crash on a
``ManyToManyField`` without a natural key when its ``Manager``s base
``QuerySet`` used ``select_related()`` (:ticket:`34620`).

View File

@ -53,12 +53,24 @@ class Author(models.Model):
return self.name return self.name
class TopicManager(models.Manager):
def get_queryset(self):
return super().get_queryset().select_related("category")
class Topic(models.Model):
name = models.CharField(max_length=255)
category = models.ForeignKey(Category, models.CASCADE, null=True)
objects = TopicManager()
class Article(models.Model): class Article(models.Model):
author = models.ForeignKey(Author, models.CASCADE) author = models.ForeignKey(Author, models.CASCADE)
headline = models.CharField(max_length=50) headline = models.CharField(max_length=50)
pub_date = models.DateTimeField() pub_date = models.DateTimeField()
categories = models.ManyToManyField(Category) categories = models.ManyToManyField(Category)
meta_data = models.ManyToManyField(CategoryMetaData) meta_data = models.ManyToManyField(CategoryMetaData)
topics = models.ManyToManyField(Topic)
class Meta: class Meta:
ordering = ("pub_date",) ordering = ("pub_date",)

View File

@ -38,7 +38,8 @@ class JsonSerializerTestCase(SerializersTestBase, TestCase):
%(first_category_pk)s, %(first_category_pk)s,
%(second_category_pk)s %(second_category_pk)s
], ],
"meta_data": [] "meta_data": [],
"topics": []
} }
} }
] ]

View File

@ -27,7 +27,8 @@ class JsonlSerializerTestCase(SerializersTestBase, TestCase):
'"headline": "Poker has no place on ESPN",' '"headline": "Poker has no place on ESPN",'
'"pub_date": "2006-06-16T11:00:00",' '"pub_date": "2006-06-16T11:00:00",'
'"categories": [%(first_category_pk)s,%(second_category_pk)s],' '"categories": [%(first_category_pk)s,%(second_category_pk)s],'
'"meta_data": []}}\n' '"meta_data": [],'
'"topics": []}}\n'
) )
@staticmethod @staticmethod

View File

@ -26,6 +26,7 @@ class XmlSerializerTestCase(SerializersTestBase, TestCase):
<field name="pub_date" type="DateTimeField">2006-06-16T11:00:00</field> <field name="pub_date" type="DateTimeField">2006-06-16T11:00:00</field>
<field name="categories" rel="ManyToManyRel" to="serializers.category"><object pk="%(first_category_pk)s"></object><object pk="%(second_category_pk)s"></object></field> <field name="categories" rel="ManyToManyRel" to="serializers.category"><object pk="%(first_category_pk)s"></object><object pk="%(second_category_pk)s"></object></field>
<field name="meta_data" rel="ManyToManyRel" to="serializers.categorymetadata"></field> <field name="meta_data" rel="ManyToManyRel" to="serializers.categorymetadata"></field>
<field name="topics" rel="ManyToManyRel" to="serializers.topic"></field>
</object> </object>
</django-objects>""" # NOQA </django-objects>""" # NOQA

View File

@ -113,6 +113,7 @@ class YamlSerializerTestCase(SerializersTestBase, TestCase):
) )
+ """ + """
meta_data: [] meta_data: []
topics: []
""" """
) )

View File

@ -277,14 +277,14 @@ class SerializersTestBase:
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 and one for each prefetched m2m
# field. # field.
with self.assertNumQueries(3): with self.assertNumQueries(4):
serializers.serialize( serializers.serialize(
self.serializer_name, self.serializer_name,
Article.objects.prefetch_related("categories", "meta_data"), Article.objects.prefetch_related("categories", "meta_data", "topics"),
) )
# One query for the Article table, and two m2m queries for each # One query for the Article table, and three m2m queries for each
# article. # article.
with self.assertNumQueries(5): with self.assertNumQueries(7):
serializers.serialize(self.serializer_name, Article.objects.all()) serializers.serialize(self.serializer_name, Article.objects.all())
def test_serialize_with_null_pk(self): def test_serialize_with_null_pk(self):
@ -409,7 +409,7 @@ class SerializersTestBase:
self.assertEqual(self._get_field_values(child_data, "parent_data"), []) self.assertEqual(self._get_field_values(child_data, "parent_data"), [])
def test_serialize_only_pk(self): def test_serialize_only_pk(self):
with self.assertNumQueries(5) as ctx: with self.assertNumQueries(7) as ctx:
serializers.serialize( serializers.serialize(
self.serializer_name, self.serializer_name,
Article.objects.all(), Article.objects.all(),
@ -420,9 +420,11 @@ class SerializersTestBase:
self.assertNotIn(connection.ops.quote_name("meta_data_id"), categories_sql) self.assertNotIn(connection.ops.quote_name("meta_data_id"), categories_sql)
meta_data_sql = ctx[2]["sql"] meta_data_sql = ctx[2]["sql"]
self.assertNotIn(connection.ops.quote_name("kind"), meta_data_sql) self.assertNotIn(connection.ops.quote_name("kind"), meta_data_sql)
topics_data_sql = ctx[3]["sql"]
self.assertNotIn(connection.ops.quote_name("category_id"), topics_data_sql)
def test_serialize_no_only_pk_with_natural_keys(self): def test_serialize_no_only_pk_with_natural_keys(self):
with self.assertNumQueries(5) as ctx: with self.assertNumQueries(7) as ctx:
serializers.serialize( serializers.serialize(
self.serializer_name, self.serializer_name,
Article.objects.all(), Article.objects.all(),
@ -434,6 +436,8 @@ class SerializersTestBase:
# CategoryMetaData has natural_key(). # CategoryMetaData has natural_key().
meta_data_sql = ctx[2]["sql"] meta_data_sql = ctx[2]["sql"]
self.assertIn(connection.ops.quote_name("kind"), meta_data_sql) self.assertIn(connection.ops.quote_name("kind"), meta_data_sql)
topics_data_sql = ctx[3]["sql"]
self.assertNotIn(connection.ops.quote_name("category_id"), topics_data_sql)
class SerializerAPITests(SimpleTestCase): class SerializerAPITests(SimpleTestCase):