Fixed #32107 -- Fixed ProtectedError.protected_objects and RestrictedError.restricted_objects.

Regression in 4ca5c565f4 and
ab3cbd8b9a.

Thanks Vitaliy Yelnik for the report.
This commit is contained in:
Hasan Ramezani 2020-10-18 23:01:56 +02:00 committed by Mariusz Felisiak
parent 0eee5c1b9c
commit 3b1746d519
3 changed files with 31 additions and 14 deletions

View File

@ -305,7 +305,7 @@ class Collector:
model.__name__, model.__name__,
', '.join(protected_objects), ', '.join(protected_objects),
), ),
chain.from_iterable(protected_objects.values()), set(chain.from_iterable(protected_objects.values())),
) )
for related_model, related_fields in model_fast_deletes.items(): for related_model, related_fields in model_fast_deletes.items():
batches = self.get_del_batches(new_objs, related_fields) batches = self.get_del_batches(new_objs, related_fields)
@ -340,7 +340,7 @@ class Collector:
model.__name__, model.__name__,
', '.join(restricted_objects), ', '.join(restricted_objects),
), ),
chain.from_iterable(restricted_objects.values()), set(chain.from_iterable(restricted_objects.values())),
) )
def related_objects(self, related_model, related_fields, objs): def related_objects(self, related_model, related_fields, objs):

View File

@ -39,3 +39,9 @@ Bugfixes
:class:`~django.contrib.postgres.constraints.ExclusionConstraint` with key :class:`~django.contrib.postgres.constraints.ExclusionConstraint` with key
transforms for :class:`~django.db.models.JSONField` in ``expressions`` transforms for :class:`~django.db.models.JSONField` in ``expressions``
(:ticket:`32096`). (:ticket:`32096`).
* Fixed a regression in Django 3.1 where
:exc:`ProtectedError.protected_objects <django.db.models.ProtectedError>` and
:exc:`RestrictedError.restricted_objects <django.db.models.RestrictedError>`
attributes returned iterators instead of :py:class:`set` of objects
(:ticket:`32107`).

View File

