Django / PIL - save thumbnail version right when image is uploaded
Solution 1
Based on xjtian's answer. This works for Python 3:
import os.path
from PIL import Image
from io import BytesIO
from django.core.files.base import ContentFile
from .my_app_settings import THUMB_SIZE
class Photo(models.Model):
photo = models.ImageField(upload_to='photos')
thumbnail = models.ImageField(upload_to='thumbs', editable=False)
def save(self, *args, **kwargs):
if not self.make_thumbnail():
# set to a default thumbnail
raise Exception('Could not create thumbnail - is the file type valid?')
super(Photo, self).save(*args, **kwargs)
def make_thumbnail(self):
image = Image.open(self.photo)
image.thumbnail(THUMB_SIZE, Image.ANTIALIAS)
thumb_name, thumb_extension = os.path.splitext(self.photo.name)
thumb_extension = thumb_extension.lower()
thumb_filename = thumb_name + '_thumb' + thumb_extension
if thumb_extension in ['.jpg', '.jpeg']:
FTYPE = 'JPEG'
elif thumb_extension == '.gif':
FTYPE = 'GIF'
elif thumb_extension == '.png':
FTYPE = 'PNG'
else:
return False # Unrecognized file type
# Save thumbnail to in-memory file as StringIO
temp_thumb = BytesIO()
image.save(temp_thumb, FTYPE)
temp_thumb.seek(0)
# set save=False, otherwise it will run in an infinite loop
self.thumbnail.save(thumb_filename, ContentFile(temp_thumb.read()), save=False)
temp_thumb.close()
return True
Solution 2
To do this, you should add a new ImageField
to your current UserImages
model to hold the thumbnail, then override your the save
method to create and save the thumbnail after the full image is saved.
I've adapted the following snippet of code from one of my projects that did exactly this, I'm pretty sure this will do exactly what you need it to do:
from cStringIO import StringIO
import os
from django.db import models
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage as storage
from PIL import Image
# Thumbnail size tuple defined in an app-specific settings module - e.g. (400, 400)
from app.settings import THUMB_SIZE
class Photo(models.Model):
"""
Photo model with automatically generated thumbnail.
"""
photo = models.ImageField(upload_to='photos')
thumbnail = models.ImageField(upload_to='thumbs', editable=False)
def save(self, *args, **kwargs):
"""
Make and save the thumbnail for the photo here.
"""
super(Photo, self).save(*args, **kwargs)
if not self.make_thumbnail():
raise Exception('Could not create thumbnail - is the file type valid?')
def make_thumbnail(self):
"""
Create and save the thumbnail for the photo (simple resize with PIL).
"""
fh = storage.open(self.photo.name, 'r')
try:
image = Image.open(fh)
except:
return False
image.thumbnail(THUMB_SIZE, Image.ANTIALIAS)
fh.close()
# Path to save to, name, and extension
thumb_name, thumb_extension = os.path.splitext(self.photo.name)
thumb_extension = thumb_extension.lower()
thumb_filename = thumb_name + '_thumb' + thumb_extension
if thumb_extension in ['.jpg', '.jpeg']:
FTYPE = 'JPEG'
elif thumb_extension == '.gif':
FTYPE = 'GIF'
elif thumb_extension == '.png':
FTYPE = 'PNG'
else:
return False # Unrecognized file type
# Save thumbnail to in-memory file as StringIO
temp_thumb = StringIO()
image.save(temp_thumb, FTYPE)
temp_thumb.seek(0)
# Load a ContentFile into the thumbnail field so it gets saved
self.thumbnail.save(thumb_filename, ContentFile(temp_thumb.read()), save=True)
temp_thumb.close()
return True
Solution 3
I wrote it based by ziiiro's answer. It works fine with Django2.2.1. Need process for save for path of image field.
models.py
from django.db import models
from my.images import make_thumbnail
class Image(models.Model):
image = models.ImageField(upload_to='')
thumbnail = models.ImageField(upload_to='', editable=False)
icon = models.ImageField(upload_to='', editable=False)
def save(self, *args, **kwargs):
# save for image
super(Image, self).save(*args, **kwargs)
make_thumbnail(self.thumbnail, self.image, (200, 200), 'thumb')
make_thumbnail(self.icon, self.image, (100, 100), 'icon')
# save for thumbnail and icon
super(Image, self).save(*args, **kwargs)
my.images.py
from django.core.files.base import ContentFile
import os.path
from PIL import Image
from io import BytesIO
def make_thumbnail(dst_image_field, src_image_field, size, name_suffix, sep='_'):
"""
make thumbnail image and field from source image field
@example
thumbnail(self.thumbnail, self.image, (200, 200), 'thumb')
"""
# create thumbnail image
image = Image.open(src_image_field)
image.thumbnail(size, Image.ANTIALIAS)
# build file name for dst
dst_path, dst_ext = os.path.splitext(src_image_field.name)
dst_ext = dst_ext.lower()
dst_fname = dst_path + sep + name_suffix + dst_ext
# check extension
if dst_ext in ['.jpg', '.jpeg']:
filetype = 'JPEG'
elif dst_ext == '.gif':
filetype = 'GIF'
elif dst_ext == '.png':
filetype = 'PNG'
else:
raise RuntimeError('unrecognized file type of "%s"' % dst_ext)
# Save thumbnail to in-memory file as StringIO
dst_bytes = BytesIO()
image.save(dst_bytes, filetype)
dst_bytes.seek(0)
# set save=False, otherwise it will run in an infinite loop
dst_image_field.save(dst_fname, ContentFile(dst_bytes.read()), save=False)
dst_bytes.close()
Related videos on Youtube
SilentDev
Updated on June 05, 2022Comments
-
SilentDev almost 2 years
This is my forms.py:
class UploadImageForm(forms.ModelForm): class Meta: model = UserImages fields = ['photo']
and this is my models.py:
class UserImages(models.Model): user = models.ForeignKey(User) photo = models.ImageField(upload_to=get_file_path)
and this is my view:
def uploadImageView(request): if request.method == 'POST': form = UploadImageForm(request.POST, request.FILES) if form.is_valid(): instance = form.save(commit=False) instance.user = request.user instance.save() return redirect('/') else: form = UploadImageForm() return render(request, 'uploadImagePage.html', {'uploadImageForm': form})
But this only saves the image being uploaded. How do I save a thumbnail version of the image as well with the thumbnail version of the image having the exact same name except with the word 'thumbail' after it?
The tutorials I read said I can do
im = Image.open(infile) im.thumbnail(size, Image.ANTIALIAS)
to get a thumbnail but in my situation, the image isn't even saved yet.
-
SilentDev almost 10 yearsI'll use this as a backup but I am looking for a way to implement a solution it from scratch. I don't think it should be hard, I just can't find the right PIL / Pillow documentation.
-
SilentDev almost 10 yearsright but the thing is, what is im? If you look at my code, I don't specify im. I do instance.save (and instance = form.save). So I don't have an im to begin with since I cannot open the file (in the example you liked to, they first did im = Image.open(infile)). How do I open a file which I do not know the location of yet since I did not save it yet? I want to save an image and a thumbnail version of the image at the same time.
-
Albert Tugushev almost 10 yearsYou should make the tumbnail after save a model instance. Save an instance like this:
obj = instance.save()
. After that you can get an image instance like this:im = Image.open(obj.photo.path)
. But it's better to override a save method of a class UploadImageForm and deal with tumbnail. -
stupidbodo over 7 yearsWouldn't it be better to throw exceptions within
make_thumbnail
instead of returning False? This way, you can know the exact cause of the exceptions instead of having a generic exception message -
user2012801 about 7 yearsThis throws
ValueError: seek of closed file
when trying to edit a photo instance in the admin app without reuploading the image (django 1.11, python 3.6), which is kind of handy as you don't need to regen the thumbnail if the image hasn't changed. To take this into account can addif not self.photo.closed:
before callingself.make_thumbnail()
-
user3750325 over 4 yearsWill the make_thumbnail method be called on edit if a non-image field of this record is updated?
-
Andy Stagg over 4 yearsThis works until you move off the base media root, IE, upload_to='my_sub_folder'. I was ending up with the thumbnail created in mediaroot/my_sub_folder/my_subfolder/. Updating the
dst_name, dst_ext
line to:dst_name, dst_ext = os.path.splitext(os.path.basename(src_image_field.name))
and the dst_fname line todst_fname = '{}{}{}{}'.format(dst_name, sep, name_suffix, dst_ext)
corrected the issue for me. -
MrColes over 4 yearsdoes this mean the thumbnails are generated every time save is called, even if
photo
is not changed (e.g., if the model had extra fields and you wanted to update those)? -
coredumped0x almost 3 yearsI built on your response and it really helped me. going from one bug to another, I questioned my sanity. I even thought of getting a gallon of gas and burning my computer.