このページでは、Django でのログイン機能の実現方法・作り方について解説していきます。
ウェブアプリではログイン機能を備えているものが多いため、Django でウェブアプリを開発していくのであれば Django でのログイン機能の実現方法は知っておいたほうが良いと思います。そのため、この Django 入門 の連載の中でもログイン機能の実現方法について解説することにしました。
このログイン機能を実現するためには、今まで Django 入門 の連載の中で解説してきた内容のほぼすべてを活用する必要があります。これまでに学んできたことを復習するという意味でもログイン機能の実現方法について学ぶのは有意義であると思います。
解説の中では、適宜、これまでに解説してきた内容へのリンクを貼るようにしていますので、忘れている内容をリンク先のページで思い出しながら、ログイン機能の実現方法について学んでいただければと思います!
また、このページは、これまでの Django 入門 の連載を読み進めてきてくださった方向けの解説を行っていきます。個別に、ログイン機能の実現方法のみ知りたいという方は、下記のページでも解説を行っていますので、こちらを参照していただいた方が良いと思います。
【Django】ログイン機能の実現方法(関数ベースビュー編)Contents
ログイン機能の実現方法
まずは「そもそもログインってどんな機能なのか?」という点を考えながら、ログインを実現するために必要になる機能や実装について考えていきたいと思います。
ログイン機能に必要となる処理
ウェブアプリにおけるログインとは、①ユーザーの情報を入力してウェブアプリへ送信し、ウェブアプリが提供する機能(サービス)の利用を要求することになります。そして、②ウェブアプリがユーザーに対してログインを許可することで、そのユーザーは③ユーザーに応じた機能が利用できるようになります。逆に言えば、④非ログインユーザーはウェブアプリの機能が利用できないということになります(もしくは使用できる機能が制限される)。
また、ログイン機能を設けるウェブアプリでは、ログアウトによって、⑤機能の利用を終了することができます。ログアウトをすればログインしていない状態になるため、④のとおり、ウェブアプリの機能が利用できなくなります。
大雑把にログインについてまとめると上記のようになると思います。重要な箇所が、太字で示した①~⑤の部分になります。
続いて、この①~⑤の部分を深掘りし、ログイン機能に必要なウェブアプリ側の動作・処理を洗い出していきたいと思います。特に、『』で囲った部分が、ログイン機能を搭載するために実現が必要となる項目となります。
①ユーザーの情報を入力してウェブアプリへ送信
まず、ウェブアプリでログインを実現するためには、「ユーザーの情報」を入力してウェブアプリへ送信する手段が必要となります。ウェブアプリの場合、こういった情報の送信手段のユーザーへの提供は、基本的にはフォームによって実現することになります。
今回は、ログインを行うためのフォームが必要となるため『ログインフォーム』を用意し、これでユーザーの情報のウェブアプリへの送信を実現していきます。
また、このログインフォームでは、「ユーザー名」と「パスワード」の入力受付が行えるようにしていきたいと思います。
②ウェブアプリがユーザーに対してログインを許可する
さらに、ウェブアプリは、ユーザーからログインフォームで送信されてきた「ユーザー名」と「パスワード」を受け取り、そのデータを送信してきたユーザーにログインを許可するかどうかを判断する必要があります。
特に、ユーザー名とパスワードがログイン時に入力される場合、一般的にはパスワード認証が行われることになります。このパスワード認証では、送信されてきた「ユーザー名」のユーザーが既に登録済みで、さらに、そのユーザーに設定されているパスワードと、送信されてきた「パスワード」とが一致する場合に認証 OK と判断されます。ログインの場合は、認証 OK であればログインを許可するという感じの動作になります。
で、このようなパスワード認証を実現するためには、まず『ユーザーの情報のデータベースでの管理』を実現する必要があります。このユーザーの情報には、ユーザー名とパスワードが含まれる必要があります。要は、データベースにユーザーの情報(ユーザー名とパスワードを含む)を管理するためのテーブルが必要です。
さらに、事前にユーザーを登録しておく必要があるため、ユーザー登録機能も必要となります。この機能は、『ユーザー登録フォーム』を用意してユーザーの情報(ユーザー名とパスワードを含む)の入力受付を行い、その入力された情報に基づいたレコードを上記のテーブルに保存することで実現できます。
そして、ログインフォームからユーザー名とパスワードが送信されてきた時に、受け取った情報とデータベースに保存されているユーザーの情報を用いて『パスワード認証』および『ログインの許可』を行えるようにする必要があります。
③ユーザーに応じた機能を利用できる
さらに、ログインが許可されたユーザーは、ウェブアプリの機能を利用することになります。
ここで重要なのが、ログイン中のユーザーに応じた機能・動作が実現できるようにウェブアプリを開発する必要があるという点になります。
「ログイン中のユーザー」とは、アプリをしているログイン状態のユーザーのことです
例えば、これまで開発してきたログイン実現前の掲示板アプリの場合、コメントを投稿する際に投稿者をユーザーが手動で選択する必要がありました。
それに対し、ログイン実現後の掲示板アプリでは、コメントを投稿する際に、ログイン中のユーザーに応じて投稿者が自動で選択されるようにする必要があります。Twitter などでも、ツイートすれば自動的にログイン中のユーザーがツイート主として設定されるようになっていはずです。あれと同じような動作を実現する必要があります。
他にも、ショッピングアプリでも、購入履歴のページはログイン中のユーザーに応じて変化するようになっているはずです。
上記のような、ユーザーに応じた機能・動作の実現のためには、ウェブアプリ内部では、まず『ログイン中のユーザーの情報の取得』が必要となります。そして、その取得した情報を表示したり、その情報に基づいて条件分岐を行ったりすることが必要となります。
④非ログインユーザーはウェブアプリの機能が利用できない
ウェブアプリでは、非ログインユーザーからの機能の利用を制限するものが多いです。たとえば Twitter であれば、ログインしなくても他のユーザーのツイートを閲覧することはできますが、ログインしないとツイートできないようになっています。このように、非ログインユーザーに対し、一部の機能のみを制限するウェブアプリもありますし、全ての機能を制限するようなウェブアプリもあります。
したがって、ウェブアプリにログイン機能を搭載するのであれば、ログインだけでなく、『非ログインユーザーへの機能制限』も実現していく必要があります。
⑤機能の利用を終了する
ログインをウェブアプリで実現するのであれば、ログインの逆の操作となる『ログアウト』も実現していく必要があります。ログアウトは、ログイン中のユーザーを非ログイン状態にするための操作となります。
たとえば1台の PC を複数人で利用するような場合、ログアウト操作ができないとログイン状態が継続されることになり、その状態のまま他の人が PC を利用すると、自分のユーザーアカウントで他の人にウェブアプリの操作等が行われる危険性があります。ログアウトは、ウェブアプリのセキュリティを向上する上で必要となります。
したがって、ユーザーが、ユーザーの望むタイミングでログアウトできるように、ウェブアプリからユーザーにログアウト手段を提供する必要があります。これは、ログアウト用のリンクをページに表示しておくことで実現されることが多いので、このページでも、ログアウトはログアウト用のリンクがクリックされた時に実行することを前提に解説を進めていきたいと思います。
ログイン機能搭載に実現が必要な項目のまとめ
ここまで説明してきたように、ログイン機能をウェブアプリに搭載するためには下記のような項目を実現していく必要があります(以降での解説に合わせて順序を変更しています)。最後の1つに関しては、ここまでの説明の中では登場してきませんでしたが、ログイン機能を搭載したウェブアプリの使いやすさを向上させるために必要な項目となります。
- ユーザーの情報のデータベースでの管理
- ユーザー登録フォーム
- ログインフォーム
- パスワード認証
- ログインの許可
- ログイン中のユーザーの情報の取得
- 非ログインユーザーへの機能制限
- ログアウト
- 適切なレスポンスの返却
ここからは、上記の9つの項目に対して、1つ1つ実現方法を説明していきたいと思います。
スポンサーリンク
ユーザーの情報のデータベースでの管理の実現
最初に、ユーザーの情報のデータベースでの管理の実現方法について解説していきます。
カスタムユーザー
の定義
まず、下記のモデルの解説ページでも説明している通り、データベースで管理するデータの形式はテーブルの持つフィールドによって決まります。そして、テーブルの持つフィールドは、Django においてはモデルクラスの定義によって決まります。なので、ユーザー名やパスワード等を管理するようにしたいのであれば、これらのフィールドを持つモデルクラスを定義してやれば良いことになります。
【Django入門6】モデルの基本ただ、こういった、ユーザーを管理するモデルクラスとしては、Django では下記ページで解説している カスタムユーザー
を利用するのが良いです。この カスタムユーザー
では、ユーザーを管理するための基本的な機能やフィールドが備えられており、さらにパスワードも暗号化した状態で安全に扱うことができます。
ということで、カスタムユーザー
をモデルクラスとして定義すれば、ウェブアプリでのユーザーの情報のデータベースでの管理が実現できることになります。カスタムユーザー
に関しては上記のページで詳しく解説していますので、詳細に関しては上記ページを参照していただければと思います。
AUTH_USER_MODEL
の設定
また、カスタムユーザー
をモデルクラスとして定義した場合は、settings.py
に下記を追記してプロジェクトで使用するユーザー管理モデルとして カスタムユーザー
を設定しておく必要もあります。これについても、先ほど紹介した カスタムユーザー
の解説ページで説明していますので、詳細に関しては カスタムユーザー
の解説ページを参照してください。
AUTH_USER_MODEL = 'アプリ名.カスタムユーザーのクラス名'
以降では、この AUTH_USER_MODEL
に設定されたモデルクラスのことを、単に AUTH_USER_MODEL
と記していきます。上記のように settings.py
への追記が行われている場合、AUTH_USER_MODEL = カスタムユーザー
となります。
ログインを実現していく上でポイントになるのが、ユーザー登録フォームからのレコードの新規保存先のテーブルと、パスワード認証時にレコードを参照するテーブルとを一致させることになります。そして、そのテーブルが カスタムユーザー
のテーブルとなるように実装していくことが重要です。
後述で説明するように、パスワード認証時には AUTH_USER_MODEL
に設定されたモデルクラスのテーブルが参照されるようになっています。それに対し、ユーザー登録フォームからのレコードの新規保存先のテーブルは、フォームの定義時に指定する model
属性によって決まります。したがって、この model
属性には AUTH_USER_MODEL
に設定されたモデルクラスを指定する必要があります。さらに、settings.py
でAUTH_USER_MODEL
に カスタムユーザー
を指定しておけば、ユーザー登録フォームからのレコードの新規保存先のテーブルと、パスワード認証時にレコードを参照するテーブルとの両方が カスタムユーザー
になることになります。
2つのテーブルが異なっていると、ユーザー登録によってユーザーの情報が登録されるテーブルと、パスワード認証時に参照するテーブルが異なることになるので、ユーザー登録を行なったとしても、その登録時に入力したユーザー名やパスワードではパスワード認証に成功しないことになってしまいます。
前述の通り、パスワード認証時には AUTH_USER_MODEL
に設定されたモデルクラスのテーブルが参照されるように Django フレームワークが作られているので、気をつけるべきは、ユーザー登録フォームの model
属性に指定するモデルクラスになります。この辺りは、後述でも補足しながら説明していきます。
参考:AUTH_USER_MODEL
のデフォルト設定
settings.py
で AUTH_USER_MODEL
の設定を行わなかった場合は、デフォルトの設定が用いられ、具体的には AUTH_USER_MODEL = User
となります。
この User
は Django フレームワークに用意されたユーザーを管理するモデルクラスとなります。この User
を利用してログインを実現していくことも可能なのですが、ユーザーを管理するモデルクラスはウェブアプリに合わせてカスタマイズすることが多いので、カスタマイズしやすい カスタムユーザー
を定義して利用することが推奨されています。そのため、このページでも カスタムユーザー
を定義し、これをプロジェクトで AUTH_USER_MODEL
として使用することを前提に解説を進めます。
ユーザー登録フォームの実現
続いて、ユーザー登録フォームの実現方法について解説していきます。
ユーザー登録フォームとは、もう少し具体的に言うと、ユーザーから各種フィールドに入力されたデータを、レコードとして「ユーザーの情報を管理するテーブル」に新規保存するためのフォームであると言えます。
こういった、フィールドに入力されたデータをレコードとしてテーブルに保存するようなフォームを実現する際には、Django では下記ページで解説したモデルフォームを利用することが多いです。
【Django入門8】モデルフォームの基本モデルフォームとは、ModelForm
というクラスを継承して定義されたフォームで、レコードの保存先となるモデルクラス(テーブル)を model
属性に、さらに、そのモデルクラスの持つフィールドの中で “フォームに表示したいフィールド” を fields
属性に指定してやれば、fields
属性に指定されたフィールドの入力受付を行うフォームが実現できます。また、このモデルクラスの save
メソッドにより、そのフォームから送信されてきたデータを、model
属性に指定したテーブルにレコードとして保存することができます。
で、先ほど説明した通り、今回はレコードの保存先としては AUTH_USER_MODEL
のテーブルを設定する必要があるため、model
属性には AUTH_USER_MODEL
、すなわち カスタムユーザー
を指定することになります。さらに fields
属性に、ユーザー登録フォームで入力受付したいフィールドを指定してやれば、ユーザー登録フォームが実現できることになります。
ただ、今回はログイン時に使用するパスワードをテーブルで管理することになるため、ModelForm
ではなく、ModelForm
のサブクラスである UserCreationForm
を継承してフォームを定義する方が良いです。UserCreationForm
を継承したフォームを利用することで、ユーザーが入力したパスワードを暗号化した状態でテーブルに保存することができるようになります。
具体的には、下記のように forms.py
にフォームを定義してやれば、そのフォームをユーザー登録フォームとして利用できるようになります。ポイントは get_user_model
関数の返却値を model
属性に指定している点になります。get_user_model
関数の返却値は、前述で説明した AUTH_USER_MODEL
となりますので、get_user_model
関数の返却値を model
属性に指定しておくことで、AUTH_USER_MODEL
のテーブルと対応したフォームが定義できることになります。
from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm
User = get_user_model()
class CreateUserForm(UserCreationForm):
class Meta:
model = User
fields = [User.USERNAME_FIELD] + User.REQUIRED_FIELDS
UserCreationForm
や get_user_model
、さらに USERNAME_FIELD
や REQUIRED_FIELDS
に関しては、下記の カスタムユーザー
の解説ページで解説していますので、詳細を知りたい方は下記ページを参照していただければと思います。
ログインフォームの実現
次は、ログインフォームの実現方法について解説していきます。
今回は、前述のとおりユーザー名とパスワードの入力受付を行うフォームをログインフォームとして実現していくことになります。フォーム自体は、下記ページで解説したフォームを利用すれば実現できます。
【Django入門5】フォームの基本具体的には、Form
を継承するクラスを定義し、そのクラスにクラス変数としてフィールドを定義すれば、そのフィールドの入力受付を行うフォームが実現できます。このログインフォームでは、フォームに入力された情報をテーブルに保存するわけではないので、前述のモデルフォームを利用する必要はありません。
ただ、単なるフォームを定義するのであれば、上記のように Form
を継承するクラスを定義してやれば良いのですが、ログインフォームを実現するのであれば、Form
ではなく、Form
のサブクラスである AuthenticationForm
を継承して、つまり AuthenticationForm
のサブクラスとしてフォームを定義する必要があります。
具体的には、下記のように定義を行うことでログインフォームが実現できます。AuthenticationForm
は django.contrib.auth.forms
で定義されていますので、事前に下記の最初の行のように import
を実行する必要があります。
from django.contrib.auth.forms import AuthenticationForm
class LoginForm(AuthenticationForm):
pass
以降でも AuthenticationForm
についての解説を行いますが、このページで解説する AuthenticationForm
に関する説明内容は、上記の LoginForm
のような、AuthenticationForm
のサブクラスにも共通のものになります。
AuthenticationForm
の特徴
この AuthenticationForm
は、下記のような特徴を持った Form
のサブクラスとなります。前述でも少し説明しましたが、この AuthenticationForm
は AUTH_USER_MODEL
に指定されたモデルクラスを利用して動作するフォームになります。
AUTH_USER_MODEL
のクラス変数USERNAME_FIELD
に指定されたフィールドとpassword
フィールドがデフォルトで定義されているpassword
フィールドに入力したパスワードは伏字で表示される
- このインスタンスで
is_valid
メソッドを実行すれば、AUTH_USER_MODEL
のテーブルが参照されてパスワード認証が実行される - このインスタンスで
get_user
メソッドを実行すれば、フォームに入力されたユーザー名を持つAUTH_USER_MODEL
のインスタンスが取得できる
カスタムユーザー
のクラス変数 USERNAME_FIELD
のデフォルトは 'username'
となっていますので、AUTH_USER_MODEL
に カスタムユーザー
を指定し、かつ、USERNAME_FIELD
を変更していなければ、AuthenticationForm
を継承するだけで、上記の特徴 1. によって username
フィールドと password
フィールド、すなわちユーザー名と、パスワードの入力の受付を行うログインフォームが実現できることになります。
逆に言えば、USERNAME_FIELD
を変更すれば、他のフィールドでのログインも実現できることになります。例えばメールアドレスでのログインの実現も可能です。ただ、今回はユーザー名でのログインを実現しようとしているので、USERNAME_FIELD
は変更しない前提で説明を進めていきます。
また、特徴 2. と特徴 3. に関しては、パスワード認証とログインに関わる特徴となりますので、後述の パスワード認証の実現 と ログインの許可の実現 で解説を行います。
AuthenticationForm
のフォームの使い方
AuthenticationForm
のサブクラスとして定義したフォームの使い方について補足しておくと、この AuthenticationForm
は前述のとおり Form
のサブクラスであるため、AuthenticationForm
のサブクラスとして定義したフォームのページへの表示方法や、フォームをビューで扱う流れ等に関しては Form
のサブクラスとして定義したフォームと基本的には同様になります。Form
のサブクラスとして定義したフォームの扱い方については下記ページで解説していますので、詳細を知りたいという方は下記ページを参照していただければと思います。
ただし、先ほど示した特徴にも記載した通り、AuthenticationForm
の場合は is_valid
メソッドの意味合いが若干異なり、さらに Form
には存在しない get_user
メソッドなどが AuthenticationForm
に追加されていたりします。そして、これらのメソッドがログインの実現に重要な役割を果たすことになります。また、AuthenticationForm
のコンストラクタの引数は Form
のコンストラクタと異なります。これらに関しては次の節以降で解説していきます。
スポンサーリンク
パスワード認証の実現
ここからは、パスワード認証の実現方法について解説していきます。
ここまでは主に、ログイン機能を実現する上で必要となるモデルやフォームに関する解説を行ってきましたが、ここで説明するパスワード認証や、次節で説明するログインの許可に関してはビューで実施することになります。なので、ここからは主にビューの実装内容についての解説を行うことになります。
また、ここからは、ビューの関数の第1引数の引数名が request
であることを前提に解説を進めますので、この点についてもご注意ください。
パスワード認証の実現方法
まず、パスワード認証の実現方法に関して説明していきます。
先ほど、AuthenticationForm
が下記のような特徴を持った Form
のサブクラスであると解説しました。
AUTH_USER_MODEL
のクラス変数USERNAME_FIELD
に指定されたフィールドとpassword
フィールドがデフォルトで定義されているpassword
フィールドに入力したパスワードは伏字で表示される
- このインスタンスで
is_valid
メソッドを実行すれば、AUTH_USER_MODEL
のテーブルが参照されてパスワード認証が実行される - このインスタンスで
get_user
メソッドを実行すれば、フォームに入力されたユーザー名を持つAUTH_USER_MODEL
のインスタンスが取得できる
2. に記載のとおり、パスワード認証は AuthenticationForm
のインスタンスに is_valid
メソッドを実行させることで行うことができます。is_valid
は、本来フォームから送信されてきた各種フィールドの値が妥当であるかどうかを判断するメソッドになるのですが、AuthenticationForm
の is_valid
では、フォームから送信されてきたユーザー名とパスワードに対してパスワード認証が実施され、パスワード認証が OK であれば True
が、NG であれば False
が返却されるようになっています。
こういったフォームを扱って機能を実現するのはビューの役割であるため、まずフォームから送信されてきたデータから AuthenticationForm
のインスタンスを生成し、そのインスタンスに is_valid
メソッドを実行させるような関数を views.py
に定義してやれば、パスワード認証が実現できることになります。
さらに、その is_valid
メソッドの返却値が True
の場合のみ、後述で説明するログインを許可する処理を実行するようにしてやれば、ログイン機能まで実現できることになります。また、is_valid
メソッドの返却値が False
の場合は、他のフォームと同様に、再度ユーザー名とパスワードを再度入力してもらうためにフォームの再表示を行うことが多いと思います。
で、上記のような、フォームのインスタンスを生成して is_valid
メソッドを実行し、is_valid
メソッドの返却値が True
の場合のみ “なんらかの機能” を実行し、False
の場合はフォームの再表示を行うという流れは、フォームを扱うビューの処理の流れとしては典型的なものとなります。このあたりに関しても下記ページで解説していますので、詳細を知りたい方は下記ページを参考に指定いただければと思います。
なので、ログイン機能と言えども、他の機能と同じようなビューの実装で実現できることになります。
AuthenticationForm
のコンストラクタの引数
ただ、1点注意していただきたいのが、AuthenticationForm
のインスタンスを生成する際に実行する AuthenticationForm
のコンストラクタの引数が、Form
とは異なるという点になります。
Form
の場合、フォームから送信されてきたデータからインスタンスを生成するのであれば、コンストラクタの第1引数 or data
引数に request.POST
を指定する必要がありました。
request.POST
はフォームから送信されてきたデータとなります
それに対し、AuthenticationForm
では、コンストラクタの第1引数には必ず request
そのものを指定する必要があります。さらに、第2引数 or data
引数で request.POST
を指定する必要があります。
form = AuthenticationForm(request, request.POST)
ここまでの説明のように、Form
のサブクラスであっても、AuthenticationForm
のサブクラスであっても、基本的にはビューからの扱い方は同様になるのですが、コンストラクタの引数の指定の仕方は異なるので、この点には注意してください。
各フィールドが空の状態のフォームを生成する際は、AuthenticationForm
の場合も、Form
の場合と同様にコンストラクタの引数は不要です
パスワードの暗号化
ここで、AuthenticationForm
の is_valid
メソッドで行われるパスワード認証について、特にパスワードの暗号化に焦点を当てて補足説明をしておきたいと思います。
AuthenticationForm
の is_valid
メソッドで行われるパスワード認証では、AUTH_USER_MODEL
のテーブルに “フォームから送信されてきたユーザー名” と “username
フィールド” が一致するレコードが存在し、さらに、そのレコードの “password
フィールド” と “フォームから送信されてきたパスワードを暗号化(ハッシュ化)した結果” とが一致する場合に認証 OK と判断されます。
ここでポイントになるのが、AUTH_USER_MODEL
のテーブルに保存されるレコードの password
フィールドのデータは暗号化されている必要があるという点になります。AuthenticationForm
の is_valid
メソッドでは、送信されてきたパスワードが暗号化され、暗号化された状態のデータで、テーブルに保存されているレコードの password
フィールドと比較されることになります。なので、テーブルに保存されているレコードの password
フィールドのデータは、送信されてきたパスワードを暗号化する時と同じアルゴリズムで暗号化されている必要があります。例えば、password
フィールドのデータが暗号化されずに平文の状態であれば、平文のデータと暗号化されたデータとで比較されるため、いくら正しいパスワードでログインしようとしてもパスワード認証の結果が必ず NG となってしまいます。
で、この password
フィールドのデータの暗号化を実現するのが、ユーザー登録フォームの実現 で紹介した CreateUserForm
のような、UserCreationForm
のサブクラスとして定義し、さらに model
属性に AUTH_USER_MODEL
(get_user_model
の返却値) を指定したフォームとなります。このフォームから送信されてきたデータを、このフォームのインスタンスの save
メソッドで保存すれば、password
フィールドが暗号化された状態のレコードが AUTH_USER_MODEL
のテーブルに保存されることになります。さらに、この暗号化は、AuthenticationForm
で行われる暗号化と同じアルゴリズムで実施されることになるため、AUTH_USER_MODEL
のテーブルへのレコードの保存に UserCreationForm
のサブクラスとして定義したフォームを利用してやれば、パスワード認証が正常に動作することになります。
ということで、UserCreationForm
と AuthenticationForm
は常にセットで利用するようにしてください。特にログインを実現する際には、ユーザーを登録するフォームに UserCreationForm
を、ログインを行うフォームに AuthenticationForm
を利用する必要があります。
ログインの許可の実現
さて、先ほど説明したように、パスワード認証に成功した場合、すなわち is_valid
メソッドの返却値が True
の場合にはログインの許可を実行することになります。
このログインの許可は、Django フレームワークから提供される login
という関数を実行することで実現できます。
この login
関数の第1引数には request
を、さらに第2引数には、ログインを許可する AUTH_USER_MODEL
のインスタンスを指定します。
login(request, AUTH_USER_MODELのインスタンス)
このように引数を指定して login
関数を実行すれば、第2引数に指定された AUTH_USER_MODEL
のインスタンスに対してログインが許可され、このインスタンスがログイン状態となります。AUTH_USER_MODEL
はユーザーを管理するテーブルですから、「AUTH_USER_MODEL
のインスタンス=ユーザー」であり、つまりはユーザーに対してログインの許可が行われ、そのユーザーがログイン状態となることになります。
そして、前述のとおり、この login
関数を実行するのは AuthenticationForm
のインスタンスの is_valid
メソッドの実行結果が True
となった場合です。さらに、前述でも示した下記の AuthenticationForm
の特徴にも記載している通り、AuthenticationForm
のインスタンスに get_user
メソッドを実行させれば、フォームに入力されたユーザー名を持つ AUTH_USER_MODEL
のインスタンスが取得できます。
AUTH_USER_MODEL
のクラス変数USERNAME_FIELD
に指定されたフィールドとpassword
フィールドがデフォルトで定義されているpassword
フィールドに入力したパスワードは伏字で表示される
- このインスタンスで
is_valid
メソッドを実行すれば、AUTH_USER_MODEL
のテーブルが参照されてパスワード認証が実行される - このインスタンスで
get_user
メソッドを実行すれば、フォームに入力されたユーザー名を持つAUTH_USER_MODEL
のインスタンスが取得できる
なので、AuthenticationForm
のインスタンスの is_valid
メソッドが True
であった場合に、そのインスタンスに get_user
メソッドを実行させたときの返却値を login
関数の第2引数に指定すれば、パスワード認証に成功したユーザーに対してログインの許可を行うことができます。そして、ログインが許可されたユーザーは、以降、ログアウトするまでログイン状態となります。
ということで、ここまで説明してきた通り、AuthenticationForm
を利用することで、ログインフォームの表示・パスワード認証・ログインの許可を実現することが可能となります。
ここで、ここまでのまとめとして、ログインフォームの表示、およびログインの許可を行うビューの関数の例を示しておきます。下記における LoginForm
は AuthenticationForm
のサブクラスとなります。また、redirect
や render
に関しては、開発するウェブアプリに合わせて引数を適切に変更する必要があるので注意してください。
from django.shortcuts import redirect, render
from django.contrib.auth import login
from .forms import LoginForm
def login_view(request):
if request.method == 'POST':
form = LoginForm(request, request.POST)
if form.is_valid():
user = form.get_user()
if user:
login(request, user)
return redirect('user', user.id)
else:
form = LoginForm()
context = {
'form': form,
}
return render(request, 'forum/login.html', context)
ログイン中のユーザーの情報の取得の実現
ということで、ここまでの解説の内容により、ログイン自体は実現できたことになります。
ただ、ログイン自体が実現できたとしても、そのログインを利用した機能・動作がウェブアプリで実現できないとログインの意味がありません。ここからは、そのログインを利用した機能・動作の実現に関する内容を説明していきます。
まず、ウェブアプリでは、ユーザーに応じた機能が実現できるように、ビューでログイン中のユーザーの取得を行い、そのユーザーの情報を表示したり、そのユーザーに応じて処理を切り替えるような動作が必要となります。
で、これを実現するためには、まずビューで「ログイン中のユーザー」を取得する必要があるのですが、これに関してはビューの関数の第1引数 request
から取得することが可能です。具体的には request.user
が、そのビューの関数の実行をリクエストしてきたユーザーとなります。また、request.user
は AUTH_USER_MODEL
のインスタンスとなります。
したがって、この request.user
を利用して処理を実行するようにすれば、ユーザーに応じて処理が変化するような機能を実現することができることになります。例えば、request.user
の各種フィールドの値をテンプレートファイルに埋め込めば、ログイン中のユーザーの情報をページとして表示することもできますし、request.user
自体 or request.user
の何かしらのフィールドの値を利用して処理を実行すれば、ログイン中のユーザーに応じた処理の実行が実現できることになります。
少し補足しておくと、request.user
に AUTH_USER_MODEL
のインスタンスがセットされるのは、ログイン状態のユーザーからのリクエストによってビューが実行された場合のみになります。非ログイン状態のユーザーからのリクエストによってビューが実行された場合には、AnonymousUser
というクラスのインスタンスがセットされることになります。この AnonymousUser
というクラスのインスタンスには、リクエストを送信してきたユーザーの情報がセットされていませんので、非ログイン状態のユーザーの情報は取得不可ということになります。
また、どちらのインスタンスもデータ属性 is_authenticated
を持っているため、このデータ属性から、リクエストを送信してきたユーザーがログイン状態か否かを調べることも可能です。具体的には、is_authenticated
が True
の場合はログイン状態のユーザーからリクエストが送信されてきている、is_authenticated
が False
の場合は非ログイン状態のユーザーからリクエストが送信されてきていると判断できます。
スポンサーリンク
非ログインユーザーへの機能制限の実現
ウェブアプリでは、非ログイン状態のユーザーへの機能制限を行うことも重要となります。
前述のとおり、ビューの関数の中で request.user.is_authenticated
を使ってリクエストを送信してきたユーザーが非ログイン状態であるかどうかを判断し、非ログイン状態の場合に処理を実行しないようにすれば機能制限を実現できることになります。
ただ、Django にはもっと便利な仕組みがあって、それが login_required
デコレーターになります。@login_required
をビューの関数の定義の上側に記述すれば、その関数は非ログイン状態のユーザーからのリクエストでは実行されないようになります。なので、非ログイン状態のユーザーには実行されたくない機能を実装しているビューの関数に対して login_required
デコレーターを適用してやれば、非ログイン状態のユーザーへの機能制限が実現できることになります。
もう少し詳しく説明すると、非ログイン状態のユーザーからのリクエストがあった場合、login_required
デコレーターが適用されたビューの関数が実行される代わりにリダイレクトレスポンスの返却が行われるようになります。これにより、非ログイン状態のユーザーからのリクエストによるビューの関数が実行されなくなるというわけです。このあたりの制御に関しては Django フレームワークによって自動的に行われます。
また、このリダイレクトレスポンスは、クライアントを “リダイレクト先の URL” に遷移させるためのレスポンスとなります。そして、このリダイレクト先の URL は settings.py
で設定可能です。具体的には、下記のように LOGIN_URL
の設定を settings.py
に追記すれば、login_required
デコレーターによってリダイレクトレスポンスが返却される際には、リダイレクト先の URL として LOGIN_URL
に設定された URL が指定されることになります。
LOGIN_URL = 'ログイン先のURL'
この LOGIN_URL
に、ログインフォームを表示するページの URL をセットしておけば、非ログインユーザーをログインフォームに誘導することができて便利だと思います。
ログアウトの実現
ログインを実現するのであれば、ログアウトの実現も必要となります。
ログアウトとは、ログイン状態のユーザーを非ログイン状態にする処理であり、これは、Django フレームワークから提供される logout
という関数を実行することで実現できます。logout
関数には第1引数で、ビューの関数の第1引数である request
を指定します。
下記は、ログアウトを実行するビューの関数の実装例となります。
from django.shortcuts import redirect, render
from django.contrib.auth import logout
def logout_view(request):
logout(request)
return render(略)
また、上記は手動でログアウト操作を行うことを想定した説明になりますが、Django で開発したウェブアプリでは、時間経過によって自動的にログアウトが実行されるようになっています。この自動ログアウトや、自動ログアウトまでの時間の設定方法は下記ページで解説していますので、詳細に関しては下記ページを参照してください。
【Django】自動ログアウト時間の設定方法適切なレスポンスの返却の実現
以上の解説により、ログイン機能に必要な処理や動作が実現できたことになります。
最後に、補足で、ログイン機能の実装時の注意点について説明しておきます。その注意点とは、「ビューの関数からは適切なレスポンスの返却を行う必要がある」になります。
例えば、先ほど説明したように、ログアウトの実現のために本質的に重要となるのは logout
関数の実行のみになります。この関数さえ実行すれば、ユーザーをログアウト状態にすることができます。なので、ログアウトを行う関数を下記のように実装して満足するようなことがあるのですが、これはビューの関数としては NG です。
def logout_view(request):
logout(request)
下記ページで解説しているように、ビューの関数では必ず HttpResponse
のサブクラスのインスタンスを return
する or 例外を発生させることが必要となります。
例外を発生させると、ユーザーが「ログアウトに失敗した?」とびっくりしてしまうので、基本的にはログアウトを行うビューでは HttpResponse
のサブクラスのインスタンスを return
するように実装するべきです。
render
関数や redirect
関数の返却値は HttpResponse
のサブクラスのインスタンスとなりますので、他のページヘリダイレクトさせたい場合は redirect
関数の返却値を、ログアウトに成功したメッセージを表示したいような場合は、それが実現できるテンプレートファイルを用意し、それを引数に指定して実行した render
関数の返却値を return
してやれば良いと思います。
で、これはログアウトだけでなくログインやユーザー登録を行うビューの関数にも言える話で、これらに成功した場合に表示させるページを検討し、それが実現できるよう、HttpResponse
のサブクラスのインスタンスを return
するビューの関数を実装していくようにしましょう。
スポンサーリンク
掲示板アプリにログイン機能を導入してみる
では、いつも通り、ここまで説明してきた内容を踏まえて、実際にログイン機能の導入例を示していきたいと思います。
この Django 入門 に関しては連載形式となっており、ここでは前回下記ページの 掲示板アプリでカスタムユーザーを利用してみる で作成したウェブアプリに対してログイン機能を導入する形で、ログイン機能の導入手順を示していきたいと思います。
【Django入門9】カスタムユーザーによるユーザー管理また、ユーザーの情報のデータベースでの管理の実現 と ユーザー登録フォームの実現 で説明したカスタムユーザー
とユーザー登録フォームに関しては、上記の カスタムユーザー
の解説ページにおける 掲示板アプリでカスタムユーザーを利用してみる で既に掲示板アプリに導入済みとなります。なので、このページでは、カスタムユーザー
(CustomUser
) とユーザー登録フォーム(RegisterForm
)が既に導入された状態の掲示板アプリへのログイン機能の導入手順を説明していくことになります。これらの導入手順を知りたい方は、別途上記ページの 掲示板アプリでカスタムユーザーを利用してみる を参照してください。
掲示板アプリのプロジェクト一式の公開先
この Django 入門 の連載を通して開発している掲示板アプリのプロジェクトは GitHub の下記レポジトリで公開しています。
https://github.com/da-eu/django-introduction
また、前述のとおり、ここでは前回の連載の 掲示板アプリでカスタムユーザーを利用してみる で作成したプロジェクトをベースに変更を加えていきます。このベースとなるプロジェクトは下記のリリースで公開していますので、必要に応じてこちらからプロジェクト一式を取得してください。
https://github.com/da-eu/django-introduction/releases/tag/django-customuser
さらに、ここから説明していく内容の変更を加えたプロジェクトも下記のリリースで公開しています。以降では、基本的には前回からの差分のみのコードを紹介していくことになるため、変更後のソースコードの全体を見たいという方は、下記からプロジェクト一式を取得してください。
https://github.com/da-eu/django-introduction/releases/tag/django-login
ログインの実現
では、早速ログインを実現していきます。
ログインフォームの定義
まずは、ログインフォームの定義を行います。ログインフォームの実現 で説明したように、ログインフォームは AuthenticationForm
を継承するだけで定義することができます。
今回は、下記のように forms.py
を変更して LoginForm
の定義を行いたいと思います。django.contrib.auth.forms
からの AuthenticationForm
の import
を忘れないように注意してください。また、RegisterForm
と PostForm
に関しては変更不要です。
from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from .models import Comment
User = get_user_model()
class LoginForm(AuthenticationForm):
pass
class RegisterForm(UserCreationForm):
class Meta:
model = User
fields = [User.USERNAME_FIELD] + User.REQUIRED_FIELDS
class PostForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['user', 'text']
widgets = {
'text': forms.Textarea
}
上記の LoginForm
は AuthenticationForm
を継承しているだけのクラスなので、別に AuthenticationForm
をそのままログインフォームとして利用してもウェブアプリの動作としては変わりません。ただし、今後カスタマイズが必要になる可能性もあるため、念のため上記のように AuthenticationForm
そのものではなく、上記の LoginForm
のような AuthenticationForm
のサブクラスを定義しておいた方が良いと思います。
ログイン用のビューの関数の定義
続いて、ログイン用のビューの関数の定義していきます。このログイン用のビューの関数の名前は login_view
としたいと思います。
この login_view
では、先ほど定義した LoginForm
を扱ってログインを実現していきます。
この login_view
に関しても、基本的には他のフォームを扱うビューの関数と同様の処理の流れで実現できることになります。このフォームを扱うビューの関数の基本的な処理の流れについては、下記ページの ビューでフォームクラスを扱うを参照していただければと思います。
ただし、パスワード認証の実現 でも説明したように、先ほど定義した LoginForm
のコンストラクタの引数には、第1引数に request
、第2引数(or data
引数)に request.POST
を指定する必要があるので注意してください。また、login_view
では、is_valid
メソッドの返却値が True
であった場合に実行する処理は login
関数の実行となります。また、login
関数実行後にもレスポンスの返却が必要となりますので、今回は、ログインを行ったユーザーの詳細ページへリダイレクトするようにしたいと思います。
現在開発しているウェブアプリでは、ユーザーの詳細ページの URL は下記に設定されていますので、この URL における <id>
部分にログインを行ったユーザーの id
フィールドの値を設定してやれば、ログインを行ったユーザーの詳細ページを表示することができます。
/forum/user/<id>/
また、このユーザーの詳細ページの URL には 'user'
という名前を設定していますので、return redirect('user', user.id)
を login
関数の後に実行すれば、上記のようなログイン後のリダイレクトを実現することが出来ます。
ということで、ログインフォームを扱うビューの関数 login_view
を定義するために、下記のように views.py
の変更を行います。
from django.shortcuts import redirect, render, get_object_or_404
from .forms import RegisterForm, PostForm, LoginForm
from .models import Comment
from django.contrib.auth import get_user_model, login
# 略
def login_view(request):
if request.method == 'POST':
form = LoginForm(request, request.POST)
if form.is_valid():
user = form.get_user()
if user:
login(request, user)
return redirect('user', user.id)
else:
form = LoginForm()
context = {
'form': form,
}
return render(request, 'forum/login.html', context)
パスワード認証の実現 と ログインの許可の実現 で解説した通り、is_valid
メソッドでパスワード認証を実施し、login
関数でログインの許可を行うという点が、この login_view
のポイントになると思います。
この login_view
によって、ログイン自体が実現できるようになりますが、login_view
の最後で実行している render
関数の引数に指定している forum/login.html
はまだ用意していないので、これに関しては、次の urls.py
の変更を行った後に作成していきます。
login_view
と URL とのマッピング
続いて、forum
フォルダ内の urls.py
を変更して、先ほど定義した login_view
と URL とのマッピングを行っていきます。具体的には、forum
フォルダ内の urls.py
を下記のように変更します(リストの末尾に要素を追加しています)。
from django.urls import path
from . import views
urlpatterns = [
path('', views.index_view, name='index'),
path('comments/', views.comments_view, name='comments'),
path('comment/<int:comment_id>/', views.comment_view, name='comment'),
path('users/', views.users_view, name='users'),
path('user/<int:user_id>/', views.user_view, name='user'),
path('register/', views.register_view, name='register'),
path('post/', views.post_view, name='post'),
path('login/', views.login_view, name='login'),
]
この forum
フォルダ内の urls.py
でのマッピング設定は、リクエストの URL が /forum/
から始まるときに参照されるようになっています。そのため、上記のように変更することで、 URL が /forum/login/
のリクエストをウェブアプリが受け取った時に login_view
が実行されるようになります。
さらに、name='login'
を指定しているため、/forum/login/
の URL に 'login'
という名前が設定され、ビューやテンプレートファイル等で、'login'
という名前から /forum/login/
を逆引きできるようになります。
ここで行った URL マッピングや URL の名前については下記ページで解説していますので、これらについて忘れてしまった方は下記ページを参照していただければと思います。
【Django入門3】ビュー(View)の基本とURLのマッピングログインフォーム表示用のテンプレートファイルの作成
ログインの実現に向け、最後にログインフォーム表示用のテンプレートファイルである login.html
を作成していきます。
ログインフォームもフォームの一種なので、ログインフォーム表示用のテンプレートファイルに関しても、今まで作成したフォーム表示用のテンプレートファイル(register.html
と post.html
)とほぼ同じ作りとなります。細部をログインフォーム用に変更してやれば良いだけです。
具体的には、このログインフォーム表示用のテンプレートファイル login.html
は下記のようなものになります。この login.html
は、他のテンプレートファイル同様に forum/templates/forum/
のフォルダの下に新規作成してください。
{% extends "forum/base.html" %}
{% block title %}
ログイン
{% endblock %}
{% block main %}
<h1>ログイン</h1>
<form action="{% url 'login' %}" method="post">
{% csrf_token %}
<table class="table table-hover">{{ form.as_table }}</table>
<p><input type="submit" class="btn btn-primary" value="送信"></p>
</form>
{% endblock %}
ところどころログインという文字列に変更していますが、基本的には register.html
や post.html
と同じ構成のファイルとなります。また、これらのファイル同様に、login.html
に関しても base.html
の継承するようにしています。テンプレートファイルの継承について詳しく知りたい方は、下記ページで解説していますので、こちらをご参照いただければと思います。
また、上記テンプレートファイルでは {% url 'login' %}
というテンプレートタグを利用しているため、上記テンプレートファイルによって表示されるフォームの 送信
ボタンを押せば、フォームに入力されたデータを含む POST
リクエストが 'login'
という名前の付けられた URL (すなわち /forum/login/
) に対して送信され、 /forum/login/
にマッピングされた login_view
が実行されることになります。
ということで、ここまでの変更により、URL が /forum/login/
であるリクエストをウェブアプリが受け取った時に login_view
が実行され、それによりログインフォームの表示やパスワード認証&ログインが実行される流れが実現できたことになります。
ユーザー登録後のログイン
ここまでは、ログインフォームからのログインを実現するための変更点についての解説を行ってきましたが、ユーザー登録後に自動的にログインされるようにした方が便利なので、ユーザー登録後にログインを行うようにウェブアプリを変更していきたいと思います。
ユーザー登録は views.py
の下記の register_view
で実現するようになっていますので、この register_view
にログインの許可を行う処理を追加していきます。
def register_view(request):
if request.method == 'POST':
form = RegisterForm(request, request.POST)
if form.is_valid():
form.save()
return redirect('users')
else:
form = RegisterForm()
context = {
'form': form
}
return render(request, 'forum/register.html', context)
まず、register_view
で使用している RegisterForm
はユーザー登録フォームで、AUTH_USER_MODEL
をベースとするモデルフォームとなります。具体的な定義に関しては ログインフォームの定義 を参照してください。
また、引数に request.POST
を指定して生成したモデルフォームのインスタンスに save
メソッドを実行させれば、そのモデルフォームのベースとなっているモデルクラス(model
属性に指定したモデルクラス)のインスタンスが取得できることになります。より具体的に言うと、テーブルに保存されたレコードに対応するインスタンスが取得できることになります。
なので、register_view
が実行している form.save()
で返却値を受け取るようにすれば、新規登録されたユーザー(レコード)に対応する AUTH_USER_MODEL
のインスタンスが返却値として得られるようになります。さらに、この返却値を第2引数に指定して login
関数を実行すれば、新規登録されたユーザーに対してログインの許可を行い、ログイン状態にすることができます。
ということで、register_view
を下記のように変更すれば、ユーザー登録後のログインが実現できることになります。ついでになりますが、ログインフォームからのログイン時と同様に、ログイン後には、ログインしたユーザーの詳細ページが表示されるよう、redirect
関数の引数を変更しています。
def register_view(request):
if request.method == 'POST':
form = RegisterForm(request, request.POST)
if form.is_valid():
user = form.save()
login(request, user)
return redirect('user', user.id)
else:
form = RegisterForm()
context = {
'form': form
}
return render(request, 'forum/register.html', context)
スポンサーリンク
ログイン中のユーザーに応じた処理
次は、ログイン中のユーザーに応じた処理が実行されるように掲示板アプリを変更していきたいと思います。
今回は、コメントに対する投稿者を、ログイン中のユーザーに応じて自動的に設定するように変更していきます。
現状の掲示板アプリでは、コメントの投稿時に、投稿者選択メニューから投稿者を手動で設定する必要がありました。
今回は、この投稿者選択メニューを削除し、投稿者がログイン中のユーザーに自動的に設定されるように変更していきたいと思います。
コメント投稿フォームの変更
まずは、投稿者選択メニューを削除するため、フォームの変更を行っていきたいと思います。
コメント投稿フォームは、現状では forms.py
で PostForm
として下記のように定義されています(import
等は省略しています)。この PostForm
の fields
属性に 'user'
が指定されているため、現状のコメント投稿フォームではユーザー選択メニューが表示されることになっています。
class PostForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['user', 'text']
widgets = {
'text': forms.Textarea
}
なので、fields
から 'user'
を取り除いてやれば、ユーザー選択メニューが表示されなくなります。ということで、下記のように PostForm
を変更すれば、投稿者選択メニューが存在しないコメント投稿フォームが実現できることになります。
class PostForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['text']
widgets = {
'text': forms.Textarea
}
コメント投稿用のビューの関数の変更
フォームで投稿者を選択できないようになったので、コメントの投稿者を自動的に設定するようにビューの関数を変更していきます。
この掲示板アプリでは、コメント投稿フォームを扱うビューの関数を下記の post_view
としています。この post_view
では、フォームから送信されてきたデータから、先ほど示した PostForm
のインスタンスを生成し、is_valid
メソッドで妥当性の検証を行った後に save
メソッドを実行しています。で、この PostForm
は Comment
をベースとするモデルフォームなので、PostForm
クラスの save
メソッドにより、フォームから送信されてきた各種フィールドの値が Comment
のインスタンスにセットされ、それがレコードとして Comment
のテーブルに保存されることになります。
def post_view(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
form.save()
return redirect('comments')
else:
form = PostForm()
context = {
'form': form,
}
return render(request, 'forum/post.html', context)
ただし、PostForm
の fields
属性から user
フィールドを取り除いたため、フォーム表示時にもuser
フィールド(投稿者選択メニュー)は表示されませんし、当然フォームから送信されてくるデータにも投稿者を示す user
フィールドが存在しなくなります。ただし、Comment
のインスタンスには user
フィールドが必須であるため、Comment
のインスタンスの user
フィールドにデータをセットしてから save
メソッドを実行させる必要があります。
また、user
フィールドにはコメントの投稿者をセットする必要があり、コメントの投稿者はログイン中のユーザーということになります。したがって、Comment
のインスタンスの user
フィールドには request.user
をセットしてやれば良いことになります。そして、これによって、「コメントの投稿者をログイン中のユーザーに自動的に設定する」が実現できることになります。ログイン中のユーザーの情報の取得の実現 でも解説している通り、request.user
は「ログイン中のユーザー」となります。
つまり、上記の post_view
を下記のように変更してやれば、「コメントの投稿者をログイン中のユーザーに自動的に設定する」を実現することができることになります。太字部分が変更点で、Comment
のインスタンスを form
から取得し、そのインスタンスの user
に request.user
をセットしてから、save
メソッドによってインスタンスのレコードとしての保存を行っています。
def post_view(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
comment = form.instance
comment.user = request.user
comment.save()
return redirect('comments')
else:
form = PostForm()
context = {
'form': form,
}
return render(request, 'forum/post.html', context)
今回は、単に投稿者をログイン中のユーザーに自動的に設定するのみの機能でしたが、request.user
を利用することで、様々な「ログイン中のユーザーに応じた機能や動作」が実現できるようになります。
非ログインユーザーへの機能制限
次は、非ログインユーザーからウェブアプリが利用できないように、非ログインユーザーへの機能制限を行っていきます。
機能制限するビューの関数への @login_required
の適用
非ログインユーザーへの機能制限の実現 でも解説したように、login_required
デコレーター(@login_required
)が適用されたビューの関数は、非ログインユーザーからのリクエストでは実行できないようになります。そして、ビューの関数が実行されない代わりにリダイレクトレスポンスが返却されることになります。
なので、非ログインユーザーから利用されたくない機能を実装しているビューの関数の先頭に @login_required
を追記してやれば、非ログインユーザーへの機能制限が実現できることになります。
今回は、ユーザー登録を行う register_view
とログインを行う login_view
以外はすべて、非ログインユーザーからの利用を禁止したいと思います。
そしてこれは、下記のように views.py
を変更することで実現できます。login_required
の import
を忘れないように注意してください。
from django.shortcuts import redirect, render, get_object_or_404
from .forms import RegisterForm, PostForm, LoginForm
from .models import Comment
from django.contrib.auth import get_user_model, login
from django.contrib.auth.decorators import login_required
User = get_user_model()
@login_required
def index_view(request):
# 略
@login_required
def users_view(request):
# 略
@login_required
def user_view(request, user_id):
# 略
@login_required
def comments_view(request):
# 略
@login_required
def comment_view(request, comment_id):
# 略
def register_view(request):
# 略
@login_required
def post_view(request):
# 略
def login_view(request):
# 略
@login_required
によるリダイレクトの遷移先の設定
前述のとおり、機能制限はリダイレクトを利用して実現されることになります。具体的には、@login_required
が適用されたビューの関数を実行するのではなく、リダイレクトレスポンスを返却することで機能制限が実現されます。
基本的には、この時のリダイレクト先はログインフォームを表示する URL で良いと思います。とりあえず、この掲示板アプリでは、リダイレクト先をログインフォームの URL としたいと思います。
ただし、この @login_required
によるリダイレクト先の URL は、デフォルトでは /accounts/login/
に設定されています。それに対し、この掲示板アプリのログインフォームの URL は /forum/login/
なので、 @login_required
によるリダイレクト先の URL の変更を行っていきたいと思います。
で、このリダイレクト先の URL は、非ログインユーザーへの機能制限の実現 でも解説したように、settings.py
に LOGIN_URL
を定義することで実現できます。したがって、settings.py
に下記を追記してやれば、@login_required
によるリダイレクト先の URL を /forum/login/
に変更することができます。
LOGIN_URL = '/forum/login/'
ログアウトの実現
次は、ログアウトを実現していきます。ログアウトの場合は、情報の入力等がいらないのでフォームは不要です。ここでは、ログアウト用のビューの関数と、その関数と URL とのマッピングを行い、次の節の ナビゲーションバーへのリンクの追加 でログアウト操作を行うためのリンクを追加していきます。
ログアウト用のビューの関数の定義
ログアウトの実現 でも解説したように、ログイン中のユーザーのログアウトは、引数に request
を指定して logout
関数を実行することで実現できます。
したがって、下記のように views.py
を変更して logout_view
関数を定義すれば、ログアウト用のビューの関数が用意できたことになります。logout
関数の import
を忘れないように注意してください。
この logout_view
では、ログアウト後にログインフォームのページへリダイレクトするように redirect('login')
の返却値を return
するようにしています。また、ログアウトであれば、ログインしていないユーザーから実行されても問題ないため、@login_required
は適用しないようにしています。
from django.shortcuts import redirect, render, get_object_or_404
from .forms import RegisterForm, PostForm, LoginForm
from .models import Comment
from django.contrib.auth import get_user_model, login, logout
from django.contrib.auth.decorators import login_required
# 略
def logout_view(request):
logout(request)
return redirect('login')
logout_view
と URL とのマッピング
続いて、ログイン用のビューの時と同様に、forum
フォルダ内の urls.py
を変更して、先ほど定義した logout_view
と URL とのマッピングを行っていきます。具体的には、forum
フォルダ内の urls.py
を下記のように変更します(リストの末尾に要素を追加しています)。
from django.urls import path
from . import views
urlpatterns = [
path('', views.index_view, name='index'),
path('comments/', views.comments_view, name='comments'),
path('comment/<int:comment_id>/', views.comment_view, name='comment'),
path('users/', views.users_view, name='users'),
path('user/<int:user_id>/', views.user_view, name='user'),
path('register/', views.register_view, name='register'),
path('post/', views.post_view, name='post'),
path('login/', views.login_view, name='login'),
path('logout/', views.logout_view, name='logout'),
]
この変更により、 URL が /forum/logout/
のリクエストをウェブアプリが受け取った時に logout_view
が実行されるようになります。
また、name='logout'
を指定しているため、/forum/logout/
の URL に 'logout'
という名前が設定され、ビューやテンプレートファイル等で、'logout'
という名前から /forum/logout/
を逆引きできるようになります。
スポンサーリンク
ナビゲーションバーへのリンクの追加
最後に、今回新規に定義した login_view
と logout_view
がリンクのクリックによって実行できるよう、ナビゲーションバーに ログイン
リンクと ログアウト
リンクの追加を行っていきたいと思います。
この掲示板アプリでは、テンプレートファイルの1つである base.html
でナビゲーションバーを実装しています。そして、/forum/templates/forum
以下の他のテンプレートファイルから base.html
を継承することで、掲示板アプリの全ページでナビゲーションバーが表示されるようになっています。
そのため、この base.html
で実装しているナビゲーションバーを変更して ログイン
リンクと ログアウト
リンクを追加していくことになります。また、ログイン
リンクのリンク先には 'login'
の名前が設定された URL を指定し、ログアウト
リンクのリンク先には 'logout'
の名前が設定された URL を指定することで、それぞれのリンクがクリックされた際に、login_view
および logout_view
が実行されるようにしていきます。
現状の base.html
は下記のようになっており、<ul class="navbar-nav mr-auto">
~ </ul>
部分がナビゲーションバーに表示される項目の実装箇所となります。なので、ここに、他のリンクと同じ感じで ログイン
リンクと ログアウト
リンクの追加を行います。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>{% block title %}{% endblock %}</title>
</head>
<body>
<header>
<nav class="navbar navbar-expand navbar-dark bg-primary">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link navbar-brand" href="{% url 'index' %}">掲示板</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'users' %}">ユーザー一覧</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'comments' %}">コメント一覧</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'post' %}">コメント投稿</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'register' %}">ユーザー登録</a>
</li>
</ul>
</nav>
</header>
<main class="container my-5 bg-light">
{% block main %}{% endblock %}
</main>
</body>
</html>
具体的には、base.html
を下記のように変更してやれば良いことになります。太字部分を追加しており、これによってナビゲーションバーに ログイン
リンクと ログアウト
リンクが表示されるようになります。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>{% block title %}{% endblock %}</title>
</head>
<body>
<header>
<nav class="navbar navbar-expand navbar-dark bg-primary">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link navbar-brand" href="{% url 'index' %}">掲示板</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'users' %}">ユーザー一覧</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'comments' %}">コメント一覧</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'post' %}">コメント投稿</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'register' %}">ユーザー登録</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'login' %}">ログイン</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'logout' %}">ログアウト</a>
</li>
</ul>
</nav>
</header>
<main class="container my-5 bg-light">
{% block main %}{% endblock %}
</main>
</body>
</html>
ログイン
リンクと ログアウト
リンクのそれぞれの href
属性には "{% url 'login' %}"
と "{% url 'logout' %}"
を指定しており、この部分は “URL の名前” から逆引きされた URL に置き換えられることになります。そして、それによって、ログイン
リンクがクリックされた際には login_view
、ログアウト
リンクがクリックされた際には logout_view
が実行されることになります。
以上の変更により、掲示板アプリへのログイン機能の導入が完了したことになります!
動作確認
最後に、変更後の掲示板アプリの動作確認を行なっておきましょう!
マイグレーションの実行とユーザー登録
今回は、models.py
の変更は行っていませんのでマイグレーションの実行は不要になります。
ですが、このページの解説を下記ページから連続して読んでくださっており、カスタムユーザー
(CustomUser
) の定義後に、まだマイグレーションを実行していないという方は、下記ページの DB 関連の初期化 と マイグレーションの実行 を参考にしてマイグレーションを実行しておく必要があります。
また、掲示板アプリの動作確認自体が未経験という方も、上記ページの マイグレーションの実行 を参考にマイグレーションを実行しておいてください。
開発用ウェブサーバーの起動
では、動作確認を進めていきたいと思います。
まずは、いつも通りの手順で Django 開発用ウェブサーバーを起動しましょう。testproject
フォルダで下記コマンドを実行すれば、Django 開発用ウェブサーバーが起動するはずです。
% python manage.py runserver
非ログインユーザーへの機能制限の確認
最初に、非ログインユーザーへの機能制限の確認を行っていきたいと思います。
まずは、下記 URL をウェブブラウザーで開いてみてください。
http://localhost:8000/forum/login/
この URL にマッピングされているビューの関数は login_view
であり、この関数には @login_required
を適用していないため、下の図のようなログインページが普通に表示されると思います。
ちなみに、ここに表示されているフォームは forms.py
に新たに定義した LoginForm
であり、上図のようなページが表示されたということは、LoginForm
の表示が適切に行われていることが確認できたことになります。
次は、ナビゲーションバーの コメント一覧
リンクをクリックしてみてください。おそらく、クリックしてもコメント一覧は表示されず、ログインページが再度表示されることになると思います。
これがビューの関数に @login_required
を適用した効果となります。コメント一覧
リンクをクリックした際には comments_view
が実行されるようになっており、さらに comments_view
には @login_required
が適用されているため、ログインしないと comments_view
によるページの表示が行われないことになります。また、この時には settings.py
で LOGIN_URL
に指定した URL へリダイレクトされることになり、LOGIN_URL
には /forum/login/
を指定しているため、ログインページへリダイレクトされたことになります。
普通にコメント一覧が表示されしまったという方は、前回の連載の中で行った管理画面へのログインによって、ログイン状態が継続されている可能性があります
この場合は、ナビゲーションバーの ログアウト
リンクをクリックしてから、再度コメント一覧を表示しみてください
他の、ログイン
・ユーザー登録
以外のリンクに関しても同様の動作となり、ウェブアプリが非ログインユーザーから使用できないようになっていることが確認できると思います。
ユーザー登録によるログイン
次は、このページの主題ともなっているログインの動作確認を行っていきたいと思います。
今回、ユーザー登録時に自動的にログインが行われるように変更を行ったため、まずはユーザー登録によってログインが行われること、さらにログインを行うことで、今まで表示できなかったページが表示できるようになったことを確認していきたいと思います。
ということで、まずはナビゲーションバーの ユーザー登録
リンクをクリックしてください。
このページは非ログインユーザーでも表示されるはずで、ユーザー登録
リンクのクリックによって下の図のようなユーザー登録フォームが表示されるはずです。
この表示されたユーザー登録フォームに適当なユーザー名・メールアドレス・年齢・パスワード(2回)を入力して 送信
ボタンをクリックしてください。
ユーザーが正常に登録されれば、ログインが自動的に行われ、その後ログインしたユーザーの詳細ページに自動的に遷移するはずです(エラーになった場合は入力内容を修正して再度 送信
ボタンをクリックしてください)。
このページ自体、ログイン前は表示不可のページだったのですが、ログインすることで表示できるようになっています。また、ナビゲーションバーのリンクをクリックすれば、ログイン前に表示できなかった他のページも表示できるようになっていることが確認できると思います(ログアウト
をクリックするとログアウトされてしまうので注意してください)。
このように、ウェブアプリにログイン機能を搭載することで、ログインの有無による機能制限を行うことができるようになります。この機能制限の実現は、ログイン機能を搭載することで得られるメリットの1つになります。
コメント投稿者の自動設定の確認
続いて、コメントの投稿の動作確認を行います。
今回ログイン機能を搭載したことで、コメント投稿時には投稿者をユーザーが手動で指定するのではなく、ログイン中のユーザーに応じて自動的に投稿者が設定されるようになっています。この点の動作を確認していきましょう!
まずは、ナビゲーションバーの コメント投稿
リンクをクリックしてコメント投稿フォームを表示してください。投稿者選択メニューが削除されていることが確認できると思います。続いて、適当なコメントを入力して 送信
ボタンをクリックしてください。
送信
ボタンをクリックすれば、コメントの投稿が完了してコメント一覧ページが表示されるはずです。で、ここで注目していただきたいのが、先ほど投稿したコメントの 投稿者
列になります。コメント投稿時には投稿者は設定しなかったにもかかわらず、投稿者
列にはログイン中のユーザーのユーザー名が表示されているはずです。
これは、コメント投稿時に実行される post_view
の中でログイン中のユーザーを取得し、そのユーザーを投稿されたコメントの投稿者に設定しているからになります。
このように、ログイン機能を搭載していれば、ユーザーに応じた様々な機能を実現することができます。上記のようなコメント投稿の投稿者の自動設定についてもそうですし、ログイン中のユーザーに応じて表示するページを切り替えるようなことも簡単に行えます。
このような、ログイン中ユーザーを取得できるようになることでウェブアプリで実現可能な機能の幅を広げられる点も、ログイン機能をウェブアプリに搭載するメリットの1つになります。
ログアウトの確認
続いて、ログアウトの動作確認を行っていきます。
ログアウトは、ナビゲーションバーの ログアウト
リンクをクリックすることで行われます。
ログアウト
リンクをクリックすれば、ログアウトが実行されてログインフォームが表示されることになります。ここで、ナビゲーションバーから コメント一覧
リンクなどをクリックしたとしても、再びログインフォームが表示されることが確認することができると思います。この点から、ログアウト
リンクのクリックによってログアウトが実施されていることが確認できます。
ログインフォームからのログインの確認
続いて、ログインフォームからのログインが実現可能であることを確認していきたいと思います。
おそらく、今ログインフォームが表示されているはずなので、ユーザー登録時に指定したユーザー名とパスワードを入力して 送信
ボタンをクリックしてください。
登録済みのユーザー名とパスワードが入力されていれば、送信
ボタンクリックによってログインしたユーザーの情報ページが表示されるはずです。
さらに、ログアウト実施後は表示できなかったコメント一覧ページなども表示できることが確認できると思います。このことから、ログインフォームからのログインも正常に動作していることが確認できたことになります。
認証失敗時の動作確認
最後に、認証に失敗した場合の動作も確認しておきたいと思います。
まず ログアウト
リンクをクリックしてログアウトを実施し、続いて ログイン
リンクをクリックしてログインフォームを表示してください。そして、先ほどはユーザー登録時に “指定した” ユーザー名とパスワードをフォームに入力しましたが、ここではユーザー登録時に “指定していない” ユーザー名とパスワードを入力して 送信
ボタンをクリックしてください。
この場合は認証に失敗し、下の図のように再度ログインフォームが表示されることになります。
そして、その後にナビゲーションバーで、例えば コメント一覧
などの他のページのリンクをクリックしてみてください。この場合、リダイレクトが行われて再びログインフォームが表示されることになるはずです。この動作より、認証に失敗した際にはログイン処理が実行されていないことが確認できます。認証に失敗した場合にも誤ってログイン処理が実行されているとウェブアプリに機能制限をかける意味もありませんので、動作確認時にはこういった失敗時の動作も確認しておくことをオススメします。
まとめ
このページでは、Django におけるログインの実現方法について解説しました!
Django において、ウェブアプリにログイン機能を搭載するためには、下記のようなことを実現する必要があります。
- ユーザーの情報のデータベースでの管理
- ユーザー登録フォーム
- ログインフォーム
- パスワード認証
- ログインの許可
- ログイン中のユーザーの情報の取得
- 非ログインユーザーへの機能制限
- ログアウト
- 適切なレスポンスの返却
実現する必要のある項目自体は多いですが、1つ1つの項目自体の実現方法に関しては、そんなに難しいと感じなかったのではないかと思います。
これは、Django フレームワークから提供される機能(関数やクラス)が充実しているからで、例えばログインフォームであれば AuthenticationForm
を継承するだけで定義できますし、ログインやログアウトに関しても login
関数や logout
関数を実行するだけで実現できてしまいます。こういった、既に用意されている豊富な機能が利用できるという点も Django を使ってウェブアプリを開発するメリットとなります。他にも便利なクラスや機能が存在しますので、今後別途紹介していきたいと思います。
ウェブアプリではログイン機能が搭載されているものが多く、Django でウェブアプリを開発する上でもログインの実現方法は知っておいた方が良いと思います。是非このページで解説した内容を理解していただき、今後のウェブアプリ開発に活かしていただければと思います!
次の Django 入門の連載においては「管理画面」について説明していきたいと思います。お試しでウェブアプリを開発するだけであれば管理画面を使わないことも多いかもしれないですが、ウェブアプリを運営していくためには管理画面も重要となります。次の連載のページは下記リンクから読むことができますので、次の連載もぜひ読んでみてください!
【Django入門11】管理画面(admin)の使い方の基本