Last time we connected a database to our app, and how to add records. We also added a backend interface to our app to allow owners to add and remove records. We configured various flask extensions and added some CSS styling to our HTML templates. Today we’re going to be looking at; managing users, adding a cart and implementing a checkout process.

App

Objectives;

  1. User management
  2. Using Blueprints
  3. Restricting access to owners
  4. Creating a checkout process
  5. Experimenting

Say hello to Blueprints

First we will start off by adding a Blueprint for authentication.
What is a blueprint? A blueprint is simply a collection of views and layouts.
Blueprints simplify what would otherwise be a hodge-podge of views and layouts.
It does this by adding structure to your app;

➜ app tree -L 3
.
├── assets
│   ├── js
│   │   └── main.coffee
│   └── scss
│       ├── _footer.scss
│       ├── main.scss
│       ├── _menugrid.scss
│       ├── _navigation.scss
│       └── _variables.scss
├── auth.py
├── create_db.py
├── favicon.ico
├── __init__.py
├── __init__.pyc
├── models.py
├── static
│   ├── css
│   │   └── main.css
│   ├── icons
│   │   ├── android-chrome-192x192.png
│   │   ├── android-chrome-512x512.png
│   │   ├── apple-touch-icon.png
│   │   ├── favicon-16x16.png
│   │   ├── favicon-32x32.png
│   │   ├── favicon.ico
│   │   └── site.webmanifest
│   └── js
│       └── main.js
├── templates
│   ├── about.html
│   ├── auth
│   │   ├── assets
│   │   ├── login.html
│   │   └── signup.html
|   |─── blog
│   |   ├── create.html
│   |   ├── index.html
│   |   └── update.html
│   ├── base.html
│   ├── coupons.html
│   ├── home.html
│   ├── menu.html
│   ├── navigation.html
│   └── support.html
├── views.py
└── views.pyc

Structure

As you can see we have added quite a number of things to our app!
Below is a list of files we have added or modified;

  1. auth.py (added)
  2. __init__.py (modified)
  3. models.py (modified)
  4. templates/auth (added)
  5. templates/blog (added)
  6. views.py (modified)

Let’s start from the top;

Getting started with Blueprints

First we need to define our Blueprint in auth.py;

import functools
from flask import ( Blueprint, flash, redirect, render_template, request, url_for, g)

bp = Blueprint('auth', __name__, url_prefix='/auth')

The __name__ refers to auth because the blueprint needs to know where it is defined.
The url_prefix as it’s name indicates, will be prepended to all URLs associated with this Blueprint.
The next step would be to import and register the Blueprint with our app.

Our new app/__init__.py

from flask import Flask

raj = Flask(__name__)
raj.config.from_object('config')

from app import models, auth, views
raj.register_blueprint(auth.bp)

WOAH! Our new __init__.py does not look like what we had previously from part 2!
So where did all the other code go?

Old app/__init__.py

1.  from flask import Flask
2.  from flask_scss import Scss                 # went to views.py
3.  from flask_admin import Admin               # went to views.py
4.  from flask_sqlalchemy import SQLAlchemy     # went to models.py
5.
6.  raj = Flask(__name__)
7.  raj.config.from_object('config')
8.  db = SQLAlchemy(raj)                        # went to models.py
9.
10. Scss(raj, asset_dir='app/assets/scss', static_dir='app/static/css') # went to views.py
11. admin = Admin(raj)                          # went to models.py
12. 
13. from app import models, views

Line 8 and line 11 both went to models.py because that’s were they are used.
It makes sense to initialize the db in our models.py, much less so for admin.
You could still import admin in your models.py like you did before - if you want.

Updated models.py

...
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin
from app import raj

db = SQLAlchemy(raj)
admin = Admin(raj)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    
    def __repr__(self):
        return '<User %r>' % self.username

...

line 10 went to views.py because they are primarily associated with views.

Updated views.py

from flask import render_template, url_for
from flask_scss import Scss
from app import raj

app_name = "Raj's Restaurant App"
Scss(raj, asset_dir='app/assets/scss', static_dir='app/static/css')

@raj.route('/')
@raj.route('/index')
def index():
	return render_template('index.html', title="Raj's Restaurant")

Our app should still be able to run as it did before since the only new thing we have added thus far is a Blueprint.
Lets go ahead and run our app to make sure everything still runs. It is recommended to fix any errors you get before going forward.
You should see something familiar when you run your app;

(pyenv) ➜ raj ./run.py 
 * Serving Flask app "app" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Running on http://localhost:3000/ (Press CTRL+C to quit)
 * Restarting with stat
