Storing images in google datastore using Flask (Python) - python

I am using flask on google app engine and am desperately looking for help to solve this.
The GAE documentation talks of storing images in the datastore using the BlobProperty , which should be done something like this:-
class MyPics(db.Model):
name=db.StringProperty()
pic=db.BlobProperty()
Now the image should be stored in the datastore by doing this:-
def storeimage():
pics=MyPics()
pics.name=request.form['name']
uploadedpic=request.files['file'] #where file is the fieldname in the form of the
file uploaded
pics.pic=db.Blob(uploadedpic)
pics.put()
redirect ... etc etc
But am unable to do this. as I get db.Blob accepts a string , but given a Filestorage object... Can someone help me with this. Also if anybody could hint me on how to stream the image back after uploading.

Ok so this is how I finally solved it:-
#userreg.route('/mypics',methods=['GET','POST'])
def mypics():
if request.method=='POST':
mydata=MyPics()
mydata.name=request.form['myname']
file=request.files['file']
filedata=file.read()
if file:
mydata.pic=db.Blob(filedata)
mydata.put()
return redirect(url_for('home'))
return render_template('mypicform.html')
The above stores the file as a blob in the datastore and then it can be retrieved by the below func:-
#userreg.route('/pic/<name>')
def getpic(name):
qu=db.Query(MyPics).filter('name =',name).get()
if qu.pic is None:
return "hello"
else:
mimetype = 'image/png'
return current_app.response_class(qu.pic,mimetype=mimetype,direct_passthrough=False)

You should consider using the BlobStore to store your data. Instead of a db.Blob you would be using blobstore.BlobReferenceProperty: http://code.google.com/appengine/docs/python/datastore/typesandpropertyclasses.html#BlobReferenceProperty
Uploading and download is quite easy as shown here: http://code.google.com/appengine/docs/python/blobstore/overview.html#Complete_Sample_App

I have following model
class Picture(db.Model):
data = db.BlobProperty()
ext = db.StringProperty()
content_type = db.StringProperty()
and store it using next code:
def create_picture():
if request.files['file']:
picture.data = request.files['file'].read()
picture.ext = request.files['file'].filename.rsplit('.', 1)[1]
picture.content_type = request.files['file'].content_type
picture.put()
flash(u'File uploaded', 'correct')
return redirect(url_for("edit_picture", id=picture.key()))
else:
return render_template('admin/pictures/new.html', title=u'Upload a picture', message=u'No picture selected')
To render a thumbnail you can use next code:
#frontend.route('/thumbnail/<id>/<width>x<height>.<ext>')
def thumbnail(id, width, height, ext):
key = db.Key(id)
picture = Picture.get(key)
img = images.Image(picture.data)
if width != '0':
w = int(width)
else:
w = img.width
if height != '0':
h = int(height)
else:
h = img.height
if img.height > h and h != 0:
w = (int(width) * img.width) / img.height;
if img.width > w:
h = (int(height) * img.height) / img.width;
thumb = images.resize(picture.data, width=w, height=h)
response = make_response(thumb)
response.headers['Content-Type'] = picture.content_type
return response

Related

How do I check if file upload field is empty?

I have a form that allows my to upload multiple image files. Everything works fine, when I select images to upload. If I try to submit the form without files, I get an error
OSError: cannot identify image file <FileStorage: '' ('application/octet-stream')>
I would like to be able to check if the field is empty and if so, bypass my save_images function.
Here is my form
class NewPortfolioProject(FlaskForm):
title = StringField('Project Name', validators=[DataRequired(), Length(max=50)])
description = TextAreaField('Description', validators=[
DataRequired(), Length(min=1, max=1000)])
tags = StringField('#Tags - space separated', validators=[DataRequired()])
link = StringField('Link to live project')
github_link = StringField('Github link', validators=[DataRequired()])
images = MultipleFileField('Add screenshots/wireframes', validators=[FileAllowed(['png', 'jpg'])])
submit = SubmitField('Save Project')
In my routes.py file I am trying to test if the MultipleFileField contains data
if form.validate_on_submit():
if not form.images.data:
portfolio.insert_one(new_doc)
else:
image_files = save_images(form.images.data)
new_doc['images'] = image_files
portfolio.insert_one(new_doc)
But this (and everything else I have tried) doesn't work.
Rather than being empty, form.images.data seems to be a filestorage object. How to I test if it is empty?
Apparently when you call form.images.data on MultipleFileField it returns a list that is never empty, which is why if form.images.data check always returns true.
I have eventually solved it using try/except in my image saving method, as in my case the error itself is raised when PIL tries to open the image and process it.
def save_image(form_picture):
random_hex = secrets.token_hex(8)
_, f_ext = os.path.splitext(form_picture.filename)
picture_fn = random_hex + f_ext
picture_path = os.path.join(current_app.root_path, 'static/images', picture_fn)
output_size = (600, 400)
try:
i = Image.open(form_picture)
i.thumbnail(output_size)
i.save(picture_path)
return picture_fn
except:
return False

