Passing Flask WTForms validations to Bootstrap Alerts via Flask-Bootstrap

3.4k Views Asked by At

new Flask user here...I am building a pretty file upload button for my flask app. The button essentially makes use of WTForms to validate that only csv and txt files can be uploaded. The upload works, but how can I pass validation errors to the screen as a bootstrap alert? For example:

  1. pressing the upload button will generate an alert "no file selected"
  2. pressing upload with a jpeg will generated "wrong file format"

any suggestions will be appreciated!

My forms.py:

class UploadForm(FlaskForm):
    validators = [FileRequired(message='There was no file!'),
                  FileAllowed(['csv', 'txt'], message='Must be a csv file!')]

    input_file = FileField('', validators=validators)
    submit = SubmitField(label="Upload")

my route.py:

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

    form = UploadForm()

    if request.method == 'POST' and form.validate_on_submit():
        input_file = request.files['input_file']

        # Do stuff
        filename = secure_filename(input_file.filename)

        # save file to disk to some folder defined in a separate config file....
        data = os.path.join(SOME_UPLOAD_FOLDER, filename)
        input_file.save(data)

        return redirect(url_for('upload'))

else:
    return render_template('upload.html', form=form)

and finally the HTML/CSS/JS:

{% extends "bootstrap/base.html" %}
{% import "bootstrap/wtf.html" as wtf %}

{% block html_attribs %} lang="en" charset="utf-8"{% endblock %}

{% block metas %}
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
{% endblock %}


{% block styles %}

    <!-- Latest compiled and minified CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

    <style>
        #browsebutton {
            background-color: white;
        }

        #my-file-selector {
            display: none;
        }
    </style>


{% endblock %}

{% block content %}

    <div class="container">
        <div class="jumbotron">
            <h3>File Uploader Example</h3>


            <div class="row">
                <form class="form-inline center-block" action="/upload" method="POST" enctype="multipart/form-data">
                    {{ form.hidden_tag() }}
                    <div class="input-group">
                        <label id="browsebutton" class="btn btn-default input-group-addon" for="my-file-selector">
                            {{ form.input_file(id="my-file-selector") }}
                            Browse...
                        </label>
                        <input type="text" class="form-control" readonly>
                    </div>
                    {{ form.submit(class_="btn btn-primary") }}
                </form>
            </div>
        </div>
    </div>

{% endblock %}


{% block scripts %}

    <! -- jquery -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <!-- Latest compiled and minified JavaScript -->
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
            integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>

    <!-- pretty upload button -->
    <script>
    $(function() {

      // We can attach the `fileselect` event to all file inputs on the page
      $(document).on('change', ':file', function() {
        var input = $(this),
            numFiles = input.get(0).files ? input.get(0).files.length : 1,
            label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
        input.trigger('fileselect', [numFiles, label]);
      });

      // We can watch for our custom `fileselect` event like this
      $(document).ready( function() {
          $(':file').on('fileselect', function(event, numFiles, label) {

              var input = $(this).parents('.input-group').find(':text'),
                  log = numFiles > 1 ? numFiles + ' files selected' : label;

              if( input.length ) {
                  input.val(log);
              } else {
                  if( log ) alert(log);
              }

          });
      });

    });
    </script>

{% endblock %}
1

There are 1 best solutions below

1
On BEST ANSWER

If you are using WTForms, I would recommend using macros to render your form fields. It is detailed here, and you can get an example there (you can find some others online to help you customize rather than having to write it from scratch).

Note that in WTForm's documentation, the macro render_field() checks if your field has errors:

{% if field.errors %}
  <ul class=errors>
  {% for error in field.errors %}
    <li>{{ error }}</li>
  {% endfor %}
  </ul>
{% endif %}

You can of course decide not to use macros, in that case, you can just use this snippet above directly in your html form with form.input_file.errors instead of field.errors