はじめに
前回は、Pythonのコード内に書いた固定データを、テンプレートを使って一覧表示するところまでを確認した。今回は、その一覧にフォームを追加し、支出データを自分で追加・削除できるようにする。
なお、今回もデータの保存先はPythonのリストのままである。そのため、アプリを再起動すると、追加・削除した内容は消えて初期状態のデータに戻ってしまう。この「消えてしまう」問題は、次回SQLiteを導入することで解決する。
今回やること
- HTMLフォームから、Flask側にデータを送る仕組みを知る
- 支出を追加するフォームを作る
- 支出を削除するボタンを作る
GETとPOST
ブラウザがサーバーにリクエストを送る方法にはいくつか種類があるが、よく使うのはGETとPOSTの2つである。
- GET:サーバーから情報を取得するためのリクエスト。ページの表示などに使う。
- POST:サーバーにデータを送信して、登録・更新・削除などの処理を依頼するためのリクエスト。
今回、支出の追加・削除はどちらも「データを送って処理してもらう」操作なので、POSTを使う。
コード解説
app.py
from flask import Flask, render_template, request, redirect, url_for
app = Flask(__name__)
expenses = [
{"date": "2026-08-01", "category": "食費", "item": "スーパー", "amount": 3200},
{"date": "2026-08-03", "category": "交通費", "item": "電車", "amount": 480},
{"date": "2026-08-05", "category": "娯楽費", "item": "映画", "amount": 1800},
]
@app.route("/")
def index():
return render_template("index.html", expenses=expenses)
@app.route("/add", methods=["POST"])
def add():
expenses.append({
"date": request.form["date"],
"category": request.form["category"],
"item": request.form["item"],
"amount": int(request.form["amount"]),
})
return redirect(url_for("index"))
@app.route("/delete/<int:index>", methods=["POST"])
def delete(index):
expenses.pop(index)
return redirect(url_for("index"))
前回までと違うポイントを説明する。
@app.route("/add", methods=["POST"]):methodsに"POST"を指定することで、このURLはPOSTリクエストしか受け付けないようにしているrequest.form["date"]:フォームから送られてきたデータを取り出している。request.formには、フォームのname属性をキーとした値が入っているexpenses.append(...):取り出したデータを、辞書としてexpensesリストに追加しているredirect(url_for("index")):処理が終わったら、一覧ページ(index)へ転送している。フォーム送信のたびに新しいページが表示されるのを防ぐための、定番の書き方である/delete/<int:index>:URLの一部(<int:index>)を、そのまま関数の引数として受け取れるようにしている。「何番目のデータを消すか」を、URLで指定する仕組みである
※今回は学習用の簡単なアプリなので、存在しない番号が指定された場合のエラー処理は省略している。
templates/index.html(抜粋)
<td>
<form action="{{ url_for('delete', index=loop.index0) }}" method="post">
<button type="submit">削除</button>
</form>
</td>
<form action="{{ url_for('add') }}" method="post">
<label>日付 <input type="date" name="date" required></label>
<label>カテゴリ <input type="text" name="category" required></label>
<label>内容 <input type="text" name="item" required></label>
<label>金額 <input type="number" name="amount" required></label>
<button type="submit">追加</button>
</form>
loop.index0:Jinja2の{% for %}ループの中で使える特別な変数で、「何番目の要素か」を0から数えた数字で取得できる。これをdeleteのURLに埋め込むことで、「削除ボタンごとに、削除対象の番号を切り替える」ということを実現している<input>のname属性(date、categoryなど)が、app.py側のrequest.form["date"]などと対応している
今回はフォーム処理の基本に集中するため、サーバー側の詳細な入力チェックは省略している。実用的なアプリでは、サーバー側でも入力値の検証が必要である。
動作確認
flask --app app run --debugで起動し、ブラウザで一覧の下に追加フォームが表示されていることを確認する。日付・カテゴリ・内容・金額を入力して「追加」を押すと、一覧に新しい行が増える。行の右にある「削除」ボタンを押すと、その行だけが消えることも確認しておく。
今回はデータをPythonのリストに保存しているため、アプリを再起動すると、プログラム起動時に作られた初期状態のリストが再び作られる。追加したデータが消えてしまっても、これは想定どおりの動作である。

今回のまとめ
- フォームからデータを送るには、
methods=["POST"]を指定したルートと、request.formを使う - 処理後は
redirect(url_for(...))で一覧ページへ戻すのが定番 - URLの一部を
<int:index>のように受け取ることで、「どのデータに対する操作か」をURLで表現できる
次回予告
次回は、いよいよSQLiteを使ってデータを永続化する。これまでの「再起動すると消える」問題を解決し、本当の意味でデータが保存されるアプリにしていく。
ソースコード
GitHub:pc-labo-python-learning/03_add-delete-expense



コメント