API: Allow to return canceled positions and fees

This commit is contained in:
Raphael Michel
2020-01-11 12:57:43 +01:00
parent 35f2b10069
commit ddbe27f351
4 changed files with 107 additions and 19 deletions
+39 -5
View File
@@ -61,9 +61,10 @@ invoice_address object Invoice address
└ vat_id_validated string ``true``, if the VAT ID has been validated against the
EU VAT service and validation was successful. This only
happens in rare cases.
positions list of objects List of non-canceled order positions (see below)
fees list of objects List of non-canceled fees included in the order total
(i.e. payment fees)
positions list of objects List of order positions (see below). By default, only
non-canceled positions are included.
fees list of objects List of fees included in the order total. By default, only
non-canceled fees are included.
├ fee_type string Type of fee (currently ``payment``, ``passbook``,
``other``)
├ value money (string) Fee amount
@@ -72,7 +73,8 @@ fees list of objects List of non-can
can be empty
├ tax_rate decimal (string) VAT rate applied for this fee
├ tax_value money (string) VAT included in this fee
tax_rule integer The ID of the used tax rule (or ``null``)
tax_rule integer The ID of the used tax rule (or ``null``)
└ canceled boolean Whether or not this fee has been canceled.
downloads list of objects List of ticket download options for order-wise ticket
downloading. This might be a multi-page PDF or a ZIP
file of tickets for outputs that do not support
@@ -145,6 +147,10 @@ last_modified datetime Last modificati
The ``invoice_address.state`` and ``url`` attributes have been added. When creating orders through the API,
vouchers are now supported and many fields are now optional.
.. versionchanged:: 3.5
The ``order.fees.canceled`` attribute has been added.
.. _order-position-resource:
@@ -159,6 +165,8 @@ Field Type Description
id integer Internal ID of the order position
order string Order code of the order the position belongs to
positionid integer Number of the position within the order
canceled boolean Whether or not this position has been canceled. Note that
by default, only non-canceled positions are shown.
item integer ID of the purchased item
variation integer ID of the purchased variation (or ``null``)
price money (string) Price of this position
@@ -224,6 +232,10 @@ pdf_data object Data object req
The ``url`` of a ticket ``download`` can now also return a ``text/uri-list`` instead of a file. See
:ref:`order-position-ticket-download` for details.
.. versionchanged:: 3.5
The attribute ``canceled`` has been added.
.. _order-payment-resource:
Order payment resource
@@ -290,6 +302,10 @@ List of all orders
Filtering for emails or order codes is now case-insensitive.
.. versionchanged:: 3.5
The ``include_canceled_positions`` and ``include_canceled_fees`` query parameters have been added.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orders/
Returns a list of all orders within a given event.
@@ -355,6 +371,7 @@ List of all orders
"id": 23442,
"order": "ABC12",
"positionid": 1,
"canceled": false,
"item": 1345,
"variation": null,
"price": "23.00",
@@ -427,6 +444,9 @@ List of all orders
:query boolean testmode: Only return orders with ``testmode`` set to ``true`` or ``false``
:query boolean require_approval: If set to ``true`` or ``false``, only categories with this value for the field
``require_approval`` will be returned.
:query include_canceled_positions: If set to ``true``, the output will contain canceled order positions. Note that this
only affects position-level cancellations, not fully-canceled orders.
:query include_canceled_fees: If set to ``true``, the output will contain cancaled order fees.
:query string email: Only return orders created with the given email address
:query string locale: Only return orders with the given customer locale
:query datetime modified_since: Only return orders that have changed since the given date. Be careful: We only
@@ -444,6 +464,10 @@ List of all orders
Fetching individual orders
--------------------------
.. versionchanged:: 3.5
The ``include_canceled_positions`` and ``include_canceled_fees`` query parameters have been added.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/
Returns information on one order, identified by its order code.
@@ -503,6 +527,7 @@ Fetching individual orders
"id": 23442,
"order": "ABC12",
"positionid": 1,
"canceled": false,
"item": 1345,
"variation": null,
"price": "23.00",
@@ -568,6 +593,9 @@ Fetching individual orders
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param code: The ``code`` field of the order to fetch
:query include_canceled_positions: If set to ``true``, the output will contain canceled order positions. Note that this
only affects position-level cancellations, not fully-canceled orders.
:query include_canceled_fees: If set to ``true``, the output will contain cancaled order fees.
: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.
@@ -1313,8 +1341,9 @@ List of all order positions
The value ``auto_checked_in`` has been added to the ``checkins``-attribute.
.. versionchanged:: 3.5
.. note:: Individually canceled order positions are currently not visible via the API at all.
The ``include_canceled_positions`` and ``include_canceled_fees`` query parameters have been added.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/
@@ -1345,6 +1374,7 @@ List of all order positions
"id": 23442,
"order": "ABC12",
"positionid": 1,
"canceled": false,
"item": 1345,
"variation": null,
"price": "23.00",
@@ -1414,6 +1444,8 @@ List of all order positions
comma-separated IDs.
:query string voucher: Only return positions with a specific voucher.
:query string voucher__code: Only return positions with a specific voucher code.
:query include_canceled_positions: If set to ``true``, the output will contain canceled order positions. Note that this
only affects position-level cancellations, not fully-canceled orders.
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:statuscode 200: no error
@@ -1447,6 +1479,7 @@ Fetching individual positions
"id": 23442,
"order": "ABC12",
"positionid": 1,
"canceled": false,
"item": 1345,
"variation": null,
"price": "23.00",
@@ -1491,6 +1524,7 @@ Fetching individual positions
: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 order position to fetch
:query include_canceled_positions: If set to ``true``, canceled positions may be returned (otherwise, they return 404).
: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.
+2 -2
View File
@@ -204,7 +204,7 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
model = OrderPosition
fields = ('id', 'order', 'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts',
'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins',
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat')
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'canceled')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -284,7 +284,7 @@ class OrderPaymentDateField(serializers.DateField):
class OrderFeeSerializer(I18nAwareModelSerializer):
class Meta:
model = OrderFee
fields = ('fee_type', 'value', 'description', 'internal_type', 'tax_rate', 'tax_value', 'tax_rule')
fields = ('fee_type', 'value', 'description', 'internal_type', 'tax_rate', 'tax_value', 'tax_rule', 'canceled')
class PaymentURLField(serializers.URLField):
+24 -10
View File
@@ -30,8 +30,8 @@ from pretix.api.serializers.order import (
from pretix.base.i18n import language
from pretix.base.models import (
CachedCombinedTicket, CachedTicket, Device, Event, Invoice, InvoiceAddress,
Order, OrderPayment, OrderPosition, OrderRefund, Quota, TeamAPIToken,
generate_position_secret, generate_secret,
Order, OrderFee, OrderPayment, OrderPosition, OrderRefund, Quota,
TeamAPIToken, generate_position_secret, generate_secret,
)
from pretix.base.payment import PaymentException
from pretix.base.services import tickets
@@ -82,20 +82,29 @@ class OrderViewSet(viewsets.ModelViewSet):
return ctx
def get_queryset(self):
if self.request.query_params.get('include_canceled_fees', 'false') == 'true':
fqs = OrderFee.all
else:
fqs = OrderFee.objects
qs = self.request.event.orders.prefetch_related(
'fees', 'payments', 'refunds', 'refunds__payment'
Prefetch('fees', queryset=fqs.all()),
'payments', 'refunds', 'refunds__payment'
).select_related(
'invoice_address'
)
if self.request.query_params.get('include_canceled_positions', 'false') == 'true':
opq = OrderPosition.all
else:
opq = OrderPosition.objects
if self.request.query_params.get('pdf_data', 'false') == 'true':
qs = qs.prefetch_related(
Prefetch(
'positions',
OrderPosition.objects.all().prefetch_related(
opq.all().prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question',
'item__category', 'addon_to', 'seat',
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation', 'seat'))
Prefetch('addons', opq.select_related('item', 'variation', 'seat'))
)
)
)
@@ -103,7 +112,7 @@ class OrderViewSet(viewsets.ModelViewSet):
qs = qs.prefetch_related(
Prefetch(
'positions',
OrderPosition.objects.all().prefetch_related(
opq.all().prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question', 'seat',
)
)
@@ -654,11 +663,16 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewS
}
def get_queryset(self):
qs = OrderPosition.objects.filter(order__event=self.request.event)
if self.request.query_params.get('include_canceled_positions', 'false') == 'true':
qs = OrderPosition.all
else:
qs = OrderPosition.objects
qs = qs.filter(order__event=self.request.event)
if self.request.query_params.get('pdf_data', 'false') == 'true':
qs = qs.prefetch_related(
'checkins', 'answers', 'answers__options', 'answers__question',
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation')),
Prefetch('addons', qs.select_related('item', 'variation')),
Prefetch('order', Order.objects.select_related('invoice_address').prefetch_related(
Prefetch(
'event',
@@ -666,7 +680,7 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewS
),
Prefetch(
'positions',
OrderPosition.objects.prefetch_related(
qs.prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question',
)
)
@@ -676,7 +690,7 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewS
)
else:
qs = qs.prefetch_related(
'checkins', 'answers', 'answers__options', 'answers__question'
'checkins', 'answers', 'answers__options', 'answers__question',
).select_related(
'item', 'order', 'order__event', 'order__event__organizer', 'seat'
)
+42 -2
View File
@@ -114,6 +114,8 @@ def order(event, item, taxrule, question):
)
o.fees.create(fee_type=OrderFee.FEE_TYPE_PAYMENT, value=Decimal('0.25'), tax_rate=Decimal('19.00'),
tax_value=Decimal('0.05'), tax_rule=taxrule)
o.fees.create(fee_type=OrderFee.FEE_TYPE_PAYMENT, value=Decimal('0.25'), tax_rate=Decimal('19.00'),
tax_value=Decimal('0.05'), tax_rule=taxrule, canceled=True)
InvoiceAddress.objects.create(order=o, company="Sample company", country=Country('NZ'),
vat_id="DE123", vat_id_validated=True)
op = OrderPosition.objects.create(
@@ -174,7 +176,8 @@ TEST_ORDERPOSITION_RES = {
"option_identifiers": []
}
],
"subevent": None
"subevent": None,
"canceled": False,
}
TEST_PAYMENTS_RES = [
{
@@ -226,6 +229,7 @@ TEST_ORDER_RES = {
"sales_channel": "web",
"fees": [
{
"canceled": False,
"fee_type": "payment",
"value": "0.25",
"description": "",
@@ -318,6 +322,22 @@ def test_order_list(token_client, organizer, event, order, item, taxrule, questi
))
assert [] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?include_canceled_positions=false'.format(organizer.slug, event.slug))
assert resp.status_code == 200
assert len(resp.data['results'][0]['positions']) == 1
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?include_canceled_positions=true'.format(organizer.slug, event.slug))
assert resp.status_code == 200
assert len(resp.data['results'][0]['positions']) == 2
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?include_canceled_fees=false'.format(organizer.slug, event.slug))
assert resp.status_code == 200
assert len(resp.data['results'][0]['fees']) == 1
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?include_canceled_fees=true'.format(organizer.slug, event.slug))
assert resp.status_code == 200
assert len(resp.data['results'][0]['fees']) == 2
@pytest.mark.django_db
def test_order_detail(token_client, organizer, event, order, item, taxrule, question):
@@ -354,6 +374,16 @@ def test_order_detail(token_client, organizer, event, order, item, taxrule, ques
assert len(resp.data['downloads']) == 1
assert len(resp.data['positions'][0]['downloads']) == 1
assert len(resp.data['positions']) == 1
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/{}/?include_canceled_positions=true'.format(organizer.slug, event.slug, order.code))
assert resp.status_code == 200
assert len(resp.data['positions']) == 2
assert len(resp.data['fees']) == 1
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/{}/?include_canceled_fees=true'.format(organizer.slug, event.slug, order.code))
assert resp.status_code == 200
assert len(resp.data['fees']) == 2
@pytest.mark.django_db
def test_payment_list(token_client, organizer, event, order):
@@ -743,6 +773,13 @@ def test_orderposition_list(token_client, organizer, event, order, item, subeven
subevent.pk + 1))
assert [] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?include_canceled_positions=false'.format(organizer.slug, event.slug))
assert len(resp.data['results']) == 1
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?include_canceled_positions=true'.format(organizer.slug, event.slug))
assert len(resp.data['results']) == 2
@pytest.mark.django_db
def test_orderposition_detail(token_client, organizer, event, order, item, question):
@@ -766,12 +803,15 @@ def test_orderposition_detail(token_client, organizer, event, order, item, quest
@pytest.mark.django_db
def test_orderposition_detail_no_canceled(token_client, organizer, event, order, item, question):
def test_orderposition_detail_canceled(token_client, organizer, event, order, item, question):
with scopes_disabled():
op = order.all_positions.filter(canceled=True).first()
resp = token_client.get('/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format(organizer.slug, event.slug,
op.pk))
assert resp.status_code == 404
resp = token_client.get('/api/v1/organizers/{}/events/{}/orderpositions/{}/?include_canceled_positions=true'.format(
organizer.slug, event.slug, op.pk))
assert resp.status_code == 200
@pytest.mark.django_db