Fixed #13251 -- Made pre/post_delete signals dispatch the origin.
This commit is contained in:
parent
f1905db6c0
commit
fa235004dd
|
@ -116,7 +116,7 @@ def get_deleted_objects(objs, request, admin_site):
|
||||||
return [], {}, set(), []
|
return [], {}, set(), []
|
||||||
else:
|
else:
|
||||||
using = router.db_for_write(obj._meta.model)
|
using = router.db_for_write(obj._meta.model)
|
||||||
collector = NestedObjects(using=using)
|
collector = NestedObjects(using=using, origin=objs)
|
||||||
collector.collect(objs)
|
collector.collect(objs)
|
||||||
perms_needed = set()
|
perms_needed = set()
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ class Command(BaseCommand):
|
||||||
ct_info = []
|
ct_info = []
|
||||||
for ct in to_remove:
|
for ct in to_remove:
|
||||||
ct_info.append(' - Content type for %s.%s' % (ct.app_label, ct.model))
|
ct_info.append(' - Content type for %s.%s' % (ct.app_label, ct.model))
|
||||||
collector = NoFastDeleteCollector(using=using)
|
collector = NoFastDeleteCollector(using=using, origin=ct)
|
||||||
collector.collect([ct])
|
collector.collect([ct])
|
||||||
|
|
||||||
for obj_type, objs in collector.data.items():
|
for obj_type, objs in collector.data.items():
|
||||||
|
|
|
@ -987,7 +987,7 @@ class Model(metaclass=ModelBase):
|
||||||
"to None." % (self._meta.object_name, self._meta.pk.attname)
|
"to None." % (self._meta.object_name, self._meta.pk.attname)
|
||||||
)
|
)
|
||||||
using = using or router.db_for_write(self.__class__, instance=self)
|
using = using or router.db_for_write(self.__class__, instance=self)
|
||||||
collector = Collector(using=using)
|
collector = Collector(using=using, origin=self)
|
||||||
collector.collect([self], keep_parents=keep_parents)
|
collector.collect([self], keep_parents=keep_parents)
|
||||||
return collector.delete()
|
return collector.delete()
|
||||||
|
|
||||||
|
|
|
@ -76,8 +76,10 @@ def get_candidate_relations_to_delete(opts):
|
||||||
|
|
||||||
|
|
||||||
class Collector:
|
class Collector:
|
||||||
def __init__(self, using):
|
def __init__(self, using, origin=None):
|
||||||
self.using = using
|
self.using = using
|
||||||
|
# A Model or QuerySet object.
|
||||||
|
self.origin = origin
|
||||||
# Initially, {model: {instances}}, later values become lists.
|
# Initially, {model: {instances}}, later values become lists.
|
||||||
self.data = defaultdict(set)
|
self.data = defaultdict(set)
|
||||||
# {model: {(field, value): {instances}}}
|
# {model: {(field, value): {instances}}}
|
||||||
|
@ -404,7 +406,8 @@ class Collector:
|
||||||
for model, obj in self.instances_with_model():
|
for model, obj in self.instances_with_model():
|
||||||
if not model._meta.auto_created:
|
if not model._meta.auto_created:
|
||||||
signals.pre_delete.send(
|
signals.pre_delete.send(
|
||||||
sender=model, instance=obj, using=self.using
|
sender=model, instance=obj, using=self.using,
|
||||||
|
origin=self.origin,
|
||||||
)
|
)
|
||||||
|
|
||||||
# fast deletes
|
# fast deletes
|
||||||
|
@ -435,7 +438,8 @@ class Collector:
|
||||||
if not model._meta.auto_created:
|
if not model._meta.auto_created:
|
||||||
for obj in instances:
|
for obj in instances:
|
||||||
signals.post_delete.send(
|
signals.post_delete.send(
|
||||||
sender=model, instance=obj, using=self.using
|
sender=model, instance=obj, using=self.using,
|
||||||
|
origin=self.origin,
|
||||||
)
|
)
|
||||||
|
|
||||||
# update collected instances
|
# update collected instances
|
||||||
|
|
|
@ -753,7 +753,7 @@ class QuerySet:
|
||||||
del_query.query.select_related = False
|
del_query.query.select_related = False
|
||||||
del_query.query.clear_ordering(force=True)
|
del_query.query.clear_ordering(force=True)
|
||||||
|
|
||||||
collector = Collector(using=del_query.db)
|
collector = Collector(using=del_query.db, origin=self)
|
||||||
collector.collect(del_query)
|
collector.collect(del_query)
|
||||||
deleted, _rows_count = collector.delete()
|
deleted, _rows_count = collector.delete()
|
||||||
|
|
||||||
|
|
|
@ -195,6 +195,12 @@ Arguments sent with this signal:
|
||||||
``using``
|
``using``
|
||||||
The database alias being used.
|
The database alias being used.
|
||||||
|
|
||||||
|
``origin``
|
||||||
|
.. versionadded:: 4.1
|
||||||
|
|
||||||
|
The origin of the deletion being the instance of a ``Model`` or
|
||||||
|
``QuerySet`` class.
|
||||||
|
|
||||||
``post_delete``
|
``post_delete``
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -219,6 +225,12 @@ Arguments sent with this signal:
|
||||||
``using``
|
``using``
|
||||||
The database alias being used.
|
The database alias being used.
|
||||||
|
|
||||||
|
``origin``
|
||||||
|
.. versionadded:: 4.1
|
||||||
|
|
||||||
|
The origin of the deletion being the instance of a ``Model`` or
|
||||||
|
``QuerySet`` class.
|
||||||
|
|
||||||
``m2m_changed``
|
``m2m_changed``
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
|
|
@ -249,7 +249,9 @@ Serialization
|
||||||
Signals
|
Signals
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
* ...
|
* The :data:`~django.db.models.signals.pre_delete` and
|
||||||
|
:data:`~django.db.models.signals.post_delete` signals now dispatch the
|
||||||
|
``origin`` of the deletion.
|
||||||
|
|
||||||
Templates
|
Templates
|
||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
|
|
@ -30,3 +30,8 @@ class Book(models.Model):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class Page(models.Model):
|
||||||
|
book = models.ForeignKey(Book, on_delete=models.CASCADE)
|
||||||
|
text = models.TextField()
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django.dispatch import receiver
|
||||||
from django.test import SimpleTestCase, TestCase
|
from django.test import SimpleTestCase, TestCase
|
||||||
from django.test.utils import isolate_apps
|
from django.test.utils import isolate_apps
|
||||||
|
|
||||||
from .models import Author, Book, Car, Person
|
from .models import Author, Book, Car, Page, Person
|
||||||
|
|
||||||
|
|
||||||
class BaseSignalSetup:
|
class BaseSignalSetup:
|
||||||
|
@ -118,9 +118,9 @@ class SignalTests(BaseSignalSetup, TestCase):
|
||||||
def test_delete_signals(self):
|
def test_delete_signals(self):
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
def pre_delete_handler(signal, sender, instance, **kwargs):
|
def pre_delete_handler(signal, sender, instance, origin, **kwargs):
|
||||||
data.append(
|
data.append(
|
||||||
(instance, sender, instance.id is None)
|
(instance, sender, instance.id is None, origin)
|
||||||
)
|
)
|
||||||
|
|
||||||
# #8285: signals can be any callable
|
# #8285: signals can be any callable
|
||||||
|
@ -128,9 +128,9 @@ class SignalTests(BaseSignalSetup, TestCase):
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
def __call__(self, signal, sender, instance, **kwargs):
|
def __call__(self, signal, sender, instance, origin, **kwargs):
|
||||||
self.data.append(
|
self.data.append(
|
||||||
(instance, sender, instance.id is None)
|
(instance, sender, instance.id is None, origin)
|
||||||
)
|
)
|
||||||
post_delete_handler = PostDeleteHandler(data)
|
post_delete_handler = PostDeleteHandler(data)
|
||||||
|
|
||||||
|
@ -140,8 +140,8 @@ class SignalTests(BaseSignalSetup, TestCase):
|
||||||
p1 = Person.objects.create(first_name="John", last_name="Smith")
|
p1 = Person.objects.create(first_name="John", last_name="Smith")
|
||||||
p1.delete()
|
p1.delete()
|
||||||
self.assertEqual(data, [
|
self.assertEqual(data, [
|
||||||
(p1, Person, False),
|
(p1, Person, False, p1),
|
||||||
(p1, Person, False),
|
(p1, Person, False, p1),
|
||||||
])
|
])
|
||||||
data[:] = []
|
data[:] = []
|
||||||
|
|
||||||
|
@ -152,8 +152,8 @@ class SignalTests(BaseSignalSetup, TestCase):
|
||||||
p2.save()
|
p2.save()
|
||||||
p2.delete()
|
p2.delete()
|
||||||
self.assertEqual(data, [
|
self.assertEqual(data, [
|
||||||
(p2, Person, False),
|
(p2, Person, False, p2),
|
||||||
(p2, Person, False),
|
(p2, Person, False, p2),
|
||||||
])
|
])
|
||||||
data[:] = []
|
data[:] = []
|
||||||
|
|
||||||
|
@ -167,6 +167,78 @@ class SignalTests(BaseSignalSetup, TestCase):
|
||||||
signals.pre_delete.disconnect(pre_delete_handler)
|
signals.pre_delete.disconnect(pre_delete_handler)
|
||||||
signals.post_delete.disconnect(post_delete_handler)
|
signals.post_delete.disconnect(post_delete_handler)
|
||||||
|
|
||||||
|
def test_delete_signals_origin_model(self):
|
||||||
|
data = []
|
||||||
|
|
||||||
|
def pre_delete_handler(signal, sender, instance, origin, **kwargs):
|
||||||
|
data.append((sender, origin))
|
||||||
|
|
||||||
|
def post_delete_handler(signal, sender, instance, origin, **kwargs):
|
||||||
|
data.append((sender, origin))
|
||||||
|
|
||||||
|
person = Person.objects.create(first_name='John', last_name='Smith')
|
||||||
|
book = Book.objects.create(name='Rayuela')
|
||||||
|
Page.objects.create(text='Page 1', book=book)
|
||||||
|
Page.objects.create(text='Page 2', book=book)
|
||||||
|
|
||||||
|
signals.pre_delete.connect(pre_delete_handler, weak=False)
|
||||||
|
signals.post_delete.connect(post_delete_handler, weak=False)
|
||||||
|
try:
|
||||||
|
# Instance deletion.
|
||||||
|
person.delete()
|
||||||
|
self.assertEqual(data, [(Person, person), (Person, person)])
|
||||||
|
data[:] = []
|
||||||
|
# Cascade deletion.
|
||||||
|
book.delete()
|
||||||
|
self.assertEqual(data, [
|
||||||
|
(Page, book),
|
||||||
|
(Page, book),
|
||||||
|
(Book, book),
|
||||||
|
(Page, book),
|
||||||
|
(Page, book),
|
||||||
|
(Book, book),
|
||||||
|
])
|
||||||
|
finally:
|
||||||
|
signals.pre_delete.disconnect(pre_delete_handler)
|
||||||
|
signals.post_delete.disconnect(post_delete_handler)
|
||||||
|
|
||||||
|
def test_delete_signals_origin_queryset(self):
|
||||||
|
data = []
|
||||||
|
|
||||||
|
def pre_delete_handler(signal, sender, instance, origin, **kwargs):
|
||||||
|
data.append((sender, origin))
|
||||||
|
|
||||||
|
def post_delete_handler(signal, sender, instance, origin, **kwargs):
|
||||||
|
data.append((sender, origin))
|
||||||
|
|
||||||
|
Person.objects.create(first_name='John', last_name='Smith')
|
||||||
|
book = Book.objects.create(name='Rayuela')
|
||||||
|
Page.objects.create(text='Page 1', book=book)
|
||||||
|
Page.objects.create(text='Page 2', book=book)
|
||||||
|
|
||||||
|
signals.pre_delete.connect(pre_delete_handler, weak=False)
|
||||||
|
signals.post_delete.connect(post_delete_handler, weak=False)
|
||||||
|
try:
|
||||||
|
# Queryset deletion.
|
||||||
|
qs = Person.objects.all()
|
||||||
|
qs.delete()
|
||||||
|
self.assertEqual(data, [(Person, qs), (Person, qs)])
|
||||||
|
data[:] = []
|
||||||
|
# Cascade deletion.
|
||||||
|
qs = Book.objects.all()
|
||||||
|
qs.delete()
|
||||||
|
self.assertEqual(data, [
|
||||||
|
(Page, qs),
|
||||||
|
(Page, qs),
|
||||||
|
(Book, qs),
|
||||||
|
(Page, qs),
|
||||||
|
(Page, qs),
|
||||||
|
(Book, qs),
|
||||||
|
])
|
||||||
|
finally:
|
||||||
|
signals.pre_delete.disconnect(pre_delete_handler)
|
||||||
|
signals.post_delete.disconnect(post_delete_handler)
|
||||||
|
|
||||||
def test_decorators(self):
|
def test_decorators(self):
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue