How do I get Django Admin to delete files when I remove an object from the database/model?

48,229

Solution 1

You can receive the pre_delete or post_delete signal (see @toto_tico's comment below) and call the delete() method on the FileField object, thus (in models.py):

class MyModel(models.Model):
    file = models.FileField()
    ...

# Receive the pre_delete signal and delete the file associated with the model instance.
from django.db.models.signals import pre_delete
from django.dispatch.dispatcher import receiver

@receiver(pre_delete, sender=MyModel)
def mymodel_delete(sender, instance, **kwargs):
    # Pass false so FileField doesn't save the model.
    instance.file.delete(False)

Solution 2

Try django-cleanup

pip install django-cleanup

settings.py

INSTALLED_APPS = (
    ...
    'django_cleanup.apps.CleanupConfig',
)

Solution 3

Django 1.5 solution: I use post_delete for various reasons that are internal to my app.

from django.db.models.signals import post_delete
from django.dispatch import receiver

@receiver(post_delete, sender=Photo)
def photo_post_delete_handler(sender, **kwargs):
    photo = kwargs['instance']
    storage, path = photo.original_image.storage, photo.original_image.path
    storage.delete(path)

I stuck this at the bottom of the models.py file.

the original_image field is the ImageField in my Photo model.

Solution 4

This code runs well on Django 1.4 also with the Admin panel.

class ImageModel(models.Model):
    image = ImageField(...)

    def delete(self, *args, **kwargs):
        # You have to prepare what you need before delete the model
        storage, path = self.image.storage, self.image.path
        # Delete the model before the file
        super(ImageModel, self).delete(*args, **kwargs)
        # Delete the file after the model
        storage.delete(path)

It's important to get the storage and the path before delete the model or the latter will persist void also if deleted.

Solution 5

You need to remove the actual file on both delete and update.

from django.db import models

class MyImageModel(models.Model):
    image = models.ImageField(upload_to='images')

    def remove_on_image_update(self):
        try:
            # is the object in the database yet?
            obj = MyImageModel.objects.get(id=self.id)
        except MyImageModel.DoesNotExist:
            # object is not in db, nothing to worry about
            return
        # is the save due to an update of the actual image file?
        if obj.image and self.image and obj.image != self.image:
            # delete the old image file from the storage in favor of the new file
            obj.image.delete()

    def delete(self, *args, **kwargs):
        # object is being removed from db, remove the file from storage first
        self.image.delete()
        return super(MyImageModel, self).delete(*args, **kwargs)

    def save(self, *args, **kwargs):
        # object is possibly being updated, if so, clean up.
        self.remove_on_image_update()
        return super(MyImageModel, self).save(*args, **kwargs)
Share:
48,229
narkeeso
Author by

narkeeso

Updated on September 12, 2021

Comments

  • narkeeso
    narkeeso almost 3 years

    I am using 1.2.5 with a standard ImageField and using the built-in storage backend. Files upload fine but when I remove an entry from admin the actual file on the server does not delete.

  • narkeeso
    narkeeso over 13 years
    Do you have an example of how to use that in a model in order to delete the file? I'm looking at the docs and see examples of how to remove the object from the database but do not see any implementations on file deletion.
  • Adam Jurczyk
    Adam Jurczyk over 12 years
    probably post_delete won't work, because file_field.delete() by default saves model to db, try file_field.delete(False) docs.djangoproject.com/en/1.3/ref/models/fields/…
  • darrinm
    darrinm over 11 years
    This doesn't work for me (Django 1.5) and the Django 1.3 CHANGELOG states: "In Django 1.3, when a model is deleted the FileField’s delete() method won’t be called. If you need cleanup of orphaned files, you’ll need to handle it yourself (for instance, with a custom management command that can be run manually or scheduled to run periodically via e.g. cron)."
  • Antony Hatchkins
    Antony Hatchkins over 11 years
    This method is wrong because it wont work for bulk delete (like admin's 'Delete selected' feature). For example MyModel.objects.all()[0].delete() will delete the file while MyModel.objects.all().delete() will not. Use signals.
  • Antony Hatchkins
    Antony Hatchkins over 11 years
    Be sure to add a check if instance.file field is not empty or it can (at least try) to delete the whole MEDIA_ROOT directory. This applies even to ImageField(null=False) fields.
  • toto_tico
    toto_tico about 11 years
    Thanks. In general, I would recommend to use the post_delete signal because it is safer in the case the delete fail for any reason. Then neither the model, neither the file would be deleted keeping the data consistent. Please correct me if my understanding of post_delete and pre_delete signals is wrong.
  • lvella
    lvella over 10 years
    This solution is wrong! delete is not always called when a row is deleted, you must use signals.
  • Sean Azlin
    Sean Azlin almost 10 years
    For anyone using Amazon S3 as a storage backend (via django-storages), this particular answer won't work. You'll get a NotImplementedError: This backend doesn't support absolute paths. You can easily fix this by passing the file field's name to storage.delete() instead of the file field's path. For example, replace the last two lines of this answer with storage, name = photo.original_image.storage, photo.original_image.name then storage.delete(name).
  • Admin
    Admin almost 10 years
    @Sean +1, I'm using that adjustment in 1.7 to delete thumbnails generated by django-imagekit on S3 via django-storages. docs.djangoproject.com/en/dev/ref/files/storage/… . Note: If you're simply using an ImageField (or FileField) you can use mymodel.myimagefield.delete(save=False) instead. docs.djangoproject.com/en/dev/ref/files/file/…
  • eugene
    eugene about 9 years
    @user2616836 Can you use mymodel.myimagefield.delete(save=False) on post_delete? In other words, I can see that I can delete the file, but can you delete the file when a model that has the imagefield gets deleted?
  • Admin
    Admin about 9 years
    @eugene Yes you can, it works (I'm not sure why though). In post_delete you do instance.myimagefield.delete(save=False), note the use of instance.
  • Mark
    Mark over 8 years
    Note that this does not delete the old file if you replace the file on a model instance
  • caliph
    caliph over 8 years
    This does not work for me in Django 1.8 outside admin. Is there a new way to do it?
  • CoderGuy123
    CoderGuy123 almost 8 years
    After limited testing, I can confirm that this package still works for Django 1.10.
  • tbm
    tbm over 7 years
    This is a very bad idea. Not only will you remove an entire directory vs. a single file (potentially affecting other files), you will do so even if the actual object deletion fails.
  • carruthd
    carruthd over 7 years
    Its not a bad idea if you were working on the problem I had ;) As I mentioned I had a unique use case where the model being deleted was a parent model. Children wrote files to the parent folder and so if you deleted the parent the desired behavior was that all files in the folder were deleted. Good point on the order of operations though. That didn't occur to me at the time.
  • tbm
    tbm over 7 years
    I would still prefer to remove the individual child files when a child is deleted; then if you need to you can remove the parent directory when it is empty.
  • carruthd
    carruthd over 7 years
    That makes sense as you are taking out child objects but if the parent object is destroyed, stepping through the children one at a time seems tedious and unnecessary. Regardless, I see now that the answer I gave wasn't specific enough to the OP question. Thank you for the comments, you made me think about using a less blunt instrument going forward.
  • Max Malysh
    Max Malysh about 7 years
    It does work in Django 1.11. You should use pre_delete signal, ** post_delete** won't work.
  • Tobias Ernst
    Tobias Ernst over 6 years
    @jonalv Connect the signal in Django 1.9 to make it work: request_finished.connect(mymodel_delete, sender=None, weak=True, dispatch_uid="mymodel_delete"). Then the signature of mymodel_delete should be: @receiver(post_delete, sender=Resource) def mymodel_delete(sender, **kwargs): instance = kwargs.get('instance')
  • routeburn
    routeburn about 6 years
    Nice. Works for me on Django 2.0. I'm also using S3 as my storage backend (django-storages.readthedocs.io/en/latest/backends/…) and it's happily deleting files from S3.
  • Aprendiendo Siempre
    Aprendiendo Siempre about 5 years
    I just user it and it works in Django 2.1 with python 3.7
  • AlexKh
    AlexKh almost 4 years
    Great solution!