В прошлом уроке мы реализовали регистрацию пользователей через функцию представления. Это было сделано осознанно: важно понять, как именно Django создаёт пользователя, где происходит валидация и почему пароль нельзя сохранять напрямую.
Однако Django — это фреймворк, ориентированный на повторное использование готовых решений.
Для регистрации пользователей в нём уже существует специальный класс формы — UserCreationForm.
Задача этого урока:
- перейти от ручной реализации к более идиоматичному Django-подходу;
- разобраться, что именно делает
UserCreationForm; - переписать регистрацию, используя классовое представление (
CreateView); - сохранить возможность расширять форму под нужды проекта cinemahub.
UserCreationForm — это встроенная форма Django, которая:
- создаёт нового пользователя;
- автоматически добавляет поля
password1иpassword2; - проверяет совпадение паролей;
- использует
set_password()внутри себя.
Проще говоря, она решает 80% задач регистрации, которые мы писали вручную в прошлом уроке.
Но:
- она не знает ничего о нашем интерфейсе;
- не проверяет уникальность email;
- не учитывает стиль проекта.
Поэтому мы наследуемся от неё и дорабатываем под себя.
Начнём с формы.
Файл: users/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import get_user_model
User = get_user_model()Теперь создадим кастомную форму регистрации.
class RegisterUserForm(UserCreationForm):
username = forms.CharField(
label='Логин',
widget=forms.TextInput(attrs={'class': 'form-input'})
)
password1 = forms.CharField(
label='Пароль',
widget=forms.PasswordInput(attrs={'class': 'form-input'})
)
password2 = forms.CharField(
label='Повтор пароля',
widget=forms.PasswordInput(attrs={'class': 'form-input'})
)
class Meta:
model = User
fields = [
'username',
'email',
'first_name',
'last_name',
'password1',
'password2'
]
labels = {
'email': 'E-mail',
'first_name': 'Имя',
'last_name': 'Фамилия',
}
widgets = {
'email': forms.TextInput(attrs={'class': 'form-input'}),
'first_name': forms.TextInput(attrs={'class': 'form-input'}),
'last_name': forms.TextInput(attrs={'class': 'form-input'}),
}Благодаря UserCreationForm Django сам:
- проверяет, что пароли совпадают;
- проверяет минимальную сложность пароля;
- хэширует пароль;
- создаёт пользователя корректным способом.
Нам больше не нужен:
clean_password2;- ручной вызов
set_password().
Это ключевое отличие от прошлого урока.
По умолчанию Django:
- не требует уникальный email;
- не валидирует его уникальность.
Для проекта cinemahub это нежелательно, поэтому добавим проверку.
Файл: users/forms.py
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise forms.ValidationError(
"Пользователь с таким E-mail уже существует"
)
return emailТеперь форма стала сложнее, и нам важно:
- видеть ошибки валидации;
- отображать ошибки каждого поля отдельно.
Файл: users/templates/users/register.html
{% extends 'base.html' %}
{% block content %}
<h1>{{ title }}</h1>
<form method="post">
{% csrf_token %}
<div class="form-error">
{{ form.non_field_errors }}
</div>
{% for f in form %}
<p>
<label for="{{ f.id_for_label }}">{{ f.label }}:</label>
{{ f }}
</p>
<div class="form-error">{{ f.errors }}</div>
{% endfor %}
<p>
<button type="submit">Регистрация</button>
</p>
</form>
{% endblock %}Теперь мы можем отказаться от функции register() и перейти к классовому представлению.
Это логичный шаг:
- форма уже готова;
- Django умеет работать с ней автоматически.
Файл: users/views.py
from django.urls import reverse_lazy
from django.views.generic.edit import CreateView
from .forms import RegisterUserFormclass RegisterUser(CreateView):
form_class = RegisterUserForm
template_name = 'users/register.html'
success_url = reverse_lazy('users:login')
extra_context = {'title': 'Регистрация'}CreateView:
- отображает форму;
- обрабатывает
POST; - сохраняет объект в базу;
- перенаправляет при успехе.
В нашем случае объект — пользователь.
success_url = reverse_lazy('users:login')После успешной регистрации пользователь:
- попадает на страницу входа;
- может сразу авторизоваться.
extra_context = {'title': 'Регистрация'}Позволяет передавать данные в шаблон без переопределения методов.
Обновим маршруты.
Файл: users/urls.py
from django.urls import path
from .views import RegisterUser
app_name = 'users'
urlpatterns = [
path('register/', RegisterUser.as_view(), name='register'),
]-
Перейдите на страницу
/users/register/ -
Попробуйте:
- несовпадающие пароли;
- слабый пароль;
- повторяющийся email;
-
Убедитесь, что:
- ошибки отображаются корректно;
- пользователь создаётся;
- пароль хранится в хэшированном виде;
- вход работает.
Ошибка: пароли не совпадают, но форма «валидна»
Причина: используется ModelForm, а не UserCreationForm
Ошибка: пользователь создаётся, но не может войти
Причина: попытка переопределить save() без вызова super()
Ошибка: email дублируется
Причина: отсутствует clean_email()
Сделайте поле email обязательным для заполнения.
Измените перенаправление после регистрации на главную страницу со списком фильмов.
Добавьте в шаблон подсказку о требованиях к паролю.
email = forms.EmailField(
label='E-mail',
required=True,
widget=forms.TextInput(attrs={'class': 'form-input'})
)success_url = reverse_lazy('movies:movie_list')<p class="help-text">
Пароль должен содержать минимум 8 символов
</p>- Чем
UserCreationFormотличается отModelForm? - Где происходит хэширование пароля?
- Почему не нужно вручную проверять совпадение паролей?
- Зачем использовать
reverse_lazy, а неreverse? - Что делает
CreateViewавтоматически? - Где лучше валидировать email?
- Какие ошибки обрабатываются на уровне формы?
- В каком месте проще изменить внешний вид формы?