1

Through my project, I have users upload a profile picture. I save the profile picture as userID.jpg. If they upload a new profile picture, I want to overwrite the old profile picture, so I don't waste storage space. By browsing previously asked questions on stackoverflow, I redefined the OverwriteStorage:

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

When I upload the profile picture, I can see in the directory on my computer that the picture has been overwritten successfully.The image is saved with the path "media/profile/userID.jpg". However, when I display the image on my site, it is still the old picture. Through the Django site, when I open the path, I see the old picture, and when I try to change it through the admin, I get the following error:

[WinError 32] The process cannot access the file because it is being used by another process: '\media\\profile\\userID.jpg'

I guess I am incorrectly overwriting the file and another instance is still open, and to solve it, I need to properly close the image before overwriting. I tried doing that, but to to no success.

Ibrahim Fakih
  • 61
  • 1
  • 8

3 Answers3

0

I did something similar but I used signals to update and delete images.

Firstable, I defined the name of the image in the helpers.py

from django.conf import settings
from datetime import datetime

def upload_to_image_post(self, filename):
    """
    Stores the image in a specific path regards to date 
    and changes the name of the image with for the name of the post
    """    
    ext = filename.split('.')[-1]
    current_date = datetime.now()

    return '%s/posts/main/{year}/{month}/{day}/%s'.format(
        year=current_date.strftime('%Y'), month=current_date.strftime('%m'), 
        day=current_date.strftime('%d')) % (settings.MEDIA_ROOT, filename)

SO, I called the def in my model, specifically in the image's field

from django.db import models
from django.utils.text import slugify
from .helpers import upload_to_image_post

class Post(models.Model):
    """
    Store a simple Post entry. 
    """
    title = models.CharField('Title', max_length=200, help_text='Title of the post')
    body = models.TextField('Body', help_text='Enter the description of the post')   
    slug = models.SlugField('Slug', max_length=200, db_index=True, unique=True, help_text='Title in format of URL')        
    image_post = models.ImageField('Image', max_length=80, blank=True, upload_to=upload_to_image_post, help_text='Main image of the post')

    class Meta:
        verbose_name = 'Post'
        verbose_name_plural = 'Posts'

Finally, I defined signals to update or delete the image before the actions (update or delete) happen in the model.

import os
from django.db import models
from django.dispatch import receiver
from django.db.models.signals import pre_delete, pre_save
from .models import Post

@receiver(pre_delete, sender=Post)
def post_delete(sender, instance, **kwargs):
    """
    Deleting the specific image of a Post after delete it
    """
    if instance.image_post:
        if os.path.isfile(instance.image_post.path):
            os.remove(instance.image_post.path)

@receiver(pre_save, sender=Post)
def post_update(sender, instance, **kwargs):
    """
    Replacing the specific image of a Post after update
    """
    if not instance.pk:
        return False

    if sender.objects.get(pk=instance.pk).image_post:
        old_image = sender.objects.get(pk=instance.pk).image_post
        new_image = instance.image_post
        if not old_image == new_image:
            if os.path.isfile(old_image.path):
                os.remove(old_image.path)
    else:
        return False

I hope, this helped you.

Samir Hinojosa
  • 825
  • 7
  • 24
  • Thanks it works great now; however, I have never used signals before, and now my site is a bit slower. Is that normal? Is it better to put the signals in the models.py or in its own file? – Ibrahim Fakih Apr 10 '19 at 14:37
  • are you saying your site is slow because of the signals? or your site was slow before that? I use signals and the performance is good. – Samir Hinojosa Apr 10 '19 at 17:59
0

It is interesting to receive and process via signals. In some cases, it may be more convenient than OverwriteStorage(FileSystemStorage).

But, os.remove(filename) is not safe/working without local filesystem. I recommend using Django File Storage API.

from django.core.files.storage import default_storage

os.path.isfile(path)  # worse
default_storage.exists(path)  # better

os.remove(path)  # worse
default_storage.delete(path)  # better
McKabi
  • 1
-1

Have it rename the old one to userID-old.jpg and then save userID.jpg. It will be quick that no one would likely notice it happening.