ホーム >  Python > Django >  パスワードリマインダ機能を作る(Django)

投稿日:   |  最終更新日:

パスワードリマインダ機能を作る(Django)

DjangoPython

Djangoでパスワードリマインダ機能を作成します。よく会員制サイトで見かける「パスワードを忘れた」→「パスワード変更ページのメール送信」→「パスワード変更」の流れを作ります。

パスワードリマインダ

前回、仮登録した後にメールから本登録させるするアプリを作成しました。今回は、ユーザがパスワードを忘れてしまった場合に備えてパスワードリマインダを作ります。

手順

Djangoに予め備わっているPasswordChangeForm、PasswordResetForm,、SetPasswordFormなどを使います。コード量も少なくて簡単に実現できます。

  • パスワードリセットページにメールアドレスを入力して送信します。
  • システムはパスワード変更用のリンクを作成し、ユーザへメールを送信します。
  • ユーザは、メール本文中のリンクよりアクセスしてパスワード変更ページを開きます。
  • ユーザは、新しいパスワードを入力します。
  • パスワード変更が完了します。

URL設計

トップページ(/index) ドメイン直下のページです。ログイン前はページタイトルのみですが、ログインすると、ヘッダーナビゲーションにログアウトのリンクを表示します。
ログイン(/login) ユーザ名とパスワードを入力してログインします。ログインが完了すると、ログイン状態になってトップページへ遷移します。
ログアウト(/logout) ログアウト状態にしてトップページへ遷移します。
パスワードリセット(リマインダ)メール送信(/password_reset) パスワードをリセットします。メールアドレスを入力すると、リセットページのURLを送信します。
パスワードリセットメール送信完了(/password_reset_done) パスワードリセットメール送信に後表示します。
パスワードリセット(リマインダ)(/password_reset_confirm) ユーザがメール本文に記載されたURLをクリックしたときに表示します。新しいパスワードを入力してリセットします。
パスワード完了(/password_reset_complete) パスワードのリセットが完了したら表示します。

仕様

①トップページです。「ドメイン/」にアクセスします。画面右上にログインページへのリンクがあります。

ログインページです。「ドメイン/login」にアクセスします。新規登録ページへのリンクもあります。

ログインページからログインした場合は「ドメイン/」(トップページ)へ遷移します。画面左上の”ログイン”が”ログアウト”になります。

ログアウトを押すと、ログアウト処理後トップページへリダイレクトします。

パスワードリセット(リマインダー)メール送信ページです。「ドメイン/password_reset」にアクセスします。

パスワードリセット(リマインダー)メール送信が完了すると、送信完了ページを表示します。「ドメイン/password_reset_done」にアクセスします。

ユーザ宛に、パスワード変更用のメールが送信されます。

Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Subject: =?utf-8?b?44GU55m76Yyy44GC44KK44GM44Go44GG44GU44GW44GE44G+44GZ?=
From: gmailアカウント名
To: ユーザのメールアドレス
Date: Fri, xx Apr 2018 00:32:23 -0000
Message-ID: <1xxxxxx.xx01.xxxxxxxxxxxxxxx@localhost.localdomain>

ユーザ さん、以下からパスワードを再登録して下さい。
http://ドメイン名/create_complete/MjUzOTg1ZDFkMTEyNDMwYjhjOGI0YjA4ZWU0ZTkyOGE/

パスワード変更用URLを開くと、パスワード変更ページを表示します。

パスワード変更が完了すると、変更完了ページを表示します。

準備

Vagrantでゲスト環境(仮想環境)を作ります。

①Userモデルをカスタマイズしています。

DjangoのUserモデルカスタマイズ(UUIDを使う)

②ユーザ登録機能を作ります。

Djangoのログイン・ユーザ登録(カスタムユーザモデル)

③仮登録した後、メールから本登録する機能を作ります。

仮登録した後、メールから本登録させる(Django)

環境

OS CentOS 7.1.1503
pyenv 1.1.3-5-g7dae197
Anaconda 3-4.3.0
MariaDB 5.5.52-1.el7
Apache 2.4.6
mod_wsgi 4.5.14
Django 1.11.3

パスワードリマインダ概要

Userモデルの継承してカスタマイズします。usernameの代わりにemailを使うようにしています。

