Введение в пользовательские модели Django
Стандартная модель django.contrib.auth.models.User покрывает базовые потребности аутентификации и авторизации во многих проектах Django. Однако, по мере роста и усложнения приложения, часто возникает необходимость расширить или полностью переопределить стандартную модель пользователя. Django предоставляет гибкие механизмы для создания пользовательских моделей, адаптированных под конкретные требования проекта.
Зачем нужна пользовательская модель пользователя?
Использование пользовательской модели дает ряд преимуществ:
Добавление специфичных полей: Возможность хранить дополнительную информацию о пользователе, не связанную напрямую с аутентификацией (например, date_of_birth, company_name, marketing_consent, api_key).
Изменение полей аутентификации: Использование email вместо username в качестве основного идентификатора для входа.
Интеграция с внешними системами: Хранение идентификаторов из других систем или специфических токенов.
Полный контроль над структурой: Возможность определить только необходимые поля и методы, оптимизируя модель под задачи.
Когда следует использовать пользовательскую модель?
Рекомендуется определить пользовательскую модель в самом начале проекта, даже если вы не планируете немедленно ее кастомизировать. Замена стандартной модели User на пользовательскую в уже существующем проекте сопряжена со значительными сложностями, особенно если существуют внешние ключи (ForeignKey, ManyToManyField), ссылающиеся на стандартную модель.
Переходите на пользовательскую модель, если:
Вам нужно хранить данные, не предусмотренные стандартной моделью.
Вы хотите использовать email или другой уникальный идентификатор для аутентификации.
Требуется кастомная логика управления пользователями (например, специфичные методы создания или валидации).
Различия между `AbstractUser` и `AbstractBaseUser`
Django предлагает два основных базовых класса для создания пользовательских моделей:
AbstractUser: Наследует все поля и методы стандартной модели User (username, first_name, last_name, email, is_staff, is_active, is_superuser, groups, user_permissions, date_joined). Идеально подходит, если вам нужно лишь добавить несколько дополнительных полей или изменить поведение существующих методов, сохраняя основную структуру User.
AbstractBaseUser: Предоставляет только базовую функциональность аутентификации (password, last_login, is_active). При использовании этого класса вы должны самостоятельно определить поля для идентификации пользователя (USERNAME_FIELD), обязательные поля при создании через команду createsuperuser (REQUIRED_FIELDS), а также реализовать менеджер (BaseUserManager) для управления созданием пользователей.
Выбор между AbstractUser и AbstractBaseUser зависит от степени кастомизации, которая вам необходима. Для простых расширений достаточно AbstractUser. Для полной переработки модели пользователя, включая механизм аутентификации, выбирайте AbstractBaseUser.
Создание пользовательской модели с использованием `AbstractUser`
Этот подход является наиболее простым способом расширить стандартную модель User.
Шаг 1: Определение пользовательской модели
Создайте новое приложение (например, users) или используйте существующее. В файле models.py определите вашу модель, наследуясь от AbstractUser:
# users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
from typing import Optional
class CustomUser(AbstractUser):
"""Расширенная пользовательская модель.
Добавляет поля для даты рождения и согласия на маркетинговые рассылки.
"""
date_of_birth: models.DateField =
models.DateField(null=True, blank=True, verbose_name="Дата рождения")
marketing_consent: models.BooleanField =
models.BooleanField(default=False, verbose_name="Согласие на маркетинг")
# Можно переопределить существующие поля, например, сделать email уникальным
email: models.EmailField =
models.EmailField(unique=True, verbose_name="Email")
# Не добавляем related_name='+', так как AbstractUser уже обрабатывает
# конфликты имен для groups и user_permissions.
class Meta:
verbose_name = "Пользователь"
verbose_name_plural = "Пользователи"
def get_full_name(self) -> str:
"""Возвращает полное имя пользователя с учетом кастомной логики."""
# Пример кастомной логики
if self.first_name and self.last_name:
return f"{self.first_name} {self.last_name}"
return self.username
def __str__(self) -> str:
return self.email # Или self.username, в зависимости от логикиШаг 2: Настройка `AUTH_USER_MODEL` в `settings.py`
Укажите Django использовать вашу новую модель. Это необходимо сделать до создания первой миграции, которая затрагивает модель пользователя.
# settings.py
AUTH_USER_MODEL = 'users.CustomUser' # 'app_name.ModelName'Шаг 3: Создание миграций и применение их
После определения модели и настройки AUTH_USER_MODEL, создайте и примените миграции:
python manage.py makemigrations users
python manage.py migrateШаг 4: Использование пользовательской модели в приложениях
В других моделях и частях кода ссылайтесь на пользовательскую модель через settings.AUTH_USER_MODEL или get_user_model():
# some_app/models.py
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
from typing import TYPE_CHECKING
# Проверка типов для предотвращения циклических импортов
if TYPE_CHECKING:
# Используем имя модели из AUTH_USER_MODEL для type hinting
# Предполагая, что users.CustomUser - это ваш AUTH_USER_MODEL
from users.models import CustomUser
class AdCampaign(models.Model):
"""Модель рекламной кампании, связанная с пользователем."""
name: models.CharField = models.CharField(max_length=255, verbose_name="Название кампании")
budget: models.DecimalField = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="Бюджет")
# Использование settings.AUTH_USER_MODEL для ForeignKey
manager: models.ForeignKey =
models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='ad_campaigns',
verbose_name="Менеджер"
)
# ... другие поля ...
def __str__(self) -> str:
return self.name
# В представлениях или других частях кода:
User = get_user_model() # Получение актуальной модели пользователя
def get_campaigns_for_user(user: 'CustomUser'): # Используем строковый литерал или TYPE_CHECKING
"""Получает кампании для указанного пользователя."""
return AdCampaign.objects.filter(manager=user)Не забудьте обновить административную панель для отображения новых полей или регистрации новой модели, если вы ее еще не регистрировали.
# users/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser
class CustomUserAdmin(UserAdmin):
"""Кастомизация отображения пользователя в админке."""
model = CustomUser
# Добавление новых полей в списки отображения и редактирования
list_display = ('email', 'username', 'first_name', 'last_name', 'is_staff', 'marketing_consent')
list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups', 'marketing_consent')
search_fields = ('email', 'username', 'first_name', 'last_name')
ordering = ('email',)
# Добавление полей в fieldsets для форм создания/редактирования
# Важно скопировать fieldsets из UserAdmin и добавить свои поля
fieldsets = UserAdmin.fieldsets + (
('Дополнительная информация', {'fields': ('date_of_birth', 'marketing_consent')}),
)
add_fieldsets = UserAdmin.add_fieldsets + (
('Дополнительная информация', {'fields': ('date_of_birth', 'marketing_consent')}),
)
admin.site.register(CustomUser, CustomUserAdmin)Создание пользовательской модели с использованием `AbstractBaseUser`
Этот подход дает максимальную гибкость, но требует больше настроек.
Шаг 1: Определение полей и методов
Создайте модель, наследуясь от AbstractBaseUser и, как правило, PermissionsMixin (для интеграции со стандартной системой прав Django).
# users/models.py
from django.contrib.auth.models import (
AbstractBaseUser, BaseUserManager, PermissionsMixin
)
from django.db import models
from django.utils import timezone
from typing import Optional, List
# Менеджер будет определен ниже
class CustomUserManager(BaseUserManager):
pass
class CustomUser(AbstractBaseUser, PermissionsMixin):
"""Пользовательская модель с email в качестве логина.
Использует AbstractBaseUser для полного контроля над полями.
"""
email: models.EmailField =
models.EmailField(unique=True, max_length=255, verbose_name="Email")
company_name: models.CharField =
models.CharField(max_length=100, blank=True, verbose_name="Название компании")
# Поля, требуемые Django
is_staff: models.BooleanField =
models.BooleanField(default=False, verbose_name="Статус персонала")
is_active: models.BooleanField =
models.BooleanField(default=True, verbose_name="Активен")
date_joined: models.DateTimeField =
models.DateTimeField(default=timezone.now, verbose_name="Дата регистрации")
# Указываем менеджер
objects: CustomUserManager = CustomUserManager()
# Поле, используемое для логина
USERNAME_FIELD: str = 'email'
# Поля, запрашиваемые при создании суперпользователя через createsuperuser
REQUIRED_FIELDS: List[str] = [] # Например, ['company_name'] если оно обязательно
class Meta:
verbose_name = "Пользователь"
verbose_name_plural = "Пользователи"
def __str__(self) -> str:
return self.email
def get_full_name(self) -> str:
"""Возвращает email, т.к. нет полей first_name/last_name."""
return self.email
def get_short_name(self) -> str:
"""Возвращает email."""
return self.emailШаг 2: Создание менеджера пользователей
При использовании AbstractBaseUser необходимо реализовать менеджер (BaseUserManager) с методами create_user и create_superuser.
# users/models.py (продолжение)
class CustomUserManager(BaseUserManager):
"""Менеджер для пользовательской модели без username."""
def _create_user(self, email: str, password: Optional[str], **extra_fields) -> CustomUser:
"""Создает и сохраняет пользователя с указанным email и паролем."""
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: str, password: Optional[str] = None, **extra_fields) -> CustomUser:
"""Создает обычного пользователя."""
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: str, password: Optional[str] = None, **extra_fields) -> CustomUser:
"""Создает суперпользователя."""
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)
# Важно: Поместите определение CustomUserManager *перед* CustomUser
# или используйте строковое представление в objects = 'CustomUserManager()' внутри модели.
# Однако, для ясности лучше определить менеджер первым.
# ... определение CustomUser как в Шаге 1 ...
# Убедитесь, что строка `objects: CustomUserManager = CustomUserManager()` присутствует в CustomUserШаг 3: Настройка `AUTH_USER_MODEL` и создание миграций
Аналогично подходу с AbstractUser, настройте AUTH_USER_MODEL в settings.py до первой миграции:
# settings.py
AUTH_USER_MODEL = 'users.CustomUser'Затем создайте и примените миграции:
python manage.py makemigrations users
python manage.py migrateНе забудьте также настроить административную панель для вашей новой модели, учитывая отсутствующие поля из UserAdmin и добавляя ваши собственные.
Миграция на пользовательскую модель в существующем проекте
Предупреждение: Миграция на пользовательскую модель в проекте с уже существующими данными и связями (ForeignKey, ManyToManyField к auth.User) — сложная и рискованная операция. Настоятельно рекомендуется определять AUTH_USER_MODEL в самом начале проекта.
Если миграция неизбежна, следуйте этим шагам с особой осторожностью:
Подготовка к миграции: Резервное копирование данных
Обязательно сделайте полное резервное копирование базы данных перед началом любых изменений. Протестируйте процесс восстановления из бэкапа.
Создание пользовательской модели и настройка `AUTH_USER_MODEL`
Создайте пользовательскую модель (CustomUser), наследуясь от AbstractUser или AbstractBaseUser.
Не создавайте миграции для этого приложения (users) пока.
Установите AUTH_USER_MODEL = 'users.CustomUser' в settings.py.
Обработка существующих связей с моделью User
Это самый сложный этап. Вам нужно будет изменить все ForeignKey и ManyToManyField, которые ссылаются на auth.User, чтобы они ссылались на settings.AUTH_USER_MODEL. Проблема в том, что Django создаст новые миграции для всех приложений, где есть такие связи, пытаясь изменить эти поля.
Часто это приводит к конфликтам и ошибкам миграции, так как стандартные миграции Django могут некорректно обрабатывать такое изменение на существующих данных.
Возможные подходы (требуют глубокого понимания миграций Django):
Ручное редактирование миграций: После генерации миграций (makemigrations) вам, скорее всего, придется вручную отредактировать файлы миграций, чтобы корректно перенести данные и изменить ограничения внешних ключей. Это может включать операции SeparateDatabaseAndState, RunPython для переноса данных, удаление старых и создание новых ограничений.
Использование сторонних библиотек: Существуют библиотеки (например, django-migration-fixture), которые могут помочь с этим процессом, но они также требуют тщательной настройки и тестирования.
Дамп и восстановление данных: Экспортировать данные из старых таблиц, применить все миграции на пустой базе данных, а затем импортировать данные обратно, преобразуя их под новую структуру. Это трудоемко и рискованно.
Создание и применение миграций
Создайте миграции для вашего приложения с пользовательской моделью (users):
python manage.py makemigrations usersСоздайте миграции для всех остальных приложений, где были изменены связи:
python manage.py makemigrationsТщательно проверьте сгенерированные файлы миграций. Вероятно, потребуется их ручная корректировка.
Примените миграции:
python manage.py migrateБудьте готовы к ошибкам и необходимости отката к резервной копии.
Распространенные ошибки и их решения
<!— wp:heading {"level": 3, "content": "\u041e\u0448\u0438\u0431\u043a\u0430: `ImproperlyConfigured: AUTH_USER_MODEL refers to model '.’ that has not been installed`»} —>Ошибка: `ImproperlyConfigured: AUTH_USER_MODEL refers to model ‘.’ that has not been installed`
Причина: Приложение (app_name), указанное в AUTH_USER_MODEL, не добавлено в INSTALLED_APPS в settings.py, или добавлено после приложений, которые от него зависят (например, django.contrib.admin, django.contrib.auth).
Решение: Убедитесь, что ваше приложение (users в примерах выше) зарегистрировано в INSTALLED_APPS, и оно находится перед django.contrib.auth и django.contrib.admin.
Проблемы с `related_name` при использовании ForeignKey к пользовательской модели
Причина: Стандартные модели Django (Group, Permission, или другие ваши модели) могут иметь обратные связи к модели User. Когда вы определяете свою модель CustomUser (особенно наследуясь от AbstractUser), имена этих обратных связей (related_name) могут конфликтовать.
Решение при наследовании от AbstractUser: AbstractUser уже обрабатывает конфликты для полей groups и user_permissions, устанавливая related_name='+'. Если вы добавляете свои собственные ForeignKey или ManyToManyField к CustomUser из других моделей, убедитесь, что вы предоставляете уникальные related_name или используйте related_name='+', если обратная связь вам не нужна.
Решение при наследовании от AbstractBaseUser: Если вы используете PermissionsMixin, он также добавляет поля groups и user_permissions. При добавлении связей из других моделей всегда указывайте явный related_name.
Некорректная работа административной панели Django
Причина 1: Вы не зарегистрировали вашу CustomUser модель в admin.py или не создали кастомный класс UserAdmin.
Решение 1: Зарегистрируйте CustomUser с использованием кастомного CustomUserAdmin, как показано в примерах выше.
Причина 2: Ваш кастомный UserAdmin неверно настроен (например, отсутствуют необходимые fieldsets или add_fieldsets для полей из AbstractBaseUser или ваших кастомных полей).
Решение 2: Убедитесь, что fieldsets и add_fieldsets в вашем CustomUserAdmin включают все поля, которые вы хотите видеть и редактировать, особенно если вы используете AbstractBaseUser.
Причина 3: Вы используете AbstractBaseUser без PermissionsMixin, но пытаетесь использовать стандартные функции админки, связанные с правами.
Решение 3: Добавьте PermissionsMixin к вашей модели CustomUser, если вам нужна интеграция с системой прав Django.