【问题标题】:Save a generated PIL image into an ImageField in django将生成的 PIL 图像保存到 django 中的 ImageField 中
【发布时间】:2018-08-10 09:46:43
【问题描述】:

我正在使用qrcode 生成二维码。当买了票,或者当买的是真的时,我想生成一个二维码图像并使用 PIL 进行一些更改。最后将修改后的画布保存到模型的Image字段中。

class Ticket(models.Model):
    booked_at = models.DateTimeField(default=timezone.now)
    qrcode_file = models.ImageField(upload_to='qrcode', blank=True, null=True)
    bought = models.BooleanField(default=False)

    def save(self, *args, **kwargs):
        if self.bought:
            ...
            ...
            qrcode_img = qrcode.make('some data')
            canvas = Image.new('RGB', (total_width, total_height), 'white')
            draw = ImageDraw.Draw(canvas)
            position = (left, top)
            canvas.paste(qrcode_img, position)

            self.qrcode_file = canvas
            self.booked_at = timezone.now()
            super(Ticket, self).save(*args, **kwargs)
            canvas.close()
            qrcode_img.close()
        else:
            self.booked_at = timezone.now()
            super(Ticket, self).save(*args, **kwargs)

但这会引发错误:

AttributeError: 'Image' 对象没有属性 '_committed'

如何将生成的 PIL 图像保存到 django 中的 ImageField 中?

【问题讨论】:

  • 这条线canvas = Image.new("RGB", (total_width, total_height), white)请问“图片”是从哪里来的?
  • @Martins 来自 PIL
  • 好吧,看完文档后才意识到。

标签: python django python-3.x python-imaging-library


【解决方案1】:

您可以使用 BytesIO 将 Pillow 文件保存到内存中的 blob。然后创建一个File 对象并将其传递给您的模型实例ImageField 的save 方法。

from io import BytesIO
from django.core.files import File

canvas = Image.new('RGB', (total_width, total_height), 'white')
...
blob = BytesIO()
canvas.save(blob, 'JPEG')  
self.qrcode_file.save('ticket-filename.jpg', File(blob), save=False) 

查看File 对象的django 文档。 https://docs.djangoproject.com/en/2.0/ref/files/file/#the-file-object

您必须使用save=False,因为默认的save=True 意味着在保存图像后会调用父模型的save 方法。你不想在这里递归,因为你通常会陷入无限循环。

【讨论】:

  • 我需要关闭博客对象还是没有必要?
  • 您不需要关闭 BytesIO。当 blob 被垃圾回收时,内存将被释放。
  • 您的方法可能会导致大规模请求的内存使用问题。
  • 如果您在单个服务器上同时创建数百万个二维码,则可能会发生这种情况。如果您对此感到担心,可以在完成后立即关闭 blob 对象,以立即释放内存。但是当 save 方法完成并且在当前范围内不再有对它的引用时,blob 应该自动关闭和删除。
【解决方案2】:

更改您的代码并使用 Django File,如下所示:

from django.core.files import File


class Ticket(models.Model):
    booked_at = models.DateTimeField(default=timezone.now)
    qrcode_file = models.ImageField(upload_to='qrcode', blank=True, null=True)
    bought = models.BooleanField(default=False)

    def save(self, *args, **kwargs):
        if self.bought:
            ...
            ...
            qrcode_img = qrcode.make('some data')
            canvas = Image.new('RGB', (total_width, total_height), 'white')
            draw = ImageDraw.Draw(canvas)
            position = (left, top)
            canvas.paste(qrcode_img, position)

            canvas.save('path/of/dest.png', 'PNG')
            destination_file = open('path/of/dest.png', 'rb')
            self.qrcode_file.save('dest.png', File(destination_file), save=False)
            destination_file.close()

            self.booked_at = timezone.now()
            super(Ticket, self).save(*args, **kwargs)
            canvas.close()
            qrcode_img.close()
        else:
            self.booked_at = timezone.now()
            super(Ticket, self).save(*args, **kwargs)

您可以将canvas 保存在media_rootupload_to 路径中,或保存在临时目录中或使用BytesIO 对象。

【讨论】:

  • 这会在磁盘中创建一个文件吗?我也必须删除文件对吗?
  • 您可以将文件保存在临时目录中,或使用 BytesIO
  • 理论上,如果您使用硬编码的path/of/dest.png,如果两个工作进程尝试并行写入和读取,您可能会遇到竞争条件。 Python 的标准库提供了一个tempfile 模块,可以在这里使用。上下文管理器with tempfile.NamedTemporaryFile(suffix='.png') as fp: 将在系统的tmp 文件夹中创建一个随机文件名,并在完成后将其删除。 docs.python.org/3/library/…
  • 是的,每个方法都有特殊的问题,但是使用磁盘保存文件可能比保存在内存上更好,这与调用此保存方法的速率有关。
猜你喜欢
  • 2015-03-30
  • 1970-01-01
  • 2012-04-29
  • 1970-01-01
  • 2013-02-13
  • 2011-12-15
  • 2010-11-21
  • 1970-01-01
  • 2015-05-24
相关资源
最近更新 更多