pretixdroid: Completely new API

This commit is contained in:
Raphael Michel
2016-08-30 12:57:06 +02:00
parent dc3e1e3d30
commit 854fb1218e
7 changed files with 91 additions and 19 deletions

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-08-30 10:09
from __future__ import unicode_literals
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('pretixbase', '0033_auto_20160821_2222'),
]
operations = [
migrations.CreateModel(
name='Checkin',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('datetime', models.DateTimeField(auto_now_add=True)),
('position', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pretixdroid_checkins', to='pretixbase.OrderPosition')),
],
),
]

View File

@@ -0,0 +1,6 @@
from django.db import models
class Checkin(models.Model):
position = models.ForeignKey('pretixbase.OrderPosition', related_name='pretixdroid_checkins')
datetime = models.DateTimeField(auto_now_add=True)

View File

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

View File

@@ -10,6 +10,9 @@
If you try to configure the app, it will ask you to scan the QR code below. If you try to configure the app, it will ask you to scan the QR code below.
{% endblocktrans %}</p> {% endblocktrans %}</p>
<div id="qrcodeCanvas"></div> <div id="qrcodeCanvas"></div>
<script type="text/json" id="qrdata">
{{ qrdata|safe }}
</script>
<script type="text/javascript" src="{% static "pretixplugins/pretixdroid/pretixdroid.js" %}"></script> <script type="text/javascript" src="{% static "pretixplugins/pretixdroid/pretixdroid.js" %}"></script>
<a href="?flush_key=1" class="btn btn-default">{% trans "Reset authentication token" %}</a> <a href="?flush_key=1" class="btn btn-default">{% trans "Reset authentication token" %}</a>
{% endblock %} {% endblock %}

View File

@@ -5,6 +5,6 @@ from . import views
urlpatterns = [ urlpatterns = [
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/pretixdroid/', views.ConfigView.as_view(), url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/pretixdroid/', views.ConfigView.as_view(),
name='config'), name='config'),
url(r'^pretixdroid/api/(?P<organizer>[^/]+)/(?P<event>[^/]+)/', views.ApiView.as_view(), url(r'^pretixdroid/api/(?P<organizer>[^/]+)/(?P<event>[^/]+)/redeem/', views.ApiRedeemView.as_view(),
name='api'), name='api.redeem'),
] ]

View File

@@ -2,17 +2,22 @@ import json
import logging import logging
import string import string
from django.db import transaction
from django.http import ( from django.http import (
HttpResponseForbidden, HttpResponseNotFound, JsonResponse, HttpResponseForbidden, HttpResponseNotFound, JsonResponse,
) )
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import TemplateView, View from django.views.generic import TemplateView, View
from pretix.base.models import Event, Order, OrderPosition from pretix.base.models import Event, Order, OrderPosition
from pretix.control.permissions import EventPermissionRequiredMixin from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.helpers.urls import build_absolute_uri from pretix.helpers.urls import build_absolute_uri
from pretix.plugins.pretixdroid.models import Checkin
logger = logging.getLogger('pretix.plugins.pretixdroid') logger = logging.getLogger('pretix.plugins.pretixdroid')
API_VERSION = 2
class ConfigView(EventPermissionRequiredMixin, TemplateView): class ConfigView(EventPermissionRequiredMixin, TemplateView):
@@ -23,44 +28,76 @@ class ConfigView(EventPermissionRequiredMixin, TemplateView):
ctx = super().get_context_data() ctx = super().get_context_data()
key = self.request.event.settings.get('pretixdroid_key') key = self.request.event.settings.get('pretixdroid_key')
if not key or 'flush_key' in self.request.GET: if not key or 'flush_key' in self.request.GET:
key = get_random_string(length=32, allowed_chars=string.ascii_uppercase + string.ascii_lowercase + string.digits) key = get_random_string(length=32,
allowed_chars=string.ascii_uppercase + string.ascii_lowercase + string.digits)
self.request.event.settings.set('pretixdroid_key', key) self.request.event.settings.set('pretixdroid_key', key)
ctx['qrdata'] = json.dumps({ ctx['qrdata'] = json.dumps({
'version': 1, 'version': API_VERSION,
'url': build_absolute_uri('plugins:pretixdroid:api', kwargs={ 'url': build_absolute_uri('plugins:pretixdroid:api.redeem', kwargs={
'organizer': self.request.event.organizer.slug, 'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug 'event': self.request.event.slug
}), })[:-7], # the slice removes the redeem/ part at the end
'key': key 'key': key
}) })
return ctx return ctx
class ApiView(View): class ApiView(View):
def get(self, request, **kwargs):
@method_decorator(csrf_exempt)
def dispatch(self, request, **kwargs):
try: try:
event = Event.objects.get( self.event = Event.objects.get(
slug=self.kwargs['event'], slug=self.kwargs['event'],
organizer__slug=self.kwargs['organizer'] organizer__slug=self.kwargs['organizer']
) )
except Event.DoesNotExist: except Event.DoesNotExist:
return HttpResponseNotFound('Unknown event') return HttpResponseNotFound('Unknown event')
if (not event.settings.get('pretixdroid_key') if (not self.event.settings.get('pretixdroid_key')
or event.settings.get('pretixdroid_key') != request.GET.get('key', '')): or self.event.settings.get('pretixdroid_key') != request.GET.get('key', '')):
return HttpResponseForbidden('Invalid key') return HttpResponseForbidden('Invalid key')
ops = OrderPosition.objects.filter( return super().dispatch(request, **kwargs)
order__event=event, order__status=Order.STATUS_PAID,
).select_related('item', 'variation')
data = [ class ApiRedeemView(ApiView):
{ def post(self, request, **kwargs):
secret = request.POST.get('secret', '!INVALID!')
response = {
'version': API_VERSION
}
try:
with transaction.atomic():
created = False
op = OrderPosition.objects.select_related('item', 'variation', 'order').get(
order__event=self.event, secret=secret
)
if op.order.status == Order.STATUS_PAID:
ci, created = Checkin.objects.get_or_create(position=op)
else:
response['status'] = 'error'
response['reason'] = 'unpaid'
if 'status' not in response:
if created:
response['status'] = 'ok'
else:
response['status'] = 'error'
response['reason'] = 'already_redeemed'
response['data'] = {
'secret': op.secret, 'secret': op.secret,
'order': op.order.code,
'item': str(op.item), 'item': str(op.item),
'variation': str(op.variation) if op.variation else None, 'variation': str(op.variation) if op.variation else None,
'attendee_name': op.attendee_name 'attendee_name': op.attendee_name
} }
for op in ops
] except OrderPosition.DoesNotExist:
return JsonResponse({'data': data, 'version': 1}) response['status'] = 'error'
response['reason'] = 'unknown_ticket'
return JsonResponse(response)