API endpoint for positions on a check-in list

This commit is contained in:
Raphael Michel
2017-12-20 15:07:31 +01:00
parent 50c3f025e2
commit f3c1296105
7 changed files with 511 additions and 4 deletions

View File

@@ -38,7 +38,7 @@ class AnswerSerializer(I18nAwareModelSerializer):
class CheckinSerializer(I18nAwareModelSerializer):
class Meta:
model = Checkin
fields = ('datetime',)
fields = ('datetime', 'list')
class OrderDownloadsField(serializers.Field):

View File

@@ -26,6 +26,9 @@ event_router.register(r'taxrules', event.TaxRuleViewSet)
event_router.register(r'waitinglistentries', waitinglist.WaitingListViewSet)
event_router.register(r'checkinlists', checkin.CheckinListViewSet)
checkinlist_router = routers.DefaultRouter()
checkinlist_router.register(r'positions', checkin.CheckinListPositionViewSet)
# Force import of all plugins to give them a chance to register URLs with the router
for app in apps.get_app_configs():
if hasattr(app, 'PretixPluginMeta'):
@@ -36,4 +39,6 @@ urlpatterns = [
url(r'^', include(router.urls)),
url(r'^organizers/(?P<organizer>[^/]+)/', include(orga_router.urls)),
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/', include(event_router.urls)),
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/checkinlists/(?P<list>[^/]+)/',
include(checkinlist_router.urls)),
]

View File

@@ -0,0 +1,23 @@
from rest_framework.filters import OrderingFilter
class RichOrderingFilter(OrderingFilter):
def filter_queryset(self, request, queryset, view):
ordering = self.get_ordering(request, queryset, view)
if ordering:
if hasattr(view, 'ordering_custom'):
newo = []
for ordering_part in ordering:
ob = view.ordering_custom.get(ordering_part)
if ob:
ob = dict(ob)
newo.append(ob.pop('_order'))
queryset = queryset.annotate(**ob)
else:
newo.append(ordering_part)
ordering = newo
return queryset.order_by(*ordering)
return queryset

View File

@@ -1,9 +1,17 @@
import django_filters
from django.db.models import F, Max, OuterRef, Prefetch, Q, 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_filters.rest_framework import DjangoFilterBackend, FilterSet
from rest_framework import viewsets
from pretix.api.serializers.checkin import CheckinListSerializer
from pretix.base.models import CheckinList
from pretix.api.serializers.order import OrderPositionSerializer
from pretix.api.views import RichOrderingFilter
from pretix.base.models import Checkin, CheckinList, Order, OrderPosition
from pretix.base.models.organizer import TeamAPIToken
from pretix.helpers.database import FixedOrderBy
class CheckinListFilter(FilterSet):
@@ -57,3 +65,79 @@ class CheckinListViewSet(viewsets.ModelViewSet):
api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None),
)
super().perform_destroy(instance)
class OrderPositionFilter(FilterSet):
order = django_filters.CharFilter(name='order', lookup_expr='code')
has_checkin = django_filters.rest_framework.BooleanFilter(method='has_checkin_qs')
attendee_name = django_filters.CharFilter(method='attendee_name_qs')
def has_checkin_qs(self, queryset, name, value):
return queryset.filter(last_checked_in__isnull=not value)
def attendee_name_qs(self, queryset, name, value):
return queryset.filter(Q(attendee_name=value) | Q(addon_to__attendee_name=value))
class Meta:
model = OrderPosition
fields = ['item', 'variation', 'attendee_name', 'secret', 'order', 'has_checkin', 'addon_to', 'subevent']
class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = OrderPositionSerializer
queryset = OrderPosition.objects.none()
filter_backends = (DjangoFilterBackend, RichOrderingFilter)
ordering = ('attendee_name', 'positionid')
ordering_fields = (
'order__code', 'order__datetime', 'positionid', 'attendee_name',
'last_checked_in', 'order__email',
)
ordering_custom = {
'attendee_name': {
'_order': F('display_name').asc(nulls_first=True),
'display_name': Coalesce('attendee_name', 'addon_to__attendee_name')
},
'-attendee_name': {
'_order': F('display_name').desc(nulls_last=True),
'display_name': Coalesce('attendee_name', 'addon_to__attendee_name')
},
'last_checked_in': {
'_order': FixedOrderBy(F('last_checked_in'), nulls_first=True),
},
'-last_checked_in': {
'_order': FixedOrderBy(F('last_checked_in'), nulls_last=True, descending=True),
},
}
filter_class = OrderPositionFilter
permission = 'can_view_orders'
@cached_property
def checkinlist(self):
return get_object_or_404(CheckinList, event=self.request.event, pk=self.kwargs.get("list"))
def get_queryset(self):
cqs = Checkin.objects.filter(
position_id=OuterRef('pk'),
list_id=self.checkinlist.pk
).order_by().values('position_id').annotate(
m=Max('datetime')
).values('m')
qs = OrderPosition.objects.filter(
order__event=self.request.event,
order__status=Order.STATUS_PAID,
subevent=self.checkinlist.subevent
).annotate(
last_checked_in=Subquery(cqs)
).prefetch_related(
Prefetch(
lookup='checkins',
queryset=Checkin.objects.filter(list_id=self.checkinlist.pk)
)
).select_related('item', 'variation', 'order', 'addon_to')
if not self.checkinlist.all_products:
qs = qs.filter(item__in=self.checkinlist.limit_products.values_list('id', flat=True))
return qs

