API: CartPositions (#948)

This commit is contained in:
Raphael Michel
2018-06-11 13:18:37 +02:00
committed by GitHub
parent 2e906b0bf5
commit 5d7931fcaf
10 changed files with 782 additions and 10 deletions
+115
View File
@@ -0,0 +1,115 @@
from datetime import timedelta
from django.utils.crypto import get_random_string
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.api.serializers.order import (
AnswerCreateSerializer, AnswerSerializer,
)
from pretix.base.models import Quota
from pretix.base.models.orders import CartPosition
class CartPositionSerializer(I18nAwareModelSerializer):
answers = AnswerSerializer(many=True)
class Meta:
model = CartPosition
fields = ('id', 'cart_id', 'item', 'variation', 'price', 'attendee_name', 'attendee_email',
'voucher', 'addon_to', 'subevent', 'datetime', 'expires', 'includes_tax',
'answers',)
class CartPositionCreateSerializer(I18nAwareModelSerializer):
answers = AnswerCreateSerializer(many=True, required=False)
expires = serializers.DateTimeField(required=False)
class Meta:
model = CartPosition
fields = ('cart_id', 'item', 'variation', 'price', 'attendee_name', 'attendee_email',
'subevent', 'expires', 'includes_tax', 'answers',)
def create(self, validated_data):
answers_data = validated_data.pop('answers')
if not validated_data.get('cart_id'):
cid = "{}@api".format(get_random_string(48))
while CartPosition.objects.filter(cart_id=cid).exists():
cid = "{}@api".format(get_random_string(48))
validated_data['cart_id'] = cid
if not validated_data.get('expires'):
validated_data['expires'] = now() + timedelta(
minutes=self.context['event'].settings.get('reservation_time', as_type=int)
)
with self.context['event'].lock():
new_quotas = (validated_data.get('variation').quotas.filter(subevent=validated_data.get('subevent'))
if validated_data.get('variation')
else validated_data.get('item').quotas.filter(subevent=validated_data.get('subevent')))
for quota in new_quotas:
avail = quota.availability()
if avail[0] != Quota.AVAILABILITY_OK or (avail[1] is not None and avail[1] < 1):
raise ValidationError(
ugettext_lazy('There is not enough quota available on quota "{}" to perform '
'the operation.').format(
quota.name
)
)
cp = CartPosition.objects.create(event=self.context['event'], **validated_data)
for answ_data in answers_data:
options = answ_data.pop('options')
answ = cp.answers.create(**answ_data)
answ.options.add(*options)
return cp
def validate_cart_id(self, cid):
if cid and not cid.endswith('@api'):
raise ValidationError('Cart ID should end in @api or be empty.')
def validate_item(self, item):
if item.event != self.context['event']:
raise ValidationError(
'The specified item does not belong to this event.'
)
if not item.active:
raise ValidationError(
'The specified item is not active.'
)
return item
def validate_subevent(self, subevent):
if self.context['event'].has_subevents:
if not subevent:
raise ValidationError(
'You need to set a subevent.'
)
if subevent.event != self.context['event']:
raise ValidationError(
'The specified subevent does not belong to this event.'
)
elif subevent:
raise ValidationError(
'You cannot set a subevent for this event.'
)
return subevent
def validate(self, data):
if data.get('item'):
if data.get('item').has_variations:
if not data.get('variation'):
raise ValidationError('You should specify a variation for this item.')
else:
if data.get('variation').item != data.get('item'):
raise ValidationError(
'The specified variation does not belong to the specified item.'
)
elif data.get('variation'):
raise ValidationError(
'You cannot specify a variation for this item.'
)
return data
+20 -4
View File
@@ -13,7 +13,7 @@ from pretix.base.models import (
Checkin, Invoice, InvoiceAddress, InvoiceLine, Order, OrderPosition,
Question, QuestionAnswer, Quota,
)
from pretix.base.models.orders import OrderFee
from pretix.base.models.orders import CartPosition, OrderFee
from pretix.base.pdf import get_variables
from pretix.base.signals import register_ticket_outputs
@@ -340,11 +340,12 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
comment = serializers.CharField(required=False, allow_blank=True)
payment_provider = serializers.CharField(required=True)
payment_info = CompatibleJSONField(required=False)
consume_carts = serializers.ListField(child=serializers.CharField(), required=False)
class Meta:
model = Order
fields = ('code', 'status', 'email', 'locale', 'payment_provider', 'fees', 'comment',
'invoice_address', 'positions', 'checkin_attention', 'payment_info')
'invoice_address', 'positions', 'checkin_attention', 'payment_info', 'consume_carts')
def validate_payment_provider(self, pp):
if pp not in self.context['event'].get_payment_providers():
@@ -399,15 +400,27 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
else:
ia = None
with self.context['event'].lock():
with self.context['event'].lock() as now_dt:
quotadiff = Counter()
consume_carts = validated_data.pop('consume_carts', [])
delete_cps = []
if consume_carts:
for cp in CartPosition.objects.filter(event=self.context['event'], cart_id__in=consume_carts):
quotas = (cp.variation.quotas.filter(subevent=cp.subevent)
if cp.variation else cp.item.quotas.filter(subevent=cp.subevent))
if cp.expires > now_dt:
quotadiff.subtract(quotas)
delete_cps.append(cp)
for pos_data in positions_data:
new_quotas = (pos_data.get('variation').quotas.filter(subevent=pos_data.get('subevent'))
if pos_data.get('variation')
else pos_data.get('item').quotas.filter(subevent=pos_data.get('subevent')))
quotadiff.update(new_quotas)
for quota, diff in quotadiff.items():
for quota, diff in quotadiff.items():
if diff > 0:
avail = quota.availability()
if avail[0] != Quota.AVAILABILITY_OK or (avail[1] is not None and avail[1] < diff):
raise ValidationError(
@@ -445,6 +458,9 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
options = answ_data.pop('options')
answ = pos.answers.create(**answ_data)
answ.options.add(*options)
for cp in delete_cps:
cp.delete()
for fee_data in fees_data:
f = OrderFee(**fee_data)
f.order = order
+3
View File
@@ -4,6 +4,8 @@ from django.apps import apps
from django.conf.urls import include, url
from rest_framework import routers
from pretix.api.views import cart
from .views import (
checkin, event, item, oauth, order, organizer, voucher, waitinglist,
)
@@ -28,6 +30,7 @@ event_router.register(r'invoices', order.InvoiceViewSet)
event_router.register(r'taxrules', event.TaxRuleViewSet)
event_router.register(r'waitinglistentries', waitinglist.WaitingListViewSet)
event_router.register(r'checkinlists', checkin.CheckinListViewSet)
event_router.register(r'cartpositions', cart.CartPositionViewSet)
checkinlist_router = routers.DefaultRouter()
checkinlist_router.register(r'positions', checkin.CheckinListPositionViewSet)
+46
View File
@@ -0,0 +1,46 @@
from django.db import transaction
from rest_framework import status, viewsets
from rest_framework.filters import OrderingFilter
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin
from rest_framework.response import Response
from pretix.api.serializers.cart import (
CartPositionCreateSerializer, CartPositionSerializer,
)
from pretix.base.models import CartPosition
class CartPositionViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = CartPositionSerializer
queryset = CartPosition.objects.none()
filter_backends = (OrderingFilter,)
ordering = ('datetime',)
ordering_fields = ('datetime', 'cart_id')
lookup_field = 'id'
permission = 'can_view_orders'
write_permission = 'can_change_orders'
def get_queryset(self):
return CartPosition.objects.filter(
event=self.request.event,
cart_id__endswith="@api"
)
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['event'] = self.request.event
return ctx
def create(self, request, *args, **kwargs):
serializer = CartPositionCreateSerializer(data=request.data, context=self.get_serializer_context())
serializer.is_valid(raise_exception=True)
with transaction.atomic():
self.perform_create(serializer)
cp = serializer.instance
serializer = CartPositionSerializer(cp, context=serializer.context)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
@@ -25,7 +25,8 @@ class AllTicketsPDF(BaseExporter):
continue
if not op.item.admission and not self.event.settings.ticket_download_nonadm:
continue
o._draw_page(p, op, op.order)
layout = o.layout_map.get(op.item_id, o.default_layout)
o._draw_page(layout, p, op, op.order)
p.save()
outbuffer = o._render_with_background(buffer)
+1
View File
@@ -48,3 +48,4 @@ django-countries
pyuca # for better sorting of country names in django-countries
defusedcsv>=1.0.1
vat_moss==0.11.0
idna==2.6 # required by current requests
+554
View File
@@ -0,0 +1,554 @@
import copy
import datetime
from decimal import Decimal
from unittest import mock
import pytest
from django.utils.timezone import now
from pytz import UTC
from pretix.base.models import Question
from pretix.base.models.orders import CartPosition
@pytest.fixture
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 taxrule(event):
return event.tax_rules.create(rate=Decimal('19.00'))
@pytest.fixture
def question(event, item):
q = event.questions.create(question="T-Shirt size", type="S", identifier="ABC")
q.items.add(item)
q.options.create(answer="XL", identifier="LVETRWVU")
return q
@pytest.fixture
def question2(event2, item2):
q = event2.questions.create(question="T-Shirt size", type="S", identifier="ABC")
q.items.add(item2)
return q
@pytest.fixture
def quota(event, item):
q = event.quotas.create(name="Budget Quota", size=200)
q.items.add(item)
return q
TEST_CARTPOSITION_RES = {
'id': 1,
'cart_id': 'aaa@api',
'item': 1,
'variation': None,
'price': '23.00',
'attendee_name': None,
'attendee_email': None,
'voucher': None,
'addon_to': None,
'subevent': None,
'datetime': '2018-06-11T10:00:00Z',
'expires': '2018-06-11T10:00:00Z',
'includes_tax': True,
'answers': []
}
@pytest.mark.django_db
def test_cp_list(token_client, organizer, event, item, taxrule, question):
testtime = datetime.datetime(2018, 6, 11, 10, 0, 0, 0, tzinfo=UTC)
with mock.patch('django.utils.timezone.now') as mock_now:
mock_now.return_value = testtime
cr = CartPosition.objects.create(
event=event, cart_id="aaa", item=item,
price=23,
datetime=datetime.datetime(2018, 6, 11, 10, 0, 0, 0),
expires=datetime.datetime(2018, 6, 11, 10, 0, 0, 0)
)
res = dict(TEST_CARTPOSITION_RES)
res["id"] = cr.pk
res["item"] = item.pk
resp = token_client.get('/api/v1/organizers/{}/events/{}/cartpositions/'.format(organizer.slug, event.slug))
assert resp.status_code == 200
assert [] == resp.data['results']
@pytest.mark.django_db
def test_cp_list_api(token_client, organizer, event, item, taxrule, question):
testtime = datetime.datetime(2018, 6, 11, 10, 0, 0, 0, tzinfo=UTC)
with mock.patch('django.utils.timezone.now') as mock_now:
mock_now.return_value = testtime
cr = CartPosition.objects.create(
event=event, cart_id="aaa@api", item=item,
price=23,
datetime=datetime.datetime(2018, 6, 11, 10, 0, 0, 0),
expires=datetime.datetime(2018, 6, 11, 10, 0, 0, 0)
)
res = dict(TEST_CARTPOSITION_RES)
res["id"] = cr.pk
res["item"] = item.pk
resp = token_client.get('/api/v1/organizers/{}/events/{}/cartpositions/'.format(organizer.slug, event.slug))
assert resp.status_code == 200
assert [res] == resp.data['results']
@pytest.mark.django_db
def test_cp_detail(token_client, organizer, event, item, taxrule, question):
testtime = datetime.datetime(2018, 6, 11, 10, 0, 0, 0, tzinfo=UTC)
with mock.patch('django.utils.timezone.now') as mock_now:
mock_now.return_value = testtime
cr = CartPosition.objects.create(
event=event, cart_id="aaa@api", item=item,
price=23,
datetime=datetime.datetime(2018, 6, 11, 10, 0, 0, 0),
expires=datetime.datetime(2018, 6, 11, 10, 0, 0, 0)
)
res = dict(TEST_CARTPOSITION_RES)
res["id"] = cr.pk
res["item"] = item.pk
resp = token_client.get('/api/v1/organizers/{}/events/{}/cartpositions/{}/'.format(organizer.slug, event.slug,
cr.pk))
assert resp.status_code == 200
assert res == resp.data
@pytest.mark.django_db
def test_cp_delete(token_client, organizer, event, item, taxrule, question):
testtime = datetime.datetime(2018, 6, 11, 10, 0, 0, 0, tzinfo=UTC)
with mock.patch('django.utils.timezone.now') as mock_now:
mock_now.return_value = testtime
cr = CartPosition.objects.create(
event=event, cart_id="aaa@api", item=item,
price=23,
datetime=datetime.datetime(2018, 6, 11, 10, 0, 0, 0),
expires=datetime.datetime(2018, 6, 11, 10, 0, 0, 0)
)
res = dict(TEST_CARTPOSITION_RES)
res["id"] = cr.pk
res["item"] = item.pk
resp = token_client.delete('/api/v1/organizers/{}/events/{}/cartpositions/{}/'.format(organizer.slug, event.slug,
cr.pk))
assert resp.status_code == 204
CARTPOS_CREATE_PAYLOAD = {
'cart_id': 'aaa@api',
'item': 1,
'variation': None,
'price': '23.00',
'attendee_name': None,
'attendee_email': None,
'addon_to': None,
'subevent': None,
'expires': '2018-06-11T10:00:00Z',
'includes_tax': True,
'answers': []
}
@pytest.mark.django_db
def test_cartpos_create(token_client, organizer, event, item, quota, question):
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
cp = CartPosition.objects.get(pk=resp.data['id'])
assert cp.price == Decimal('23.00')
assert cp.item == item
@pytest.mark.django_db
def test_cartpos_cart_id_noapi(token_client, organizer, event, item, quota, question):
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
res['cart_id'] = 'aaa'
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
@pytest.mark.django_db
def test_cartpos_cart_id_optional(token_client, organizer, event, item, quota, question):
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
del res['cart_id']
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
cp = CartPosition.objects.get(pk=resp.data['id'])
assert cp.price == Decimal('23.00')
assert cp.item == item
assert len(cp.cart_id) > 48
@pytest.mark.django_db
def test_cartpos_create_subevent_validation(token_client, organizer, event, item, subevent, subevent2, quota, question):
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'subevent': ['You need to set a subevent.']}
res['subevent'] = subevent2.pk
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'subevent': ['The specified subevent does not belong to this event.']}
@pytest.mark.django_db
def test_cartpos_create_item_validation(token_client, organizer, event, item, item2, quota, question):
item.active = False
item.save()
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'item': ['The specified item is not active.']}
item.active = True
item.save()
res['item'] = item2.pk
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'item': ['The specified item does not belong to this event.']}
var2 = item2.variations.create(value="A")
res['item'] = item.pk
res['variation'] = var2.pk
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'non_field_errors': ['You cannot specify a variation for this item.']}
var1 = item.variations.create(value="A")
res['item'] = item.pk
res['variation'] = var1.pk
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
res['variation'] = var2.pk
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'non_field_errors': ['The specified variation does not belong to the specified item.']}
res['variation'] = None
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'non_field_errors': ['You should specify a variation for this item.']}
@pytest.mark.django_db
def test_cartpos_expires_optional(token_client, organizer, event, item, quota, question):
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
del res['expires']
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
cp = CartPosition.objects.get(pk=resp.data['id'])
assert cp.price == Decimal('23.00')
assert cp.item == item
assert cp.expires - now() > datetime.timedelta(minutes=25)
assert cp.expires - now() < datetime.timedelta(minutes=35)
@pytest.mark.django_db
def test_cartpos_create_answer_validation(token_client, organizer, event, item, quota, question, question2):
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['answers'] = [{
"question": 1,
"answer": "S",
"options": []
}]
res['item'] = item.pk
res['answers'][0]['question'] = question2.pk
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'answers': [{'question': ['The specified question does not belong to this event.']}]}
res['answers'][0]['question'] = question.pk
res['answers'][0]['options'] = [question.options.first().pk]
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {
'answers': [{'non_field_errors': ['You should not specify options if the question is not of a choice type.']}]}
question.type = Question.TYPE_CHOICE
question.save()
res['answers'][0]['options'] = []
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {
'answers': [{'non_field_errors': ['You need to specify options if the question is of a choice type.']}]}
question.options.create(answer="L")
res['answers'][0]['options'] = [
question.options.first().pk,
question.options.last().pk,
]
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'answers': [{'non_field_errors': ['You can specify at most one option for this question.']}]}
question.type = Question.TYPE_FILE
question.save()
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'answers': [{'non_field_errors': ['File uploads are currently not supported via the API.']}]}
question.type = Question.TYPE_CHOICE_MULTIPLE
question.save()
res['answers'][0]['options'] = [
question.options.first().pk,
question.options.last().pk,
]
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
pos = CartPosition.objects.get(pk=resp.data['id'])
answ = pos.answers.first()
assert answ.question == question
assert answ.answer == "XL, L"
question.type = Question.TYPE_NUMBER
question.save()
res['answers'][0]['options'] = []
res['answers'][0]['answer'] = '3.45'
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
pos = CartPosition.objects.get(pk=resp.data['id'])
answ = pos.answers.first()
assert answ.answer == "3.45"
question.type = Question.TYPE_NUMBER
question.save()
res['answers'][0]['options'] = []
res['answers'][0]['answer'] = 'foo'
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'answers': [{'non_field_errors': ['A valid number is required.']}]}
question.type = Question.TYPE_BOOLEAN
question.save()
res['answers'][0]['options'] = []
res['answers'][0]['answer'] = 'True'
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
pos = CartPosition.objects.get(pk=resp.data['id'])
answ = pos.answers.first()
assert answ.answer == "True"
question.type = Question.TYPE_BOOLEAN
question.save()
res['answers'][0]['answer'] = '0'
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
pos = CartPosition.objects.get(pk=resp.data['id'])
answ = pos.answers.first()
assert answ.answer == "False"
question.type = Question.TYPE_BOOLEAN
question.save()
res['answers'][0]['answer'] = 'bla'
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'answers': [{'non_field_errors': ['Please specify "true" or "false" for boolean questions.']}]}
question.type = Question.TYPE_DATE
question.save()
res['answers'][0]['answer'] = '2018-05-14'
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
pos = CartPosition.objects.get(pk=resp.data['id'])
answ = pos.answers.first()
assert answ.answer == "2018-05-14"
question.type = Question.TYPE_DATE
question.save()
res['answers'][0]['answer'] = 'bla'
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {
'answers': [{'non_field_errors': ['Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]].']}]}
question.type = Question.TYPE_DATETIME
question.save()
res['answers'][0]['answer'] = '2018-05-14T13:00:00Z'
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
pos = CartPosition.objects.get(pk=resp.data['id'])
answ = pos.answers.first()
assert answ.answer == "2018-05-14 13:00:00+00:00"
question.type = Question.TYPE_DATETIME
question.save()
res['answers'][0]['answer'] = 'bla'
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'answers': [{'non_field_errors': [
'Datetime has wrong format. Use one of these formats instead: '
'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].']}]}
question.type = Question.TYPE_TIME
question.save()
res['answers'][0]['answer'] = '13:00:00'
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
pos = CartPosition.objects.get(pk=resp.data['id'])
answ = pos.answers.first()
assert answ.answer == "13:00:00"
question.type = Question.TYPE_TIME
question.save()
res['answers'][0]['answer'] = 'bla'
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'answers': [
{'non_field_errors': ['Time has wrong format. Use one of these formats instead: hh:mm[:ss[.uuuuuu]].']}]}
@pytest.mark.django_db
def test_cartpos_create_quota_validation(token_client, organizer, event, item, quota, question):
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
quota.size = 0
quota.save()
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == ['There is not enough quota available on quota "Budget Quota" to perform the operation.']
+33 -1
View File
@@ -11,7 +11,7 @@ from django_countries.fields import Country
from pytz import UTC
from pretix.base.models import InvoiceAddress, Order, OrderPosition, Question
from pretix.base.models.orders import OrderFee
from pretix.base.models.orders import CartPosition, OrderFee
from pretix.base.services.invoices import (
generate_cancellation, generate_invoice,
)
@@ -1618,6 +1618,38 @@ def test_order_create_quota_validation(token_client, organizer, event, item, quo
assert resp.data == ['There is not enough quota available on quota "Budget Quota" to perform the operation.']
@pytest.mark.django_db
def test_order_create_quota_consume_cart(token_client, organizer, event, item, quota, question):
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
res['positions'][0]['item'] = item.pk
res['positions'][0]['answers'][0]['question'] = question.pk
cr = CartPosition.objects.create(
event=event, cart_id="uxLJBUMEcnxOLI2EuxLYN1hWJq9GKu4yWL9FEgs2m7M0vdFi@api", item=item,
price=23,
expires=now() + datetime.timedelta(hours=3)
)
quota.size = 1
quota.save()
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == ['There is not enough quota available on quota "Budget Quota" to perform the operation.']
res['consume_carts'] = [cr.cart_id]
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
assert not CartPosition.objects.filter(pk=cr.pk).exists()
@pytest.mark.django_db
def test_order_create_free(token_client, organizer, event, item, quota, question):
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)