Add support for bulk-webhooks

This commit is contained in:
Raphael Michel
2020-11-06 11:46:06 +01:00
parent a7565342c0
commit eeb3c1a960
4 changed files with 99 additions and 61 deletions

View File

@@ -49,9 +49,7 @@ class LoggingMixin:
:param user: The user performing the action (optional) :param user: The user performing the action (optional)
""" """
from pretix.api.models import OAuthAccessToken, OAuthApplication from pretix.api.models import OAuthAccessToken, OAuthApplication
from pretix.api.webhooks import get_all_webhook_events, notify_webhooks from pretix.api.webhooks import notify_webhooks
from ..notifications import get_all_notification_types
from ..services.notifications import notify from ..services.notifications import notify
from .devices import Device from .devices import Device
from .event import Event from .event import Event
@@ -93,21 +91,11 @@ class LoggingMixin:
if save: if save:
logentry.save() logentry.save()
no_types = get_all_notification_types() if logentry.notification_type:
wh_types = get_all_webhook_events()
no_type = None
wh_type = None
typepath = logentry.action_type
while (not no_type or not wh_types) and '.' in typepath:
wh_type = wh_type or wh_types.get(typepath + ('.*' if typepath != logentry.action_type else ''))
no_type = no_type or no_types.get(typepath + ('.*' if typepath != logentry.action_type else ''))
typepath = typepath.rsplit('.', 1)[0]
if no_type:
notify.apply_async(args=(logentry.pk,)) notify.apply_async(args=(logentry.pk,))
if wh_type: if logentry.webhook_type:
notify_webhooks.apply_async(args=(logentry.pk,)) notify_webhooks.apply_async(args=(logentry.pk,))
return logentry return logentry

View File

@@ -63,14 +63,42 @@ class LogEntry(models.Model):
return response return response
return self.action_type return self.action_type
@property
def webhook_type(self):
from pretix.api.webhooks import get_all_webhook_events
wh_types = get_all_webhook_events()
wh_type = None
typepath = self.action_type
while not wh_type and '.' in typepath:
wh_type = wh_type or wh_types.get(typepath + ('.*' if typepath != self.action_type else ''))
typepath = typepath.rsplit('.', 1)[0]
return wh_type
@property
def notification_type(self):
from pretix.base.notifications import get_all_notification_types
no_type = None
no_types = get_all_notification_types()
typepath = self.action_type
while not no_type and '.' in typepath:
no_type = no_type or no_types.get(typepath + ('.*' if typepath != self.action_type else ''))
typepath = typepath.rsplit('.', 1)[0]
return no_type
@cached_property @cached_property
def organizer(self): def organizer(self):
from .organizer import Organizer
if self.event: if self.event:
return self.event.organizer return self.event.organizer
elif hasattr(self.content_object, 'event'): elif hasattr(self.content_object, 'event'):
return self.content_object.event.organizer return self.content_object.event.organizer
elif hasattr(self.content_object, 'organizer'): elif hasattr(self.content_object, 'organizer'):
return self.content_object.organizer return self.content_object.organizer
elif isinstance(self.content_object, Organizer):
return self.content_object
return None return None
@cached_property @cached_property
@@ -188,3 +216,15 @@ class LogEntry(models.Model):
def delete(self, using=None, keep_parents=False): def delete(self, using=None, keep_parents=False):
raise TypeError("Logs cannot be deleted.") raise TypeError("Logs cannot be deleted.")
@classmethod
def bulk_postprocess(cls, objects):
from pretix.api.webhooks import notify_webhooks
from ..services.notifications import notify
to_notify = [o.id for o in objects if o.notification_type]
if to_notify:
notify.apply_async(args=(to_notify,))
to_wh = [o.id for o in objects if o.webhook_type]
if to_wh:
notify_webhooks.apply_async(args=(to_wh,))

View File

