API: Expose history of check-ins (Z#23206049)

This commit is contained in:
Raphael Michel
2025-10-10 17:47:05 +02:00
committed by Raphael Michel
parent 7d5df2b69e
commit 9a69b76880
6 changed files with 215 additions and 3 deletions

View File

@@ -325,7 +325,7 @@ class AnswerSerializer(I18nAwareModelSerializer):
return data
class CheckinSerializer(I18nAwareModelSerializer):
class InlineCheckinSerializer(I18nAwareModelSerializer):
device_id = serializers.SlugRelatedField(
source='device',
slug_field='device_id',
@@ -337,6 +337,21 @@ class CheckinSerializer(I18nAwareModelSerializer):
fields = ('id', 'datetime', 'list', 'auto_checked_in', 'gate', 'device', 'device_id', 'type')
class CheckinSerializer(I18nAwareModelSerializer):
device_id = serializers.SlugRelatedField(
source='device',
slug_field='device_id',
read_only=True,
)
class Meta:
model = Checkin
fields = (
'id', 'successful', 'error_reason', 'error_explanation', 'position', 'datetime', 'list', 'created',
'auto_checked_in', 'gate', 'device', 'device_id', 'type'
)
class PrintLogSerializer(serializers.ModelSerializer):
device_id = serializers.SlugRelatedField(
source='device',
@@ -560,7 +575,7 @@ class OrderPositionPluginDataField(serializers.Field):
class OrderPositionSerializer(I18nAwareModelSerializer):
checkins = CheckinSerializer(many=True, read_only=True)
checkins = InlineCheckinSerializer(many=True, read_only=True)
print_logs = PrintLogSerializer(many=True, read_only=True)
answers = AnswerSerializer(many=True)
downloads = PositionDownloadsField(source='*', read_only=True)

View File

@@ -92,6 +92,7 @@ event_router.register(r'taxrules', event.TaxRuleViewSet)
event_router.register(r'seats', event.SeatViewSet)
event_router.register(r'waitinglistentries', waitinglist.WaitingListViewSet)
event_router.register(r'checkinlists', checkin.CheckinListViewSet)
event_router.register(r'checkins', checkin.CheckinViewSet)
event_router.register(r'cartpositions', cart.CartPositionViewSet)
event_router.register(r'scheduled_exports', exporters.ScheduledEventExportViewSet)
event_router.register(r'exporters', exporters.EventExportersViewSet, basename='exporters')

View File

@@ -56,7 +56,8 @@ from pretix.api.serializers.checkin import (
)
from pretix.api.serializers.item import QuestionSerializer
from pretix.api.serializers.order import (
CheckinListOrderPositionSerializer, FailedCheckinSerializer,
CheckinListOrderPositionSerializer, CheckinSerializer,
FailedCheckinSerializer,
)
from pretix.api.views import RichOrderingFilter
from pretix.api.views.order import OrderPositionFilter
@@ -96,6 +97,16 @@ with scopes_disabled():
)
return queryset.filter(expr)
class CheckinFilter(FilterSet):
created_since = django_filters.IsoDateTimeFilter(field_name='created', lookup_expr='gte')
created_before = django_filters.IsoDateTimeFilter(field_name='created', lookup_expr='lt')
datetime_since = django_filters.IsoDateTimeFilter(field_name='datetime', lookup_expr='gte')
datetime_before = django_filters.IsoDateTimeFilter(field_name='datetime', lookup_expr='lt')
class Meta:
model = Checkin
fields = ['successful', 'error_reason', 'list', 'type', 'gate', 'device', 'auto_checked_in']
class CheckinListViewSet(viewsets.ModelViewSet):
serializer_class = CheckinListSerializer
@@ -1080,3 +1091,25 @@ class CheckinRPCAnnulView(views.APIView):
checkin_annulled.send(ci.position.order.event, checkin=ci)
return Response({"status": "ok"}, status=status.HTTP_200_OK)
class CheckinViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = CheckinSerializer
queryset = Checkin.all.none()
filter_backends = (DjangoFilterBackend, RichOrderingFilter)
filterset_class = CheckinFilter
ordering = ('created', 'id')
ordering_fields = ('created', 'datetime', 'id',)
permission = 'can_view_orders'
def get_queryset(self):
qs = Checkin.all.filter().select_related(
"position",
"device",
)
return qs
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['event'] = self.request.event
return ctx

View File

@@ -236,6 +236,21 @@ TEST_LIST_RES = {
"rules": {}
}
TEST_HISTORY_RES = {
"successful": True,
"error_reason": None,
"error_explanation": None,
"position": 1234,
"datetime": "2017-12-25T12:45:23Z",
"created": "2017-12-25T12:45:23Z",
"list": 2,
"auto_checked_in": False,
"gate": None,
"device": None,
"device_id": None,
"type": "entry",
}
@pytest.fixture
def clist(event, item):
@@ -1366,3 +1381,57 @@ def test_expand(token_client, organizer, event, clist, clist_all, item, other_it
))
assert resp.status_code == 200
assert 'value' in resp.data['results'][0]['variation']
@pytest.mark.django_db
def test_history(token_client, organizer, event, clist, order):
with scopes_disabled():
ci = order.positions.first().checkins.create(list=clist, type=Checkin.TYPE_ENTRY, datetime=now())
res = dict(TEST_HISTORY_RES)
res["id"] = ci.pk
res["datetime"] = ci.datetime.isoformat().replace('+00:00', 'Z')
res["created"] = ci.created.isoformat().replace('+00:00', 'Z')
res["list"] = clist.pk
res["position"] = ci.position_id
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkins/'.format(
organizer.slug, event.slug,
))
assert resp.status_code == 200
assert res == resp.data['results'][0]
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkins/?auto_checked_in=false'.format(
organizer.slug, event.slug,
))
assert len(resp.data['results']) == 1
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkins/?auto_checked_in=true'.format(
organizer.slug, event.slug,
))
assert len(resp.data['results']) == 0
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkins/?successful=true'.format(
organizer.slug, event.slug,
))
assert len(resp.data['results']) == 1
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkins/?successful=false'.format(
organizer.slug, event.slug,
))
assert len(resp.data['results']) == 0
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkins/?type=entry'.format(
organizer.slug, event.slug,
))
assert len(resp.data['results']) == 1
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkins/?type=exit'.format(
organizer.slug, event.slug,
))
assert len(resp.data['results']) == 0
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkins/?created_before=2099-01-01T00:00:00Z'.format(
organizer.slug, event.slug,
))
assert len(resp.data['results']) == 1
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkins/?created_before=2017-01-01T00:00:00Z'.format(
organizer.slug, event.slug,
))
assert len(resp.data['results']) == 0

View File

@@ -54,6 +54,7 @@ event_urls = [
(None, 'taxrules/'),
('can_view_orders', 'waitinglistentries/'),
('can_view_orders', 'checkinlists/'),
('can_view_orders', 'checkins/'),
(None, 'seats/'),
]
@@ -176,6 +177,8 @@ event_permission_sub_urls = [
('post', 'can_change_orders', 'orders/ABC12/refunds/1/done/', 404),
('get', 'can_view_orders', 'checkinlists/', 200),
('post', 'can_change_orders', 'checkinlists/1/failed_checkins/', 400),
('get', 'can_view_orders', 'checkins/', 200),
('get', 'can_view_orders', 'checkins/1/', 404),
('post', 'can_change_event_settings', 'checkinlists/', 400),
('put', 'can_change_event_settings', 'checkinlists/1/', 404),
('patch', 'can_change_event_settings', 'checkinlists/1/', 404),