Django-Admin.ru

Своя модель пользователя в Django (AUTH_USER_MODEL)

Своя модель пользователя в Django (custom user model): AbstractUser или AbstractBaseUser, вход по email, админка, createsuperuser и смена модели в проекте.

Редакция Django-Admin.ru
· 20 мин чтения

Содержание · 14 разделов
  1. Стандартная модель django.contrib.auth.models.User
  2. Какой способ выбрать
  3. Своя модель на основе AbstractUser
  4. Meta и удаление ненужных полей
  5. Вход по email без username
  6. Email без учёта регистра
  7. AbstractBaseUser и PermissionsMixin: модель с нуля
  8. Профиль пользователя через OneToOneField
  9. Proxy-модель пользователя
  10. get_user_model() и settings.AUTH_USER_MODEL
  11. Регистрация в админке: UserAdmin и формы
  12. Нужны ли свои UserCreationForm и UserChangeForm
  13. createsuperuser с кастомной моделью
  14. Смена модели пользователя в середине проекта
  15. Частые ошибки
  16. fields.E304: Reverse accessor ‘Group.user_set’ clashes
  17. AUTH_USER_MODEL refers to model ‘accounts.User’ that has not been installed
  18. ValueError: Dependency on app with no migrations: accounts
  19. Manager isn’t available; ‘auth.User’ has been swapped
  20. IntegrityError: UNIQUE constraint failed: profiles_profile.user_id
  21. Частые вопросы
  22. Нужна ли своя модель, если стандартной User хватает?
  23. Как сделать несколько типов пользователей, например ученика и учителя?
  24. Как сделать вход по номеру телефона?
  25. Можно ли сделать UUID первичным ключом пользователя?
  26. Как входить и по email, и по username?
  27. Итог

Своя модель пользователя (custom user model) в Django — это класс-наследник AbstractUser или AbstractBaseUser, который вы указываете в настройке AUTH_USER_MODEL вместо стандартной django.contrib.auth.models.User. Заводите её в начале проекта, до первой команды migrate: тогда добавить поле или перейти на вход по email можно обычной миграцией. В уже работающем проекте замена возможна, но требует ручной правки истории миграций.

# accounts/models.py
from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    pass
# config/settings.py
AUTH_USER_MODEL = "accounts.User"  # "<метка приложения>.<Модель>"

Ниже — как устроена стандартная модель User, какой из четырёх способов расширения выбрать, как сделать вход по email без username, зарегистрировать модель в админке, создать суперпользователя и перейти на свою модель в проекте с данными. Код проверен на Django 6.1, примеры админки и createsuperuser — также на 5.2 LTS. Общая картина аутентификации — в обзоре «Аутентификация в Django».

Стандартная модель django.contrib.auth.models.User

User собрана из трёх абстрактных классов. От того, какой из них вы возьмёте за основу, зависит набор полей в вашей модели.

ПолеТип и ограниченияОткуда берётся
passwordCharField(max_length=128), хеш пароляAbstractBaseUser
last_loginDateTimeField, может быть пустымAbstractBaseUser
is_superuserBooleanField, по умолчанию FalsePermissionsMixin
groups, user_permissionsManyToManyField на Group/PermissionPermissionsMixin
usernameCharField(max_length=150), уникальноеAbstractUser
first_name, last_nameCharField(max_length=150), необязательныеAbstractUser
emailEmailField(max_length=254), не уникаленAbstractUser
is_staffBooleanField, доступ в админкуAbstractUser
is_activeBooleanField, по умолчанию TrueAbstractUser
date_joinedDateTimeField, по умолчанию nowAbstractUser

Таблица в базе называется auth_user, первичный ключ — id типа AutoField (32-битное целое). Логином служит username (USERNAME_FIELD = "username"), команда createsuperuser дополнительно спрашивает email (REQUIRED_FIELDS = ["email"]). Пользователей создаёт менеджер UserManager с методами create_user() и create_superuser().

Иерархия выглядит так:

  • AbstractBaseUser — пароль и его хеширование, last_login, свойства is_authenticated и is_anonymous. Поле is_active здесь — просто атрибут класса со значением True.
  • PermissionsMixin — флаг is_superuser, группы, индивидуальные права и методы has_perm(), has_module_perms(), get_all_permissions().
  • AbstractUser = AbstractBaseUser + PermissionsMixin + поля username, имя, фамилия, email, is_staff, is_active, date_joined.
  • User — AbstractUser с пометкой swappable = "AUTH_USER_MODEL", поэтому его можно заменить своей моделью.

