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
+107 -1
View File
@@ -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 Question
from pretix.base.models import Question, SeatingPlan
from pretix.base.models.orders import CartPosition
@@ -64,6 +64,7 @@ TEST_CARTPOSITION_RES = {
'datetime': '2018-06-11T10:00:00Z',
'expires': '2018-06-11T10:00:00Z',
'includes_tax': True,
'seat': None,
'answers': []
}
@@ -621,3 +622,108 @@ def test_cartpos_create_quota_validation(token_client, organizer, event, item, q
)
assert resp.status_code == 400
assert resp.data == ['There is not enough quota available on quota "Budget Quota" to perform the operation.']
@pytest.fixture
def seat(event, organizer, item):
SeatingPlan.objects.create(
name="Plan", organizer=organizer, layout="{}"
)
event.seat_category_mappings.create(
layout_category='Stalls', product=item
)
return event.seats.create(name="A1", product=item, seat_guid="A1")
@pytest.mark.django_db
def test_cartpos_create_with_seat(token_client, organizer, event, item, quota, seat, question):
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
res['seat'] = seat.seat_guid
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
with scopes_disabled():
p = CartPosition.objects.get(pk=resp.data['id'])
assert p.seat == seat
@pytest.mark.django_db
def test_cartpos_create_with_blocked_seat(token_client, organizer, event, item, quota, seat, question):
seat.blocked = True
seat.save()
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
res['seat'] = seat.seat_guid
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 == ['The selected seat "A1" is not available.']
@pytest.mark.django_db
def test_cartpos_create_with_used_seat(token_client, organizer, event, item, quota, seat, question):
CartPosition.objects.create(
event=event, cart_id='aaa', item=item,
price=21.5, expires=now() + datetime.timedelta(minutes=10), seat=seat
)
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
res['seat'] = seat.seat_guid
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 == ['The selected seat "A1" is not available.']
@pytest.mark.django_db
def test_cartpos_create_with_unknown_seat(token_client, organizer, event, item, quota, seat, question):
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
res['seat'] = seat.seat_guid + '_'
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 == ['The specified seat does not exist.']
@pytest.mark.django_db
def test_cartpos_create_require_seat(token_client, organizer, event, item, quota, seat, 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 == ['The specified product requires to choose a seat.']
@pytest.mark.django_db
def test_cartpos_create_unseated(token_client, organizer, event, item, quota, seat, question):
with scopes_disabled():
item2 = event.items.create(name="Budget Ticket", default_price=23)
quota.items.add(item2)
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item2.pk
res['seat'] = seat.seat_guid
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 == ['The specified product does not allow to choose a seat.']
+305 -1
View File
@@ -8,7 +8,9 @@ from django_countries.fields import Country
from django_scopes import scopes_disabled
from pytz import UTC
from pretix.base.models import Event, InvoiceAddress, Order, OrderPosition
from pretix.base.models import (
Event, InvoiceAddress, Order, OrderPosition, SeatingPlan,
)
from pretix.base.models.orders import OrderFee
@@ -69,6 +71,8 @@ TEST_EVENT_RES = {
"location": None,
"slug": "dummy",
"has_subevents": False,
"seating_plan": None,
"seat_category_mapping": {},
"meta_data": {"type": "Conference"},
'plugins': [
'pretix.plugins.banktransfer',
@@ -618,3 +622,303 @@ def test_event_with_order_position_not_delete(token_client, organizer, event, it
'set \'live\' to false to hide the event and take the shop offline instead."}'
with scopes_disabled():
assert organizer.events.filter(pk=event.id).exists()
@pytest.fixture
def seatingplan(event, organizer, item):
return SeatingPlan.objects.create(
name="Plan", organizer=organizer, layout="""{
"name": "Grosser Saal",
"categories": [
{
"name": "Stalls",
"color": "red"
}
],
"zones": [
{
"name": "Main Area",
"position": {
"x": 0,
"y": 0
},
"rows": [
{
"row_number": "0",
"seats": [
{
"seat_guid": "0-0",
"seat_number": "0-0",
"position": {
"x": 0,
"y": 0
},
"category": "Stalls"
},
{
"seat_guid": "0-1",
"seat_number": "0-1",
"position": {
"x": 33,
"y": 0
},
"category": "Stalls"
},
{
"seat_guid": "0-2",
"seat_number": "0-2",
"position": {
"x": 66,
"y": 0
},
"category": "Stalls"
}
],
"position": {
"x": 0,
"y": 0
}
}
]
}
],
"size": {
"width": 600,
"height": 400
}
}"""
)
@pytest.mark.django_db
def test_event_update_seating(token_client, organizer, event, item, seatingplan):
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug),
{
"seating_plan": seatingplan.pk,
"seat_category_mapping": {
"Stalls": item.pk
}
},
format='json'
)
assert resp.status_code == 200
event.refresh_from_db()
assert event.seating_plan == seatingplan
with scopes_disabled():
assert event.seats.count() == 3
assert event.seats.filter(product=item).count() == 3
m = event.seat_category_mappings.get()
assert m.layout_category == 'Stalls'
assert m.product == item
@pytest.mark.django_db
def test_event_update_seating_invalid_product(token_client, organizer, event, item, seatingplan):
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug),
{
"seating_plan": seatingplan.pk,
"seat_category_mapping": {
"Stalls": item.pk + 2
}
},
format='json'
)
assert resp.status_code == 400
assert resp.content.decode() == '{"seat_category_mapping":["Item \'%d\' does not exist."]}' % (item.pk + 2)
@pytest.mark.django_db
def test_event_update_seating_change_mapping(token_client, organizer, event, item, seatingplan):
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug),
{
"seating_plan": seatingplan.pk,
"seat_category_mapping": {
"Stalls": item.pk
}
},
format='json'
)
assert resp.status_code == 200
event.refresh_from_db()
assert event.seating_plan == seatingplan
with scopes_disabled():
assert event.seats.count() == 3
assert event.seats.filter(product=item).count() == 3
m = event.seat_category_mappings.get()
assert m.layout_category == 'Stalls'
assert m.product == item
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug),
{
"seat_category_mapping": {
"VIP": item.pk,
}
},
format='json'
)
assert resp.status_code == 200
event.refresh_from_db()
assert event.seating_plan == seatingplan
with scopes_disabled():
assert event.seats.count() == 3
m = event.seat_category_mappings.get()
assert event.seats.filter(product=None).count() == 3
assert m.layout_category == 'VIP'
assert m.product == item
@pytest.mark.django_db
def test_remove_seating(token_client, organizer, event, item, seatingplan):
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug),
{
"seating_plan": seatingplan.pk,
"seat_category_mapping": {
"Stalls": item.pk
}
},
format='json'
)
assert resp.status_code == 200
event.refresh_from_db()
assert event.seating_plan == seatingplan
with scopes_disabled():
assert event.seats.count() == 3
assert event.seat_category_mappings.count() == 1
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug),
{
"seating_plan": None
},
format='json'
)
assert resp.status_code == 200
event.refresh_from_db()
assert event.seating_plan is None
with scopes_disabled():
assert event.seats.count() == 0
assert event.seat_category_mappings.count() == 0
@pytest.mark.django_db
def test_remove_seating_forbidden(token_client, organizer, event, item, seatingplan, order_position):
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug),
{
"seating_plan": seatingplan.pk,
"seat_category_mapping": {
"Stalls": item.pk
}
},
format='json'
)
assert resp.status_code == 200
event.refresh_from_db()
assert event.seating_plan == seatingplan
with scopes_disabled():
assert event.seats.count() == 3
assert event.seat_category_mappings.count() == 1
order_position.seat = event.seats.first()
order_position.save()
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug),
{
"seating_plan": None
},
format='json'
)
assert resp.status_code == 400
assert resp.content.decode() == '{"seating_plan":["You can not change the plan since seat \\"0-0\\" is not ' \
'present in the new plan and is already sold."]}'
@pytest.mark.django_db
def test_no_seating_for_series(token_client, organizer, event, item, seatingplan, order_position):
event.has_subevents = True
event.save()
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug),
{
"seating_plan": seatingplan.pk,
"seat_category_mapping": {
"Stalls": item.pk
}
},
format='json'
)
assert resp.status_code == 400
assert resp.content.decode() == '{"non_field_errors":["Event series should not directly be assigned a seating plan."]}'
@pytest.mark.django_db
def test_event_create_with_seating(token_client, organizer, event, meta_prop, seatingplan):
resp = token_client.post(
'/api/v1/organizers/{}/events/'.format(organizer.slug),
{
"name": {
"de": "Demo Konference 2020 Test",
"en": "Demo Conference 2020 Test"
},
"live": False,
"currency": "EUR",
"date_from": "2017-12-27T10:00:00Z",
"date_to": "2017-12-28T10:00:00Z",
"date_admission": None,
"is_public": False,
"presale_start": None,
"presale_end": None,
"location": None,
"slug": "2030",
"seating_plan": seatingplan.pk,
"meta_data": {
meta_prop.name: "Conference"
}
},
format='json'
)
assert resp.status_code == 201
with scopes_disabled():
event = Event.objects.get(slug=resp.data['slug'])
assert event.seating_plan == seatingplan
assert event.seats.count() == 3
assert event.seat_category_mappings.count() == 0
@pytest.mark.django_db
def test_event_create_with_seating_maps(token_client, organizer, event, meta_prop, seatingplan):
resp = token_client.post(
'/api/v1/organizers/{}/events/'.format(organizer.slug),
{
"name": {
"de": "Demo Konference 2020 Test",
"en": "Demo Conference 2020 Test"
},
"live": False,
"currency": "EUR",
"date_from": "2017-12-27T10:00:00Z",
"date_to": "2017-12-28T10:00:00Z",
"date_admission": None,
"is_public": False,
"presale_start": None,
"presale_end": None,
"location": None,
"slug": "2030",
"seating_plan": seatingplan.pk,
"seat_category_mapping": {
"Foo": 1,
},
"meta_data": {
meta_prop.name: "Conference"
}
},
format='json'
)
assert resp.status_code == 400
assert resp.content.decode() == '{"seat_category_mapping":["You cannot specify seat category mappings on event creation."]}'
+202 -1
View File
@@ -13,7 +13,9 @@ from pytz import UTC
from stripe.error import APIConnectionError
from tests.plugins.stripe.test_provider import MockedCharge
from pretix.base.models import InvoiceAddress, Order, OrderPosition, Question
from pretix.base.models import (
InvoiceAddress, Order, OrderPosition, Question, SeatingPlan,
)
from pretix.base.models.orders import (
CartPosition, OrderFee, OrderPayment, OrderRefund,
)
@@ -139,6 +141,7 @@ TEST_ORDERPOSITION_RES = {
"pseudonymization_id": "ABCDEFGHKL",
"checkins": [],
"downloads": [],
"seat": None,
"answers": [
{
"question": 1,
@@ -2519,6 +2522,204 @@ def test_order_create_paid_generate_invoice(token_client, organizer, event, item
assert p.payment_date.minute == 20
@pytest.fixture
def seat(event, organizer, item):
SeatingPlan.objects.create(
name="Plan", organizer=organizer, layout="{}"
)
event.seat_category_mappings.create(
layout_category='Stalls', product=item
)
return event.seats.create(name="A1", product=item, seat_guid="A1")
@pytest.mark.django_db
def test_order_create_with_seat(token_client, organizer, event, item, quota, seat, question):
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
res['positions'][0]['item'] = item.pk
res['positions'][0]['seat'] = seat.seat_guid
res['positions'][0]['answers'][0]['question'] = question.pk
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
with scopes_disabled():
o = Order.objects.get(code=resp.data['code'])
p = o.positions.first()
assert p.seat == seat
@pytest.mark.django_db
def test_order_create_with_blocked_seat(token_client, organizer, event, item, quota, seat, question):
seat.blocked = True
seat.save()
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
res['positions'][0]['item'] = item.pk
res['positions'][0]['seat'] = seat.seat_guid
res['positions'][0]['answers'][0]['question'] = question.pk
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 == {
'positions': [
{'seat': ['The selected seat "A1" is not available.']},
]
}
@pytest.mark.django_db
def test_order_create_with_used_seat(token_client, organizer, event, item, quota, seat, question):
CartPosition.objects.create(
event=event, cart_id='aaa', item=item,
price=21.5, expires=now() + datetime.timedelta(minutes=10), seat=seat
)
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
res['positions'][0]['item'] = item.pk
res['positions'][0]['seat'] = seat.seat_guid
res['positions'][0]['answers'][0]['question'] = question.pk
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 == {
'positions': [
{'seat': ['The selected seat "A1" is not available.']},
]
}
@pytest.mark.django_db
def test_order_create_with_unknown_seat(token_client, organizer, event, item, quota, seat, question):
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
res['positions'][0]['item'] = item.pk
res['positions'][0]['seat'] = seat.seat_guid + '_'
res['positions'][0]['answers'][0]['question'] = question.pk
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 == {
'positions': [
{'seat': ['The specified seat does not exist.']},
]
}
@pytest.mark.django_db
def test_order_create_require_seat(token_client, organizer, event, item, quota, seat, question):
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
res['positions'][0]['item'] = item.pk
res['positions'][0]['answers'][0]['question'] = question.pk
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 == {
'positions': [
{'seat': ['The specified product requires to choose a seat.']},
]
}
@pytest.mark.django_db
def test_order_create_unseated(token_client, organizer, event, item, quota, seat, question):
with scopes_disabled():
item2 = event.items.create(name="Budget Ticket", default_price=23)
quota.items.add(item2)
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
res['positions'][0]['item'] = item2.pk
res['positions'][0]['answers'][0]['question'] = question.pk
res['positions'][0]['seat'] = seat.seat_guid
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 == {
'positions': [
{'seat': ['The specified product does not allow to choose a seat.']},
]
}
@pytest.mark.django_db
def test_order_create_with_duplicate_seat(token_client, organizer, event, item, quota, seat, question):
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
res['positions'] = [
{
"positionid": 1,
"item": item.pk,
"variation": None,
"price": "23.00",
"attendee_name_parts": {"full_name": "Peter"},
"attendee_email": None,
"addon_to": None,
"answers": [],
"subevent": None,
"seat": seat.seat_guid
},
{
"positionid": 2,
"item": item.pk,
"variation": None,
"price": "23.00",
"attendee_name_parts": {"full_name": "Peter"},
"attendee_email": None,
"addon_to": 1,
"answers": [],
"subevent": None,
"seat": seat.seat_guid
}
]
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 == {
'positions': [
{},
{'seat': ['The selected seat "A1" is not available.']},
]
}
@pytest.mark.django_db
def test_order_create_with_seat_consumed_from_cart(token_client, organizer, event, item, quota, seat, question):
CartPosition.objects.create(
event=event, cart_id='aaa', item=item,
price=21.5, expires=now() + datetime.timedelta(minutes=10), seat=seat
)
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
res['positions'][0]['item'] = item.pk
res['positions'][0]['seat'] = seat.seat_guid
res['positions'][0]['answers'][0]['question'] = question.pk
res['consume_carts'] = ['aaa']
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
with scopes_disabled():
o = Order.objects.get(code=resp.data['code'])
p = o.positions.first()
assert p.seat == seat
REFUND_CREATE_PAYLOAD = {
"state": "created",
"provider": "manual",
+174
View File
@@ -0,0 +1,174 @@
import copy
import json
import pytest
from django_scopes import scopes_disabled
from pretix.base.models import SeatingPlan
SAMPLE_PLAN = """{
"name": "Sample plan",
"categories": [
{
"name": "Stalls",
"color": "#33ffff"
}
],
"zones": [
{
"name": "Main Area",
"position": {
"x": 0,
"y": 0
},
"row_number_position": "left",
"rows": [
{
"row_number": "0",
"seats": [
{
"seat_guid": "0-0",
"seat_number": "0-0",
"position": {
"x": 0,
"y": 0
},
"category": "Stalls"
},
{
"seat_guid": "0-1",
"seat_number": "0-1",
"position": {
"x": 30,
"y": 0
},
"category": "Stalls"
}
],
"position": {
"x": 40,
"y": 25
}
}
]
}
],
"size": {
"width": 600,
"height": 500
}
}"""
@pytest.fixture
def seatingplan(organizer, event):
wh = organizer.seating_plans.create(
name="Plan",
layout=SAMPLE_PLAN
)
return wh
TEST_PLAN_RES = {
"id": 1,
"name": "Plan",
"layout": json.loads(SAMPLE_PLAN)
}
@pytest.mark.django_db
def test_plan_list(token_client, organizer, event, seatingplan):
res = dict(TEST_PLAN_RES)
res["id"] = seatingplan.pk
resp = token_client.get('/api/v1/organizers/{}/seatingplans/'.format(organizer.slug))
assert resp.status_code == 200
assert [res] == resp.data['results']
@pytest.mark.django_db
def test_plan_detail(token_client, organizer, event, seatingplan):
res = dict(TEST_PLAN_RES)
res["id"] = seatingplan.pk
resp = token_client.get('/api/v1/organizers/{}/seatingplans/{}/'.format(organizer.slug, seatingplan.pk))
assert resp.status_code == 200
assert res == resp.data
TEST_PLAN_CREATE_PAYLOAD = {
"name": "Plan 2",
"layout": json.loads(SAMPLE_PLAN)
}
@pytest.mark.django_db
def test_plan_create(token_client, organizer, event):
resp = token_client.post(
'/api/v1/organizers/{}/seatingplans/'.format(organizer.slug),
TEST_PLAN_CREATE_PAYLOAD,
format='json'
)
assert resp.status_code == 201
with scopes_disabled():
cl = SeatingPlan.objects.get(pk=resp.data['id'])
assert json.loads(cl.layout) == TEST_PLAN_CREATE_PAYLOAD['layout']
@pytest.mark.django_db
def test_plan_create_invalid_layout(token_client, organizer, event):
res = copy.copy(TEST_PLAN_CREATE_PAYLOAD)
res['layout'] = {'foo': 'bar'}
resp = token_client.post(
'/api/v1/organizers/{}/seatingplans/'.format(organizer.slug),
res,
format='json'
)
assert resp.status_code == 400
@pytest.mark.django_db
def test_plan_patch(token_client, organizer, event, seatingplan):
resp = token_client.patch(
'/api/v1/organizers/{}/seatingplans/{}/'.format(organizer.slug, seatingplan.pk),
{
'name': 'Foo'
},
format='json'
)
assert resp.status_code == 200
seatingplan.refresh_from_db()
assert seatingplan.name == "Foo"
@pytest.mark.django_db
def test_plan_delete(token_client, organizer, event, seatingplan):
resp = token_client.delete(
'/api/v1/organizers/{}/seatingplans/{}/'.format(organizer.slug, seatingplan.pk),
)
assert resp.status_code == 204
with scopes_disabled():
assert SeatingPlan.objects.count() == 0
@pytest.mark.django_db
def test_plan_patch_used(token_client, organizer, event, seatingplan):
event.seating_plan = seatingplan
event.save()
resp = token_client.patch(
'/api/v1/organizers/{}/seatingplans/{}/'.format(organizer.slug, seatingplan.pk),
{
'name': 'Foo'
},
format='json'
)
assert resp.status_code == 403
@pytest.mark.django_db
def test_plan_delete_used(token_client, organizer, event, seatingplan):
event.seating_plan = seatingplan
event.save()
resp = token_client.delete(
'/api/v1/organizers/{}/seatingplans/{}/'.format(organizer.slug, seatingplan.pk),
)
assert resp.status_code == 403
+258 -1
View File
@@ -7,7 +7,9 @@ from django_countries.fields import Country
from django_scopes import scopes_disabled
from pytz import UTC
from pretix.base.models import InvoiceAddress, Order, OrderPosition
from pretix.base.models import (
InvoiceAddress, Order, OrderPosition, SeatingPlan, SubEvent,
)
from pretix.base.models.orders import OrderFee
@@ -72,6 +74,8 @@ TEST_SUBEVENT_RES = {
'name': {'en': 'Foobar'},
'date_from': '2017-12-27T10:00:00Z',
'presale_end': None,
'seating_plan': None,
"seat_category_mapping": {},
'id': 1,
'variation_price_overrides': [],
'location': None,
@@ -573,3 +577,256 @@ def test_subevent_with_order_position_not_delete(token_client, organizer, event,
'orders. Please set \'active\' to false instead to hide it from users."}'
with scopes_disabled():
assert event.subevents.filter(pk=subevent.id).exists()
@pytest.fixture
def seatingplan(event, organizer, item):
return SeatingPlan.objects.create(
name="Plan", organizer=organizer, layout="""{
"name": "Grosser Saal",
"categories": [
{
"name": "Stalls",
"color": "red"
}
],
"zones": [
{
"name": "Main Area",
"position": {
"x": 0,
"y": 0
},
"rows": [
{
"row_number": "0",
"seats": [
{
"seat_guid": "0-0",
"seat_number": "0-0",
"position": {
"x": 0,
"y": 0
},
"category": "Stalls"
},
{
"seat_guid": "0-1",
"seat_number": "0-1",
"position": {
"x": 33,
"y": 0
},
"category": "Stalls"
},
{
"seat_guid": "0-2",
"seat_number": "0-2",
"position": {
"x": 66,
"y": 0
},
"category": "Stalls"
}
],
"position": {
"x": 0,
"y": 0
}
}
]
}
],
"size": {
"width": 600,
"height": 400
}
}"""
)
@pytest.mark.django_db
def test_subevent_update_seating(token_client, organizer, event, item, subevent, seatingplan):
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/subevents/{}/'.format(organizer.slug, event.slug, subevent.pk),
{
"seating_plan": seatingplan.pk,
"seat_category_mapping": {
"Stalls": item.pk
}
},
format='json'
)
assert resp.status_code == 200
subevent.refresh_from_db()
assert subevent.seating_plan == seatingplan
with scopes_disabled():
assert subevent.seats.count() == 3
assert subevent.seats.filter(product=item).count() == 3
m = subevent.seat_category_mappings.get()
assert m.layout_category == 'Stalls'
assert m.product == item
@pytest.mark.django_db
def test_subevent_update_seating_invalid_product(token_client, organizer, event, item, seatingplan, subevent):
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/subevents/{}/'.format(organizer.slug, event.slug, subevent.pk),
{
"seating_plan": seatingplan.pk,
"seat_category_mapping": {
"Stalls": item.pk + 2
}
},
format='json'
)
assert resp.status_code == 400
assert resp.content.decode() == '{"seat_category_mapping":["Item \'%d\' does not exist."]}' % (item.pk + 2)
@pytest.mark.django_db
def test_subevent_update_seating_change_mapping(token_client, organizer, event, item, seatingplan, subevent):
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/subevents/{}/'.format(organizer.slug, event.slug, subevent.pk),
{
"seating_plan": seatingplan.pk,
"seat_category_mapping": {
"Stalls": item.pk
}
},
format='json'
)
assert resp.status_code == 200
subevent.refresh_from_db()
assert subevent.seating_plan == seatingplan
with scopes_disabled():
assert subevent.seats.count() == 3
assert subevent.seats.filter(product=item).count() == 3
m = subevent.seat_category_mappings.get()
assert m.layout_category == 'Stalls'
assert m.product == item
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/subevents/{}/'.format(organizer.slug, event.slug, subevent.pk),
{
"seat_category_mapping": {
"VIP": item.pk,
}
},
format='json'
)
assert resp.status_code == 200
subevent.refresh_from_db()
assert subevent.seating_plan == seatingplan
with scopes_disabled():
assert subevent.seats.count() == 3
m = subevent.seat_category_mappings.get()
assert subevent.seats.filter(product=None).count() == 3
assert m.layout_category == 'VIP'
assert m.product == item
@pytest.mark.django_db
def test_remove_seating(token_client, organizer, event, item, seatingplan, subevent):
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/subevents/{}/'.format(organizer.slug, event.slug, subevent.pk),
{
"seating_plan": seatingplan.pk,
"seat_category_mapping": {
"Stalls": item.pk
}
},
format='json'
)
assert resp.status_code == 200
subevent.refresh_from_db()
assert subevent.seating_plan == seatingplan
with scopes_disabled():
assert subevent.seats.count() == 3
assert subevent.seat_category_mappings.count() == 1
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/subevents/{}/'.format(organizer.slug, event.slug, subevent.pk),
{
"seating_plan": None
},
format='json'
)
assert resp.status_code == 200
subevent.refresh_from_db()
assert subevent.seating_plan is None
with scopes_disabled():
assert subevent.seats.count() == 0
assert subevent.seat_category_mappings.count() == 0
@pytest.mark.django_db
def test_remove_seating_forbidden(token_client, organizer, event, item, seatingplan, order_position, subevent):
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/subevents/{}/'.format(organizer.slug, event.slug, subevent.pk),
{
"seating_plan": seatingplan.pk,
"seat_category_mapping": {
"Stalls": item.pk
}
},
format='json'
)
assert resp.status_code == 200
subevent.refresh_from_db()
assert subevent.seating_plan == seatingplan
with scopes_disabled():
assert subevent.seats.count() == 3
assert subevent.seat_category_mappings.count() == 1
order_position.seat = subevent.seats.first()
order_position.save()
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/subevents/{}/'.format(organizer.slug, event.slug, subevent.pk),
{
"seating_plan": None
},
format='json'
)
assert resp.status_code == 400
assert resp.content.decode() == '{"seating_plan":["You can not change the plan since seat \\"0-0\\" is not ' \
'present in the new plan and is already sold."]}'
@pytest.mark.django_db
def test_subevent_create_with_seating(token_client, organizer, event, subevent, item, seatingplan):
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/subevents/'.format(organizer.slug, event.slug),
{
"name": {
"de": "Demo Subevent 2020 Test",
"en": "Demo Subevent 2020 Test"
},
"active": False,
"date_from": "2017-12-27T10:00:00Z",
"date_to": "2017-12-28T10:00:00Z",
"date_admission": None,
"presale_start": None,
"presale_end": None,
"location": None,
"item_price_overrides": [
{
"item": item.pk,
"price": "23.42"
}
],
"variation_price_overrides": [],
"seat_category_mapping": {
"Stalls": item.pk
},
"meta_data": {},
"seating_plan": seatingplan.pk,
},
format='json'
)
assert resp.status_code == 201
with scopes_disabled():
subevent = SubEvent.objects.get(pk=resp.data['id'])
assert subevent.seats.count() == 3
assert subevent.seat_category_mappings.count() == 1