重要なお知らせ Techpitが新しくなりました! サイトリニューアルに伴いパスワードの再設定をお願いします。 詳細はこちら
3-1

ログイン・ログアウト機能の作成(前編)

このパートでは、ユーザのログインとログアウト機能を実装していきます。

本教材で開発するWEBアプリは複数ユーザが利用することを想定しているため、ログインとログアウト機能は必須機能となります。

本パートの目標物

本パートでは、DjangoのWEBアプリに以下の機能を実装します。

Copied!
・ユーザ名、パスワードによるログイン機能 ・ログアウト機能

3_11

具体的には以下の流れで実装を行います。

目標物を作成するまでの流れ

Copied!
1. settings.pyの設定(ログイン先、ログアウト先のURLの設定) 2. ユーザ認証用のDjangoアプリケーションの作成 3. forms.pyの設定(ログイン用フォームの定義) 4. views.pyの設定(ログイン、ログアウト用のビューの定義) 5. url.pyの設定(ログイン、ログアウト用URLパターンの定義) 6. テンプレート設定(ログイン、ログアウト用)

それでは、ログイン・ログアウト機能の作成手順を説明していきます。

1. settings.pyの設定(ログイン先、ログアウト先のURLの設定)

まず最初に、ログインとログアウトに関わるDjangoの基本設定を行います。

settings.pyを開いて、末尾に以下の設定を追加しましょう。

python
123
Copied!
tutorial └── tutorial └──settings.py #変更対象
python
123
Copied!
LOGIN_URL = '/login' LOGIN_REDIRECT_URL = '/pdfmr/top' LOGOUT_REDIRECT_URL='/login'

各パラメータの説明は以下の通りです。
3_02

上記パラメータで指定する値は、urls.pyで定義しているpathメソッドの第1引数で指定するURLパターンの組み合わせ(プロジェクト直下のurls.pyとアプリケーション直下のurls.py)を指定しています。

例えば、今回はログオン後にリダイレクトされるURL(LOGIN_REDIRECT_URL)としてTOP画面のURL(http://127.0.0.1:8000/pdfmr/top/) を指定します。
設定値としては「/pdfmr/top」のようになります。

同様にログオンURLはhttp://127.0.0.1:8000/login となるので、設定値は「/login」とします。

以上でsettings.pyの設定は完了です。

2. 認証用のDjangoアプリケーションの作成

つづいて、ユーザ認証用のDjangoアプリケーションを新規に作成します。

以下のコマンドを実行してaccountsというアプリケーションを作成しましょう。

python
12
Copied!
$ cd /tutorial $ python manage.py startapp accounts
python
12345678910111213141516
Copied!
../tutorial ├── accounts #新規作成 | ├── __init__.py | ├── admin.py | ├── apps.py | └── models.py | └── tests.py | └── views.py ├── templates | └── pdfmr ├── manage.py └── tutorial ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py

以上でユーザ認証用のアプリケーション作成は完了です。

3. forms.pyの設定

つづいて、ユーザ認証画面のフォームを作成していきます。

今回は、Djangoに標準で用意されているAuthenticationFormというクラスを利用します。

AuthenticationFormクラスには標準でユーザ名とパスワードのフォームが定義されているため、簡単にユーザ名とパスワードで認証する画面フォームを作成できます。

[コラム]

Copied!
「AuthenticationFormクラスってどうやって知るの?」 「AuthenticationFormクラスに標準で定義されている項目ってどうすればわかるの?」

ここでは、そんな疑問にお答えします。

基本的には、ウェブ検索で「Django ユーザ認証」などと調べるとこういったよく使われる便利なDjango標準のクラスの情報が出てきます。

ただし、実際にクラスの中の定義がどうなっているかは実態(Django本体のソースコード)を見てみないとよくわかりません。

以下に、私がよくやっているコードを確認する手順をご紹介します。

今回利用する「AuthenticationForm」クラスの例で説明します。

ネットで「Django ユーザ認証」などと調べるとこのクラスの利用例がでてきますが、使い方として以下のようなコードが最初に出てきます。

python
1
Copied!
from django.contrib.auth.forms import AuthenticationForm

これをわかりやすく翻訳すると、「django本体のcontribフォルダ配下にあるauthフォルダ直下のforms.py内で定義されているAuthenticationFormクラスをインポートしますよ」ということです。

また、django本体は以下の場所にあります。

Copied!
\Lib\site-packages\django

仮想環境を利用していない場合はPythonやAnacondaをインストールしたホームディレクトリ配下に存在しています。

Copied!
例)C:\ProgramData\Anaconda3\Lib\site-packages\django