[2019-09-24 12:39:21,361] INFO in flask_scss: Pyscss loaded!
 * Debugger is active!
 * Debugger PIN: 214-588-298

Back to our Blueprint!

All we have done is register our auth Blueprint with our app.
The next step is to define the routes for our authentication pages;

  1. Sign Up
  2. Log In

1. Sign Up

What is this route going to do?

  • Take in a user’s information
  • Make sure the user is not already registered
  • Add the user into our database

What type of information are we taking from a new user?

  1. firstname
  2. Lastname
  3. user_name
  4. user_email
  5. user_password

So what is the problem???
If I remeber correctly, our User model only has three attributes;

app/models.py

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)

Update models.py

Before we update our models.py, let us close any databse sessions;

$ (pyenv) python
>>> from app.models import db
>>> db.session.close()
>>> exit()

Ok, now we can go ahead and update our models.py;

app/models.py

... # Other import statements

import bcrypt

class User(db.Model):
    __tablename__ = 'users'
    firstname = db.Column(db.String(127))
    lastname = db.Column(db.String(127))
    email = db.Column(db.String(120), unique=True, primary_key=True)    
    username = db.Column(db.String(80), unique=True)
    password = db.Column(db.String(127), unique=True)
    
    def __init__(self, username, email, password):        
        self.username = username
        self.email = email.lower()
        self.set_password(password)
    
    def set_password(self, password):
        self.password = bcrypt.hashpw(password, bcrypt.gensalt())
    
    def check_password(self, password):
        return bcrypt.checkpw(password, self.password)
    
    def __repr__(self):
        return '<Firstname: %r, Lastname: %r, Email: %r >' %\
             (self.firstname, self.lastname, self.email)

... ### rest of the code

All the other code stays the same.
You will notice that we have also added a number of functions to our class,

  1. __init__
  2. set_password
  3. check_password

__init__

This is simply a constructor for our User.
It allows us to easily create users.

set_password and check_password

When we create new User a password hash is generated which is then stored as a password.
This is also why we need bcrypt.
I should also add that you should never store plain user passwords in your app.
You should also avoid storing hashed passwords unsalted.

If you’re curious, salting simply adds entropy to your password before it is hashed. This prevents rainbow attacks.

Before we go any further, it is best if we deleted our old database because we have not set it up for migrations so any updates to our models are going to break SQL queries to our db because it is using previously defined models.

~> psql postgres
~> drop raj_db
~> createdb raj_db

Time to Signup,

Ok, ok, so we can now define our signup route.

We will need to import the database (db) to check if our new user has already signed up.
We also need the database to add them if the are indeed a new user.
Remeber that we initialized our database in our models.py

app/auth.py

from app.models import User, db

...

db.create_all()
db.session.commit()

# Sign Up view
@bp.route('/signup', methods=('GET', 'POST'))
def signup():
    if request.method == 'POST': 
        firstname = request.form['firstname']
        lastname = request.form['lastname']
        user_name = request.form['username']
        user_email = request.form['email']
        user_password = request.form['password']
        
        error = None
        
        if not user_name or firstname or lastname:
            error = 'All names are required.'
        elif not user_email:
            error = 'Email is required.'
        elif User.query.filter_by(email=user_email).first() is not None:
            error = 'The User {} is already registered.'.format(username)
        elif not user_password:
            error = 'Password can not be empty!'

        if error is None:
            new_user = User(username=user_name, email=user_email, password=user_password)
            new_user.firstname = firstname
            new_user.lastname = lastname            
            db.session.add( new_user )
            db.session.commit()
            return redirect(url_for('auth.login'))
        flash(error)
    return render_template('auth/signup.html')

    ... # other code

We obtain user information via the request.form and check to see if the form has been filled.
Then we check our db for anyone with the same email since this is defined as the primary key in our User model.
This ensures that no two users can have the same email address.

If there are no errors we create a User and add them to our db all in one line.
The password hasing is handled by the set_password function of our User model.

If everything goes well we redirect the user to the login page, otherwise we render the auth/signup.html template.

Wait, where’s the auth/signup.html template? We haven’t created it yet! Lets do so;

auth/signup.html


{% extends 'base.html' %}

{% block header %}
<h1 class="su-title">{% block title %}Sign Up{% endblock %}</h1>
{% endblock %}

{% block content %}
<div class="signup-form">
  <form method="post">

    <label for="firstname">First Name: </label>
    <input name="firstname" id="firstname" required>

    <label for="lastname">Last Name: </label>
    <input name="lastname" id="lastname" required>

    <label for="email">Email: </label>
    <input name="email" id="email" required>

    <label for="username">Username</label>
    <input name="username" id="username" required>

    <label for="password">Password</label>
    <input type="password" name="password" id="password" required>
    <input type="submit" value="Signup">

  </form>
