From 2ecf81bdfeff76b2dd60bbcfb34b429906dd496a Mon Sep 17 00:00:00 2001 From: Mira Weller Date: Mon, 23 Sep 2024 18:45:33 +0200 Subject: [PATCH] Experimental organizer-level plugin implementation --- src/pretix/base/models/event.py | 4 +- src/pretix/base/models/organizer.py | 12 ++++++ src/pretix/base/signals.py | 43 +++++++++++++------ src/pretix/control/signals.py | 4 +- src/pretix/plugins/ticketoutputpdf/signals.py | 3 +- 5 files changed, 47 insertions(+), 19 deletions(-) diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index 8a02cf5eb..a07845799 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -742,8 +742,8 @@ class Event(EventMixin, LoggedModel): Returns the names of the plugins activated for this event as a list. """ if self.plugins is None: - return [] - return self.plugins.split(",") + return self.organizer.get_plugins() + return self.plugins.split(",") + self.organizer.get_plugins() def get_cache(self): """ diff --git a/src/pretix/base/models/organizer.py b/src/pretix/base/models/organizer.py index c9e0ee3ba..43f83d679 100644 --- a/src/pretix/base/models/organizer.py +++ b/src/pretix/base/models/organizer.py @@ -91,6 +91,10 @@ class Organizer(LoggedModel): verbose_name=_("Short form"), unique=True ) + plugins = models.TextField( + null=True, blank=True, + verbose_name=_("Plugins"), + ) class Meta: verbose_name = _("Organizer") @@ -119,6 +123,14 @@ class Organizer(LoggedModel): """ self.settings.cookie_consent = True + def get_plugins(self): + """ + Returns the names of the plugins activated for this organizer as a list. + """ + if self.plugins is None: + return [] + return self.plugins.split(",") + def get_cache(self): """ Returns an :py:class:`ObjectRelatedCache` object. This behaves equivalent to diff --git a/src/pretix/base/signals.py b/src/pretix/base/signals.py index aa78157fb..d0b7aa6d6 100644 --- a/src/pretix/base/signals.py +++ b/src/pretix/base/signals.py @@ -96,22 +96,27 @@ def is_receiver_active(sender, receiver): return is_app_active(sender, app) -class EventPluginSignal(django.dispatch.Signal): +def is_plugin_host(sender): + return hasattr(sender, 'get_plugins') + + +class PluginSignal(django.dispatch.Signal): """ This is an extension to Django's built-in signals which differs in a way that it sends out it's events only to receivers which belong to plugins that are enabled for the given Event. """ - def send(self, sender: Event, **named) -> List[Tuple[Callable, Any]]: + def send(self, sender, **named) -> List[Tuple[Callable, Any]]: """ Send signal from sender to all connected receivers that belong to plugins enabled for the given Event. - sender is required to be an instance of ``pretix.base.models.Event``. + sender is required to be a plugin host (an object with a `get_plugins` method), + for example a ``pretix.base.models.Event``. """ - if sender and not isinstance(sender, Event): - raise ValueError("Sender needs to be an event.") + if sender and not is_plugin_host(sender): + raise ValueError("Sender needs to be a plugin host.") responses = [] if not self.receivers or self.sender_receivers_cache.get(sender) is NO_RECEIVERS: @@ -126,16 +131,17 @@ class EventPluginSignal(django.dispatch.Signal): responses.append((receiver, response)) return responses - def send_chained(self, sender: Event, chain_kwarg_name, **named) -> List[Tuple[Callable, Any]]: + def send_chained(self, sender, chain_kwarg_name, **named) -> List[Tuple[Callable, Any]]: """ Send signal from sender to all connected receivers. The return value of the first receiver will be used as the keyword argument specified by ``chain_kwarg_name`` in the input to the second receiver and so on. The return value of the last receiver is returned by this method. - sender is required to be an instance of ``pretix.base.models.Event``. + sender is required to be a plugin host (an object with a `get_plugins` method), + for example a ``pretix.base.models.Event``. """ - if sender and not isinstance(sender, Event): - raise ValueError("Sender needs to be an event.") + if sender and not is_plugin_host(sender): + raise ValueError("Sender needs to be a plugin host.") response = named.get(chain_kwarg_name) if not self.receivers or self.sender_receivers_cache.get(sender) is NO_RECEIVERS: @@ -150,16 +156,17 @@ class EventPluginSignal(django.dispatch.Signal): response = receiver(signal=self, sender=sender, **named) return response - def send_robust(self, sender: Event, **named) -> List[Tuple[Callable, Any]]: + def send_robust(self, sender, **named) -> List[Tuple[Callable, Any]]: """ Send signal from sender to all connected receivers. If a receiver raises an exception instead of returning a value, the exception is included as the result instead of stopping the response chain at the offending receiver. - sender is required to be an instance of ``pretix.base.models.Event``. + sender is required to be a plugin host (an object with a `get_plugins` method), + for example a ``pretix.base.models.Event``. """ - if sender and not isinstance(sender, Event): - raise ValueError("Sender needs to be an event.") + if sender and not is_plugin_host(sender): + raise ValueError("Sender needs to be a plugin host.") responses = [] if ( @@ -194,6 +201,14 @@ class EventPluginSignal(django.dispatch.Signal): return sorted_list +class OrganizerPluginSignal(PluginSignal): + pass + + +class EventPluginSignal(PluginSignal): + pass + + class GlobalSignal(django.dispatch.Signal): def send_chained(self, sender: Event, chain_kwarg_name, **named) -> List[Tuple[Callable, Any]]: """ @@ -245,7 +260,7 @@ class Registry: return self.by_key.get(key).get(value, (None, None)) -class EventPluginRegistry(Registry): +class PluginRegistry(Registry): def __init__(self, keys): super().__init__({"plugin": lambda o: get_defining_app(o), **keys}) diff --git a/src/pretix/control/signals.py b/src/pretix/control/signals.py index a7e27ef6e..99f2ac4bc 100644 --- a/src/pretix/control/signals.py +++ b/src/pretix/control/signals.py @@ -34,7 +34,7 @@ from django.dispatch import Signal -from pretix.base.signals import DeprecatedSignal, EventPluginSignal +from pretix.base.signals import DeprecatedSignal, EventPluginSignal, OrganizerPluginSignal html_page_start = Signal() """ @@ -221,7 +221,7 @@ Deprecated signal, no longer works. We just keep the definition so old plugins d break the installation. """ -nav_organizer = Signal() +nav_organizer = OrganizerPluginSignal() """ Arguments: 'organizer', 'request' diff --git a/src/pretix/plugins/ticketoutputpdf/signals.py b/src/pretix/plugins/ticketoutputpdf/signals.py index 64f44d713..b8d7d4b30 100644 --- a/src/pretix/plugins/ticketoutputpdf/signals.py +++ b/src/pretix/plugins/ticketoutputpdf/signals.py @@ -26,8 +26,9 @@ from django.dispatch import receiver from django.template.loader import get_template from django.utils.translation import gettext_lazy as _ +from pretix.base.logentrytypes import EventLogEntryType from pretix.base.models import Event, SalesChannel -from pretix.base.models.log import EventLogEntryType, log_entry_types +from pretix.base.models.log import log_entry_types from pretix.base.signals import ( # NOQA: legacy import EventPluginSignal, event_copy_data, item_copy_data, layout_text_variables, logentry_display, logentry_object_link, register_data_exporters,