仮想環境を作成して有効化した上で、pipでdjangoを導入している場合は、仮想環境フォルダ配下に存在しています。
本教材の例だと、myenvフォルダ(仮想環境)配下ですね。

Copied!
例)C:\techpit\pdfminer\venv\myenv\Lib\site-packages\django

そして、django本体のcontrib\authフォルダ直下に存在しているforms.pyを確認してみます。

ソースコードが長いので「AuthenticationForm」で検索すると1件HITします。

HITしたソースコードが以下の通りです。(長いので一部省略)

python
12345678910111213141516171819202122
Copied!
class AuthenticationForm(forms.Form): """ Base class for authenticating users. Extend this to get a form that accepts username/password logins. """ username = UsernameField(widget=forms.TextInput(attrs={'autofocus': True})) password = forms.CharField( label=_("Password"), strip=False, widget=forms.PasswordInput, ) error_messages = { 'invalid_login': _( "Please enter a correct %(username)s and password. Note that both " "fields may be case-sensitive." ), 'inactive': _("This account is inactive."), } ~~省略~~

上記コードを見ると分かる通り、AuthenticationFormクラスはforms.Formクラスを承継して作られていることがわかります。
また、AuthenticationFormクラス内にはusernamepasswordという属性が定義されていることがわかるので、「ユーザ名とパスワードという項目を定義しているフォームなんだな」ということが分かります。

最初のうちは難しい部分もありますが、こんな感じで確認する癖をつけていくと、「どんな項目が定義されているのか?」、「どんな処理を行っているか?」ということが少しずつ理解できるようになってきます。

実際に確認したわけではありませんが、強者と呼ばれる人たちは、こんな風にパッケージ製品のソースコードを覗いて新しい知見を増やしているはずです。

また、django本体のフォルダには様々なモジュールファイルが存在しています。
たまに興味を持って覗くと「こんな便利そうなクラスあるんだ!使えそう!」といった新たな発見があることもありますので、ぜひ覗く癖をつけるようにしましょう。

さて、少しコラムが長くなりましたが、ユーザ認証フォーム(forms.py)の設定に戻ります。

\tutorial\accounts直下にforms.pyというファイルを新規作成しましょう。

python
123456789101112
Copied!
../tutorial ├── accounts | ├── __init__.py | ├── admin.py | ├── apps.py | └── models.py | └── tests.py | └── views.py | └── forms.py #新規作成 ├── templates ├── manage.py └── tutorial

forms.pyファイルを作成したら以下のコードを記載しましょう。

