0

There are related questions here and here but they don't solve my problem.

Let's say I have a file testdata.csv like this:

A,B
1,3
2,4

and I want the user allow to upload the file and then - just for simplicity - display its content on the same page without affecting any other elements on this page. So my desired outcome would look like this:

enter image description here

How would I do this? I found a couple of approaches that use a form to load the file and send its (modified) content back (to e.g. download it) but I struggle to find a solution to pass the JSON response back to modify the page (here: add the table).

This is my entire code:

from flask import Flask, render_template, request, jsonify
import pandas as pd
import numpy as np
import json

# Initialize the Flask application
app = Flask(__name__)


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


@app.route('/_get_table', methods=["POST", "GET"])
def get_table():

    # how to catch the file here and send its content back?
    # file = ????
    # print(file)
    df = pd.DataFrame({"A": [1, 2], "B": [3, 4]})

    return jsonify(my_table=json.loads(df.to_json(orient="split"))["data"],
                   columns=[{"title": str(col)} for col in json.loads(df.to_json(orient="split"))["columns"]])

    # if I use a form, I can use
    # file = request.files['myfile']
    # however, how do I then send the JSON response?

if __name__ == '__main__':
    app.run(debug=True)

and my index.html file:

<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
    <link href="https://cdn.datatables.net/1.10.16/css/jquery.dataTables.min.css" rel="stylesheet">
  </head>
  <body>
    <div class="container">
      <div class="header">
        <h3 class="text-center text-muted">Some great stuff</h3>
      </div>

      <hr class="mb-4">

      <div class="custom-file">
         <input id="myfile" name="myfile" type="file" class="custom-file-input">

         <label for="myfile" class="custom-file-label">
           Choose file...
         </label>
      </div><br><br>

      <button class="btn btn-primary" type="button" id="upload_document">Upload and process!</button>

      <hr class="mb-4">

      <table id="pretty_table" class="table table-striped"></table>

      <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
      <script src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js" type="text/javascript"></script>
      <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
      <script type="text/javascript">
        $(document).ready(function() {

          var table = null;
          $('#upload_document').bind('click', function() {

            $.getJSON('/_get_table', {
              // what to pass here?
            }, function(data) {

              if (table !== null) {
                table.destroy();
                table = null;
                $("#pretty_table").empty();
              }
              table = $("#pretty_table").DataTable({
                data: data.my_table,
                columns: data.columns

              });
            });
            return false;
          });

          $('.custom-file-input').on('change', function() {
             let fileName = $(this).val().split('\\').pop();
             $(this).next('.custom-file-label').addClass("selected").html(fileName);
          });
        });
      </script>
  </body>
</html>
Cleb
  • 25,102
  • 20
  • 116
  • 151
  • Search how to upload file with ajax (not hard to find lots of results). Will need to use FormData API and $.ajax instead of simple `getJSON` – charlietfl May 19 '18 at 18:53
  • @charlietfl: Ok, thanks for the comment. You mean, something like [this](https://www.mkyong.com/jquery/jquery-ajax-submit-a-multipart-form/)? Never used this before; let's see if I get it to work... If it is not too much work for you, I would highly appreciate an answer (the html/jquery part would be sufficient), thanks! – Cleb May 19 '18 at 18:58
  • Yes...that is the basics in that link – charlietfl May 19 '18 at 18:59
  • @charlietfl: Ok, these things always give me a headache, but I will try... :) – Cleb May 19 '18 at 19:07
  • An alternative is to use FileReader API and parse the csv in browser – charlietfl May 19 '18 at 19:24
  • @charlietfl: have never used that either, but will take a look. Would it be much work for you to write the FormData jquery part? (just asking ;) ) – Cleb May 19 '18 at 19:37
  • 1
    It's already written in that link. Simply a matter of matching selector to your form – charlietfl May 19 '18 at 19:56
  • @charlietfl: Thanks, that got me on the right track; I added an answer below which contains all required files to obtain the desired outcome. – Cleb May 19 '18 at 23:18

