diff --git a/src/pretix/plugins/pretixdroid/__init__.py b/src/pretix/plugins/pretixdroid/__init__.py
index 7736d8941..604dd7949 100644
--- a/src/pretix/plugins/pretixdroid/__init__.py
+++ b/src/pretix/plugins/pretixdroid/__init__.py
@@ -17,8 +17,5 @@ class PretixdroidApp(AppConfig):
category = 'INTEGRATION'
description = _("This plugin allows you to use the pretixdroid and pretixdesk apps for your event.")
- def ready(self):
- from . import signals # NOQA
-
default_app_config = 'pretix.plugins.pretixdroid.PretixdroidApp'
diff --git a/src/pretix/plugins/pretixdroid/forms.py b/src/pretix/plugins/pretixdroid/forms.py
deleted file mode 100644
index 6e3a0016f..000000000
--- a/src/pretix/plugins/pretixdroid/forms.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from django import forms
-from django.urls import reverse
-from django.utils.translation import gettext_lazy as _
-from django_scopes.forms import (
- SafeModelChoiceField, SafeModelMultipleChoiceField,
-)
-
-from pretix.control.forms.widgets import Select2
-from pretix.plugins.pretixdroid.models import AppConfiguration
-
-
-class AppConfigurationForm(forms.ModelForm):
- class Meta:
- model = AppConfiguration
- fields = ('all_items', 'items', 'list', 'show_info', 'allow_search', 'app')
- widgets = {
- 'items': forms.CheckboxSelectMultiple(attrs={
- 'data-inverse-dependency': '#id_all_items'
- }),
- 'app': forms.RadioSelect
- }
- field_classes = {
- 'items': SafeModelMultipleChoiceField,
- 'list': SafeModelChoiceField,
- }
-
- def __init__(self, **kwargs):
- self.event = kwargs.pop('event')
- super().__init__(**kwargs)
- self.fields['items'].queryset = self.event.items.all()
- self.fields['list'].queryset = self.event.checkin_lists.all()
- self.fields['list'].widget = Select2(
- attrs={
- 'data-model-select2': 'generic',
- 'data-select2-url': reverse('control:event.orders.checkinlists.select2', kwargs={
- 'event': self.event.slug,
- 'organizer': self.event.organizer.slug,
- }),
- 'data-placeholder': _('Check-in list')
- }
- )
- self.fields['list'].widget.choices = self.fields['list'].choices
- self.fields['list'].required = True
diff --git a/src/pretix/plugins/pretixdroid/migrations/0004_delete_appconfiguration.py b/src/pretix/plugins/pretixdroid/migrations/0004_delete_appconfiguration.py
new file mode 100644
index 000000000..38b1d8e2f
--- /dev/null
+++ b/src/pretix/plugins/pretixdroid/migrations/0004_delete_appconfiguration.py
@@ -0,0 +1,16 @@
+# Generated by Django 3.0.9 on 2020-10-06 16:23
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('pretixdroid', '0003_appconfiguration_squashed_0005_auto_20180106_2122'),
+ ]
+
+ operations = [
+ migrations.DeleteModel(
+ name='AppConfiguration',
+ ),
+ ]
diff --git a/src/pretix/plugins/pretixdroid/models.py b/src/pretix/plugins/pretixdroid/models.py
deleted file mode 100644
index 266076d22..000000000
--- a/src/pretix/plugins/pretixdroid/models.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import string
-
-from django.db import models
-from django.utils.crypto import get_random_string
-from django.utils.translation import gettext_lazy as _
-
-
-class AppConfiguration(models.Model):
- event = models.ForeignKey('pretixbase.Event', on_delete=models.CASCADE)
- key = models.CharField(max_length=190, db_index=True)
- all_items = models.BooleanField(default=True, verbose_name=_('Can scan all products'))
- items = models.ManyToManyField('pretixbase.Item', blank=True, verbose_name=_('Can scan these products'))
- show_info = models.BooleanField(default=True, verbose_name=_('Show information'),
- help_text=_('If disabled, the device can not see how many tickets exist and how '
- 'many are already scanned. pretixdroid 1.6 or pretixdesk only.'))
- allow_search = models.BooleanField(default=True, verbose_name=_('Search allowed'),
- help_text=_('If disabled, the device can not search for attendees by name. '
- 'pretixdroid 1.6 or pretixdesk only.'))
- app = models.CharField(max_length=190, verbose_name=_('Scan software'), default='pretixdroid', choices=(
- ('pretixdroid', _('pretixdroid – for Android smartphones')),
- ('pretixdesk', _('pretixdesk – for desktop computers')),
- ))
- list = models.ForeignKey(
- 'pretixbase.CheckinList', on_delete=models.CASCADE, verbose_name=_('Check-in list')
- )
-
- @property
- def subevent(self):
- return self.list.subevent
-
- def save(self, **kwargs):
- if not self.key:
- self.key = get_random_string(
- length=32, allowed_chars=string.ascii_uppercase + string.ascii_lowercase + string.digits
- )
- return super().save(**kwargs)
diff --git a/src/pretix/plugins/pretixdroid/signals.py b/src/pretix/plugins/pretixdroid/signals.py
deleted file mode 100644
index a2a9e8c5e..000000000
--- a/src/pretix/plugins/pretixdroid/signals.py
+++ /dev/null
@@ -1,38 +0,0 @@
-
-from django.dispatch import receiver
-from django.urls import resolve, reverse
-from django.utils.translation import gettext_lazy as _
-
-from pretix.base.signals import logentry_display
-from pretix.control.logdisplay import _display_checkin
-from pretix.control.signals import nav_event
-
-
-@receiver(nav_event, dispatch_uid="pretixdroid_nav")
-def control_nav_import(sender, request=None, **kwargs):
- url = resolve(request.path_info)
- if not request.user.has_event_permission(request.organizer, request.event, 'can_change_orders', request=request):
- return []
- return [
- {
- 'label': _('Check-in devices'),
- 'url': reverse('plugins:pretixdroid:config', kwargs={
- 'event': request.event.slug,
- 'organizer': request.event.organizer.slug,
- }),
- 'parent': reverse('control:event.orders.checkinlists', kwargs={
- 'event': request.event.slug,
- 'organizer': request.event.organizer.slug,
- }),
- 'active': (url.namespace == 'plugins:pretixdroid' and url.url_name == 'config'),
- 'icon': 'mobile',
- }
- ]
-
-
-@receiver(signal=logentry_display, dispatch_uid="pretixdroid_logentry_display")
-def pretixcontrol_logentry_display(sender, logentry, **kwargs):
- if logentry.action_type != 'pretix.plugins.pretixdroid.scan':
- return
-
- return _display_checkin(sender, logentry)
diff --git a/src/pretix/plugins/pretixdroid/static/pretixplugins/pretixdroid/play_store_en.png b/src/pretix/plugins/pretixdroid/static/pretixplugins/pretixdroid/play_store_en.png
deleted file mode 100644
index 1fa74c1b3..000000000
Binary files a/src/pretix/plugins/pretixdroid/static/pretixplugins/pretixdroid/play_store_en.png and /dev/null differ
diff --git a/src/pretix/plugins/pretixdroid/static/pretixplugins/pretixdroid/pretixdroid.js b/src/pretix/plugins/pretixdroid/static/pretixplugins/pretixdroid/pretixdroid.js
deleted file mode 100644
index 66f5b5e71..000000000
--- a/src/pretix/plugins/pretixdroid/static/pretixplugins/pretixdroid/pretixdroid.js
+++ /dev/null
@@ -1,7 +0,0 @@
-$(function () {
- jQuery('#qrcodeCanvas').qrcode(
- {
- text: $("#qrdata").html()
- }
- );
-});
diff --git a/src/pretix/plugins/pretixdroid/templates/pretixplugins/pretixdroid/configuration.html b/src/pretix/plugins/pretixdroid/templates/pretixplugins/pretixdroid/configuration.html
deleted file mode 100644
index 2dd453042..000000000
--- a/src/pretix/plugins/pretixdroid/templates/pretixplugins/pretixdroid/configuration.html
+++ /dev/null
@@ -1,127 +0,0 @@
-{% extends "pretixcontrol/event/base.html" %}
-{% load i18n %}
-{% load bootstrap3 %}
-{% load static %}
-{% block title %}{% trans "Check-in device configuration" %}{% endblock %}
-{% block content %}
-
{% trans "Check-in device configuration" %}
-
- {% if not configs or "create" in request.GET %}
-
-
{% trans "We've got a new app!" %}
-
- {% blocktrans trimmed %}
- We've retired pretixdesk and pretixdroid in favor of our new app pretixSCAN that works on all major
- platforms, allows convenient switching between events, has better performance when dealing with large
- events and supports printing badges. We suggest that you switch to pretixSCAN for your events, but you
- can continue using pretixdesk for at least all of 2019, if you like.
- {% endblocktrans %}
-
-
-
-
{% trans "Our new app: pretixSCAN" %}
-
-
{% trans "Available on Android, iOS, Windows, and Linux." %}
-
{% trans "Configuration is available in your organizer account's device list." %}
-
- {% trans "Switch to my device list" %}
-
-
-
-
{% trans "Our old apps: pretixdesk and pretixdroid" %}
-
-
{% trans "Available on Android, Windows, and Linux." %}
-
{% trans "Scroll down to create a configuration" %}
-
-
-
- {% endif %}
-
- {% if not configs or "create" in request.GET %}
- {% trans "Create app configuration" %}
-
- {% blocktrans trimmed %}
- To start scanning tickets with our apps, first create a configuration code here:
- {% endblocktrans %}
-
-
- {% endif %}
-
-
- {% if configs and "create" not in request.GET %}
- {% trans "Existing app configurations" %}
-
- {% trans "Create a new configuration" %}
-
-
- {% endif %}
-{% endblock %}
-
diff --git a/src/pretix/plugins/pretixdroid/templates/pretixplugins/pretixdroid/configuration_code.html b/src/pretix/plugins/pretixdroid/templates/pretixplugins/pretixdroid/configuration_code.html
deleted file mode 100644
index 43fc3577c..000000000
--- a/src/pretix/plugins/pretixdroid/templates/pretixplugins/pretixdroid/configuration_code.html
+++ /dev/null
@@ -1,73 +0,0 @@
-{% extends "pretixcontrol/event/base.html" %}
-{% load i18n %}
-{% load bootstrap3 %}
-{% load static %}
-{% block title %}{% trans "Device configuration" %}{% endblock %}
-{% block content %}
- {% if config.app == "pretixdroid" %}
-
- {% trans "1. Download app" %}
-
-
-
-
-
-
-
- {% blocktrans trimmed %}
- Android, Google Play and the Google Play logo are trademarks of Google Inc.
- {% endblocktrans %}
-
-
- {% trans "2. Scan code" %}
-
-
- {% trans "3. Start scanning tickets" %}
-
- {% else %}
-
- {% trans "1. Download pretixdesk" %}
-
-
- {% trans "Open download page" %}
-
-
- {% trans "2. Connect device" %}
-
-
- {% trans "Connect with pretixdesk" %}
-
-
-
- {% blocktrans trimmed %}
- If this link does not open the pretixdesk application or if you want to set the application up on a
- separate device, copy the following code and paste it into the application:
- {% endblocktrans %}
-
-
-
-
- {% trans "3. Start scanning tickets" %}
-
- {% if request.event.testmode %}
-
- {% trans "Test mode orders will only be scanned if you scan online. If you scan in asynchronous mode, test mode orders won't be there." %}
-
- {% endif %}
- {% endif %}
-{% endblock %}
-
diff --git a/src/pretix/plugins/pretixdroid/urls.py b/src/pretix/plugins/pretixdroid/urls.py
deleted file mode 100644
index 789a5387d..000000000
--- a/src/pretix/plugins/pretixdroid/urls.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from django.conf.urls import include, url
-
-from . import views
-
-pretixdroid_api_patterns = [
- url(r'^redeem/', views.ApiRedeemView.as_view(),
- name='api.redeem'),
- url(r'^search/', views.ApiSearchView.as_view(),
- name='api.search'),
- url(r'^download/', views.ApiDownloadView.as_view(),
- name='api.download'),
- url(r'^status/', views.ApiStatusView.as_view(),
- name='api.status'),
-]
-
-urlpatterns = [
- url(r'^control/event/(?P[^/]+)/(?P[^/]+)/pretixdroid/$', views.ConfigView.as_view(),
- name='config'),
- url(r'^control/event/(?P[^/]+)/(?P[^/]+)/pretixdroid/(?P\d+)/$',
- views.ConfigCodeView.as_view(), name='config.code'),
- url(r'^pretixdroid/api/(?P[^/]+)/(?P[^/]+)/(?P\d+)/',
- include(pretixdroid_api_patterns)),
- url(r'^pretixdroid/api/(?P[^/]+)/(?P[^/]+)/', include(pretixdroid_api_patterns)),
-]
diff --git a/src/pretix/plugins/pretixdroid/views.py b/src/pretix/plugins/pretixdroid/views.py
deleted file mode 100644
index 6cc7ec828..000000000
--- a/src/pretix/plugins/pretixdroid/views.py
+++ /dev/null
@@ -1,433 +0,0 @@
-import json
-import logging
-import urllib.parse
-
-import dateutil.parser
-from django.contrib import messages
-from django.core.exceptions import ValidationError
-from django.db.models import Count, Max, OuterRef, Q, Subquery
-from django.http import (
- HttpResponseForbidden, HttpResponseNotFound, JsonResponse,
-)
-from django.shortcuts import get_object_or_404, redirect
-from django.urls import reverse
-from django.utils.decorators import method_decorator
-from django.utils.functional import cached_property
-from django.utils.timezone import now
-from django.utils.translation import gettext_lazy as _
-from django.views.decorators.csrf import csrf_exempt
-from django.views.generic import TemplateView, View
-from django_scopes import scope, scopes_disabled
-
-from pretix.base.models import Checkin, Event, Order, OrderPosition
-from pretix.base.models.event import SubEvent
-from pretix.base.services.checkin import (
- CheckInError, RequiredQuestionsError, perform_checkin,
-)
-from pretix.control.permissions import EventPermissionRequiredMixin
-from pretix.helpers.urls import build_absolute_uri
-from pretix.multidomain.urlreverse import (
- build_absolute_uri as event_absolute_uri,
-)
-from pretix.plugins.pretixdroid.forms import AppConfigurationForm
-from pretix.plugins.pretixdroid.models import AppConfiguration
-
-logger = logging.getLogger('pretix.plugins.pretixdroid')
-API_VERSION = 3
-
-
-class ConfigCodeView(EventPermissionRequiredMixin, TemplateView):
- template_name = 'pretixplugins/pretixdroid/configuration_code.html'
- permission = 'can_change_orders'
-
- def get(self, request, **kwargs):
- try:
- self.object = self.request.event.appconfiguration_set.get(pk=kwargs.get("config"))
- except AppConfiguration.DoesNotExist:
- messages.error(request, _('The selected configuration does not exist.'))
- return redirect(reverse('plugins:pretixdroid:config', kwargs={
- 'organizer': self.request.event.organizer.slug,
- 'event': self.request.event.slug,
- }))
- return super().get(request, **kwargs)
-
- def get_context_data(self, **kwargs):
- ctx = super().get_context_data()
- url = build_absolute_uri('plugins:pretixdroid:api.redeem', kwargs={
- 'organizer': self.request.event.organizer.slug,
- 'event': self.request.event.slug
- })
- if self.object.subevent:
- url = build_absolute_uri('plugins:pretixdroid:api.redeem', kwargs={
- 'organizer': self.request.event.organizer.slug,
- 'event': self.request.event.slug,
- 'subevent': self.object.subevent.pk
- })
-
- data = {
- 'version': API_VERSION,
- 'url': url[:-7], # the slice removes the redeem/ part at the end
- 'key': self.object.key,
- 'allow_search': self.object.allow_search,
- 'show_info': self.object.show_info
- }
- ctx['config'] = self.object
- ctx['query'] = urllib.parse.urlencode(data, safe=':/')
- ctx['qrdata'] = json.dumps(data)
- return ctx
-
-
-class ConfigView(EventPermissionRequiredMixin, TemplateView):
- template_name = 'pretixplugins/pretixdroid/configuration.html'
- permission = 'can_change_orders'
-
- @cached_property
- def add_form(self):
- return AppConfigurationForm(
- event=self.request.event,
- instance=AppConfiguration(event=self.request.event),
- data=self.request.POST if self.request.method == "POST" and "add" in self.request.POST else None
- )
-
- def post(self, request, *args, **kwargs):
- if "add" in self.request.POST and self.add_form.is_valid():
- self.add_form.save()
- self.request.event.log_action('pretix.plugins.pretixdroid.config.added', user=self.request.user,
- data=dict(self.add_form.cleaned_data))
- return redirect(reverse('plugins:pretixdroid:config.code', kwargs={
- 'organizer': self.request.event.organizer.slug,
- 'event': self.request.event.slug,
- 'config': self.add_form.instance.pk
- }))
- elif "delete" in self.request.POST:
- try:
- ac = self.request.event.appconfiguration_set.get(pk=request.POST.get("delete"))
- self.request.event.log_action('pretix.plugins.pretixdroid.config.deleted', user=self.request.user,
- data={'id': ac.pk})
- ac.delete()
- messages.success(request, _('The selected configuration has been deleted.'))
- except AppConfiguration.DoesNotExist:
- messages.error(request, _('The selected configuration does not exist.'))
- return redirect(reverse('plugins:pretixdroid:config', kwargs={
- 'organizer': self.request.event.organizer.slug,
- 'event': self.request.event.slug,
- }))
- else:
- return self.get(request, *args, **kwargs)
-
- def get_context_data(self, **kwargs):
- ctx = super().get_context_data()
- ctx['add_form'] = self.add_form
- ctx['configs'] = self.request.event.appconfiguration_set.select_related('list').prefetch_related('items')
- return ctx
-
-
-class ApiView(View):
- @method_decorator(csrf_exempt)
- def dispatch(self, request, **kwargs):
- with scopes_disabled():
- try:
- self.event = Event.objects.get(
- slug=self.kwargs['event'],
- organizer__slug=self.kwargs['organizer']
- )
- except Event.DoesNotExist:
- return HttpResponseNotFound('Unknown event')
- with scope(organizer=self.event.organizer):
- try:
- self.config = self.event.appconfiguration_set.get(key=request.GET.get("key", "-unset-"))
- except AppConfiguration.DoesNotExist:
- return HttpResponseForbidden('Invalid key')
-
- self.subevent = None
- if self.event.has_subevents:
- if self.config.list.subevent:
- self.subevent = self.config.list.subevent
- if 'subevent' in kwargs and kwargs['subevent'] != str(self.subevent.pk):
- return HttpResponseForbidden('Invalid subevent selected.')
- elif 'subevent' in kwargs:
- self.subevent = get_object_or_404(SubEvent, event=self.event, pk=kwargs['subevent'])
- else:
- return HttpResponseForbidden('No subevent selected.')
- else:
- if 'subevent' in kwargs:
- return HttpResponseForbidden('Subevents not enabled.')
-
- return super().dispatch(request, **kwargs)
-
-
-class ApiRedeemView(ApiView):
- def post(self, request, **kwargs):
- secret = request.POST.get('secret', '!INVALID!')
- force = request.POST.get('force', 'false') in ('true', 'True')
- ignore_unpaid = request.POST.get('ignore_unpaid', 'false') in ('true', 'True')
- nonce = request.POST.get('nonce')
- response = {
- 'version': API_VERSION
- }
-
- if 'datetime' in request.POST:
- dt = dateutil.parser.parse(request.POST.get('datetime'))
- else:
- dt = now()
-
- try:
- op = OrderPosition.objects.get(order__event=self.event, secret=secret, subevent=self.subevent)
- except OrderPosition.DoesNotExist:
- response['status'] = 'error'
- response['reason'] = 'unknown_ticket'
- else:
- given_answers = {}
- for q in op.item.questions.filter(ask_during_checkin=True):
- if 'answer_{}'.format(q.pk) in request.POST:
- try:
- given_answers[q] = q.clean_answer(request.POST.get('answer_{}'.format(q.pk)))
- except ValidationError:
- pass
-
- try:
- if not self.config.all_items and op.item_id not in [i.pk for i in self.config.items.all()]:
- raise CheckInError('', 'product')
- perform_checkin(
- op=op,
- clist=self.config.list,
- given_answers=given_answers,
- force=force,
- ignore_unpaid=ignore_unpaid,
- nonce=nonce,
- datetime=dt,
- questions_supported=bool(request.POST.get('questions_supported'))
- )
- except RequiredQuestionsError as e:
- response['status'] = 'incomplete'
- response['questions'] = [serialize_question(q) for q in e.questions]
- except CheckInError as e:
- response['status'] = 'error'
- response['reason'] = e.code
- else:
- response['status'] = 'ok'
-
- response['data'] = serialize_op(op, redeemed=op.order.status == Order.STATUS_PAID or force,
- clist=self.config.list)
-
- return JsonResponse(response)
-
-
-def serialize_question(q, items=False):
- d = {
- 'id': q.pk,
- 'type': q.type,
- 'question': str(q.question),
- 'required': q.required,
- 'position': q.position,
- 'options': [
- {
- 'id': o.pk,
- 'answer': str(o.answer)
- } for o in q.options.all()
- ] if q.type in ('C', 'M') else []
- }
- if items:
- d['items'] = [i.pk for i in q.items.all()]
- return d
-
-
-def serialize_op(op, redeemed, clist):
- name = op.attendee_name
- if not name and op.addon_to:
- name = op.addon_to.attendee_name
- if not name:
- try:
- name = op.order.invoice_address.name
- except:
- pass
- checkin_allowed = (
- op.order.status == Order.STATUS_PAID
- or (
- op.order.status == Order.STATUS_PENDING
- and clist.include_pending
- )
- )
- return {
- 'secret': op.secret,
- 'order': op.order.code,
- 'item': str(op.item),
- 'item_id': op.item_id,
- 'variation': str(op.variation) if op.variation else None,
- 'variation_id': op.variation_id,
- 'attendee_name': name,
- 'attention': op.item.checkin_attention or op.order.checkin_attention,
- 'redeemed': redeemed,
- 'paid': op.order.status == Order.STATUS_PAID,
- 'checkin_allowed': checkin_allowed,
- 'addons_text': ", ".join([
- '{} - {}'.format(p.item, p.variation) if p.variation else str(p.item)
- for p in op.addons.all()
- ])
- }
-
-
-class ApiSearchView(ApiView):
- def get(self, request, **kwargs):
- query = request.GET.get('query', '!INVALID!')
- response = {
- 'version': API_VERSION
- }
-
- if len(query) >= 4:
- cqs = Checkin.objects.filter(
- position_id=OuterRef('pk'),
- list_id=self.config.list.pk
- ).order_by().values('position_id').annotate(
- m=Max('datetime')
- ).values('m')
-
- qs = OrderPosition.objects.filter(
- order__event=self.event,
- subevent=self.config.list.subevent
- ).annotate(
- last_checked_in=Subquery(cqs)
- ).select_related('item', 'variation', 'order', 'order__invoice_address', 'addon_to').prefetch_related(
- 'addons', 'addons__item', 'addons__variation'
- )
-
- if not self.config.list.all_products:
- qs = qs.filter(item__in=self.config.list.limit_products.values_list('id', flat=True))
-
- if not self.config.all_items:
- qs = qs.filter(item__in=self.config.items.all())
-
- if not self.config.allow_search:
- ops = qs.filter(
- Q(secret__istartswith=query)
- )[:25]
- else:
- ops = qs.filter(
- Q(secret__istartswith=query)
- | Q(attendee_name_cached__icontains=query)
- | Q(addon_to__attendee_name_cached__icontains=query)
- | Q(order__code__istartswith=query)
- | Q(order__invoice_address__name_cached__icontains=query)
- )[:25]
-
- response['results'] = [serialize_op(op, bool(op.last_checked_in), self.config.list) for op in ops]
- else:
- response['results'] = []
-
- return JsonResponse(response)
-
-
-class ApiDownloadView(ApiView):
- def get(self, request, **kwargs):
- response = {
- 'version': API_VERSION
- }
-
- cqs = Checkin.objects.filter(
- position_id=OuterRef('pk'),
- list_id=self.config.list.pk
- ).order_by().values('position_id').annotate(
- m=Max('datetime')
- ).values('m')
-
- qs = OrderPosition.objects.filter(
- order__event=self.event,
- order__status__in=[Order.STATUS_PAID] + ([Order.STATUS_PENDING] if self.config.list.include_pending else
- []),
- order__testmode=False,
- subevent=self.config.list.subevent
- ).annotate(
- last_checked_in=Subquery(cqs)
- ).select_related('item', 'variation', 'order', 'addon_to').prefetch_related(
- 'addons', 'addons__item', 'addons__variation'
- )
-
- if not self.config.list.all_products:
- qs = qs.filter(item__in=self.config.list.limit_products.values_list('id', flat=True))
-
- if not self.config.all_items:
- qs = qs.filter(item__in=self.config.items.all())
-
- response['results'] = [serialize_op(op, bool(op.last_checked_in), self.config.list) for op in qs]
-
- questions = self.event.questions.filter(ask_during_checkin=True).prefetch_related('items', 'options')
- response['questions'] = [serialize_question(q, items=True) for q in questions]
- return JsonResponse(response)
-
-
-class ApiStatusView(ApiView):
- def get(self, request, **kwargs):
-
- cqs = Checkin.objects.filter(
- position__order__event=self.event, position__subevent=self.subevent,
- position__order__status__in=[Order.STATUS_PAID] + ([Order.STATUS_PENDING] if
- self.config.list.include_pending else []),
- list=self.config.list
- )
- pqs = OrderPosition.objects.filter(
- order__event=self.event,
- order__status__in=[Order.STATUS_PAID] + ([Order.STATUS_PENDING] if self.config.list.include_pending else
- []),
- subevent=self.subevent,
- )
- if not self.config.list.all_products:
- pqs = pqs.filter(item__in=self.config.list.limit_products.values_list('id', flat=True))
-
- ev = self.subevent or self.event
- response = {
- 'version': API_VERSION,
- 'event': {
- 'name': str(ev.name),
- 'list': self.config.list.name,
- 'slug': self.event.slug,
- 'organizer': {
- 'name': str(self.event.organizer),
- 'slug': self.event.organizer.slug
- },
- 'subevent': self.subevent.pk if self.subevent else str(self.event),
- 'date_from': ev.date_from,
- 'date_to': ev.date_to,
- 'timezone': self.event.settings.timezone,
- 'url': event_absolute_uri(self.event, 'presale:event.index')
- },
- 'checkins': cqs.count(),
- 'total': pqs.count()
- }
-
- op_by_item = {
- p['item']: p['cnt']
- for p in pqs.order_by().values('item').annotate(cnt=Count('id'))
- }
- op_by_variation = {
- p['variation']: p['cnt']
- for p in pqs.order_by().values('variation').annotate(cnt=Count('id'))
- }
- c_by_item = {
- p['position__item']: p['cnt']
- for p in cqs.order_by().values('position__item').annotate(cnt=Count('id'))
- }
- c_by_variation = {
- p['position__variation']: p['cnt']
- for p in cqs.order_by().values('position__variation').annotate(cnt=Count('id'))
- }
-
- response['items'] = []
- for item in self.event.items.order_by('pk').prefetch_related('variations'):
- i = {
- 'id': item.pk,
- 'name': str(item),
- 'admission': item.admission,
- 'checkins': c_by_item.get(item.pk, 0),
- 'total': op_by_item.get(item.pk, 0),
- 'variations': []
- }
- for var in item.variations.all():
- i['variations'].append({
- 'id': var.pk,
- 'name': str(var),
- 'checkins': c_by_variation.get(var.pk, 0),
- 'total': op_by_variation.get(var.pk, 0),
- })
- response['items'].append(i)
-
- return JsonResponse(response)