From 4ca5c565f4dc9e97845036e86416abc5cfde766c Mon Sep 17 00:00:00 2001 From: Hasan Ramezani Date: Tue, 28 Jan 2020 10:51:36 +0100 Subject: [PATCH] Refs #27852 -- Fixed object deletion to show all restricted related objects rather than just the first one. --- django/db/models/deletion.py | 30 +++++++++++++++++------------- tests/delete/models.py | 4 ++++ tests/delete/tests.py | 29 ++++++++++++++++++++--------- 3 files changed, 41 insertions(+), 22 deletions(-) diff --git a/django/db/models/deletion.py b/django/db/models/deletion.py index e7d245936c..3385ed85c7 100644 --- a/django/db/models/deletion.py +++ b/django/db/models/deletion.py @@ -328,19 +328,23 @@ class Collector: self.clear_restricted_objects_from_set(related_model, instances) for qs in self.fast_deletes: self.clear_restricted_objects_from_queryset(qs.model, qs) - for model, fields in self.restricted_objects.items(): - for field, objs in fields.items(): - for obj in objs: - raise RestrictedError( - "Cannot delete some instances of model '%s' " - "because they are referenced through a restricted " - "foreign key: '%s.%s'." % ( - field.remote_field.model.__name__, - obj.__class__.__name__, - field.name, - ), - objs, - ) + if self.restricted_objects.values(): + restricted_objects = defaultdict(list) + for related_model, fields in self.restricted_objects.items(): + for field, objs in fields.items(): + if objs: + key = "'%s.%s'" % (related_model.__name__, field.name) + restricted_objects[key] += objs + if restricted_objects: + raise RestrictedError( + 'Cannot delete some instances of model %r because ' + 'they are referenced through restricted foreign keys: ' + '%s.' % ( + model.__name__, + ', '.join(restricted_objects), + ), + chain.from_iterable(restricted_objects.values()), + ) def related_objects(self, related_model, related_fields, objs): """ diff --git a/tests/delete/models.py b/tests/delete/models.py index 618b36bda9..440581dc54 100644 --- a/tests/delete/models.py +++ b/tests/delete/models.py @@ -177,6 +177,10 @@ class B2(models.Model): delete_top = models.ForeignKey(DeleteTop, models.CASCADE) +class B3(models.Model): + restrict = models.ForeignKey(R, models.RESTRICT) + + class DeleteBottom(models.Model): b1 = models.ForeignKey(B1, models.RESTRICT) b2 = models.ForeignKey(B2, models.CASCADE) diff --git a/tests/delete/tests.py b/tests/delete/tests.py index f78e61704a..279788d2d3 100644 --- a/tests/delete/tests.py +++ b/tests/delete/tests.py @@ -8,10 +8,10 @@ from django.db.models.sql.constants import GET_ITERATOR_CHUNK_SIZE from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature from .models import ( - B1, B2, MR, A, Avatar, B, Base, Child, DeleteBottom, DeleteTop, GenericB1, - GenericB2, GenericDeleteBottom, HiddenUser, HiddenUserProfile, M, M2MFrom, - M2MTo, MRNull, Origin, P, Parent, R, RChild, RChildChild, Referrer, S, T, - User, create_a, get_default_r, + B1, B2, B3, MR, A, Avatar, B, Base, Child, DeleteBottom, DeleteTop, + GenericB1, GenericB2, GenericDeleteBottom, HiddenUser, HiddenUserProfile, + M, M2MFrom, M2MTo, MRNull, Origin, P, Parent, R, RChild, RChildChild, + Referrer, S, T, User, create_a, get_default_r, ) @@ -164,7 +164,18 @@ class OnDeleteTests(TestCase): a = create_a('restrict') msg = ( "Cannot delete some instances of model 'R' because they are " - "referenced through a restricted foreign key: 'A.restrict'." + "referenced through restricted foreign keys: 'A.restrict'." + ) + with self.assertRaisesMessage(RestrictedError, msg): + a.restrict.delete() + + def test_restrict_multiple(self): + a = create_a('restrict') + B3.objects.create(restrict=a.restrict) + msg = ( + "Cannot delete some instances of model 'R' because they are " + "referenced through restricted foreign keys: 'A.restrict', " + "'B3.restrict'." ) with self.assertRaisesMessage(RestrictedError, msg): a.restrict.delete() @@ -174,8 +185,8 @@ class OnDeleteTests(TestCase): a.restrict.p = P.objects.create() a.restrict.save() msg = ( - "Cannot delete some instances of model 'R' because they are " - "referenced through a restricted foreign key: 'A.restrict'." + "Cannot delete some instances of model 'P' because they are " + "referenced through restricted foreign keys: 'A.restrict'." ) with self.assertRaisesMessage(RestrictedError, msg): a.restrict.p.delete() @@ -203,7 +214,7 @@ class OnDeleteTests(TestCase): DeleteBottom.objects.create(b1=b1, b2=b2) msg = ( "Cannot delete some instances of model 'B1' because they are " - "referenced through a restricted foreign key: 'DeleteBottom.b1'." + "referenced through restricted foreign keys: 'DeleteBottom.b1'." ) with self.assertRaisesMessage(RestrictedError, msg): b1.delete() @@ -225,7 +236,7 @@ class OnDeleteTests(TestCase): GenericDeleteBottom.objects.create(generic_b1=generic_b1, generic_b2=generic_b2) msg = ( "Cannot delete some instances of model 'GenericB1' because they " - "are referenced through a restricted foreign key: " + "are referenced through restricted foreign keys: " "'GenericDeleteBottom.generic_b1'." ) with self.assertRaisesMessage(RestrictedError, msg):