diff --git a/src/pretix/base/models/base.py b/src/pretix/base/models/base.py index f7205e2735..5c3a95553a 100644 --- a/src/pretix/base/models/base.py +++ b/src/pretix/base/models/base.py @@ -49,9 +49,7 @@ class LoggingMixin: :param user: The user performing the action (optional) """ from pretix.api.models import OAuthAccessToken, OAuthApplication - from pretix.api.webhooks import get_all_webhook_events, notify_webhooks - - from ..notifications import get_all_notification_types + from pretix.api.webhooks import notify_webhooks from ..services.notifications import notify from .devices import Device from .event import Event @@ -93,21 +91,11 @@ class LoggingMixin: if save: logentry.save() - no_types = get_all_notification_types() - 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: + if logentry.notification_type: notify.apply_async(args=(logentry.pk,)) - if wh_type: + if logentry.webhook_type: notify_webhooks.apply_async(args=(logentry.pk,)) + return logentry diff --git a/src/pretix/base/models/log.py b/src/pretix/base/models/log.py index b37c6fa956..fd942b94d7 100644 --- a/src/pretix/base/models/log.py +++ b/src/pretix/base/models/log.py @@ -63,14 +63,42 @@ class LogEntry(models.Model): return response 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 def organizer(self): + from .organizer import Organizer + if self.event: return self.event.organizer elif hasattr(self.content_object, 'event'): return self.content_object.event.organizer elif hasattr(self.content_object, 'organizer'): return self.content_object.organizer + elif isinstance(self.content_object, Organizer): + return self.content_object return None @cached_property @@ -188,3 +216,15 @@ class LogEntry(models.Model): def delete(self, using=None, keep_parents=False): 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,)) diff --git a/src/pretix/base/services/notifications.py b/src/pretix/base/services/notifications.py index 256c30263d..8c896b6951 100644 --- a/src/pretix/base/services/notifications.py +++ b/src/pretix/base/services/notifications.py @@ -15,55 +15,59 @@ from pretix.helpers.urls import build_absolute_uri @app.task(base=TransactionAwareTask, acks_late=True) @scopes_disabled() -def notify(logentry_id: int): - logentry = LogEntry.all.select_related('event', 'event__organizer').get(id=logentry_id) - if not logentry.event: - return # Ignore, we only have event-related notifications right now - types = get_all_notification_types(logentry.event) +def notify(logentry_ids: list): + if not isinstance(logentry_ids, list): + logentry_ids = [logentry_ids] - notification_type = None - 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] + qs = LogEntry.all.select_related('event', 'event__organizer').filter(id__in=logentry_ids) - if not notification_type: - return # No suitable plugin + _event, _at, notify_specific, notify_global = None, None, None, None + 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 - 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) + notification_type = logentry.notification_type - # Get all notification settings, both specific to this event as well as global - notify_specific = { - (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) - ) - } + if not notification_type: + break # No suitable plugin - 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)) + if _event != logentry.event or _at != logentry.action_type or notify_global is None: + _event = logentry.event + _at = logentry.action_type + # 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(): - 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)) + # Get all notification settings, both specific to this event as well as global + notify_specific = { + (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(): + 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) diff --git a/src/pretix/control/views/subevents.py b/src/pretix/control/views/subevents.py index 7ee7a43a95..ec4ab7d1d1 100644 --- a/src/pretix/control/views/subevents.py +++ b/src/pretix/control/views/subevents.py @@ -4,7 +4,7 @@ from datetime import datetime, timedelta from dateutil.rrule import DAILY, MONTHLY, WEEKLY, YEARLY, rrule, rruleset from django.contrib import messages 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.functions import Coalesce from django.forms import inlineformset_factory @@ -863,7 +863,13 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Crea f.subevent = se 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() messages.success(self.request, pgettext_lazy('subevent', '{} new dates have been created.').format(len(subevents)))