🐛 Типичные ошибки — Урок 26
⚡ Топ-6 ошибок
- aggregate вместо annotate — нужны данные по каждому объекту, а не одно итоговое
- Нет output_field в ExpressionWrapper →
FieldError - Срез до filter() →
TypeError: Cannot filter a query once a slice has been taken - Нет
'rest_framework'в INSTALLED_APPS → шаблоны DRF не найдены - Не вызван
is_valid()передsave()→ данные не прошли валидацию - OuterRef без Subquery →
ValueError
Ошибка 1: Путаница aggregate() vs annotate()
Неправильно
# Хотим количество книг по каждому автору, но используем aggregate
result = Book.objects.aggregate(count=Count('id'))
# Возвращает {'count': 42} — всего книг, не по авторам!
Правильно
# annotate() + values() для группировки
result = Book.objects.values('author').annotate(
book_count=Count('id')
)
# Возвращает [{'author': 1, 'book_count': 5}, ...]
Правило: aggregate() — одно значение для всей таблицы; annotate() — значение для каждой группы/объекта.
Ошибка 2: Отсутствует output_field в ExpressionWrapper
Неправильно
# FieldError: Cannot resolve expression type
books = Book.objects.annotate(
diff=ExpressionWrapper(
F('price') - F('discounted_price')
# output_field не указан!
)
)
Правильно
from django.db.models import ExpressionWrapper, F, fields
books = Book.objects.annotate(
diff=ExpressionWrapper(
F('price') - F('discounted_price'),
output_field=fields.DecimalField(max_digits=10, decimal_places=2)
)
)
Правило: output_field обязателен когда Django не может автоматически определить тип результата арифметического выражения.
Ошибка 3: Срез перед filter()
Неправильно
# TypeError: Cannot filter a query once a slice has been taken.
books = Book.objects.all()[:10].filter(is_bestseller=True)
Правильно
# Сначала filter, потом срез
books = Book.objects.filter(is_bestseller=True)[:10]
Правило: срезы всегда применяются последними, после всех filter(), order_by(), annotate().
Ошибка 4: Subquery возвращает несколько значений
Неправильно
# django.core.exceptions.FieldError: Subquery must return only one column.
subquery = Book.objects.filter(
author=OuterRef('author')
).values('title', 'price') # два поля — ошибка!
books = Book.objects.annotate(sub=Subquery(subquery))
Правильно
# Subquery должен возвращать ОДНО поле
subquery = Book.objects.filter(
author=OuterRef('author')
).values('author').annotate(
min_price=Min('price')
).values('min_price') # только одно поле
books = Book.objects.annotate(min_p=Subquery(subquery))
Правило: Subquery должен возвращать ровно одно поле — завершайте цепочку .values('одно_поле').
Ошибка 5: DRF не добавлен в INSTALLED_APPS
Симптом
# TemplateDoesNotExist: rest_framework/api.html
# или ImportError при использовании Browsable API
Правильно
# settings.py
INSTALLED_APPS = [
...
'rest_framework', # обязательно добавить!
]
Правило: после pip install djangorestframework нужно добавить 'rest_framework' в INSTALLED_APPS. Без этого DRF работает частично, но Browsable API и некоторые функции недоступны.
Ошибка 6: save() без is_valid()
Неправильно
@api_view(['POST'])
def task_create(request):
serializer = TaskSerializer(data=request.data)
serializer.save() # AssertionError: You must call is_valid() before calling save()
return Response(serializer.data)
Правильно
@api_view(['POST'])
def task_create(request):
serializer = TaskSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Правило: всегда вызывайте is_valid() перед save(). При невалидных данных возвращайте 400 с serializer.errors.
Ошибка 7: OuterRef вне Subquery
Неправильно
# ValueError: This queryset contains a reference to an outer query...
books = Book.objects.filter(
author=OuterRef('author') # OuterRef без Subquery — ошибка
)
Правильно
# OuterRef используется только внутри Subquery
inner = Book.objects.filter(
author=OuterRef('author')
).values('author').annotate(cnt=Count('id')).values('cnt')
books = Book.objects.annotate(
author_book_count=Subquery(inner)
)
Правило: OuterRef имеет смысл только внутри Subquery — это ссылка на поле родительского запроса, поэтому должен существовать «родительский» контекст.
Ошибка 8: Неправильная сортировка по связанному полю
Неправильно
# Сортировка по id автора (числу), а не имени
books = Book.objects.order_by('author')
# result: порядок определяется первичным ключом Author
Правильно
# Сортировка по имени автора через двойное подчёркивание
books = Book.objects.order_by('author__name')
# result: алфавитный порядок по имени
Правило: для сортировки по полю связанной модели всегда используйте related_field__field_name.