From dad572a914bc25b068f9ff53f88eac27255009ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= Date: Fri, 22 Mar 2024 17:40:51 +0100 Subject: [PATCH] api: add primary_event in event details (#88559) --- chrono/agendas/models.py | 3 ++ chrono/api/views.py | 29 +++++++++++++++---- tests/api/datetimes/test_all.py | 5 +++- .../datetimes/test_events_multiple_agendas.py | 13 +++++++-- tests/api/datetimes/test_recurring_events.py | 2 +- .../fillslot/test_events_multiple_agendas.py | 12 +++++++- tests/api/fillslot/test_recurring_events.py | 8 ++--- tests/api/test_agenda.py | 2 +- tests/api/test_booking.py | 2 +- tests/api/test_event.py | 2 ++ 10 files changed, 62 insertions(+), 16 deletions(-) diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py index 6f2ddd02..c387491d 100644 --- a/chrono/agendas/models.py +++ b/chrono/agendas/models.py @@ -1171,6 +1171,9 @@ class Agenda(WithSnapshotMixin, WithApplicationMixin, WithInspectMixin, models.M event_queryset = Agenda.filter_for_guardian( event_queryset, guardian_external_id, user_external_id ) + event_queryset = event_queryset.prefetch_related( + Prefetch('primary_event', queryset=Event.objects.all().order_by()) + ) return qs.filter(kind='events').prefetch_related( Prefetch( diff --git a/chrono/api/views.py b/chrono/api/views.py index 0391f6fa..062a85c0 100644 --- a/chrono/api/views.py +++ b/chrono/api/views.py @@ -331,6 +331,7 @@ def get_short_event_detail( details = { 'id': '%s@%s' % (agenda.slug, event.slug) if multiple_agendas else event.slug, 'slug': event.slug, # kept for compatibility + 'primary_event': None, 'text': get_event_text(event, agenda), 'label': event.label or '', 'agenda_label': agenda.label, @@ -345,6 +346,12 @@ def get_short_event_detail( 'check_locked': event.check_locked, 'invoiced': event.invoiced, } + if event.primary_event: + details['primary_event'] = ( + '%s@%s' % (agenda.slug, event.primary_event.slug) + if multiple_agendas + else event.primary_event.slug + ) for key, value in event.get_custom_fields().items(): details['custom_field_%s' % key] = value return details @@ -663,6 +670,7 @@ class Datetimes(APIView): entries = Event.annotate_queryset_for_user(entries, user_external_id) if lock_code: entries = Event.annotate_queryset_for_lock_code(entries, lock_code=lock_code) + entries = entries.prefetch_related(Prefetch('primary_event', queryset=Event.objects.all().order_by())) entries = entries.order_by('start_datetime', 'duration', 'label') if payload['hide_disabled']: @@ -743,6 +751,9 @@ class MultipleAgendasDatetimes(APIView): entries = Event.annotate_queryset_for_user(entries, user_external_id, with_status=with_status) if lock_code: Event.annotate_queryset_for_lock_code(entries, lock_code) + entries = entries.prefetch_related( + Prefetch('primary_event', queryset=Event.objects.all().order_by()) + ) if check_overlaps: entries = Event.annotate_queryset_with_overlaps(entries) @@ -1845,6 +1856,7 @@ class RecurringFillslots(APIView): min_start=start_datetime, max_start=end_datetime, ) + events = events.prefetch_related(Prefetch('primary_event', queryset=Event.objects.all().order_by())) return events @@ -2003,6 +2015,7 @@ class EventsFillslots(APIView): output_field=BooleanField(), ) ) + events = events.prefetch_related(Prefetch('primary_event', queryset=Event.objects.all().order_by())) waiting_list_event_ids = [event.pk for event in events if event.in_waiting_list] extra_data = get_extra_data(request, payload) @@ -2227,9 +2240,13 @@ class MultipleAgendasEventsFillslotsRevert(APIView): if booking.previous_state == 'cancelled': bookings_to_cancel.append(booking) - events = Event.objects.filter( - pk__in=[b.event_id for b in bookings_to_cancel + bookings_to_book + bookings_to_delete] - ).prefetch_related('agenda') + events = ( + Event.objects.filter( + pk__in=[b.event_id for b in bookings_to_cancel + bookings_to_book + bookings_to_delete] + ) + .prefetch_related('agenda') + .prefetch_related(Prefetch('primary_event', queryset=Event.objects.all().order_by())) + ) events_by_id = {x.id: x for x in events} with transaction.atomic(): cancellation_datetime = now() @@ -2753,10 +2770,12 @@ class BookingsAPI(ListAPIView): return Response({'err': 0, 'data': data}) def get_queryset(self): + event_queryset = Event.objects.all().prefetch_related( + 'agenda', 'desk', Prefetch('primary_event', queryset=Event.objects.all().order_by()) + ) return ( Booking.objects.filter(primary_booking__isnull=True, cancellation_datetime__isnull=True) - .select_related('event', 'event__agenda', 'event__desk') - .prefetch_related('user_checks') + .prefetch_related('user_checks', Prefetch('event', queryset=event_queryset)) .order_by('event__start_datetime', 'event__slug', 'event__agenda__slug', 'pk') ) diff --git a/tests/api/datetimes/test_all.py b/tests/api/datetimes/test_all.py index c352a32a..3808d243 100644 --- a/tests/api/datetimes/test_all.py +++ b/tests/api/datetimes/test_all.py @@ -129,6 +129,7 @@ def test_datetime_api_label(app): agenda=agenda, ) resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug) + assert resp.json['data'][0]['primary_event'] is None assert resp.json['data'][0]['text'] == 'Hello world' assert resp.json['data'][0]['label'] == 'Hello world' @@ -692,6 +693,8 @@ def test_recurring_events_api(app, user, freezer): assert data[0]['id'] == 'abc--2021-01-19-1305' assert data[0]['datetime'] == '2021-01-19 13:05:00' assert data[0]['text'] == "Rock'n roll (Jan. 19, 2021, 1:05 p.m.)" + assert data[0]['label'] == "Rock'n roll" + assert data[0]['primary_event'] == 'abc' assert data[3]['id'] == 'abc--2021-02-09-1305' assert Event.objects.count() == 6 @@ -713,7 +716,7 @@ def test_recurring_events_api(app, user, freezer): # check querysets with CaptureQueriesContext(connection) as ctx: app.get('/api/agenda/%s/datetimes/' % agenda.slug) - assert len(ctx.captured_queries) == 3 + assert len(ctx.captured_queries) == 4 # events follow agenda display template agenda.event_display_template = '{{ event.label }} - {{ event.start_datetime }}' diff --git a/tests/api/datetimes/test_events_multiple_agendas.py b/tests/api/datetimes/test_events_multiple_agendas.py index e4a48cd2..b9ec3607 100644 --- a/tests/api/datetimes/test_events_multiple_agendas.py +++ b/tests/api/datetimes/test_events_multiple_agendas.py @@ -34,12 +34,14 @@ def test_datetimes_multiple_agendas(app): Desk.objects.create(agenda=first_agenda, slug='_exceptions_holder') Event.objects.create( slug='event', + label='Event', start_datetime=now() + datetime.timedelta(days=5), places=5, agenda=first_agenda, ) event = Event.objects.create( # base recurring event not visible in datetimes api slug='recurring', + label='Recurring', start_datetime=now() + datetime.timedelta(hours=1), recurrence_days=[localtime().isoweekday()], recurrence_end_date=now() + datetime.timedelta(days=15), @@ -60,11 +62,18 @@ def test_datetimes_multiple_agendas(app): Booking.objects.create(event=event) agenda_slugs = '%s,%s' % (first_agenda.slug, second_agenda.slug) - resp = app.get('/api/agendas/datetimes/', params={'agendas': agenda_slugs}) + with CaptureQueriesContext(connection) as ctx: + resp = app.get('/api/agendas/datetimes/', params={'agendas': agenda_slugs}) + assert len(ctx.captured_queries) == 3 assert len(resp.json['data']) == 5 assert resp.json['data'][0]['id'] == 'first-agenda@recurring--2021-05-06-1700' + assert resp.json['data'][0]['text'] == 'Recurring (May 6, 2021, 5 p.m.)' + assert resp.json['data'][0]['label'] == 'Recurring' + assert resp.json['data'][0]['primary_event'] == 'first-agenda@recurring' assert resp.json['data'][1]['id'] == 'first-agenda@event' - assert resp.json['data'][1]['text'] == 'May 11, 2021, 4 p.m.' + assert resp.json['data'][1]['text'] == 'Event' + assert resp.json['data'][1]['label'] == 'Event' + assert resp.json['data'][1]['primary_event'] is None assert resp.json['data'][1]['places']['available'] == 5 assert resp.json['data'][2]['id'] == 'second-agenda@event' diff --git a/tests/api/datetimes/test_recurring_events.py b/tests/api/datetimes/test_recurring_events.py index 9c0b647a..702abe19 100644 --- a/tests/api/datetimes/test_recurring_events.py +++ b/tests/api/datetimes/test_recurring_events.py @@ -434,7 +434,7 @@ def test_recurring_events_api_list_multiple_agendas_queries(app): '/api/agendas/recurring-events/?subscribed=category-a&user_external_id=xxx&guardian_external_id=father_id' ) assert len(resp.json['data']) == 40 - assert len(ctx.captured_queries) == 5 + assert len(ctx.captured_queries) == 6 @pytest.mark.freeze_time('2021-09-06 12:00') diff --git a/tests/api/fillslot/test_events_multiple_agendas.py b/tests/api/fillslot/test_events_multiple_agendas.py index 69f23ce8..8fc2cb7e 100644 --- a/tests/api/fillslot/test_events_multiple_agendas.py +++ b/tests/api/fillslot/test_events_multiple_agendas.py @@ -950,6 +950,7 @@ def test_api_events_fillslots_multiple_agendas_revert(app, user): 'slug': 'event', 'text': 'Event', 'url': None, + 'primary_event': None, } ], 'deleted_booking_count': 0, @@ -986,6 +987,7 @@ def test_api_events_fillslots_multiple_agendas_revert(app, user): 'slug': 'event', 'text': 'Event', 'url': None, + 'primary_event': None, } ], 'deleted_booking_count': 0, @@ -1028,6 +1030,7 @@ def test_api_events_fillslots_multiple_agendas_revert(app, user): 'slug': 'event', 'text': 'Event', 'url': None, + 'primary_event': None, } ], } @@ -1064,6 +1067,7 @@ def test_api_events_fillslots_multiple_agendas_revert(app, user): 'slug': 'event', 'text': 'Event', 'url': None, + 'primary_event': None, } ], } @@ -1100,6 +1104,7 @@ def test_api_events_fillslots_multiple_agendas_revert(app, user): 'slug': 'event', 'text': 'Event', 'url': None, + 'primary_event': None, } ], 'booked_booking_count': 0, @@ -1138,6 +1143,7 @@ def test_api_events_fillslots_multiple_agendas_revert(app, user): 'slug': 'event', 'text': 'Event', 'url': None, + 'primary_event': None, } ], 'booked_booking_count': 0, @@ -1169,10 +1175,14 @@ def test_api_events_fillslots_multiple_agendas_revert(app, user): duration=120, places=1, agenda=agenda, + recurrence_days=[7], + recurrence_end_date=now() + datetime.timedelta(days=14), # 2 weeks ) + event.create_all_recurrences() + event = event.recurrences.first() Booking.objects.create( event=event, request_uuid=request_uuid, previous_state='unbooked', cancellation_datetime=now() ) with CaptureQueriesContext(connection) as ctx: resp = app.post(revert_url) - assert len(ctx.captured_queries) == 14 + assert len(ctx.captured_queries) == 15 diff --git a/tests/api/fillslot/test_recurring_events.py b/tests/api/fillslot/test_recurring_events.py index 751da38f..1a1a6e85 100644 --- a/tests/api/fillslot/test_recurring_events.py +++ b/tests/api/fillslot/test_recurring_events.py @@ -107,7 +107,7 @@ def test_recurring_events_api_fillslots(app, user, freezer, action): params['user_external_id'] = 'user_id_3' with CaptureQueriesContext(connection) as ctx: resp = app.post_json(fillslots_url, params=params) - assert len(ctx.captured_queries) in [12, 13] + assert len(ctx.captured_queries) in [15, 16] # everything goes in waiting list assert events.filter(booked_waiting_list_places=1).count() == 6 # but an event was full @@ -1368,7 +1368,7 @@ def test_recurring_events_api_fillslots_multiple_agendas_queries(app, user): ) assert resp.json['booking_count'] == 180 assert resp.json['cancelled_booking_count'] == 0 - assert len(ctx.captured_queries) == 15 + assert len(ctx.captured_queries) == 17 with CaptureQueriesContext(connection) as ctx: resp = app.post_json( @@ -1382,7 +1382,7 @@ def test_recurring_events_api_fillslots_multiple_agendas_queries(app, user): ) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 5 - assert len(ctx.captured_queries) == 17 + assert len(ctx.captured_queries) == 18 father = Person.objects.create(user_external_id='father_id', first_name='John', last_name='Doe') mother = Person.objects.create(user_external_id='mother_id', first_name='Jane', last_name='Doe') @@ -1401,7 +1401,7 @@ def test_recurring_events_api_fillslots_multiple_agendas_queries(app, user): params={'slots': events_to_book, 'user_external_id': 'xxx'}, ) assert resp.json['booking_count'] == 100 - assert len(ctx.captured_queries) == 14 + assert len(ctx.captured_queries) == 16 @pytest.mark.freeze_time('2022-03-07 14:00') # Monday of 10th week diff --git a/tests/api/test_agenda.py b/tests/api/test_agenda.py index 03e3348b..6132f2a7 100644 --- a/tests/api/test_agenda.py +++ b/tests/api/test_agenda.py @@ -320,7 +320,7 @@ def test_agendas_api(settings, app): with CaptureQueriesContext(connection) as ctx: resp = app.get('/api/agenda/', params={'with_open_events': '1'}) - assert len(ctx.captured_queries) == 3 + assert len(ctx.captured_queries) == 4 def test_agenda_detail_api(app): diff --git a/tests/api/test_booking.py b/tests/api/test_booking.py index 64e419fa..c58bce87 100644 --- a/tests/api/test_booking.py +++ b/tests/api/test_booking.py @@ -188,7 +188,7 @@ def test_bookings_api(app, user): with CaptureQueriesContext(connection) as ctx: resp = app.get('/api/bookings/', params={'user_external_id': 'enfant-1234'}) - assert len(ctx.captured_queries) == 3 + assert len(ctx.captured_queries) == 6 assert resp.json['err'] == 0 assert resp.json['data'] == [ diff --git a/tests/api/test_event.py b/tests/api/test_event.py index 99d36403..98fc2537 100644 --- a/tests/api/test_event.py +++ b/tests/api/test_event.py @@ -44,6 +44,7 @@ def test_status(app, user): 'err': 0, 'id': 'event-slug', 'slug': 'event-slug', + 'primary_event': None, 'text': str(event), 'label': '', 'agenda_label': 'Foo bar', @@ -84,6 +85,7 @@ def test_status(app, user): 'err': 0, 'id': 'event-slug', 'slug': 'event-slug', + 'primary_event': None, 'text': str(event), 'label': '', 'agenda_label': 'Foo bar',