Skip to content
Code Blue プログラミング学習ノート
Python/Flask

Flaskアプリ制作演習

はじめに

Flaskの基本文法を一通り学んだあと、多くの初学者がつまずくのが「実際のアプリでは、どの機能をどの順番で作ればよいのか」という点です。

ルーティング、テンプレート、フォーム、データベース、ログイン機能などは、それぞれ単体で学ぶこともできます。しかし、実務のWebアプリでは、それらを組み合わせて1つのシステムとして動かす必要があります。

そこで本演習では、社内向けの **備品貸出・在庫管理システム** を題材にします。

このアプリでは、社員がノートPC、モニター、ケーブル、プロジェクターなどの備品を検索し、必要な備品を借りる申請を行います。管理者は申請を確認し、承認または却下します。返却された備品の状態も管理できるため、単なる一覧アプリではなく、実務に近い業務システムとして学習できます。

作成するアプリの概要

アプリ名は仮に **EquipFlow** とします。

EquipFlowは、次のような機能を持つ社内用Webアプリです。

  • – ユーザーがログインできる
  • – 備品一覧を検索できる
  • – 備品の詳細を確認できる
  • – 備品の貸出申請ができる
  • – 管理者が申請を承認・却下できる
  • – ユーザーが返却登録できる
  • – 管理者が備品マスタを登録・編集・削除できる
  • – 備品写真をアップロードできる
  • – APIで備品や申請情報を取得できる
  • – 操作履歴を監査ログとして保存できる
  • – テストとセキュリティ対策を実装できる

初学者向けに言い換えると、これは **ログイン付きの在庫管理アプリ** です。最初はログイン画面と一覧画面だけでも十分ですが、少しずつ機能を追加することで、実務アプリに必要な考え方をまとめて学べます。

この演習で学ぶ技術

このアプリでは、Flaskの基本から実務寄りの機能まで段階的に扱います。

主な技術は以下です。

| 分野 | 使用する技術 | 役割 |
|---|---|---|
| Webアプリ本体 | Flask | URLと処理をつなぐ |
| 画面表示 | Jinja2 | HTMLテンプレートを作る |
| デザイン | Bootstrap / CSS | 管理画面を見やすくする |
| フォーム | Flask-WTF / WTForms | 入力フォームとバリデーションを作る |
| データベース | SQLAlchemy | ユーザー、備品、申請を保存する |
| DB変更管理 | Flask-Migrate | テーブル変更を履歴管理する |
| ログイン | Flask-Login | 認証状態を管理する |
| 画像処理 | Pillow | 備品写真のサムネイルを作る |
| 非同期処理 | Celery | メール通知や期限超過チェックを裏側で実行する |
| 外部連携 | requests | 地図APIなどからデータを取得する |
| API | FlaskのJSONレスポンス | 外部システム向けの入口を作る |
| テスト | pytest | 機能が壊れていないか確認する |
| セキュリティ | CSRF/XSS/SQLi対策 | 安全なアプリにする |

要件定義:何を作るのかを決める

目的

  • 備品の所在・貸出状況を一元管理し、貸出手続きと監査性を向上する。

対象ユーザー

  • 一般ユーザー:貸出申請、返却登録、自分の履歴閲覧
  • 管理者:備品マスタ管理、申請承認、ユーザー/ロール管理、監査ログ確認

前提/制約

  • 認証必須(ログインなしアクセス不可)
  • 備品写真のアップロードは画像のみ(png/jpg/webp)
  • 期限超過通知は定期タスクで送信
  • 監査ログは改ざん防止のため更新不可(追記のみ)
  • APIはJSON、認証はセッションクッキー(管理画面)+APIトークン(API)

非機能要件

  • セキュリティ:CSRF、XSS、SQLi対策、パスワードハッシュ、権限分離
  • 可用性:想定同時利用 50人、ピーク時も検索が2秒以内
  • 保守性:マイグレーション管理、構造化ログ、テストカバレッジ70%以上
  • 性能:備品一覧はページネーション、検索結果はキャッシュ(短時間)

画面遷移

  • ログイン → ダッシュボード
  • ダッシュボード
    • 備品一覧 → 備品詳細 →(貸出申請)
    • マイページ(自分の貸出履歴)→ 返却登録
    • (管理者のみ)申請一覧 → 申請詳細 → 承認/却下
    • (管理者のみ)備品管理(一覧→新規→編集)
    • (管理者のみ)ユーザー管理(一覧→詳細→ロール変更)
    • (管理者のみ)監査ログ閲覧
  • エラーページ(403/404/500)は共通レイアウトで遷移
