I wrote a simple Flask application without using any module to help
with the forms.
Today, I wanted to give WTForms a try, using the Flask-WTForms extension.
Everything looks great, but I'm having issues porting my code because
all of the examples fail to demonstrate a key point.
For example, take a look at my original login view:
@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
try:
user = get_user(username, password) # NOTE: get_user raises
an exception if the username/password combo is invalid
session['username'] = user.username
url = request.args.get('next', url_for('index'))
return redirect(url)
except Exception as e:
error = str(e) # I just handle the exception and pass the
error message to the template
return render_template('login.html', error=error)
Now, in attempting to port to WTForms:
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm(request.form)
if request.method == 'POST' and form.validate():
username = form.username.data
password = form.password.data
user = get_user(username, password) # now what?
session['username'] = user.username
url = request.args.get('next', url_for('index'))
return redirect(url)
return render_template('login.html', form=form)
The issue? My form is "valid", in that the username and password are
not blank and are the right length, etc., but that doesn't necessarily
mean the username exists in the DB or the password is correct. This is
extra validation that needs to be performed. But it isn't particular
to any given field of the form.
How can I perform extra validation that isn't particular to a field?
I could pass a separate error variable to the template to handle these
higher-level errors, but it seems like I shouldn't have to have two
ways of passing errors to the template (forms.errors and error)
How do you guys handle this?
(My first post to the mailing list. Flask is awesome -- exactly what I
was looking for, a lighter-weight web framework for Python that
doesn't suck!)
Thanks!
Michael
Hi, On 7/29/11 3:36 AM, Michael Fogleman wrote: > Everything looks great, but I'm having issues porting my code because > all of the examples fail to demonstrate a key point. Aye, That should be pointed out in the docs better. All fields have an 'errors' attribute which you can modify. If you want to perform validation that spawns more than one field you just need to override the general 'validate' method and hook in your custom logic there. Here is how I do validation of Login forms: from flask.wtf import Form, TextField, PasswordField, validators from myapplication.models import User class LoginForm(Form): username = TextField('Username', [validators.Required()]) password = PasswordField('Password', [validators.Required()]) def __init__(self, *args, **kwargs): Form.__init__(self, *args, **kwargs) self.user = None def validate(self): # regular validation rv = Form.validate(self) if not rv: return False user = User.query.filter_by(username=self.username.data).first() if self.user is None: self.username.errors.append('Unknown username') return False if not user.check_password(self.password.data): self.password.errors.append('Invalid password') return False self.user = user return True And here is how you use it in a view: from flask import flash, redirect, url_for, session, render_template @app.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm() if form.validate_on_submit(): flash(u'Successfully logged in as %s' % form.user.username) session['user_id'] = form.user.id return redirect(url_for('index')) return render_template('login.html', form=form) For login views it's nice being able to redirect the user back, in case you are interested in that you might want to have a look at this: http://flask.pocoo.org/snippets/63/ Regards, Armin
Thanks! I've ported everything over now and am very happy with the results.
By the way, I had my ChangePasswordForm extend my LoginForm, so it
inherted the password validation - nice!
class LoginForm(Form):
username = TextField('Username', [validators.Required()])
password = PasswordField('Password', [validators.Required()])
def validate(self):
if not super(LoginForm, self).validate():
return False
user = User.query.filter_by(username=self.username.data).first()
if not user:
self.username.errors.append('Invalid username.')
return False
if not user.check_password(self.password.data):
self.password.errors.append('Invalid password.')
return False
self.user = user
return True
class ChangePasswordForm(LoginForm):
username = TextField('Username', [validators.Required()])
password = PasswordField('Old Password', [validators.Required()])
new_password = PasswordField('New Password', [validators.Length(6)])
confirm_password = PasswordField('Confirm Password',
[validators.EqualTo('new_password')])
On Fri, Jul 29, 2011 at 5:04 AM, Armin Ronacher
<armin.ronacher@active-4.com> wrote:
> Hi,
>
> On 7/29/11 3:36 AM, Michael Fogleman wrote:
>> Everything looks great, but I'm having issues porting my code because
>> all of the examples fail to demonstrate a key point.
> Aye, That should be pointed out in the docs better. All fields have an
> 'errors' attribute which you can modify. If you want to perform
> validation that spawns more than one field you just need to override the
> general 'validate' method and hook in your custom logic there.
>
> Here is how I do validation of Login forms:
>
> from flask.wtf import Form, TextField, PasswordField, validators
> from myapplication.models import User
>
>
> class LoginForm(Form):
> username = TextField('Username', [validators.Required()])
> password = PasswordField('Password', [validators.Required()])
>
> def __init__(self, *args, **kwargs):
> Form.__init__(self, *args, **kwargs)
> self.user = None
>
> def validate(self):
> # regular validation
> rv = Form.validate(self)
> if not rv:
> return False
>
> user = User.query.filter_by(username=self.username.data).first()
> if self.user is None:
> self.username.errors.append('Unknown username')
> return False
>
> if not user.check_password(self.password.data):
> self.password.errors.append('Invalid password')
> return False
>
> self.user = user
> return True
>
>
> And here is how you use it in a view:
>
> from flask import flash, redirect, url_for, session, render_template
>
> @app.route('/login', methods=['GET', 'POST'])
> def login():
> form = LoginForm()
> if form.validate_on_submit():
> flash(u'Successfully logged in as %s' % form.user.username)
> session['user_id'] = form.user.id
> return redirect(url_for('index'))
> return render_template('login.html', form=form)
>
>
> For login views it's nice being able to redirect the user back, in case
> you are interested in that you might want to have a look at this:
> http://flask.pocoo.org/snippets/63/
>
>
> Regards,
> Armin
>
Just do it in the view, same as you're original example. You can pass the
error with the message flashing, there is no need to put that back into the
form (it is not really a form error is it?). Another option is a custom
validator, but I'm not a fan of that kind of logic in the forms.
@blueprint.endpoint('account.login')
def login():
if request.method == "POST":
form = forms.LoginForm(request.form)
if form.validate():
user = form.user
if login_user(user, remember=form.data['remember_me']):
flash("Logged in!")
return redirect(
form.next.data or url_for("account.index"))
else:
flash("Sorry, but you could not log in.")
else:
flash("Invalid username.")
else:
form = forms.LoginForm(request.args)
return render_template("account/login.html", form=form)
The login_user method above is querying the database.
FWIW: I recommend Flask-Login[1] for logging in/out and user session
management.
[1] http://pypi.python.org/pypi/Flask-Login
HTH, Daniel
2011/7/29 Michael Fogleman <fogleman@gmail.com>
> I wrote a simple Flask application without using any module to help
> with the forms.
>
> Today, I wanted to give WTForms a try, using the Flask-WTForms extension.
>
> Everything looks great, but I'm having issues porting my code because
> all of the examples fail to demonstrate a key point.
>
> For example, take a look at my original login view:
>
> @app.route('/login', methods=['GET', 'POST'])
> def login():
> error = None
> if request.method == 'POST':
> username = request.form['username']
> password = request.form['password']
> try:
> user = get_user(username, password) # NOTE: get_user raises
> an exception if the username/password combo is invalid
> session['username'] = user.username
> url = request.args.get('next', url_for('index'))
> return redirect(url)
> except Exception as e:
> error = str(e) # I just handle the exception and pass the
> error message to the template
> return render_template('login.html', error=error)
>
> Now, in attempting to port to WTForms:
>
> @app.route('/login', methods=['GET', 'POST'])
> def login():
> form = LoginForm(request.form)
> if request.method == 'POST' and form.validate():
> username = form.username.data
> password = form.password.data
> user = get_user(username, password) # now what?
> session['username'] = user.username
> url = request.args.get('next', url_for('index'))
> return redirect(url)
> return render_template('login.html', form=form)
>
> The issue? My form is "valid", in that the username and password are
> not blank and are the right length, etc., but that doesn't necessarily
> mean the username exists in the DB or the password is correct. This is
> extra validation that needs to be performed. But it isn't particular
> to any given field of the form.
>
> How can I perform extra validation that isn't particular to a field?
>
> I could pass a separate error variable to the template to handle these
> higher-level errors, but it seems like I shouldn't have to have two
> ways of passing errors to the template (forms.errors and error)
>
> How do you guys handle this?
>
> (My first post to the mailing list. Flask is awesome -- exactly what I
> was looking for, a lighter-weight web framework for Python that
> doesn't suck!)
>
> Thanks!
> Michael
>