mirror of
https://github.com/pretix/pretix.git
synced 2026-08-27 13:24:41 +00:00
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:
@@ -330,6 +330,8 @@ Order position endpoints
|
|||||||
``order__status__in``, ``subevent__in``, ``addon_to__in``, and ``search``. The search for attendee names and order
|
``order__status__in``, ``subevent__in``, ``addon_to__in``, and ``search``. The search for attendee names and order
|
||||||
codes is now case-insensitive.
|
codes is now case-insensitive.
|
||||||
|
|
||||||
|
The ``.../redeem/`` endpoint has been added.
|
||||||
|
|
||||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/(list)/positions/
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/(list)/positions/
|
||||||
|
|
||||||
Returns a list of all order positions within a given event. The result is the same as
|
Returns a list of all order positions within a given event. The result is the same as
|
||||||
@@ -427,7 +429,7 @@ Order position endpoints
|
|||||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||||
:statuscode 404: The requested check-in list does not exist.
|
:statuscode 404: The requested check-in list does not exist.
|
||||||
|
|
||||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/(list)/positions/(id)
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/(list)/positions/(id)/
|
||||||
|
|
||||||
Returns information on one order position, identified by its internal ID.
|
Returns information on one order position, identified by its internal ID.
|
||||||
The result format is the same as the :ref:`order-position-resource`, with one important difference: the
|
The result format is the same as the :ref:`order-position-resource`, with one important difference: the
|
||||||
@@ -437,7 +439,7 @@ Order position endpoints
|
|||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
|
|
||||||
GET /api/v1/organizers/bigevents/events/sampleconf/checkinlists/1/positions/ HTTP/1.1
|
GET /api/v1/organizers/bigevents/events/sampleconf/checkinlists/1/positions/23442/ HTTP/1.1
|
||||||
Host: pretix.eu
|
Host: pretix.eu
|
||||||
Accept: application/json, text/javascript
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
@@ -494,3 +496,127 @@ Order position endpoints
|
|||||||
:statuscode 401: Authentication failure
|
:statuscode 401: Authentication failure
|
||||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||||
:statuscode 404: The requested order position or check-in list does not exist.
|
:statuscode 404: The requested order position or check-in list does not exist.
|
||||||
|
|
||||||
|
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/(list)/positions/(id)/redeem/
|
||||||
|
|
||||||
|
Tries to redeem an order position, identified by its internal ID, i.e. checks the attendee in. This endpoint
|
||||||
|
accepts a number of optional requests in the body.
|
||||||
|
|
||||||
|
:<json boolean questions_supported: When this parameter is set to ``true``, handling of questions is supported. If
|
||||||
|
you do not implement question handling in your user interface, you **must**
|
||||||
|
set this to ``false``. In that case, questions will just be ignored. Defaults
|
||||||
|
to ``true``.
|
||||||
|
:<json datetime datetime: Specifies the datetime of the check-in. If not supplied, the current time will be used.
|
||||||
|
:<json boolean force: Specifies that the check-in should succeed regardless of previous check-ins or required
|
||||||
|
questions that have not been filled. Defaults to ``false``.
|
||||||
|
:<json boolean ignore_unpaid: Specifies that the check-in should succeed even if the order is in pending state.
|
||||||
|
Defaults to ``false``.
|
||||||
|
:<json string nonce: You can set this parameter to a unique random value to identify this check-in. If you're sending
|
||||||
|
this request twice with the same nonce, the second request will also succeed but will always
|
||||||
|
create only one check-in object even when the previous request was successful as well. This
|
||||||
|
allows for a certain level of idempotency and enables you to re-try after a connection failure.
|
||||||
|
:<json object answers: If questions are supported/required, you may/must supply a mapping of question IDs to their
|
||||||
|
respective answers. The answers should always be strings. In case of (multiple-)choice-type
|
||||||
|
answers, the string should contain the (comma-separated) IDs of the selected options.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
POST /api/v1/organizers/bigevents/events/sampleconf/checkinlists/1/positions/234/redeem/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"force": false,
|
||||||
|
"ignore_unpaid": false,
|
||||||
|
"nonce": "Pvrk50vUzQd0DhdpNRL4I4OcXsvg70uA",
|
||||||
|
"datetime": null,
|
||||||
|
"questions_supported": true,
|
||||||
|
"answers": {
|
||||||
|
"4": "XS"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
**Example successful response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 201 Created
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"status": "ok"
|
||||||
|
}
|
||||||
|
|
||||||
|
**Example response with required questions**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 400 Bad Request
|
||||||
|
Content-Type: text/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"status": "incomplete"
|
||||||
|
"questions": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"question": {"en": "T-Shirt size"},
|
||||||
|
"type": "C",
|
||||||
|
"required": false,
|
||||||
|
"items": [1, 2],
|
||||||
|
"position": 1,
|
||||||
|
"identifier": "WY3TP9SL",
|
||||||
|
"ask_during_checkin": true,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"identifier": "LVETRWVU",
|
||||||
|
"position": 0,
|
||||||
|
"answer": {"en": "S"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"identifier": "DFEMJWMJ",
|
||||||
|
"position": 1,
|
||||||
|
"answer": {"en": "M"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"identifier": "W9AH7RDE",
|
||||||
|
"position": 2,
|
||||||
|
"answer": {"en": "L"}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
**Example error response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Type: text/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"reason": "unpaid",
|
||||||
|
}
|
||||||
|
|
||||||
|
Possible error reasons:
|
||||||
|
|
||||||
|
* ``unpaid`` - Ticket is not paid for or has been refunded
|
||||||
|
* ``already_redeemed`` - Ticket already has been redeemed
|
||||||
|
* ``product`` - Tickets with this product may not be scanned at this device
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
:param list: The ID of the check-in list to look for
|
||||||
|
:param id: The ``id`` field of the order position to fetch
|
||||||
|
:statuscode 201: no error
|
||||||
|
:statuscode 400: Invalid or incomplete request, see above
|
||||||
|
:statuscode 401: Authentication failure
|
||||||
|
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||||
|
:statuscode 404: The requested order position or check-in list does not exist.
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ pretixdroid HTTP API
|
|||||||
The pretixdroid plugin provides a HTTP API that the `pretixdroid Android app`_
|
The pretixdroid plugin provides a HTTP API that the `pretixdroid Android app`_
|
||||||
uses to communicate with the pretix server.
|
uses to communicate with the pretix server.
|
||||||
|
|
||||||
.. warning:: This API is intended **only** to serve the pretixdroid Android app. There are no backwards compatibility
|
.. warning:: This API is **DEPRECATED** and will probably go away soon. It is used **only** to serve the pretixdroid
|
||||||
guarantees on this API. We will not add features that are not required for the Android App. There is a
|
Android app. There are no backwards compatibility guarantees on this API. We will not add features that
|
||||||
general-purpose :ref:`rest-api` that not yet provides all features that this API provides, but will do
|
are not required for the Android App. There is a general-purpose :ref:`rest-api` that provides all
|
||||||
so in the future.
|
features that you need to check in.
|
||||||
|
|
||||||
.. versionchanged:: 1.12
|
.. versionchanged:: 1.12
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ gettext
|
|||||||
gunicorn
|
gunicorn
|
||||||
hardcoded
|
hardcoded
|
||||||
hostname
|
hostname
|
||||||
|
idempotency
|
||||||
inofficial
|
inofficial
|
||||||
invalidations
|
invalidations
|
||||||
iterable
|
iterable
|
||||||
|
|||||||
@@ -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 import Count, F, Max, OuterRef, Prefetch, Subquery
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
from django.utils.timezone import now
|
||||||
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from rest_framework.decorators import detail_route
|
from rest_framework.decorators import detail_route
|
||||||
|
from rest_framework.fields import DateTimeField
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from pretix.api.serializers.checkin import CheckinListSerializer
|
from pretix.api.serializers.checkin import CheckinListSerializer
|
||||||
|
from pretix.api.serializers.item import QuestionSerializer
|
||||||
from pretix.api.serializers.order import OrderPositionSerializer
|
from pretix.api.serializers.order import OrderPositionSerializer
|
||||||
from pretix.api.views import RichOrderingFilter
|
from pretix.api.views import RichOrderingFilter
|
||||||
from pretix.api.views.order import OrderPositionFilter
|
from pretix.api.views.order import OrderPositionFilter
|
||||||
from pretix.base.models import Checkin, CheckinList, Order, OrderPosition
|
from pretix.base.models import Checkin, CheckinList, Order, OrderPosition
|
||||||
from pretix.base.models.organizer import TeamAPIToken
|
from pretix.base.models.organizer import TeamAPIToken
|
||||||
|
from pretix.base.services.checkin import (
|
||||||
|
CheckInError, RequiredQuestionsError, perform_checkin,
|
||||||
|
)
|
||||||
from pretix.helpers.database import FixedOrderBy
|
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))
|
qs = qs.filter(item__in=self.checkinlist.limit_products.values_list('id', flat=True))
|
||||||
|
|
||||||
return qs
|
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)
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ from decimal import Decimal
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from django.utils.timezone import now
|
||||||
from django_countries.fields import Country
|
from django_countries.fields import Country
|
||||||
|
from i18nfield.strings import LazyI18nString
|
||||||
from pytz import UTC
|
from pytz import UTC
|
||||||
|
|
||||||
|
from pretix.api.serializers.item import QuestionSerializer
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
Checkin, CheckinList, InvoiceAddress, Order, OrderPosition,
|
Checkin, CheckinList, InvoiceAddress, Order, OrderPosition,
|
||||||
)
|
)
|
||||||
@@ -436,3 +439,219 @@ def test_status(token_client, organizer, event, clist_all, item, other_item, ord
|
|||||||
'variations': []
|
'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]}
|
||||||
|
|||||||
Reference in New Issue
Block a user