Resizing an image using PIL in pre_save signal - python

Here's my code:
from PIL import Image
from pilkit.processors import ResizeToFit
def before_saving_shop(sender, instance, **kwargs):
img = Image.open(instance.logo)
processor = ResizeToFit(100, 100)
instance.logo = processor.process(img)
pre_save.connect(before_saving_shop, sender=Shop)
I am getting this exception:
Exception Value: _committed
Please help.

You no need use signal for this purpose. Just redefine save method of the Shop model like this:
class Shop(models.Model):
....
def save(self):
img = Image.open(self.logo)
processor = ResizeToFit(100, 100)
self.logo = processor.process(img)
super(Shop, self).save()

Related

Django Value Error "seek of closed file" using PIL for image resize when updating a record

I have the following model:
class Players(models.Model):
team = models.ForeignKey(Teams, verbose_name=_('Team'), on_delete=models.CASCADE)
player_name = models.CharField(_('Player Name'),max_length=200)
player_description = models.TextField(_('Player Description'))
player_image = models.ImageField(_('Profile Pic'),upload_to='upload/player_image', null=True, blank=True)
player_social = models.CharField(_('Social Media Tag'),max_length=200)
class Meta:
verbose_name = _("Players")
verbose_name_plural = _("Players")
def __str__(self):
return self.team.team_name + ' - ' + self.player_name
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if self.player_image:
image_resize(self.player_image, 250)
The last function will call another for image resizing taking the file and an expected max width:
# Use PIL to resize images; set target width on each
def image_resize(image, tgt_width):
img = PIL.Image.open(image)
img.load()
width, height = img.size
target_width = tgt_width
h_coefficient = width/tgt_width
target_height = height/h_coefficient
img = img.resize((int(target_width), int(target_height)), PIL.Image.ANTIALIAS)
img.save(image.path, quality=100)
img.close()
image.close()
My view for updating is as follows:
#method_decorator(superuser_required, name='dispatch')
class PlayerUpdateView(UpdateView):
model = Players
template_name = 'scoreboard/players/player_update.html'
form_class = PlayersForm
context_object_name: str = 'player'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
team_id = Players.objects.get(pk=self.kwargs['pk']).team.id
context["team"] = Teams.objects.get(pk=team_id)
return context
def form_valid(self, form):
form.save()
return super().form_valid(form)
def get_success_url(self):
team_id = Players.objects.get(pk=self.kwargs['pk']).team.id
return reverse_lazy('team_detail', kwargs={'pk': team_id})
It doesn't matter if I provide a new image file or not, I get the same "seek of closed file" error:
File "D:\00_www\hts-score\overlay\scoreboard\models.py", line 62, in save
image_resize(self.player_image, 250)
File "D:\00_www\hts-score\overlay\scoreboard\models.py", line 15, in image_resize
img = PIL.Image.open(image)
File "D:\00_www\hts-score\venv\lib\site-packages\PIL\Image.py", line 3096, in open
fp.seek(0)
ValueError: seek of closed file
How can I get the image to be processed anyway? What am I missing?
I tried adding the if method so in case there is no file, it won't trigger the resize function. I was expecting the function to check for the presence of an image file, then process it. Adding img.load() doesn't help.
So, after a few tries I found this answer. I'm not very sure why it works, but it seems like the with method opens and closes the file correctly. Found this on PIL Documentation. The image_resize() function will look like this:
def image_resize(image, tgt_width):
with Image.open(image) as img:
width, height = img.size
ratio = width / height
tgt_height = int(tgt_width / ratio)
img = img.resize((tgt_width, tgt_height), Image.ANTIALIAS)
img.save(image.path)
With the if condition at the save method, it works every time.

AttributeError: 'ResizeImage' object has no attribute 'thumbnail'

