Flask WTF always fails - python

I'm trying to build a user input form using Flask WTF.
The problem is that when I debug my code, it always fails at
if not form.validate_on_submit():
even if I don't have anything to fail, it just go inside that if statement and my submitted data stays in the form rather than going back to index page.
my server side code
#post_user_blueprint.route('/post_user', methods=['GET', 'POST'])
def post_user():
form = InputForm(request.form)
if not form.validate_on_submit():
return render_template('wtf_input.html', form=form)
return redirect(url_for('.index'))
html (using helper class to macro the input field and error)
<form action="/post_user" method='post' name='post_user' enctype=multipart/form-data >
{% from "_formhelpers.html" import render_field %}
{{ form.csrf }}
{{render_field(form.individual_first_name)}}
{{render_field(form.individual_last_name)}}
<input type='submit' value='SUBMIT'/>
</form>
WTF class
class InputForm(FlaskForm):
individual_first_name = StringField("First Name",
[validators.InputRequired("Please enter first name")])
individual_last_name = StringField("Patient Last Name",
[validators.InputRequired("Please enter last name")])
submit = SubmitField("Send")
Thanks in advance

Related

Custom validation for WTForm fileds in Flask

I have used the built-in validators, but none of them is printing a message on the page. Also, I want to create a custom validator to check duplicate username. I have written the function, as I am a beginner, I don't know how to use it. Pls resolve the problem.
from flask import Flask, app, render_template, request, url_for
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField
from wtforms.validators import InputRequired, EqualTo, ValidationError
app = Flask(__name__)
app.config['SECRET_KEY'] = "PQrs12t46uvvrty567"
class MyForm(FlaskForm):
username = StringField('Username', validators=[InputRequired(message="This field is required.")])
password=PasswordField('Password', validators=[InputRequired(message=("enter the password"))])
confirm_password=PasswordField('Confirm Password', validators=[EqualTo('password')])
submit = SubmitField('Register')
def isDuplicate():
appdatabase={"Alis":"Smith","Mike":"Brown","Paul":"Miller"}
form = MyForm()
for user in appdatabase:
if form.username == appdatabase[user]:
raise ValidationError("Username already exists! Please choose another one.")
#app.route('/')
def base():
form = MyForm()
return render_template('customvalidator.html', form = form)
#app.route('/submitform', methods=["GET","POST"])
def submitform():
form = MyForm()
if form.validate_on_submit():
return 'Form accepted successfully.'
else:
return 'Incorrect form data'
if __name__=="__main__":
app.run(debug=True)
HTML file
<!DOCTYPE html>
<html>
<head><title>My website</title></head>
<body>
<h1>Registration form</h1>
<form action="{{ url_for('submitform') }}" method="post">
{{ form.csrf_token }}
{{ form.username.label }}
{{ form.username }}
<ul>
{% for error in form.username.errors %}
<li style="color: red;">{{ error }} </li>
{% endfor %}
</ul>
<br>
{{ form.password.label }}
{{ form.password }} <br><br>
{{ form.confirm_password.label }}
{{ form.confirm_password }} <br><br>
{{ form.submit}}
</form>
</body>
</html>
Try changing the isDuplicate function to this:
def isDuplicate(form, field):
appdatabase={"Alis":"Smith","Mike":"Brown","Paul":"Miller"}
form = MyForm()
for user in appdatabase:
if form.username.data == appdatabase[user]:
raise ValidationError("Username already exists! Please choose another one.")
Notice the added form and field parameters to the function to allow its use as a custom validator. form.username was also changed to form.username.data to access the data of the username field. (This whole function will need to be defined before the form.)
You will then need to add this custom validator to your list of validators for the username field in your form like so:
username = StringField('Username', validators=[InputRequired(message="This field is required."),isDuplicate])
With these changes, the custom validation should work, but we still haven't solved the issue of the custom error messages. To do this, add a novalidate attribute to your HTML form tag. Adding this will disable the default validation on forms and allow you to display custom messages.
With that in place, I believe the messages still won't work because of how you are handling the form submission. When the form does not validate, you will want to display the same form template instead of showing them whether it submitted or not.
My personal opinion is that merging your two form routes will be the simplest option. Have a route define the form, check for when it gets validated or not, and then render the form template. This will enable you to keep everything together and not render a different template when you only want to display the same template with a few added error messages.
Credits:
Custom validators (check the last point)
SO question on displaying validation messages
-which led me to this one on actually how to disable the default messages.

