WesDec
WesDec

Reputation: 2158

Overridden delete method of child model does not get called when deleting parent?

I have a two models, each with an image. One has a foreign key to the parent. When I delete the parent I want to delete the parent and child along with their image file on the disk. To do that I override the delete method:

class MyModelParent(models.Model):
   image = models.ImageField(upload_to = "images/" )

   def delete(self, *args, **kwargs):     
      if self.image:
          self.image.delete()
      super(MyModelParent, self).delete(*args, **kwargs)

class MyModelChild(models.Model):
   parent = models.ForeignKey(MyModelParent)
   image = models.ImageField(upload_to = "images/" )

   def delete(self, *args, **kwargs):     
      if self.image:
          self.image.delete()
      super(MyModelChild, self).delete(*args, **kwargs)

When I delete an instance of MyModelParent, its overridden delete() gets called, but not the ones of the children (even though they get deleted from the DB), so their images remain on the disk. Anyone know what I am doing wrong?

Upvotes: 9

Views: 2179

Answers (3)

aganders3
aganders3

Reputation: 5945

You're not doing anything wrong. The problem is that the delete() method of any child is not called when cascading.

From the documentation for delete (cascaded delete uses the database query):

The delete() method does a bulk delete and does not call any delete() methods on your models. It does, however, emit the pre_delete and post_delete signals for all deleted objects (including cascaded deletions).

However, the pre_delete and post_delete signals are still sent. What you need to do is connect a callback that will listen for one of these signals and do any extra cleanup needed. See the relevant documentation for more information on connecting signals.

Upvotes: 13

Chris Pratt
Chris Pratt

Reputation: 239270

@Nate's answer covers part of the problem but just the low level part. Deleting the parent should cause the child (which has an implicit foreign key to the parent) to also be deleted in the cascade, but the child's delete() is not run in this scenario.

All that is correct, but more to the point, it's because you're deleting the parent and not the child. In inheritiance, the parent knows nothing about the child's methods, so as far as it's concerned they might as well not exist, and hence are ignored. If you want the custom child method to run, you must run it from the child not the parent.

Upvotes: 1

Nate
Nate

Reputation: 12819

This behavior is due to the fact that (from Making Queries | Deleting objects, in the doc) the CASCADE option of a ForeignKey's on_delete keyword argument (which is the default) merely sets a database constraint:

Keep in mind that this will, whenever possible, be executed purely in SQL, and so the delete() methods of individual object instances will not necessarily be called during the process. If you've provided a custom delete() method on a model class and want to ensure that it is called, you will need to "manually" delete instances of that model (e.g., by iterating over a QuerySet and calling delete() on each object individually) rather than using the bulk delete() method of a QuerySet.

When Django deletes an object, by default it emulates the behavior of the SQL constraint ON DELETE CASCADE -- in other words, any objects which had foreign keys pointing at the object to be deleted will be deleted along with it.

Upvotes: 2

Related Questions