I want to have an image uploaded in the Django admin, resized and with registries on the database before writing to the disk.
Im using Python Imaging Library (PIL)
I was trying this:
from PIL import Image
class Categorias(models.Model):
image_height = models.PositiveIntegerField(editable=False,null=True)
image_width = models.PositiveIntegerField(editable=False,null=True)
the_image = models.ImageField(upload_to="uploads/image/category",width_field="image_width",height_field="image_height")
def save(self):
if not self.id and not self.the_image:
return;
image = Image.open(self.the_image)
ratio_height = (890*self.image_height)/self.image_width
size = (890,ratio_height)
image = image.resize(size, Image.ANTIALIAS)
image.save(self.the_image.path)
super(Categorias, self).save()
But this was saving the original image and the resized one to the disk, but the resized one has no registries on the database, only the original.
And what i want is just a resized image with registries on the database.
So i looked at this article:
Save image created via PIL to django model
And im trying this but with no sucess:
def save(self):
if not self.id and not self.the_image:
return;
image = Image.open(self.the_image)
ratio_height = (890*self.image_height)/self.image_width
size = (890,ratio_height)
tempfile = image.resize(size, Image.ANTIALIAS)
tempfile_io =StringIO.StringIO()
tempfile.save(tempfile_io, format='JPEG')
image_file = InMemoryUploadedFile(tempfile_io, None, "image.jpg",'image/jpeg',tempfile_io.len, None)
self.the_image = image.save(self.the_image.path,image_file)
super(Categorias, self).save()
Im getting the error:
TypeError at /admin/imagens/categorias/add/
__init__() takes exactly 8 arguments (7 given)
Im trying to solve this with no sucess
Full error on: http://pastebin.com/jxia5wPp
The error is trown when i click on save on the admin page.
Related
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.
I'm working with Django in Python 3, and I have a model with an ImageField and I'm trying to override the .save() to do tiff to jpeg conversion:
from PIL import Image
from io import BytesIO
from django.core.files.base import ContentFile
class MyModel(models.Model):
image = models.ImageField(upload_to=upload_image_to, editable=True, null=True, blank=True)
def save(self, *args, **kwargs):
pil_image_obj = Image.open(self.image)
new_image_io = BytesIO()
rgb_pil_image_obj = pil_image_obj.convert("RGB")
rgb_pil_image_obj.save(new_image_io, quality=90, format='JPEG')
# temp_name = self.image.name
# self.image.delete(save=False)
# self.image.save(
# temp_name,
# content=ContentFile(new_image_io.getvalue()),
# save=False
# )
super().save(*args, **kwargs)
However, this leads to:
tempfile.tif: Cannot read TIFF header.
*** OSError: -2
If I try the save experiment but only opening the TIFF file from disk instead of feeding PIL.Image with the Django InMemoryUploadedFile, then everything works absolutely fine and the tiff is converted to a jpeg.
Also pil_image_obj.verify() doesn't throw any exceptions.
I'm using Pillow==5.3.0
What could be the issue? And is there any other way to do such a conversion?
You need to pass the path of the image file to Image.open(). Currently you're passing the field itself.
pil_image_obj = Image.open(self.image.path) # Pass the path
I'm trying to overwrite save() method of the model to resize images. Every format works, except when saving a .jpg image. It's not saving images with .jpg extension.
I read the Pillow documentation and there's no JPG format.
class Business(models.Model):
photo = models.ImageField(_('photo'), storage=OverwriteStorage(),
upload_to=image_upload_to, blank=True, null=True)
def save(self, **kwargs):
"""
Changing dimensions of images if they are to big.
Set max height or width to 800px depending on the image is portrait or landscape.
"""
# Opening the uploaded image
im = Image.open(self.photo)
print(im)
output = BytesIO()
# set the max width or height
im.thumbnail((800, 800))
# find the ext of the file
ext = self.photo.name.split('.')[1].upper()
if ext in {'JPEG', 'PNG', 'GIF', 'TIFF'}:
# after modifications, save it to the output
im.save(output, format=ext, quality=100)
output.seek(0)
# change the imagefield value to be the newley modifed image value
self.photo = InMemoryUploadedFile(output, 'ImageField', "%s.jpg" % self.photo.name.split('.')[0],
'image/jpeg', sys.getsizeof(output), None)
super(User, self).save()
I don't know what I am missing here.
And what's the best way to do this on a custom User model. Using a signal, overwriting ImageField or ...
Any help is appreciated :)
You handled extension JPEG, but not JPG.
You may handle it simply with something like that before your if:
if ext == 'JPG':
ext = 'JPEG'
Important: You can not save the files as a .jpg
You must use another extension such as .jpeg
Example:
You have to use the Image object you import
from PIL import Image
def image_ex():
imagefile = 'images/original-image.jpg'
new_name = os.path.splitext('{}'.format(filename))[0]+'.jpeg'
Image.open(imagefile).rotate(270).filter(ImageFilter.DETAIL).save(new_name)
image_ex()
That works. Just remember dont save as a .jpg file extensions.
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()
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.