how to compress the image before uploading to s3 in django?

I am working on an application where a user can upload an image. I want to reduce the size of the image in 200-500kb.
This is my models.py file
class Report_item(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL)
title = models.CharField(max_length=255, help_text='*Title for the post e.g. item identity')
image = models.ImageField(default="add Item image",
upload_to=get_uplaod_file_name)
def __str__(self):
return self.title + " " + str(self.publish)
And this is my views.py file
class ReportCreate(generic.CreateView):
model = Report_item
fields = ['title','image']
def get_form(self, form_class=None):
if form_class is None:
form_class = self.get_form_class()
form = super(ReportCreate, self).get_form(form_class)
form.fields['title'].widget = TextInput(
attrs={'placeholder': '*Enter UID e.g. CBSE Marksheet Roll nunber 0506***'})
return form
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.owner = self.request.user
self.object.save()
return FormMixin.form_valid(self, form)
I am using Django 1.11 and S3 storage. Kindly help me to compress the image before uploading to s3.
So we need to define a save method in models in order to compress the image before save. Following code help me what I want to achieve for my problem.
from io import BytesIO
import sys
from PIL import Image
from django.core.files.uploadedfile import InMemoryUploadedFile
class Report_item(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL)
title = models.CharField(max_length=255, help_text='*Title for the post e.g. item identity')
image = models.ImageField(default="add Item image",
upload_to=get_uplaod_file_name)
def save(self):
# Opening the uploaded image
im = Image.open(self.image)
output = BytesIO()
# Resize/modify the image
im = im.resize((100, 100))
# 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.split('.')[0], 'image/jpeg',
sys.getsizeof(output), None)
super(Report_item, self).save()
WIth the help of this 5 Mb image compress to 4 kb approx.
Suppose you want to upload an image after resizing it. You can use below code, just pass the image object and you will get resized image in return.
def GetThumbnail(f):
try:
name = str(f).split('.')[0]
image = Image.open(f)
image.thumbnail((400, 400), Image.ANTIALIAS)
thumbnail = BytesIO()
# Default quality is quality=75
image.save(thumbnail, format='JPEG', quality=50)
thumbnail.seek(0)
newImage = InMemoryUploadedFile(thumbnail,
None,
name + ".jpg",
'image/jpeg',
thumbnail.tell(),
None)
return newImage
except Exception as e:
return e
#jitesh2796, to retain the height to width ratio, modify the accepted answer to include the following:
def save(self):
...
original_width, original_height = im.size
aspect_ratio = round(original_width / original_height)
desired_height = 100 # Edit to add your desired height in pixels
desired_width = desired_height * aspect_ratio
# Resize the image
im = im.resize((desired_width, desired_height))
...
Note you may also need to use round() to ensure that the aspect ratio is an integer.

Images getting rotated in Django Template

