How can I programatically save an Image to an ImageField using Django-Cumulus? - django

I am using Django-Cumulus to store images to Rackspace's Cloudfiles platform.
I want to, dynamically, manipulate my images and save them as a new ImageField for my Model. For example, I have a Photo model with these ImageFields: image, thumb_256x256
In my Form's save() method, I am letting the user specify the cropping locations (using JCrop).
Anyways, I know how to grab the existing image file that the user uploaded. I also know how to apply manipulations with PIL. The problem I'm running into is creating a new Rackspace File and writing to it.
I keep getting the exception "NoSuchObject".
Here's some example code:
def save(self, commit=True):
""" Override the Save method to create a thumbnail of the image. """
m = super(PhotoUpdateForm, self).save(commit=False)
image = Image.open(m.image.file)
image.thumbnail((256,256), Image.ANTIALIAS)
thumb_io = CloudFilesStorageFile(storage=CLOUDFILES_STORAGE, name='foo/bar/test.jpg')
image.save(thumb_io.file, format='JPEG')
Also, once I get to this point -- what's the best way of setting this image to the model's other ImageField? (m.thumb_256x256 in my case)
Thanks in advanced!
Update: The name of the actual Cloudfiles Django app I'm using is "django-cumulus"

Here is a temporary solution. I'm having an issue with setting the new filename properly. It simply appends a _X to the filename. So for example, somefilename.jpg becomes somefilename_1.jpg whenever I save a new version.
This code is a bit ugly but does get the job done. It creates a cropped version of the image and will also generate a thumbnail if needed.
def save(self, commit=True):
""" Override the Save method to create a thumbnail of the image. """
m = super(PhotoUpdateForm, self).save(commit=False)
# Cropped Version
if set(('x1', 'x2', 'y1', 'y2')) <= set(self.cleaned_data):
box = int(self.cleaned_data['x1']), \
int(self.cleaned_data['y1']), \
int(self.cleaned_data['x2']), \
int(self.cleaned_data['y2'])
image = Image.open(m.image.file)
image = image.crop(box)
temp_file = NamedTemporaryFile(delete=True)
image.save(temp_file, format="JPEG")
m.image.save("image.jpg", File(temp_file))
cropped = True # Let's rebuild the thumbnail
# 256x256 Thumbnail
if not m.thumb_256x256 or cropped:
if not image:
image = Image.open(m.image.file)
image.thumbnail((256,256), Image.ANTIALIAS)
temp_file = NamedTemporaryFile(delete=True)
image.save(temp_file, format="JPEG")
m.thumb_256x256.save("thumbnail.jpg", File(temp_file))
if commit: m.save()
return m

Related

Django generate file and save it to model

My Django app generates a file. It takes img1.png and watermark.png and past them together and saves it again in the folder.
Everything works as expected.
This is the function:
def generate():
img1 = Image.open(f'{current_path}/media/pdf/img1.png')
img2 = Image.open(f'{current_path}/media/watermark.png')
img1.paste(img2, (150, 250), img2)
img1.save(f'{current_path}/media/pdf/generatedfile.png')
When working locally on my computer everything is good with specifying the path. However, in production it does not work anymore. I need to save the generatedfile.png directly on AWS S3.
For this reason I have create a simple model:
class pngUploadModel(models.Model):
auto_increment_id = models.AutoField(primary_key=True, default=True)
image = models.ImageField(null=True, blank=True, upload_to="png/")
I am able to upload images to this model using the admin interface. Everything still works as expected.
Now to my struggle.
I need to generate the image, and saving it "directly" to the model. Without saving it first to the path (because this will not work in production).
Approach:
def generate():
img1 = Image.open(f'{current_path}/media/pdf/img1.png')
img2 = Image.open(f'{current_path}/media/watermark.png')
img1.paste(img2, (150, 250), img2)
img1.save(f'{current_path}/media/pdf/generatedfile.png')
try:
filename = pngUploadModel.objects.create()
filename.image = img2
print('worked!')
except Exception as e:
print(f'this is the error message: {e}')
Output:
It creates an object in my model which I can see on my admin panel, but the model is empty, there is no image.
How can I save the generated image to my model, without having to save it first to my local directory. I was thinking to use something like a tempfile but I do not know if it is the right way.
If I'm correct, you want to get the generated file from the file path (f'{current_path}/media/pdf/generatedfile.png') and save it to your pngUploadModel.
An approach that I remember taking recently was to use the same prefix of the generated filename, setting that to be where the model instance image field points. For example:
def generate():
img1 = Image.open(f'{current_path}/media/pdf/img1.png')
img2 = Image.open(f'{current_path}/media/watermark.png')
img1.paste(img2, (150, 250), img2)
img1.save(f'{current_path}/media/pdf/generatedfile.png')
# the prefix string of the generated file -> f'{current_path}/media/pdf/generatedfile.png'
try:
genFile = pngUploadModel()
# Using the path/prefix of the generated file to be set to the image field
genFile.image = f'{current_path}/media/pdf/generatedfile.png'
genFile.save()
print('worked!')
except Exception as e:
print(f'this is the error message: {e}')
I used this answer as my guide then and it worked perfectly.
Another way is to save the generated file to the image field by passing a few arguments to the save() on the image/file field. Example:
from django.core.files.base import ContentFile # ensure you import
def generate():
prefix = f'{current_path}/media/pdf/generatedfile.png'
img1 = Image.open(f'{current_path}/media/pdf/img1.png')
img2 = Image.open(f'{current_path}/media/watermark.png')
img1.paste(img2, (150, 250), img2)
img1.save(prefix)
# the prefix string of the generated file -> f'{current_path}/media/pdf/generatedfile.png'
# with open('/path/to/already/existing/file', 'rb') as f:
with open(prefix, 'rb') as f:
data = f.read()
genFile = pngUploadModel()
genFile.image.save('generatedfile.png', ContentFile(data))
genFile.save()
Ideally, that should work. You can also view other answers to this question as they might be helpful or can be used for future reference.

