如何将Pillow图像对象保存到Django ImageField?

20

有一个用于缩略图的 Django 模型,例如:

class Thumb(models.Model):
    thumb = models.ImageField(upload_to='uploads/thumb/', null=True, default=None)
视图使用pillow软件包来生成缩略图,并应该使用类似以下代码将其保存在一个名为Thumb的实例中:
image.thumbnail((50, 50))
inst.thumb.save('thumb.jpg', ???)

如何正确地为inst.thumb.save???处制作image数据?

我能够使下面的代码起作用:

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

但是,为了传递内部数据到inst.thumb.save,写一个临时文件似乎相当笨拙,因此我想知道是否有更优雅的方法。有关Django类NamedTemporaryFile的文档。

4个回答

31

这是一个可行的示例(Python3,Django 1.11),它从Model.ImageField中获取图像,使用PIL(Pillow)对其进行调整大小操作,然后将结果文件保存到同一ImageField中。希望这样很容易适应您需要对模型图像进行的任何处理。

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
)

3
这会导致出现“Image对象没有'read'属性”的错误。 - Yosef SL
1
@YosefSalmalian,你可能需要提出一个新问题,因为在这个答案的代码中没有调用PIL.Image上的read方法。 - Escher
我也遇到了读取错误,我认为是在Django保存时出现的。 - wobbily_col

2

你可以为你的模型创建 pre_save 接收器:

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()

使用Pillow库在create_thumbnail方法中创建图像的缩略图。此方法与django-storages很好地配合使用,并将缩略图保存在名称为“article_slug_thumbnail.jpeg”的自定义存储中。

我的upload_image_location方法:

def upload_image_location(instance, filename, thumbnail=False):
    _, ext = os.path.splitext(filename)
    return f'articles/{instance.slug}{f"_thumbnail" if thumbnail else ""}{ext}'

1
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)

4
为了帮助您更好地理解,我将为您提供翻译。原文是“为你的回答添加上下文和解释会非常有帮助”。我的翻译是“增加上下文和解释可以使您的答案更易于理解。” - FloLie

-1
from io import BytesIO
from PIL import Image
from django.core.files.images import ImageFile
import requests

img_url = 'https://cdn.pixabay.com/photo/2021/08/25/20/42/field-6574455__340.jpg'

res = Image.open(requests.get(img_url, stream=True).raw)
filename = 'sample.jpeg'
img_object= ImageFile(BytesIO(res.fp.getvalue()), name=filename)

// django_image_field = img_object

2
目前您的回答不清晰。请[编辑]以添加更多细节,帮助他人理解它是如何回答问题的。您可以在帮助中心找到更多编写好回答的信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接