Issue with csrf_token while using Flask and WTForms

I'm trying to set up a basic "Contact" form for my website, which will basically take in values and then put them into a CSV file. The issue I'm having is that the entries cannot be validated cause they're missing a csrf_token?
Here's the relevant code from my app.py:
#app.route('/contact_end', methods=['POST'])
def handle_contact():
form = ContactForm()
print(form.name.data)
if form.validate_on_submit():
print("yup")
with open('data/messages.csv', 'a') as f:
print("oh shit")
writer = csv.writer(f)
writer.writerow([form.name.data, form.email.data, form.message.data])
print("waddup")
return redirect(url_for('contact_handler.html'), name=form.name.data)
print(form.errors)
return render_template('contact.html', form=form)
It skips over the if statement as it never ends out printing the "yup", and instead it prints out the error:
{'csrf_token': ['The CSRF token is missing.']}
The template that this connects to is:
{% extends "base_template.html" %}
{% block title %}Contact us {% endblock %}
{% block content %}
<p>Feel free to use the contact form below to send us any questions you might have.</p></br>
<form action="/contact_end" method="post">
{{ form.csrf_token }}
<label>Your Name <input type="text" name="name"/></label></br>
<label>Your Email <input type="text" name="email"/></label></br>
<label>Your Name <textarea name="message"></textarea></label></br>
<button type="submit">Send</button>
<button type="reset">Clear</button>
</form>
{% endblock %}
I've tried messing with form.csrf_token and .hidden_tags(), but with no success.
As well, this is the initial part of app.py that brings you to the page in the first place, the one above is the endpoint for the form:
#app.route('/contact')
def contact():
return render_template('contact.html', form=form)
Finally, here's my ContactForm class:
class ContactForm(FlaskForm):
print("yep")
name = StringField('Name', validators=[InputRequired()])
email = EmailField('Email', validators=[InputRequired(), Email()])
message = TextAreaField('Message', validators=[InputRequired()])
I've made sure to set my secret key, as well. Anyone have any idea why this isn't working? Many thanks.
You still need to create the form instance in your contact() function:
#app.route('/contact')
def contact():
form = ContactForm()
return render_template('contact.html', form=form)

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>

Flask app submit target="_blank" form only after wtforms validation

