mirror of
https://github.com/pretix/pretix.git
synced 2026-05-05 15:14:04 +00:00
Overhaul of our check-in features (#1647)
This commit is contained in:
@@ -35,11 +35,10 @@ class CheckinListForm(forms.ModelForm):
|
||||
'event': self.event.slug,
|
||||
'organizer': self.event.organizer.slug,
|
||||
}),
|
||||
'data-placeholder': pgettext_lazy('subevent', 'Date')
|
||||
'data-placeholder': pgettext_lazy('subevent', 'All dates')
|
||||
}
|
||||
)
|
||||
self.fields['subevent'].widget.choices = self.fields['subevent'].choices
|
||||
self.fields['subevent'].required = True
|
||||
else:
|
||||
del self.fields['subevent']
|
||||
|
||||
@@ -52,13 +51,43 @@ class CheckinListForm(forms.ModelForm):
|
||||
'limit_products',
|
||||
'subevent',
|
||||
'include_pending',
|
||||
'auto_checkin_sales_channels'
|
||||
'auto_checkin_sales_channels',
|
||||
'allow_multiple_entries',
|
||||
'allow_entry_after_exit',
|
||||
'rules',
|
||||
]
|
||||
widgets = {
|
||||
'limit_products': forms.CheckboxSelectMultiple(attrs={
|
||||
'data-inverse-dependency': '<[name$=all_products]'
|
||||
}),
|
||||
'auto_checkin_sales_channels': forms.CheckboxSelectMultiple(),
|
||||
}
|
||||
field_classes = {
|
||||
'limit_products': SafeModelMultipleChoiceField,
|
||||
'subevent': SafeModelChoiceField,
|
||||
}
|
||||
|
||||
|
||||
class SimpleCheckinListForm(forms.ModelForm):
|
||||
def __init__(self, **kwargs):
|
||||
self.event = kwargs.pop('event')
|
||||
kwargs.pop('locales', None)
|
||||
super().__init__(**kwargs)
|
||||
self.fields['limit_products'].queryset = self.event.items.all()
|
||||
|
||||
class Meta:
|
||||
model = CheckinList
|
||||
localized_fields = '__all__'
|
||||
fields = [
|
||||
'name',
|
||||
'all_products',
|
||||
'limit_products',
|
||||
'include_pending',
|
||||
]
|
||||
widgets = {
|
||||
'limit_products': forms.CheckboxSelectMultiple(attrs={
|
||||
'data-inverse-dependency': '<[name$=all_products]'
|
||||
}),
|
||||
'auto_checkin_sales_channels': forms.CheckboxSelectMultiple()
|
||||
}
|
||||
field_classes = {
|
||||
'limit_products': SafeModelMultipleChoiceField,
|
||||
|
||||
@@ -757,10 +757,10 @@ class CheckInFilterForm(FilterForm):
|
||||
'-code': ('-order__code', '-item__name'),
|
||||
'email': ('order__email', 'item__name'),
|
||||
'-email': ('-order__email', '-item__name'),
|
||||
'status': (FixedOrderBy(F('last_checked_in'), nulls_first=True, descending=True), 'order__code'),
|
||||
'-status': (FixedOrderBy(F('last_checked_in'), nulls_last=True), '-order__code'),
|
||||
'timestamp': (FixedOrderBy(F('last_checked_in'), nulls_first=True), 'order__code'),
|
||||
'-timestamp': (FixedOrderBy(F('last_checked_in'), nulls_last=True, descending=True), '-order__code'),
|
||||
'status': (FixedOrderBy(F('last_entry'), nulls_first=True, descending=True), 'order__code'),
|
||||
'-status': (FixedOrderBy(F('last_entry'), nulls_last=True), '-order__code'),
|
||||
'timestamp': (FixedOrderBy(F('last_entry'), nulls_first=True), 'order__code'),
|
||||
'-timestamp': (FixedOrderBy(F('last_entry'), nulls_last=True, descending=True), '-order__code'),
|
||||
'item': ('item__name', 'variation__value', 'order__code'),
|
||||
'-item': ('-item__name', '-variation__value', '-order__code'),
|
||||
'seat': ('seat__sorting_rank', 'seat__guid'),
|
||||
@@ -783,6 +783,7 @@ class CheckInFilterForm(FilterForm):
|
||||
label=_('Check-in status'),
|
||||
choices=(
|
||||
('', _('All attendees')),
|
||||
('2', pgettext_lazy('checkin state', 'Present')),
|
||||
('1', _('Checked in')),
|
||||
('0', _('Not checked in')),
|
||||
),
|
||||
@@ -823,9 +824,13 @@ class CheckInFilterForm(FilterForm):
|
||||
if fdata.get('status'):
|
||||
s = fdata.get('status')
|
||||
if s == '1':
|
||||
qs = qs.filter(last_checked_in__isnull=False)
|
||||
qs = qs.filter(last_entry__isnull=False)
|
||||
elif s == '2':
|
||||
qs = qs.filter(last_entry__isnull=False).filter(
|
||||
Q(last_exit__isnull=True) | Q(last_exit__lt=F('last_entry'))
|
||||
)
|
||||
elif s == '0':
|
||||
qs = qs.filter(last_checked_in__isnull=True)
|
||||
qs = qs.filter(last_entry__isnull=True)
|
||||
|
||||
if fdata.get('ordering'):
|
||||
ob = self.orders[fdata.get('ordering')]
|
||||
|
||||
@@ -14,7 +14,7 @@ from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix.base.models import (
|
||||
CheckinList, Event, ItemVariation, LogEntry, OrderPosition,
|
||||
Checkin, CheckinList, Event, ItemVariation, LogEntry, OrderPosition,
|
||||
)
|
||||
from pretix.base.signals import logentry_display
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
@@ -134,7 +134,7 @@ def _display_checkin(event, logentry):
|
||||
show_dt = False
|
||||
if 'datetime' in data:
|
||||
dt = dateutil.parser.parse(data.get('datetime'))
|
||||
show_dt = abs((logentry.datetime - dt).total_seconds()) > 60 or 'forced' in data
|
||||
show_dt = abs((logentry.datetime - dt).total_seconds()) > 5 or 'forced' in data
|
||||
tz = pytz.timezone(event.settings.timezone)
|
||||
dt_formatted = date_format(dt.astimezone(tz), "SHORT_DATETIME_FORMAT")
|
||||
|
||||
@@ -146,6 +146,18 @@ def _display_checkin(event, logentry):
|
||||
else:
|
||||
checkin_list = _("(unknown)")
|
||||
|
||||
if data.get('type') == Checkin.TYPE_EXIT:
|
||||
if show_dt:
|
||||
return _('Position #{posid} has been checked out at {datetime} for list "{list}".').format(
|
||||
posid=data.get('positionid'),
|
||||
datetime=dt_formatted,
|
||||
list=checkin_list
|
||||
)
|
||||
else:
|
||||
return _('Position #{posid} has been checked out for list "{list}".').format(
|
||||
posid=data.get('positionid'),
|
||||
list=checkin_list
|
||||
)
|
||||
if data.get('first'):
|
||||
if show_dt:
|
||||
return _('Position #{posid} has been checked in at {datetime} for list "{list}".').format(
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
{% if 'can_change_event_settings' in request.eventpermset %}
|
||||
<a href="{% url "control:event.orders.checkinlists.edit" event=request.event.slug organizer=request.event.organizer.slug list=checkinlist.pk %}"
|
||||
class="btn btn-default">
|
||||
<span class="fa fa-edit"></span>
|
||||
{% trans "Edit list" %}
|
||||
<span class="fa fa-wrench"></span>
|
||||
{% trans "Edit list configuration" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url "control:event.orders.export" event=request.event.slug organizer=request.event.organizer.slug %}?identifier=checkinlistpdf&checkinlistpdf-list={{ checkinlist.pk }}"
|
||||
@@ -122,19 +122,30 @@
|
||||
{{ e.secret|slice:":10" }}…
|
||||
</td>
|
||||
<td>
|
||||
{% if not e.last_checked_in %}
|
||||
{% if not e.last_entry %}
|
||||
<span class="label label-danger">{% trans "Not checked in" %}</span>
|
||||
{% else %}
|
||||
<span class="label label-success">{% trans "Checked in" %}</span>
|
||||
{% if e.auto_checked_in %}
|
||||
<span class="fa fa-magic text-muted"
|
||||
data-toggle="tooltip" title="{% trans "Checked in automatically" %}"></span>
|
||||
{% if e.last_exit and e.last_exit_aware > e.last_entry_aware %}
|
||||
<span class="label label-success">{% trans "Checked in but left" %}</span>
|
||||
{% else %}
|
||||
<span class="label label-success">{% trans "Checked in" %}</span>
|
||||
{% if e.auto_checked_in %}
|
||||
<span class="fa fa-magic text-muted"
|
||||
data-toggle="tooltip" title="{% trans "Checked in automatically" %}"></span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if e.last_checked_in %}
|
||||
{{ e.last_checked_in_aware|date:"SHORT_DATETIME_FORMAT" }}
|
||||
{% if e.last_entry %}
|
||||
{{ e.last_entry_aware|date:"SHORT_DATETIME_FORMAT" }}
|
||||
{% endif %}
|
||||
{% if e.last_exit %}
|
||||
<small><br>
|
||||
{% blocktrans trimmed with date=e.last_exit_aware|date:"SHORT_DATETIME_FORMAT" %}
|
||||
Exit: {{ date }}
|
||||
{% endblocktrans %}
|
||||
</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -146,6 +157,9 @@
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Check-In selected attendees" %}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-default btn-save" name="checkout" value="true">
|
||||
{% trans "Check-Out selected attendees" %}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-default btn-save" name="revert" value="true">
|
||||
{% trans "Revert selected check-ins" %}
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{% extends "pretixcontrol/items/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load static %}
|
||||
{% load compress %}
|
||||
{% block title %}
|
||||
{% if checkinlist %}
|
||||
{% blocktrans with name=checkinlist.name %}Check-in list: {{ name }}{% endblocktrans %}
|
||||
@@ -15,35 +17,66 @@
|
||||
<h1>{% trans "Check-in list" %}</h1>
|
||||
{% endif %}
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
<script type="text/plain"
|
||||
id="product-select2">{% url "control:event.items.select2" event=request.event.slug organizer=request.organizer.slug %}</script>
|
||||
<script type="text/plain"
|
||||
id="variations-select2">{% url "control:event.items.variations.select2" event=request.event.slug organizer=request.organizer.slug %}</script>
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form %}
|
||||
<fieldset>
|
||||
<legend>{% trans "General information" %}</legend>
|
||||
{% bootstrap_field form.name layout="control" %}
|
||||
{% if form.subevent %}
|
||||
{% bootstrap_field form.subevent layout="control" %}
|
||||
{% endif %}
|
||||
{% bootstrap_field form.include_pending layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<div class="tabbed-form">
|
||||
<fieldset>
|
||||
<legend>{% trans "General" %}</legend>
|
||||
{% bootstrap_field form.name layout="control" %}
|
||||
{% if form.subevent %}
|
||||
{% bootstrap_field form.subevent layout="control" %}
|
||||
{% endif %}
|
||||
{% bootstrap_field form.include_pending layout="control" %}
|
||||
{% bootstrap_field form.all_products layout="control" %}
|
||||
{% bootstrap_field form.limit_products layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Advanced" %}</legend>
|
||||
<div class="alert alert-info">
|
||||
{% blocktrans trimmed %}
|
||||
These settings on this page are intended for professional users with very specific check-in
|
||||
situations. Please reach out to support if you have questions about setting this up.
|
||||
{% endblocktrans %}
|
||||
<br>
|
||||
</div>
|
||||
<div class="alert alert-warning">
|
||||
{% blocktrans trimmed %}
|
||||
Make sure to always use the latest version of our scanning apps for these options to work.
|
||||
{% endblocktrans %}
|
||||
<br>
|
||||
<strong>
|
||||
{% blocktrans trimmed %}
|
||||
If you make use of these advanced options, we recommend using our Android and Desktop apps.
|
||||
Custom check-in rules do not work offline with our iOS scanning app.
|
||||
{% endblocktrans %}
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
<legend>{% trans "Products" %}</legend>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
Please select the products that should be part of this check-in list.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% bootstrap_field form.all_products layout="control" %}
|
||||
{% bootstrap_field form.limit_products layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Advanced" %}</legend>
|
||||
{% bootstrap_field form.auto_checkin_sales_channels layout="control" %}
|
||||
</fieldset>
|
||||
{% bootstrap_field form.allow_multiple_entries layout="control" %}
|
||||
{% bootstrap_field form.allow_entry_after_exit layout="control" %}
|
||||
{% bootstrap_field form.auto_checkin_sales_channels layout="control" %}
|
||||
|
||||
<h3>{% trans "Custom check-in rule" %}</h3>
|
||||
<div id="rules-editor" class="form-inline">
|
||||
<checkin-rules-editor></checkin-rules-editor>
|
||||
</div>
|
||||
<div class="disabled-withoutjs sr-only">
|
||||
{{ form.rules }}
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% compress js %}
|
||||
<script type="text/javascript" src="{% static "vuejs/vue.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/checkinrules.js" %}"></script>
|
||||
{% endcompress %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -97,7 +97,13 @@
|
||||
</div>
|
||||
</td>
|
||||
{% if request.event.has_subevents %}
|
||||
<td>{{ cl.subevent.name }} – {{ cl.subevent.get_date_range_display }}</td>
|
||||
{% if cl.subevent %}
|
||||
<td>{{ cl.subevent.name }} – {{ cl.subevent.get_date_range_display }}</td>
|
||||
{% else %}
|
||||
<td>
|
||||
<em>{% trans "All" %}</em>
|
||||
</td>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<td>
|
||||
{% for channel in cl.auto_checkin_sales_channels %}
|
||||
@@ -121,7 +127,7 @@
|
||||
<td class="text-right flip">
|
||||
<a href="{% url "control:event.orders.checkinlists.show" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}" class="btn btn-default btn-sm"><i class="fa fa-eye"></i></a>
|
||||
{% if "can_change_event_settings" in request.eventpermset %}
|
||||
<a href="{% url "control:event.orders.checkinlists.edit" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||
<a href="{% url "control:event.orders.checkinlists.edit" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}" class="btn btn-default btn-sm"><i class="fa fa-wrench"></i></a>
|
||||
<a href="{% url "control:event.orders.checkinlists.delete" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
@@ -298,10 +298,14 @@
|
||||
{% endif %}
|
||||
{% if line.checkins.all %}
|
||||
{% for c in line.checkins.all %}
|
||||
{% if c.auto_checked_in %}
|
||||
{% if c.type == "exit" %}
|
||||
<span class="fa fa-fw fa-sign-out" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Exit scan: {{ date }}{% endblocktrans %}"></span>
|
||||
{% elif c.forced %}
|
||||
<span class="fa fa-fw fa-warning" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Additional entry scan: {{ date }}{% endblocktrans %}"></span>
|
||||
{% elif c.auto_checked_in %}
|
||||
<span class="fa fa-fw fa-magic" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Automatically checked in: {{ date }}{% endblocktrans %}"></span>
|
||||
{% else %}
|
||||
<span class="fa fa-fw fa-check" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}First scanned: {{ date }}{% endblocktrans %}"></span>
|
||||
<span class="fa fa-fw fa-check" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Entry scan: {{ date }}{% endblocktrans %}"></span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
@@ -324,6 +328,10 @@
|
||||
{% if line.subevent %}
|
||||
<br/>
|
||||
<span class="fa fa-calendar"></span> {{ line.subevent.name }} · {{ line.subevent.get_date_range_display }}
|
||||
{% if event.settings.show_times %}
|
||||
<span class="fa fa-clock-o"></span>
|
||||
{{ line.subevent.date_from|date:"TIME_FORMAT" }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if not line.canceled %}
|
||||
<div class="position-buttons">
|
||||
|
||||
@@ -162,6 +162,8 @@ urlpatterns = [
|
||||
url(r'^items/(?P<item>\d+)/down$', item.item_move_down, name='event.items.down'),
|
||||
url(r'^items/(?P<item>\d+)/delete$', item.ItemDelete.as_view(), name='event.items.delete'),
|
||||
url(r'^items/typeahead/meta/$', typeahead.item_meta_values, name='event.items.meta.typeahead'),
|
||||
url(r'^items/select2$', typeahead.items_select2, name='event.items.select2'),
|
||||
url(r'^items/select2/variation$', typeahead.variations_select2, name='event.items.variations.select2'),
|
||||
url(r'^categories/$', item.CategoryList.as_view(), name='event.items.categories'),
|
||||
url(r'^categories/select2$', typeahead.category_select2, name='event.items.categories.select2'),
|
||||
url(r'^categories/(?P<category>\d+)/delete$', item.CategoryDelete.as_view(),
|
||||
|
||||
@@ -30,7 +30,15 @@ class CheckInListShow(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
def get_queryset(self, filter=True):
|
||||
cqs = Checkin.objects.filter(
|
||||
position_id=OuterRef('pk'),
|
||||
list_id=self.list.pk
|
||||
list_id=self.list.pk,
|
||||
type=Checkin.TYPE_ENTRY
|
||||
).order_by().values('position_id').annotate(
|
||||
m=Max('datetime')
|
||||
).values('m')
|
||||
cqs_exit = Checkin.objects.filter(
|
||||
position_id=OuterRef('pk'),
|
||||
list_id=self.list.pk,
|
||||
type=Checkin.TYPE_EXIT
|
||||
).order_by().values('position_id').annotate(
|
||||
m=Max('datetime')
|
||||
).values('m')
|
||||
@@ -38,13 +46,17 @@ class CheckInListShow(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
qs = OrderPosition.objects.filter(
|
||||
order__event=self.request.event,
|
||||
order__status__in=[Order.STATUS_PAID, Order.STATUS_PENDING] if self.list.include_pending else [Order.STATUS_PAID],
|
||||
subevent=self.list.subevent
|
||||
).annotate(
|
||||
last_checked_in=Subquery(cqs),
|
||||
last_entry=Subquery(cqs),
|
||||
last_exit=Subquery(cqs_exit),
|
||||
auto_checked_in=Exists(
|
||||
Checkin.objects.filter(position_id=OuterRef('pk'), list_id=self.list.pk, auto_checked_in=True)
|
||||
)
|
||||
).select_related('item', 'variation', 'order', 'addon_to')
|
||||
if self.list.subevent:
|
||||
qs = qs.filter(
|
||||
subevent=self.list.subevent
|
||||
)
|
||||
|
||||
if not self.list.all_products:
|
||||
qs = qs.filter(item__in=self.list.limit_products.values_list('id', flat=True))
|
||||
@@ -69,19 +81,35 @@ class CheckInListShow(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['checkinlist'] = self.list
|
||||
ctx['seats'] = self.list.subevent.seating_plan if self.list.subevent else self.request.event.seating_plan
|
||||
if self.request.event.has_subevents:
|
||||
ctx['seats'] = (
|
||||
self.list.subevent.seating_plan_id if self.list.subevent
|
||||
else self.request.event.subevents.filter(seating_plan__isnull=False).exists()
|
||||
)
|
||||
else:
|
||||
ctx['seats'] = self.request.event.seating_plan_id
|
||||
ctx['filter_form'] = self.filter_form
|
||||
for e in ctx['entries']:
|
||||
if e.last_checked_in:
|
||||
if isinstance(e.last_checked_in, str):
|
||||
if e.last_entry:
|
||||
if isinstance(e.last_entry, str):
|
||||
# Apparently only happens on SQLite
|
||||
e.last_checked_in_aware = make_aware(dateutil.parser.parse(e.last_checked_in), UTC)
|
||||
elif not is_aware(e.last_checked_in):
|
||||
e.last_entry_aware = make_aware(dateutil.parser.parse(e.last_entry), UTC)
|
||||
elif not is_aware(e.last_entry):
|
||||
# Apparently only happens on MySQL
|
||||
e.last_checked_in_aware = make_aware(e.last_checked_in, UTC)
|
||||
e.last_entry_aware = make_aware(e.last_entry, UTC)
|
||||
else:
|
||||
# This would be correct, so guess on which database it works… Yes, it's PostgreSQL.
|
||||
e.last_checked_in_aware = e.last_checked_in
|
||||
e.last_entry_aware = e.last_entry
|
||||
if e.last_exit:
|
||||
if isinstance(e.last_exit, str):
|
||||
# Apparently only happens on SQLite
|
||||
e.last_exit_aware = make_aware(dateutil.parser.parse(e.last_exit), UTC)
|
||||
elif not is_aware(e.last_exit):
|
||||
# Apparently only happens on MySQL
|
||||
e.last_exit_aware = make_aware(e.last_exit, UTC)
|
||||
else:
|
||||
# This would be correct, so guess on which database it works… Yes, it's PostgreSQL.
|
||||
e.last_exit_aware = e.last_exit
|
||||
return ctx
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
@@ -111,17 +139,22 @@ class CheckInListShow(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
messages.success(request, _('The selected check-ins have been reverted.'))
|
||||
else:
|
||||
for op in positions:
|
||||
created = False
|
||||
if op.order.status == Order.STATUS_PAID or (self.list.include_pending and op.order.status == Order.STATUS_PENDING):
|
||||
ci, created = Checkin.objects.get_or_create(position=op, list=self.list, defaults={
|
||||
'datetime': now(),
|
||||
})
|
||||
t = Checkin.TYPE_EXIT if request.POST.get('checkout') == 'true' else Checkin.TYPE_ENTRY
|
||||
if self.list.allow_multiple_entries or t != Checkin.TYPE_ENTRY:
|
||||
ci = Checkin.objects.create(position=op, list=self.list, datetime=now(), type=t)
|
||||
created = True
|
||||
else:
|
||||
ci, created = Checkin.objects.get_or_create(position=op, list=self.list, defaults={
|
||||
'datetime': now(),
|
||||
})
|
||||
op.order.log_action('pretix.event.checkin', data={
|
||||
'position': op.id,
|
||||
'positionid': op.positionid,
|
||||
'first': created,
|
||||
'forced': False,
|
||||
'datetime': now(),
|
||||
'type': t,
|
||||
'list': self.list.pk,
|
||||
'web': True
|
||||
}, user=request.user)
|
||||
@@ -177,6 +210,11 @@ class CheckinListCreate(EventPermissionRequiredMixin, CreateView):
|
||||
permission = 'can_change_event_settings'
|
||||
context_object_name = 'checkinlist'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
r = super().dispatch(request, *args, **kwargs)
|
||||
r['Content-Security-Policy'] = 'script-src \'unsafe-eval\''
|
||||
return r
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse('control:event.orders.checkinlists', kwargs={
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
@@ -204,6 +242,11 @@ class CheckinListUpdate(EventPermissionRequiredMixin, UpdateView):
|
||||
permission = 'can_change_event_settings'
|
||||
context_object_name = 'checkinlist'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
r = super().dispatch(request, *args, **kwargs)
|
||||
r['Content-Security-Policy'] = 'script-src \'unsafe-eval\''
|
||||
return r
|
||||
|
||||
def get_object(self, queryset=None) -> CheckinList:
|
||||
try:
|
||||
return self.request.event.checkin_lists.get(
|
||||
|
||||
@@ -268,8 +268,8 @@ def checkin_widget(sender, subevent=None, lazy=False, **kwargs):
|
||||
for cl in qs:
|
||||
widgets.append({
|
||||
'content': None if lazy else NUM_WIDGET.format(
|
||||
num='{}/{}'.format(cl.checkin_count, cl.position_count),
|
||||
text=_('Checked in – {list}').format(list=escape(cl.name))
|
||||
num='{}/{}'.format(cl.inside_count, cl.position_count),
|
||||
text=_('Present – {list}').format(list=escape(cl.name))
|
||||
),
|
||||
'lazy': 'checkin-{}'.format(cl.pk),
|
||||
'display_size': 'small',
|
||||
|
||||
@@ -38,9 +38,9 @@ from pretix.base.decimal import round_decimal
|
||||
from pretix.base.email import get_email_context
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import (
|
||||
CachedCombinedTicket, CachedFile, CachedTicket, Invoice, InvoiceAddress,
|
||||
Item, ItemVariation, LogEntry, Order, QuestionAnswer, Quota,
|
||||
generate_position_secret, generate_secret,
|
||||
CachedCombinedTicket, CachedFile, CachedTicket, Checkin, Invoice,
|
||||
InvoiceAddress, Item, ItemVariation, LogEntry, Order, QuestionAnswer,
|
||||
Quota, generate_position_secret, generate_secret,
|
||||
)
|
||||
from pretix.base.models.orders import (
|
||||
CancellationRequest, OrderFee, OrderPayment, OrderPosition, OrderRefund,
|
||||
@@ -252,7 +252,7 @@ class OrderDetail(OrderView):
|
||||
).prefetch_related(
|
||||
'item__questions', 'issued_gift_cards',
|
||||
Prefetch('answers', queryset=QuestionAnswer.objects.prefetch_related('options').select_related('question')),
|
||||
'checkins', 'checkins__list'
|
||||
Prefetch('checkins', queryset=Checkin.objects.select_related('list').order_by('datetime')),
|
||||
).order_by('positionid')
|
||||
|
||||
positions = []
|
||||
|
||||
@@ -25,7 +25,7 @@ from pretix.base.models.items import (
|
||||
)
|
||||
from pretix.base.reldate import RelativeDate, RelativeDateWrapper
|
||||
from pretix.base.services.quotas import QuotaAvailability
|
||||
from pretix.control.forms.checkin import CheckinListForm
|
||||
from pretix.control.forms.checkin import SimpleCheckinListForm
|
||||
from pretix.control.forms.filter import SubEventFilterForm
|
||||
from pretix.control.forms.item import QuotaForm
|
||||
from pretix.control.forms.subevents import (
|
||||
@@ -192,11 +192,11 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
'include_pending': False,
|
||||
}
|
||||
]
|
||||
extra = 1
|
||||
extra = 0
|
||||
|
||||
formsetclass = inlineformset_factory(
|
||||
SubEvent, CheckinList,
|
||||
form=CheckinListForm, formset=CheckinListFormSet,
|
||||
form=SimpleCheckinListForm, formset=CheckinListFormSet,
|
||||
can_order=False, can_delete=True, extra=extra,
|
||||
)
|
||||
if self.object:
|
||||
|
||||
@@ -13,8 +13,8 @@ from django.utils.timezone import make_aware
|
||||
from django.utils.translation import gettext as _, pgettext
|
||||
|
||||
from pretix.base.models import (
|
||||
EventMetaProperty, EventMetaValue, ItemMetaProperty, ItemMetaValue, Order,
|
||||
Organizer, User, Voucher,
|
||||
EventMetaProperty, EventMetaValue, ItemMetaProperty, ItemMetaValue,
|
||||
ItemVariation, Order, Organizer, User, Voucher,
|
||||
)
|
||||
from pretix.control.forms.event import EventWizardCopyForm
|
||||
from pretix.control.permissions import event_permission_required
|
||||
@@ -322,6 +322,68 @@ def quotas_select2(request, **kwargs):
|
||||
return JsonResponse(doc)
|
||||
|
||||
|
||||
@event_permission_required(None)
|
||||
def items_select2(request, **kwargs):
|
||||
query = request.GET.get('query', '')
|
||||
try:
|
||||
page = int(request.GET.get('page', '1'))
|
||||
except ValueError:
|
||||
page = 1
|
||||
|
||||
qs = request.event.items.filter(
|
||||
name__icontains=i18ncomp(query)
|
||||
).order_by('position')
|
||||
|
||||
total = qs.count()
|
||||
pagesize = 20
|
||||
offset = (page - 1) * pagesize
|
||||
doc = {
|
||||
'results': [
|
||||
{
|
||||
'id': e.pk,
|
||||
'text': str(e),
|
||||
}
|
||||
for e in qs[offset:offset + pagesize]
|
||||
],
|
||||
'pagination': {
|
||||
"more": total >= (offset + pagesize)
|
||||
}
|
||||
}
|
||||
return JsonResponse(doc)
|
||||
|
||||
|
||||
@event_permission_required(None)
|
||||
def variations_select2(request, **kwargs):
|
||||
query = request.GET.get('query', '')
|
||||
try:
|
||||
page = int(request.GET.get('page', '1'))
|
||||
except ValueError:
|
||||
page = 1
|
||||
|
||||
q = Q(item__event=request.event)
|
||||
for word in query.split():
|
||||
q &= Q(value__icontains=i18ncomp(word)) | Q(item__name__icontains=i18ncomp(ord))
|
||||
|
||||
qs = ItemVariation.objects.filter(q).order_by('item__position', 'item__name', 'position', 'value').select_related('item')
|
||||
|
||||
total = qs.count()
|
||||
pagesize = 20
|
||||
offset = (page - 1) * pagesize
|
||||
doc = {
|
||||
'results': [
|
||||
{
|
||||
'id': e.pk,
|
||||
'text': str(e.item) + " – " + str(e),
|
||||
}
|
||||
for e in qs[offset:offset + pagesize]
|
||||
],
|
||||
'pagination': {
|
||||
"more": total >= (offset + pagesize)
|
||||
}
|
||||
}
|
||||
return JsonResponse(doc)
|
||||
|
||||
|
||||
@event_permission_required(None)
|
||||
def category_select2(request, **kwargs):
|
||||
query = request.GET.get('query', '')
|
||||
|
||||
Reference in New Issue
Block a user