@@ -15,55 +15,59 @@ from pretix.helpers.urls import build_absolute_uri
@app.task(base=TransactionAwareTask, acks_late=True) @app.task(base=TransactionAwareTask, acks_late=True)
@scopes_disabled() @scopes_disabled()
def notify(logentry_id: int): def notify(logentry_ids: list):
logentry = LogEntry.all.select_related('event', 'event__organizer').get(id=logentry_id) if not isinstance(logentry_ids, list):
if not logentry.event: logentry_ids = [logentry_ids]
return # Ignore, we only have event-related notifications right now
types = get_all_notification_types(logentry.event)
notification_type = None qs = LogEntry.all.select_related('event', 'event__organizer').filter(id__in=logentry_ids)
typepath = logentry.action_type
while not notification_type and '.' in typepath:
notification_type = types.get(typepath + ('.*' if typepath != logentry.action_type else ''))
typepath = typepath.rsplit('.', 1)[0]
if not notification_type: _event, _at, notify_specific, notify_global = None, None, None, None
return # No suitable plugin for logentry in qs:
if not logentry.event:
break # Ignore, we only have event-related notifications right now
# All users that have the permission to get the notification notification_type = logentry.notification_type
users = logentry.event.get_users_with_permission(
notification_type.required_permission
).filter(notifications_send=True, is_active=True)
if logentry.user:
users = users.exclude(pk=logentry.user.pk)
# Get all notification settings, both specific to this event as well as global if not notification_type:
notify_specific = { break # No suitable plugin
(ns.user, ns.method): ns.enabled
for ns in NotificationSetting.objects.filter(
event=logentry.event,
action_type=notification_type.action_type,
user__pk__in=users.values_list('pk', flat=True)
)
}
notify_global = {
(ns.user, ns.method): ns.enabled
for ns in NotificationSetting.objects.filter(
event__isnull=True,
action_type=notification_type.action_type,
user__pk__in=users.values_list('pk', flat=True)
)
}
for um, enabled in notify_specific.items(): if _event != logentry.event or _at != logentry.action_type or notify_global is None:
user, method = um _event = logentry.event
if enabled: _at = logentry.action_type
send_notification.apply_async(args=(logentry_id, notification_type.action_type, user.pk, method)) # All users that have the permission to get the notification
users = logentry.event.get_users_with_permission(
notification_type.required_permission
).filter(notifications_send=True, is_active=True)
if logentry.user:
users = users.exclude(pk=logentry.user.pk)
for um, enabled in notify_global.items(): # Get all notification settings, both specific to this event as well as global
user, method = um notify_specific = {
if enabled and um not in notify_specific: (ns.user, ns.method): ns.enabled
send_notification.apply_async(args=(logentry_id, notification_type.action_type, user.pk, method)) for ns in NotificationSetting.objects.filter(
event=logentry.event,
action_type=notification_type.action_type,
user__pk__in=users.values_list('pk', flat=True)
)
}
notify_global = {
(ns.user, ns.method): ns.enabled
for ns in NotificationSetting.objects.filter(
event__isnull=True,
action_type=notification_type.action_type,
user__pk__in=users.values_list('pk', flat=True)
)
}
for um, enabled in notify_specific.items():
user, method = um
if enabled:
send_notification.apply_async(args=(logentry.id, notification_type.action_type, user.pk, method))
for um, enabled in notify_global.items():
user, method = um
if enabled and um not in notify_specific:
send_notification.apply_async(args=(logentry.id, notification_type.action_type, user.pk, method))
@app.task(base=ProfiledTask, acks_late=True) @app.task(base=ProfiledTask, acks_late=True)

View File

@@ -4,7 +4,7 @@ from datetime import datetime, timedelta
from dateutil.rrule import DAILY, MONTHLY, WEEKLY, YEARLY, rrule, rruleset from dateutil.rrule import DAILY, MONTHLY, WEEKLY, YEARLY, rrule, rruleset
from django.contrib import messages from django.contrib import messages
from django.core.files import File from django.core.files import File
from django.db import transaction from django.db import transaction, connections
from django.db.models import F, IntegerField, OuterRef, Prefetch, Subquery, Sum from django.db.models import F, IntegerField, OuterRef, Prefetch, Subquery, Sum
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
from django.forms import inlineformset_factory from django.forms import inlineformset_factory
@@ -863,7 +863,13 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Crea
f.subevent = se f.subevent = se
f.save() f.save()
LogEntry.objects.bulk_create(log_entries) if connections['default'].features.can_return_rows_from_bulk_insert:
LogEntry.objects.bulk_create(log_entries)
LogEntry.bulk_postprocess(log_entries)
else:
for le in log_entries:
le.save()
LogEntry.bulk_postprocess(log_entries)
self.request.event.cache.clear() self.request.event.cache.clear()
messages.success(self.request, pgettext_lazy('subevent', '{} new dates have been created.').format(len(subevents))) messages.success(self.request, pgettext_lazy('subevent', '{} new dates have been created.').format(len(subevents)))