Web Technologies/2021-2022/Laboratory 13
Form validation using WTForms
editValidating 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