Opened 17 years ago

Last modified 8 months ago

#6870 new Bug

pre_delete should be sent before collecting ForeignKey relationships

Reported by: Waldemar Kornewald Owned by: nobody
Component: Database layer (models, ORM) Version: dev
Severity: Normal Keywords: pre_delete signals related
Cc: cgieringer, Chris Chambers, code@…, jakerothenbuhler@…, jashugan, Ülgen Sarıkavak Triage Stage: Accepted
Has patch: yes Needs documentation: yes
Needs tests: no Patch needs improvement: yes
Easy pickings: no UI/UX: no

Description

If you have:

class MyModel(Model):
    user = ForeignKey(User, related_name='my_models', null=True)

You can't use a pre_delete signal to set user.my_models.clear() because the check for the related models is done before the signal is sent. It would be great if that were done after sending the signal.

Attachments (2)

6870.pre_delete. r13248.diff (5.2 KB ) - added by cgieringer 15 years ago.
Adds a pre_signal argument to _collect_sub_objects to allow the notification of listeners to pre_deletes before related objects are collected.
6870.pre_delete.r13248.diff (8.3 KB ) - added by cgieringer 15 years ago.
Removed "".format to make Python 2.4 compliant

Download all attachments as: .zip

Change History (19)

comment:1 by James Bennett, 17 years ago

This feels like a symptom of a different problem, namely the inability to emulate anything other than ON DELETE CASCADE; if we had that you wouldn't be trying to use signals to do this.

comment:2 by Eric Holscher, 16 years ago

Component: UncategorizedCore framework
Triage Stage: UnreviewedDesign decision needed

comment:3 by Ilya Semenov, 15 years ago

While it actually is a symptom of a different (yet unsolved!) problem, the original suggestion makes sense per se. It shouldn't be disregarded just because that will magically allow to do some (useful) unrelated things, right?

comment:4 by Alexey Kinyov, 15 years ago

It seems, there's alternative approach for such usecase #12899, which I belive, is more consistent.

by cgieringer, 15 years ago

Adds a pre_signal argument to _collect_sub_objects to allow the notification of listeners to pre_deletes before related objects are collected.

comment:5 by cgieringer, 15 years ago

Cc: cgieringer added
Has patch: set
Needs documentation: set
Needs tests: set

I agree with the ticket creator that django should send the pre_delete signal at a time when it is still possible to modify the model in a way that affects deletion. It seems that the contract of all pre_xx signal should include informing listeners before any undoable changes have occurred in the deletion, save, etc.

The most common usage for a correctly behaving pre_delete signal is apparently to remove related objects to prevent cascade deletion, and the feature requested in #7539 would eventually provide another means to that usage. But that possible feature doesn't change the fact that pre_delete breaks its contract (or what it seems its contract should be) by collecting the related objects for deletion before informing listeners that the objects will be deleted, thereby offering no consistent way to prevent cascade deletion. The only way to get around this limitation is to define both a custom model delete and manager delete for each model requiring this functionality, which is a tedious and error-prone requirement.

Currently the pre_delete signal is sent from django.db.models.query.delete_objects. The least-complicated place to send the signal while meeting its contract is from the beginning of _collect_sub_objects, because this is both the first place that related objects are seen (as that method traverses relations from the original model) and the last place that a change to a model's related objects can be made before they are cached in seen_objects and then sent for deletion.

I have attached a patch which adds a pre_signal argument to django.db.model.base.Model's _collect_sub_objects. The argument should be one of Django's signals from django.db.models.signals, and it will be called at the beginning of the method if the model was not autocreated. The argument is called pre_signal because although _collect_sub_objects is presently only used for finding related objects for deletion, it could possibly be used for some other purpose for which a different pre_xx signal should be sent. The patch adds _collect_sub_objects(..., pre_signal=pre_signal) to recursions and _collect_sub_objects(..., pre_signal=pre_delete) to the two places the method is called. It removes the sending of pre_delete from delete_objects.

Thank you to all the Django developers, this framework is my favorite piece of software.

comment:6 by cgieringer, 15 years ago

Keywords: pre_delete signals related added
Needs tests: unset

I added a test that deletes a model and asserts that another model with a foreign key to it had the opportunity to be rescued from deletion via a pre_delete signal

by cgieringer, 15 years ago

Attachment: 6870.pre_delete.r13248.diff added

Removed "".format to make Python 2.4 compliant

comment:7 by Chris Chambers, 14 years ago

Cc: Chris Chambers added

comment:8 by Carl Meyer, 14 years ago

Patch needs improvement: set
Triage Stage: Design decision neededAccepted

Now that #7539 is fixed, the use-case scope here is reduced. Nevertheless, moving the signal to fire before related objects are collected is not particularly difficult, seems reasonable, and increases the usefulness of the signal. In my preliminary test, doing so causes only one test failure, and that is spurious (because pre_delete signal is being used to test the order of actual deletion).

The only concern I have here is the subtle backwards-incompatibility reflected by that test failure: pre-delete signals will now fire in a different order than previously. OTOH, the documentation makes no guarantees about order of pre_delete signals firing, and if anything the order after this change is more logical (it would now be the order of collection).

The patch will look quite different now that #7539 has landed. Here's an initial take on it, but I haven't yet resolved the failing deletion-order test: https://github.com/carljm/django/compare/master...t6870

comment:9 by Julien Phalip, 14 years ago

Component: Core frameworkDatabase layer (models, ORM)

See a possibly related feature request in #13251.

comment:10 by Andreas, 14 years ago

Cc: code@… added

comment:11 by Julien Phalip, 14 years ago

Severity: Normal
Type: Bug

comment:12 by Jake Rothenbuhler, 13 years ago

Cc: jakerothenbuhler@… added
Easy pickings: unset
UI/UX: unset

comment:13 by jashugan, 13 years ago

Cc: jashugan added

The original use-case still exists for generic relations, which does not support the on_delete functionality given by #7593.

comment:14 by Łukasz Langa, 13 years ago

There are more use-cases than that. One of those is Admin's object deletion. Let me give you an example:

I have a Team model and a Member model. Teams have Members. Trivial stuff. I also log every change on a Team or its Members by creating LogEntry objects. So, when a Member is deleted, I have a pre_delete signal that saves the LogEntry ("member parted") for the related Team. Now, when I try to delete a Team from the Admin, during deletion of existing Members, log entries are created by the pre_delete signal and the transaction fails.

Currently as a workaround you have to either let orphaned objects litter the DB by using on_delete or to create another pre_delete signal for the Team model which does instance.logentry_set.all().delete().

comment:15 by anonymous, 12 years ago

I've run in the same problem, not easy to debug. I'd be great to see it fixed someday.

comment:16 by Daniel Whitney, 6 years ago

Will this be addressed at any point? I've been running into this issue for the past month.

comment:17 by Ülgen Sarıkavak, 8 months ago

Cc: Ülgen Sarıkavak added
Note: See TracTickets for help on using tickets.
Back to Top