🐛 Типичные ошибки практикума 4
Что идёт не так при работе с ORM-запросами и DRF
⚡ Топ-5 ошибок
- safe=False забыт —
JsonResponse([...])без safe=False → TypeError - many=True забыт —
Serializer(queryset)без many=True → неверный результат - ExtractWeekDay-индексы — понедельник=2, не 1 (как в Python)
- URL-порядок —
tags/create/послеtags/<int:tag_id>/→ "create" не int → 404 - Импорт 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