【Flask】TODOアプリ作成その1:ログイン機能の実装ガイド

概要

Pythonのflaskフレームワークを使用して、TODOリストを管理するWEBアプリを作成したのでまとめた。
flaskのGET/POSTの方法やセッション等の扱いについて、当アプリを作成しながら振り返ることが目的。

当記事ではログイン機能まわりの作成について扱う。

 

TODOアプリの構成

TODOアプリでは利用者毎にタスク一覧が紐づく構成となる。
そのため、ログインした利用者によって表示されるタスク一覧が変わる。

以下の構成でアプリを作成する。

ER図

【flaskその1】ER図

 

全体像

【flaskその1】アプリ全体像

 

前提

以下の前提でプログラミングを行う。

・利用者の登録機能については実装しない
・タスクのカテゴリ登録機能については実装しない
・利用者のログイン状態の考慮は行わない
・HTML画面についてはchatGPTを使用して作成する

 

DBファイルの作成

まずはTODOアプリの根幹をなすDBを作成する。
DBはSQLiteを使用する。

前提

SQLiteの環境構築ができており、環境変数にPATHを通していること。

あわせて読みたい

概要 PythonでSQLite3を使用するにあたり、SQLite3の環境構築を実施したので方法についてまとめた。SQLite3実行ファイルをダウンロードして環境変数のPATHを通すところまで行っている。  […]

【SQLite】環境構築:ダウンロードから環境変数の設定まで
あわせて読みたい

