In this blog post I'll show you how to upload files with the Flask Microframework. The code from this example is taken from my MinCloud open source project. Maybe this is not the ready to copy-paste-example, but I hope I explain all the stuff that goes on. If you have any questions, just leave a comment below.

Code

First my upload code which is available here. (MIT License)

@myapp.route("/api/upload", methods=['GET', 'POST'])
@flask_login.login_required
def _upload():
    if request.method == 'POST':
        file = request.files['files[]']

        # get filename and folders
        file_name = secure_filename(file.filename)
        directory = str(unique_id())
        upload_folder = myapp.config['UPLOAD_FOLDER']

        if file.filename == '':
            return redirect(request.url)
        if file and allowed_file(file.filename):

            save_dir = os.path.join(upload_folder, directory)

            if not os.path.exists(save_dir):
                os.makedirs(save_dir)

            cmpl_path = os.path.join(save_dir, file_name)
            file.save(cmpl_path)
            size = os.stat(cmpl_path).st_size

            # create our file from the model and add it to the database
            dbfile = File(file_name, directory, size, file.mimetype)

            g.user.uploads.append(dbfile)
            db.session().add(dbfile)
            db.session().commit()

            if "image" in dbfile.mimetype:
                get_thumbnail(cmpl_path, "100")
                thumbnail_url = request.host_url + 'thumbs/' + directory
            else:
                thumbnail_url = ""

            url = request.host_url + 'uploads/' + directory
            delete_url = url
            delete_type = "DELETE"

            file = {"name": file_name, "url": url, "thumbnailUrl": thumbnail_url, "deleteUrl": delete_url,
                    "deleteType": delete_type}
            return jsonify(files=[file])

        else:
            return jsonify(files=[{"name": file_name, "error": responds['FILETYPE_NOT_ALLOWED']}])

Step-by-Step

@myapp.route("/api/upload", methods=['GET', 'POST'])
@flask_login.login_required
def _upload():
    if request.method == 'POST':
        file = request.files['files[]']

If you look at the head of my upload function you'll see I require an login and the function is routed from /api/upload. So if you send an HTTP-GET or an HTTP-POST to the url this function is called.

As I use the jQuery-FileUpload from Blueimp. I access the file through request.files['files[]'] and then process it further.

# get filename and folders
        file_name = secure_filename(file.filename)
        directory = str(unique_id())
        upload_folder = myapp.config['UPLOAD_FOLDER']

        if file.filename == '':
            return redirect(request.url)

Then I'll get the filename it's important to use a secure filename which checks for filenames that could compromise your server. Such as ../../../etc/passwd. For more examples visit the werkzeug page.

Upload Folder

The upload folder I'm currently setting in my config file and get it with myapp.config['UPLOAD_FOLDER'].

Then if the filename is just nothing I return a redirect to the request.url.

  if file and allowed_file(file.filename):

            save_dir = os.path.join(upload_folder, directory)

            if not os.path.exists(save_dir):
                os.makedirs(save_dir)

            cmpl_path = os.path.join(save_dir, file_name)
            file.save(cmpl_path)
            size = os.stat(cmpl_path).st_size

I check with my allowed_file(file.filename) function if the extension of the file is in my allowed list of extension. I'm planning to do this with a database setting in future updates.

As I want to save some metadata in my database I'll get the size from the file.

Also I set up my file saving folders.

/opt/mincloud/uploads/
├── directory/
    ├── uploadedfile.extensions

So I'll save a uploaded file in my upload folder in an created folder with an unique id. This unique id is also saved in my database.

# create our file from the model and add it to the database
            dbfile = File(file_name, directory, size, file.mimetype)

            g.user.uploads.append(dbfile)
            db.session().add(dbfile)
            db.session().commit()

            if "image" in dbfile.mimetype:
                get_thumbnail(cmpl_path, "100")
                thumbnail_url = request.host_url + 'thumbs/' + directory
            else:
                thumbnail_url = ""

In this section I'll create an file for my database, and append it to the users uploads.

I use a thumbnail library and do some thumbnail stuff.

url = request.host_url + 'uploads/' + directory
            delete_url = url
            delete_type = "DELETE"

            file = {"name": file_name, "url": url, "thumbnailUrl": thumbnail_url, "deleteUrl": delete_url,
                    "deleteType": delete_type}
            return jsonify(files=[file])

        else:
            return jsonify(files=[{"name": file_name, "error": responds['FILETYPE_NOT_ALLOWED']}])

And at last I'll create the download url and the return json which complies with the jQuery-File Upload Library.