フォルダ・ファイル構成
以下のコマンドを実行し、必要なパッケージをインストールします。
pip install flask sqlalchemy flask-sqlalchemy flask-wtf mysql-connector-python pymysql
          - project
              
- controllers
                    
- content.py
 - user.py
 
 - models
                  
- user.py
 
 - static
                  
- css
                        
- style.css
 
 - js
                        
- scripts.js
 
 
 - css
                        
 - templates
                  
- content
                      
- index.html
 
 - share
                      
- layout.html
 
 - user
                      
- login.html
 
 
 - content
                      
 - __init__.py
 - app.py
 - config.py
 - database.py
 
 - controllers
                    
 
__init__.py
__init__.pyを置くことで、そのディレクトリ全体をFlaskアプリケーションとして定義します。
__init__.py
from flask import Flask, redirect, url_for, session, flash
from .database import db
from .config import *
from .controllers.user import user
from .controllers.content import content
from flask_wtf.csrf import CSRFProtect
from flask_login import LoginManager, login_required, logout_user, current_user
from .models.user import User
# <-- comment.login-manager
# flask-login の初期化
# クラスの作成
login_manager = LoginManager()
# /comment.login-manager -->
# <-- comment.login_required
# リダイレクトするビュー関数と、エラーメッセージを定義
# ビュー関数 login にリダイレクトするように指定する
login_manager.login_view = 'user.login'
# 未ログインユーザーにメッセージ表示
def localize_callback(*args, **kwarg):
    return 'このページにアクセスするには、ログインが必要です'
login_manager.localize_callback = localize_callback
# /comment.login_required -->
@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))
def create_app():
    app = Flask(__name__)
    # session を使う際に SECRET_KEY を設定
    app.config['SECRET_KEY'] = 'secret_key'
    # CSRF 対策
    csrf = CSRFProtect(app)
    app.config.from_object(config.Config)
    db.init_app(app)
    app_name = [
        user,
        content
    ]
    for i in app_name:
        app.register_blueprint(i)
    
    login_manager.init_app(app)
    # ログアウト処理
    @app.route('/logout')
    @login_required
    def logout():
        logout_user()
        flash('ログアウトしました。') 
        return redirect(url_for('user.login'))
    
    return app
          register_blueprint()は、アプリを登録するメソッドです。
app.py
from . import create_app
app = create_app()
if __name__ == '__main__':
    app.run()
          config.py
class SystemConfig:
    DEBUG = True
    
    SQLALCHEMY_DATABASE_URI = 'mysql://{user}:{password}@{host}/{db_name}?charset=utf8'.format(**{
        'user': 'root',
        'password': 'password',
        'host': 'localhost',
        'db_name': 'sample' # データベース名
    })
SQLALCHEMY_TRACK_MODIFICATIONS = "false"
Config = SystemConfig
          database.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def init_db(app):
    db.init_app(app)
          models/user.py
from .. import db
from flask_login import UserMixin
class User(UserMixin, db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True)
    mail = db.Column(db.String(255), nullable=False)
    password = db.Column(db.Integer, nullable=False)
          controller
controllers/content.py
from flask import Blueprint, render_template, request, redirect
from flask_login import login_required
content = Blueprint("content", __name__, url_prefix='/content')
@content.route('/')
@login_required
def index():
    return render_template('content/index.html')
          controllers/user.py
from flask import Blueprint, render_template, request, redirect, url_for, session, flash
from sqlalchemy.exc import NoResultFound, MultipleResultsFound
from flask_login import login_user
from ..models.user import User
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
import bcrypt
user = Blueprint("user", __name__, url_prefix='/user')
class LoginForm(FlaskForm):
    mail = StringField('mail')
    password = PasswordField('Password')
    submit = SubmitField()
@user.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    # POST の処理
    if request.method == "POST":
        mail = request.form.get('mail')
        password = request.form.get('password')
        try:
            # Userテーブルから mail に一致するユーザを取得
            user = User.query.filter_by(mail=mail).one_or_none()
            
            if user is None:
                flash('ログインできませんでした。') 
                return redirect(url_for('user.login'))
            
            if bcrypt.checkpw(password.encode("utf8"), user.password.encode("utf8")):
                # ユーザー情報をセッションに格納
                login_user(user)
                return redirect('/content')
            else:
                flash('ログインできませんでした。') 
                return redirect(url_for('user.login'))
        except NoResultFound:
            flash('データがありません。') 
            return redirect(url_for('user.login'))
        except MultipleResultsFound:
            flash('データが複数あります。') 
            return redirect(url_for('user.login'))
    else:
        # GET の処理
        return render_template('user/login.html', form = form)
          static
static/css/style.css
li {color: red;}
          static/js/scripts.js
          templates
templates/content/index.html
{%- extends "./share/layout.html" %}
{%- block content %}
<h1>content/index</h1>
<p><a href="/logout">ログアウト</a></p>
<p>{{ message }}</p>
{% if current_user.is_authenticated %}
  <p>Hello, {{ current_user.mail }}.</p>
{% endif %}
{%- endblock %}
          templates/share/layout.html
<!doctype html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <title></title>
        <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}" />
        <script src="{{ url_for('static', filename='js/scripts.js') }}"></script>
    </head>
    <body>
      <div class="content">
        <div id="main">
        {%- block content %} {% endblock %}
        </div>
      </div>
    </body>
</html>
          templates/user/login.html
{%- extends "./share/layout.html" %}
{%- block content %}
<h1>user/login</h1>
<ul>
  {% with messages = get_flashed_messages() %}
    {% for message in messages %}
      <li>{{ message }}</li>
    {% endfor %}
  {% endwith %}
</ul>
<form action="/user/login" method="post">
  {{ form.hidden_tag() }}
  <p>{{ form.mail.label }} {{ form.mail() }}</p>
  <p>{{ form.password.label }} {{ form.password() }}</p>
  <p>{{ form.submit() }}</p>
</form>
{%- endblock %}