Регистрация с подтверждением по email в Django: пошагово
К концу инструкции у вас будет форма регистрации, которая создаёт неактивного пользователя, шлёт письмо с токеном на почту и активирует аккаунт по ссылке. Примерно час работы на Django 5.x.

В конце этой инструкции у вас будет рабочий процесс: пользователь заполняет форму, получает письмо со ссылкой, кликает — аккаунт активируется. Пока он не подтвердил почту, войти нельзя. Реализуем всё на встроенных инструментах Django 5.x без сторонних пакетов вроде django-allauth — так вы поймёте, что происходит под капотом, а не соберёте чёрный ящик. На работу уйдёт около часа, если проект уже создан.
Механизм подтверждения строится на двух вещах из стандартной библиотеки Django: генераторе одноразовых токенов (PasswordResetTokenGenerator) и кодировании id пользователя в URL-безопасную строку (urlsafe_base64_encode). Токен привязан к состоянию пользователя, поэтому после активации ссылка перестаёт работать сама по себе — отдельно хранить и чистить токены в базе не нужно.
Что нужно иметь до начала
Проверьте это до первого шага, чтобы не упереться в нехватку доступов на середине.
- Django 5.x и Python 3.10+. Проверьте:
python -m django --version. - Созданный проект и приложение. Ниже приложение называется
accounts. Если у вас другое имя — подставляйте своё во всех путях импорта. - Настроенная отправка почты. На время разработки хватит консольного бэкенда — письма будут печататься в терминал. Реальный SMTP подключим в конце.
- Стандартная модель
User. Инструкция работает с моделью по умолчанию черезget_user_model(). Если у вас кастомная модель — важно, чтобы в ней было полеis_active(в стандартной оно есть).
Одна оговорка: менять модель пользователя (свою AUTH_USER_MODEL) после первой миграции проекта — болезненно. Если планируете кастомную модель, делайте это до всего остального. Здесь эту тему не разворачиваем.
Шаг 1. Настройки почты и активной модели пользователя
Откройте settings.py и добавьте почтовый бэкенд для разработки:
# settings.py
# Письма печатаются в консоль вместо реальной отправки
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
DEFAULT_FROM_EMAIL = "no-reply@example.com"
Проверка: перезапустите runserver — ошибок при старте быть не должно.
Шаг 2. Генератор токена активации
Создайте файл accounts/tokens.py. Мы наследуемся от штатного генератора и добавляем в хеш поле is_active: как только пользователь станет активным, старая ссылка станет недействительной.
# accounts/tokens.py
from django.contrib.auth.tokens import PasswordResetTokenGenerator
class AccountActivationTokenGenerator(PasswordResetTokenGenerator):
def _make_hash_value(self, user, timestamp):
# Токен зависит от id, времени и статуса активации.
# После активации is_active меняется — токен перестаёт совпадать.
return f"{user.pk}{timestamp}{user.is_active}"
account_activation_token = AccountActivationTokenGenerator()
Проверка: файл импортируется без ошибок — python -c "import accounts.tokens" из корня проекта (при активном виртуальном окружении и настроенном DJANGO_SETTINGS_MODULE).
Шаг 3. Форма регистрации
В accounts/forms.py расширяем стандартную UserCreationForm, добавляя обязательное поле email — по умолчанию оно необязательное.
# accounts/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 SignUpForm(UserCreationForm):
email = forms.EmailField(required=True)
class Meta:
model = User
fields = ("username", "email", "password1", "password2")
def clean_email(self):
email = self.cleaned_data["email"]
if User.objects.filter(email__iexact=email).exists():
raise forms.ValidationError("Пользователь с такой почтой уже есть.")
return email
Шаг 4. Вьюхи: регистрация и активация
В accounts/views.py. Регистрация создаёт неактивного пользователя и шлёт письмо. Активация проверяет токен и включает аккаунт.
# accounts/views.py
from django.contrib.auth import get_user_model, login
from django.contrib.sites.shortcuts import get_current_site
from django.shortcuts import render, redirect
from django.template.loader import render_to_string
from django.utils.encoding import force_bytes, force_str
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from .forms import SignUpForm
from .tokens import account_activation_token
User = get_user_model()
def signup(request):
if request.method == "POST":
form = SignUpForm(request.POST)
if form.is_valid():
user = form.save(commit=False)
user.is_active = False # до подтверждения войти нельзя
user.save()
current_site = get_current_site(request)
subject = "Подтвердите регистрацию"
message = render_to_string("accounts/activation_email.html", {
"user": user,
"domain": current_site.domain,
"uid": urlsafe_base64_encode(force_bytes(user.pk)),
"token": account_activation_token.make_token(user),
"protocol": "https" if request.is_secure() else "http",
})
user.email_user(subject, message)
return render(request, "accounts/activation_sent.html")
else:
form = SignUpForm()
return render(request, "accounts/signup.html", {"form": form})
def activate(request, uidb64, token):
try:
uid = force_str(urlsafe_base64_decode(uidb64))
user = User.objects.get(pk=uid)
except (TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None
if user is not None and account_activation_token.check_token(user, token):
user.is_active = True
user.save()
login(request, user)
return render(request, "accounts/activation_ok.html")
return render(request, "accounts/activation_invalid.html")
Метод email_user есть у стандартной модели пользователя и берёт адрес из поля email. Если у вас кастомная модель без этого метода — используйте django.core.mail.send_mail с явным адресом.
Шаг 5. Маршруты
В accounts/urls.py. Обратите внимание на конвертеры пути — uidb64 и token идут как строки.
# accounts/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("signup/", views.signup, name="signup"),
path("activate/<uidb64>/<token>/", views.activate, name="activate"),
]
И подключите их в корневом urls.py через include("accounts.urls").
Шаг 6. Шаблоны
Минимальный набор в templates/accounts/. Ссылка в письме собирается по имени маршрута activate.
{# templates/accounts/activation_email.html #}
Здравствуйте, {{ user.username }}!
Подтвердите регистрацию по ссылке:
{{ protocol }}://{{ domain }}{% url 'activate' uidb64=uid token=token %}
Если вы не регистрировались — просто проигнорируйте письмо.
Файл signup.html выводит форму ({{ form.as_p }} и кнопку submit внутри <form method="post"> с {% csrf_token %}), а activation_sent.html, activation_ok.html и activation_invalid.html — короткие сообщения о статусе.
Проверка всего процесса:
- Откройте
/accounts/signup/, заполните форму, отправьте. - В терминале, где запущен
runserver, появится тело письма со ссылкой активации. - Скопируйте ссылку в браузер — увидите страницу успеха, пользователь залогинен.
- Откройте ту же ссылку повторно — теперь она недействительна, потому что
is_activeужеTrue.
Главная идея: неактивный пользователь в базе — это ещё не рабочий аккаунт. Django не даст ему войти, покаis_activeравноFalse, и это ваш встроенный барьер против регистрации на чужую почту.
Если не получилось
- NoReverseMatch: Reverse for 'activate' not found. Маршрут
activateне подключён или имя не совпадает. Проверьтеname="activate"иincludeв корневомurls.py. - Письмо не приходит, а в консоли пусто. Не задан
EMAIL_BACKENDилиrunserverне перезапущен после правки настроек. Также убедитесь, что дошли доform.is_valid()— при ошибке формы письмо не шлётся. - Ссылка сразу показывает «недействительна». Частая причина —
_make_hash_valueиспользует изменяемое поле, которое поменялось между отправкой и кликом. В нашем варианте меняется толькоis_active, так что до активации ссылка живёт корректно. - TypeError при декодировании uid. Проверьте, что в шаблоне письма переданы именно
uidb64=uidиtoken=token, а в маршруте те же имена параметров. - Пользователь входит, хотя не подтвердил почту. Значит
user.is_active = Falseне сохранился доuser.save(). Проверьте порядок:save(commit=False), затем присвоение, затемsave().
Что настроить дальше
Для продакшена замените консольный бэкенд на SMTP: задайте EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" и параметры EMAIL_HOST, EMAIL_PORT, EMAIL_HOST_USER, EMAIL_USE_TLS. Учётные данные держите в переменных окружения, не в коде.
Дальше стоит добавить: срок жизни ссылки (штатный генератор ограничивает его настройкой PASSWORD_RESET_TIMEOUT — по умолчанию три дня), повторную отправку письма для тех, кто не успел, и защиту формы регистрации от ботов. Отправку письма при заметной нагрузке имеет смысл вынести в фоновую задачу (Celery или аналог), чтобы запрос не ждал SMTP — но это отдельная тема.
Дефектный акт по фото: Осмотр по фотографиям: классификация дефектов, ссылки на нормы, требования к устранению. Готовый акт в Word.
Спросить за 80 ₽Источники: Django docs: Cryptographic signing и token generators, Django docs: Sending email
Частые вопросы
У меня Django 4.2, а не 5.x — код подойдёт?
Ссылка активации сразу показывает «недействительна» — почему?
Нужно ли хранить токены активации в базе и чистить их?
Что подводного всплывёт в проде?
Можно ли использовать django-allauth вместо ручной реализации?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.