forked from CGM_Public/pretix_original
Seat-specific vouchers (#1486)
* Basic functionality * API * Do not delete seats with vouchers * Show seat in list of seats * Validate availability of seats * Fix invalid logic in Seat.is_available * Show voucher name in edit form
This commit is contained in:
@@ -8,7 +8,7 @@ from django.utils.timezone import now
|
||||
from django_scopes import scopes_disabled
|
||||
from pytz import UTC
|
||||
|
||||
from pretix.base.models import Event, Voucher
|
||||
from pretix.base.models import Event, SeatingPlan, Voucher
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -44,7 +44,8 @@ TEST_VOUCHER_RES = {
|
||||
'tag': 'Foo',
|
||||
'comment': '',
|
||||
'show_hidden_items': True,
|
||||
'subevent': None
|
||||
'subevent': None,
|
||||
'seat': None,
|
||||
}
|
||||
|
||||
|
||||
@@ -1049,3 +1050,223 @@ def test_create_multiple_vouchers_duplicate_code(token_client, organizer, event,
|
||||
assert resp.data == [{}, {'code': ['Duplicate voucher code in request.']}]
|
||||
with scopes_disabled():
|
||||
assert Voucher.objects.count() == 0
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def seatingplan(organizer, event):
|
||||
plan = SeatingPlan.objects.create(
|
||||
name="Plan", organizer=organizer, layout="{}"
|
||||
)
|
||||
event.seating_plan = plan
|
||||
event.save()
|
||||
return plan
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def seat1(item, event):
|
||||
return event.seats.create(name="A1", product=item, seat_guid="A1")
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_multiple_vouchers_duplicate_seat(token_client, organizer, event, item, seat1, seatingplan):
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/vouchers/batch_create/'.format(organizer.slug, event.slug),
|
||||
data=[
|
||||
{
|
||||
'code': 'ABCDEFGHI',
|
||||
'max_usages': 1,
|
||||
'valid_until': None,
|
||||
'block_quota': False,
|
||||
'allow_ignore_quota': False,
|
||||
'price_mode': 'set',
|
||||
'value': '12.00',
|
||||
'item': item.pk,
|
||||
'variation': None,
|
||||
'quota': None,
|
||||
'tag': 'Foo',
|
||||
'comment': '',
|
||||
'subevent': None,
|
||||
'seat': 'A1',
|
||||
},
|
||||
{
|
||||
'code': 'ABCDEFGHI',
|
||||
'max_usages': 1,
|
||||
'valid_until': None,
|
||||
'block_quota': True,
|
||||
'allow_ignore_quota': False,
|
||||
'price_mode': 'set',
|
||||
'value': '12.00',
|
||||
'item': item.pk,
|
||||
'variation': None,
|
||||
'quota': None,
|
||||
'tag': 'Foo',
|
||||
'comment': '',
|
||||
'subevent': None,
|
||||
'seat': 'A1',
|
||||
}
|
||||
], format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == [{}, {'code': ['Duplicate seat ID in request.']}]
|
||||
with scopes_disabled():
|
||||
assert Voucher.objects.count() == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_set_seat_ok(token_client, organizer, event, seatingplan, seat1, item):
|
||||
with scopes_disabled():
|
||||
v = event.vouchers.create(item=item)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'seat': 'A1'
|
||||
},
|
||||
)
|
||||
with scopes_disabled():
|
||||
v.refresh_from_db()
|
||||
assert v.seat == seat1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_save_set_seat(token_client, organizer, event, seatingplan, seat1, item):
|
||||
with scopes_disabled():
|
||||
v = event.vouchers.create(item=item, seat=seat1)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'seat': 'A1'
|
||||
},
|
||||
)
|
||||
with scopes_disabled():
|
||||
v.refresh_from_db()
|
||||
assert v.seat == seat1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_set_seat_unknown(token_client, organizer, event, seatingplan, seat1, item):
|
||||
with scopes_disabled():
|
||||
v = event.vouchers.create(item=item)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'seat': 'unknown'
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_seat_seat_productmissing(token_client, organizer, event, seatingplan, seat1, item, quota):
|
||||
with scopes_disabled():
|
||||
v = event.vouchers.create(quota=quota)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'seat': 'A1'
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_seat_seat_productwrong(token_client, organizer, event, seatingplan, seat1, item, quota):
|
||||
with scopes_disabled():
|
||||
i2 = event.items.create(name="Budget Ticket", default_price=23)
|
||||
v = event.vouchers.create(item=i2)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'seat': 'A1'
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_seat_seat_usages(token_client, organizer, event, seatingplan, seat1, item, quota):
|
||||
with scopes_disabled():
|
||||
v = event.vouchers.create(item=item, max_usages=2)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'seat': 'A1'
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_seat_seat_duplicate(token_client, organizer, event, seatingplan, seat1, item, quota):
|
||||
with scopes_disabled():
|
||||
event.vouchers.create(item=item, seat=seat1)
|
||||
v = event.vouchers.create(item=item)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'seat': 'A1'
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_set_seat_subevent(token_client, organizer, event, seatingplan, seat1, item, quota):
|
||||
with scopes_disabled():
|
||||
event.has_subevents = True
|
||||
event.save()
|
||||
se1 = event.subevents.create(name="Foobar", date_from=datetime.datetime(2017, 12, 27, 10, 0, 0, tzinfo=UTC))
|
||||
se2 = event.subevents.create(name="Baz", date_from=datetime.datetime(2017, 12, 27, 10, 0, 0, tzinfo=UTC))
|
||||
seat1 = event.seats.create(name="A1", product=item, seat_guid="A1", subevent=se1)
|
||||
event.seats.create(name="A1", product=item, seat_guid="A1", subevent=se2)
|
||||
v = event.vouchers.create(item=item)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'seat': 'A1',
|
||||
'subevent': se1.pk
|
||||
},
|
||||
)
|
||||
with scopes_disabled():
|
||||
v.refresh_from_db()
|
||||
assert v.seat == seat1
|
||||
assert v.subevent == se1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_set_seat_subevent_required(token_client, organizer, event, seatingplan, seat1, item, quota):
|
||||
with scopes_disabled():
|
||||
event.has_subevents = True
|
||||
event.save()
|
||||
se1 = event.subevents.create(name="Foobar", date_from=datetime.datetime(2017, 12, 27, 10, 0, 0, tzinfo=UTC))
|
||||
se2 = event.subevents.create(name="Baz", date_from=datetime.datetime(2017, 12, 27, 10, 0, 0, tzinfo=UTC))
|
||||
seat1 = event.seats.create(name="A1", product=item, seat_guid="A1", subevent=se1)
|
||||
event.seats.create(name="A1", product=item, seat_guid="A1", subevent=se2)
|
||||
event.vouchers.create(item=item, seat=seat1)
|
||||
v = event.vouchers.create(item=item)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'seat': 'A1',
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_set_seat_subevent_invalid(token_client, organizer, event, seatingplan, seat1, item, quota):
|
||||
with scopes_disabled():
|
||||
event.has_subevents = True
|
||||
event.save()
|
||||
se1 = event.subevents.create(name="Foobar", date_from=datetime.datetime(2017, 12, 27, 10, 0, 0, tzinfo=UTC))
|
||||
se2 = event.subevents.create(name="Baz", date_from=datetime.datetime(2017, 12, 27, 10, 0, 0, tzinfo=UTC))
|
||||
seat1 = event.seats.create(name="A1", product=item, seat_guid="A1", subevent=se1)
|
||||
event.seats.create(name="B1", product=item, seat_guid="B1", subevent=se2)
|
||||
event.vouchers.create(item=item, seat=seat1, subevent=se2)
|
||||
v = event.vouchers.create(item=item)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'seat': 'A1',
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
@@ -2020,7 +2020,7 @@ class SeatingTestCase(TestCase):
|
||||
|
||||
@classscope(attr='organizer')
|
||||
def test_free(self):
|
||||
assert set(self.event.free_seats) == {self.seat_a1, self.seat_a2}
|
||||
assert set(self.event.free_seats()) == {self.seat_a1, self.seat_a2}
|
||||
assert self.seat_a1.is_available()
|
||||
assert self.seat_a2.is_available()
|
||||
|
||||
@@ -2028,7 +2028,7 @@ class SeatingTestCase(TestCase):
|
||||
def test_blocked(self):
|
||||
self.seat_a1.blocked = True
|
||||
self.seat_a1.save()
|
||||
assert set(self.event.free_seats) == {self.seat_a2}
|
||||
assert set(self.event.free_seats()) == {self.seat_a2}
|
||||
assert not self.seat_a1.is_available()
|
||||
assert self.seat_a2.is_available()
|
||||
|
||||
@@ -2043,7 +2043,7 @@ class SeatingTestCase(TestCase):
|
||||
order=o, item=self.ticket, variation=None, price=Decimal("12"),
|
||||
seat=self.seat_a1
|
||||
)
|
||||
assert set(self.event.free_seats) == {self.seat_a2}
|
||||
assert set(self.event.free_seats()) == {self.seat_a2}
|
||||
assert not self.seat_a1.is_available()
|
||||
|
||||
@classscope(attr='organizer')
|
||||
@@ -2057,7 +2057,7 @@ class SeatingTestCase(TestCase):
|
||||
order=o, item=self.ticket, variation=None, price=Decimal("12"),
|
||||
seat=self.seat_a1
|
||||
)
|
||||
assert set(self.event.free_seats) == {self.seat_a2}
|
||||
assert set(self.event.free_seats()) == {self.seat_a2}
|
||||
assert not self.seat_a1.is_available()
|
||||
|
||||
@classscope(attr='organizer')
|
||||
@@ -2071,7 +2071,7 @@ class SeatingTestCase(TestCase):
|
||||
order=o, item=self.ticket, variation=None, price=Decimal("12"),
|
||||
seat=self.seat_a1
|
||||
)
|
||||
assert set(self.event.free_seats) == {self.seat_a1, self.seat_a2}
|
||||
assert set(self.event.free_seats()) == {self.seat_a1, self.seat_a2}
|
||||
assert self.seat_a1.is_available()
|
||||
|
||||
@classscope(attr='organizer')
|
||||
@@ -2080,7 +2080,7 @@ class SeatingTestCase(TestCase):
|
||||
event=self.event, cart_id='a', item=self.ticket, seat=self.seat_a1,
|
||||
price=23, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
assert set(self.event.free_seats) == {self.seat_a2}
|
||||
assert set(self.event.free_seats()) == {self.seat_a2}
|
||||
assert not self.seat_a1.is_available()
|
||||
|
||||
@classscope(attr='organizer')
|
||||
@@ -2089,7 +2089,7 @@ class SeatingTestCase(TestCase):
|
||||
event=self.event, cart_id='a', item=self.ticket, seat=self.seat_a1,
|
||||
price=23, expires=now() - timedelta(minutes=10)
|
||||
)
|
||||
assert set(self.event.free_seats) == {self.seat_a1, self.seat_a2}
|
||||
assert set(self.event.free_seats()) == {self.seat_a1, self.seat_a2}
|
||||
assert self.seat_a1.is_available()
|
||||
|
||||
@classscope(attr='organizer')
|
||||
@@ -2106,7 +2106,7 @@ class SeatingTestCase(TestCase):
|
||||
order=o, item=self.ticket, variation=None, price=Decimal("12"),
|
||||
seat=self.seat_a1, subevent=se1
|
||||
)
|
||||
assert set(se1.free_seats) == set()
|
||||
assert set(se1.free_seats()) == set()
|
||||
assert not self.seat_a1.is_available()
|
||||
|
||||
@classscope(attr='organizer')
|
||||
@@ -2123,7 +2123,7 @@ class SeatingTestCase(TestCase):
|
||||
order=o, item=self.ticket, variation=None, price=Decimal("12"),
|
||||
seat=self.seat_a1, subevent=se1
|
||||
)
|
||||
assert set(se1.free_seats) == {self.seat_a1}
|
||||
assert set(se1.free_seats()) == {self.seat_a1}
|
||||
assert self.seat_a1.is_available()
|
||||
|
||||
@classscope(attr='organizer')
|
||||
@@ -2135,7 +2135,7 @@ class SeatingTestCase(TestCase):
|
||||
event=self.event, cart_id='a', item=self.ticket, seat=self.seat_a1,
|
||||
price=23, expires=now() + timedelta(minutes=10), subevent=se1
|
||||
)
|
||||
assert set(se1.free_seats) == set()
|
||||
assert set(se1.free_seats()) == set()
|
||||
assert not self.seat_a1.is_available()
|
||||
|
||||
@classscope(attr='organizer')
|
||||
@@ -2147,7 +2147,25 @@ class SeatingTestCase(TestCase):
|
||||
event=self.event, cart_id='a', item=self.ticket, seat=self.seat_a1,
|
||||
price=23, expires=now() - timedelta(minutes=10), subevent=se1
|
||||
)
|
||||
assert set(se1.free_seats) == {self.seat_a1}
|
||||
assert set(se1.free_seats()) == {self.seat_a1}
|
||||
assert self.seat_a1.is_available()
|
||||
|
||||
@classscope(attr='organizer')
|
||||
def test_voucher_active(self):
|
||||
Voucher.objects.create(
|
||||
event=self.event, code='a', item=self.ticket, seat=self.seat_a1,
|
||||
valid_until=now() + timedelta(minutes=10)
|
||||
)
|
||||
assert set(self.event.free_seats()) == {self.seat_a2}
|
||||
assert not self.seat_a1.is_available()
|
||||
|
||||
@classscope(attr='organizer')
|
||||
def test_voucher_expired(self):
|
||||
Voucher.objects.create(
|
||||
event=self.event, code='a', item=self.ticket, seat=self.seat_a1,
|
||||
valid_until=now() - timedelta(minutes=10)
|
||||
)
|
||||
assert set(self.event.free_seats()) == {self.seat_a2, self.seat_a1}
|
||||
assert self.seat_a1.is_available()
|
||||
|
||||
|
||||
|
||||
@@ -2918,6 +2918,30 @@ class CartSeatingTest(CartTestMixin, TestCase):
|
||||
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
|
||||
self.assertEqual(len(objs), 0)
|
||||
|
||||
def test_add_specific_voucher(self):
|
||||
with scopes_disabled():
|
||||
v = self.event.vouchers.create(item=self.ticket, seat=self.seat_a1)
|
||||
self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
|
||||
'seat_%d' % self.ticket.id: self.seat_a1,
|
||||
'_voucher_code': v.code,
|
||||
}, follow=True)
|
||||
with scopes_disabled():
|
||||
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
|
||||
self.assertEqual(len(objs), 1)
|
||||
self.assertEqual(objs[0].voucher, v)
|
||||
self.assertEqual(objs[0].seat, self.seat_a1)
|
||||
|
||||
def test_add_specific_voucher_wrong_seat(self):
|
||||
with scopes_disabled():
|
||||
v = self.event.vouchers.create(item=self.ticket, seat=self.seat_a1)
|
||||
self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
|
||||
'seat_%d' % self.ticket.id: self.seat_a2,
|
||||
'_voucher_code': v.code,
|
||||
}, follow=True)
|
||||
with scopes_disabled():
|
||||
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
|
||||
self.assertEqual(len(objs), 0)
|
||||
|
||||
def test_add_seat_unknown(self):
|
||||
self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
|
||||
'seat_%d' % self.ticket.id: 'asdasdasd',
|
||||
|
||||
Reference in New Issue
Block a user