Saving BytesIO to Django ImageField - python

I have a web scraper that I want to download an image of the page it's scraping and save it as a "screenshot" ImageField in a Django model. I am using this code:
def save_screenshot(source,screenshot):
box = (0, 0, 1200, 600)
im = Image.open(io.BytesIO(screenshot))
region = im.crop(box)
tempfile_io = io.BytesIO()
region.save(tempfile_io, 'JPEG', optimize=True, quality=70)
source.screenshot.save(source.slug_name+"-screenshot",ContentFile(tempfile_io.getvalue()),save=True)
It saves the screenshot to the /media/news_source_screenshots/ directory but doesn't save it to the model. The model field is defined as:
screenshot = models.ImageField(upload_to='news_source_screenshots',blank=True,null=True)
What am I missing?

So it turns out the above code works great! The issue was that I was calling the above method using a piece of code like this:
source = NewsSource.objects.get(name=name)
html,screenshot = get_url(source.url)
save_screenshot(source,screenshot)
source.save()
So the save_sceenshot method worked but then the work it had done was overwritten by my source.save() call. Go figure!

Related

images do not match pil, django model

I'm trying to create a QR Code in my Django model which creates itself whenever a new record is saved. The problem is that when testing in my dev environment everything is fine but in production I get an error on the specified line below saying: ValueError, images do not match.
from django.core.files import File
import qrcode
from io import BytesIO
from PIL import Image
from datetime import date
# this is the save method of the django model class, I just left out the other methods
def save(self, *args, **kwargs):
img = qrcode.make(self.account_id)
canvas = Image.new('RGB', (400, 400), 'white')
canvas.paste(img) # Error occurs on this line
buffer = BytesIO()
canvas.save(buffer, 'PNG')
canvas.close()
fname = f'pdf-qr-code-{self.account_id}.png'
self.menu_qr_code.save(fname, File(buffer), save=False)
super(PDFMenu, self).save(*args, **kwargs)
I don't know if maybe the problem is with my configurations on my ubuntu server since that's the place where the problem takes place but maybe a new way of creating a qr code within this custom save method is necessary.
Thanks in advance for the help.

Django choose in what location images are loaded in model's save method

Here's my save method for model with field image = models.ImageField(upload_to='products/%Y/%m/%d', blank=True):
def save(self, *args, **kwargs):
if self.image:
self.image = compress(self.image)
if self.second_image:
self.second_image = compress(self.second_image)
if self.third_image:
self.third_image = compress(self.third_image)
if self.fourth_image:
self.fourth_image = compress(self.fourth_image)
super().save(*args, **kwargs)
It works fine and compresses all images but changes image's directory every time when I click save in django admin. It makes all images' paths be like:
before edited and saved: products/2020/11/05/img.jpeg
after: products/2020/11/05/products/2020/11/05/img.jpeg
click save one more time: products/2020/11/05/products/2020/11/05/products/2020/11/05/img.jpeg
And then I get this error:
SuspiciousFileOperation at /admin/shop/product/6/change/
Storage can not find an available filename for "products\2020\11\05\products\2020\11\05\products\2020\...... .jpeg".
Please make sure that the corresponding file field allows sufficient "max_length".
How can I fix this problem? I think I need to choose location where saved images would be stored. Django doesn't let me use absolute path in upload_to field so I have no idea.
compress func is:
from io import BytesIO
from PIL import Image
from django.core.files import File
def compress(image):
im = Image.open(image)
if im.mode != 'RGB':
im = im.convert('RGB')
im_io = BytesIO()
im.save(im_io, 'JPEG', quality=70)
compressed_image = File(im_io, name=image.name)
return compressed_image
The problem is that the filename is updated everytime. At each step, you should make sure that the filename is only a filename. Something like this:
import os
if self.image:
self.image = compress(self.image)
self.image.name = os.path.basename(self.image.name)
I don't know exactly what is your compress function, but maybe you could also check that it doesn't do something weird with the filename.

Getting image from Firebase and creating stripe.File

I am trying transfer a file to Stripe from a firebase database using the stripe.File.create() method. Here is the code that I am using:
file_url = storage.child("/path/to/file").get_url(token=None)
response = requests.get(file_url, stream=True)
img = Image.open(BytesIO(response.content))
stripe.File.create(
purpose="identity_document",
file=img
)
But when I run this code I get:
Request req_E7fskNVgpHHlRm: Invalid hash
I believe I am getting the correct image from firebase, since I can run the following line and get the image saved to my local drive:
img.save("test.jpg")
But Stripe doest not seem to like the file format that I am giving it. I believe the file has to be supplied in binary mode, so perhaps I simply need to do edit the line img = Image.open(BytesIO(response.content)) to get the file in binary mode.
Any feedback is appreciated.
I figured it out. img = Image.open(BytesIO(response.content)) creates a JpegImageFile object, which is an image. But Stripe is expecting a file, something like io.BufferedReader object. So Stripe is saying that you are supplying an image, when really it just wants a file. To fix the issue, just change the img = line to:
imgFile = BytesIO(response.content)

How to get image from the web and save it to a imageField using the url

I'm trying to get a image from the web and save it to the imageField using the images url.
The below code spits out a error ('JpegImageFile' object has no attribute '_committed').
from PIL import Image
import urllib.request
import io
if form.is_valid():
instance = form.save(commit=False)
URL = 'http://www.image.jpg'
with urllib.request.urlopen(URL) as url:
file = io.BytesIO(url.read())
img = Image.open(file)
instance.image = img
instance.image has to be of type django.core.files.images.ImageFile I am testing my models with this code. Maybe it points you to the correct direction :
# This gets me a temp Image for testing. In your case it would be a real file
def get_test_image_file(tmpdir=None):
file = tempfile.NamedTemporaryFile(suffix='.png', dir=tmpdir)
return ImageFile(file, name=file.name)
and the for the model test I do :
def test_addimage_create(self):
adimage = AdImage.objects.create(advertiser=self.advertiser,
picture=get_test_image_file(tmpdir=MEDIA_ROOT))
self.assertIsInstance(adimage, AdImage)
so in your case instead of instance.image = img. img has to be an Object of Type ImageFile which then can be assigned to the form.instance. Btw. you should assign the values to the instance before save()

Upload Image To Imgur After Resizeing In PIL

I am writing a script which will get an image from a link. Then the image will be resized using the PIL module and the uploaded to Imgur using pyimgur. I dont want to save the image on disk, instead manipulate the image in memory and then upload it from memory to Imgur.
The Script:
from pyimgur import Imgur
import cStringIO
import requests
from PIL import Image
LINK = "http://pngimg.com/upload/cat_PNG106.png"
CLIENT_ID = '29619ae5d125ae6'
im = Imgur(CLIENT_ID)
def _upload_image(img, title):
uploaded_image = im.upload_image(img, title=title)
return uploaded_image.link
def _resize_image(width, height, link):
#Retrieve our source image from a URL
fp = requests.get(link)
#Load the URL data into an image
img = cStringIO.StringIO(fp.content)
im = Image.open(img)
#Resize the image
im2 = im.resize((width, height), Image.NEAREST)
#saving the image into a cStringIO object to avoid writing to disk
out_im2 = cStringIO.StringIO()
im2.save(out_im2, 'png')
return out_im2.getvalue()
When I run this script I get this error: TypeError: file() argument 1 must be encoded string without NULL bytes, not str
Anyone has a solution in mind?
It looks like the same problem as this, and the solution is to use StringIO.
A common tip for searching such issues is to search using the generic part of the error message/string.

Categories