26

I have a model - Product, which contains a thumbnail image. I have another model which contains images associated with the product - ProductImage. I want to delete both the thumbnail and the images from the server when the product instance is deleted, and for a while this seemed to worked, but not anymore.

Relevant code...

Class Product(models.Model):
    title = Charfield
    thumbnail = ImageField(upload_to='thumbnails/', verbose_name='thumbnail', blank=True, )

Class ProductImage(models.Model):
    product = models.ForeignKey(plant, default=None, related_name='images')
    image = models.ImageField(upload_to='images/', verbose_name='image',)

The following delete method (in the product class) was working, but I changed my code and it no longer works - and from what i have read it is best practice to use post_delete, rather then override delete()

def delete(self):
    images = ProductImage.objects.filter(product=self)
    if images:
        for image in images:
            image.delete()
    super(Product, self).delete()

How can I rewrite a delete method which will achieve what I want? I have tried to use post_delete but so far I have been unsuccessful because I am not sure how to apply it when it comes to deleting the ProductImage instance...

tim
  • 379
  • 2
  • 5
  • 14

3 Answers3

47

And here's an example with the post_delete:

import os
from django.db import models

def _delete_file(path):
   """ Deletes file from filesystem. """
   if os.path.isfile(path):
       os.remove(path)

@receiver(models.signals.post_delete, sender=ProductImage)
def delete_file(sender, instance, *args, **kwargs):
    """ Deletes image files on `post_delete` """
    if instance.image:
        _delete_file(instance.image.path)

@receiver(models.signals.post_delete, sender=Product)
def delete_file(sender, instance, *args, **kwargs):
    """ Deletes thumbnail files on `post_delete` """
    if instance.thumbnail:
        _delete_file(instance.thumbnail.path)

Overriding the delete() method:

class Product(models.Model):
    ...

    def delete(self):
        images = ProductImage.objects.filter(product=self)
        for image in images:
            image.delete()
        self.thumbnail.delete()
        super(Product, self).delete()


class ProductImage(models.Model):
    ...

    def delete(self):
        self.image.delete()
        super(ProductImage, self).delete()

Read about Cascade delete: docs

sobolevn
  • 16,714
  • 6
  • 62
  • 60
  • Is `post_delete` preferable? If I overide `delete()` as above, the thumbnail is deleted but the related images are not, unless its a problem with the view – tim Oct 12 '15 at 12:48
  • I personally prefer signals. – sobolevn Oct 12 '15 at 12:54
  • Thanks, the `delete()` override works well, I couldn't get `post_delete` to successfully delete the images, only the thumbnail – tim Oct 12 '15 at 13:14
  • I am trying the post-delete signal approach but I can't get that method to trigger at all. Do I have to register the method somehow or has this changed in Django 1.9 perhaps? – jonalv May 10 '16 at 13:25
  • @jonalv, I guess nothing has really changed: https://docs.djangoproject.com/en/1.9/ref/signals/#post-delete – sobolevn May 10 '16 at 21:33
  • 3
    `post_delete` seems preferable because the override of method `delete()` doesn't work for me with the _delete admin action_. – Davide Brunato Feb 06 '18 at 16:21
  • if I put post_delete in my models.py file then it throws error at sender, Error:NameError: name 'Product' is not defined. so how to resolve it – chirag soni May 07 '19 at 09:27
  • Very helpful, appreciate the help.. – Thobs More Jan 30 '22 at 19:00
  • 1
    Worth mentioning that deleting the file using `os.remove` wouldn't work depending on your file storage (e.g. it wouldn't on S3 or Cloudfiles). It is more appropriate to use the [file field's delete method instead](https://docs.djangoproject.com/en/4.0/ref/models/fields/#django.db.models.fields.files.FieldFile.delete). – Michel Sabchuk Apr 28 '22 at 11:22
5

In 1.11 Django. Code work!

import os
from django.db import models
from django.utils import timezone

from django.db.models.signals import pre_delete
from django.dispatch.dispatcher import receiver

class Post(models.Model):
    category = models.ForeignKey(Category, verbose_name='Категория')
    title = models.CharField('Заголовок', max_length=200, unique=True)
    url = models.CharField('ЧПУ', max_length=200, unique=True)
    photo = models.ImageField('Изображение', upload_to="blog/images/%Y/%m/%d/", default='', blank=True)
    content = models.TextField('Контент')
    created_date = models.DateTimeField('Дата', default=timezone.now)


def _delete_file(path):
    # Deletes file from filesystem.
    if os.path.isfile(path):
        os.remove(path)


@receiver(pre_delete, sender=Post)
def delete_img_pre_delete_post(sender, instance, *args, **kwargs):
    if instance.photo:
        _delete_file(instance.photo.path)
0

To effectively delete files from your system try:

files = modelName.objects.all().get(columnName=criteria)

files.columnNameFile.delete() files.columnNameFile2.delete() . . .