Replacing a Django image doesn't delete origin

2019-01-21 22:50发布

In Django, if you have a ImageFile in a model, deleting will remove the associated file from disk as well as removing the record from the database.

Shouldn't replacing an image also remove the unneeded file from disk? Instead, I see that it keeps the original and adds the replacement.

Now deleting the object won't delete the original file only the replacement.

Are there any good strategies to doing this? I don't want to have a bunch of orphan files if my users replace their images frequently.

8条回答
虎瘦雄心在
2楼-- · 2019-01-21 23:23

Shouldn't replacing an image also remove the unneeded file from disk?

In the olden days, FileField was eager to clean up orphaned files. But that changed in Django 1.2:

In earlier Django versions, when a model instance containing a FileField was deleted, FileField took it upon itself to also delete the file from the backend storage. This opened the door to several potentially serious data-loss scenarios, including rolled-back transactions and fields on different models referencing the same file. In Django 1.2.5, FileField will never delete files from the backend storage.

查看更多
冷血范
3楼-- · 2019-01-21 23:27

The best strategy I've found is to make a custom save method in the model:

class Photo(models.Model):

    image = ImageField(...) # works with FileField also

    def save(self, *args, **kwargs):
        # delete old file when replacing by updating the file
        try:
            this = Photo.objects.get(id=self.id)
            if this.image != self.image:
                this.image.delete(save=False)
        except: pass # when new photo then we do nothing, normal case          
        super(Photo, self).save(*args, **kwargs)

And beware, as with the updating which doesn't delete the back end file, deleting an instance model (here Photo) will not delete the back-end file, not in Django 1.3 anyway, you'll have to add more custom code to do that (or regularly do some dirty cron job).

Finally test all your update/delete cases with your ForeignKey, ManytoMany and others relations to check if the back-end files are correctly deleted. Believe only what you test.

查看更多
Juvenile、少年°
4楼-- · 2019-01-21 23:30

The code in the following working example will, upon uploading an image in an ImageField, detect if a file with the same name exists, and in that case, delete that file before storing the new one.

It could easily be modified so that it deletes the old file regardless of the filename. But that's not what I wanted in my project.

Add the following class:

from django.core.files.storage import FileSystemStorage
class OverwriteStorage(FileSystemStorage):
    def _save(self, name, content):
        if self.exists(name):
            self.delete(name)
        return super(OverwriteStorage, self)._save(name, content)

    def get_available_name(self, name):
        return name

And use it with ImageField like so:

class MyModel(models.Model):
    myfield = models.ImageField(
        'description of purpose',
        upload_to='folder_name',
        storage=OverwriteStorage(),  ### using OverwriteStorage here
        max_length=500,
        null=True,
        blank=True,
        height_field='height',
        width_field='width'
    )
    height = models.IntegerField(blank=True, null=True)
    width = models.IntegerField(blank=True, null=True)
查看更多
趁早两清
5楼-- · 2019-01-21 23:30

Here is a code that can work with or without upload_to=... or blank=True, and when the submitted file has the same name as the old one.

(py3 syntax, tested on Django 1.7)

class Attachment(models.Model):

    document = models.FileField(...)  # or ImageField

    def delete(self, *args, **kwargs):
        self.document.delete(save=False)
        super().delete(*args, **kwargs)

    def save(self, *args, **kwargs):
        if self.pk:
            old = self.__class__._default_manager.get(pk=self.pk)
            if old.document.name and (not self.document._committed or not self.document.name):
                old.document.delete(save=False)
        super().save(*args, **kwargs)

Remember that this kind of solution is only applicable if you are in a non transactional context (no rollback, because the file is definitively lost)

查看更多
Juvenile、少年°
6楼-- · 2019-01-21 23:31

If you don't use transactions or you don't afraid of loosing files on transaction rollback, you can use django-cleanup

查看更多
劳资没心,怎么记你
7楼-- · 2019-01-21 23:37

I used a simple method with popen, so when i save my Info model i delete the former file before linking to the new:

import os

try:
    os.popen("rm %s" % str(info.photo.path))
except:
    #deal with error
    pass
info.photo = nd['photo']
查看更多
登录 后发表回答