In my Flask app I have a form generated with wtforms and jinja templates. If validation passes I want to redirect in a new tab, else I want to stay on the same page and display the errors. However if I set target="_blank" in my form, it opens a new tab without validation passing and shows the errors there. Removing target="_blank" will not open a new tab. Is there a way of achieving this without rewriting the whole validation in js? Thanks!
Code:
from wtforms import Form, TextAreaField, StringField, validators
class SubmitForm(Form):
field1 = StringField(u'field1', [validators.DataRequired()])
field2 = TextAreaField(u'field2', [validators.DataRequired()])
#app.route('/', methods=['POST'])
def sub():
form = SubmitForm(request.form)
if request.method == 'POST' and form.validate():
# great success
return redirect('/my_redirect_uri')
return render_template('index.html', form=form)
#app.route('/')
def layout():
return render_template('index.html', form=SubmitForm())
index.html:
{% from "_formhelpers.html" import render_field %}
<form method=post action="/" target="_blank">
<dl>
{{ render_field(form.field1) }}
{{ render_field(form.field2) }}
</dl>
<p><input type=submit value=Submit>
</form>
_formhelpers.html(not that relevant but for the sake of completness):
{% macro render_field(field) %}
<dt>{{ field.label }}
<dd>{{ field(**kwargs)|safe }}
{% if field.errors %}
<ul class=errors>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</dd>
{% endmacro %}
Main issue
You must pass the form object as well as the variables representing your form fields namely field1 and field2.
Details
What is being stated above means that you need to change:
return redirect('/my_redirect_uri')
to
return redirect('/my_redirect_uri', form=form, field1=field1, field2=field2)
This also means that you have to adjustments to your view method:
#app.route('/', methods=['POST'])
def sub():
form = SubmitForm(request.form)
if request.method == 'POST' and form.validate():
# great success
field1 = form.field1.data
field2 = form.field2.data
return redirect('/my_redirect_uri', form=form, field1=field1, field2=field2)
return render_template('index.html', form=form)
Now there are some improvements to your program:
Code less principle:
Replace if request.method == 'POST' and form.validate(): by if form.validate_on_submit(): (you save one instruction)
Use url_for():
For the sake of scalability because in future versions and reedits of your program, any changes made to route names will be automatically available when using url_for() as it generates URLs using the URL map. You may use it in return redirect('/my_redirect_uri') (you may check the link I provided for further information)
Use sessions:
Sessions will make your application "able to remember" the form data being sent. In your example, you can use a session this way:
session['field1'] = form.field1.data
session['field2'] = form.field2.data
This means, for example, the above line:
return redirect('/my_redirect_uri', form=form, field1=field1, field2=field2)
must be changed to:
return redirect('my_redirect_uri', form=form, session.get('field1'), session.get('field2'))
Note that if you want to implement sessions, you will need to set a secret key because they are stored on the client side, so they need to be protected (cryptographically in Flask's philosophy). This mean you must configure your application this way:
app.config['SECRET_KEY'] = 'some secret phrase of your own'
Actually, the problem is not related to the browser tab you opened. The whole issue emanates from the redirect() statement.
More details on why it is good to use sessions? Check the next last section:
A word about redirect:
Your POST request is handled by redirect, consequently you loose access to the form data when the POST request ends.
The redirect statement is simply a response which, when received, the browser issues a GET request. This mechanism is there mainly for the following (and similar) situation:
If you try to refresh the same browser window on which you submitted the form data, the browser will prompt you a pop up window to confirm that you want to send the data (again) because the browser remembers, by design, the last request it performed. Of course, this is nasty for the user of your application. There we need sessions. This is also helpful for the browser tab to which you redirect.

How to unit test a form submission when multiple forms on a route?

I want to add unit tests to my flask app that tests form behavior on valid and invalid logins + signups. Currently, I have the signup form and a login form hosted on one page and route, and am using a hidden input field to identify which of the two forms is submitted / determine next actions.
My question is - how do I write a unit test that targets a specific form on a page? All the examples I've seen so far post data to a specific route, which is currently what I am doing. But that is failing because I need an added way to say "and we're submitting x form".
So is there a way to add "and we're submitting x form" in the post request?
**
edited to add, here are the different ways I've tried to pass the hidden form data in the post data dict, with no success.
data = dict(username="test#gmail.com", password="test", login_form)
data = dict(username="test#gmail.com", password="test", "login_form")
data = dict(username="test#gmail.com", password="test", login_form=True)
login unit test:
from app import app
import unittest
class FlaskTestCase(unittest.TestCase):
#ensure that login works with correct credentials
def test_correct_login(self):
tester = app.test_client(self)
response = tester.post(
'/login',
data = dict(username="test#gmail.com", password="test"),
follow_redirects=True
)
self.assertIn(b'you are logged in', response.data)
login route in views.py:
#app.route('/login', methods=['POST', 'GET'])
def login():
login_form = LoginForm()
signup_form = SignupForm()
error_login = ''
error_signup = ''
#login form
if 'login_form' in request.form and login_form.validate():
# do login form stuff
#signup form
if 'signup_form' in request.form and signup_form.validate():
# do signup form stuff
return render_template('login.html', login_form=login_form, signup_form=signup_form, error=error)
login.html:
<div class="login-form form-400">
<h3>Log In To Your Account</h3>
<form action="" method="post">
<input type="hidden" name="login_form">
{% if error_login != '' %}
<label class="error">
{{ error_login }}
</label>
{% endif %}
{% from "_formhelper.html" import render_field %}
{{ login_form.hidden_tag() }}
{{ render_field(login_form.email, placeholder="Your Email", class="form-item__full", type="email") }}
{{ render_field(login_form.password, placeholder="Your Password", class="form-item__full") }}
<input type="submit" value="Login" class="button button-blue">
</form>
</div>
<p class="login-divider">or</p>
<div class="signup-form form-400">
<h3>Create a New Account</h3>
<form action="" method="post">
<input type="hidden" name="signup_form">
{% if error_signup != '' %}
<label class="error">
{{ error_signup | safe}}
</label>
{% endif %}
{% from "_formhelper.html" import render_field %}
{{ signup_form.hidden_tag() }}
{{ render_field(signup_form.username, placeholder="Pick a Username", class="form-item__full") }}
{{ render_field(signup_form.email, placeholder="Your Email", class="form-item__full", type="email") }}
{{ render_field(signup_form.password, placeholder="Create a Password", class="form-item__full") }}
<input type="submit" value="Sign Up" class="button button-green">
</form>
</div>
Ok I figured it out. To pass the login_form info, I had to end up passing an empty string on the login_form like this:
def test_correct_login(self):
tester = app.test_client(self)
response = tester.post(
'/login',
data = dict(username="test#gmail.com", password="test", login_form=""),
follow_redirects=True
)
self.assertIn(b'you are logged in', response.data)
I did this by throwing a print request.form in my views.py for this route and then saw the empty string.
It was still failing, but because the login_form.validate() was failing because of the csrf token added by the WTForms module. In the end, this discussion had the answer.
Flask-WTF / WTForms with Unittest fails validation, but works without Unittest
Thanks #drdrez for your suggestions!
Update:
Thanks for updating your question with what you've already tried! I have a few other ideas about what's causing the issue.
Let's continue to look at the HTML and try to understand the technologies your program is built on top of. In the server side login.html file, notice these lines:
{% from "_formhelper.html" import render_field %}
{{ login_form.hidden_tag() }}
{{ render_field(login_form.email, placeholder="Your Email", class="form-item__full", type="email") }}
{{ render_field(login_form.password, placeholder="Your Password", class="form-item__full") }}
It isn't HTML, and is probably being processed on the server side to produce HTML and serve to the client. The line that contains login_form.hidden_tag() looks interesting, so I would recommend loading this page in your browser and inspecting the HTML served to the client. Unfortunately, I haven't used Flask before, so I can't give any more direct help.
However, my advice is to continue digging into how Flask and the HTML Form works. The nice thing about Python is you have access to libraries' source code, which allows you to figure out how they work so you can learn how to use them and fix bugs in your application that uses them.
Sorry I can't give you more direct help, good luck!
Let's look at login.html. When you submit a form, how does the login route in views.py know which form was submitted? If you know HTML Forms, <input> elements nested in a form are used to, in this case, post data to your server/application.
Back to login.html, notice these two lines:
...
<h3>Log In To Your Account</h3>
<input type="hidden" name="login_form">
...
<h3>Create a New Account</h3>
<form action="" method="post">
<input type="hidden" name="signup_form">
...
Those are <input> elements, with a type of "hidden", so they won't display, with names of "login_form" and "signup_form", which are included in the data that is submitted by the form.
Now in the login route in views.py, you'll notice there two lines:
#login form
if 'login_form' in request.form and login_form.validate():
# do login form stuff
#signup form
if 'signup_form' in request.form and signup_form.validate():
# do signup form stuff
Those are testing to see if the phrase "login_form" or "signup_form" are in present in the list request.form. Back to your unit test now:
response = tester.post(
'/login',
data = dict(username="test#gmail.com", password="test"),
follow_redirects=True
)
Notice the data you are passing in the dict, this is mimicking the form data, so you should probably include either "login_form" or "signup_form" to mimic the behavior of the HTML form correctly.
If you're unfamiliar with HTML Forms and HTTP Post, I would suggest searching for some tutorials, or just reading documentation on MDN or elsewhere. When building software on top of a technology (like HTTP and HTML), it can be helpful to understand how those technologies work when you run into bugs in your own software.
Hope this helps, let me know if I can clarify anything!
You might be experiencing a problem because you have not flagged the request as being of the form application content type. I note you are trying to access request.form, which requires that the data package is parsed in a certain way. You could try to do something like the following:
response = tester.post(
'/login',
data = dict(username="test#gmail.com", password="test"),
follow_redirects=True,
headers = {"Content-Type":"application/x-www-form-urlencoded"}
)

Categories

Resources