User registration in Flask - frontend, part 2 of 2
Posted on
by Kevin FoongThis is the second part of my tutorial to create a fully functioning user registration and login form in Flask. In the first part of the tutorial we looked at the backend side especially Flask SQLAlchemy and the database. I assume you have already set up everything from the first part and this will just be a continuation of it. In this part we will focus on the Flask side, in particular Flask WTF and Flask Login. Part 1 is available here.
1. First install flask-wtf and flask-login in your project's virtual environment. "wtforms[email]" is just an extra package from wtforms which provides email validation which we will use later.
pip install flask-wtf
pip install wtforms[email]
pip install flask-login
2. Create forms.py under your "app" folder.
Flask-WTF is a great package in helping you build forms in Flask. It also helps you perform validation checking on individual fields. Lets take a look at one example to see how it works:
password = PasswordField('Password', validators=[InputRequired()])
We specify that we want a password field. This will be rendered as an HTML <input type="password">
element. The validators
parameter takes a list of validations you want to perform on the field. In this case we are saying that it is a mandatory field (InputRequired).
password2 = PasswordField('Repeat Password', validators=[InputRequired(), EqualTo('password')])
This is the second password field in which there is a validator to ensure that it matches the first password field.
A few other points include,
- In LoginForm we have added a UserMixin class. This provides helpful properties that we can use in our Flask app such as
is_authenticated
to tell us if the current is logged in. - In RegistrationForm there is a
validate_email
function. This is a custom validator provided by fask-wtf which enables you to add your own validation code. In this case we use it to check the database to see if the email already exists. User.query.filter_by(email=email.data).first()
is the equivalent to SQL
SELECT TOP(1)
FROM user
WHERE email = email.data
The source code for forms.py is below.
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import InputRequired, Email, EqualTo, ValidationError
from flask_login import UserMixin
from .models import User
class LoginForm(FlaskForm, UserMixin):
email = StringField('Username (email)', validators=[InputRequired()])
password = PasswordField('Password', validators=[InputRequired()])
remember_me = BooleanField('Remember Me')
submit = SubmitField('Login')
class RegistrationForm(FlaskForm):
email = StringField('Email', validators=[InputRequired(), Email()])
password = PasswordField('Password', validators=[InputRequired()])
password2 = PasswordField('Repeat Password', validators=[InputRequired(), EqualTo('password')])
submit = SubmitField('Register')
# flask-wtf takes validate_<field_name> as custom validators
# so the below validator gets invoked on username
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user is not None:
raise ValidationError('Email has previously registered')
3. Create routes.py under your "app" folder.
Most of this is quite standard Flask. A few key points regarding Flask-login to note:
- Flask-login provides a
login_user
andlogout_user
function.login-user
works by storing the User object in the client session andlogout-user
removes it from the session. You can then check if the user is logged in throughcurrent_user.is_authenticated
. This method is available to us because we enabled it by adding the UserMixin class (also provided by flask-login) to our User model. - Flask-login also manages the "remember me" functionality. You just need to pass
remember=true
tologin_user
, and a cookie will be set on the user's PC. According to the Flask-login documentation "A cookie is saved on the user’s computer, and then Flask-Login will automatically restore the user ID from that cookie if it is not in the session". - Flask-login provides a
@login-required
decorator which you can put on top of any function that requires the user to be logged in. If the user is not logged in the function decorated with@login_manager.unauthorized_handler
will be called which in our case is theunauthorized
function. - Flask-login requires you to always have a callback decorated with
@login_manager.user_loader
. This function returns the user object, given the user_id.
from flask import render_template, redirect, url_for, flash, request
from flask_login import current_user, login_user, logout_user, login_required
from . import app, db, login_manager
from .forms import LoginForm, RegistrationForm
from .models import User
@app.route('/')
@app.route('/index')
def index():
return render_template('index.html')
@app.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('index'))
next_page = request.args.get('next')
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user is None or not user.check_password(form.password.data):
flash('Invalid username or password')
return redirect(url_for('login', next=next_page))
login_user(user, remember=form.remember_me.data)
flash('You are now logged in')
if not next_page:
return redirect(url_for('index'))
return redirect(url_for(next_page))
return render_template('login.html', form=form)
@app.route('/logout')
@login_required
def logout():
logout_user()
flash('You are now logged out')
return redirect(url_for('index'))
@app.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('index'))
form = RegistrationForm()
if form.validate_on_submit():
user = User(email=form.email.data)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash('Registration successful')
return redirect(url_for('login'))
return render_template('register.html', form=form)
# handler when you are trying to access a page but you are not logged in
@login_manager.unauthorized_handler
def unauthorized():
flash('You must be logged in to view this page')
return redirect(url_for('login', next=request.endpoint))
# Callback used by flask-login
# This callback is used to reload the user object from the user ID stored in the session
@login_manager.user_loader
def load_user(user_id):
return User.query.get(user_id)
4. Create a "templates" sub-folder under your "app" folder. In the templates folder create index.html, login.html and logout.html
Here I provided basic templates for index, login and registration. They are basic HTML without any CSS to keep things simple.
index.html
<body>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div style="color:red;">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<h1>User Registration</h1>
<p>Welcome to my page</p>
<a href="{{ url_for('login') }}">Login</a>
<a href="{{ url_for('logout') }}">Logout</a>
<a href="{{ url_for('register') }}">Register</a>
</body>
login.html
<body>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div style="color:red;">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<h2>Login</h2>
<form action="" method="post">
{{ form.csrf_token }}
<div>
{{ form.email.label }}
{{ form.email }}
{% for error in form.email.errors %}
<p style="color:red;">{{ error }}</p>
{% endfor %}
</div>
<div>
{{ form.password.label }}
{{ form.password }}
{% for error in form.password.errors %}
<p style="color:red;">{{ error }}</p>
{% endfor %}
</div>
<div>
{{ form.remember_me }} Remember me
</div>
<div>
{{ form.submit }}
</div>
</form>
</body>
register.html
<body>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div style="color:red;">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<h2>Register</h2>
<form action="" method="post">
{{ form.csrf_token }}
<div>
{{ form.email.label }}
{{ form.email }}
{% for error in form.email.errors %}
<p style="color:red;">{{ error }}</p>
{% endfor %}
</div>
<div>
{{ form.password.label }}
{{ form.password }}
{% for error in form.password.errors %}
<p style="color:red;">{{ error }}</p>
{% endfor %}
</div>
<div>
{{ form.password2.label }}
{{ form.password2 }}
{% for error in form.password2.errors %}
<p style="color:red;">{{ error }}</p>
{% endfor %}
</div>
<div>
{{ form.submit }}
</div>
</form>
</body>
5. Update __init__.py
Lastly we need to update __init__.py to ensure we initialise flask-login and import all our Python files.
from flask import Flask
from config import Config
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
migrate = Migrate(app, db, compare_type=True)
login_manager = LoginManager(app)
from . import models, routes, forms
The final project directory should now look like this.
Run Flask, go to http://localhost:5000 and you should be able to register as a new user into your database and perform login and logout.
Enjoy!