Determine which WTForms button was pressed in a Flask view - python

I have a page with multiple links to redirect the user to different pages. I thought using a form would be nicer, so I defined a WTForms Form with multiple SubmitFields. How do I determine which button was clicked and redirect based on that?
class MainForm(Form):
user_stats = SubmitField('User Stats')
room_stats = SubmitField('Room Stats')
#main.route('/')
#login_required
def index():
form = MainForm()
return render_template('index.html', form=form)
<form action="#" method="post">
{{ wtf.quick_form(form) }}
</form>

You added two buttons to the form, so check which of the fields' data is True.
from flask import Flask, render_template, redirect, url_for
from flask_wtf import Form
from wtforms import SubmitField
app = Flask(__name__)
app.secret_key = 'davidism'
class StatsForm(Form):
user_stats = SubmitField()
room_stats = SubmitField()
#app.route('/stats', methods=['GET', 'POST'])
def stats():
form = StatsForm()
if form.validate_on_submit():
if form.user_stats.data:
return redirect(url_for('user_stats'))
elif form.room_stats.data:
return redirect(url_for('room_stats'))
return render_template('stats.html', form=form)
app.run(debug=True)
<form method="post">
{{ form.hidden_tag() }}
{{ form.user_stats }}
{{ form.room_stats }}
</form>

Related

Prevent duplicate form submissions while awaiting a response Flask Python

I am running a large request via API and massaging some data before the user sees it. I am looking to prevent the user from clicking the download button while all this information is processed. What would be the best way to accomplish this through a Flask Form?
Here is my HTML:
<form method="POST">
<button type="submit" name="download" value="download" class="button is-primary is-light">Download</button>
{% if error_statement %}
<article class="message is-danger">
<div class="message-body">
{{ error_statement }}
</div>
</article>
{% endif %}
</form>
Here is my Flask Form:
from datetime import date
import pandas as pd
from flask_wtf import FlaskForm
from wtforms import *
from flask import (
Flask,
g,
redirect,
render_template,
request,
session,
url_for,
flash,
Response
)
app = Flask(__name__)
app.secret_key = 'secret'
#app.route('/home', methods=["POST", "GET"])
def home():
class MyForm(FlaskForm):
submit = SubmitField('Download')
if request.method == 'POST':
form = MyForm()
if request.form['download'] == 'download':
#At this point I have code where I call a bunch of APIs and convert data to a CSV file
#This process takes anywhere between 1-3 minutes to complete
if not final_df.empty:
today = date.today()
return Response(final_df.to_csv(index=False, header=True), mimetype="text/csv", headers={"Content-disposition": "attachment; filename=export" + today.strftime("%Y/%m/%d") + ".csv"})
else:
error_statement = 'Something Went Wrong Please Try Again'
return render_template("login.html", error_statement=error_statement)
return render_template('home.html', form=form)
return redirect(url_for('login'))
Can anyone provide guidance on how to prevent the user from clicking the download button while my data is processed?

How to redirect while keeping form data using Flask and WTForms?

Let's have a page with a registration form on it. It's in section #registration. If user submits invalid data, the page should return him back to the #registration section and display to which fields were submitted invalid values.
I tried to render template and make response and redirect to it but I'm getting a TypeError:
File ".../app/routes.py", line 28, in index
return redirect(url_for('.index', form=form, _anchor='registration'), 302, response)
File ".../python3.7/site-packages/werkzeug/utils.py", line 507, in redirect
mimetype="text/html",
TypeError: __call__() got an unexpected keyword argument 'mimetype'
The function looks like this:
#app.route('/', methods=['GET', 'POST'])
def index():
form = RegisterForm()
if form.validate_on_submit():
# everithing OK
return redirect(url_for('.index', _anchor='registration'))
# If form was submitted but contained invalid information
if form.is_submitted():
response = make_response(render_template('index.html', form=form))
return redirect(url_for('.index', _anchor='registration'), 302, response)
return render_template('index.html', form=form)
You can't send content with a redirect response, you can only say "go to this url". Also flask accepts a Response class, not an instance as a parameter for redirect.
To solve your problem, you need to use a session (or simply flashing) to preserve state across requests.
Here's a prototype:
from flask import Flask, render_template_string, request, session, redirect
from werkzeug import MultiDict
from flask_wtf import FlaskForm
from wtforms import StringField, IntegerField
from wtforms.validators import AnyOf
app = Flask(__name__)
app.secret_key = 'secret'
class MyForm(FlaskForm):
name = StringField('name', validators=[AnyOf(['secretname'])])
#app.route('/', methods=['POST', 'GET'])
def form_page():
form = MyForm()
html = '''
{% for error in form.name.errors %} <span>{{ error }}</span> {% endfor %}
<form method="POST" action="/">
{{ form.csrf_token }}
{{ form.name.label }} {{ form.name(size=20) }}
<input type="submit" value="Go">
</form>
'''
if request.method == 'GET':
formdata = session.get('formdata', None)
if formdata:
form = MyForm(MultiDict(formdata))
form.validate()
session.pop('formdata')
return render_template_string(html, form=form)
if form.validate_on_submit():
# use the form somehow
# ...
return redirect('/#registered')
if form.is_submitted() and not form.validate():
session['formdata'] = request.form
return redirect('/#invalid')
if __name__ == "__main__":
app.run()
When you run the server, you get:
After you submit an invalid form, you get redirected to /#invalid and form is populated as you'd expect:
http://flask.pocoo.org/docs/1.0/quickstart/#sessions
http://flask.pocoo.org/docs/1.0/patterns/flashing/#message-flashing-pattern
http://flask.pocoo.org/docs/1.0/api/#flask.redirect

