ImageField overwrite image file with same name
DjangoDjango ModelsDjango Problem Overview
I have model UserProfile
with field avatar = models.ImageField(upload_to=upload_avatar)
upload_avatar
function names image file according user.id
(12.png for example).
But when user updates the avatar, new avatar name coincide with old avatar name and Django adds suffix to file name (12-1.png for example).
There are way to overwrite file instead of create new file?
Django Solutions
Solution 1 - Django
Yeah, this has come up for me, too. Here's what I've done.
Model:
from app.storage import OverwriteStorage
class Thing(models.Model):
image = models.ImageField(max_length=SOME_CONST, storage=OverwriteStorage(), upload_to=image_path)
Also defined in models.py:
def image_path(instance, filename):
return os.path.join('some_dir', str(instance.some_identifier), 'filename.ext')
In a separate file, 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
Obviously, these are sample values here, but overall this works well for me and this should be pretty straightforward to modify as necessary.
Solution 2 - Django
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
Solution 3 - Django
You can write storage class even better this way:
class OverwriteStorage(FileSystemStorage):
def get_available_name(self, name, max_length=None):
self.delete(name)
return name
Basically this will overwrite the function get_available_name
to delete the file if already exists and return the name of the file already storaged
Solution 4 - Django
ahem... it may sound unorthodox, but my solution, at present, is to check&remove the existing file within the callback I already use for providing the name of the uploaded file. In 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')
Solution 5 - Django
Just refference your model image field, delete it and save again.
model.image.delete()
model.image.save()
Solution 6 - Django
For Django 1.10 I found I had to modify the top answer to include the max_length argument in the Function:
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
Solution 7 - Django
You can try to define your own Filesystemstorage and override the default get_availbale_name method.
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
For your image you could define a fs like this:
fs = MyFileSystemStorage(base_url='/your/url/',
location='/var/www/vhosts/domain/file/path/')
avatar = models.ImageField(upload_to=upload_avatar, storage=fs)
Hope this helps.
Solution 8 - Django
I tried the solutions mentioned here. But it seem not to work at django 1.10. It would raise the following error somewhere at the admin's template:
url() missing 1 required positional argument: 'name'
So I came up with my own solution, which consists on creating a pre_save signal that tries to get the instance from the database before it's saved and remove it's file path:
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)