For a django project I have a model that features an image field. The idea is that the user uploads an image and django renames it according to a chosen pattern before storing it in the media folder.
To achieve that I have created a helper class in a separate file utils.py. The class will callable within the upload_to
parameter of the django ImageField model. Images should be renamed by concatenating .name and .id properties of the created item. The problem with my solution is that if the image is uploaded upon creating anew the item object, then there is no .id value to use (just yet). So instead of having let's say: banana_12.jpg I get banana_None.jpg. If instead I upload an image on an already existing item object, then the image is renamed correctly.
Here is my solution. How can I improve the code to make it work on new item object too?
Is there a better way to do this?
# utils.py
from django.utils.deconstruct import deconstructible
import os
@deconstructible
class RenameImage(object):
"""Renames a given image according to pattern"""
def __call__(self, instance, filename):
"""Sets the name of an uploaded image"""
self.name, self.extension = os.path.splitext(filename)
self.folder = instance.__class__.__name__.lower()
image_name = f"{instance}_{instance.id}{self.extension}"
return os.path.join(self.folder, image_name)
rename_image = RenameImage()
# models.py
from .utils import rename_image
class Item(models.Model):
# my model for the Item object
name = models.CharField(max_length=30)
info = models.CharField(max_length=250, blank=True)
image = models.ImageField(upload_to=rename_image, blank=True, null=True) # <-- helper called here
# ...
I created the helper class RenameImage which for the reasons explained above works correctly only if the object already exists.
__EDIT
I have made some improvements based on the post_save advice. The user uploaded image gets resized and renamed as I want but then I get a RecursionError: maximum recursion depth exceeded
. It seems that by calling the save() method on the instance I get into a signal loop...
Any idea on how to resolve this?
from django.db.models.signals import post_save
from .models import Item
from PIL import Image
import os
def resize_rename(sender, instance, created, max_height=300, max_width=300, **kwargs):
if instance.image: # only fire if there is an image
with open(instance.image.path, 'r+b') as f:
image = Image.open(f)
# Resize the image if larger than max values
if image.height > max_height or image.width > max_width:
output_size = (max_height, max_width)
image.thumbnail(output_size, Image.ANTIALIAS)
image.save(instance.image.path, quality=100)
# Grab the image extension
_name, extension = os.path.splitext(instance.image.name)
# Use old_name to create the desired new_name while keeping the same dirs
old_name = instance.image.name.split('/')[-1] # e.g. IMG_3402.jpg
new_name = instance.image.name.replace(old_name, f"{instance.name}_{instance.id}{extension}")
# Rename if name is not the right one already
if old_name != new_name:
old_path = instance.image.path
new_path = old_path.replace(old_name, f"{instance.name}_{instance.id}{extension}")
instance.image.name = new_name # Assign the new name
os.replace(old_path, new_path) # Replace with the new file
instance.save(update_fields=['image']) # Save the instance
post_save.connect(resize_rename, sender=Item)