🐛 Типичные ошибки
⚡ Топ-5 ошибок
- AUTH_USER_MODEL после миграций:
RuntimeError: Model class apps.users.models.User doesn't declare an explicit app_label— добавь в settings.py ДО первой миграции, удали БД и начни заново - re_password в create:
TypeError: User() got an unexpected keyword argument 're_password'— добавьvalidated_data.pop('re_password') - Порядок URL: 404 на
/files/download/5/— поставьfiles/download/<pk>/ВЫШЕfiles/<pk>/ - Опечатка Assignee: DRF игнорирует поле 'Assignee' (с большой буквы) в fields — используй 'assignee'
- 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 сам закроет файл после передачи