mirror of
https://github.com/pretix/pretix.git
synced 2026-09-15 16:34:42 +00:00
Check-in API: Extend reach of "force" flag (#3187)
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 177 KiB After Width: | Height: | Size: 180 KiB |
@@ -38,27 +38,27 @@ else
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
"Is the order in status PAID or PENDING\nand is the position not canceled?" --> if "" then
|
"Is the order in status PAID or PENDING\nand is the position not canceled?" --> if "" then
|
||||||
-right->[no] "Return error CANCELED"
|
-right->[no && !force] "Return error CANCELED"
|
||||||
else
|
else
|
||||||
-down->[yes] "Is one or more block set on the ticket?"
|
-down->[yes || force] "Is one or more block set on the ticket?"
|
||||||
--> if "" then
|
--> if "" then
|
||||||
-right->[no] "Return error BLOCKED"
|
-right->[no && !force] "Return error BLOCKED"
|
||||||
else
|
else
|
||||||
-down->[yes] "If this is not an exit, is the valid_from/valid_until\nconstraint on the ticket fulfilled?"
|
-down->[yes || force] "If this is not an exit, is the valid_from/valid_until\nconstraint on the ticket fulfilled?"
|
||||||
--> if "" then
|
--> if "" then
|
||||||
-right->[no] "Return error INVALID_TIME"
|
-right->[no && !force] "Return error INVALID_TIME"
|
||||||
else
|
else
|
||||||
-down->[yes] "Is the product part of the check-in list?"
|
-down->[yes || force] "Is the product part of the check-in list?"
|
||||||
--> if "" then
|
--> if "" then
|
||||||
-right->[no] "Return error PRODUCT"
|
-right->[no && !force] "Return error PRODUCT"
|
||||||
else
|
else
|
||||||
-down->[yes] "Is the subevent part of the check-in list?"
|
-down->[yes || force] "Is the subevent part of the check-in list?"
|
||||||
--> if "" then
|
--> if "" then
|
||||||
-right->[no] "Return error PRODUCT "
|
-right->[no && !force] "Return error PRODUCT "
|
||||||
else
|
else
|
||||||
-down->[yes] "Is the order in status PAID\nor is this a forced upload?"
|
-down->[yes] "Is the order in status PAID?"
|
||||||
--> if "" then
|
--> if "" then
|
||||||
-right->[no] "Is Order.require_approval set?"
|
-right->[no && !force] "Is Order.require_approval set?"
|
||||||
--> if "" then
|
--> if "" then
|
||||||
-->[no] "Is Order.valid_if_pending set?"
|
-->[no] "Is Order.valid_if_pending set?"
|
||||||
--> if "" then
|
--> if "" then
|
||||||
@@ -80,7 +80,7 @@ else
|
|||||||
-->[yes] "Return error UNPAID "
|
-->[yes] "Return error UNPAID "
|
||||||
endif
|
endif
|
||||||
else
|
else
|
||||||
-down->[yes] "Is this an entry or exit?\nIs the upload forced?"
|
-down->[yes || force] "Is this an entry or exit?\nIs the upload forced?"
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|||||||
@@ -714,40 +714,53 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
|
|||||||
# !!!!!!!!!
|
# !!!!!!!!!
|
||||||
|
|
||||||
dt = datetime or now()
|
dt = datetime or now()
|
||||||
|
force_used = False
|
||||||
|
|
||||||
if op.canceled or op.order.status not in (Order.STATUS_PAID, Order.STATUS_PENDING):
|
if op.canceled or op.order.status not in (Order.STATUS_PAID, Order.STATUS_PENDING):
|
||||||
raise CheckInError(
|
if force:
|
||||||
_('This order position has been canceled.'),
|
force_used = True
|
||||||
'canceled' if canceled_supported else 'unpaid'
|
else:
|
||||||
)
|
raise CheckInError(
|
||||||
|
_('This order position has been canceled.'),
|
||||||
|
'canceled' if canceled_supported else 'unpaid'
|
||||||
|
)
|
||||||
|
|
||||||
if op.blocked:
|
if op.blocked:
|
||||||
raise CheckInError(
|
if force:
|
||||||
_('This ticket has been blocked.'), # todo provide reason
|
force_used = True
|
||||||
'blocked'
|
else:
|
||||||
)
|
raise CheckInError(
|
||||||
|
_('This ticket has been blocked.'), # todo provide reason
|
||||||
|
'blocked'
|
||||||
|
)
|
||||||
|
|
||||||
if type != Checkin.TYPE_EXIT and op.valid_from and op.valid_from > now():
|
if type != Checkin.TYPE_EXIT and op.valid_from and op.valid_from > now():
|
||||||
raise CheckInError(
|
if force:
|
||||||
_('This ticket is only valid after {datetime}.').format(
|
force_used = True
|
||||||
datetime=date_format(op.valid_from, 'SHORT_DATETIME_FORMAT')
|
else:
|
||||||
),
|
raise CheckInError(
|
||||||
'invalid_time',
|
_('This ticket is only valid after {datetime}.').format(
|
||||||
_('This ticket is only valid after {datetime}.').format(
|
datetime=date_format(op.valid_from, 'SHORT_DATETIME_FORMAT')
|
||||||
datetime=date_format(op.valid_from, 'SHORT_DATETIME_FORMAT')
|
),
|
||||||
),
|
'invalid_time',
|
||||||
)
|
_('This ticket is only valid after {datetime}.').format(
|
||||||
|
datetime=date_format(op.valid_from, 'SHORT_DATETIME_FORMAT')
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
if type != Checkin.TYPE_EXIT and op.valid_until and op.valid_until < now():
|
if type != Checkin.TYPE_EXIT and op.valid_until and op.valid_until < now():
|
||||||
raise CheckInError(
|
if force:
|
||||||
_('This ticket was only valid before {datetime}.').format(
|
force_used = True
|
||||||
datetime=date_format(op.valid_until, 'SHORT_DATETIME_FORMAT')
|
else:
|
||||||
),
|
raise CheckInError(
|
||||||
'invalid_time',
|
_('This ticket was only valid before {datetime}.').format(
|
||||||
_('This ticket was only valid before {datetime}.').format(
|
datetime=date_format(op.valid_until, 'SHORT_DATETIME_FORMAT')
|
||||||
datetime=date_format(op.valid_until, 'SHORT_DATETIME_FORMAT')
|
),
|
||||||
),
|
'invalid_time',
|
||||||
)
|
_('This ticket was only valid before {datetime}.').format(
|
||||||
|
datetime=date_format(op.valid_until, 'SHORT_DATETIME_FORMAT')
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# Do this outside of transaction so it is saved even if the checkin fails for some other reason
|
# Do this outside of transaction so it is saved even if the checkin fails for some other reason
|
||||||
checkin_questions = list(
|
checkin_questions = list(
|
||||||
@@ -770,40 +783,57 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
|
|||||||
op = opqs.get(pk=op.pk)
|
op = opqs.get(pk=op.pk)
|
||||||
|
|
||||||
if not clist.all_products and op.item_id not in [i.pk for i in clist.limit_products.all()]:
|
if not clist.all_products and op.item_id not in [i.pk for i in clist.limit_products.all()]:
|
||||||
raise CheckInError(
|
if force:
|
||||||
_('This order position has an invalid product for this check-in list.'),
|
force_used = True
|
||||||
'product'
|
else:
|
||||||
)
|
raise CheckInError(
|
||||||
elif clist.subevent_id and op.subevent_id != clist.subevent_id:
|
_('This order position has an invalid product for this check-in list.'),
|
||||||
raise CheckInError(
|
'product'
|
||||||
_('This order position has an invalid date for this check-in list.'),
|
)
|
||||||
'product'
|
|
||||||
)
|
if clist.subevent_id and op.subevent_id != clist.subevent_id:
|
||||||
elif op.order.status != Order.STATUS_PAID and not force and op.order.require_approval:
|
if force:
|
||||||
raise CheckInError(
|
force_used = True
|
||||||
_('This order is not yet approved.'),
|
else:
|
||||||
'unpaid'
|
raise CheckInError(
|
||||||
)
|
_('This order position has an invalid date for this check-in list.'),
|
||||||
elif op.order.status != Order.STATUS_PAID and not force and not op.order.valid_if_pending and not (
|
'product'
|
||||||
|
)
|
||||||
|
|
||||||
|
if op.order.status != Order.STATUS_PAID and op.order.require_approval:
|
||||||
|
if force:
|
||||||
|
force_used = True
|
||||||
|
else:
|
||||||
|
raise CheckInError(
|
||||||
|
_('This order is not yet approved.'),
|
||||||
|
'unpaid'
|
||||||
|
)
|
||||||
|
elif op.order.status != Order.STATUS_PAID and not op.order.valid_if_pending and not (
|
||||||
ignore_unpaid and clist.include_pending and op.order.status == Order.STATUS_PENDING
|
ignore_unpaid and clist.include_pending and op.order.status == Order.STATUS_PENDING
|
||||||
):
|
):
|
||||||
raise CheckInError(
|
if force:
|
||||||
_('This order is not marked as paid.'),
|
force_used = True
|
||||||
'unpaid'
|
else:
|
||||||
)
|
raise CheckInError(
|
||||||
|
_('This order is not marked as paid.'),
|
||||||
|
'unpaid'
|
||||||
|
)
|
||||||
|
|
||||||
if type == Checkin.TYPE_ENTRY and clist.rules and not force:
|
if type == Checkin.TYPE_ENTRY and clist.rules:
|
||||||
rule_data = LazyRuleVars(op, clist, dt)
|
rule_data = LazyRuleVars(op, clist, dt)
|
||||||
logic = _get_logic_environment(op.subevent or clist.event)
|
logic = _get_logic_environment(op.subevent or clist.event)
|
||||||
if not logic.apply(clist.rules, rule_data):
|
if not logic.apply(clist.rules, rule_data):
|
||||||
reason = _logic_explain(clist.rules, op.subevent or clist.event, rule_data)
|
if force:
|
||||||
raise CheckInError(
|
force_used = True
|
||||||
_('Entry not permitted: {explanation}.').format(
|
else:
|
||||||
explanation=reason
|
reason = _logic_explain(clist.rules, op.subevent or clist.event, rule_data)
|
||||||
),
|
raise CheckInError(
|
||||||
'rules',
|
_('Entry not permitted: {explanation}.').format(
|
||||||
reason=reason
|
explanation=reason
|
||||||
)
|
),
|
||||||
|
'rules',
|
||||||
|
reason=reason
|
||||||
|
)
|
||||||
|
|
||||||
if require_answers and not force and questions_supported:
|
if require_answers and not force and questions_supported:
|
||||||
raise RequiredQuestionsError(
|
raise RequiredQuestionsError(
|
||||||
@@ -837,7 +867,7 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
|
|||||||
device=device,
|
device=device,
|
||||||
gate=device.gate if device else None,
|
gate=device.gate if device else None,
|
||||||
nonce=nonce,
|
nonce=nonce,
|
||||||
forced=force and (not entry_allowed or from_revoked_secret),
|
forced=force and (not entry_allowed or from_revoked_secret or force_used),
|
||||||
force_sent=force,
|
force_sent=force,
|
||||||
raw_barcode=raw_barcode,
|
raw_barcode=raw_barcode,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -356,6 +356,24 @@ def test_forced_multiple(token_client, organizer, clist, event, order):
|
|||||||
assert resp.data['status'] == 'ok'
|
assert resp.data['status'] == 'ok'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_forced_canceled(token_client, organizer, clist, event, order):
|
||||||
|
order.status = Order.STATUS_CANCELED
|
||||||
|
order.save()
|
||||||
|
with scopes_disabled():
|
||||||
|
p = order.positions.first()
|
||||||
|
resp = _redeem(token_client, organizer, clist, p.secret, {})
|
||||||
|
assert resp.status_code == 400
|
||||||
|
assert resp.data['status'] == 'error'
|
||||||
|
resp = _redeem(token_client, organizer, clist, p.secret, {'force': True})
|
||||||
|
assert resp.status_code == 201
|
||||||
|
assert resp.data['status'] == 'ok'
|
||||||
|
with scopes_disabled():
|
||||||
|
ci = p.checkins.get()
|
||||||
|
assert ci.force_sent
|
||||||
|
assert ci.forced
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_forced_flag_set_if_required(token_client, organizer, clist, event, order):
|
def test_forced_flag_set_if_required(token_client, organizer, clist, event, order):
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ def test_checkin_canceled_order(position, clist):
|
|||||||
perform_checkin(position, clist, {}, canceled_supported=True)
|
perform_checkin(position, clist, {}, canceled_supported=True)
|
||||||
assert excinfo.value.code == 'canceled'
|
assert excinfo.value.code == 'canceled'
|
||||||
assert position.checkins.count() == 0
|
assert position.checkins.count() == 0
|
||||||
|
perform_checkin(position, clist, {}, canceled_supported=True, force=True)
|
||||||
|
assert position.checkins.count() == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -127,6 +129,8 @@ def test_checkin_blocked_position(position, clist):
|
|||||||
perform_checkin(position, clist, {}, type=Checkin.TYPE_EXIT)
|
perform_checkin(position, clist, {}, type=Checkin.TYPE_EXIT)
|
||||||
assert excinfo.value.code == 'blocked'
|
assert excinfo.value.code == 'blocked'
|
||||||
assert position.checkins.count() == 0
|
assert position.checkins.count() == 0
|
||||||
|
perform_checkin(position, clist, {}, type=Checkin.TYPE_EXIT, force=True)
|
||||||
|
assert position.checkins.count() == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -139,9 +143,12 @@ def test_checkin_valid_from(event, position, clist):
|
|||||||
assert excinfo.value.code == 'invalid_time'
|
assert excinfo.value.code == 'invalid_time'
|
||||||
assert excinfo.value.reason == 'This ticket is only valid after 2020-01-01 12:00.'
|
assert excinfo.value.reason == 'This ticket is only valid after 2020-01-01 12:00.'
|
||||||
assert position.checkins.count() == 0
|
assert position.checkins.count() == 0
|
||||||
|
# Force is allowed
|
||||||
|
perform_checkin(position, clist, {}, force=True)
|
||||||
|
assert position.checkins.count() == 1
|
||||||
|
|
||||||
perform_checkin(position, clist, {}, type=Checkin.TYPE_EXIT)
|
perform_checkin(position, clist, {}, type=Checkin.TYPE_EXIT)
|
||||||
assert position.checkins.count() == 1
|
assert position.checkins.count() == 2
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -154,18 +161,25 @@ def test_checkin_valid_until(event, position, clist):
|
|||||||
assert excinfo.value.code == 'invalid_time'
|
assert excinfo.value.code == 'invalid_time'
|
||||||
assert excinfo.value.reason == 'This ticket was only valid before 2020-01-01 09:00.'
|
assert excinfo.value.reason == 'This ticket was only valid before 2020-01-01 09:00.'
|
||||||
assert position.checkins.count() == 0
|
assert position.checkins.count() == 0
|
||||||
|
# Force is allowed
|
||||||
|
perform_checkin(position, clist, {}, force=True)
|
||||||
|
assert position.checkins.count() == 1
|
||||||
|
|
||||||
perform_checkin(position, clist, {}, type=Checkin.TYPE_EXIT)
|
perform_checkin(position, clist, {}, type=Checkin.TYPE_EXIT)
|
||||||
assert position.checkins.count() == 1
|
assert position.checkins.count() == 2
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_checkin_invalid_product(position, clist):
|
def test_checkin_invalid_product(position, clist):
|
||||||
clist.all_products = False
|
clist.all_products = False
|
||||||
|
clist.allow_multiple_entries = True
|
||||||
clist.save()
|
clist.save()
|
||||||
with pytest.raises(CheckInError) as excinfo:
|
with pytest.raises(CheckInError) as excinfo:
|
||||||
perform_checkin(position, clist, {})
|
perform_checkin(position, clist, {})
|
||||||
assert excinfo.value.code == 'product'
|
assert excinfo.value.code == 'product'
|
||||||
|
|
||||||
|
perform_checkin(position, clist, {}, force=True)
|
||||||
|
|
||||||
clist.limit_products.add(position.item)
|
clist.limit_products.add(position.item)
|
||||||
perform_checkin(position, clist, {})
|
perform_checkin(position, clist, {})
|
||||||
|
|
||||||
@@ -185,6 +199,8 @@ def test_checkin_invalid_subevent(position, clist, event):
|
|||||||
perform_checkin(position, clist, {})
|
perform_checkin(position, clist, {})
|
||||||
assert excinfo.value.code == 'product'
|
assert excinfo.value.code == 'product'
|
||||||
|
|
||||||
|
perform_checkin(position, clist, {}, force=True)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_checkin_all_subevents(position, clist, event):
|
def test_checkin_all_subevents(position, clist, event):
|
||||||
@@ -228,6 +244,8 @@ def test_require_approval(position, clist):
|
|||||||
with pytest.raises(CheckInError) as excinfo:
|
with pytest.raises(CheckInError) as excinfo:
|
||||||
perform_checkin(position, clist, {}, ignore_unpaid=True)
|
perform_checkin(position, clist, {}, ignore_unpaid=True)
|
||||||
assert excinfo.value.code == 'unpaid'
|
assert excinfo.value.code == 'unpaid'
|
||||||
|
perform_checkin(position, clist, {}, ignore_unpaid=True, force=True)
|
||||||
|
assert position.checkins.count() == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
|
|||||||
Reference in New Issue
Block a user