forked from CGM_Public/pretix_original
Add event selection endpoint (#1827)
* Add event selection endpoint * Minor fixes * Add filter by gate
This commit is contained in:
@@ -22,8 +22,10 @@ class PretixScanSecurityProfile(AllowListSecurityProfile):
|
||||
verbose_name = _('pretixSCAN')
|
||||
allowlist = (
|
||||
('GET', 'api-v1:version'),
|
||||
('GET', 'api-v1:device.eventselection'),
|
||||
('POST', 'api-v1:device.update'),
|
||||
('POST', 'api-v1:device.revoke'),
|
||||
('POST', 'api-v1:device.roll'),
|
||||
('GET', 'api-v1:event-list'),
|
||||
('GET', 'api-v1:event-detail'),
|
||||
('GET', 'api-v1:subevent-list'),
|
||||
@@ -48,8 +50,10 @@ class PretixScanNoSyncSecurityProfile(AllowListSecurityProfile):
|
||||
verbose_name = _('pretixSCAN (kiosk mode, online only)')
|
||||
allowlist = (
|
||||
('GET', 'api-v1:version'),
|
||||
('GET', 'api-v1:device.eventselection'),
|
||||
('POST', 'api-v1:device.update'),
|
||||
('POST', 'api-v1:device.revoke'),
|
||||
('POST', 'api-v1:device.roll'),
|
||||
('GET', 'api-v1:event-list'),
|
||||
('GET', 'api-v1:event-detail'),
|
||||
('GET', 'api-v1:subevent-list'),
|
||||
@@ -72,8 +76,10 @@ class PretixPosSecurityProfile(AllowListSecurityProfile):
|
||||
verbose_name = _('pretixPOS')
|
||||
allowlist = (
|
||||
('GET', 'api-v1:version'),
|
||||
('GET', 'api-v1:device.eventselection'),
|
||||
('POST', 'api-v1:device.update'),
|
||||
('POST', 'api-v1:device.revoke'),
|
||||
('POST', 'api-v1:device.roll'),
|
||||
('GET', 'api-v1:event-list'),
|
||||
('GET', 'api-v1:event-detail'),
|
||||
('GET', 'api-v1:subevent-list'),
|
||||
|
||||
@@ -86,6 +86,7 @@ urlpatterns = [
|
||||
url(r"^device/update$", device.UpdateView.as_view(), name="device.update"),
|
||||
url(r"^device/roll$", device.RollKeyView.as_view(), name="device.roll"),
|
||||
url(r"^device/revoke$", device.RevokeKeyView.as_view(), name="device.revoke"),
|
||||
url(r"^device/eventselection$", device.EventSelectionView.as_view(), name="device.eventselection"),
|
||||
url(r"^me$", user.MeView.as_view(), name="user.me"),
|
||||
url(r"^version$", version.VersionView.as_view(), name="version"),
|
||||
]
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import logging
|
||||
|
||||
from django.db.models import Exists, OuterRef, Q
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.utils.timezone import now
|
||||
from rest_framework import serializers
|
||||
from rest_framework import serializers, status
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from pretix.api.auth.device import DeviceTokenAuthentication
|
||||
from pretix.base.models import Device
|
||||
from pretix.base.models import CheckinList, Device, SubEvent
|
||||
from pretix.base.models.devices import Gate, generate_api_token
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -122,3 +124,156 @@ class RevokeKeyView(APIView):
|
||||
|
||||
serializer = DeviceSerializer(device)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class EventSelectionView(APIView):
|
||||
authentication_classes = (DeviceTokenAuthentication,)
|
||||
|
||||
@property
|
||||
def base_event_qs(self):
|
||||
qs = self.request.auth.organizer.events.annotate(
|
||||
first_date=Coalesce('date_admission', 'date_from'),
|
||||
last_date=Coalesce('date_to', 'date_from'),
|
||||
).filter(
|
||||
live=True,
|
||||
has_subevents=False
|
||||
).order_by('first_date')
|
||||
if self.request.auth.gate:
|
||||
has_cl = CheckinList.objects.filter(
|
||||
event=OuterRef('pk'),
|
||||
gates__in=[self.request.auth.gate]
|
||||
)
|
||||
qs = qs.annotate(has_cl=Exists(has_cl)).filter(has_cl=True)
|
||||
return qs
|
||||
|
||||
@property
|
||||
def base_subevent_qs(self):
|
||||
qs = SubEvent.objects.annotate(
|
||||
first_date=Coalesce('date_admission', 'date_from'),
|
||||
last_date=Coalesce('date_to', 'date_from'),
|
||||
).filter(
|
||||
event__organizer=self.request.auth.organizer,
|
||||
event__live=True,
|
||||
active=True,
|
||||
).select_related('event').order_by('first_date')
|
||||
if self.request.auth.gate:
|
||||
has_cl = CheckinList.objects.filter(
|
||||
Q(subevent__isnull=True) | Q(subevent=OuterRef('pk')),
|
||||
event_id=OuterRef('event_id'),
|
||||
gates__in=[self.request.auth.gate]
|
||||
)
|
||||
qs = qs.annotate(has_cl=Exists(has_cl)).filter(has_cl=True)
|
||||
return qs
|
||||
|
||||
def get(self, request, format=None):
|
||||
device = request.auth
|
||||
current_event = None
|
||||
current_subevent = None
|
||||
if 'current_event' in request.query_params:
|
||||
current_event = device.organizer.events.filter(slug=request.query_params['current_event']).first()
|
||||
if current_event and 'current_subevent' in request.query_params:
|
||||
current_subevent = current_event.subevents.filter(pk=request.query_params['current_subevent']).first()
|
||||
if current_event and current_event.has_subevents and not current_subevent:
|
||||
current_event = None
|
||||
|
||||
if current_event:
|
||||
current_ev = current_subevent or current_event
|
||||
current_ev_start = current_ev.date_admission or current_ev.date_from
|
||||
tz = current_event.timezone
|
||||
if current_ev.date_to and current_ev_start < now() < current_ev.date_to:
|
||||
# The event that is selected is currently running. Good enough.
|
||||
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.
|
||||
# In any case, we'll need to decide between 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 two!
|
||||
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:
|
||||
# Ooops, no events here
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
elif upcoming_ev and not last_started_ev:
|
||||
# No event running, so let's take the next one
|
||||
return self._suggest_event(current_event, upcoming_ev)
|
||||
elif last_started_ev and not upcoming_ev:
|
||||
# No event upcoming, so let's take the next one
|
||||
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:
|
||||
tz = (upcoming_event or last_started_ev).timezone
|
||||
|
||||
lse_d = last_started_ev.date_from.astimezone(tz).date()
|
||||
upc_d = upcoming_ev.date_from.astimezone(tz).date()
|
||||
now_d = now().astimezone(tz).date()
|
||||
if lse_d == now_d and upc_d != now_d:
|
||||
# Last event was today, next is tomorrow, stick with today
|
||||
return self._suggest_event(current_event, last_started_ev)
|
||||
elif lse_d != now_d and upc_d == now_d:
|
||||
# Last event was yesterday, next is today, stick with today
|
||||
return self._suggest_event(current_event, upcoming_ev)
|
||||
|
||||
# Both last and next event are today, we switch over in the middle
|
||||
if now() > last_started_ev.last_date + (upcoming_ev.first_date - last_started_ev.last_date) / 2:
|
||||
return self._suggest_event(current_event, upcoming_ev)
|
||||
else:
|
||||
return self._suggest_event(current_event, last_started_ev)
|
||||
|
||||
def _suggest_event(self, current_event, ev):
|
||||
current_checkinlist = None
|
||||
if current_event and 'current_checkinlist' in self.request.query_params:
|
||||
current_checkinlist = current_event.checkin_lists.filter(
|
||||
pk=self.request.query_params['current_checkinlist']
|
||||
).first()
|
||||
if isinstance(ev, SubEvent):
|
||||
checkinlist_qs = ev.event.checkin_lists.filter(Q(subevent__isnull=True) | Q(subevent=ev))
|
||||
else:
|
||||
checkinlist_qs = ev.checkin_lists
|
||||
|
||||
if self.request.auth.gate:
|
||||
checkinlist_qs = checkinlist_qs.filter(gates__in=[self.request.auth.gate])
|
||||
|
||||
checkinlist = None
|
||||
if current_checkinlist:
|
||||
checkinlist = checkinlist_qs.filter(Q(name=current_checkinlist.name) | Q(pk=current_checkinlist.pk)).first()
|
||||
if not checkinlist:
|
||||
checkinlist = checkinlist_qs.first()
|
||||
r = {
|
||||
'event': {
|
||||
'slug': ev.event.slug if isinstance(ev, SubEvent) else ev.slug,
|
||||
'name': str(ev.event.name) if isinstance(ev, SubEvent) else str(ev.name),
|
||||
},
|
||||
'subevent': ev.pk if isinstance(ev, SubEvent) else None,
|
||||
'checkinlist': checkinlist.pk if checkinlist else None,
|
||||
}
|
||||
|
||||
if r == {
|
||||
'event': {
|
||||
'slug': current_event.slug if current_event else None,
|
||||
'name': str(current_event.name) if current_event else None,
|
||||
},
|
||||
'subevent': (
|
||||
int(self.request.query_params.get('current_subevent'))
|
||||
if self.request.query_params.get('current_subevent') else None
|
||||
),
|
||||
'checkinlist': (
|
||||
int(self.request.query_params.get('current_checkinlist'))
|
||||
if self.request.query_params.get('current_checkinlist') else None
|
||||
),
|
||||
}:
|
||||
return Response(status=status.HTTP_304_NOT_MODIFIED)
|
||||
return Response(r)
|
||||
|
||||
18
src/pretix/base/migrations/0169_checkinlist_gates.py
Normal file
18
src/pretix/base/migrations/0169_checkinlist_gates.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.0.10 on 2020-10-24 15:55
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0168_auto_20201023_1447'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='checkinlist',
|
||||
name='gates',
|
||||
field=models.ManyToManyField(to='pretixbase.Gate'),
|
||||
),
|
||||
]
|
||||
@@ -21,6 +21,11 @@ class CheckinList(LoggedModel):
|
||||
default=False,
|
||||
help_text=_('With this option, people will be able to check in even if the '
|
||||
'order have not been paid.'))
|
||||
gates = models.ManyToManyField(
|
||||
'Gate', verbose_name=_("Gates"), blank=True,
|
||||
help_text=_("Does not have any effect for the validation of tickets, only for the automatic configuration of "
|
||||
"check-in devices.")
|
||||
)
|
||||
allow_entry_after_exit = models.BooleanField(
|
||||
verbose_name=_('Allow re-entering after an exit scan'),
|
||||
default=True
|
||||
|
||||
@@ -44,6 +44,11 @@ class CheckinListForm(forms.ModelForm):
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
if not self.event.organizer.gates.exists():
|
||||
del self.fields['gates']
|
||||
else:
|
||||
self.fields['gates'].queryset = self.event.organizer.gates.all()
|
||||
|
||||
if self.event.has_subevents:
|
||||
self.fields['subevent'].queryset = self.event.subevents.all()
|
||||
self.fields['subevent'].widget = Select2(
|
||||
@@ -73,17 +78,22 @@ class CheckinListForm(forms.ModelForm):
|
||||
'allow_multiple_entries',
|
||||
'allow_entry_after_exit',
|
||||
'rules',
|
||||
'gates',
|
||||
'exit_all_at',
|
||||
]
|
||||
widgets = {
|
||||
'limit_products': forms.CheckboxSelectMultiple(attrs={
|
||||
'data-inverse-dependency': '<[name$=all_products]'
|
||||
}),
|
||||
'gates': forms.CheckboxSelectMultiple(attrs={
|
||||
'class': 'scrolling-multiple-choice'
|
||||
}),
|
||||
'auto_checkin_sales_channels': forms.CheckboxSelectMultiple(),
|
||||
'exit_all_at': forms.TimeInput(attrs={'class': 'timepickerfield'}),
|
||||
}
|
||||
field_classes = {
|
||||
'limit_products': SafeModelMultipleChoiceField,
|
||||
'gates': SafeModelMultipleChoiceField,
|
||||
'subevent': SafeModelChoiceField,
|
||||
'exit_all_at': NextTimeField,
|
||||
}
|
||||
@@ -96,6 +106,11 @@ class SimpleCheckinListForm(forms.ModelForm):
|
||||
super().__init__(**kwargs)
|
||||
self.fields['limit_products'].queryset = self.event.items.all()
|
||||
|
||||
if not self.event.organizer.gates.exists():
|
||||
del self.fields['gates']
|
||||
else:
|
||||
self.fields['gates'].queryset = self.event.organizer.gates.all()
|
||||
|
||||
class Meta:
|
||||
model = CheckinList
|
||||
localized_fields = '__all__'
|
||||
@@ -105,13 +120,18 @@ class SimpleCheckinListForm(forms.ModelForm):
|
||||
'limit_products',
|
||||
'include_pending',
|
||||
'allow_entry_after_exit',
|
||||
'gates',
|
||||
]
|
||||
widgets = {
|
||||
'limit_products': forms.CheckboxSelectMultiple(attrs={
|
||||
'data-inverse-dependency': '<[name$=all_products]'
|
||||
}),
|
||||
'gates': forms.CheckboxSelectMultiple(attrs={
|
||||
'class': 'scrolling-multiple-choice'
|
||||
}),
|
||||
}
|
||||
field_classes = {
|
||||
'limit_products': SafeModelMultipleChoiceField,
|
||||
'subevent': SafeModelChoiceField,
|
||||
'gates': SafeModelMultipleChoiceField,
|
||||
}
|
||||
|
||||
@@ -60,6 +60,9 @@
|
||||
{% bootstrap_field form.allow_entry_after_exit layout="control" %}
|
||||
{% bootstrap_field form.exit_all_at layout="control" %}
|
||||
{% bootstrap_field form.auto_checkin_sales_channels layout="control" %}
|
||||
{% if form.gates %}
|
||||
{% bootstrap_field form.gates layout="control" %}
|
||||
{% endif %}
|
||||
|
||||
<h3>{% trans "Custom check-in rule" %}</h3>
|
||||
<div id="rules-editor" class="form-inline">
|
||||
|
||||
@@ -509,6 +509,9 @@
|
||||
{% bootstrap_field form.all_products layout="control" %}
|
||||
{% bootstrap_field form.limit_products layout="control" %}
|
||||
{% bootstrap_field form.allow_entry_after_exit layout="control" %}
|
||||
{% if form.gates %}
|
||||
{% bootstrap_field form.gates layout="control" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -538,6 +541,9 @@
|
||||
{% bootstrap_field cl_formset.empty_form.all_products layout="control" %}
|
||||
{% bootstrap_field cl_formset.empty_form.limit_products layout="control" %}
|
||||
{% bootstrap_field cl_formset.empty_form.allow_entry_after_exit layout="control" %}
|
||||
{% if cl_formset.empty_form.gates %}
|
||||
{% bootstrap_field cl_formset.empty_form.gates layout="control" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endescapescript %}
|
||||
|
||||
@@ -193,6 +193,9 @@
|
||||
{% bootstrap_field form.all_products layout="control" %}
|
||||
{% bootstrap_field form.limit_products layout="control" %}
|
||||
{% bootstrap_field form.allow_entry_after_exit layout="control" %}
|
||||
{% if form.gates %}
|
||||
{% bootstrap_field form.gates layout="control" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -222,6 +225,9 @@
|
||||
{% bootstrap_field cl_formset.empty_form.all_products layout="control" %}
|
||||
{% bootstrap_field cl_formset.empty_form.limit_products layout="control" %}
|
||||
{% bootstrap_field cl_formset.empty_form.allow_entry_after_exit layout="control" %}
|
||||
{% if cl_formset.empty_form.gates %}
|
||||
{% bootstrap_field cl_formset.empty_form.gates layout="control" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endescapescript %}
|
||||
|
||||
228
src/tests/api/test_device_event_selection.py
Normal file
228
src/tests/api/test_device_event_selection.py
Normal file
@@ -0,0 +1,228 @@
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
from django_scopes import scopes_disabled
|
||||
from freezegun import freeze_time
|
||||
|
||||
tz = pytz.timezone("Asia/Tokyo")
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_no_events(device_client, device):
|
||||
resp = device_client.get(f'/api/v1/device/eventselection?current_event=e1')
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_choose_between_events(device_client, device):
|
||||
with scopes_disabled():
|
||||
e1 = device.organizer.events.create(
|
||||
name="Event", slug="e1", live=True,
|
||||
date_from=tz.localize(datetime(2020, 1, 10, 14, 0)),
|
||||
date_to=tz.localize(datetime(2020, 1, 10, 15, 0)),
|
||||
)
|
||||
cl1 = e1.checkin_lists.create(name="Same name")
|
||||
e2 = device.organizer.events.create(
|
||||
name="Event", slug="e2", live=True,
|
||||
date_from=tz.localize(datetime(2020, 1, 10, 16, 0)),
|
||||
date_to=tz.localize(datetime(2020, 1, 10, 17, 0)),
|
||||
)
|
||||
e2.checkin_lists.create(name="Other name")
|
||||
cl2 = e2.checkin_lists.create(name="Same name")
|
||||
e2.checkin_lists.create(name="Yet another name")
|
||||
tomorrow = device.organizer.events.create(
|
||||
name="Event", slug="tomorrow", live=True,
|
||||
date_from=tz.localize(datetime(2020, 1, 11, 15, 0)),
|
||||
date_to=tz.localize(datetime(2020, 1, 11, 16, 0)),
|
||||
)
|
||||
cl3 = tomorrow.checkin_lists.create(name="Just any name")
|
||||
for e in device.organizer.events.all():
|
||||
e.settings.timezone = "Asia/Tokyo"
|
||||
|
||||
# Keep current when still running
|
||||
with freeze_time("2020-01-10T14:30:00+09:00"):
|
||||
resp = device_client.get(f'/api/v1/device/eventselection?current_event=e1¤t_checkinlist={cl1.pk}')
|
||||
assert resp.status_code == 304
|
||||
with freeze_time("2020-01-10T16:30: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
|
||||
|
||||
# Next one only
|
||||
with freeze_time("2020-01-10T12:30:00+09:00"):
|
||||
resp = device_client.get(f'/api/v1/device/eventselection')
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['event']['slug'] == 'e1'
|
||||
|
||||
# Last one only
|
||||
with freeze_time("2020-01-10T17:30:00+09:00"):
|
||||
resp = device_client.get(f'/api/v1/device/eventselection')
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['event']['slug'] == 'e2'
|
||||
|
||||
# Running one
|
||||
with freeze_time("2020-01-10T14:30:00+09:00"):
|
||||
resp = device_client.get(f'/api/v1/device/eventselection')
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['event']['slug'] == 'e1'
|
||||
with freeze_time("2020-01-10T16:01:00+09:00"):
|
||||
resp = device_client.get(f'/api/v1/device/eventselection?current_event=e1¤t_checkinlist={cl1.pk}')
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['event']['slug'] == 'e2'
|
||||
assert resp.data['checkinlist'] == cl2.pk
|
||||
|
||||
# Prefer the one on the same day
|
||||
with freeze_time("2020-01-10T23:59:00+09:00"):
|
||||
resp = device_client.get(f'/api/v1/device/eventselection?current_event=e1¤t_checkinlist={cl1.pk}')
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['event']['slug'] == 'e2'
|
||||
assert resp.data['checkinlist'] == cl2.pk
|
||||
with freeze_time("2020-01-11T01:00:00+09:00"):
|
||||
resp = device_client.get(f'/api/v1/device/eventselection?current_event=e1¤t_checkinlist={cl1.pk}')
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['event']['slug'] == 'tomorrow'
|
||||
assert resp.data['checkinlist'] == cl3.pk
|
||||
|
||||
# Switch at half-time
|
||||
with freeze_time("2020-01-10T15:29:00+09:00"):
|
||||
resp = device_client.get(f'/api/v1/device/eventselection')
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['event']['slug'] == 'e1'
|
||||
with freeze_time("2020-01-10T15:31:00+09:00"):
|
||||
resp = device_client.get(f'/api/v1/device/eventselection')
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['event']['slug'] == 'e2'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_choose_between_subevents(device_client, device):
|
||||
with scopes_disabled():
|
||||
e = device.organizer.events.create(
|
||||
name="Event", slug="e1", live=True,
|
||||
date_from=tz.localize(datetime(2020, 1, 10, 14, 0)),
|
||||
has_subevents=True,
|
||||
)
|
||||
e.settings.timezone = "Asia/Tokyo"
|
||||
se1 = e.subevents.create(
|
||||
name="Event", active=True,
|
||||
date_from=tz.localize(datetime(2020, 1, 10, 14, 0)),
|
||||
date_to=tz.localize(datetime(2020, 1, 10, 15, 0)),
|
||||
)
|
||||
cl1 = e.checkin_lists.create(name="Same name", subevent=se1)
|
||||
se2 = e.subevents.create(
|
||||
name="Event", active=True,
|
||||
date_from=tz.localize(datetime(2020, 1, 10, 16, 0)),
|
||||
date_to=tz.localize(datetime(2020, 1, 10, 17, 0)),
|
||||
)
|
||||
cl2 = e.checkin_lists.create(name="Same name", subevent=se2)
|
||||
cl3 = e.checkin_lists.create(name="Other name")
|
||||
e.checkin_lists.create(name="Yet another name", subevent=se2)
|
||||
se_tomorrow = e.subevents.create(
|
||||
name="Event", active=True,
|
||||
date_from=tz.localize(datetime(2020, 1, 11, 15, 0)),
|
||||
date_to=tz.localize(datetime(2020, 1, 11, 16, 0)),
|
||||
)
|
||||
with freeze_time("2020-01-10T14:30:00+09:00"):
|
||||
resp = device_client.get(f'/api/v1/device/eventselection?current_event=e1¤t_subevent={se1.pk}')
|
||||
assert resp.status_code == 304
|
||||
with freeze_time("2020-01-10T16:30:00+09:00"):
|
||||
resp = device_client.get(f'/api/v1/device/eventselection?current_event=e1¤t_subevent={se1.pk}')
|
||||
assert resp.status_code == 200
|
||||
resp = device_client.get(f'/api/v1/device/eventselection?current_event=e1¤t_subevent={se2.pk}')
|
||||
assert resp.status_code == 304
|
||||
|
||||
# Next one only
|
||||
with freeze_time("2020-01-10T12:30:00+09:00"):
|
||||
resp = device_client.get(f'/api/v1/device/eventselection')
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['event']['slug'] == 'e1'
|
||||
assert resp.data['subevent'] == se1.pk
|
||||
|
||||
# Last one only
|
||||
with freeze_time("2020-01-10T17:30:00+09:00"):
|
||||
resp = device_client.get(f'/api/v1/device/eventselection')
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['event']['slug'] == 'e1'
|
||||
assert resp.data['subevent'] == se2.pk
|
||||
|
||||
# Running one
|
||||
with freeze_time("2020-01-10T14:30:00+09:00"):
|
||||
resp = device_client.get(f'/api/v1/device/eventselection')
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['event']['slug'] == 'e1'
|
||||
assert resp.data['subevent'] == se1.pk
|
||||
with freeze_time("2020-01-10T16:01:00+09:00"):
|
||||
resp = device_client.get(
|
||||
f'/api/v1/device/eventselection?current_event=e1¤t_checkinlist={cl1.pk}¤t_subevent={se1.pk}')
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['event']['slug'] == 'e1'
|
||||
assert resp.data['subevent'] == se2.pk
|
||||
assert resp.data['checkinlist'] == cl2.pk
|
||||
|
||||
# Prefer the one on the same day
|
||||
with freeze_time("2020-01-10T23:59:00+09:00"):
|
||||
resp = device_client.get(f'/api/v1/device/eventselection')
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['event']['slug'] == 'e1'
|
||||
assert resp.data['subevent'] == se2.pk
|
||||
with freeze_time("2020-01-11T01:00:00+09:00"):
|
||||
resp = device_client.get(
|
||||
f'/api/v1/device/eventselection?current_event=e1¤t_checkinlist={cl1.pk}¤t_subevent={se1.pk}')
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['event']['slug'] == 'e1'
|
||||
assert resp.data['subevent'] == se_tomorrow.pk
|
||||
assert resp.data['checkinlist'] == cl3.pk
|
||||
|
||||
# Switch at half-time
|
||||
with freeze_time("2020-01-10T15:29:00+09:00"):
|
||||
resp = device_client.get(f'/api/v1/device/eventselection')
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['event']['slug'] == 'e1'
|
||||
assert resp.data['subevent'] == se1.pk
|
||||
with freeze_time("2020-01-10T15:31:00+09:00"):
|
||||
resp = device_client.get(f'/api/v1/device/eventselection')
|
||||
assert resp.data['event']['slug'] == 'e1'
|
||||
assert resp.data['subevent'] == se2.pk
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_require_gate(device_client, device):
|
||||
with scopes_disabled():
|
||||
g = device.organizer.gates.create(name="Gate 1")
|
||||
device.gate = g
|
||||
device.save()
|
||||
e = device.organizer.events.create(
|
||||
name="Event", slug="e1", live=True,
|
||||
date_from=tz.localize(datetime(2020, 1, 10, 14, 0)),
|
||||
has_subevents=True,
|
||||
)
|
||||
e.settings.timezone = "Asia/Tokyo"
|
||||
se0 = e.subevents.create(
|
||||
name="Event", active=True,
|
||||
date_from=tz.localize(datetime(2020, 1, 10, 9, 0)),
|
||||
date_to=tz.localize(datetime(2020, 1, 10, 10, 0)),
|
||||
)
|
||||
e.subevents.create(
|
||||
name="Event", active=True,
|
||||
date_from=tz.localize(datetime(2020, 1, 10, 14, 0)),
|
||||
date_to=tz.localize(datetime(2020, 1, 10, 15, 0)),
|
||||
)
|
||||
cl1 = e.checkin_lists.create(name="Same name", subevent=se0)
|
||||
se2 = e.subevents.create(
|
||||
name="Event", active=True,
|
||||
date_from=tz.localize(datetime(2020, 1, 10, 16, 0)),
|
||||
date_to=tz.localize(datetime(2020, 1, 10, 17, 0)),
|
||||
)
|
||||
e.checkin_lists.create(name="Same name", subevent=se2)
|
||||
cl3 = e.checkin_lists.create(name="Other name", subevent=se2)
|
||||
cl3.gates.add(g)
|
||||
|
||||
with freeze_time("2020-01-10T11:00:00+09:00"):
|
||||
resp = device_client.get(
|
||||
f'/api/v1/device/eventselection?current_event=e1¤t_checkinlist={cl1.pk}¤t_subevent={se0.pk}')
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['event']['slug'] == 'e1'
|
||||
assert resp.data['subevent'] == se2.pk
|
||||
assert resp.data['checkinlist'] == cl3.pk
|
||||
Reference in New Issue
Block a user