Add check-in capabilities to official RESTful API (#884)

* Add check-in capabilities to official RESTful API

* Add deprecation note
This commit is contained in:
Raphael Michel
2018-04-25 16:02:07 +02:00
committed by GitHub
parent 4f83d69205
commit 1a0e2031d2
5 changed files with 409 additions and 6 deletions

View File

@@ -1,18 +1,25 @@
from django.core.exceptions import ValidationError
from django.db.models import Count, F, Max, OuterRef, Prefetch, Subquery
from django.db.models.functions import Coalesce
from django.shortcuts import get_object_or_404
from django.utils.functional import cached_property
from django.utils.timezone import now
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from rest_framework import viewsets
from rest_framework.decorators import detail_route
from rest_framework.fields import DateTimeField
from rest_framework.response import Response
from pretix.api.serializers.checkin import CheckinListSerializer
from pretix.api.serializers.item import QuestionSerializer
from pretix.api.serializers.order import OrderPositionSerializer
from pretix.api.views import RichOrderingFilter
from pretix.api.views.order import OrderPositionFilter
from pretix.base.models import Checkin, CheckinList, Order, OrderPosition
from pretix.base.models.organizer import TeamAPIToken
from pretix.base.services.checkin import (
CheckInError, RequiredQuestionsError, perform_checkin,
)
from pretix.helpers.database import FixedOrderBy
@@ -201,3 +208,53 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
qs = qs.filter(item__in=self.checkinlist.limit_products.values_list('id', flat=True))
return qs
@detail_route(methods=['POST'])
def redeem(self, *args, **kwargs):
force = bool(self.request.data.get('force', False))
ignore_unpaid = bool(self.request.data.get('ignore_unpaid', False))
nonce = self.request.data.get('nonce')
op = self.get_object()
if 'datetime' in self.request.data:
dt = DateTimeField().to_internal_value(self.request.data.get('datetime'))
else:
dt = now()
given_answers = {}
if 'answers' in self.request.data:
aws = self.request.data.get('answers')
for q in op.item.questions.filter(ask_during_checkin=True):
if str(q.pk) in aws:
try:
given_answers[q] = q.clean_answer(aws[str(q.pk)])
except ValidationError:
pass
try:
perform_checkin(
op=op,
clist=self.checkinlist,
given_answers=given_answers,
force=force,
ignore_unpaid=ignore_unpaid,
nonce=nonce,
datetime=dt,
questions_supported=self.request.data.get('questions_supported', True)
)
except RequiredQuestionsError as e:
return Response({
'status': 'incomplete',
'questions': [
QuestionSerializer(q).data for q in e.questions
]
}, status=400)
except CheckInError as e:
return Response({
'status': 'error',
'reason': e.code
}, status=400)
else:
return Response({
'status': 'ok',
}, status=201)

View File

@@ -4,9 +4,12 @@ from decimal import Decimal
from unittest import mock
import pytest
from django.utils.timezone import now
from django_countries.fields import Country
from i18nfield.strings import LazyI18nString
from pytz import UTC
from pretix.api.serializers.item import QuestionSerializer
from pretix.base.models import (
Checkin, CheckinList, InvoiceAddress, Order, OrderPosition,
)
@@ -436,3 +439,219 @@ def test_status(token_client, organizer, event, clist_all, item, other_item, ord
'variations': []
}
]
@pytest.mark.django_db
def test_custom_datetime(token_client, organizer, clist, event, order):
dt = now() - datetime.timedelta(days=1)
dt = dt.replace(microsecond=0)
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {
'datetime': dt.isoformat()
}, format='json')
assert resp.status_code == 201
assert resp.data['status'] == 'ok'
assert Checkin.objects.last().datetime == dt
@pytest.mark.django_db
def test_only_once(token_client, organizer, clist, event, order):
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {}, format='json')
assert resp.status_code == 201
assert resp.data['status'] == 'ok'
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {}, format='json')
assert resp.status_code == 400
assert resp.data['status'] == 'error'
assert resp.data['reason'] == 'already_redeemed'
@pytest.mark.django_db
def test_reupload_same_nonce(token_client, organizer, clist, event, order):
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {'nonce': 'foobar'}, format='json')
assert resp.status_code == 201
assert resp.data['status'] == 'ok'
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {'nonce': 'foobar'}, format='json')
assert resp.status_code == 201
assert resp.data['status'] == 'ok'
@pytest.mark.django_db
def test_multiple_different_list(token_client, organizer, clist, clist_all, event, order):
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {'nonce': 'foobar'}, format='json')
assert resp.status_code == 201
assert resp.data['status'] == 'ok'
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist_all.pk, order.positions.first().pk
), {'nonce': 'baz'}, format='json')
assert resp.status_code == 201
assert resp.data['status'] == 'ok'
@pytest.mark.django_db
def test_forced_multiple(token_client, organizer, clist, event, order):
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {}, format='json')
assert resp.status_code == 201
assert resp.data['status'] == 'ok'
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {'force': True}, format='json')
assert resp.status_code == 201
assert resp.data['status'] == 'ok'
@pytest.mark.django_db
def test_require_paid(token_client, organizer, clist, event, order):
order.status = Order.STATUS_PENDING
order.save()
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {}, format='json')
assert resp.status_code == 404
clist.include_pending = True
clist.save()
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {}, format='json')
assert resp.status_code == 400
assert resp.data['status'] == 'error'
assert resp.data['reason'] == 'unpaid'
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {'ignore_unpaid': True}, format='json')
assert resp.status_code == 201
assert resp.data['status'] == 'ok'
@pytest.fixture
def question(event, item):
q = event.questions.create(question=LazyI18nString('Size'), type='C', required=True, ask_during_checkin=True)
a1 = q.options.create(answer=LazyI18nString("M"))
a2 = q.options.create(answer=LazyI18nString("L"))
q.items.add(item)
return q, a1, a2
@pytest.mark.django_db
def test_question_number(token_client, organizer, clist, event, order, question):
question[0].options.all().delete()
question[0].type = 'N'
question[0].save()
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {}, format='json')
assert resp.status_code == 400
assert resp.data['status'] == 'incomplete'
assert resp.data['questions'] == [QuestionSerializer(question[0]).data]
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {'answers': {question[0].pk: "3.24"}}, format='json')
print(resp.data)
assert resp.status_code == 201
assert resp.data['status'] == 'ok'
assert order.positions.first().answers.get(question=question[0]).answer == '3.24'
@pytest.mark.django_db
def test_question_choice(token_client, organizer, clist, event, order, question):
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {}, format='json')
assert resp.status_code == 400
assert resp.data['status'] == 'incomplete'
assert resp.data['questions'] == [QuestionSerializer(question[0]).data]
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {'answers': {question[0].pk: str(question[1].pk)}}, format='json')
assert resp.status_code == 201
assert resp.data['status'] == 'ok'
assert order.positions.first().answers.get(question=question[0]).answer == 'M'
assert list(order.positions.first().answers.get(question=question[0]).options.all()) == [question[1]]
@pytest.mark.django_db
def test_question_invalid(token_client, organizer, clist, event, order, question):
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {'answers': {question[0].pk: "A"}}, format='json')
assert resp.status_code == 400
assert resp.data['status'] == 'incomplete'
assert resp.data['questions'] == [QuestionSerializer(question[0]).data]
@pytest.mark.django_db
def test_question_required(token_client, organizer, clist, event, order, question):
question[0].required = True
question[0].save()
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {}, format='json')
assert resp.status_code == 400
assert resp.data['status'] == 'incomplete'
assert resp.data['questions'] == [QuestionSerializer(question[0]).data]
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {'answers': {question[0].pk: ""}}, format='json')
assert resp.status_code == 400
assert resp.data['status'] == 'incomplete'
assert resp.data['questions'] == [QuestionSerializer(question[0]).data]
@pytest.mark.django_db
def test_question_optional(token_client, organizer, clist, event, order, question):
question[0].required = False
question[0].save()
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {}, format='json')
assert resp.status_code == 400
assert resp.data['status'] == 'incomplete'
assert resp.data['questions'] == [QuestionSerializer(question[0]).data]
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {'answers': {question[0].pk: ""}}, format='json')
assert resp.status_code == 201
assert resp.data['status'] == 'ok'
@pytest.mark.django_db
def test_question_multiple_choice(token_client, organizer, clist, event, order, question):
question[0].type = 'M'
question[0].save()
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {}, format='json')
assert resp.status_code == 400
assert resp.data['status'] == 'incomplete'
assert resp.data['questions'] == [QuestionSerializer(question[0]).data]
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
), {'answers': {question[0].pk: "{},{}".format(question[1].pk, question[2].pk)}}, format='json')
assert resp.status_code == 201
assert resp.data['status'] == 'ok'
assert order.positions.first().answers.get(question=question[0]).answer == 'M, L'
assert set(order.positions.first().answers.get(question=question[0]).options.all()) == {question[1], question[2]}