diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py index 0d7507f33f..80eaac26d4 100644 --- a/django/db/models/fields/related_descriptors.py +++ b/django/db/models/fields/related_descriptors.py @@ -277,6 +277,22 @@ class ForwardOneToOneDescriptor(ForwardManyToOneDescriptor): return obj return super().get_object(instance) + def __set__(self, instance, value): + super().__set__(instance, value) + # If the primary key is a link to a parent model and a parent instance + # is being set, update the value of the inherited pk(s). + if self.field.primary_key and self.field.remote_field.parent_link: + opts = instance._meta + # Inherited primary key fields from this object's base classes. + inherited_pk_fields = [ + field for field in opts.concrete_fields + if field.primary_key and field.remote_field + ] + for field in inherited_pk_fields: + rel_model_pk_name = field.remote_field.model._meta.pk.attname + raw_value = getattr(value, rel_model_pk_name) if value is not None else None + setattr(instance, rel_model_pk_name, raw_value) + class ReverseOneToOneDescriptor: """ diff --git a/tests/model_inheritance_regress/tests.py b/tests/model_inheritance_regress/tests.py index 7ed84cc5d0..b8f8ae6d87 100644 --- a/tests/model_inheritance_regress/tests.py +++ b/tests/model_inheritance_regress/tests.py @@ -527,3 +527,40 @@ class ModelInheritanceTest(TestCase): restaurant = italian_restaurant.restaurant_ptr self.assertEqual(restaurant.place_ptr.restaurant, restaurant) self.assertEqual(restaurant.italianrestaurant, italian_restaurant) + + def test_id_field_update_on_ancestor_change(self): + place1 = Place.objects.create(name='House of Pasta', address='944 Fullerton') + place2 = Place.objects.create(name='House of Pizza', address='954 Fullerton') + place3 = Place.objects.create(name='Burger house', address='964 Fullerton') + restaurant1 = Restaurant.objects.create( + place_ptr=place1, + serves_hot_dogs=True, + serves_pizza=False, + ) + restaurant2 = Restaurant.objects.create( + place_ptr=place2, + serves_hot_dogs=True, + serves_pizza=False, + ) + + italian_restaurant = ItalianRestaurant.objects.create( + restaurant_ptr=restaurant1, + serves_gnocchi=True, + ) + # Changing the parent of a restaurant changes the restaurant's ID & PK. + restaurant1.place_ptr = place3 + self.assertEqual(restaurant1.pk, place3.pk) + self.assertEqual(restaurant1.id, place3.id) + self.assertEqual(restaurant1.pk, restaurant1.id) + restaurant1.place_ptr = None + self.assertIsNone(restaurant1.pk) + self.assertIsNone(restaurant1.id) + # Changing the parent of an italian restaurant changes the restaurant's + # ID & PK. + italian_restaurant.restaurant_ptr = restaurant2 + self.assertEqual(italian_restaurant.pk, restaurant2.pk) + self.assertEqual(italian_restaurant.id, restaurant2.id) + self.assertEqual(italian_restaurant.pk, italian_restaurant.id) + italian_restaurant.restaurant_ptr = None + self.assertIsNone(italian_restaurant.pk) + self.assertIsNone(italian_restaurant.id)