ログイン
  ↓
ダッシュボード
  ↓
備品一覧 → 備品詳細 → 貸出申請
  ↓
マイページ → 自分の貸出履歴 → 返却登録
```

管理者の場合は、さらに次のような画面も使います。

```text
管理者ダッシュボード
  ↓
申請一覧 → 申請詳細 → 承認 / 却下
  ↓
備品管理 → 新規登録 / 編集 / 削除
  ↓
ユーザー管理 → ロール変更
  ↓
監査ログ確認

機能要件

1. 認証・認可

認証とは「あなたは誰ですか?」を確認する仕組みです。ログイン機能がこれに当たります。認可とは「あなたはこの操作をしてよいですか?」を確認する仕組みです。管理者だけが備品を削除できる、といった制限がこれに当たります。

Flaskでは、認証には `Flask-Login` を使うと便利です。ユーザー登録時にはパスワードをそのまま保存せず、必ずハッシュ化して保存します。

たとえば、一般ユーザーが `/admin/items` にアクセスした場合は、403 Forbiddenとして拒否します。画面にボタンを表示しないだけでなく、URLを直接入力されても処理側で拒否することが重要です。

  • ユーザー登録、ログイン、ログアウト
  • パスワードはハッシュ化して保存
  • ロール(user/admin)によるアクセス制御(デコレータ)

2. 備品管理(管理者)

備品管理は、管理者向けの中心機能です。登録する情報の例は次のとおりです。

- 管理番号
- 備品名
- カテゴリ
- 状態
- 保管場所
- 説明文
- 備品写真

状態には、たとえば次のような値を用意します。

| 状態 | 意味 |
|---|---|
| available | 利用可能 |
| borrowed | 貸出中 |
| repair | 修理中 |
| disposed | 廃棄済み |

備品写真を登録できるようにすると、利用者がどの備品か判断しやすくなります。画像はアップロード後にサムネイルを作成し、一覧画面では軽い画像を表示するようにします。

  • 備品のCRUD
  • 状態(利用可/貸出中/修理中/廃棄)管理
  • 画像アップロード、サムネイル自動生成、表示最適化

3. 貸出フロー(一般+管理者)

このアプリの業務的な中心は、貸出フローです。一般ユーザーは、備品詳細画面から貸出申請を行います。申請には、開始日、返却予定日、理由を入力します。

管理者は申請一覧を見て、承認または却下を行います。承認された場合、備品の状態は `available` から `borrowed` に変わります。

返却時には、ユーザーまたは管理者が返却登録を行います。返却された備品が問題なければ `available` に戻し、故障していれば `repair` に変更します。このように、申請の状態と備品の状態を連動させることが大切です。

  • 一般:貸出申請(開始日・返却予定日・理由)
  • 管理者:承認/却下、却下理由登録
  • 承認後、備品は「貸出中」に遷移
  • 返却登録により「利用可」に戻す(破損時は「修理中」も選択可)

4. 検索・一覧

備品が増えてくると、単純な一覧だけでは使いづらくなります。

そこで、次のような検索機能を追加します。

- キーワード検索

- カテゴリ検索

- 状態検索

- 拠点検索

- ページネーション

ページネーションとは、一覧を1ページにすべて表示せず、10件ずつ、20件ずつのように分けて表示する仕組みです。また、データベースから関連データを取得するときには、N+1問題にも注意します。初学者の段階では難しく感じるかもしれませんが、「一覧画面が遅くなる原因の1つ」として覚えておくとよいです。

  • 備品一覧:カテゴリ/状態/キーワードで検索、ページネーション
  • 貸出履歴:ユーザー別、期間フィルタ
  • クエリ最適化(N+1回避)、必要に応じてインデックス

5. 通知(非同期)

貸出申請が承認されたときや却下されたときには、ユーザーにメール通知を送ると便利です。ただし、メール送信を画面表示の処理と同時に行うと、ユーザーが待たされることがあります。そのため、Celeryなどを使って非同期処理にします。

非同期処理とは、画面の処理とは別に、裏側で時間のかかる処理を実行する方法です。このアプリでは、たとえば次のような処理を非同期化できます。

  • 承認・却下時メール通知(Celery)
  • 期限超過の確認
  • 期限超過の定期チェック(1日1回)で督促メール

6. 外部API連携

