[Django]-Pillow Resizing images (thumbnail) on Amazon S3 – Django

6👍

✅

Fairly new to Django/Python, but this is how I solved it. Save large file to AWS, open it with Pillow, then resize and save in memory. Then push to AWS using default_storage just as the django-storages help docs suggest. Note that img.thumbnail will retain aspect with just the longer edge of the image set to 1000 pixels. image is the Django model ImageField.

from django.core.files.storage import default_storage
from io import BytesIO

..

def save(self, *args, **kwargs):
    #run save of parent class above to save original image to disk
    super().save(*args, **kwargs)

    memfile = BytesIO()

    img = Image.open(self.image)
    if img.height > 1000 or img.width > 1000:
        output_size = (1000, 1000)
        img.thumbnail(output_size, Image.ANTIALIAS)
        img.save(memfile, 'JPEG', quality=95)
        default_storage.save(self.image.name, memfile)
        memfile.close()
        img.close()

2👍

You can make it easier and use easy_thumbnails app.

If you want to crop the image on save then you can do it with:

from easy_thumbnails.fields import ThumbnailerImageField

CROP_SETTINGS = {'size': (1000, 500), 'crop': 'smart'}

class UploadStudyOffer(models.Model):

    image =ThumbnailerImageField(upload_to=get_image_path,
                                         resize_source=CROP_SETTINGS)

Or you can manually specify the size of the image in the template:

{% load thumbnail %}

<img src="{% thumbnail offer.image 1000x500 crop %}" alt="" />

0👍

Most of what I found online suggested removing the Pillow resize and writing an AWS Lambda function to handle the resize on upload. I initially tried that approach, but according to the AWS docs you shouldn’t use the same bucket for input and output, meaning I had to create a second S3 bucket just for resized images. I couldn’t figure out how to get that setup working with django-storages.

A second approach I found mentioned using a buffer to save the resized image into, and then saving that to AWS. The examples of this that I found were either incomplete or used old versions of python. Here is what actually worked for me.

user/models.py

from app.utils import image_resize

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    image = models.ImageField(default="profile-default.png", 
    upload_to="profile_pics")

    def __str__(self):
        return f"{self.user.username} Profile"

    def save(self, *args, **kwargs):
        image_resize(self.image, 512, 512)
        super().save(*args, **kwargs)

app/utils.py

from django.core.files import File
from pathlib import Path
from PIL import Image
from io import BytesIO

image_types = {
    "jpg": "JPEG",
    "jpeg": "JPEG",
    "png": "PNG",
    "gif": "GIF",
    "tif": "TIFF",
    "tiff": "TIFF",
}


def image_resize(image, width, height):
    # Open the image using Pillow
    img = Image.open(image)
    # check if either the width or height is greater than the max
    if img.width > width or img.height > height:
        output_size = (width, height)
        # Create a new resized “thumbnail” version of the image with Pillow
        img.thumbnail(output_size)
        # Find the file name of the image
        img_filename = Path(image.file.name).name
        # Spilt the filename on “.” to get the file extension only
        img_suffix = Path(image.file.name).name.split(".")[-1]
        # Use the file extension to determine the file type from the image_types dictionary
        img_format = image_types[img_suffix]
        # Save the resized image into the buffer, noting the correct file type
        buffer = BytesIO()
        img.save(buffer, format=img_format)
        # Wrap the buffer in File object
        file_object = File(buffer)
        # Save the new resized file as usual, which will save to S3 using django-storages
        image.save(img_filename, file_object)

I’m overriding the save method still, and calling a function I’ve placed in utils.py of my main application. The following happens in the image_resize function:

The image_function checks if the image is too wide or tall and, if it is, saves a resized version first to a memory buffer and then to S3. Back in the save method we call super().save() to save the remaining fields. The super().save() needs to be called after the image.save() or both the original and the resized images will get uploaded to S3.

I hope that was helpful to someone.

You can Visit https://blog.soards.me/posts/resize-image-on-save-in-django-before-sending-to-amazon-s3/ for more details

Leave a comment