7.

Django のリダイレクト|redirect() と PRG パターン・301 の注意

編集
この記事の要点
  • 基本は from django.shortcuts import redirectredirect()
  • 渡せるのは URL 名 / モデル / URL 文字列の 3 種類。URL 名で書くのが原則
  • POST を処理したら必ずリダイレクトする(PRG パターン)。再読み込みでの二重登録を防ぐ
  • 恒久的な移転は permanent=True(301)。ブラウザに強くキャッシュされるので慎重に
  • ユーザー入力の URL へそのまま飛ばさない。url_has_allowed_host_and_scheme() で検証する

redirect() の 3 つの渡し方

from django.shortcuts import redirect

# 1. URL 名(推奨)
return redirect("blog:index")
return redirect("blog:detail", pk=article.pk)

# 2. モデルインスタンス(get_absolute_url が呼ばれる)
return redirect(article)

# 3. URL 文字列
return redirect("/blog/")
return redirect("https://example.com/")

URL 名で書けば、ルーティングを変えてもビューを直す必要がありません。urls.pyname= の付け方は aタグのhrefの記載方法 と同じ仕組みです。

POST の後は必ずリダイレクト

from django.contrib import messages
from django.shortcuts import redirect, render

def create(request):
    if request.method == "POST":
        form = ArticleForm(request.POST)
        if form.is_valid():
            article = form.save()
            messages.success(request, "登録しました")
            return redirect("blog:detail", pk=article.pk)   # ← ここが重要
    else:
        form = ArticleForm()

    return render(request, "blog/create.html", {"form": form})

保存後に render() で画面を返すと、ブラウザの再読み込みで同じ POST が再送されます。「登録ボタンを 1 回押したのに 2 件登録された」の典型的な原因です。

この「POST → Redirect → GET」の流れを PRG パターンと呼びます。バリデーションエラーのときだけ render() で戻し、成功時は必ずリダイレクトします。

クエリ文字列を付ける

from urllib.parse import urlencode
from django.urls import reverse

url = reverse("blog:index") + "?" + urlencode({"page": 2, "q": "検索語"})
return redirect(url)

redirect("blog:index", page=2) と書いても ?page=2 にはなりません。URL のパス部分の引数として解釈されるため、該当する path() が無ければ NoReverseMatch になります。

ステータスコードの違い

コード意味Django での書き方
302一時的(既定)redirect(...)
301恒久的な移転redirect(..., permanent=True)
303POST の後は GET で取り直せHttpResponseSeeOther
307 / 308メソッドを変えずに転送通常は使わない

301 はブラウザに強くキャッシュされます。一度返すと、設定を戻してもユーザーの手元では古い転送先が使われ続けます。検索エンジンに移転を伝える目的以外では 302 を使ってください。

ログイン後に元のページへ戻す

from django.shortcuts import redirect
from django.utils.http import url_has_allowed_host_and_scheme

def login_view(request):
    if request.method == "POST":
        # ... 認証処理 ...
        next_url = request.POST.get("next") or request.GET.get("next")

        if next_url and url_has_allowed_host_and_scheme(
            next_url,
            allowed_hosts={request.get_host()},
            require_https=request.is_secure(),
        ):
            return redirect(next_url)

        return redirect("home")

検証せずに redirect(request.GET["next"]) と書いてはいけません。攻撃者が ?next=https://evil.example/ を仕込んだリンクを配ると、自サイトのログインを経由して偽サイトへ誘導できてしまいます(オープンリダイレクト)。

クラスベースビューの場合

from django.urls import reverse_lazy
from django.views.generic import CreateView, RedirectView

class ArticleCreateView(CreateView):
    model = Article
    fields = ["title", "body"]
    success_url = reverse_lazy("blog:index")     # 固定の遷移先

    # 動的に決めたいときはこちら
    def get_success_url(self):
        return reverse("blog:detail", kwargs={"pk": self.object.pk})

class OldPageView(RedirectView):
    pattern_name = "blog:index"
    permanent = True

クラス属性で URL を組み立てるときは reverse_lazy を使います。reverse だとモジュール読み込み時に評価され、URL 設定がまだ読まれていないため ImproperlyConfigured になります。

modelget_absolute_url() が定義されていれば success_url は省略できます。

そのほかのリダイレクト

from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect

return HttpResponseRedirect("/blog/")            # redirect() の中身はこれ
return HttpResponsePermanentRedirect("/blog/")   # 301

# 直前のページへ戻す(Referer が無い場合に備えて既定値を用意する)
return redirect(request.META.get("HTTP_REFERER", "/"))

HTTP_REFERER送られてこないことがあり、値も偽装できます。遷移先の決定に使う場合も、前節と同じ検証を通してください。

うまくいかないとき

症状原因
リダイレクトされないredirect(...)return していない
NoReverseMatchURL 名・名前空間・引数が合っていない
ERR_TOO_MANY_REDIRECTS転送先が自分自身を指している。条件分岐を見直す
末尾スラッシュで 1 回余分に転送されるAPPEND_SLASH の働き。path() の定義に合わせる
301 が消えないブラウザのキャッシュ。シークレットウィンドウで確認する
messages が表示されない遷移先のテンプレートで {% for m in messages %} を出していない

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. クラスベースビュー(主流)の作り方とviewの分割
  2. 関数ベースビューの作り方とviewの分割
  3. URLディスパッチャー(ルーティング処理)
  4. GETとPOSTパラメータ受け取り
  5. クラスベースビューでGET/POSTリクエストの受け取り方
  6. クラスベースビューでテンプレートに値を渡す方法
  7. ビューでリダイレクト
  8. cookieの値の設定と取得
  9. HTTPステータスコードの返し方

最近更新/作成されたページ