From f9936deed1ff13b20e18bd9ca2b0750b52706b6c Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Sun, 4 Jun 2023 20:49:07 +0200 Subject: [PATCH] Fixed #34620 -- Fixed serialization crash on m2m fields without natural keys when base querysets use select_related(). Regression in 19e0587ee596debf77540d6a08ccb6507e60b6a7. Thanks Martin Svoboda for the report. --- django/core/serializers/python.py | 4 +++- django/core/serializers/xml_serializer.py | 4 +++- docs/releases/4.2.2.txt | 4 ++++ tests/serializers/models/base.py | 12 ++++++++++++ tests/serializers/test_json.py | 3 ++- tests/serializers/test_jsonl.py | 3 ++- tests/serializers/test_xml.py | 1 + tests/serializers/test_yaml.py | 1 + tests/serializers/tests.py | 16 ++++++++++------ 9 files changed, 38 insertions(+), 10 deletions(-) diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index 36048601af..0dc504aa34 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -79,7 +79,9 @@ class Serializer(base.Serializer): return self._value_from_field(value, value._meta.pk) 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( field.name, diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py index 1d3269c41a..e0129a59bb 100644 --- a/django/core/serializers/xml_serializer.py +++ b/django/core/serializers/xml_serializer.py @@ -155,7 +155,9 @@ class Serializer(base.Serializer): self.xml.addQuickElement("object", attrs={"pk": str(value.pk)}) 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( field.name, diff --git a/docs/releases/4.2.2.txt b/docs/releases/4.2.2.txt index 1bada4073b..9b7d6c1b66 100644 --- a/docs/releases/4.2.2.txt +++ b/docs/releases/4.2.2.txt @@ -43,3 +43,7 @@ Bugfixes * 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 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`). diff --git a/tests/serializers/models/base.py b/tests/serializers/models/base.py index af9ca2b20a..e9f548ad3c 100644 --- a/tests/serializers/models/base.py +++ b/tests/serializers/models/base.py @@ -53,12 +53,24 @@ class Author(models.Model): 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): author = models.ForeignKey(Author, models.CASCADE) headline = models.CharField(max_length=50) pub_date = models.DateTimeField() categories = models.ManyToManyField(Category) meta_data = models.ManyToManyField(CategoryMetaData) + topics = models.ManyToManyField(Topic) class Meta: ordering = ("pub_date",) diff --git a/tests/serializers/test_json.py b/tests/serializers/test_json.py index 0c625684ac..65d521faac 100644 --- a/tests/serializers/test_json.py +++ b/tests/serializers/test_json.py @@ -38,7 +38,8 @@ class JsonSerializerTestCase(SerializersTestBase, TestCase): %(first_category_pk)s, %(second_category_pk)s ], - "meta_data": [] + "meta_data": [], + "topics": [] } } ] diff --git a/tests/serializers/test_jsonl.py b/tests/serializers/test_jsonl.py index 16a0759e74..3137b037a9 100644 --- a/tests/serializers/test_jsonl.py +++ b/tests/serializers/test_jsonl.py @@ -27,7 +27,8 @@ class JsonlSerializerTestCase(SerializersTestBase, TestCase): '"headline": "Poker has no place on ESPN",' '"pub_date": "2006-06-16T11:00:00",' '"categories": [%(first_category_pk)s,%(second_category_pk)s],' - '"meta_data": []}}\n' + '"meta_data": [],' + '"topics": []}}\n' ) @staticmethod diff --git a/tests/serializers/test_xml.py b/tests/serializers/test_xml.py index 5d8b9c04cc..c9df2f2a5b 100644 --- a/tests/serializers/test_xml.py +++ b/tests/serializers/test_xml.py @@ -26,6 +26,7 @@ class XmlSerializerTestCase(SerializersTestBase, TestCase): 2006-06-16T11:00:00 + """ # NOQA diff --git a/tests/serializers/test_yaml.py b/tests/serializers/test_yaml.py index 683e0e0688..6db6f046fd 100644 --- a/tests/serializers/test_yaml.py +++ b/tests/serializers/test_yaml.py @@ -113,6 +113,7 @@ class YamlSerializerTestCase(SerializersTestBase, TestCase): ) + """ meta_data: [] + topics: [] """ ) diff --git a/tests/serializers/tests.py b/tests/serializers/tests.py index 67ae8f43de..58bd74d326 100644 --- a/tests/serializers/tests.py +++ b/tests/serializers/tests.py @@ -277,14 +277,14 @@ class SerializersTestBase: def test_serialize_prefetch_related_m2m(self): # One query for the Article table and one for each prefetched m2m # field. - with self.assertNumQueries(3): + with self.assertNumQueries(4): serializers.serialize( 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. - with self.assertNumQueries(5): + with self.assertNumQueries(7): serializers.serialize(self.serializer_name, Article.objects.all()) def test_serialize_with_null_pk(self): @@ -409,7 +409,7 @@ class SerializersTestBase: self.assertEqual(self._get_field_values(child_data, "parent_data"), []) def test_serialize_only_pk(self): - with self.assertNumQueries(5) as ctx: + with self.assertNumQueries(7) as ctx: serializers.serialize( self.serializer_name, Article.objects.all(), @@ -420,9 +420,11 @@ class SerializersTestBase: self.assertNotIn(connection.ops.quote_name("meta_data_id"), categories_sql) meta_data_sql = ctx[2]["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): - with self.assertNumQueries(5) as ctx: + with self.assertNumQueries(7) as ctx: serializers.serialize( self.serializer_name, Article.objects.all(), @@ -434,6 +436,8 @@ class SerializersTestBase: # CategoryMetaData has natural_key(). meta_data_sql = ctx[2]["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):