Using Flask for Scientific Web Applications

Uploading of files

Overview of the application

Many user interfaces need the user to provide data files. A minimalistic application is to have a button for uploading a single file. As example we use a file with a series of numbers, and the application's purpose is to compute the mean and standard deviation of the numbers. The first user interface just has the Choose File and Compute buttons:

Clicking on Choose File brings up a file browser where the user can choose the file to uploaded to the application. Say this is a file testfile.dat. The interface now looks like

Pressing thereafter Compute leads to storage of testfile.dat on the server in a subdirectory uploads and computation of basic statistics of the numbers in the file. The resulting output looks like

The text "No file chosen" is automatically displayed by the widget object used for file upload and indicates that a new file can be chosen. Below we shall present all parts of the code needed to create this interactive application.

The model class

The widget FieldField is used for an input field with a Choose File button:

import wtforms as wtf

class Average(wtf.Form):
    filename   = wtf.FileField(validators=
                               [wtf.validators.InputRequired()])

The controller file

The controller file needs some special code to specify a directory to store uploaded files. We also include some code to check that the file has a name with the right extension.

# Relative path of directory for uploaded files
UPLOAD_DIR = 'uploads/'

app.config['UPLOAD_FOLDER'] = UPLOAD_DIR
app.secret_key = 'MySecretKey'

if not os.path.isdir(UPLOAD_DIR):
    os.mkdir(UPLOAD_DIR)

# Allowed file types for file upload
ALLOWED_EXTENSIONS = set(['txt', 'dat', 'npy'])

def allowed_file(filename):
    """Does filename have the right extension?"""
    return '.' in filename and \ 
           filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS

The index function must have code for saving the file, and as usual, calling the compute function and rendering a new page:

def index():
    form = Average(request.form)
    filename = None  # default
    if request.method == 'POST':

        # Save uploaded file on server if it exists and is valid
        if request.files:
            file = request.files[form.filename.name]
            if file and allowed_file(file.filename):
                # Make a valid version of filename for any file ystem
                filename = secure_filename(file.filename)
                file.save(os.path.join(app.config['UPLOAD_FOLDER'],
                                       filename))

        result = compute_function(filename)
    else:
        result = None

    return render_template("view.html", form=form, result=result)

The compute function

We assume that the uploaded file is available in the uploads subdirectory, so the compute function needs to open this file, read the numbers, and compute statistics. The file reading and computations are easily done by numpy functions. The results are presented in an HTML table.

import numpy as np
import os

def compute_mean_std(filename=None):
    data = np.loadtxt(os.path.join('uploads', filename))
    return """
Data from file <tt>%s</tt>:
<p>
<table border=1>
<tr><td> mean    </td><td> %.3g </td></tr>
<tr><td> st.dev. </td><td> %.3g </td></tr>
""" % (filename, np.mean(data), np.std(data))

The HTML template

Although the present minimalistic application only needs a very simple HTML template, we reuse a quite generic template known from previous examples, where the input variables are listed to the left and the output of the compute function is presented to the right. Such a template looks like

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Flask Average app</title>
  </head>
  <body>

  <!-- Input and Results are typeset as a two-column table -->
  <table>
  <tr>
  <td valign="top">
    <h2>Input:</h2>

      <form method=post action="" enctype="multipart/form-data">
        <table>
          {% for field in form %}
            <tr><td>{{ field.name }}</td>
                <td>{{ field(size=20) }}</td>
                <td>{% if field.errors %}
                  <ul class=errors>
                  {% for error in field.errors %}
                    <li>{{ error }}</li>
                  {% endfor %}</ul>
                {% endif %}</td></tr>
          {% endfor %}
        </table>
        <p><input type="submit" value="Compute">
    </form></p>
  </td>

  <td valign="top">
    {% if result != None %}
      <h2>Results:</h2>
        {{ result|safe }}

    {% endif %}
  </td>
  </tr>
  </table>
  </body>
</html>

The complete set of files is found in the upload directory.