I am loading some images into a Django model.
When I display these images through a Django template, portrait images are rotated. If I view these same images through Django Admin by clicking on the link they display as I would expect.
This is my view to load the images:
def upload_profile(request, user_id):
variables = {}
if request.POST:
user_form = UserForm(request.POST, instance=User.objects.get(id=request.user.id))
myuser_form = MyUserForm(request.POST, request.FILES, instance=MyUser.objects.get(user__id=request.user.id))
if user_form.is_valid() and myuser_form.is_valid():
user_form.save()
myuser = myuser_form.save()
myuser.photo = apply_orientation(myuser.photo)
myuser.save()
return game_selection(request)
variables['user_form'] = user_form
variables['myuser_form'] = myuser_form
return render(request, 'esc/profile.html', variables)
And this is how I am applying the rotation:
def flip_horizontal(im): return im.transpose(Image.FLIP_LEFT_RIGHT)
def flip_vertical(im): return im.transpose(Image.FLIP_TOP_BOTTOM)
def rotate_180(im): return im.transpose(Image.ROTATE_180)
def rotate_90(im): return im.transpose(Image.ROTATE_90)
def rotate_270(im): return im.transpose(Image.ROTATE_270)
def transpose(im): return rotate_90(flip_horizontal(im))
def transverse(im): return rotate_90(flip_vertical(im))
orientation_funcs = [None,
lambda x: x,
flip_horizontal,
rotate_180,
flip_vertical,
transpose,
rotate_270,
transverse,
rotate_90
]
def apply_orientation(im):
"""
Extract the oritentation EXIF tag from the image, which should be a PIL Image instance,
and if there is an orientation tag that would rotate the image, apply that rotation to
the Image instance given to do an in-place rotation.
:param Image im: Image instance to inspect
:return: A possibly transposed image instance
"""
try:
kOrientationEXIFTag = 0x0112
if hasattr(im, '_getexif'): # only present in JPEGs
e = im._getexif() # returns None if no EXIF data
if e is not None:
#log.info('EXIF data found: %r', e)
orientation = e[kOrientationEXIFTag]
f = orientation_funcs[orientation]
return f(im)
except:
# We'd be here with an invalid orientation value or some random error?
pass # log.exception("Error applying EXIF Orientation tag")
return im
My code never seems to pass the condition if hasattr(im, '_getexif'):
**** EDIT ****
So it's not getting through because no matter what image I load there is no '_getexif'.
If I add print getattr(im, '_getexif') I get the following error, with all images
'ImageFieldFile' object has no attribute '_getexif'
So I dropped all the code above and replaced it with the following
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill, Transpose, SmartResize
class MyUser(models.Model):
avatar = models.ImageField(upload_to='upload/avatars', max_length=255, blank=True, null=True)
avatar_thumbnail = ImageSpecField(
source='avatar',
processors = [Transpose(),SmartResize(200, 200)],
format = 'JPEG',
options = {'quality': 75}
)
It works

If Content-Type is image-like do something, elif is text/html then do something else. How to do this?

first of all, I'm apology for the title. I dont know how to explain this so i write the title like this.
I want to check the Content-Type and if it is image/ run these code
from PIL import Image
import io, uuid, requests
class LinkCreateView(CreateView):
model = Link
form_class = LinkForm
def form_valid(self, form):
headers = {'content-type': 'image/'}
r = requests.get(form.instance.url, headers=headers)
i = Image.open(io.BytesIO(r.content)).convert('RGB')
image_io = io.BytesIO()
try:
crop_image = ImageOps.fit(i, method=Image.ANTIALIAS, size=(65, 65), bleed=0.5, centering=(0.5, 0.5))
hash = str(uuid.uuid1())
name = "{}.jpeg".format(hash)
crop_image.save(image_io, format="jpeg")
form.instance.img.save(name, ContentFile(image_io.getvalue()))
finally:
image_io.close()
f = form.save(commit=False)
f.rank_score = 0.0
f.submitter = self.request.user
f.save()
return super(CreateView, self).form_valid(form)
If it is text/html then run these code;
from PIL import Image
import io, uuid, requests, lxml.html
url = ("http://website.com")
page = requests.get(url)
hash = str(uuid.uuid1())
tree = lxml.html.fromstring(page.content)
og = tree.xpath('/html/head/meta[#property="og:image"][1]/#content')
r = requests.get(og[0])
i = Image.open(io.BytesIO(r.content)).convert('RGB')
crop_image = ImageOps.fit(i, method=Image.ANTIALIAS, size=(65,65), bleed=0.5, centering=(0.5, 0.5))
crop_image.save("{}.jpeg".format(hash))
I tried if, else things but it didn't work. I dont know how to implement this second piece of code to first one. Both code work perfectly by itself.
I tried like these code:
if page.headers['Content-Type'] == 'text/html:
.....
elif page.headers['Content-Type'] == 'image/':
.....
Thanks to LearnerEarner my problem is solved. Working code is below
class LinkCreateView(CreateView):
model = Link
form_class = LinkForm
def form_valid(self, form):
r = requests.get(form.instance.url)
image_io = io.BytesIO()
hash = str(uuid.uuid1())
try:
if r.headers['Content-Type'].startswith("image/"):
i = Image.open(io.BytesIO(r.content)).convert('RGB')
crop_image = ImageOps.fit(i, method=Image.ANTIALIAS, size=(65, 65), bleed=0.5, centering=(0.5, 0.5))
name = "{}.jpeg".format(hash)
crop_image.save(image_io, format="jpeg")
form.instance.img.save(name, ContentFile(image_io.getvalue()))
elif r.headers['Content-Type'].startswith("text/html"):
tree = lxml.html.fromstring(r.content)
og = tree.xpath('/html/head/meta[#property="og:image"][1]/#content')
r = requests.get(og[0])
i = Image.open(io.BytesIO(r.content)).convert('RGB')
crop_image = ImageOps.fit(i, method=Image.ANTIALIAS, size=(65, 65), bleed=0.5, centering=(0.5, 0.5))
name = "{}.jpeg".format(hash)
crop_image.save(image_io, format="jpeg")
form.instance.img.save(name, ContentFile(image_io.getvalue()))
finally:
image_io.close()
f = form.save(commit=False)
f.rank_score = 0.0
f.submitter = self.request.user
f.save()
return super(CreateView, self).form_valid(form)
I tried like these code:
if page.headers['Content-Type'] == 'text/html:
.....
elif page.headers['Content-Type'] == 'image/':
.....
As far as my knowledge goes there is no generic Content-Type like 'image/'(unless you are explicitly sending such Content-Type in response headers). The MIME Type has to be specific.
Print and observe the response headers from your requests.get to see the exact Content-Type before you use it in the else condition.
If all that you expect are HTML and images, you need not have a specific condition for images. You can try something like this:
if page.headers['Content-Type'] == 'text/html':
....
else:
try:
#treat it as image and proceed
except:
#may be not an image
OR
You can do this:
if page.headers['Content-Type'] == 'text/html:
.....
elif page.headers['Content-Type'].startswith("image/"):
.....
Please refer to this list for MIME Types.

