Django admin upload and image to s3 and then resize the image and save a thumb problem - django

I am having error after error trying to upload and resize images to s3 with pil and botos3 and the django default_storage. I am trying to do this on save in the admin.
here is the code:
from django.db import models
from django.forms import CheckboxSelectMultiple
import tempfile
from django.conf import settings
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage as s3_storage
from django.core.cache import cache
from datetime import datetime
import Image, os
import PIL.Image as PIL
import re, os, sys, urlparse
class screenshot(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200)
image = models.ImageField(upload_to='screenshots')
thumbnail = models.ImageField(upload_to='screenshots-thumbs', blank=True, null=True, editable=False)
def save(self):
super(screenshot, self).save() # Call the "real" save() method
if self.image:
thumb = Image.open(self.image.path)
thumb.thumbnail(100, 100)
filename = str(self.slug)
temp_image = open(os.path.join('tmp',filename), 'w')
thumb.save(temp_image, 'JPEG')
from django.core.files import File
thumb_data = open(os.path.join('/tmp',filename), 'r')
thumb_file = File(thumb_data)
new_file.thumb.save(str(self.slug) + '.jpg', thumb_file)
def __str__(self):
return self.title
This is just one of the many ways I have tried to get it working, and I either get (2, 'No such file or directory') or some other error.
Please can someone help me to get it working. I want it to use the django backend to get the image uploaded to be resized and saved as the thumbnail and then saved. Let me know if you need to know any information. I would be happy to use the django snippet - http://djangosnippets.org/snippets/224/ but I don't know what data to feed it. I get the same IOErrors and 'no such path/filename' even though the main image is uploading to s3 fine. I have also tried things like:
myimage = open(settings.MEDIA_URL + str(self.image))
myimage_io = StringIO.StringIO()
imageresize = myimage.resize((100,100), Image.ANTIALIAS)
imageresize.save('resize_100_100_aa.jpg', 'JPEG', quality=75)
It's been 3 days of looking now so I am starting to go spare! Thanks

I had a similar problem, but in my case using sorl-thumbnail was not an option. I found that I can open an Image directly from S3BotoStorage by passing in a file descriptor instead of a path.
So instead of
thumb = Image.open(self.image.path)
use
thumb = Image.open(s3_storage.open(self.image.name))
Then you can process and save the new file locally as you were doing before.

Why don't you try sorl-thumbnail. It has the exact same interface as the default ImageField django provides and it seems like it would be a lot nicer to work with than the roll-your-own support.
Storage support
Pluggable Engine support (PIL, pgmagick)
Pluggable Key Value Store support (redis, cached db)
Pluggable Backend support
Admin integration with possibility to delete
Dummy generation
Flexible, simple syntax, generates no html
ImageField for model that deletes thumbnails
CSS style cropping options
Margin calculation for vertical positioning

Related

How to save pillow processed image in already existing Django object

I have created a object model as below
from django.db import models
# Create your models here.
class ImageModel(models.Model):
image = models.ImageField(upload_to='images/')
editedImg = models.ImageField(upload_to='images/')
def delete(self, *args, **kwargs):
self.image.delete()
self.editedImg.delete()
super().delete(*args, **kwargs)
And here is what i am trying to do in a function
from django.shortcuts import render
from EditorApp.forms import ImageForm
from EditorApp.models import ImageModel
from django.http import HttpResponseRedirect
from PIL import Image
def edit_column(request):
codArr = request.POST.getlist('codArr[]')
imgs = ImageModel.objects.first()
orgImage = ImageModel.objects.first().image
orgImage = Image.open(orgImage)
croppedImg = orgImage.crop((int(codArr[0]), int(codArr[1]), int(codArr[2]), int(codArr[3])))
# croppedImg.show()
# imgs.editedImg = croppedImg
# imgs.save()
return HttpResponseRedirect("/editing/")
What i am trying to do is the codArr consists of coordinates of top(x, y) and bottom(x, y) in the array form(Which is not an issue and is tested(croppedImg.show() showed the desired cropped image) and handled and used to crop the image). Image crop is working fine. But what i am trying to do is to save the cropped image in editedImg of the model used above. The above commented one is what i tried but throw a error AttributeError: _committed
As i have not used any name for image in model as its not required.
Kindly help please, Would be very thankfull.
you should do it like this:
from io import BytesIO
from api.models import ProductPicture
from django.core import files
codArr = request.POST.getlist('codArr[]')
img_obj = ImageModel.objects.first()
orgImage = img_obj.image
orgImage = Image.open(orgImage)
croppedImg = orgImage.crop((int(codArr[0]), int(codArr[1]), int(codArr[2]), int(codArr[3])))
thumb_io = BytesIO() # create a BytesIO object
croppedImg.save(thumb_io, 'png')
editedImg = files.File(thumb_io, name=file_name)
img_obj.editedImg = editedImg
img_obj.save()
You can use Python's context manager to open the image and save it to the desired storage in that case I'm using the images dir.
Pillow will crop the image and image.save() will save it to the filesystem and after that, you can add it to Django's ImageField and save it into the DB.
The context manager takes care of the file opening and closing, Pillow
takes care of the image, and Django takes care of the DB.
from PIL import Image
with Image.open(orgImage) as image:
file_name = image.filename # Can be replaced by orgImage filename
cropped_path = f"images/croped-{file_name}"
# The crop method from the Image module takes four coordinates as input.
# The right can also be represented as (left+width)
# and lower can be represented as (upper+height).
(left, upper, right, lower) = (20, 20, 100, 100)
# Here the image "image" is cropped and assigned to new variable im_crop
im_crop = image.crop((left, upper, right, lower))
im_crop.save(cropped_path)
imgs.editedImg = cropped_path
imgs.save()
Pillow's reference

