add support for check-in into overlapping events (#2039)

When events overlap, check-in only worked for the currently running event. If events run back-to-back, it can happen, that admission should start earlier and overlaps the currently running event. This checks if an overlapping event has started even if the current event is still running.
This commit is contained in:
Richard Schreiber
2021-04-26 13:13:21 +02:00
committed by GitHub
parent 8921ccb8c1
commit a0b3c70e2a
2 changed files with 53 additions and 21 deletions

View File

@@ -187,6 +187,16 @@ class EventSelectionView(APIView):
qs = qs.annotate(has_cl=Exists(has_cl)).filter(has_cl=True) qs = qs.annotate(has_cl=Exists(has_cl)).filter(has_cl=True)
return qs return qs
def _max_first_date_event(self, a, b):
if a and b:
return a if a.first_date > b.first_date else b
return a or b
def _min_first_date_event(self, a, b):
if a and b:
return a if a.first_date < b.first_date else b
return a or b
def get(self, request, format=None): def get(self, request, format=None):
device = request.auth device = request.auth
current_event = None current_event = None
@@ -200,27 +210,35 @@ class EventSelectionView(APIView):
if current_event: if current_event:
current_ev = current_subevent or current_event current_ev = current_subevent or current_event
else:
current_ev = None
# The event that is selected might not currently be running. We cannot rely on all events having a proper end date.
# Also, if events run back-to-back, the later event can overlap the earlier event due to its admission-time.
# In any case, we'll need to decide between the current event, the event that last started (and might still be running) and the
# event that starts next (and might already be letting people in), so let's get these as well!
# No matter if current event is given in query_params, always check whether another event already
# started overlaps can happen through e.g. admission-time overlapping or misconfig).
# Note that last_started here means either admission started or the event itself started.
last_started_ev = self._max_first_date_event(
self.base_event_qs.filter(first_date__lte=now()).last(),
self.base_subevent_qs.filter(first_date__lte=now()).last()
)
if last_started_ev and current_ev != last_started_ev and \
last_started_ev.date_to and now() < last_started_ev.date_to:
return self._suggest_event(current_event, last_started_ev)
if current_event:
current_ev_start = current_ev.date_admission or current_ev.date_from current_ev_start = current_ev.date_admission or current_ev.date_from
tz = current_event.timezone tz = current_event.timezone
if current_ev.date_to and current_ev_start < now() < current_ev.date_to: if current_ev.date_to and current_ev_start <= now() < current_ev.date_to:
# The event that is selected is currently running. Good enough. # The event that is selected is currently running. Good enough.
return Response(status=status.HTTP_304_NOT_MODIFIED) return Response(status=status.HTTP_304_NOT_MODIFIED)
# The event that is selected is not currently running. We cannot rely on all events having a proper end date. upcoming_ev = self._min_first_date_event(
# In any case, we'll need to decide between the event that last started (and might still be running) and the self.base_event_qs.filter(first_date__gt=now()).first(),
# event that starts next (and might already be letting people in), so let's get these two! self.base_subevent_qs.filter(first_date__gt=now()).first()
last_started_ev = self.base_event_qs.filter(first_date__lte=now()).last() or self.base_subevent_qs.filter( )
first_date__lte=now()).last()
upcoming_event = self.base_event_qs.filter(first_date__gt=now()).first()
upcoming_subevent = self.base_subevent_qs.filter(first_date__gt=now()).first()
if upcoming_event and upcoming_subevent:
if upcoming_event.first_date > upcoming_subevent.first_date:
upcoming_ev = upcoming_subevent
else:
upcoming_ev = upcoming_event
else:
upcoming_ev = upcoming_event or upcoming_subevent
if not upcoming_ev and not last_started_ev: if not upcoming_ev and not last_started_ev:
# Ooops, no events here # Ooops, no events here
@@ -232,12 +250,8 @@ class EventSelectionView(APIView):
# No event upcoming, so let's take the next one # No event upcoming, so let's take the next one
return self._suggest_event(current_event, last_started_ev) return self._suggest_event(current_event, last_started_ev)
if last_started_ev.date_to and now() < last_started_ev.date_to:
# The event that last started is currently running. Good enough.
return self._suggest_event(current_event, last_started_ev)
if not current_event: if not current_event:
tz = (upcoming_event or last_started_ev).timezone tz = (upcoming_ev or last_started_ev).timezone
lse_d = last_started_ev.date_from.astimezone(tz).date() lse_d = last_started_ev.date_from.astimezone(tz).date()
upc_d = upcoming_ev.date_from.astimezone(tz).date() upc_d = upcoming_ev.date_from.astimezone(tz).date()

View File

@@ -116,6 +116,15 @@ def test_choose_between_events(device_client, device):
assert resp.status_code == 200 assert resp.status_code == 200
assert resp.data['event']['slug'] == 'e2' assert resp.data['event']['slug'] == 'e2'
# check for overlapping events
e2.date_admission = tz.localize(datetime(2020, 1, 10, 14, 45))
e2.save()
with freeze_time("2020-01-10T14:45:00+09:00"):
resp = device_client.get(f'/api/v1/device/eventselection?current_event=e1')
assert resp.status_code == 200
resp = device_client.get(f'/api/v1/device/eventselection?current_event=e2')
assert resp.status_code == 304
@pytest.mark.django_db @pytest.mark.django_db
def test_choose_between_subevents(device_client, device): def test_choose_between_subevents(device_client, device):
@@ -207,6 +216,15 @@ def test_choose_between_subevents(device_client, device):
assert resp.data['event']['slug'] == 'e1' assert resp.data['event']['slug'] == 'e1'
assert resp.data['subevent'] == se2.pk assert resp.data['subevent'] == se2.pk
# check for overlapping events
se2.date_admission = tz.localize(datetime(2020, 1, 10, 14, 45))
se2.save()
with freeze_time("2020-01-10T14:45:00+09:00"):
resp = device_client.get(f'/api/v1/device/eventselection?current_event=e1&current_subevent={se1.pk}')
assert resp.status_code == 200
resp = device_client.get(f'/api/v1/device/eventselection?current_event=e1&current_subevent={se2.pk}')
assert resp.status_code == 304
@pytest.mark.django_db @pytest.mark.django_db
def test_require_gate(device_client, device): def test_require_gate(device_client, device):