投稿日: | 最終更新日:
パスワードリマインダ機能を作る(Django)
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のログイン・ユーザ登録(カスタムユーザモデル)
③仮登録した後、メールから本登録する機能を作ります。
環境
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で確認して下さい。
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です。
メールテンプレート
パスワードの再登録
送信するメールの件名のテンプレートです。
送信するメール本文のテンプレートです。本登録までのURLと、パスワード変更用のid(uidb64)とトークンを表示します。
テンプレート
「Base.html」、「index.html」、は、前回の通りです。
{% extends 'base.html' %} {% block content %} <div class="container-fluid" style="margin-top: 60px;"> <div class="row"> <div class="col-md-6 offset-md-3"> <div class="card"> <div class="card-header"> ログイン</div> <div class="card-body"> <div class="card-block"> <form class="my-form" action="{% url 'users:login' %}" method="POST"> {% csrf_token %} <div class="row"> <div class="form-group col-md-11"> <label for="id_email">メールアドレス</label> {{ form.username }} {{ form.username.errors }} <p class="help-block mb-0">{{ form.username.help_text }}</p> </div> <div class="form-group col-md-11 pb-3"> <label for="id_email">パスワード</label> {{ form.password }} {{ form.password.errors }} </div> <div class="form-group col-md-11"> <input type='hidden' name='next' value='{{ next }}' /> <button type="submit" class="btn btn-block btn-outline-primary"> ログイン</button> </div> </div> </form> <div class="card-text"> <p class="text-right mb-0"><a class="nav-link" href="{% url 'users:password_reset' %}">パスワードを忘れた方</a></p> <p class="text-right mb-0"><a class="nav-link" href="{% url 'users:create' %}">新規登録会員</a></p> </div><!-- end card-text --> </div> </div><!-- end card-body--> </div><!-- end card--> </div> </div><!-- end row --> </div><!-- end container-fluid--> {% endblock %}
「login.html」は、ログイン画面を定義します。「パスワードを忘れた方」にリンクを追加しました。
{% extends 'base.html' %} {% block content %} <div class="container-fluid" style="margin-top: 60px;"> <div class="row"> <div class="col-md-6 offset-md-3"> <div class="card"> <div class="card-header">パスワードリセットページ</div> <div class="card-body"> <div class="card-block"> <form class="my-form" action="" method="POST"> <div class="row"> <div class="form-group col-md-11 pb-3"> <label for="id_email">メールアドレス</label> {{ form.email }} </div> <div class="form-group col-md-11"> {% csrf_token %} <button type="submit" class="btn btn-block btn-outline-primary">送信</button> </div> </div> </form> </div> </div><!-- end card-body--> </div><!-- end card--> </div> </div><!-- end row --> </div><!-- end container-fluid--> {% endblock %}
「password_reset_form.html」は、パスワードリセット(リマインダ)メール送信画面を定義します。
{% extends "base.html" %} {% block content %} <div class="container-fluid" style="margin-top: 60px;"> <div class="row"> <div class="col-md-6 offset-md-3"> <div class="card"> <div class="card-header">パスワードリセットページ</div> <div class="card-body"> <div class="card-block"> <p class="card-text"> メールアドレスにパスワード初期化ページのURLを送付しました。 </p> </div> </div><!-- end card-body--> </div><!-- end card--> </div> </div><!-- end row --> </div><!-- end container-fluid--> {% endblock %}
「password_reset_done.html」は、パスワードリセット(リマインダ)メール送信完了画面を定義します。
{% extends 'base.html' %} {% block content %} <div class="container-fluid" style="margin-top: 60px;"> <div class="row"> <div class="col-md-6 offset-md-3"> <div class="card"> <div class="card-header">パスワードリセットページ</div> <div class="card-body"> <div class="card-block"> <p class="card-text"> 新パスワードを入力してください。 </p> <form class="my-form" action="" method="POST"> {% csrf_token %} <div class="row"> <div class="form-group col-md-11"> <label for="id_new_password1">新しいパスワード</label> {{ form.new_password1 }} {{ form.new_password1.errors }} </div> <div class="form-group col-md-11 pb-3"> <label for="id_new_password2">新しいパスワード(確認)</label> {{ form.new_password2 }} {{ form.new_password2.errors }} </div> <div class="form-group col-md-11"> <input type='hidden' name='next' value='{{ next }}' /> <button type="submit" class="btn btn-block btn-outline-primary">送信</button> </div> </div> </form> </div> </div><!-- end card-body--> </div><!-- end card--> </div> </div><!-- end row --> </div><!-- end container-fluid--> {% endblock %}
「password_reset_confirm.html」は、パスワード変更画面を定義します。
{% extends 'base.html' %} {% block content %} <div class="container-fluid" style="margin-top: 60px;"> <div class="row"> <div class="col-md-6 offset-md-3"> <div class="card"> <div class="card-header">パスワードリセットページ</div> <div class="card-body p-3"> <p class="card-text">パスワードをリセットしました。</p> </div><!-- end card-body--> </div><!-- end card--> </div><!-- end col-md-6 offset-md-3 --> </div><!-- end row --> </div><!-- end container-fluid--> {% endblock %}
「password_reset_complete.html」は、パスワード変更完了画面を定義します。
- Python 114
- 制作 54
- RaspberryPi 41
- Django 40
- WordPress 40
- Linux 27
- VPS 22
- JavaScript 21
- PHP 20
- HTML・CSS 19
- AWS 16
- 仮想環境 15
- レスポンシブデザイン 13
- マイコン 11
- WEB全般 11
- 動画製作 9
- Webサービス 8
- 統合開発環境 8
- 機械学習 8
- PyCharm 7
- jQuery 7
- AfterEffects 7
- 起業・設立 7
- Django REST framework 6
- C# 6
- デザイン 6
- SEO 6
- pydata 6
- Visual Studio 5
- 数学 5
- 携帯サイト 5
- heroku 5
- Mac 5
- illustrator 5
- node.js 5
- Anaconda 5
- Nginx 4
- Jupyter Notebook 4
- インフラ 4
- Google Colaboratory 4
- symfony 4
- Webスクレイピング 3
- photoshop 3
- Go言語 3
- PC 3
- ツール 3
- Docker 3
- facebook 3
- 作業効率化 3
- データベース 3
- Cloud9 3
- コマンド 2
- micro:bit 2
- Kali Linux 2
- Webサーバー 2
- MariaDB 2
- ドローン 2
- コンテナ 2
- DaVinci Resolve 2
- ネットワーク 2
- Java 2
- movie 2
- PCDJ 2
- 音楽 2
- XSERVER 2
- Ansible 1
- Vue.js 1
- JSON 1
- Bootstrap 1
- バージョン管理システム 1
- SSL 1
- S3 1
- ムームードメイン 1
- ネットワーク 1
- アニメーション 1
- D3.js 1
- Rhino 1
- アニメ 1
- git 1
- windows 1
- アクセス解析 1
- スマートフォン 1
- アフィリエイトノウハウ 1
- 知識 1
- TypeScript 1
- 役立つ本・書籍 1
- データサイエンス 1
- ESP32 1
- AI 1
- ownCloud 1
- API 1