Шаг 02. БД, кастомная модель User и первая миграция

📁 Серия: Капстоун C ⏱️ ~35 мин 🎯 Сложность: Средняя
#AbstractUser #AUTH_USER_MODEL #migrate #createsuperuser

⚡ Кратко: что делаем на этом шаге

Цель: Создать apps/users/, написать кастомную модель User (AbstractUser + email + bio + avatar), установить AUTH_USER_MODEL = "users.User" ДО первой миграции, применить миграции, создать суперпользователя.

  • Файлы: apps/users/models.py, apps/users/admin.py, config/settings.py
  • Команды: python manage.py startapp users apps/users, python manage.py makemigrations, python manage.py migrate, python manage.py createsuperuser
  • Результат: Django Admin доступен; суперпользователь создан через кастомную модель

🎯 Цель этапа

Кастомная модель пользователя — это одно из самых важных решений, которое нужно принять до первой миграции. Если использовать стандартный auth.User, в будущем будет невозможно добавить поля без болезненной миграции всей базы. Хорошая практика: всегда начинать Django-проект с кастомного User.

💡 Ключевая идея — AUTH_USER_MODEL до migrate: Django создаёт внутренние таблицы (auth_user, django_session и т.д.) при первом manage.py migrate. Если вы запустили migrate со стандартным User, а потом хотите переключиться — нужно дропнуть базу и начать заново. В этом проекте мы делаем всё правильно: кастомный User → AUTH_USER_MODELпотом migrate.

После этого шага у нас будет

  • Приложение apps/users/ с кастомной моделью User
  • AUTH_USER_MODEL = "users.User" в settings.py
  • Все миграции применены (встроенные + наши)
  • Суперпользователь создан, Django Admin открывается в браузере

📄 Затрагиваемые файлы

ФайлДействиеОписание
apps/users/__init__.pyСоздать (startapp)Пакет приложения users
apps/users/apps.pyИзменитьname = "apps.users"
apps/users/models.pyЗаполнитьКастомная модель User
apps/users/admin.pyЗаполнитьРегистрация кастомного User в Admin
apps/users/migrations/0001_initial.pyСоздаётся makemigrationsПервая миграция users
config/settings.pyИзменитьAUTH_USER_MODEL, INSTALLED_APPS += "apps.users"

🔨 Шаги

1. Создаём приложение users

💻 Терминал
# Создаём приложение внутри папки apps/
python manage.py startapp users apps/users
💡 Синтаксис startapp с путём: startapp users apps/users — первый аргумент (users) это имя приложения, второй (apps/users) — путь, куда создать файлы. После создания нужно поправить apps.py, чтобы name совпадал с путём в INSTALLED_APPS.

2. Правим apps/users/apps.py

📄 apps/users/apps.py
# apps/users/apps.py
from django.apps import AppConfig


class UsersConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "apps.users"          # Полный путь — важно!
    verbose_name = "Пользователи"

3. Пишем кастомную модель User

📄 apps/users/models.py
# apps/users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models


class User(AbstractUser):
    """
    Кастомная модель пользователя.

    Расширяем AbstractUser дополнительными полями.
    Используем email как USERNAME_FIELD для входа через JWT.
    username оставляем (inherited) для совместимости с Admin.
    """

    email = models.EmailField(
        "Email",
        unique=True,
        help_text="Используется для входа в систему.",
    )
    bio = models.TextField(
        "О себе",
        blank=True,
        help_text="Краткое описание пользователя.",
    )
    avatar = models.URLField(
        "Аватар (URL)",
        blank=True,
        help_text="Ссылка на изображение аватара.",
    )

    # Входим по email, не по username
    USERNAME_FIELD = "email"
    # username обязателен, но не используется при аутентификации
    REQUIRED_FIELDS = ["username"]

    class Meta:
        verbose_name = "Пользователь"
        verbose_name_plural = "Пользователи"
        ordering = ["email"]

    def __str__(self) -> str:
        return self.email
💡 USERNAME_FIELD = "email": Когда мы настроим JWT на шаге 08, пользователи будут логиниться по email, а не по username. REQUIRED_FIELDS — список полей, запрашиваемых при createsuperuser (не включает USERNAME_FIELD и password).

4. Регистрируем User в Admin

📄 apps/users/admin.py
# apps/users/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from .models import User


@admin.register(User)
class UserAdmin(BaseUserAdmin):
    """
    Наследуем от встроенного UserAdmin, чтобы сохранить
    форму изменения пароля и другую стандартную функциональность.
    """
    list_display = ("email", "username", "is_active", "is_staff", "date_joined")
    list_filter = ("is_active", "is_staff", "is_superuser")
    search_fields = ("email", "username")
    ordering = ("email",)

    # Добавляем наши поля в форму редактирования
    fieldsets = BaseUserAdmin.fieldsets + (
        ("Профиль", {"fields": ("bio", "avatar")}),
    )

