diff --git a/src/pretix/api/serializers/media.py b/src/pretix/api/serializers/media.py index d759f14684..c372a979b3 100644 --- a/src/pretix/api/serializers/media.py +++ b/src/pretix/api/serializers/media.py @@ -81,11 +81,21 @@ class ReusableMediaSerializer(I18nAwareModelSerializer): queryset=self.context['organizer'].issued_gift_cards.all() ) - if 'linked_orderposition' in self.context['request'].query_params.getlist('expand'): - # Permission Check performed in to_representation - self.fields['linked_orderposition'] = NestedOrderPositionSerializer(read_only=True) + # keep linked_orderposition (singular) for backwards compatibility, will be overwritten in self.validate + self.fields['linked_orderposition'] = serializers.PrimaryKeyRelatedField( + required=False, + allow_null=True, + queryset=OrderPosition.all.filter(order__event__organizer=self.context['organizer']), + ) + + if 'linked_orderposition' in self.context['request'].query_params.getlist('expand') or 'linked_orderpositions' in self.context['request'].query_params.getlist('expand'): + self.fields['linked_orderpositions'] = NestedOrderPositionSerializer( + many=True, + read_only=True + ) else: - self.fields['linked_orderposition'] = serializers.PrimaryKeyRelatedField( + self.fields['linked_orderpositions'] = serializers.PrimaryKeyRelatedField( + many=True, required=False, allow_null=True, queryset=OrderPosition.all.filter(order__event__organizer=self.context['organizer']), @@ -106,6 +116,21 @@ class ReusableMediaSerializer(I18nAwareModelSerializer): def validate(self, data): data = super().validate(data) + if 'linked_orderposition' in data: + linked_orderposition = data['linked_orderposition'] + # backwards-compatibility + if 'linked_orderpositions' in data: + raise ValidationError({ + 'linked_orderposition': _('You cannot use linked_orderposition and linked_orderpositions at the same time.') + }) + if self.instance and self.instance.linked_orderpositions.count() > 1: + raise ValidationError({ + 'linked_orderposition': _('There are more than one linked_orderposition. You need to use linked_orderpositions.') + }) + + data['linked_orderpositions'] = [linked_orderposition] if linked_orderposition else [] + del data['linked_orderposition'] + if 'type' in data and 'identifier' in data: qs = self.context['organizer'].reusable_media.filter( identifier=data['identifier'], type=data['type'] @@ -121,14 +146,28 @@ class ReusableMediaSerializer(I18nAwareModelSerializer): def to_representation(self, instance): r = super().to_representation(instance) request = self.context.get('request') + + ops = r.get('linked_orderpositions', []) # late permission evaluations for checks that depend on the actual linked events expand_nested = self.context['request'].query_params.getlist('expand') perm_holder = request.auth if isinstance(request.auth, (Device, TeamAPIToken)) else request.user - if 'linked_orderposition' in expand_nested: - if instance.linked_orderposition is not None: - event = instance.linked_orderposition.order.event + if ops and 'linked_orderposition' in expand_nested or 'linked_orderpositions' in expand_nested: + ops_noperm = [] + for lop in instance.linked_orderpositions.all().prefetch_related("order__event", "order__event__organizer"): + event = lop.order.event if not perm_holder.has_event_permission(event.organizer, event, 'event.orders:read', request): - r['linked_orderposition'] = {'id': instance.linked_orderposition.id} + ops_noperm.append(lop.id) + if ops_noperm: + ops = [ + {'id': op.id} if op.id in ops_noperm + else op + for op in ops + ] + r['linked_orderpositions'] = ops + + # add linked_orderposition (singular) for backwards compatibility + if len(ops) < 2: + r['linked_orderposition'] = ops[0] if ops else None if 'linked_giftcard.owner_ticket' in expand_nested: gc = instance.linked_giftcard @@ -151,7 +190,7 @@ class ReusableMediaSerializer(I18nAwareModelSerializer): 'active', 'expires', 'customer', - 'linked_orderposition', + 'linked_orderpositions', 'linked_giftcard', 'info', 'notes', diff --git a/src/pretix/api/views/media.py b/src/pretix/api/views/media.py index 7b3be781f6..30bc030075 100644 --- a/src/pretix/api/views/media.py +++ b/src/pretix/api/views/media.py @@ -53,10 +53,12 @@ with scopes_disabled(): customer = django_filters.CharFilter(field_name='customer__identifier') updated_since = django_filters.IsoDateTimeFilter(field_name='updated', lookup_expr='gte') created_since = django_filters.IsoDateTimeFilter(field_name='created', lookup_expr='gte') + # backwards-compatible + linked_orderposition = django_filters.NumberFilter(field_name='linked_orderpositions__id') class Meta: model = ReusableMedium - fields = ['identifier', 'type', 'active', 'customer', 'linked_orderposition', 'linked_giftcard'] + fields = ['identifier', 'type', 'active', 'customer', 'linked_orderpositions', 'linked_giftcard'] class ReusableMediaViewSet(viewsets.ModelViewSet): @@ -75,7 +77,7 @@ class ReusableMediaViewSet(viewsets.ModelViewSet): ).order_by().values('card').annotate(s=Sum('value')).values('s') return self.request.organizer.reusable_media.prefetch_related( Prefetch( - 'linked_orderposition', + 'linked_orderpositions', queryset=OrderPosition.objects.select_related( 'order', 'order__event', 'order__event__organizer', 'seat', ).prefetch_related( @@ -157,7 +159,7 @@ class ReusableMediaViewSet(viewsets.ModelViewSet): type=s.validated_data["type"], identifier=s.validated_data["identifier"], ) - m.linked_orderposition = None # not relevant for cross-organizer + m.linked_orderpositions = None # not relevant for cross-organizer m.customer = None # not relevant for cross-organizer s = self.get_serializer(m) return Response({"result": s.data})