api: iter MultipleAgendasEventsCheckStatus on user_checks (#82849)
gitea/chrono/pipeline/head This commit looks good Details

This commit is contained in:
Lauréline Guérin 2023-10-27 10:33:01 +02:00
parent e4864ea95b
commit 4291cc73db
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
4 changed files with 163 additions and 34 deletions

View File

@ -323,6 +323,10 @@ class BookingSerializer(serializers.ModelSerializer):
'cancellation_datetime',
]
def __init__(self, *args, **kwargs):
self.user_check = kwargs.pop('user_check', None)
super().__init__(*args, **kwargs)
def to_internal_value(self, data):
if 'color' in data:
# legacy
@ -341,19 +345,17 @@ class BookingSerializer(serializers.ModelSerializer):
ret.pop('user_absence_reason', None)
ret.pop('user_presence_reason', None)
else:
user_was_present = self.instance.user_check.presence if self.instance.user_check else None
user_was_present = self.user_check.presence if self.user_check else None
ret['user_was_present'] = user_was_present
ret['user_absence_reason'] = (
self.instance.user_check.type_slug if user_was_present is False else None
)
ret['user_presence_reason'] = (
self.instance.user_check.type_slug if user_was_present is True else None
)
ret['user_absence_reason'] = self.user_check.type_slug if user_was_present is False else None
ret['user_presence_reason'] = self.user_check.type_slug if user_was_present is True else None
if self.instance.event.agenda.kind == 'events' and self.instance.event.agenda.partial_bookings:
self.instance.user_check_start_time = self.instance.user_check.start_time
self.instance.user_check_end_time = self.instance.user_check.end_time
self.instance.computed_start_time = self.instance.user_check.computed_start_time
self.instance.computed_end_time = self.instance.user_check.computed_end_time
self.instance.user_check_start_time = self.user_check.start_time if self.user_check else None
self.instance.user_check_end_time = self.user_check.end_time if self.user_check else None
self.instance.computed_start_time = (
self.user_check.computed_start_time if self.user_check else None
)
self.instance.computed_end_time = self.user_check.computed_end_time if self.user_check else None
for key in ['', 'user_check_', 'computed_']:
start_key, end_key, minutes_key = (
'%sstart_time' % key,

View File

@ -2204,7 +2204,7 @@ class MultipleAgendasEventsCheckStatus(APIView):
booking_queryset = Booking.objects.filter(
event__in=events,
user_external_id=user_external_id,
).prefetch_related('user_checks')
).prefetch_related(Prefetch('user_checks', queryset=BookingCheck.objects.order_by('start_time')))
bookings_by_event_id = collections.defaultdict(list)
for booking in booking_queryset:
bookings_by_event_id[booking.event_id].append(booking)
@ -2212,33 +2212,61 @@ class MultipleAgendasEventsCheckStatus(APIView):
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]
booking.event = event # prevent db calls
if booking.cancellation_datetime is not None:
check_status = {'status': 'cancelled'}
elif not booking.user_check:
check_status = {'status': 'error', 'error_reason': 'booking-not-checked'}
else:
check_status = {
'status': 'presence' if booking.user_check.presence else 'absence',
'check_type': booking.user_check.type_slug,
if check_status:
data.append(
{
'event': serializers.EventSerializer(event).data,
'check_status': check_status,
'booking': {},
}
data.append(
{
'event': serializers.EventSerializer(event).data,
'check_status': check_status,
'booking': serializers.BookingSerializer(booking).data if booking else {},
)
continue
booking = bookings_by_event_id[event.pk][0]
booking.event = event # prevent db calls
if booking.cancellation_datetime is not None:
check_status = {'status': 'cancelled'}
data.append(
{
'event': serializers.EventSerializer(event).data,
'check_status': check_status,
'booking': serializers.BookingSerializer(booking).data,
}
)
continue
user_check = None
for user_check in booking.user_checks.all():
# user_checks are prefetched and ordered
check_status = {
'status': 'presence' if user_check.presence else 'absence',
'check_type': user_check.type_slug,
}
)
data.append(
{
'event': serializers.EventSerializer(event).data,
'check_status': check_status,
'booking': serializers.BookingSerializer(booking, user_check=user_check).data,
}
)
if user_check is None:
# no user_check object
check_status = {'status': 'error', 'error_reason': 'booking-not-checked'}
data.append(
{
'event': serializers.EventSerializer(event).data,
'check_status': check_status,
'booking': serializers.BookingSerializer(booking).data,
}
)
return Response({'err': 0, 'data': data})
@ -2564,7 +2592,12 @@ class BookingsAPI(ListAPIView):
data = []
for booking in bookings:
serialized_booking = self.serializer_class(booking).data
user_checks = booking.user_checks.all()
user_check = None
if len(user_checks) == 1:
# XXX which behavior for partial bookings, and multi-check ?
user_check = user_checks[0]
serialized_booking = self.serializer_class(booking, user_check=user_check).data
if booking.event.agenda.kind == 'events':
serialized_booking['event'] = get_event_detail(request, booking.event)
data.append(serialized_booking)
@ -2627,7 +2660,12 @@ class BookingAPI(APIView):
def get(self, request, *args, **kwargs):
self.check_booking()
serializer = self.serializer_class(self.booking)
user_checks = self.booking.user_checks.all()
user_check = None
if len(user_checks) == 1:
# XXX which behavior for partial bookings, and multi-check ?
user_check = user_checks[0]
serializer = self.serializer_class(self.booking, user_check=user_check)
response = serializer.data
response.update(
{

View File

@ -153,6 +153,7 @@ def test_bookings_api(app, user):
label='Event A', agenda=events_agenda, start_datetime=now() + datetime.timedelta(days=3), places=10
)
events_booking1 = Booking.objects.create(event=events_event, user_external_id='enfant-1234')
events_booking1.mark_user_presence()
events_booking2 = Booking.objects.create(event=events_event, user_external_id='enfant-1234')
Booking.objects.create(event=events_event, user_external_id='foobar')
Booking.objects.create(event=events_event)
@ -214,7 +215,7 @@ def test_bookings_api(app, user):
'user_last_name': '',
'user_email': '',
'user_phone_number': '',
'user_was_present': None,
'user_was_present': True,
'user_absence_reason': None,
'user_presence_reason': None,
'use_color_for': None,

View File

@ -5,7 +5,7 @@ import pytest
from django.db import connection
from django.test.utils import CaptureQueriesContext
from chrono.agendas.models import Agenda, Booking, Event, EventsType, Subscription
from chrono.agendas.models import Agenda, Booking, BookingCheck, Event, EventsType, Subscription
from chrono.utils.timezone import localtime, make_aware, now
pytestmark = pytest.mark.django_db
@ -1590,6 +1590,94 @@ def test_events_check_status_events(app, user, partial_bookings):
'label': '',
}
if partial_bookings:
booking1.mark_user_presence(
check_type_slug='foo-reason', start_time=datetime.time(7, 55), end_time=datetime.time(12, 15)
)
BookingCheck.objects.create(
booking=booking1,
start_time=datetime.time(13, 00),
end_time=datetime.time(17, 15),
presence=False,
)
booking1.refresh_computed_times(commit=True)
user_check1, user_check2 = booking1.user_checks.all().order_by('start_time')
assert user_check1.computed_start_time == datetime.time(8, 0)
assert user_check1.computed_end_time == datetime.time(12, 30)
assert user_check2.computed_start_time == datetime.time(12, 30)
assert user_check2.computed_end_time == datetime.time(17, 30)
with CaptureQueriesContext(connection) as ctx:
resp = app.get(url, params=params)
assert len(ctx.captured_queries) == 6
assert resp.json['err'] == 0
assert len(resp.json['data']) == 4
assert resp.json['data'][0]['event']['slug'] == 'notchecked-event-slug'
assert resp.json['data'][0]['check_status'] == {
'error_reason': 'event-not-checked',
'status': 'error',
}
assert resp.json['data'][0]['booking'] == {}
assert resp.json['data'][1]['event']['slug'] == 'event-slug'
assert resp.json['data'][1]['check_status'] == {
'check_type': 'foo-reason',
'status': 'presence',
}
assert {
'id': booking2.pk,
'user_absence_reason': None,
'user_presence_reason': 'foo-reason',
'user_was_present': True,
'start_time': None,
'end_time': None,
'duration': None,
'user_check_start_time': None,
'user_check_end_time': None,
'user_check_duration': None,
'computed_start_time': None,
'computed_end_time': None,
'computed_duration': None,
}.items() <= resp.json['data'][1]['booking'].items()
assert resp.json['data'][2]['event']['slug'] == 'recurring-event-slug--2022-05-30-1600'
assert resp.json['data'][2]['check_status'] == {
'check_type': 'foo-reason',
'status': 'presence',
}
assert {
'id': booking1.pk,
'user_absence_reason': None,
'user_presence_reason': 'foo-reason',
'user_was_present': True,
'start_time': '08:00:00',
'end_time': '17:00:00',
'duration': 540,
'user_check_start_time': '07:55:00',
'user_check_end_time': '12:15:00',
'user_check_duration': 260,
'computed_start_time': '08:00:00',
'computed_end_time': '12:30:00',
'computed_duration': 270,
}.items() <= resp.json['data'][2]['booking'].items()
assert resp.json['data'][3]['event']['slug'] == 'recurring-event-slug--2022-05-30-1600'
assert resp.json['data'][3]['check_status'] == {
'check_type': None,
'status': 'absence',
}
assert {
'id': booking1.pk,
'user_absence_reason': None,
'user_presence_reason': None,
'user_was_present': False,
'start_time': '08:00:00',
'end_time': '17:00:00',
'duration': 540,
'user_check_start_time': '13:00:00',
'user_check_end_time': '17:15:00',
'user_check_duration': 255,
'computed_start_time': '12:30:00',
'computed_end_time': '17:30:00',
'computed_duration': 300,
}.items() <= resp.json['data'][3]['booking'].items()
@pytest.mark.freeze_time('2022-05-30 14:00')
def test_events_check_status_agendas_filter(app, user):