5. Добавляем в settings.py

📄 config/settings.py (добавить/изменить)
# config/settings.py — изменения

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    # Сторонние
    "rest_framework",
    # Наши приложения
    "apps.users",               # ← добавляем
]

# ...

# КРИТИЧНО: задать ДО первой миграции!
AUTH_USER_MODEL = "users.User"
⚠️ AUTH_USER_MODEL — критический порядок! Строку AUTH_USER_MODEL = "users.User" нужно добавить в settings.py до запуска python manage.py migrate. Если сначала запустить migrate (создадутся таблицы с auth_user), а потом поменять модель — Django выдаст ошибку и база данных потребует пересоздания.

6. Создаём и применяем миграции

💻 Терминал
# Создаём миграцию для нашего User
python manage.py makemigrations users

# Применяем ВСЕ миграции (встроенные Django + наша)
python manage.py migrate

Ожидаемый вывод migrate (фрагмент):

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, users
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying users.0001_initial... OK
  Applying admin.0001_initial... OK
  ...
  Applying sessions.0001_initial... OK

7. Создаём суперпользователя

💻 Терминал
python manage.py createsuperuser

Django запросит: Email (наш USERNAME_FIELD), Username (из REQUIRED_FIELDS), Password.

8. Запускаем сервер и проверяем Admin

💻 Терминал
python manage.py runserver

Открываем в браузере: http://127.0.0.1:8000/admin/

🧠 Объяснение логики

AbstractUser vs AbstractBaseUser

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

  • AbstractUser — наследует всё стандартное поведение (username, first_name, last_name, email, is_active, is_staff и т.д.). Мы только добавляем поля. Это 95% случаев.
  • AbstractBaseUser — минималистичная основа: только password и last_login. Нужен, когда стандартные поля мешают (например, нет username вообще). Сложнее в реализации.

Для Task Manager AbstractUser идеален: сохраняем всю стандартную функциональность + добавляем bio и avatar.

Почему email как USERNAME_FIELD?

В большинстве современных API пользователи логинятся по email, не по username. Email уникален (поле с unique=True), легко запомнить. Устанавливая USERNAME_FIELD = "email", мы говорим Django и SimpleJWT: «ищи пользователя по этому полю при аутентификации».

name = "apps.users" в AppConfig

Без правки apps.py Django не найдёт приложение по пути "apps.users" в INSTALLED_APPS. Поле name в AppConfig должно совпадать с тем, что написано в INSTALLED_APPS.

⚠️ Частая ошибка: Забыть изменить name в apps.py. Django создаст приложение с именем "users", а INSTALLED_APPS содержит "apps.users" — это разные строки, Django не найдёт модель.

✅ Проверка

1. Проверяем миграции

💻 Терминал
python manage.py showmigrations users
Успех:
users
 [X] 0001_initial

2. Проверяем через Django shell

💻 Django shell
from apps.users.models import User
from django.contrib.auth import get_user_model

# get_user_model() должен вернуть нашу кастомную модель
assert get_user_model() is User, "AUTH_USER_MODEL не установлен!"

# Проверяем суперпользователя
admin = User.objects.filter(is_superuser=True).first()
print(admin.email)   # ваш email
print(admin.username)
print(type(admin))   # <class 'apps.users.models.User'>

3. Django Admin

Открываем http://127.0.0.1:8000/admin/, логинимся по email + паролю суперпользователя.

Успех: В разделе «ПОЛЬЗОВАТЕЛИ» видим нашу модель User с полями email, username, is_active, is_staff. Форма редактирования содержит раздел «Профиль» с полями bio и avatar.

Диагностика: если что-то пошло не так

  • ValueError: Dependency on app with no migrations: users — не выполнили makemigrations users перед migrate
  • django.db.migrations.exceptions.InconsistentMigrationHistory — migrate был запущен до установки AUTH_USER_MODEL. Удалите db.sqlite3 и запустите migrate заново
  • Admin показывает стандартный User, не кастомный — не добавили AUTH_USER_MODEL в settings.py или не зарегистрировали кастомный UserAdmin

➡️ Что дальше

На следующем шаге мы создаём остальные модели предметной области: Project, Task и Comment — со всеми FK/M2M-связями, TextChoices и полными миграциями.

  • Готово: кастомный User, AUTH_USER_MODEL, первая миграция, суперпользователь, Admin открывается
  • Далее (шаг 03): apps/projects/, apps/tasks/, все модели со связями
  • После шага 03 схема БД будет полностью определена