[PYTHON] Codes that could be reused in Flask (sign up, login)

Introduction

I'm making a web app in Flask and I have some code that could be reused, so I'll share it. We haven't mentioned which code provides what functionality here, so please check it out for yourself.

Premise

Installation

Install the extension to be used this time in advance. You don't need to specify the version because we will prepare all the latest ones.

requirements.txt


Flask
Flask-Login
Flask-Migrate
Flask-SQLAlchemy
Flask-WTF
SQLAlchemy
Werkzeug
WTForms
Jinja2
email-validator
pip install -r requirements.txt

file organization

Flask uses something called Blueprint to split the functionality.

├───main.py # main app.py file to be called to start server for web app
├───requirements.txt # File of pip install statements for your app
├───migrations # folder created for migrations by calling
├───myproject # main project folder, sub-components will be in separate folders
│   │   data.sqlite
│   │   models.py
│   │   __init__.py
│   │
│   │───auth
│   │   │   forms.py
│   │   │   views.py
│   │   │
│   │   │───templates
│   │   │   └───auth
│   │   │           login.html
│   │   │           signup.html
│   │
│   ├───static # Where you store your CSS, JS, Images, Fonts, etc...
│   ├───templates
│          base.html
│          home.html

In this configuration, __init__.py contains config, but you can separate it as config.py.

We have prepared a common layout for base.html and reused it as{{% extends "base.html"%}}using a template engine called jinja2 (layout. The name hmtl is also often used).

