【问题标题】:ImageField overwrite image file with same nameImageField 覆盖同名图片文件
【发布时间】:2012-03-20 08:01:19
【问题描述】:

我有模型UserProfile 字段avatar = models.ImageField(upload_to=upload_avatar)

upload_avatar 函数根据user.id 命名图像文件(例如12.png)。

但是当用户更新头像时,新头像名称与旧头像名称一致,Django 会在文件名中添加后缀(例如 12-1.png)。

有办法覆盖文件而不是创建新文件

【问题讨论】:

  • 您可以将日期时间添加到您的实际图像中..您可以将其用作后缀..例如“2016_987890_image.jpg”..这也将帮助您搜索图像
  • 这很简单,这里有一个完整的代码示例:stackoverflow.com/a/4905384/585783

标签: django django-models


【解决方案1】:

你可以这样写存储类更好:

class OverwriteStorage(FileSystemStorage):

    def get_available_name(self, name, max_length=None):
        self.delete(name)
        return name

基本上这将覆盖函数get_available_name,如果文件已存在则删除该文件并返回已存储文件的名称

【讨论】:

  • self.delete 会检查文件是否存在,因此不需要self.exists 检查。
  • 最后返回 super().get_available_name(name, max_length) 将允许文件名缩短算法工作,以防namemax_length 长。
【解决方案2】:

对于 Django 1.10,我发现我必须修改最佳答案以在函数中包含 max_length 参数:

from django.core.files.storage import FileSystemStorage
import os

class OverwriteStorage(FileSystemStorage):
def get_available_name(self, name, max_length=None):
    if self.exists(name):
        os.remove(os.path.join(settings.MEDIA_ROOT, name))
    return name

