Search code examples
pythondjangopython-3.xpython-imaging-library

Save a generated PIL image into an ImageField in django


I am using qrcode to generate qrcode. When a ticket is bought, or when bought is true, I would like to generate a qrcode image and make some changes using PIL. Finally save the modified canvas into the Image field of the model.

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)

But this throws an error:

AttributeError: 'Image' object has no attribute '_committed'

How can I save a generated PIL image into an ImageField in django?


Solution

  • You can use a BytesIO to save the Pillow file to an in-memory blob. Then create a File object and pass that to your model instance ImageField's save method.

    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) 
    

    Check out the django documentation for the File object. https://docs.djangoproject.com/en/stable/ref/files/file/#the-file-object

    You have to use save=False, since the default save=True means that the parent model's save method would be called after the image is saved. You don't want recursion here, since you would typically end up in an infinite loop.