Django: Как Использовать Пользовательскую Модель (User Model) для Аутентификации и Управления Пользователями?

Django предоставляет гибкую систему аутентификации, но стандартная модель django.contrib.auth.models.User не всегда удовлетворяет требованиям сложных проектов. Использование пользовательской модели (Custom User Model) позволяет разработчикам определять структуру данных пользователя в соответствии со спецификой приложения.

Зачем использовать пользовательскую модель пользователя?

Необходимость в пользовательской модели возникает, когда:

Требуется изменить поле username (например, использовать email в качестве идентификатора).

Нужно добавить дополнительные поля, не связанные напрямую с аутентификацией, но являющиеся неотъемлемой частью сущности пользователя (например, date_of_birth, phone_number, company_id).

Необходимо изменить или расширить стандартное поведение модели пользователя, добавив специфические методы или свойства.

Планируется интеграция с внешними системами аутентификации, требующими особой структуры данных.

Принятие решения об использовании пользовательской модели критически важно на начальном этапе проекта, так как изменение модели пользователя после создания миграций и данных может быть очень трудоемким.

Обзор стандартной модели User в Django

Стандартная модель User включает базовый набор полей: username, first_name, last_name, email, password, is_staff, is_active, is_superuser, date_joined, last_login, а также связи с моделями Group и Permission. Она хорошо подходит для многих стандартных сценариев, но ее структура фиксирована.

Преимущества и недостатки использования пользовательской модели

Преимущества:

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

Масштабируемость: Легче адаптировать модель под будущие требования.

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

Недостатки:

Сложность: Требует больше настроек на начальном этапе.

Необратимость: Сложно вернуться к стандартной модели после начала использования пользовательской.

Совместимость: Некоторые сторонние приложения могут ожидать стандартную модель User, что потребует дополнительной адаптации.

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

Процесс создания пользовательской модели включает несколько шагов, начиная с настройки проекта.

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

Перед созданием первой миграции или запуском manage.py migrate необходимо определить пользовательскую модель в настройках проекта. Если вы уже создали миграции со стандартной моделью, изменение потребует значительных усилий.

Рекомендуется создавать пользовательскую модель в отдельном приложении (например, users или accounts).

python manage.py startapp users

Определение пользовательской модели пользователя (AbstractUser или AbstractBaseUser)

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

AbstractUser: Наследует все поля и методы стандартной модели User. Идеально подходит, если нужно добавить или изменить несколько полей, сохранив основную функциональность.

# users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.translation import gettext_lazy as _

class CustomUser(AbstractUser):
    """Расширенная модель пользователя.

    Наследует все поля от AbstractUser, но позволяет добавлять новые.
    """
    email = models.EmailField(_('email address'), unique=True) # Делаем email уникальным и основным для логина
    phone_number = models.CharField(_('phone number'), max_length=20, blank=True, null=True)
    date_of_birth = models.DateField(_('date of birth'), blank=True, null=True)

    # Указываем email как поле для аутентификации
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username'] # Список полей, запрашиваемых при создании суперпользователя

    def __str__(self) -> str:
        return self.email

AbstractBaseUser: Предоставляет только базовую функциональность аутентификации (пароль, last_login, is_active). Требует явного определения всех остальных полей, включая поле для идентификации пользователя (USERNAME_FIELD), обязательные поля (REQUIRED_FIELDS) и методы для управления правами доступа (has_perm, has_module_perms, is_staff). Подходит для полностью кастомных систем.

# users/models.py
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from typing import Optional

class CustomUserManager(BaseUserManager):
    """Менеджер для кастомной модели пользователя.

    Определяет методы для создания обычных пользователей и суперпользователей.
    """
    def _create_user(self, email: str, password: Optional[str], **extra_fields) -> 'CustomUser':
        """Создает и сохраняет пользователя с указанным email и паролем."""
        if not email:
            raise ValueError('The Email field must be set')
        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)
        extra_fields.setdefault('is_active', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(email, password, **extra_fields)

class CustomUser(AbstractBaseUser, PermissionsMixin):
    """Полностью кастомная модель пользователя.

    Использует email как основной идентификатор.
    """
    email = models.EmailField(_('email address'), unique=True)
    first_name = models.CharField(_('first name'), max_length=150, blank=True)
    last_name = models.CharField(_('last name'), max_length=150, blank=True)
    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_('Designates whether the user can log into this admin site.'),
    )
    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_(
            'Designates whether this user should be treated as active. '
            'Unselect this instead of deleting accounts.'
        ),
    )
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)

    objects = CustomUserManager() # Подключение кастомного менеджера

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = [] # При использовании email как USERNAME_FIELD, email автоматически обязателен

    def __str__(self) -> str:
        return self.email

    # Методы has_perm и has_module_perms наследуются от PermissionsMixin

Миграции и обновление базы данных

