Remove pretixdroid code

This commit is contained in:
Raphael Michel
2020-10-06 18:27:53 +02:00
parent 13eabdd7f4
commit fa2222e629
11 changed files with 16 additions and 784 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,7 +0,0 @@
$(function () {
jQuery('#qrcodeCanvas').qrcode(
{
text: $("#qrdata").html()
}
);
});

View File

@@ -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 %}
<h1>{% trans "Check-in device configuration" %}</h1>
{% if not configs or "create" in request.GET %}
<div class="alert alert-info pretixscan-migration">
<h2 class="mar">{% trans "We've got a new app!" %}</h2>
<p>
{% 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 %}
</p>
<div class="row">
<div class="col-md-6 col-sm-12 text-center">
<h3>{% trans "Our new app: pretixSCAN" %}</h3>
<img src="{% static "pretixbase/img/pretixscan.svg" %}">
<p>{% trans "Available on Android, iOS, Windows, and Linux." %}</p>
<p>{% trans "Configuration is available in your organizer account's device list." %}</p>
<a href="{% url "control:organizer.devices" organizer=request.organizer.slug %}"
class="btn btn-primary btn-lg">
{% trans "Switch to my device list" %}
</a>
</div>
<div class="col-md-6 col-sm-12 text-center">
<h3>{% trans "Our old apps: pretixdesk and pretixdroid" %}</h3>
<img src="{% static "pretixbase/img/pretixdroid.png" %}">
<p>{% trans "Available on Android, Windows, and Linux." %}</p>
<p><strong>{% trans "Scroll down to create a configuration" %}</strong></p>
</div>
</div>
</div>
{% endif %}
{% if not configs or "create" in request.GET %}
<h2>{% trans "Create app configuration" %}</h2>
<p>
{% blocktrans trimmed %}
To start scanning tickets with our apps, first create a configuration code here:
{% endblocktrans %}
</p>
<form action="?add" method="post" class="form-horizontal">
{% 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" %}
<div class="form-group">
<div class="col-md-offset-3 col-md-9">
<button type="submit" class="btn btn-primary btn-save" name="add" value="1">
{% trans "Create configuration" %}
</button>
</div>
</div>
</form>
{% endif %}
{% if configs and "create" not in request.GET %}
<h2>{% trans "Existing app configurations" %}</h2>
<a href="?create=1" class="btn btn-default">
{% trans "Create a new configuration" %}
</a>
<form action="?" method="post" class="form-horizontal">
{% csrf_token %}
<table class="table">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Check-in list" %}</th>
<th>{% trans "Items" %}</th>
<th>{% trans "Show info" %}</th>
<th>{% trans "Allow search" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for ac in configs %}
<tr>
<td>
{% if ac.app == "pretixdroid" %}
<span class="fa fa-fw fa-android"></span>
{% elif ac.app == "pretixdesk" %}
<span class="fa fa-fw fa-desktop"></span>
{% endif %}
{{ ac.key|slice:"0:8" }}…
</td>
<td>
<a href="{% url "control:event.orders.checkinlists.show" organizer=request.event.organizer.slug event=request.event.slug list=ac.list.id %}">{{ ac.list }}</a>
</td>
<td>
{% if ac.all_items %}
{% trans "All" %}
{% else %}
{% for item in ac.items.all %}
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=item.id %}">{{ item.name }}</a>
{% if forloop.revcounter0 > 0 %}<br>{% endif %}
{% endfor %}
{% endif %}
</td>
<td>{% if ac.show_info %}{% trans "Yes" %}{% else %}{% trans "No" %}{% endif %}</td>
<td>{% if ac.allow_search %}{% trans "Yes" %}{% else %}{% trans "No" %}{% endif %}</td>
<td class="text-right">
<a href="{% url "plugins:pretixdroid:config.code" organizer=request.event.organizer.slug event=request.event.slug config=ac.pk %}" class="btn btn-default">
<span class="fa fa-qrcode"></span> {% trans "Configure device" %}
</a>
<button class="btn btn-danger" name="delete" value="{{ ac.pk }}">
<span class="fa fa-trash"></span>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</form>
{% endif %}
{% endblock %}

View File

@@ -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" %}
<h1>
{% trans "pretixdroid configuration" %}
<a href="{% url "plugins:pretixdroid:config" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default">
{% trans "Back to overview" %}
</a>
</h1>
<h2>{% trans "1. Download app" %}</h2>
<p>
<a href="http://play.google.com/store/apps/details?id=eu.pretix.pretixdroid">
<img src="{% static "pretixplugins/pretixdroid/play_store_en.png" %}" alt="
{% trans "Download the app from the Google Play Store" %}" height="70">
</a>
</p>
<p>
<small>
{% blocktrans trimmed %}
Android, Google Play and the Google Play logo are trademarks of Google Inc.
{% endblocktrans %}
</small>
</p>
<h2>{% trans "2. Scan code" %}</h2>
<div id="qrcodeCanvas"></div>
<script type="text/json" id="qrdata">
{{ qrdata|safe }}
</script>
<h2>{% trans "3. Start scanning tickets" %}</h2>
<script type="text/javascript" src="{% static "pretixplugins/pretixdroid/pretixdroid.js" %}"></script>
{% else %}
<h1>
{% trans "pretixdesk configuration" %}
<a href="{% url "plugins:pretixdroid:config" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default">
{% trans "Back to overview" %}
</a>
</h1>
<h2>{% trans "1. Download pretixdesk" %}</h2>
<p>
<a href="https://pretix.eu/about/en/desk" class="btn btn-lg btn-primary" target="_blank">
{% trans "Open download page" %}
</a>
</p>
<h2>{% trans "2. Connect device" %}</h2>
<p>
<a href="pretixdesk://setup?{{ query }}" class="btn btn-lg btn-primary">
{% trans "Connect with pretixdesk" %}
</a>
</p>
<p>
{% 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 %}
</p>
<p>
<input type="text" class="form-control" value="pretixdesk://setup?{{ query }}" readonly>
</p>
<h2>{% trans "3. Start scanning tickets" %}</h2>
<script type="text/javascript" src="{% static "pretixplugins/pretixdroid/pretixdroid.js" %}"></script>
{% if request.event.testmode %}
<div class="alert-info">
{% 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." %}
</div>
{% endif %}
{% endif %}
{% endblock %}

View File

@@ -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<organizer>[^/]+)/(?P<event>[^/]+)/pretixdroid/$', views.ConfigView.as_view(),
name='config'),
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/pretixdroid/(?P<config>\d+)/$',
views.ConfigCodeView.as_view(), name='config.code'),
url(r'^pretixdroid/api/(?P<organizer>[^/]+)/(?P<event>[^/]+)/(?P<subevent>\d+)/',
include(pretixdroid_api_patterns)),
url(r'^pretixdroid/api/(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include(pretixdroid_api_patterns)),
]

View File

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