ログイン・ログアウト機能の作成(前編)
このパートでは、ユーザのログインとログアウト機能を実装していきます。
本教材で開発するWEBアプリは複数ユーザが利用することを想定しているため、ログインとログアウト機能は必須機能となります。
本パートの目標物
本パートでは、DjangoのWEBアプリに以下の機能を実装します。
Copied!・ユーザ名、パスワードによるログイン機能
・ログアウト機能
具体的には以下の流れで実装を行います。
目標物を作成するまでの流れ
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を開いて、末尾に以下の設定を追加しましょう。
python123 Copied!tutorial
└── tutorial
└──settings.py #変更対象
python123 Copied!LOGIN_URL = '/login'
LOGIN_REDIRECT_URL = '/pdfmr/top'
LOGOUT_REDIRECT_URL='/login'
各パラメータの説明は以下の通りです。
上記パラメータで指定する値は、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というアプリケーションを作成しましょう。
python12 Copied!$ cd /tutorial
$ python manage.py startapp accounts
python12345678910111213141516 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 ユーザ認証」などと調べるとこのクラスの利用例がでてきますが、使い方として以下のようなコードが最初に出てきます。
python1 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したソースコードが以下の通りです。(長いので一部省略)
python12345678910111213141516171819202122 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クラス内にはusernameとpasswordという属性が定義されていることがわかるので、「ユーザ名とパスワードという項目を定義しているフォームなんだな」ということが分かります。
最初のうちは難しい部分もありますが、こんな感じで確認する癖をつけていくと、「どんな項目が定義されているのか?」、「どんな処理を行っているか?」ということが少しずつ理解できるようになってきます。
実際に確認したわけではありませんが、強者と呼ばれる人たちは、こんな風にパッケージ製品のソースコードを覗いて新しい知見を増やしているはずです。
また、django本体のフォルダには様々なモジュールファイルが存在しています。
たまに興味を持って覗くと「こんな便利そうなクラスあるんだ!使えそう!」といった新たな発見があることもありますので、ぜひ覗く癖をつけるようにしましょう。
さて、少しコラムが長くなりましたが、ユーザ認証フォーム(forms.py)の設定に戻ります。
\tutorial\accounts直下にforms.pyというファイルを新規作成しましょう。
python123456789101112 Copied!../tutorial
├── accounts
| ├── __init__.py
| ├── admin.py
| ├── apps.py
| └── models.py
| └── tests.py
| └── views.py
| └── forms.py #新規作成
├── templates
├── manage.py
└── tutorial
forms.pyファイルを作成したら以下のコードを記載しましょう。
python12345678910 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クラスをインポートします。
python1 Copied!from django.contrib.auth.forms import AuthenticationForm
続いて、以下のとおりAuthenticationFormクラスを承継してLoginFormという名称でユーザ認証用のクラスを定義します。
python1 Copied!class LoginForm(AuthenticationForm):
最後に以下の__init__関数ですが、super()を使って承継元(AuthenticationForm)の__init__メソッドを呼び出しています。
これは、親クラスのメソッド(AuthenticationForm)を子クラス(LoginForm)でも使いつつ、新たにパラメーターやメソッドを追加するためにこんなことをしています。
今回は、承継元(AuthenticationForm)で定義されているユーザ、パスワードという項目に対してWidgetを適用します。
Widgetとはform-controlクラスやプレースホルダーといったフォームの見た目を整えるためのもので、以下のコードでWidgetを適用します。
python12345 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にはアカウント認証のための標準クラスが存在しているため、必要に応じて標準クラスを承継して認証機能を実装していきます。
本教材では、最低限ユーザ認証に必要な下記のクラスを利用します。
まずは、tutorial\accounts\views.pyファイルを開きましょう。
python123456789101112 Copied!../tutorial
├── accounts
| ├── __init__.py
| ├── admin.py
| ├── apps.py
| └── models.py
| └── tests.py
| └── views.py #変更するファイル
| └── forms.py
├── templates
├── manage.py
└── tutorial
ファイルを開いたら以下のコードを追記してください。
python123456789101112131415161718 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行で必要なモジュールをインポートします。
python1234 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)を定義します。
python1234 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)を定義します。
python123 Copied!class Logout(LoginRequiredMixin, LogoutView):
"""ログアウトページ"""
template_name = 'accounts/login.html'
[注意点]
Copied!複数のクラスを承継する場合は、LoginRequiredMixinを一番最初に指定するようにしましょう。
一番最初に指定しないとうまく動作しなくなりますので注意しましょう。
Logoutクラス側では利用するテンプレートファイルだけ指定します。
ここでは、ログアウト後に表示される画面テンプレートのファイル「accounts/login.html」を指定します。
python1 Copied!template_name = 'accounts/login.html'
以上でビューの設定は完了です。
Django標準のLoginView, LogoutViewクラスを利用するとこのような短いコードでログイン、ログアウト処理を簡単に実装できます。
5. urls.pyの設定
続いてユーザ認証画面用のURLパターンの設定を行います。
まずは、tutorial\accounts直下に空のurls.pyファイルを作成しましょう。
python12345678910111213 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ファイルを作成したら、以下のコードを追記しましょう。
python12345678910 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'),
]
コードの解説
まず最初に必要なモジュールをインポートします。
python12 Copied!from django.urls import path
from . import views
1行目でpathメソッドを利用するためdjango.urlsからpathをインポートし、2行目で先ほど定義したビューの定義をインポートしています。
続いて、以下のコードではaccountsアプリケーション用URLの名前空間名を指定しています。
python1 Copied!app_name ='accounts'
つづいて、ログオン認証画面用のURLパターンを定義します。
python12 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をインクルードする設定を追加しましょう。
python123456 Copied!../tutorial
├── accounts
├── templates
├── manage.py
└── tutorial
└── urls.py #修正
変更前
python1234 Copied!urlpatterns = [
path('admin/', admin.site.urls),
path('pdfmr/', include('pdfmr.urls')),
]
変更後
python12345 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の以下の設定で定義したことを思い出しましょう。
python123 Copied!LOGIN_URL = '/login'
LOGIN_REDIRECT_URL = '/pdfmr/top'
LOGOUT_REDIRECT_URL='/login'
以上で、URLパターンの定義は完了です。
6. テンプレートファイルの作成
最後にユーザ認証画面のテンプレートファイルを作成していきます。
まずは\tutorial\templates\accountsフォルダを作成しましょう。
その後、空のlogin.htmlファイルを作成します。
フォルダ構成は以下のようになります。
python12345678 Copied!../tutorial
├── accounts
├── templates
| ├── pdfmr
| ├── accounts #新規作成
| | └── login.html #新規作成
├── manage.py
└── tutorial
続いて、login.htmlに以下のコードを設定しましょう。
html12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 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の静的ファイルがロードできるようにします。
python1 Copied!{% load staticfiles %}
続いてタイトルは以下の通り、「PDFtoExcel」とします。
html1 Copied! <title>PDFtoExcel</title>
続いて、login.htmlのメイン部分となるフォーム部分(下図の赤枠)を定義しているのが以下のformタグで囲まれた部分です。
html123456789101112131415161718192021 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 %} テンプレートタグを使うことが推奨されています。
python1 Copied! {% csrf_token %}
【補足】
Copied!CSRF「クロスサイト・リクエスト・フォージェリ」とは、WEBブラウザに保存されているCookie等の値が自動的にリクエストヘッダに
セットされて送信されるという性質を利用して、副作用を伴う重要なリクエストを利用者の意図に反して強要する攻撃のことです。
続いて、以下の部分でフォームオブジェクト(form)からフォーム定義情報を1つずつ取り出してフォームのフィールド情報を{{field}}として表示させてあげます。
python12345 Copied!{% for field in form %}
<div class="form-group has-feedback">
{{ field }}
</div>
{% endfor %}
上記のループ処理で表示しているのが下図の赤枠の部分(ユーザ名、パスワードのフォーム)です。
初めのうちは、テンプレート内にformオブジェクトが渡ってくるまでの流れがイメージしにくいため、全体のフロー図を書きましたので参考にしてみてください。
※わかる方は飛ばしていただいて問題ありません。
続いて以下の部分でユーザ認証が失敗した場合にエラーメッセージを表示します。
python12345 Copied!{% for error in form.non_field_errors %}
<div class="alert alert-danger" role="alert">
<p>{{ error }}</p>
</div>
{% endfor %}
こんな感じのエラーを表示してくれます。
認証系のエラーはform.non_field_errorsに情報が格納されるため、for文でform.non_field_errorsから情報を取得して{{error}}としてテンプレートに表示させてあげます。
あとは、以下の部分でログインボタンを定義します。
python123 Copied!<div class="col-xs-4">
<button type="submit" class="btn btn-primary btn-block btn-flat">ログオン</button>
</div>
以上で、ログイン画面のテンプレートは完了です。
ここで、以下のコマンドを実行してユーザ認証機能をマイグレーションしておきましょう。
python12 Copied!$ python manage.py makemigrations
$ python manage.py migrate
また、ログイン用のテストユーザを作成しておきます。
python123456 Copied!$ python manage.py createsuperuser
ユーザー名 (leave blank to use 'sinfo'): test01 #test01と入力
メールアドレス: #メールアドレスは空のままEnterキーを押す
Password: #パスワードを2回入力する
Password (again):
Superuser created successfully.
次に以下のURLにアクセスしてログイン処理の動作確認を行いましょう。
※開発サーバを起動していない場合は以下のコマンドで起動してください。
python12 Copied!$ cd tutorial
$ python manage.py runserver
ログオン画面が表示されたら、ユーザ名とパスワードを入力してログオンボタンを押した後に、以下のTOP画面に遷移すればOKです。
http://127.0.0.1:8000/pdfmr/top/
以上で、ログイン・ログアウト機能(前編)の実装は完了です。
後編では、TOP画面にログオンユーザ名を表示する機能とログアウトボタン機能を実装していきます。
お疲れさまでした。