forked from CGM_Public/pretix_original
API: Write methods for quotas (#657)
* MKBDIGI-183: Added quotas API write methods * MKBDIGI-183: Fixed code formatting * MKBDIGI-183: Added test for permission requirements * MKBDIGI-183: Documentation corrections * MKBDIGI-183: Removed redundant create/update locks * MKBDIGI-183: Added quota validation to check that items and variations corresponds to each other * MKBDIGI-183: Added quota validation to check that item belong to the same event as the endpoint * MKBDIGI-183: Added subevent validation to check that subevent belong to the same event as the endpoint * MKBDIGI-183: Added subevent validation to check that subevent is null for non-series events * MKBDIGI-183: Changed validation error text * MKBDIGI-183: Added logging for subevents * MKBDIGI-183: Fixed code formatting * MKBDIGI-183: Fixed validation error in API test * MKBDIGI-183: Fixed documentation errors * MKBDIGI-183: Fixed typos in validation messages * MKBDIGI-183: Refactored validation loop vars check * MKBDIGI-183: Updated error strings in test assersions * MKBDIGI-183: Fixed logging for API quota update to account changing subevents
This commit is contained in:
committed by
Raphael Michel
parent
445afcc50c
commit
e4167380b9
@@ -4,7 +4,7 @@ Quotas
|
||||
Resource description
|
||||
--------------------
|
||||
|
||||
Questions define how many times an item can be sold.
|
||||
Quotas define how many times an item can be sold.
|
||||
The quota resource contains the following public fields:
|
||||
|
||||
.. rst-class:: rest-resource-table
|
||||
@@ -106,6 +106,131 @@ Endpoints
|
||||
: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)/quotas/
|
||||
|
||||
Creates a new quota
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/v1/organizers/bigevents/events/sampleconf/quotas/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content: application/json
|
||||
|
||||
{
|
||||
"name": "Ticket Quota",
|
||||
"size": 200,
|
||||
"items": [1, 2],
|
||||
"variations": [1, 4, 5, 7],
|
||||
"subevent": null
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Ticket Quota",
|
||||
"size": 200,
|
||||
"items": [1, 2],
|
||||
"variations": [1, 4, 5, 7],
|
||||
"subevent": null
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer of the event/item to create a quota for
|
||||
:param event: The ``slug`` field of the event to create a quota for
|
||||
:statuscode 201: no error
|
||||
:statuscode 400: The quota 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.
|
||||
|
||||
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/quotas/(id)/
|
||||
|
||||
Update a quota. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide all fields of
|
||||
the resource, other fields will be resetted 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`` field.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PATCH /api/v1/organizers/bigevents/events/sampleconf/quotas/1/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
Content-Length: 94
|
||||
|
||||
{
|
||||
"name": "New Ticket Quota",
|
||||
"size": 100,
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 2,
|
||||
"name": "New Ticket Quota",
|
||||
"size": 100,
|
||||
"items": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"variations": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"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 quota rule to modify
|
||||
:statuscode 200: no error
|
||||
:statuscode 400: The quota 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 change this resource.
|
||||
|
||||
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/quota/(id)/
|
||||
|
||||
Delete a quota. Note that if you delete a quota the items the quota acts on might no longer be available for sale.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
DELETE /api/v1/organizers/bigevents/events/sampleconf/quotas/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 quotas 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.
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/quotas/(id)/availability/
|
||||
|
||||
Returns availability information on one quota, identified by its ID.
|
||||
|
||||
@@ -282,7 +282,7 @@ Endpoints
|
||||
|
||||
: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 tax rule to modify
|
||||
:param id: The ``id`` field of the voucher to modify
|
||||
:statuscode 200: no error
|
||||
:statuscode 400: The voucher could not be modified due to invalid submitted data
|
||||
:statuscode 401: Authentication failure
|
||||
@@ -310,7 +310,7 @@ Endpoints
|
||||
|
||||
: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 tax rule to delete
|
||||
:param id: The ``id`` field of the voucher 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.
|
||||
|
||||
@@ -73,3 +73,16 @@ class QuotaSerializer(I18nAwareModelSerializer):
|
||||
class Meta:
|
||||
model = Quota
|
||||
fields = ('id', 'name', 'size', 'items', 'variations', 'subevent')
|
||||
|
||||
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)
|
||||
|
||||
Quota.clean_variations(full_data.get('items'), full_data.get('variations'))
|
||||
Quota.clean_items(event, full_data.get('items'), full_data.get('variations'))
|
||||
Quota.clean_subevent(event, full_data.get('subevent'))
|
||||
|
||||
return data
|
||||
|
||||
@@ -11,6 +11,7 @@ from pretix.api.serializers.item import (
|
||||
QuotaSerializer,
|
||||
)
|
||||
from pretix.base.models import Item, ItemCategory, Question, Quota
|
||||
from pretix.base.models.organizer import TeamAPIToken
|
||||
|
||||
|
||||
class ItemFilter(FilterSet):
|
||||
@@ -77,7 +78,7 @@ class QuotaFilter(FilterSet):
|
||||
fields = ['subevent']
|
||||
|
||||
|
||||
class QuotaViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
class QuotaViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = QuotaSerializer
|
||||
queryset = Quota.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter,)
|
||||
@@ -85,10 +86,80 @@ class QuotaViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
ordering_fields = ('id', 'size')
|
||||
ordering = ('id',)
|
||||
permission = 'can_change_items'
|
||||
write_permission = 'can_change_items'
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.event.quotas.all()
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.log_action(
|
||||
'pretix.event.quota.added',
|
||||
user=self.request.user,
|
||||
api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None),
|
||||
data=self.request.data
|
||||
)
|
||||
if serializer.instance.subevent:
|
||||
serializer.instance.subevent.log_action(
|
||||
'pretix.subevent.quota.added',
|
||||
user=self.request.user,
|
||||
api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None),
|
||||
data=self.request.data
|
||||
)
|
||||
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['event'] = self.request.event
|
||||
return ctx
|
||||
|
||||
def perform_update(self, serializer):
|
||||
current_subevent = serializer.instance.subevent
|
||||
serializer.save(event=self.request.event)
|
||||
request_subevent = serializer.instance.subevent
|
||||
serializer.instance.log_action(
|
||||
'pretix.event.quota.changed',
|
||||
user=self.request.user,
|
||||
api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None),
|
||||
data=self.request.data
|
||||
)
|
||||
if current_subevent == request_subevent:
|
||||
if current_subevent is not None:
|
||||
current_subevent.log_action(
|
||||
'pretix.subevent.quota.changed',
|
||||
user=self.request.user,
|
||||
api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None),
|
||||
data=self.request.data
|
||||
)
|
||||
else:
|
||||
if request_subevent is not None:
|
||||
request_subevent.log_action(
|
||||
'pretix.subevent.quota.added',
|
||||
user=self.request.user,
|
||||
api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None),
|
||||
data=self.request.data
|
||||
)
|
||||
if current_subevent is not None:
|
||||
current_subevent.log_action(
|
||||
'pretix.subevent.quota.deleted',
|
||||
user=self.request.user,
|
||||
api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None),
|
||||
)
|
||||
serializer.instance.rebuild_cache()
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
instance.log_action(
|
||||
'pretix.event.quota.deleted',
|
||||
user=self.request.user,
|
||||
api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None),
|
||||
)
|
||||
if instance.subevent:
|
||||
instance.subevent.log_action(
|
||||
'pretix.subevent.quota.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=['get'])
|
||||
def availability(self, request, *args, **kwargs):
|
||||
quota = self.get_object()
|
||||
|
||||
@@ -897,3 +897,30 @@ class Quota(LoggedModel):
|
||||
|
||||
class QuotaExceededException(Exception):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def clean_variations(items, variations):
|
||||
for variation in variations:
|
||||
if variation.item not in items:
|
||||
raise ValidationError(_('All variations must belong to an item contained in the items list.'))
|
||||
break
|
||||
|
||||
@staticmethod
|
||||
def clean_items(event, items, variations):
|
||||
for item in items:
|
||||
if event != item.event:
|
||||
raise ValidationError(_('One or more items do not belong to this event.'))
|
||||
if item.has_variations:
|
||||
if not any(var.item == item for var in variations):
|
||||
raise ValidationError(_('One or more items has variations but none of these are in the variations list.'))
|
||||
|
||||
@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.'))
|
||||
|
||||
@@ -33,6 +33,28 @@ def event(organizer, meta_prop):
|
||||
return e
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def event2(organizer, meta_prop):
|
||||
e = Event.objects.create(
|
||||
organizer=organizer, name='Dummy2', slug='dummy2',
|
||||
date_from=datetime(2017, 12, 27, 10, 0, 0, tzinfo=UTC),
|
||||
plugins='pretix.plugins.banktransfer,pretix.plugins.ticketoutputpdf'
|
||||
)
|
||||
e.meta_values.create(property=meta_prop, value="Conference")
|
||||
return e
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def event3(organizer, meta_prop):
|
||||
e = Event.objects.create(
|
||||
organizer=organizer, name='Dummy3', slug='dummy3',
|
||||
date_from=datetime(2017, 12, 27, 10, 0, 0, tzinfo=UTC),
|
||||
plugins='pretix.plugins.banktransfer,pretix.plugins.ticketoutputpdf'
|
||||
)
|
||||
e.meta_values.create(property=meta_prop, value="Conference")
|
||||
return e
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def team(organizer):
|
||||
return Team.objects.create(
|
||||
@@ -65,8 +87,17 @@ def token_client(client, team):
|
||||
def subevent(event, meta_prop):
|
||||
event.has_subevents = True
|
||||
event.save()
|
||||
se = event.subevents.create(name="Foobar",
|
||||
date_from=datetime(2017, 12, 27, 10, 0, 0, tzinfo=UTC))
|
||||
se = event.subevents.create(name="Foobar", date_from=datetime(2017, 12, 27, 10, 0, 0, tzinfo=UTC))
|
||||
|
||||
se.meta_values.create(property=meta_prop, value="Workshop")
|
||||
return se
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def subevent2(event2, meta_prop):
|
||||
event2.has_subevents = True
|
||||
event2.save()
|
||||
se = event2.subevents.create(name="Foobar", date_from=datetime(2017, 12, 27, 10, 0, 0, tzinfo=UTC))
|
||||
|
||||
se.meta_values.create(property=meta_prop, value="Workshop")
|
||||
return se
|
||||
|
||||
@@ -2,6 +2,8 @@ from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
|
||||
from pretix.base.models import Quota
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def category(event):
|
||||
@@ -55,6 +57,16 @@ def item(event):
|
||||
return event.items.create(name="Budget Ticket", default_price=23)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def item2(event2):
|
||||
return event2.items.create(name="Budget Ticket", default_price=23)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def item3(event):
|
||||
return event.items.create(name="Budget Ticket", default_price=23)
|
||||
|
||||
|
||||
TEST_ITEM_RES = {
|
||||
"name": {"en": "Budget Ticket"},
|
||||
"default_price": "23.00",
|
||||
@@ -199,6 +211,22 @@ TEST_QUOTA_RES = {
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def variations(item):
|
||||
v = list()
|
||||
v.append(item.variations.create(value="ChildA1"))
|
||||
v.append(item.variations.create(value="ChildA2"))
|
||||
return v
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def variations2(item2):
|
||||
v = list()
|
||||
v.append(item2.variations.create(value="ChildB1"))
|
||||
v.append(item2.variations.create(value="ChildB2"))
|
||||
return v
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_quota_list(token_client, organizer, event, quota, item, subevent):
|
||||
res = dict(TEST_QUOTA_RES)
|
||||
@@ -232,6 +260,171 @@ def test_quota_detail(token_client, organizer, event, quota, item):
|
||||
assert res == resp.data
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_quota_create(token_client, organizer, event, event2, item):
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"name": "Ticket Quota",
|
||||
"size": 200,
|
||||
"items": [item.pk],
|
||||
"variations": [],
|
||||
"subevent": None
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
quota = Quota.objects.get(pk=resp.data['id'])
|
||||
assert quota.name == "Ticket Quota"
|
||||
assert quota.size == 200
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event2.slug),
|
||||
{
|
||||
"name": "Ticket Quota",
|
||||
"size": 200,
|
||||
"items": [item.pk],
|
||||
"variations": [],
|
||||
"subevent": None
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["One or more items do not belong to this event."]}'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_quota_create_with_variations(token_client, organizer, event, item, variations, variations2):
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"name": "Ticket Quota",
|
||||
"size": 200,
|
||||
"items": [item.pk],
|
||||
"variations": [variations[0].pk],
|
||||
"subevent": None
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"name": "Ticket Quota",
|
||||
"size": 200,
|
||||
"items": [item.pk],
|
||||
"variations": [100],
|
||||
"subevent": None
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"variations":["Invalid pk \\"100\\" - object does not exist."]}'
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"name": "Ticket Quota",
|
||||
"size": 200,
|
||||
"items": [item.pk],
|
||||
"variations": [variations[0].pk, variations2[0].pk],
|
||||
"subevent": None
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["All variations must belong to an item contained in the items list."]}'
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"name": "Ticket Quota",
|
||||
"size": 200,
|
||||
"items": [item.pk],
|
||||
"variations": [],
|
||||
"subevent": None
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["One or more items has variations but none of these are in the variations list."]}'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_quota_create_with_subevent(token_client, organizer, event, event3, item, variations, subevent, subevent2):
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"name": "Ticket Quota",
|
||||
"size": 200,
|
||||
"items": [item.pk],
|
||||
"variations": [variations[0].pk],
|
||||
"subevent": subevent.pk
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"name": "Ticket Quota",
|
||||
"size": 200,
|
||||
"items": [item.pk],
|
||||
"variations": [variations[0].pk],
|
||||
"subevent": None
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["Subevent cannot be null for event series."]}'
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"name": "Ticket Quota",
|
||||
"size": 200,
|
||||
"items": [item.pk],
|
||||
"variations": [variations[0].pk],
|
||||
"subevent": subevent2.pk
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["The subevent does not belong to this event."]}'
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event3.slug),
|
||||
{
|
||||
"name": "Ticket Quota",
|
||||
"size": 200,
|
||||
"items": [],
|
||||
"variations": [],
|
||||
"subevent": subevent2.pk
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["The subevent does not belong to this event."]}'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_quota_update(token_client, organizer, event, quota, item):
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/quotas/{}/'.format(organizer.slug, event.slug, quota.pk),
|
||||
{
|
||||
"name": "Ticket Quota Update",
|
||||
"size": 111,
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
quota = Quota.objects.get(pk=resp.data['id'])
|
||||
assert quota.name == "Ticket Quota Update"
|
||||
assert quota.size == 111
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_quota_availability(token_client, organizer, event, quota, item):
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/quotas/{}/availability/'.format(
|
||||
|
||||
@@ -34,6 +34,10 @@ event_permission_urls = [
|
||||
('put', 'can_change_vouchers', 'vouchers/1/', 404),
|
||||
('patch', 'can_change_vouchers', 'vouchers/1/', 404),
|
||||
('delete', 'can_change_vouchers', 'vouchers/1/', 404),
|
||||
('post', 'can_change_items', 'quotas/', 400),
|
||||
('put', 'can_change_items', 'quotas/1/', 404),
|
||||
('patch', 'can_change_items', 'quotas/1/', 404),
|
||||
('delete', 'can_change_items', 'quotas/1/', 404),
|
||||
('post', 'can_change_orders', 'orders/ABC12/mark_paid/', 404),
|
||||
('post', 'can_change_orders', 'orders/ABC12/mark_pending/', 404),
|
||||
('post', 'can_change_orders', 'orders/ABC12/mark_expired/', 404),
|
||||
|
||||
Reference in New Issue
Block a user