Djangoのログイン認証のパスワードリセット処理を実装する方法をご紹介します。
以下の記事の続きです。
Djangoのログイン処理を実装する方法②~パスワード変更~
条件
- Django 2.1.3
- Python 3.7.0
- PyCharm Professional
パスワードリセット処理
パスの追加
パスワードリセット処理を使用するためにurls.pyにパスを追加します。
from django.contrib import admin
from django.urls import path, include
from django.views.generic.base import TemplateView
from django.contrib.auth import views as auth_views
path('admin/', admin.site.urls),
path('accounts/', include('django.contrib.auth.urls')),
path('', TemplateView.as_view(template_name='home.html'), name='home'),
path('password_change/', auth_views.PasswordChangeView.as_view(template_name='password_change_form.html'), name='password_change'),
path('password_change/done/', auth_views.PasswordChangeDoneView.as_view(template_name='password_change_done.html'), name='password_change_done'),
path('password_reset/', auth_views.PasswordResetView.as_view(template_name='password_reset_form.html'), name='password_reset'), # 追加
path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(template_name='password_reset_done.html'), name='password_reset_done'), # 追加
path('reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(template_name='password_reset_confirm.html'), name='password_reset_confirm'), # 追加
path('reset/done/', auth_views.PasswordResetCompleteView.as_view(template_name='password_reset_complete.html'), name='password_reset_complete'), # 追加
from django.contrib import admin
from django.urls import path, include
from django.views.generic.base import TemplateView
from django.contrib.auth import views as auth_views
urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('django.contrib.auth.urls')),
path('', TemplateView.as_view(template_name='home.html'), name='home'),
path('password_change/', auth_views.PasswordChangeView.as_view(template_name='password_change_form.html'), name='password_change'),
path('password_change/done/', auth_views.PasswordChangeDoneView.as_view(template_name='password_change_done.html'), name='password_change_done'),
path('password_reset/', auth_views.PasswordResetView.as_view(template_name='password_reset_form.html'), name='password_reset'), # 追加
path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(template_name='password_reset_done.html'), name='password_reset_done'), # 追加
path('reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(template_name='password_reset_confirm.html'), name='password_reset_confirm'), # 追加
path('reset/done/', auth_views.PasswordResetCompleteView.as_view(template_name='password_reset_complete.html'), name='password_reset_complete'), # 追加
]
from django.contrib import admin
from django.urls import path, include
from django.views.generic.base import TemplateView
from django.contrib.auth import views as auth_views
urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('django.contrib.auth.urls')),
path('', TemplateView.as_view(template_name='home.html'), name='home'),
path('password_change/', auth_views.PasswordChangeView.as_view(template_name='password_change_form.html'), name='password_change'),
path('password_change/done/', auth_views.PasswordChangeDoneView.as_view(template_name='password_change_done.html'), name='password_change_done'),
path('password_reset/', auth_views.PasswordResetView.as_view(template_name='password_reset_form.html'), name='password_reset'), # 追加
path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(template_name='password_reset_done.html'), name='password_reset_done'), # 追加
path('reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(template_name='password_reset_confirm.html'), name='password_reset_confirm'), # 追加
path('reset/done/', auth_views.PasswordResetCompleteView.as_view(template_name='password_reset_complete.html'), name='password_reset_complete'), # 追加
]
テンプレート追加
以下のパスからパスワード変更用のテンプレートをコピーします。
コピー元
- \venv\Lib\site-packages\django\contrib\admin\templates\registration\password_reset_form.html
- \venv\Lib\site-packages\django\contrib\admin\templates\registration\password_reset_done.html
- \venv\Lib\site-packages\django\contrib\admin\templates\registration\password_reset_confirm.html
- \venv\Lib\site-packages\django\contrib\admin\templates\registration\password_reset_complete.html
コピー先(templatesフォルダの下)
- \templates\password_reset_form.html
- \templates\password_reset_done.html
- \templates\password_reset_confirm.html
- \templates\password_reset_complete.html

動作確認
manage.py runserverを実行して以下のURLを開きます。
http://127.0.0.1:8000/password_reset/
http://127.0.0.1:8000/password_reset/done/
http://127.0.0.1:8000/reset/done/
以下のような表示になればOKです。



