From 5d20e0207867615f6989714261ea3c0a3eb85d88 Mon Sep 17 00:00:00 2001 From: Akash Kumar Sen Date: Tue, 30 May 2023 20:08:43 +0530 Subject: [PATCH] Fixed #33414 -- Fixed creating diamond-shaped MTI objects for common ancestor with primary key that has a default. Co-authored-by: Simon Charette --- django/db/models/base.py | 31 ++++++++++++++++++++----------- tests/model_inheritance/models.py | 20 ++++++++++++++++++++ tests/model_inheritance/tests.py | 9 +++++++++ 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/django/db/models/base.py b/django/db/models/base.py index 7aabe0b667..959b72c93b 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -900,10 +900,12 @@ class Model(AltersData, metaclass=ModelBase): save_base.alters_data = True - def _save_parents(self, cls, using, update_fields): + def _save_parents(self, cls, using, update_fields, updated_parents=None): """Save all the parents of cls using values from self.""" meta = cls._meta inserted = False + if updated_parents is None: + updated_parents = {} for parent, field in meta.parents.items(): # Make sure the link fields are synced between parent and self. if ( @@ -912,16 +914,23 @@ class Model(AltersData, metaclass=ModelBase): and getattr(self, field.attname) is not None ): setattr(self, parent._meta.pk.attname, getattr(self, field.attname)) - parent_inserted = self._save_parents( - cls=parent, using=using, update_fields=update_fields - ) - updated = self._save_table( - cls=parent, - using=using, - update_fields=update_fields, - force_insert=parent_inserted, - ) - if not updated: + if (parent_updated := updated_parents.get(parent)) is None: + parent_inserted = self._save_parents( + cls=parent, + using=using, + update_fields=update_fields, + updated_parents=updated_parents, + ) + updated = self._save_table( + cls=parent, + using=using, + update_fields=update_fields, + force_insert=parent_inserted, + ) + if not updated: + inserted = True + updated_parents[parent] = updated + elif not parent_updated: inserted = True # Set the parent's PK value to self. if field: diff --git a/tests/model_inheritance/models.py b/tests/model_inheritance/models.py index dc0e238f7e..aabfdfc03d 100644 --- a/tests/model_inheritance/models.py +++ b/tests/model_inheritance/models.py @@ -186,3 +186,23 @@ class Child(Parent): class GrandChild(Child): pass + + +class CommonAncestor(models.Model): + id = models.IntegerField(primary_key=True, default=1) + + +class FirstParent(CommonAncestor): + first_ancestor = models.OneToOneField( + CommonAncestor, models.CASCADE, primary_key=True, parent_link=True + ) + + +class SecondParent(CommonAncestor): + second_ancestor = models.OneToOneField( + CommonAncestor, models.CASCADE, primary_key=True, parent_link=True + ) + + +class CommonChild(FirstParent, SecondParent): + pass diff --git a/tests/model_inheritance/tests.py b/tests/model_inheritance/tests.py index 4542e6c3cc..f1ecacbe29 100644 --- a/tests/model_inheritance/tests.py +++ b/tests/model_inheritance/tests.py @@ -9,6 +9,7 @@ from django.test.utils import CaptureQueriesContext, isolate_apps from .models import ( Base, Chef, + CommonChild, CommonInfo, CustomSupplier, GrandChild, @@ -149,6 +150,14 @@ class ModelInheritanceTests(TestCase): # accidentally found). self.assertSequenceEqual(s.titles.all(), []) + def test_create_diamond_mti_default_pk(self): + # 1 INSERT for each base. + with self.assertNumQueries(4): + common_child = CommonChild.objects.create() + # 3 SELECTs for the parents, 1 UPDATE for the child. + with self.assertNumQueries(4): + common_child.save() + def test_update_parent_filtering(self): """ Updating a field of a model subclass doesn't issue an UPDATE