Shorter and more useful global dashboard

This commit is contained in:
Raphael Michel
2018-01-15 11:32:30 +01:00
parent 6b7338aff0
commit 96247d5fa0
4 changed files with 167 additions and 68 deletions

View File

@@ -341,6 +341,9 @@ class EventFilterForm(FilterForm):
('notlive', _('Shop not live')), ('notlive', _('Shop not live')),
('future', _('Presale not started')), ('future', _('Presale not started')),
('past', _('Presale over')), ('past', _('Presale over')),
('date_future', _('Single event running or in the future')),
('date_past', _('Single event in the past')),
('series', _('Event series')),
), ),
required=False required=False
) )
@@ -388,6 +391,24 @@ class EventFilterForm(FilterForm):
qs = qs.filter(presale_start__gte=now()) qs = qs.filter(presale_start__gte=now())
elif fdata.get('status') == 'past': elif fdata.get('status') == 'past':
qs = qs.filter(presale_end__lte=now()) qs = qs.filter(presale_end__lte=now())
elif fdata.get('status') == 'date_future':
qs = qs.filter(
Q(has_subevents=False) &
Q(
Q(Q(date_to__isnull=True) & Q(date_from__gte=now()))
| Q(Q(date_to__isnull=False) & Q(date_to__gte=now()))
)
)
elif fdata.get('status') == 'date_past':
qs = qs.filter(
Q(has_subevents=False) &
Q(
Q(Q(date_to__isnull=True) & Q(date_from__lt=now()))
| Q(Q(date_to__isnull=False) & Q(date_to__lt=now()))
)
)
elif fdata.get('status') == 'series':
qs = qs.filter(has_subevents=True)
if fdata.get('organizer'): if fdata.get('organizer'):
qs = qs.filter(organizer=fdata.get('organizer')) qs = qs.filter(organizer=fdata.get('organizer'))

View File

@@ -2,29 +2,88 @@
{% load i18n %} {% load i18n %}
{% block title %}{% trans "Dashboard" %}{% endblock %} {% block title %}{% trans "Dashboard" %}{% endblock %}
{% block content %} {% block content %}
<h1>{% trans "Dashboard" %}</h1> <h1>{% trans "Dashboard" %}</h1>
<div class="dropdown-container"> <div class="dropdown-container">
<input type="text" class="form-control" id="dashboard_query" <input type="text" class="form-control" id="dashboard_query"
placeholder="{% trans "Go to event" %}" placeholder="{% trans "Go to event" %}"
data-typeahead-query autofocus> data-typeahead-query autofocus>
<ul data-event-typeahead data-source="{% url "control:events.typeahead" %}" data-typeahead-field="#dashboard_query" <ul data-event-typeahead data-source="{% url "control:events.typeahead" %}" data-typeahead-field="#dashboard_query"
class="event-dropdown dropdown-menu"> class="event-dropdown dropdown-menu">
</ul> </ul>
</div> </div>
<h2>{% trans "Your upcoming events" %}</h2>
<div class="dashboard"> <div class="dashboard">
{% for w in widgets %} <div class="widget-small widget-container">
<a href="{% url "control:events.add" %}" class="widget">
<div class="newevent"><span class="fa fa-plus-circle"></span>{% trans "Create a new event" %}</div>
</a>
</div>
{% for w in upcoming %}
<div class="widget-{{ w.display_size|default:"small" }} {{ w.container_class|default:"widget-container" }}"> <div class="widget-{{ w.display_size|default:"small" }} {{ w.container_class|default:"widget-container" }}">
{% if w.url %} <div class="widget">
<a href="{{ w.url }}" class="widget"> {{ w.content|safe }}
{{ w.content|safe }} </div>
</a>
{% else %}
<div class="widget">
{{ w.content|safe }}
</div>
{% endif %}
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% if upcoming %}
<p class="">
<a href="{% url "control:events" %}?ordering=date_from&status=date_future" class="">
{% trans "View all upcoming events" %}
</a>
</p>
{% endif %}
{% if past %}
<h2>{% trans "Your most recent events" %}</h2>
<div class="dashboard">
{% for w in past %}
<div class="widget-{{ w.display_size|default:"small" }} {{ w.container_class|default:"widget-container" }}">
<div class="widget">
{{ w.content|safe }}
</div>
</div>
{% endfor %}
</div>
<p class="">
<a href="{% url "control:events" %}?ordering=date_from&status=-date_to" class="">
{% trans "View all recent events" %}
</a>
</p>
{% endif %}
{% if series %}
<h2>{% trans "Your event series" %}</h2>
<div class="dashboard">
{% for w in series %}
<div class="widget-{{ w.display_size|default:"small" }} {{ w.container_class|default:"widget-container" }}">
<div class="widget">
{{ w.content|safe }}
</div>
</div>
{% endfor %}
</div>
<p class="">
<a href="{% url "control:events" %}?ordering=-date_to&status=series" class="">
{% trans "View all event series" %}
</a>
</p>
{% endif %}
{% if widgets %}
<h2>{% trans "Other features" %}</h2>
<div class="dashboard">
{% for w in widgets %}
<div class="widget-{{ w.display_size|default:"small" }} {{ w.container_class|default:"widget-container" }}">
{% if w.url %}
<a href="{{ w.url }}" class="widget">
{{ w.content|safe }}
</a>
{% else %}
<div class="widget">
{{ w.content|safe }}
</div>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
{% endblock %} {% endblock %}