①以下のようなDjangoアプリ構成を作ります。プロジェクト名が「pj1」で、アプリケーション名が「users」です。プロジェクトの設定は済ませたものとします。赤字のファイルは、今回変更する部分です。

  • pj1/
  • pj1/
  • __init__.py
  • settings.py
  • urls.py
  • wsgi.py
  • users/
  • __init__.py
  • admin.py
  • apps.py
  • urls.py
  • views.py
  • models.py
  • token_manager.py
  • templates/
  • base.html
  • index.html
  • login.html
  • password_reset_form.html
  • password_reset_done.html
  • password_reset_confirm.html
  • password_reset_complete.html
  • mailtemplate/password_reset/
  • message.txt
  • subject.txt
  • manage.py

settings.py

pj1ディレクトリ下のsettings.pyを変更します。ログイン関連のURLや、Gmailで送信する設定をしています。

LOGIN_URL = "users:login"  # ログインするページ。デフォルトにするなら"/admin/login/"等も
LOGIN_REDIRECT_URL = 'users:index'  # ログインページに直接飛んだとき、ログイン完了後のリダイレクト先

# メールを実際に送らず、コンソール画面へ表示する
#EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_HOST_USER = 'gmailアカウント名'
EMAIL_HOST_PASSWORD = 'gmailパスワード'
EMAIL_USE_TLS = True

models.py

①usersディレクトリ配下のmodels.pyを編集します。内容は前回と同じです。

DjangoのUserモデルカスタマイズ(UUIDを使う)

urls.py

①pj1ディレクトリのurls.pyを以下のように編集します。

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url('admin/', admin.site.urls),
    url(r'^', include('users.urls', namespace = 'users'))
]

②usersディレクトリのurls.pyを以下のように編集します。

from django.conf.urls import include, url
from django.contrib.auth import views as auth_views
from . import views
 
app_name = 'users'
 
urlpatterns = [
    url(r'^$', views.index, name='index'),

    # ログイン、ログアウト
    url(r'^login/$', views.login, name='login'),
    url(r'^logout/$', views.logout, name='logout'),
    url(r'^regist/$', views.regist, name='regist'),
    url(r'^regist_save/$', views.regist_save, name='regist_save'),

〜省略〜

    # パスワードリマインダ
    url(r'^password_reset/$', views.password_reset, name='password_reset'),
    url(r'^password_reset_done', views.password_reset_done,
        name='password_reset_done'),
    url(r'^password_reset_confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
        views.password_reset_confirm, name='password_reset_confirm'),
    url(r'^password_reset_complete/$', views.password_reset_complete,
        name='password_reset_complete'),

]

forms.py

①usersディレクトリ配下のforms.pyを編集します。以下の内容を追記します。

from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.forms import AuthenticationForm
from users.models import  User
from django.contrib.auth.forms import PasswordChangeForm, PasswordResetForm, SetPasswordForm

〜省略〜

class ForgetPasswordForm(PasswordResetForm):
 
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['email'].widget.attrs['class'] = 'form-control'
        self.fields['email'].widget.attrs['placeholder'] = 'メールアドレス'
 
 
class ChangePasswordForm(PasswordChangeForm):
 
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['new_password1'].widget.attrs['class'] = 'form-control'
        self.fields['new_password2'].widget.attrs['class'] = 'form-control'
        self.fields['old_password'].widget.attrs['class'] = 'form-control'
 
 
class PasswordConfirmForm(SetPasswordForm):
 
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['new_password1'].widget.attrs['class'] = 'form-control'
        self.fields['new_password1'].widget.attrs['placeholder'] = '新パスワード'
        self.fields['new_password2'].widget.attrs['class'] = 'form-control'
        self.fields['new_password2'].widget.attrs['placeholder'] = '新パスワード(確認)'

views.py

①usersディレクトリのviews.pyを以下のように編集します。

from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_POST
from .forms import (
  LoginForm,
  RegisterForm,
  UserPasswordChangeForm,
  ForgetPasswordForm,
  PasswordConfirmForm
)
from django.contrib.auth.models import User
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth import views as auth_views
from django.contrib.auth import get_user_model
from django.core.mail import send_mail
from django.template.loader import get_template
from django.utils.encoding import force_bytes, force_text
from django.contrib.sites.shortcuts import get_current_site
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.contrib.auth import login as auth_login
from .models import Activate
from users.backends import EmailModelBackend

