Регистрация пользователей через функции представления

Курс по Django: https://stepik.org/a/183363

Архив проекта: 64_sitewomen.zip

Продолжаем тему авторизации и регистрации пользователей. Процесс авторизации мы с вами в целом уже рассмотрели, пришло время познакомиться с регистрацией пользователей на сайте. У нас с вами уже есть ссылка «Регистрация» на панели главного меню, давайте создадим для нее маршрут, функцию представления и шаблон по аналогии с тем, как мы это делали для задачи авторизации.

Вначале опишем шаблон формы регистрации в файле users/register.html следующим образом:

{% extends 'base.html' %}
 
{% block content %}
<h1>Регистрация</h1>
 
<form method="post">
    {% csrf_token %}
<input type="hidden" name="next" value="{{ next }}" />
    {{ form.as_p }}
    <p ><button type="submit">Регистрация</button></p>
</form>
 
{% endblock %}

Здесь совершенно ничего нового для вас нет, мы это уже делали, когда говорили об авторизации. Далее, объявим класс формы регистрации в файле users/forms.py:

class RegisterUserForm(forms.ModelForm):
    username = forms.CharField(label="Логин")
    password = forms.CharField(label="Пароль", widget=forms.PasswordInput)
    password2 = forms.CharField(label="Повтор пароля", widget=forms.PasswordInput)
 
    class Meta:
        model = get_user_model()
        fields = ['username', 'email', 'first_name', 'last_name', 'password', 'password2']
        labels = {
            'email': 'E-mail',
            'first_name': 'Имя',
            'last_name': 'Фамилия',
        }

У вас может возникнуть вопрос, откуда мы знаем, какие поля следует прописывать в форме регистрации? Очень просто. Во-первых, есть документация:

https://docs.djangoproject.com/en/4.2/topics/auth/default/

где подробно описываются стандартные поля модели User.

Итак, форма у нас определена. Теперь можно объявить функцию представления в файле users/views.py следующим образом:

def register(request):
    form = RegisterUserForm()
    return render(request, 'users/register.html', {'form': form})

И свяжем ее с маршрутом (в файле users/urls.py):

urlpatterns = [
    path('login/', views.LoginUser.as_view(), name='login'),
    path('logout/', LogoutView.as_view(), name='logout'),
    path('register/', views.register, name='register'),
]

Осталось прописать этот маршрут для ссылки «Регистрация». Откроем файл base.html и внесем следующие изменения:

<li class="last"><a href="{% url 'users:login' %}">Войти</a> | <a href="{% url 'users:register' %}">Регистрация</a></li>

Проверим, что получилось. Запустим веб-сервер и перейдем по ссылке «Регистрация». Видим отображение формы регистрации.

Проверка данных формы

Я сразу же в класс формы внесу одно дополнение – метод clean_password2(), который будет проверять равенство введенных паролей. Напомню, что все методы формы, которые начинаются с префикса clean_ и продолжаются именем поля, автоматически вызываются при проверке корректности переданных данных. Метод реализуем следующим образом:

    def clean_password2(self):
        cd = self.cleaned_data
        if cd['password'] != cd['password2']:
            raise forms.ValidationError("Пароли не совпадают!")
        return cd['password2']

И, сразу же, пропишу еще одну проверку на уникальность введенного E-mail адреса. В таблице user это поле не помечено, как уникальное, поэтому разные пользователи могут вводить один и тот же E-mail. Дополнительной проверкой мы это исключим:

    def clean_email(self):
        email = self.cleaned_data['email']
        if User.objects.filter(email=email).exists():
            raise forms.ValidationError("Такой E-mail уже существует!")
        return email

Все, теперь при попытке ввести повторяющийся E-mail адрес в форме будет отображаться ошибка.

Реализация процесса регистрации пользователя

Теперь воспользуемся этой формой и реализуем функционал для регистрации новых пользователей. Перейдем в файл users/views.py и перепишем функцию register() следующим образом:

def register(request):
    if request.method == "POST":
        form = RegisterUserForm(request.POST)
        if form.is_valid():
            user = form.save(commit=False)  # создание объекта без сохранения в БД
            user.set_password(form.cleaned_data['password'])
            user.save()
            return render(request, 'users/register_done.html')
    else:
        form = RegisterUserForm()
    return render(request, 'users/register.html', {'form': form})

В целом, здесь все похоже на то, как мы делали при авторизации. Добавилось только формирование объекта пользователя с помощью метода save() формы. Причем, с параметром commit=False, который запрещает запись данных непосредственно в БД. Мы это делаем позже, после установки пароля с помощью метода set_password(). Зачем понадобилось использовать этот метод? Дело в том, что пароли в таблице БД хранятся в зашифрованном виде. Там хранится только их хэш – закодированная определенным образом последовательность символов. Раскодировать хэш невозможно: алгоритмы хэширования работают в одну сторону – кодирования. Обратной операции они не подразумевают. Благодаря этому злоумышленник, которому удается скачать БД не сможет получить доступ к аккаунтам пользователей, так как у него не будет паролей. Но тогда как сам фреймворк Django сможет сравнить переданный ему от пользователя пароль, если в таблице хранится только его хэш? Нет ничего проще! Django снова по тому же алгоритму захэширует переданный пароль и будет сравнивать их хэши. Если хэши совпадут, то делается вывод о корректности переданного пароля.  Мало того, результат работы алгоритма хэширования уникальный для каждого проекта из-за секретного ключа SECRET_KEY, который прописан в файле settings.py. Этот ключ используется для внесения дополнительных искажений в результирующий хэш. Поэтому злоумышленнику, по хорошему, еще нужно знать и эту дополнительную информацию.

Конечно, при большом желании взламываются многие системы. Есть приемы преодоления и этих преград защиты. Но сделать это будет весьма непросто. Поэтому для большинства рядовых сайтов, включая и интернет-магазины, такого механизма хранения паролей вполне достаточно.

Итак, возвращаясь к нашей функции представления, нам нужно определить еще один шаблон с именем register_done.html:

{% extends 'base.html' %}
 
{% block content %}
<h1>Добро пожаловать!</h1>
 
<p >Вы успешно зарегистрировались на сайте! Для входа в систему необходимо авторизоваться по этой <a href="{% url 'users:login' %}">ссылке</a>
</p>
{% endblock %}

Все, регистрация пользователя в самом простом варианте реализована на сайте.

Курс по Django: https://stepik.org/a/183363

Видео по теме