flask form.validate_on_submit() fails using wtforms

10,663

You call form.validate() everytime someone visits '/'. When the request is a GET, there is no form data causing validation to fail. You only want to try to validate the form when the request is a POST.

One way to do that is to check the request's method.

if request.method == 'POST':
    if form.validate():
        session['dataserver'] = ds = form.dataserver.data
        return redirect(url_for('login'))
    else:
        flash('Failed validation')

Another common way to do this is with validate_on_submit. It handles checking for POSTs as well as validating the form.

if form.validate_on_submit():
    session['dataserver'] = ds = form.dataserver.data
    return redirect(url_for('login'))

Here you'd lose your ability to flash your 'validation failed' message. That may be acceptable, though, because you can check for form errors in your template.

{% if form.errors %}
    failed validation
{% endif %}

UPDATE

If you want to see the errors (which you may not), you can print them in the template instead of the generic "failed validation" message.

{% if form.errors %}
    {{ form.errors }}
{% endif %}
Share:
10,663
bhudson
Author by

bhudson

Updated on June 04, 2022

Comments

  • bhudson
    bhudson almost 2 years

    Using @dirn's suggestions, the validation error does not display anymore, but it still seems to fail as neither the print statement in the root() function displays/runs nor does the new form.errors show.

    App Code:

    #!/usr/bin/env python
    
    import cherrypy
    import os
    from flask import Flask, render_template, redirect, url_for, session, request, flash, abort, Markup
    from flask.ext.bootstrap import Bootstrap
    from sybload import funcs, forms
    
    app = Flask(__name__)
    app.debug = True
    app.config['SECRET_KEY'] = os.urandom(24)
    app.config['CSRF_ENABLED'] = True
    bootstrap = Bootstrap(app)
    
    dataserver_info = funcs.get_dataserver_info()
    dataservers = funcs.get_dataservers(dataserver_info)
    
    @app.route('/', methods=['GET', 'POST'])
    def root():
        session.clear()
        form = forms.DataServersForm()
        form.dataserver.choices = zip(dataservers, dataservers)
        if form.validate_on_submit():
            session['dataserver'] = form.dataserver.data
            # print statement below never runs
            print session['dataserver'], form.dataserver.data
            return redirect(url_for('login'))
        return render_template('root.html', title='Sybase Load App', form=form)
    
    def run_server():
        cherrypy.tree.graft(app, '/')
        cherrypy.config.update({
            'log.access_file': 'logs/access.log',
            'log.error_file': 'logs/errors.log',
            'engine.autoreload_on': True,
            'log.screen': True,
            'server.socket_host': '0.0.0.0',
            'server.socket_port': 5000,
            'tools.sessions.on': True,
            'tools.sessions.secure': True,
            'tools.sessions.httponly': True
        })
        cherrypy.engine.start()
        cherrypy.engine.block()
    
    if __name__ == '__main__':
        run_server()
    

    Template (jinja2):

        {% block body %}
        <form method='post' action='{{ url_for('login') }}'>
            {{ form.hidden_tag() }}
    
            {{ form.dataserver.label }}<br>
            {{ form.dataserver }}<br><br>
    
            {{ form.submit }}
        </form>
        <!-- Below never displays -->
        {% if form.errors %}
            failed validation
        {% endif %}
        {% endblock %}
    

    Form:

    from flask.ext.wtf import Form
    from wtforms import StringField, PasswordField, SelectField, SelectMultipleField, SubmitField, BooleanField
    from wtforms.validators import Required
    import funcs
    
    class DataServersForm(Form):
        dataserver = SelectField('Dataservers', validators=[Required()])
        submit = SubmitField('Submit')
    
  • bhudson
    bhudson about 9 years
    After applying both of your suggestions, I believe there is still something wrong. The session['dataserver'] is never set properly. @app.route('/', methods=['GET', 'POST']) def root(): session.clear() form = forms.DataServersForm() form.dataserver.choices = zip(dataservers, dataservers) if form.validate_on_submit(): session['dataserver'] = form.dataserver.data print session['dataserver'], form.dataserver.data return redirect(url_for('login')) return render_template('root.html', title='Sybase Load App', form=form)
  • dirn
    dirn about 9 years
    Code doesn't work so well in comments, please update your question instead.
  • dirn
    dirn about 9 years
    Have you tried inspecting form.errors to see if the form isn't validating?
  • bhudson
    bhudson about 9 years
    Yes. form.errors before form.validate_on_submit() yields an empty dict, while it never even runs (e.g. print form.errors) if it's inside the "if form.validate_on_submit():" block.
  • dirn
    dirn about 9 years
    If form validation fails, nothing inside the if will happen. You need to check it after (outside) the if. One such place to do so would be in the template like I mentioned in the answer.
  • bhudson
    bhudson about 9 years
    Thanks for your help! I managed to fix the last issue. It was validating properly, but the statement "session['dataserver'] = form.dataserver.data" had to be changed to "session['dataserver'] = request.form['dataserver']". I'm not quite sure why (get vs. post maybe?). It makes me think I need "if request.method == 'POST' and form.validate_on_submit():" instead.
  • bhudson
    bhudson about 9 years
    Note: I also removed the "action='{{ url_for('login') }}'" line in my template.
  • Admin
    Admin over 2 years
    Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.