1 Answers1

0

@charlietfl in the comments and this link got me on the right track. One can use FormData and then an .ajax call to achieve the desired outcome. So the important parts of above's link are (the complete files can be found below):

<form method="POST" enctype="multipart/form-data" id="fileUploadForm">
    <div class="custom-file">
       <input id="myfile" name="myfile" type="file" class="custom-file-input">

         <label for="myfile" class="custom-file-label">
             Choose file...
         </label>
    </div>
</form>

and

// Get form
var form = $('#fileUploadForm')[0];

// Create an FormData object
var data = new FormData(form);

and

$.ajax({
        type: "POST",
        enctype: 'multipart/form-data',
        url: "/_get_table",
        data: data,
        processData: false,

The processData: false, is important to prevent jQuery form transforming the data into a query string as explained in the link above.

On the flask site, we can then easily retrieve the file by doing:

file = request.files['myfile']

df = pd.read_csv(file)

which turns the .csv into a pandas dataframe. Clearly, there should be checks etc before doing this.

The entire HTML page index_formdata.html would look like this:

<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
    <link href="https://cdn.datatables.net/1.10.16/css/jquery.dataTables.min.css" rel="stylesheet">
  </head>
  <body>
    <div class="container">
      <div class="header">
        <h3 class="text-center text-muted">Some great stuff</h3>
      </div>

      <hr class="mb-4">
      <form method="POST" enctype="multipart/form-data" id="fileUploadForm">
        <div class="custom-file">
           <input id="myfile" name="myfile" type="file" class="custom-file-input">

           <label for="myfile" class="custom-file-label">
             Choose file...
           </label>
        </div>
      </form><br><br>

      <button class="btn btn-primary" type="button" id="upload_document">Upload and process!</button>

      <hr class="mb-4">

      <table id="pretty_table" class="table table-striped"></table>

      <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
      <script src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js" type="text/javascript"></script>
      <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
      <script type="text/javascript">
        $(document).ready(function () {

            var table = null;

            $("#upload_document").click(function (event) {

                //stop submit the form, we will post it manually.
                event.preventDefault();

                // Get form
                var form = $('#fileUploadForm')[0];

                // Create an FormData object
                var data = new FormData(form);

                // disabled the submit button
                $("#upload_document").prop("disabled", true);

                $.ajax({
                    type: "POST",
                    enctype: 'multipart/form-data',
                    url: "/_get_table",
                    data: data,
                    processData: false,
                    contentType: false,
                    cache: false,
                    timeout: 600000,
                    success: function (data) {

                      if (table !== null) {
                        table.destroy();
                        table = null;
                        $("#pretty_table").empty();
                      }
                      table = $("#pretty_table").DataTable({
                        data: data.my_table,
                        columns: data.columns

                      });
                      $("#upload_document").prop("disabled", false);

                    },
                    error: function (e) {

                        alert(e.responseText);
                        console.log("ERROR : ", e);
                        $("#upload_document").prop("disabled", false);

                    }
                });

            });
            $('.custom-file-input').on('change', function() {
               let fileName = $(this).val().split('\\').pop();
               $(this).next('.custom-file-label').addClass("selected").html(fileName);
            });

        });
      </script>
  </body>
</html>

the flask file as follows:

from flask import Flask, render_template, request, jsonify
import pandas as pd
import numpy as np
import json

# Initialize the Flask application
app = Flask(__name__)


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


@app.route('/_get_table', methods=["POST", "GET"])
def get_table():

    # catch file from the form; if you have several files, just make several requests (access by name)
    file = request.files['myfile']

    df = pd.read_csv(file)

    return jsonify(my_table=json.loads(df.to_json(orient="split"))["data"],
                   columns=[{"title": str(col)} for col in json.loads(df.to_json(orient="split"))["columns"]])

if __name__ == '__main__':
    app.run(debug=True)

and the testdata.csv file

A,B,C
1,3,123
2,4,456
Cleb
  • 25,102
  • 20
  • 116
  • 151