Check-in API: Extend reach of "force" flag (#3187)

This commit is contained in:
Raphael Michel
2023-04-03 10:26:25 +02:00
committed by GitHub
parent 496e4c800a
commit 634445b79d
5 changed files with 135 additions and 69 deletions

View File

@@ -714,40 +714,53 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
# !!!!!!!!!
dt = datetime or now()
force_used = False
if op.canceled or op.order.status not in (Order.STATUS_PAID, Order.STATUS_PENDING):
raise CheckInError(
_('This order position has been canceled.'),
'canceled' if canceled_supported else 'unpaid'
)
if force:
force_used = True
else:
raise CheckInError(
_('This order position has been canceled.'),
'canceled' if canceled_supported else 'unpaid'
)
if op.blocked:
raise CheckInError(
_('This ticket has been blocked.'), # todo provide reason
'blocked'
)
if force:
force_used = True
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():
raise CheckInError(
_('This ticket is only valid after {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 force:
force_used = True
else:
raise CheckInError(
_('This ticket is only valid after {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():
raise CheckInError(
_('This ticket was only valid before {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')
),
)
if force:
force_used = True
else:
raise CheckInError(
_('This ticket was only valid before {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
checkin_questions = list(
@@ -770,40 +783,57 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
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()]:
raise CheckInError(
_('This order position has an invalid product for this check-in list.'),
'product'
)
elif clist.subevent_id and op.subevent_id != clist.subevent_id:
raise CheckInError(
_('This order position has an invalid date for this check-in list.'),
'product'
)
elif op.order.status != Order.STATUS_PAID and not force and op.order.require_approval:
raise CheckInError(
_('This order is not yet approved.'),
'unpaid'
)
elif op.order.status != Order.STATUS_PAID and not force and not op.order.valid_if_pending and not (
if force:
force_used = True
else:
raise CheckInError(
_('This order position has an invalid product for this check-in list.'),
'product'
)
if clist.subevent_id and op.subevent_id != clist.subevent_id:
if force:
force_used = True
else:
raise CheckInError(
_('This order position has an invalid date for this check-in list.'),
'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
):
raise CheckInError(
_('This order is not marked as paid.'),
'unpaid'
)
if force:
force_used = True
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)
logic = _get_logic_environment(op.subevent or clist.event)
if not logic.apply(clist.rules, rule_data):
reason = _logic_explain(clist.rules, op.subevent or clist.event, rule_data)
raise CheckInError(
_('Entry not permitted: {explanation}.').format(
explanation=reason
),
'rules',
reason=reason
)
if force:
force_used = True
else:
reason = _logic_explain(clist.rules, op.subevent or clist.event, rule_data)
raise CheckInError(
_('Entry not permitted: {explanation}.').format(
explanation=reason
),
'rules',
reason=reason
)
if require_answers and not force and questions_supported:
raise RequiredQuestionsError(
@@ -837,7 +867,7 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
device=device,
gate=device.gate if device else None,
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,
raw_barcode=raw_barcode,
)

View File

@@ -356,6 +356,24 @@ def test_forced_multiple(token_client, organizer, clist, event, order):
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
def test_forced_flag_set_if_required(token_client, organizer, clist, event, order):
with scopes_disabled():

View File

@@ -100,6 +100,8 @@ def test_checkin_canceled_order(position, clist):
perform_checkin(position, clist, {}, canceled_supported=True)
assert excinfo.value.code == 'canceled'
assert position.checkins.count() == 0
perform_checkin(position, clist, {}, canceled_supported=True, force=True)
assert position.checkins.count() == 1
@pytest.mark.django_db
@@ -127,6 +129,8 @@ def test_checkin_blocked_position(position, clist):
perform_checkin(position, clist, {}, type=Checkin.TYPE_EXIT)
assert excinfo.value.code == 'blocked'
assert position.checkins.count() == 0
perform_checkin(position, clist, {}, type=Checkin.TYPE_EXIT, force=True)
assert position.checkins.count() == 1
@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.reason == 'This ticket is only valid after 2020-01-01 12:00.'
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)
assert position.checkins.count() == 1
assert position.checkins.count() == 2
@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.reason == 'This ticket was only valid before 2020-01-01 09:00.'
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)
assert position.checkins.count() == 1
assert position.checkins.count() == 2
@pytest.mark.django_db
def test_checkin_invalid_product(position, clist):
clist.all_products = False
clist.allow_multiple_entries = True
clist.save()
with pytest.raises(CheckInError) as excinfo:
perform_checkin(position, clist, {})
assert excinfo.value.code == 'product'
perform_checkin(position, clist, {}, force=True)
clist.limit_products.add(position.item)
perform_checkin(position, clist, {})
@@ -185,6 +199,8 @@ def test_checkin_invalid_subevent(position, clist, event):
perform_checkin(position, clist, {})
assert excinfo.value.code == 'product'
perform_checkin(position, clist, {}, force=True)
@pytest.mark.django_db
def test_checkin_all_subevents(position, clist, event):
@@ -228,6 +244,8 @@ def test_require_approval(position, clist):
with pytest.raises(CheckInError) as excinfo:
perform_checkin(position, clist, {}, ignore_unpaid=True)
assert excinfo.value.code == 'unpaid'
perform_checkin(position, clist, {}, ignore_unpaid=True, force=True)
assert position.checkins.count() == 1
@pytest.mark.django_db