Reputation: 3183
I am try resize an image when this is saved.
Specifically, my images are stored on Amazon S3, then I use the django-storages
and boto3
third party applications
When a image is saved, this is stored in my Amazon S3 bucket, having a acces url such as follow:
The code to save and resize the image is this:
class UploadStudyOffer(models.Model):
study_offer = models.ForeignKey(StudiesOffert, related_name='uploadsstudyoffer')
image = models.ImageField(upload_to=get_image_path)
# images folder per object
def save(self, *args, **kwargs):
super(UploadStudyOffer, self).save(*args, **kwargs)
# We first check to make sure an image exists
if self.image:
# Open image and check their size
image = Image.open(self.image)
i_width, i_height = image.size
max_size = (100,100)
# We resize the image if it's too large
if i_width > 1000:
image.thumbnail(max_size, Image.ANTIALIAS)
image.save(self.image.path)
When I upload an image, I get this message:
Exception Type: NotImplementedError at /host/study-offer/algoritmos-para-ensenanza/edit/images/
Exception Value: This backend doesn't support absolute paths.
And I am not sure, if the error is manage at storages
or boto
backends or in Pillow
.
Then at level of Pillow
I found the following options in the moment of save the image, such as follow:
I change the section code:
image.save(self.image.path)
to:
image.save(self.image.name)
And I get this error:
File "/home/bgarcial/workspace/hostayni_platform/hosts/models.py" in save
542. image.save(self.image.name) #[Errno 2] No such file or directory: 'studyoffer_images/ingenieria-de-sistemas/15061122523583.jpg'
File "/home/bgarcial/.virtualenvs/hostayni/lib/python3.6/site-packages/PIL/Image.py" in save
1725. fp = builtins.open(filename, "w+b")
Exception Type: FileNotFoundError at /host/study-offer/algoritmos-para-ensenanza/edit/images/
Exception Value: [Errno 2] No such file or directory: 'studyoffer_images/algoritmos-para-ensenanza/1900-X-1080-Wallpapers-022.jpg'
Of course, my image is stored om Amazon S3 and not locally in my project or hard disk, then I use the url
parameter of this way:
I change
image.save(self.image.name)
to
image.save(self.image.url)
And I get this error:
Exception Type: FileNotFoundError at /host/study-offer/algoritmos-para-ensenanza/edit/images/
Exception Value: [Errno 2] No such file or directory: 'https://s3-sa-east-1.amazonaws.com/ihost-project/media/studyoffer_images/algoritmos-para-ensenanza/15061122523583.jpg'
Getting the amazon s3 image URL does not works, even though the url is a valid url https://s3-sa-east-1.amazonaws.com/ihost-project/media/studyoffer_images/algoritmos-para-ensenanza/15061122523583.jpg
Then I change
image.save(self.image.url)
to:
image.save(self.image.file)
And my image is uploaded without any errors, but is not resized and is uploaded as its original format.
How to can I process a image uploaded from my application and their result was saved on Amazon S3 to after use them?
Upvotes: 3
Views: 3460
Reputation: 89
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
Upvotes: 0
Reputation: 86
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()
Upvotes: 7
Reputation: 5017
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="" />
Upvotes: 2