🐛 Типичные ошибки

⚡ Топ-5 ошибок

  1. AUTH_USER_MODEL после миграций: RuntimeError: Model class apps.users.models.User doesn't declare an explicit app_label — добавь в settings.py ДО первой миграции, удали БД и начни заново
  2. re_password в create: TypeError: User() got an unexpected keyword argument 're_password' — добавь validated_data.pop('re_password')
  3. Порядок URL: 404 на /files/download/5/ — поставь files/download/<pk>/ ВЫШЕ files/<pk>/
  4. Опечатка Assignee: DRF игнорирует поле 'Assignee' (с большой буквы) в fields — используй 'assignee'
  5. FileResponse + закрытый файл: ValueError: I/O operation on closed file — не закрывай файл вручную, FileResponse сделает это сам

Ошибка 1. AUTH_USER_MODEL добавлен после первых миграций

Симптом:

RuntimeError: Model class apps.users.models.User doesn't declare an explicit app_label
# или
ValueError: Related model 'auth.User' cannot be resolved
# или
django.db.utils.OperationalError: table "users_user" already exists

Причина: Django создаёт таблицы на основе стандартной модели User из django.contrib.auth при первой миграции. Если после этого поменять AUTH_USER_MODEL, возникают конфликты.

Решение:

# 1. Удалить существующую БД
rm db.sqlite3
# 2. Удалить все файлы миграций (кроме __init__.py)
# 3. Добавить в settings.py ДО makemigrations
AUTH_USER_MODEL = 'users.User'
# 4. Заново
python manage.py makemigrations
python manage.py migrate

Ошибка 2. re_password не удалён из validated_data

Симптом:

TypeError: User() got an unexpected keyword argument 're_password'

Причина: Поле re_password объявлено в сериализаторе, но не существует в модели User. При вызове User(**validated_data) Django пытается передать несуществующий аргумент.

Неправильно:

def create(self, validated_data):
    password = validated_data.pop('password')
    # re_password остался в validated_data!
    user = User(**validated_data)  # TypeError!

Правильно:

def create(self, validated_data):
    password = validated_data.pop('password')
    validated_data.pop('re_password')   # обязательно!
    user = User(**validated_data)
    user.set_password(password)
    user.save()
    return user

Ошибка 3. Неправильный порядок URL-паттернов

Симптом:

GET /api/v1/projects/files/download/5/ → 404
# Или: ProjectFile matching query does not exist

Причина: Если files/<int:pk>/ стоит выше files/download/<pk>/, Django сначала пытается сопоставить "download" с <int:pk>. Строка "download" не конвертируется в int — возникает ошибка маршрутизации.

Неправильно:

urlpatterns = [
    path('files/<int:pk>/', ProjectFileDetailGenericView.as_view()),
    path('files/download/<int:pk>/', DownloadProjectFileView.as_view()),  # никогда не достигается
]

Правильно:

urlpatterns = [
    path('files/', ProjectFileListGenericView.as_view()),
    path('files/download/<int:pk>/', DownloadProjectFileView.as_view()),  # сначала!
    path('files/<int:pk>/', ProjectFileDetailGenericView.as_view()),
]

Ошибка 4. Опечатка 'Assignee' вместо 'assignee' в fields

Симптом:

Field name `Assignee` is not valid for model `Task`.
# или поле просто игнорируется без ошибки

Причина: В лекции допущена опечатка — поле указано с заглавной буквы 'Assignee', тогда как имя атрибута модели и сериализатора — строчными.

Правильно:

class Meta:
    model = Task
    fields = (
        'deadline',
        'assignee',   # строчная!
    )

Ошибка 5. Пароль не хешируется

Симптом:

# Пользователь создан, но не может войти в систему
# В БД пароль хранится в открытом виде: "mypassword123"

Причина: Прямое присвоение user.password = password сохраняет пароль как plain text — Django не сможет его верифицировать при логине.

Неправильно:

user = User(**validated_data)
user.password = password   # plain text!

Правильно:

user = User(**validated_data)
user.set_password(password)   # хеширует через PBKDF2

Ошибка 6. FileResponse + ручное закрытие файла

Симптом:

ValueError: I/O operation on closed file.
# или файл скачивается пустым (0 байт)

Причина: FileResponse стримит файл лениво — он читает данные по мере отправки. Если закрыть файловый дескриптор вручную до завершения отправки, Django не сможет прочитать содержимое.

Неправильно:

file_handle = project_file.file_path.open()
response = FileResponse(file_handle)
file_handle.close()   # преждевременно!

Правильно:

file_handle = project_file.file_path.open()
response = FileResponse(file_handle)
# FileResponse сам закроет файл после передачи