После определения модели и указания ее в settings.py (см. следующий раздел), необходимо создать и применить миграции:

python manage.py makemigrations users
python manage.py migrate

Настройка аутентификации с использованием пользовательской модели

Интеграция пользовательской модели в систему аутентификации Django требует нескольких шагов.

Указание AUTH_USER_MODEL в settings.py

Это самый важный шаг, который необходимо выполнить до создания первой миграции. В файле settings.py укажите путь к вашей пользовательской модели в формате 'app_label.ModelName':

# settings.py
AUTH_USER_MODEL = 'users.CustomUser' # Замените users.CustomUser на актуальный путь

Django будет использовать эту настройку для всех операций, связанных с пользователями (например, в ForeignKey, ManyToManyField, формах аутентификации).

Использование пользовательской модели в формах аутентификации

Стандартные формы Django (AuthenticationForm, UserCreationForm, PasswordChangeForm и т.д.) автоматически адаптируются к пользовательской модели, указанной в AUTH_USER_MODEL. Однако, если вы создаете кастомные формы, используйте get_user_model() для получения актуальной модели пользователя:

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

# Получаем модель пользователя, определенную в settings.AUTH_USER_MODEL
UserModel = get_user_model()

class CustomUserCreationForm(UserCreationForm):
    """Форма для создания нового пользователя.

    Добавляет или изменяет поля по сравнению со стандартной формой.
    """
    class Meta(UserCreationForm.Meta):
        model = UserModel
        # Указываем поля, которые будут в форме регистрации
        # Если используем email как USERNAME_FIELD, 'username' можно убрать
        fields = ('email', 'first_name', 'last_name') # Пример

class CustomUserChangeForm(UserChangeForm):
    """Форма для изменения данных пользователя в админке.

    Исключает поле пароля, т.к. оно управляется отдельно.
    """
    class Meta(UserChangeForm.Meta):
        model = UserModel
        # Поля, доступные для редактирования в админке
        fields = ('email', 'first_name', 'last_name', 'is_active', 'is_staff', 'groups', 'user_permissions') # Пример
Реклама

Настройка пользовательского менеджера пользователей

Если вы используете AbstractBaseUser или хотите изменить логику создания пользователей для AbstractUser, необходимо определить пользовательский менеджер (BaseUserManager) и указать его в модели пользователя (objects = CustomUserManager()).

Менеджер должен реализовывать как минимум методы create_user и create_superuser, как показано в примере с AbstractBaseUser выше. Эти методы используются Django, например, при выполнении команды createsuperuser.

Управление пользователями и их данными

После настройки модели и аутентификации, управление пользователями осуществляется стандартными средствами Django, адаптированными под вашу модель.

Создание, обновление и удаление пользователей через административную панель

Для управления пользователями через админ-панель Django необходимо зарегистрировать вашу модель и, опционально, кастомный класс UserAdmin:

# users/admin.py
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin
from .forms import CustomUserCreationForm, CustomUserChangeForm # Импорт кастомных форм

UserModel = get_user_model()

@admin.register(UserModel)
class CustomUserAdmin(UserAdmin):
    """Кастомизация отображения модели пользователя в админке."""
    add_form = CustomUserCreationForm # Форма для создания пользователя
    form = CustomUserChangeForm # Форма для редактирования пользователя
    model = UserModel
    list_display = ['email', 'username', 'first_name', 'last_name', 'is_staff', 'is_active'] # Отображаемые колонки в списке
    list_filter = ['is_staff', 'is_active', 'groups'] # Фильтры
    search_fields = ['email', 'username', 'first_name', 'last_name'] # Поля для поиска
    ordering = ['email'] # Сортировка по умолчанию

    # Настройка полей, отображаемых на странице редактирования/создания
    # Fieldsets наследуются от UserAdmin, но их нужно адаптировать под вашу модель
    # Особенно важно указать правильное USERNAME_FIELD
    fieldsets = (
        (None, {'fields': ('email', 'password')}), # Используем email вместо username
        ('Personal info', {'fields': ('first_name', 'last_name', 'date_of_birth', 'phone_number')}),
        ('Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}),
        ('Important dates', {'fields': ('last_login', 'date_joined')}),
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            # Указываем поля для формы создания, включая обязательные (REQUIRED_FIELDS)
            'fields': ('email', 'password', 'password2', 'first_name', 'last_name', 'username'), 
        }),
    )

Работа с полями и методами пользовательской модели

С пользовательской моделью можно работать как с любой другой моделью Django:

from django.contrib.auth import get_user_model

UserModel = get_user_model()

def get_active_users_emails() -> list[str]:
    """Возвращает список email активных пользователей."""
    return list(UserModel.objects.filter(is_active=True).values_list('email', flat=True))

