🐛 Частые ошибки DRF-блока
⚡ Топ-3 ошибки
- AttributeError: queryset not set — забыли указать queryset или get_queryset() в Generic View / ViewSet.
- N+1 запросов — доступ к связанным объектам без select_related/prefetch_related.
- Soft Delete не работает — не переопределили SoftDeleteManager или используете
Model._default_manager.
1. queryset не задан
Ошибка: AssertionError: 'BookListView' should either include a queryset attribute, or override the get_queryset() method.
Причина: Класс Generic View / ViewSet не имеет ни атрибута queryset, ни метода get_queryset().
# Неверно
class BookListView(ListAPIView):
serializer_class = BookSerializer
# queryset не указан!
# Верно — атрибут
class BookListView(ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# Верно — метод
class BookListView(ListAPIView):
serializer_class = BookSerializer
def get_queryset(self):
return Book.objects.all()
2. Проблема N+1 запросов
Симптом: Запрос работает, но при 100 объектах выполняется 101 SQL-запрос. Логирование показывает повторяющиеся SELECT.
# Неверно — N+1
books = Book.objects.all()
for book in books:
print(book.publisher.name) # каждый раз отдельный запрос!
# Верно — select_related для FK
books = Book.objects.select_related('publisher').all()
# Верно — prefetch_related для M2M
books = Book.objects.prefetch_related('genres').all()
3. Soft Delete — записи всё равно видны
Симптом: После вызова obj.delete() объект всё равно возвращается в API.
# Неверно — нет SoftDeleteManager
class Category(models.Model):
is_deleted = models.BooleanField(default=False)
def delete(self): self.is_deleted = True; self.save()
# Category.objects.all() вернёт и удалённые!
# Верно — менеджер фильтрует автоматически
class SoftDeleteManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_deleted=False)
class Category(models.Model):
is_deleted = models.BooleanField(default=False)
objects = SoftDeleteManager()
def delete(self): self.is_deleted = True; self.save()
4. @action не доступен в URL
Ошибка: GET /genres/statistic/ возвращает 404.
# Неверно — ViewSet не подключён через Router
urlpatterns = [
path('genres/', GenreViewSet.as_view({'get': 'list'})),
# @action не будет зарегистрирован!
]
# Верно — используйте Router
router = DefaultRouter()
router.register(r'genres', GenreViewSet)
urlpatterns = [path('', include(router.urls))]
# GET /genres/statistic/ теперь работает
5. Пагинация не возвращает метаданные
Симптом: Ответ API — просто список объектов, без count, next, previous.
# Неверно — pagination_class не указан
class BookListView(ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# нет pagination_class
# Верно — указать класс пагинации
class BookListView(ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
pagination_class = PageNumberPagination
# Или глобально в settings.py:
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
}
6. Транзакция не откатывается при ошибке
Симптом: При ошибке во второй операции первая операция сохраняется в БД (частичные данные).
# Неверно — нет атомарности
def create_task(request):
category = Category.objects.create(name='Work')
task = Task.objects.create(category=category, title='') # ошибка!
# category уже сохранена в БД!
# Верно — transaction.atomic откатит оба
def create_task(request):
with transaction.atomic():
category = Category.objects.create(name='Work')
task = Task.objects.create(category=category, title='')
# если Task.create() бросит исключение — Category тоже откатится
7. Логирование SQL не работает
Симптом: LOGGING настроен, но SQL-запросы не выводятся.
# Частая причина 1: DEBUG = False
# SQL-логирование работает только при DEBUG = True
DEBUG = True # убедитесь, что это True в dev-окружении
# Частая причина 2: неверный ключ логгера
# Неверно
'loggers': {'django.db': {'level': 'DEBUG'}} # неполный ключ
# Верно
'loggers': {'django.db.backends': {'level': 'DEBUG'}}
8. DjangoFilterBackend не подключён
Ошибка: ImproperlyConfigured: Specified DEFAULT_FILTER_BACKENDS is not available или фильтрация по полям не работает.
# Неверно — пакет не установлен / не добавлен в INSTALLED_APPS
filter_backends = [DjangoFilterBackend] # ImportError
# Верно
# 1. Установить: pip install django-filter
# 2. settings.py
INSTALLED_APPS = ['django_filters', ...]
# 3. Импорт
from django_filters.rest_framework import DjangoFilterBackend
9. CursorPagination — ошибка поля для cursor
Ошибка: CursorPagination requires a 'created' field in the queryset ordering.
# Неверно — поле 'created' не существует в модели
class MyCursorPagination(CursorPagination):
ordering = 'created' # по умолчанию CursorPagination ищет 'created'
# Верно — явно указать существующее поле
class MyCursorPagination(CursorPagination):
page_size = 10
ordering = '-created_at' # поле, которое реально есть в модели