【Flask】ToDoアプリを作ろう|Chapter7-4

一つ前のページではメモ帳アプリを作成しました。
今回は ToDoアプリ を作成していきましょう。
Chapter1:Flask入門編
Chapter2:Jinja2入門編
Chapter3:フィルター編
Chapter4:フォーム編
Chapter5:データベース編
Chapter6:エラーハンドリングとデバッグ編
Chapter7:アプリ開発編
・Chapter7-1:おみくじアプリを作ろう
・Chapter7-2:Blueprintを使おう
・Chapter7-3:メモ帳アプリを作ろう
・Chapter7-4:ToDoアプリを作ろう ◁今回はここ
Webアプリケーションの開発において、ユーザーごとに情報を管理する仕組みは欠かせません。
今回作成する「ToDoアプリ」は、ユーザーごとにタスクを管理し、それぞれのタスクの完了状態を切り替えることができる、本格的なWebアプリケーションの一例です。
この章では、前章で作成したメモ帳アプリをさらに発展させ、ユーザー認証機能を持つToDoアプリを開発します。
また、Flaskでユーザー管理を行うための拡張機能「Flask-Login」の使い方も学びます。
ログインしていないユーザーはタスク管理ページにアクセスできず、ログインしたユーザーのみが自分のタスクを追加・更新・削除・完了管理できるようになります。
この章を学習することで、以下のスキルを身につけることができます:
- Flask-Loginを使ったユーザー認証の実装
- ログイン・ログアウト機能の構築
- ログイン必須のルーティング設定
- ログイン中のユーザー情報の取得と活用
- ユーザーごとにToDoタスクを管理する方法
- タスクの完了状態の切り替えと保存
- アプリケーションの構成を分割してモジュール化する方法
ToDoアプリは実用的かつ応用の効く学習題材です。
ぜひこの章で、実践的なFlaskアプリ開発の力を一段と高めていきましょう。
- Flask開発を Stream Deck でボタン化しよう!
-
Flaskは非常に軽量かつシンプルなフレームワークですが、それゆえに定型作業が多く、開発は単調な作業の連続になりがちです。
それこそがFlaskのメリットであり、習得難易度が低い理由でもありますが、単調な作業は退屈で、ミスも起こりやすいでしょう。
そこで役に立つのが Stream Deck 。
このような定型手順が多い作業を “ボタン化” することで視覚化。
圧倒的に 効率的 かつ ストレスフリー な開発環境が簡単に手に入ります↓↓
あわせて読みたいFlask開発をStreamDeckでボタン化しようあわせて読みたいプログラマー向けStream Deckの選び方|初心者でも失敗しないモデル比較ガイド
Flask-Loginの導入と設定

Flask-Loginとは何か?
Flask-Loginは、Flaskアプリケーションにログイン機能を追加するための拡張機能です。
ユーザーのログイン状態をセッションで管理し、以下のような処理を簡単に実装できます。
- ユーザーのログイン状態の保存・確認
- ログインが必要なページへのアクセス制限
- ログイン中のユーザー情報の取得
- ログアウト処理
Flask-Loginのインストール方法
Flask-LoginはPythonのパッケージとして提供されており、以下のコマンドでインストールできます:
pip install flask-login
仮想環境を使用している場合は、仮想環境を有効化した状態で上記のコマンドを実行してください。
Flask-Loginの使い方
Flask-Loginを使うためには、次の4つの準備が必要です:
LoginManagerオブジェクトの生成とFlaskアプリへの登録- ユーザークラスに
UserMixinを継承 user_loader関数を定義し、IDからユーザーを取得- 必要な関数(
login_user,logout_user,current_user,@login_required)の活用
from flask_login import LoginManager # インポート
# LoginManagerの作成とアプリ登録
login_manager = LoginManager()# インスタンス生成
login_manager.init_app(app) # Flaskアプリに登録
# 未ログイン時のリダイレクト先設定
login_manager.login_view = 'auth.login'
# ユーザークラスの読み込み関数
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id)) # ユーザーIDからDB検索詳しい使い方は、実際にToDoアプリを作成しながら見ていきましょう。
Flask ToDoアプリの構成とファイル紹介
このToDoアプリでは、Flaskアプリケーションをモジュール構造に分けて管理し、認証処理とタスク管理機能を明確に分離しています。以下にファイル構成を示します。
todo_app/ ├── app/ │ ├── __init__.py # アプリケーションの初期化 │ ├── models.py # データベースのモデル定義 │ ├── forms.py # フォーム定義 │ ├── auth/ │ │ ├── __init__.py │ │ ├── routes.py # ユーザー登録・ログインなどのルーティング │ │ └── templates/auth/ │ │ ├── login.html # ログイン画面のテンプレート │ │ └── register.html # 登録画面のテンプレート │ └── todo/ │ ├── __init__.py │ ├── routes.py # タスク関連のルーティング │ └── templates/todo/ │ ├── list.html # タスク一覧表示テンプレート │ ├── form.html # タスク入力フォーム │ └── base.html # テンプレートのベース ├── config.py # アプリケーション設定 └── run.py # アプリケーション起動用スクリプト
app/ディレクトリ内に全てのアプリケーションロジックを配置し、パッケージとして管理。auth/とtodo/をそれぞれBlueprintとして定義することで、認証系とタスク系のルーティングを分離。- テンプレートは機能ごとにディレクトリを分け、視認性と保守性を向上。
この構造により、拡張性と保守性の高いFlaskアプリケーションを実現できます。

