Djangoで、パスワード変更ページと忘れた際の再設定ページ

Python - Django
2018年10月31日12:07に更新(約14日前)
2018年10月21日9:55に作成(約24日前)

旧ブログ移行記事です。

概要

Djangoで、会員登録機能を自作するシリーズの1つです。

前回、ユーザー情報閲覧・更新ページを作りました。 今回はパスワードの変更ページと、パスワードを忘れた際の再設定ページを作ります。

パスワードの変更

まず、パスワードの変更から実装していきます。

見た目

パスワード変更ページへのリンクが増えました

今のパスワードと、新しいパスワードを入力します。

無事に変更できたら、このように表示されます。

urls.py

以下の2つが必要です。

    path('password_change/', views.PasswordChange.as_view(), name='password_change'),
    path('password_change/done/', views.PasswordChangeDone.as_view(), name='password_change_done'),

base.html

パスワード変更ページへのリンクが増えました。

        <ul class="navbar-nav mr-auto">
          {% if user.is_authenticated %}
          <li>
            <a class="nav-item nav-link" href="{% url 'register:user_detail' user.pk %}">ユーザー情報閲覧</a>
          </li>
          <li>
            <a class="nav-item nav-link" href="{% url 'register:user_update' user.pk %}">ユーザー情報更新</a>
          </li>
          <li>
            <a class="nav-item nav-link" href="{% url 'register:logout' %}">ログアウト</a>
          </li>
          <li>
            <a class="nav-item nav-link" href="{% url 'register:password_change' %}">パスワードの変更</a>
          </li>
          {% else %}
          <li class="nav-item">
            <a class="nav-item nav-link" href="{% url 'register:login' %}">
            ようこそ、ゲスト!ログインはこちら
          </a>
          </li>
          {% endif %}
        </ul>

forms.py

PasswordChangeFormという用意されたクラスを使うと簡単です。それと、Bootstrap4対応もしておきます。

from django import forms
from django.contrib.auth.forms import (
    AuthenticationForm, UserCreationForm, PasswordChangeForm
)
from django.contrib.auth import get_user_model
...
...
class MyPasswordChangeForm(PasswordChangeForm):
    """パスワード変更フォーム"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields.values():
            field.widget.attrs['class'] = 'form-control'

views.py

こちらも、用意されたクラスを使えばすぐです。 importは、増えたものだけ書いています。

..
...
from django.contrib.auth.views import (
    LoginView, LogoutView, PasswordChangeView, PasswordChangeDoneView
)
...
...
from django.urls import reverse_lazy
...
...
from .forms import (
    LoginForm, UserCreateForm, UserUpdateForm, MyPasswordChangeForm
)
...
...
class PasswordChange(PasswordChangeView):
    """パスワード変更ビュー"""
    form_class = MyPasswordChangeForm
    success_url = reverse_lazy('register:password_change_done')
    template_name = 'register/password_change.html'


class PasswordChangeDone(PasswordChangeDoneView):
    """パスワード変更しました"""
    template_name = 'register/password_change_done.html'

password_change.html

よくあるフォームのテンプレートですが、一応htmlを作りました。

{% extends "register/base.html" %}
{% block content %}
<form action="" method="POST">
    {{ form.non_field_errors }}
    {% for field in form %}
    <div class="form-group">
        <label for="{{ field.id_for_label }}">{{ field.label_tag }}</label>
        {{ field }}
        {{ field.errors }}
    </div>
    {% endfor %}
    {% csrf_token %}
    <button type="submit" class="btn btn-primary btn-lg">送信</button>
</form>
{% endblock %}

password_change_done.html

変更した、と伝えるだけです。

{% extends "register/base.html" %}
{% block content %}
<p>
    パスワードを変更しました。<br>
    <a class="btn btn-primary btn-lg" href="{% url 'register:login' %}">ログイン</a>
</p>
{% endblock %}

パスワード忘れ

パスワードを忘れた際の再発行機能も、Djangoで用意されています。

見た目

ログインページに、パスワードを忘れた方へのリンクが増えました。

クリックすると、メールアドレスを入力するよう求められます。

入力して送信すると、再設定用ページのURLがメールで送付されます。

再設定ページで、パスワードを入力して送信すると反映されます。

urls.py

4つほど増えます。

    path('password_reset/', views.PasswordReset.as_view(), name='password_reset'),
    path('password_reset/done/', views.PasswordResetDone.as_view(), name='password_reset_done'),
    path('password_reset/confirm/<uidb64>/<token>/', views.PasswordResetConfirm.as_view(), name='password_reset_confirm'),
    path('password_reset/complete/', views.PasswordResetComplete.as_view(), name='password_reset_complete'),

foms.py

メールアドレスを入力するMyPasswordResetFormと、再設定のためのMySetPasswordFormができました。

from django import forms
from django.contrib.auth.forms import (
    AuthenticationForm, UserCreationForm, PasswordChangeForm,
    PasswordResetForm, SetPasswordForm
)
...
...
...
class MyPasswordResetForm(PasswordResetForm):
    """パスワード忘れたときのフォーム"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields.values():
            field.widget.attrs['class'] = 'form-control'