Какой способ выбрать

СпособЧто получаетеКогда подходитМожно в работающем проекте
AbstractUserвсе поля User плюс свои, можно убрать usernameновый проект; нужны дополнительные поля или вход по emailтолько через перенос (см. ниже)
AbstractBaseUser + PermissionsMixinтолько пароль и права, остальные поля описываете саминужен полностью свой набор полей: нет имени и фамилии, логин — телефонтолько через перенос
Профиль с OneToOneFieldотдельная таблица с данными пользователяпроект уже в работе; данные одного приложения; сторонний пакетда, обычная миграция
Proxy-модельсвои методы, менеджеры, сортировка без новых полейнужно другое поведение, а не данные: «сотрудники», «клиенты»да

Для нового проекта почти всегда выбирайте AbstractUser, даже если пока хватает стандартных полей: замена модели после первой миграции обходится дорого, а пустой наследник ничего не стоит. Создатели Django рекомендуют этот подход в документации. AbstractBaseUser берут, когда стандартные поля мешают и вы готовы дописать менеджер и админку. Профиль и proxy-модель AUTH_USER_MODEL не меняют и работают с любой моделью пользователя — стандартной или своей.

Своя модель на основе AbstractUser

Последовательность для нового проекта, где migrate ещё не запускали:

  1. Создайте отдельное приложение для пользователей: python manage.py startapp accounts. В активированном виртуальном окружении команда python одинакова на всех ОС; если вызываете интерпретатор напрямую, на Windows это py manage.py ..., на macOS и Linux — python3 manage.py ....

  2. Опишите модель:

    # accounts/models.py
    from django.contrib.auth.models import AbstractUser
    from django.db import models
    
    
    class User(AbstractUser):
        phone = models.CharField("Телефон", max_length=20, blank=True)
  3. Подключите приложение и укажите модель в settings.py:

    INSTALLED_APPS = [
        # ...стандартные приложения
        "accounts",
    ]
    
    AUTH_USER_MODEL = "accounts.User"
  4. Зарегистрируйте модель в админке (подробно — в разделе про админку ниже):

    # accounts/admin.py
    from django.contrib import admin
    from django.contrib.auth.admin import UserAdmin
    
    from .models import User
    
    
    @admin.register(User)
    class CustomUserAdmin(UserAdmin):
        fieldsets = UserAdmin.fieldsets + (("Дополнительно", {"fields": ["phone"]}),)
        add_fieldsets = UserAdmin.add_fieldsets + ((None, {"fields": ["phone"]}),)
        list_display = ["username", "email", "phone", "is_staff"]
  5. Создайте миграции и таблицы:

    python manage.py makemigrations accounts
    python manage.py migrate
    python manage.py createsuperuser
Migrations for 'accounts':
  accounts\migrations\0001_initial.py
    + Create model User

В базе появятся таблицы accounts_user, accounts_user_groups и accounts_user_user_permissions, а auth_user создаваться не будет. Модель пользователя должна создаваться в первой миграции своего приложения (0001_initial), иначе возникнут ошибки зависимостей.

Meta и удаление ненужных полей

Если модели нужен свой Meta (ограничения, индексы, сортировка), наследуйте его от AbstractUser.Meta. Иначе потеряются verbose_name «user» и «users», и makemigrations создаст лишнюю операцию Change Meta options on user:

class User(AbstractUser):
    phone = models.CharField("Телефон", max_length=20, blank=True)

    class Meta(AbstractUser.Meta):
        ordering = ["username"]

Ненужное унаследованное поле убирают присваиванием None: first_name = None, last_name = None. Так же удаляют username при входе по email.

Вход по email без username

Чтобы логином был email, нужны три изменения: убрать поле username, сделать email уникальным и указать его в USERNAME_FIELD, а также заменить менеджер. Стандартный UserManager ожидает аргумент username, поэтому без своего менеджера createsuperuser упадёт с ошибкой TypeError: ... missing 1 required positional argument: 'username'.

# accounts/managers.py
from django.contrib.auth.base_user import BaseUserManager


class UserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, email, password, **extra_fields):
        if not email:
            raise ValueError("Укажите email")
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email, password=None, **extra_fields):
        extra_fields.setdefault("is_staff", False)
        extra_fields.setdefault("is_superuser", False)
        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, email, password=None, **extra_fields):
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_superuser", True)
        if extra_fields.get("is_staff") is not True:
            raise ValueError("У суперпользователя должно быть is_staff=True")
        if extra_fields.get("is_superuser") is not True:
            raise ValueError("У суперпользователя должно быть is_superuser=True")
        return self._create_user(email, password, **extra_fields)
# accounts/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models

from .managers import UserManager


class User(AbstractUser):
    username = None
    email = models.EmailField("Email", unique=True)

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []

    objects = UserManager()

    def __str__(self):
        return self.email

Атрибуты модели, которые читает Django:

  • USERNAME_FIELD — имя уникального поля-логина. Неуникальное поле manage.py check не пропустит: auth.E003 'User.email' must be unique because it is named as the 'USERNAME_FIELD'.
  • REQUIRED_FIELDS — поля, которые createsuperuser спросит помимо логина и пароля. Сам USERNAME_FIELD сюда не добавляйте, иначе получите auth.E002.
  • EMAIL_FIELD — поле с адресом для сброса пароля, по умолчанию "email".
  • use_in_migrations = True — менеджер доступен в миграциях данных.

Стандартный ModelBackend, LoginView и форма входа админки после этого принимают email в поле логина. В authenticate() и Client.login() можно передать и username=, и email=:

>>> from django.contrib.auth import authenticate
>>> authenticate(username="admin@example.com", password="Adm1n-pass-42")
<User: admin@example.com>

Email без учёта регистра

normalize_email() приводит к нижнему регистру только домен: Anna@Example.COM превращается в Anna@example.com. Поиск при входе точный, поэтому пользователь, набравший anna@example.com, не войдёт, а в базе могут оказаться два аккаунта с адресами, отличающимися регистром. Закройте обе проблемы в менеджере и в модели:

# accounts/managers.py — дополнения к UserManager
class UserManager(BaseUserManager):
    use_in_migrations = True

    @classmethod
    def normalize_email(cls, email):
        # весь адрес в нижнем регистре
        return (email or "").strip().lower()

    def get_by_natural_key(self, username):
        # вход без учёта регистра
        return self.get(**{f"{self.model.USERNAME_FIELD}__iexact": username})

    # _create_user, create_user, create_superuser — как выше
# accounts/models.py
from django.db.models.functions import Lower


class User(AbstractUser):
    # ...поля как выше

    class Meta(AbstractUser.Meta):
        constraints = [
            models.UniqueConstraint(
                Lower("email"),
                name="user_email_ci_unique",
                violation_error_message="Пользователь с таким email уже есть.",
            )
        ]

AbstractUser.clean() вызывает normalize_email() менеджера, поэтому формы и админка тоже сохраняют адрес в нижнем регистре. Ограничение в базе не даст создать дубликат в обход форм:

>>> User.objects.create_user("  Anna@Example.COM ", password="S3cret-pass-77").email
'anna@example.com'
>>> authenticate(username="ANNA@example.com", password="S3cret-pass-77")
<User: anna@example.com>
>>> User.objects.create(email="ANNA@example.com")
Traceback (most recent call last):
  ...
django.db.utils.IntegrityError: UNIQUE constraint failed: index 'user_email_ci_unique'

Для асинхронного входа (aauthenticate()) переопределите так же aget_by_natural_key().

AbstractBaseUser и PermissionsMixin: модель с нуля

AbstractBaseUser даёт только пароль и last_login. Всё остальное — поля, менеджер, методы для админки — описываете сами. PermissionsMixin добавляет is_superuser, группы и права: без него админке понадобятся собственные методы has_perm() и has_module_perms().

Пример: логин — email, вместо имени и фамилии одно обязательное поле full_name.

# accounts/models.py
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.contrib.auth.models import PermissionsMixin
from django.db import models
from django.utils import timezone


class UserManager(BaseUserManager):
    use_in_migrations = True

    def create_user(self, email, full_name, password=None, **extra_fields):
        if not email:
            raise ValueError("Укажите email")
        user = self.model(
            email=self.normalize_email(email), full_name=full_name, **extra_fields
        )
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, full_name, password=None, **extra_fields):
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_superuser", True)
        return self.create_user(email, full_name, password, **extra_fields)


