🐛 Типичные ошибки практикума 5

Разбор ошибок с диагностикой и исправлением

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

  1. Нет on_delete в ForeignKey/OneToOneTypeError: ForeignKey() missing 1 required argument: 'on_delete'
  2. null=True без blank=True → Django-форма не позволяет оставить поле пустым, хотя в БД NULL допустим
  3. list_editable без list_displayImproperlyConfigured: 'field' is in list_editable but not in list_display
  4. Пропущена запятая в list_display → строки склеиваются, Django ищет несуществующее поле
  5. ProtectedError при удалении Category → сначала нужно удалить/перенести все Products

Ошибка 1: Не указан on_delete

Ошибка:
TypeError: ForeignKey() missing 1 required positional argument: 'on_delete'

Причина: в Django 2.0+ параметр on_delete стал обязательным. В старых учебниках встречается код без него.

Неправильно

category = models.ForeignKey(Category)
# TypeError в Django 2.0+

Правильно

category = models.ForeignKey(
    Category,
    on_delete=models.PROTECT
)

Ошибка 2: null=True без blank=True

Симптом: в Django-форме невозможно оставить поле пустым, хотя оно nullable.

Причина: null=True — это настройка БД (разрешить NULL). blank=True — настройка Django-форм (разрешить пустое значение при валидации). Оба нужны вместе для опциональных полей.

Неправильно

description = models.TextField(null=True)
# В форме обязательно для заполнения!

Правильно

description = models.TextField(
    null=True, blank=True
)
# И в БД NULL, и форма принимает пустое

Ошибка 3: list_editable без list_display

Ошибка:
ImproperlyConfigured: 'ProductAdmin.list_editable[0]', 'price'
is not contained in 'list_display'.

Причина: Django требует, чтобы поле из list_editable было видно в таблице (присутствовало в list_display). Иначе пользователь не видит что редактирует.

Неправильно

list_display = ['name', 'category']
list_editable = ['price']  # price не в list_display!

Правильно

list_display = ['name', 'category', 'price']
list_editable = ['price']  # price есть в list_display

Ошибка 4: Пропущена запятая в списке list_display

Ошибка:
FieldDoesNotExist: Product has no field named 'quantityarticle'

Причина: Python автоматически склеивает соседние строковые литералы без оператора. Если между элементами кортежа/списка нет запятой, строки объединяются в одну.

Неправильно (из источника — опечатка)

list_display = [
    'quantity'   # нет запятой!
    'article',
]
# Python видит: 'quantityarticle'

Правильно

list_display = [
    'quantity',  # запятая обязательна
    'article',
]
Эта опечатка присутствует в оригинальном решении практикума (задание 3.3). При копировании кода из лекции всегда проверяйте запятые в многострочных списках.

Ошибка 5: ProtectedError при удалении связанного объекта

Ошибка:
django.db.models.deletion.ProtectedError:
("Cannot delete some instances of model 'Category' because they are
referenced through protected foreign keys: 'Product.category'", ...)

Причина: on_delete=models.PROTECT намеренно запрещает удаление Category, пока в ней есть Product. Это защита данных.

Решение: сначала удалить или перенести в другую категорию все продукты, затем удалить категорию.

# Перенести все продукты перед удалением категории:
other_category = Category.objects.get(name='Other')
Product.objects.filter(category=old_category).update(category=other_category)
old_category.delete()  # теперь безопасно

Ошибка 6: Забыли сделать makemigrations после изменения models.py

Симптом: изменения в модели не отражаются в БД. Поля отсутствуют или таблица не создана.

Причина: изменения в models.py не попадают в БД автоматически — нужно явно создать миграцию и применить её.

python manage.py makemigrations  # создать файл миграции
python manage.py migrate         # применить к БД

Ошибка 7: FloatField вместо DecimalField для денег

Симптом: цена 1.10 хранится и отображается как 1.0999999... из-за погрешности float.

Причина: FloatField хранит числа как IEEE 754 float — с погрешностью. Для финансовых данных нужен DecimalField.

Неправильно

price = models.FloatField()

Правильно

price = models.DecimalField(
    max_digits=10, decimal_places=2
)