How to create an image to unit test an API

I have a simple model representing a female that a user can view, edit and create:
class Female(models.Model):
firstName = models.CharField(max_length=255)
lastName = models.CharField(max_length=255)
profileImage = models.ImageField()
bio = models.TextField()
fantasticBio = models.TextField()
I am using a multi-part form to send the data for a create via an Angular service. This works fine. The django view that handles the creation is:
#api_view(['POST'])
def createFemale(request):
serializedFemale = FemaleSerializer(data=request.data)
if serializedFemale.is_valid():
serializedFemale.save()
return Response(serializedFemale.data)
else:
return Response(serializedFemale.errors, status=status.HTTP_400_BAD_REQUEST)
My problem is that I am unable to fully unit test this view. I am having trouble creating an image that I can use to test the view via the test client. My knowledge of image generation is limited so I may be generating the image wrong, however it seems to be accepted as a Django ImageField when I write it directly to the database in my set up. The relevant test code is as follows:
def createImageFile():
"""
Convinience function to create a test image
"""
image = Image.new('RGBA', size=(50, 50), color=(256, 0, 0))
image_file = BytesIO()
image.save(image_file, 'PNG')
img_str = base64.b64encode(image_file.getvalue())
img_str = str.encode("data:image/png;base64,") + img_str
return ImageFile(img_str)
def test_createFemale(self):
"""
A valid Female created with the API should be in the database
"""
profileImage = SimpleUploadedFile("test.png", createImageFile().file, content_type="image/png")
femaleToCreate = {}
femaleToCreate['firstName'] = "Test"
femaleToCreate['lastName'] = "Female"
femaleToCreate['profileImage'] = profileImage
femaleToCreate['bio'] = "Test bio"
femaleToCreate['fantasticBio'] = "Fantastic test bio"
response = self.client.post(url, femaleToCreate)
self.assertEquals(response.status_code, 200) # Make sure valid request returns success response
The response I receive from the server is:
{'profileImage': ['Upload a valid image. The file you uploaded was either not an image or a corrupted image.']}
How can I create an image that will be accepted by my API from within my unit test?
Please refrain from suggesting a file read of an existing image, I have considered this option and chosen not to pursue it as I believe it is a bad practice.
Thank you for any help.
Ok so I've got round this as follows:
def test_images_by_component_id_update(self):
image = PIL.Image.new('RGB', size=(1, 1))
file = tempfile.NamedTemporaryFile(suffix='.jpg')
image.save(file)
with open(file.name, 'rb') as f:
data = {'image': f}
response = self.client.patch(reverse('foo', args=(1, 1)), data=data, format='multipart')
self.assertEqual(200, response.status_code)
Notice I have to reopen the file, this took a while to debug. For whatever reason it doesn't like the NamedTemporaryFile.file attribute so I had to reopen.

Categories