Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to save pillow image object to Django ImageField?

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.

like image 488
EquipDev Avatar asked Oct 05 '15 09:10

EquipDev


2 Answers

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
)
like image 139
Escher Avatar answered Oct 22 '22 23:10

Escher


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}'
like image 38
QuadX Avatar answered Oct 22 '22 23:10

QuadX