Flask実践|ToDoアプリを作ろう【チャプター7-04】

ながみえ

一つ前のページではメモ帳アプリを作成しました。

今回は 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アプリ開発の力を一段と高めていきましょう。

本記事は 有料記事(100円)ですが、現在は期間限定で無料公開中です。

<<前のページ

Flaskの記事一覧

次のページ>>

Flask-Loginの導入と設定

【Python】勉強猫がノートパソコンを前にして学習を始める様子。記事内の学習スタート用イラスト

Flask-Loginとは何か?

Flask-Loginは、Flaskアプリケーションにログイン機能を追加するための拡張機能です。

ユーザーのログイン状態をセッションで管理し、以下のような処理を簡単に実装できます。

  • ユーザーのログイン状態の保存・確認
  • ログインが必要なページへのアクセス制限
  • ログイン中のユーザー情報の取得
  • ログアウト処理

Flask-Loginのインストール方法

Flask-LoginはPythonのパッケージとして提供されており、以下のコマンドでインストールできます:

pip install flask-login

仮想環境を使用している場合は、仮想環境を有効化した状態で上記のコマンドを実行してください。

基本構文と使い方

Flask-Loginを使うためには、次の4つの準備が必要です:

  1. LoginManagerオブジェクトの生成とFlaskアプリへの登録
  2. ユーザークラスにUserMixinを継承
  3. user_loader関数を定義し、IDからユーザーを取得
  4. 必要な関数(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アプリを作成しながら見ていきましょう。

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アプリケーションを実現できます。

【Python】勉強猫がノートパソコンを見ながら考え込む様子。記事内の休憩用イラスト

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 = False
  • SECRET_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'))  # 紐づくユーザーID
  • UserMixin を使うことで Flask-Login に必要なメソッドを自動的に提供。
  • パスワードは ハッシュ化 して保存し、認証時には check_password で照合。
  • UserTodo の間には1対多のリレーション(User.todos)が存在。
  • load_user 関数により、セッション管理時にユーザーをDBから取得可能にしています。

ユーザー認証機能を実装しよう 〜 ログインと登録の仕組み 〜

ToDoアプリにログイン機能を追加することで、「誰がどのタスクを作成したのか」を管理できるようになります。

本節では、Flaskにおけるユーザー認証機能の基本的な実装方法を学びます。

以下のPythonファイルはtodo/app/authフォルダ直下に、htmlファイルはtodo/app/auth/templates/todoの中に保存して下さい。

__init__.py(Blueprintの定義)

このファイルでは、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対策が自動的に挿入されます。

【Python】勉強猫がコーヒーを片手にリラックスしている様子。記事内の休憩用イラスト

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 routes
  • Blueprint は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 titleblock content により、各ページごとのタイトルやコンテンツを挿入可能にしています。

まとめ

この章では、ユーザーごとにタスクを管理できるToDoアプリを題材として、より実践的なFlaskアプリ開発の技術を学びました。

特に、Flask-Loginを使ったユーザー認証の導入と、それを活かしたログイン必須のページ構成、さらにユーザーごとのタスクの管理やタスクの完了状態の切り替えなど、より高度なアプリケーション設計を体験していただきました。

今回の学習を通して身につけた主なポイントは次の通りです:

  • Flask-Loginのインストールと初期設定
  • LoginManagerの導入とユーザーロード関数の実装
  • login_user, logout_user, current_user, @login_requiredの使い方
  • ユーザーごとのデータ管理(filter_by()による絞り込み)
  • タスクの完了状態を更新するための処理(update()

今回作成したToDoアプリは、認証機能付きのWebアプリケーションの基本形とも言えます。

ぜひこの章で学んだ内容を活かし、自分だけの機能を加えたアプリへと発展させてみてください。

あなたの手で、より本格的なWebアプリケーションを完成させていく準備は全て整いました。

もっと分かりやすい学習サイトにするために

この記事を読んで「ここが分かりにくかった」「ここが難しかった」等の意見を募集しています。

世界一わかりやすいFlask学習サイトにするため、ぜひ 問い合わせフォーム からご意見下さい。

<<前のページ

Flaskの記事一覧

次のページ>>

FAQ|

Q
Q1.

Q
Q2.
Q
Q3.
記事URLをコピーしました