Refs #27852 -- Fixed object deletion to show all protected related objects rather than just the first one.
Thanks Anton Samarchyan for the initial patch.
This commit is contained in:
parent
e348ab0d43
commit
ab3cbd8b9a
|
@ -265,6 +265,7 @@ class Collector:
|
|||
if keep_parents:
|
||||
parents = set(model._meta.get_parent_list())
|
||||
model_fast_deletes = defaultdict(list)
|
||||
protected_objects = defaultdict(list)
|
||||
for related in get_candidate_relations_to_delete(model._meta):
|
||||
# Preserve parent reverse relationships if keep_parents=True.
|
||||
if keep_parents and related.model in parents:
|
||||
|
@ -292,7 +293,23 @@ class Collector:
|
|||
))
|
||||
sub_objs = sub_objs.only(*tuple(referenced_fields))
|
||||
if sub_objs:
|
||||
field.remote_field.on_delete(self, field, sub_objs, self.using)
|
||||
try:
|
||||
field.remote_field.on_delete(self, field, sub_objs, self.using)
|
||||
except ProtectedError as error:
|
||||
key = "'%s.%s'" % (
|
||||
error.protected_objects[0].__class__.__name__,
|
||||
field.name,
|
||||
)
|
||||
protected_objects[key] += error.protected_objects
|
||||
if protected_objects:
|
||||
raise ProtectedError(
|
||||
'Cannot delete some instances of model %r because they are '
|
||||
'referenced through protected foreign keys: %s.' % (
|
||||
model.__name__,
|
||||
', '.join(protected_objects),
|
||||
),
|
||||
chain.from_iterable(protected_objects.values()),
|
||||
)
|
||||
for related_model, related_fields in model_fast_deletes.items():
|
||||
batches = self.get_del_batches(new_objs, related_fields)
|
||||
for batch in batches:
|
||||
|
|
|
@ -68,6 +68,10 @@ class A(models.Model):
|
|||
o2o_setnull = models.ForeignKey(R, models.SET_NULL, null=True, related_name="o2o_nullable_set")
|
||||
|
||||
|
||||
class B(models.Model):
|
||||
protect = models.ForeignKey(R, models.PROTECT)
|
||||
|
||||
|
||||
def create_a(name):
|
||||
a = A(name=name)
|
||||
for name in ('auto', 'auto_nullable', 'setvalue', 'setnull', 'setdefault',
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
from math import ceil
|
||||
|
||||
from django.db import IntegrityError, connection, models
|
||||
from django.db.models.deletion import Collector, RestrictedError
|
||||
from django.db.models.deletion import (
|
||||
Collector, ProtectedError, RestrictedError,
|
||||
)
|
||||
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, Base, Child, DeleteBottom, DeleteTop, GenericB1,
|
||||
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,
|
||||
|
@ -72,11 +74,22 @@ class OnDeleteTests(TestCase):
|
|||
a = create_a('protect')
|
||||
msg = (
|
||||
"Cannot delete some instances of model 'R' because they are "
|
||||
"referenced through a protected foreign key: 'A.protect'"
|
||||
"referenced through protected foreign keys: 'A.protect'."
|
||||
)
|
||||
with self.assertRaisesMessage(IntegrityError, msg):
|
||||
a.protect.delete()
|
||||
|
||||
def test_protect_multiple(self):
|
||||
a = create_a('protect')
|
||||
B.objects.create(protect=a.protect)
|
||||
msg = (
|
||||
"Cannot delete some instances of model 'R' because they are "
|
||||
"referenced through protected foreign keys: 'A.protect', "
|
||||
"'B.protect'."
|
||||
)
|
||||
with self.assertRaisesMessage(ProtectedError, msg):
|
||||
a.protect.delete()
|
||||
|
||||
def test_do_nothing(self):
|
||||
# Testing DO_NOTHING is a bit harder: It would raise IntegrityError for a normal model,
|
||||
# so we connect to pre_delete and set the fk to a known value.
|
||||
|
|
Loading…
Reference in New Issue