Метод annotate
Метод annotate применяется к QuerySet и возвращает
новый QuerySet, каждый объект которого дополнен новым
атрибутом - аннотацией. В качестве аргументов метод
принимает именованные параметры, где имя становится
ключом аннотации, а значение - это выражение для
вычисления значения. Это может быть агрегатная функция
(Count, Sum, Avg и другие),
выражение F или любая другая функция базы данных.
Синтаксис
Model.objects.annotate(**annotations)
Пример . С Count
Посчитаем количество комментариев к каждой статье.
Для этого используем агрегатную функцию Count,
передав в неё имя связанного поля:
from myapp.models import Article
from django.db.models import Count
articles_with_comments = Article.objects.annotate(
comments_count=Count('comments')
)
for article in articles_with_comments:
print(f"{article.title}: {article.comments_count} comments")
Результат выполнения кода:
"Article 1: 5 comments"
"Article 2: 2 comments"
"Article 3: 0 comments"
Пример . С Sum
Рассчитаем общую сумму всех заказов для каждого пользователя.
Используем аннотацию с функцией Sum:
from myapp.models import User, Order
from django.db.models import Sum
users_with_total = User.objects.annotate(
total_orders_amount=Sum('orders__amount')
)
for user in users_with_total:
print(f"{user.username}: ${user.total_orders_amount or 0}")
Результат выполнения кода:
"john: $1500"
"jane: $3200"
"bob: $0"
Пример . С Avg
Вычислим средний рейтинг для каждого продукта.
Для пустых наборов значений результат будет равен None,
поэтому можно использовать Coalesce для замены:
from myapp.models import Product, Review
from django.db.models import Avg, Coalesce, Value
from django.db.models.functions import Coalesce
products_with_rating = Product.objects.annotate(
avg_rating=Coalesce(Avg('reviews__rating'), Value(0))
)
for product in products_with_rating:
print(f"{product.name}: {product.avg_rating:.1f}")
Результат выполнения кода:
"Laptop: 4.5"
"Mouse: 4.8"
"Keyboard: 0.0"
Пример . С F-выражениями
Создадим аннотацию, которая вычисляет значение на основе других полей модели. Например, посчитаем итоговую цену с учётом скидки:
from myapp.models import Product
from django.db.models import F, ExpressionWrapper, FloatField
products_with_discount = Product.objects.annotate(
price_with_discount=ExpressionWrapper(
F('price') * (1 - F('discount') / 100),
output_field=FloatField()
)
)
for product in products_with_discount:
print(f"{product.name}: ${product.price_with_discount:.2f}")
Результат выполнения кода:
"Laptop: $850.00"
"Mouse: $24.00"
"Keyboard: $45.00"
Пример . С условиями в аннотации
Добавим аннотацию, которая показывает статус товара на основе
количества на складе. Используем Case и When:
from myapp.models import Product
from django.db.models import Case, When, Value, IntegerField
products_with_status = Product.objects.annotate(
stock_status=Case(
When(stock_quantity__gte=100, then=Value('Много')),
When(stock_quantity__gte=50, then=Value('Достаточно')),
When(stock_quantity__gt=0, then=Value('Мало')),
default=Value('Нет в наличии'),
output_field=IntegerField()
)
)
for product in products_with_status:
print(f"{product.name}: {product.stock_status}")
Результат выполнения кода:
"Laptop: Много"
"Mouse: Достаточно"
"Keyboard: Мало"