I'm using flask wtforms with input validation. Everything's working fine, except with validation failure my ValidationError message is not getting displayed to the user...
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField, ValidationError # URLField, EmailField
from wtforms.validators import DataRequired, Email, EqualTo
class SignupForm(FlaskForm):
name = StringField("Name:", validators=[DataRequired()])
email = StringField("Email:", validators=[DataRequired(), Email()])
password = PasswordField("Password:", validators=[DataRequired(), EqualTo('pass_confirm', message='Passwords must match')])
pass_confirm = PasswordField("Confirm:", validators=[DataRequired()])
submit = SubmitField("Create Account")
def validate_email(self, field):
if User.query.filter_by(email=field.data).first():
raise ValidationError('Your email has already been registered.')
def validate_name(self, field):
if User.query.filter_by(name=field.data).first():
raise ValidationError('This username is taken.')
and in my view:
#users.route('/signup', methods=['GET','POST'])
def signup():
form = SignupForm()
if form.validate_on_submit():
url = form.name.data # todo: generate unique url
user = User(name=form.name.data,
url=url,
email = form.email.data,
password = form.password.data)
db.session.add(user)
db.session.commit()
return redirect(url_for('users.login'))
return render_template('signup.html', form=form)
and this is the template:
<div class="jumbotron">
<h1>Sign up Page</h1>
<p>Please fill out the form</p>
<form method="POST">
{{ form.hidden_tag() }}
<div class="form-group">
{{ form.name.label(class='form-group') }}
{{ form.name(class='form-control') }}
</div>
<div class="form-group">
{{ form.email.label(class='form-group') }}
{{ form.email(class='form-control') }}
</div>
<div class="form-group">
{{ form.password.label(class='form-group') }}
{{ form.password(class='form-control') }}
</div>
<div class="form-group">
{{ form.pass_confirm.label(class='form-group') }}
{{ form.pass_confirm(class='form-control') }}
</div>
{{ form.submit(class='btn btn-primary') }}
</form>
</div>
Upon submitting an invalid form (either invalid email or name) the form simply returns blank, without indicating what the user did wrong. How can I display those validationerrors to the user? Much appreciated!
form must get some data
#users.route('/signup', methods=['GET','POST'])
def signup():
form = SignupForm(request.form)
or
#users.route('/signup', methods=['GET','POST'])
def signup():
form = SignupForm(request.json)
I believe you are importing ValidationError incorrectly
Try this importing it from wtforms.validators like this->
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField # URLField, EmailField
from wtforms.validators import DataRequired, Email, EqualTo, ValidationError
In addition to this, you can add flash messages to your screen from your routes code such as this.
from flask import flash
if form.validate_on_submit():
flash(f'Info was saved to the database for user {form.name.data}', success)
else:
flash('Failed to save to database', 'danger')
Related
I have been trying to incorporate wtforms validators inside 2 forms I have on my index.html(this is a Uni assignment). Everything worked well before I did anything, but security was very bad. After I have inserted some wtform validators, everything works OK. But usability is quite bad. If I get an error whilst filling out one of the forms, I get error messages not only in the form I was writing stuff in, but also in the other separate form on index.html.
I suspect it has something with the way the assignment is set up. Like I said, I have 2 forms on my index.html. In a separate python file, where all the forms are configured, there is the following setup (where LoginForm and RegisterForm are the two forms on index.html):
class LoginForm(FlaskForm):
#more code here
class RegisterForm(FlaskForm):
#more code here
class IndexForm(FlaskForm):
login = FormField(LoginForm)
register = FormField(RegisterForm)
My first thought was to just delete the IndexForm code (and obviously update the code everywhere else where it was needed). I can´t see why the class IndexForm has to wrap the other 2. But that didn´t work out. So I suspect there is a reason why it´s set up the way it is. But how can I make RegisterForm and LoginForm behave such that a validation error in one of them, doesn´t trigger error messages in the other? I have inserted more verbose code below, if that could be of any help.
forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField, FormField, TextAreaField, FileField
from wtforms.fields.html5 import DateField
from wtforms.validators import InputRequired, Length, EqualTo, NoneOf
# defines all forms in the application, these will be instantiated by the template,
# and the routes.py will read the values of the fields
# TODO: Add validation, maybe use wtforms.validators??
# TODO: There was some important security feature that wtforms provides, but I don't remember what; implement it
#invalidInput = [ADD THINGS HERE]
class LoginForm(FlaskForm):
username = StringField('Username', render_kw={'placeholder': 'Username'}, validators=[InputRequired()])
password = PasswordField('Password', render_kw={'placeholder': 'Password'}, validators=[InputRequired()])
remember_me = BooleanField('Remember me') # TODO: It would be nice to have this feature implemented, probably by using cookies
submit = SubmitField('Sign In')
class RegisterForm(FlaskForm):
first_name = StringField('First Name', render_kw={'placeholder': 'First Name'}, validators=[InputRequired(), NoneOf(invalidInput, message="Invalid input")])
last_name = StringField('Last Name', render_kw={'placeholder': 'Last Name'}, validators=[InputRequired()])
username = StringField('Username', render_kw={'placeholder': 'Username'}, validators=[
InputRequired(), Length(min=5, max=50, message="Must be between 5 and 50 characters")])
password = PasswordField('Password', render_kw={'placeholder': 'Password'}, validators=[
InputRequired(), Length(min=8, max=50, message="Must be between 8 and 50 characters"), EqualTo('confirm_password', message='Passwords must match')])
confirm_password = PasswordField('Confirm Password', render_kw={'placeholder': 'Confirm Password'}, validators=[
InputRequired(), Length(min=8, max=50, message="Must be between 8 and 50 characters")])
submit = SubmitField('Sign Up')
class IndexForm(FlaskForm):
login = FormField(LoginForm)
register = FormField(RegisterForm)
routes.py
from app import app, query_db
from app.forms import IndexForm, PostForm, FriendsForm, ProfileForm, CommentsForm
from datetime import datetime
import os
#############################
from flask_wtf import FlaskForm
from wtforms import (StringField, PasswordField, SubmitField, TextAreaField, IntegerField, BooleanField, RadioField)
from wtforms.validators import InputRequired, Length
# this file contains all the different routes, and the logic for communicating with the database
# home page/login/registration
#app.route('/', methods=['GET', 'POST'])
#app.route('/index', methods=['GET', 'POST'])
def index():
form = IndexForm()
if form.login.validate_on_submit() and form.login.is_submitted() and form.login.submit.data:
user = query_db('SELECT * FROM Users WHERE username="{}";'.format(form.login.username.data), one=True)
print("user is ", user)
if user == None:
flash('Sorry, this user does not exist!')
elif user['password'] == form.login.password.data:
return redirect(url_for('stream', username=form.login.username.data))
else:
flash('Sorry, wrong password!')
elif form.register.validate_on_submit() and form.register.is_submitted() and form.register.submit.data:
flash("New user registered!")
query_db('INSERT INTO Users (username, first_name, last_name, password) VALUES("{}", "{}", "{}", "{}");'.format(form.register.username.data, form.register.first_name.data,
form.register.last_name.data, form.register.password.data))
print(form.register.username.data)
print(form.register.password.data)
return redirect(url_for('index'))
return render_template('index.html', title='Welcome', form=form)
index.html
{% import "bootstrap/wtf.html" as wtf %}
{% block content %}
<div class="jumbotron jumbotron-fluid">
<div class="container">
<h1 class="display-4">Social Insecurity</h1>
<p class="lead">The social network for the insecure™</p>
</div>
</div>
<div class="container-fluid">
<div class="row">
<div class="col-sm-12 col-lg-6">
<div class="card text-center">
<div class="card-header">
Sign In
</div>
<div class="card-body">
<h5 class="card-title">Access an existing profile</h5>
<form action="" method="POST" novalidate>
{{ wtf.form_field(form.login.username) }}
{{ wtf.form_field(form.login.password) }}
{{ wtf.form_field(form.login.remember_me) }}
{{ wtf.form_field(form.login.submit, class="btn btn-primary") }}
</form>
</div>
</div>
</div>
<div class="col-sm-12 col-lg-6">
<div class="card text-center">
<div class="card-header">
Register
</div>
<div class="card-body">
<h5 class="card-title">Create a new profile</h5>
<form action="", method="POST" novalidate>
{{ wtf.form_field(form.register.csrf_token) }}
{{ wtf.form_field(form.register.first_name) }}
{{ wtf.form_field(form.register.last_name) }}
{{ wtf.form_field(form.register.username) }}
{{ wtf.form_field(form.register.password) }}
{{ wtf.form_field(form.register.confirm_password) }}
{{ wtf.form_field(form.register.submit, class="btn btn-primary") }}
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}```
Solved it by removing the validators from LoginForm in forms.py. No need to validate these fields, if I have already validated them when creating a new user.
In this Script, I set the message for InputRequired in wtforms.validator to showing error. also, I giving max and min for a length of character. But these arguments not working.
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, IntegerField, SubmitField,StringField
from wtforms.validators import ValidationError, Length, EqualTo, InputRequired, Email
class Login(FlaskForm):
email =StringField(validators=[InputRequired(message = 'please input something'), Length(min=10, max=20), Email(message='this is not email')])
password = PasswordField(validators=[InputRequired(message='please input something'), Length(min=8, max=20, message='you must be input more than 8 character')])
submit = SubmitField('Enter')
class Register(FlaskForm):
email =StringField(validators=[InputRequired(message = 'please input something'), Length(min=10, max=20), Email(message='this is not email')])
phone = IntegerField(validators=[InputRequired(message='please input something')])
password = PasswordField(validators=[InputRequired(message='please input something'), Length(min=8, max=20, message='you must be input more than 8 character')])
submit = SubmitField('Enter')
This Script is login.html
{% extends "base.html" %}
{% block content %}
<h3>this is Home page</h3>
<form action="#">
<!-- {{forlogin.hidden_tag()}} -->
{{forlogin.email(placeholder="email")}}
<br>
{{forlogin.password(placeholder="password")}}
<br>
{{forlogin.submit}}
</form>
{% endblock content %}
From the Field class documentation in WTForms documentation:
errors
If validate encounters any errors, they will be inserted into this list.
So to show the errors, you have to show the items in the list on your page:
<form action="#">
{{login.email(placeholder="email")}}{% for error in login.email.errors %} {{ error }} {% endfor%}
</form>
I do not know what the for was meant to do between the double braces, I removed it. It caused errors in Jinja2.
I'm trying to setup a flask webapp with some basic login/register system controlled by flask-WTF forms.
Here is my code:
html
<!-- Register form -->
<div class="form">
<div class="form-area">
<h2>Register</h2>
<form action="{{ url_for('register') }}">
{{ form.csrf_token() }}
{{ form.hidden_tag() }}
{{ form.name(placeholder='name') }}
{{ form.surname(placeholder='surname') }}
{{ form.email(placeholder='email') }}
{{ form.password(placeholder='password') }}
{{ form.confirm_password(placeholder='confirm password') }}
<input type="submit" value="Register">
</form>
<p>Already registered? Log in here</p>
</div>
<div class="error-area">
{% for error in form.confirm_password.errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
</div>
class
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField
from wtforms.validators import InputRequired, Length, EqualTo
class RegisterForm(FlaskForm):
name = StringField('name', validators=[InputRequired()])
surname = StringField('surname', validators=[InputRequired()])
email = StringField('email', validators=[InputRequired()])
password = PasswordField('password', validators=[InputRequired(), Length(min=6)])
confirm_password = PasswordField('confirm passord', validators=[InputRequired(), Length(min=6), EqualTo(password)])
flask
#app.route('/register')
def register():
#declare a register form
form = RegisterForm()
#validate form
if form.validate_on_submit():
print('validate')
return '<h1>Success</h1>'
else:
print('not validated')
print(form.errors)
return render_template('register.html', form=form)
The problem with my code is that validation seems not to be working. Even if I fill the form with the "valid" input, form.validate_on_submit() always fail.
What I can't understand is that even when I try to print array errors, no error shows.
What am I missing?
There are a few issues here. Firstly, in your html, you haven't set a method attribute for the form. This means that it defaults to GET, which is why the form isn't validating. This can be changed like so:
<form action="{{ url_for('register') }}" method='POST'>
Incidentally, as the view that loads the form is the same as the target, you can leave out the action attribute, giving us:
<form method='POST'>
Secondly, in your class, you have a couple of issues with the confirm_password field. Firstly, you have a typo in PasswordField('confirm passord'. Secondly, the EqualTo() validator expects a string, not a field. We need to change this line in full to:
confirm_password = PasswordField('confirm password', validators=[InputRequired(), Length(min=6), EqualTo('password')])
Finally, in your flask, we need to accept POST requests to the view. This can be done by altering #app.route():
#app.route('/register', methods=['POST', 'GET'])
Having made these changes, the form should work as expected.
Someone wrote a page which lets you fill out a form and do something one at a time, I am working on a another page with a "bulk" functionality, which needs two fewer form fields.
I have been trying to recycle their Form object rather than define a new one which is only very slightly different.
Can explain this with the example from the docs:
https://flask.palletsprojects.com/en/1.0.x/patterns/wtforms/
The form object is created:
from wtforms import Form, BooleanField, StringField, PasswordField, validators
class RegistrationForm(Form):
username = StringField('Username', [validators.Length(min=4, max=25)])
email = StringField('Email Address', [validators.Length(min=6, max=35)])
password = PasswordField('New Password', [
validators.DataRequired(),
validators.EqualTo('confirm', message='Passwords must match')
])
confirm = PasswordField('Repeat Password')
accept_tos = BooleanField('I accept the TOS', [validators.DataRequired()])
There is an endpoint:
#app.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm(request.form)
if request.method == 'POST' and form.validate():
user = User(form.username.data, form.email.data,
form.password.data)
db_session.add(user)
flash('Thanks for registering')
return redirect(url_for('login'))
return render_template('register.html', form=form)
and register.html (part of it below)
<form method=post>
<dl>
{{ render_field(form.username) }}
{{ render_field(form.email) }}
{{ render_field(form.password) }}
{{ render_field(form.confirm) }}
{{ render_field(form.accept_tos) }}
</dl>
<p><input type=submit value=Register>
</form>
Now say you are making another page which does not need the email field but needs all the other fields. If you delete the line:
{{ render_field(form.email) }}
out of the html, the page will render okay, but when the user clicks submit, form.validate() will be False and so it will not submit.
How can you give the form object something in the .py file so that it ignores a field which failed to be sent across from the browser?
Tried
form.email = "field not needed here"
...before the validation step, but it didn't seem to work
I guess that you want something like this:
The form object:
from wtforms import Form, BooleanField, StringField, PasswordField, validators
class CustomForm(Form):
username = StringField('Username', [validators.Length(min=4, max=25)])
password = PasswordField('New Password', [
validators.DataRequired(),
validators.EqualTo('confirm', message='Passwords must match')
])
confirm = PasswordField('Repeat Password')
accept_tos = BooleanField('I accept the TOS', [validators.DataRequired()])
Your new endpoint, let's say we want to create a User without email:
#app.route('/endpoint', methods=['GET', 'POST'])
def register():
form = CustomForm(request.form)
if request.method == 'POST' and form.validate():
user = UserWithoutEmail(form.username.data, form.password.data)
db_session.add(user)
flash('Thanks for registering')
return redirect(url_for('login'))
return render_template('register.html', form=form)
and register.html to show everything but email:
<form method=post>
<dl>
{{ render_field(form.username) }}
{{ render_field(form.password) }}
{{ render_field(form.confirm) }}
{{ render_field(form.accept_tos) }}
</dl>
<p><input type=submit value=Register>
</form>
I have found a tidy solution adapted from here
class RegistrationForm2(RegistrationForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
delattr(self, 'email')
I like it cause it does not involve repeating the whole class again. Not sure if it is more or less elegant than filling in fake values in the .py form and avoiding defining a new form but it works.
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.