概要 SQLite3のデータベースファイルを作成し、テーブル操作を行ったのでその方法についてまとめた。   前提 前提として、SQLite3の環境構築をしていること。[sitecar[…]

【SQLite】データベースファイルの作成と基本操作

 

todoデータベースの作成

todoデータベースと、TODOアプリにて使用する各種テーブルを作成する。

①DBファイルを作成するパスへ移動する

【flaskその1】DBファイル作成パス

 

②コマンドプロンプトを起動する

【flaskその1】コマンドプロンプトを起動

 

③DBファイルの作成
コマンドプロンプト


sqlite3 todo.db

【flaskその1】DB作成

 

以下のテーブルを作成する。

/* 利用者テーブル */
CREATE TABLE user (
  id INTEGER PRIMARY KEY,-- 利用者ID
  name TEXT NOT NULL, -- 名前
  password TEXT NOT NULL -- パスワード
);

/* TODOカテゴリテーブル */
CREATE TABLE todo_category (
  id INTEGER PRIMARY KEY, -- カテゴリID
  category_name TEXT NOT NULL -- カテゴリ
);

/* TODOタスクテーブル */
CREATE TABLE todo (
  id INTEGER PRIMARY KEY, -- タスクID
  title TEXT NOT NULL, -- タイトル
  content TEXT NOT NULL, -- タスク内容
  memo TEXT, -- メモ
  status INTEGER DEFAULT 0, -- ステータス
  due_date DATETIME NOT NULL, -- 期日
  category_id INTEGER, -- カテゴリID(FK)
  user_id INTEGER, -- 利用者ID(FK)
  FOREIGN KEY (category_id) REFERENCES TODO_Category(id),
  FOREIGN KEY (user_id) REFERENCES User(id)
);

 

作成完了。

【flaskその1】テーブル作成完了

 

レコード追加

userテーブルは、登録機能を作成しないので予めデータを挿入しておく。

あわせて読みたい

概要 コマンドではなくGUIでSQLiteのDBファイルを操作したので、その方法についてまとめた。A5:SQLツールをダウンロードして使用する。   前提 SQLiteの環境を構築し[…]

【SQLite】A5:SQLツールを用いたSQLite操作の基本ガイド

userテーブル

【flaskその1】userテーブル

 

ログイン機能

概要

以下の赤文字箇所について実装する。

【flaskその1】ログイン概要

 

ファイル構成

【flaskその1】ファイル構成

 

画面遷移

ログイン画面

【flaskその1】ログイン画面表示

 

入力エラーまたはログインエラーの場合

【flaskその1】ログインエラー

 

ログイン成功>TOP画面表示

【flaskその1】ログイン成功後TOP画面遷移

TOP画面に遷移した際、セッションに格納した利用者の名前を表示する。
ログアウトボタンを押下した場合、セッションをクリアしてログイン画面に遷移する。

 

詳細

models.py

 


from sqlalchemy.orm import declarative_base
from sqlalchemy import Column, Integer, String

# ベースモデル作成
Base = declarative_base()

# 利用者モデル
class User(Base):
  """ userクラス """
  # テーブル名称
  __tablename__ = "user"
  # カラム
  id = Column(Integer, primary_key=True)
  name = Column(String)
  password = Column(String)

models.pyではORマッピングするためのクラスを集約する。
今回はログイン機能を作成するため、userテーブルに紐づくuserクラスを定義した。

data_access.py

 


from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import User

def get_session():
    """ セッション情報を返却 """
    engine = create_engine('sqlite:///db/todo.db')
    Session = sessionmaker(bind=engine)
    session = Session()

    return session

def find_user(input_name, input_password):
  """ 利用者検索 """
  session = get_session()
  user = session.query(User).filter_by(name=input_name, password=input_password).first()

  return user

data_access.pyはDBまわりの操作を行う。
各種テーブル操作はこのファイルに集約する。

DBのアクセスまわりはsqlalchemyモジュールを使用した。

nameとpasswordをキーに、userテーブルを検索してその結果をuserオブジェクトにセットして返却する。
該当するレコードが存在しない場合、Noneを返却する。

 

あわせて読みたい

概要 sqlalchemyモジュールを使用してDBを操作する方法についてまとめた。 基本的なCRUD機能の記述方法について扱う。   前提 SQLiteのDB環境を構築していること。

【sqlalchemy】PythonでDB操作:SQLAlchemyの基本的な使用方法

business_logic.py

 


import data_access

def find_user(name, password):
  """ userテーブルに利用者が存在するか確認する """
  user = data_access.find_user(name, password)
  if user:
    # 名前とパスワード一致
    return True
  else:
    # 名前とパスワード不一致
    return False

business_logic.pyでは業務処理を集約する。
今回はログイン判定をするための処理を作成した。

nameとpasswordをキーにuserテーブルを検索し、その結果を判定している。
レコードの取得ができた場合、Trueを返却する。
レコードの取得ができない場合、Falseを返却する。

views.py

 


from flask import Flask, render_template, request, redirect, url_for, session
import business_logic
app = Flask(__name__)
app.secret_key = "yiYKQmFC6MTVKs5THpKkD"

@app.route("/")
def index():
    return redirect(url_for("show_login"))

@app.route("/todo_apps")
def show_login():
    """ ログイン画面表示 """
    return render_template("login.html")

@app.route("/todo_apps/login", methods=["POST"])
def login():
    # 入力値検証
    errors = []
    if not validate_login(request.form, errors):
        return render_template("login.html", errors=errors)
    
    # ログイン確認
    name = request.form["name"]
    password = request.form["password"]
    is_login = business_logic.find_user(name, password)
    if is_login:
        # ログイン成功
        session["name"] = name
        return redirect(url_for("top"))
    else:
        # ログイン失敗
        errors.append("名前またはパスワードが正しくありません。")
        return render_template("login.html", errors=errors)

@app.route("/todo_apps/top")
def top():
    """ TODOタスク一覧画面表示 """
    return render_template("top.html")

@app.route("/logout")
def logout():
    """ ログアウト """
    session.clear()
    return redirect(url_for("show_login"))

def validate_login(form, errors):
    """ ログイン情報の入力チェックを行う """
    is_valid = True
    name = form.get("name")
    password = form.get("password")
    if not name or not password:
        errors.append("名前またはパスワードを入力してください。")
        is_valid = False

    return is_valid


if __name__ == '__main__':
    # 8080ポートで起動
    app.run(port=8080, debug=True)

views.pyではルーティング操作を集約する。
リクエストに応じて業務処理を呼び出し、その結果をレスポンスとして画面に返却する。

また、業務処理を呼び出す前に入力チェック等も行う。

 

login.html

 


<!DOCTYPE html>
<html>

<head>
  <title>ログイン</title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css">
</head>

<body>
  <div class="container">
    <div class="row justify-content-center mt-5">
      <div class="col-md-8">
        <div class="card">
          <div class="card-header text-center">
            <h4>ログイン</h4>
          </div>
          <div class="card-body">
            {% for msg in errors %}
            <div id="error-message" class="text-danger mb-3">{{ msg }}</div>
            {% endfor %}
            <form method="post" action="{{ url_for('login') }}">
              <div class="form-group mb-3">
                <label for="username">名前</label>
                <input type="text" class="form-control" name="name" placeholder="名前">
              </div>
              <div class="form-group">
                <label for="password">パスワード</label>
                <input type="password" class="form-control" name="password" placeholder="パスワード">
              </div>
              <button type="submit" class="btn btn-primary mt-3">ログイン</button>
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>
</body>

</html>

ベースはchatGPTにより作成した。
bootstrapを使用したスタインリングを行っている。

{% for msg in errors %}
<div id="error-message" class="text-danger mb-3">{{ msg }}</div>
{% endfor %}

上記はエラーメッセージが存在する場合、表示している。
ログインボタンを押下した後、エラーが存在する場合errorsリストにエラーメッセージが格納されて返却される。

 

<form method="post" action="{{ url_for("login") }}">

HTMLテンプレート側でurl_forメソッドを使用して、views.pyのloginメソッドへPOSTリクエストしている。

 

top.html

 


<!DOCTYPE html>
<html>

<head>
  <title>TODOトップ</title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css">
</head>

<body>
  <div class="container mt-5">
    <h2 class="text-center">{{ session["name"] }} さんのタスク一覧</h2> 
    <a href="{{ url_for('logout') }}"
      class="btn btn-primary">ログアウト</a>
    <div class="card mt-4">
      <div class="card-body">
        <table class="table table-striped">
          <thead>
            <tr>
              <th>タイトル</th>
              <th>カテゴリ</th>
              <th>タスク名</th>
              <th>期日</th>
              <th>ステータス</th>
            </tr>
          </thead>
          <tbody> <!-- タスク一覧表示領域 --> </tbody>
        </table>
      </div>
    </div>
  </div>
</body>

</html>

以下はログイン後のセッションに格納された利用者名を表示させる記述。

<h2 class="text-center">{{ session["name"] }} さんのタスク一覧</h2>

 

スポンサーリンク