🐛 Частые ошибки DRF-блока
⚡ Топ-3 ошибки
- validated_data до is_valid() — AssertionError. Всегда вызывай is_valid() первым.
- Нет many=True при сериализации QuerySet — сериализатор ожидает один объект, а не список.
- Не добавили 'rest_framework' в INSTALLED_APPS — Browsable API не работает, статические файлы DRF не подключаются.
1. Доступ к validated_data без is_valid()
Проблема
serializer = BookSerializer(data=request.data)
# AssertionError: You must call `.is_valid()` before accessing `.validated_data`.
book = Book.objects.create(**serializer.validated_data)
Решение
serializer = BookSerializer(data=request.data)
if serializer.is_valid():
book = serializer.save() # вызывает create() внутри
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
2. Забыли many=True при сериализации QuerySet
Проблема
books = Book.objects.all()
# Ошибка — сериализует только один объект, а не список
serializer = BookSerializer(books)
return Response(serializer.data)
Решение
books = Book.objects.all()
serializer = BookSerializer(books, many=True) # many=True для QuerySet/list
return Response(serializer.data)
3. 'rest_framework' не в INSTALLED_APPS
Проблема
Забыли добавить DRF в INSTALLED_APPS. Browsable API не отображается, статика DRF (CSS/JS) не подключается.
Решение
# settings.py
INSTALLED_APPS = [
...
'rest_framework', # обязательно!
]
4. Неверный HTTP-статус при создании объекта
Проблема
@api_view(['POST'])
def book_create(request):
serializer = BookSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data) # возвращает 200 вместо 201
Решение
return Response(serializer.data, status=status.HTTP_201_CREATED)
При создании ресурса стандарт REST требует 201 Created.
5. aggregate с annotate вместе без values()
Проблема
# Ошибка в семантике: annotate без values группирует по pk каждого объекта
author_counts = Book.objects.annotate(cnt=Count('id'))
# Выдаёт каждую книгу с cnt=1, а не количество книг каждого автора
Решение
# Сначала values() для группировки, затем annotate
author_counts = Book.objects.values('author').annotate(cnt=Count('id'))
# [{'author': 'Orwell', 'cnt': 3}, ...]
6. Нет обработки DoesNotExist в detail view
Проблема
@api_view(['GET'])
def book_detail(request, pk):
book = Book.objects.get(pk=pk) # Book.DoesNotExist -> 500 Internal Server Error
serializer = BookSerializer(book)
return Response(serializer.data)
Решение
@api_view(['GET'])
def book_detail(request, pk):
try:
book = Book.objects.get(pk=pk)
except Book.DoesNotExist:
return Response({'error': 'Book not found'}, status=status.HTTP_404_NOT_FOUND)
serializer = BookSerializer(book)
return Response(serializer.data)
Generic views делают это автоматически через get_object_or_404.
7. Несохранённые изменения: save() забыт после update()
Проблема
@api_view(['PUT'])
def book_update(request, pk):
book = Book.objects.get(pk=pk)
serializer = BookSerializer(book, data=request.data)
if serializer.is_valid():
# Забыли serializer.save() !
return Response(serializer.data) # данные не сохранены в БД
Решение
if serializer.is_valid():
serializer.save() # обязательно!
return Response(serializer.data)
8. Срез QuerySet после filter применяется в Python, не в SQL
Проблема
# Получить 10 последних книг автора — МЕДЛЕННО
books = list(Book.objects.filter(author="Orwell"))
last_10 = books[-10:] # сначала грузит все книги в память!
Решение
# SQL-уровень — эффективно
last_10 = Book.objects.filter(author="Orwell").order_by('-id')[:10]