I'm using SQLite for SQL, but I can't deploy to Heroku and it's not suitable for scale, so when using SQL, it's better to actually use MySQL or PostgreSQL. I think. Since we use ʻORM called SQLAlchemyto handle the database, the same code can be used even if the SQL is different (setup is different).data.sqlite`` migrations` is a file / folder created by yourself.

It is the request of Blueprint to have a redundant structure like myproject \ auth \ templates \ auth.

ʻAuth \ forms.py receives the sign-up and login forms, and ʻauth \ views.py receives the data entered from the forms, processes them, and renders them.

main.py and __ init__.py

Since main.py only makes calls, it looks like this:

main.py


from flask import render_template

from myproject import app

@app.route('/')
def index():
    return render_template('home.html')

if __name__ == '__main__':
    app.run(debug=True)

This file registers with config and blueprint, and does all the initialization.

__init__.py


import os
from flask import Flask, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager

login_manager = LoginManager()

app = Flask(__name__, static_folder='static', template_folder='templates')

basedir = os.path.abspath(os.path.dirname(__file__))

#If you added the Heroku Postgres add-on, the environment variable DATABASE_In the URL
#This value is set because the URL to connect to the PostgreSQL database is set.
#When set(When running on Heroku)Use it,
#When not set(Debugging locally, etc.)Makes it use the local SQLite database.

SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URI') or 'sqlite:///' + os.path.join(basedir, 'data.sqlite')

app.config['SECRET_KEY'] = 'mysecretkey'
app.config['SQLALCHEMY_DATABASE_URI'] = SQLALCHEMY_DATABASE_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)
Migrate(app, db)

#Be sure to import here after finishing the definition of db
from myproject.auth.views import auth_blueprint

app.register_blueprint(auth_blueprint, url_prefix='/authenticate')

login_manager.init_app(app)
login_manager.login_view = "auth.login"

The last line, login_manager.login_view =" auth.login ", points to the login screen that is redirected when you try to access it, even though it has @ login_required.

For example, if you try to go to \ logout while you are not logged in, the login function defined in views.py will be called (since the login screen is rendered by the return of the function, go to that screen. Masu).

User database

The table is defined in models.py. I keep my username, email address, and hashed password.

As an aside, pressing the "Forgot your password?" Button doesn't tell you your password because it doesn't remain in the database in the first place, so you can't tell it.

models.py



from werkzeug.security import generate_password_hash, check_password_hash
from flask_login  import UserMixin

from myproject import db, login_manager

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

class User(db.Model, UserMixin):

    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key = True)
    username = db.Column(db.String(64), unique=True, index=True)
    email = db.Column(db.String(64), unique=True, index=True)
    password_hash = db.Column(db.String(128))

    def __init__(self, email, username, password):
        self.email = email
        self.username = username
        self.password_hash = generate_password_hash(password)

    def check_password(self,password):
        return check_password_hash(self.password_hash, password)

Sign-up process

It is intended for general user sign-up. The user

Enter the.

First is the sign-up form.

forms.py


from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired,Email,EqualTo

class SignupForm(FlaskForm):
    email = StringField('Email', validators=[DataRequired(),Email()])
    username = StringField('Username', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired(), EqualTo('pass_confirm', message='Passwords Must Match!')])
    pass_confirm = PasswordField('Confirm password', validators=[DataRequired()])
    submit = SubmitField('Sign Up')

Next is internal processing. The user name and e-mail address must not overlap in the database, so if they match in the database, an error will be issued and you will be asked to enter them again.

If you sign up successfully, you will be taken to the login form.

views.py


from flask import (Blueprint, render_template,
                     redirect, url_for, request,
                     flash, abort)
from flask_login import login_user, login_required, logout_user, current_user
from werkzeug.security import generate_password_hash, check_password_hash

import os
import sys

sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

from myproject import db, app
from myproject.models import User
from myproject.auth.forms import LoginForm, SignupForm

auth_blueprint = Blueprint('auth',
                           __name__,
                           template_folder='templates/auth')

@auth_blueprint.route('/signup', methods=['GET', 'POST'])
def signup():
    form = SignupForm()

    if form.validate_on_submit():

        if User.query.filter_by(email=form.email.data).first():
            flash('Email has been registered already!')
            redirect(url_for('auth.signup'))


        elif User.query.filter_by(username=form.username.data).first():
            flash('Username has been registered already!')
            redirect(url_for('auth.signup'))

        else:
            user = User(email=form.email.data,
                    username=form.username.data,
                    password=form.password.data)

            db.session.add(user)
            db.session.commit()
            flash('Now You can login!')
            return redirect(url_for('auth.login'))

    return  render_template('signup.html', form=form)

Login process

It is assumed that a general user will log in.

The user enters their email address and password. It is described in the same file as the sign-up form.

forms.py


from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired,Email,EqualTo

class LoginForm(FlaskForm):
    email = StringField('Email', validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    submit = SubmitField('Log In')


class SignupForm(FlaskForm):
    email = StringField('Email', validators=[DataRequired(),Email()])
    username = StringField('Username', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired(), EqualTo('pass_confirm', message='Passwords Must Match!')])
    pass_confirm = PasswordField('Confirm password', validators=[DataRequired()])
    submit = SubmitField('Sign Up')

Next is internal processing. Identify the user by email address. The password is compared with the user's password hash value using the check_password function played in models.py.

If you log in successfully, you can jump to any location.

By the way, I will also leave the logout process.

views.py



from flask import (Blueprint, render_template,
                     redirect, url_for, request,
                     flash, abort)
from flask_login import login_user, login_required, logout_user, current_user
from werkzeug.security import generate_password_hash, check_password_hash

import os
import sys

sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

from myproject import db, app
from myproject.models import User
from myproject.auth.forms import LoginForm, SignupForm

auth_blueprint = Blueprint('auth',
                           __name__,
                           template_folder='templates/auth')

@auth_blueprint.route('/logout')
@login_required
def logout():
    logout_user()
    flash('You logged out!')
    return redirect(url_for('index'))

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


    form = LoginForm()
    if form.validate_on_submit():

        user = User.query.filter_by(email=form.email.data).first()

        if user is None:
            flash('something wrong!')
            redirect(url_for('auth.login'))
        elif user.check_password(form.password.data):

            login_user(user)

            flash('Logged in successfully.')

            # login => somewhere
            return redirect(url_for('somewhere'))
        else:
            pass

    return   render_template('login.html', form=form)

@auth_blueprint.route('/signup', methods=['GET', 'POST'])
def signup():
    form = SignupForm()

    if form.validate_on_submit():

        if User.query.filter_by(email=form.email.data).first():
            flash('Email has been registered already!')
            redirect(url_for('auth.signup'))


        elif User.query.filter_by(username=form.username.data).first():
            flash('Username has been registered already!')
            redirect(url_for('auth.signup'))

        else:
            user = User(email=form.email.data,
                    username=form.username.data,
                    password=form.password.data)

            db.session.add(user)
            db.session.commit()
            flash('Now You can login!')
            return redirect(url_for('auth.login'))

    return  render_template('signup.html', form=form)

Display flash () message

Flash () is received as follows.

example.html



{% with messages = get_flashed_messages() %}
  {% if messages %}
    {% for message in messages %}

      {{ message }}

    {% endfor %}
  {% endif %}
{% endwith %}

Change the display before and after the login status

You may want to change the display before and after the login status. For example, when you log in, you will see a logout button.

In this case, you can use current_user.is_authenticated for conditional branching.

example.html



{% if current_user.is_authenticated %}
    <a class="nav-item nav-link" href="#">{{ current_user.username }}</a>
    <a class="nav-item nav-link" href="{{ url_for('task.scheduletoday') }}">Today's schedule</a>
    <a class="nav-item nav-link" href="{{ url_for('task.alltasks')}}">Task list</a>
    <a class="nav-item nav-link" href="{{ url_for('auth.logout') }}">Log out</a>
{% else %}
    <a class="nav-item nav-link" href="{{ url_for('auth.login') }}">Login</a>
{% endif %}

You can do it like this.

Access restrictions while logged in

If you try to access a location that requires login without logging in, you can limit it as long as @login_required is written, but you will be able to access locations that do not require login while logged in. If this is possible, you will be in an uncertain situation where you can log in again even though you are logged in. In such a case

render_template('home.html')

not


return  redirect(url_for('somewhere')) if current_user.is_authenticated else render_template('home.html')

You can do it like this.

Database update

Once you have defined a new table, you need to update it for it to take effect.

---------------------------------------
MACOS/Linux
    $ export FLASK_APP=main.py
Windows
    $ set FLASK_APP=main.py

flask db init

#Only the first so far
----------------------------------------
flask db migrate -m "message"
flask db upgrade

You can do it like this.

Try in a local environment

python main.py

in conclusion

I have summarized the code that I have written so far that can be reused. If you have a better way to write it, please let us know.

Recommended Posts

Codes that could be reused in Flask (sign up, login)
A record that GAMEBOY could not be done in Python. (PYBOY)
Tkinter could not be imported in Python