View File

@@ -1,6 +1,15 @@
import pytest
import datetime
import time
from decimal import Decimal
from unittest import mock
from pretix.base.models import CheckinList
import pytest
from django_countries.fields import Country
from pytz import UTC
from pretix.base.models import (
CheckinList, InvoiceAddress, Order, OrderPosition,
)
@pytest.fixture
@@ -18,6 +27,84 @@ def other_item(event):
return event.items.create(name="Budget Ticket", default_price=23)
@pytest.fixture
def order(event, item, other_item, taxrule):
testtime = datetime.datetime(2017, 12, 1, 10, 0, 0, tzinfo=UTC)
with mock.patch('django.utils.timezone.now') as mock_now:
mock_now.return_value = testtime
o = Order.objects.create(
code='FOO', event=event, email='dummy@dummy.test',
status=Order.STATUS_PAID, secret="k24fiuwvu8kxz3y1",
datetime=datetime.datetime(2017, 12, 1, 10, 0, 0, tzinfo=UTC),
expires=datetime.datetime(2017, 12, 10, 10, 0, 0, tzinfo=UTC),
total=46, payment_provider='banktransfer', locale='en'
)
InvoiceAddress.objects.create(order=o, company="Sample company", country=Country('NZ'))
OrderPosition.objects.create(
order=o,
positionid=1,
item=item,
variation=None,
price=Decimal("23"),
attendee_name="Peter",
secret="z3fsn8jyufm5kpk768q69gkbyr5f4h6w"
)
OrderPosition.objects.create(
order=o,
positionid=2,
item=other_item,
variation=None,
price=Decimal("23"),
attendee_name="Michael",
secret="sf4HZG73fU6kwddgjg2QOusFbYZwVKpK"
)
return o
TEST_ORDERPOSITION1_RES = {
"id": 1,
"order": "FOO",
"positionid": 1,
"item": 1,
"variation": None,
"price": "23.00",
"attendee_name": "Peter",
"attendee_email": None,
"voucher": None,
"tax_rate": "0.00",
"tax_value": "0.00",
"tax_rule": None,
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"addon_to": None,
"checkins": [],
"downloads": [],
"answers": [],
"subevent": None
}
TEST_ORDERPOSITION2_RES = {
"id": 2,
"order": "FOO",
"positionid": 2,
"item": 1,
"variation": None,
"price": "23.00",
"attendee_name": "Michael",
"attendee_email": None,
"voucher": None,
"tax_rate": "0.00",
"tax_value": "0.00",
"tax_rule": None,
"secret": "sf4HZG73fU6kwddgjg2QOusFbYZwVKpK",
"addon_to": None,
"checkins": [],
"downloads": [],
"answers": [],
"subevent": None
}
TEST_LIST_RES = {
"name": "Default",
"all_products": False,
@@ -35,6 +122,12 @@ def clist(event, item):
return c
@pytest.fixture
def clist_all(event, item):
c = event.checkin_lists.create(name="Default", all_products=True)
return c
@pytest.mark.django_db
def test_list_list(token_client, organizer, event, clist, item, subevent):
res = dict(TEST_LIST_RES)
@@ -166,3 +259,130 @@ def test_list_update(token_client, organizer, event, clist):
assert resp.status_code == 200
cl = CheckinList.objects.get(pk=resp.data['id'])
assert cl.name == "VIP"
@pytest.mark.django_db
def test_list_all_items_positions(token_client, organizer, event, clist, clist_all, item, other_item, order):
p1 = dict(TEST_ORDERPOSITION1_RES)
p1["id"] = order.positions.first().pk
p1["item"] = item.pk
p2 = dict(TEST_ORDERPOSITION2_RES)
p2["id"] = order.positions.last().pk
p2["item"] = other_item.pk
# All items
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?ordering=positionid'.format(
organizer.slug, event.slug, clist_all.pk
))
assert resp.status_code == 200
assert [p1, p2] == resp.data['results']
# Check-ins on other list ignored
order.positions.first().checkins.create(list=clist)
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?ordering=positionid'.format(
organizer.slug, event.slug, clist_all.pk
))
assert resp.status_code == 200
assert [p1, p2] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?has_checkin=1'.format(
organizer.slug, event.slug, clist_all.pk
))
assert resp.status_code == 200
assert [] == resp.data['results']
# Only checked in
c = order.positions.first().checkins.create(list=clist_all)
p1['checkins'] = [
{
'list': clist_all.pk,
'datetime': c.datetime.isoformat().replace('+00:00', 'Z')
}
]
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?has_checkin=1'.format(
organizer.slug, event.slug, clist_all.pk
))
assert resp.status_code == 200
assert [p1] == resp.data['results']
# Only not checked in
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?has_checkin=0'.format(
organizer.slug, event.slug, clist_all.pk
))
assert resp.status_code == 200
assert [p2] == resp.data['results']
# Order by checkin
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?ordering=-last_checked_in'.format(
organizer.slug, event.slug, clist_all.pk
))
assert resp.status_code == 200
assert [p1, p2] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?ordering=last_checked_in'.format(
organizer.slug, event.slug, clist_all.pk
))
assert resp.status_code == 200
assert [p2, p1] == resp.data['results']
# Order by checkin date
time.sleep(1)
c = order.positions.last().checkins.create(list=clist_all)
p2['checkins'] = [
{
'list': clist_all.pk,
'datetime': c.datetime.isoformat().replace('+00:00', 'Z')
}
]
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?ordering=-last_checked_in'.format(
organizer.slug, event.slug, clist_all.pk
))
assert resp.status_code == 200
assert [p2, p1] == resp.data['results']
# Order by attendee_name
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?ordering=-attendee_name'.format(
organizer.slug, event.slug, clist_all.pk
))
assert resp.status_code == 200
assert [p1, p2] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?ordering=attendee_name'.format(
organizer.slug, event.slug, clist_all.pk
))
assert resp.status_code == 200
assert [p2, p1] == resp.data['results']
# Paid only
order.status = Order.STATUS_PENDING
order.save()
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/'.format(
organizer.slug, event.slug, clist_all.pk
))
assert resp.status_code == 200
assert [] == resp.data['results']
@pytest.mark.django_db
def test_list_limited_items_positions(token_client, organizer, event, clist, item, order):
p1 = dict(TEST_ORDERPOSITION1_RES)
p1["id"] = order.positions.first().pk
p1["item"] = item.pk
# All items
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?ordering=positionid'.format(
organizer.slug, event.slug, clist.pk
))
assert resp.status_code == 200
assert [p1] == resp.data['results']
@pytest.mark.django_db
def test_list_limited_items_position_detail(token_client, organizer, event, clist, item, order):
p1 = dict(TEST_ORDERPOSITION1_RES)
p1["id"] = order.positions.first().pk
p1["item"] = item.pk
# All items
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/'.format(
organizer.slug, event.slug, clist.pk, order.positions.first().pk
))
assert resp.status_code == 200
assert p1 == resp.data