Compare commits

...

1 Commits

Author SHA1 Message Date
Raphael Michel
82e4c331fc Log entries: Check config before bulk-scheduling 2025-10-02 14:03:31 +02:00
3 changed files with 73 additions and 19 deletions

View File

@@ -22,6 +22,7 @@
from datetime import timedelta
from django.db import models
from django.db.models import Exists, OuterRef, Q
from django.urls import reverse
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
@@ -111,6 +112,24 @@ class OAuthRefreshToken(AbstractRefreshToken):
)
class WebHookQuerySet(models.QuerySet):
def for_notification(self, action_type, organizer, event):
event_listener = WebHookEventListener.objects.filter(
webhook=OuterRef('pk'),
action_type=action_type
)
webhooks = WebHook.objects.annotate(has_el=Exists(event_listener)).filter(
organizer=organizer,
has_el=True,
enabled=True
)
if event:
webhooks = webhooks.filter(
Q(all_events=True) | Q(limit_events__pk=event.pk if not isinstance(event, int) else event)
)
return webhooks
class WebHook(models.Model):
organizer = models.ForeignKey('pretixbase.Organizer', on_delete=models.CASCADE, related_name='webhooks')
enabled = models.BooleanField(default=True, verbose_name=_("Enable webhook"))
@@ -119,6 +138,8 @@ class WebHook(models.Model):
limit_events = models.ManyToManyField('pretixbase.Event', verbose_name=_("Limit to events"), blank=True)
comment = models.CharField(verbose_name=_("Comment"), max_length=255, null=True, blank=True)
objects = models.Manager.from_queryset(WebHookQuerySet)()
class Meta:
ordering = ('id',)

View File

@@ -27,16 +27,13 @@ from datetime import timedelta
import requests
from django.db import DatabaseError, connection, transaction
from django.db.models import Exists, OuterRef, Q
from django.dispatch import receiver
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django_scopes import scope, scopes_disabled
from requests import RequestException
from pretix.api.models import (
WebHook, WebHookCall, WebHookCallRetry, WebHookEventListener,
)
from pretix.api.models import WebHook, WebHookCall, WebHookCallRetry
from pretix.api.signals import register_webhook_events
from pretix.base.models import LogEntry
from pretix.base.services.tasks import ProfiledTask, TransactionAwareTask
@@ -454,20 +451,9 @@ def notify_webhooks(logentry_ids: list):
_org = logentry.organizer
_at = logentry.action_type
# All webhooks that registered for this notification
event_listener = WebHookEventListener.objects.filter(
webhook=OuterRef('pk'),
action_type=notification_type.action_type
webhooks = WebHook.objects.for_notification(
notification_type.action_type, logentry.organizer, logentry.event_id
)
webhooks = WebHook.objects.annotate(has_el=Exists(event_listener)).filter(
organizer=logentry.organizer,
has_el=True,
enabled=True
)
if logentry.event_id:
webhooks = webhooks.filter(
Q(all_events=True) | Q(limit_events__pk=logentry.event_id)
)
for wh in webhooks:
send_webhook.apply_async(args=(logentry.id, notification_type.action_type, wh.pk))

View File

@@ -38,8 +38,11 @@ import logging
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import connections, models
from django.db.models import Q
from django.utils.functional import cached_property
from pretix.api.models import WebHook
class VisibleOnlyManager(models.Manager):
def get_queryset(self):
@@ -181,12 +184,56 @@ class LogEntry(models.Model):
@classmethod
def bulk_postprocess(cls, objects):
from pretix.api.webhooks import notify_webhooks
from pretix.base.models import NotificationSetting
from ..services.notifications import notify
to_notify = [o.id for o in objects if o.notification_type]
# Regular LogEntry.save() always kicks off notify and notify_webhooks tasks, regardless of whether a webhook
# listener exists. However, in bulk processing, it makes sense to check once and then only create the task if
# there is something to do.
_webhook_active_cache = {}
_notification_active_cache = {}
def _is_webhook_active(logentry):
nonlocal _webhook_active_cache
key = (logentry.action_type, logentry.organizer_id, logentry.event_id)
if key not in _webhook_active_cache:
notification_type = logentry.webhook_type
_webhook_active_cache[key] = notification_type and WebHook.objects.for_notification(
notification_type.action_type, logentry.organizer, logentry.event_id
).exists()
return _webhook_active_cache[key]
def _is_notification_active(logentry):
nonlocal _notification_active_cache
key = (logentry.action_type, logentry.organizer_id, logentry.event_id)
if key not in _notification_active_cache:
notification_type = logentry.notification_type
if notification_type and logentry.event:
# We only have event-related notifications right now
users = logentry.event.get_users_with_permission(
notification_type.required_permission
).filter(notifications_send=True, is_active=True)
_notification_active_cache[key] = NotificationSetting.objects.filter(
# This is not technically fully correct since it's returning True if a user has the
# notification enabled on the global level and then disabled on the per-event-level,
# but it's good enough as a first check to avoid useless celery tasks for bulk actions
Q(event_id=logentry.event_id) | Q(event__isnull=True),
action_type=notification_type.action_type,
user__pk__in=users.values_list('pk', flat=True),
enabled=True,
).exists()
else:
_webhook_active_cache[key] = False
return _notification_active_cache[key]
to_notify = [o.id for o in objects if _is_notification_active(o)]
if to_notify:
notify.apply_async(args=(to_notify,))
to_wh = [o.id for o in objects if o.webhook_type]
to_wh = [o.id for o in objects if _is_webhook_active(o)]
if to_wh:
notify_webhooks.apply_async(args=(to_wh,))