</div>
{% endblock %}

All the fields have to match what we ask for in the signup route app/auth.py.


2. Log In

Ok, so a user has signed up, how or where do they login? We need to define another route for this in our auth.py;

app/auth.py


... # Sign up code

# Login view
@bp.route('/login', methods=('GET', 'POST'))
def login():
    if request.method == 'POST':
        user_name = request.form['username']
        user_password = request.form['password']        
        
        error = None        
        user_u = User.query.filter_by(username=user_name).first()

        if user_u is None:
            error = 'Username does not exist.'
        elif not user_u.check_password(user_password):
            error = 'Incorrect password.'

        if error is None:
            # session.clear()
            # session['user_id'] = user['id']
            return redirect(url_for('index'))

        flash(error)
    return render_template('auth/login.html')

@bp.route('/logout')
def logout():
    # session.clear()
    db.session.close()
    return redirect(url_for('index'))

@login_manager.user_loader
def load_user(email):
    return User.query.get(str(email))

As before, we obtain our information via the request.form.
We then make sure that the user actually exists before checking if their password is correct.
It is important to note that there are two matches being done here;

  1. Does this user exist.
  2. Is their password correct.

If the user exists we then use that user’s check_password function as defined in our models to authenticate the plain-text password we’ve obtained.

Now in our case, two users can have the same username because of the way our signup works.
This becomes dangerous because if one user gets information that he shares the same username with many other users, he can guess a correct password and login to an account that does not belong to him.

However, no two users can have the same email address. So the solution to our problem would be to take in a user’s email address instead of their username.

... # other code

def login():
    if request.method == 'POST':
        user_email = request.form['email']
        user_password = request.form['password']        
        
        error = None        
        user_u = User.query.filter_by(email=user_email).first()

        ... # other code

Now a user either exists or they don’t because all User emails are unique by definition.
Well of course if one user happens to know the email addresses of other users he can go to town and start guessing their passwords. (Good luck!)
Let’s get back to the other “good” users. If the login is successful, we redirect the user to the index page.
If the login is not successful, we render the auth/login.html template which does not exist.
Lets create it.

auth/login.html


{% extends 'templates/base.html' %}

{% block header %}
  <h1>{% block title %}Log In{% endblock %}</h1>
{% endblock %}

{% block content %}
  <form method="post">
    <label for="username">Username</label>
    <input name="username" id="username" required>
    <label for="password">Password</label>
    <input type="password" name="password" id="password" required>
    <input type="submit" value="Log In">
  </form>
{% endblock %}

logout

@bp.route('/logout')
def logout():
    # session.clear()
    db.session.close()
    return redirect(url_for('index'))

We end the current session and logout a user

The last functions of app/auth.py

load_user

@login_manager.user_loader
def load_user(email):
    return User.query.get(str(email))

We simply return the user with an email that matches one we have provided but to do this we need a login manager.

app/auth.py

import functools
from app import raj
from app.models import User, db
from flask_login import LoginManager, login_user, logout_user, current_user, login_required
from flask import ( Blueprint, flash, redirect, render_template, request, url_for, g)

bp = Blueprint('auth', __name__, url_prefix='/auth')

login_manager = LoginManager()
login_manager.init_app(raj)

db.create_all()
db.session.commit()
... # rest of code


Who is a User anyway

If you look at an updated version of our base.html;


<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="description" content="Raj's Restaurant" />
    <meta name="keywords" content="HTML, CSS, JavaScript" />
    <meta name="author" content="Lehbyte" />
    <meta name="application-name" content="Raj's Application" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>{{ title }}</title>
    <!-- <link rel="stylesheet" type="text/css" href="./static/css/main.css" /> -->
    <link
      rel="stylesheet"
      type="text/css"
      href="{{url_for('static',filename='css/main.css')}}"
    />
  </head>
  <!-- body -->

  <body>
    <main>
      <!-- authentication -->
      <div class="auth">
        {% if user %}
        <p class="loggedin">{{ user["username"] }}</p>
        <button><a href="{{ url_for('auth.logout') }}">Log Out</a></button>
        {% else %}
        <div class="Signup">
          <button><a href="{{ url_for('auth.signup') }}">SignUp</a></button>
        </div>
        <div class="login">
          <form method="post">
            <input
              class="user"
              type="text"
              id="user"
              placeholder="Username"
              required
            />
            <input
              class="password"
              type="password"
              id="password"
              placeholder="Password"
              required
            />
            <input type="submit" value="Log In" />
          </form>
        </div>
        {% endif %}
      </div>

      <!-- Main content -->

      <div class="container">
        <header>
          {% block header %}{% endblock %}
        </header>

        {% for message in get_flashed_messages() %}
        <div class="flash">{{ message }}</div>
        {% endfor %} {% block content %}{% endblock %}
      </div>
    </main>
  </body>
  <!--body -->
  <!-- footer -->
  <footer>
    <div class="footer-wrap">
      <div class="footer-left">
        <h3>Left section</h3>
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
        tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
        veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
        commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
        velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
        occaecat cupidatat non proident, sunt in culpa qui officia deserunt
        mollit anim id est laborum."
      </div>
      <div class="footer-middle">
        <h3>Middle section</h3>
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
        tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
        veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
        commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
        velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
        occaecat cupidatat non proident, sunt in culpa qui officia deserunt
        mollit anim id est laborum."
      </div>
      <div class="footer-right">
        <h3>Right Section</h3>
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
        tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
        veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
        commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
        velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
        occaecat cupidatat non proident, sunt in culpa qui officia deserunt
        mollit anim id est laborum."
      </div>
    </div>
    <p>Copyright Raj's Restaurant &copy; 2019</p>
  </footer>
