替换Django图像不会删除原始图像

时间:2010-12-09 02:00:38

标签: python django imagefield

在Django中,如果模型中有ImageFile,删除将从磁盘中删除相关文件,并从数据库中删除记录。

不应更换图像还会从磁盘中删除不需要的文件吗?相反,我发现它保留了原始内容并添加了替代品。

现在删除该对象不会仅删除原始文件。

这样做有什么好的策略吗?如果我的用户经常更换他们的图片,我不想要一堆孤儿文件。

8 个答案:

答案 0 :(得分:26)

我发现的最佳策略是在模型中制作自定义保存方法:

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)

请注意,与不删除后端文件的更新一样,删除实例模型(此处为Photo)将删除后端文件,而不是在Django 1.3中,你必须添加更多自定义代码才能做到这一点(或经常做一些脏的cron工作)。

最后使用ForeignKey,ManytoMany和其他关系测试所有更新/删除案例,以检查后端文件是否被正确删除。 只相信你测试的内容

答案 1 :(得分:12)

  

不应该替换图像也会从磁盘中删除不需要的文件吗?

在过去,FileField渴望清理孤立的文件。但是在Django 1.2中发生了变化:

  

在早期的Django版本中,当删除包含FileField的模型实例时,FileField会自行删除后端存储中的文件。这为几个可能严重的数据丢失情况打开了大门,包括回滚事务和引用同一文件的不同模型上的字段。在Django 1.2.5中,FileField永远不会从后端存储中删除文件。

答案 2 :(得分:8)

以下工作示例中的代码将在ImageField中上传图像时检测是否存在具有相同名称的文件,在这种情况下,在存储新文件之前删除该文件。

可以轻松修改它,以便删除旧文件而不管文件名。但这不是我想要的项目。

添加以下课程:

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

并像ImageField一样使用它:

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)

答案 3 :(得分:2)

如果您不使用交易,或者您不担心在事务回滚时丢失文件,则可以使用django-cleanup

答案 4 :(得分:1)

关于这个问题有很多门票虽然很可能不会成为核心问题。最全面的是http://code.djangoproject.com/ticket/11663。如果您正在寻找解决方案,补丁和故障单注释可以为您提供一些指导。

您还可以考虑使用不同的StorageBackend,例如Django snippet 976提供的覆盖文件存储系统。http://djangosnippets.org/snippets/976/。您可以将默认存储更改为此后端,也可以在每个FileField / ImageField声明上覆盖它。

答案 5 :(得分:1)

以下代码可以使用或不使用upload_to=...blank=True,并且提交的文件与旧文件的名称相同。

(py3语法,在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)

请记住,这种解决方案仅适用于非事务性上下文(无回滚,因为文件明确丢失)

答案 6 :(得分:0)

我使用popen的简单方法,所以当我保存我的Info模型时,我会在链接到新文件之前删除以前的文件:

import os

try:
    os.popen("rm %s" % str(info.photo.path))
except:
    #deal with error
    pass
info.photo = nd['photo']

答案 7 :(得分:0)

我保存了原始文件,如果已更改,请删除它。

class Document(models.Model):
    document = FileField()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._document = self.document

    def save(self, *args, **kwargs):
        if self.document != self._document:
            self._document.delete()
            super().save(*args, **kwargs)