Skip to content

Instantly share code, notes, and snippets.

@valberg
Created April 20, 2012 14:55
Show Gist options
  • Save valberg/2429288 to your computer and use it in GitHub Desktop.
Save valberg/2429288 to your computer and use it in GitHub Desktop.
Django create thumbnail form ImageField and save in a different ImageField - now with updating!
# Extension of http://www.yilmazhuseyin.com/blog/dev/create-thumbnails-imagefield-django/
# Note: image_folder and thumbnail_folder are both a callable (ie. a lambda that does a '/'.join())
class Image(Media):
image = models.ImageField(
upload_to=image_folder
)
thumbnail = models.ImageField(
upload_to=thumbnail_folder,
max_length=500,
null=True,
blank=True
)
def create_thumbnail(self):
# original code for this method came from
# http://snipt.net/danfreak/generate-thumbnails-in-django-with-pil/
# If there is no image associated with this.
# do not create thumbnail
if not self.image:
return
from PIL import Image
from cStringIO import StringIO
from django.core.files.uploadedfile import SimpleUploadedFile
import os
# Set our max thumbnail size in a tuple (max width, max height)
THUMBNAIL_SIZE = (99, 66)
DJANGO_TYPE = self.image.file.content_type
if DJANGO_TYPE == 'image/jpeg':
PIL_TYPE = 'jpeg'
FILE_EXTENSION = 'jpg'
elif DJANGO_TYPE == 'image/png':
PIL_TYPE = 'png'
FILE_EXTENSION = 'png'
# Open original photo which we want to thumbnail using PIL's Image
image = Image.open(StringIO(self.image.read()))
# We use our PIL Image object to create the thumbnail, which already
# has a thumbnail() convenience method that contrains proportions.
# Additionally, we use Image.ANTIALIAS to make the image look better.
# Without antialiasing the image pattern artifacts may result.
image.thumbnail(THUMBNAIL_SIZE, Image.ANTIALIAS)
# Save the thumbnail
temp_handle = StringIO()
image.save(temp_handle, PIL_TYPE)
temp_handle.seek(0)
# Save image to a SimpleUploadedFile which can be saved into
# ImageField
suf = SimpleUploadedFile(os.path.split(self.image.name)[-1],
temp_handle.read(), content_type=DJANGO_TYPE)
# Save SimpleUploadedFile into image field
self.thumbnail.save(
'%s_thumbnail.%s' % (os.path.splitext(suf.name)[0], FILE_EXTENSION),
suf,
save=False
)
def save(self, *args, **kwargs):
self.create_thumbnail()
force_update = False
# If the instance already has been saved, it has an id and we set
# force_update to True
if self.id:
force_update = True
# Force an UPDATE SQL query if we're editing the image to avoid integrity exception
super(Image, self).save(force_update=force_update)
@twbagustin
Copy link

Reached this snippet from google, what if I upload a .gif image here ? :B

@ffeast
Copy link

ffeast commented Jun 5, 2014

Here is a ready-to-use django library that works the way your snippet does:
https://github.com/skitoo/django-thumbs

Not sure about updates though

@dimkoug
Copy link

dimkoug commented May 19, 2017

For python 3 support replace StringIO with BytesIO

from io import BytesIO

@neeraj9194
Copy link

neeraj9194 commented Sep 3, 2018

self.image.read() in line 44, sets the pointer to the last byte and causes a problem with Google storage multipart upload, so you can set seek(0).
google storage error: ValueError: Size 266518 was specified but the file-like object only had 0 bytes remaining.

add this at line 45
self.image_file.seek(0)

If anyone has a better solution really like to hear...

@vQuadX
Copy link

vQuadX commented Jul 27, 2019

You can use this method for creating thumbnail image:

from PIL import Image
from django.db.models.fields.files import ImageFieldFile
from django.core.files.uploadedfile import InMemoryUploadedFile

def _create_thumbnail(image_field: ImageFieldFile, thumbnail_image_field: ImageFieldFile, size: tuple):
    image = Image.open(image_field.file.file)
    image.thumbnail(size=size)
    image_file = BytesIO()
    image.save(image_file, image.format)
    thumbnail_image_field.save(
        image_field.name,
        InMemoryUploadedFile(
            image_file,
            None, '',
            image_field.file.content_type,
            image.size,
            image_field.file.charset,
        ),
        save=False
    )

@maxims94
Copy link

Django extracts the content-type from the extension of the given filename, not the content_type parameter of SimpleUploadedFile. So, you might as well use the normal ContentFile.

I ended up using the following:

  def resize(self, geom):
    from PIL import Image

    img = Image.open(self.image.path)
    img.thumbnail(geom)

    def image_to_byte_array(img):
      byte_arr = io.BytesIO()
      img.save(byte_arr, format=img.format)
      byte_arr = byte_arr.getvalue()
      return byte_arr
    
    img_byte = image_to_byte_array(img)
    img_file = ContentFile(img_byte)

    new_res = Resource()
    old_filename = os.path.split(self.image.name)[-1]
    new_res.image.save(old_filename,img_file,save=False)

    return new_res

@Fingel
Copy link

Fingel commented Feb 27, 2024

In 2024 this seems to work fine:

    def create_thumbnail(self):
        if not self.image:
            return

        image = Image.open(self.image)
        image.thumbnail((800, 800))
        image_file = BytesIO()
        image.save(image_file, image.format)
        self.thumb.save(
            self.image.name,
            InMemoryUploadedFile(
                image_file,
                None,
                None,
                self.image.file.content_type,
                image_file.tell(),
                self.image.file.charset,
            ),
            save=False,
        )

    def save(self, *args, **kwargs):
        self.create_thumbnail()
        return super().save(*args, **kwargs)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment