Web-based check-in interface (#1985)

This commit is contained in:
Raphael Michel
2021-03-30 09:34:11 +02:00
committed by GitHub
parent b06cded172
commit 92a50cb2d1
56 changed files with 3578 additions and 58 deletions

View File

@@ -46,8 +46,12 @@ class EventPermission(BasePermission):
else:
request.eventpermset = perm_holder.get_event_permission_set(request.organizer, request.event)
if required_permission and required_permission not in request.eventpermset:
return False
if isinstance(required_permission, (list, tuple)):
if not any(p in request.eventpermset for p in required_permission):
return False
else:
if required_permission and required_permission not in request.eventpermset:
return False
elif 'organizer' in request.resolver_match.kwargs:
if not request.organizer or not perm_holder.has_organizer_permission(request.organizer, request=request):
@@ -57,8 +61,12 @@ class EventPermission(BasePermission):
else:
request.orgapermset = perm_holder.get_organizer_permission_set(request.organizer)
if required_permission and required_permission not in request.orgapermset:
return False
if isinstance(required_permission, (list, tuple)):
if not any(p in request.eventpermset for p in required_permission):
return False
else:
if required_permission and required_permission not in request.orgapermset:
return False
if isinstance(request.auth, OAuthAccessToken):
if not request.auth.allow_scopes(['write']) and request.method not in SAFE_METHODS:

View File

@@ -2,6 +2,7 @@ from django.utils.translation import gettext as _
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from pretix.api.serializers.event import SubEventSerializer
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.channels import get_all_sales_channels
from pretix.base.models import CheckinList
@@ -20,6 +21,9 @@ class CheckinListSerializer(I18nAwareModelSerializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'subevent' in self.context['request'].query_params.getlist('expand'):
self.fields['subevent'] = SubEventSerializer(read_only=True)
for exclude_field in self.context['request'].query_params.getlist('exclude'):
p = exclude_field.split('.')
if p[0] in self.fields:
@@ -50,4 +54,6 @@ class CheckinListSerializer(I18nAwareModelSerializer):
if channel not in get_all_sales_channels():
raise ValidationError(_('Unknown sales channel.'))
CheckinList.validate_rules(data.get('rules'))
return data

View File

@@ -13,7 +13,11 @@ from rest_framework.exceptions import ValidationError
from rest_framework.relations import SlugRelatedField
from rest_framework.reverse import reverse
from pretix.api.serializers.event import SubEventSerializer
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.api.serializers.item import (
InlineItemVariationSerializer, ItemSerializer,
)
from pretix.base.channels import get_all_sales_channels
from pretix.base.decimal import round_decimal
from pretix.base.i18n import language
@@ -349,8 +353,9 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'request' in self.context and not self.context['request'].query_params.get('pdf_data', 'false') == 'true':
self.fields.pop('pdf_data')
request = self.context.get('request')
if not request or not self.context['request'].query_params.get('pdf_data', 'false') == 'true' or 'can_view_orders' not in request.eventpermset:
self.fields.pop('pdf_data', None)
def validate(self, data):
if data.get('attendee_name') and data.get('attendee_name_parts'):
@@ -484,6 +489,18 @@ class CheckinListOrderPositionSerializer(OrderPositionSerializer):
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'require_attention',
'order__status')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'subevent' in self.context['request'].query_params.getlist('expand'):
self.fields['subevent'] = SubEventSerializer(read_only=True)
if 'item' in self.context['request'].query_params.getlist('expand'):
self.fields['item'] = ItemSerializer(read_only=True)
if 'variation' in self.context['request'].query_params.getlist('expand'):
self.fields['variation'] = InlineItemVariationSerializer(read_only=True)
class OrderPaymentTypeField(serializers.Field):
# TODO: Remove after pretix 2.2
@@ -584,7 +601,7 @@ class OrderSerializer(I18nAwareModelSerializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.context['request'].query_params.get('pdf_data', 'false') == 'true':
self.fields['positions'].child.fields.pop('pdf_data')
self.fields['positions'].child.fields.pop('pdf_data', None)
for exclude_field in self.context['request'].query_params.getlist('exclude'):
p = exclude_field.split('.')

View File

@@ -95,7 +95,7 @@ class TeamSerializer(serializers.ModelSerializer):
'id', 'name', 'all_events', 'limit_events', 'can_create_events', 'can_change_teams',
'can_change_organizer_settings', 'can_manage_gift_cards', 'can_change_event_settings',
'can_change_items', 'can_view_orders', 'can_change_orders', 'can_view_vouchers',
'can_change_vouchers'
'can_change_vouchers', 'can_checkin_orders'
)
def validate(self, data):

View File

@@ -25,13 +25,14 @@ from pretix.base.models import (
CachedFile, Checkin, CheckinList, Event, Order, OrderPosition, Question,
)
from pretix.base.services.checkin import (
CheckInError, RequiredQuestionsError, perform_checkin,
CheckInError, RequiredQuestionsError, SQLLogic, perform_checkin,
)
from pretix.helpers.database import FixedOrderBy
with scopes_disabled():
class CheckinListFilter(FilterSet):
subevent_match = django_filters.NumberFilter(method='subevent_match_qs')
ends_after = django_filters.rest_framework.IsoDateTimeFilter(method='ends_after_qs')
class Meta:
model = CheckinList
@@ -42,19 +43,35 @@ with scopes_disabled():
Q(subevent_id=value) | Q(subevent_id__isnull=True)
)
def ends_after_qs(self, queryset, name, value):
expr = (
Q(subevent__isnull=True) |
Q(
Q(Q(subevent__date_to__isnull=True) & Q(subevent__date_from__gte=value))
| Q(Q(subevent__date_to__isnull=False) & Q(subevent__date_to__gte=value))
)
)
return queryset.filter(expr)
class CheckinListViewSet(viewsets.ModelViewSet):
serializer_class = CheckinListSerializer
queryset = CheckinList.objects.none()
filter_backends = (DjangoFilterBackend,)
filterset_class = CheckinListFilter
permission = 'can_view_orders'
permission = ('can_view_orders', 'can_checkin_orders',)
write_permission = 'can_change_event_settings'
def get_queryset(self):
qs = self.request.event.checkin_lists.prefetch_related(
'limit_products',
)
if 'subevent' in self.request.query_params.getlist('expand'):
qs = qs.prefetch_related(
'subevent', 'subevent__event', 'subevent__subeventitem_set', 'subevent__subeventitemvariation_set',
'subevent__seat_category_mappings', 'subevent__meta_values'
)
return qs
def perform_create(self, serializer):
@@ -155,15 +172,37 @@ class CheckinListViewSet(viewsets.ModelViewSet):
with scopes_disabled():
class CheckinOrderPositionFilter(OrderPositionFilter):
check_rules = django_filters.rest_framework.BooleanFilter(method='check_rules_qs')
# check_rules is currently undocumented on purpose, let's get a feel for the performance impact first
def __init__(self, *args, **kwargs):
self.checkinlist = kwargs.pop('checkinlist')
super().__init__(*args, **kwargs)
def has_checkin_qs(self, queryset, name, value):
return queryset.filter(last_checked_in__isnull=not value)
def check_rules_qs(self, queryset, name, value):
if not self.checkinlist.rules:
return queryset
return queryset.filter(SQLLogic(self.checkinlist).apply(self.checkinlist.rules))
class ExtendedBackend(DjangoFilterBackend):
def get_filterset_kwargs(self, request, queryset, view):
kwargs = super().get_filterset_kwargs(request, queryset, view)
# merge filterset kwargs provided by view class
if hasattr(view, 'get_filterset_kwargs'):
kwargs.update(view.get_filterset_kwargs())
return kwargs
class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = CheckinListOrderPositionSerializer
queryset = OrderPosition.all.none()
filter_backends = (DjangoFilterBackend, RichOrderingFilter)
filter_backends = (ExtendedBackend, RichOrderingFilter)
ordering = ('attendee_name_cached', 'positionid')
ordering_fields = (
'order__code', 'order__datetime', 'positionid', 'attendee_name',
@@ -187,8 +226,13 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
}
filterset_class = CheckinOrderPositionFilter
permission = 'can_view_orders'
write_permission = 'can_change_orders'
permission = ('can_view_orders', 'can_checkin_orders')
write_permission = ('can_change_orders', 'can_checkin_orders')
def get_filterset_kwargs(self):
return {
'checkinlist': self.checkinlist,
}
@cached_property
def checkinlist(self):
@@ -209,7 +253,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
order__event=self.request.event,
).annotate(
last_checked_in=Subquery(cqs)
)
).prefetch_related('order__event', 'order__event__organizer')
if self.checkinlist.subevent:
qs = qs.filter(
subevent=self.checkinlist.subevent
@@ -255,6 +299,22 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
if not self.checkinlist.all_products and not ignore_products:
qs = qs.filter(item__in=self.checkinlist.limit_products.values_list('id', flat=True))
if 'subevent' in self.request.query_params.getlist('expand'):
qs = qs.prefetch_related(
'subevent', 'subevent__event', 'subevent__subeventitem_set', 'subevent__subeventitemvariation_set',
'subevent__seat_category_mappings', 'subevent__meta_values'
)
if 'item' in self.request.query_params.getlist('expand'):
qs = qs.prefetch_related('item', 'item__addons', 'item__bundles', 'item__meta_values', 'item__variations').select_related('item__tax_rule')
if 'variation' in self.request.query_params.getlist('expand'):
qs = qs.prefetch_related('variation')
if 'pk' not in self.request.resolver_match.kwargs and 'can_view_orders' not in self.request.eventpermset \
and len(self.request.query_params.get('search', '')) < 3:
qs = qs.none()
return qs
@action(detail=False, methods=['POST'], url_name='redeem', url_path='(?P<pk>.*)/redeem')

View File

@@ -259,7 +259,7 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
qs = filter_qs_by_attr(qs, self.request)
return qs.prefetch_related(
'subeventitem_set', 'subeventitemvariation_set', 'seat_category_mappings'
'subeventitem_set', 'subeventitemvariation_set', 'seat_category_mappings', 'meta_values'
)
def list(self, request, **kwargs):

View File

@@ -49,7 +49,9 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
write_permission = 'can_change_items'
def get_queryset(self):
return self.request.event.items.select_related('tax_rule').prefetch_related('variations', 'addons', 'bundles').all()
return self.request.event.items.select_related('tax_rule').prefetch_related(
'variations', 'addons', 'bundles', 'meta_values'
).all()
def perform_create(self, serializer):
serializer.save(event=self.request.event)