class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField("Email", unique=True)
    full_name = models.CharField("Полное имя", max_length=150)
    is_active = models.BooleanField("Активен", default=True)
    is_staff = models.BooleanField("Доступ в админку", default=False)
    date_joined = models.DateTimeField("Дата регистрации", default=timezone.now)

    USERNAME_FIELD = "email"
    EMAIL_FIELD = "email"
    REQUIRED_FIELDS = ["full_name"]

    objects = UserManager()

    class Meta:
        verbose_name = "пользователь"
        verbose_name_plural = "пользователи"

    def __str__(self):
        return self.email

    def get_full_name(self):
        return self.full_name

    def get_short_name(self):
        return self.full_name.split()[0] if self.full_name else self.email

Что обязательно для работы с админкой и стандартными представлениями:

  • поле is_active — неактивного пользователя ModelBackend не пустит;
  • поле is_staff — без него вход в админку невозможен;
  • get_short_name() — его результат админка показывает в приветствии в шапке;
  • сигнатура create_user() и create_superuser() должна принимать логин, все поля из REQUIRED_FIELDS и password: так их вызывает createsuperuser.

Профиль пользователя через OneToOneField

Профиль — обычная модель со связью один к одному. Её можно добавить в любой момент, в том числе к стандартному User, без вмешательства в миграции auth. Расплата — отдельная таблица и лишний JOIN или запрос при чтении данных.

# profiles/models.py
from django.conf import settings
from django.db import models


class Profile(models.Model):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="profile",
    )
    city = models.CharField("Город", max_length=100, blank=True)
    avatar = models.URLField("Аватар", blank=True)

Профиль сам не появляется. Создавайте его сигналом post_save и подключайте сигналы в AppConfig.ready():

# profiles/signals.py
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver

from .models import Profile


@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)
# profiles/apps.py
from django.apps import AppConfig


class ProfilesConfig(AppConfig):
    name = "profiles"

    def ready(self):
        from . import signals  # noqa: F401
>>> user = User.objects.create_user("boris", password="S3cret-pass-77")
>>> user.profile
<Profile: Профиль boris>
>>> User.objects.select_related("profile").get(username="boris")  # один запрос

У пользователей, созданных до появления сигнала, профиля нет, и user.profile вызовет RelatedObjectDoesNotExist. Создайте недостающие профили миграцией данных или используйте Profile.objects.get_or_create(user=request.user) там, где профиль читается. Про связи между моделями подробнее — в статье о ForeignKey и OneToOneField.

Proxy-модель пользователя

Proxy-модель работает с той же таблицей, но может иметь свои менеджеры, методы, сортировку и отдельный раздел в админке. Новых полей у неё быть не может.

# accounts/models.py
class StaffManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(is_staff=True)


class Staff(User):
    objects = StaffManager()

    class Meta:
        proxy = True
        verbose_name = "сотрудник"
        verbose_name_plural = "сотрудники"
>>> list(Staff.objects.values_list("username", flat=True))
['admin']
>>> Staff._meta.db_table
'accounts_user'

Наследуйте proxy-модель от действующей модели пользователя. Proxy поверх django.contrib.auth.models.User перестанет работать, как только проект перейдёт на свою модель через AUTH_USER_MODEL.

get_user_model() и settings.AUTH_USER_MODEL

После замены модели from django.contrib.auth.models import User указывает на отключённый класс, и любой запрос через него падает с ошибкой Manager isn't available; 'auth.User' has been swapped for 'accounts.User'. Ссылайтесь на модель так:

ГдеЧто использовать
ForeignKey, OneToOneField, ManyToManyFieldsettings.AUTH_USER_MODEL
sender у сигналовsettings.AUTH_USER_MODEL
представления, формы, сервисы, тестыget_user_model()
код внутри приложения accountsпрямой импорт from .models import User
from django.conf import settings
from django.db import models


class Article(models.Model):
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="articles",
    )
>>> from django.conf import settings
>>> from django.contrib.auth import get_user_model
>>> settings.AUTH_USER_MODEL
'accounts.User'
>>> get_user_model()
<class 'accounts.models.User'>

settings.AUTH_USER_MODEL — строка, она безопасна при импорте models.py и не создаёт циклических импортов. Миграции с такими ссылками получают зависимость swappable_dependency(settings.AUTH_USER_MODEL) и работают с любой моделью пользователя. Если ваше приложение предназначено для повторного использования в других проектах, своей модели пользователя в нём быть не должно — только ссылки через settings.AUTH_USER_MODEL.

