mirror of
https://github.com/pretix/pretix.git
synced 2026-05-05 15:14:04 +00:00
Shorter and more useful global dashboard
This commit is contained in:
@@ -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'))
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user