🐛 Типичные ошибки — Урок 36

← К оглавлению урока

⚡ Топ-6 ошибок урока

  1. Не подключить SoftDeleteManager → удалённые записи видны в API
  2. Вызвать super().delete() в переопределённом методе → физическое удаление
  3. Забыть select_related → N+1 запросов на продакшне
  4. Использовать select_related для ManyToMany → исключение или неверный результат
  5. Неправильный порядок декораторов @transaction.atomic и @api_view
  6. Забыть 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