Аннотируйте индивидуально каждый элемент в наборе запросов Django Queryset

Я пытаюсь получить queryset, который говорит мне, подписан ли пользователь на данный список рассылки, и получить доступ к этому bool с помощью mailing_list.is_subbed

В идеале мы должны иметь набор запросов, где каждый элемент имеет аннотированное поле "is_subbed", которое равно True или False в зависимости от того, подписан ли данный пользователь на список рассылки или нет.

Для контекста, это представление будет обслуживать форму с чекбоксами, которые отмечаются/отмечаются в зависимости от статуса пользователя. Страница доступна в режиме инкогнито через url, который содержит токен, содержащий 1) email пользователя и 2) ID записи отправки почты (которая содержит детали, такие как список рассылки, в который она была отправлена, подробности ниже)

>

В текущем состоянии функция is_subbed вызывается только один раз на первом элементе, и результирующий bool аннотируется к каждому элементу, я бы хотел, чтобы она выполнялась для каждого элемента в queryset. Как я могу это сделать? На данный момент, если первый элемент возвращает True после подачи на is_subbed, каждый флажок будет отмечен, потому что аннотированное поле is_subbed устанавливается в True для каждого элемента.

Вот моя текущая работа: Реферат :

  1. The view
  2. Implementation of function and models used in said view
  3. Snippet I use to access my results in jinja2

views.py

class PressSubscriptionManagementView(TemplateView):
    """Allows management of Press subscriptions
    Displays the current subscriptions to the different press Mailing lists
    and allows the user to sub / unsub from a checkbox form.

    Press mailing list are those whose attribute mailing_list_type is "PR"
    """
    template_name = "mailing_list/press_subscription_management.html"

    def is_subbed(self, user: User, mailing_list: MailingList) -> bool:
        """
        Check if the user is subbed to the mailing list
        """
        return user_current_state(user, mailing_list).event_type == "sub"

    def get_context_data(self, **kwargs) -> dict:
        context = super().get_context_data(**kwargs)
        email, send_record_id = token_valid(kwargs["token"])
        if email and send_record_id:
            context["user"] = User.objects.get(email=email)
            # In the current state, is_subbed is only called once on the first
            # item in the list. If this call returns True, every checkbox is
            # checked. None otherwise.
            context["press_subscription_list"] = \
                MailingList.objects.filter(mailing_list_type="PR").order_by(
                    "-id").annotate(
                        is_subbed=ExpressionWrapper(
                            Value(
                                self.is_subbed(
                                    context["user"],
                                    F('mailing_list__id')
                                    ),
                                output_field=BooleanField()),
                            output_field=BooleanField()
                            )
                        )
        else:
            context["press_subscription_list"] = \
                MailingList.objects.filter(
                    mailing_list_type="PR").order_by("-id")

        return context

реализация user_current_state :

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)

Внедрение MailingList и MailingListEvent :

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)


class MailingList(models.Model):
    """Represent things a mailing list.
    """
    class Meta:
        permissions = (
            ("may_view_list",
             "May see list of mailing lists and their metrics"),
        )
    # This is a user-visible name that can change.
    mailing_list_name = models.CharField(max_length=80, blank=False)
    # This is the unique name that can't change.  It just makes code a
    # bit more readable than referring to the pk id.
    mailing_list_token = models.CharField(max_length=80, blank=False,
                                          unique=True)
    # How often the user should expect to be contacted.
    # Zero means no frequency objective.
    contact_frequency_weeks = models.IntegerField(default=0)
    list_created = models.DateTimeField(default=django.utils.timezone.now)
    list_active = models.BooleanField(default=False)
    is_petition = models.BooleanField(default=False)

И наконец, реализация SendRecord :

class TopicBlogEmailSendRecord(models.Model):

    """Represent the fact that we sent an email.
    """
    slug = models.SlugField(max_length=90, allow_unicode=True, blank=True)
    mailinglist = models.ForeignKey(MailingList, on_delete=models.PROTECT)
    recipient = models.ForeignKey(User, on_delete=models.PROTECT)
    send_time = models.DateTimeField(auto_now=True)
    open_time = models.DateTimeField(null=True, blank=True)
    click_time = models.DateTimeField(null=True, blank=True)
    unsubscribe_time = models.DateTimeField(null=True, blank=True)

В шаблоне я получаю доступ к значению следующим образом :

{% for mailing_list in press_subscription_list %}
    [...]
    <input type="checkbox" name="{{ mailing_list.mailing_list_name }}"
    value="{{ mailing_list.id }}" id="mailing_list_id_{{ mailing_list.id }}"
    {% if mailing_list.is_subbed %} checked {% endif %} />
    [...]
{% endfor %}

Вместо вашего метода is_subbed() можно использовать подзапрос в сочетании с условным выражением.

Например, что-то вроде этого:

def get_context_data(self, **kwargs) -> dict:
    ...
    # subquery mailing list events for each of the user's mailing lists
    events = MailingListEvent.objects.filter(
        user=context['user'], mailing_list=models.OuterRef('pk')
    ).order_by('-event_timestamp')
    # annotate using subquery
    annotated_mailing_lists = MailingList.objects.annotate(
        latest_event_type=models.Subquery(events.values('event_type')[:1]),
        is_subbed=models.Case(
            models.When(latest_event_type='sub', then=True),
            default=False,
            output_field=models.BooleanField(),
        )
    )
    context["press_subscription_list"] = annotated_mailing_lists.filter(...)...

Примечания:

  • Это предполагает, что ваша модель MailingListEvent определяет поля event_type и event_timestamp (которые отсутствуют в вашем примере).
  • Для ясности я использовал When(latest_event_type='sub', ..., но вы, вероятно, должны использовать MailingListEvent.EventType.SUBSCRIBE вместо 'sub'.
Вернуться на верх