⚖️ Старый 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.