python
12345678910
Copied!
from django.contrib.auth.forms import AuthenticationForm class LoginForm(AuthenticationForm): """ログオンフォーム""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field in self.fields.values(): field.widget.attrs['class'] = 'form-control' field.widget.attrs['placeholder'] = field.label

コードの解説

まず最初に以下のコードでAuthenticationFormクラスをインポートします。

python
1
Copied!
from django.contrib.auth.forms import AuthenticationForm

続いて、以下のとおりAuthenticationFormクラスを承継してLoginFormという名称でユーザ認証用のクラスを定義します。

python
1
Copied!
class LoginForm(AuthenticationForm):

最後に以下の__init__関数ですが、super()を使って承継元(AuthenticationForm)の__init__メソッドを呼び出しています。

これは、親クラスのメソッド(AuthenticationForm)を子クラス(LoginForm)でも使いつつ、新たにパラメーターやメソッドを追加するためにこんなことをしています。

今回は、承継元(AuthenticationForm)で定義されているユーザ、パスワードという項目に対してWidgetを適用します。
Widgetとはform-controlクラスやプレースホルダーといったフォームの見た目を整えるためのもので、以下のコードでWidgetを適用します。

python
12345
Copied!
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field in self.fields.values(): field.widget.attrs['class'] = 'form-control' # ※1 field.widget.attrs['placeholder'] = field.label # ※2
Copied!
 ※1:全てのフォームの部品のclass属性に「form-control」を指定(bootstrapのフォームデザインを利用するため)  ※2:全てのフォームの部品にpaceholderを定義して、入力フォームにフォーム名が表示されるように指定。

Pythonのクラスの承継、メソッドのオーバーライドがわかっていないと理解しづらいため、もし理解が足りない方はPythonクラスの基礎を再度復習しましょう!

また、フォームの見た目を整えるwidget(ウィジェット)については公式HPにもいろいろ情報が載っていますので参考にしてみてください。

参考:[Djangoのウィジェットについて]https://docs.djangoproject.com/ja/2.2/ref/forms/widgets/

以上でフォームの設定は完了です。

4. views.pyの設定

Djangoにはアカウント認証のための標準クラスが存在しているため、必要に応じて標準クラスを承継して認証機能を実装していきます。

本教材では、最低限ユーザ認証に必要な下記のクラスを利用します。

3_01

まずは、tutorial\accounts\views.pyファイルを開きましょう。

python
123456789101112
Copied!
../tutorial ├── accounts | ├── __init__.py | ├── admin.py | ├── apps.py | └── models.py | └── tests.py | └── views.py #変更するファイル | └── forms.py ├── templates ├── manage.py └── tutorial

ファイルを開いたら以下のコードを追記してください。

python
123456789101112131415161718
Copied!
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' # ここまで追加

コードの解説

まず最初の4行で必要なモジュールをインポートします。

python
1234
Copied!
from django.shortcuts import render from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.views import(LoginView, LogoutView) from .forms import LoginForm # forms.pyで定義したユーザ認証画面用フォームをImport

つづいて、LoginViewクラスを承継してログイン用クラス(Login)を定義します。

python
1234
Copied!
class Login(LoginView): """ログインページ""" form_class = LoginForm template_name = 'accounts/login.html'

form_classに先ほどforms.pyで定義したフォームクラス「LoginForm」を指定することでログイン処理時にLoginFormで定義したフォームデザインが利用されるようになります。

また、ログイン画面として利用するテンプレートファイルをtemplate_nameに設定します。
今回は、accounts/login.htmlとします。

次に、LoginRequiredMixin, LogoutViewクラスを承継してログアウト処理用のクラス(Logout)を定義します。

python
123
Copied!
class Logout(LoginRequiredMixin, LogoutView): """ログアウトページ""" template_name = 'accounts/login.html'

[注意点]

Copied!
複数のクラスを承継する場合は、LoginRequiredMixinを一番最初に指定するようにしましょう。 一番最初に指定しないとうまく動作しなくなりますので注意しましょう。

Logoutクラス側では利用するテンプレートファイルだけ指定します。
ここでは、ログアウト後に表示される画面テンプレートのファイル「accounts/login.html」を指定します。

python
1
Copied!
template_name = 'accounts/login.html'

以上でビューの設定は完了です。

Django標準のLoginView, LogoutViewクラスを利用するとこのような短いコードでログイン、ログアウト処理を簡単に実装できます。

5. urls.pyの設定

続いてユーザ認証画面用のURLパターンの設定を行います。

まずは、tutorial\accounts直下に空のurls.pyファイルを作成しましょう。

python
12345678910111213
Copied!
../tutorial ├── accounts | ├── __init__.py | ├── admin.py | ├── apps.py | └── models.py | └── tests.py | └── views.py | └── forms.py | └── urls.py #新規作成 ├── templates ├── manage.py └── tutorial

空のurls.pyファイルを作成したら、以下のコードを追記しましょう。

python
12345678910
Copied!
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'), ]

コードの解説

まず最初に必要なモジュールをインポートします。

python
12
Copied!
from django.urls import path from . import views

1行目でpathメソッドを利用するためdjango.urlsからpathをインポートし、2行目で先ほど定義したビューの定義をインポートしています。

続いて、以下のコードではaccountsアプリケーション用URLの名前空間名を指定しています。

python
1
Copied!
app_name ='accounts'

つづいて、ログオン認証画面用のURLパターンを定義します。

python
12
Copied!
path('login/', views.Login.as_view(), name='login'), path('logout/', views.Logout.as_view(), name='logout'),

上記設定では、以下の内容を定義しています。

<ログイン用の設定>

Copied!
URLパス:http://127.0.0.1/login 呼ばれるビュー関数: views.py内に定義したLoginクラス URLパターン名称:login

<ログアウト用の設定>

Copied!
URLパス:http://127.0.0.1/logout 呼ばれるビュー関数: views.py内に定義したLooutクラス URLパターン名称:logout

最後に、プロジェクト直下のurls.pyにaccounts\urls.pyをインクルードする設定を追加しましょう。

python
123456
Copied!
../tutorial ├── accounts ├── templates ├── manage.py └── tutorial └── urls.py #修正

変更前

python
1234
Copied!
urlpatterns = [ path('admin/', admin.site.urls), path('pdfmr/', include('pdfmr.urls')), ]

変更後

python
12345
Copied!
urlpatterns = [ path('admin/', admin.site.urls), path('pdfmr/', include('pdfmr.urls')), path('', include('accounts.urls')), #追加 ]

以上で、ユーザログイン、ログアウト機能の実装は完了です。

【補足】

この時点で、以下のようなログインとログアウト機能が実装されたことになります。

http://127.0.0.1/login にアクセスしてログインが完了すると http://127.0.0.1/pdfmr/top 画面へ遷移する。
ログアウトボタンを押すと、ログアウト処理が実行された後に http://127.0.0.1/login へ戻る。

ログイン後、ログアウト後に遷移するURLはsettings.pyの以下の設定で定義したことを思い出しましょう。

python
123
Copied!
LOGIN_URL = '/login' LOGIN_REDIRECT_URL = '/pdfmr/top' LOGOUT_REDIRECT_URL='/login'

以上で、URLパターンの定義は完了です。

6. テンプレートファイルの作成

最後にユーザ認証画面のテンプレートファイルを作成していきます。

まずは\tutorial\templates\accountsフォルダを作成しましょう。
その後、空のlogin.htmlファイルを作成します。

フォルダ構成は以下のようになります。

python
12345678
Copied!
../tutorial ├── accounts ├── templates | ├── pdfmr | ├── accounts #新規作成 | |  └── login.html #新規作成 ├── manage.py └── tutorial

続いて、login.htmlに以下のコードを設定しましょう。

html
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
Copied!
{% load staticfiles %} <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>PDFtoExcel</title> <!-- Tell the browser to be responsive to screen width --> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> <!-- Bootstrap 3.3.7 --> <link rel="stylesheet" href="{% static 'AdminLTE-2.4.12/bower_components/bootstrap/dist/css/bootstrap.min.css' %}"> <!-- Font Awesome --> <link rel="stylesheet" href="{% static 'AdminLTE-2.4.12/bower_components/font-awesome/css/font-awesome.min.css' %}"> <!-- Ionicons --> <link rel="stylesheet" href="{% static 'AdminLTE-2.4.12/bower_components/Ionicons/css/ionicons.min.css' %}"> <!-- Theme style --> <link rel="stylesheet" href="{% static 'AdminLTE-2.4.12/dist/css/AdminLTE.min.css' %}"> <!-- iCheck --> <link rel="stylesheet" href="{% static 'AdminLTE-2.4.12/plugins/iCheck/square/blue.css' %}"> <!-- Google Font --> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic"> </head> <body class="hold-transition login-page"> <div class="login-box"> <div class="login-logo"> <b>PDFtoExcel</b> </div> <!-- /.login-logo --> <div class="login-box-body"> <p class="login-box-msg"><b>ログオン画面</b></p> <form action="" method="POST"> {% csrf_token %} {% for field in form %} <div class="form-group has-feedback"> {{ field }} </div> {% endfor %} <div class="row"> {% for error in form.non_field_errors %} <div class="alert alert-danger" role="alert"> <p>{{ error }}</p> </div> {% endfor %} <!-- /.col --> <div class="col-xs-4"> <button type="submit" class="btn btn-primary btn-block btn-flat">ログオン</button> </div> <!-- /.col --> </div> </form> </div> <!-- /.login-box-body --> </div> <!-- /.login-box --> <!-- jQuery 3 --> <script src="{% static 'AdminLTE-2.4.12/bower_components/jquery/dist/jquery.min.js' %}"></script> <!-- Bootstrap 3.3.7 --> <script src="{% static 'AdminLTE-2.4.12/bower_components/bootstrap/dist/js/bootstrap.min.js' %}"></script> <!-- iCheck --> <script src="{% static 'AdminLTE-2.4.12/plugins/iCheck/icheck.min.js' %}"></script> </body> </html>

コードの解説

HTML、JavaScriptに関する内容は本教材の本質ではないためDjangoに関する部分を中心に解説していきます。

まず最初に、login.htmlでもAdminLTEの静的ファイルを活用するためにlogin.htmlの冒頭でDjangoの静的ファイルがロードできるようにします。

python
1
Copied!
{% load staticfiles %}

続いてタイトルは以下の通り、「PDFtoExcel」とします。

html
1
Copied!
<title>PDFtoExcel</title>

続いて、login.htmlのメイン部分となるフォーム部分(下図の赤枠)を定義しているのが以下のformタグで囲まれた部分です。

3_03

html
123456789101112131415161718192021
Copied!
<form action="" method="POST"> {% csrf_token %} {% for field in form %} <div class="form-group has-feedback"> {{ field }} </div> {% endfor %} <div class="row"> {% for error in form.non_field_errors %} <div class="alert alert-danger" role="alert"> <p>{{ error }}</p> </div> {% endfor %} <!-- /.col --> <div class="col-xs-4"> <button type="submit" class="btn btn-primary btn-block btn-flat">ログオン</button> </div> <!-- /.col --> </div> </form>

まず、最初に以下のコードを記載することでCSRF対策を行っています。

POST フォームには全て、 {% csrf_token %} テンプレートタグを使うことが推奨されています。

python
1
Copied!
{% csrf_token %}

【補足】

Copied!
CSRF「クロスサイト・リクエスト・フォージェリ」とは、WEBブラウザに保存されているCookie等の値が自動的にリクエストヘッダに セットされて送信されるという性質を利用して、副作用を伴う重要なリクエストを利用者の意図に反して強要する攻撃のことです。

続いて、以下の部分でフォームオブジェクト(form)からフォーム定義情報を1つずつ取り出してフォームのフィールド情報を{{field}}として表示させてあげます。

python
12345
Copied!
{% for field in form %} <div class="form-group has-feedback"> {{ field }} </div> {% endfor %}

上記のループ処理で表示しているのが下図の赤枠の部分(ユーザ名、パスワードのフォーム)です。

3_04

初めのうちは、テンプレート内にformオブジェクトが渡ってくるまでの流れがイメージしにくいため、全体のフロー図を書きましたので参考にしてみてください。

※わかる方は飛ばしていただいて問題ありません。

3_05

続いて以下の部分でユーザ認証が失敗した場合にエラーメッセージを表示します。

python
12345
Copied!
{% for error in form.non_field_errors %} <div class="alert alert-danger" role="alert"> <p>{{ error }}</p> </div> {% endfor %}

こんな感じのエラーを表示してくれます。

3_06

認証系のエラーはform.non_field_errorsに情報が格納されるため、for文でform.non_field_errorsから情報を取得して{{error}}としてテンプレートに表示させてあげます。

あとは、以下の部分でログインボタンを定義します。

python
123
Copied!
<div class="col-xs-4"> <button type="submit" class="btn btn-primary btn-block btn-flat">ログオン</button> </div>

以上で、ログイン画面のテンプレートは完了です。

ここで、以下のコマンドを実行してユーザ認証機能をマイグレーションしておきましょう。

python
12
Copied!
$ python manage.py makemigrations $ python manage.py migrate

また、ログイン用のテストユーザを作成しておきます。

python
123456
Copied!
$ python manage.py createsuperuser ユーザー名 (leave blank to use 'sinfo'): test01 #test01と入力 メールアドレス: #メールアドレスは空のままEnterキーを押す Password: #パスワードを2回入力する Password (again): Superuser created successfully.

次に以下のURLにアクセスしてログイン処理の動作確認を行いましょう。

http://127.0.0.1:8000/login

※開発サーバを起動していない場合は以下のコマンドで起動してください。

python
12
Copied!
$ cd tutorial $ python manage.py runserver

ログオン画面が表示されたら、ユーザ名とパスワードを入力してログオンボタンを押した後に、以下のTOP画面に遷移すればOKです。

http://127.0.0.1:8000/pdfmr/top/

3_07

以上で、ログイン・ログアウト機能(前編)の実装は完了です。

後編では、TOP画面にログオンユーザ名を表示する機能とログアウトボタン機能を実装していきます。

お疲れさまでした。

現在のパート (0)
全パート (4)
みんなで助け合おう!
現在のパートのディスカッション 全0件