Django База [2023]: Форма восстановления пароля #23

Django Django База [2023]: Форма восстановления пароля #23


В этой статье мы рассмотрим создание формы для восстановления пароля и процесс отправки письма на указанный email во время восстановления пароля в Django.

В предыдущей статье мы рассматривали, как работать с SMTP-сервером для добавления возможности отправки писем с сайта.

Создание формы для запроса на восстановление пароля и установки нового пароля

В файл forms.py нашего приложения system в самый низ добавим следующий фрагмент кода:

system/forms.py
from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm


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'
            })


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'
            })

В этих формах мы просто настраиваем стиль под Bootstrap, потому что в системе аутентификации Django нас все устраивает. Наследуемся от PasswordResetForm и SetPasswordForm.

В одном из уроков мы уже использовали SetPasswordForm, но в нашем случае мы будем использовать наследование для формы, которая используется неавторизованными пользователями сайта.

Создаем необходимые представления для работы форм

Во views.py нашего приложения system в самый низ добавим следующие представления:

system/views.py
from django.contrib.auth.views import PasswordResetView, PasswordResetConfirmView
from django.urls import reverse_lazy
from django.contrib.messages.views import SuccessMessageMixin

from .forms import UserForgotPasswordForm, UserSetNewPasswordForm


class UserForgotPasswordView(SuccessMessageMixin, PasswordResetView):
    """
    Представление по сбросу пароля по почте
    """
    form_class = UserForgotPasswordForm
    template_name = 'system/user_password_reset.html'
    success_url = reverse_lazy('home')
    success_message = 'Письмо с инструкцией по восстановлению пароля отправлена на ваш email'
    subject_template_name = 'system/email/password_subject_reset_mail.txt'
    email_template_name = 'system/email/password_reset_mail.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = 'Запрос на восстановление пароля'
        return context


class UserPasswordResetConfirmView(SuccessMessageMixin, PasswordResetConfirmView):
    """
    Представление установки нового пароля
    """
    form_class = UserSetNewPasswordForm
    template_name = 'system/user_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

В первом представлении UserForgotPasswordView:

  • Функционал для запроса пароля, где мы вводим свой email адрес.
  • Используем форму UserForgotPasswordForm.
  • Наследуемся от PasswordResetView представления, встроенного в Django.
  • Указываем соответствующие шаблоны как для формы, так и для писем.
  • Подмешиваем миксин SuccessMessageMixin для оповещения пользователя об успешной отправке письма с инструкцией по восстановлению пароля.

Во втором представлении UserPasswordResetConfirmView:

  • Функционал для ввода нового пароля.
  • Используем форму UserSetNewPasswordForm.
  • Наследуемся от PasswordResetConfirmView представления, встроенного в Django.
  • Указываем соответствующие шаблоны для формы.
  • Подмешиваем миксин SuccessMessageMixin для оповещения пользователя об успешной смене пароля.

Обработка в urls.py созданных представлений

В файле urls.py нашего приложения system дополним ссылки новыми строками:

system/urls.py
from django.urls import path

from .views import ProfileUpdateView, ProfileDetailView, UserRegisterView, UserLoginView, UserPasswordChangeView, UserForgotPasswordView, UserPasswordResetConfirmView

urlpatterns = [
    path('user/edit/', ProfileUpdateView.as_view(), name='profile_edit'),
    path('user/<str:slug>/', ProfileDetailView.as_view(), name='profile_detail'),
    path('register/', UserRegisterView.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'),
]

В примере выше, мы добавили созданные представления.

В url для представления UserPasswordResetConfirmView: <uidb64>/<token> - это необходимый токен для подтверждения пользователя для смены пароля, который придет на email адрес.

Создаем шаблоны для форм и писем

В templates/system я создам файлы: user_password_reset.html и user_password_set_new.html, а также папку email, а внутри папки email два файла: password_reset_mail.html и password_subject_reset_mail.txt

Django База [2023]: Форма восстановления пароля #23
Должно получиться следующим образом

В файл user_password_reset.html добавим следующую разметку:

templates/system/user_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 %}

В файл user_password_set_new.html следующую разметку:

templates/system/user_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 %}

В файл email/password_reset_mail.html добавим следующую разметку:

templates/system/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 %}

И в файл email/password_subject_reset_mail.txt добавим текст темы письма:

templates/system/email/password_subject_reset_mail.txt
Инструкция по восстановлению пароля на сайте

Настройка settings.py для корректной работы отправки писем

Нам необходимо дополнить установленные приложения в Django INSTALLED_APPS, добавив django.contrib.sites и установить новое свойство SITE_ID:

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',
    'modules.blog.apps.BlogConfig',
    'modules.system.apps.SystemConfig',
    'mptt',
    'debug_toolbar',
]

SITE_ID = 1

Приминение миграций от django.contrib.sites

Также нам необходимо применить новые миграции, добавленные приложением django.contrib.sites с помощью команды py manage.py migrate.

Результат:

Терминал
(venv) PS C:\Users\Razilator\Desktop\Base\backend> py 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\Base\backend> 

Добавление адреса в админ-панеле

Теперь нам необходимо указать правильный домен, в нашем случае это локальная машина. Переходим в админку, в раздел сайты, изменяем существующий example.com:

Django База [2023]: Форма восстановления пароля #23
Меняем example.com

Если вы используете доменное имя, то вписывайте его.

Django База [2023]: Форма восстановления пароля #23
Сохраняем введенные данные

Проверяем работу формы по восстановлению пароля

Переходим на страницу для запроса инструкции по восстановлению: http://127.0.0.1:8000/password-reset/

Django База [2023]: Форма восстановления пароля #23
Жмем восстановить пароль после ввода email адреса
Django База [2023]: Форма восстановления пароля #23
Письмо доставляется на наш email
Django База [2023]: Форма восстановления пароля #23
Получаем письмо на email
Django База [2023]: Форма восстановления пароля #23
Вписываем новый пароль
Django База [2023]: Форма восстановления пароля #23
Подтверждаем пароль, можем авторизоваться с новым паролем

Отлично, нам этом работа завершена. Надеюсь у вас все получилось!