概要
Pythonのflaskフレームワークを使用して、TODOリストを管理するWEBアプリを作成したのでまとめた。
flaskのGET/POSTの方法やセッション等の扱いについて、当アプリを作成しながら振り返ることが目的。
当記事ではログイン機能まわりの作成について扱う。
TODOアプリの構成
TODOアプリでは利用者毎にタスク一覧が紐づく構成となる。
そのため、ログインした利用者によって表示されるタスク一覧が変わる。
以下の構成でアプリを作成する。
ER図
全体像
前提
以下の前提でプログラミングを行う。
・タスクのカテゴリ登録機能については実装しない
・利用者のログイン状態の考慮は行わない
・HTML画面についてはchatGPTを使用して作成する
DBファイルの作成
まずはTODOアプリの根幹をなすDBを作成する。
DBはSQLiteを使用する。
前提
SQLiteの環境構築ができており、環境変数にPATHを通していること。
概要 PythonでSQLite3を使用するにあたり、SQLite3の環境構築を実施したので方法についてまとめた。SQLite3実行ファイルをダウンロードして環境変数のPATHを通すところまで行っている。 […]
概要 SQLite3のデータベースファイルを作成し、テーブル操作を行ったのでその方法についてまとめた。 前提 前提として、SQLite3の環境構築をしていること。[sitecar[…]
todoデータベースの作成
todoデータベースと、TODOアプリにて使用する各種テーブルを作成する。
①DBファイルを作成するパスへ移動する
②コマンドプロンプトを起動する
③DBファイルの作成
コマンドプロンプト
sqlite3 todo.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) );
作成完了。
レコード追加
userテーブルは、登録機能を作成しないので予めデータを挿入しておく。
概要 コマンドではなくGUIでSQLiteのDBファイルを操作したので、その方法についてまとめた。A5:SQLツールをダウンロードして使用する。 前提 SQLiteの環境を構築し[…]
userテーブル
ログイン機能
概要
以下の赤文字箇所について実装する。
ファイル構成
画面遷移
ログイン画面
入力エラーまたはログインエラーの場合
ログイン成功>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環境を構築していること。
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>