I'm making a class to make a nicer "thumbnail" for a picture.
The functions works fine, but inside the class I got the "object" has no "attribute 'thumbnail'". I'm not an expert on classes, but maybe a short recommendation? The "open" method worked fine!
class ResizeImage:
from PIL import Image
def newImage(self,dimensiune):
NouaPoza = Image.new('RGBA', (dimensiune, dimensiune), (255, 255, 255, 0))
self.thumbnail((dimensiune, dimensiune), Image.ANTIALIAS)
coordonateCentrare = ((dimensiune - self.size[0]) // 2, (dimensiune - self.size[1]) // 2)
NouaPoza.paste(self,coordonateCentrare)
return NouaPoza
def openVechi(self,fisier_in):
self = Image.open(fisier_in)
return self
def saveNou(self,fisier_out):
self.save(fisier_out)
if __name__ == '__main__':
fisier_in = "[...]"
fisier_out = "[...]"
poza = ResizeImage()
poza.openVechi(fisier_in)
poza.newImage(500)
poza.saveNou(fisier_out)
Thank you in advance!
P.S. Working just with functions was ok, like:
def thumbnail(poza,dimensiune):
poza.thumbnail((dimensiune,dimensiune),Image.ANTIALIAS)
EDIT
I believe the right declaration is:
class ResizeImage(Image.Image):
def newImage(self,dimensiune):
self.thumbnail((dimensiune,dimensiune),Image.ANTIALIAS)
BUT I get the following error:
File "C:/Users/claudiu.ivanescu/PycharmProjects/eDX/NewImage.py", line 11, in newImage
self.thumbnail((dimensiune,dimensiune),Image.ANTIALIAS)
File "C:\Users\claudiu.ivanescu\AppData\Local\Programs\Python\Python36-32\lib\site-packages\PIL\Image.py", line 2059, in thumbnail
x, y = self.size
AttributeError: 'ResizeImage' object has no attribute 'size'
After some researching I renounce to make the class as before. I put all the details as attributes for object. And all the other method I access them without args.
There is the new class:
class resizeImage:
def __init__(self,dimension,fileIn,fileOut)
[...]
def createThumbnail(self):
from PIL import Image
picture = Image.open(self.fileIn)
[...]
thumbnail.save(self.fileOut)
def delThumbnail(self):
import os,sys
os.remove(self.fileOut)
Maybe will be useful for some people!

Django - Get I/O error on changing object attribute

In my views.py file, I am trying to add 1 to a BigIntegerField named visited_counter.
views.py :
def view_page(request,id_page):
page = get_object_or_404(Page,title=id_page)
page.visited_counter= page.visited_counter +1
page.save()
return render(request,'app/page.html',locals())
models.py :
class Page(models.Model):
title = models.CharField(max_length=30)
visited_counter= models.BigIntegerField()
landscape= models.BooleanField()
thumbnail = models.ImageField(storage=OverwriteStorage(),upload_to=thumbnail_path)
backgroundpicture =models.ImageField(storage=OverwriteStorage(),upload_to=background_path)
def save(self, *args, **kwargs):
if self.backgroundpicture.width >= self.backgroundpicture.height:
self.landscape=True
else:
self.landscape=False
if self.backgroundpicture:
from PIL import Image
import glob, os
from cStringIO import StringIO
from django.core.files.uploadedfile import SimpleUploadedFile
image = Image.open(self.backgroundpicture) ####LINE ERROR####
try:
# thumbnail
THUMBNAIL_SIZE = (160, 160) # dimensions
# Convert to RGB if necessary
if image.mode not in ('L', 'RGB'): image = image.convert('RGB')
# create a thumbnail + use antialiasing for a smoother thumbnail
image.thumbnail(THUMBNAIL_SIZE, Image.ANTIALIAS)
# fetch image into memory
temp_handle = StringIO()
image.save(temp_handle, 'JPEG')
temp_handle.seek(0)
# save it
file_name, file_ext = os.path.splitext(self.backgroundpicture.name.rpartition('/')[-1])
suf = SimpleUploadedFile(file_name + file_ext, temp_handle.read(), content_type='JPEG')
self.thumbnail.save(file_name + '.jpg', suf, save=False)
except ImportError:
pass
super(Page, self).save(*args, **kwargs)
When I create a 'Page object', I have no problem.... The save function is doing her job very well, but when I want to access to the object via the view_page function. I get a I/O operation on closed file error on that line: image = Image.open(self.backgroundpicture).
I didn't find any other Q/A related to this case, so I am stuck...
The trick is to add an if condition in your save method and check if it is necessary to read the whole code in the save function.
For this you add a function named, has_changed
def has_changed(instance, field, manager='objects'):
"""Returns true if a field has changed in a model
May be used in a model.save() method.
"""
if not instance.pk:
return True
manager = getattr(instance.__class__, manager)
old = getattr(manager.get(pk=instance.pk), field)
return not getattr(instance, field) == old
And you use it in the model definition like this:
if has_changed(self, 'backgroundpicture'):
if self.backgroundpicture.width >= self.backgroundpicture.height:
self.landscape=True
...

Django Image upload and resize

I have a standard Django form with an image field. When the image is uploaded, I would like to make sure that the image is no larger than 300px by 300px. Here is my code:
def post(request):
if request.method == 'POST':
instance = Product(posted_by=request.user)
form = ProductModelForm(request.POST or None, request.FILES or None)
if form.is_valid():
new_product = form.save(commit=False)
if 'image' in request.FILES:
img = Image.open(form.cleaned_data['image'])
img.thumbnail((300, 300), Image.ANTIALIAS)
# this doesnt save the contents here...
img.save(new_product.image)
# ..because this prints the original width (2830px in my case)
print new_product.image.width
The problem I am facing is, it is not clear to me how I get the Image type converted to the type that ImageField type.
From the documentation on ImageField's save method:
Note that the content argument should be an instance of django.core.files.File, not Python's built-in file object.
This means you would need to convert the PIL.Image (img) to a Python file object, and then convert the Python object to a django.core.files.File object. Something like this (I have not tested this code) might work:
img.thumbnail((300, 300), Image.ANTIALIAS)
# Convert PIL.Image to a string, and then to a Django file
# object. We use ContentFile instead of File because the
# former can operate on strings.
from django.core.files.base import ContentFile
djangofile = ContentFile(img.tostring())
new_product.image.save(filename, djangofile)
There you go, just change a little bit to suit your need:
class PhotoField(forms.FileField, object):
def __init__(self, *args, **kwargs):
super(PhotoField, self).__init__(*args, **kwargs)
self.help_text = "Images over 500kb will be resized to keep under 500kb limit, which may result in some loss of quality"
def validate(self,image):
if not str(image).split('.')[-1].lower() in ["jpg","jpeg","png","gif"]:
raise ValidationError("File format not supported, please try again and upload a JPG/PNG/GIF file")
def to_python(self, image):
try:
limit = 500000
num_of_tries = 10
img = Image.open(image.file)
width, height = img.size
ratio = float(width) / float(height)
upload_dir = settings.FILE_UPLOAD_TEMP_DIR if settings.FILE_UPLOAD_TEMP_DIR else '/tmp'
tmp_file = open(os.path.join(upload_dir, str(uuid.uuid1())), "w")
tmp_file.write(image.file.read())
tmp_file.close()
while os.path.getsize(tmp_file.name) > limit:
num_of_tries -= 1
width = 900 if num_of_tries == 0 else width - 100
height = int(width / ratio)
img.thumbnail((width, height), Image.ANTIALIAS)
img.save(tmp_file.name, img.format)
image.file = open(tmp_file.name)
if num_of_tries == 0:
break
except:
pass
return image
Source: http://james.lin.net.nz/2012/11/19/django-snippet-reduce-image-size-during-upload/
How about using standard image field https://github.com/humanfromearth/django-stdimage
Here is an app that can take care of that: django-smartfields
from django.db import models
from smartfields import fields
from smartfields.dependencies import FileDependency
from smartfields.processors import ImageProcessor
class Product(models.Model):
image = fields.ImageField(dependencies=[
FileDependency(processor=ImageProcessor(
scale={'max_width': 300, 'max_height': 300}))
])
Try my solution here: https://stackoverflow.com/a/25222000/3731039
Highlight
Using Pillow for image processing (two packages required: libjpeg-dev, zlib1g-dev)
Using Model and ImageField as storage
Using HTTP POST or PUT with multipart/form
No need to save the file to disk manually.
Create multiple resolutions and stores their dimensions.
You can use my library django-sizedimagefield for this, it has no extra dependency and is very simple to use.

Save image created via PIL to django model

I have successfully created and rotated an image that was uploaded via email to a directory on my server using the following code:
image = ContentFile(b64decode(part.get_payload()))
im = Image.open(image)
tempfile = im.rotate(90)
tempfile.save("/srv/www/mysite.com/public_html/media/images/rotate.jpg", "JPEG")
img = Photo(user=user)
img.img.save('rotate.jpg', tempfile)
img.save()
The rotated image exists in the directory, however when I try to add that image to my model, it is not saving. What am I missing? Any help would be greatly appreciated.
I solved the issue with the following code:
image = ContentFile(b64decode(part.get_payload()))
im = Image.open(image)
tempfile = im.rotate(270)
tempfile_io =StringIO.StringIO()
tempfile.save(tempfile_io, format='JPEG')
image_file = InMemoryUploadedFile(tempfile_io, None, 'rotate.jpg','image/jpeg',tempfile_io.len, None)
img = Photo(user=user)
img.img.save('rotate.jpg', image_file)
img.save()
I found the answer here How do you convert a PIL `Image` to a Django `File`?. Works flawlessly!!!
from django.db import models
from .abstract_classes import DateTimeCreatedModified
from PIL import Image
from io import BytesIO
from django.core.files.base import ContentFile
from enum import Enum
class MediaSize(Enum):
# size base file name
DEFAULT = ((1080, 1080), 'bus_default')
MEDIUM = ((612, 612), 'bus_medium')
THUMBNAIL = ((161, 161), 'bus_thumbnail')
class Media(DateTimeCreatedModified):
# The original image.
original = models.ImageField()
# Resized images...
default = models.ImageField()
medium = models.ImageField()
thumbnail = models.ImageField()
def resizeImg(self, img_size):
img: Image.Image = Image.open(self.original)
img.thumbnail(img_size, Image.ANTIALIAS)
outputIO = BytesIO()
img.save(outputIO, format=img.format, quality=100)
return outputIO, img
def handleResize(self, mediaSize: MediaSize):
imgSize = mediaSize.value[0]
imgName = mediaSize.value[1]
outputIO, img = self.resizeImg(imgSize)
return {
# Files aren't be overwritten
# because `AWS_S3_FILE_OVERWRITE = False`
# is set in `settings.py`.
'name': f'{imgName}.{img.format}',
'content': ContentFile(outputIO.getvalue()),
'save': False,
}
def save(self, **kwargs):
if not self.default:
self.default.save(
**self.handleResize(MediaSize.DEFAULT)
)
if not self.medium:
self.medium.save(
**self.handleResize(MediaSize.MEDIUM)
)
if not self.thumbnail:
self.thumbnail.save(
**self.handleResize(MediaSize.THUMBNAIL)
)
super().save(**kwargs)

Categories

Resources