mirror of
https://github.com/pretix/pretix.git
synced 2026-08-13 11:17:01 +00:00
Move default statistics to plugin
This commit is contained in:
@@ -83,7 +83,7 @@ Dashboards
|
||||
|
||||
.. automodule:: pretix.control.signals
|
||||
:no-index:
|
||||
:members: event_dashboard_widgets, event_dashboard_widgets_override, user_dashboard_widgets, event_dashboard_top
|
||||
:members: event_dashboard_statistics, user_dashboard_widgets, event_dashboard_top
|
||||
|
||||
Ticket designs
|
||||
""""""""""""""
|
||||
|
||||
@@ -133,47 +133,14 @@ This signal is sent out to include custom HTML in the top part of the the event
|
||||
Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped.
|
||||
|
||||
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
An additional keyword argument ``subevent`` *can* contain a sub-event.
|
||||
"""
|
||||
|
||||
event_dashboard_widgets = EventPluginSignal()
|
||||
event_dashboard_statistics = EventPluginSignal()
|
||||
"""
|
||||
This signal is sent out to include widgets in the event dashboard. Receivers
|
||||
should return a list of dictionaries, where each dictionary can have the keys:
|
||||
|
||||
* content (SafeString, containing HTML)
|
||||
* display_size (str, one of "full" (whole row), "big" (half a row) or "small"
|
||||
(quarter of a row). May be ignored on small displays, default is "small")
|
||||
* priority (int, used for ordering, higher comes first, default is 1)
|
||||
* url (str, optional, if the full widget should be a link)
|
||||
This signal is sent out to include statistical content on the event dashboard.
|
||||
Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped.
|
||||
|
||||
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
An additional keyword argument ``subevent`` *can* contain a sub-event.
|
||||
|
||||
The keyword argument ``widgets_override_active`` is set to ``True`` when a plugin
|
||||
has returned content from :py:data:`event_dashboard_widgets_override` and is taking
|
||||
over the KPI section of the dashboard. Receivers that produce widgets which would
|
||||
be redundant with such a replacement (e.g. the built-in attendees/revenue/products
|
||||
KPI tiles) should return an empty list in that case. Receivers that provide widgets
|
||||
unrelated to the KPI tiles (waitinglist, quotas, check-in lists, shop state, etc.)
|
||||
should ignore the flag and continue to return their widgets — those will render
|
||||
below the override block.
|
||||
"""
|
||||
|
||||
event_dashboard_widgets_override = EventPluginSignal()
|
||||
"""
|
||||
This signal allows a plugin to take over the KPI section of the event dashboard
|
||||
with custom HTML. Receivers should return an HTML string (or an empty string /
|
||||
``None`` to opt out). If any receiver returns non-empty HTML, that HTML is
|
||||
rendered at the top of the dashboard, and receivers of
|
||||
:py:data:`event_dashboard_widgets` are informed via the
|
||||
``widgets_override_active=True`` keyword argument so they can suppress widgets
|
||||
that the override replaces. The remaining widgets (those that don't opt out)
|
||||
still render below the override block. Multiple receivers' return values are
|
||||
concatenated.
|
||||
|
||||
As with all event plugin signals, the ``sender`` keyword argument will contain
|
||||
the event. An additional keyword argument ``subevent`` *can* contain a sub-event.
|
||||
"""
|
||||
|
||||
user_dashboard_widgets = GlobalSignal()
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{% load i18n %}
|
||||
{% load humanize %}
|
||||
{% if count %}
|
||||
<a href="{% url "control:event.orders.waitinglist" event=request.event.slug organizer=request.organizer.slug %}">
|
||||
{% blocktrans trimmed with number=count|intcomma count count=count %}
|
||||
{{ number }} person waiting
|
||||
{% plural %}
|
||||
{{ number }} persons waiting
|
||||
{% endblocktrans %}
|
||||
</a>
|
||||
{% endif %}
|
||||
@@ -71,16 +71,25 @@
|
||||
hx-indicator="#warnings_indicator"
|
||||
hx-trigger="load"></div>
|
||||
|
||||
{% if stats %}
|
||||
{{ stats }}
|
||||
{% endif %}
|
||||
<p> </p>
|
||||
|
||||
{% if not request.event.items.exists %}
|
||||
{% include "pretixcontrol/event/dashboard_partial_welcome.html" %}
|
||||
{% endif %}
|
||||
|
||||
{% if not request.event.has_subevents %}
|
||||
{% elif not request.event.has_subevents %}
|
||||
{% include "pretixcontrol/event/fragment_timeline.html" %}
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="pull-right">
|
||||
<div hx-get="{% url "control:event.index.waiting" organizer=request.organizer.slug event=request.event.slug %}"
|
||||
hx-trigger="load">
|
||||
<span class="loading-mock loading-mock-text-short"></span>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="panel-title">{% trans "Quotas" %}</h2>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
@@ -124,43 +133,6 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if widgets_override and 0 %}
|
||||
{{ widgets_override|safe }}
|
||||
{% endif %}
|
||||
{% if widgets %}
|
||||
<div class="dashboard">
|
||||
{% for w in widgets %}
|
||||
<div class="widget-container widget-{{ w.display_size|default:"small" }} {% if w.lazy %}widget-lazy-loading{% endif %}" data-lazy-id="{{ w.lazy }}">
|
||||
{% if w.url %}{# backwards compatibility #}
|
||||
<a href="{{ w.url }}" class="widget">
|
||||
{% if w.lazy %}
|
||||
<span class="fa fa-cog fa-4x"></span>
|
||||
{% else %}
|
||||
{{ w.content }}
|
||||
{% endif %}
|
||||
</a>
|
||||
{% elif w.link %}
|
||||
<a href="{{ w.link }}" class="widget">
|
||||
{% if w.lazy %}
|
||||
<span class="fa fa-cog fa-4x´"></span>
|
||||
{% else %}
|
||||
{{ w.content }}
|
||||
{% endif %}
|
||||
</a>
|
||||
{% else %}
|
||||
<div class="widget">
|
||||
{% if w.lazy %}
|
||||
<span class="fa fa-cog fa-4x"></span>
|
||||
{% else %}
|
||||
{{ w.content }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<p> </p>
|
||||
{% if "event.orders:read" in request.eventpermset or "event.orders:write" in request.eventpermset or "event.settings.general:write" in request.eventpermset or "event.items:write" in request.eventpermset %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
|
||||
@@ -272,10 +272,10 @@ urlpatterns = [
|
||||
re_path(r'^event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include([
|
||||
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'^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'^dashboard/partials/quotas$', dashboards.event_index_quotas_lazy, name='event.index.quotas'),
|
||||
re_path(r'^dashboard/partials/waiting$', dashboards.event_index_waiting_lazy, name='event.index.waiting'),
|
||||
re_path(r'^dashboard/partials/checkin$', dashboards.event_index_checkin_lazy, name='event.index.checkin'),
|
||||
re_path(r'^dashboard/partials/comment$', event.EventComment.as_view(), name='event.index.comment'),
|
||||
re_path(r'^live/$', event.EventLive.as_view(), name='event.live'),
|
||||
|
||||
@@ -33,192 +33,48 @@
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.humanize.templatetags.humanize import intcomma
|
||||
from django.db.models import (
|
||||
Count, IntegerField, Max, Min, OuterRef, Prefetch, Q, Subquery, Sum,
|
||||
Count, IntegerField, Max, Min, OuterRef, Q, Subquery,
|
||||
)
|
||||
from django.db.models.functions import Coalesce, Greatest
|
||||
from django.dispatch import receiver
|
||||
from django.http import Http404, JsonResponse
|
||||
from django.shortcuts import render
|
||||
from django.urls import reverse
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.html import conditional_escape, escape, format_html
|
||||
from django.utils.html import (
|
||||
conditional_escape, escape, format_html, format_html_join,
|
||||
)
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _, ngettext, pgettext
|
||||
|
||||
from pretix.base.decimal import round_decimal
|
||||
from pretix.base.models import (
|
||||
Item, ItemCategory, ItemVariation, Order, OrderPosition, OrderRefund,
|
||||
Question, Quota, SubEvent, Voucher, WaitingListEntry,
|
||||
Item, ItemCategory, Order, OrderRefund, Question, Quota, Voucher,
|
||||
WaitingListEntry,
|
||||
)
|
||||
from pretix.base.timeline import timeline_for_event
|
||||
from pretix.control.signals import (
|
||||
event_dashboard_widgets, event_dashboard_widgets_override,
|
||||
user_dashboard_widgets,
|
||||
event_dashboard_statistics, user_dashboard_widgets,
|
||||
)
|
||||
from pretix.helpers.daterange import daterange
|
||||
|
||||
from ...base.models.orders import CancellationRequest
|
||||
from ...base.models.organizer import TeamQuerySet
|
||||
from ...base.templatetags.money import money_filter
|
||||
from ..logdisplay import OVERVIEW_BANLIST
|
||||
from .utils import prepare_quotas_for_boxes
|
||||
|
||||
NUM_WIDGET = '<div class="numwidget"><span class="num">{num}</span><span class="text">{text}</span></div>'
|
||||
|
||||
|
||||
@receiver(signal=event_dashboard_widgets)
|
||||
def base_widgets(sender, subevent=None, lazy=False, widgets_override_active=False, **kwargs):
|
||||
if widgets_override_active:
|
||||
return []
|
||||
if not lazy:
|
||||
if subevent:
|
||||
opqs = OrderPosition.objects.filter(subevent=subevent)
|
||||
else:
|
||||
opqs = OrderPosition.objects
|
||||
|
||||
tickc = opqs.filter(
|
||||
order__event=sender, item__admission=True,
|
||||
order__status__in=(Order.STATUS_PAID, Order.STATUS_PENDING),
|
||||
).count()
|
||||
|
||||
paidc = opqs.filter(
|
||||
order__event=sender, item__admission=True,
|
||||
order__status=Order.STATUS_PAID,
|
||||
).count()
|
||||
|
||||
if subevent:
|
||||
rev = opqs.filter(
|
||||
order__event=sender, order__status=Order.STATUS_PAID
|
||||
).aggregate(
|
||||
sum=Sum('price')
|
||||
)['sum'] or Decimal('0.00')
|
||||
else:
|
||||
rev = Order.objects.filter(
|
||||
event=sender,
|
||||
status=Order.STATUS_PAID
|
||||
).aggregate(sum=Sum('total'))['sum'] or Decimal('0.00')
|
||||
|
||||
return [
|
||||
def event_index_waiting_lazy(request, organizer, event):
|
||||
wles = WaitingListEntry.objects.filter(event=request.event, voucher__isnull=True)
|
||||
return render(
|
||||
request,
|
||||
'pretixcontrol/event/dashboard_partial_waiting.html',
|
||||
{
|
||||
'content': None if lazy else format_html(NUM_WIDGET, num=intcomma(tickc), text=_('Attendees (ordered)')),
|
||||
'lazy': 'attendees-ordered',
|
||||
'display_size': 'small',
|
||||
'priority': 100,
|
||||
'url': reverse('control:event.orders', kwargs={
|
||||
'event': sender.slug,
|
||||
'organizer': sender.organizer.slug
|
||||
}) + ('?subevent={}'.format(subevent.pk) if subevent else '')
|
||||
},
|
||||
{
|
||||
'content': None if lazy else format_html(NUM_WIDGET, num=intcomma(paidc), text=_('Attendees (paid)')),
|
||||
'lazy': 'attendees-paid',
|
||||
'display_size': 'small',
|
||||
'priority': 100,
|
||||
'url': reverse('control:event.orders.overview', kwargs={
|
||||
'event': sender.slug,
|
||||
'organizer': sender.organizer.slug
|
||||
}) + ('?subevent={}'.format(subevent.pk) if subevent else '')
|
||||
},
|
||||
{
|
||||
'content': None if lazy else format_html(
|
||||
NUM_WIDGET,
|
||||
num=money_filter(round_decimal(rev, sender.currency), sender.currency, hide_currency=True),
|
||||
text=_('Total revenue ({currency})').format(currency=sender.currency)
|
||||
),
|
||||
'lazy': 'total-revenue',
|
||||
'display_size': 'small',
|
||||
'priority': 100,
|
||||
'url': reverse('control:event.orders.overview', kwargs={
|
||||
'event': sender.slug,
|
||||
'organizer': sender.organizer.slug
|
||||
}) + ('?subevent={}'.format(subevent.pk) if subevent else '')
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@receiver(signal=event_dashboard_widgets)
|
||||
def waitinglist_widgets(sender, subevent=None, lazy=False, **kwargs):
|
||||
widgets = []
|
||||
|
||||
wles = WaitingListEntry.objects.filter(event=sender, subevent=subevent, voucher__isnull=True)
|
||||
if wles.exists():
|
||||
if not lazy:
|
||||
quota_cache = {}
|
||||
happy = 0
|
||||
tuples = wles.values('item', 'variation').order_by().annotate(cnt=Count('id'))
|
||||
|
||||
items = {
|
||||
i.pk: i for i in sender.items.filter(id__in=[t['item'] for t in tuples]).prefetch_related(
|
||||
Prefetch('quotas',
|
||||
to_attr='_subevent_quotas',
|
||||
queryset=sender.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent)),
|
||||
)
|
||||
}
|
||||
vars = {
|
||||
i.pk: i for i in ItemVariation.objects.filter(
|
||||
item__event=sender, id__in=[t['variation'] for t in tuples if t['variation']]
|
||||
).prefetch_related(
|
||||
Prefetch('quotas',
|
||||
to_attr='_subevent_quotas',
|
||||
queryset=sender.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent)),
|
||||
)
|
||||
}
|
||||
|
||||
for wlt in tuples:
|
||||
item = items.get(wlt['item'])
|
||||
variation = vars.get(wlt['variation'])
|
||||
if not item:
|
||||
continue
|
||||
quotas = (
|
||||
variation._get_quotas(subevent=subevent)
|
||||
if variation
|
||||
else item._get_quotas(subevent=subevent)
|
||||
)
|
||||
row = (
|
||||
variation.check_quotas(subevent=subevent, count_waitinglist=False, _cache=quota_cache)
|
||||
if variation
|
||||
else item.check_quotas(subevent=subevent, count_waitinglist=False, _cache=quota_cache)
|
||||
)
|
||||
if row[1] is None:
|
||||
happy += wlt['cnt']
|
||||
elif row[1] > 0:
|
||||
happy += min(wlt['cnt'], row[1])
|
||||
for q in quotas:
|
||||
if q.size is not None:
|
||||
quota_cache[q.pk] = (quota_cache[q.pk][0], quota_cache[q.pk][1] - min(wlt['cnt'], row[1]))
|
||||
|
||||
widgets.append({
|
||||
'content': None if lazy else format_html(
|
||||
NUM_WIDGET, num=intcomma(happy), text=_('available to give to people on waiting list')
|
||||
),
|
||||
'lazy': 'waitinglist-avail',
|
||||
'priority': 50,
|
||||
'url': reverse('control:event.orders.waitinglist', kwargs={
|
||||
'event': sender.slug,
|
||||
'organizer': sender.organizer.slug,
|
||||
})
|
||||
})
|
||||
widgets.append({
|
||||
'content': None if lazy else format_html(
|
||||
NUM_WIDGET, num=intcomma(wles.count()), text=_('total waiting list length')
|
||||
),
|
||||
'lazy': 'waitinglist-length',
|
||||
'display_size': 'small',
|
||||
'priority': 50,
|
||||
'url': reverse('control:event.orders.waitinglist', kwargs={
|
||||
'event': sender.slug,
|
||||
'organizer': sender.organizer.slug,
|
||||
})
|
||||
})
|
||||
|
||||
return widgets
|
||||
'count': wles.count,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def build_json_response(widgets):
|
||||
@@ -228,33 +84,20 @@ def build_json_response(widgets):
|
||||
|
||||
|
||||
def event_index(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
|
||||
can_view_orders = request.user.has_event_permission(
|
||||
request.organizer,
|
||||
request.event,
|
||||
'event.orders:read',
|
||||
request=request
|
||||
)
|
||||
|
||||
can_view_orders = request.user.has_event_permission(request.organizer, request.event, 'event.orders:read',
|
||||
request=request)
|
||||
|
||||
widgets = []
|
||||
widgets_override = ''
|
||||
stats = []
|
||||
if can_view_orders:
|
||||
for r, result in event_dashboard_widgets_override.send(sender=request.event, subevent=subevent, request=request):
|
||||
if result:
|
||||
widgets_override += result
|
||||
for r, result in event_dashboard_widgets.send(
|
||||
sender=request.event, subevent=subevent, lazy=True,
|
||||
widgets_override_active=bool(widgets_override),
|
||||
):
|
||||
widgets.extend(result)
|
||||
for r, result in event_dashboard_statistics.send(sender=request.event, request=request):
|
||||
stats.append(result)
|
||||
|
||||
ctx = {
|
||||
'widgets': rearrange(widgets),
|
||||
'widgets_override': widgets_override,
|
||||
'subevent': subevent,
|
||||
'stats': format_html_join("", "{}", [(s,) for s in stats]),
|
||||
}
|
||||
|
||||
if not request.event.has_subevents:
|
||||
@@ -273,22 +116,6 @@ def event_index(request, organizer, event):
|
||||
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)
|
||||
|
||||
@@ -564,6 +564,8 @@ class SubEventDetail(EventPermissionRequiredMixin, DetailView):
|
||||
}
|
||||
for t in timeline_for_event(self.request.event, self.object)
|
||||
]
|
||||
ctx['today'] = now().astimezone(self.request.event.timezone).date()
|
||||
ctx['nearly_now'] = now().astimezone(self.request.event.timezone) - timedelta(seconds=20)
|
||||
|
||||
return super().get_context_data(
|
||||
**kwargs,
|
||||
|
||||
@@ -19,12 +19,21 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from decimal import Decimal
|
||||
|
||||
from django.contrib.humanize.templatetags.humanize import intcomma
|
||||
from django.db.models import Sum
|
||||
from django.dispatch import receiver
|
||||
from django.template.loader import get_template
|
||||
from django.urls import resolve, reverse
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from pretix.base.decimal import round_decimal
|
||||
from pretix.base.models import Order, OrderPosition
|
||||
from pretix.base.signals import order_paid, order_placed
|
||||
from pretix.control.signals import nav_event
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
from pretix.control.signals import event_dashboard_statistics, nav_event
|
||||
|
||||
|
||||
@receiver(nav_event, dispatch_uid="statistics_nav")
|
||||
@@ -58,3 +67,62 @@ def clear_cache(sender, *args, **kwargs):
|
||||
|
||||
order_placed.connect(clear_cache)
|
||||
order_paid.connect(clear_cache)
|
||||
|
||||
|
||||
@receiver(event_dashboard_statistics, dispatch_uid="statistics_event_dashboard_state")
|
||||
def event_dashboard_stats_receivers(sender, request, **kwargs):
|
||||
if len(event_dashboard_statistics.receivers) > 1:
|
||||
# Only render this as a fallback if no other plugins exist
|
||||
return ""
|
||||
|
||||
opqs = OrderPosition.objects
|
||||
tickc = opqs.filter(
|
||||
order__event=sender, item__admission=True,
|
||||
order__status__in=(Order.STATUS_PAID, Order.STATUS_PENDING),
|
||||
).count()
|
||||
paidc = opqs.filter(
|
||||
order__event=sender, item__admission=True,
|
||||
order__status=Order.STATUS_PAID,
|
||||
).count()
|
||||
rev = Order.objects.filter(
|
||||
event=sender,
|
||||
status=Order.STATUS_PAID
|
||||
).aggregate(sum=Sum('total'))['sum'] or Decimal('0.00')
|
||||
num_widget = '<div class="numwidget"><span class="num">{num}</span><span class="text">{text}</span></div>'
|
||||
|
||||
widgets = [
|
||||
{
|
||||
'content': format_html(num_widget, num=intcomma(tickc), text=_('Attendees (ordered)')),
|
||||
'display_size': 'small',
|
||||
'priority': 100,
|
||||
'url': reverse('control:event.orders', kwargs={
|
||||
'event': sender.slug,
|
||||
'organizer': sender.organizer.slug
|
||||
})
|
||||
},
|
||||
{
|
||||
'content': format_html(num_widget, num=intcomma(paidc), text=_('Attendees (paid)')),
|
||||
'display_size': 'small',
|
||||
'priority': 100,
|
||||
'url': reverse('control:event.orders.overview', kwargs={
|
||||
'event': sender.slug,
|
||||
'organizer': sender.organizer.slug
|
||||
})
|
||||
},
|
||||
{
|
||||
'content': format_html(
|
||||
num_widget,
|
||||
num=money_filter(round_decimal(rev, sender.currency), sender.currency, hide_currency=True),
|
||||
text=_('Total revenue ({currency})').format(currency=sender.currency)
|
||||
),
|
||||
'display_size': 'small',
|
||||
'priority': 100,
|
||||
'url': reverse('control:event.orders.overview', kwargs={
|
||||
'event': sender.slug,
|
||||
'organizer': sender.organizer.slug
|
||||
})
|
||||
},
|
||||
]
|
||||
template = get_template('pretixplugins/statistics/dashboard.html')
|
||||
ctx = {'request': request, 'widgets': widgets}
|
||||
return template.render(ctx)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<div class="dashboard">
|
||||
{% for w in widgets %}
|
||||
<div class="widget-container widget-{{ w.display_size|default:"small" }}">
|
||||
{% if w.url %}
|
||||
<a href="{{ w.url }}" class="widget">
|
||||
{{ w.content }}
|
||||
</a>
|
||||
{% else %}
|
||||
<div class="widget">
|
||||
{{ w.content }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -1,16 +1,5 @@
|
||||
/* global $,gettext */
|
||||
|
||||
$(function () {
|
||||
if ($('div[data-lazy-id]').length == 0) {
|
||||
return
|
||||
}
|
||||
$.getJSON('widgets.json' + ($('select[name=\'subevent\']').val() ? '?subevent=' + $('select[name=\'subevent\']').val() : ''), function (data) {
|
||||
$.each(data.widgets, function (k, v) {
|
||||
$('[data-lazy-id=' + v.lazy + ']').removeClass('widget-lazy-loading')
|
||||
$('[data-lazy-id=' + v.lazy + '] .widget').html(v.content)
|
||||
})
|
||||
})
|
||||
})
|
||||
$(function () {
|
||||
$('.timeline').each(function () {
|
||||
let $tl = $(this)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*global $, gettext, ngettext, interpolate */
|
||||
/*global $, gettext, ngettext, interpolate, htmx */
|
||||
|
||||
function formatPrice(price, currency, locale) {
|
||||
if (!window.Intl || !Intl.NumberFormat) return price;
|
||||
@@ -1120,3 +1120,9 @@ $(function () {
|
||||
return $(this).find("button:not([type=button]), input[type=submit]").length > 0;
|
||||
}).areYouSure( {'message': gettext('You have unsaved changes!')});
|
||||
});
|
||||
|
||||
htmx.on('htmx:afterSettle', (e) => {
|
||||
console.log("afterSwap", e)
|
||||
setup_basics($(e.detail.elt))
|
||||
form_handlers($(e.detail.elt))
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user