Своя модель пользователя в Django (AUTH_USER_MODEL)
Своя модель пользователя в Django (custom user model): AbstractUser или AbstractBaseUser, вход по email, админка, createsuperuser и смена модели в проекте.
Содержание · 14 разделов
- Стандартная модель django.contrib.auth.models.User
- Какой способ выбрать
- Своя модель на основе AbstractUser
- Meta и удаление ненужных полей
- Вход по email без username
- Email без учёта регистра
- AbstractBaseUser и PermissionsMixin: модель с нуля
- Профиль пользователя через OneToOneField
- Proxy-модель пользователя
- get_user_model() и settings.AUTH_USER_MODEL
- Регистрация в админке: UserAdmin и формы
- Нужны ли свои UserCreationForm и UserChangeForm
- createsuperuser с кастомной моделью
- Смена модели пользователя в середине проекта
- Частые ошибки
- fields.E304: Reverse accessor ‘Group.user_set’ clashes
- AUTH_USER_MODEL refers to model ‘accounts.User’ that has not been installed
- ValueError: Dependency on app with no migrations: accounts
- Manager isn’t available; ‘auth.User’ has been swapped
- IntegrityError: UNIQUE constraint failed: profiles_profile.user_id
- Частые вопросы
- Нужна ли своя модель, если стандартной User хватает?
- Как сделать несколько типов пользователей, например ученика и учителя?
- Как сделать вход по номеру телефона?
- Можно ли сделать UUID первичным ключом пользователя?
- Как входить и по email, и по username?
- Итог
Своя модель пользователя (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 собрана из трёх абстрактных классов. От того, какой из них вы возьмёте за основу, зависит набор полей в вашей модели.
| Поле | Тип и ограничения | Откуда берётся |
|---|---|---|
password | CharField(max_length=128), хеш пароля | AbstractBaseUser |
last_login | DateTimeField, может быть пустым | AbstractBaseUser |
is_superuser | BooleanField, по умолчанию False | PermissionsMixin |
groups, user_permissions | ManyToManyField на Group/Permission | PermissionsMixin |
username | CharField(max_length=150), уникальное | AbstractUser |
first_name, last_name | CharField(max_length=150), необязательные | AbstractUser |
email | EmailField(max_length=254), не уникален | AbstractUser |
is_staff | BooleanField, доступ в админку | AbstractUser |
is_active | BooleanField, по умолчанию True | AbstractUser |
date_joined | DateTimeField, по умолчанию now | AbstractUser |
Таблица в базе называется 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 ещё не запускали:
-
Создайте отдельное приложение для пользователей:
python manage.py startapp accounts. В активированном виртуальном окружении командаpythonодинакова на всех ОС; если вызываете интерпретатор напрямую, на Windows этоpy manage.py ..., на macOS и Linux —python3 manage.py .... -
Опишите модель:
# 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) -
Подключите приложение и укажите модель в
settings.py:INSTALLED_APPS = [ # ...стандартные приложения "accounts", ] AUTH_USER_MODEL = "accounts.User" -
Зарегистрируйте модель в админке (подробно — в разделе про админку ниже):
# 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"] -
Создайте миграции и таблицы:
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, ManyToManyField | settings.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:
-
Сделайте резервную копию базы. Убедитесь, что все миграции применены (
python manage.py showmigrations), а в коде и сторонних пакетах на пользователя ссылаются черезsettings.AUTH_USER_MODELиget_user_model(). -
Создайте приложение
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-битный. -
Добавьте
"accounts"вINSTALLED_APPS, пропишитеAUTH_USER_MODEL = "accounts.User", зарегистрируйте модель в админке черезUserAdmin. -
Выполните
python manage.py makemigrations accounts. Запускатьmigrateпока нельзя — он остановится наInconsistentMigrationHistory. -
Отметьте новую миграцию примененной без выполнения и перенесите тип содержимого пользователя в новое приложение:
# 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") -
Проверьте результат:
python manage.py migrateдолжен вывестиNo migrations to apply, аpython manage.py makemigrations --check—No changes detected. Вход, группы, внешние ключи и записи журнала админки остаются на месте. -
Обновите проверки прав в коде и шаблонах: права на пользователя теперь называются
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