Overhaul of our check-in features (#1647)

This commit is contained in:
Raphael Michel
2020-05-13 18:01:49 +02:00
committed by GitHub
parent 640b9c876d
commit c056db46b6
36 changed files with 2604 additions and 169 deletions

View File

@@ -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,

View File

@@ -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')]

View File

@@ -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(

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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>

View File

@@ -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 }} &middot; {{ 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">

View File

@@ -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(),

View File

@@ -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(

View File

@@ -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',

View File

@@ -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 = []

View File

@@ -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:

View File

@@ -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', '')