🐛 Типичные ошибки: Практикум 6
⚡ Топ-5 ошибок
- ExpressionWrapper без output_field —
FieldError: Expression contains mixed types. Добавьoutput_field=fields.DurationField(). - aggregate() вместо annotate() для группировки —
aggregate()возвращает одно значение, не группы. Для GROUP BY нуженvalues().annotate(). - datetime.now() вместо timezone.now() — RuntimeWarning при
USE_TZ=True. Используйfrom django.utils import timezone; timezone.now(). - Срез после filter() не работает — срез закрывает QuerySet. Сначала все фильтры, срез — последним.
- Попытка взять .days у None — если нет записей с датами,
aggregate(...)вернётNone. Нужна проверка перед.days.
Ошибка 1: ExpressionWrapper без output_field
Ошибка:
# НЕПРАВИЛЬНО — Django не знает тип результата вычитания дат
average_lifetime = ProductDetail.objects.annotate(
lifetime=F('expiration_date') - F('manufacturing_date')
).aggregate(avg=Avg('lifetime'))['avg']
# Вывод ошибки:
# django.core.exceptions.FieldError: Expression contains mixed types:
# DateField, DateField. You must set output_field.
Решение:
# ПРАВИЛЬНО — явный output_field
from django.db.models import ExpressionWrapper, fields
average_lifetime = ProductDetail.objects.annotate(
lifetime=ExpressionWrapper(
F('expiration_date') - F('manufacturing_date'),
output_field=fields.DurationField() # обязательно!
)
).aggregate(avg=Avg('lifetime'))['avg']
Ошибка 2: Путаница aggregate() и annotate()
Ошибка:
# НЕПРАВИЛЬНО — ожидаем список по категориям, но получаем одно значение
result = Product.objects.aggregate(
average_price=Avg('price')
)
# result == {'average_price': 350.0} — одно число для всех продуктов!
Решение:
# ПРАВИЛЬНО — values() + annotate() для группировки
result = Product.objects.values('category__name').annotate(
average_price=Avg('price')
).order_by('category__name')
# QuerySet с записями для каждой категории
Правило: если нужно одно значение для всей таблицы —
aggregate(). Если нужны значения для каждой группы — values().annotate().
Ошибка 3: datetime.now() вместо timezone.now()
Ошибка:
# НЕПРАВИЛЬНО — naive datetime
from datetime import datetime, timedelta
one_month_ago = datetime.now() - timedelta(days=30)
orders = Order.objects.filter(order_date__gte=one_month_ago)
# RuntimeWarning: DateTimeField Order.order_date received a naive datetime
# while time zone support is active.
Решение:
# ПРАВИЛЬНО — timezone-aware datetime
from django.utils import timezone
one_month_ago = timezone.now() - timezone.timedelta(days=30)
orders = Order.objects.filter(order_date__gte=one_month_ago)
Ошибка 4: Модификация QuerySet после среза
Ошибка:
# НЕПРАВИЛЬНО — нельзя применять filter() после среза
products = Product.objects.all()[:5]
active_products = products.filter(is_active=True)
# AssertionError: Cannot filter a query once a slice has been taken.
Решение:
# ПРАВИЛЬНО — сначала все модификации, срез последним
products = Product.objects.filter(is_active=True)[:5]
# Или с сортировкой:
products = Product.objects.filter(is_active=True).order_by('-price')[:5]
Ошибка 5: AttributeError на None из aggregate()
Ошибка:
# НЕПРАВИЛЬНО — если нет записей, average_lifetime == None
average_lifetime = ProductDetail.objects.annotate(
lifetime=ExpressionWrapper(...)
).aggregate(average_lifetime=Avg('lifetime'))['average_lifetime']
print(f"{average_lifetime.days} дней")
# AttributeError: 'NoneType' object has no attribute 'days'
Решение:
# ПРАВИЛЬНО — проверка на None
average_lifetime = ProductDetail.objects.annotate(
lifetime=ExpressionWrapper(
F('expiration_date') - F('manufacturing_date'),
output_field=fields.DurationField()
)
).aggregate(average_lifetime=Avg('lifetime'))['average_lifetime']
if average_lifetime is not None:
print(f"Средняя продолжительность жизни: {average_lifetime.days} дней")
else:
print("Нет данных о продуктах")
Ошибка 6: Неверное имя related_name при обращении к ProductDetail
Ошибка:
# НЕПРАВИЛЬНО — используем неверный related_name
Product.objects.values('category__name').annotate(
total_weight=Sum(F('productdetail__weight') * F('quantity'))
)
# FieldError: Cannot resolve keyword 'productdetail' into field.
Решение:
# ПРАВИЛЬНО — используем фактический related_name модели
# (в данном практикуме: 'details')
Product.objects.values('category__name').annotate(
total_weight=Sum(F('details__weight') * F('quantity'))
)
Проверяйте
related_name в определении модели. Если он не задан явно, Django использует modelname_set (lowercase имени модели + _set). В данном практикуме — задан как details.
Ошибка 7: Загрузка всех объектов для среза
Ошибка:
# НЕПРАВИЛЬНО — загружаем ВСЕ объекты в память
products = list(Product.objects.all())
first_five = products[:5] # срез питоновского списка — уже после загрузки всех
Решение:
# ПРАВИЛЬНО — срез на уровне QuerySet (SQL LIMIT)
first_five = Product.objects.all()[:5] # SQL: SELECT ... LIMIT 5
Django QuerySet ленивый (lazy). Срез
[:5] применённый к QuerySet транслируется в SQL LIMIT 5. Вызов list() перед срезом сначала загружает все строки в Python, потом берёт первые 5 — очень неэффективно на больших таблицах.