api: new endpoint to get events and check status (#65770)
This commit is contained in:
parent
7ee7625f86
commit
4617dbdebb
|
@ -51,6 +51,22 @@ class CommaSeparatedStringField(serializers.ListField):
|
|||
return super().to_internal_value(data)
|
||||
|
||||
|
||||
class DateRangeMixin(metaclass=serializers.SerializerMetaclass):
|
||||
datetime_formats = ['%Y-%m-%d', '%Y-%m-%d %H:%M', 'iso-8601']
|
||||
|
||||
date_start = serializers.DateTimeField(required=False, input_formats=datetime_formats)
|
||||
date_end = serializers.DateTimeField(required=False, input_formats=datetime_formats)
|
||||
|
||||
|
||||
class AgendaSlugsMixin(metaclass=serializers.SerializerMetaclass):
|
||||
agendas = CommaSeparatedStringField(
|
||||
required=False, child=serializers.SlugField(max_length=160, allow_blank=False)
|
||||
)
|
||||
|
||||
def get_agenda_qs(self):
|
||||
return Agenda.objects.filter(kind='events').select_related('events_type')
|
||||
|
||||
|
||||
class SlotSerializer(serializers.Serializer):
|
||||
label = serializers.CharField(max_length=250, allow_blank=True)
|
||||
user_external_id = serializers.CharField(max_length=250, allow_blank=True)
|
||||
|
@ -123,6 +139,18 @@ class MultipleAgendasEventsSlotsSerializer(EventsSlotsSerializer):
|
|||
return value
|
||||
|
||||
|
||||
class MultipleAgendasEventsCheckStatusSerializer(AgendaSlugsMixin, DateRangeMixin, serializers.Serializer):
|
||||
user_external_id = serializers.CharField(required=False, max_length=250, allow_blank=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
for field in ['agendas', 'user_external_id', 'date_start', 'date_end']:
|
||||
self.fields[field].required = True
|
||||
|
||||
def validate_agendas(self, value):
|
||||
return get_objects_from_slugs(value, qs=self.get_agenda_qs())
|
||||
|
||||
|
||||
class RecurringFillslotsSerializer(MultipleAgendasEventsSlotsSerializer):
|
||||
include_booked_events_detail = serializers.BooleanField(default=False)
|
||||
|
||||
|
@ -179,8 +207,16 @@ class BookingSerializer(serializers.ModelSerializer):
|
|||
'user_presence_reason',
|
||||
'color',
|
||||
'extra_data',
|
||||
'creation_datetime',
|
||||
'cancellation_datetime',
|
||||
]
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'in_waiting_list',
|
||||
'extra_data',
|
||||
'creation_datetime',
|
||||
'cancellation_datetime',
|
||||
]
|
||||
read_only_fields = ['id', 'in_waiting_list', 'extra_data']
|
||||
|
||||
def to_representation(self, instance):
|
||||
ret = super().to_representation(instance)
|
||||
|
@ -248,13 +284,6 @@ class StatisticsFiltersSerializer(serializers.Serializer):
|
|||
)
|
||||
|
||||
|
||||
class DateRangeMixin(metaclass=serializers.SerializerMetaclass):
|
||||
datetime_formats = ['%Y-%m-%d', '%Y-%m-%d %H:%M', 'iso-8601']
|
||||
|
||||
date_start = serializers.DateTimeField(required=False, input_formats=datetime_formats)
|
||||
date_end = serializers.DateTimeField(required=False, input_formats=datetime_formats)
|
||||
|
||||
|
||||
class DateRangeSerializer(DateRangeMixin, serializers.Serializer):
|
||||
pass
|
||||
|
||||
|
@ -280,19 +309,13 @@ class DatetimesSerializer(DateRangeSerializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class AgendaOrSubscribedSlugsMixin(DateRangeMixin):
|
||||
agendas = CommaSeparatedStringField(
|
||||
required=False, child=serializers.SlugField(max_length=160, allow_blank=False)
|
||||
)
|
||||
class AgendaOrSubscribedSlugsMixin(AgendaSlugsMixin):
|
||||
subscribed = CommaSeparatedStringField(
|
||||
required=False, child=serializers.SlugField(max_length=160, allow_blank=False)
|
||||
)
|
||||
user_external_id = serializers.CharField(required=False, max_length=250, allow_blank=False)
|
||||
guardian_external_id = serializers.CharField(required=False, max_length=250, allow_blank=False)
|
||||
|
||||
def get_agenda_qs(self):
|
||||
return Agenda.objects.filter(kind='events').select_related('events_type')
|
||||
|
||||
def validate(self, attrs):
|
||||
super().validate(attrs)
|
||||
if 'agendas' not in attrs and 'subscribed' not in attrs:
|
||||
|
@ -350,7 +373,7 @@ class MultipleAgendasDatetimesSerializer(AgendaOrSubscribedSlugsMixin, Datetimes
|
|||
return attrs
|
||||
|
||||
|
||||
class AgendaOrSubscribedSlugsSerializer(AgendaOrSubscribedSlugsMixin, serializers.Serializer):
|
||||
class AgendaOrSubscribedSlugsSerializer(AgendaOrSubscribedSlugsMixin, DateRangeMixin, serializers.Serializer):
|
||||
pass
|
||||
|
||||
|
||||
|
@ -363,16 +386,12 @@ class RecurringEventsListSerializer(AgendaOrSubscribedSlugsSerializer):
|
|||
check_overlaps = serializers.BooleanField(default=False)
|
||||
|
||||
|
||||
class AgendaSlugsSerializer(serializers.Serializer):
|
||||
agendas = CommaSeparatedStringField(
|
||||
required=True, child=serializers.SlugField(max_length=160, allow_blank=False)
|
||||
)
|
||||
|
||||
|
||||
class EventSerializer(serializers.ModelSerializer):
|
||||
recurrence_days = StringOrListField(
|
||||
required=False, child=serializers.IntegerField(min_value=0, max_value=6)
|
||||
)
|
||||
primary_event = serializers.SlugRelatedField(read_only=True, slug_field='slug')
|
||||
agenda = serializers.SlugRelatedField(read_only=True, slug_field='slug')
|
||||
|
||||
class Meta:
|
||||
model = Event
|
||||
|
@ -386,31 +405,37 @@ class EventSerializer(serializers.ModelSerializer):
|
|||
'places',
|
||||
'waiting_list_places',
|
||||
'label',
|
||||
'slug',
|
||||
'description',
|
||||
'pricing',
|
||||
'url',
|
||||
'primary_event',
|
||||
'agenda',
|
||||
]
|
||||
read_only_fields = ['slug']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if self.instance.agenda.events_type and not self.instance.primary_event:
|
||||
field_classes = {
|
||||
'text': serializers.CharField,
|
||||
'textarea': serializers.CharField,
|
||||
'bool': serializers.NullBooleanField,
|
||||
}
|
||||
field_options = {
|
||||
'text': {'allow_blank': True},
|
||||
'textarea': {'allow_blank': True},
|
||||
}
|
||||
for custom_field in self.instance.agenda.events_type.get_custom_fields():
|
||||
field_class = field_classes[custom_field['field_type']]
|
||||
field_name = 'custom_field_%s' % custom_field['varname']
|
||||
self.fields[field_name] = field_class(
|
||||
required=False,
|
||||
**(field_options.get(custom_field['field_type']) or {}),
|
||||
)
|
||||
if not self.instance.agenda.events_type:
|
||||
return
|
||||
field_classes = {
|
||||
'text': serializers.CharField,
|
||||
'textarea': serializers.CharField,
|
||||
'bool': serializers.NullBooleanField,
|
||||
}
|
||||
field_options = {
|
||||
'text': {'allow_blank': True},
|
||||
'textarea': {'allow_blank': True},
|
||||
}
|
||||
for custom_field in self.instance.agenda.events_type.get_custom_fields():
|
||||
field_class = field_classes[custom_field['field_type']]
|
||||
field_name = 'custom_field_%s' % custom_field['varname']
|
||||
self.fields[field_name] = field_class(
|
||||
required=False,
|
||||
read_only=self.instance.primary_event is not None,
|
||||
**(field_options.get(custom_field['field_type']) or {}),
|
||||
)
|
||||
|
||||
def validate(self, attrs):
|
||||
if not self.instance.agenda.events_type:
|
||||
|
@ -435,6 +460,25 @@ class EventSerializer(serializers.ModelSerializer):
|
|||
attrs['custom_fields'] = custom_fields
|
||||
return attrs
|
||||
|
||||
def to_representation(self, instance):
|
||||
ret = super().to_representation(instance)
|
||||
if not self.instance.agenda.events_type:
|
||||
return ret
|
||||
defaults = {
|
||||
'text': '',
|
||||
'textarea': '',
|
||||
'bool': None,
|
||||
}
|
||||
custom_fields = self.instance.custom_fields
|
||||
for custom_field in self.instance.agenda.events_type.get_custom_fields():
|
||||
varname = custom_field['varname']
|
||||
field_name = 'custom_field_%s' % varname
|
||||
value = defaults[custom_field['field_type']]
|
||||
if varname in custom_fields:
|
||||
value = custom_fields[varname]
|
||||
ret[field_name] = value
|
||||
return ret
|
||||
|
||||
|
||||
class AgendaSerializer(serializers.ModelSerializer):
|
||||
edit_role = serializers.CharField(required=False, max_length=150)
|
||||
|
|
|
@ -28,6 +28,11 @@ urlpatterns = [
|
|||
views.agendas_events_fillslots,
|
||||
name='api-agendas-events-fillslots',
|
||||
),
|
||||
url(
|
||||
r'^agendas/events/check-status/$',
|
||||
views.agendas_events_check_status,
|
||||
name='api-agendas-events-check-status',
|
||||
),
|
||||
url(r'^agenda/(?P<agenda_identifier>[\w-]+)/$', views.agenda),
|
||||
url(r'^agenda/(?P<agenda_identifier>[\w-]+)/datetimes/$', views.datetimes, name='api-agenda-datetimes'),
|
||||
url(
|
||||
|
|
|
@ -2106,6 +2106,75 @@ class MultipleAgendasEventsFillslots(EventsFillslots):
|
|||
agendas_events_fillslots = MultipleAgendasEventsFillslots.as_view()
|
||||
|
||||
|
||||
class MultipleAgendasEventsCheckStatus(APIView):
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
serializer_class = serializers.MultipleAgendasEventsCheckStatusSerializer
|
||||
|
||||
def get(self, request):
|
||||
serializer = self.serializer_class(data=request.query_params)
|
||||
|
||||
if not serializer.is_valid():
|
||||
raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors, err=1)
|
||||
agendas = serializer.validated_data['agendas']
|
||||
agendas_by_id = {a.pk: a for a in agendas}
|
||||
user_external_id = serializer.validated_data['user_external_id']
|
||||
date_start = serializer.validated_data['date_start']
|
||||
date_end = serializer.validated_data['date_end']
|
||||
|
||||
events = Event.objects.filter(
|
||||
agenda__in=agendas,
|
||||
agenda__subscriptions__user_external_id=user_external_id,
|
||||
agenda__subscriptions__date_start__lte=F('start_datetime'),
|
||||
agenda__subscriptions__date_end__gt=F('start_datetime'),
|
||||
recurrence_days__isnull=True,
|
||||
cancelled=False,
|
||||
start_datetime__gte=date_start,
|
||||
start_datetime__lt=date_end,
|
||||
).prefetch_related(Prefetch('primary_event', queryset=Event.objects.all().order_by()))
|
||||
booking_queryset = Booking.objects.filter(
|
||||
event__in=events,
|
||||
user_external_id=user_external_id,
|
||||
).select_related('user_check_type')
|
||||
bookings_by_event_id = collections.defaultdict(list)
|
||||
for booking in booking_queryset:
|
||||
bookings_by_event_id[booking.event_id].append(booking)
|
||||
|
||||
data = []
|
||||
for event in events:
|
||||
event.agenda = agendas_by_id[event.agenda_id] # agenda is already fetched, reuse it
|
||||
check_status = {}
|
||||
booking = None
|
||||
if not event.checked:
|
||||
check_status = {'status': 'error', 'error_reason': 'event-not-checked'}
|
||||
elif not bookings_by_event_id[event.pk]:
|
||||
check_status = {'status': 'not-booked'}
|
||||
elif len(bookings_by_event_id[event.pk]) > 1:
|
||||
check_status = {'status': 'error', 'error_reason': 'too-many-bookings-found'}
|
||||
else:
|
||||
booking = bookings_by_event_id[event.pk][0]
|
||||
if booking.cancellation_datetime is not None:
|
||||
check_status = {'status': 'cancelled'}
|
||||
elif booking.user_was_present is None:
|
||||
check_status = {'status': 'error', 'error_reason': 'booking-not-checked'}
|
||||
else:
|
||||
check_status = {
|
||||
'status': 'presence' if booking.user_was_present else 'absence',
|
||||
'check_type': booking.user_check_type.slug if booking.user_check_type else '',
|
||||
}
|
||||
data.append(
|
||||
{
|
||||
'event': serializers.EventSerializer(event).data,
|
||||
'check_status': check_status,
|
||||
'booking': serializers.BookingSerializer(booking).data if booking else {},
|
||||
}
|
||||
)
|
||||
|
||||
return Response({'err': 0, 'data': data})
|
||||
|
||||
|
||||
agendas_events_check_status = MultipleAgendasEventsCheckStatus.as_view()
|
||||
|
||||
|
||||
class SubscriptionFilter(filters.FilterSet):
|
||||
date_start = filters.DateFilter(lookup_expr='gte')
|
||||
date_end = filters.DateFilter(lookup_expr='lt')
|
||||
|
|
|
@ -4,7 +4,7 @@ from unittest import mock
|
|||
import pytest
|
||||
from django.db import connection
|
||||
from django.test.utils import CaptureQueriesContext
|
||||
from django.utils.timezone import make_aware, now
|
||||
from django.utils.timezone import localtime, make_aware, now
|
||||
|
||||
from chrono.agendas.models import (
|
||||
Agenda,
|
||||
|
@ -155,6 +155,8 @@ def test_bookings_api(app, user):
|
|||
'user_presence_reason': '',
|
||||
'color': None,
|
||||
'extra_data': None,
|
||||
'cancellation_datetime': None,
|
||||
'creation_datetime': localtime(meetings_booking1.creation_datetime).isoformat(),
|
||||
},
|
||||
{
|
||||
'id': events_booking1.pk,
|
||||
|
@ -169,6 +171,8 @@ def test_bookings_api(app, user):
|
|||
'color': None,
|
||||
'extra_data': None,
|
||||
'event': resp.json['data'][1]['event'],
|
||||
'cancellation_datetime': None,
|
||||
'creation_datetime': localtime(events_booking1.creation_datetime).isoformat(),
|
||||
},
|
||||
{
|
||||
'id': events_booking2.pk,
|
||||
|
@ -183,6 +187,8 @@ def test_bookings_api(app, user):
|
|||
'color': None,
|
||||
'extra_data': None,
|
||||
'event': resp.json['data'][1]['event'],
|
||||
'cancellation_datetime': None,
|
||||
'creation_datetime': localtime(events_booking2.creation_datetime).isoformat(),
|
||||
},
|
||||
]
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import datetime
|
||||
|
||||
import pytest
|
||||
from django.utils.timezone import localtime, now
|
||||
from django.db import connection
|
||||
from django.test.utils import CaptureQueriesContext
|
||||
from django.utils.timezone import localtime, make_aware, now
|
||||
|
||||
from chrono.agendas.models import Agenda, Booking, Event, EventsType
|
||||
from chrono.agendas.models import Agenda, Booking, CheckType, CheckTypeGroup, Event, EventsType, Subscription
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
@ -702,6 +704,43 @@ def test_update_event(app, user):
|
|||
}
|
||||
|
||||
|
||||
def test_event_read_only_fields(app, user):
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='events')
|
||||
agenda2 = Agenda.objects.create(label='Foo bar 2', kind='events')
|
||||
event = Event.objects.create(
|
||||
slug='event', start_datetime=now() + datetime.timedelta(days=5), places=1, agenda=agenda
|
||||
)
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
api_url = '/api/agenda/%s/event/' % agenda.slug
|
||||
params = {
|
||||
'slug': 'slug',
|
||||
'agenda': agenda2.slug,
|
||||
'primary_event': event.slug,
|
||||
'start_datetime': now().isoformat(),
|
||||
'places': 42,
|
||||
}
|
||||
resp = app.post(api_url, params=params)
|
||||
assert resp.json['err'] == 0
|
||||
new_event = Event.objects.latest('pk')
|
||||
assert new_event.slug == 'foo-bar-event'
|
||||
assert new_event.agenda == agenda
|
||||
assert new_event.primary_event is None
|
||||
|
||||
api_url = '/api/agenda/%s/event/%s/' % (agenda.slug, new_event.slug)
|
||||
params = {
|
||||
'slug': 'slug',
|
||||
'agenda': agenda2.slug,
|
||||
'primary_event': event.slug,
|
||||
}
|
||||
resp = app.patch(api_url, params=params)
|
||||
assert resp.json['err'] == 0
|
||||
new_event.refresh_from_db()
|
||||
assert new_event.slug == 'foo-bar-event'
|
||||
assert new_event.agenda == agenda
|
||||
assert new_event.primary_event is None
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2021-11-01 10:00')
|
||||
def test_delete_event(app, user):
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='events')
|
||||
|
@ -777,3 +816,587 @@ def test_delete_recurring_event_forbidden(app, user):
|
|||
resp = app.delete('/api/agenda/%s/event/%s/' % (agenda.slug, event.slug))
|
||||
assert resp.json['err'] == 0
|
||||
assert not Event.objects.exists()
|
||||
|
||||
|
||||
def test_events_check_status_params(app, user):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
|
||||
# missing user_external_id
|
||||
resp = app.get(
|
||||
'/api/agendas/events/check-status/',
|
||||
params={'agendas': 'foo', 'date_start': '2022-05-01', 'date_end': '2022-06-01'},
|
||||
status=400,
|
||||
)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_desc'] == 'invalid payload'
|
||||
assert resp.json['errors']['user_external_id'] == ['This field is required.']
|
||||
|
||||
# missing agendas
|
||||
resp = app.get(
|
||||
'/api/agendas/events/check-status/',
|
||||
params={'user_external_id': 'child:42', 'date_start': '2022-05-01', 'date_end': '2022-06-01'},
|
||||
status=400,
|
||||
)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_desc'] == 'invalid payload'
|
||||
assert resp.json['errors']['agendas'] == ['This field is required.']
|
||||
|
||||
# unknown agenda
|
||||
resp = app.get(
|
||||
'/api/agendas/events/check-status/',
|
||||
params={
|
||||
'user_external_id': 'child:42',
|
||||
'agendas': 'foo, bar',
|
||||
'date_start': '2022-05-01',
|
||||
'date_end': '2022-06-01',
|
||||
},
|
||||
status=400,
|
||||
)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_desc'] == 'invalid payload'
|
||||
assert resp.json['errors']['agendas'] == ['invalid slugs: bar, foo']
|
||||
Agenda.objects.create(label='Foo')
|
||||
resp = app.get(
|
||||
'/api/agendas/events/check-status/',
|
||||
params={
|
||||
'user_external_id': 'child:42',
|
||||
'agendas': 'foo, bar',
|
||||
'date_start': '2022-05-01',
|
||||
'date_end': '2022-06-01',
|
||||
},
|
||||
status=400,
|
||||
)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_desc'] == 'invalid payload'
|
||||
assert resp.json['errors']['agendas'] == ['invalid slugs: bar']
|
||||
|
||||
# wrong kind
|
||||
wrong_agenda = Agenda.objects.create(label='Bar')
|
||||
for kind in ['meetings', 'virtual']:
|
||||
wrong_agenda.kind = kind
|
||||
wrong_agenda.save()
|
||||
resp = app.get(
|
||||
'/api/agendas/events/check-status/',
|
||||
params={
|
||||
'user_external_id': 'child:42',
|
||||
'agendas': 'foo, bar',
|
||||
'date_start': '2022-05-01',
|
||||
'date_end': '2022-06-01',
|
||||
},
|
||||
status=400,
|
||||
)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_desc'] == 'invalid payload'
|
||||
assert resp.json['errors']['agendas'] == ['invalid slugs: bar']
|
||||
|
||||
# missing date_start
|
||||
resp = app.get(
|
||||
'/api/agendas/events/check-status/',
|
||||
params={'user_external_id': 'child:42', 'agendas': 'foo', 'date_end': '2022-06-01'},
|
||||
status=400,
|
||||
)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_desc'] == 'invalid payload'
|
||||
assert resp.json['errors']['date_start'] == ['This field is required.']
|
||||
|
||||
# missing date_end
|
||||
resp = app.get(
|
||||
'/api/agendas/events/check-status/',
|
||||
params={'user_external_id': 'child:42', 'agendas': 'foo', 'date_start': '2022-05-01'},
|
||||
status=400,
|
||||
)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_desc'] == 'invalid payload'
|
||||
assert resp.json['errors']['date_end'] == ['This field is required.']
|
||||
|
||||
# bad date format
|
||||
resp = app.get(
|
||||
'/api/agendas/events/check-status/',
|
||||
params={'user_external_id': 'child:42', 'agendas': 'foo', 'date_start': 'wrong', 'date_end': 'wrong'},
|
||||
status=400,
|
||||
)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_desc'] == 'invalid payload'
|
||||
assert 'wrong format' in resp.json['errors']['date_start'][0]
|
||||
assert 'wrong format' in resp.json['errors']['date_end'][0]
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2022-05-30 14:00')
|
||||
def test_events_check_status(app, user):
|
||||
agenda = Agenda.objects.create(label='Foo')
|
||||
event = Event.objects.create(
|
||||
slug='event-slug',
|
||||
label='Event Label',
|
||||
start_datetime=now(),
|
||||
places=10,
|
||||
agenda=agenda,
|
||||
checked=True,
|
||||
)
|
||||
Subscription.objects.create(
|
||||
agenda=agenda,
|
||||
user_external_id='child:42',
|
||||
date_start=datetime.date(year=2021, month=9, day=1),
|
||||
date_end=datetime.date(year=2022, month=9, day=1),
|
||||
)
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
url = '/api/agendas/events/check-status/'
|
||||
params = {
|
||||
'user_external_id': 'child:42',
|
||||
'agendas': 'foo',
|
||||
'date_start': '2022-05-01',
|
||||
'date_end': '2022-06-01',
|
||||
}
|
||||
|
||||
# not booked
|
||||
resp = app.get(url, params=params)
|
||||
assert resp.json['err'] == 0
|
||||
assert len(resp.json['data']) == 1
|
||||
assert resp.json['data'][0]['check_status'] == {
|
||||
'status': 'not-booked',
|
||||
}
|
||||
assert resp.json['data'][0]['booking'] == {}
|
||||
|
||||
# 2 bookings found, error
|
||||
booking = Booking.objects.create(event=event, user_external_id='child:42')
|
||||
booking2 = Booking.objects.create(event=event, user_external_id='child:42')
|
||||
Booking.objects.create(event=event, user_external_id='other')
|
||||
resp = app.get(url, params=params)
|
||||
assert resp.json['err'] == 0
|
||||
assert len(resp.json['data']) == 1
|
||||
assert resp.json['data'][0]['check_status'] == {
|
||||
'status': 'error',
|
||||
'error_reason': 'too-many-bookings-found',
|
||||
}
|
||||
assert resp.json['data'][0]['booking'] == {}
|
||||
|
||||
# booking cancelled
|
||||
booking2.delete()
|
||||
booking.cancellation_datetime = now()
|
||||
booking.save()
|
||||
resp = app.get(url, params=params)
|
||||
assert resp.json['err'] == 0
|
||||
assert len(resp.json['data']) == 1
|
||||
assert resp.json['data'][0]['check_status'] == {
|
||||
'status': 'cancelled',
|
||||
}
|
||||
assert list(resp.json['data'][0]['booking'].keys()) == [
|
||||
'id',
|
||||
'in_waiting_list',
|
||||
'user_first_name',
|
||||
'user_last_name',
|
||||
'user_email',
|
||||
'user_phone_number',
|
||||
'user_was_present',
|
||||
'user_absence_reason',
|
||||
'user_presence_reason',
|
||||
'color',
|
||||
'extra_data',
|
||||
'creation_datetime',
|
||||
'cancellation_datetime',
|
||||
]
|
||||
assert resp.json['data'][0]['booking']['cancellation_datetime'] == localtime(now()).isoformat()
|
||||
|
||||
# booking not checked
|
||||
booking.cancellation_datetime = None
|
||||
booking.save()
|
||||
resp = app.get(url, params=params)
|
||||
assert resp.json['err'] == 0
|
||||
assert len(resp.json['data']) == 1
|
||||
assert resp.json['data'][0]['check_status'] == {
|
||||
'status': 'error',
|
||||
'error_reason': 'booking-not-checked',
|
||||
}
|
||||
assert resp.json['data'][0]['booking']['cancellation_datetime'] is None
|
||||
|
||||
# absence
|
||||
booking.user_was_present = False
|
||||
booking.save()
|
||||
resp = app.get(url, params=params)
|
||||
assert resp.json['err'] == 0
|
||||
assert len(resp.json['data']) == 1
|
||||
assert resp.json['data'][0]['check_status'] == {
|
||||
'status': 'absence',
|
||||
'check_type': '',
|
||||
}
|
||||
assert resp.json['data'][0]['booking']['user_was_present'] is False
|
||||
assert resp.json['data'][0]['booking']['user_absence_reason'] == ''
|
||||
assert resp.json['data'][0]['booking']['user_presence_reason'] == ''
|
||||
|
||||
# absence with check type
|
||||
group = CheckTypeGroup.objects.create(label='Foo bar')
|
||||
check_type = CheckType.objects.create(label='Foo reason', group=group, kind='absence')
|
||||
booking.user_check_type = check_type
|
||||
booking.save()
|
||||
resp = app.get(url, params=params)
|
||||
assert resp.json['err'] == 0
|
||||
assert len(resp.json['data']) == 1
|
||||
assert resp.json['data'][0]['check_status'] == {
|
||||
'status': 'absence',
|
||||
'check_type': 'foo-reason',
|
||||
}
|
||||
assert resp.json['data'][0]['booking']['user_was_present'] is False
|
||||
assert resp.json['data'][0]['booking']['user_absence_reason'] == 'foo-reason'
|
||||
assert resp.json['data'][0]['booking']['user_presence_reason'] == ''
|
||||
|
||||
# presence
|
||||
booking.user_check_type = None
|
||||
booking.user_was_present = True
|
||||
booking.save()
|
||||
resp = app.get(url, params=params)
|
||||
assert resp.json['err'] == 0
|
||||
assert len(resp.json['data']) == 1
|
||||
assert resp.json['data'][0]['check_status'] == {
|
||||
'status': 'presence',
|
||||
'check_type': '',
|
||||
}
|
||||
assert resp.json['data'][0]['booking']['user_was_present'] is True
|
||||
assert resp.json['data'][0]['booking']['user_absence_reason'] == ''
|
||||
assert resp.json['data'][0]['booking']['user_presence_reason'] == ''
|
||||
|
||||
# presence with check type
|
||||
check_type.kind = 'presence'
|
||||
check_type.save()
|
||||
booking.user_check_type = check_type
|
||||
booking.save()
|
||||
resp = app.get(url, params=params)
|
||||
assert resp.json['err'] == 0
|
||||
assert len(resp.json['data']) == 1
|
||||
assert resp.json['data'][0]['check_status'] == {
|
||||
'status': 'presence',
|
||||
'check_type': 'foo-reason',
|
||||
}
|
||||
assert resp.json['data'][0]['booking']['user_was_present'] is True
|
||||
assert resp.json['data'][0]['booking']['user_absence_reason'] == ''
|
||||
assert resp.json['data'][0]['booking']['user_presence_reason'] == 'foo-reason'
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2022-05-30 14:00')
|
||||
def test_events_check_status_events(app, user):
|
||||
events_type = EventsType.objects.create(
|
||||
label='Foo',
|
||||
custom_fields=[
|
||||
{'varname': 'text', 'label': 'Text', 'field_type': 'text'},
|
||||
{'varname': 'textarea', 'label': 'TextArea', 'field_type': 'textarea'},
|
||||
{'varname': 'bool', 'label': 'Bool', 'field_type': 'bool'},
|
||||
],
|
||||
)
|
||||
group = CheckTypeGroup.objects.create(label='Foo bar')
|
||||
check_type = CheckType.objects.create(label='Foo reason', group=group, kind='absence')
|
||||
agenda = Agenda.objects.create(label='Foo', events_type=events_type)
|
||||
start_datetime = now()
|
||||
# recurring event
|
||||
recurring_event = Event.objects.create(
|
||||
slug='recurring-event-slug',
|
||||
label='Recurring Event Label',
|
||||
start_datetime=start_datetime,
|
||||
recurrence_days=[start_datetime.weekday()],
|
||||
recurrence_end_date=start_datetime + datetime.timedelta(days=7),
|
||||
places=10,
|
||||
agenda=agenda,
|
||||
custom_fields={
|
||||
'text': 'foo',
|
||||
'textarea': 'foo bar',
|
||||
'bool': True,
|
||||
},
|
||||
)
|
||||
recurring_event.create_all_recurrences()
|
||||
first_event = recurring_event.recurrences.get()
|
||||
first_event.checked = True
|
||||
first_event.save()
|
||||
event = Event.objects.create(
|
||||
slug='event-slug',
|
||||
label='Event Label',
|
||||
start_datetime=start_datetime - datetime.timedelta(days=1),
|
||||
places=10,
|
||||
agenda=agenda,
|
||||
checked=True,
|
||||
)
|
||||
# not checked event
|
||||
notchecked_event = Event.objects.create(
|
||||
slug='notchecked-event-slug',
|
||||
label='Not Checked Event Label',
|
||||
start_datetime=start_datetime - datetime.timedelta(days=2),
|
||||
places=10,
|
||||
agenda=agenda,
|
||||
checked=False,
|
||||
)
|
||||
# cancelled event, not returned
|
||||
Event.objects.create(
|
||||
slug='cancelled',
|
||||
label='Cancelled',
|
||||
start_datetime=start_datetime,
|
||||
places=10,
|
||||
agenda=agenda,
|
||||
cancelled=True,
|
||||
)
|
||||
Subscription.objects.create(
|
||||
agenda=agenda,
|
||||
user_external_id='child:42',
|
||||
date_start=datetime.date(year=2021, month=9, day=1),
|
||||
date_end=datetime.date(year=2022, month=9, day=1),
|
||||
)
|
||||
booking1 = Booking.objects.create(
|
||||
event=first_event, user_external_id='child:42', user_was_present=True, user_check_type=check_type
|
||||
)
|
||||
booking2 = Booking.objects.create(
|
||||
event=event, user_external_id='child:42', user_was_present=True, user_check_type=check_type
|
||||
)
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
url = '/api/agendas/events/check-status/'
|
||||
params = {
|
||||
'user_external_id': 'child:42',
|
||||
'agendas': 'foo',
|
||||
'date_start': '2022-05-01',
|
||||
'date_end': '2022-06-01',
|
||||
}
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = app.get(url, params=params)
|
||||
assert len(ctx.captured_queries) == 5
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == [
|
||||
{
|
||||
'event': {
|
||||
'description': None,
|
||||
'duration': None,
|
||||
'label': 'Not Checked Event Label',
|
||||
'slug': 'notchecked-event-slug',
|
||||
'places': 10,
|
||||
'pricing': None,
|
||||
'publication_datetime': None,
|
||||
'recurrence_days': None,
|
||||
'recurrence_end_date': None,
|
||||
'recurrence_week_interval': 1,
|
||||
'start_datetime': localtime(notchecked_event.start_datetime).isoformat(),
|
||||
'url': None,
|
||||
'waiting_list_places': 0,
|
||||
'agenda': agenda.slug,
|
||||
'primary_event': None,
|
||||
'custom_field_bool': None,
|
||||
'custom_field_text': '',
|
||||
'custom_field_textarea': '',
|
||||
},
|
||||
'check_status': {'error_reason': 'event-not-checked', 'status': 'error'},
|
||||
'booking': {},
|
||||
},
|
||||
{
|
||||
'event': {
|
||||
'description': None,
|
||||
'duration': None,
|
||||
'label': 'Event Label',
|
||||
'slug': 'event-slug',
|
||||
'places': 10,
|
||||
'pricing': None,
|
||||
'publication_datetime': None,
|
||||
'recurrence_days': None,
|
||||
'recurrence_end_date': None,
|
||||
'recurrence_week_interval': 1,
|
||||
'start_datetime': localtime(event.start_datetime).isoformat(),
|
||||
'url': None,
|
||||
'waiting_list_places': 0,
|
||||
'agenda': agenda.slug,
|
||||
'primary_event': None,
|
||||
'custom_field_bool': None,
|
||||
'custom_field_text': '',
|
||||
'custom_field_textarea': '',
|
||||
},
|
||||
'check_status': {'check_type': 'foo-reason', 'status': 'presence'},
|
||||
'booking': {
|
||||
'cancellation_datetime': None,
|
||||
'color': None,
|
||||
'creation_datetime': localtime(now()).isoformat(),
|
||||
'extra_data': None,
|
||||
'id': booking2.pk,
|
||||
'in_waiting_list': False,
|
||||
'user_absence_reason': '',
|
||||
'user_email': '',
|
||||
'user_first_name': '',
|
||||
'user_last_name': '',
|
||||
'user_phone_number': '',
|
||||
'user_presence_reason': check_type.slug,
|
||||
'user_was_present': True,
|
||||
},
|
||||
},
|
||||
{
|
||||
'event': {
|
||||
'description': None,
|
||||
'duration': None,
|
||||
'label': 'Recurring Event Label',
|
||||
'slug': 'recurring-event-slug--2022-05-30-1600',
|
||||
'places': 10,
|
||||
'pricing': None,
|
||||
'publication_datetime': None,
|
||||
'recurrence_days': None,
|
||||
'recurrence_end_date': None,
|
||||
'recurrence_week_interval': 1,
|
||||
'start_datetime': localtime(first_event.start_datetime).isoformat(),
|
||||
'url': None,
|
||||
'waiting_list_places': 0,
|
||||
'agenda': agenda.slug,
|
||||
'primary_event': recurring_event.slug,
|
||||
'custom_field_text': 'foo',
|
||||
'custom_field_textarea': 'foo bar',
|
||||
'custom_field_bool': True,
|
||||
},
|
||||
'check_status': {'check_type': 'foo-reason', 'status': 'presence'},
|
||||
'booking': {
|
||||
'cancellation_datetime': None,
|
||||
'color': None,
|
||||
'creation_datetime': localtime(now()).isoformat(),
|
||||
'extra_data': None,
|
||||
'id': booking1.pk,
|
||||
'in_waiting_list': False,
|
||||
'user_absence_reason': '',
|
||||
'user_email': '',
|
||||
'user_first_name': '',
|
||||
'user_last_name': '',
|
||||
'user_phone_number': '',
|
||||
'user_presence_reason': check_type.slug,
|
||||
'user_was_present': True,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2022-05-30 14:00')
|
||||
def test_events_check_status_agendas_filter(app, user):
|
||||
agenda1 = Agenda.objects.create(label='Foo')
|
||||
agenda2 = Agenda.objects.create(label='Foo 2')
|
||||
Event.objects.create(
|
||||
slug='event-1',
|
||||
label='Event 1',
|
||||
start_datetime=now(),
|
||||
places=10,
|
||||
agenda=agenda1,
|
||||
)
|
||||
Event.objects.create(
|
||||
slug='event-2',
|
||||
label='Event 2',
|
||||
start_datetime=now(),
|
||||
places=10,
|
||||
agenda=agenda2,
|
||||
)
|
||||
Subscription.objects.create(
|
||||
agenda=agenda1,
|
||||
user_external_id='child:42',
|
||||
date_start=datetime.date(year=2021, month=9, day=1),
|
||||
date_end=datetime.date(year=2022, month=9, day=1),
|
||||
)
|
||||
Subscription.objects.create(
|
||||
agenda=agenda2,
|
||||
user_external_id='child:42',
|
||||
date_start=datetime.date(year=2021, month=9, day=1),
|
||||
date_end=datetime.date(year=2022, month=9, day=1),
|
||||
)
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
url = '/api/agendas/events/check-status/'
|
||||
params = {
|
||||
'user_external_id': 'child:42',
|
||||
'agendas': 'foo, foo-2',
|
||||
'date_start': '2022-05-01',
|
||||
'date_end': '2022-06-01',
|
||||
}
|
||||
resp = app.get(url, params=params)
|
||||
assert len(resp.json['data']) == 2
|
||||
assert resp.json['data'][0]['event']['slug'] == 'event-1'
|
||||
assert resp.json['data'][1]['event']['slug'] == 'event-2'
|
||||
|
||||
params['agendas'] = 'foo'
|
||||
resp = app.get(url, params=params)
|
||||
assert len(resp.json['data']) == 1
|
||||
assert resp.json['data'][0]['event']['slug'] == 'event-1'
|
||||
|
||||
params['agendas'] = 'foo-2'
|
||||
resp = app.get(url, params=params)
|
||||
assert len(resp.json['data']) == 1
|
||||
assert resp.json['data'][0]['event']['slug'] == 'event-2'
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'event_date, expected',
|
||||
[
|
||||
# just before first day
|
||||
((2022, 4, 30, 12, 0), False),
|
||||
# first day
|
||||
((2022, 5, 1, 12, 0), True),
|
||||
# last day
|
||||
((2022, 5, 31, 12, 0), True),
|
||||
# just after last day
|
||||
((2022, 6, 1, 12, 0), False),
|
||||
],
|
||||
)
|
||||
def test_events_check_status_date_filter(app, user, event_date, expected):
|
||||
agenda = Agenda.objects.create(label='Foo')
|
||||
Event.objects.create(
|
||||
slug='event',
|
||||
label='Event',
|
||||
start_datetime=make_aware(datetime.datetime(*event_date)),
|
||||
places=10,
|
||||
agenda=agenda,
|
||||
)
|
||||
Subscription.objects.create(
|
||||
agenda=agenda,
|
||||
user_external_id='child:42',
|
||||
date_start=datetime.date(year=2021, month=9, day=1),
|
||||
date_end=datetime.date(year=2022, month=9, day=1),
|
||||
)
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
url = '/api/agendas/events/check-status/'
|
||||
params = {
|
||||
'user_external_id': 'child:42',
|
||||
'agendas': 'foo',
|
||||
'date_start': '2022-05-01',
|
||||
'date_end': '2022-06-01',
|
||||
}
|
||||
resp = app.get(url, params=params)
|
||||
assert len(resp.json['data']) == int(expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'event_date, expected',
|
||||
[
|
||||
# just before first day
|
||||
((2022, 4, 30, 12, 0), False),
|
||||
# first day
|
||||
((2022, 5, 1, 12, 0), True),
|
||||
# last day
|
||||
((2022, 5, 31, 12, 0), True),
|
||||
# just after last day
|
||||
((2022, 6, 1, 12, 0), False),
|
||||
],
|
||||
)
|
||||
def test_events_check_status_subscription_filter(app, user, freezer, event_date, expected):
|
||||
agenda = Agenda.objects.create(label='Foo')
|
||||
Event.objects.create(
|
||||
slug='event',
|
||||
label='Event',
|
||||
start_datetime=make_aware(datetime.datetime(*event_date)),
|
||||
places=10,
|
||||
agenda=agenda,
|
||||
)
|
||||
Subscription.objects.create(
|
||||
agenda=agenda,
|
||||
user_external_id='child:42',
|
||||
date_start=datetime.date(year=2022, month=5, day=1),
|
||||
date_end=datetime.date(year=2022, month=6, day=1),
|
||||
)
|
||||
Subscription.objects.create(
|
||||
agenda=agenda,
|
||||
user_external_id='other',
|
||||
date_start=datetime.date(year=2022, month=4, day=1),
|
||||
date_end=datetime.date(year=2022, month=7, day=1),
|
||||
)
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
url = '/api/agendas/events/check-status/'
|
||||
params = {
|
||||
'user_external_id': 'child:42',
|
||||
'agendas': 'foo',
|
||||
'date_start': '2022-04-01',
|
||||
'date_end': '2022-07-01',
|
||||
}
|
||||
resp = app.get(url, params=params)
|
||||
assert len(resp.json['data']) == int(expected)
|
||||
|
|
Loading…
Reference in New Issue