How to save pillow image object to Django ImageField?
Solution 1
Here's a working example (Python3, django 1.11) that takes the image from an Model.ImageField
, performs a resize operation on it using PIL (Pillow), and then saves the resulting file to the same ImageField. Hopefully this should be easy to adapt to any processing you have to do to your models' images.
from io import BytesIO
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.core.files.base import ContentFile
from PIL import Image
IMAGE_WIDTH = 100
IMAGE_HEIGHT = 100
def resize_image(image_field, width=IMAGE_WIDTH, height=IMAGE_HEIGHT, name=None):
"""
Resizes an image from a Model.ImageField and returns a new image as a ContentFile
"""
img = Image.open(image_field)
if img.size[0] > width or img.size[1] > height:
new_img = img.resize((width, height))
buffer = BytesIO()
new_img.save(fp=buffer, format='JPEG')
return ContentFile(buffer.getvalue())
#assuming your Model instance is called `instance`
image_field = instance.image_field
img_name = 'my_image.jpg'
img_path = settings.MEDIA_ROOT + img_name
pillow_image = resize_image(
image_field,
width=IMAGE_WIDTH,
height=IMAGE_HEIGHT,
name=img_path)
image_field.save(img_name, InMemoryUploadedFile(
pillow_image, # file
None, # field_name
img_name, # file name
'image/jpeg', # content_type
pillow_image.tell, # size
None) # content_type_extra
)
Solution 2
You can create pre_save receiver for your Model:
from io import BytesIO
from functools import partial
from django.db import models
from django.core.files.uploadedfile import InMemoryUploadedFile
from PIL import Image
class Article(models.Model):
title = models.CharField(max_length=120)
slug = models.SlugField(max_length=120, unique=True)
image = models.ImageField(
upload_to=upload_image_location
)
thumbnail_image = models.ImageField(
upload_to=partial(upload_image_location, thumbnail=True),
editable=False, blank=True
)
def create_thumbnail(self):
image = Image.open(self.image.file.file)
image.thumbnail(size=(310, 230))
image_file = BytesIO()
image.save(image_file, image.format)
self.thumbnail_image.save(
self.image.name,
InMemoryUploadedFile(
image_file,
None, '',
self.image.file.content_type,
image.size,
self.image.file.charset,
),
save=False
)
@receiver(models.signals.pre_save, sender=Article)
def prepare_images(sender, instance, **kwargs):
if instance.pk:
try:
article = Article.objects.get(pk=instance.pk)
old_image = article.image
old_thumbnail_image = article.thumbnail_image
except Article.DoesNotExist:
return
else:
new_image_extension = os.path.splitext(instance.image.name)[1]
if old_image and not old_image.name.endswith(new_image_extension):
old_image.delete(save=False)
old_thumbnail_image.delete(save=False)
if not instance.thumbnail_image or not instance.image._committed:
instance.create_thumbnail()
Thumbnail of image is created in create_thumbnail
method using Pillow. This method work fine with django-storages and saving thumbnail in custom storage with name like 'article_slug_thumbnail.jpeg'.
My upload_image_location method:
def upload_image_location(instance, filename, thumbnail=False):
_, ext = os.path.splitext(filename)
return f'articles/{instance.slug}{f"_thumbnail" if thumbnail else ""}{ext}'
Solution 3
class Profile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
image = models.ImageField(default = 'default.jpg',upload_to='profile_pics')
def __str__(self):
return f'{self.user.username} Profile'
def save(self):
super().save()
img = Image.open(self.image.path)
if img.height >300 or img.width >300:
oputput_size = (300,300)
img.thumbnail(oputput_size)
img.save(self.image.path)
Related videos on Youtube
EquipDev
Updated on September 14, 2022Comments
-
EquipDev over 1 year
Having a Django model for thumbnail image like:
class Thumb(models.Model): thumb = models.ImageField(upload_to='uploads/thumb/', null=True, default=None)
The view generates a thumbnail with the
pillow
package, and should save this in aThumb
instance, using code like:image.thumbnail((50, 50)) inst.thumb.save('thumb.jpg', ???)
What is the right way to make
image
data for theinst.thumb.save
at???
?I was able to get the below to work:
thumb_temp = NamedTemporaryFile() image.save(thumb_temp, 'JPEG', quality=80) thumb_temp.flush() inst.thumb.save('thumb.jpg', File(thumb_temp)) thumb_temp.close() # Probably required to ensure temp file delete at close
But it seems rather clumsy to write a temporary file just to pass internal data to
inst.thumb.save
, so I wonder if there is a more elegant way to do it. Documentation for Django classNamedTemporaryFile
. -
Yosef Salmalian almost 3 yearsthis raises Image object has no attribute 'read' error
-
Escher almost 3 years@YosefSalmalian you should probably make a new question, as
read
is not called onPIL.Image
in this answer's code. -
FloLie almost 3 yearsIt would be very helpful, to add context and an explanation to your answer.