mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	[4.2.x] Fixed #34620 -- Fixed serialization crash on m2m fields without natural keys when base querysets use select_related().
Regression in19e0587ee5. Thanks Martin Svoboda for the report. Backport off9936deed1from main
This commit is contained in:
		| @@ -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, | ||||||
|   | |||||||
| @@ -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, | ||||||
|   | |||||||
| @@ -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`). | ||||||
|   | |||||||
| @@ -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",) | ||||||
|   | |||||||
| @@ -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": [] | ||||||
|   } |   } | ||||||
| } | } | ||||||
| ] | ] | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -113,6 +113,7 @@ class YamlSerializerTestCase(SerializersTestBase, TestCase): | |||||||
|         ) |         ) | ||||||
|         + """ |         + """ | ||||||
|     meta_data: [] |     meta_data: [] | ||||||
|  |     topics: [] | ||||||
| """ | """ | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -279,14 +279,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): | ||||||
| @@ -411,7 +411,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(), | ||||||
| @@ -422,9 +422,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(), | ||||||
| @@ -436,6 +438,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): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user