mirror of
https://github.com/pretix/pretix.git
synced 2026-05-03 14:54:04 +00:00
Add support for reserved seating (#1228)
* Initial work on seating * Add seat guids * Add product_list_top * CartAdd: Ignore item when a seat is passed * Cart display * product_list_top → render_seating_plan * Render seating plan in voucher redemption * Fix failing tests * Add tests for extending cart positions with seats * Add subevent_forms to docs * Update schema, migrations * Dealing with expired orders * steps to order change * Change order positions * Allow to add seats * tests for ocm * Fix things after rebase * Seating plans API * Add more tests for cart behaviour * Widget support * Adjust widget tests * Re-enable CSP * Update schema * Api: position.seat * Add guid to word list * API: (sub)event.seating_plan * Vali fixes * Fix api * Fix reference in test * Fix test for real
This commit is contained in:
@@ -12,6 +12,7 @@ from django_scopes import scope
|
||||
from pretix.base.decimal import round_decimal
|
||||
from pretix.base.models import (
|
||||
CartPosition, Event, InvoiceAddress, Item, Order, OrderPosition, Organizer,
|
||||
SeatingPlan,
|
||||
)
|
||||
from pretix.base.models.items import SubEventItem
|
||||
from pretix.base.models.orders import OrderFee, OrderPayment, OrderRefund
|
||||
@@ -635,6 +636,19 @@ class OrderChangeManagerTests(TestCase):
|
||||
self.quota.items.add(self.ticket2)
|
||||
self.quota.items.add(self.shirt)
|
||||
|
||||
self.stalls = Item.objects.create(event=self.event, name='Stalls', tax_rule=self.tr7,
|
||||
default_price=Decimal('23.00'), admission=True)
|
||||
self.plan = SeatingPlan.objects.create(
|
||||
name="Plan", organizer=self.o, layout="{}"
|
||||
)
|
||||
self.event.seat_category_mappings.create(
|
||||
layout_category='Stalls', product=self.stalls
|
||||
)
|
||||
self.quota.items.add(self.stalls)
|
||||
self.seat_a1 = self.event.seats.create(name="A1", product=self.stalls, seat_guid="A1")
|
||||
self.seat_a2 = self.event.seats.create(name="A2", product=self.stalls, seat_guid="A2")
|
||||
self.seat_a3 = self.event.seats.create(name="A3", product=self.stalls, seat_guid="A3")
|
||||
|
||||
def _enable_reverse_charge(self):
|
||||
self.tr7.eu_reverse_charge = True
|
||||
self.tr7.home_country = Country('DE')
|
||||
@@ -1631,3 +1645,206 @@ class OrderChangeManagerTests(TestCase):
|
||||
assert self.order.status == Order.STATUS_PENDING
|
||||
assert o2.total == Decimal('0.00')
|
||||
assert o2.status == Order.STATUS_PAID
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_change_seat_circular(self):
|
||||
self.op1.seat = self.seat_a1
|
||||
self.op1.save()
|
||||
self.op2.seat = self.seat_a2
|
||||
self.op2.save()
|
||||
self.ocm.change_seat(self.op1, self.seat_a2)
|
||||
self.ocm.change_seat(self.op2, self.seat_a1)
|
||||
self.ocm.commit()
|
||||
self.op1.refresh_from_db()
|
||||
self.op2.refresh_from_db()
|
||||
assert self.op1.seat == self.seat_a2
|
||||
assert self.op2.seat == self.seat_a1
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_change_seat_duplicate(self):
|
||||
self.op1.seat = self.seat_a1
|
||||
self.op1.save()
|
||||
self.op2.seat = self.seat_a2
|
||||
self.op2.save()
|
||||
self.ocm.change_seat(self.op1, self.seat_a2)
|
||||
with self.assertRaises(OrderError):
|
||||
self.ocm.commit()
|
||||
self.op1.refresh_from_db()
|
||||
assert self.op1.seat == self.seat_a1
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_change_seat_and_cancel(self):
|
||||
self.op1.seat = self.seat_a1
|
||||
self.op1.save()
|
||||
self.op2.seat = self.seat_a2
|
||||
self.op2.save()
|
||||
self.ocm.change_seat(self.op1, self.seat_a2)
|
||||
self.ocm.cancel(self.op2)
|
||||
self.ocm.commit()
|
||||
self.op1.refresh_from_db()
|
||||
assert self.op1.seat == self.seat_a2
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_change_seat(self):
|
||||
self.op1.seat = self.seat_a1
|
||||
self.op1.save()
|
||||
self.ocm.change_seat(self.op1, self.seat_a2)
|
||||
self.ocm.commit()
|
||||
self.op1.refresh_from_db()
|
||||
assert self.op1.seat == self.seat_a2
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_change_add_seat(self):
|
||||
# does this make sense or do we block it based on the item? this is currently not reachable through the UI
|
||||
self.ocm.change_seat(self.op1, self.seat_a1)
|
||||
self.ocm.commit()
|
||||
self.op1.refresh_from_db()
|
||||
assert self.op1.seat == self.seat_a1
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_remove_seat(self):
|
||||
# does this make sense or do we block it based on the item? this is currently not reachable through the UI
|
||||
self.op1.seat = self.seat_a1
|
||||
self.op1.save()
|
||||
self.ocm.change_seat(self.op1, None)
|
||||
self.ocm.commit()
|
||||
self.op1.refresh_from_db()
|
||||
assert self.op1.seat is None
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_add_with_seat(self):
|
||||
self.ocm.add_position(self.stalls, None, price=Decimal('13.00'), seat=self.seat_a3)
|
||||
self.ocm.commit()
|
||||
op3 = self.order.positions.last()
|
||||
assert op3.item == self.stalls
|
||||
assert op3.seat == self.seat_a3
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_add_with_taken_seat(self):
|
||||
self.op1.seat = self.seat_a1
|
||||
self.op1.save()
|
||||
self.ocm.add_position(self.stalls, None, price=Decimal('13.00'), seat=self.seat_a1)
|
||||
with self.assertRaises(OrderError):
|
||||
self.ocm.commit()
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_add_with_seat_required(self):
|
||||
with self.assertRaises(OrderError):
|
||||
self.ocm.add_position(self.stalls, None, price=Decimal('13.00'))
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_add_with_seat_forbidden(self):
|
||||
with self.assertRaises(OrderError):
|
||||
self.ocm.add_position(self.ticket, None, price=Decimal('13.00'), seat=self.seat_a1)
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_add_with_seat_blocked(self):
|
||||
self.seat_a1.blocked = True
|
||||
self.seat_a1.save()
|
||||
self.ocm.add_position(self.stalls, None, price=Decimal('13.00'), seat=self.seat_a1)
|
||||
with self.assertRaises(OrderError):
|
||||
self.ocm.commit()
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_change_seat_to_blocked(self):
|
||||
self.seat_a2.blocked = True
|
||||
self.seat_a2.save()
|
||||
self.op1.seat = self.seat_a1
|
||||
self.op1.save()
|
||||
self.ocm.change_seat(self.op1, self.seat_a2)
|
||||
with self.assertRaises(OrderError):
|
||||
self.ocm.commit()
|
||||
self.op1.refresh_from_db()
|
||||
assert self.op1.seat == self.seat_a1
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_change_seat_require_subevent_change(self):
|
||||
self.event.has_subevents = True
|
||||
self.event.save()
|
||||
se1 = self.event.subevents.create(name="Foo", date_from=now())
|
||||
se2 = self.event.subevents.create(name="Bar", date_from=now())
|
||||
self.op1.subevent = se1
|
||||
self.op1.seat = self.seat_a1
|
||||
self.op1.save()
|
||||
self.quota.subevent = se2
|
||||
self.quota.save()
|
||||
self.seat_a1.subevent = se1
|
||||
self.seat_a1.save()
|
||||
self.seat_a2.subevent = se2
|
||||
self.seat_a2.save()
|
||||
self.ocm.change_seat(self.op1, self.seat_a2)
|
||||
with self.assertRaises(OrderError):
|
||||
self.ocm.commit()
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_change_subevent_require_seat_change(self):
|
||||
self.event.has_subevents = True
|
||||
self.event.save()
|
||||
se1 = self.event.subevents.create(name="Foo", date_from=now())
|
||||
se2 = self.event.subevents.create(name="Bar", date_from=now())
|
||||
self.op1.subevent = se1
|
||||
self.op1.seat = self.seat_a1
|
||||
self.op1.save()
|
||||
self.quota.subevent = se2
|
||||
self.quota.save()
|
||||
self.seat_a1.subevent = se1
|
||||
self.seat_a1.save()
|
||||
self.seat_a2.subevent = se2
|
||||
self.seat_a2.save()
|
||||
self.ocm.change_subevent(self.op1, se2)
|
||||
with self.assertRaises(OrderError):
|
||||
self.ocm.commit()
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_change_subevent_and_seat(self):
|
||||
self.event.has_subevents = True
|
||||
self.event.save()
|
||||
se1 = self.event.subevents.create(name="Foo", date_from=now())
|
||||
se2 = self.event.subevents.create(name="Bar", date_from=now())
|
||||
self.op1.subevent = se1
|
||||
self.op1.seat = self.seat_a1
|
||||
self.op1.save()
|
||||
self.quota.subevent = se2
|
||||
self.quota.save()
|
||||
self.seat_a1.subevent = se1
|
||||
self.seat_a1.save()
|
||||
self.seat_a2.subevent = se2
|
||||
self.seat_a2.save()
|
||||
self.ocm.change_subevent(self.op1, se2)
|
||||
self.ocm.change_seat(self.op1, self.seat_a2)
|
||||
self.ocm.commit()
|
||||
self.op1.refresh_from_db()
|
||||
assert self.op1.seat == self.seat_a2
|
||||
assert self.op1.subevent == se2
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_change_seat_inside_subevent(self):
|
||||
self.event.has_subevents = True
|
||||
self.event.save()
|
||||
se1 = self.event.subevents.create(name="Foo", date_from=now())
|
||||
self.op1.subevent = se1
|
||||
self.op1.seat = self.seat_a1
|
||||
self.op1.save()
|
||||
self.seat_a1.subevent = se1
|
||||
self.seat_a1.save()
|
||||
self.seat_a2.subevent = se1
|
||||
self.seat_a2.save()
|
||||
self.ocm.change_seat(self.op1, self.seat_a2)
|
||||
self.ocm.commit()
|
||||
self.op1.refresh_from_db()
|
||||
assert self.op1.seat == self.seat_a2
|
||||
assert self.op1.subevent == se1
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_add_with_seat_and_subevent_mismatch(self):
|
||||
self.event.has_subevents = True
|
||||
self.event.save()
|
||||
se1 = self.event.subevents.create(name="Foo", date_from=now())
|
||||
se2 = self.event.subevents.create(name="Bar", date_from=now())
|
||||
self.quota.subevent = se2
|
||||
self.quota.save()
|
||||
self.seat_a1.subevent = se1
|
||||
self.seat_a1.save()
|
||||
self.ocm.change_subevent(self.op1, se2)
|
||||
with self.assertRaises(OrderError):
|
||||
self.ocm.add_position(self.ticket, None, price=Decimal('13.00'), subevent=se2, seat=self.seat_a1)
|
||||
|
||||
Reference in New Issue
Block a user