📖 Теория: ORM-инструменты практикума 6

⚡ Ключевые концепции практикума 6

  • aggregate(): одно значение для всего QuerySet — Model.objects.aggregate(total=Sum(...))
  • annotate(): вычисленное поле для каждой строки — .values('group').annotate(avg=Avg(...))
  • ExpressionWrapper: арифметика над полями с явным типом — ExpressionWrapper(F('a') - F('b'), output_field=DurationField())
  • order_by: order_by('-price') — убывание; несколько полей — order_by('country', 'city')
  • Срезы: [:5] — первые 5; order_by('-price')[:10] — топ-10 дорогих
  • Временные метки: timezone.now() - timezone.timedelta(days=30) + filter(date__gte=...)

1. Агрегация — aggregate() vs annotate()

Оба метода используют агрегирующие функции (Sum, Avg, Min, Max, Count), но работают по-разному:

Метод Что делает Возвращает
aggregate() Вычисляет одно значение для всего QuerySet Словарь {'key': value}
annotate() Добавляет вычисленное поле к каждому объекту/группе QuerySet с дополнительными полями
# aggregate — одно значение для всего QuerySet
from django.db.models import Sum, F
from store.models import Product

result = Product.objects.aggregate(
    total_value=Sum(F('price') * F('quantity'))
)
# result == {'total_value': Decimal('12345.67')}

# annotate — значение для каждой группы
from django.db.models import Avg
averages = Product.objects.values('category__name').annotate(
    average_price=Avg('price')
).order_by('category__name')
# QuerySet с полями 'category__name' и 'average_price'

2. F-объекты в агрегациях

F() позволяет ссылаться на значение поля модели прямо в запросе, не загружая объекты в Python. Особенно полезен в арифметических выражениях для Sum:

from django.db.models import Sum, F
# Итого = цена * количество — вычисляется на уровне SQL
total = Product.objects.aggregate(
    total_value=Sum(F('price') * F('quantity'))
)['total_value']

3. ExpressionWrapper и DurationField

Когда нужно вычесть одну дату из другой и получить разницу (тип timedelta), используется ExpressionWrapper с явным output_field:

from django.db.models import Avg, F, ExpressionWrapper, fields
from store.models import ProductDetail

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

print(f"Средняя продолжительность жизни: {average_lifetime.days} дней")
Важно: без output_field=fields.DurationField() Django не знает, какой тип у результата вычитания дат — запрос упадёт с FieldError.

4. order_by() — сортировка

Сортировка результатов QuerySet. Префикс - означает убывание:

# Убывание по цене
products = Product.objects.order_by('-price')

# Несколько полей: сначала по стране, потом по городу
addresses = Address.objects.order_by('country', 'city')

# Сортировка по аннотированному полю
from django.db.models import Count
orders = Order.objects.annotate(
    item_count=Count('order_items')
).order_by('-item_count')

5. Временные метки и timezone

Для работы с датами в Django нужно использовать django.utils.timezone, чтобы корректно учитывать часовые пояса:

from django.utils import timezone
from store.models import Order

# Дата 30 дней назад (timezone-aware)
one_month_ago = timezone.now() - timezone.timedelta(days=30)

# Заказы за последний месяц
recent_orders = Order.objects.filter(order_date__gte=one_month_ago)
Совет: всегда используйте timezone.now() вместо datetime.now() — последний возвращает naive datetime без timezone, что может вызвать предупреждения или ошибки при USE_TZ = True.

6. Срезы QuerySet

Срезы работают как Python-срезы и транслируются в SQL LIMIT/OFFSET:

from store.models import Product

# Первые 5 продуктов (LIMIT 5)
first_five = Product.objects.all()[:5]

# Топ-10 дорогих продуктов (ORDER BY price DESC LIMIT 10)
top_ten = Product.objects.order_by('-price')[:10]
Ограничение: нельзя применять отрицательные индексы к QuerySet ([-1] не работает). Для последнего элемента используйте .order_by('-id').first().

7. Связанные поля в lookup (__)

В практикуме часто используются двойные подчёркивания для перехода по связям:

# Группировка по имени категории (через ForeignKey)
Product.objects.values('category__name').annotate(avg=Avg('price'))

# Группировка по клиенту (через ForeignKey в Order)
Order.objects.values('customer__first_name', 'customer__last_name').annotate(
    order_count=Count('id')
)

# Доступ к полям связанной модели ProductDetail
Product.objects.values('category__name').annotate(
    total_weight=Sum(F('details__weight') * F('quantity'))
)
← К оглавлению урока    Справочник →