How to save pillow image object to Django ImageField?

13,505

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)
Share:
13,505

Related videos on Youtube

EquipDev
Author by

EquipDev

Updated on September 14, 2022

Comments

  • EquipDev
    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 a Thumb instance, using code like:

    image.thumbnail((50, 50))
    inst.thumb.save('thumb.jpg', ???)
    

    What is the right way to make image data for the inst.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 class NamedTemporaryFile.

  • Yosef Salmalian
    Yosef Salmalian almost 3 years
    this raises Image object has no attribute 'read' error
  • Escher
    Escher almost 3 years
    @YosefSalmalian you should probably make a new question, as read is not called on PIL.Image in this answer's code.
  • FloLie
    FloLie almost 3 years
    It would be very helpful, to add context and an explanation to your answer.