@ -75,19 +75,21 @@ class OnDeleteTests(TestCase):
"Cannot delete some instances of model 'R' because they are " "Cannot delete some instances of model 'R' because they are "
"referenced through protected foreign keys: 'A.protect'." "referenced through protected foreign keys: 'A.protect'."
) )
with self.assertRaisesMessage(ProtectedError, msg): with self.assertRaisesMessage(ProtectedError, msg) as cm:
a.protect.delete() a.protect.delete()
self.assertEqual(cm.exception.protected_objects, {a})
def test_protect_multiple(self): def test_protect_multiple(self):
a = create_a('protect') a = create_a('protect')
B.objects.create(protect=a.protect) b = B.objects.create(protect=a.protect)
msg = ( msg = (
"Cannot delete some instances of model 'R' because they are " "Cannot delete some instances of model 'R' because they are "
"referenced through protected foreign keys: 'A.protect', " "referenced through protected foreign keys: 'A.protect', "
"'B.protect'." "'B.protect'."
) )
with self.assertRaisesMessage(ProtectedError, msg): with self.assertRaisesMessage(ProtectedError, msg) as cm:
a.protect.delete() a.protect.delete()
self.assertEqual(cm.exception.protected_objects, {a, b})
def test_protect_path(self): def test_protect_path(self):
a = create_a('protect') a = create_a('protect')
@ -97,8 +99,9 @@ class OnDeleteTests(TestCase):
"Cannot delete some instances of model 'P' because they are " "Cannot delete some instances of model 'P' because they are "
"referenced through protected foreign keys: 'R.p'." "referenced through protected foreign keys: 'R.p'."
) )
with self.assertRaisesMessage(ProtectedError, msg): with self.assertRaisesMessage(ProtectedError, msg) as cm:
a.protect.p.delete() a.protect.p.delete()
self.assertEqual(cm.exception.protected_objects, {a})
def test_do_nothing(self): def test_do_nothing(self):
# Testing DO_NOTHING is a bit harder: It would raise IntegrityError for a normal model, # Testing DO_NOTHING is a bit harder: It would raise IntegrityError for a normal model,
@ -176,19 +179,21 @@ class OnDeleteTests(TestCase):
"Cannot delete some instances of model 'R' because they are " "Cannot delete some instances of model 'R' because they are "
"referenced through restricted foreign keys: 'A.restrict'." "referenced through restricted foreign keys: 'A.restrict'."
) )
with self.assertRaisesMessage(RestrictedError, msg): with self.assertRaisesMessage(RestrictedError, msg) as cm:
a.restrict.delete() a.restrict.delete()
self.assertEqual(cm.exception.restricted_objects, {a})
def test_restrict_multiple(self): def test_restrict_multiple(self):
a = create_a('restrict') a = create_a('restrict')
B3.objects.create(restrict=a.restrict) b3 = B3.objects.create(restrict=a.restrict)
msg = ( msg = (
"Cannot delete some instances of model 'R' because they are " "Cannot delete some instances of model 'R' because they are "
"referenced through restricted foreign keys: 'A.restrict', " "referenced through restricted foreign keys: 'A.restrict', "
"'B3.restrict'." "'B3.restrict'."
) )
with self.assertRaisesMessage(RestrictedError, msg): with self.assertRaisesMessage(RestrictedError, msg) as cm:
a.restrict.delete() a.restrict.delete()
self.assertEqual(cm.exception.restricted_objects, {a, b3})
def test_restrict_path_cascade_indirect(self): def test_restrict_path_cascade_indirect(self):
a = create_a('restrict') a = create_a('restrict')
@ -198,8 +203,9 @@ class OnDeleteTests(TestCase):
"Cannot delete some instances of model 'P' because they are " "Cannot delete some instances of model 'P' because they are "
"referenced through restricted foreign keys: 'A.restrict'." "referenced through restricted foreign keys: 'A.restrict'."
) )
with self.assertRaisesMessage(RestrictedError, msg): with self.assertRaisesMessage(RestrictedError, msg) as cm:
a.restrict.p.delete() a.restrict.p.delete()
self.assertEqual(cm.exception.restricted_objects, {a})
# Object referenced also with CASCADE relationship can be deleted. # Object referenced also with CASCADE relationship can be deleted.
a.cascade.p = a.restrict.p a.cascade.p = a.restrict.p
a.cascade.save() a.cascade.save()
@ -221,13 +227,14 @@ class OnDeleteTests(TestCase):
delete_top = DeleteTop.objects.create() delete_top = DeleteTop.objects.create()
b1 = B1.objects.create(delete_top=delete_top) b1 = B1.objects.create(delete_top=delete_top)
b2 = B2.objects.create(delete_top=delete_top) b2 = B2.objects.create(delete_top=delete_top)
DeleteBottom.objects.create(b1=b1, b2=b2) delete_bottom = DeleteBottom.objects.create(b1=b1, b2=b2)
msg = ( msg = (
"Cannot delete some instances of model 'B1' because they are " "Cannot delete some instances of model 'B1' because they are "
"referenced through restricted foreign keys: 'DeleteBottom.b1'." "referenced through restricted foreign keys: 'DeleteBottom.b1'."
) )
with self.assertRaisesMessage(RestrictedError, msg): with self.assertRaisesMessage(RestrictedError, msg) as cm:
b1.delete() b1.delete()
self.assertEqual(cm.exception.restricted_objects, {delete_bottom})
self.assertTrue(DeleteTop.objects.exists()) self.assertTrue(DeleteTop.objects.exists())
self.assertTrue(B1.objects.exists()) self.assertTrue(B1.objects.exists())
self.assertTrue(B2.objects.exists()) self.assertTrue(B2.objects.exists())
@ -243,14 +250,18 @@ class OnDeleteTests(TestCase):
delete_top = DeleteTop.objects.create() delete_top = DeleteTop.objects.create()
generic_b1 = GenericB1.objects.create(generic_delete_top=delete_top) generic_b1 = GenericB1.objects.create(generic_delete_top=delete_top)
generic_b2 = GenericB2.objects.create(generic_delete_top=delete_top) generic_b2 = GenericB2.objects.create(generic_delete_top=delete_top)
GenericDeleteBottom.objects.create(generic_b1=generic_b1, generic_b2=generic_b2) generic_delete_bottom = GenericDeleteBottom.objects.create(
generic_b1=generic_b1,
generic_b2=generic_b2,
)
msg = ( msg = (
"Cannot delete some instances of model 'GenericB1' because they " "Cannot delete some instances of model 'GenericB1' because they "
"are referenced through restricted foreign keys: " "are referenced through restricted foreign keys: "
"'GenericDeleteBottom.generic_b1'." "'GenericDeleteBottom.generic_b1'."
) )
with self.assertRaisesMessage(RestrictedError, msg): with self.assertRaisesMessage(RestrictedError, msg) as cm:
generic_b1.delete() generic_b1.delete()
self.assertEqual(cm.exception.restricted_objects, {generic_delete_bottom})
self.assertTrue(DeleteTop.objects.exists()) self.assertTrue(DeleteTop.objects.exists())
self.assertTrue(GenericB1.objects.exists()) self.assertTrue(GenericB1.objects.exists())
self.assertTrue(GenericB2.objects.exists()) self.assertTrue(GenericB2.objects.exists())