Django ImageField用相同的名称覆盖图像文件

Django ImageField用相同的名称覆盖图像文件,django,django-models,Django,Django Models,我有modelUserProfile和fieldavatar=models.ImageField(upload\u to=upload\u avatar) upload\u avatar函数根据user.id(例如12.png)命名图像文件 但当用户更新化身时,新的化身名称与旧的化身名称一致,Django在文件名中添加后缀(例如12-1.png) 有一种方法可以覆盖文件而不是创建新文件?您可以尝试定义自己的文件系统存储并覆盖默认的get\u availbale\u name方法 from dja

我有model
UserProfile
和field
avatar=models.ImageField(upload\u to=upload\u avatar)

upload\u avatar
函数根据
user.id
(例如12.png)命名图像文件

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


有一种方法可以覆盖文件而不是创建新文件?

您可以尝试定义自己的文件系统存储并覆盖默认的get\u availbale\u 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)

希望这能有所帮助。

是的,我也想到了这个。这就是我所做的

型号:

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')
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')
在单独的文件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

显然,这些是示例值,但总的来说,这对我来说很好,并且根据需要修改应该非常简单。

嗯。。。这听起来可能有点离经叛道,但目前我的解决方案是检查并删除回调中的现有文件,我已经使用回调来提供上传文件的名称。在models.py中:

def image_path(instance, filename):
    return os.path.join('some_dir', str(instance.some_identifier), 'filename.ext')
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')

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

class OverwriteStorage(FileSystemStorage):

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

基本上,这将覆盖函数
get\u available\u name
,删除已存在的文件,并返回已存储文件的名称

只需参考模型图像字段,将其删除并再次保存

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
model.image.delete()
model.image.save()

我尝试了这里提到的解决方案。但在django 1.10上似乎不起作用。它将在管理员模板的某个位置引发以下错误:

url()缺少1个必需的位置参数:“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)

对于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

谢谢,这是我需要的。也许你知道当用户清除他们的头像时如何删除图像文件吗?@Marco默认情况下,如果文件存在,django不会覆盖该文件。但是,更好的方法是使用
self.delete
,使用它您不需要检查
self.exists
,它会忽略在检查文件是否存在和实际删除之间删除文件(由另一个线程或进程)时所产生的错误。在我看来,您应该覆盖文件删除的_save,如以下用户2732686的回答所述,该回答存在严重缺陷。如果存储配置为将文件存储在不同于
MEDIA\u ROOT
的目录中,该怎么办?你甚至可以删除一个完全不同的文件。正如其他人指出的,最好使用
self.delete(name)
。因为它被标记为正确答案,所以如果它被更正会更好,因为它会被复制粘贴很多。这到现在仍然有用。从Django 2.1实现此功能的任何人都应该添加max_length=None。因此,
def get\u available\u name(self,name,max\u length=None)
self.delete
将检查文件是否存在,因此无需进行
self.exists
检查。最后返回
super().get\u available\u name(name,max\u length)
将允许文件名缩短算法工作,如果
name
max\u length
长,你为什么不投票给这个答案呢?在我看来,重写保存以删除文件比在保存方法之前只调用一次get\u available\u name要好得多。。。如果你出于某种原因在代码中的其他地方使用它,它可能会导致bug…@Kukosk这个答案没有更多的更新,主要是因为它晚了3年,可能是因为它缺少描述。FWIW我宁愿不要覆盖父类的内部方法。如果删除了_save()或更改了其参数,则此代码将中断,并且此更改不会在发行说明中提及,因为它不是公共API的一部分。请小心此解决方案。两个进程同时保存同一文件的争用条件可能会触发基类
FileSystemStorage.\u save()
中的无限循环。如果在调用基类的
\u save()
方法时文件存在,它将捕获产生的
EEXIST
异常,并在调用
get\u available\u name()
后重试。但是,由于上面已覆盖
get\u available\u name()
以不修改文件名,因此将再次发生
EEXIST
异常。这个序列被无限重复。@Coderji最终使用了这个解决方案,它确保原始文件在
get\u available\u name()
中被删除。您可以将日期时间添加到实际图像中。。你可以用它作为后缀。。比如“2016_987890_image.jpg”。。这将有助于您搜索图像,这里有一个完整的代码示例:这对我很有用。这个解决方案简单明了。