パスワードリセット画面の変更
このままでは管理画面を継承しているため、継承元を変更します。
以下4つのファイルについて「extends」を変更します。
- password_reset_form.html
- password_reset_done.html
- password_reset_confirm.html
- password_reset_complete.html
変更前
{% extends "admin/base_site.html" %}
{% extends "admin/base_site.html" %}
{% extends "admin/base_site.html" %}
変更後
{% extends 'base.html' %}
{% extends 'base.html' %}
{% extends 'base.html' %}
再度動作確認をすると以下のような画面になります。
(画面デザインは適宜変更してください。)



リンクの追加
base.htmlにパスワードリセットのリンクを追加します。
未ログイン状態のみ表示されるようにします。
<!-- templates/base.html -->
<title>{% block title %}Django Auth Tutorial{% endblock %}</title>
{% if user.is_authenticated %}
<li><a href="{% url 'logout' %}" class="logout">ログアウト</a></li>
{% if form_name == "password_change" %}
<li><a href="{% url 'password_change' %}" class="logout">パスワード変更</a></li>
<li><a href="{% url 'login' %}" class="login">ログイン</a></li>
<li><a href="{% url 'password_reset' %}">パスワードリセット</a></li>
<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{% block title %}Django Auth Tutorial{% endblock %}</title>
</head>
<body>
<main>
{% if user.is_authenticated %}
<li><a href="{% url 'logout' %}" class="logout">ログアウト</a></li>
{% if form_name == "password_change" %}
{% else %}
<li><a href="{% url 'password_change' %}" class="logout">パスワード変更</a></li>
{% endif %}
{% else %}
<li><a href="{% url 'login' %}" class="login">ログイン</a></li>
<li><a href="{% url 'password_reset' %}">パスワードリセット</a></li>
{% endif %}
{% block content %}
{% endblock %}
</main>
</body>
</html>
<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{% block title %}Django Auth Tutorial{% endblock %}</title>
</head>
<body>
<main>
{% if user.is_authenticated %}
<li><a href="{% url 'logout' %}" class="logout">ログアウト</a></li>
{% if form_name == "password_change" %}
{% else %}
<li><a href="{% url 'password_change' %}" class="logout">パスワード変更</a></li>
{% endif %}
{% else %}
<li><a href="{% url 'login' %}" class="login">ログイン</a></li>
<li><a href="{% url 'password_reset' %}">パスワードリセット</a></li>
{% endif %}
{% block content %}
{% endblock %}
</main>
</body>
</html>