Регистрация в админке: UserAdmin и формы

Обычный ModelAdmin для модели пользователя не подходит: пароль отобразится как текстовое поле с хешем, а при создании пользователя не будет формы ввода пароля. Наследуйте django.contrib.auth.admin.UserAdmin — он подключает формы создания, изменения и смены пароля. Общие приёмы настройки — в статье о регистрации моделей в админке.

Для наследника AbstractUser с полем username достаточно дописать свои поля в fieldsets (страница изменения) и add_fieldsets (страница добавления), как в примере выше. Если поле не добавить в add_fieldsets, при создании пользователя его не будет на форме.

Для модели со входом по email стандартный UserAdmin ссылается на username и не проходит проверку:

<class 'django.contrib.auth.admin.UserAdmin'>: (admin.E033) The value of 'ordering[0]' refers to 'username', which is not a field of 'accounts.User'.

Переопределите все места, где упоминается username:

# accounts/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin

from .models import User


@admin.register(User)
class CustomUserAdmin(UserAdmin):
    ordering = ["email"]
    list_display = ["email", "first_name", "last_name", "is_staff"]
    search_fields = ["email", "first_name", "last_name"]
    fieldsets = (
        (None, {"fields": ("email", "password")}),
        ("Личные данные", {"fields": ("first_name", "last_name")}),
        ("Права", {"fields": ("is_active", "is_staff", "is_superuser", "groups", "user_permissions")}),
        ("Даты", {"fields": ("last_login", "date_joined")}),
    )
    add_fieldsets = (
        (None, {
            "classes": ("wide",),
            "fields": ("email", "usable_password", "password1", "password2"),
        }),
    )

Для модели на AbstractBaseUser из примера выше настройка та же, только вместо имени и фамилии — full_name. Стандартный list_filter (is_staff, is_superuser, is_active, groups) подходит, пока эти поля есть в модели. Поле usable_password (переключатель «вход по паролю включён») появилось в форме добавления в Django 5.1; в более старых версиях уберите его из add_fieldsets.

Нужны ли свои UserCreationForm и UserChangeForm

Внутри админки — нет. UserAdmin строит формы через modelform_factory() и подставляет вашу модель и поля из fieldsets, поэтому стандартные AdminUserCreationForm и UserChangeForm работают и с наследником AbstractUser, и с моделью на AbstractBaseUser. Добавление, изменение и смену пароля через админку мы проверили для всех трёх моделей из статьи.

Вне админки — да. UserCreationForm и UserChangeForm в Meta привязаны к auth.User, и форма регистрации на их основе без изменений упадёт с Manager isn't available; 'auth.User' has been swapped. Укажите свою модель:

# accounts/forms.py
from django.contrib.auth.forms import UserChangeForm, UserCreationForm

from .models import User


class SignUpForm(UserCreationForm):
    class Meta(UserCreationForm.Meta):
        model = User
        fields = UserCreationForm.Meta.fields + ("email", "phone")


class ProfileEditForm(UserChangeForm):
    password = None  # убрать ссылку на смену пароля

    class Meta(UserChangeForm.Meta):
        model = User
        fields = ("first_name", "last_name", "email", "phone")

Для модели со входом по email в SignUpForm задайте fields = ("email",). AuthenticationForm, PasswordChangeForm, SetPasswordForm работают с любой моделью на основе AbstractBaseUser, а PasswordResetForm требует поле с email и is_active. Как оформить регистрацию целиком — в статье о формах Django.

createsuperuser с кастомной моделью

Команда спрашивает поле из USERNAME_FIELD, затем каждое поле из REQUIRED_FIELDS (подпись берётся из verbose_name) и пароль. Для модели на AbstractBaseUser из примера выше:

Email: olga@example.com
Полное имя: Ольга Смирнова
Password:
Password (again):
Superuser created successfully.

В неинтерактивном режиме (Docker, CI) каждое обязательное поле передают аргументом или переменной окружения DJANGO_SUPERUSER_<ПОЛЕ>, пароль — только переменной DJANGO_SUPERUSER_PASSWORD:

