Python | 1 日 120 分 × 7 日アプリ学習:Flaskで作る超簡単Webアプリ(中級編)

Web APP Python
スポンサーリンク

5日目のゴール

5日目のテーマは
「Flaskのルーティングを“実戦寄りの振る舞い”に近づける」 ことです。

ここまでであなたはすでに

固定URLと動的URLを定義できる
クエリパラメータ(?q=xxx)を扱える
GET と POST を切り替えてフォーム付きページを書ける

というところまで来ています。

5日目では、そこにさらに

リダイレクト(別のURLに飛ばす)
POSTしたあとに同じページを再読み込みしない「POST/Redirect/GET」パターン
エラーページ(404など)を自分で用意する

という「現場でよく使うルーティングの振る舞い」を足していきます。


リダイレクトとは何か

「一度受け取ってから、別のURLに案内する」

リダイレクトは、サーバー側からブラウザに対して

「このURLじゃなくて、あっちのURLを見に行って」

と指示する仕組みです。

例えば、次のようなことをしたいときに使います。

フォームを送信したあと、結果ページではなく「一覧ページ」に戻したい
古いURLにアクセスされたら、新しいURLに自動で案内したい

Flaskでは、redirecturl_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アプリ」の形が見えてきます。

タイトルとURLをコピーしました