#外部ファイル
from .token_manager import create_expiration_date, create_key

User = get_user_model()


def index(request):
  context = {
    'users':request.user,
  }
  return render(request, 'index.html', context)


def login(request):
    context = {
        'template_name': 'login.html',
        'authentication_form': LoginForm
    }
    return auth_views.login(request, **context)
 

def logout(request):
    context = {
        'template_name': 'index.html',
    }
    return auth_views.logout(request, **context)


〜省略〜


def password_reset(request):
    context = {
        'post_reset_redirect': reverse_lazy('users:password_reset_done'),
        'template_name': 'password_reset_form.html',
        'email_template_name': 'mailtemplate/password_reset/message.txt',
        'subject_template_name': 'mailtemplate/password_reset/subject.txt',
        'password_reset_form': ForgetPasswordForm,
    }
    return auth_views.password_reset(request, **context)


def password_reset_done(request):
    context = {
        'template_name': 'password_reset_done.html',
    }
    return auth_views.password_reset_done(request, **context)


def password_reset_confirm(request, uidb64, token):
    context = {
        'uidb64': uidb64,
        'token': token,
        'post_reset_redirect': reverse_lazy('users:password_reset_complete'),
        'template_name': 'password_reset_confirm.html',
        'set_password_form': PasswordConfirmForm,
    }
    return auth_views.password_reset_confirm(request, **context)
 
 
def password_reset_complete(request):
    context = {
        'template_name': 'password_reset_complete.html',
    }
    return auth_views.password_reset_complete(request, **context)

解説

def password_reset(request):
    context = {
        'post_reset_redirect': reverse_lazy('users:password_reset_done'),
        'template_name': 'password_reset_form.html',
        'email_template_name': 'mailtemplate/password_reset/message.txt',
        'subject_template_name': 'mailtemplate/password_reset/subject.txt',
        'password_reset_form': ForgetPasswordForm,
    }
    return auth_views.password_reset(request, **context)

パスワードのリセットページのビューです。contextに以下の内容を指定します。

post_reset_refirect 入力後のview
email_template_name メールの本文のテンプレート
subject_template_name タイトルのテンプレート
password_reset_form forms.pyのForgetPasswordForm(デフォルトはPasswordResetForm)

これらを指定しましたら、「django.contrib.auth.views」のpassword_resetに渡します。「django.contrib.auth.views」の詳細は以下のURLで確認して下さい。

views.py

def password_reset_done(request):
    context = {
        'template_name': 'easy_regist/password_reset_done.html',
    }
    return auth_views.password_reset_done(request, **context)

パスワワードリセットページ入力後に、リダイレクトするviewです。templateを指定するだけです。

def password_reset_confirm(request, uidb64, token):
    context = {
        'uidb64': uidb64,
        'token': token,
        'post_reset_redirect': reverse_lazy('users:password_reset_complete'),
        'template_name': 'password_reset_confirm.html',
        'set_password_form': PasswordConfirmForm,
    }
    return auth_views.password_reset_confirm(request, **context)

メールのURLにアクセスし、パスワードを設定します。

def password_reset_complete(request):
    context = {
        'template_name': 'password_reset_complete.html',
    }
    return auth_views.password_reset_complete(request, **context)

パスワード設定後にリダイレクトされるviewです。

メールテンプレート

パスワードの再登録

送信するメールの件名のテンプレートです。

{{ user.nick_name }} さん、以下からパスワードを再登録してください。
{{ protocol}}://{{ domain }}{% url 'users:password_reset_confirm' uidb64=uid token=token %}

送信するメール本文のテンプレートです。本登録までのURLと、パスワード変更用のid(uidb64)とトークンを表示します。

テンプレート

「Base.html」、「index.html」、は、前回の通りです。

