目次
こんにちは。sinyです。
この記事では、Djangoのユーザセッション管理機能の実装事例についてご紹介します。
通常のユーザ認証機能の実装
まずは、通常のDjangoユーザ認証の実装を行います。
settings.pyの設定
まずは、認証系に必要なsettngis.pyの設定を追加します。
LOGIN_URL='/accounts/' LOGIN_REDIRECT_URL='/accounts/' LOGOUT_REDIRECT_URL='/accounts/login'
パラメータ | 説明 |
LOGIN_URL |
ログオンが必要なページに認証していないユーザがアクセスした場合にリダイレクトするURLを指定 |
LOGIN_REDIRECT_URL |
ログオン後にリダイレクトされるURLを指定 |
LOGOUT_REDIRECT_URL |
ログアウト後にリダイレクトされるURLを指定 |
ユーザ認証用アプリケーションの作成
以下のコマンドでユーザ認証用に新規アプリケーション(accounts)を作成します。
python manage.py startapp accounts
また、settings.pyのINSTALLED_APPに「 'accounts.apps.AccountsConfig',」を追加しておきます。
urls.pyの設定
まず、プロジェクト直下のurls.pyを以下の通り設定します。
from django.contrib import admin from django.urls import path,include from django.conf.urls import include, url urlpatterns = [ path('admin/', admin.site.urls), path('accounts/', include('accounts.urls')), ]
続いて、accountsアプリケーション直下のurls.pyを以下の通り設定します。
from django.urls import path from . import views app_name = 'accounts' urlpatterns = [ path('login/', views.Login.as_view(), name='login'), path('logout/', views.Logout.as_view(), name='logout'), path('', views.index, name='index'), ]
- 4行目:accountsアプリケーションのURLの名前空間名を指定しています。
- 7行目:ログオン認証画面用のURLパターンを定義しています。(http://127.0.0.1/login)
- 8行目:ログアウト用のURLパターンを定義しています。
- 9行目:ログオン後のINDEXページを定義します。
ログオン用フォームの作成
accountsアプリケーション配下にforms.pyを新規作成し、以下の通り設定します。
from django.contrib.auth.forms import( AuthenticationForm ) class LoginForm(AuthenticationForm): #ログオンフォームの定義 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for fields in self.fields.values(): fields.widget.attrs['class'] = 'form-control' fields.widget.attrs['placeholder']= fields.label
- 1~3行目:Django標準のAuthenticationFormクラスを利用すると簡単にログオン用フォームを生成することができるため、django.contrib.auth.formsからAuthenticationFormをインポートしています。
- 5行目:AuthenticationFormクラスを承継してログオン用のフォームクラス(LoginForm)を定義しています。
-
9~11行目:ここでは以下の設定を行っています。
①全てのフォームの部品のclass属性に「form-control」を指定(bootstrapのフォームデザインを利用するため)
②全てのフォームの部品にpaceholderを定義して、入力フォームにフォーム名が表示されるように指定。
※上記のすべてのフォームとは、AuthenticationFormクラス内で定義されているfields(フォームのカラム:ユーザ名、パスワード)のことです。
※Djangoではウィジェット(widget)と呼ばれる HTML の入力エレメントを表現するためのオブジェク トが存在していて、field.widget.attrs[属性] = <設定値>という形でフォームのデザインを定義することができます。
accountsアプリケーションのviews.py設定
accountsアプリケーションのviews.pyに以下のコードを追記します。
from django.shortcuts import render from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.views import( LoginView, LogoutView ) from . forms import LoginForm class Login(LoginView): #ログインページ form_class = LoginForm template_name = 'accounts/login.html' class Logout(LoginRequiredMixin, LogoutView): #ログアウトページ template_name = 'accounts/login.html' def index(request): return render(request, 'accounts/index.html')
- 9行目:LoginViewクラスを承継して、ログオン用クラス(Login)を定義しています。
- 11行目:form_classでログオンページに利用するフォームクラス(LoginForm)を指定しています。
- 12行目:ログオンページ用のテンプレートファイルを指定しています。
- 15行目:LoginRequireMixin、LogoutViewクラスを承継してログアウト用のクラス(Logout)を定義しています。
LoginRequireMixinクラスを承継しているのは、ログオンしているユーザでないとログアウトボタンを押せないようにするためです。(LoginRequiremixinはログオンしているユーザだけが制御できるようにするためのクラス)
template_nameでログアウト後に表示されるテンプレートファイルを指定します。 - 20行目:ログオン後に遷移するテスト用のTOPページを定義した関数です。
今回は、特にモデルの定義はしないので、一旦このままマイグレーションを実行しておきます。
python manage.py makemigrations python manage.py migrate
テンプレートの作成
まずは、テンプレートの場所をプロジェクト直下のtemplatesに変更するためにsettings.pyに以下の設定をします。
'DIRS': [os.path.join(BASE_DIR, 'templates')],
EMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], # DIRSに設定を入れる。 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
プロジェクトフォルダ直下に「\templates\accounts」を作成します。
続いて、base.html(共通テンプレート)、index.html(ログオン後のTOP画面),login.html(ログオン画面)を以下の通り作成します。
※テンプレート内の細かいデザインの設定部分は設定例ですので、必ずしも同じでなくてよいです。
■base.html
<!doctype html> <html lang="ja"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous"> <title>Tutorialサイト</title> </head> <body> <!-- ナビバー --> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <a class="navbar-brand" href="{% url 'accounts:login' %}">Tutorialサイト</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> {% if user.is_authenticated %} <a class="btn btn-primary" href="{% url 'accounts:logout' %}">ログアウト</a> {% endif %} </div> </nav> <!-- メインコンテント --> <div class="container mt-3"> {% block content %}{% endblock %} </div> <!-- Optional JavaScript --> <!-- jQuery first, then Popper.js, then Bootstrap JS --> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js" integrity="sha384-cs/chFZiN24E4KMATLdqdvsezGxaGsi4hLGOzlXwp5UZB1LY//20VyM2taTB4QvJ" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js" integrity="sha384-uefMccjFJAIv6A+rW+L4AHf99KvxDjWSu1z9VI8SKNVmz4sk7buKt/6v9KI65qnm" crossorigin="anonymous"></script> </body> </html>
認証済みユーザの場合、Userオブジェクトのis_authenticatedにTrueが設定されます。
■index.html
{% extends "accounts/base.html" %} {% block content %} ログオンしました。 {% endblock %}
■login.html
{% extends "accounts/base.html" %} {% block content %} <form action="" method="POST"> <div class="col-md-6 offset-md-3"> <div class="card"> <div class="card-body"> {% for field in form %} {{ field }} <hr> {% endfor %} {% for error in form.non_field_errors %} <div class="alert alert-danger" role="alert"> <p>{{ error }}</p> </div> {% endfor %} <button type="submit" class="btn btn-success btn-lg btn-block" >ログイン</button> <input type="hidden" name="next" value="{{ next }}" /> {% csrf_token %} </div> </div> </div> </form> {% endblock %}
- 9~12行目:テンプレートタグのfor文でformオブジェクトから要素を1つずつfield変数に格納し、{{field}}でフォームを表示しています。
-
14~18行目: 認証時のエラーは form.non_field_errorsから取得できます。
エラー内容は{{ error }}と記載することで表示できます。 -
以下のようにfor文の中にdivのclassでalert-dangerを指定することでエラーが発生した場合に赤枠でエラー内容が表示されるように設定しています。
以上で、http://127.0.0.1:8000/accounts/login/にアクセス後にログオンすると、以下のようにログオン画面が表示されます。
ログオンすると、以下のようなTOP画面にログオンします。
さて、ここまでは通常のユーザ認証機能の実装でした。
いよいよ、本題のユーザセッション管理機能を実装していきます。
ユーザセッション管理機能の実装
まず、必要なモジュールをpipでインストールします。
pip install django-user-sessions
settings.pyのINSTALLED_APPS に 'user_sessions' を追加します。
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'user_sessions', #追加 'accounts.apps.AccountsConfig', ]
settings.pyのMIDDLEWAREに「'user_sessions.middleware.SessionMiddleware',」を追加します。
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'user_sessions.middleware.SessionMiddleware', #追加 ]
また、settings.pyの末尾に「SESSION_ENGINE = 'user_sessions.backends.db'」を追加します。
次に、プロジェクト直下のurls.pyに以下の設定を追加します。
from django.contrib import admin from django.urls import path,include from django.conf.urls import include, url urlpatterns = [ path('admin/', admin.site.urls), path('accounts/', include('accounts.urls')), path('', include('user_sessions.urls', 'user_sessions')), #追加 ]
この時点で、再度「python manage.py migrate」を実行します。
以上で設定は完了です。
http://127.0.0.1:8000/account/sessions/にアクセスすると、以下のようなセッション管理画面が表示されます。
デフォルトだと上記画面の通り、ユーザ名が表示されていないのと、英語表記なのでカスタマイズしてみます。
上記画面のテンプレートをカスタマイズするには「Lib\site-packages\user_sessions\templates\user_sessions\session_list.html」を以下の通り修正します。
{% extends "user_sessions/_base.html" %} {% load user_sessions i18n %} {% block content %} {% trans "<em>unknown on unknown</em>" as unknown_on_unknown %} {% trans "<em>unknown</em>" as unknown %} <h1>{% trans "アクティブセッション一覧" %}</h1> <table class="table"> <thead> <tr> <th>{% trans "IPアドレス" %}</th> <th>{% trans "ユーザ名" %}</th> <th>{% trans "デバイス" %}</th> <th>{% trans "最終アクション" %}</th> <th>{% trans "セッションの終了" %}</th> </tr> </thead> {% for object in object_list %} <tr {% if object.session_key == session_key %}class="active"{% endif %}> <td>{{ object.ip}}</td> <td>{{ object.user }} </td> <td>{{ object.user_agent|device|default_if_none:unknown_on_unknown|safe }}</td> <td> {% if object.session_key == session_key %} {% blocktrans with time=object.last_activity|timesince %}{{ time }} ago (this session){% endblocktrans %} {% else %} {% blocktrans with time=object.last_activity|timesince %}{{ time }} ago{% endblocktrans %} {% endif %} </td> <td> <form method="post" action="{% url 'user_sessions:session_delete' object.pk %}"> {% csrf_token %} {% if object.session_key == session_key %} <button type="submit" class="btn btn-xs btn-link">{% trans "End Session" %}</button> {% else %} <button type="submit" class="btn btn-xs btn-warning">{% trans "End Session" %}</button> {% endif %} </form> </td> </tr> {% endfor %} </table> {% if object_list.count > 1 %} <form method="post" action="{% url 'user_sessions:session_delete_other' %}"> {% csrf_token %} <p>{% blocktrans %}You can also end all other sessions but the current. This will log you out on all other devices.{% endblocktrans %}</p> <button type="submit" class="btn btn-default btn-warning">{% trans "End All Other Sessions" %}</button> </form> {% endif %} {% endblock %}
{{ object.user }} → ユーザ名が表示されるように追加
再度、「http://127.0.0.1:8000/account/sessions/」にアクセスすると以下のような画面に変わります。
画面右側の「End Session」をクリックすると自分のセッションを切断することができます。
※上記管理画には自分のセッションしか表示されないようです。
上記画面は、自分自身のセッションしか切断できませんが、Adminサイト上からは全ユーザのセッションを切断することができます。
adminサイトにログオンすると「セッション」が追加されているのでクリックします。
ログオンしている全ユーザのセッション情報が表示されるので、切断したいユーザにチェックを入れてセッション切断を実行すれば、セッションをKILLすることができます。
以上、「Djangoユーザセッション管理画面の実装」でした。
参考になれば幸いです。