def update_user_phone(user_id: int, new_phone: str) -> bool:
    """Обновляет номер телефона пользователя."""
    try:
        user = UserModel.objects.get(pk=user_id)
        user.phone_number = new_phone
        user.save(update_fields=['phone_number'])
        return True
    except UserModel.DoesNotExist:
        return False

# Получение текущего пользователя в представлении (view)
# request.user будет экземпляром вашей CustomUser модели
# user = request.user
# print(user.phone_number)

Добавление дополнительных полей профиля пользователя (OneToOneField)

Если не хочется переопределять модель пользователя только ради добавления данных профиля (например, аватар, биография), можно использовать связанную модель через OneToOneField. Это сохраняет стандартную или базовую пользовательскую модель чистой.

# users/models.py
from django.db import models
from django.conf import settings # Импортируем настройки для ссылки на AUTH_USER_MODEL

class Profile(models.Model):
    """Модель профиля пользователя, связанная с основной моделью."""
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL, 
        on_delete=models.CASCADE, 
        primary_key=True, # Делаем связь первичным ключом для эффективности
        related_name='profile' # Позволяет обращаться user.profile
    )
    bio = models.TextField(blank=True)
    avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
    website = models.URLField(blank=True)

    def __str__(self) -> str:
        return f"Profile for {self.user.email}"

# Сигнал для автоматического создания профиля при создании пользователя
from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)

@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def save_user_profile(sender, instance, **kwargs):
    instance.profile.save()

Этот подход позволяет разделить аутентификационные данные и данные профиля, что может быть полезно для организации кода и данных.

Продвинутые темы и лучшие практики

Работа с пользовательской моделью требует внимания к деталям, особенно в части безопасности и тестирования.

Использование пользовательских разрешений и групп

Пользовательская модель, унаследованная от AbstractUser или AbstractBaseUser с PermissionsMixin, полностью поддерживает стандартную систему разрешений и групп Django. Вы можете создавать кастомные разрешения на уровне моделей и назначать их пользователям или группам.

# В модели, где нужны кастомные права
class MarketingCampaign(models.Model):
    name = models.CharField(max_length=200)
    # ... другие поля

    class Meta:
        permissions = [
            ("can_launch_campaign", "Can launch marketing campaign"),
            ("can_view_report", "Can view campaign report"),
        ]

# Проверка прав в коде
# if request.user.has_perm('app_label.can_launch_campaign'):
#     # Логика запуска кампании

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

При тестировании необходимо убедиться, что:

Создание пользователей и суперпользователей через менеджер работает корректно.

Аутентификация (логин, логаут) функционирует правильно.

Кастомные поля сохраняются и извлекаются.

Система разрешений работает как ожидается.

Формы (UserCreationForm, AuthenticationForm и т.д.) работают с пользовательской моделью.

Используйте django.test.TestCase и фабрики (например, factory-boy) для создания тестовых пользователей.

Рекомендации по безопасности при работе с пользовательскими данными

Никогда не храните пароли в открытом виде. Django автоматически хеширует пароли при использовании user.set_password() и проверяет их с помощью user.check_password().

Валидируйте все входные данные, особенно email, телефон и другие поля, используемые для идентификации или коммуникации.

Используйте HTTPS для защиты данных при передаче.

Ограничивайте доступ к чувствительным данным с помощью системы разрешений Django.

Регулярно обновляйте Django и другие зависимости для получения исправлений безопасности.

Не используйте предсказуемые идентификаторы (например, последовательные ID) для сброса пароля или других критичных операций.

Распространенные ошибки и способы их устранения

ValueError: The field 'username' clashes with the field 'username' from model 'auth.user': Возникает, если вы пытаетесь изменить AUTH_USER_MODEL после создания миграций для стандартной модели auth.User. Решение: удалить базу данных и миграции (только на ранней стадии разработки!) или выполнить сложную миграцию данных.

Забыли указать AUTH_USER_MODEL = 'myapp.MyUser' в settings.py до первой миграции: Приводит к использованию стандартной модели. Лечение аналогично предыдущему пункту.

Ошибки при создании суперпользователя (createsuperuser): Обычно связаны с неправильной реализацией CustomUserManager (особенно create_superuser) или неверно указанным USERNAME_FIELD и REQUIRED_FIELDS.

Сторонние приложения не работают: Некоторые приложения могут жестко ссылаться на django.contrib.auth.models.User. Используйте get_user_model() в своем коде и ищите обновленные версии сторонних приложений или создавайте адаптеры.

Проблемы с related_name в ForeignKey или ManyToManyField к модели пользователя: Если вы определяете связи до того, как Django обработает AUTH_USER_MODEL, используйте строку 'settings.AUTH_USER_MODEL' вместо прямого импорта модели.

Тщательное планирование и следование рекомендациям Django помогут избежать большинства проблем при внедрении пользовательской модели пользователя.


Добавить комментарий