Fixed #28147 -- Fixed loss of assigned parent when saving child after parent.

Thanks Erwin Junge for the initial patch.
This commit is contained in:
Rob 2019-05-20 22:06:30 +10:00 committed by Mariusz Felisiak
parent 266e7e0ecc
commit 519016e5f2
5 changed files with 33 additions and 10 deletions

View File

@ -271,6 +271,7 @@ answer newbie questions, and generally made Django that much better:
Erik Karulf <erik@karulf.com> Erik Karulf <erik@karulf.com>
Erik Romijn <django@solidlinks.nl> Erik Romijn <django@solidlinks.nl>
eriks@win.tue.nl eriks@win.tue.nl
Erwin Junge <erwin@junge.nl>
Esdras Beleza <linux@esdrasbeleza.com> Esdras Beleza <linux@esdrasbeleza.com>
Espen Grindhaug <http://grindhaug.org/> Espen Grindhaug <http://grindhaug.org/>
Eugene Lazutkin <http://lazutkin.com/blog/> Eugene Lazutkin <http://lazutkin.com/blog/>
@ -744,6 +745,7 @@ answer newbie questions, and generally made Django that much better:
Robert Wittams Robert Wittams
Rob Golding-Day <rob@golding-day.com> Rob Golding-Day <rob@golding-day.com>
Rob Hudson <https://rob.cogit8.org/> Rob Hudson <https://rob.cogit8.org/>
Rob Nguyen <tienrobertnguyenn@gmail.com>
Robin Munn <http://www.geekforgod.com/> Robin Munn <http://www.geekforgod.com/>
Rodrigo Pinheiro Marques de Araújo <fenrrir@gmail.com> Rodrigo Pinheiro Marques de Araújo <fenrrir@gmail.com>
Romain Garrigues <romain.garrigues.cs@gmail.com> Romain Garrigues <romain.garrigues.cs@gmail.com>

View File

@ -684,7 +684,8 @@ class Model(metaclass=ModelBase):
# database to raise an IntegrityError if applicable. If # database to raise an IntegrityError if applicable. If
# constraints aren't supported by the database, there's the # constraints aren't supported by the database, there's the
# unavoidable risk of data corruption. # unavoidable risk of data corruption.
if obj and obj.pk is None: if obj:
if obj.pk is None:
# Remove the object from a related instance cache. # Remove the object from a related instance cache.
if not field.remote_field.multiple: if not field.remote_field.multiple:
field.remote_field.delete_cached_value(obj) field.remote_field.delete_cached_value(obj)
@ -692,6 +693,10 @@ class Model(metaclass=ModelBase):
"save() prohibited to prevent data loss due to " "save() prohibited to prevent data loss due to "
"unsaved related object '%s'." % field.name "unsaved related object '%s'." % field.name
) )
elif getattr(self, field.attname) is None:
# Use pk from related object if it has been saved after
# an assignment.
setattr(self, field.attname, obj.pk)
# If the relationship's pk/to_field was changed, clear the # If the relationship's pk/to_field was changed, clear the
# cached relationship. # cached relationship.
if obj and getattr(obj, field.target_field.attname) != getattr(self, field.attname): if obj and getattr(obj, field.target_field.attname) != getattr(self, field.attname):

View File

@ -1031,6 +1031,10 @@ class OneToOneField(ForeignKey):
setattr(instance, self.name, data) setattr(instance, self.name, data)
else: else:
setattr(instance, self.attname, data) 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): def _check_unique(self, **kwargs):
# Override ForeignKey since check isn't applicable here. # Override ForeignKey since check isn't applicable here.

View File

@ -70,6 +70,10 @@ class Child(models.Model):
parent = models.ForeignKey(Parent, models.CASCADE) parent = models.ForeignKey(Parent, models.CASCADE)
class ChildNullableParent(models.Model):
parent = models.ForeignKey(Parent, models.CASCADE, null=True)
class ToFieldChild(models.Model): class ToFieldChild(models.Model):
parent = models.ForeignKey(Parent, models.CASCADE, to_field='name', related_name='to_field_children') parent = models.ForeignKey(Parent, models.CASCADE, to_field='name', related_name='to_field_children')

View File

@ -8,8 +8,8 @@ from django.test import TestCase
from django.utils.translation import gettext_lazy from django.utils.translation import gettext_lazy
from .models import ( from .models import (
Article, Category, Child, City, District, First, Parent, Record, Relation, Article, Category, Child, ChildNullableParent, City, District, First,
Reporter, School, Student, Third, ToFieldChild, Parent, Record, Relation, Reporter, School, Student, Third, ToFieldChild,
) )
@ -522,6 +522,14 @@ class ManyToOneTests(TestCase):
self.assertIsNot(c.parent, p) self.assertIsNot(c.parent, p)
self.assertEqual(c.parent, p) self.assertEqual(c.parent, p)
def test_save_nullable_fk_after_parent(self):
parent = Parent()
child = ChildNullableParent(parent=parent)
parent.save()
child.save()
child.refresh_from_db()
self.assertEqual(child.parent, parent)
def test_save_nullable_fk_after_parent_with_to_field(self): def test_save_nullable_fk_after_parent_with_to_field(self):
parent = Parent(name='jeff') parent = Parent(name='jeff')
child = ToFieldChild(parent=parent) child = ToFieldChild(parent=parent)