Flask view shows 400 error instead of template with form

I'm trying to display a page with a form, then add a Player to the database when the form is submitted. However, I can't view the form because the browser always shows a 400 Bad Request error. Other posts indicate that this could be because the name of the form input doesn't match the key I get from request.form, but all my keys match. Why do I get this error?
<form method="post">
{{ form.hidden_tag() }}
<input name="name">
<input name="available">
<input type="submit">
</form>
#app.route('/addplayer', methods=['GET', 'POST'])
def addplayer():
connect('basketball_contracts', host='localhost', port=27017)
n = request.form['name']
a = request.form['available']
post= Post(
name=n,
available=a
)
post.tags = ['test']
post.save()
return render_template('addplayer.html', form=form)
Your view accepts GET and POST requests. request.form is only filled out on POST. If you try to access a key that doesn't exist, it raises a 400 error. No keys will exist when you GET the page initially.
The common pattern for this is to guard code that requires request.form in an if request.method == 'POST' block. Return a redirect after handling the POST request, otherwise return the rendered template.
from flask import url_for, redirect, render_template
#app.route('/addplayer', methods=['GET', 'POST'])
def addplayer():
if request.method == 'POST':
Post(
name=request.form['name'],
available=request.form['available']
).save()
return redirect(url_for('index'))
return render_template('addplayer.html')
Since you appear to be using Flask-WTF, you can use the form's validate_on_submit method instead of checking method. In that case, you can also access the data through the form instance, and use the form to render the inputs for you.
from flask import url_for, redirect, render_template
#app.route('/addplayer', methods=['GET', 'POST'])
def addplayer():
form = AddPlayerForm()
if form.validate_on_submit():
Post(
name=form.name.data,
available=form.available.data
).save()
return redirect(url_for('index'))
return render_template('addplayer.html', form=form)
<form method=post>
{{ form.hidden_tag() }}
{{ form.name.label}} {{ form.name }}<br>
{{ form.available.label }} {{ form.available }}<br>
<input type=submit value="Add Player">
</form>

Validate that a WTForms BooleanField is checked

I am creating a form using Flask-WTForms.
I am using a BooleanField so that a user can indicate they agree terms.
I cannot validate the BooleanField upon submission to ensure that it has been checked. I have tried using Required(), DataRequired() and custom validation but in each case I have not received a validation error.
Here are the nuts and bolts of the application:
from flask import Flask, render_template, session, redirect, url_for, flash
from flask_wtf import Form
from wtforms import BooleanField, SubmitField
from wtforms.validators import Required, DataRequired
from flask_bootstrap import Bootstrap
app = Flask(__name__)
app.config['SECRET_KEY'] = 'impossibletoknow'
bootstrap = Bootstrap(app)
class AgreeForm(Form):
agreement = BooleanField('I agree.', validators=[DataRequired()])
submit = SubmitField('Submit')
#app.route('/', methods=['GET', 'POST'])
def index():
form = AgreeForm()
if form.validate_on_submit():
agreement = form.agreement.data
if agreement is True:
flash('You agreed!')
return redirect(url_for('index', form=form))
form.agreement.data = None
agreement = False
return render_template('index.html', form=form)
if __name__ == '__main__':
app.run(debug=True)
And here is the index.html template...
{% import "bootstrap/wtf.html" as wtf %}
{% block content %}
<div class="container">
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
{% endfor %}
{{ wtf.quick_form(form) }}
</div>
{% endblock %}
Any suggestions would be gratefully received.
Works for me— you do need to use DataRequired() (Required is being deprecated):
from flask import Flask, render_template
from flask_wtf import Form
from wtforms import BooleanField
from wtforms.validators import DataRequired
app = Flask(__name__)
app.secret_key = 'STACKOVERFLOW'
class ExampleForm(Form):
checkbox = BooleanField('Agree?', validators=[DataRequired(), ])
#app.route('/', methods=['post', 'get'])
def home():
form = ExampleForm()
if form.validate_on_submit():
return str(form.checkbox.data)
else:
return render_template('example.html', form=form)
if __name__ == '__main__':
app.run(debug=True, port=5060)
Template:
<form method="post">
{{ form.hidden_tag() }}
{{ form.checkbox() }}
<button type="submit">Go!</button>
</form>
<h1>Form Errors</h1>
{{ form.errors }}
You do not have to include DataRequired() in your form, because it does not make sense, being a boolean. You must take of the incoming form data in the post method by saying if true.

