Skip to content

Commit

Permalink
Send post_delete signals immediately
Browse files Browse the repository at this point in the history
In a normal relational construct, if you're listening for an event
that signals a child was deleted, you dont expect that the parent
was deleted already.

This change ensures that post_delete signals are fired immediately
after objects are deleted in the graph.
  • Loading branch information
dcramer committed Jan 14, 2013
1 parent 4720117 commit 272de9e
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 10 deletions.
22 changes: 12 additions & 10 deletions django/db/models/deletion.py
Expand Up @@ -75,17 +75,17 @@ def __init__(self, using):
self.using = using
# Initially, {model: set([instances])}, later values become lists.
self.data = {}
self.field_updates = {} # {model: {(field, value): set([instances])}}
self.field_updates = {} # {model: {(field, value): set([instances])}}
# fast_deletes is a list of queryset-likes that can be deleted without
# fetching the objects into memory.
self.fast_deletes = []
self.fast_deletes = []

# Tracks deletion-order dependency for databases without transactions
# or ability to defer constraint checks. Only concrete model classes
# should be included, as the dependencies exist only between actual
# database tables; proxy models are represented here by their concrete
# parent.
self.dependencies = {} # {model: set([models])}
self.dependencies = {} # {model: set([models])}

def add(self, objs, source=None, nullable=False, reverse_dependency=False):
"""
Expand Down Expand Up @@ -262,6 +262,14 @@ def sort(self):
self.data = SortedDict([(model, self.data[model])
for model in sorted_models])

def send_post_delete_signals(self, model, instances):
if model._meta.auto_created:
return
for obj in instances:
signals.post_delete.send(
sender=model, instance=obj, using=self.using
)

@force_managed
def delete(self):
# sort instance collections
Expand Down Expand Up @@ -300,13 +308,7 @@ def delete(self):
query = sql.DeleteQuery(model)
pk_list = [obj.pk for obj in instances]
query.delete_batch(pk_list, self.using)

# send post_delete signals
for model, obj in self.instances_with_model():
if not model._meta.auto_created:
signals.post_delete.send(
sender=model, instance=obj, using=self.using
)
self.send_post_delete_signals(model, instances)

# update collected instances
for model, instances_for_fieldvalues in six.iteritems(self.field_updates):
Expand Down
12 changes: 12 additions & 0 deletions tests/modeltests/delete/tests.py
Expand Up @@ -229,6 +229,18 @@ def log_pre_delete(sender, **kwargs):
models.signals.post_delete.disconnect(log_post_delete)
models.signals.post_delete.disconnect(log_pre_delete)

def test_relational_post_delete_signals_happen_before_parent_object(self):
def log_post_delete(instance, **kwargs):
self.assertTrue(R.objects.filter(pk=instance.r_id))

models.signals.post_delete.connect(log_post_delete, sender=S)

r = R.objects.create(pk=1)
S.objects.create(pk=1, r=r)
r.delete()

models.signals.post_delete.disconnect(log_post_delete)

@skipUnlessDBFeature("can_defer_constraint_checks")
def test_can_defer_constraint_checks(self):
u = User.objects.create(
Expand Down

0 comments on commit 272de9e

Please sign in to comment.