Web Technologies/2021-2022/Laboratory 13
Form validation using WTForms
[edit | edit source]Validating form is going to be one of the most essential steps when building web applications.
Today we will see an approach of using WTFormsand Flask-login in order to implement a register/login mechanism.
We will start off with creating a simple User model:
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(25))
email = db.Column(db.String(35))
password = db.Column(db.String(120))
def __init__(self, username, email, password):
self.username = username
self.email = email
self.password = password
Then we will create two classes for our Registration and Login forms. WTForms lets us define fields such as StringField, PasswordField together with validators such as Length, Email, DataRequired:
class RegistrationForm(FlaskForm):
username = StringField('Username', [validators.Length(min=4, max=25)])
email = StringField('Email Address', [validators.Length(min=6, max=35), validators.Email()])
password = PasswordField('New Password', [
validators.DataRequired(),
validators.EqualTo('confirm', message='Passwords must match')
])
confirm = PasswordField('Repeat Password')
class LoginForm(FlaskForm):
email = StringField('Email',
validators=[validators.DataRequired(),
validators.Length(1, 64),
validators.Email()])
password = PasswordField('Password', validators=[validators.DataRequired()])
submit = SubmitField('Log In')
def validate(self, extra_validators):
initial_validation = super(LoginForm, self).validate()
if not initial_validation:
return False
user = User.query.filter_by(email=self.email.data).first()
if not user:
self.email.errors.append('Unknown email')
return False
if not user.verify_password(self.password.data):
self.password.errors.append('Invalid password')
return False
return True
We will have to implement routes for login/register:
@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)
db.session.commit()
flash('Thanks for registering')
return redirect(url_for('login'))
return render_template('register.html', form=form)
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm(request.form)
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user is not None and user.verify_password(form.password.data):
login_user(user)
redirect_url = request.args.get('next') or url_for('main.login')
return redirect(redirect_url)
return render_template('login.html', form=form)
We will write a Jinja2 macrofor our views. This macro will help us render each individual field of a form, containing our labels and displaying errors if any.
{% 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 %}
So for example this is what our register.html template will look like with the help of render_field macro:
<h1>Register</h1>
{% from "_formhelpers.html" import render_field %}
<form method="POST">
<dl>
{{ render_field(form.username) }}
{{ render_field(form.email) }}
{{ render_field(form.password) }}
{{ render_field(form.confirm) }}
</dl>
<p><input type="submit" value="Register">
</form>
Respectively, this will be our login.html form:
<h1>Login</h1>
{% from "_formhelpers.html" import render_field %}
<form id="loginForm" method="POST" role="form">
{{ form.hidden_tag }}
{{ render_field(form.email, placeholder="email") }}<br>
{{ render_field(form.password, placeholder="password") }}<br>
<p><input type="submit" value="Login"></p>
</form>
<a href="/">index</a><br>
<a href="/register">register</a><br>
The following imports and additional configurations steps needed in main.py:
import os
from flask import Flask, render_template, redirect, flash, url_for
from flask_sqlalchemy import SQLAlchemy
from flask import request, json
from flask_wtf import FlaskForm
import email_validator
from wtforms import BooleanField, StringField, PasswordField, SubmitField, validators
from flask_login import LoginManager
from flask_login import UserMixin
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
db = SQLAlchemy(app)
login_manager = LoginManager()
login_manager.session_protection = 'strong'
login_manager.login_view = 'main.login'
@login_manager.user_loader
def load_user(user_id):
return User.get(user_Id)
We will have to configure our login manager:
if __name__ == '__main__':
app.secret_key = 'test123'
app.config['SESSION_TYPE'] = 'filesystem'
login_manager.init_app(app)
app.run()
In the end, you should have a directory structure similar to:
.
├── app
│ ├── instance
│ │ └── test.db
│ ├── main.py
│ └── templates
│ ├── _formhelpers.html
│ ├── index.html
│ ├── login.html
│ └── register.html
├── requirements.txt
└── venv