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
Related
With Flask-Admin and Flask how can I submit a form\view based on ModelView from code?
I'm trying to create a separate view\form that would allow user to add multiple entries with one form. Specifically allowing to upload multiple images with common prefix name and common parameters. I'd like to do it by submitting a single-image upload form from code, because it does some additional processing like resizing images and I'd like to let Flask-Admin handle connecting database entries and files.
Here's the form I'd like to submit from code:
class ImageView(ModelView):
def _list_thumbnail(view, context, model, name):
if not model.path:
return ''
return Markup('<img src="%s">' % url_for('media',
filename=form.thumbgen_filename(model.path)))
column_labels = dict(show_in_header="Show In Header?",
path="Image")
form_create_rules = ("name",
"tags",
rules.Text(
"Use this image as header. If more than one image is selected header image will be random each time the page is loaded."),
"show_in_header",
"path")
form_excluded_columns = ("timestamp")
column_formatters = {
'path': _list_thumbnail
}
thumbnail_size = config("media", "thumbnail_size")
form_extra_fields = {
'path': BroImageUploadField('Image',
base_path=IMAGES_FOLDER,
thumbnail_size=(thumbnail_size, thumbnail_size, True),
endpoint="media",
url_relative_path='media',
relative_path=None)
}
def is_accessible(self):
return current_user.is_authenticated
def inaccessible_callback(self, name, **kwargs):
# redirect to login page if user doesn't have access
return redirect(url_for('login', next=request.url))
And here I'm creating a form and I'm just not sure that .process() is the function to submit it? Is there one at all?
lass MultipleImagesUploadView(BaseView):
#expose("/", methods=["GET", "POST"])
def index(self):
if request.method == "POST":
a_form = MultipleImagesForm(request.form)
base_name = a_form.base_name.data
tags = a_form.tags.data
show_in_header = a_form.show_in_header.data
print (request.files.getlist(a_form.images.name))
uploaded_files = request.files.getlist(a_form.images.name)
for i, uf in enumerate(uploaded_files):
try:
name, ext = os.path.splitext(uf.filename)
if ext not in IMAGE_EXTENSIONS:
flash("Image file {} was skipped as it's extension is not supported ({}).".format(uf.filename, ext), category="warning")
continue
image_contents = uf.stream.read()
image_form = ImageView()
image_form.name.data = "{}_{}".format(base_name, i)
image_form.tags.data = tags
image_form.show_in_header.data = show_in_header
image_form.path.data = image_contents
image_form.process()
except Exception as e:
flash ("Unhandled exception: {}".format(e), category="warning")
flash("Images were added to the gallery.", category='success')
a_form = MultipleImagesForm()
print("############", a_form)
return self.render('/admin/multiple_images_upload.html', form=a_form)
I can't figure out a way to submit a form from code, been trying to find the answer in docs and google for hours now with no luck.
Found the issue. In my case I was missing the enctype="multipart/form-data". Without that files part was sent as empty.
Also changed to using from flask_wtf import FlaskForm and enabling it as {{ form.files(class="form-control", multiple="") }} in the template.
Files can then be accessed with uploaded_files = request.files.getlist("files") on POST request, it will hold array of file-objects.
I hope this helps someone. If any additional formatting is required I will add or expand the answer.
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.
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
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.
I have a form in google app engine where I want to upload an image and all my text at the same time. Do I have to seperate this into two seperate pages and actions?
Here is my upload handler:
class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
def upload(self, reseller_id, imgfile):
upload_files = imgfile
blob_info = upload_files[0]
key = blob_info.key()
r = Reseller.get_by_id(reseller_id)
r.blob_key_logo = str(key)
r.put();
Here is my creation of a new reseller object:
class NewReseller(BaseHandler):
def get(self):
if self.user:
self.render("new_reseller.html")
else:
self.redirect("/display_resellers")
def post(self):
name = self.request.get('name')
website = self.request.get('website')
information = self.request.get('information')
address = self.request.get('address')
city = self.request.get('city')
state = self.request.get('state')
zipcode = self.request.get('zipcode')
email = self.request.get('email')
phone = self.request.get('phone')
r = Reseller( name = name,
website = website,
information = information,
address = address,
city = city,
state = state,
zipcode = zipcode,
email = email,
phone = phone)
r.put()
theresellerid = r.key().id()
#And then Upload the image
u = UploadHandler()
logo_img = u.get_uploads('logo_img')
u.upload(theid, logo_img)
self.redirect('/display_resellers')
I think my problem here is this line:
logo_img = u.get_uploads('logo_img')
it pops out the error message
for key, value in self.request.params.items():
AttributeError: 'NoneType' object has no attribute 'params'
Somehow I need this NewReseller class to inherit the .getuploads from BlobstoreUploadHandler so I can do:
logo_img = self.get_uploads('logo_img')
Or there is probably a better way because this seems a little messy.
So my question is how to upload files and data in one form on just one page. I could do it with two seperate pages. One for adding the reseller and one for adding the image but that seems over complicated.
I tried to follow some steps and clues from this question:
Upload files in Google App Engine
******Edit***** Working Implementation Below:
class EditReseller(BaseHandler, blobstore_handlers.BlobstoreUploadHandler):
def get(self, reseller_id):
if self.user:
reseller = Reseller.get_by_id(int(reseller_id))
upload_url = blobstore.create_upload_url('/upload')
image = True
if reseller.blob_key_logo is None:
image = False
self.render('edit_reseller.html', r=reseller, reseller_id=reseller_id, upload_url=upload_url, image=image)
else:
self.redirect('/admin')
class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
def post(self):
reseller_id = self.request.get('reseller_id')
upload_files = self.get_uploads('logo_img')
if upload_files:
blob_info = upload_files[0]
key = blob_info.key()
r = Reseller.get_by_id(int(reseller_id))
r.blob_key_logo = str(key)
r.put();
name = self.request.get('name')
website = self.request.get('website')
information = self.request.get('information')
address = self.request.get('address')
city = self.request.get('city')
state = self.request.get('state')
zipcode = self.request.get('zipcode')
email = self.request.get('email')
phone = self.request.get('phone')
if name and website and information and email and phone and address and city and state and zipcode:
r = Reseller.get_by_id(int(reseller_id))
r.name = name
r.website = website
r.information = information
r.address = address
r.city = city
r.state = state
r.zipcode = zipcode
r.email = email
r.phone = phone
r.put()
else:
error = "Looks like your missing some critical info"
self.render("edit_reseller.html", name=name, website=website, information=information, address=address, city=city, zipcode=zipcode, email=email, phone=phone, error=error)
self.redirect("/edit_reseller/" + reseller_id)
You just need to put the logic of the UploadHandler inside the Reseller(BaseHandler) and make Reseller inherit from blobstore_handlers.BlobstoreUploadHandler.
The call to get_uploads fails, as the NewReseller Class does not inherit from BlobstoreUploadHandler. The BlobstoreUploadHandler class takes over the upload operation so you do not need to create a post method, just add the corresponding logic from post ( name = self.request.get('name'), r = Reseller(), r.put(), etc. ) and add it to the upload method.
You should not call or create a new a handler instance by hand (unless you know what you are doing), as it would be missing the things that make it work.
The complete app sample at the official docs, might also be helpful.