Compare commits

...

4 Commits

Author SHA1 Message Date
Mira Weller
2ecf81bdfe Experimental organizer-level plugin implementation 2024-09-23 18:45:33 +02:00
Mira Weller
e395834811 Fix circular imports 2024-09-23 18:45:20 +02:00
Mira Weller
86a32d7856 Add inline "json_script" as supported data source for select2 2024-09-23 18:44:16 +02:00
Mira Weller
47e8549f19 Add full_code property to OrderPosition 2024-09-23 18:43:18 +02:00
9 changed files with 92 additions and 47 deletions

View File

@@ -4,11 +4,34 @@ from django.urls import reverse
from django.utils.html import escape
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from pretix.base.models.log import make_link
from pretix.base.signals import EventPluginRegistry
from pretix.base.signals import PluginRegistry
class LogEntryTypeRegistry(EventPluginRegistry):
def make_link(a_map, wrapper, is_active=True, event=None, plugin_name=None):
if a_map:
if is_active:
a_map['val'] = '<a href="{href}">{val}</a>'.format_map(a_map)
elif event and plugin_name:
a_map['val'] = (
'<i>{val}</i> <a href="{plugin_href}">'
'<span data-toggle="tooltip" title="{errmes}" class="fa fa-warning fa-fw"></span></a>'
).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'] = '<i>{val}</i> <span data-toggle="tooltip" title="{errmes}" class="fa fa-warning fa-fw"></span>'.format_map({
**a_map,
"errmes": _("The relevant plugin is currently not active."),
})
return wrapper.format_map(a_map)
class LogEntryTypeRegistry(PluginRegistry):
def new_from_dict(self, data):
def reg(clz):
for action_type, plain in data.items():

View File

@@ -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):
"""

View File

@@ -42,7 +42,7 @@ from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from pretix.base.logentrytypes import log_entry_types
from pretix.base.logentrytypes import log_entry_types, make_link
from pretix.base.signals import is_app_active, logentry_object_link
@@ -51,30 +51,6 @@ class VisibleOnlyManager(models.Manager):
return super().get_queryset().filter(visible=True)
def make_link(a_map, wrapper, is_active=True, event=None, plugin_name=None):
if a_map:
if is_active:
a_map['val'] = '<a href="{href}">{val}</a>'.format_map(a_map)
elif event and plugin_name:
a_map['val'] = (
'<i>{val}</i> <a href="{plugin_href}">'
'<span data-toggle="tooltip" title="{errmes}" class="fa fa-warning fa-fw"></span></a>'
).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'] = '<i>{val}</i> <span data-toggle="tooltip" title="{errmes}" class="fa fa-warning fa-fw"></span>'.format_map({
**a_map,
"errmes": _("The relevant plugin is currently not active."),
})
return wrapper.format_map(a_map)
class LogEntry(models.Model):
"""
Represents a change or action that has been performed on another object

View File

@@ -2835,6 +2835,14 @@ class OrderPosition(AbstractPosition):
(self.order.event.settings.change_allow_user_addons and ItemAddOn.objects.filter(base_item_id__in=[op.item_id for op in positions]).exists())
)
@property
def full_code(self):
"""
A ticket code which is unique among all events of a single organizer,
built by concatenating the event slug and the order code.
"""
return '{order}-{position}'.format(order=self.order.full_code, position=self.positionid)
class Transaction(models.Model):
"""

View File

@@ -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

View File

@@ -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})

View File

@@ -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'

View File

@@ -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,

View File

@@ -552,6 +552,16 @@ var form_handlers = function (el) {
language: $("body").attr("data-select2-locale"),
});
el.find('[data-model-select2=json_script]').each(function() {
const selectedValue = this.value;
this.replaceChildren();
$(this).select2({
theme: "bootstrap",
language: $("body").attr("data-select2-locale"),
data: JSON.parse($(this.getAttribute('data-select2-src')).text()),
}).val(selectedValue).trigger('change');
});
el.find('input[data-typeahead-url]').each(function () {
var $inp = $(this);
if ($inp.data("ttTypeahead") || $inp.hasClass("tt-hint")) {