Flask: Drag & Drop + Click & Select example | single page app

Flask: Drag & Drop + Click & Select | example single page app


I needed to create a simple web page that takes the MS Excel file as input, reads it, and shows the result.

I made my analysis for available solutions and I found out the common way is to use Dropzone.js or flask-dropzone.

It turns out that it is quite overkilling for my solution: it is focused on images, it allows multiple files uploads but the greatest problem for me was to create a single-page app (result is shown on the same page as upload).

After some searching and reading manuals I finished with the following result:

Project structure

MyProject
│   app.py
│
├───static
│       app.css
│       app.js
│
├───templates
│       index.html
│       response.html
│
└───uploads

Code

app.py


#!/usr/bin/python3
# -*- coding: utf-8 -*-

from flask import Flask, jsonify, redirect, render_template, request, url_for
from werkzeug.utils import secure_filename

app = Flask(__name__)
app.config["UPLOAD_FOLDER"] = "uploads"


@app.route("/")
def index():
    return render_template("index.html")


@app.route("/uploadfile", methods=["POST"])
def send_file():
    fileob = request.files["file_for_upload"]
    filename = secure_filename(fileob.filename)
    save_path = "{}/{}".format(app.config["UPLOAD_FOLDER"], filename)
    fileob.save(save_path)
    message = f"successful_upload of {filename}"
    return jsonify(
        {
            "htmlresponse": render_template("response.html", msg=message)
        }
    )


if __name__ == "__main__":
    app.run(debug=True, port=5555)

static/app.css


.dropzone {
    box-shadow: 0 0 8px rgb(207, 207, 207);
    border: 1px solid #dedede;
    border-radius: 15px;
    background: #f5f5f5;
    text-align: center;
    font-family: Roboto;
    cursor: pointer;
}

.dropzone:hover {
    box-shadow: inset 1px 1px 1px #999;
}

.dropzone i {
    font-size: 5rem;
}

.dropzone b {
    font-size: 1.3rem;
}

.dropzone .dz-message {
    color: rgba(0,0,0,.54);
    font-weight: 500;
    font-size: initial;
    text-transform: uppercase;
}

static/app.js


$(function() {
    function selectFile() {
        var inputFile = document.createElement("input");
        inputFile.type = "file";
        // filter input files to MS Excel
        inputFile.accept =
            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
            + ",application/vnd.ms-excel";
        inputFile.onchange = function() {
            var files = Array.from(inputFile.files);
            $("#targetLayer").hide(); // hide result area
            uploadFiles(files);
        };
        inputFile.click();
    };

    var showUploadResult = function(data) {
        $("#targetLayer").show();
        // append to html: $("#targetLayer").append(data.htmlresponse);
        $("#targetLayer").html(data.htmlresponse);
    };

    var showError = function(data) {
        $("#targetLayer").show();
        $("#targetLayer").html(data.statusText);
    };

    var dragHandler = function(evt) {
        evt.preventDefault();
    };

    function uploadFiles(files) {
        var formData = new FormData();
        formData.append("file_for_upload", files[0]);

        var req = {
            url: "/uploadfile",
            method: "post",
            processData: false,
            contentType: false,
            data: formData
        };

        var promise = $.ajax(req);
        promise.then(showUploadResult, showError);
    };

    var dropHandler = function(evt) {
        evt.preventDefault();
        var files = evt.originalEvent.dataTransfer.files;
        uploadFiles(files);
    };

    var handlers = {
        click: selectFile,
        dragover: dragHandler,
        drop: dropHandler,
    };

    $(".dropzone").on(handlers);
});

templates/index.html


<!DOCTYPE html>
<html lang="en">
<head>
    <title>Sample app</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,400i,500,500i,700" rel="stylesheet">
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
    <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
    <link href="{{url_for('static', filename='app.css')}}" rel="stylesheet" type="text/css" />
</head>
<body>
    <div class="container">
    <h1>Drag & Drop + Click & Select example</h1>
        <div id="droparea" class="dropzone dz-message" style="width: 20%;">
            <i class="material-icons text-muted">cloud_upload</i><br/>
            <b>Drop your file<br/>here</b>
        </div>
    </div>
    <div class="container" id="targetLayer" style="display:none;"></div>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
    <script src="{{url_for('static', filename='app.js')}}"></script>
</body>
</html>

templates/response.html


<p><i style="color:gray">{{ msg }}</i></p>

Comments

Popular posts from this blog