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

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

Razilator

В этой статье мы рассмотрим создание формы для восстановления пароля и процесс отправки письма на указанный 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

Должно получиться следующим образом
Должно получиться следующим образом

В файл 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:

Меняем example.com
Меняем example.com

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

Сохраняем введенные данные
Сохраняем введенные данные

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

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

Жмем восстановить пароль после ввода email адреса
Жмем восстановить пароль после ввода email адреса
Письмо доставляется на наш email
Письмо доставляется на наш email
Получаем письмо на email
Получаем письмо на email
Вписываем новый пароль
Вписываем новый пароль
Подтверждаем пароль, можем авторизоваться с новым паролем
Подтверждаем пароль, можем авторизоваться с новым паролем

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

;