Аннотируйте каждый объект QuerySet подсчетом всех вариантов выбора связанных объектов, сгруппированных по значению выбора
Допустим, у меня есть две модели:
class Mailing(models.Model):
...
class Message(models.Model):
date_created = models.DateTimeField(default=aware_utc_now)
class DeliveryStatusChoices(models.TextChoices): # Number of choices may vary.
PENDING = 'pending'
SUCCESS = 'success'
FAIL = 'fail'
status = models.CharField(
choices=DeliveryStatusChoices.choices,
default=DeliveryStatusChoices.PENDING,
max_length=50
)
mailing = models.ForeignKey(
'Mailing',
on_delete=models.CASCADE,
related_name='messages'
)
Я ищу способ аннотировать рассылки с подсчетом связанных сообщений, сгруппированных по значению выбора. Чтобы можно было сделать что-то вроде:
mailing.stats
и получаем:
{'pending': 15, 'success': 20, 'fail': 2}
Подсчитываются только сообщения, относящиеся к конкретной рассылке.
Я выяснил, как получить статистику для конкретной рассылки:
models.Message.objects.filter(mailing_id=1).values('status').annotate(count=Count('status'))
вывод:
<QuerySet [{'status': 'pending', 'count': 5}, {'status': 'success', 'count': 2}, {'status': 'fail', 'count': 3}]>
annotate не может быть вызван на конкретном объекте. Таким образом, цикл for не будет работать.
Также формат вывода не соответствует желаемому. Но это нормально.
Другой способ, который я нашел:
result = []
for mailing in models.Mailing.objects.all():
statuses = mailing.messages.values('status').annotate(count=Count('status'))
mailing_result = {'id': mailing.id}
for status in statuses:
mailing_result[status['status']] = status['count']
result.append(mailing_result)
Но это решение дает мне просто список dicts.
Иногда мне нужно получить связанные объекты на основе некоторых критериев:
messages = Prefetch(
'messages',
models.Message.objects.filter(
date_created__gte=yesterday_midnight,
date_created__lt=today_midnight
)
)
mailings = models.Mailing.objects.prefetch_related(messages)
В этом случае я хотел бы генерировать статистику, подсчитывая только префетченные сообщения. Например, если рассылка содержит 20 сообщений, но только 15 соответствуют критериям, то рассылка должна быть аннотирована статистикой для этих 15 сообщений.
Как насчет чего-то вроде:
from django.db.models import Q, Count
mailings = Mailing.objects.annotate(
pending_messages_count = Count(
'messages',
filter = Q(messages__status = 'pending')
),
success_messages_count = ...
)
EDIT:
Вы можете создать словарь аннотаций следующим образом, а затем распаковать их в свой набор запросов:
annotations = {
status + '_message_count' : Count(
status,
filter = Q('messages__status' = status)
) for status in statuses
}
mailings = Mailing.objects.annotate(**annotations)