diff --git a/src/pretix/base/models/log.py b/src/pretix/base/models/log.py index 39a2b35cb..424f7190a 100644 --- a/src/pretix/base/models/log.py +++ b/src/pretix/base/models/log.py @@ -44,7 +44,7 @@ from django.utils.functional import cached_property from django.utils.html import escape from django.utils.translation import gettext_lazy as _, pgettext_lazy -from pretix.base.signals import logentry_object_link, EventPluginRegistry +from pretix.base.signals import logentry_object_link, EventPluginRegistry, is_app_active class VisibleOnlyManager(models.Manager): @@ -52,21 +52,21 @@ class VisibleOnlyManager(models.Manager): return super().get_queryset().filter(visible=True) -def make_link(a_map, wrapper, is_active=True, event=None): +def make_link(a_map, wrapper, is_active=True, event=None, plugin_name=None): if a_map: if is_active: a_map['val'] = '{val}'.format_map(a_map) - elif event: - a_map['val'] = '{val} '.format_map({ + elif event and plugin_name: + a_map['val'] = '{val} '.format_map({ **a_map, "errmes": _("The relevant plugin is currently not active. To activate it, click here to go to the plugin settings."), "plugin_href": reverse('control:event.settings.plugins', kwargs={ 'organizer': event.organizer.slug, 'event': event.slug, - }), + }) + '#plugin_' + plugin_name, }) else: - a_map['val'] = '{val} '.format_map({ + a_map['val'] = '{val} '.format_map({ **a_map, "errmes": _("The relevant plugin is currently not active."), }) @@ -157,6 +157,15 @@ class LogEntry(models.Model): SubEvent, Voucher, ) + log_entry_type, meta = log_entry_types.find(action_type=self.action_type) + if log_entry_type: + link_info = log_entry_type.get_object_link_info(self) + if is_app_active(self.event, meta['plugin']): + return make_link(link_info, log_entry_type.object_link_wrapper) + else: + return make_link(link_info, log_entry_type.object_link_wrapper, is_active=False, + event=self.event, plugin_name=meta['plugin'] and getattr(meta['plugin'], 'name')) + try: if self.content_type.model_class() is Event: return '' @@ -165,10 +174,6 @@ class LogEntry(models.Model): except: return '' - log_entry_type, meta = log_entry_types.find(action_type=self.action_type) - if log_entry_type: - return log_entry_type.get_object_link(self, is_active=meta['plugin'] == 'CORE' or (meta['plugin'] and meta['plugin'] in self.event.get_plugins())) - for receiver, response in logentry_object_link.send(self.event, logentry=self): if response: return response @@ -228,9 +233,9 @@ class LogEntryType: def get_object_link_info(self, logentry) -> dict: pass - def get_object_link(self, logentry, is_active): + def get_object_link(self, logentry): a_map = self.get_object_link_info(logentry) - return make_link(a_map, self.object_link_wrapper, is_active, logentry.event) + return make_link(a_map, self.object_link_wrapper) object_link_wrapper = '{val}' diff --git a/src/pretix/base/signals.py b/src/pretix/base/signals.py index b22177f5d..fd185260f 100644 --- a/src/pretix/base/signals.py +++ b/src/pretix/base/signals.py @@ -52,6 +52,50 @@ def _populate_app_cache(): app_cache[ac.name] = ac +def get_defining_app(o): + # If sentry packed this in a wrapper, unpack that + if "sentry" in o.__module__: + o = o.__wrapped__ + + # Find the Django application this belongs to + searchpath = o.__module__ + + # Core modules are always active + if any(searchpath.startswith(cm) for cm in settings.CORE_MODULES): + return 'CORE' + + if not app_cache: + _populate_app_cache() + + while True: + app = app_cache.get(searchpath) + if "." not in searchpath or app: + break + searchpath, _ = searchpath.rsplit(".", 1) + return app + + +def is_app_active(sender, app): + if app == 'CORE': + return True + + excluded = settings.PRETIX_PLUGINS_EXCLUDE + if sender and app and app.name in sender.get_plugins() and app.name not in excluded: + if not hasattr(app, 'compatibility_errors') or not app.compatibility_errors: + return True + return False + + +def is_receiver_active(sender, receiver): + if sender is None: + # Send to all events! + return True + + app = get_defining_app(receiver) + + return is_app_active(sender, app) + + class EventPluginSignal(django.dispatch.Signal): """ This is an extension to Django's built-in signals which differs in a way that it sends @@ -59,22 +103,6 @@ class EventPluginSignal(django.dispatch.Signal): Event. """ - def _is_active(self, sender, receiver): - if sender is None: - # Send to all events! - return True - - name, app = get_defining_app(receiver) - if app == 'CORE': - return True - - # Only fire receivers from active plugins and core modules - excluded = settings.PRETIX_PLUGINS_EXCLUDE - if sender and app and app.name in sender.get_plugins() and app.name not in excluded: - if not hasattr(app, 'compatibility_errors') or not app.compatibility_errors: - return True - return False - def send(self, sender: Event, **named) -> List[Tuple[Callable, Any]]: """ Send signal from sender to all connected receivers that belong to @@ -93,7 +121,7 @@ class EventPluginSignal(django.dispatch.Signal): _populate_app_cache() for receiver in self._sorted_receivers(sender): - if self._is_active(sender, receiver): + if is_receiver_active(sender, receiver): response = receiver(signal=self, sender=sender, **named) responses.append((receiver, response)) return responses @@ -117,7 +145,7 @@ class EventPluginSignal(django.dispatch.Signal): _populate_app_cache() for receiver in self._sorted_receivers(sender): - if self._is_active(sender, receiver): + if is_receiver_active(sender, receiver): named[chain_kwarg_name] = response response = receiver(signal=self, sender=sender, **named) return response @@ -144,7 +172,7 @@ class EventPluginSignal(django.dispatch.Signal): _populate_app_cache() for receiver in self._sorted_receivers(sender): - if self._is_active(sender, receiver): + if is_receiver_active(sender, receiver): try: response = receiver(signal=self, sender=sender, **named) except Exception as err: @@ -217,32 +245,9 @@ class Registry: return self.by_key.get(key).get(value, (None, None)) -def get_defining_app(o) -> Tuple[Optional[str], Optional["django.apps.AppConfig"]]: - # If sentry packed this in a wrapper, unpack that - if "sentry" in o.__module__: - o = o.__wrapped__ - - # Find the Django application this belongs to - searchpath = o.__module__ - - # Core modules are always active - if any(searchpath.startswith(cm) for cm in settings.CORE_MODULES): - return 'CORE', 'CORE' - - if not app_cache: - _populate_app_cache() - - while True: - app = app_cache.get(searchpath) - if "." not in searchpath or app: - break - searchpath, _ = searchpath.rsplit(".", 1) - return app and app.name, app - - class EventPluginRegistry(Registry): def __init__(self, keys): - super().__init__({"plugin": lambda o: get_defining_app(o)[0], **keys}) + super().__init__({"plugin": lambda o: get_defining_app(o), **keys}) event_live_issues = EventPluginSignal() diff --git a/src/pretix/control/logdisplay.py b/src/pretix/control/logdisplay.py index 307b6b2c7..8332fac6b 100644 --- a/src/pretix/control/logdisplay.py +++ b/src/pretix/control/logdisplay.py @@ -57,7 +57,7 @@ from pretix.base.models.log import ( QuotaLogEntryType, TaxRuleLogEntryType, VoucherLogEntryType, log_entry_types, ) -from pretix.base.signals import logentry_display, orderposition_blocked_display +from pretix.base.signals import logentry_display, orderposition_blocked_display, app_cache from pretix.base.templatetags.money import money_filter OVERVIEW_BANLIST = [ @@ -674,6 +674,7 @@ class CoreUserImpersonatedLogEntryType(UserImpersonatedLogEntryType): class CoreLogEntryType(LogEntryType): pass + @log_entry_types.new_from_dict({ 'pretix.event.item_meta_property.added': _('A meta property has been added to this event.'), 'pretix.event.item_meta_property.deleted': _('A meta property has been removed from this event.'), @@ -684,8 +685,6 @@ class CoreLogEntryType(LogEntryType): 'pretix.event.checkinlist.changed': _('The check-in list has been changed.'), 'pretix.event.settings': _('The event settings have been changed.'), 'pretix.event.tickets.settings': _('The ticket download settings have been changed.'), - 'pretix.event.plugins.enabled': _('A plugin has been enabled.'), - 'pretix.event.plugins.disabled': _('A plugin has been disabled.'), 'pretix.event.live.activated': _('The shop has been taken live.'), 'pretix.event.live.deactivated': _('The shop has been taken offline.'), 'pretix.event.testmode.activated': _('The shop has been taken into test mode.'), @@ -704,6 +703,27 @@ class CoreLogEntryType(LogEntryType): class CoreEventLogEntryType(EventLogEntryType): pass + +@log_entry_types.new_from_dict({ + 'pretix.event.plugins.enabled': _('The plugin has been enabled.'), + 'pretix.event.plugins.disabled': _('The plugin has been disabled.'), +}) +class EventPluginStateLogEntryType(EventLogEntryType): + object_link_wrapper = _('Plugin {val}') + + def get_object_link_info(self, logentry) -> dict: + if 'plugin' in logentry.parsed_data: + app = app_cache.get(logentry.parsed_data['plugin']) + if app and hasattr(app, 'PretixPluginMeta'): + return { + 'href': reverse('control:event.settings.plugins', kwargs={ + 'organizer': logentry.event.organizer.slug, + 'event': logentry.event.slug, + }) + '#plugin_' + logentry.parsed_data['plugin'], + 'val': app.PretixPluginMeta.name + } + + @log_entry_types.new_from_dict({ 'pretix.event.item.added': _('The product has been created.'), 'pretix.event.item.changed': _('The product has been changed.'), @@ -736,7 +756,7 @@ class VariationLogEntryType(ItemLogEntryType): logentry.parsed_data['value'] = '?' else: logentry.parsed_data['value'] = LazyI18nString(logentry.parsed_data['value']) - return super().display(logentry_display) + return super().display(logentry) @log_entry_types.new_from_dict({ diff --git a/src/pretix/control/templates/pretixcontrol/event/plugins.html b/src/pretix/control/templates/pretixcontrol/event/plugins.html index 39ca03277..5575abc89 100644 --- a/src/pretix/control/templates/pretixcontrol/event/plugins.html +++ b/src/pretix/control/templates/pretixcontrol/event/plugins.html @@ -23,7 +23,7 @@ {{ catlabel }}
{% for plugin in plist %} -
+
{% if plugin.featured %}