⚖️ Старый vs Новый: устаревшие паттерны практикума 6

⚡ Главные изменения Django 3.x → 5.x

  • Временные метки: datetime.now() (naive) → timezone.now() (aware)
  • Разница дат без ExpressionWrapper работала в старых Django/PostgreSQL — теперь требует явного output_field=DurationField()
  • Устаревший timedelta из Python → timezone.timedelta из Django для однообразия

1. Временные метки: datetime vs timezone

Из лекции (старый подход)

# Использование datetime напрямую
from datetime import datetime, timedelta
from store.models import Order

one_month_ago = datetime.now() - timedelta(days=30)
recent_orders = Order.objects.filter(
    order_date__gte=one_month_ago
)

Проблема: datetime.now() возвращает naive datetime. При USE_TZ = True (по умолчанию в Django 4+) Django выдаст RuntimeWarning и сравнение может быть некорректным.

Современный подход (Django 5.x)

# Корректное использование timezone-aware datetime
from django.utils import timezone
from store.models import Order

one_month_ago = timezone.now() - timezone.timedelta(days=30)
recent_orders = Order.objects.filter(
    order_date__gte=one_month_ago
)

timezone.now() возвращает timezone-aware datetime, совместимый с USE_TZ = True.

2. ExpressionWrapper — поведение без output_field

Из лекции (потенциально проблемный)

# В некоторых версиях Django/PostgreSQL
# вычитание DateField могло работать без ExpressionWrapper
# (зависело от backend)
average_lifetime = ProductDetail.objects.annotate(
    lifetime=F('expiration_date') - F('manufacturing_date')
).aggregate(avg=Avg('lifetime'))['avg']

На SQLite и в новых версиях Django это вызывает FieldError: Expression contains mixed types: DateField, DateField.

Правильный подход (Django 5.x)

from django.db.models import (
    Avg, F, ExpressionWrapper, fields
)

average_lifetime = ProductDetail.objects.annotate(
    lifetime=ExpressionWrapper(
        F('expiration_date') - F('manufacturing_date'),
        output_field=fields.DurationField()  # обязательно!
    )
).aggregate(avg=Avg('lifetime'))['avg']

print(f"{average_lifetime.days} дней")

3. aggregate — старый стиль именования результата

Менее явный вариант

# aggregate без явного alias
# Django автоматически назовёт ключ 'price__sum'
result = Product.objects.aggregate(Sum('price'))
# result == {'price__sum': Decimal('12345')}

# Менее читаемо — нужно знать правило именования

Явный alias (рекомендуется)

# Явное именование ключа результата
result = Product.objects.aggregate(
    total_price=Sum('price')
)
# result == {'total_price': Decimal('12345')}

# Читаемо, self-documenting

4. Итерация по QuerySet — загрузка в список

Старый стиль из некоторых примеров

# Преобразование всего QuerySet в список
products = list(Product.objects.all())
for p in products[:5]:
    print(p.name)

Загружает ВСЕ записи в память, затем берёт 5 — неэффективно.

Правильный подход

# Срез применяется на уровне SQL (LIMIT)
products = Product.objects.all()[:5]
for p in products:
    print(p.name)
# SQL: SELECT ... FROM product LIMIT 5
Замечание: Код из лекции Django Practicum 6 в целом современный и совместим с Django 5.x. Основное, что стоит помнить: всегда используйте timezone.now() вместо datetime.now() и явно указывайте output_field в ExpressionWrapper.
← К оглавлению урока    Задания →