I just started coding with Flask and I want to set up CSRF on a small form in my application. I am following this https://wtforms.readthedocs.io/en/stable/csrf.html for session based implementation. I looked around the web for a while for a solution to a similar problem but I had no success, apologies if this is a duplicate question.
Problem with this code:
When I run it in the virtual environment I get AttributeError: 'Request' object has no attribute 'POST'
-
Goal: implement csrf on wtform instance
Environment: wtf version 2.02, flask 0.10, venv with python 2.7
fci_form.py
from flask import session, request
from flask.ext.wtf import Form
from wtforms import TextField, validators, SubmitField
from wtforms.validators import Required, Length
from wtforms.csrf.session import SessionCSRF
from datetime import timedelta
import config # my config file
# create super class
class MyForm(Form):
class Meta:
csrf = True
csrf_class = SessionCSRF
csrf_secret = config.secret_key
csrf_time_limit = timedelta(minutes=20)
@property
def csrf_context(self):
return request.session
# create a class for the form
class postcodeInput(MyForm):
postcode = TextField('postcode',[validators.Required(message=(u"Where is your postcode?")),validators.Length(min=2, max=10)])
submit = SubmitField('Submit')
views.py
from flask import Flask, render_template, request, __version__, url_for, session, abort, flash, redirect
# importing the class called postcode_input
from fci_form import postcodeInput
import config
import fciUtils
#pdb.set_trace()
app = Flask(__name__)
app.debug = True
# Views of the app
@app.route('/')
def index():
return render_template('home.html')
@app.route('/fci', methods=['GET', 'POST'])
def fci_form():
error = None
form = postcodeInput(request.POST, meta={'csrf_context': request.session})
if form.validate_on_submit():
# handle user input
postcode = request.form['postcode']
# calculate fci
result = fciUtils.fciReturn(postcode)
return render_template('fci_form.html',form = form, result = result)
elif request.method == 'GET':
return render_template('fci_form.html', form = form)
else:
error = 'Enter a valid postcode'
return render_template('fci_form.html', form=form, error=error)
if __name__ == '__main__':
app.secret_key = config.secret_key
app.run(threaded=True)
The template is fci_form.html in the/templates folder
{% extends 'layout.html' %}
{% block form %}
<section>
<header><h1>...</h1><br></header>
<form action="{{ url_for('fci_form')}}" method='POST'>
<p>Enter your London postcode:</p>
<section>
{% if error %}
<p class="error"><strong>Error: </strong>{{error}}</p>
{% endif %}
{{form.csrf_token}}
{{form.postcode}}
{{form.submit(class_="pure-button")}}
</section>
</form>
</section>
{% endblock %}
{% block result %}
<section>
<h4>{{result}}</h4>
</section>
{% endblock %}
What am I missing here?
From the github README for the WTForms project:
WTForms is a flexible forms validation and rendering library for Python web development. It is framework agnostic and can work with whatever web framework and template engine you choose.
..emphasis mine. Framework agnostic means that this isn't just a library for Flask and that examples such as these (from https://wtforms.readthedocs.io/en/stable/csrf.html#using-csrf):
def view():
form = UserForm(request.POST)
if request.POST and form.validate():
pass # Form is valid and CSRF succeeded
return render('user.html', form=form)
... aren't necessarily a working pattern in any web framework, rather just a general illustration to exhibit how the library works.
That example transposed to a Flask specific example might look something like this:
@app.route('/submit', methods=("GET", "POST"))
def submit():
form = UserForm(request.form)
if request.method == "POST" and form.validate():
pass # Form is valid and CSRF succeeded
return render_template('submit.html', form=form)
The README goes on to say:
There are various community libraries that provide closer integration with popular frameworks.
One such example is Flask-WTF, and the 'hello world' for their library around WTForms looks like this:
@app.route('/submit', methods=('GET', 'POST'))
def submit():
form = MyForm()
if form.validate_on_submit():
return redirect('/success')
return render_template('submit.html', form=form)
Notice that request.form
doesn't have to be passed to the MyForm
constructor as it did in the vanilla WTForms example (UserForm(request.form)
), and that there is a method available on the forms called validate_on_submit()
which both tests that the request is a POST
request and also that the submitted form content passes validation.
Along with easier handling of passing POST
data through to forms, and validation, Flask-WTF
also simplifies CSRF token management, which can be read about here.