Я использую журнал событий, который отслеживает подписки и отказы от подписки на заданные списки рассылки.
Моя цель - один раз попасть в базу данных (sqlite), чтобы получить количество подписанных пользователей, мне не нужны объекты, просто число.
модели.py
class MailingListEvent(models.Model):
"""Events on mailing lists.
This represents subscribes, unsubscribes, and bounces. We'd like
to understand what happens and when, not just the current state of
the system.
"""
class EventType(models.TextChoices):
SUBSCRIBE = 'sub', 'inscription'
UNSUBSCRIBE = 'unsub', 'désinscription'
BOUNCE = 'bounce', 'bounce'
user = models.ForeignKey(User, on_delete=models.CASCADE)
mailing_list = models.ForeignKey(MailingList,
on_delete=models.CASCADE)
event_timestamp = models.DateTimeField(default=django.utils.timezone.now)
event_type = models.CharField(max_length=6, choices=EventType.choices)
Пока я нашел только это решение для работы:
def user_subscribe_count(mailing_list):
"""Return the number of users currently subscribed to the mailing list.
We want to know how many users are currently subscribed. Note
that individual users might subscribe and unsubscribe multiple
times. Other (future) events could happen as well.
"""
user_list = MailingListEvent.objects.filter(mailing_list=mailing_list).values_list('user',flat=True).distinct()
users_subscribed = list()
for user in user_list:
user_state = user_current_state(User.objects.get(pk=user),mailing_list)
if user_state.event_type == MailingListEvent.EventType.SUBSCRIBE:
users_subscribed.append(user_state)
return len(users_subscribed)
def user_current_state(user, mailing_list):
"""Return user's most current state on the provided mailing list
Return the most recent event associated with this user in this
mailing list.
"""
try:
the_user = MailingListEvent.objects.filter(
Q(event_type=MailingListEvent.EventType.SUBSCRIBE) |
Q(event_type=MailingListEvent.EventType.UNSUBSCRIBE),
user=user, mailing_list=mailing_list).latest(
'event_timestamp')
return the_user
except MailingListEvent.DoesNotExist:
return MailingListEvent(
user=user, mailing_list=mailing_list,
event_type=MailingListEvent.EventType.UNSUBSCRIBE)
Я знаю, что в Django есть метод .count(), но преобразование в список все равно уже попадает в базу данных.
Может ли кто-нибудь предложить запрос, который вернет количество пользователей, подписанных в настоящее время, с учетом этой модели, пожалуйста?
@WillemVanOnsem Не совсем так, один пользователь теоретически может сделать несколько событий отмены подписки через предоставленную нами ссылку для отмены подписки. Я думаю, мы могли бы ограничить это поведение, а затем выполнить SUB - UNSUB, чтобы получить правильное число.
Можете показать реализацию user_current_state
?
@WillemVanOnsem Добавил реализацию в тему.
Вы можете аннотировать User
последними MailingListEvent
для этого MailingList
, используя Выражение Subquery
[Django-doc]:
from django.db.models import OuterRef, Subquery
User.objects.alias(
latest_action=Subquery(
MailingListEvent.objects.filter(
mailing_list=mailing_list,
user=OuterRef('pk')
).order_by('-event_timestamp').values('event_type')[:1]
)
).filter(latest_action='sub').count()
Если последним действием было BOUNCE
, то это нет будет считаться подписанным пользователем.
Note: It is normally better to make use of the
settings.AUTH_USER_MODEL
[Django-doc] to refer to the user model, than to use theUser
model [Django-doc] directly. For more information you can see the referencing theUser
model section of the documentation.
Note: Django's
DateTimeField
[Django-doc] has aauto_now_add=…
parameter [Django-doc] to work with timestamps. This will automatically assign the current datetime when creating the object, and mark it as non-editable (editable=False
), such that it does not appear inModelForm
s by default.
Спасибо за предложение! Я реализовал это и протестировал решение в случае, когда вывод должен был быть 2 (один пользователь просто подписался, другой сделал подписку-отмену-под-отмену-подписку), к сожалению, он вернул 0.
@Benjamin_Mourgues: что вы получаете, когда используете .values('latest_action')
вместо .filter(latest_action='sub').count()
, обычно это должно указывать последнее действие на User
.
Я получаю FieldError: Cannot select the 'latest_action' alias. Use annotate() to promote it.
@Benjamin_Mourgues: и в этом случае используйте .annotate(..)
вместо .alias(..)
.
Поэтому я отредактировал предложенную вами функцию с помощью annotate
вместо alias
, она по-прежнему возвращает 0. Однако я применил .values('latest_action')
, чтобы увидеть, какие выходные данные она производит, и это длинный набор запросов, полный {'latest_action': None}. Спасибо за помощь кстати, я ценю.
@Benjamin_Mourgues: вы уверены, что используете правильный mailing_list
? Похоже, что, возможно, для другого списка рассылки пользователи подписаны/отписаны?
Итак, это подсчет количества событий подписки минус количество событий отказа от подписки?