Кастомная модель пользователя в Django: почему её ставят сразу
Замена стандартной User в Django — операция, которую почти невозможно откатить после первой миграции. Разбираем два рабочих подхода, цену каждого и типичную ошибку с миграциями.

Стандартная django.contrib.auth.models.User живёт с логином, именем и почтой. Проект растёт, и в какой-то момент вам нужно логинить пользователя по email, а не по username, или хранить телефон как основной идентификатор. Проблема в том, что менять модель пользователя после первой миграции — операция, которую официальная документация прямо называет очень тяжёлой и не рекомендует. Ниже — два подхода, которые советует сам Django, и почему выбор нужно сделать в первый день, а не на третьем месяце.
Материал для Django 4.2 LTS и 5.x. Поведение AbstractUser и AbstractBaseUser в этих версиях стабильно, но перед копированием сверьтесь с разделом Customizing authentication в документации вашей версии.
Почему менять пользователя после старта — боль
Django привязывает модель пользователя к настройке AUTH_USER_MODEL. Все внешние ключи на пользователя — в вашем коде и в сторонних приложениях — резолвятся через неё. Первая же миграция фиксирует, на какую таблицу ссылаются ForeignKey и ManyToManyField. Поменять это потом означает переписать зависимости в чужих миграциях, что документация Django описывает как задачу, для которой нет простого пути.
Правило одно: если есть хоть малейший шанс, что стандартной модели не хватит, ставьте кастомную до первой миграции. Пустая кастомная модель, повторяющая стандартную, ничего не стоит — а переезд стоит дней работы.
Отсюда практический вывод: даже если сейчас вам хватает стандартной User, в новом проекте почти всегда стоит завести свою модель заранее. Это дешёвая страховка.
Два подхода, которые рекомендует Django
Django предлагает наследоваться от одного из двух базовых классов. Разница в том, сколько готового вы получаете и сколько придётся писать руками.
AbstractUser — расширить готовое
Класс AbstractUser — это вся стандартная модель без привязки к таблице: username, first_name, last_name, email, флаги is_staff и is_active, права и группы. Вы наследуетесь и добавляете свои поля. Всё, что работало со стандартным пользователем, продолжает работать: админка, формы, права.
Это выбор по умолчанию, когда вам нужно добавить поля, а не переделать саму схему аутентификации.
# myapp/models.py — Django 5.x
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
# добавляем свои поля поверх стандартных
phone = models.CharField(max_length=20, blank=True)
is_verified = models.BooleanField(default=False)
def __str__(self):
return self.get_username()
И одна строка в настройках проекта:
# settings.py
AUTH_USER_MODEL = "myapp.User"
AbstractBaseUser — собрать с нуля
Класс AbstractBaseUser даёт только пароль, его хеширование и токен последнего входа. Ни username, ни email, ни флагов прав — всё это вы описываете сами. Берут его, когда стандартная схема мешает: например, вы хотите логин строго по email и вообще без поля username, или у вас составной идентификатор.
Здесь придётся написать три вещи: сами поля, менеджер модели с методами create_user и create_superuser, и указать USERNAME_FIELD. Плюс, если нужны права и админка, подмешать PermissionsMixin.
# myapp/models.py — Django 5.x, логин по email
from django.contrib.auth.models import (
AbstractBaseUser, BaseUserManager, PermissionsMixin,
)
from django.db import models
class UserManager(BaseUserManager):
def create_user(self, email, password=None, **extra):
if not email:
raise ValueError("Email обязателен")
email = self.normalize_email(email)
user = self.model(email=email, **extra)
user.set_password(password) # хеширует пароль
user.save(using=self._db)
return user
def create_superuser(self, email, password=None, **extra):
extra.setdefault("is_staff", True)
extra.setdefault("is_superuser", True)
return self.create_user(email, password, **extra)
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = "email" # по этому полю идёт логин
REQUIRED_FIELDS = [] # спрашивается при createsuperuser
def __str__(self):
return self.email
Обратите внимание: USERNAME_FIELD не должен попадать в REQUIRED_FIELDS — Django и так спросит его при создании суперпользователя, дубликат вызовет ошибку.
Сравнение подходов
| Критерий | AbstractUser | AbstractBaseUser |
|---|---|---|
| Что даёт из коробки | username, email, имена, флаги, права | только пароль и его хеширование |
| Сколько кода писать | минимум: свои поля | поля, менеджер, USERNAME_FIELD |
| Логин по email без username | требует ручного отключения поля | естественный выбор |
| Совместимость с админкой | из коробки | своя UserAdmin |
| Риск ошибиться | низкий | выше: менеджер, права |
Когда что выбирать
- Нужны стандартная схема плюс пара своих полей (телефон, аватар, флаг верификации) — берите
AbstractUser. Не усложняйте. - Логин строго по email или другому идентификатору, username лишний —
AbstractBaseUser. Схема будет чистой, без мёртвого поля username. - Полей много и они про профиль, а не про аутентификацию — рассмотрите отдельную модель профиля с
OneToOneFieldна пользователя. Тогда саму модель пользователя можно не трогать вовсе.
Что ломается на практике
Три ошибки встречаются чаще всего.
- Кастомную модель поставили после первой миграции. Появляется ошибка вида
InconsistentMigrationHistoryили конфликты зависимостей вauth. Простого штатного пути миграции существующей базы на новую модель пользователя нет — при работающем проекте это ручная переработка миграций и данных. На новом проекте проще снести базу и мигрировать заново. - В коде жёстко импортируют стандартную
User. В моделях ссылайтесь строкой из настроек, а в остальном коде — черезget_user_model():from django.conf import settings from django.contrib.auth import get_user_model # в models.py — ссылка на пользователя author = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, ) # в остальном коде — получить класс модели User = get_user_model() - Для
AbstractBaseUserне сделали свою админку. СтандартнаяUserAdminждёт поля username и password в определённых формах и падает. Регистрируйте модель со своимModelAdmin, гдеfieldsetsи формы соответствуют вашим полям.
Что делать дальше
Заведите модель в первый день, даже пустую копию стандартной, и сразу пропишите AUTH_USER_MODEL. После этого настройте админку под свои поля и напишите тесты на create_user и create_superuser — на них ломается больше всего логики регистрации. Инвалидация сессий при смене пароля и работа с токенами восстановления — отдельная тема, здесь мы её не разворачивали. За деталями по каждому классу смотрите раздел Customizing authentication в документации вашей версии Django.
Создатель презентаций: Структура и текст каждого слайда: заголовки-утверждения, одна мысль на слайд, заметки докладчику.
Спросить за 150 ₽Источники: Django docs — Customizing authentication in Django, Django docs — Substituting a custom User model
Частые вопросы
А если проект уже в проде на стандартной User — можно ли перейти на кастомную?
У меня другая версия — Django 3.2. Код подойдёт?
Обязательно ли писать свой менеджер для AbstractUser?
Можно добавить поле в стандартную User без кастомной модели?
Почему нельзя класть USERNAME_FIELD в REQUIRED_FIELDS?
Не сработало: после смены модели миграции не применяются, ошибка в auth.
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.