Upload multiple images with Python, Flask and Flask-Dropzone

Dustin D'Avignon
6 min readNov 23, 2017

--

I came across Flask in my software engineering class in college. I was already interested in learning more web development and was hoping to continue with Node and Express, but instead we were to use Flask. Although I was reluctant at first, Flask has quickly grown on me for the ease of use with small projects. In this post we are going to create a small application that will allow users to drag and drop multiple images to be uploaded and viewed on a results page.

First we will need to gather our dependencies. We don’t want to reinvent the wheel if we don’t have to. Fortunately for us Flask has many useful extensions that can be found on the Flask extensions registry. We will be using Flask-Uploads extension as well as a Flask-Dropzone extension for file uploads that allows us to use Flask with Dropzone.js.

Let’s start our virtualenv and install the dependencies.

pip install flask flask-uploads flask-dropzone

Now we can lay down the boilerplate code for our Flask web server. Create an app.py file for our server and put in the following code.

# app.pyfrom flask import Flask, render_template, requestapp = Flask(__name__)@app.route(‘/’)
def index():
return render_template(‘index.html’)

Next create an index.html file for our us to render in a directory called templates.

# templates/index.html<!DOCTYPE html>
<html>
<head>
<title>Flask App</title>
</head>
<body>
<h1>Hello from Flask!</h1>
</body>
</html>

We can now test our setup. Run the server with:

export FLASK_APP=app.py
flask run

You should see the Hello from Flask! header if all went well.

Next lets configure Flask-Dropzone for our image uploads. We will need to import Flask-Dropzone and initialize it in our application. To do this we will need to add:

# app.pyfrom flask_dropzone import Dropzone...dropzone = Dropzone(app)...# Dropzone settingsapp.config['DROPZONE_UPLOAD_MULTIPLE'] = True
app.config['DROPZONE_ALLOWED_FILE_CUSTOM'] = True
app.config['DROPZONE_ALLOWED_FILE_TYPE'] = 'image/*'
app.config['DROPZONE_REDIRECT_VIEW'] = 'results'

You can see that we have configured Dropzone to allow for multiple images and after a successful upload we get redirected to a results route that we will need to create. But before we do that let’s setup Flask-Uploads.

We will need to add to our application:

# app.pyfrom flask_uploads import UploadSet, configure_uploads, IMAGES, patch_request_classimport os
...
# Uploads settings
app.config['UPLOADED_PHOTOS_DEST'] = os.getcwd() + '/uploads'
photos = UploadSet('photos', IMAGES)
configure_uploads(app, photos)
patch_request_class(app) # set maximum file size, default is 16MB

And we can also add our /results route so now our app.py file should look like:

# app.pyfrom flask import Flask, render_template
from flask_dropzone import Dropzone
from flask_uploads import UploadSet, configure_uploads, IMAGES, patch_request_class
import osapp = Flask(__name__)
dropzone = Dropzone(app)
# Dropzone settings
app.config['DROPZONE_UPLOAD_MULTIPLE'] = True
app.config['DROPZONE_ALLOWED_FILE_CUSTOM'] = True
app.config['DROPZONE_ALLOWED_FILE_TYPE'] = 'image/*'
app.config['DROPZONE_REDIRECT_VIEW'] = 'results'
# Uploads settings
app.config['UPLOADED_PHOTOS_DEST'] = os.getcwd() + '/uploads'
photos = UploadSet('photos', IMAGES)
configure_uploads(app, photos)
patch_request_class(app) # set maximum file size, default is 16MB
@app.route('/')
def index():
return render_template('index.html')

@app.route('/results')
def results():
return render_template('results.html')

Go ahead and create the results.html file in the templates folder. For now we can just put in some placeholder text.

# templates/results.html<h1>Hello Results</h1>

Now we have a basic setup for our app and we can work on the front-end of our application. We are now going to display our Dropzone area so users can upload their cool cat photos.

In our index.html file we need to load dropzone in the head tag and we might as well style it while we are at it.