# macOS / Linux
DJANGO_SUPERUSER_EMAIL=admin@example.com \
DJANGO_SUPERUSER_FULL_NAME="Иван Петров" \
DJANGO_SUPERUSER_PASSWORD=Adm1n-pass-42 \
python manage.py createsuperuser --noinput
# Windows PowerShell
$env:DJANGO_SUPERUSER_EMAIL = "admin@example.com"
$env:DJANGO_SUPERUSER_FULL_NAME = "Иван Петров"
$env:DJANGO_SUPERUSER_PASSWORD = "Adm1n-pass-42"
py manage.py createsuperuser --noinput

Если обязательное поле не передать, команда завершится ошибкой:

CommandError: You must use --full_name with --noinput.

Названия аргументов совпадают с именами полей (--email, --full_name), список виден в python manage.py createsuperuser --help. Остальные сценарии — смена пароля, создание в Docker, назначение администратора — разобраны в статье «Как создать суперпользователя в Django».

Смена модели пользователя в середине проекта

Если таблицы уже созданы со стандартным User, простое добавление AUTH_USER_MODEL не сработает:

django.db.migrations.exceptions.InconsistentMigrationHistory: Migration admin.0001_initial is applied before its dependency accounts.0001_initial on database 'default'.

Причина в том, что admin, ваши приложения и сторонние пакеты уже применили миграции, ссылающиеся на auth.User, а новая модель должна была появиться раньше них. Кроме внешних ключей, от модели пользователя зависят таблицы групп и прав, тип содержимого (ContentType) в журнале админки и строки прав вида auth.change_user. Автоматически makemigrations этот переход не сделает.

Сначала оцените, нужна ли замена. Если требуется только хранить дополнительные данные, профиль с OneToOneField решает задачу обычной миграцией. Если проект ещё в разработке и данные не нужны, проще удалить базу и миграции и начать заново с правильной моделью.

Когда пользователей нужно сохранить, работает перенос «на месте»: новая модель сначала полностью повторяет auth.User и использует ту же таблицу. Порядок, проверенный нами на Django 6.1 и SQLite:

  1. Сделайте резервную копию базы. Убедитесь, что все миграции применены (python manage.py showmigrations), а в коде и сторонних пакетах на пользователя ссылаются через settings.AUTH_USER_MODEL и get_user_model().

  2. Создайте приложение accounts и модель, идентичную стандартной, с таблицей auth_user:

    # accounts/models.py
    from django.contrib.auth.models import AbstractUser
    from django.db import models
    
    
    class User(AbstractUser):
        id = models.AutoField(primary_key=True)  # как у auth.User
    
        class Meta(AbstractUser.Meta):
            db_table = "auth_user"

    Модель называется User, поэтому промежуточные таблицы auth_user_groups и auth_user_user_permissions сохраняют имена. Тип id задан явно: с Django 6.0 новые модели по умолчанию получают BigAutoField, а в существующей таблице auth_user ключ 32-битный.

  3. Добавьте "accounts" в INSTALLED_APPS, пропишите AUTH_USER_MODEL = "accounts.User", зарегистрируйте модель в админке через UserAdmin.

  4. Выполните python manage.py makemigrations accounts. Запускать migrate пока нельзя — он остановится на InconsistentMigrationHistory.

  5. Отметьте новую миграцию примененной без выполнения и перенесите тип содержимого пользователя в новое приложение:

    # python manage.py shell
    from django.contrib.contenttypes.models import ContentType
    from django.db import connection
    from django.db.migrations.recorder import MigrationRecorder
    
    MigrationRecorder(connection).record_applied("accounts", "0001_initial")
    ContentType.objects.filter(app_label="auth", model="user").update(app_label="accounts")
  6. Проверьте результат: python manage.py migrate должен вывести No migrations to apply, а python manage.py makemigrations --check — No changes detected. Вход, группы, внешние ключи и записи журнала админки остаются на месте.

  7. Обновите проверки прав в коде и шаблонах: права на пользователя теперь называются accounts.change_user, а не auth.change_user.

После этого модель меняют обычными миграциями. Например, уберите db_table и добавьте поле — Django переименует таблицы:

Migrations for 'accounts':
  accounts\migrations\0002_user_phone_alter_user_table.py
    + Add field phone to user
    ~ Rename table for user to (default)

Шаг 5 выполняют один раз на каждой существующей базе: локальной, тестовом сервере, продакшене — в момент выкладки нового кода. Новые базы (тесты, CI, свежая установка) создаются обычным migrate без ручных действий. Прогоните перенос на копии рабочей базы в той же СУБД, что и на сервере: SQLite и PostgreSQL по-разному проверяют внешние ключи. Обсуждение подходов и вариант через dumpdata/loaddata собраны в тикете Django #25313. Как устроена история миграций — в статье о миграциях Django.