ValueError when uploading resized django images to google cloud

i have this model which works fine when uploading resized images to the media file on my django project
class ItemImage(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
item = models.ForeignKey(Item, on_delete=models.CASCADE)
image = models.ImageField(null=True, blank=True,upload_to='item_img/')
created = models.DateTimeField(auto_now_add=True)
def save(self):
im = Image.open(self.image)
im_name = uuid.uuid4()
im = im.convert('RGB')
output = BytesIO()
# Resize/modify the image
im = im.resize((700, 700))
# after modifications, save it to the output
im.save(output, format='JPEG', quality=90)
output.seek(0)
# change the imagefield value to be the newley modifed image value
self.image = InMemoryUploadedFile(output, 'ImageField', "%s.jpg" % self.image.name, 'image/jpeg',
sys.getsizeof(output), None)
super(ItemImage, self).save()
def __str__(self):
return self.item.title
when i changed the file storage to google cloud i faced this error when uploading the images
ValueError at /ar/dashboard/my_items/edit_item/add_item_image/2/
Size 120495 was specified but the file-like object only had 120373 bytes remaining.
note that the images are uploaded successfully when i remove the save method that is added so is there anything that i need to change in that save method when dealing with gcloud?
i found a similar problem on github and he explained the error as follow "I think this is an error in the end user code which GCS rejects and the other services are more liberal about. The call sys.getsizeof(fi_io) yields the size of the BytesIO object, not the size of the buffer"
so i changed sys.getsizeof(output) to len(output.getbuffer())
and that's it it works with both google cloud and local media files

django, upload tif image

I am trying to upload a tif image (greyscale), using django ImageField.
I have installed Pillow==8.3.1 and using Python 3.9.
The app works only with PNG/JPEG images.
Here is the model I am using:
class Upload(models.Model):
image = models.ImageField(upload_to='images')
title = models.CharField(max_length=200)
action = models.CharField(max_length=50,choices=ACTION_CHOICES)
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
#breakpoint()
# def __str__(self):
# pixels = tfi.imread(self.image)
# return np.shape(np.array(pixels))
def save(self,*args,**kwargs):
#open image
#breakpoint()
if self.action=='tif':
pixels = tfi.imread(self.image)
else:
pixels = Image.open(self.image)
#pixels = tfi.imread(self.image)
pixels = np.array(pixels)
pixels=pixels[:,:,0]
#pixels = pixels[0,:,:]
#use the normalisation method
img = get_image(pixels)
im_pil=Image.fromarray(img)
#save
buffer = BytesIO()
im_pil.save(buffer,format='png')
image_png = buffer.getvalue()
self.image.save(str(self.image), ContentFile(image_png),save=False)
super().save(*args,**kwargs)
#return self.image_png
Django's ImageField requires the third-party package Pillow. It depends on the pillow to verify that a file is indeed an image or not. This is not dependant on the file type, but on the content of the file itself.
please check your version of pillow you are using, and check the corresponding documentation of your pillow version.
https://pypi.org/project/Pillow/
supported image format by pillow 8.3.1
but if still something doesn't work for you you can always use FileField which doesn't look for what file or format u are uploading until u are uploading a file please check out documentation
https://www.geeksforgeeks.org/filefield-django-models/
Although I would recommend you to use imagefield for only image and read the documentation now it totally up to you what you want to use.

Resize thumbnails django Heroku, 'backend doesn't support absolute paths'

I've got an app deployed on Heroku using Django, and so far it seems to be working but I'm having a problem uploading new thumbnails. I have installed Pillow to allow me to resize images when they're uploaded and save the resized thumbnail, not the original image. However, every time I upload, I get the following error: "This backend doesn't support absolute paths." When I reload the page, the new image is there, but it is not resized. I am using Amazon AWS to store the images.
I'm suspecting it has something to do with my models.py. Here is my resize code:
class Projects(models.Model):
project_thumbnail = models.FileField(upload_to=get_upload_file_name, null=True, blank=True)
def __unicode__(self):
return self.project_name
def save(self):
if not self.id and not self.project_description:
return
super(Projects, self).save()
if self.project_thumbnail:
image = Image.open(self.project_thumbnail)
(width, height) = image.size
image.thumbnail((200,200), Image.ANTIALIAS)
image.save(self.project_thumbnail.path)
Is there something that I'm missing? Do I need to tell it something else?
Working with Heroku and AWS, you just need to change the method of FileField/ImageField 'path' to 'name'. So in your case it would be:
image.save(self.project_thumbnail.name)
instead of
image.save(self.project_thumbnail.path)
I found the answer with the help of others googling as well, since my searches didn't pull the answers I wanted. It was a problem with Pillow and how it uses absolute paths to save, so I switched to using the storages module as a temp save space and it's working now. Here's the code:
from django.core.files.storage import default_storage as storage
...
def save(self):
if not self.id and not self.project_description:
return
super(Projects, self).save()
if self.project_thumbnail:
size = 200, 200
image = Image.open(self.project_thumbnail)
image.thumbnail(size, Image.ANTIALIAS)
fh = storage.open(self.project_thumbnail.name, "w")
format = 'png' # You need to set the correct image format here
image.save(fh, format)
fh.close()
NotImplementedError: This backend doesn't support absolute paths - can be fixed by replacing file.path with file.name
How it looks in the the console
c = ContactImport.objects.last()
>>> c.json_file
<FieldFile: protected/json_files/data_SbLN1MpVGetUiN_uodPnd9yE2prgeTVTYKZ.json>
>>> c.json_file.name
'protected/json_files/data_SbLN1MpVGetUiN_uodPnd9yE2prgeTVTYKZ.json'

Saving JPG format with PIL

I'm using PIL to make thumbnails of images I upload and everything is fine with PNGs or GIFs. However, uploading JPGs is giving me a headache. I kept getting a invalid format type for a while, and then I found this at the bottom of the JPG page on the PIL website...
Note: To enable JPEG support, you need to build and install the IJG
JPEG library before building the Python Imaging Library. See the
distribution README for details.
Anyway, so I deployed to Heroku and for some reason it seems to be no longer giving me the invalid format error that I had been getting on my local... except even though there is now a photo object living in the db, I can't seem to access them. I drop their location into into an image tag but I keep getting a broken image link symbol.
Here is what my override save looks like in models:
def save(self, force_update=False, force_insert=False, thumb_size=(90,150)):
image = Image.open(self.image)
if image.mode not in ('L', 'RGB'):
image = image.convert('RGB')
# save the original size
self.image_width, self.image_height = image.size
image.thumbnail(thumb_size, Image.ANTIALIAS)
# save the thumbnail to memory
temp_handle = StringIO()
image.save(temp_handle, format='JPEG')
temp_handle.seek(0) # rewind the file
# save to the thumbnail field
suf = SimpleUploadedFile(os.path.split(self.image.name)[-1],
temp_handle.read(),
content_type='image/jpg')
self.thumbnail.save(suf.name, suf, save=False)
self.thumbnail_width, self.thumbnail_height = image.size
#save the image object
super(Photo, self).save(force_update, force_insert)