# templates/index.html<head>
<title>Flask App</title>
{{ dropzone.load() }}
{{ dropzone.style('border: 2px dashed #0087F7; margin: 10%; min-height: 400px;') }}
</head>

Next we create the form in the body and declare the action of the form. For simplicity we will use the index view but you can easily add a new route if you choose.

# templates/index.html<body>
<h1>Hello from Flask!</h1>
{{ dropzone.create(action_view='index') }}
</body>

Your index.html file should now look similar to:

# templates/index.html<!DOCTYPE html>
<html>
<head>
<title>Flask App</title>
{{ dropzone.load() }}
{{ dropzone.style('border: 2px dashed #0087F7; margin: 10%; min-height: 400px;') }}
</head>
<body>
<h1>Hello from Flask!</h1>
{{ dropzone.create(action_view='index') }}
</body>
</html>

Go ahead and run the app with flask run from the terminal and you should see something like this:

Now lets setup our index route to allow the post request that we will receive from our Dropzone form:

# app.pyfrom flask import Flask, render_template, request...@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
file_obj = request.files
for f in file_obj:
file = request.files.get(f)
print (file.filename)
return "uploading..."
return render_template('index.html')

Now that we let Flask know that the index route can except post requests, we can grab the data from the uploaded files from the request object. Since we are allowing Dropzone to upload multiple files, we need to iterate through the file object to get our individual uploads.

Go ahead and run the server and drop in some photos. You should see the images upload with a progress bar and the filenames print out in your server console. Cool! We are almost there. Now we need to save our files to our uploads folder and get the urls to display the images in our results page.

# app.py@app.route('/', methods=['GET', 'POST'])
def index():

# list to hold our uploaded image urls
file_urls = []

if request.method == 'POST':
file_obj = request.files
for f in file_obj:
file = request.files.get(f)

# save the file with to our photos folder
filename = photos.save(
file,
name=file.filename
)
# append image urls
file_urls.append(photos.url(filename))

return "uploading..."
return render_template('index.html')

Now if we upload images we can see they get put into a folder named uploads. Let’s set up the results.html file to display our image results.

# templates/results.html<h1>Hello Results Page!</h1>
<a href="{{ url_for('index') }}">Back</a><p>
<ul>
{% for file_url in file_urls %}
<li><img style="height: 150px" src="{{ file_url }}"></li>
{% endfor %}
</ul>

Now here is where things get tricky. We will need to pass all of our file urls to another view. We can achieve this with using the Flask session object. Session allows us to store information specific to a user from one request to the next. Since this is implemented on top of cookies we will need to set the secret key. This is something that should be really kept secret since your cookies could be modified if someone has access to your key.

# app.pyfrom flask import Flask, redirect, render_template, request, session, url_for...app.config['SECRET_KEY'] = 'supersecretkeygoeshere'...@app.route('/', methods=['GET', 'POST'])
def index():

# set session for image results
if "file_urls" not in session:
session['file_urls'] = []
# list to hold our uploaded image urls
file_urls = session['file_urls']
# handle image upload from Dropzone
if request.method == 'POST':
file_obj = request.files
for f in file_obj:
file = request.files.get(f)

# save the file with to our photos folder
filename = photos.save(
file,
name=file.filename
)
# append image urls
file_urls.append(photos.url(filename))

session['file_urls'] = file_urls
return "uploading..."
# return dropzone template on GET request
return render_template('index.html')
@app.route('/results')
def results():

# redirect to home if no images to display
if "file_urls" not in session or session['file_urls'] == []:
return redirect(url_for('index'))

# set the file_urls and remove the session variable
file_urls = session['file_urls']
session.pop('file_urls', None)

return render_template('results.html', file_urls=file_urls)

Now we should see be able to see our results.

Yay! Cat photos!

And that’s it! Of course there are many more ways we can extends this application aside from appearances. One approach would be to store the photo urls in a database to be accessed later and replace the filename with something more unique. If you made it this far thanks for reading and feel free to comment and leave some suggestions for my first post! 🔥🔥🔥

You can view the code for this demo here.

--

--