ImageField overwrite image file with same name

DjangoDjango Models

Django 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)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDeadlyView Question on Stackoverflow
Solution 1 - DjangoLockjawView Answer on Stackoverflow
Solution 2 - Djangouser2732686View Answer on Stackoverflow
Solution 3 - Djangocheap_grayhatView Answer on Stackoverflow
Solution 4 - DjangoPynchiaView Answer on Stackoverflow
Solution 5 - DjangoGal FridmanView Answer on Stackoverflow
Solution 6 - DjangoXeteskianView Answer on Stackoverflow
Solution 7 - DjangoJingoView Answer on Stackoverflow
Solution 8 - DjangoMauricioView Answer on Stackoverflow