mirror of
https://github.com/pretix/pretix.git
synced 2026-09-14 16:24:43 +00:00
API: Add status view to checkin list resource
This commit is contained in:
@@ -44,6 +44,10 @@ include_pending boolean If ``true``, th
|
|||||||
Endpoints
|
Endpoints
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
.. versionchanged:: 1.15
|
||||||
|
|
||||||
|
The ``../status/`` detail endpoint has been added.
|
||||||
|
|
||||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/
|
||||||
|
|
||||||
Returns a list of all check-in lists within a given event.
|
Returns a list of all check-in lists within a given event.
|
||||||
@@ -128,6 +132,72 @@ 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.
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/(id)/status/
|
||||||
|
|
||||||
|
Returns detailed status information on a check-in list, identified by its ID.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/checkinlists/1/status/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"checkin_count": 17,
|
||||||
|
"position_count": 42,
|
||||||
|
"event": {
|
||||||
|
"name": "Demo Converence",
|
||||||
|
},
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "T-Shirt",
|
||||||
|
"id": 1,
|
||||||
|
"checkin_count": 1,
|
||||||
|
"admission": False,
|
||||||
|
"position_count": 1,
|
||||||
|
"variations": [
|
||||||
|
{
|
||||||
|
"value": "Red",
|
||||||
|
"id": 1,
|
||||||
|
"checkin_count": 1,
|
||||||
|
"position_count": 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Blue",
|
||||||
|
"id": 2,
|
||||||
|
"checkin_count": 4,
|
||||||
|
"position_count": 8
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ticket",
|
||||||
|
"id": 2,
|
||||||
|
"checkin_count": 15,
|
||||||
|
"admission": True,
|
||||||
|
"position_count": 22,
|
||||||
|
"variations": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
:param id: The ``id`` field of the check-in list to fetch
|
||||||
|
:statuscode 200: no error
|
||||||
|
:statuscode 401: Authentication failure
|
||||||
|
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||||
|
|
||||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/
|
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/
|
||||||
|
|
||||||
Creates a new check-in list.
|
Creates a new check-in list.
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
from django.db.models import 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_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.response import Response
|
||||||
|
|
||||||
from pretix.api.serializers.checkin import CheckinListSerializer
|
from pretix.api.serializers.checkin import CheckinListSerializer
|
||||||
from pretix.api.serializers.order import OrderPositionSerializer
|
from pretix.api.serializers.order import OrderPositionSerializer
|
||||||
@@ -66,6 +68,74 @@ class CheckinListViewSet(viewsets.ModelViewSet):
|
|||||||
)
|
)
|
||||||
super().perform_destroy(instance)
|
super().perform_destroy(instance)
|
||||||
|
|
||||||
|
@detail_route(methods=['GET'])
|
||||||
|
def status(self, *args, **kwargs):
|
||||||
|
clist = self.get_object()
|
||||||
|
cqs = Checkin.objects.filter(
|
||||||
|
position__order__event=clist.event,
|
||||||
|
position__order__status__in=[Order.STATUS_PAID] + ([Order.STATUS_PENDING] if clist.include_pending else []),
|
||||||
|
list=clist
|
||||||
|
)
|
||||||
|
pqs = OrderPosition.objects.filter(
|
||||||
|
order__event=clist.event,
|
||||||
|
order__status__in=[Order.STATUS_PAID] + ([Order.STATUS_PENDING] if clist.include_pending else []),
|
||||||
|
subevent=clist.subevent,
|
||||||
|
)
|
||||||
|
if not clist.all_products:
|
||||||
|
pqs = pqs.filter(item__in=clist.limit_products.values_list('id', flat=True))
|
||||||
|
|
||||||
|
ev = clist.subevent or clist.event
|
||||||
|
response = {
|
||||||
|
'event': {
|
||||||
|
'name': str(ev.name),
|
||||||
|
},
|
||||||
|
'checkin_count': cqs.count(),
|
||||||
|
'position_count': 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'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if not clist.all_products:
|
||||||
|
items = clist.limit_products
|
||||||
|
else:
|
||||||
|
items = clist.event.items
|
||||||
|
|
||||||
|
response['items'] = []
|
||||||
|
for item in items.order_by('category__position', 'position', 'pk').prefetch_related('variations'):
|
||||||
|
i = {
|
||||||
|
'id': item.pk,
|
||||||
|
'name': str(item),
|
||||||
|
'admission': item.admission,
|
||||||
|
'checkin_count': c_by_item.get(item.pk, 0),
|
||||||
|
'position_count': op_by_item.get(item.pk, 0),
|
||||||
|
'variations': []
|
||||||
|
}
|
||||||
|
for var in item.variations.all():
|
||||||
|
i['variations'].append({
|
||||||
|
'id': var.pk,
|
||||||
|
'value': str(var),
|
||||||
|
'checkin_count': c_by_variation.get(var.pk, 0),
|
||||||
|
'position_count': op_by_variation.get(var.pk, 0),
|
||||||
|
})
|
||||||
|
response['items'].append(i)
|
||||||
|
|
||||||
|
return Response(response)
|
||||||
|
|
||||||
|
|
||||||
class CheckinOrderPositionFilter(OrderPositionFilter):
|
class CheckinOrderPositionFilter(OrderPositionFilter):
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from django_countries.fields import Country
|
|||||||
from pytz import UTC
|
from pytz import UTC
|
||||||
|
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
CheckinList, InvoiceAddress, Order, OrderPosition,
|
Checkin, CheckinList, InvoiceAddress, Order, OrderPosition,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -83,7 +83,6 @@ TEST_ORDERPOSITION1_RES = {
|
|||||||
"subevent": None
|
"subevent": None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TEST_ORDERPOSITION2_RES = {
|
TEST_ORDERPOSITION2_RES = {
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"order": "FOO",
|
"order": "FOO",
|
||||||
@@ -313,14 +312,16 @@ def test_list_all_items_positions(token_client, organizer, event, clist, clist_a
|
|||||||
assert [p2] == resp.data['results']
|
assert [p2] == resp.data['results']
|
||||||
|
|
||||||
# Order by checkin
|
# Order by checkin
|
||||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?ordering=-last_checked_in'.format(
|
resp = token_client.get(
|
||||||
organizer.slug, event.slug, clist_all.pk
|
'/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?ordering=-last_checked_in'.format(
|
||||||
))
|
organizer.slug, event.slug, clist_all.pk
|
||||||
|
))
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
assert [p1, p2] == resp.data['results']
|
assert [p1, p2] == resp.data['results']
|
||||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?ordering=last_checked_in'.format(
|
resp = token_client.get(
|
||||||
organizer.slug, event.slug, clist_all.pk
|
'/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?ordering=last_checked_in'.format(
|
||||||
))
|
organizer.slug, event.slug, clist_all.pk
|
||||||
|
))
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
assert [p2, p1] == resp.data['results']
|
assert [p2, p1] == resp.data['results']
|
||||||
|
|
||||||
@@ -333,9 +334,10 @@ def test_list_all_items_positions(token_client, organizer, event, clist, clist_a
|
|||||||
'datetime': c.datetime.isoformat().replace('+00:00', 'Z')
|
'datetime': c.datetime.isoformat().replace('+00:00', 'Z')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?ordering=-last_checked_in'.format(
|
resp = token_client.get(
|
||||||
organizer.slug, event.slug, clist_all.pk
|
'/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?ordering=-last_checked_in'.format(
|
||||||
))
|
organizer.slug, event.slug, clist_all.pk
|
||||||
|
))
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
assert [p2, p1] == resp.data['results']
|
assert [p2, p1] == resp.data['results']
|
||||||
|
|
||||||
@@ -387,3 +389,50 @@ def test_list_limited_items_position_detail(token_client, organizer, event, clis
|
|||||||
))
|
))
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
assert p1 == resp.data
|
assert p1 == resp.data
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_status(token_client, organizer, event, clist_all, item, other_item, order):
|
||||||
|
op = order.positions.first()
|
||||||
|
var1 = item.variations.create(value="XS")
|
||||||
|
var2 = item.variations.create(value="S")
|
||||||
|
op.variation = var1
|
||||||
|
op.save()
|
||||||
|
Checkin.objects.create(position=op, list=clist_all)
|
||||||
|
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/status/'.format(
|
||||||
|
organizer.slug, event.slug, clist_all.pk,
|
||||||
|
))
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert resp.data['checkin_count'] == 1
|
||||||
|
assert resp.data['position_count'] == 2
|
||||||
|
assert resp.data['items'] == [
|
||||||
|
{
|
||||||
|
'name': str(item.name),
|
||||||
|
'id': item.pk,
|
||||||
|
'checkin_count': 1,
|
||||||
|
'admission': False,
|
||||||
|
'position_count': 1,
|
||||||
|
'variations': [
|
||||||
|
{
|
||||||
|
'id': var1.pk,
|
||||||
|
'value': 'XS',
|
||||||
|
'checkin_count': 1,
|
||||||
|
'position_count': 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': var2.pk,
|
||||||
|
'value': 'S',
|
||||||
|
'checkin_count': 0,
|
||||||
|
'position_count': 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': other_item.name,
|
||||||
|
'id': other_item.pk,
|
||||||
|
'checkin_count': 0,
|
||||||
|
'admission': False,
|
||||||
|
'position_count': 1,
|
||||||
|
'variations': []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user