Respect per-event plugin availability in OrganizerPluginEvents view (#5983)

* Allow plugins to declare their availability per event

* Fix message type

* small optimization of PluginsField serializer
This commit is contained in:
luelista
2026-05-04 11:34:05 +02:00
committed by GitHub
parent 0acaed41be
commit 27183a26ee
3 changed files with 47 additions and 46 deletions

View File

@@ -115,10 +115,10 @@ class PluginsField(serializers.Field):
def to_representation(self, obj): def to_representation(self, obj):
from pretix.base.plugins import get_all_plugins from pretix.base.plugins import get_all_plugins
active_plugins = set(obj.get_plugins())
return sorted([ return sorted([
p.module for p in get_all_plugins() p.module for p in get_all_plugins()
if not p.name.startswith('.') and getattr(p, 'visible', True) and p.module in obj.get_plugins() if not p.name.startswith('.') and getattr(p, 'visible', True) and p.module in active_plugins
]) ])
def to_internal_value(self, data): def to_internal_value(self, data):

View File

@@ -49,14 +49,39 @@ class PluginType(Enum):
EXPORT = 4 EXPORT = 4
def plugin_is_available(meta, event=None, organizer=None):
if not hasattr(meta.app, 'is_available'):
return True
level = getattr(meta, "level", PLUGIN_LEVEL_EVENT)
if level == PLUGIN_LEVEL_EVENT:
if event:
return meta.app.is_available(event)
elif organizer:
if not hasattr(organizer, '_plugin_availability_fallback_event'):
with scope(organizer=organizer):
setattr(organizer, '_plugin_availability_fallback_event', organizer.events.first())
return (
organizer._plugin_availability_fallback_event
and meta.app.is_available(organizer._plugin_availability_fallback_event)
)
elif level == PLUGIN_LEVEL_ORGANIZER:
if organizer:
return meta.app.is_available(organizer)
elif event:
return meta.app.is_available(event.organizer)
elif level == PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID and (event or organizer):
return meta.app.is_available(event or organizer)
return True
def get_all_plugins(*, event=None, organizer=None) -> List[type]: def get_all_plugins(*, event=None, organizer=None) -> List[type]:
""" """
Returns the PretixPluginMeta classes of all plugins found in the installed Django apps. Returns the PretixPluginMeta classes of all plugins found in the installed Django apps.
""" """
assert not event or not organizer assert not event or not organizer
plugins = [] plugins = []
event_fallback = None
event_fallback_used = False
for app in apps.get_app_configs(): for app in apps.get_app_configs():
if hasattr(app, 'PretixPluginMeta'): if hasattr(app, 'PretixPluginMeta'):
meta = app.PretixPluginMeta meta = app.PretixPluginMeta
@@ -65,28 +90,8 @@ def get_all_plugins(*, event=None, organizer=None) -> List[type]:
if app.name in settings.PRETIX_PLUGINS_EXCLUDE: if app.name in settings.PRETIX_PLUGINS_EXCLUDE:
continue continue
level = getattr(meta, "level", PLUGIN_LEVEL_EVENT) if not plugin_is_available(meta, event, organizer):
if level == PLUGIN_LEVEL_EVENT: continue
if event and hasattr(app, 'is_available'):
if not app.is_available(event):
continue
elif organizer and hasattr(app, 'is_available'):
if not event_fallback_used:
with scope(organizer=organizer):
event_fallback = organizer.events.first()
event_fallback_used = True
if not event_fallback or not app.is_available(event_fallback):
continue
elif level == PLUGIN_LEVEL_ORGANIZER:
if organizer and hasattr(app, 'is_available'):
if not app.is_available(organizer):
continue
elif event and hasattr(app, 'is_available'):
if not app.is_available(event.organizer):
continue
elif level == PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID and (event or organizer) and hasattr(app, 'is_available'):
if not app.is_available(event or organizer):
continue
plugins.append(meta) plugins.append(meta)
return sorted( return sorted(

View File

@@ -102,7 +102,7 @@ from pretix.base.models.organizer import (
from pretix.base.payment import PaymentException from pretix.base.payment import PaymentException
from pretix.base.plugins import ( from pretix.base.plugins import (
PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID, PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID,
PLUGIN_LEVEL_ORGANIZER, PLUGIN_LEVEL_ORGANIZER, plugin_is_available,
) )
from pretix.base.services.export import ( from pretix.base.services.export import (
init_organizer_exporters, multiexport, scheduled_organizer_export, init_organizer_exporters, multiexport, scheduled_organizer_export,
@@ -597,6 +597,13 @@ class OrganizerCreate(CreateView):
}) })
def available_plugins(organizer):
from pretix.base.plugins import get_all_plugins
return (p for p in get_all_plugins(organizer=organizer) if not p.name.startswith('.')
and getattr(p, 'visible', True))
class OrganizerPlugins(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, TemplateView, SingleObjectMixin): class OrganizerPlugins(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, TemplateView, SingleObjectMixin):
model = Organizer model = Organizer
context_object_name = 'organizer' context_object_name = 'organizer'
@@ -606,12 +613,6 @@ class OrganizerPlugins(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
def get_object(self, queryset=None) -> Organizer: def get_object(self, queryset=None) -> Organizer:
return self.request.organizer return self.request.organizer
def available_plugins(self, organizer):
from pretix.base.plugins import get_all_plugins
return (p for p in get_all_plugins(organizer=organizer) if not p.name.startswith('.')
and getattr(p, 'visible', True))
def prepare_links(self, pluginmeta, key): def prepare_links(self, pluginmeta, key):
links = getattr(pluginmeta, key, []) links = getattr(pluginmeta, key, [])
try: try:
@@ -637,7 +638,7 @@ class OrganizerPlugins(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
from pretix.base.plugins import CATEGORY_LABELS, CATEGORY_ORDER from pretix.base.plugins import CATEGORY_LABELS, CATEGORY_ORDER
context = super().get_context_data(*args, **kwargs) context = super().get_context_data(*args, **kwargs)
plugins = list(self.available_plugins(self.object)) plugins = list(available_plugins(self.object))
active_counter = Counter() active_counter = Counter()
events_total = 0 events_total = 0
@@ -685,7 +686,7 @@ class OrganizerPlugins(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
self.object = self.get_object() self.object = self.get_object()
plugins_available = { plugins_available = {
p.module: p for p in self.available_plugins(self.object) p.module: p for p in available_plugins(self.object)
} }
choose_events_next = False choose_events_next = False
with transaction.atomic(): with transaction.atomic():
@@ -786,12 +787,6 @@ class OrganizerPluginEvents(OrganizerDetailViewMixin, OrganizerPermissionRequire
} }
return kwargs return kwargs
def available_plugins(self, organizer):
from pretix.base.plugins import get_all_plugins
return (p for p in get_all_plugins(organizer=organizer) if not p.name.startswith('.')
and getattr(p, 'visible', True))
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
return super().get_context_data( return super().get_context_data(
plugin=self.plugin, plugin=self.plugin,
@@ -799,12 +794,10 @@ class OrganizerPluginEvents(OrganizerDetailViewMixin, OrganizerPermissionRequire
) )
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
plugins_available = { try:
p.module: p for p in self.available_plugins(self.request.organizer) self.plugin = next(p for p in available_plugins(self.request.organizer) if p.module == kwargs["plugin"])
} except StopIteration:
if kwargs["plugin"] not in plugins_available:
raise Http404(_("Unknown plugin.")) raise Http404(_("Unknown plugin."))
self.plugin = plugins_available[kwargs["plugin"]]
level = getattr(self.plugin, "level", PLUGIN_LEVEL_EVENT) level = getattr(self.plugin, "level", PLUGIN_LEVEL_EVENT)
if level == PLUGIN_LEVEL_ORGANIZER: if level == PLUGIN_LEVEL_ORGANIZER:
raise Http404(_("This plugin can only be enabled for the entire organizer account.")) raise Http404(_("This plugin can only be enabled for the entire organizer account."))
@@ -835,6 +828,9 @@ class OrganizerPluginEvents(OrganizerDetailViewMixin, OrganizerPermissionRequire
logentries_to_save = [] logentries_to_save = []
for e in self.request.organizer.events.filter(pk__in=events_to_enable): for e in self.request.organizer.events.filter(pk__in=events_to_enable):
if not plugin_is_available(self.plugin, organizer=self.request.organizer, event=e):
messages.warning(self.request, _("This plugin cannot be activated for event {}.").format(e.name))
continue
logentries_to_save.append( logentries_to_save.append(
e.log_action('pretix.event.plugins.enabled', user=self.request.user, data={'plugin': self.plugin.module}, save=False) e.log_action('pretix.event.plugins.enabled', user=self.request.user, data={'plugin': self.plugin.module}, save=False)
) )