forked from CGM_Public/pretix_original
API: Allow organizer-level access of orders and invoices (#3547)
This commit is contained in:
@@ -614,7 +614,7 @@ class PaymentURLField(serializers.URLField):
|
||||
def to_representation(self, instance: OrderPayment):
|
||||
if instance.state != OrderPayment.PAYMENT_STATE_CREATED:
|
||||
return None
|
||||
return build_absolute_uri(self.context['event'], 'presale:event.order.pay', kwargs={
|
||||
return build_absolute_uri(instance.order.event, 'presale:event.order.pay', kwargs={
|
||||
'order': instance.order.code,
|
||||
'secret': instance.order.secret,
|
||||
'payment': instance.pk,
|
||||
@@ -659,7 +659,7 @@ class OrderRefundSerializer(I18nAwareModelSerializer):
|
||||
|
||||
class OrderURLField(serializers.URLField):
|
||||
def to_representation(self, instance: Order):
|
||||
return build_absolute_uri(self.context['event'], 'presale:event.order', kwargs={
|
||||
return build_absolute_uri(instance.event, 'presale:event.order', kwargs={
|
||||
'order': instance.code,
|
||||
'secret': instance.secret,
|
||||
})
|
||||
@@ -694,6 +694,7 @@ class OrderListSerializer(serializers.ListSerializer):
|
||||
|
||||
|
||||
class OrderSerializer(I18nAwareModelSerializer):
|
||||
event = SlugRelatedField(slug_field='slug', read_only=True)
|
||||
invoice_address = InvoiceAddressSerializer(allow_null=True)
|
||||
positions = OrderPositionSerializer(many=True, read_only=True)
|
||||
fees = OrderFeeSerializer(many=True, read_only=True)
|
||||
@@ -709,7 +710,7 @@ class OrderSerializer(I18nAwareModelSerializer):
|
||||
model = Order
|
||||
list_serializer_class = OrderListSerializer
|
||||
fields = (
|
||||
'code', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
|
||||
'code', 'event', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
|
||||
'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads',
|
||||
'checkin_attention', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
|
||||
'url', 'customer', 'valid_if_pending'
|
||||
@@ -1593,6 +1594,7 @@ class InlineInvoiceLineSerializer(I18nAwareModelSerializer):
|
||||
|
||||
|
||||
class InvoiceSerializer(I18nAwareModelSerializer):
|
||||
event = SlugRelatedField(slug_field='slug', read_only=True)
|
||||
order = serializers.SlugRelatedField(slug_field='code', read_only=True)
|
||||
refers = serializers.SlugRelatedField(slug_field='full_invoice_no', read_only=True)
|
||||
lines = InlineInvoiceLineSerializer(many=True)
|
||||
@@ -1601,7 +1603,7 @@ class InvoiceSerializer(I18nAwareModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Invoice
|
||||
fields = ('order', 'number', 'is_cancellation', 'invoice_from', 'invoice_from_name', 'invoice_from_zipcode',
|
||||
fields = ('event', 'order', 'number', 'is_cancellation', 'invoice_from', 'invoice_from_name', 'invoice_from_zipcode',
|
||||
'invoice_from_city', 'invoice_from_country', 'invoice_from_tax_id', 'invoice_from_vat_id',
|
||||
'invoice_to', 'invoice_to_company', 'invoice_to_name', 'invoice_to_street', 'invoice_to_zipcode',
|
||||
'invoice_to_city', 'invoice_to_state', 'invoice_to_country', 'invoice_to_vat_id', 'invoice_to_beneficiary',
|
||||
|
||||
@@ -61,6 +61,8 @@ orga_router.register(r'membershiptypes', organizer.MembershipTypeViewSet)
|
||||
orga_router.register(r'reusablemedia', media.ReusableMediaViewSet)
|
||||
orga_router.register(r'teams', organizer.TeamViewSet)
|
||||
orga_router.register(r'devices', organizer.DeviceViewSet)
|
||||
orga_router.register(r'orders', order.OrganizerOrderViewSet)
|
||||
orga_router.register(r'invoices', order.InvoiceViewSet)
|
||||
orga_router.register(r'exporters', exporters.OrganizerExportersViewSet, basename='exporters')
|
||||
|
||||
team_router = routers.DefaultRouter()
|
||||
@@ -77,7 +79,7 @@ event_router.register(r'questions', item.QuestionViewSet)
|
||||
event_router.register(r'discounts', discount.DiscountViewSet)
|
||||
event_router.register(r'quotas', item.QuotaViewSet)
|
||||
event_router.register(r'vouchers', voucher.VoucherViewSet)
|
||||
event_router.register(r'orders', order.OrderViewSet)
|
||||
event_router.register(r'orders', order.EventOrderViewSet)
|
||||
event_router.register(r'orderpositions', order.OrderPositionViewSet)
|
||||
event_router.register(r'invoices', order.InvoiceViewSet)
|
||||
event_router.register(r'revokedsecrets', order.RevokedSecretViewSet, basename='revokedsecrets')
|
||||
|
||||
@@ -44,6 +44,7 @@ from rest_framework.exceptions import (
|
||||
APIException, NotFound, PermissionDenied, ValidationError,
|
||||
)
|
||||
from rest_framework.mixins import CreateModelMixin
|
||||
from rest_framework.permissions import SAFE_METHODS
|
||||
from rest_framework.response import Response
|
||||
|
||||
from pretix.api.models import OAuthAccessToken
|
||||
@@ -185,7 +186,7 @@ with scopes_disabled():
|
||||
)
|
||||
|
||||
|
||||
class OrderViewSet(viewsets.ModelViewSet):
|
||||
class OrderViewSetMixin:
|
||||
serializer_class = OrderSerializer
|
||||
queryset = Order.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||
@@ -193,19 +194,12 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
ordering_fields = ('datetime', 'code', 'status', 'last_modified')
|
||||
filterset_class = OrderFilter
|
||||
lookup_field = 'code'
|
||||
permission = 'can_view_orders'
|
||||
write_permission = 'can_change_orders'
|
||||
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['event'] = self.request.event
|
||||
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false') == 'true'
|
||||
ctx['exclude'] = self.request.query_params.getlist('exclude')
|
||||
ctx['include'] = self.request.query_params.getlist('include')
|
||||
return ctx
|
||||
def get_base_queryset(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_queryset(self):
|
||||
qs = self.request.event.orders
|
||||
qs = self.get_base_queryset()
|
||||
if 'fees' not in self.request.GET.getlist('exclude'):
|
||||
if self.request.query_params.get('include_canceled_fees', 'false') == 'true':
|
||||
fqs = OrderFee.all
|
||||
@@ -227,11 +221,12 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
opq = OrderPosition.all
|
||||
else:
|
||||
opq = OrderPosition.objects
|
||||
if request.query_params.get('pdf_data', 'false') == 'true':
|
||||
if request.query_params.get('pdf_data', 'false') == 'true' and getattr(request, 'event', None):
|
||||
prefetch_related_objects([request.organizer], 'meta_properties')
|
||||
prefetch_related_objects(
|
||||
[request.event],
|
||||
Prefetch('meta_values', queryset=EventMetaValue.objects.select_related('property'), to_attr='meta_values_cached'),
|
||||
Prefetch('meta_values', queryset=EventMetaValue.objects.select_related('property'),
|
||||
to_attr='meta_values_cached'),
|
||||
'questions',
|
||||
'item_meta_properties',
|
||||
)
|
||||
@@ -266,13 +261,12 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
)
|
||||
|
||||
def _get_output_provider(self, identifier):
|
||||
responses = register_ticket_outputs.send(self.request.event)
|
||||
for receiver, response in responses:
|
||||
prov = response(self.request.event)
|
||||
if prov.identifier == identifier:
|
||||
return prov
|
||||
raise NotFound('Unknown output provider.')
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['exclude'] = self.request.query_params.getlist('exclude')
|
||||
ctx['include'] = self.request.query_params.getlist('include')
|
||||
ctx['pdf_data'] = False
|
||||
return ctx
|
||||
|
||||
@scopes_disabled() # we are sure enough that get_queryset() is correct, so we save some perforamnce
|
||||
def list(self, request, **kwargs):
|
||||
@@ -289,6 +283,45 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return Response(serializer.data, headers={'X-Page-Generated': date})
|
||||
|
||||
|
||||
class OrganizerOrderViewSet(OrderViewSetMixin, viewsets.ReadOnlyModelViewSet):
|
||||
def get_base_queryset(self):
|
||||
perm = "can_view_orders" if self.request.method in SAFE_METHODS else "can_change_orders"
|
||||
if isinstance(self.request.auth, (TeamAPIToken, Device)):
|
||||
return Order.objects.filter(
|
||||
event__organizer=self.request.organizer,
|
||||
event__in=self.request.auth.get_events_with_permission(perm)
|
||||
)
|
||||
elif self.request.user.is_authenticated:
|
||||
return Order.objects.filter(
|
||||
event__organizer=self.request.organizer,
|
||||
event__in=self.request.user.get_events_with_permission(perm)
|
||||
)
|
||||
else:
|
||||
raise PermissionDenied()
|
||||
|
||||
|
||||
class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
|
||||
permission = 'can_view_orders'
|
||||
write_permission = 'can_change_orders'
|
||||
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['event'] = self.request.event
|
||||
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false') == 'true'
|
||||
return ctx
|
||||
|
||||
def get_base_queryset(self):
|
||||
return self.request.event.orders
|
||||
|
||||
def _get_output_provider(self, identifier):
|
||||
responses = register_ticket_outputs.send(self.request.event)
|
||||
for receiver, response in responses:
|
||||
prov = response(self.request.event)
|
||||
if prov.identifier == identifier:
|
||||
return prov
|
||||
raise NotFound('Unknown output provider.')
|
||||
|
||||
@action(detail=True, url_name='download', url_path='download/(?P<output>[^/]+)')
|
||||
def download(self, request, output, **kwargs):
|
||||
provider = self._get_output_provider(output)
|
||||
@@ -1782,11 +1815,24 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
write_permission = 'can_change_orders'
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.event.invoices.prefetch_related('lines').select_related('order', 'refers').annotate(
|
||||
perm = "can_view_orders" if self.request.method in SAFE_METHODS else "can_change_orders"
|
||||
if getattr(self.request, 'event', None):
|
||||
qs = self.request.event.invoices
|
||||
elif isinstance(self.request.auth, (TeamAPIToken, Device)):
|
||||
qs = Invoice.objects.filter(
|
||||
event__organizer=self.request.organizer,
|
||||
event__in=self.request.auth.get_events_with_permission(perm)
|
||||
)
|
||||
elif self.request.user.is_authenticated:
|
||||
qs = Invoice.objects.filter(
|
||||
event__organizer=self.request.organizer,
|
||||
event__in=self.request.user.get_events_with_permission(perm)
|
||||
)
|
||||
return qs.prefetch_related('lines').select_related('order', 'refers').annotate(
|
||||
nr=Concat('prefix', 'invoice_no')
|
||||
)
|
||||
|
||||
@action(detail=True, )
|
||||
@action(detail=True)
|
||||
def download(self, request, **kwargs):
|
||||
invoice = self.get_object()
|
||||
|
||||
@@ -1805,7 +1851,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
return resp
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
def regenerate(self, request, **kwarts):
|
||||
def regenerate(self, request, **kwargs):
|
||||
inv = self.get_object()
|
||||
if inv.canceled:
|
||||
raise ValidationError('The invoice has already been canceled.')
|
||||
@@ -1815,7 +1861,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
raise PermissionDenied('The invoice file is no longer stored on the server.')
|
||||
elif inv.sent_to_organizer:
|
||||
raise PermissionDenied('The invoice file has already been exported.')
|
||||
elif now().astimezone(self.request.event.timezone).date() - inv.date > datetime.timedelta(days=1):
|
||||
elif now().astimezone(inv.event.timezone).date() - inv.date > datetime.timedelta(days=1):
|
||||
raise PermissionDenied('The invoice file is too old to be regenerated.')
|
||||
else:
|
||||
inv = regenerate_invoice(inv)
|
||||
@@ -1830,7 +1876,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
return Response(status=204)
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
def reissue(self, request, **kwarts):
|
||||
def reissue(self, request, **kwargs):
|
||||
inv = self.get_object()
|
||||
if inv.canceled:
|
||||
raise ValidationError('The invoice has already been canceled.')
|
||||
|
||||
Reference in New Issue
Block a user