Django ログイン認証後にログイン認証前に要求した URL へのリダイレクトを実装します。
Django でログイン認証が必要なページを実装する方法として、大きく2つあると思います。
1つは、Django のデコレータ login_required を使う方法です。一番簡単な方法ですが、ページ数が増えてくると、毎回デコレータをつけるのが、大変になってきます。
2つ目は、ミドルウェアを使う方法です。この方法は、Stack Overflow で見つけたのですが、便利なので、よく使っています。
しかし、2つ目の方法で実装した場合、1つ目の Django デコレータで実装していた時にできていた、認証後に、認証前に要求した URL へのリダイレクト機能が動かなくなります。(Get パラメータの next= がつくやつ)
この記事は、そんな私の方のように、ミドルウェアで実装して、next= がなくなった人に向けての記事です。
Contents
実装イメージ
- http://192.168.33.10:8000/page/ にアクセス
- 認証が必要なので、ログインページへリダイレクト
- ログイン後、1 でアクセスしたページにリダイレクト
環境とバージョン
- CentOS 7.5.1804
- Python 3.6.4
- Django 2.1.1
インストール
それでは、環境を作っていきます。
Python
$ sudo yum install https://centos7.iuscommunity.org/ius-release.rpm $ sudo yum install python36u python36u-libs python36u-devel python36u-pip
Python 3.6 をインストールします。
Django
$ sudo pip install --upgrade django
次に Django をインストールします。
実装
環境の次は、実装に入ります。
settings.py
... ALLOWED_HOSTS = ['*'] INSTALLED_APPS = [ ... 'app.apps.AppConfig', ] MIDDLEWARE = [ ... 'login.middleware.LoginRequiredMiddleware', ] LOGIN_URL = '/login/' LOGIN_REDIRECT_URL = '/' LOGOUT_REDIRECT_URL = LOGIN_URL ...
まず、settings.py です。MIDDLEWARE にこれから自作するミドルウェアのパスを追加します。
middleware.py
from django.http import HttpResponseRedirect from django.conf import settings from re import compile from django.utils.deprecation import MiddlewareMixin EXEMPT_URLS = [compile(settings.LOGIN_URL.lstrip('/'))] if hasattr(settings, 'LOGIN_EXEMPT_URLS'): EXEMPT_URLS += [compile(expr) for expr in settings.LOGIN_EXEMPT_URLS] class LoginRequiredMiddleware(MiddlewareMixin): def process_request(self, request): if not request.user.is_authenticated: path = request.path_info.lstrip('/') if not any(m.match(path) for m in EXEMPT_URLS): if request.path: return HttpResponseRedirect(settings.LOGIN_URL + '?next=' + request.path) else: return HttpResponseRedirect(settings.LOGIN_URL)
Stack Overflow を参考に実装しています。settings.py の LOGIN_EXEMPT_URLS に認証が不要な URL を設定し、ログイン URL にリダイレクトします。
この Stack Overflow 通りに実装すると、view 毎に login_required のデコレータを付けなくても良くなり、便利です。しかし、next= が動かなくなってしまうので、処理の最後の方で、request.path があれば、next= をつけるところが今回のポイントとなります。
urls.py
from django.urls import path from django.contrib.auth import views as auth_views from django.views.generic import TemplateView urlpatterns = [ path('', TemplateView.as_view(template_name='app/top.html'), name='top'), path('page/', TemplateView.as_view(template_name='app/page.html'), name='page'), path('login/', auth_views.LoginView.as_view(template_name='app/login.html'), name='login'), path('logout/', auth_views.LogoutView.as_view(), name='logout'), ]
urls.py です。特に説明はありません。
login.html
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="description" content=""> <meta name="author" content=""> <title>Login</title> <!-- Bootstrap core CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstr\ ap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"\ crossorigin="anonymous"> <!-- Custom styles for this template --> <link href="https://getbootstrap.com/docs/4.1/examples/sign-in/signin.css" rel="stylesheet"\ > </head> <body class="text-center"> <form class="form-signin" method="post">{% csrf_token %} {{ form.errors }} <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1> <label for="inputEmail" class="sr-only">Email address</label> <input type="email" id="inputEmail" name="username" class="form-control" placeholder="Ema\ il address" required autofocus> <label for="inputPassword" class="sr-only">Password</label> <input type="password" id="inputPassword" name="password" class="form-control" placeholde\ r="Password" required> <input type="hidden" name="next" value="{{ request.GET.next }}"> <div class="checkbox mb-3"> <label> <input type="checkbox" value="remember-me"> Remember me </label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button> <p class="mt-5 mb-3 text-muted">© 2017-2018</p> </form> </body> </html>
Bootstrap のサンプルをベースにログイン HTML を作成しました。先ほど、ミドルウェアで next= の処理がポイントと言いましたが、ここでもポイントがあります。
ここでさらに、input hidden で next を指定しないといけません。
これで、login_required デコレータと同じ動きになります。
top.html
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="description" content=""> <meta name="author" content=""> <title>TOP</title> <!-- Bootstrap core CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstr\ ap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"\ crossorigin="anonymous"> </head> <body> <main role="main" class="container"> <h1 class="mt-5">Top</h1> <a class="btn btn-link" href="{% url 'logout' %}">logout</a> </main> </body> </html>
ログイン後にリダイレクトする用のトップページを適当に作成します。
page.html
<html> ページ </html>
もう一つ任意のページを適当に作成します。
動作確認
実装が終わりましたので、動作を確認していきます。
DB マイグレーション
$ python3.6 manage.py migrate
ログイン認証用のテーブルが必要なので、DB マイグレーションをします。
ログインアカウントの作成
$ python3.6 manage.py createsuperuser
createsuperuser コマンドで、ログインアカウントを作成します。
デバッグサーバの起動
$ python3.6 manage.py runserver 0.0.0.0:8000
Django デバッグサーバの runserver を起動し、http://IPアドレス:8000/page/ にアクセスします。すると、ログインページにリダイレクトされ、先ほど作成したアカウントでログインすると、/page/ にリダイレクトすることが確認できると思います。
また、ログインページに直でアクセスし、ログインすると、settings.py で設定した LOGIN_REDIRECT_URL にリダイレクトします。
まとめ
Django ログイン認証後にログイン認証前に要求した URL へのリダイレクトを実装しました。
私と同じようにミドルウェアで login_required の代わりを実装し、next ができなくなった人が、この記事を読んで少しでも役に立ってもらえれば、とても嬉しいです。