Flaskアプリの起動ファイルと設定ファイル
まずはFlaskアプリを実行するための起動スクリプト(run.py)と、アプリ全体の設定を一元管理する構成ファイル(config.py)を作成します。
以下のコードを書き移し、ToDoファイル直下に保存して下さい。
Flaskアプリの起動を司る run.py
このファイルはFlaskアプリケーションを起動するためのエントリーポイントです。
Flaskの開発サーバーを立ち上げる際にこのファイルを使います。
from app import create_app # アプリケーションのインスタンスを生成する関数をインポート
app = create_app() # アプリケーションのインスタンスを作成(ファクトリーパターン)
if __name__ == "__main__":
# このファイルが直接実行されたときだけ、開発サーバーを起動
app.run(debug=True) # デバッグモード有効:自動リロードや詳細エラーメッセージが有効になるcreate_app()はアプリケーション本体を構築する関数で、モジュールの中で定義されています。if __name__ == "__main__"という構文は、他のモジュールからインポートされた場合には実行されず、スクリプトとして直接実行されたときだけ処理されるようにします。debug=Trueにより、コードの変更が自動で反映されるなど、開発中に便利な機能が有効になります。
アプリの設定を管理する config.py
このファイルはFlaskアプリケーションの設定値をクラスとして定義します。
セキュリティキーやデータベースの接続設定など、アプリケーション全体で使用する設定を一元的に管理します。
import os # OSの機能を使うための標準モジュール
# このファイルのディレクトリを基準にパスを作成
basedir = os.path.abspath(os.path.dirname(__file__))
class Config:
# セッション管理などに使われる秘密鍵(環境変数がなければデフォルト値を使用)
SECRET_KEY = os.environ.get('SECRET_KEY', 'dev_secret_key')
# データベースのURI(環境変数がなければSQLiteを使用)
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URI') or \
'sqlite:///' + os.path.join(basedir, 'todo_app.db')
# SQLAlchemyのイベント通知機能を無効化(パフォーマンス向上のため)
SQLALCHEMY_TRACK_MODIFICATIONS = FalseSECRET_KEYはセッションの暗号化やCSRF対策に使われる重要な値です。SQLALCHEMY_DATABASE_URIは使用するデータベースの場所を示すURIです。環境変数が未設定の場合、SQLiteを使ってローカルにtodo_app.dbを作成します。SQLALCHEMY_TRACK_MODIFICATIONSはSQLAlchemyのオーバーヘッドを減らすため、追跡機能をオフにしています。
アプリケーションの本体構成とデータ管理
Flaskアプリケーションを初期化するコード(__init__.py)、フォームの定義(forms.py)、データベースモデルの設計(models.py)を追加しましょう。
以下の3つのコードをToDo/appフォルダ直下に保存してください。
アプリケーションの初期化を行う __init__.py
このファイルはアプリのエンジン部分です。
Flaskインスタンスを作成し、各種拡張機能(データベース、ログイン管理など)を初期化、ルーティングの定義やBlueprintの登録を行います。
from flask import Flask, redirect # Flask本体とリダイレクト機能をインポート
from flask_sqlalchemy import SQLAlchemy # SQLAlchemyを使ったDB操作のための拡張
from flask_login import LoginManager # ログイン管理を行う拡張
from config import Config # アプリ設定が書かれたconfig.pyをインポート
# 拡張機能の初期化(まだFlaskアプリには関連付けない)
db = SQLAlchemy()
login = LoginManager()
login.login_view = 'auth.login' # ログインが必要なページに未ログインでアクセスしたときのリダイレクト先
def create_app():
app = Flask(__name__) # Flaskアプリケーションのインスタンスを生成
app.config.from_object(Config) # Configクラスから設定を読み込む
db.init_app(app) # アプリにSQLAlchemyを登録
login.init_app(app) # アプリにLoginManagerを登録
# アプリケーションコンテキスト内でルーティングなどを定義
with app.app_context():
# 認証機能とToDo機能のBlueprintをインポート
from app.auth import bp as auth_bp
from app.todo import bp as todo_bp
# BlueprintをURLプレフィックス付きで登録
app.register_blueprint(auth_bp, url_prefix='/auth')
app.register_blueprint(todo_bp, url_prefix='/todo')
# データベース内に必要なテーブルを全て作成
db.create_all()
# ルートURLにアクセスされたらToDoリスト画面にリダイレクト
@app.route('/')
def root():
return redirect('/todo/')
return app # 初期化済みのFlaskアプリケーションを返すBlueprintを用いることで、機能ごとのモジュール分割が可能になります。- アプリコンテキスト内で
db.create_all()を呼び、初期化時に必要なテーブルを作成。 root()関数により、/にアクセスすると/todo/に自動リダイレクトされます。
ユーザー登録・ログイン・ToDo登録フォームを定義する forms.py
このファイルはユーザー登録やログイン、ToDo作成のためのフォームを Flask-WTF を用いて定義します。
from flask_wtf import FlaskForm # Flask-WTFでのフォーム作成を支援
from wtforms import StringField, PasswordField, BooleanField, SubmitField # フォームのフィールドをインポート
from wtforms.validators import DataRequired, Length, EqualTo, ValidationError # 入力バリデーション
from app.models import User # ユーザー重複チェックのためにUserモデルをインポート
# 新規ユーザー登録フォーム
class RegistrationForm(FlaskForm):
username = StringField('ユーザー名', validators=[
DataRequired(), # 入力必須
Length(min=1, max=64) # 文字数制限
])
password = PasswordField('パスワード', validators=[
DataRequired(),
Length(min=6) # パスワードは6文字以上
])
password2 = PasswordField('パスワード(確認)', validators=[
DataRequired(),
EqualTo('password') # 一致しているか確認
])
submit = SubmitField('登録')
# ユーザー名が既に使用されていないかチェックする独自バリデーション
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user:
raise ValidationError('このユーザー名は既に使用されています。')
# ログインフォーム
class LoginForm(FlaskForm):
username = StringField('ユーザー名', validators=[DataRequired()])
password = PasswordField('パスワード', validators=[DataRequired()])
remember_me = BooleanField('ログイン状態を保持する') # セッションを保持するかどうか
submit = SubmitField('ログイン')
# ToDo作成・編集用フォーム
class TodoForm(FlaskForm):
title = StringField('タイトル', validators=[
DataRequired(),
Length(max=120) # タイトルの最大長
])
submit = SubmitField('保存')- バリデーション付きのフォームを簡潔に定義可能。
validate_usernameメソッドを使って重複ユーザー名のチェックを実装。EqualToバリデータで2つのパスワードが一致することを検証。
ユーザーとToDoのデータモデルを定義する models.py
このファイルでは、ユーザーとToDoのデータ構造を定義します。
ORM(SQLAlchemy)を使って、Pythonのクラスとしてデータベースモデルを構築しています。
from app import db, login # アプリで使うDBとログインマネージャをインポート
from flask_login import UserMixin # Flask-Loginが必要とするユーザークラスのベース
from werkzeug.security import generate_password_hash, check_password_hash # パスワードのハッシュ化・検証
# ユーザーモデル
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True) # 主キー
username = db.Column(db.String(64), unique=True, nullable=False) # 一意のユーザー名
password_hash = db.Column(db.String(128), nullable=False) # パスワードはハッシュで保存
todos = db.relationship('Todo', backref='author', lazy='dynamic') # ユーザーが所有するToDoの一覧
# パスワードをハッシュ化して保存
def set_password(self, password):
self.password_hash = generate_password_hash(password)
# 入力されたパスワードとハッシュが一致するか確認
def check_password(self, password):
return check_password_hash(self.password_hash, password)
# ログイン時に使われるユーザー読み込み関数
@login.user_loader
def load_user(id):
return User.query.get(int(id)) # ユーザーIDからUserインスタンスを取得
# ToDoモデル
class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120), nullable=False) # タイトルは必須
done = db.Column(db.Boolean, default=False) # 完了済みかどうか
user_id = db.Column(db.Integer, db.ForeignKey('user.id')) # 紐づくユーザーIDUserMixinを使うことでFlask-Loginに必要なメソッドを自動的に提供。- パスワードは
ハッシュ化して保存し、認証時にはcheck_passwordで照合。 UserとTodoの間には1対多のリレーション(User.todos)が存在。load_user関数により、セッション管理時にユーザーをDBから取得可能にしています。
ユーザー認証機能を実装しよう 〜 ログインと登録の仕組み 〜
ToDoアプリにログイン機能を追加することで、「誰がどのタスクを作成したのか」を管理できるようになります。
本節では、Flaskにおけるユーザー認証機能の基本的な実装方法を学びます。
以下のPythonファイルはtodo/app/authフォルダ直下に、htmlファイルはtodo/app/auth/templates/todoの中に保存して下さい。
Blueprintの定義 __init__.py
このファイルでは、auth モジュールとしてBlueprintを定義しています。
BlueprintはFlaskアプリをモジュール単位で構成するための仕組みです。
from flask import Blueprint
# 'auth'という名前のBlueprintを作成。テンプレートの格納フォルダも指定。
bp = Blueprint('auth', __name__, template_folder='templates')
# ルーティング設定をインポートして有効化
from app.auth import routesこのコードで auth というBlueprintを定義しています。
これによりアプリケーション本体と独立して認証関連の処理を切り出して構成できます。
最後のインポート文はBlueprintにルーティング処理を読み込ませています。
認証のルーティング routes.py
このファイルでは、ユーザー登録・ログイン・ログアウトに関するルーティングを定義しています。
from flask import render_template, redirect, url_for, flash, request # 必要なFlaskの関数とモジュールをインポート
from flask_login import login_user, logout_user, login_required # Flask-Loginの機能:ユーザーのログイン・ログアウト管理
from app.auth import bp # Blueprintのインスタンスをインポート
from app import db # データベース操作に必要なインスタンス
from app.models import User # ユーザーモデルとフォームクラスをインポート
from app.forms import RegistrationForm, LoginForm
# 登録ページへのルート(/register)
@bp.route('/register', methods=['GET','POST'])
def register():
form = RegistrationForm() # フォームのインスタンス作成
# フォームがPOSTされてバリデーションを通過したとき
if form.validate_on_submit():
# 入力されたユーザー名をもとに新しいUserオブジェクトを作成
user = User(username=form.username.data)
# パスワードはハッシュ化して保存(セキュリティ対策)
user.set_password(form.password.data)
# データベースに追加して保存
db.session.add(user)
db.session.commit()
# フラッシュメッセージを表示(1回だけ表示される通知)
flash('登録が完了しました。ログインしてください。')
# ログインページへリダイレクト
return redirect(url_for('auth.login'))
# 初期表示またはバリデーション失敗時、登録フォームを表示
return render_template('auth/register.html', form=form)
# ログインページへのルート(/login)
@bp.route('/login', methods=['GET','POST'])
def login():
form = LoginForm() # フォームのインスタンス作成
# フォームがPOSTされてバリデーションを通過したとき
if form.validate_on_submit():
# 入力されたユーザー名を持つユーザーをデータベースから取得
user = User.query.filter_by(username=form.username.data).first()
# パスワードも含めて正しいか確認
if user and user.check_password(form.password.data):
# ログイン状態にする(remember_meはセッションの永続化用)
login_user(user, remember=form.remember_me.data)
# ログイン後にリダイレクトするURLを取得(指定がなければToDoページ)
next_page = request.args.get('next') or url_for('todo.index')
return redirect(next_page)
# ログイン失敗時にメッセージ表示
flash('ユーザー名またはパスワードが違います。')
# 初期表示または失敗時にフォームを再表示
return render_template('auth/login.html', form=form)
# ログアウト処理(/logout)
@bp.route('/logout')
@login_required # ログインしていないとアクセスできない
def logout():
logout_user() # セッションからログアウト
return redirect(url_for('auth.login')) # ログインページへリダイレクト/register:登録フォーム送信時に新しいユーザーを作成してデータベースに保存します。/login:ログイン情報が正しければセッションを開始し、ToDoリストページにリダイレクトします。/logout:ログアウトしてログインページにリダイレクトします。
ログインページ login.html
ログインフォームを表示するテンプレートです。
{% extends 'todo/base.html' %} <!-- 共通レイアウトのbase.htmlを継承 -->
{% block content %}
<h2>ログイン</h2>
<form method="post"> <!-- フォーム送信はPOSTメソッド -->
{{ form.hidden_tag() }} <!-- CSRFトークンの挿入(セキュリティ対策) -->
<!-- ユーザー名入力 -->
<p>{{ form.username.label }}<br>{{ form.username(size=32) }}</p>
<!-- パスワード入力 -->
<p>{{ form.password.label }}<br>{{ form.password(size=32) }}</p>
<!-- ログイン状態を保持するチェックボックス -->
<p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
<!-- 送信ボタン -->
<p>{{ form.submit() }}</p>
</form>
{% endblock %}Flask-WTFを使って自動生成されたフォームを表示します。
remember_me はログイン状態を保持するオプションです。テンプレートは base.html を継承して共通レイアウトを利用しています。
新規登録ページ register.html
ユーザー登録用のフォームテンプレートです。
{% extends 'todo/base.html' %} <!-- 共通テンプレートを継承 -->
{% block content %}
<h2>新規登録</h2>
<form method="post"> <!-- POSTで送信 -->
{{ form.hidden_tag() }} <!-- CSRFトークン -->
<!-- ユーザー名入力 -->
<p>{{ form.username.label }}<br>{{ form.username(size=32) }}</p>
<!-- パスワード入力 -->
<p>{{ form.password.label }}<br>{{ form.password(size=32) }}</p>
<!-- パスワード確認 -->
<p>{{ form.password2.label }}<br>{{ form.password2(size=32) }}</p>
<!-- 送信ボタン -->
<p>{{ form.submit() }}</p>
</form>
{% endblock %}password2 はパスワード確認用フィールドです。form.hidden_tag() によってCSRF対策が自動的に挿入されます。

