Customize our WTF Forms Validators

Some useful links

The links to the YouTube videos, and the GitHub account for this section are below:

All the information in this section has been taken from the next links:

Customize our WTF Forms Validators

We customize the messages disclosed to the user. If for example, we try to register by using the same username or email, we get an ugly message. To avoid those messages that could not be very useful for the users of our app, we can customize our WTF Forms Validators.

In the package users, in the forms.py file.

  1. Copy and paste from the link above, and then customize.
    
    class MyForm(Form):
    name = StringField('Name', [InputRequired()])
    
    def validate_name(form, field):
        if len(field.data) > 50:
        raise ValidationError('Name must be less than 50 characters')
                                
  2. We import the tools that we need:
    
    from capp.models import User
    from wtforms.validators import ValidationError                                
                                
  3. Customize username and email errors.
    
    def validate_username(self, username):
        user = User.query.filter_by(username=username.data).first()
        if user:
            raise ValidationError('That username is taken.//
             Please choose a different one.')
                                  
    def validate_email(self, email):
        user = User.query.filter_by(email=email.data).first()
        if user:
            raise ValidationError('That email is taken. //
            Please choose a different one.') 
                                

In the package users, forms.py file

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, 
    BooleanField
from wtforms.validators import DataRequired, Length, Email, 
    EqualTo, ValidationError
from capp.models import User

class RegistrationForm(FlaskForm):
    username = StringField('Username', 
        validators=[DataRequired(), Length(min=2, max=30)])
    email = StringField('Email',
        validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    confirm_password = PasswordField('Confirm Password',
        validators=[DataRequired(), EqualTo('password')]) 
    submit = SubmitField('Sign Up')

    def validate_username(self, username):
    user = User.query.filter_by(username=username.data).first()
    if user:
        raise ValidationError('That username is taken. //
        Please choose a different one.')

    def validate_email(self, email):
    user = User.query.filter_by(email=email.data).first()
    if user:
        raise ValidationError('That email is taken. //
        Please choose a different one.')

Flask-login package

Some useful links

The links to the YouTube video, and the GitHub account for this section are below:

All the information in this section has been taken from the next links:

What is Flask-login?

Flask-Login provides user session management for Flask. It handles the common tasks of logging in, logging out, and remembering your users’ sessions over extended periods of time.

It will

How to use Flask-login?

  1. Installation

    Install the extension with pip: pip install flask-login.

  2. Configuring your application

    The most important part of an application that uses Flask-Login is the LoginManager class. You should create one for your application somewhere in your code, like this:
    
    from flask_login import LoginManager
    login_manager = LoginManager()
                        
    The login manager contains the code that lets your application and Flask-Login work together, such as how to load a user from an ID, where to send users when they need to log in, and the like.

  3. How it Works

    You will need to provide a user_loader callback. This callback is used to reload the user object from the user ID stored in the session. It should take the str ID of a user and return the corresponding user object. For example:
    
    @login_manager.user_loader
    def load_user(user_id):
        return User.get(user_id)
                        
    It should return None (not raise an exception) if the ID is not valid. (In that case, the ID will manually be removed from the session and processing will continue.)

  4. Your User Class

    The class that you use to represent users needs to implement these properties and methods:
    1. is_authenticated

      This property should return True if the user is authenticated, i.e. they have provided valid credentials. (Only authenticated users will fulfill the criteria of login_required.)

    2. There are other classes that we are not going to use, but that can be studied in the link the Flask-login link that I provide above in this section.

  5. API Documentation

    This documentation is automatically generated from Flask-Login's source code.
    1. flask_login.current_user

      A proxy for the current user.

    2. flask_login.login_user(user, remember=False, duration=None, force=False, fresh=True)

      Logs a user in. You should pass the actual user object to this. If the user's is_active property is False, they will not be logged in unless force is True.

      This will return True if the log in attempt succeeds, and False if it fails (i.e. because the user is inactive).

      Parameters:
      • user (object) - The user object to log in.
      • remember (bool) - Whether to remember the user after their session expires. Defaults to False.
      • duration (datetime.timedelta) - The amount of time before the remember cookie expires. If None the value set in the settings is used. Defaults to None.
      • force (bool) - If the user is inactive, setting this to True will log them in regardless. Defaults to False.
      • fresh (bool) - setting this to False will log in the user with a session marked as not “fresh”. Defaults to True.

    3. flask_login.logout_user()

      Logs a user out. (You do not need to pass the actual user.) This will also clean up the remember me cookie if it exists.

  6. User Object Helpers

    class flask_login.UserMixin

    This provides default implementations for the methods that Flask-Login expects user objects to have.

Flask-login in our app.

In this section, we use Flask-login in our application. We introduce four changes that allow us to use different Flask-login functionalities.

  1. Installation:
    
    pip install flask-login
                                
  2. Configuration. In the __init__.py file:
    1. Import the tools that we need:
      
      from flask_login import LoginManager
      login_manager.login_view = 'users.login'
      login_manager.login_message_category = 'info'
                                          
    2. Configure the application
      
      login_manager= LoginManager(application)
                                          

  3. How it Works. In the models.py file:
    1. Import the tools that we need
      
          from capp import login_manager
                                          
    2. Import UserMixin:
      
          from flask_login import UserMixin
                                          
    3. Define the function:
      
          @login_manager.user_loader
          def load_user(user_id):
          return User.query.get(int(user_id)) 
                                          
    4. Use it in the Classes:
      
          class User(db.Model, UserMixin)
                                          

In the __init__.py file:

from flask_login import LoginManager

login_manager= LoginManager(application)
login_manager.login_view = 'users.login'
login_manager.login_message_category = 'info'
In the models.py file:

from capp import login_manager
from flask_login import UserMixin

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))
In the package users, in the routes.py file:

from capp.users.forms import RegistrationForm
from capp.models import User
from capp import db, bcrypt
from flask_login import login_user

users=Blueprint('users',__name__)

@users.route('/register', methods=['GET','POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        user_hashed_password = //
        bcrypt.generate_password_hash(form.password.data).decode('utf-8')
        user = User(username=form.username.data, email=form.email.data, //
        password=user_hashed_password)
        db.session.add(user)
        db.session.commit()
        flash('Your account has been created! Now, you are able to //
        login!', 'success')
        return redirect(url_for('users.login'))
    return render_template('users/register.html', //
    title='register', form=form)

  1. In the package users, routes.py file, login route:
    1. Import the tools that we need
      
          from flask_login import login_user
                                     
    2. Modify the route
      
          user = User.query.filter_by(email=form.email.data).first()
          if user and bcrypt.check_password_hash(user.password, form.password.data):
          login_user(user, remember=form.remember.data)
          flash('You have logged in! Now, you can start to use our App!', 'success')
          else:
          flash('Login Unsuccessful. Please check email and password!', 'danger') 
                                      
    3. Check field:
      
          login_user(user, remember=form.remember.data)
                                      

User authenticated, log out and redirect to the last page visited

In this section, we focus on three different uses of Flask-login that are useful for our app.

  1. If the user is authenticated, we do not want that the user log in our app again, but we want to redirect her to the home page. To do that, in the package users, in the routes.py file, we proceed in two steps:
    1. We import the tools that we need:
      
      from flask_login import current_user
                                          
    2. In the login route, we need to redirect the user to the home page:
      
      if current_user.is_authenticated:
          return redirect(url_for('home.home_home'))
                                          
  2. We want to allow the user to log out. To do that, in the package users, in the routes.py file, we proceed in two steps:
    1. We import the tools that we need:
      
      from flask_login import logout_user
                                          
    2. We need to create a new route:
      
      @users.route('/logout')
      def logout():
          logout_user()
          return redirect(url_for('home.home_home')) 
                                          

  3. Given that the carbon app routes is connected to a database to record the kilometers, the type of transport and the type of fuel, we do not want to allow the user to access to that web page until the user has not logged in. To do that, in the package carbon app, in the routes.py file, we proceed in two steps:
    1. We import the tools that we need:
      
      from flask_login import login_required
                                          
    2. We introduce a new decorator in the routes that we want to modify:
      
      @carbon_app.route('/carbon_app')
      @login_required
                                          

In the package user, routes.py file:

from flask import render_template, Blueprint, redirect, flash,
    url_for, request
from capp.users.forms import RegistrationForm, LoginForm
from capp.models import User
from capp import db, bcrypt
from flask_login import login_user, current_user, logout_user 

users=Blueprint('users',__name__)

@users.route('/register', methods=['GET','POST'])
def register():
  ...

@users.route('/login', methods=['GET','POST'])
def login():
  form = LoginForm()
  if current_user.is_authenticated:
        return redirect(url_for('home.home_home'))
  if form.validate_on_submit():
    user = User.query.filter_by(email=form.email.data).first()
    if user and bcrypt.check_password_hash(user.password, 
        form.password.data):
        login_user(user, remember=form.remember.data)
        next_page = request.args.get('next')
        flash('You have logged in! Now, you can start to 
          use our Carbon App!', 'success')
        return redirect(next_page) if next_page else 
          redirect(url_for('home.home_home'))
    else:
        flash('Login Unsuccessful. Please check email and password!', 
          'danger') 
  return render_template('users/login.html', title='login', form=form)

@users.route('/logout')
def logout():    
    logout_user()
    return redirect(url_for('home.home_home'))
In the package carbon_app, routes.py file:

@carbon_app.route('/carbon_app')
@login_required

@carbon_app.route('/carbon_app/new_entry')
@login_required

@carbon_app.route('/carbon_app/your_data')
@login_required    

  1. If the user tries to access to the routes that we want to protect forcing the user to be logged in, the user will be redirected to the login webpage. In that case, after logging in, we want that the user is redirected to the last page that she wanted to access. To do that, in the package users, in the routes.py file, we proceed in two steps:
    1. We import the tools that we need:
      
      from flask import request
                                     
    2. We modify the login route:
      
      if user and bcrypt.check_password_hash(user.password, form.password.data):
      login_user(user, remember=form.remember.data)
      next_page = request.args.get('next')
      flash('You have logged in! Now, you can start to use our Carbon App!', 'success')
      return redirect(next_page) if next_page else redirect(url_for('home.home_home'))
      else:
                                      

Changes in the HTML files to accommodate the changes in the routes.py file

In this section, we introduce some changes that allows us to accommodate the changes introduced in the routes.py files. When the user has logged in, we do not want that the register and the log in links appear in our app, since that could confound the user. Therefore, as soon as the user has logged in, we change the navigation bar, and the home page.

The links to the YouTube video, and the GitHub account for this section are below:

Changes in the navigation bar (layout.html file):

{% if current_user.is_authenticated %}
<nav>
    <ul>
        <li>
        <a href="{{url_for('users.logout')}}">Logout</a>
        </li>
        <li>
            <a href="{{url_for('home.home_home')}}">Home</a>
        </li>
        <li>
            <a href="{{url_for('methodology.methodology_home')}}">Methology</a>
        </li>
        <li>
            <a href="{{url_for('carbon_app.carbon_app_home')}}">Carbon App</a>
        </li>
    </ul>
</nav> 
{% else %}
<nav>
    <ul>
        <li>
        <a href="{{url_for('users.register')}}">Register</a>
        </li>
        <li>
        <a href="{{url_for('users.login')}}">Login</a>
        </li>
        <li>
            <a href="{{url_for('home.home_home')}}">Home</a>
        </li>
        <li>
            <a href="{{url_for('methodology.methodology_home')}}">Methology</a>
        </li>
        <li>
            <a href="{{url_for('carbon_app.carbon_app_home')}}">Carbon App</a>
        </li>
    </ul>
</nav>
{% endif %}
                
Changes in the home page (home.html file):

{% if current_user.is_authenticated %}
{% else %}
<div class="container_buttons_links_header_home">{
    <div class="btn">
        <a href="{{url_for('users.register')}}" 
        class="btn btn-primary btn-lg mr-2", 
        style="background-color:#007bff;">Register</a>
    </div>
    <div class="btn">
        <a href="{{url_for('users.login')}}" 
        class="btn btn-primary btn-lg mr-2", 
        style="background-color:#007bff;">Login</a>
    </div>
</div>
{% endif %}    

Weekly challenge

This chapter is very specific to user authentication, and there is not much that can be done within our app. Therefore, the weekly challenge consists in go through the material of the chapter and understand how the Flask-login package can be useful in our app.