From 19e0587ee596debf77540d6a08ccb6507e60b6a7 Mon Sep 17 00:00:00 2001 From: Mark Evans Date: Sat, 3 Sep 2022 09:53:58 -0400 Subject: [PATCH] Fixed #33937 -- Optimized serialization of related m2m fields without natural keys. --- AUTHORS | 1 + django/core/serializers/python.py | 8 ++++++- django/core/serializers/xml_serializer.py | 8 ++++++- tests/serializers/tests.py | 27 +++++++++++++++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 8c2ab361d2..e3c54397ff 100644 --- a/AUTHORS +++ b/AUTHORS @@ -616,6 +616,7 @@ answer newbie questions, and generally made Django that much better: Mario Gonzalez Mariusz Felisiak Mark Biggers + Mark Evans Mark Gensler mark@junklight.com Mark Lavin diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index a3918bf9d2..fa3bce2948 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -70,14 +70,20 @@ class Serializer(base.Serializer): def m2m_value(value): return value.natural_key() + def queryset_iterator(obj, field): + return getattr(obj, field.name).iterator() + else: def m2m_value(value): return self._value_from_field(value, value._meta.pk) + def queryset_iterator(obj, field): + return getattr(obj, field.name).only("pk").iterator() + m2m_iter = getattr(obj, "_prefetched_objects_cache", {}).get( field.name, - getattr(obj, field.name).iterator(), + queryset_iterator(obj, field), ) self._current[field.name] = [m2m_value(related) for related in m2m_iter] diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py index 8d3918cfaa..1d3269c41a 100644 --- a/django/core/serializers/xml_serializer.py +++ b/django/core/serializers/xml_serializer.py @@ -146,14 +146,20 @@ class Serializer(base.Serializer): self.xml.endElement("natural") self.xml.endElement("object") + def queryset_iterator(obj, field): + return getattr(obj, field.name).iterator() + else: def handle_m2m(value): self.xml.addQuickElement("object", attrs={"pk": str(value.pk)}) + def queryset_iterator(obj, field): + return getattr(obj, field.name).only("pk").iterator() + m2m_iter = getattr(obj, "_prefetched_objects_cache", {}).get( field.name, - getattr(obj, field.name).iterator(), + queryset_iterator(obj, field), ) for relobj in m2m_iter: handle_m2m(relobj) diff --git a/tests/serializers/tests.py b/tests/serializers/tests.py index 48899cb3fb..3cc937d58d 100644 --- a/tests/serializers/tests.py +++ b/tests/serializers/tests.py @@ -410,6 +410,33 @@ class SerializersTestBase: self.assertEqual(self._get_field_values(child_data, "parent_m2m"), []) self.assertEqual(self._get_field_values(child_data, "parent_data"), []) + def test_serialize_only_pk(self): + with self.assertNumQueries(5) as ctx: + serializers.serialize( + self.serializer_name, + Article.objects.all(), + use_natural_foreign_keys=False, + ) + + categories_sql = ctx[1]["sql"] + 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) + + def test_serialize_no_only_pk_with_natural_keys(self): + with self.assertNumQueries(5) as ctx: + serializers.serialize( + self.serializer_name, + Article.objects.all(), + use_natural_foreign_keys=True, + ) + + categories_sql = ctx[1]["sql"] + self.assertNotIn(connection.ops.quote_name("meta_data_id"), categories_sql) + # CategoryMetaData has natural_key(). + meta_data_sql = ctx[2]["sql"] + self.assertIn(connection.ops.quote_name("kind"), meta_data_sql) + class SerializerAPITests(SimpleTestCase): def test_stream_class(self):