5日目のゴール
5日目のテーマは
「Flaskのルーティングを“実戦寄りの振る舞い”に近づける」 ことです。
ここまでであなたはすでに
固定URLと動的URLを定義できる
クエリパラメータ(?q=xxx)を扱える
GET と POST を切り替えてフォーム付きページを書ける
というところまで来ています。
5日目では、そこにさらに
リダイレクト(別のURLに飛ばす)
POSTしたあとに同じページを再読み込みしない「POST/Redirect/GET」パターン
エラーページ(404など)を自分で用意する
という「現場でよく使うルーティングの振る舞い」を足していきます。
リダイレクトとは何か
「一度受け取ってから、別のURLに案内する」
リダイレクトは、サーバー側からブラウザに対して
「このURLじゃなくて、あっちのURLを見に行って」
と指示する仕組みです。
例えば、次のようなことをしたいときに使います。
フォームを送信したあと、結果ページではなく「一覧ページ」に戻したい
古いURLにアクセスされたら、新しいURLに自動で案内したい
Flaskでは、redirect と url_for を組み合わせて使うのが定番です。
Flaskでのリダイレクトの基本形
redirect(url_for("関数名")) の流れ
最小の例から見てみましょう。
from flask import Flask, redirect, url_for
app = Flask(__name__)
@app.route("/")
def index():
return "トップページです。"
@app.route("/old")
def old():
return redirect(url_for("index"))
if __name__ == "__main__":
app.run(debug=True)
Pythonここで起きていることをかみ砕きます。
/old にアクセスすると、old 関数が呼ばれる
でも、ここでは文字列を返さずに redirect(...) を返している
redirect(url_for("index")) は
「index 関数に対応するURL(今は /)にリダイレクトする」
という意味になる
ブラウザ側から見ると、/old にアクセスしたつもりが、
自動的に / に移動させられます。
ここでの重要ポイントは、
リダイレクトは「別のURLに行き直させる」動きであって、
「その場で別の関数を呼ぶ」のとは違う
という感覚です。
POST/Redirect/GET パターンのイメージ
「フォーム送信後にリロードすると、もう一度送信されてしまう問題」
4日目で作ったようなフォーム付きページでは、
POST のあとにそのまま結果を表示していました。
このとき、ブラウザで「再読み込み(リロード)」をすると、
「同じPOSTをもう一度送信しますか?」と聞かれることがあります。
これは、ブラウザが
「さっきのページは POST で取得したから、リロードすると同じPOSTをもう一回送ることになるよ」
と警告している状態です。
これを避けるために、
現場では「POST/Redirect/GET」というパターンがよく使われます。
流れはこうです。
フォームを表示(GET)
フォーム送信(POST)
サーバー側で処理したあと、結果ページや一覧ページにリダイレクト(Redirect)
結果ページは GET で表示される(GET)
こうしておくと、
ユーザーがリロードしても「GETのページを再読み込みするだけ」になり、
同じPOSTが二重に送信されることを防げます。
例題:メモを1件だけ保存するミニアプリ(PRGパターン)
仕様を言葉で整理する
トップページ /
メモ入力フォーム /memo
メモはサーバー側の変数に1件だけ保存
フォーム送信後は /memo にリダイレクトして、GETで結果を表示
コードを書いてみます。
from flask import Flask, request, redirect, url_for
app = Flask(__name__)
saved_memo = ""
@app.route("/")
def index():
return """
<h1>メモアプリ(1件だけ)</h1>
<p><a href="/memo">メモを書く</a></p>
"""
@app.route("/memo", methods=["GET", "POST"])
def memo():
global saved_memo
if request.method == "POST":
text = request.form.get("text", "").strip()
saved_memo = text
return redirect(url_for("memo"))
return f"""
<h1>メモを書く</h1>
<form method="post">
<textarea name="text" rows="4" cols="40" placeholder="ここにメモを書いてください">{saved_memo}</textarea>
<br>
<button type="submit">保存</button>
</form>
<p>現在のメモ:</p>
<pre>{saved_memo}</pre>
<p><a href="/">トップに戻る</a></p>
"""
if __name__ == "__main__":
app.run(debug=True)
Pythonここでの重要ポイントを深掘りします。
methods=["GET", "POST"] で、/memo がGETとPOST両方を受け付ける
フォームの表示と送信後の処理を、同じURLで行っている
POST のときは、処理が終わったら redirect(url_for("memo")) を返している
これが「POST/Redirect/GET」の「Redirect」にあたる
ブラウザは /memo に GET でアクセスし直す
GET のときは、フォームと現在のメモを表示している
結果表示は GET のページなので、リロードしてもPOSTは再送されない
このパターンを一度自分で書いてみると、
「ああ、こうやって二重送信を防ぐのか」という感覚が体に入ります。
404エラーページを自分で用意する
「ページが見つからない」ときも、アプリの一部として扱う
3日目で、
定義していないURLにアクセスすると自動的に「404 Not Found」になる、
という話をしました。
デフォルトの404ページは、Flaskが用意したシンプルなものですが、
自分のアプリ用に、もう少し親切なページを出したくなることがあります。
Flaskでは、errorhandler デコレータを使って
エラーコードごとのページを定義できます。
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return "トップページです。"
@app.errorhandler(404)
def page_not_found(error):
return """
<h1>404 - ページが見つかりません</h1>
<p>URLが間違っているか、このページは削除された可能性があります。</p>
<p><a href="/">トップに戻る</a></p>
""", 404
if __name__ == "__main__":
app.run(debug=True)
Pythonここでのポイントを整理します。
@app.errorhandler(404) は、「404エラーが発生したときに呼ばれる関数」を登録する
どのルートにもマッチしなかったときなどに、この関数が使われる
関数は「レスポンスの内容」と「ステータスコード」を返す
上の例では、HTML文字列と 404 をタプルで返している
これで、
存在しないURLにアクセスされたときも、
アプリの雰囲気に合ったメッセージを出せるようになります。
ルーティングとエラーの関係を整理する
「マッチしなかったら404」「自分でハンドリングもできる」
ここまでのルーティングの流れを、エラーの視点からまとめるとこうなります。
Flaskは、リクエストが来たときに
登録されているルート(@app.route)の中から
「このURLとメソッドに合うもの」を探す
見つかれば、その関数を呼び出し、返り値をレスポンスとして返す
見つからなければ、404エラーとして扱う
404エラーが発生したとき、@app.errorhandler(404) が定義されていれば、その関数が呼ばれる
定義されていなければ、Flaskのデフォルトの404ページが表示される
つまり、
「ルーティングにマッチしなかったときの最後の砦」が
404エラーハンドラだと思ってください。
5日目のミニアプリ:メモ+404カスタムの小さなサイト
全体を一つにまとめる
トップ /
メモ /memo(POST/Redirect/GET)
404カスタム
をまとめた例です。
from flask import Flask, request, redirect, url_for
app = Flask(__name__)
saved_memo = ""
@app.route("/")
def index():
return """
<h1>ミニサイト</h1>
<p><a href="/memo">メモを書く</a></p>
<p>存在しないURLにアクセスして、404ページも試してみてください。</p>
"""
@app.route("/memo", methods=["GET", "POST"])
def memo():
global saved_memo
if request.method == "POST":
text = request.form.get("text", "").strip()
saved_memo = text
return redirect(url_for("memo"))
return f"""
<h1>メモを書く</h1>
<form method="post">
<textarea name="text" rows="4" cols="40" placeholder="ここにメモを書いてください">{saved_memo}</textarea>
<br>
<button type="submit">保存</button>
</form>
<p>現在のメモ:</p>
<pre>{saved_memo}</pre>
<p><a href="/">トップに戻る</a></p>
"""
@app.errorhandler(404)
def page_not_found(error):
return """
<h1>404 - ページが見つかりません</h1>
<p>URLが間違っているか、このページは存在しません。</p>
<p><a href="/">トップに戻る</a></p>
""", 404
if __name__ == "__main__":
app.run(debug=True)
Pythonこれで、
フォーム送信後にリロードしても二重送信にならない
存在しないURLにアクセスしても、アプリらしい404ページが出る
という「ちょっと現場っぽい振る舞い」を体験できます。
5日目で絶対に押さえてほしい本質
「ルーティングは“どこに行くか”だけじゃなく、“どこに戻すか”も含めて設計する」
今日いちばん大事なのは、
ルーティングをこういう視点で見られるようになることです。
URLに対して「どの関数を呼ぶか」を決めるだけでなく、
処理が終わったあとに「どのURLに戻すか(リダイレクトするか)」も設計する。
フォーム送信(POST)のあとに、
そのまま結果を返すのではなく、
一度リダイレクトして GET のページに着地させることで、
二重送信を防ぐ「POST/Redirect/GET」パターンを使える。
ルーティングにマッチしなかったときの404も、
アプリの一部として自分でデザインできる。
ここまで来たあなたは、
もう「Flaskでページを出せる人」ではなく、
「ユーザーの動きとエラーの流れまで含めてルーティングを設計できる人」 です。
6日目以降は、
ここにテンプレートファイルや、
もう少し複雑なURL設計(例えば /memo/<int:id> のような複数メモ対応)を重ねていくと、
一気に「小さな実用Webアプリ」の形が見えてきます。

