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

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

⚡ Топ-6 ошибок

  • aggregate вместо annotate — нужны данные по каждому объекту, а не одно итоговое
  • Нет output_field в ExpressionWrapperFieldError
  • Срез до filter()TypeError: Cannot filter a query once a slice has been taken
  • Нет 'rest_framework' в INSTALLED_APPS → шаблоны DRF не найдены
  • Не вызван is_valid() перед save() → данные не прошли валидацию
  • OuterRef без SubqueryValueError

Ошибка 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.