Compare commits

..
Author SHA1 Message Date
7aaca873e3 Update src/pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
Co-authored-by: pajowu <engelhardt@pretix.eu>
2026-07-27 14:40:00 +02:00
Raphael Michel 0254788124 Event-dashboard: Lazy-load warnings
I don't like this, it's not pretty, but we have no quick way of fixing
the overpaid orders check otherwise and a slow query on the event
dashboard feels really bad, because even if you don't wanna see the
dashboard ou are always going through it in navigation and that can get
very annoying very quickly.
2026-07-26 21:58:14 +02:00
8 changed files with 124 additions and 101 deletions
@@ -0,0 +1,58 @@
{% load i18n %}
{% if has_overpaid_orders %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
This event contains <strong>overpaid orders</strong>, for example due to duplicate payment attempts.
You should review the cases and consider refunding the overpaid amount to the user.
{% endblocktrans %}
<a href="{% url "control:event.orders" event=request.event.slug organizer=request.event.organizer.slug %}?status=overpaid"
class="btn btn-primary">{% trans "Show overpaid orders" %}</a>
</div>
{% endif %}
{% if has_pending_refunds %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
This event contains <strong>pending refunds</strong> that you should take care of.
{% endblocktrans %}
<a href="{% url "control:event.orders.refunds" event=request.event.slug organizer=request.event.organizer.slug %}"
class="btn btn-primary">{% trans "Show pending refunds" %}</a>
</div>
{% endif %}
{% if has_cancellation_requests %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
This event contains <strong>requested cancellations</strong> that you should take care of.
{% endblocktrans %}
<a href="{% url "control:event.orders" event=request.event.slug organizer=request.event.organizer.slug %}?status=rc"
class="btn btn-primary">{% trans "Show orders requesting cancellation" %}</a>
</div>
{% endif %}
{% if has_pending_approvals %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
This event contains <strong>pending approvals</strong> that you should take care of.
{% endblocktrans %}
<a href="{% url "control:event.orders" event=request.event.slug organizer=request.event.organizer.slug %}?status=pa"
class="btn btn-primary">{% trans "Show orders pending approval" %}</a>
</div>
{% endif %}
{% if has_pending_orders_with_full_payment %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
This event contains <strong>fully paid orders</strong> that are not marked as paid, probably
because no quota was left at the time their payment arrived. You should review the cases and consider
either refunding the customer or creating more space.
{% endblocktrans %}
<a href="{% url "control:event.orders" event=request.event.slug organizer=request.event.organizer.slug %}?status=pendingpaid"
class="btn btn-primary">{% trans "Show affected orders" %}</a>
</div>
{% endif %}
{% if has_sync_problems %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
Orders in this event could not be <strong>synced to an external system</strong> as configured.
{% endblocktrans %}
<a href="{% url "control:event.datasync.failedjobs" event=request.event.slug organizer=request.event.organizer.slug %}"
class="btn btn-primary">{% trans "Show sync problems" %}</a>
</div>
{% endif %}
@@ -14,6 +14,7 @@
{% else %}
{{ request.event.get_date_range_display }}
{% endif %}
<span id="warnings_loading" class="fa fa-cog fa-spin"></span>
</small>
</h1>
<div class="helper-space-below">
@@ -31,63 +32,7 @@
</div>
<div class="clearfix"></div>
</div>
{% if has_overpaid_orders %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
This event contains <strong>overpaid orders</strong>, for example due to duplicate payment attempts.
You should review the cases and consider refunding the overpaid amount to the user.
{% endblocktrans %}
<a href="{% url "control:event.orders" event=request.event.slug organizer=request.event.organizer.slug %}?status=overpaid"
class="btn btn-primary">{% trans "Show overpaid orders" %}</a>
</div>
{% endif %}
{% if has_pending_refunds %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
This event contains <strong>pending refunds</strong> that you should take care of.
{% endblocktrans %}
<a href="{% url "control:event.orders.refunds" event=request.event.slug organizer=request.event.organizer.slug %}"
class="btn btn-primary">{% trans "Show pending refunds" %}</a>
</div>
{% endif %}
{% if has_cancellation_requests %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
This event contains <strong>requested cancellations</strong> that you should take care of.
{% endblocktrans %}
<a href="{% url "control:event.orders" event=request.event.slug organizer=request.event.organizer.slug %}?status=rc"
class="btn btn-primary">{% trans "Show orders requesting cancellation" %}</a>
</div>
{% endif %}
{% if has_pending_approvals %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
This event contains <strong>pending approvals</strong> that you should take care of.
{% endblocktrans %}
<a href="{% url "control:event.orders" event=request.event.slug organizer=request.event.organizer.slug %}?status=pa"
class="btn btn-primary">{% trans "Show orders pending approval" %}</a>
</div>
{% endif %}
{% if has_pending_orders_with_full_payment %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
This event contains <strong>fully paid orders</strong> that are not marked as paid, probably
because no quota was left at the time their payment arrived. You should review the cases and consider
either refunding the customer or creating more space.
{% endblocktrans %}
<a href="{% url "control:event.orders" event=request.event.slug organizer=request.event.organizer.slug %}?status=pendingpaid"
class="btn btn-primary">{% trans "Show affected orders" %}</a>
</div>
{% endif %}
{% if has_sync_problems %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
Orders in this event could not be <strong>synced to an external system</strong> as configured.
{% endblocktrans %}
<a href="{% url "control:event.datasync.failedjobs" event=request.event.slug organizer=request.event.organizer.slug %}"
class="btn btn-primary">{% trans "Show sync problems" %}</a>
</div>
{% endif %}
<div id="warnings_target"></div>
{% eventsignal request.event "pretix.control.signals.event_dashboard_top" request=request %}
{% if request.event.has_subevents %}
+2 -1
View File
@@ -273,7 +273,8 @@ urlpatterns = [
re_path(r'^$', dashboards.event_index, name='event.index'),
re_path(r'^qrcode.(?P<filetype>(png|jpeg|gif|svg))$', event.EventQRCode.as_view(), name='event.qrcode'),
re_path(r'^widgets.json$', dashboards.event_index_widgets_lazy, name='event.index.widgets'),
re_path(r'^logs/embed$', dashboards.event_index_log_lazy, name='event.index.logs'),
re_path(r'^dashboard/partials/logs$', dashboards.event_index_log_lazy, name='event.index.logs'),
re_path(r'^dashboard/partials/warnings$', dashboards.event_index_warnings_lazy, name='event.index.warnings'),
re_path(r'^live/$', event.EventLive.as_view(), name='event.live'),
re_path(r'^transfer_session/$', event.EventTransferSession.as_view(), name='event.transfer_session'),
re_path(r'^logs/$', event.EventLog.as_view(), name='event.log'),
+42 -30
View File
@@ -379,6 +379,42 @@ def event_index(request, organizer, event):
'comment_form': CommentForm(initial={'comment': request.event.comment}, readonly=not can_change_event_settings),
}
ctx['timeline'] = [
{
'date': t.datetime.astimezone(request.event.timezone).date(),
'entry': t,
'time': t.datetime.astimezone(request.event.timezone)
}
for t in timeline_for_event(request.event, subevent)
]
ctx['today'] = now().astimezone(request.event.timezone).date()
ctx['nearly_now'] = now().astimezone(request.event.timezone) - timedelta(seconds=20)
resp = render(request, 'pretixcontrol/event/index.html', ctx)
return resp
def event_index_widgets_lazy(request, organizer, event):
subevent = None
if request.GET.get("subevent", "") != "" and request.event.has_subevents:
i = request.GET.get("subevent", "")
try:
subevent = request.event.subevents.get(pk=i)
except SubEvent.DoesNotExist:
pass
widgets = []
for r, result in event_dashboard_widgets.send(sender=request.event, subevent=subevent, lazy=False):
widgets.extend(result)
return build_json_response(widgets)
def event_index_warnings_lazy(request, organizer, event):
can_view_orders = request.user.has_event_permission(request.organizer, request.event, 'event.orders:read',
request=request)
can_change_event_settings = request.user.has_event_permission(request.organizer, request.event,
'event.settings.general:write', request=request)
ctx = {}
ctx['has_overpaid_orders'] = can_view_orders and Order.annotate_overpayments(request.event.orders).filter(
Q(~Q(status=Order.STATUS_CANCELED) & Q(pending_sum_t__lt=0))
| Q(Q(status=Order.STATUS_CANCELED) & Q(pending_sum_rc__lt=0))
@@ -402,35 +438,11 @@ def event_index(request, organizer, event):
| Q(failed_attempts__gt=0)
).exists()
ctx['timeline'] = [
{
'date': t.datetime.astimezone(request.event.timezone).date(),
'entry': t,
'time': t.datetime.astimezone(request.event.timezone)
}
for t in timeline_for_event(request.event, subevent)
]
ctx['today'] = now().astimezone(request.event.timezone).date()
ctx['nearly_now'] = now().astimezone(request.event.timezone) - timedelta(seconds=20)
resp = render(request, 'pretixcontrol/event/index.html', ctx)
# resp['Content-Security-Policy'] = "style-src 'unsafe-inline'"
return resp
def event_index_widgets_lazy(request, organizer, event):
subevent = None
if request.GET.get("subevent", "") != "" and request.event.has_subevents:
i = request.GET.get("subevent", "")
try:
subevent = request.event.subevents.get(pk=i)
except SubEvent.DoesNotExist:
pass
widgets = []
for r, result in event_dashboard_widgets.send(sender=request.event, subevent=subevent, lazy=False):
widgets.extend(result)
return build_json_response(widgets)
return render(
request,
'pretixcontrol/event/dashboard_partial_warnings.html',
ctx
)
def event_index_log_lazy(request, organizer, event):
@@ -465,7 +477,7 @@ def event_index_log_lazy(request, organizer, event):
return render(
request,
'pretixcontrol/event/logs_embed.html',
'pretixcontrol/event/dashboard_partial_logs.html',
{
'logs': qs[:5]
}
-10
View File
@@ -27,7 +27,6 @@ from http import cookies
from django.conf import settings
from django.core.exceptions import SuspiciousFileOperation
from django.middleware.csrf import CsrfViewMiddleware as BaseCsrfMiddleware
from PIL import Image
from requests.adapters import HTTPAdapter
from urllib3.connection import HTTPConnection, HTTPSConnection
@@ -43,7 +42,6 @@ from urllib3.util.timeout import _DEFAULT_TIMEOUT
from pretix.helpers.reportlab import ThumbnailingImageReader
from pretix.helpers.ssrf import should_block_access
from pretix.multidomain.middlewares import CsrfViewMiddleware
def monkeypatch_vobject_performance():
@@ -244,13 +242,6 @@ def monkeypatch_reportlab_imagereader():
utils.ImageReader.__init__ = new_init
def monkeypatch_csrf_middleware():
# Some views from django or plugins use the middleware or the derived decorators directly,
# so we have to also patch the stock middleware
BaseCsrfMiddleware._get_secret = CsrfViewMiddleware._get_secret
BaseCsrfMiddleware._set_csrf_cookie = CsrfViewMiddleware._set_csrf_cookie
def monkeypatch_all_at_ready():
monkeypatch_vobject_performance()
monkeypatch_pillow_safer()
@@ -258,4 +249,3 @@ def monkeypatch_all_at_ready():
monkeypatch_urllib3_ssrf_protection()
monkeypatch_cookie_morsel()
monkeypatch_reportlab_imagereader()
monkeypatch_csrf_middleware()
+13
View File
@@ -302,6 +302,19 @@ class CsrfViewMiddleware(BaseCsrfMiddleware):
# Content varies with the CSRF cookie, so set the Vary header.
patch_vary_headers(response, ('Cookie',))
def process_response(self, request, response):
if (
not settings.CSRF_USE_SESSIONS
and request.is_secure()
and settings.CSRF_COOKIE_NAME in response.cookies
and response.cookies[settings.CSRF_COOKIE_NAME].value
):
logger.warning("Usage of djangos CsrfViewMiddleware detected (legacy cookie found in response). "
"This may be caused by using csrf_project or requires_csrf_token from django.views.decorators.csrf. "
"Use the pretix.multidomain.middlewares equivalent instead.")
return super().process_response(request, response)
def handle_duplicated_csrftoken(request, response):
# Due to a Safari bug, in some browser, two csrftoken cookies can exist:
@@ -15,8 +15,12 @@ $(function () {
if ($("#logs_target").length == 0) {
return;
}
$.get("logs/embed", function (data) {
$("#logs_target").html(data)
add_log_expand_handlers($("#logs_target"))
$.get("dashboard/partials/logs", function (data) {
$("#logs_target").html(data)
add_log_expand_handlers($("#logs_target"))
});
$.get("dashboard/partials/warnings", function (data) {
$("#warnings_loading").remove()
$("#warnings_target").html(data)
});
});