Compare commits

...
Author SHA1 Message Date
Raphael Michel 96a4b3d7f6 Navigation and dashboard: Do not crash page if a plugin fails
Generally, I don't think it's a good approach for plugins to fail
silently. A missing menu item or button is a weird failure mode, people
will search for it and the system will feel buggy. However, a single bug
in a plugin should not cause the entire system to feel "down" since the
entire backend can't load, not even the dashboard right after login. So
here, I think the trade-off is warranted to fail silently -- and
escalate quickly via Sentry.
2026-11-03 17:12:23 +01:00
Raphael Michel 0a73faa8bd Bump to i18nfield 1.12.0 2026-11-03 10:38:37 +01:00
Tobias Kunze def5042661 Ignore spaces in bank transfer queries (#6724)
When copying an order code from e.g. the order list, it's easy to
include spaces when pasting, and then this does not match anything. And
as the JavaScript side of the search *does* compare the stripped string
(normalizeQuery), just deleting the whitespace does not trigger the
order to be found either, you have delete one more letter.
2026-11-03 09:12:38 +01:00
5 changed files with 46 additions and 11 deletions

No files matched your search

+1 -1
View File
@@ -46,7 +46,7 @@ dependencies = [
"django-formtools==2.7",
"django-hierarkey==2.0.*,>=2.0.2",
"django-hijack==3.7.*",
"django-i18nfield==1.11.*",
"django-i18nfield==1.12.*",
"django-libsass==0.9",
"django-localflavor==5.1",
"django-markup",
+38 -3
View File
@@ -147,7 +147,7 @@ class PluginSignal(Generic[T], django.dispatch.Signal):
def _is_receiver_active(self, sender, receiver):
return is_receiver_active(sender, receiver)
def send(self, sender: T, **named) -> List[Tuple[Callable, Any]]:
def send(self, sender: T, skip_exceptions=False, **named) -> List[Tuple[Callable, Any]]:
"""
Send signal from sender to all connected receivers that belong to
plugins enabled for the given event / organizer.
@@ -164,8 +164,19 @@ class PluginSignal(Generic[T], django.dispatch.Signal):
for receiver in self._live_receivers(sender)[0]:
if self._is_receiver_active(sender, receiver):
response = receiver(signal=self, sender=sender, **named)
responses.append((receiver, response))
try:
response = receiver(signal=self, sender=sender, **named)
except Exception as e:
if skip_exceptions:
logger.exception("Signal handler skipped due to exception")
if settings.SENTRY_ENABLED:
import sentry_sdk
sentry_sdk.capture_exception(e)
else:
raise
else:
responses.append((receiver, response))
return responses
def send_chained(self, sender: T, chain_kwarg_name, **named) -> List[Tuple[Callable, Any]]:
@@ -312,6 +323,30 @@ class OrganizerPluginSignal(PluginSignal[Organizer]):
class GlobalSignal(django.dispatch.Signal):
def send(self, sender, skip_exceptions=False, **named):
if (
not self.receivers
or self.sender_receivers_cache.get(sender) is NO_RECEIVERS
):
return []
responses = []
sync_receivers, _ = self._live_receivers(sender)
for receiver in sync_receivers:
try:
response = receiver(signal=self, sender=sender, **named)
except Exception as e:
if skip_exceptions:
logger.exception("Signal handler skipped due to exception")
if settings.SENTRY_ENABLED:
import sentry_sdk
sentry_sdk.capture_exception(e)
else:
raise
else:
responses.append((receiver, response))
return responses
def send_chained(self, sender: Event, chain_kwarg_name, **named) -> List[Tuple[Callable, Any]]:
"""
Send signal from sender to all connected receivers. The return value of the first receiver
+3 -3
View File
@@ -135,7 +135,7 @@ def get_event_navigation(request: HttpRequest):
# It would be better to allow plugins to handle the permission themselves, but for backwards compatibility
# we need to have it in the "if" statement
event_settings += sorted(
sum((list(a[1]) for a in nav_event_settings.send(request.event, request=request)), []),
sum((list(a[1]) for a in nav_event_settings.send(request.event, skip_exceptions=True, request=request)), []),
key=lambda r: r['label']
)
if event_settings:
@@ -487,7 +487,7 @@ def get_global_navigation(request):
})
merge_in(nav, sorted(
sum((list(a[1]) for a in nav_global.send(request, request=request)), []),
sum((list(a[1]) for a in nav_global.send(request, skip_exceptions=True, request=request)), []),
key=lambda r: (1 if r.get('parent') else 0, r['label'])
))
return nav
@@ -718,7 +718,7 @@ def get_organizer_navigation(request):
})
merge_in(nav, sorted(
sum((list(a[1]) for a in nav_organizer.send(request.organizer, request=request, organizer=request.organizer)),
sum((list(a[1]) for a in nav_organizer.send(request.organizer, skip_exceptions=True, request=request, organizer=request.organizer)),
[]),
key=lambda r: (1 if r.get('parent') else 0, r['label'])
))
+3 -3
View File
@@ -370,7 +370,7 @@ def event_index(request, organizer, event):
widgets = []
if can_view_orders:
for r, result in event_dashboard_widgets.send(sender=request.event, subevent=subevent, lazy=True):
for r, result in event_dashboard_widgets.send(sender=request.event, subevent=subevent, lazy=True, skip_exceptions=True):
widgets.extend(result)
ctx = {
@@ -406,7 +406,7 @@ def event_index_widgets_lazy(request, organizer, event):
request=request)
widgets = []
if can_view_orders:
for r, result in event_dashboard_widgets.send(sender=request.event, subevent=subevent, lazy=False):
for r, result in event_dashboard_widgets.send(sender=request.event, subevent=subevent, lazy=False, skip_exceptions=True):
widgets.extend(result)
return build_json_response(widgets)
@@ -666,7 +666,7 @@ def user_index_widgets_lazy(request):
def user_index(request):
widgets = []
for r, result in user_dashboard_widgets.send(request, user=request.user):
for r, result in user_dashboard_widgets.send(request, skip_exceptions=True, user=request.user):
widgets.extend(result)
ctx = {
+1 -1
View File
@@ -239,7 +239,7 @@ class ActionView(View):
})
def get(self, request, *args, **kwargs):
u = request.GET.get('query', '')
u = request.GET.get('query', '').strip()
if len(u) < 2:
return JsonResponse({'results': []})