From 6f30ecb365d262adfdddd06614031e10aca17ddd Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sun, 29 Apr 2018 14:28:32 +0200 Subject: [PATCH] Refs #654 -- Writable API methods for waiting list entries (#885) * Refs #654 -- Writable API methods for waiting list entries * Update test_waitinglist.py --- doc/api/resources/vouchers.rst | 2 +- doc/api/resources/waitinglist.rst | 164 ++++++++++++ src/pretix/api/serializers/waitinglist.py | 26 ++ src/pretix/api/views/waitinglist.py | 55 +++- src/pretix/base/models/waitinglist.py | 42 ++- src/pretix/control/logdisplay.py | 3 + src/pretix/control/views/waitinglist.py | 2 +- src/tests/api/test_permissions.py | 6 + src/tests/api/test_waitinglist.py | 313 ++++++++++++++++++++++ 9 files changed, 599 insertions(+), 14 deletions(-) diff --git a/doc/api/resources/vouchers.rst b/doc/api/resources/vouchers.rst index 411f99f9f6..44a673ea05 100644 --- a/doc/api/resources/vouchers.rst +++ b/doc/api/resources/vouchers.rst @@ -251,7 +251,7 @@ Endpoints { "price_mode": "set", - "value": "24.00", + "value": "24.00" } **Example response**: diff --git a/doc/api/resources/waitinglist.rst b/doc/api/resources/waitinglist.rst index 799e53b036..b4924981a0 100644 --- a/doc/api/resources/waitinglist.rst +++ b/doc/api/resources/waitinglist.rst @@ -27,6 +27,12 @@ subevent integer ID of the date ===================================== ========================== ======================================================= +.. versionchanged:: 1.15 + + The write operations ``POST``, ``PATCH``, ``PUT``, and ``DELETE`` have been added as well as a method to send out + vouchers. + + Endpoints --------- @@ -121,3 +127,161 @@ Endpoints :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. + +.. http:post:: /api/v1/organizers/(organizer)/events/(event)/waitinglistentries/ + + Create a new entry. + + **Example request**: + + .. sourcecode:: http + + POST /api/v1/organizers/bigevents/events/sampleconf/waitinglistentries/ HTTP/1.1 + Host: pretix.eu + Accept: application/json, text/javascript + Content-Type: application/json + Content-Length: 408 + + { + "email": "waiting@example.org", + "item": 3, + "variation": null, + "locale": "de", + "subevent": null + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 201 Created + Vary: Accept + Content-Type: application/json + + { + "id": 1, + "created": "2017-12-01T10:00:00Z", + "email": "waiting@example.org", + "voucher": null, + "item": 3, + "variation": null, + "locale": "de", + "subevent": null + } + + :param organizer: The ``slug`` field of the organizer to create an entry for + :param event: The ``slug`` field of the event to create an entry for + :statuscode 201: no error + :statuscode 400: The voucher could not be created due to invalid submitted data. + :statuscode 401: Authentication failure + :statuscode 403: The requested organizer/event does not exist **or** you have no permission to create this + resource **or** entries cannot be created for this item at this time. + +.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/waitinglistentries/(id)/ + + Update an entry. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide all fields of + the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you + want to change. + + You can change all fields of the resource except the ``id``, ``voucher`` and ``created`` fields. You can only change + an entry as long as no ``voucher`` is set. + + **Example request**: + + .. sourcecode:: http + + PATCH /api/v1/organizers/bigevents/events/sampleconf/waitinglistentries/1/ HTTP/1.1 + Host: pretix.eu + Accept: application/json, text/javascript + Content-Type: application/json + Content-Length: 408 + + { + "item": 4 + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Vary: Accept + Content-Type: application/json + + { + "id": 1, + "created": "2017-12-01T10:00:00Z", + "email": "waiting@example.org", + "voucher": null, + "item": 4, + "variation": null, + "locale": "de", + "subevent": null + } + + :param organizer: The ``slug`` field of the organizer to modify + :param event: The ``slug`` field of the event to modify + :param id: The ``id`` field of the entry to modify + :statuscode 200: no error + :statuscode 400: The entry could not be modified due to invalid submitted data + :statuscode 401: Authentication failure + :statuscode 403: The requested organizer/event does not exist **or** you have no permission to create this + resource **or** entries cannot be created for this item at this time **or** this entry already + has a voucher assigned + +.. http:post:: /api/v1/organizers/(organizer)/events/(event)/waitinglistentries/(id)/send_voucher/ + + Manually sends a voucher to someone on the waiting list + + **Example request**: + + .. sourcecode:: http + + POST /api/v1/organizers/bigevents/events/sampleconf/waitinglistentries/1/send_voucher/ HTTP/1.1 + Host: pretix.eu + Accept: application/json, text/javascript + Content-Type: application/json + Content-Length: 0 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 No Content + Vary: Accept + + :param organizer: The ``slug`` field of the organizer to modify + :param event: The ``slug`` field of the event to modify + :param id: The ``id`` field of the entry to modify + :statuscode 204: no error + :statuscode 400: The voucher could not be sent out, see body for details (e.g. voucher has already been sent or + item is not available). + :statuscode 401: Authentication failure + :statuscode 403: The requested organizer/event does not exist **or** you have no permission to do this + +.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/waitinglistentries/(id)/ + + Delete an entry. Note that you cannot delete an entry once it is assigned a voucher. + + **Example request**: + + .. sourcecode:: http + + DELETE /api/v1/organizers/bigevents/events/sampleconf/waitinglistentries/1/ HTTP/1.1 + Host: pretix.eu + Accept: application/json, text/javascript + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 No Content + Vary: Accept + + :param organizer: The ``slug`` field of the organizer to modify + :param event: The ``slug`` field of the event to modify + :param id: The ``id`` field of the entry to delete + :statuscode 204: no error + :statuscode 401: Authentication failure + :statuscode 403: The requested organizer/event does not exist **or** you have no permission to delete this + resource **or** this entry already has a voucher assigned. diff --git a/src/pretix/api/serializers/waitinglist.py b/src/pretix/api/serializers/waitinglist.py index b799cc5eb7..ed0ca9dd04 100644 --- a/src/pretix/api/serializers/waitinglist.py +++ b/src/pretix/api/serializers/waitinglist.py @@ -1,3 +1,5 @@ +from rest_framework.exceptions import ValidationError + from pretix.api.serializers.i18n import I18nAwareModelSerializer from pretix.base.models import WaitingListEntry @@ -7,3 +9,27 @@ class WaitingListSerializer(I18nAwareModelSerializer): class Meta: model = WaitingListEntry fields = ('id', 'created', 'email', 'voucher', 'item', 'variation', 'locale', 'subevent') + read_only_fields = ('id', 'created', 'voucher') + + def validate(self, data): + data = super().validate(data) + event = self.context['event'] + + full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {} + full_data.update(data) + + WaitingListEntry.clean_duplicate(full_data.get('email'), full_data.get('item'), full_data.get('variation'), + full_data.get('subevent'), self.instance.pk if self.instance else None) + WaitingListEntry.clean_itemvar(event, full_data.get('item'), full_data.get('variation')) + WaitingListEntry.clean_subevent(event, full_data.get('subevent')) + + if 'item' in data or 'variation' in data: + availability = ( + full_data.get('variation').check_quotas(count_waitinglist=True, subevent=full_data.get('subevent')) + if full_data.get('variation') + else full_data.get('item').check_quotas(count_waitinglist=True, subevent=full_data.get('subevent')) + ) + if availability[0] == 100: + raise ValidationError("This product is currently available.") + + return data diff --git a/src/pretix/api/views/waitinglist.py b/src/pretix/api/views/waitinglist.py index 44e850310d..8ab436bfd0 100644 --- a/src/pretix/api/views/waitinglist.py +++ b/src/pretix/api/views/waitinglist.py @@ -1,10 +1,14 @@ import django_filters from django_filters.rest_framework import DjangoFilterBackend, FilterSet from rest_framework import viewsets +from rest_framework.decorators import detail_route +from rest_framework.exceptions import PermissionDenied, ValidationError from rest_framework.filters import OrderingFilter +from rest_framework.response import Response from pretix.api.serializers.waitinglist import WaitingListSerializer -from pretix.base.models import WaitingListEntry +from pretix.base.models import TeamAPIToken, WaitingListEntry +from pretix.base.models.waitinglist import WaitingListException class WaitingListFilter(FilterSet): @@ -18,7 +22,7 @@ class WaitingListFilter(FilterSet): fields = ['item', 'variation', 'email', 'locale', 'has_voucher', 'subevent'] -class WaitingListViewSet(viewsets.ReadOnlyModelViewSet): +class WaitingListViewSet(viewsets.ModelViewSet): serializer_class = WaitingListSerializer queryset = WaitingListEntry.objects.none() filter_backends = (DjangoFilterBackend, OrderingFilter) @@ -26,6 +30,53 @@ class WaitingListViewSet(viewsets.ReadOnlyModelViewSet): ordering_fields = ('id', 'created', 'email', 'item') filter_class = WaitingListFilter permission = 'can_view_orders' + write_permission = 'can_change_orders' def get_queryset(self): return self.request.event.waitinglistentries.all() + + def get_serializer_context(self): + ctx = super().get_serializer_context() + ctx['event'] = self.request.event + return ctx + + def perform_create(self, serializer): + serializer.save(event=self.request.event) + serializer.instance.log_action( + 'pretix.event.orders.waitinglist.added', + user=self.request.user, + api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None), + ) + + def perform_update(self, serializer): + if serializer.instance.voucher: + raise PermissionDenied('This entry can not be changed as it has already been assigned a voucher.') + serializer.save(event=self.request.event) + serializer.instance.log_action( + 'pretix.event.orders.waitinglist.changed', + user=self.request.user, + api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None), + ) + + def perform_destroy(self, instance): + if instance.voucher: + raise PermissionDenied('This entry can not be deleted as it has already been assigned a voucher.') + + instance.log_action( + 'pretix.event.orders.waitinglist.deleted', + user=self.request.user, + api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None), + ) + super().perform_destroy(instance) + + @detail_route(methods=['POST']) + def send_voucher(self, *args, **kwargs): + try: + self.get_object().send_voucher( + user=self.request.user, + api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None), + ) + except WaitingListException as e: + raise ValidationError(str(e)) + else: + return Response(status=204) diff --git a/src/pretix/base/models/waitinglist.py b/src/pretix/base/models/waitinglist.py index ca582a3511..9143bd82c9 100644 --- a/src/pretix/base/models/waitinglist.py +++ b/src/pretix/base/models/waitinglist.py @@ -73,15 +73,11 @@ class WaitingListEntry(LoggedModel): return '%s waits for %s' % (str(self.email), str(self.item)) def clean(self): - if WaitingListEntry.objects.filter( - item=self.item, variation=self.variation, email=self.email, voucher__isnull=True - ).exclude(pk=self.pk).exists(): - raise ValidationError(_('You are already on this waiting list! We will notify ' - 'you as soon as we have a ticket available for you.')) - if not self.variation and self.item.has_variations: - raise ValidationError(_('Please select a specific variation of this product.')) + WaitingListEntry.clean_duplicate(self.email, self.item, self.variation, self.subevent, self.pk) + WaitingListEntry.clean_itemvar(self.event, self.item, self.variation) + WaitingListEntry.clean_subevent(self.event, self.subevent) - def send_voucher(self, quota_cache=None, user=None): + def send_voucher(self, quota_cache=None, user=None, api_token=None): availability = ( self.variation.check_quotas(count_waitinglist=False, subevent=self.subevent, _cache=quota_cache) if self.variation @@ -116,8 +112,8 @@ class WaitingListEntry(LoggedModel): 'email': self.email, 'waitinglistentry': self.pk, 'subevent': self.subevent.pk if self.subevent else None, - }, user=user) - self.log_action('pretix.waitinglist.voucher', user=user) + }, user=user, api_token=api_token) + self.log_action('pretix.waitinglist.voucher', user=user, api_token=api_token) self.voucher = v self.save() @@ -136,3 +132,29 @@ class WaitingListEntry(LoggedModel): self.event, locale=self.locale ) + + @staticmethod + def clean_itemvar(event, item, variation): + if event != item.event: + raise ValidationError(_('The selected item does not belong to this event.')) + if item.has_variations and (not variation or variation.item != item): + raise ValidationError(_('Please select a specific variation of this product.')) + + @staticmethod + def clean_subevent(event, subevent): + if event.has_subevents: + if not subevent: + raise ValidationError(_('Subevent cannot be null for event series.')) + if event != subevent.event: + raise ValidationError(_('The subevent does not belong to this event.')) + else: + if subevent: + raise ValidationError(_('The subevent does not belong to this event.')) + + @staticmethod + def clean_duplicate(email, item, variation, subevent, pk): + if WaitingListEntry.objects.filter( + item=item, variation=variation, email=email, voucher__isnull=True, subevent=subevent + ).exclude(pk=pk).exists(): + raise ValidationError(_('You are already on this waiting list! We will notify ' + 'you as soon as we have a ticket available for you.')) diff --git a/src/pretix/control/logdisplay.py b/src/pretix/control/logdisplay.py index 68cfd0fd95..a6dcc42747 100644 --- a/src/pretix/control/logdisplay.py +++ b/src/pretix/control/logdisplay.py @@ -239,6 +239,9 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs): 'pretix.event.permissions.changed': _('A user\'s permissions have been changed.'), 'pretix.event.permissions.deleted': _('A user has been removed from the event team.'), 'pretix.waitinglist.voucher': _('A voucher has been sent to a person on the waiting list.'), + 'pretix.event.orders.waitinglist.deleted': _('An entry has been removed from the waiting list.'), + 'pretix.event.orders.waitinglist.changed': _('An entry has been changed on the waiting list.'), + 'pretix.event.orders.waitinglist.added': _('An entry has been added to the waiting list.'), 'pretix.team.created': _('The team has been created.'), 'pretix.team.changed': _('The team settings have been changed.'), 'pretix.team.deleted': _('The team has been deleted.'), diff --git a/src/pretix/control/views/waitinglist.py b/src/pretix/control/views/waitinglist.py index 0d75c1ba4d..f75bcd1845 100644 --- a/src/pretix/control/views/waitinglist.py +++ b/src/pretix/control/views/waitinglist.py @@ -157,7 +157,7 @@ class EntryDelete(EventPermissionRequiredMixin, DeleteView): def delete(self, request, *args, **kwargs): self.object = self.get_object() success_url = self.get_success_url() - self.object.log_action('pretix.event.orders.waitinglist.delete', user=self.request.user) + self.object.log_action('pretix.event.orders.waitinglist.deleted', user=self.request.user) self.object.delete() messages.success(self.request, _('The selected entry has been deleted.')) return HttpResponseRedirect(success_url) diff --git a/src/tests/api/test_permissions.py b/src/tests/api/test_permissions.py index e3fe9fa220..83e554deeb 100644 --- a/src/tests/api/test_permissions.py +++ b/src/tests/api/test_permissions.py @@ -26,6 +26,12 @@ event_permission_sub_urls = [ ('get', 'can_view_vouchers', 'vouchers/', 200), ('get', 'can_view_orders', 'invoices/', 200), ('get', 'can_view_orders', 'waitinglistentries/', 200), + ('get', 'can_view_orders', 'waitinglistentries/1/', 404), + ('post', 'can_change_orders', 'waitinglistentries/', 400), + ('delete', 'can_change_orders', 'waitinglistentries/1/', 404), + ('patch', 'can_change_orders', 'waitinglistentries/1/', 404), + ('put', 'can_change_orders', 'waitinglistentries/1/', 404), + ('post', 'can_change_orders', 'waitinglistentries/1/send_voucher/', 404), ('get', 'can_change_items', 'categories/', 200), ('get', 'can_change_items', 'items/', 200), ('get', 'can_change_items', 'questions/', 200), diff --git a/src/tests/api/test_waitinglist.py b/src/tests/api/test_waitinglist.py index 8c7dc058cf..89b074ee02 100644 --- a/src/tests/api/test_waitinglist.py +++ b/src/tests/api/test_waitinglist.py @@ -12,6 +12,13 @@ def item(event): return event.items.create(name="Budget Ticket", default_price=23) +@pytest.fixture +def quota(event, item): + q = event.quotas.create(name="Budget Ticket", size=0) + q.items.add(item) + return q + + @pytest.fixture def wle(event, item): testtime = datetime.datetime(2017, 12, 1, 10, 0, 0, tzinfo=UTC) @@ -114,3 +121,309 @@ def test_wle_detail(token_client, organizer, event, wle, item): wle.pk)) assert resp.status_code == 200 assert res == resp.data + + +@pytest.mark.django_db +def test_delete_wle(token_client, organizer, event, wle, item): + resp = token_client.delete( + '/api/v1/organizers/{}/events/{}/waitinglistentries/{}/'.format(organizer.slug, event.slug, wle.pk), + ) + assert resp.status_code == 204 + assert not event.waitinglistentries.filter(pk=wle.id).exists() + + +@pytest.mark.django_db +def test_delete_wle_assigned(token_client, organizer, event, wle, item): + v = event.vouchers.create(item=item, price_mode='set', value=12, tag='Foo') + wle.voucher = v + wle.save() + resp = token_client.delete( + '/api/v1/organizers/{}/events/{}/waitinglistentries/{}/'.format(organizer.slug, event.slug, wle.pk), + ) + assert resp.status_code == 403 + assert event.waitinglistentries.filter(pk=wle.id).exists() + + +def create_wle(token_client, organizer, event, data, expected_failure=False): + resp = token_client.post( + '/api/v1/organizers/{}/events/{}/waitinglistentries/'.format(organizer.slug, event.slug), + data=data, format='json' + ) + if expected_failure: + assert resp.status_code == 400 + else: + print(resp.data) + assert resp.status_code == 201 + return WaitingListEntry.objects.get(pk=resp.data['id']) + + +@pytest.mark.django_db +def test_wle_create_success(token_client, organizer, event, item, quota): + w = create_wle( + token_client, organizer, event, + data={ + 'email': 'testdummy@pretix.eu', + 'item': item.pk, + 'variation': None, + 'locale': 'en', + 'subevent': None + }, + expected_failure=False + ) + assert w.email == "testdummy@pretix.eu" + assert w.item == item + assert w.variation is None + assert w.locale == 'en' + + +@pytest.mark.django_db +def test_wle_require_fields(token_client, organizer, event, item, quota): + create_wle( + token_client, organizer, event, + data={ + 'item': item.pk, + 'variation': None, + 'locale': 'en', + 'subevent': None + }, + expected_failure=True + ) + create_wle( + token_client, organizer, event, + data={ + 'email': 'testdummy@pretix.eu', + 'variation': None, + 'locale': 'en', + 'subevent': None + }, + expected_failure=True + ) + v = item.variations.create(value="S") + create_wle( + token_client, organizer, event, + data={ + 'email': 'testdummy@pretix.eu', + 'item': item.pk, + 'variation': None, + 'locale': 'en', + 'subevent': None + }, + expected_failure=True + ) + event.has_subevents = True + create_wle( + token_client, organizer, event, + data={ + 'email': 'testdummy@pretix.eu', + 'item': item.pk, + 'variation': v.pk, + 'locale': 'en', + 'subevent': None + }, + expected_failure=True + ) + + +@pytest.mark.django_db +def test_wle_create_available(token_client, organizer, event, item, quota): + quota.size = 10 + quota.save() + create_wle( + token_client, organizer, event, + data={ + 'email': 'testdummy@pretix.eu', + 'item': item.pk, + 'variation': None, + 'locale': 'en', + 'subevent': None + }, + expected_failure=True + ) + + +@pytest.mark.django_db +def test_wle_create_duplicate(token_client, organizer, event, item, quota): + create_wle( + token_client, organizer, event, + data={ + 'email': 'testdummy@pretix.eu', + 'item': item.pk, + 'variation': None, + 'locale': 'en', + 'subevent': None + }, + expected_failure=False + ) + create_wle( + token_client, organizer, event, + data={ + 'email': 'testdummy@pretix.eu', + 'item': item.pk, + 'variation': None, + 'locale': 'en', + 'subevent': None + }, + expected_failure=True + ) + + +def change_wle(token_client, organizer, event, wle, data, expected_failure=False): + resp = token_client.patch( + '/api/v1/organizers/{}/events/{}/waitinglistentries/{}/'.format(organizer.slug, event.slug, wle.pk), + data=data, format='json' + ) + if expected_failure: + assert resp.status_code in (400, 403) + else: + assert resp.status_code == 200 + wle.refresh_from_db() + + +@pytest.mark.django_db +def test_wle_change_email(token_client, organizer, event, item, wle, quota): + change_wle( + token_client, organizer, event, wle, + data={ + 'email': 'foo@pretix.eu', + }, + expected_failure=False + ) + assert wle.email == 'foo@pretix.eu' + + +@pytest.mark.django_db +def test_wle_change_assigned(token_client, organizer, event, item, wle, quota): + v = event.vouchers.create(item=item, price_mode='set', value=12, tag='Foo') + wle.voucher = v + wle.save() + change_wle( + token_client, organizer, event, wle, + data={ + 'email': 'foo@pretix.eu', + }, + expected_failure=True + ) + assert wle.email == 'waiting@example.org' + + +@pytest.mark.django_db +def test_wle_change_to_available_item(token_client, organizer, event, item, wle, quota): + i = event.items.create(name="Budget Ticket", default_price=23) + q = event.quotas.create(name="Budget Ticket", size=1) + q.items.add(i) + change_wle( + token_client, organizer, event, wle, + data={ + 'item': i.pk + }, + expected_failure=True + ) + assert wle.item == item + + +@pytest.mark.django_db +def test_wle_change_to_unavailable_item(token_client, organizer, event, item, wle, quota): + i = event.items.create(name="Budget Ticket", default_price=23) + v = i.variations.create(value="S") + q = event.quotas.create(name="Budget Ticket", size=0) + q.items.add(i) + q.variations.add(v) + change_wle( + token_client, organizer, event, wle, + data={ + 'item': i.pk, + 'variation': v.pk + }, + expected_failure=False + ) + assert wle.item == i + assert wle.variation == v + + +@pytest.mark.django_db +def test_wle_change_to_unavailable_item_missing_var(token_client, organizer, event, item, wle, quota): + i = event.items.create(name="Budget Ticket", default_price=23) + v = i.variations.create(value="S") + q = event.quotas.create(name="Budget Ticket", size=0) + q.items.add(i) + q.variations.add(v) + change_wle( + token_client, organizer, event, wle, + data={ + 'item': i.pk, + }, + expected_failure=True + ) + assert wle.item == item + assert wle.variation is None + + +@pytest.mark.django_db +def test_wle_change_subevent_of_wrong_event(token_client, organizer, event, item, wle, subevent, subevent2): + wle.subevent = subevent + wle.save() + change_wle( + token_client, organizer, event, wle, + data={ + 'subevent': subevent2.pk, + }, + expected_failure=True + ) + + +@pytest.mark.django_db +def test_wle_change_to_duplicate(token_client, organizer, event, item, wle, quota): + wle.pk = None + wle.email = 'foo@pretix.eu' + wle.save() + change_wle( + token_client, organizer, event, wle, + data={ + 'email': 'waiting@example.org', + }, + expected_failure=True + ) + + +@pytest.mark.django_db +def test_wle_send_voucher(token_client, organizer, event, item, wle, quota): + quota.size = 10 + quota.save() + resp = token_client.post( + '/api/v1/organizers/{}/events/{}/waitinglistentries/{}/send_voucher/'.format(organizer.slug, event.slug, + wle.pk), + data={}, format='json' + ) + assert resp.status_code == 204 + wle.refresh_from_db() + assert wle.voucher + + +@pytest.mark.django_db +def test_wle_send_voucher_twice(token_client, organizer, event, item, wle, quota): + quota.size = 10 + quota.save() + resp = token_client.post( + '/api/v1/organizers/{}/events/{}/waitinglistentries/{}/send_voucher/'.format(organizer.slug, event.slug, + wle.pk), + data={}, format='json' + ) + assert resp.status_code == 204 + resp = token_client.post( + '/api/v1/organizers/{}/events/{}/waitinglistentries/{}/send_voucher/'.format(organizer.slug, event.slug, + wle.pk), + data={}, format='json' + ) + assert resp.status_code == 400 + + +@pytest.mark.django_db +def test_wle_send_voucher_unavailable(token_client, organizer, event, item, wle, quota): + resp = token_client.post( + '/api/v1/organizers/{}/events/{}/waitinglistentries/{}/send_voucher/'.format(organizer.slug, event.slug, + wle.pk), + data={}, format='json' + ) + assert resp.status_code == 400 + wle.refresh_from_db() + assert not wle.voucher