Доработки системы авторизации и регистрации, добавление сессий

⏱ 30 мин · 🧩 заданий: 6

Регистрация

Встроенной view регистрации в Django нет, но есть форма UserCreationForm (имя, пароль и его повтор с проверкой валидаторами). Расширим её полем email:

# account/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User


class UserRegistrationForm(UserCreationForm):
    email = forms.EmailField(required=True)

    class Meta(UserCreationForm.Meta):
        model = User
        fields = ["username", "first_name", "email"]

    def clean_email(self):
        email = self.cleaned_data["email"].lower()
        if User.objects.filter(email__iexact=email).exists():
            raise forms.ValidationError("Пользователь с таким email уже зарегистрирован.")
        return email

View:

# account/views.py
from django.contrib import messages
from django.contrib.auth import login
from django.shortcuts import redirect, render

from .forms import UserRegistrationForm


def register(request):
    if request.method == "POST":
        form = UserRegistrationForm(request.POST)
        if form.is_valid():
            user = form.save()
            login(request, user)
            messages.success(request, "Добро пожаловать! Регистрация прошла успешно.")
            return redirect("dashboard")
    else:
        form = UserRegistrationForm()
    return render(request, "account/register.html", {"form": form})

path("register/", views.register, name="register").

Регистрация на этом учебном сайте работает именно так.

Вход по email

По умолчанию вход — по username. Чтобы разрешить вход по email, напишем бэкенд аутентификации:

# account/authentication.py
from django.contrib.auth.models import User


class EmailAuthBackend:
    def authenticate(self, request, username=None, password=None):
        try:
            user = User.objects.get(email__iexact=username)
        except (User.DoesNotExist, User.MultipleObjectsReturned):
            return None
        if user.check_password(password) and user.is_active:
            return user
        return None

    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None
# settings.py
AUTHENTICATION_BACKENDS = [
    "django.contrib.auth.backends.ModelBackend",
    "account.authentication.EmailAuthBackend",
]

Django перебирает бэкенды по очереди, пока один из них не вернёт пользователя. Теперь в поле «Имя пользователя» можно ввести и логин, и email.

Сообщения

Фреймворк messages показывает одноразовые уведомления («Профиль сохранён»):

messages.success(request, "Профиль обновлён")
messages.error(request, "Не удалось сохранить")
messages.warning(request, "...")
messages.info(request, "...")

В base.html:

{% if messages %}
  <ul class="messages">
    {% for message in messages %}
      <li class="{{ message.tags }}">{{ message }}</li>
    {% endfor %}
  </ul>
{% endif %}

Сообщение показывается один раз — после вывода оно удаляется. Сообщения хранятся в сессии или cookie, поэтому переживают редирект.

Сессии

HTTP не хранит состояние: каждый запрос — сам по себе. Как же сайт «помнит», что вы вошли? С помощью сессий.

  1. При первом обращении Django создаёт сессию и отправляет браузеру cookie sessionid со случайным ключом.
  2. Браузер присылает эту cookie с каждым запросом.
  3. По ключу Django находит данные сессии (по умолчанию — в таблице django_session).

login() записывает в сессию id пользователя — так request.user узнаёт вас на следующих страницах.

Работа с сессией

request.session ведёт себя как словарь:

request.session["theme"] = "dark"            # сохранить
theme = request.session.get("theme", "light")  # прочитать
del request.session["theme"]                  # удалить
"theme" in request.session                    # проверить
request.session.flush()                       # очистить сессию полностью

Пример — последние просмотренные посты:

def post_detail(request, ...):
    ...
    viewed = request.session.get("viewed_posts", [])
    if post.id not in viewed:
        viewed = [post.id] + viewed[:4]
        request.session["viewed_posts"] = viewed

Изменение вложенных объектов

Django сохраняет сессию, когда вы присваиваете ключ. Если вы меняете список или словарь «на месте» (request.session["cart"].append(5)), установите request.session.modified = True.

Настройки сессий

Настройка Значение
SESSION_ENGINE Где хранить: db (по умолчанию), cache, cached_db, file, signed_cookies
SESSION_COOKIE_AGE Время жизни в секундах (по умолчанию 2 недели)
SESSION_EXPIRE_AT_BROWSER_CLOSE Завершать при закрытии браузера
SESSION_COOKIE_SECURE Отправлять cookie только по HTTPS (включите в продакшене)

Функция «Запомнить меня»:

if not form.cleaned_data.get("remember_me"):
    request.session.set_expiry(0)      # до закрытия браузера

Храните в сессии только небольшие данные, которые можно сериализовать в JSON (числа, строки, списки, словари), — не объекты моделей.

Итог

  • Регистрация: UserCreationForm + своя view + login() после сохранения.
  • Свой бэкенд аутентификации позволяет входить по email.
  • messages — одноразовые уведомления, переживающие редирект.
  • Сессия — словарь request.session, привязанный к посетителю через cookie.

Практика решено 0 из 6

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Написать код

Создайте форму регистрации UserRegistrationForm на основе UserCreationForm, добавив в поля модели User поле email (в Meta унаследуйте UserCreationForm.Meta).

Задание 2 Написать код

Напишите view register: на POST проверяет форму UserRegistrationForm, сохраняет пользователя, сразу выполняет вход (login(request, user)) и перенаправляет на dashboard. На GET — пустая форма. Шаблон — account/register.html.

Задание 3 Один вариант ответа

Где по умолчанию Django хранит данные сессии?

Задание 4 Написать код

Реализуйте счётчик просмотров страницы для текущего посетителя: возьмите из сессии значение views (по умолчанию 0), увеличьте на 1 и сохраните обратно в сессию.

Задание 5 Один вариант ответа

Вы изменили список в сессии: request.session["cart"].append(5). Почему изменение может не сохраниться?

Задание 6 Несколько вариантов

Какие уровни сообщений есть во фреймворке django.contrib.messages?

Войдите, чтобы отмечать пройденные уроки.