Leon
Leon

Reputation: 6544

Delete images on foreign key from disk

models.py:

class Car(models.Model):
    ...

class Pictures(models.Model):
    car = models.ForeignKey(Car, related_name='pictures')
    width = models.PositiveIntegerField(editable=False, default=780)
    height = models.PositiveIntegerField(editable=False, default=585)
    image = models.ImageField(upload_to = get_file_path, max_length=64, height_field='height', width_field='width')

    def __unicode__(self):
        return str(self.id)

    def delete(self, *args, **kwargs): 
        storage, path = self.image.storage, self.image.path
        super(Pictures, self).delete(*args, **kwargs)
        storage.delete(path)

It works nice (I delete one picture from admin panel and this picture is automatically deleted from disk).

But when I deleted Car object through admin panel, images are not removed from disk.

How to fix that?

Thanks!

Upvotes: 1

Views: 182

Answers (2)

Kevin Cherepski
Kevin Cherepski

Reputation: 1483

I started looking into this and found some interesting things regarding the admin and deletion of objects.

When deleting an object from the admin, the following function is called,

django/contrib/admin/options.py -> delete_model()

which in turn calls obj.delete() with obj being the current object being deleted.

The delete method for an object then runs the following code,

collector = Collector(using=using)
collector.collect([self])
collector.delete()

The collector object now has an attribute 'data' which contains all of the related objects. When collector.delete() gets run, it executes the function query.delete_batch(pk_list, self.using) which does a bulk deletion using the argument pk_list which is a list of primary keys for related objects being deleted. The bulk deletion function in turn doesn't call the delete method of each related object being deleted.

The good thing here is that the pre_save and post_save signals do get called for all related objects so we could move your custom deletion code into either a pre_save or post_save signal for the Pictures model.

This should work I'm thinking but I haven't had a chance to test.

Upvotes: 0

Ngenator
Ngenator

Reputation: 11259

I'm sure the problem here is that the ORM uses ON DELETE CASCADE to have the database handle removing the relations, meaning your delete method won't get called.

You could probably just apply the same technique you used here and do:

class Car(models.Model):
    ...

    def delete(self, *args, **kwargs): 
        for picture in self.pictures.all():
            storage, path = picture.image.storage, picture.image.path
            storage.delete(path)
        super(Car, self).delete(*args, **kwargs)

However, you are better off using signals instead of overriding the delete methods https://docs.djangoproject.com/en/dev/ref/signals/#post-delete

Note that the delete() method for an object is not necessarily called when deleting objects in bulk using a QuerySet. To ensure customized delete logic gets executed, you can use pre_delete and/or post_delete signals.

Upvotes: 1

Related Questions