備品には保管場所があります。たとえば「東京本社 3階」「福岡オフィス 倉庫A」などです。住所情報をもとに地図APIを使うと、緯度・経度を取得できます。この処理をジオコーディングと呼びます。

Flask側では `requests` を使って外部APIにアクセスします。APIキーはコードに直接書かず、環境変数で管理します。外部APIを使うときは、次の点に注意します。

  • 拠点住所から地図ジオコーディング(requests)
  • 通信に失敗する可能性がある
  • タイムアウトを設定する
  • 取得したデータを必要な形に整える
  • 備品保管拠点の地図表示用に緯度経度を保持
  • APIキーは環境変数で管理、ログに出さない

7. REST API

管理画面は人間がブラウザで使う画面です。一方、REST APIは外部システムやJavaScriptからデータを取得・操作するための入口です。このアプリでは、次のようなAPIを用意します。

  • GET /api/items(検索+ページネーション)
  • GET /api/items/
  • POST /api/requests(申請)
  • GET /api/requests/mine(自分の申請)
  • 管理者向け:PATCH /api/requests/(承認/却下)
  • エラー時はJSONで統一(コード、メッセージ、詳細)

APIでは、HTMLではなくJSONを返します。エラー時も形式を統一し、`code`、`message`、`details` のような情報を返すと扱いやすくなります。

8. エラーハンドリング/ログ

実務のアプリでは、エラーが起きないことよりも、エラーが起きたときに安全に処理できることが重要です。たとえば、存在しない備品IDにアクセスされた場合は404ページを表示します。権限がない画面にアクセスされた場合は403ページを表示します。予期しないエラーが起きた場合は500ページを表示します。

ユーザーには分かりやすいメッセージを表示し、開発者向けの詳しいエラー情報はログに残します。ログには、「発生時刻 / ユーザーID / URL / エラー内容 / IPアドレス 」のような情報を残すと調査しやすくなります。

  • 403/404/500のカスタムページ
  • 例外は適切に握り、監査ログ+アプリログへ記録
  • ログはリクエストIDを付与

9. テスト

  • 単体:モデル、サービス層、バリデーション
  • 統合:主要画面・API・権限
  • カバレッジ計測(閾値設定)

テーブル設計書(案)

このアプリでは、少なくとも次のテーブルを使います。たとえば、備品情報とユーザー情報と申請情報を1つのテーブルにまとめると、あとから検索や集計が難しくなります。ユーザー、備品、申請を分け、それぞれを外部キーでつなげることで、管理しやすいデータ構造になります。

users

  • id(PK)
  • email(Unique, NotNull)
  • password_hash(NotNull)
  • name(NotNull)
  • is_active(Bool, default true)
  • created_at, updated_at

roles

  • id(PK)
  • name(Unique) 例: user/admin

user_roles(多対多)

  • user_id(FK users.id, PK)
  • role_id(FK roles.id, PK)

locations(拠点)

  • id(PK)
  • name(NotNull)
  • address(NotNull)
  • latitude, longitude(ジオコーディング結果)
  • created_at, updated_at

items(備品)

  • id(PK)
  • asset_tag(Unique, NotNull) 管理番号
  • name(NotNull)
  • category(NotNull) 例: pc/monitor/other
  • status(NotNull) 例: available/borrowed/repair/disposed
  • location_id(FK locations.id)
  • description(Text)
  • image_path
  • thumb_path
  • created_at, updated_at
  • index: (status), (category), (location_id)

loan_requests(貸出申請)

  • id(PK)
  • user_id(FK users.id, NotNull)
  • item_id(FK items.id, NotNull)
  • start_date(NotNull)
  • due_date(NotNull)
  • reason(Text, NotNull)
  • status(NotNull) 例: pending/approved/rejected/returned
  • decision_by(FK users.id, Null) 承認者
  • decision_note(Text, Null)
  • decided_at(Null)
  • returned_at(Null)
  • created_at, updated_at
  • index: (user_id, status), (item_id, status), (due_date)

audit_logs(監査ログ)

  • id(PK)
  • actor_user_id(FK users.id, Null) 未ログイン操作の可能性考慮
  • action(NotNull) 例: ITEM_CREATE, REQUEST_APPROVE
  • target_type(NotNull) 例: item/request/user
  • target_id(NotNull)
  • detail_json(JSON/Text)
  • ip_address
  • user_agent
  • created_at
  • 更新/削除禁止(アプリ側で制御)

