ImageField覆盖具有相同名称的图像文件

Dea*_*dly 47 django django-models

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

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

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

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

Loc*_*jaw 62

是的,这也是我的想法.这就是我所做的.

模型:

from app.storage import OverwriteStorage

class Thing(models.Model):
    image = models.ImageField(max_length=SOME_CONST, storage=OverwriteStorage(), upload_to=image_path)
Run Code Online (Sandbox Code Playgroud)

还在models.py中定义:

def image_path(instance, filename):
    return os.path.join('some_dir', str(instance.some_identifier), 'filename.ext')
Run Code Online (Sandbox Code Playgroud)

在单独的文件中,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
Run Code Online (Sandbox Code Playgroud)

显然,这些是示例值,但总体而言,这对我来说效果很好,并且根据需要进行修改应该非常简单.

  • 这直到现在仍然有用。任何从 Django 2.1 实现这一点的人都应该添加 max_length=None。因此 <code>def get_available_name(self, name, max_length=None)<code> (4认同)
  • @Marco默认情况下,django不会覆盖文件(如果存在).但是,更好的方法是使用`self.delete`,使用它你不需要`self.exists`检查它忽略了如果文件被删除(由另一个线程或进程)之间引发的错误检查是否存在和实际删除. (3认同)

use*_*686 19

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
Run Code Online (Sandbox Code Playgroud)

  • @Kukosk 这个答案没有得到更多的支持,主要是因为它晚了 3 年,也许是因为它缺乏描述。 (2认同)
  • FWIW我宁愿不覆盖父类的内部方法.如果删除了_save()或更改了其args,则此代码将中断,并且发行说明中不会提及更改,因为它不是公共API的一部分. (2认同)
  • 小心这个解决方案。2 个进程同时保存同一个文件的竞争条件会触发基类“FileSystemStorage._save()”中的无限循环。如果在调用基类的 `_save()` 方法时文件存在,它将捕获产生的 `EEXIST` 异常,并在调用 `get_available_name()` 后重试。然而,因为`get_available_name()` 已经被上面覆盖而不修改文件名,`EEXIST` 异常将再次发生。该序列无限重复。 (2认同)

Pyn*_*hia 12

嗯...这可能听起来不正统,但我目前的解决方案是检查并删除我已用于提供上传文件名称的回调中的现有文件.在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')
Run Code Online (Sandbox Code Playgroud)


小智 10

您可以通过以下方式更好地编写存储类:

class OverwriteStorage(FileSystemStorage):

    def get_available_name(self, name, max_length=None):
        self.delete(name)
        return name
Run Code Online (Sandbox Code Playgroud)

基本上,这将覆盖get_available_name删除文件的功能(如果已存在)并返回已存储的文件的名称

  • `self.delete`将检查文件是否存在,因此不需要`self.exists`检查. (6认同)

小智 8

对于 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
Run Code Online (Sandbox Code Playgroud)


Jin*_*ngo 6

您可以尝试定义自己的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
Run Code Online (Sandbox Code Playgroud)

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

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

希望这可以帮助.


Gal*_*man 6

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

model.image.delete()
model.image.save()
Run Code Online (Sandbox Code Playgroud)