'forms.ContactForm object' has no attribute 'hidden_tag'

I am trying to create a contact form using flask but keep getting this error when the page is rendered.
'forms.ContactForm object' has no attribute 'hidden_tag'
Here are my files:
contact.html
{% extends "layout.html" %}
{% block content %}
<h2>Contact</h2>
<form action="{{ url_for('contact') }}" method=post>
{{ form.hidden_tag() }}
{{ form.name.label }}
{{ form.name }}
{{ form.email.label }}
{{ form.email }}
{{ form.subject.label }}
{{ form.subject }}
{{ form.message.label }}
{{ form.message }}
{{ form.submit }}
</form>
{% endblock %}
forms.py
from flask.ext.wtf import Form
from wtforms import Form, TextField, TextAreaField, SubmitField, validators
class ContactForm(Form):
name = TextField("Name", [validators.Required()])
email = TextField("Email",[validators.Required(), validators.email()])
subject = TextField("Subject", [validators.Required()])
message = TextAreaField("Message", [validators.Required()])
submit = SubmitField("Send")
routes.py
from flask import Flask, render_template, request
from forms import ContactForm
app = Flask(__name__)
app.secret_key = 'development key'
#app.route('/')
def home():
return render_template('home.html')
#app.route('/about')
def about():
return render_template('about.html')
#app.route('/contact', methods=['GET', 'POST'])
def contact():
form = ContactForm()
if request.method == 'POST':
return 'Form posted.'
elif request.method == 'GET':
return render_template('contact.html', form=form)
if __name__ == '__main__':
app.run(debug=True)
All the other page templates are working perfectly fine. Any advice would be awesome! Thanks for the help!
I just fixed this problem as well.
Your problem is that you imported Form twice, rendering your flask-wtf Form import useless.
from flask_wtf import Form
from wtforms import Form, TextField, TextAreaField, SubmitField, validators
# ^^^ Remove
Only the flask-wtf extension has the special Form class which can handle CSRF automatically / other stuff.
I tried to fix this, too.
After removing brackets "()" appended after hidden_tag, it works.
{{ form.hidden_tag }}
It took me some time to fix this.
First import Form, fields, bootstrap as:
from flask_wtf import Form
from wtforms import StringField #etc
from flask_bootstrap import Bootstrap
Config secret key and bootstrap
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret key'
Bootstrap(app)
create your form as your used to:
class ContactForm(Form):
name = TextField("Name", [validators.Required()])
email = TextField("Email",[validators.Required(), validators.email()])
subject = TextField("Subject", [validators.Required()])
message = TextAreaField("Message", [validators.Required()])
submit = SubmitField("Send")
Nothing special in the routing aswell, just return it normaly.
In the html:
{% extends "bootstrap/base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% if form %}
{{ wtf.quick_form(form, ) }}
{% endif %}
And that's it. Hope you find some (or all) of it useful.
Update for #Yuji'Tomita'Tomita answer :
You should import FlaskForm instead of Form
from flask_wtf import FlaskForm
from wtforms import TextField, TextAreaField, SubmitField, validators
The error that you're seeing is telling you that forms.ContactForm has no method called "hidden_tag". You're referencing that method on the 6th line of contact.html like this:
{{ form.hidden_tag() }}
According to the flask documentation, this is the correct way to implement CSRF protection.
I would start by removing the line that references "form.hidden_tag()", then see if your form works. Then go back and implement CSRF protection per those instructions from the documentation.

Categories

Resources