From fa2222e629b0b5827a8c7896d416459ef0fc2255 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Tue, 6 Oct 2020 18:27:53 +0200 Subject: [PATCH] Remove pretixdroid code --- src/pretix/plugins/pretixdroid/__init__.py | 3 - src/pretix/plugins/pretixdroid/forms.py | 43 -- .../0004_delete_appconfiguration.py | 16 + src/pretix/plugins/pretixdroid/models.py | 36 -- src/pretix/plugins/pretixdroid/signals.py | 38 -- .../pretixdroid/play_store_en.png | Bin 14212 -> 0 bytes .../pretixplugins/pretixdroid/pretixdroid.js | 7 - .../pretixdroid/configuration.html | 127 ----- .../pretixdroid/configuration_code.html | 73 --- src/pretix/plugins/pretixdroid/urls.py | 24 - src/pretix/plugins/pretixdroid/views.py | 433 ------------------ 11 files changed, 16 insertions(+), 784 deletions(-) delete mode 100644 src/pretix/plugins/pretixdroid/forms.py create mode 100644 src/pretix/plugins/pretixdroid/migrations/0004_delete_appconfiguration.py delete mode 100644 src/pretix/plugins/pretixdroid/models.py delete mode 100644 src/pretix/plugins/pretixdroid/signals.py delete mode 100644 src/pretix/plugins/pretixdroid/static/pretixplugins/pretixdroid/play_store_en.png delete mode 100644 src/pretix/plugins/pretixdroid/static/pretixplugins/pretixdroid/pretixdroid.js delete mode 100644 src/pretix/plugins/pretixdroid/templates/pretixplugins/pretixdroid/configuration.html delete mode 100644 src/pretix/plugins/pretixdroid/templates/pretixplugins/pretixdroid/configuration_code.html delete mode 100644 src/pretix/plugins/pretixdroid/urls.py delete mode 100644 src/pretix/plugins/pretixdroid/views.py diff --git a/src/pretix/plugins/pretixdroid/__init__.py b/src/pretix/plugins/pretixdroid/__init__.py index 7736d89419..604dd7949c 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 6e3a0016f8..0000000000 --- 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 0000000000..38b1d8e2f0 --- /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 266076d22f..0000000000 --- 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 a2a9e8c5e7..0000000000 --- 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 1fa74c1b373dce5aea37b78bd4a6c9bd7039c2a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14212 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 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 %} -

-
- {% csrf_token %} - {% bootstrap_form_errors add_form %} - {% bootstrap_field add_form.list layout="horizontal" %} - {% bootstrap_field add_form.all_items layout="horizontal" %} - {% bootstrap_field add_form.items layout="horizontal" %} - {% bootstrap_field add_form.show_info layout="horizontal" %} - {% bootstrap_field add_form.allow_search layout="horizontal" %} - {% bootstrap_field add_form.app layout="horizontal" %} -
-
- -
-
-
- {% endif %} - - - {% if configs and "create" not in request.GET %} -

{% trans "Existing app configurations" %}

- - {% trans "Create a new configuration" %} - -
- {% csrf_token %} - - - - - - - - - - - - - {% for ac in configs %} - - - - - - - - - {% endfor %} - -
{% trans "ID" %}{% trans "Check-in list" %}{% trans "Items" %}{% trans "Show info" %}{% trans "Allow search" %}
- {% if ac.app == "pretixdroid" %} - - {% elif ac.app == "pretixdesk" %} - - {% endif %} - {{ ac.key|slice:"0:8" }}… - - {{ ac.list }} - - {% if ac.all_items %} - {% trans "All" %} - {% else %} - {% for item in ac.items.all %} - {{ item.name }} - {% if forloop.revcounter0 > 0 %}
{% endif %} - {% endfor %} - {% endif %} -
{% if ac.show_info %}{% trans "Yes" %}{% else %}{% trans "No" %}{% endif %}{% if ac.allow_search %}{% trans "Yes" %}{% else %}{% trans "No" %}{% endif %} - - {% trans "Configure device" %} - - -
-
- {% 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 43fc3577c0..0000000000 --- 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 "pretixdroid configuration" %} - - {% trans "Back to overview" %} - -

-

{% trans "1. Download app" %}

-

- - 
-    {% trans - -

-

- - {% 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 "pretixdesk configuration" %} - - {% trans "Back to overview" %} - -

-

{% 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 789a5387df..0000000000 --- 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 6cc7ec8286..0000000000 --- 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)