🐛 Типичные ошибки — Урок 36
⚡ Топ-6 ошибок урока
- Не подключить SoftDeleteManager → удалённые записи видны в API
- Вызвать
super().delete()в переопределённом методе → физическое удаление - Забыть
select_related→ N+1 запросов на продакшне - Использовать
select_relatedдля ManyToMany → исключение или неверный результат - Неправильный порядок декораторов
@transaction.atomicи@api_view - Забыть
transaction.set_autocommit(True)в блокеfinally
Раздел 1: Ошибки мягкого удаления
Ошибка 1: Не подключить SoftDeleteManager к модели
Неверно
# models.py
class Book(models.Model):
is_deleted = models.BooleanField(default=False)
def delete(self, *args, **kwargs):
self.is_deleted = True
self.save()
# objects = SoftDeleteManager() — ЗАБЫЛИ!
# Результат: book.delete() ставит is_deleted=True,
# но Book.objects.all() возвращает ВСЕ записи,
# включая "удалённые"!
Верно
# models.py
from .managers import SoftDeleteManager
class Book(models.Model):
is_deleted = models.BooleanField(default=False)
objects = SoftDeleteManager() # подключить!
def delete(self, *args, **kwargs):
self.is_deleted = True
self.save()
# Теперь Book.objects.all() исключает is_deleted=True
Ошибка 2: Вызов super().delete() в мягком удалении
Неверно
def delete(self, *args, **kwargs):
self.is_deleted = True
self.save()
super().delete(*args, **kwargs) # физически удаляет!
Запись будет сначала помечена как удалённая, а затем физически удалена из базы.
Верно
def delete(self, *args, **kwargs):
self.is_deleted = True
self.save()
# super().delete() НЕ вызываем!
Ошибка 3: Нет миграции после добавления поля
# После добавления is_deleted в модель
# ОБЯЗАТЕЛЬНО:
python manage.py makemigrations
python manage.py migrate
# Иначе: django.db.utils.OperationalError: table has no column named is_deleted
Раздел 2: Ошибки ленивой загрузки
Ошибка 4: Использование select_related для ManyToManyField
Неверно
# genres — ManyToManyField
books = Book.objects.select_related('genres')
# ValueError или неверный результат — select_related
# работает только с FK и OneToOne!
Верно
# Для ManyToMany — prefetch_related
books = Book.objects.prefetch_related('genres')
# Для FK — select_related
books = Book.objects.select_related('publisher')
Ошибка 5: Игнорирование N+1 в сериализаторах DRF
Неверно
class BookListView(ListAPIView):
queryset = Book.objects.all() # N+1 в сериализаторе!
serializer_class = BookSerializer
# Если BookSerializer включает вложенный PublisherSerializer
# — каждая книга делает дополнительный запрос к publisher
Верно
class BookListView(ListAPIView):
queryset = (Book.objects
.select_related('publisher')
.prefetch_related('genres'))
serializer_class = BookSerializer
Раздел 3: Ошибки транзакций
Ошибка 6: Неправильный порядок декораторов
Неверно
@api_view(['POST']) # ← api_view внешний
@transaction.atomic # ← atomic внутренний
def create_view(request):
...
Декораторы применяются снизу вверх. В этом порядке transaction.atomic оборачивает только логику DRF-view, но не весь HTTP-цикл.
Верно
@transaction.atomic # ← atomic внешний
@api_view(['POST'])
def create_view(request):
...
Рекомендуется использовать with transaction.atomic(): внутри view — это явнее и предсказуемее.
Ошибка 7: Забыть set_autocommit(True) в finally
Неверно
transaction.set_autocommit(False)
try:
...
transaction.commit()
return Response(...)
except Exception as e:
transaction.rollback()
return Response({'error': str(e)}, status=400)
# Если finally нет — следующий запрос к базе
# может не иметь автокоммита!
Верно
transaction.set_autocommit(False)
try:
...
transaction.commit()
return Response(...)
except Exception as e:
transaction.rollback()
return Response({'error': str(e)}, status=400)
finally:
transaction.set_autocommit(True) # обязательно!
Ошибка 8: on_commit вне блока atomic
Неверно
book = Book.objects.create(...)
# on_commit вне atomic выполняется немедленно
transaction.on_commit(lambda: send_email(book.id))
# Нет гарантии, что данные уже в базе
Верно
with transaction.atomic():
book = Book.objects.create(...)
# on_commit внутри atomic — ждёт COMMIT
transaction.on_commit(lambda: send_email(book.id))
# Email отправляется только после гарантированного коммита
Ошибка 9: Неверный импорт модуля transaction
# Неверно:
from django.db import transactions # нет такого модуля!
from django import transaction # неверный путь
# Верно:
from django.db import transaction