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:
Raphael Michel
2019-06-25 11:00:03 +02:00
committed by GitHub
parent f79d17cb6a
commit 93089d87e3
77 changed files with 3689 additions and 164 deletions

View File

@@ -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)