ToDo機能を構築する 〜 Blueprintとルーティング、テンプレート連携の基本 〜
の章では、todo/ ディレクトリの中で完結する「ToDo機能」の構成要素を一つずつ確認していきます。
以下のPythonファイルはtodo/app/todoフォルダ直下に、htmlファイルはtodo/app/todo/templates/todoの中に保存して下さい。
Blueprintを定義するファイル __init__.py
このファイルでは、todo モジュールの Blueprint を作成して Flask アプリに登録する準備をします。
Blueprint により、アプリケーションを複数のモジュールに分割して管理できます。
from flask import Blueprint
# 'todo' という名前のBlueprintを作成。テンプレートフォルダはこのディレクトリ内の 'templates'
bp = Blueprint('todo', __name__, template_folder='templates')
# このBlueprintにルーティングを登録するためにroutesモジュールを読み込む
from app.todo import routesBlueprintはFlaskでアプリを分割管理するための仕組みです。template_folder='templates'により、templates/todo/内のテンプレートがこのBlueprintで使えるようになります。from app.todo import routesにより、このBlueprintに関連するルートが読み込まれます。
ToDoアプリのルーティング処理 routes.py
ToDoの一覧表示、追加、編集、削除、状態切り替え(完了/未完了)といった基本機能を提供するルーティング群です。
全ルートはログインユーザーのみに制限されています。
from flask import render_template, redirect, url_for, flash, request
from flask_login import login_required, current_user
from app.todo import bp # このBlueprintに属する
from app import db
from app.models import Todo
from app.forms import TodoForm
# ToDo一覧表示
@bp.route('/')
@login_required # ログインしていないとアクセスできない
def index():
todos = current_user.todos.all() # 現在のユーザーのタスクを取得
return render_template('todo/list.html', todos=todos)
# タスク追加
@bp.route('/add', methods=['GET','POST'])
@login_required
def add():
form = TodoForm() # フォームのインスタンスを生成
if form.validate_on_submit(): # POSTかつバリデーション成功時
todo = Todo(title=form.title.data, author=current_user) # タスク作成
db.session.add(todo) # データベースに追加
db.session.commit() # コミット
flash('タスクを追加しました。') # メッセージ表示
return redirect(url_for('todo.index')) # 一覧へリダイレクト
return render_template('todo/form.html', form=form, title='新しいタスク')
# タスク編集
@bp.route('/<int:id>/edit', methods=['GET','POST'])
@login_required
def edit(id):
todo = Todo.query.get_or_404(id) # IDでタスク取得(存在しないと404)
if todo.author != current_user: # 他人のタスクにはアクセス不可
flash('アクセス権がありません。')
return redirect(url_for('todo.index'))
form = TodoForm(obj=todo) # フォームに初期値を設定
if form.validate_on_submit():
todo.title = form.title.data # タイトルを更新
db.session.commit()
flash('タスクを更新しました。')
return redirect(url_for('todo.index'))
return render_template('todo/form.html', form=form, title='タスク編集')
# タスク削除
@bp.route('/<int:id>/delete', methods=['POST'])
@login_required
def delete(id):
todo = Todo.query.get_or_404(id)
if todo.author != current_user:
flash('アクセス権がありません。')
else:
db.session.delete(todo)
db.session.commit()
flash('タスクを削除しました。')
return redirect(url_for('todo.index'))
# タスク状態のトグル(完了/未完了)
@bp.route('/<int:id>/toggle', methods=['POST'])
@login_required
def toggle(id):
todo = Todo.query.get_or_404(id)
if todo.author == current_user:
todo.done = not todo.done # 状態を反転
db.session.commit()
flash('タスク状態を変更しました。')
return redirect(url_for('todo.index'))index():現在ログイン中のユーザーのタスク一覧を取得して表示。add():フォームを通じて新しいタスクを作成。edit(id):指定IDのタスクを編集。所有者以外は編集不可。delete(id):指定IDのタスクを削除。所有者以外は削除不可。toggle(id):完了/未完了の状態を切り替える。
すべての操作は @login_required デコレータにより認証が必要です。
タスク入力フォームのテンプレート form.html
このテンプレートは、ToDoの追加や編集を行うフォームを表示するものです。Flask-WTF のフォームオブジェクトを利用しています。
{% extends 'todo/base.html' %} <!-- 共通のベーステンプレートを継承 -->
{% block title %} - {{ title }}{% endblock %}
{% block content %}
<h2>{{ title }}</h2>
<form method="post">
{{ form.hidden_tag() }} <!-- CSRF対策などの隠しフィールド -->
<p>
{{ form.title.label }}<br>
{{ form.title(size=64) }} <!-- 入力欄 -->
{% for e in form.title.errors %}
<span style="color:red">{{ e }}</span> <!-- バリデーションエラー表示 -->
{% endfor %}
</p>
<p>{{ form.submit() }}</p> <!-- 送信ボタン -->
</form>
{% endblock %}form.hidden_tag()によりCSRFトークンなどの非表示フィールドが挿入されます。form.titleはモデルに紐づいたタイトル入力欄で、バリデーションエラーがあれば下に表示されます。form.submit()により送信ボタンが生成されます。
ToDo一覧を表示するテンプレート list.html
このテンプレートは、ログインユーザーのタスク一覧を表示し、それぞれの完了状態の切り替え、編集、削除が行えるようになっています。
{% extends 'todo/base.html' %}
{% block title %} - ToDo一覧{% endblock %}
{% block content %}
<h2>ToDo一覧</h2>
<ul>
{% for todo in todos %}
<li>
<!-- 状態のトグルボタン -->
<form method="post" action="{{ url_for('todo.toggle', id=todo.id) }}" style="display:inline">
<button type="submit">{{ '✅' if todo.done else '☐' }}</button>
</form>
{{ todo.title }}
<!-- 編集リンク -->
[<a href="{{ url_for('todo.edit', id=todo.id) }}">編集</a>]
<!-- 削除フォーム -->
<form method="post" action="{{ url_for('todo.delete', id=todo.id) }}" style="display:inline">
<button type="submit">削除</button>
</form>
</li>
{% else %}
<li>タスクがありません。</li>
{% endfor %}
</ul>
{% endblock %}todo.doneに応じてトグルボタンの表示が変わります(☐ or ✅)。- 編集と削除はそれぞれリンクとフォームで構成されています。
- タスクが存在しない場合にはメッセージを表示します。
共通レイアウトテンプレート base.html
アプリケーションの共通HTMLレイアウトを定義します。
ログイン状態に応じたナビゲーションや、Flashメッセージ表示エリアも含まれています。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>ToDoアプリ{% block title %}{% endblock %}</title>
</head>
<body>
<!-- ナビゲーションバー -->
<nav>
{% if current_user.is_authenticated %}
{{ current_user.username }} さん |
<a href="{{ url_for('todo.index') }}">ToDo一覧</a> |
<a href="{{ url_for('todo.add') }}">追加</a> |
<a href="{{ url_for('auth.logout') }}">ログアウト</a>
{% else %}
<a href="{{ url_for('auth.login') }}">ログイン</a> |
<a href="{{ url_for('auth.register') }}">登録</a>
{% endif %}
</nav>
<hr>
<!-- Flashメッセージの表示 -->
{% with msgs = get_flashed_messages() %}
{% if msgs %}
<ul>
{% for m in msgs %}
<li>{{ m }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<!-- 子テンプレートが挿入するコンテンツ -->
{% block content %}{% endblock %}
</body>
</html>current_userを利用して、ログイン状態によってナビゲーションリンクを切り替えます。get_flashed_messages()を使って、ルートで設定したフラッシュメッセージを表示します。block titleとblock contentにより、各ページごとのタイトルやコンテンツを挿入可能にしています。
まとめ
この章では、ユーザーごとにタスクを管理できるToDoアプリを題材として、より実践的なFlaskアプリ開発の技術を学びました。
特に、Flask-Loginを使ったユーザー認証の導入と、それを活かしたログイン必須のページ構成、さらにユーザーごとのタスクの管理やタスクの完了状態の切り替えなど、より高度なアプリケーション設計を体験していただきました。
今回の学習を通して身につけた主なポイントは次の通りです:
- Flask-Loginのインストールと初期設定
LoginManagerの導入とユーザーロード関数の実装login_user,logout_user,current_user,@login_requiredの使い方- ユーザーごとのデータ管理(
filter_by()による絞り込み) - タスクの完了状態を更新するための処理(
update())
今回作成したToDoアプリは、認証機能付きのWebアプリケーションの基本形とも言えます。
ぜひこの章で学んだ内容を活かし、自分だけの機能を加えたアプリへと発展させてみてください。
あなたの手で、より本格的なWebアプリケーションを完成させていく準備は全て整いました。
- サイト改善アンケート|1分だけ、ご意見をお聞かせください
-
本サイトでは、みなさまの学習をよりサポートできるサービスを目指しております。
そのため、ご利用者のみなさまの「プログラミングを学習する理由」などをアンケート形式でお伺いしています。ご協力いただけますと幸いです。
アンケート