{% extends 'base.html' %}
{% block content %}
<div class=&quot;container-fluid&quot; style=&quot;margin-top: 60px;&quot;&gt;
  <div class=&quot;row&quot;&gt;
    <div class=&quot;col-md-6 offset-md-3&quot;&gt;
      <div class=&quot;card&quot;&gt;
        <div class=&quot;card-header&quot;&gt; ログイン&lt;/div&gt;
        <div class=&quot;card-body&quot;&gt;
          <div class=&quot;card-block&quot;&gt; 
            <form class=&quot;my-form&quot; action=&quot;{% url 'users:login' %}&quot; method=&quot;POST&quot;&gt;
            {% csrf_token %}
              <div class=&quot;row&quot;&gt;
                <div class=&quot;form-group col-md-11&quot;&gt;
                  <label for=&quot;id_email&quot;&gt;メールアドレス&lt;/label&gt;
                  {{ form.username }}
                  {{ form.username.errors }}
                  <p class=&quot;help-block mb-0&quot;&gt;{{ form.username.help_text }}&lt;/p&gt;
                </div&gt;
                <div class=&quot;form-group col-md-11 pb-3&quot;&gt;
                  <label for=&quot;id_email&quot;&gt;パスワード&lt;/label&gt;
                  {{ form.password }}
                  {{ form.password.errors }}
                </div&gt;
                <div class=&quot;form-group col-md-11&quot;&gt;
                  <input type='hidden' name='next' value='{{ next }}' /&gt;
                  <button type=&quot;submit&quot; class=&quot;btn btn-block btn-outline-primary&quot;&gt; ログイン&lt;/button&gt;
                </div&gt;
              </div&gt;
            &lt;/form&gt;
            &lt;div class=&quot;card-text&quot;&gt;
              &lt;p class=&quot;text-right mb-0&quot;&gt;&lt;a class=&quot;nav-link&quot; href=&quot;{% url 'users:password_reset' %}&quot;&gt;パスワードを忘れた方&lt;/a&gt;&lt;/p&gt;
              &lt;p class=&quot;text-right mb-0&quot;&gt;&lt;a class=&quot;nav-link&quot; href=&quot;{% url 'users:create' %}&quot;&gt;新規登録会員&lt;/a&gt;&lt;/p&gt;
            &lt;/div&gt;&lt;!-- end card-text --&gt;
          &lt;/div&gt;
        &lt;/div&gt;&lt;!-- end card-body--&gt;
      &lt;/div&gt;&lt;!-- end card--&gt;
    &lt;/div&gt;
  &lt;/div&gt;&lt;!-- end row --&gt;
&lt;/div&gt;&lt;!-- end container-fluid--&gt;
{% endblock %}

「login.html」は、ログイン画面を定義します。「パスワードを忘れた方」にリンクを追加しました。

{% extends 'base.html' %}
{% block content %}
&lt;div class=&quot;container-fluid&quot; style=&quot;margin-top: 60px;&quot;&gt;
  &lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;col-md-6 offset-md-3&quot;&gt;
      &lt;div class=&quot;card&quot;&gt;
        &lt;div class=&quot;card-header&quot;&gt;パスワードリセットページ&lt;/div&gt;
        &lt;div class=&quot;card-body&quot;&gt;
          &lt;div class=&quot;card-block&quot;&gt; 
            &lt;form class=&quot;my-form&quot; action=&quot;&quot; method=&quot;POST&quot;&gt;
              &lt;div class=&quot;row&quot;&gt;
                &lt;div class=&quot;form-group col-md-11 pb-3&quot;&gt;
                  &lt;label for=&quot;id_email&quot;&gt;メールアドレス&lt;/label&gt;
                  {{ form.email }}
                &lt;/div&gt;
                &lt;div class=&quot;form-group col-md-11&quot;&gt;
                  {% csrf_token %}
                  &lt;button type=&quot;submit&quot; class=&quot;btn btn-block btn-outline-primary&quot;&gt;送信&lt;/button&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/form&gt;
          &lt;/div&gt;
        &lt;/div&gt;&lt;!-- end card-body--&gt;
      &lt;/div&gt;&lt;!-- end card--&gt;
    &lt;/div&gt;
  &lt;/div&gt;&lt;!-- end row --&gt;
&lt;/div&gt;&lt;!-- end container-fluid--&gt;
{% endblock %}

「password_reset_form.html」は、パスワードリセット(リマインダ)メール送信画面を定義します。

