Quasar Uploader and file handler in Flask
Posted on
by Kevin FoongIn this blog post we will be exploring the Quasar QUploader component in order to upload photos and how to handle this in the backend via Flask. I assume you already have some basic knowledge about Vue, Quasar and some Flask.
Front End - Quasar
We can start with the base code taken from the Quasar QUploader page - https://quasar.dev/vue-components/uploader
Notes for the template section:
- Upon user clicking the upload button on the component, the
uploadImage
function will be called. - Our uploader only accepts image type JPG and PNG. It does not matter what the file extension is (for eg. "jpeg", "jpg") because we used
image/jpeg
,image/png
- Our uploader will only accept 2 images to be uploaded.
- There are 3 separate events:
@uploaded
- This is called on successful upload.@failed
- This is called on failed upload. Note that if there is a backend error Flask sends back JSON data with the HTTP error code and a reason for the error. We get this data in Quasar viainfo.xhr.response
@rejected
- This is called on validation error. For example this function will be called if we tried to upload a GIF image and will not proceed further.
Notes for the script section:
- Our factory function
uploadImage
is where we make the request to our backend endpoint. Quasar handles all this for you. You only need to pass it a URL and the type of request (ie. POST). uploadImage
is called once for each image we have to upload. So if we have two images, it will be called twice. (The Flask endpoint will also be called twice). This is why we see individual (successful, not successful) notifications for each image. Note that the method only needs to return an object with url and method of the endpoint and Quasar does the rest. No need for you to use Axios.- Note that we used the Notify component quite a bit so don't forget to add it in your
quasar.conf.js
file
framework: {
// Quasar plugins
plugins: ['Notify']
}
<template>
<q-page padding>
<q-uploader
style="max-width: 300px"
:factory="uploadImage"
accept="image/jpeg, image/png"
label="Max number of files (2)"
multiple
max-files="2"
@uploaded="onUploaded"
@failed="onFailed"
@rejected="onRejected"
/>
</q-page>
</template>
<script>
export default {
name: 'Home',
methods: {
uploadImage(file) {
return {
url: 'http://localhost:5000/api/upload',
method: 'POST'
}
},
onUploaded(info) {
let files = info.files
files.forEach(item => {
this.$q.notify({
type: 'positive',
message: `${item.name} successfully uploaded`
})
})
},
onFailed(info) {
let err = JSON.parse(info.xhr.response)
console.log(err)
let files = info.files
files.forEach(item => {
this.$q.notify({
type: 'negative',
message: `${item.name} - ${err.error} Error ${err.message}`
})
})
},
onRejected(rejectedEntries) {
this.$q.notify({
type: 'negative',
message: `${rejectedEntries.length} file(s) did not pass validation constraints`
})
}
}
}
</script>
Back End - Flask
I used a few interesting Python packages in the Flask backend including the following:
- PILLOW - to resize uploaded images to a maximum resolution, in our case 1800 x 1800 pixels. This is to prevent very large images from being uploaded to prevent overblown file storage space and also to cater for bandwidth - https://pillow.readthedocs.io/en/stable/
- StringGenerator - a handy function to create random file names for our uploaded images - https://pypi.org/project/StringGenerator/
- Pathlib - a very handy Python utility for working with files and folders as it provides a uniform way of accessing files and folders on different systems (ie. Windows, MAC, Linux, etc). https://docs.python.org/3/library/pathlib.html
Notes for the Flask implementation:
- This is just a simple API endpoint accessed via
/api/upload
- Quasar includes the file which we obtain in Flask via
request.files
- We save the images to the
static/uploads
folder. If it has not been created we create it. - We use the StringGenerator package to generate a random filename for the image that is 10 characters long.
- We use PILLOW to get the image, resize it to a maximum resolution of 1800x1800 and then save it as a JPG file.
- If its successful we return a 204 code which means it the file has been saved successfully. A 204 response is returned to Quasar without a body.
- At various points if there are errors we return a 400 Bad Request. The file is not uploaded. Quasar displays this error along with the error message to the user in the
onFailed
method. - This tutorial didn't include writing data to the database and retrieving the image back to the front-end, but you could easily do that using the code presented here and extending it.
from flask import request, jsonify
from PIL import Image
from strgen import StringGenerator
from pathlib import Path
from . import app
def bad_request(message):
payload = {'error': '400', 'message': message}
response = jsonify(payload)
response.status_code = 400
return response
# handle photo uploads
@app.route('/api/upload', methods=['POST'])
def upload():
# get the file
file_dict = request.files.to_dict()
if len(file_dict) < 1:
return bad_request('No file found')
key = list(file_dict.keys())[0]
file = file_dict[key]
# folder location to save image
basedir = Path(__file__).parent
folder = basedir / 'static/uploads'
if not folder.is_dir():
folder.mkdir()
# get a random file name 10 characters long
filename = StringGenerator("[\d\w]{10}").render() + '.jpg'
try:
# use PILLOW to reduce image to max size 1800 x 1800 pixels
size = (1800, 1800)
img = Image.open(file.stream)
# for png images need to convert to RGB format
img = img.convert('RGB')
img.thumbnail(size)
img.save(folder / filename)
except IOError as e:
return bad_request('Unable to convert and save file')
# if you like you can also write any data regarding the upload to the database here
# return 204 - successful and no body
return '', 204
The Quasar QUploader component has other options and configurations which are all covered in the Quasar documentation so make sure you check it out.