forked from CGM_Public/pretix_original
Lazy-load dashboard widgets
This commit is contained in:
@@ -47,6 +47,7 @@
|
|||||||
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/orderchange.js" %}"></script>
|
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/orderchange.js" %}"></script>
|
||||||
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/typeahead.js" %}"></script>
|
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/typeahead.js" %}"></script>
|
||||||
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/quicksetup.js" %}"></script>
|
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/quicksetup.js" %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/dashboard.js" %}"></script>
|
||||||
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/tabs.js" %}"></script>
|
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/tabs.js" %}"></script>
|
||||||
<script type="text/javascript" src="{% static "pretixbase/js/details.js" %}"></script>
|
<script type="text/javascript" src="{% static "pretixbase/js/details.js" %}"></script>
|
||||||
<script type="text/javascript" src="{% static "pretixbase/js/asynctask.js" %}"></script>
|
<script type="text/javascript" src="{% static "pretixbase/js/asynctask.js" %}"></script>
|
||||||
|
|||||||
@@ -95,18 +95,30 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="dashboard">
|
<div class="dashboard">
|
||||||
{% for w in widgets %}
|
{% for w in widgets %}
|
||||||
<div class="widget-container widget-{{ w.display_size|default:"small" }}">
|
<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 #}
|
{% if w.url %}{# backwards compatibility #}
|
||||||
<a href="{{ w.url }}" class="widget">
|
<a href="{{ w.url }}" class="widget">
|
||||||
{{ w.content|safe }}
|
{% if w.lazy %}
|
||||||
|
<span class="fa fa-cog fa-4x"></span>
|
||||||
|
{% else %}
|
||||||
|
{{ w.content|safe }}
|
||||||
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
{% elif w.link %}
|
{% elif w.link %}
|
||||||
<a href="{{ w.link }}" class="widget">
|
<a href="{{ w.link }}" class="widget">
|
||||||
{{ w.content|safe }}
|
{% if w.lazy %}
|
||||||
|
<span class="fa fa-cog fa-4x´"></span>
|
||||||
|
{% else %}
|
||||||
|
{{ w.content|safe }}
|
||||||
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="widget">
|
<div class="widget">
|
||||||
{{ w.content|safe }}
|
{% if w.lazy %}
|
||||||
|
<span class="fa fa-cog fa-4x"></span>
|
||||||
|
{% else %}
|
||||||
|
{{ w.content|safe }}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ urlpatterns = [
|
|||||||
url(r'^search/orders/$', search.OrderSearch.as_view(), name='search.orders'),
|
url(r'^search/orders/$', search.OrderSearch.as_view(), name='search.orders'),
|
||||||
url(r'^event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include([
|
url(r'^event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include([
|
||||||
url(r'^$', dashboards.event_index, name='event.index'),
|
url(r'^$', dashboards.event_index, name='event.index'),
|
||||||
|
url(r'^widgets.json$', dashboards.event_index_widgets_lazy, name='event.index.widgets'),
|
||||||
url(r'^live/$', event.EventLive.as_view(), name='event.live'),
|
url(r'^live/$', event.EventLive.as_view(), name='event.live'),
|
||||||
url(r'^logs/$', event.EventLog.as_view(), name='event.log'),
|
url(r'^logs/$', event.EventLog.as_view(), name='event.log'),
|
||||||
url(r'^delete/$', event.EventDelete.as_view(), name='event.delete'),
|
url(r'^delete/$', event.EventDelete.as_view(), name='event.delete'),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from django.db.models import (
|
|||||||
)
|
)
|
||||||
from django.db.models.functions import Coalesce, Greatest
|
from django.db.models.functions import Coalesce, Greatest
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
from django.http import JsonResponse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@@ -36,44 +37,46 @@ NUM_WIDGET = '<div class="numwidget"><span class="num">{num}</span><span class="
|
|||||||
|
|
||||||
|
|
||||||
@receiver(signal=event_dashboard_widgets)
|
@receiver(signal=event_dashboard_widgets)
|
||||||
def base_widgets(sender, subevent=None, **kwargs):
|
def base_widgets(sender, subevent=None, lazy=False, **kwargs):
|
||||||
prodc = Item.objects.filter(
|
if not lazy:
|
||||||
event=sender, active=True,
|
prodc = Item.objects.filter(
|
||||||
).filter(
|
event=sender, active=True,
|
||||||
(Q(available_until__isnull=True) | Q(available_until__gte=now())) &
|
).filter(
|
||||||
(Q(available_from__isnull=True) | Q(available_from__lte=now()))
|
(Q(available_until__isnull=True) | Q(available_until__gte=now())) &
|
||||||
).count()
|
(Q(available_from__isnull=True) | Q(available_from__lte=now()))
|
||||||
|
).count()
|
||||||
|
|
||||||
if subevent:
|
if subevent:
|
||||||
opqs = OrderPosition.objects.filter(subevent=subevent)
|
opqs = OrderPosition.objects.filter(subevent=subevent)
|
||||||
else:
|
else:
|
||||||
opqs = OrderPosition.objects
|
opqs = OrderPosition.objects
|
||||||
|
|
||||||
tickc = opqs.filter(
|
tickc = opqs.filter(
|
||||||
order__event=sender, item__admission=True,
|
order__event=sender, item__admission=True,
|
||||||
order__status__in=(Order.STATUS_PAID, Order.STATUS_PENDING),
|
order__status__in=(Order.STATUS_PAID, Order.STATUS_PENDING),
|
||||||
).count()
|
).count()
|
||||||
|
|
||||||
paidc = opqs.filter(
|
paidc = opqs.filter(
|
||||||
order__event=sender, item__admission=True,
|
order__event=sender, item__admission=True,
|
||||||
order__status=Order.STATUS_PAID,
|
order__status=Order.STATUS_PAID,
|
||||||
).count()
|
).count()
|
||||||
|
|
||||||
if subevent:
|
if subevent:
|
||||||
rev = opqs.filter(
|
rev = opqs.filter(
|
||||||
order__event=sender, order__status=Order.STATUS_PAID
|
order__event=sender, order__status=Order.STATUS_PAID
|
||||||
).aggregate(
|
).aggregate(
|
||||||
sum=Sum('price')
|
sum=Sum('price')
|
||||||
)['sum'] or Decimal('0.00')
|
)['sum'] or Decimal('0.00')
|
||||||
else:
|
else:
|
||||||
rev = Order.objects.filter(
|
rev = Order.objects.filter(
|
||||||
event=sender,
|
event=sender,
|
||||||
status=Order.STATUS_PAID
|
status=Order.STATUS_PAID
|
||||||
).aggregate(sum=Sum('total'))['sum'] or Decimal('0.00')
|
).aggregate(sum=Sum('total'))['sum'] or Decimal('0.00')
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
'content': NUM_WIDGET.format(num=tickc, text=_('Attendees (ordered)')),
|
'content': None if lazy else NUM_WIDGET.format(num=tickc, text=_('Attendees (ordered)')),
|
||||||
|
'lazy': 'attendees-ordered',
|
||||||
'display_size': 'small',
|
'display_size': 'small',
|
||||||
'priority': 100,
|
'priority': 100,
|
||||||
'url': reverse('control:event.orders', kwargs={
|
'url': reverse('control:event.orders', kwargs={
|
||||||
@@ -82,7 +85,8 @@ def base_widgets(sender, subevent=None, **kwargs):
|
|||||||
}) + ('?subevent={}'.format(subevent.pk) if subevent else '')
|
}) + ('?subevent={}'.format(subevent.pk) if subevent else '')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'content': NUM_WIDGET.format(num=paidc, text=_('Attendees (paid)')),
|
'content': None if lazy else NUM_WIDGET.format(num=paidc, text=_('Attendees (paid)')),
|
||||||
|
'lazy': 'attendees-paid',
|
||||||
'display_size': 'small',
|
'display_size': 'small',
|
||||||
'priority': 100,
|
'priority': 100,
|
||||||
'url': reverse('control:event.orders.overview', kwargs={
|
'url': reverse('control:event.orders.overview', kwargs={
|
||||||
@@ -91,8 +95,9 @@ def base_widgets(sender, subevent=None, **kwargs):
|
|||||||
}) + ('?subevent={}'.format(subevent.pk) if subevent else '')
|
}) + ('?subevent={}'.format(subevent.pk) if subevent else '')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'content': NUM_WIDGET.format(
|
'content': None if lazy else NUM_WIDGET.format(
|
||||||
num=formats.localize(round_decimal(rev, sender.currency)), text=_('Total revenue ({currency})').format(currency=sender.currency)),
|
num=formats.localize(round_decimal(rev, sender.currency)), text=_('Total revenue ({currency})').format(currency=sender.currency)),
|
||||||
|
'lazy': 'total-revenue',
|
||||||
'display_size': 'small',
|
'display_size': 'small',
|
||||||
'priority': 100,
|
'priority': 100,
|
||||||
'url': reverse('control:event.orders.overview', kwargs={
|
'url': reverse('control:event.orders.overview', kwargs={
|
||||||
@@ -101,7 +106,8 @@ def base_widgets(sender, subevent=None, **kwargs):
|
|||||||
}) + ('?subevent={}'.format(subevent.pk) if subevent else '')
|
}) + ('?subevent={}'.format(subevent.pk) if subevent else '')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'content': NUM_WIDGET.format(num=prodc, text=_('Active products')),
|
'content': None if lazy else NUM_WIDGET.format(num=prodc, text=_('Active products')),
|
||||||
|
'lazy': 'active-products',
|
||||||
'display_size': 'small',
|
'display_size': 'small',
|
||||||
'priority': 100,
|
'priority': 100,
|
||||||
'url': reverse('control:event.items', kwargs={
|
'url': reverse('control:event.items', kwargs={
|
||||||
@@ -113,32 +119,36 @@ def base_widgets(sender, subevent=None, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
@receiver(signal=event_dashboard_widgets)
|
@receiver(signal=event_dashboard_widgets)
|
||||||
def waitinglist_widgets(sender, subevent=None, **kwargs):
|
def waitinglist_widgets(sender, subevent=None, lazy=False, **kwargs):
|
||||||
widgets = []
|
widgets = []
|
||||||
|
|
||||||
wles = WaitingListEntry.objects.filter(event=sender, subevent=subevent, voucher__isnull=True)
|
wles = WaitingListEntry.objects.filter(event=sender, subevent=subevent, voucher__isnull=True)
|
||||||
if wles.count():
|
if wles.count():
|
||||||
quota_cache = {}
|
if not lazy:
|
||||||
itemvar_cache = {}
|
quota_cache = {}
|
||||||
happy = 0
|
itemvar_cache = {}
|
||||||
|
happy = 0
|
||||||
|
|
||||||
for wle in wles:
|
for wle in wles:
|
||||||
if (wle.item, wle.variation) not in itemvar_cache:
|
if (wle.item, wle.variation) not in itemvar_cache:
|
||||||
itemvar_cache[(wle.item, wle.variation)] = (
|
itemvar_cache[(wle.item, wle.variation)] = (
|
||||||
wle.variation.check_quotas(subevent=wle.subevent, count_waitinglist=False, _cache=quota_cache)
|
wle.variation.check_quotas(subevent=wle.subevent, count_waitinglist=False, _cache=quota_cache)
|
||||||
if wle.variation
|
if wle.variation
|
||||||
else wle.item.check_quotas(subevent=wle.subevent, count_waitinglist=False, _cache=quota_cache)
|
else wle.item.check_quotas(subevent=wle.subevent, count_waitinglist=False, _cache=quota_cache)
|
||||||
)
|
)
|
||||||
row = itemvar_cache.get((wle.item, wle.variation))
|
row = itemvar_cache.get((wle.item, wle.variation))
|
||||||
if row[1] is None:
|
if row[1] is None:
|
||||||
itemvar_cache[(wle.item, wle.variation)] = (row[0], row[1])
|
itemvar_cache[(wle.item, wle.variation)] = (row[0], row[1])
|
||||||
happy += 1
|
happy += 1
|
||||||
elif row[1] > 0:
|
elif row[1] > 0:
|
||||||
itemvar_cache[(wle.item, wle.variation)] = (row[0], row[1] - 1)
|
itemvar_cache[(wle.item, wle.variation)] = (row[0], row[1] - 1)
|
||||||
happy += 1
|
happy += 1
|
||||||
|
|
||||||
widgets.append({
|
widgets.append({
|
||||||
'content': NUM_WIDGET.format(num=str(happy), text=_('available to give to people on waiting list')),
|
'content': None if lazy else NUM_WIDGET.format(
|
||||||
|
num=str(happy), text=_('available to give to people on waiting list')
|
||||||
|
),
|
||||||
|
'lazy': 'waitinglist',
|
||||||
'priority': 50,
|
'priority': 50,
|
||||||
'url': reverse('control:event.orders.waitinglist', kwargs={
|
'url': reverse('control:event.orders.waitinglist', kwargs={
|
||||||
'event': sender.slug,
|
'event': sender.slug,
|
||||||
@@ -146,7 +156,8 @@ def waitinglist_widgets(sender, subevent=None, **kwargs):
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
widgets.append({
|
widgets.append({
|
||||||
'content': NUM_WIDGET.format(num=str(wles.count()), text=_('total waiting list length')),
|
'content': None if lazy else NUM_WIDGET.format(num=str(wles.count()), text=_('total waiting list length')),
|
||||||
|
'lazy': lazy,
|
||||||
'display_size': 'small',
|
'display_size': 'small',
|
||||||
'priority': 50,
|
'priority': 50,
|
||||||
'url': reverse('control:event.orders.waitinglist', kwargs={
|
'url': reverse('control:event.orders.waitinglist', kwargs={
|
||||||
@@ -159,14 +170,18 @@ def waitinglist_widgets(sender, subevent=None, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
@receiver(signal=event_dashboard_widgets)
|
@receiver(signal=event_dashboard_widgets)
|
||||||
def quota_widgets(sender, subevent=None, **kwargs):
|
def quota_widgets(sender, subevent=None, lazy=False, **kwargs):
|
||||||
widgets = []
|
widgets = []
|
||||||
|
|
||||||
for q in sender.quotas.filter(subevent=subevent):
|
for q in sender.quotas.filter(subevent=subevent):
|
||||||
status, left = q.availability(allow_cache=True)
|
if not lazy:
|
||||||
|
status, left = q.availability(allow_cache=True)
|
||||||
widgets.append({
|
widgets.append({
|
||||||
'content': NUM_WIDGET.format(num='{}/{}'.format(left, q.size) if q.size is not None else '\u221e',
|
'content': None if lazy else NUM_WIDGET.format(
|
||||||
text=_('{quota} left').format(quota=escape(q.name))),
|
num='{}/{}'.format(left, q.size) if q.size is not None else '\u221e',
|
||||||
|
text=_('{quota} left').format(quota=escape(q.name))
|
||||||
|
),
|
||||||
|
'lazy': 'quota-{}'.format(q.pk),
|
||||||
'display_size': 'small',
|
'display_size': 'small',
|
||||||
'priority': 50,
|
'priority': 50,
|
||||||
'url': reverse('control:event.items.quotas.show', kwargs={
|
'url': reverse('control:event.items.quotas.show', kwargs={
|
||||||
@@ -209,14 +224,18 @@ def shop_state_widget(sender, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
@receiver(signal=event_dashboard_widgets)
|
@receiver(signal=event_dashboard_widgets)
|
||||||
def checkin_widget(sender, subevent=None, **kwargs):
|
def checkin_widget(sender, subevent=None, lazy=False, **kwargs):
|
||||||
widgets = []
|
widgets = []
|
||||||
qs = sender.checkin_lists.filter(subevent=subevent)
|
qs = sender.checkin_lists.filter(subevent=subevent)
|
||||||
qs = CheckinList.annotate_with_numbers(qs, sender)
|
if not lazy:
|
||||||
|
qs = CheckinList.annotate_with_numbers(qs, sender)
|
||||||
for cl in qs:
|
for cl in qs:
|
||||||
widgets.append({
|
widgets.append({
|
||||||
'content': NUM_WIDGET.format(num='{}/{}'.format(cl.checkin_count, cl.position_count),
|
'content': None if lazy else NUM_WIDGET.format(
|
||||||
text=_('Checked in – {list}').format(list=escape(cl.name))),
|
num='{}/{}'.format(cl.checkin_count, cl.position_count),
|
||||||
|
text=_('Checked in – {list}').format(list=escape(cl.name))
|
||||||
|
),
|
||||||
|
'lazy': 'checkin-{}'.format(cl.pk),
|
||||||
'display_size': 'small',
|
'display_size': 'small',
|
||||||
'priority': 50,
|
'priority': 50,
|
||||||
'url': reverse('control:event.orders.checkinlists.show', kwargs={
|
'url': reverse('control:event.orders.checkinlists.show', kwargs={
|
||||||
@@ -263,7 +282,7 @@ def event_index(request, organizer, event):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
widgets = []
|
widgets = []
|
||||||
for r, result in event_dashboard_widgets.send(sender=request.event, subevent=subevent):
|
for r, result in event_dashboard_widgets.send(sender=request.event, subevent=subevent, lazy=True):
|
||||||
widgets.extend(result)
|
widgets.extend(result)
|
||||||
|
|
||||||
can_change_orders = request.user.has_event_permission(request.organizer, request.event, 'can_change_orders',
|
can_change_orders = request.user.has_event_permission(request.organizer, request.event, 'can_change_orders',
|
||||||
@@ -320,6 +339,22 @@ def event_index(request, organizer, event):
|
|||||||
return resp
|
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 JsonResponse({'widgets': widgets})
|
||||||
|
|
||||||
|
|
||||||
def annotated_event_query(request):
|
def annotated_event_query(request):
|
||||||
active_orders = Order.objects.filter(
|
active_orders = Order.objects.filter(
|
||||||
event=OuterRef('pk'),
|
event=OuterRef('pk'),
|
||||||
|
|||||||
13
src/pretix/static/pretixcontrol/js/ui/dashboard.js
Normal file
13
src/pretix/static/pretixcontrol/js/ui/dashboard.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/*global $,gettext*/
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
if ($("div[data-lazy-id]").length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$.getJSON("widgets.json", 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -27,6 +27,16 @@
|
|||||||
width: 25%;
|
width: 25%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dashboard .widget-container.widget-lazy-loading {
|
||||||
|
text-align: center;
|
||||||
|
.fa-cog {
|
||||||
|
color: #ccc;
|
||||||
|
margin-top: 30px;
|
||||||
|
-webkit-animation: fa-spin 4s infinite linear;
|
||||||
|
animation: fa-spin 4s infinite linear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.dashboard-panels .panel-heading .fa {
|
.dashboard-panels .panel-heading .fa {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user