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

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

  1. ExpressionWrapper без output_fieldFieldError: Expression contains mixed types. Добавь output_field=fields.DurationField().
  2. aggregate() вместо annotate() для группировкиaggregate() возвращает одно значение, не группы. Для GROUP BY нужен values().annotate().
  3. datetime.now() вместо timezone.now() — RuntimeWarning при USE_TZ=True. Используй from django.utils import timezone; timezone.now().
  4. Срез после filter() не работает — срез закрывает QuerySet. Сначала все фильтры, срез — последним.
  5. Попытка взять .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 — очень неэффективно на больших таблицах.
← К оглавлению урока    Ресурсы →