Django Reportlab getKeepWithNext

I am trying to add an image to a PDF generated in Reportlab. I am trying to access the image from a Django field, specifying the full path of the file.
When I run the below code I get: "Exception Value: getKeepWithNext".
Any help as to what I am doing wrong would be greatly appreciated.
def holding_pdf(self, course_slug, holding_slug):
buffer = io.BytesIO()
holding = HoldingDetail.objects.get(identifier=holding_slug)
doc = SimpleDocTemplate(buffer,
rightMargin=72,
leftMargin=72,
topMargin=72,
bottomMargin=72,
pagesize=A4,
title=f"Why the {holding.name} is in the portfolio.pdf")
elements = []
styles = getSampleStyleSheet()
elements.append(Paragraph(str(holding.logo.path), styles['Normal']))
elements.append(Image(holding.logo.path))
print(holding.logo.path)
doc.build(elements)
buffer.seek(0)
return FileResponse(buffer, as_attachment=False, filename=f"Why the {holding.name} is in the portfolio.pdf")
I think that you're using an incorrect Image class. Verify the python imports in your file, and sure you use the Image flowable class provides by reportlab.
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus import Paragraph, SimpleDocTemplate, Image

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 admin: restricting image uploads to JPEGs

I have a Django admin setup where users can upload images. The system only allows for JPEG-formatted images. I put together a validation system to check all the images uploaded are JPEGS. In my Images model I have an override for clean():
class Image(models.Model):
image = models.ImageField(upload_to="images/", blank=True, null=True, help_text='JPEG images only', max_length=100)
...
def clean(self):
import Image
if "images/" in str( self.image ):
i = Image.open( "%s/%s" % ( settings.MEDIA_ROOT, self.image ) )
if i.format != "JPEG":
raise validators.ValidationError, u'You can only upload JPEG images'
The problem is that this will only find an image once it's uploaded and the record is being re-saved. If it's being created for the first time clean() function will be called before the image is saved into the media folder.
Is there a function I could override which will be able to raise an issue with a file being uploaded in the wrong format prior to the record being saved but after the file has at least been stored or is there a way of finding the temporary file location during the execution of clean()?
It's not as fool proof as actually loading up the image with PIL and checking its format, but the field has a name attribute that you can check when cleaning the model.
import re
p = re.compile(r'.*\.(jpg|jpeg)$', re.I)
filename = self.your_file_field.name
if not p.match(filename):
raise ValidationError('You must upload a JPEG image')
in uploaded you can use:
...
import Image
from cStringIO import StringIO
self.image.open()
i = Image.open(StringIO(self.image.file.read())
...
for check if file uploaded:
from django.core.files.uploadedfile import InMemoryUploadedFile
if isinstance(self.image.file, InMemoryUploadedFile):
...
I check all it in django 1.3

Storing Images on App Engine using Django

I'm trying to upload and save a resized image in a db.BlobProperty field on Google App Engine using Django.
the relevant part of my view that process the request looks like this:
image = images.resize(request.POST.get('image'), 100, 100)
recipe.large_image = db.Blob(image)
recipe.put()
Which seems like it would be the logical django equivalent of the example in the docs:
from google.appengine.api import images
class Guestbook(webapp.RequestHandler):
def post(self):
greeting = Greeting()
if users.get_current_user():
greeting.author = users.get_current_user()
greeting.content = self.request.get("content")
avatar = images.resize(self.request.get("img"), 32, 32)
greeting.avatar = db.Blob(avatar)
greeting.put()
self.redirect('/')
(source: http://code.google.com/appengine/docs/python/images/usingimages.html#Transform)
But, I keep getting an error that says: NotImageError / Empty image data.
and refers to this line:
image = images.resize(request.POST.get('image'), 100, 100)
I'm having trouble getting to the image data. Seems like it's not being uploaded but I can't figure out why. My form has the enctype="multipart/form-data" and all that. I think something's wrong with how I'm referring to the image data. "request.POST.get('image')" but I can't figure out how else to reference it. Any ideas?
Thanks in advance.
After some guidance from "hcalves" I figured out the problem. First of all, the default version of Django that comes bundled with App Engine is version 0.96 and how the framework handles uploaded files has changed since then. However in order to maintain compatibility with older apps you have to explicitly tell App Engine to use Django 1.1 like this:
from google.appengine.dist import use_library
use_library('django', '1.1')
You can read more about that in the app engine docs.
Ok, so here's the solution:
from google.appengine.api import images
image = request.FILES['large_image'].read()
recipe.large_image = db.Blob(images.resize(image, 480))
recipe.put()
Then, to serve the dynamic images back again from the datastore, build a handler for images like this:
from django.http import HttpResponse, HttpResponseRedirect
def recipe_image(request,key_name):
recipe = Recipe.get_by_key_name(key_name)
if recipe.large_image:
image = recipe.large_image
else:
return HttpResponseRedirect("/static/image_not_found.png")
#build your response
response = HttpResponse(image)
# set the content type to png because that's what the Google images api
# stores modified images as by default
response['Content-Type'] = 'image/png'
# set some reasonable cache headers unless you want the image pulled on every request
response['Cache-Control'] = 'max-age=7200'
return response
You access uploaded data via request.FILES['field_name'].
http://docs.djangoproject.com/en/dev/topics/http/file-uploads/
Reading more about Google's Image API, seems to me you should be doing something like this:
from google.appengine.api import images
image = Image(request.FILES['image'].read())
image = image.resize(100, 100)
recipe.large_image = db.Blob(image)
recipe.put()
request.FILES['image'].read() should work because it's supposed to be a Django's UploadedFile instance.