Частые ошибки

fields.E304: Reverse accessor ‘Group.user_set’ clashes

Модель-наследник AbstractUser описана, но AUTH_USER_MODEL не задан: в проекте оказались два пользователя с одинаковыми обратными связями на Group и Permission. Добавьте AUTH_USER_MODEL = "accounts.User" в настройки.

AUTH_USER_MODEL refers to model ‘accounts.User’ that has not been installed

Приложение с моделью не добавлено в INSTALLED_APPS или в значении опечатка. Формат — метка приложения и имя класса через точку, без models: "accounts.User", а не "accounts.models.User".

ValueError: Dependency on app with no migrations: accounts

Вы запустили migrate до makemigrations accounts. Создайте миграцию приложения с моделью пользователя и повторите.

Manager isn’t available; ‘auth.User’ has been swapped

Код обращается к django.contrib.auth.models.User напрямую или использует стандартные UserCreationForm/UserChangeForm вне админки. Замените импорт на get_user_model() и укажите свою модель в Meta форм.

IntegrityError: UNIQUE constraint failed: profiles_profile.user_id

В UserAdmin подключён inline профиля, а профиль создаёт ещё и сигнал. При добавлении пользователя через админку профиль сохраняется дважды. Покажите inline только на странице изменения:

class CustomUserAdmin(UserAdmin):
    def get_inlines(self, request, obj):
        # на странице добавления профиля ещё нет: его создаст сигнал
        return [ProfileInline] if obj else []

Частые вопросы

Нужна ли своя модель, если стандартной User хватает?

Для нового проекта — да: класс User(AbstractUser) с pass ведёт себя как стандартный, а через год позволит добавить поле одной миграцией. Для работающего проекта, где нужны лишь дополнительные данные, выгоднее профиль с OneToOneField.

Как сделать несколько типов пользователей, например ученика и учителя?

AUTH_USER_MODEL в проекте один. Добавьте в модель поле роли (CharField с choices) и при необходимости proxy-модели с менеджерами для каждой роли. Данные, которые есть только у одного типа, храните в отдельных профилях. Доступ к разделам разграничивайте правами и группами.

Как сделать вход по номеру телефона?

Тем же способом, что и по email: уникальное поле phone, USERNAME_FIELD = "phone" и менеджер, принимающий телефон первым аргументом. Номер приводите к одному формату до сохранения и до поиска — переопределите get_by_natural_key(), иначе +7 900... и 8900... окажутся разными логинами.

Можно ли сделать UUID первичным ключом пользователя?

Можно, и решать это нужно до первой миграции: id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False). Админка, createsuperuser и вход работают без изменений, адрес страницы пользователя в админке будет содержать UUID.

Как входить и по email, и по username?

Своя модель для этого не обязательна: достаточно собственного бэкенда аутентификации, который ищет пользователя по username или по email. Пример бэкенда есть в FAQ обзора «Аутентификация в Django». Если нужны ещё подтверждение email и вход через соцсети, используйте django-allauth.

Итог

  • Своя модель пользователя задаётся настройкой AUTH_USER_MODEL = "app.Model" и должна появиться до первого migrate, в миграции 0001_initial своего приложения.
  • AbstractUser подходит для большинства проектов: добавляет поля к стандартным и позволяет убрать username ради входа по email. AbstractBaseUser с PermissionsMixin нужен, когда стандартный набор полей мешает.
  • Для входа по email задайте USERNAME_FIELD = "email", уникальный email, REQUIRED_FIELDS без логина и свой менеджер; регистр адреса нормализуйте сами.
  • В коде используйте get_user_model(), во внешних ключах и сигналах — settings.AUTH_USER_MODEL.
  • В админке наследуйте UserAdmin и перечислите свои поля в fieldsets и add_fieldsets; свои формы нужны только вне админки.
  • В работающем проекте данные проще хранить в профиле; замена модели возможна через таблицу auth_user, ручную отметку миграции и перенос ContentType.

Справочник по всем атрибутам и методам — в официальной документации: Customizing authentication in Django.

  • #Django
  • #AUTH_USER_MODEL
  • #AbstractUser
  • #Аутентификация
  • #Django Admin
↑ ↓ выборEnter открытьEsc закрыть