diff --git a/AUTHORS b/AUTHORS index a9d4bd435965e..4487126aa3358 100644 --- a/AUTHORS +++ b/AUTHORS @@ -271,6 +271,7 @@ answer newbie questions, and generally made Django that much better: Erik Karulf Erik Romijn eriks@win.tue.nl + Erwin Junge Esdras Beleza Espen Grindhaug Eugene Lazutkin @@ -744,6 +745,7 @@ answer newbie questions, and generally made Django that much better: Robert Wittams Rob Golding-Day Rob Hudson + Rob Nguyen Robin Munn Rodrigo Pinheiro Marques de Araújo Romain Garrigues diff --git a/django/db/models/base.py b/django/db/models/base.py index eeb5163b9623f..1be910115f702 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -684,14 +684,19 @@ def save(self, force_insert=False, force_update=False, using=None, # database to raise an IntegrityError if applicable. If # constraints aren't supported by the database, there's the # unavoidable risk of data corruption. - if obj and obj.pk is None: - # Remove the object from a related instance cache. - if not field.remote_field.multiple: - field.remote_field.delete_cached_value(obj) - raise ValueError( - "save() prohibited to prevent data loss due to " - "unsaved related object '%s'." % field.name - ) + if obj: + if obj.pk is None: + # Remove the object from a related instance cache. + if not field.remote_field.multiple: + field.remote_field.delete_cached_value(obj) + raise ValueError( + "save() prohibited to prevent data loss due to " + "unsaved related object '%s'." % field.name + ) + elif getattr(self, field.attname) is None: + # Use pk from related object if it has been saved after + # the original assignment + setattr(self, field.attname, obj.pk) # If the relationship's pk/to_field was changed, clear the # cached relationship. if obj and getattr(obj, field.target_field.attname) != getattr(self, field.attname): diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 276bcb11ff5bf..7bef2f0abad48 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -1031,6 +1031,10 @@ def save_form_data(self, instance, data): setattr(instance, self.name, data) else: setattr(instance, self.attname, data) + # remote field object must be cleared otherwise Model.save() + # will reassign attname using the related object pk + if data is None: + setattr(instance, self.name, data) def _check_unique(self, **kwargs): # Override ForeignKey since check isn't applicable here. diff --git a/tests/many_to_one/models.py b/tests/many_to_one/models.py index 96b84ccddb9f8..efb3f220fde66 100644 --- a/tests/many_to_one/models.py +++ b/tests/many_to_one/models.py @@ -74,6 +74,10 @@ class ToFieldChild(models.Model): parent = models.ForeignKey(Parent, models.CASCADE, to_field='name', related_name='to_field_children') +class ChildNullableParent(models.Model): + parent = models.ForeignKey(Parent, models.CASCADE, null=True) + + # Multiple paths to the same model (#7110, #7125) class Category(models.Model): name = models.CharField(max_length=20) diff --git a/tests/many_to_one/tests.py b/tests/many_to_one/tests.py index a949606cdfcb7..4d4ccb554a7d3 100644 --- a/tests/many_to_one/tests.py +++ b/tests/many_to_one/tests.py @@ -8,8 +8,8 @@ from django.utils.translation import gettext_lazy from .models import ( - Article, Category, Child, City, District, First, Parent, Record, Relation, - Reporter, School, Student, Third, ToFieldChild, + Article, Category, Child, ChildNullableParent, City, District, First, + Parent, Record, Relation, Reporter, School, Student, Third, ToFieldChild, ) @@ -522,6 +522,14 @@ def test_fk_assignment_and_related_object_cache(self): self.assertIsNot(c.parent, p) self.assertEqual(c.parent, p) + def test_save_nullable_parent_then_child(self): + parent = Parent() + child = ChildNullableParent(parent=parent) + parent.save() + child.save() + child.refresh_from_db() + self.assertEqual(child.parent, parent) + def test_save_nullable_parent_then_child_to_field(self): parent_id = "jeff" parent = Parent(name=parent_id)