View File

@@ -17,7 +17,7 @@ from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _, ungettext from django.utils.translation import ugettext_lazy as _, ungettext
from pretix.base.models import ( from pretix.base.models import (
Event, Item, Order, OrderPosition, RequiredAction, SubEvent, Voucher, Item, Order, OrderPosition, RequiredAction, SubEvent, Voucher,
WaitingListEntry, WaitingListEntry,
) )
from pretix.base.models.checkin import CheckinList from pretix.base.models.checkin import CheckinList
@@ -280,11 +280,43 @@ def event_index(request, organizer, event):
return render(request, 'pretixcontrol/event/index.html', ctx) return render(request, 'pretixcontrol/event/index.html', ctx)
@receiver(signal=user_dashboard_widgets) def annotated_event_query(user):
def user_event_widgets(**kwargs): active_orders = Order.objects.filter(
user = kwargs.pop('user') event=OuterRef('pk'),
status__in=[Order.STATUS_PENDING, Order.STATUS_PAID]
).order_by().values('event').annotate(
c=Count('*')
).values(
'c'
)
required_actions = RequiredAction.objects.filter(
event=OuterRef('pk'),
done=False
)
qs = user.get_events_with_any_permission().annotate(
order_count=Subquery(active_orders, output_field=IntegerField()),
has_ra=Exists(required_actions)
).annotate(
min_from=Min('subevents__date_from'),
max_from=Max('subevents__date_from'),
max_to=Max('subevents__date_to'),
max_fromto=Greatest(Max('subevents__date_to'), Max('subevents__date_from')),
).annotate(
order_to=Coalesce('max_fromto', 'max_to', 'max_from', 'date_to', 'date_from'),
)
return qs
def widgets_for_event_qs(qs, user, nmax):
widgets = [] widgets = []
# Get set of events where we have the permission to show the # of orders
events_with_orders = set(qs.filter(
Q(organizer_id__in=user.teams.filter(all_events=True, can_view_orders=True).values_list('organizer', flat=True))
| Q(id__in=user.teams.filter(can_view_orders=True).values_list('limit_events__id', flat=True))
).values_list('id', flat=True))
tpl = """ tpl = """
<a href="{url}" class="event"> <a href="{url}" class="event">
<div class="name">{event}</div> <div class="name">{event}</div>
@@ -299,50 +331,21 @@ def user_event_widgets(**kwargs):
</div> </div>
""" """
active_orders = Order.objects.filter( events = qs.prefetch_related(
event=OuterRef('pk'),
status__in=[Order.STATUS_PENDING, Order.STATUS_PAID]
).order_by().values('event').annotate(
c=Count('*')
).values(
'c'
)
required_actions = RequiredAction.objects.filter(
event=OuterRef('pk'),
done=False
)
# Get set of events where we have the permission to show the # of orders
events_with_orders = set(Event.objects.filter(
Q(organizer_id__in=user.teams.filter(all_events=True, can_view_orders=True).values_list('organizer', flat=True))
| Q(id__in=user.teams.filter(can_view_orders=True).values_list('limit_events__id', flat=True))
).values_list('id', flat=True))
events = user.get_events_with_any_permission().annotate(
order_count=Subquery(active_orders, output_field=IntegerField()),
has_ra=Exists(required_actions)
).annotate(
min_from=Min('subevents__date_from'),
max_from=Max('subevents__date_from'),
max_to=Max('subevents__date_to'),
max_fromto=Greatest(Max('subevents__date_to'), Max('subevents__date_from'))
).annotate(
order_from=Coalesce('min_from', 'date_from'),
order_to=Coalesce('max_fromto', 'max_to', 'max_from', 'date_to'),
).order_by(
'-order_from', 'name'
).prefetch_related(
'_settings_objects', 'organizer___settings_objects' '_settings_objects', 'organizer___settings_objects'
).select_related('organizer')[:100] ).select_related('organizer')[:nmax]
for event in events: for event in events:
dr = event.get_date_range_display() tz = pytz.timezone(event.cache.get_or_set('timezone', lambda: event.settings.timezone))
tz = pytz.timezone(event.settings.timezone)
if event.has_subevents: if event.has_subevents:
dr = daterange( dr = daterange(
(event.min_from).astimezone(tz), (event.min_from).astimezone(tz),
(event.max_fromto or event.max_to or event.max_from).astimezone(tz) (event.max_fromto or event.max_to or event.max_from).astimezone(tz)
) )
else:
if event.date_to:
dr = daterange(event.date_from.astimezone(tz), event.date_to.astimezone(tz))
else:
dr = date_format(event.date_from.astimezone(tz), "DATE_FORMAT")
if event.has_ra: if event.has_ra:
status = ('danger', _('Action required')) status = ('danger', _('Action required'))
@@ -400,26 +403,42 @@ def user_event_widgets(**kwargs):
return widgets return widgets
@receiver(signal=user_dashboard_widgets)
def new_event_widgets(**kwargs):
return [
{
'content': '<div class="newevent"><span class="fa fa-plus-circle"></span>{t}</div>'.format(
t=_('Create a new event')
),
'display_size': 'small',
'priority': 50,
'url': reverse('control:events.add')
}
]
def user_index(request): def user_index(request):
widgets = [] widgets = []
for r, result in user_dashboard_widgets.send(request, user=request.user): for r, result in user_dashboard_widgets.send(request, user=request.user):
widgets.extend(result) widgets.extend(result)
ctx = { ctx = {
'widgets': rearrange(widgets), 'widgets': rearrange(widgets),
'upcoming': widgets_for_event_qs(
annotated_event_query(request.user).filter(
Q(has_subevents=False) &
Q(
Q(Q(date_to__isnull=True) & Q(date_from__gte=now()))
| Q(Q(date_to__isnull=False) & Q(date_to__gte=now()))
)
).order_by('date_from'),
request.user,
7
),
'past': widgets_for_event_qs(
annotated_event_query(request.user).filter(
Q(has_subevents=False) &
Q(
Q(Q(date_to__isnull=True) & Q(date_from__lt=now()))
| Q(Q(date_to__isnull=False) & Q(date_to__lt=now()))
)
).order_by('-order_to'),
request.user,
8
),
'series': widgets_for_event_qs(
annotated_event_query(request.user).filter(
has_subevents=True
).order_by('-order_to'),
request.user,
8
),
} }
return render(request, 'pretixcontrol/dashboard.html', ctx) return render(request, 'pretixcontrol/dashboard.html', ctx)

View File

@@ -42,7 +42,7 @@ class EventList(PaginationMixin, ListView):
max_fromto=Greatest(Max('subevents__date_to'), Max('subevents__date_from')) max_fromto=Greatest(Max('subevents__date_to'), Max('subevents__date_from'))
).annotate( ).annotate(
order_from=Coalesce('min_from', 'date_from'), order_from=Coalesce('min_from', 'date_from'),
order_to=Coalesce('max_fromto', 'max_to', 'max_from', 'date_to'), order_to=Coalesce('max_fromto', 'max_to', 'max_from', 'date_to', 'date_from'),
) )
sum_tickets_paid = Quota.objects.filter( sum_tickets_paid = Quota.objects.filter(