class MySetPasswordForm(SetPasswordForm):
    """パスワード再設定用フォーム(パスワード忘れて再設定)"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields.values():
            field.widget.attrs['class'] = 'form-control'

login.html

ログインページにでも、パスワードを忘れた方用のリンクを作っておきましょう。

    <!-- 右側、会員登録エリア -->
    <div class="card col-md-6">
        <div class="card-body">
            <a href="{% url 'register:user_create' %}" class="btn btn-success btn-lg btn-block" >会員登録</a><hr>
            <a href="{% url 'register:password_reset' %}" class="btn btn-success btn-lg btn-block" >パスワードを忘れた方</a>
        </div>
    </div><!-- 右側、会員登録エリアおわり -->

views.py

Djangoで用意されたビューと、先程定義したフォームを使うだけです。

...
from django.contrib.auth.views import (
    LoginView, LogoutView, PasswordChangeView, PasswordChangeDoneView,
    PasswordResetView, PasswordResetDoneView, PasswordResetConfirmView, PasswordResetCompleteView
)
...
from django.urls import reverse_lazy
...
from .forms import (
    LoginForm, UserCreateForm, UserUpdateForm, MyPasswordChangeForm,
    MyPasswordResetForm, MySetPasswordForm
)
...
...
...
class PasswordReset(PasswordResetView):
    """パスワード変更用URLの送付ページ"""
    subject_template_name = 'register/mail_template/password_reset/subject.txt'
    email_template_name = 'register/mail_template/password_reset/message.txt'
    template_name = 'register/password_reset_form.html'
    form_class = MyPasswordResetForm
    success_url = reverse_lazy('register:password_reset_done')


class PasswordResetDone(PasswordResetDoneView):
    """パスワード変更用URLを送りましたページ"""
    template_name = 'register/password_reset_done.html'


class PasswordResetConfirm(PasswordResetConfirmView):
    """新パスワード入力ページ"""
    form_class = MySetPasswordForm
    success_url = reverse_lazy('register:password_reset_complete')
    template_name = 'register/password_reset_confirm.html'


class PasswordResetComplete(PasswordResetCompleteView):
    """新パスワード設定しましたページ"""
    template_name = 'register/password_reset_complete.html'

mail_template/password_reset/subject.txt

パスワード再設定用メールの、題名です。

ほにゃらら - パスワードの再設定

mail_template/password_reset/message.txt

パスワード再設定用メールの本文です。各変数は、ビュー側で勝手に作ってくれています。

{{ user.username }} 様

下記URLよりサイトにアクセスの上、パスワードの再設定を行ってください。

再設定用URL
{{ protocol}}://{{ domain }}{% url 'register:password_reset_confirm' uid token %}

ほにゃらら

password_reset_form.html

メールアドレスを入力するページですね。好きなようにカスタマイズしてください。

{% extends "register/base.html" %}
{% block content %}
<form action="" method="POST">
    {{ form.non_field_errors }}
    {% for field in form %}
    <div class="form-group">
        <label for="{{ field.id_for_label }}">{{ field.label_tag }}</label>
        {{ field }}
        {{ field.errors }}
    </div>
    {% endfor %}
    {% csrf_token %}
    <button type="submit" class="btn btn-primary btn-lg">送信</button>
</form>
{% endblock %}

password_reset_done.html

メール送信したよ、というページ

{% extends "register/base.html" %}
{% block content %}
<p>
    パスワード再設定用のメールを送信しました。<br>
    メールに記載されているリンクから再設定を行ってください。
</p>
{% endblock %}

password_reset_confirm.html

新しいパスワードを設定するページ。好きなようにカスタマイズしてください。

{% extends "register/base.html" %}
{% block content %}
<form action="" method="POST">
    {{ form.non_field_errors }}
    {% for field in form %}
    <div class="form-group">
        <label for="{{ field.id_for_label }}">{{ field.label_tag }}</label>
        {{ field }}
        {{ field.errors }}
    </div>
    {% endfor %}
    {% csrf_token %}
    <button type="submit" class="btn btn-primary btn-lg">送信</button>
</form>
{% endblock %}

password_reset_complete.html

再設定したぞ、というページ

{% extends "register/base.html" %}
{% block content %}
<p>
    パスワード再設定を完了しました。<br>
    <a class="btn btn-primary btn-lg" href="{% url 'register:login' %}">ログイン</a>
</p>
{% endblock %}

記事にコメントする