【问题标题】:Replacing a Django image doesn't delete original替换 Django 图像不会删除原始图像
【发布时间】:2010-12-09 02:00:38
【问题描述】:

在 Django 中,如果模型中有一个 ImageFile,删除将删除磁盘中的关联文件以及从数据库中删除记录。

替换图像不应该同时从磁盘中删除不需要的文件吗?相反,我看到它保留了原始并添加了替换。

现在删除对象不会删除原始文件,只会删除替换。

有什么好的策略可以做到这一点吗?如果我的用户经常更换他们的图像,我不希望有一堆孤立文件。

【问题讨论】:

  • 您是否尝试过在模型上使用保存方法来检查文件是否正在更新并在必要时删除旧文件?
  • 我有一个similar problem and have added a question for it。在接下来的几天里,您可能会在那里找到您想要的东西......

标签: python django imagefield


【解决方案1】:

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

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)

请注意,与不删除后端文件的更新一样,删除实例模型(此处为照片)将不会删除后端文件,无论如何都不会在 Django 1.3 中删除,您必须添加更多自定义代码才能做到这一点(或定期做一些肮脏的 cron 工作)。

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

【讨论】:

  • 在 2 个网站上使用了将近 3 年之后,我可以确认这种方法已准备好用于生产并且零问题。我现在在带有 Django 1.6.2 的第三个网站中使用此方法。它仍然运行良好。
  • 在 django 1.9 的 ModelAdmin 类的 save_model()delete_model() 函数中使用它,它的工作原理就像一个魅力
  • 我看到文件名仍然附加了一个哈希码,以避免与原始文件名冲突。这可能是一个缺点?就像文件名被指定为 username.jpg 一样,第一次存储保存为 username.jpg,但随后它更新为 username_lksjdflaj.jpg。也就是文件名冲突解决还是发生了……
  • 今天我在 Django 3.0.5 上测试了你的解决方案,它可以工作。谢谢!
【解决方案2】:

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

在过去,FileField 急于清理孤立文件。但这在Django 1.2 发生了变化:

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

【讨论】:

    【解决方案3】:

    以下工作示例中的代码将在 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)
    

    【讨论】:

    • @Philip007 嗯,这是很久以前写的……但我很确定是文件名?
    • max_length 对图像字段有什么作用??
    • @dietbacon 来自 Django 文档:“FileField 实例在您的数据库中创建为 varchar 列,默认最大长度为 100 个字符。与其他字段一样,您可以使用 max_length 参数更改最大长度。 "
    【解决方案4】:

    如果你不使用事务或者不怕事务回滚时丢失文件,可以使用django-cleanup

    【讨论】:

      【解决方案5】:

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

      您还可以考虑使用不同的 StorageBackend,例如 Django sn-p 976.http://djangosnippets.org/snippets/976/ 提供的 Overwrite File Storage System。您可以将默认存储更改为此后端,也可以在每个 FileField/ImageField 声明中覆盖它。

      【讨论】:

      • 我认为 sn-p 976 存在问题。考虑一个 UserProfile 模型,该模型具有使用带有 upload_to="%Y/%m/%d" 的 OverwriteStorage 后端的配置文件图像字段。同一天,两个用户上传了一张名为“me.jpg”的头像。第一次上传将在 2012/02/11/me.jpg 中进行。第二次上传将删除并替换此图像,并导致两个 UserProfile 图像字段引用同一个图像。
      • OverwriteStorage 的预期行为是替换文件。这意味着确保名称的唯一性在于网站开发人员而不是后端。
      【解决方案6】:

      这是一个代码,可以使用或不使用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)
      

      请记住,这种解决方案仅在您处于非事务性上下文中时才适用(没有回滚,因为文件肯定会丢失)

      【讨论】:

      • 对于仍在使用 python 2.x 的用户,请使用 super(Attachment,self) 而不是 super()
      【解决方案7】:

      我对@9​​87654321@ 使用了一个简单的方法,所以当我保存我的Info 模型时,我会在链接到新文件之前删除之前的文件:

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

      【讨论】:

        【解决方案8】:

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

        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)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-06-19
          • 1970-01-01
          • 2021-09-04
          • 1970-01-01
          • 1970-01-01
          • 2011-12-17
          • 2018-02-24
          • 1970-01-01
          相关资源
          最近更新 更多