🐛 Типичные ошибки — Урок 29
⚡ Топ-7 ошибок урока
- StringRelatedField для записи — он read-only, используй SlugRelatedField или PrimaryKeyRelatedField
- Нет queryset= в SlugRelatedField/PrimaryKeyRelatedField — поле не может валидировать входные данные
- Нет many=True для M2M поля — сериализатор ожидает объект, а не список
- Нет .as_view() в urls.py для CBV — Django не умеет вызывать класс как view
- Нет partial=True при PATCH — все поля становятся обязательными
- N+1 проблема — нет select_related/prefetch_related при сериализации связей
- Response(status=201) вместо Response(status=status.HTTP_201_CREATED) — магические числа
1. StringRelatedField при попытке записи
Ошибка: Использование StringRelatedField для поля, в которое нужно записывать данные.
# НЕПРАВИЛЬНО — StringRelatedField только для чтения
class BookSerializer(serializers.ModelSerializer):
publisher = serializers.StringRelatedField()
# POST с {"publisher": "awesome-publisher"} → ОШИБКА
# StringRelatedField не поддерживает запись
# ПРАВИЛЬНО — SlugRelatedField для чтения/записи по slug
class BookSerializer(serializers.ModelSerializer):
publisher = serializers.SlugRelatedField(
slug_field='slug',
queryset=Publisher.objects.all()
)
# POST с {"publisher": "awesome-publisher"} — работает
Правило: StringRelatedField = только GET. Для POST/PUT/PATCH используйте SlugRelatedField или PrimaryKeyRelatedField.
2. Отсутствие queryset= в related fields
Ошибка:
AssertionError: Relational field must provide a 'queryset' argument
# НЕПРАВИЛЬНО
publisher = serializers.PrimaryKeyRelatedField()
# или
publisher = serializers.SlugRelatedField(slug_field='slug')
# ПРАВИЛЬНО
publisher = serializers.PrimaryKeyRelatedField(
queryset=Publisher.objects.all() # обязателен для записи
)
publisher = serializers.SlugRelatedField(
slug_field='slug',
queryset=Publisher.objects.all() # или read_only=True
)
Исключение: если поле только для чтения — используйте read_only=True вместо queryset=.
3. Нет many=True для ManyToManyField
Ошибка: Сериализатор ожидает один объект, а не список.
# НЕПРАВИЛЬНО — genres — это M2M, но many=True не указан
genres = serializers.PrimaryKeyRelatedField(
queryset=Genre.objects.all()
)
# POST с {"genres": [1, 2]} → ошибка или только первый жанр
# ПРАВИЛЬНО
genres = serializers.PrimaryKeyRelatedField(
queryset=Genre.objects.all(),
many=True # обязателен для ManyToManyField
)
4. Забыли .as_view() в urls.py
Ошибка:
TypeError: view must be a callable or a list/tuple in the case of include()
# НЕПРАВИЛЬНО — класс не является view-функцией
urlpatterns = [
path('books/', BookListCreateView, name='books'), # TypeError!
]
# ПРАВИЛЬНО
urlpatterns = [
path('books/', BookListCreateView.as_view(), name='books'),
]
5. PUT без partial=True принимает неполные данные
Ошибка: PATCH-запрос с одним полем падает с
400 Bad Request.
# НЕПРАВИЛЬНО — PATCH без partial=True требует все поля
def patch(self, request, pk):
book = self.get_object(pk)
serializer = BookSerializer(book, data=request.data)
# PATCH {"title": "New Title"} → ошибка: author is required
# ПРАВИЛЬНО
def patch(self, request, pk):
book = self.get_object(pk)
serializer = BookSerializer(book, data=request.data, partial=True)
# partial=True: только указанные поля обновляются
6. N+1 проблема при сериализации связей
Проблема: Для каждой книги в списке выполняется отдельный запрос к Publisher и каждому Genre.
# НЕПРАВИЛЬНО — N+1 запросов для 100 книг = 100+ запросов
def get(self, request):
books = Book.objects.all() # 1 запрос
# Для каждой книги: book.publisher (ещё 1 запрос) + book.genres (ещё 1 запрос)
serializer = BookSerializer(books, many=True)
return Response(serializer.data)
# ПРАВИЛЬНО — 3 запроса независимо от количества книг
def get(self, request):
books = Book.objects.select_related('publisher')\
.prefetch_related('genres')\
.all()
serializer = BookSerializer(books, many=True)
return Response(serializer.data)
7. Магические числа вместо статус-констант
Проблема: Код с числами
200, 201, 404 трудно читать и легко ошибиться.
# НЕПРАВИЛЬНО — магические числа
return Response(data, status=201)
return Response({'error': '...'}, status=404)
# ПРАВИЛЬНО — читаемые константы
from rest_framework import status
return Response(data, status=status.HTTP_201_CREATED)
return Response({'error': '...'}, status=status.HTTP_404_NOT_FOUND)
8. Дублирование try/except в каждом методе
Проблема: Нарушение принципа DRY — одинаковый код в get, put, delete.
# НЕПРАВИЛЬНО — дублирование
class BookDetailView(APIView):
def get(self, request, pk):
try:
book = Book.objects.get(pk=pk)
except Book.DoesNotExist:
return Response({'error': 'Not found'}, status=404)
...
def put(self, request, pk):
try:
book = Book.objects.get(pk=pk)
except Book.DoesNotExist:
return Response({'error': 'Not found'}, status=404)
...
# ПРАВИЛЬНО — вынести в get_object()
class BookDetailView(APIView):
def get_object(self, pk):
try:
return Book.objects.get(pk=pk)
except Book.DoesNotExist:
return None
def get(self, request, pk):
book = self.get_object(pk)
if book is None:
return Response({'error': 'Not found'}, status=status.HTTP_404_NOT_FOUND)
...