ImageField перезаписывает файл с тем же именем
У меня есть модель UserProfile
с полем avatar = models.ImageField(upload_to=upload_avatar)
upload_avatar
файл имен функций в соответствии с user.id
(например, 12.png).
Но когда пользователь обновляет аватар, имя нового аватара совпадает со старым именем аватара, а Django добавляет суффикс к имени файла (например, 12-1.png).
Есть ли способ перезаписать файл вместо создания нового файла?
Ответы
Ответ 1
Да, это тоже пришло мне в голову. Вот что я сделал.
Модель:
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 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
Очевидно, что здесь приведены значения выборки, но в целом это хорошо работает для меня, и это должно быть довольно простым для изменения при необходимости.
Ответ 2
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
Ответ 3
ahem... это может показаться неортодоксальным, но мое решение в настоящее время состоит в том, чтобы проверить и удалить существующий файл в обратном вызове, который я уже использовал для предоставления имени загруженного файла. В 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')
Ответ 4
Вы можете написать класс хранения еще лучше:
class OverwriteStorage(FileSystemStorage):
def get_available_name(self, name, max_length=None):
self.delete(name)
return name
В основном это перезапишет функцию get_available_name
чтобы удалить файл, если он уже существует, и вернуть имя уже сохраненного файла
Ответ 5
Вы можете попытаться определить свою собственную файловую систему и переопределить метод 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)
Надеюсь, что это поможет.
Ответ 6
Просто повторите поле изображения модели, удалите его и сохраните снова.
model.image.delete()
model.image.save()
Ответ 7
Для 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
Ответ 8
Я попробовал решения, упомянутые здесь. Но, похоже, он не работает в джанго 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 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)