diff --git a/django/db/models/deletion.py b/django/db/models/deletion.py index 537011487a..753f4f5076 100644 --- a/django/db/models/deletion.py +++ b/django/db/models/deletion.py @@ -285,8 +285,8 @@ class Collector: # update fields for model, instances_for_fieldvalues in self.field_updates.items(): - query = sql.UpdateQuery(model) for (field, value), instances in instances_for_fieldvalues.items(): + query = sql.UpdateQuery(model) query.update_batch([obj.pk for obj in instances], {field.name: value}, self.using) diff --git a/docs/releases/1.11.10.txt b/docs/releases/1.11.10.txt index d078959357..49e19614a5 100644 --- a/docs/releases/1.11.10.txt +++ b/docs/releases/1.11.10.txt @@ -9,4 +9,5 @@ Django 1.11.10 fixes several bugs in 1.11.9. Bugfixes ======== -* ... +* Fixed incorrect foreign key nullification if a model has two foreign keys to + the same model and a target model is deleted (:ticket:`29016`). diff --git a/docs/releases/2.0.2.txt b/docs/releases/2.0.2.txt index 4f0878c8d0..92026be5f8 100644 --- a/docs/releases/2.0.2.txt +++ b/docs/releases/2.0.2.txt @@ -11,3 +11,6 @@ Bugfixes * Fixed hidden content at the bottom of the "The install worked successfully!" page for some languages (:ticket:`28885`). + +* Fixed incorrect foreign key nullification if a model has two foreign keys to + the same model and a target model is deleted (:ticket:`29016`). diff --git a/tests/delete_regress/models.py b/tests/delete_regress/models.py index f0145de65b..90eae1ba1c 100644 --- a/tests/delete_regress/models.py +++ b/tests/delete_regress/models.py @@ -56,6 +56,8 @@ class Email(Contact): class Researcher(models.Model): contacts = models.ManyToManyField(Contact, related_name="research_contacts") + primary_contact = models.ForeignKey(Contact, models.SET_NULL, null=True, related_name='primary_contacts') + secondary_contact = models.ForeignKey(Contact, models.SET_NULL, null=True, related_name='secondary_contacts') class Food(models.Model): diff --git a/tests/delete_regress/tests.py b/tests/delete_regress/tests.py index 7f913257ab..a8b1cb0386 100644 --- a/tests/delete_regress/tests.py +++ b/tests/delete_regress/tests.py @@ -4,7 +4,7 @@ from django.db import connection, models, transaction from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature from .models import ( - Award, AwardNote, Book, Child, Eaten, Email, File, Food, FooFile, + Award, AwardNote, Book, Child, Contact, Eaten, Email, File, Food, FooFile, FooFileProxy, FooImage, FooPhoto, House, Image, Item, Location, Login, OrderedPerson, OrgUnit, Person, Photo, PlayedWith, PlayedWithNote, Policy, Researcher, Toy, Version, @@ -334,7 +334,7 @@ class Ticket19102Tests(TestCase): self.assertTrue(Login.objects.filter(pk=self.l2.pk).exists()) -class OrderedDeleteTests(TestCase): +class DeleteTests(TestCase): def test_meta_ordered_delete(self): # When a subquery is performed by deletion code, the subquery must be # cleared of all ordering. There was a but that caused _meta ordering @@ -344,3 +344,27 @@ class OrderedDeleteTests(TestCase): OrderedPerson.objects.create(name='Bob', lives_in=h) OrderedPerson.objects.filter(lives_in__address='Foo').delete() self.assertEqual(OrderedPerson.objects.count(), 0) + + def test_foreign_key_delete_nullifies_correct_columns(self): + """ + With a model (Researcher) that has two foreign keys pointing to the + same model (Contact), deleting an instance of the target model + (contact1) nullifies the correct fields of Researcher. + """ + contact1 = Contact.objects.create(label='Contact 1') + contact2 = Contact.objects.create(label='Contact 2') + researcher1 = Researcher.objects.create( + primary_contact=contact1, + secondary_contact=contact2, + ) + researcher2 = Researcher.objects.create( + primary_contact=contact2, + secondary_contact=contact1, + ) + contact1.delete() + researcher1.refresh_from_db() + researcher2.refresh_from_db() + self.assertIsNone(researcher1.primary_contact) + self.assertEqual(researcher1.secondary_contact, contact2) + self.assertEqual(researcher2.primary_contact, contact2) + self.assertIsNone(researcher2.secondary_contact)