{% extends &quot;base.html&quot; %}
{% block content %}
&lt;div class=&quot;container-fluid&quot; style=&quot;margin-top: 60px;&quot;&gt;
  &lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;col-md-6 offset-md-3&quot;&gt;
      &lt;div class=&quot;card&quot;&gt;
        &lt;div class=&quot;card-header&quot;&gt;パスワードリセットページ&lt;/div&gt;
        &lt;div class=&quot;card-body&quot;&gt;
          &lt;div class=&quot;card-block&quot;&gt; 
            &lt;p class=&quot;card-text&quot;&gt;
              メールアドレスにパスワード初期化ページのURLを送付しました。
            &lt;/p&gt;
          &lt;/div&gt;
        &lt;/div&gt;&lt;!-- end card-body--&gt;
      &lt;/div&gt;&lt;!-- end card--&gt;
    &lt;/div&gt;
  &lt;/div&gt;&lt;!-- end row --&gt;
&lt;/div&gt;&lt;!-- end container-fluid--&gt;
{% endblock %}

「password_reset_done.html」は、パスワードリセット(リマインダ)メール送信完了画面を定義します。

{% extends 'base.html' %}
{% block content %}
&lt;div class=&quot;container-fluid&quot; style=&quot;margin-top: 60px;&quot;&gt;
  &lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;col-md-6 offset-md-3&quot;&gt;
      &lt;div class=&quot;card&quot;&gt;
        &lt;div class=&quot;card-header&quot;&gt;パスワードリセットページ&lt;/div&gt;
        &lt;div class=&quot;card-body&quot;&gt;
          &lt;div class=&quot;card-block&quot;&gt;
            &lt;p class=&quot;card-text&quot;&gt;
              新パスワードを入力してください。
            &lt;/p&gt;
            &lt;form class=&quot;my-form&quot; action=&quot;&quot; method=&quot;POST&quot;&gt;
            {% csrf_token %}
              &lt;div class=&quot;row&quot;&gt;
                &lt;div class=&quot;form-group col-md-11&quot;&gt;
                  &lt;label for=&quot;id_new_password1&quot;&gt;新しいパスワード&lt;/label&gt;
                  {{ form.new_password1 }}
                  {{ form.new_password1.errors }}
                &lt;/div&gt;
                &lt;div class=&quot;form-group col-md-11  pb-3&quot;&gt;
                  &lt;label for=&quot;id_new_password2&quot;&gt;新しいパスワード(確認)&lt;/label&gt;
                  {{ form.new_password2 }}
                  {{ form.new_password2.errors }}
                &lt;/div&gt;
                &lt;div class=&quot;form-group col-md-11&quot;&gt;
                  &lt;input type='hidden' name='next' value='{{ next }}' /&gt;
                  &lt;button type=&quot;submit&quot; class=&quot;btn btn-block btn-outline-primary&quot;&gt;送信&lt;/button&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/form&gt;
          &lt;/div&gt;
        &lt;/div&gt;&lt;!-- end card-body--&gt;
      &lt;/div&gt;&lt;!-- end card--&gt;
    &lt;/div&gt;
  &lt;/div&gt;&lt;!-- end row --&gt;
&lt;/div&gt;&lt;!-- end container-fluid--&gt;
{% endblock %}

「password_reset_confirm.html」は、パスワード変更画面を定義します。

{% extends 'base.html' %}
{% block content %}
&lt;div class=&quot;container-fluid&quot; style=&quot;margin-top: 60px;&quot;&gt;
  &lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;col-md-6 offset-md-3&quot;&gt;
      &lt;div class=&quot;card&quot;&gt;
        &lt;div class=&quot;card-header&quot;&gt;パスワードリセットページ&lt;/div&gt;
        &lt;div class=&quot;card-body p-3&quot;&gt;
          &lt;p class=&quot;card-text&quot;&gt;パスワードをリセットしました。&lt;/p&gt;
        &lt;/div&gt;&lt;!-- end card-body--&gt;
      &lt;/div&gt;&lt;!-- end card--&gt;
    &lt;/div&gt;&lt;!-- end col-md-6 offset-md-3 --&gt;
  &lt;/div&gt;&lt;!-- end row --&gt;
&lt;/div&gt;&lt;!-- end container-fluid--&gt;
{% endblock %}

「password_reset_complete.html」は、パスワード変更完了画面を定義します。

トラックバック用のURL
プロフィール

名前:イワサキ ユウタ 職業:システムエンジニア、ウェブマスター、フロントエンドエンジニア 誕生:1986年生まれ 出身:静岡県 特技:ウッドベース 略歴 20

最近の投稿
人気記事
カテゴリー
広告