✅ Решения практикума 6
⚡ Ключевые паттерны решений
# Задача 1: Sum(F*F)
Product.objects.aggregate(total_value=Sum(F('price') * F('quantity')))['total_value']
# Задача 2: group by category
Product.objects.values('category__name').annotate(average_price=Avg('price')).order_by('category__name')
# Задача 3: Min/Max
Product.objects.aggregate(min_price=Min('price'), max_price=Max('price'))
# Задача 7: ExpressionWrapper
ProductDetail.objects.annotate(lifetime=ExpressionWrapper(F('expiration_date')-F('manufacturing_date'), output_field=fields.DurationField())).aggregate(avg=Avg('lifetime'))['avg'].days
# Задача 13: timezone
Order.objects.filter(order_date__gte=timezone.now()-timezone.timedelta(days=30))
# Задача 15: slice
Product.objects.order_by('-price')[:10]
Агрегация
Задача 1: Общая стоимость всех продуктов
Идея: aggregate() возвращает одно значение для всего QuerySet. Перемножаем price и quantity с помощью F() — всё вычисляется на уровне SQL.
from django.db.models import Sum, F
from store.models import Product
total_value = Product.objects.aggregate(
total_value=Sum(F('price') * F('quantity'))
)['total_value']
print(f"Общая стоимость всех продуктов: {total_value}")
Пояснение: F('price') * F('quantity') создаёт выражение на уровне SQL. Sum() суммирует результаты по всем строкам. aggregate() выполняет запрос и возвращает словарь — берём значение по ключу 'total_value'.
Задача 2: Средняя цена продуктов по категориям
Идея: Паттерн GROUP BY в Django: values('group_field').annotate(aggregate_func).
from django.db.models import Avg
from store.models import Product, Category
average_price_by_category = Product.objects.values('category__name').annotate(
average_price=Avg('price')
).order_by('category__name')
for entry in average_price_by_category:
print(f"Категория: {entry['category__name']}, Средняя цена: {entry['average_price']}")
Пояснение: values('category__name') — группировка по имени категории (через ForeignKey). annotate(average_price=Avg('price')) — добавляет вычисленное поле к каждой группе. Результат — QuerySet словарей с ключами category__name и average_price.
Задача 3: Самый дешевый и самый дорогой продукт
Идея: Min() и Max() можно вызвать отдельно или совместить в одном aggregate().
from django.db.models import Min, Max
from store.models import Product
cheapest_product = Product.objects.aggregate(
min_price=Min('price')
)['min_price']
most_expensive_product = Product.objects.aggregate(
max_price=Max('price')
)['max_price']
print(f"Самый дешевый продукт: {cheapest_product}")
print(f"Самый дорогой продукт: {most_expensive_product}")
Пояснение: Два отдельных вызова aggregate() — каждый делает SQL-запрос. Можно объединить: Product.objects.aggregate(min_price=Min('price'), max_price=Max('price')) — один запрос.
Задача 4: Заказы — количество и сумма по клиентам
Идея: Группировка по нескольким полям клиента, два аннотируемых поля одновременно.
from django.db.models import Count, Sum, F
from store.models import Order, Customer, OrderItem
orders_summary_by_customer = Order.objects.values(
'customer__first_name',
'customer__last_name'
).annotate(
order_count=Count('id'),
total_spent=Sum(F('order_items__price') * F('order_items__quantity'))
).order_by('customer__last_name')
for entry in orders_summary_by_customer:
print(
f"Клиент: {entry['customer__first_name']} {entry['customer__last_name']}, "
f"Заказов: {entry['order_count']}, "
f"Общая сумма: {entry['total_spent']}"
)
Пояснение: values() с несколькими полями группирует по комбинации значений. Count('id') считает строки в группе. Sum(F('order_items__price') * F('order_items__quantity')) — сумма по всем позициям заказа через связь order_items.
Задача 5: Общий вес продуктов по категориям
Идея: Доступ к данным из связанной модели ProductDetail через двойное подчёркивание (details__weight).
from django.db.models import Sum, F
from store.models import Product
total_weight_by_category = Product.objects.values('category__name').annotate(
total_weight=Sum(F('details__weight') * F('quantity'))
).order_by('category__name')
for entry in total_weight_by_category:
print(f"Категория: {entry['category__name']}, Общий вес: {entry['total_weight']}")
Пояснение: details__weight — обращение к полю weight в модели ProductDetail через RelatedName details. Умножаем на quantity из самого Product.
Задача 6: Количество продуктов у поставщиков
from django.db.models import Count
from store.models import Product
products_count_by_supplier = Product.objects.values('supplier__name').annotate(
product_count=Count('id')
).order_by('supplier__name')
for entry in products_count_by_supplier:
print(f"Поставщик: {entry['supplier__name']}, Количество продуктов: {entry['product_count']}")
Пояснение: Паттерн аналогичен задаче 2. Count('id') подсчитывает строки в каждой группе поставщиков.
Задача 7: Средняя продолжительность жизни продуктов
Идея: Вычитание двух DateField требует ExpressionWrapper с явным output_field=fields.DurationField().
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} дней")
Пояснение: Шаги: 1) annotate(lifetime=...) — создаём поле разницы дат для каждой строки; 2) aggregate(Avg('lifetime')) — вычисляем среднее по всем строкам; 3) результат — объект timedelta, у которого берём .days. ExpressionWrapper необходим, чтобы Django знал тип результата и мог правильно строить SQL.
Задача 8: Общая сумма заказов для каждого клиента
from django.db.models import Sum, F
from store.models import Order, OrderItem
total_spent_by_customer = Order.objects.values(
'customer__first_name',
'customer__last_name'
).annotate(
total_spent=Sum(F('order_items__price') * F('order_items__quantity'))
).order_by('customer__last_name')
for entry in total_spent_by_customer:
print(
f"Клиент: {entry['customer__first_name']} {entry['customer__last_name']}, "
f"Общая сумма заказов: {entry['total_spent']}"
)
Пояснение: Похоже на задачу 4, но без Count — только итоговая сумма через связь order_items.
Сортировка (order_by)
Задача 9: Сортировка продуктов по цене
from store.models import Product
# Сортировка по цене по убыванию
products_descending = Product.objects.order_by('-price')
print("Продукты по убыванию цены:")
for product in products_descending:
print(f"{product.name}: {product.price}")
Пояснение: order_by('-price') — префикс - означает DESC (убывание). SQL: ORDER BY price DESC.
Задача 10: Сортировка заказов по общей стоимости
from django.db.models import Sum, F
from store.models import Order
# Сортировка заказов по общей стоимости
orders_by_total = Order.objects.annotate(
total=Sum(F('order_items__price') * F('order_items__quantity'))
).order_by('-total')
print("Заказы по общей стоимости:")
for order in orders_by_total:
print(f"Заказ {order.id}, Общая стоимость: {order.total}")
Пояснение: annotate() добавляет поле total к каждому объекту Order, затем order_by('-total') сортирует по нему.
Задача 11: Сортировка адресов по стране и городу
from store.models import Address
# Сортировка по стране и городу
addresses_sorted = Address.objects.order_by('country', 'city')
print("Адреса по стране и городу:")
for address in addresses_sorted:
print(f"{address.country}, {address.city}, {address.street}")
Пояснение: Несколько полей в order_by() — аналог SQL ORDER BY country ASC, city ASC. Приоритет — первый аргумент.
Задача 12: Сортировка заказов по количеству позиций
from django.db.models import Count
from store.models import Order
# Сортировка по количеству позиций заказа
orders_by_item_count = Order.objects.annotate(
item_count=Count('order_items')
).order_by('-item_count')
print("Заказы по количеству позиций:")
for order in orders_by_item_count:
print(f"Заказ {order.id}, Количество позиций: {order.item_count}")
Пояснение: Count('order_items') подсчитывает связанные объекты OrderItem для каждого заказа. Сортировка по убыванию — заказы с наибольшим количеством позиций первые.
Временные метки
Задача 13: Заказы за последний месяц
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)
print("Заказы за последний месяц:")
for order in recent_orders:
print(f"Заказ {order.id}, Дата: {order.order_date}")
Пояснение: timezone.now() — текущий момент с учётом часового пояса. timezone.timedelta(days=30) — 30 дней. Лукап __gte — greater than or equal (>=). SQL: WHERE order_date >= '...'.
Срезы
Задача 14: Первые 5 продуктов
from store.models import Product
# Первые 5 продуктов
first_five_products = Product.objects.all()[:5]
print("Первые 5 продуктов:")
for product in first_five_products:
print(f"{product.name}: {product.price}")
Пояснение: Срез [:5] транслируется в SQL LIMIT 5. QuerySet lazy — запрос выполняется только при итерации в цикле.
Задача 15: 10 самых дорогих продуктов
from store.models import Product
# 10 самых дорогих продуктов
top_ten_expensive_products = Product.objects.order_by('-price')[:10]
print("10 самых дорогих продуктов:")
for product in top_ten_expensive_products:
print(f"{product.name}: {product.price}")
Пояснение: Комбинация order_by('-price')[:10] — сначала сортировка по убыванию цены, затем ограничение. SQL: ORDER BY price DESC LIMIT 10.