How to use PIL in django models - django

I like to resize an uploaded image(ImageField) before finally storing, I heard that python has an image library called PIL and I would like to use it do that task but I'm not sure on how to start.
Any suggestions on how to do it?
Thanks

you can override model's save function where you can open file and resize it (not recommended, as it will resize it each time you save a model), you an resize after file upload (e.g. before/during form.save())
but IMHO a far better solution is to use a dedicated app for this, my favourite is sorl-thumbnails

I've just found out on how to do it but is there a way to simplify it? I'm new in python and django so I'm not sure if this is the proper way to do it.
below is my code:
from django.db.models.signals import pre_delete, pre_save
def on_save_image(sender, **kwargs):
import PIL
obj = kwargs['instance']
if obj.file:
try:
original = sender.objects.get(pk = obj.pk)
if original.file:
#if change image then delete original file
original.file.delete()
except ObjectDoesNotExist:
pass
finally:
img = PIL.Image.open(obj.file)
img.thumbnail((500, 500))
# reset pointer to start at 0 again
obj.file.open()
img.save(obj.file)
pre_save.connect(on_save_image, sender = Image)
# delete file in memory
def on_delete_image(sender, **kwargs):
obj = kwargs['instance']
if obj.file:
obj.file.delete()
pre_delete.connect(on_delete_image, sender = Image)
Thanks

Related

Django correctly delete image after updating file

There is a similar question here but that is from 2011 and Django 1.2. Many things have changed since then. I'm using Django 1.9.
I'm wondering how should I do this correctly, without any risks of messing up the filesystem. I have an ImageField in a model:
class CartaMagicPy(models.Model):
imagen = models.ImageField(null=True, upload_to=ubicar_magicpy)
On a first moment, the image is saved. But then, the user crops the image and I get a new image assigned to the same ImageField.
The old image is not deleted from the filesystem. How can I delete it? The problem is that model_object.imagen.path after being updated contains the path to the new image. And if I delete it before I update it with the new image, I risk that maybe the save could fail and then I end up with no image at all. Is there a combination of post-save signals I could use? Any advice will help.
As seen in this answer, "there's an app for that": django-cleanup.
If you prefer to implement it yourself, the accepted answer to the question #raphv linked in the comment gives some good pointers.
A good solution for updated change followup is to use model signals; I tend to use post_init and post_save, in this case to delete the old image file after the change has been made. Something like so:
from django.db.models.signals import post_init, post_save
from django.dispatch import receiver
from myapp.models import CartaMagicPy
#receiver(post_init, sender= CartaMagicPy)
def backup_image_path(sender, instance, **kwargs):
instance._current_imagen_file = instance.imagen
#receiver(post_save, sender= CartaMagicPy)
def delete_old_image(sender, instance, **kwargs):
if hasattr(instance, '_current_imagen_file'):
if instance._current_imagen_file != instance.imagen.path:
instance._current_imagen_file.delete(save=False)
This also seems to be generally what the linked package above does, plus some additional safety checks and cleanup.
We could optimize if the image accepts null values with the following code.
#receiver(post_init, sender= CartaMagicPy)
def backup_image_path(sender, instance, **kwargs):
instance._current_imagen_file = instance.imagen
#receiver(post_save, sender=CartaMagicPy)
def delete_old_image(sender, instance, **kwargs):
if hasattr(instance, '_current_imagen_file'):
if instance.imagen:
if instance._current_imagen_file != instance.imagen.path:
instance._current_imagen_file.delete(save=False)
else:
if instance._current_imagen_file:
instance._current_imagen_file.delete()
I am not sure, but you can try this
use Django-cleanup
pip install django-cleanup
settings.py
INSTALLED_APPS = (
...
'django_cleanup', # should go after your apps
...
)

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'

upload images from URL to easy_thumbnails field

I want to upload many images from URLs while I create objects with a script.
#models.py
class Widget(TimeStampedModel):
name = CharField ... etc, etc
pic = ThumbnailerImageField(_('Widget Pic'),
upload_to='widget/pic/',
help_text = _('Please submit your picture here.'),
null=True, blank=True)
so I thought of using the save method in that class to download and save the images. So my script creates the Widget objects and saves the image url, and then the save method tries to download and save the image. My save method so far is:
def save(self, *args, **kwargs):
if self.pic:
if self.pic.name.startswith( 'http://') and self.pic.name.endswith(('.png', '.gif', '.jpg', '.jpeg', '.svg')):
my_temp_pic = open('test.image', 'w')
my_temp_pic.write(urllib2.urlopen(self.pic.name).read())
my_temp_pic.close()
my_temp_pic = open('test.image')
thumbnailer = get_thumbnailer(my_temp_pic, relative_name = self.slug+'.'+self.pic.name.split('.')[-1])
self.pic = thumbnailer.get_thumbnail({'size': (200, 0), 'crop': False})
super(Widget, self).save(*args, **kwargs)
I've tried to open the file in different ways with .read() or .open() ... but the only way I found (above) feels quite hackish (save some temp file with the image, re-open, then save). Is there a better way? I'm I missing a straightforward way to do this?
Save the temporary file is the only solution I know too. Check this: http://djangosnippets.org/snippets/1890/
So basically you don't need to do hackish like close() and open() again. You can do:
from django.core.files import File
from django.core.files.temp import NamedTemporaryFile
# ... your code here ...
my_temp_pic = NamedTemporaryFile(delete=True)
my_temp_pic.write(urllib2.urlopen(self.pic.name).read())
my_temp_pic.flush()
relative_name = '%s.%s' % (self.slug, self.pic.name.split('.')[-1])
thumbnailer = get_thumbnailer(my_temp_pic, relative_name=relative_name)
# ... your code again ...
Hope it helps.

