🐛 Типичные ошибки практикума 4

Что идёт не так при работе с ORM-запросами и DRF

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

  1. safe=False забытJsonResponse([...]) без safe=False → TypeError
  2. many=True забытSerializer(queryset) без many=True → неверный результат
  3. ExtractWeekDay-индексы — понедельник=2, не 1 (как в Python)
  4. URL-порядокtags/create/ после tags/<int:tag_id>/ → "create" не int → 404
  5. Импорт Avg — пишут from django import AVG, правильно: from django.db.models import Avg

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

Ошибка 1: safe=False пропущен в JsonResponse

Неверно

return JsonResponse(
    serialized_data.data,  # список!
    status=status.HTTP_200_OK
    # safe=False не передан
)

TypeError: In order to allow non-dict objects to be serialized set the safe parameter to False.

Верно

return JsonResponse(
    serialized_data.data,
    status=status.HTTP_200_OK,
    safe=False  # обязательно для списков
)
safe=False нужен ВСЕГДА, когда serialized_data.data — список (результат many=True). Для одного объекта (словарь) — не нужен, но ставить не вредно.

Ошибка 2: many=True пропущен при сериализации QuerySet

Неверно

all_projects = Project.objects.all()
# QuerySet — нужен many=True!
serialized = AllProjectsSerializer(all_projects)
# .data вернёт данные ПЕРВОГО объекта

Верно

all_projects = Project.objects.all()
serialized = AllProjectsSerializer(
    all_projects, many=True  # QuerySet!
)
# .data — список словарей

Ошибка 3: Неверная нумерация дней в ExtractWeekDay

Неверно

# Думаем, что понедельник=1 (как в Python)
files = ProjectFile.objects.annotate(
    weekday=ExtractWeekDay('created_at')
).filter(weekday=1)  # это ВОСКРЕСЕНЬЕ!

Верно

# ExtractWeekDay (ISO): воскресенье=1, понедельник=2
files = ProjectFile.objects.annotate(
    weekday=ExtractWeekDay('created_at')
).filter(weekday=2)  # понедельник

# Карта дней:
# 1=Вс, 2=Пн, 3=Вт, 4=Ср, 5=Чт, 6=Пт, 7=Сб
⚠️ Проверить по документации: нумерация дней ExtractWeekDay может зависеть от конкретной СУБД (PostgreSQL, MySQL, SQLite). В Django docs уточните для вашей СУБД.

Ошибка 4: URL-маршрут tags/create/ стоит после <int:tag_id>

Неверно — 404 на /tags/create/

urlpatterns = [
    path('tags/', get_all_tags),
    path('tags/<int:tag_id>/', get_tag_by_id),   # первым!
    path('tags/create/', create_new_tag),           # никогда не достигнет
    ...
]

Django попытается преобразовать "create" в int → ValueError → 404

Верно — литеральный путь раньше

urlpatterns = [
    path('tags/', get_all_tags),
    path('tags/create/', create_new_tag),          # сначала!
    path('tags/<int:tag_id>/', get_tag_by_id),    # потом
    path('tags/<int:tag_id>/update/', update_required_tag),
    path('tags/<int:tag_id>/delete/', delete_required_tag),
]

Ошибка 5: Неверный импорт Avg

Неверно

# В лекции написано "импортируйте AVG из django"
from django import AVG             # ошибка!
from django.db import AVG          # ошибка!
from django.db.models import AVG   # ошибка (AVG != Avg)

Верно

from django.db.models import Avg   # правильно!
# Класс называется Avg, а не AVG

Project.objects.annotate(avg_tasks=Avg('tasks__id'))

Ошибка 6: aggregate() vs annotate()

Путаница

# Хотим количество файлов ДЛЯ КАЖДОГО проекта
# Неверно: aggregate() даёт ОДНО значение для всего QS
result = Project.objects.aggregate(
    num_files=Count('project_files')
)
# result = {'num_files': 42} — общее число файлов, не по проекту!

Верно

# annotate() добавляет поле к КАЖДОМУ объекту
projects = Project.objects.annotate(
    num_files=Count('project_files')
)
for p in projects:
    print(p.name, p.num_files)  # отдельно для каждого!
Правило: aggregate() → один словарь для всего QuerySet. annotate() → добавляет поле к каждому объекту в QuerySet.

Ошибка 7: Не добавлен rest_framework в INSTALLED_APPS

# Симптом: ImportError или AttributeError при использовании DRF

# settings.py — обязательно добавить:
INSTALLED_APPS = [
    'django.contrib.admin',
    ...
    'rest_framework',  # НЕ ЗАБЫТЬ!
    'management_app',
]
Без 'rest_framework' в INSTALLED_APPS декоратор @api_view может работать, но DRF-шаблоны API Browsable Interface, рендереры и permissions не будут доступны.

Ошибка 8: Пагинатор загружает весь QuerySet в память

Распространённое заблуждение

# Многие думают, что list(tasks) до Paginator — норма
tasks = list(Task.objects.all())   # загружает ВСЁ в память!
paginator = Paginator(tasks, 10)

Верно — передавать QuerySet, не список

# Paginator сам сделает LIMIT/OFFSET в SQL
tasks = Task.objects.all()         # QuerySet, не list!
paginator = Paginator(tasks, 10)
page = paginator.get_page(1)       # SQL: LIMIT 10 OFFSET 0

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