結合テスト(主要観点)

単体テストは、1つの関数や1つのモデルが正しく動くかを確認します。一方、結合テストでは、複数の機能をつなげて確認します。たとえば、次のような流れをテストします。

IT-01 認証~一覧表示

  • 前提:一般ユーザーが存在
  • 手順:ログイン→備品一覧表示→検索→ページ遷移
  • 期待:未ログイン時はログインへリダイレクト、検索条件が反映、ページネーション正常

IT-02 申請~承認~状態遷移

  • 前提:備品がavailable、管理者が存在
  • 手順:一般が申請→管理者が承認→備品詳細確認
  • 期待:申請status=approved、備品status=borrowed、承認メールが非同期キューに積まれる

IT-03 却下フロー

  • 手順:申請→却下(理由必須)
  • 期待:status=rejected、備品statusはavailableのまま、却下理由が履歴に残る

IT-04 返却登録

  • 手順:承認済み申請を返却登録(返却時状態選択)
  • 期待:status=returned、returned_at設定、備品statusがavailableまたはrepairへ

IT-05 画像アップロード

  • 手順:備品にjpgをアップロード→詳細表示
  • 期待:拡張子/ContentTypeチェック、サムネ生成、パスがDBに保存、一覧はサムネ表示

IT-06 API認証と権限制御

  • 手順:トークン無しでPOST /api/requests→失敗、一般トークンで管理PATCH→失敗、管理トークンで成功
  • 期待:401/403が正しく返る、エラーJSON形式統一

IT-07 例外とエラーページ

  • 手順:存在しない備品IDアクセス、権限無し画面アクセス
  • 期待:404/403がカスタムページで表示、ログに記録される

IT-08 期限超過通知(疑似)

  • 手順:due_dateが過去のapprovedを作成→定期タスク相当処理を実行
  • 期待:対象抽出が正しい、通知が重複送信されない(送信済みフラグ or auditで制御)

受け入れテスト仕様書(抜粋)

受け入れテストでは、開発者目線ではなく利用者目線で確認します。たとえば、次のような観点です。

  • – 一般ユーザーが迷わず備品を借りられるか
  • – 管理者が備品台帳を管理できるか
  • – 不正な操作が拒否されるか
  • – 画面のエラー表示が分かりやすいか
  • – 検索が遅すぎないか
  • – 障害時にログから原因を追跡できるか

受け入れテストまで考えることで、単なる学習用アプリではなく、実務に近い完成度を目指せます。

UAT-01 基本業務が完遂できること

  • シナリオ:一般ユーザーが備品を探し、申請し、承認され、返却まで完了
  • 合格条件:各ステップで画面・メール通知・状態遷移が矛盾なく完了

UAT-02 管理者が備品台帳を維持できること

  • シナリオ:備品の新規登録、画像登録、拠点紐付け、廃棄まで実行
  • 合格条件:一覧/検索に反映、監査ログに記録、誤操作時もエラーが分かりやすい

UAT-03 セキュリティ要件を満たすこと

  • シナリオ:CSRFが必要なフォームでトークン無し送信、XSSを含む入力、SQLi風入力
  • 合格条件:送信拒否/無害化、エラーが適切、データ破壊が起きない、機密がログに出ない

UAT-04 性能要件を満たすこと

  • シナリオ:備品1000件、貸出履歴5000件相当データで一覧検索
  • 合格条件:主要検索が2秒以内、ページネーションが破綻しない、N+1が発生しない

UAT-05 運用要件(障害時の追跡)

  • シナリオ:意図的に例外を起こす、404を発生させる
  • 合格条件:ログにリクエストID・ユーザー・エラーが残り、原因追跡が可能

セキュリティと性能

最後に、セキュリティと性能を確認します。

代表的な対策は次の通りです。

| リスク | 対策 |

|---|---|

| CSRF | Flask-WTFのCSRFトークンを使う |

| XSS | Jinja2の自動エスケープを活かす |

| SQLインジェクション | SQLAlchemyの安全なクエリを使う |

| 権限突破 | サーバー側でロールチェックする |

| パスワード漏えい | ハッシュ化して保存する |

| 不正アップロード | 拡張子、MIMEタイプ、サイズを確認する |

| 検索遅延 | ページネーション、インデックス、キャッシュを使う |

Flask初学者の場合、最初からすべてを完璧に作る必要はありません。ただし、「あとで必要になる品質」を知ったうえで段階的に作ることが重要です。