Python + Djangoでパスワードリセット画面を実装する

Djangoのパスワードリセット機能を試します。

以下のリンクの続きです。
[Python + Djangoで独自のユーザ認証を実装する](https://qiita.com/t-iguchi/items/54424f619829a6d135fc)
[Python + Djangoでパスワード変更画面を実装する](https://qiita.com/t-iguchi/items/67430e164de0e6701dc8)
###1.パスワードリセットのフロー
パスワードリセットは、システム上で、メールアドレスを入力して送信し、ユーザはメール受信後に、本文に記載されているURLをクリックします。パスワード変更画面（パスワードリセット用）が開くので、パスワードを変更すると、パスワードリセット完了の画面が表示され完了します。

- (1)パスワードリセット（メールアドレス入力）画面
- (2)パスワードリセット（メール送信）画面
- (3)メールのURLをクリック
- (4)パスワードリセット（パスワード変更）画面
- (5)パスワードリセット（パスワードリセット完了）画面

Djangoでは、このパスワードリセットも最初から実装されているのでそれを利用します。

###2.実装
####(1)accounts\urls.py
パスワードリセット用に、URLを４つ追加します。

```accounts\urls.py
from django.urls import path
from . import views

app_name = 'accounts'
urlpatterns = [
    path('', views.index.as_view(), name='index'),
    path('password_change/', views.PasswordChange.as_view(), name='password_change'),
    path('password_change/done/', views.PasswordChangeDone.as_view(), name='password_change_done'),
    path('password_reset/', views.PasswordReset.as_view(), name='password_reset'), #追加
    path('password_reset/done/', views.PasswordResetDone.as_view(), name='password_reset_done'), #追加
    path('reset/<uidb64>/<token>/', views.PasswordResetConfirm.as_view(), name='password_reset_confirm'), #追加
    path('reset/done/', views.PasswordResetComplete.as_view(), name='password_reset_complete'), #追加
]
```

####(2)accounts\views.py
パスワードリセット用に、views.pyを編集します。formクラスはDjangoで用意されているのでそのまま利用します。

```accounts\views.py
from django.shortcuts import render
# PasswordResetView, PasswordResetDoneView, PasswordResetConfirmView, PasswordResetCompleteViewを追加
from django.contrib.auth.views import PasswordChangeView, PasswordChangeDoneView, PasswordResetView, PasswordResetDoneView, PasswordResetConfirmView, PasswordResetCompleteView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views import generic
from django.urls import reverse_lazy

class index(LoginRequiredMixin, generic.TemplateView):
    """メニュービュー"""
    template_name = 'accounts/top.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs) # 継承元のメソッドCALL
        context["form_name"] = "top"
        return context


class PasswordChange(LoginRequiredMixin, PasswordChangeView):
    """パスワード変更ビュー"""
    success_url = reverse_lazy('accounts:password_change_done')
    template_name = 'accounts/password_change.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs) # 継承元のメソッドCALL
        context["form_name"] = "password_change"
        return context


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

# --- ここから追加
class PasswordReset(PasswordResetView):
    """パスワード変更用URLの送付ページ"""
    subject_template_name = 'accounts/mail_template/reset/subject.txt'
    email_template_name = 'accounts/mail_template/reset/message.txt'
    template_name = 'accounts/password_reset_form.html'
    success_url = reverse_lazy('accounts:password_reset_done')


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


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


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

# --- ここまで

```

####(3)templates\accounts\password_reset_form.html

```templates\accounts\password_reset_form.html
{% extends "commons/base.html" %}
{% block headertitle %}
  パスワードリセット
{% endblock %}
{% block content %}
<form action="" method="POST">
    {{ form.non_field_errors }}
    <p>メールを受信してパスワードの変更手続きを行います。</p>
    <p>メールアドレスを入力して送信ボタンを押してください。</p>
    {% 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 %}

    <pre>メールアドレスはあらかじめシステムに登録が必要です。
メールが届かない場合はシステム管理者に連絡してください。</pre>
    <br/>
    <div class="form-group row">
      <div class="col-6">
        <button type="submit" class="btn btn-primary btn-block">送信</button>
      </div>
    </div>
</form>
{% endblock %}
```
####(4)templates\accounts\password_reset_done.html

```templates\accounts\password_reset_done.html
{% extends "commons/base.html" %}

{% block headertitle %}
  パスワードリセット（メール送信）
{% endblock %}

{% block content %}
<form action="" method="POST">
<p>パスワードリセットのメールを送信しました。
<br/>
メールに記載されているリンクからパスワードの再設定を行ってください。
<br/>
<br/>
<br/>
<a class="btn btn-primary col-4" href="{% url 'accounts:index' %}">ログイン画面へ</a>
</p>
</form>
{% endblock %}
```
####(5)templates\accounts\password_reset_confirm.html

```templates\accounts\password_reset_confirm.html
{% extends "commons/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-block">送信</button>
</form>
{% endblock %}
```
####(6)templates\accounts\password_reset_complete.html

```templates\accounts\password_reset_complete.html
{% extends "commons/base.html" %}
{% block content %}
<form action="" method="POST">
<p>
    パスワード再設定を完了しました。<br>
    <a class="btn btn-primary btn-block" href="{% url 'accounts:index' %}">ログイン</a>
</p>
</form>
{% endblock %}
```

以上で実装終わりです。

※パスワードリセット（メールアドレス入力）画面で入力するメールアドレスは、プロジェクトの認証ユーザに設定されているメールアドレスである必要があります。管理サイトからメールアドレスを設定しましょう。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/246741/9467ca28-50eb-a48f-dfa4-966a2765cf2b.png)

###3.動作確認

では、確認してみます。

####(1)パスワードリセット（メールアドレス入力）画面
![image.png](https://qiita-image-store.s3.amazonaws.com/0/246741/933e6576-5f0b-8911-d78d-2f5a17ae792e.png)

####(2)パスワードリセット（メール送信）画面
![image.png](https://qiita-image-store.s3.amazonaws.com/0/246741/fec4cef9-a167-f91c-ca2e-390bbf067049.png)


####(3)メール
送信されたメール。ユーザ：Djangoに設定したメールアドレスに送信されています。

```
django 様


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


再設定用URL
http://localhost:8000/reset/MQ/4yb-77d2a24a746ddeceb805/
```

####(4)パスワードリセット（パスワード変更）画面
![image.png](https://qiita-image-store.s3.amazonaws.com/0/246741/34a13018-a2dd-7f85-a649-194259872da9.png)

####(5)パスワードリセット（パスワードリセット完了）画面
![image.png](https://qiita-image-store.s3.amazonaws.com/0/246741/cd3efd8a-d88f-bb6c-f684-a368bb2f6ae4.png)

パスワードリセットもDjangoは簡単です。