メール送信設定
パスワードリセット時のメール送信処理について設定します。
コンソール出力
開発環境でメールを送信する必要がない場合、メール情報をコンソール出力して確認する方法があります。
settings.pyに「EMAIL_BACKEND」を以下のように設定します。
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# sample/settings.py
# メール情報のコンソール出力
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# sample/settings.py
# メール情報のコンソール出力
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
パスワードリセット画面からメール送信を行うと、コンソールに以下のようなメール文が出力されます。
Content-Transfer-Encoding: 8bit
=?utf-8?b?MTI3LjAuMC4xOjgwMDAg44Gu44OR44K544Ov44O844OJ44Oq44K744OD44OI?=
From: webmaster@localhost
To: sample@mail.sample.co.jp
Date: Wed, 14 Nov 2018 01:55:05 -0000
Message-ID: <154216050569.15624.209423990845700430@DESKTOP-2R7EC85>
このメールは 127.0.0.1:8000 で、あなたのアカウントのパスワードリセットが要求されたため、送信されました。
http://127.0.0.1:8000/reset/MQ/51a-77efc24ce9f5571cfde0/
-------------------------------------------------------------------------------
[14/Nov/2018 10:55:05] "GET /password_reset/done/ HTTP/1.1" 200 771
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Subject:
=?utf-8?b?MTI3LjAuMC4xOjgwMDAg44Gu44OR44K544Ov44O844OJ44Oq44K744OD44OI?=
From: webmaster@localhost
To: sample@mail.sample.co.jp
Date: Wed, 14 Nov 2018 01:55:05 -0000
Message-ID: <154216050569.15624.209423990845700430@DESKTOP-2R7EC85>
このメールは 127.0.0.1:8000 で、あなたのアカウントのパスワードリセットが要求されたため、送信されました。
次のページで新しいパスワードを選んでください:
http://127.0.0.1:8000/reset/MQ/51a-77efc24ce9f5571cfde0/
あなたのユーザー名 (念のため): admin
ご利用ありがとうございました!
127.0.0.1:8000 チーム
-------------------------------------------------------------------------------
[14/Nov/2018 10:55:05] "GET /password_reset/done/ HTTP/1.1" 200 771
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Subject:
=?utf-8?b?MTI3LjAuMC4xOjgwMDAg44Gu44OR44K544Ov44O844OJ44Oq44K744OD44OI?=
From: webmaster@localhost
To: sample@mail.sample.co.jp
Date: Wed, 14 Nov 2018 01:55:05 -0000
Message-ID: <154216050569.15624.209423990845700430@DESKTOP-2R7EC85>
このメールは 127.0.0.1:8000 で、あなたのアカウントのパスワードリセットが要求されたため、送信されました。
次のページで新しいパスワードを選んでください:
http://127.0.0.1:8000/reset/MQ/51a-77efc24ce9f5571cfde0/
あなたのユーザー名 (念のため): admin
ご利用ありがとうございました!
127.0.0.1:8000 チーム
-------------------------------------------------------------------------------
[14/Nov/2018 10:55:05] "GET /password_reset/done/ HTTP/1.1" 200 771
メール本文にあるリンクをクリックすると、新しいパスワードを入力する画面が表示されます。

新しいパスワードを入力してパスワードの変更ボタンを押すと、パスワードが変更され以下の画面が表示されます。

ファイル出力
開発環境でメールを送信する必要がない場合、コンソール出力の他にファイル出力して確認する方法もあります。
settings.pyに「EMAIL_BACKEND」を以下のように設定します。
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = os.path.join(BASE_DIR, "sent_emails")
# メール情報のファイル出力
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = os.path.join(BASE_DIR, "sent_emails")
# メール情報のファイル出力
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = os.path.join(BASE_DIR, "sent_emails")
パスワードリセット処理を行うと、プロジェクトフォルダの下に「sent_emails」というフォルダが作成され、メール本文がlogとしてファイル出力されます。

メール送信
メール送信する場合、以下のように設定します。
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.test.com'
EMAIL_HOST_USER = 'test@test.com'
EMAIL_HOST_PASSWORD = 'passwordpassword'
EMAIL_USE_TLS = True # TLS (Transport Layer Security)設定
DEFAULT_FROM_EMAIL = 'test@test.com' # メールのfrom
# sample/settings.py
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.test.com'
EMAIL_PORT = 587
EMAIL_HOST_USER = 'test@test.com'
EMAIL_HOST_PASSWORD = 'passwordpassword'
EMAIL_USE_TLS = True # TLS (Transport Layer Security)設定
DEFAULT_FROM_EMAIL = 'test@test.com' # メールのfrom
# sample/settings.py
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.test.com'
EMAIL_PORT = 587
EMAIL_HOST_USER = 'test@test.com'
EMAIL_HOST_PASSWORD = 'passwordpassword'
EMAIL_USE_TLS = True # TLS (Transport Layer Security)設定
DEFAULT_FROM_EMAIL = 'test@test.com' # メールのfrom
メールのfromはデフォルトで「webmaster@localhost」という設定になっています。
(django/conf/global_settings.pyの「DEFAULT_FROM_EMAIL」に記述されている)
smtpの設定によっては「@localhost」という値が含まれるとメールが送信されない場合があるので、settings.pyで改めて「DEFAULT_FROM_EMAIL」を設定しなおす方が無難です。
以上でパスワードリセット処理の追加は終わりです。
参考
本記事は主に以下のサイトを参考にさせていただきました。
Django Password Reset Tutorial (Part 3)
https://wsvincent.com/django-user-authentication-tutorial-password-reset/
Django
“Djangoのログイン処理を実装する方法③~パスワードリセット~” に対して2件のコメントがあります。