</html>


Specifically under authentication;
You will see that we check to see if a user exists in our app’s context and then render different html code based on that.

Where does this user get passed to our base template? Lets take a look at our updated views.py

app/views.py

from flask import request, render_template, redirect, url_for, g
from auth import login_manager, current_user
from functools import wraps
from flask_scss import Scss
from app import raj

app_name = "Raj's Restaurant App"
Scss(raj, asset_dir='app/assets/scss', static_dir='app/static/css')

def templated(template=None):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            template_name = template
            if template_name is None:
                template_name = request.endpoint.replace('.','/') + '.html'
            ctx = f(*args, **kwargs)
            if ctx is None:
                ctx = {}
            elif not isinstance(ctx, dict):
                return ctx
            return render_template(template_name, **ctx)
        return decorated_function
    return decorator

@raj.before_request
def before_request():
    g.user = current_user

@raj.route('/')
@raj.route('/home')
@raj.route('/index')
@templated()
def home():
    user = g.user
    return dict(title=app_name, user=user)

@raj.route('/about')
@templated('about.html')
def about():
    return dict(title=app_name)

@raj.route('/coupons')
def coupons():
    return render_template('coupons.html',title=app_name)

@raj.route('/blog')
def blog():
    return render_template('blog/index.html',title=app_name)

@raj.route('/support', methods=['GET','POST'])
def support():
    return render_template('support.html',title=app_name)

Whoah!

On line 2 we import a login_manager and current_user from auth.
Then make sure that g.user is the current_user before any request.

We will work out the details in the next tutorial but for now all you need to know is that a guest is also a user within the app’s context.

templated - ed

This was just fancy code for simplifying template rendering, nothig too special.


Front matter

You may have noticed a change in the way we load static files


    <link href="{{url_for('static',filename='css/main.css')}}"/>

Is better because it simplifies css loading for Blueprints.
If we tried to use the old method of loading our static files;

<link rel="stylesheet" type="text/css" href="./static/css/main.css" />

We would almost endup with a 404 resource not found error for the case of auth/signup.html and auth/login.html.
Why? Because our auth Blueprint prefixes every URL with /auth
so a request for /static/css/main.css would become /auth/static/css/main.css which does not exist.

Fancy Foot Work!

We have also added a footer on our frontend. assets/scss/_footer.scss

footer{    
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    font-weight: normal;
    font-size: 16px;
    color: burlywood;// #e0b172;
    background-color: black;
    position: relative;     
}
.footer-wrap{   
    display: flex;
    flex-direction: row;
    padding: 20px;
}
.footer-left,
.footer-middle,
.footer-right{
    padding: 10px;    
}
footer h3,
footer p{    
    color: snow;
    padding: 10px;
    text-align: center;    
}
footer p{
    padding: 10px;
}
@supports (display: flex){
    @media screen and (max-width: 516px){
        .footer-wrap{
            flex-direction: column;
        }
    }
}

Do not forget to @import 'footer'; at the bottom of your assets/scss/main.scss file.

Let’s also make our grid layout work on smaller screens;

assets/scss/_menugrid.scss


... /* previous code */

@media screen and (max-width: 516px){
    .food-grid{                        
        grid-template-columns: 100%;
    }
    .food-item{        
        background-color: seashell;        
    }
}

You can view other style changes in this project’s repo.

Part 4

Next time, we will be looking at;

  1. Defining checkout process
  2. How to add images to our app
  3. Creating a blog Blueprint

Happy coding. 🙂