Skip to content

Commit e314f5f

Browse files
authored
feat: manage attendees in admin mode (#898)
* manage attendees in admin mode * substitue item with products in checkin settings * use path() instead of url
1 parent b0f93ac commit e314f5f

File tree

7 files changed

+401
-7
lines changed

7 files changed

+401
-7
lines changed

app/eventyay/control/forms/filter.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,100 @@ def filter_qs(self, qs):
891891
return qs
892892

893893

894+
class AttendeeFilterForm(FilterForm):
895+
def __init__(self, *args, **kwargs):
896+
super().__init__(*args, **kwargs)
897+
self._checkin_status = None
898+
899+
query = forms.CharField(
900+
label=_('Name or email'),
901+
required=False,
902+
widget=forms.TextInput(attrs={'placeholder': _('Name or email')}),
903+
)
904+
905+
event_query = forms.CharField(
906+
label=_('Event name'),
907+
required=False,
908+
widget=forms.TextInput(attrs={'placeholder': _('Event name')}),
909+
)
910+
911+
event_status = forms.ChoiceField(
912+
label=_('Event filter'),
913+
choices=(
914+
('', _('All events')),
915+
('ongoing', _('Ongoing events')),
916+
('recent', _('Recent events')),
917+
),
918+
required=False,
919+
)
920+
921+
checkin_status = forms.ChoiceField(
922+
label=_('Check-in status'),
923+
choices=(
924+
('', _('All attendees')),
925+
('present', _('Present')),
926+
('left', _('Checked in but left')),
927+
('checked_in', _('Checked in')),
928+
('not_checked_in', _('Not checked in')),
929+
),
930+
required=False,
931+
)
932+
933+
ordering = forms.CharField(required=False, widget=forms.HiddenInput())
934+
935+
def filter_qs(self, qs):
936+
fdata = self.cleaned_data
937+
938+
# Search by attendee name or email
939+
if fdata.get('query'):
940+
qs = qs.filter(
941+
Q(attendee_name_cached__icontains=fdata['query'])
942+
| Q(order__email__icontains=fdata['query'])
943+
| Q(attendee_email__icontains=fdata['query'])
944+
)
945+
946+
# Search by event name
947+
if fdata.get('event_query'):
948+
qs = qs.filter(
949+
Q(order__event__name__icontains=fdata['event_query'])
950+
| Q(order__event__slug__icontains=fdata['event_query'])
951+
)
952+
953+
# Event status filter
954+
if fdata.get('event_status'):
955+
now_ = now()
956+
if fdata['event_status'] == 'ongoing':
957+
qs = qs.filter(
958+
Q(order__event__date_from__lte=now_)
959+
& (Q(order__event__date_to__gte=now_) | Q(order__event__date_to__isnull=True))
960+
)
961+
elif fdata['event_status'] == 'recent':
962+
qs = qs.filter(
963+
Q(order__event__date_to__lt=now_)
964+
| (Q(order__event__date_to__isnull=True) & Q(order__event__date_from__lt=now_))
965+
)
966+
967+
# Check-in status filter
968+
checkin_status = fdata.get('checkin_status')
969+
if checkin_status:
970+
qs = qs.annotate(
971+
entry_time=Max('checkins__datetime', filter=Q(checkins__type=Checkin.TYPE_ENTRY)),
972+
exit_time=Max('checkins__datetime', filter=Q(checkins__type=Checkin.TYPE_EXIT)),
973+
)
974+
if checkin_status == 'present':
975+
qs = qs.filter(entry_time__isnull=False).filter(
976+
Q(exit_time__isnull=True) | Q(exit_time__lt=F('entry_time'))
977+
)
978+
elif checkin_status == 'left':
979+
qs = qs.filter(exit_time__isnull=False, exit_time__gt=F('entry_time'))
980+
elif checkin_status == 'checked_in':
981+
qs = qs.filter(entry_time__isnull=False)
982+
elif checkin_status == 'not_checked_in':
983+
qs = qs.filter(entry_time__isnull=True)
984+
985+
return qs
986+
987+
894988
class SubmissionFilterForm(forms.Form):
895989
query = forms.CharField(
896990
label=_('Title or speaker'),

app/eventyay/control/navigation.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,12 @@ def get_admin_navigation(request):
588588
'active': 'organizers' in url.url_name,
589589
'icon': 'group',
590590
},
591+
{
592+
'label': _('All Attendees'),
593+
'url': reverse('control:admin.attendees'),
594+
'active': 'attendees' in url.url_name,
595+
'icon': 'ticket',
596+
},
591597
{
592598
'label': _('All Sessions'),
593599
'url': reverse('control:admin.submissions'),
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
{% extends "pretixcontrol/admin/base.html" %}
2+
{% load i18n %}
3+
{% load urlreplace %}
4+
{% load bootstrap3 %}
5+
6+
{% block title %}
7+
{% translate "Attendees" %}
8+
{% endblock %}
9+
10+
{% block content %}
11+
<h1>
12+
{% translate "Attendees" %}
13+
</h1>
14+
<p>
15+
{% translate "This is a list of all paid attendees and their check-in status." %}
16+
</p>
17+
18+
<div class="panel panel-default">
19+
<div class="panel-heading">
20+
<h3 class="panel-title">{% translate "Filter" %}</h3>
21+
</div>
22+
23+
<div class="panel-body">
24+
<form method="get" class="row filter-form">
25+
<div class="col-md-3 col-sm-6 col-xs-12">
26+
{% bootstrap_field filter_form.query layout='inline' %}
27+
</div>
28+
<div class="col-md-3 col-sm-6 col-xs-12">
29+
{% bootstrap_field filter_form.event_query layout='inline' %}
30+
</div>
31+
<div class="col-md-3 col-sm-6 col-xs-12">
32+
{% bootstrap_field filter_form.event_status layout='inline' %}
33+
</div>
34+
<div class="col-md-3 col-sm-6 col-xs-12">
35+
{% bootstrap_field filter_form.checkin_status layout='inline' %}
36+
</div>
37+
<div class="col-xs-12 text-right" style="margin-top:10px;">
38+
<button class="btn btn-primary" type="submit">
39+
<span class="fa fa-filter"></span>
40+
<span class="hidden-md">
41+
{% translate "Filter" %}
42+
</span>
43+
</button>
44+
</div>
45+
</form>
46+
</div>
47+
</div>
48+
49+
{% if attendees %}
50+
<div class="table-responsive">
51+
<table class="table table-condensed table-hover">
52+
<thead>
53+
<tr>
54+
<th>
55+
{% translate "Name" %}
56+
<a href="?{% url_replace request 'ordering' 'name' %}">
57+
<i class="fa fa-caret-up"></i>
58+
</a>
59+
<a href="?{% url_replace request 'ordering' '-name' %}">
60+
<i class="fa fa-caret-down"></i>
61+
</a>
62+
</th>
63+
<th>
64+
{% translate "Email" %}
65+
<a href="?{% url_replace request 'ordering' 'email' %}">
66+
<i class="fa fa-caret-up"></i>
67+
</a>
68+
<a href="?{% url_replace request 'ordering' '-email' %}">
69+
<i class="fa fa-caret-down"></i>
70+
</a>
71+
</th>
72+
<th>
73+
{% translate "Event" %}
74+
<a href="?{% url_replace request 'ordering' 'event' %}">
75+
<i class="fa fa-caret-up"></i>
76+
</a>
77+
<a href="?{% url_replace request 'ordering' '-event' %}">
78+
<i class="fa fa-caret-down"></i>
79+
</a>
80+
</th>
81+
<th>
82+
{% translate "Order Code" %}
83+
<a href="?{% url_replace request 'ordering' 'order_code' %}">
84+
<i class="fa fa-caret-up"></i>
85+
</a>
86+
<a href="?{% url_replace request 'ordering' '-order_code' %}">
87+
<i class="fa fa-caret-down"></i>
88+
</a>
89+
</th>
90+
<th>
91+
{% translate "Product" %}
92+
<a href="?{% url_replace request 'ordering' 'product' %}">
93+
<i class="fa fa-caret-up"></i>
94+
</a>
95+
<a href="?{% url_replace request 'ordering' '-product' %}">
96+
<i class="fa fa-caret-down"></i>
97+
</a>
98+
</th>
99+
<th>
100+
{% translate "Check in Status" %}
101+
<a href="?{% url_replace request 'ordering' 'check_in_status' %}">
102+
<i class="fa fa-caret-up"></i>
103+
</a>
104+
<a href="?{% url_replace request 'ordering' '-check_in_status' %}">
105+
<i class="fa fa-caret-down"></i>
106+
</a>
107+
</th>
108+
</tr>
109+
</thead>
110+
111+
<tbody>
112+
{% for attendee in attendees %}
113+
<tr>
114+
<td>
115+
{{ attendee.name }}
116+
</td>
117+
<td>
118+
{{ attendee.email }}
119+
</td>
120+
<td>
121+
<strong>
122+
<a href="{% url 'control:event.index' organizer=attendee.organizer_slug event=attendee.event_slug %}">
123+
{{ attendee.event }}
124+
</a>
125+
</strong>
126+
</td>
127+
<td>
128+
<strong>
129+
<a href="{% url 'control:event.order' event=attendee.event_slug organizer=attendee.organizer_slug code=attendee.order_code %}">
130+
{{ attendee.order_code }}
131+
</a>
132+
</strong>
133+
{% if attendee.testmode %}
134+
<span class="label label-warning">{% translate "TEST MODE" %}</span>
135+
{% endif %}
136+
</td>
137+
<td>{{ attendee.product }}</td>
138+
<td>
139+
{% if attendee.check_in_status == "Not checked in" %}
140+
<span class="label label-danger">
141+
{{ attendee.check_in_status }}
142+
</span>
143+
{% elif attendee.check_in_status == "Checked in but left" %}
144+
<span class="label label-success">
145+
{{ attendee.check_in_status }}
146+
</span>
147+
{% elif attendee.check_in_status == "Checked in" %}
148+
<span class="label label-success">
149+
{{ attendee.check_in_status }}
150+
</span>
151+
{% else %}
152+
{{ attendee.check_in_status }}
153+
{% endif %}
154+
</td>
155+
</tr>
156+
{% endfor %}
157+
</tbody>
158+
</table>
159+
</div>
160+
161+
{% include "pretixcontrol/pagination.html" %}
162+
163+
{% else %}
164+
<div class="empty-collection">
165+
<p>
166+
{% translate "There are no attendees to show." %}
167+
</p>
168+
</div>
169+
{% endif %}
170+
{% endblock %}

app/eventyay/control/templates/pretixcontrol/checkin/index.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ <h1>
4242
{% bootstrap_field filter_form.status layout='inline' %}
4343
</div>
4444
<div class="col-md-3 col-sm-6 col-xs-12">
45-
{% bootstrap_field filter_form.item layout='inline' %}
45+
{% bootstrap_field filter_form.product layout='inline' %}
4646
</div>
4747
<div class="col-md-2 col-sm-6 col-xs-12">
4848
<button class="btn btn-primary btn-block" type="submit">
@@ -71,8 +71,8 @@ <h1>
7171
<th></th>
7272
<th>{% trans "Order code" %} <a href="?{% url_replace request 'ordering' '-code'%}"><i class="fa fa-caret-down"></i></a>
7373
<a href="?{% url_replace request 'ordering' 'code'%}"><i class="fa fa-caret-up"></i></a></th>
74-
<th>{% trans "Item" %} <a href="?{% url_replace request 'ordering' '-item'%}"><i class="fa fa-caret-down"></i></a>
75-
<a href="?{% url_replace request 'ordering' 'item'%}"><i class="fa fa-caret-up"></i></a></th>
74+
<th>{% trans "product" %} <a href="?{% url_replace request 'ordering' '-product'%}"><i class="fa fa-caret-down"></i></a>
75+
<a href="?{% url_replace request 'ordering' 'product'%}"><i class="fa fa-caret-up"></i></a></th>
7676
{% if request.event.has_subevents and not checkinlist.subevent %}
7777
<th>{% trans "Date" context "subevents" %} <a href="?{% url_replace request 'ordering' '-date'%}"><i class="fa fa-caret-down"></i></a>
7878
<a href="?{% url_replace request 'ordering' 'date'%}"><i class="fa fa-caret-up"></i></a></th>
@@ -109,7 +109,7 @@ <h1>
109109
<span class="label label-warning">{% trans "TEST MODE" %}</span>
110110
{% endif %}
111111
</td>
112-
<td>{{ e.item }}{% if e.variation %} – {{ e.variation }}{% endif %}</td>
112+
<td>{{ e.product }}{% if e.variation %} – {{ e.variation }}{% endif %}</td>
113113
{% if request.event.has_subevents and not checkinlist.subevent %}
114114
<td>
115115
{{ e.subevent.name }} – {{ e.subevent.get_date_range_display }} {{ e.subevent.date_from|date:"TIME_FORMAT" }}

app/eventyay/control/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,7 @@
608608
url(r'^$', admin.AdminDashboard.as_view(), name='admin.dashboard'),
609609
url(r'^organizers/$', admin.OrganizerList.as_view(), name='admin.organizers'),
610610
url(r'^events/$', admin.AdminEventList.as_view(), name='admin.events'),
611+
path('attendees/', admin.AttendeeListView.as_view(), name='admin.attendees'),
611612
path('submissions/', admin.SubmissionListView.as_view(), name='admin.submissions'),
612613
url(r'^task_management', admin.TaskList.as_view(), name='admin.task_management'),
613614
url(r'^sudo/(?P<id>\d+)/$', user.EditStaffSession.as_view(), name='admin.user.sudo.edit'),

0 commit comments

Comments
 (0)