В этом уроке по Django 4.1 мы поговорим о создании формы восстановления пароля с запросом токена на ваш зарегистрированный email.
Руководство по настройке email в Django из 26 урока
Создадим необходимые формы для ввода email и восстановления пароля:
modules/system/forms/authenticated.py
from django.contrib.auth.forms import PasswordResetForm
class UserForgotPasswordForm(PasswordResetForm):
"""
Запрос на восстановление пароля
"""
def __init__(self, *args, **kwargs):
"""
Обновление стилей формы
"""
super().__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].widget.attrs.update({
'class': 'form-control',
'autocomplete': 'off'
})
Пояснения:
- Наследуемся от PasswordResetForm
- Настраиваем Bootstrap стили
И создаем форму для установки пароля:
from django.contrib.auth.forms import SetPasswordForm
class UserSetNewPasswordForm(SetPasswordForm):
"""
Изменение пароля пользователя после подтверждения
"""
def __init__(self, *args, **kwargs):
"""
Обновление стилей формы
"""
super().__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].widget.attrs.update({
'class': 'form-control',
'autocomplete': 'off'
})
Пояснения:
- Ранее в уроке по изменению пароля мы уже наследовались от SetPasswordForm, но в этот раз это сделано для гостей!
Теперь создадим необходимые представления:
modules/system/views/authenticated.py
from modules.system.forms.authenticated import
UserForgotPasswordForm
from django.contrib.auth.views import PasswordResetView
from django.urls import reverse_lazy
from django.contrib.messages.views import SuccessMessageMixin
class UserForgotPasswordView(SuccessMessageMixin, PasswordResetView):
"""
Представление по сбросу пароля по почте
"""
form_class = UserForgotPasswordForm
template_name = 'modules/system/authenticated/password-reset.html'
success_url = reverse_lazy('home')
success_message = 'Письмо с инструкцией по восстановлению пароля отправлено на ваш email'
subject_template_name = 'modules/system/authenticated/email/password-subject-reset-mail.html'
email_template_name = 'modules/system/authenticated/email/password-reset-mail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = 'Сброс пароля на сайте'
return context
Пояснения:
- Это мы создали представление для ввода email адреса.
- Импортировали нашу форму UserForgotPasswordForm
- И наследуемся от PasswordResetView, встроенного представления в Django
- Указываем в email_template_name/subject_template_name шаблон на инструкцию
- template_name - наша форма для ввода email
- success_url - перенаправление после успеха
- success_message - уведомление от SuccessMessageMixin об успешной отправке
А теперь сразу создадим представление для установки пароля:
from django.contrib.auth.views import PasswordResetConfirmView
from modules.system.forms.authenticated import UserSetNewPasswordForm
from django.urls import reverse_lazy
from django.contrib.messages.views import SuccessMessageMixin
class UserPasswordResetConfirmView(SuccessMessageMixin, PasswordResetConfirmView):
"""
Представление установки нового пароля
"""
form_class = UserSetNewPasswordForm
template_name = 'modules/system/authenticated/password-set-new.html'
success_url = reverse_lazy('home')
success_message = 'Вы успешно восстановили пароль, теперь вы можете авторизоваться с новым паролем'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = 'Установить новый пароль'
return context
Пояснения:
- Наследуемся от PasswordResetConfirmView встроенного в Django
- UserSetNewPasswordForm используем нашу форму для установки нового пароля
А теперь создадим необходимые ссылки!
from django.urls import path
from modules.system.views import ProfileView, ProfileEditView, RegisterCreateView, UserLoginView, \
UserPasswordChangeView, UserForgotPasswordView, UserPasswordResetConfirmView
urlpatterns = [
path('user/edit/', ProfileEditView.as_view(), name='profile-edit'),
path('user/<str:slug>/', ProfileView.as_view(), name='profile'),
path('register/', RegisterCreateView.as_view(), name='register'),
path('login/', UserLoginView.as_view(), name='login'),
path('password-change/', UserPasswordChangeView.as_view(), name='password-change'),
path('password-reset/', UserForgotPasswordView.as_view(), name='password-reset'),
path('set-new-password/<uidb64>/<token>/', UserPasswordResetConfirmView.as_view(), name='password-reset-confirm'),
]
Пояснения:
- Добавил наши представления UserForgotPasswordView и UserPasswordResetConfirmView
- <uidb64>/<token> - это то, что придет на наш email. Необходимый токен для подтверждения пользователя для смены пароля.
А теперь создадим необходимые шаблоны как для форм, так и для email:
templates/modules/system/authenticated/password-reset.html
{% extends 'main.html' %}
{% block content %}
<div class="card mb-3 border-0 nth-shadow">
<div class="card-body">
<div class="card-title nth-card-title">
<h4>Восстановление пароля на сайте</h4>
</div>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-dark mt-2">Восстановить пароль</button>
</form>
</div>
</div>
{% endblock %}
Теперь для установки пароля:
templates/modules/system/authenticated/password-set-new.html
{% extends 'main.html' %}
{% block content %}
<div class="card mb-3 border-0 nth-shadow">
<div class="card-body">
<div class="card-title nth-card-title">
<h4>Установить новый пароль</h4>
</div>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-dark mt-2">Подтвердить</button>
</form>
</div>
</div>
{% endblock %}
Отлично. Шаблоны настроили, теперь шаблон для письма
В папочке templates/modules/system/authenticated/ я создам папку email, а внутри нее password-reset-mail.html
templates/modules/system/authenticated/email/password-reset-mail.html
{% autoescape off %}
Вы получили это электронное письмо, потому что запросили сброс пароля для своей учетной записи пользователя на {{ site_name }}.
Перейдите на следующую страницу и выберите новый пароль:
{% block reset_link %}
{{ protocol }}://{{ domain }}{% url 'password-reset-confirm' uidb64=uid token=token %}
{% endblock %}
Ваше имя пользователя, если вы забыли: {{ user.get_username }}
Спасибо за использование нашего сайта!
The {{ site_name }} team
{% endautoescape %}
И для темы письма:
templates/modules/system/authenticated/email/password-subject-reset-mail.html
Письмо о восстановлении пароля на сайте Notehunter
Но, нам нужно ещё кое-что сделать. Перейдем в backend/settings.py и добавим в INSTALLED_APPS - 'django.contrib.sites' и SITE_ID = 1
backend/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sites',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'mptt',
'modules.blog.apps.BlogConfig',
'modules.system.apps.SystemConfig',
]
SITE_ID = 1
И применим миграции в базу данных:
(venv) PS C:\Users\Razilator\Desktop\Courses\App\backend> python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, blog, contenttypes, sessions, sites, system
Running migrations:
Applying sites.0001_initial... OK
Applying sites.0002_alter_domain_unique... OK
(venv) PS C:\Users\Razilator\Desktop\Courses\App\backend>
А далее, идем в админку в новую категорию "Сайты" и изменяем сайт example.com

Сохраняем, без каких либо http, https://!
Перехожу по ссылке неавторизованным пользователем: http://127.0.0.1:8000/s/password-reset/

Нажимаю восстановить:

Захожу на свою почту и вижу письмо:


И перейдя по ссылке, устанавливаем пароль в форме установки пароля:

И получаем успех об установленном пароле

Отлично, у нас все получилось как нужно! Теперь пользователи смогут восстанавливать свои пароля посредством запроса на свой email адрес.