【讨论】:

    【解决方案3】:

    我尝试了这里提到的解决方案。但它似乎不适用于 django 1.10。它会在管理员模板的某处引发以下错误:

    url() missing 1 required positional argument: 'name'

    所以我想出了自己的解决方案,其中包括创建一个 pre_save 信号,该信号试图在保存实例之前从数据库中获取实例并删除它的文件路径:

    from django.db.models.signals import pre_save
    
    
    @receiver(pre_save, sender=Attachment)
    def attachment_file_update(sender, **kwargs):
        attachment = kwargs['instance']
        # As it was not yet saved, we get the instance from DB with 
        # the old file name to delete it. Which won't happen if it's a new instance
        if attachment.id:
            attachment = Attachment.objects.get(pk=attachment.id)
            storage, path = attachment.its_file.storage, attachment.its_file.path
            storage.delete(path)
    

    【讨论】:

      【解决方案4】:

      只需引用您的模型图像字段,将其删除并再次保存。

      model.image.delete()
      model.image.save()
      

      【讨论】:

      • 这个解决方案为我节省了大量时间。你值得一个拇指。
      【解决方案5】:
      class OverwriteStorage(get_storage_class()):
      
          def _save(self, name, content):
              self.delete(name)
              return super(OverwriteStorage, self)._save(name, content)
      
          def get_available_name(self, name):
              return name
      

      【讨论】:

      • 你为什么不赞成这个答案?在我看来,重写 _save 以删除文件比期望 get_available_name 在保存方法之前只被调用一次要好得多...如果您出于某种原因在代码中的其他地方使用它可能会导致错误...
      • @Kukosk 这个答案没有更多的支持,主要是因为它晚了 3 年,也许是因为它缺乏描述。
      • FWIW 我宁愿不覆盖父类的内部方法。如果 _save() 被删除或更改其参数,则此代码将中断,并且该更改不会在发行说明中提及,因为它不是公共 API 的一部分。
      • 警惕这种解决方案。 2 个进程同时保存同一个文件的竞争条件可能会在基类FileSystemStorage._save() 中触发无限循环。如果在调用基类的_save() 方法时文件存在,它将捕获生成的EEXIST 异常,并在调用get_available_name() 后再次尝试。但是,因为get_available_name() 已在上面被覆盖以不修改文件名,所以EEXIST 异常将再次发生。该序列无限重复。
      • @Coderji 我最终使用了这个解决方案stackoverflow.com/a/19961911/2091925,它确保在get_available_name()中删除原始文件
      【解决方案6】:

      咳咳……这听起来可能不合常规,但我目前的解决方案是检查并删除回调中的现有文件,我已经使用它来提供上传文件的名称。在models.py中:

      import os
      from django.conf import settings
      
      def avatar_file_name(instance, filename):
          imgname = 'whatever.xyz'
          fullname = os.path.join(settings.MEDIA_ROOT, imgname)
          if os.path.exists(fullname):
              os.remove(fullname)
          return imgname
      class UserProfile(models.Model):
          avatar = models.ImageField(upload_to=avatar_file_name,
                                      default=IMGNOPIC, verbose_name='avatar')
      

      【讨论】:

      【解决方案7】:

      是的,我也遇到过这个问题。这就是我所做的。

      型号:

      from app.storage import OverwriteStorage
      
      class Thing(models.Model):
          image = models.ImageField(max_length=SOME_CONST, storage=OverwriteStorage(), upload_to=image_path)
      

      也在models.py中定义:

      def image_path(instance, filename):
          return os.path.join('some_dir', str(instance.some_identifier), 'filename.ext')
      

      在一个单独的文件中,storage.py:

      from django.core.files.storage import FileSystemStorage
      from django.conf import settings
      import os
      
      class OverwriteStorage(FileSystemStorage):
      
          def get_available_name(self, name):
              """Returns a filename that's free on the target storage system, and
              available for new content to be written to.
      
              Found at http://djangosnippets.org/snippets/976/
      
              This file storage solves overwrite on upload problem. Another
              proposed solution was to override the save method on the model
              like so (from https://code.djangoproject.com/ticket/11663):
      
              def save(self, *args, **kwargs):
                  try:
                      this = MyModelName.objects.get(id=self.id)
                      if this.MyImageFieldName != self.MyImageFieldName:
                          this.MyImageFieldName.delete()
                  except: pass
                  super(MyModelName, self).save(*args, **kwargs)
              """
              # If the filename already exists, remove it as if it was a true file system
              if self.exists(name):
                  os.remove(os.path.join(settings.MEDIA_ROOT, name))
              return name
      

      显然,这些是此处的示例值,但总体而言,这对我来说效果很好,而且应该很容易根据需要进行修改。

      【讨论】:

      • 谢谢,有我需要的。也许你知道当用户清除他们的头像时如何删除图像文件?
      • @Marco 默认情况下,django 不会覆盖存在的文件。但是,更好的方法是使用self.delete,使用它您不需要self.exists 检查,并且它会忽略在检查是否存在和检查文件之间(由另一个线程或进程)删除文件时引发的错误实际删除。
      • 在我看来,你应该重写 _save 来删除文件,如下 user2732686 的回答中所述
      • 这个答案存在严重缺陷。如果存储配置为将文件存储在与MEDIA_ROOT 不同的目录中怎么办?你甚至可以删除一个完全不同的文件。正如其他人指出的那样,最好使用self.delete(name)。既然被标记为正确答案,最好能改正,因为会被大量复制粘贴。
      • 这到现在仍然有用。任何从 Django 2.1 实现这一点的人都应该添加 max_length=None。因此 def get_available_name(self, name, max_length=None)
      【解决方案8】:

      您可以尝试定义自己的 Filesystemstorage 并覆盖默认的 get_availbale_name 方法。

      from django.core.files.storage import FileSystemStorage 
      import os
      
      class MyFileSystemStorage(FileSystemStorage):
          def get_available_name(self, name):
              if os.path.exists(self.path(name)):
                  os.remove(self.path(name))
              return name
      

      对于您的图像,您可以像这样定义 fs:

      fs = MyFileSystemStorage(base_url='/your/url/', 
           location='/var/www/vhosts/domain/file/path/')
      avatar = models.ImageField(upload_to=upload_avatar, storage=fs)
      

      希望这会有所帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-06-08
        • 2011-12-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-03-31
        • 2018-01-17
        相关资源
        最近更新 更多