Fixed #32107 -- Fixed ProtectedError.protected_objects and RestrictedError.restricted_objects.
Regression in4ca5c565f4
andab3cbd8b9a
. Thanks Vitaliy Yelnik for the report.
This commit is contained in:
parent
0eee5c1b9c
commit
3b1746d519
|
@ -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):
|
||||||
|
|
|
@ -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`).
|
||||||
|
|
|
@ -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())
|
||||||
|
|
Loading…
Reference in New Issue