Django - Getting PIL Image save method to work with Amazon s3boto Storage

In order to resize images upon upload (using PIL), I'm overriding the save method for my Article model like so:
def save(self):
super(Article, self).save()
if self.image:
size = (160, 160)
image = Image.open(self.image)
image.thumbnail(size, Image.ANTIALIAS)
image.save(self.image.path)
This works locally but in production I get an error:
NotImplementedError: This backend doesn't support absolute paths.
I tried replacing the image.save line with
image.save(self.image.url)
but then I get an IOError:
[Errno 2] No such file or directory: 'https://my_bucket_name.s3.amazonaws.com/article/article_images/2.jpg'
That is the correct location of the image though. If I put that address in the browser, the image is there. I tried a number of other things but so far, no luck.
You should try and avoid saving to absolute paths; there is a File Storage API which abstracts these types of operations for you.
Looking at the PIL Documentation, it appears that the save() function supports passing a file-like object instead of a path.
I'm not in an environment where I can test this code, but I believe you would need to do something like this instead of your last line:
from django.core.files.storage import default_storage as storage
fh = storage.open(self.image.name, "w")
format = 'png' # You need to set the correct image format here
image.save(fh, format)
fh.close()
For me default.storage.write() did not work, image.save() did not work, this one worked. See this code if anyone is still interested. I apologize for the indentation. My project was using Cloudinary and Django small project.
from io import BytesIO
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage as storage
def save(self, *args, **kargs):
super(User, self).save(*args, **kargs)
# After save, read the file
image_read = storage.open(self.profile_image.name, "r")
image = Image.open(image_read)
if image.height > 200 or image.width > 200:
size = 200, 200
# Create a buffer to hold the bytes
imageBuffer = BytesIO()
# Resize
image.thumbnail(size, Image.ANTIALIAS)
# Save the image as jpeg to the buffer
image.save(imageBuffer, image.format)
# Check whether it is resized
image.show()
# Save the modified image
user = User.objects.get(pk=self.pk)
user.profile_image.save(self.profile_image.name, ContentFile(imageBuffer.getvalue()))
image_read = storage.open(user.profile_image.name, "r")
image = Image.open(image_read)
image.show()
image_read.close()
If you are working with cloud storages for files in Django
NotImplementedError: This backend doesn't support absolute paths
To fix it you need to replace file.path with file.name
For code in the the question: image.save(self.image.path) with image.save(self.image.name)
Here how it looks like in the console
>>> c = ContactImport.objects.last()
>>> c.json_file.name
'protected/json_files/data_SbLN1MpVGetUiN_uodPnd9yE2prgeTVTYKZ.json'
>>> c.json_file
<FieldFile: protected/json_files/data_SbLN1MpVGetUiN_uodPnd9yE2prgeTVTYKZ.json>
>>> c.json_file.url
'https://storage.googleapis.com/super-secret/media/api/protected/json_files/data_SbLN1MpVGetUiN_uodPnd9yE2prgeTVTYKZ.json?Expires=1631378947&GoogleAccessId=secret&Signature=ga7...'

django-photologue upload_to

I have been playing around with django-photologue for a while, and find this a great alternative to all other image handlings apps out there.
One thing though, I also use django-cumulus to push my uploads to my CDN instead of running it on my local machine / server.
When I used imagekit, I could always pass a upload_to='whatever' but I cannot seem to do this with photologue as it automatically inserts the imagefield. How would I go about achieving some sort of an overwrite?
Regards
I think you can hook into the pre_save signal of the Photo model, and change the upload_to field, just before the instance is saved to the database.
Take a look at this:
http://docs.djangoproject.com/en/dev/topics/signals/
Managed to find a workaround for it, however this requires you to make the changes in photologue/models.py
if PHOTOLOGUE_PATH is not None:
if callable(PHOTOLOGUE_PATH):
get_storage_path = PHOTOLOGUE_PATH
else:
parts = PHOTOLOGUE_PATH.split('.')
module_name = '.'.join(parts[:-1])
module = __import__(module_name)
get_storage_path = getattr(module, parts[-1])
else:
def get_storage_path(instance, filename):
dirname = 'photos'
if hasattr(instance, 'upload_to'):
if callable(instance.upload_to):
dirname = instance.upload_to()
else: dirname = instance.upload_to
return os.path.join(PHOTOLOGUE_DIR,
os.path.normpath( force_unicode(datetime.now().strftime(smart_str(dirname))) ),
filename)
And then in your apps models do the following:
class MyModel(ImageModel):
text = ...
name = ...
def upload_to(self):
return 'yourdirectorynamehere'
You can use the setting PHOTOLOGUE_PATH to provide your own callable. Define a method which takes instance and filename as parameters then return whatever you want. For example in your settings.py:
import photologue
...
def PHOTOLOGUE_PATH(instance, filename):
folder = 'myphotos' # Add your logic here
return os.path.join(photologue.models.PHOTOLOGUE_DIR, folder, filename)
Presumably (although I've not tested this) you could find out more about the Photo instance (e.g. what other instances it relates to) and choose the folder accordingly.