Fixed #28147 -- Fixed loss of assigned parent when saving child after parent.
Thanks Erwin Junge for the initial patch.
This commit is contained in:
parent
266e7e0ecc
commit
519016e5f2
2
AUTHORS
2
AUTHORS
|
@ -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>
|
||||||
|
|
|
@ -684,14 +684,19 @@ 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:
|
||||||
# Remove the object from a related instance cache.
|
if obj.pk is None:
|
||||||
if not field.remote_field.multiple:
|
# Remove the object from a related instance cache.
|
||||||
field.remote_field.delete_cached_value(obj)
|
if not field.remote_field.multiple:
|
||||||
raise ValueError(
|
field.remote_field.delete_cached_value(obj)
|
||||||
"save() prohibited to prevent data loss due to "
|
raise ValueError(
|
||||||
"unsaved related object '%s'." % field.name
|
"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
|
||||||
|
# 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):
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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')
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue