Flask-WTF – validate_on_submit() is never executed
Question:
I’m using Flask-WTF:
Here is my form:
from flask.ext.wtf import Form, TextField
class BookNewForm(Form):
name = TextField('Name')
Here is the controller:
@book.route('/book/new', methods=['GET', 'POST'])
def customers_new():
form = BookNewForm()
if form.is_submitted():
print "submitted"
if form.validate():
print "valid"
if form.validate_on_submit():
flash("Successfully created a new book")
return redirect(url_for('.books_show'))
return render_template('views/books_new.html', form=form)
Now the problem is, if you look at my print statements, it always prints submitted, but it NEVER prints valid and validate_on_submit() is never executed. Why?
Answers:
you can print errors
print form.errors
or
app.logger.debug(form.errors)
and if you got csrf-error, you should set form.csrf_token in your template.
You’re not inserting the CSRF field in the HTML form.
<form method=post>
{{ form.csrf_token }}
{{ form.name }}
<input type=submit>
</form>
After adding form.csrf_token
to the template (docs), the form will validate as expected.
Add print(form.errors)
after validating the form to see the errors that were raised. errors
will be empty before validation. In this case, there is an error about missing
@book.route('/book/new_no_csrf', methods=['GET', 'POST'])
def customers_new_no_csrf():
form = BookNewForm()
print(form.errors)
if form.is_submitted():
print("submitted")
if form.validate():
print("valid")
print(form.errors)
if form.validate_on_submit():
flash("Successfully created a new book")
return redirect(url_for('.books_show'))
return render_template('books_new.html', form=form)
{}
submitted
{'csrf_token': [u'CSRF token missing']}
127.0.0.1 - - [29/May/2012 02:01:08] "POST /book/new_no_csrf HTTP/1.1" 200 -
127.0.0.1 - - [29/May/2012 02:01:08] "GET /favicon.ico HTTP/1.1" 404 -
I was clearing the flask session if I wasn’t logged in before every request. This was causing this issue.
@main.before_request
def before_request():
if not current_user.is_authenticated():
# TODO clean sessions may cause CSRF missing issue
session.clear()
print "Session Cleared"
return redirect(url_for('auth.login'))
insert this after the tag in template html file:
{{ form.csrf_token }}
I came across this when trying to render a FormField
being iterated over my FieldList
in my template. I had to embed two hidden_tag elements one for the FieldList
form and one for the FieldForm
form, search the template comments for keyword “HIDDEN TAG”
class ParamRangeForm( FlaskForm ):
minX = FloatField( )
maxX = FloatField( )
class ParamRangesForm( FlaskForm ):
paramRanges = FieldList( FormField( ParamRangeForm ) )
submit = SubmitField( 'Submit' )
def loadParams( self ) :
for paramName in ["p1" , "p2" , "p3", "p4"] :
prf = ParamRangeForm( )
prf.minX = -100.9#float('-inf')
prf.maxX = 100.5#float('-inf')
self.paramRanges.append_entry( prf )
...
<form action="" method="POST" enctype="multipart/form-data">
{{ rangesForm.hidden_tag() }} <!--#### HIDDEN TAG #1 -->
<table>
<!--Print Column Headers-->
<thead>
<tr>
<th class="ColumnHeader">Parameter</td>
<th class="ColumnHeader">Min</td>
<th class="ColumnHeader">Max</td>
</tr>
</thead>
<!--Print Parameter Rows-->
<tbody>
{% for paramRange in rangesForm.paramRanges %}
<tr>
{{ paramRange.hidden_tag() }} <!--#### HIDDEN TAG #2 -->
<td>p{{ loop.index }}</td>
<td>{{ paramRange.minX }}</td>
<td>{{ paramRange.maxX }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{{ rangesForm.submit() }}
</form>
I think the API has changed.Maybe try changing
from flask.ext.wtf import Form
to:
from flask_wtf import Form
I spent a several hours debugging a validation issue with Flask-WTF. The issue like many others was a CSRF validation issue. However, mine was not caused by any of the common issues I have found.
The standard Flask-WTF implementation of CSRF requires two things be delivered to the browser.
One: The hidden CSRF form field e.g.
<input id="csrf_token" name="csrf_token" type="hidden" value="ImYzODdmZTdhYTRlMmNkYWRjYmRlYWFmZjQxMDllZTQ1OWZmYzg3MTki.XKvOPg.gUCkF9j-vg0PrL2PRH-v43GeHu0">
Two: The session cookie HTTP response header e.g.
Set-Cookie: session=eyJjc3JmX3Rva2VuIjoiZjM4N2ZlN2FhNGUyY2RhZGNiZGVhYWZmNDEwOWVlNDU5ZmZjODcxOSJ9.XKvOPg.a3-W62MHvaGVkv2GYCi-dgpLE3Y; HttpOnly; Path=/
If either of these are missing the browser will fail to send the proper CSRF validation. Of course, this in turn causes the form validation to fail.
If the csrf_token
hidden field is present in the form but the session cookie is missing, you will receive the following response when the form is submitted…
Bad Request
The CSRF session token is missing.
In my case the session cookie was missing because of a bug in my code. I needed to serve a custom HTTP header across the entire Flask site. I included it like this…
class LocalFlask(Flask):
def process_response(self, response):
response.headers['my-header'] = 'My Header Value'
return response
app = LocalFlask(__name__)
This however causes anything that rellys on the the Flask.response.headers
method to fail. One of those is Flaks-WTF setting the session cookie HTTP header.
This can be solved by adding the super()
method to the LocalFlask
class so that it inherits methods form the Flask
class.
class LocalFlask(Flask):
def process_response(self, response):
response.headers['my-header'] = 'My Header Value'
#LocalFlask inherits methods from Flask
super(LocalFlask, self).process_response(response)
return response
app = LocalFlask(__name__)
Well I tried all the solutions mentioned
form.hidden_tag()
form.csrf_token
form.csrf
with
app.secret_key=""
app.config["SECRET_KEY"]=""
but form.validate_on_submit()
always returned false.
None of these seem to work for me, so I used the basic method and this method
import request
request.method="POST"
or
form.is_submitted()
These two worked for me
In my case, form.validate_on_submit()
and form.validate()
always return False
if I access the route via Simple Browser: Show command in vscode but return True if I use chrome
I had such problem and solved it by adding {{ form.csfr_token }} in tag :
<form method=post>
{{ form.csrf_token }}
.
.
.
</form>
Be careful about auto suggestion which may complete your expression by this :
{{ form._csrf_token }}
The above expression is similar to the correct one but will not work. By adding the right code (which is mentioned in "form tag" validation_on_submit() method will response the right result.
I’m using Flask-WTF:
Here is my form:
from flask.ext.wtf import Form, TextField
class BookNewForm(Form):
name = TextField('Name')
Here is the controller:
@book.route('/book/new', methods=['GET', 'POST'])
def customers_new():
form = BookNewForm()
if form.is_submitted():
print "submitted"
if form.validate():
print "valid"
if form.validate_on_submit():
flash("Successfully created a new book")
return redirect(url_for('.books_show'))
return render_template('views/books_new.html', form=form)
Now the problem is, if you look at my print statements, it always prints submitted, but it NEVER prints valid and validate_on_submit() is never executed. Why?
you can print errors
print form.errors
or
app.logger.debug(form.errors)
and if you got csrf-error, you should set form.csrf_token in your template.
You’re not inserting the CSRF field in the HTML form.
<form method=post>
{{ form.csrf_token }}
{{ form.name }}
<input type=submit>
</form>
After adding form.csrf_token
to the template (docs), the form will validate as expected.
Add print(form.errors)
after validating the form to see the errors that were raised. errors
will be empty before validation. In this case, there is an error about missing
@book.route('/book/new_no_csrf', methods=['GET', 'POST'])
def customers_new_no_csrf():
form = BookNewForm()
print(form.errors)
if form.is_submitted():
print("submitted")
if form.validate():
print("valid")
print(form.errors)
if form.validate_on_submit():
flash("Successfully created a new book")
return redirect(url_for('.books_show'))
return render_template('books_new.html', form=form)
{}
submitted
{'csrf_token': [u'CSRF token missing']}
127.0.0.1 - - [29/May/2012 02:01:08] "POST /book/new_no_csrf HTTP/1.1" 200 -
127.0.0.1 - - [29/May/2012 02:01:08] "GET /favicon.ico HTTP/1.1" 404 -
I was clearing the flask session if I wasn’t logged in before every request. This was causing this issue.
@main.before_request
def before_request():
if not current_user.is_authenticated():
# TODO clean sessions may cause CSRF missing issue
session.clear()
print "Session Cleared"
return redirect(url_for('auth.login'))
insert this after the tag in template html file:
{{ form.csrf_token }}
I came across this when trying to render a FormField
being iterated over my FieldList
in my template. I had to embed two hidden_tag elements one for the FieldList
form and one for the FieldForm
form, search the template comments for keyword “HIDDEN TAG”
class ParamRangeForm( FlaskForm ):
minX = FloatField( )
maxX = FloatField( )
class ParamRangesForm( FlaskForm ):
paramRanges = FieldList( FormField( ParamRangeForm ) )
submit = SubmitField( 'Submit' )
def loadParams( self ) :
for paramName in ["p1" , "p2" , "p3", "p4"] :
prf = ParamRangeForm( )
prf.minX = -100.9#float('-inf')
prf.maxX = 100.5#float('-inf')
self.paramRanges.append_entry( prf )
...
<form action="" method="POST" enctype="multipart/form-data">
{{ rangesForm.hidden_tag() }} <!--#### HIDDEN TAG #1 -->
<table>
<!--Print Column Headers-->
<thead>
<tr>
<th class="ColumnHeader">Parameter</td>
<th class="ColumnHeader">Min</td>
<th class="ColumnHeader">Max</td>
</tr>
</thead>
<!--Print Parameter Rows-->
<tbody>
{% for paramRange in rangesForm.paramRanges %}
<tr>
{{ paramRange.hidden_tag() }} <!--#### HIDDEN TAG #2 -->
<td>p{{ loop.index }}</td>
<td>{{ paramRange.minX }}</td>
<td>{{ paramRange.maxX }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{{ rangesForm.submit() }}
</form>
I think the API has changed.Maybe try changing
from flask.ext.wtf import Form
to:
from flask_wtf import Form
I spent a several hours debugging a validation issue with Flask-WTF. The issue like many others was a CSRF validation issue. However, mine was not caused by any of the common issues I have found.
The standard Flask-WTF implementation of CSRF requires two things be delivered to the browser.
One: The hidden CSRF form field e.g.
<input id="csrf_token" name="csrf_token" type="hidden" value="ImYzODdmZTdhYTRlMmNkYWRjYmRlYWFmZjQxMDllZTQ1OWZmYzg3MTki.XKvOPg.gUCkF9j-vg0PrL2PRH-v43GeHu0">
Two: The session cookie HTTP response header e.g.
Set-Cookie: session=eyJjc3JmX3Rva2VuIjoiZjM4N2ZlN2FhNGUyY2RhZGNiZGVhYWZmNDEwOWVlNDU5ZmZjODcxOSJ9.XKvOPg.a3-W62MHvaGVkv2GYCi-dgpLE3Y; HttpOnly; Path=/
If either of these are missing the browser will fail to send the proper CSRF validation. Of course, this in turn causes the form validation to fail.
If the csrf_token
hidden field is present in the form but the session cookie is missing, you will receive the following response when the form is submitted…
Bad Request
The CSRF session token is missing.
In my case the session cookie was missing because of a bug in my code. I needed to serve a custom HTTP header across the entire Flask site. I included it like this…
class LocalFlask(Flask):
def process_response(self, response):
response.headers['my-header'] = 'My Header Value'
return response
app = LocalFlask(__name__)
This however causes anything that rellys on the the Flask.response.headers
method to fail. One of those is Flaks-WTF setting the session cookie HTTP header.
This can be solved by adding the super()
method to the LocalFlask
class so that it inherits methods form the Flask
class.
class LocalFlask(Flask):
def process_response(self, response):
response.headers['my-header'] = 'My Header Value'
#LocalFlask inherits methods from Flask
super(LocalFlask, self).process_response(response)
return response
app = LocalFlask(__name__)
Well I tried all the solutions mentioned
form.hidden_tag()
form.csrf_token
form.csrf
with
app.secret_key=""
app.config["SECRET_KEY"]=""
but form.validate_on_submit()
always returned false.
None of these seem to work for me, so I used the basic method and this method
import request
request.method="POST"
or
form.is_submitted()
These two worked for me
In my case, form.validate_on_submit()
and form.validate()
always return False
if I access the route via Simple Browser: Show command in vscode but return True if I use chrome
I had such problem and solved it by adding {{ form.csfr_token }} in tag :
<form method=post>
{{ form.csrf_token }}
.
.
.
</form>
Be careful about auto suggestion which may complete your expression by this :
{{ form._csrf_token }}
The above expression is similar to the correct one but will not work. By adding the right code (which is mentioned in "form tag" validation_on_submit() method will response the right result.