From 479efa769115b16ce4566107bda87cf35a23c6fd Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Wed, 29 Jun 2022 17:15:03 +0200 Subject: [PATCH 2/2] api: use custody agendas date start (#66330) --- chrono/agendas/models.py | 27 +++- .../datetimes/test_events_multiple_agendas.py | 147 +++++++++++++++++- tests/api/datetimes/test_recurring_events.py | 53 ++++++- .../fillslot/test_events_multiple_agendas.py | 70 +++++++++ tests/api/fillslot/test_recurring_events.py | 33 +++- 5 files changed, 317 insertions(+), 13 deletions(-) diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py index 1427534d..b224edbc 100644 --- a/chrono/agendas/models.py +++ b/chrono/agendas/models.py @@ -837,15 +837,29 @@ class Agenda(models.Model): @staticmethod def filter_for_guardian(qs, guardian_external_id, child_external_id): + agendas = SharedCustodyAgenda.objects.filter(children__user_external_id=child_external_id) qs = ( qs.annotate(week=ExtractWeek('start_datetime')) .annotate(week_number=Cast('week', models.IntegerField())) .annotate(odd_week=F('week_number') % 2) ) + + previous_date_start = None + filtered_qs = Event.objects.none() + for agenda in agendas.order_by('-date_start'): + filtered_qs |= Agenda.filter_for_custody_agenda( + qs, agenda, guardian_external_id, date_end=previous_date_start + ) + previous_date_start = agenda.date_start + + return filtered_qs + + @staticmethod + def filter_for_custody_agenda(qs, agenda, guardian_external_id, date_end=None): rules = ( SharedCustodyRule.objects.filter( guardian__user_external_id=guardian_external_id, - agenda__children__user_external_id=child_external_id, + agenda=agenda, ) .annotate(day=Func(F('days'), function='unnest')) .annotate(week_day=(F('day') + 1) % 7 + 1) # convert ISO day number to db lookup day number @@ -859,7 +873,7 @@ class Agenda(models.Model): ) all_periods = SharedCustodyPeriod.objects.filter( - agenda__children__user_external_id=child_external_id, + agenda=agenda, date_start__lte=OuterRef('start_datetime'), date_end__gt=OuterRef('start_datetime'), ) @@ -879,9 +893,14 @@ class Agenda(models.Model): ) rules_lookup = (rules_lookup | Q(in_holiday_period=True)) & Q(in_excluded_holiday_period=False) - return qs.filter( - (rules_lookup | Q(in_exceptional_period=True)) & Q(in_excluded_exceptional_period=False) + qs = qs.filter( + (rules_lookup | Q(in_exceptional_period=True)) & Q(in_excluded_exceptional_period=False), + start_datetime__gte=agenda.date_start, ) + if date_end: + qs = qs.filter(start_datetime__lt=date_end) + + return qs @staticmethod def prefetch_recurring_events(qs, with_overlaps=False): diff --git a/tests/api/datetimes/test_events_multiple_agendas.py b/tests/api/datetimes/test_events_multiple_agendas.py index 5603af98..5eda6442 100644 --- a/tests/api/datetimes/test_events_multiple_agendas.py +++ b/tests/api/datetimes/test_events_multiple_agendas.py @@ -420,13 +420,14 @@ def test_datetimes_multiple_agendas_queries(app): 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') child = Person.objects.create(user_external_id='xxx', first_name='James', last_name='Doe') - agenda = SharedCustodyAgenda.objects.create( - first_guardian=father, second_guardian=mother, date_start=now() - datetime.timedelta(days=5) - ) - agenda.children.add(child) + for i in range(5): + agenda = SharedCustodyAgenda.objects.create( + first_guardian=father, second_guardian=mother, date_start=now() - datetime.timedelta(days=5 + i) + ) + agenda.children.add(child) - SharedCustodyRule.objects.create(agenda=agenda, guardian=father, days=list(range(7)), weeks='even') - SharedCustodyRule.objects.create(agenda=agenda, guardian=mother, days=list(range(7)), weeks='odd') + SharedCustodyRule.objects.create(agenda=agenda, guardian=father, days=list(range(7)), weeks='even') + SharedCustodyRule.objects.create(agenda=agenda, guardian=mother, days=list(range(7)), weeks='odd') with CaptureQueriesContext(connection) as ctx: resp = app.get( @@ -440,7 +441,7 @@ def test_datetimes_multiple_agendas_queries(app): }, ) assert len(resp.json['data']) == 30 - assert len(ctx.captured_queries) == 2 + assert len(ctx.captured_queries) == 3 @pytest.mark.freeze_time('2021-05-06 14:00') @@ -1216,6 +1217,138 @@ def test_datetimes_multiple_agendas_shared_custody_holiday_rules(app): assert len(resp.json['data']) == 1 +@pytest.mark.freeze_time('2022-03-07 14:00') # Monday of 10th week +def test_datetimes_multiple_agendas_shared_custody_date_start(app): + agenda = Agenda.objects.create(label='First agenda', kind='events') + Desk.objects.create(agenda=agenda, slug='_exceptions_holder') + start_datetime = make_aware(datetime.datetime(year=2022, month=3, day=9, hour=14, minute=0)) + wednesday_event = Event.objects.create( + slug='event-wednesday', + start_datetime=start_datetime, + recurrence_days=[2], + recurrence_end_date=start_datetime + datetime.timedelta(days=30), + places=5, + agenda=agenda, + ) + wednesday_event.create_all_recurrences() + Subscription.objects.create( + agenda=agenda, + user_external_id='child_id', + date_start=now(), + date_end=now() + datetime.timedelta(days=30), + ) + + 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') + child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe') + agenda = SharedCustodyAgenda.objects.create( + first_guardian=father, second_guardian=mother, date_start=now() + ) + agenda.children.add(child) + SharedCustodyRule.objects.create(agenda=agenda, guardian=father, days=list(range(7))) + + resp = app.get( + '/api/agendas/datetimes/', + params={'subscribed': 'all', 'user_external_id': 'child_id', 'guardian_external_id': 'father_id'}, + ) + assert [d['id'] for d in resp.json['data']] == [ + 'first-agenda@event-wednesday--2022-03-09-1400', + 'first-agenda@event-wednesday--2022-03-16-1400', + 'first-agenda@event-wednesday--2022-03-23-1400', + 'first-agenda@event-wednesday--2022-03-30-1400', + ] + + resp = app.get( + '/api/agendas/datetimes/', + params={'subscribed': 'all', 'user_external_id': 'child_id', 'guardian_external_id': 'mother_id'}, + ) + assert len(resp.json['data']) == 0 + + agenda = SharedCustodyAgenda.objects.create( + first_guardian=father, second_guardian=mother, date_start=datetime.date(year=2022, month=3, day=10) + ) + agenda.children.add(child) + SharedCustodyRule.objects.create(agenda=agenda, guardian=mother, days=list(range(7))) + + resp = app.get( + '/api/agendas/datetimes/', + params={'subscribed': 'all', 'user_external_id': 'child_id', 'guardian_external_id': 'father_id'}, + ) + assert [d['id'] for d in resp.json['data']] == [ + 'first-agenda@event-wednesday--2022-03-09-1400', + ] + + resp = app.get( + '/api/agendas/datetimes/', + params={'subscribed': 'all', 'user_external_id': 'child_id', 'guardian_external_id': 'mother_id'}, + ) + assert [d['id'] for d in resp.json['data']] == [ + 'first-agenda@event-wednesday--2022-03-16-1400', + 'first-agenda@event-wednesday--2022-03-23-1400', + 'first-agenda@event-wednesday--2022-03-30-1400', + ] + + agenda = SharedCustodyAgenda.objects.create( + first_guardian=father, second_guardian=mother, date_start=datetime.date(year=2022, month=3, day=17) + ) + agenda.children.add(child) + SharedCustodyRule.objects.create(agenda=agenda, guardian=father, days=list(range(7)), weeks='odd') + SharedCustodyRule.objects.create(agenda=agenda, guardian=mother, days=list(range(7)), weeks='even') + + resp = app.get( + '/api/agendas/datetimes/', + params={'subscribed': 'all', 'user_external_id': 'child_id', 'guardian_external_id': 'father_id'}, + ) + assert [d['id'] for d in resp.json['data']] == [ + 'first-agenda@event-wednesday--2022-03-09-1400', + 'first-agenda@event-wednesday--2022-03-30-1400', + ] + + resp = app.get( + '/api/agendas/datetimes/', + params={'subscribed': 'all', 'user_external_id': 'child_id', 'guardian_external_id': 'mother_id'}, + ) + assert [d['id'] for d in resp.json['data']] == [ + 'first-agenda@event-wednesday--2022-03-16-1400', + 'first-agenda@event-wednesday--2022-03-23-1400', + ] + + other_person = Person.objects.create(user_external_id='other_person', first_name='O', last_name='P') + agenda = SharedCustodyAgenda.objects.create( + first_guardian=other_person, + second_guardian=mother, + date_start=datetime.date(year=2022, month=3, day=22), + ) + agenda.children.add(child) + SharedCustodyRule.objects.create(agenda=agenda, guardian=other_person, days=list(range(7)), weeks='odd') + SharedCustodyRule.objects.create(agenda=agenda, guardian=mother, days=list(range(7)), weeks='even') + + resp = app.get( + '/api/agendas/datetimes/', + params={'subscribed': 'all', 'user_external_id': 'child_id', 'guardian_external_id': 'father_id'}, + ) + assert [d['id'] for d in resp.json['data']] == [ + 'first-agenda@event-wednesday--2022-03-09-1400', + ] + + resp = app.get( + '/api/agendas/datetimes/', + params={'subscribed': 'all', 'user_external_id': 'child_id', 'guardian_external_id': 'other_person'}, + ) + assert [d['id'] for d in resp.json['data']] == [ + 'first-agenda@event-wednesday--2022-03-30-1400', + ] + + resp = app.get( + '/api/agendas/datetimes/', + params={'subscribed': 'all', 'user_external_id': 'child_id', 'guardian_external_id': 'mother_id'}, + ) + assert [d['id'] for d in resp.json['data']] == [ + 'first-agenda@event-wednesday--2022-03-16-1400', + 'first-agenda@event-wednesday--2022-03-23-1400', + ] + + def test_datetimes_multiple_agendas_with_status(app): group = CheckTypeGroup.objects.create(label='Foo bar') check_type_absence = CheckType.objects.create(label='Foo reason', group=group, kind='absence') diff --git a/tests/api/datetimes/test_recurring_events.py b/tests/api/datetimes/test_recurring_events.py index 77a73fbe..4c37cf5c 100644 --- a/tests/api/datetimes/test_recurring_events.py +++ b/tests/api/datetimes/test_recurring_events.py @@ -253,6 +253,57 @@ def test_recurring_events_api_list_shared_custody(app): assert [x['id'] for x in resp.json['data']] == ['foo-bar@event:0', 'foo-bar@event:1', 'foo-bar@event:2'] +@pytest.mark.freeze_time('2021-12-13 14:00') # Monday of 50th week +def test_recurring_events_api_list_shared_custody_start_date(app): + agenda = Agenda.objects.create( + label='Foo bar', kind='events', minimal_booking_delay=1, maximal_booking_delay=30 + ) + Desk.objects.create(agenda=agenda, slug='_exceptions_holder') + event = Event.objects.create( + slug='event', + start_datetime=now(), + recurrence_days=[0, 1, 2], + recurrence_end_date=now() + datetime.timedelta(days=30), + places=5, + agenda=agenda, + ) + event.create_all_recurrences() + + resp = app.get('/api/agendas/recurring-events/', params={'agendas': agenda.slug}) + assert [x['id'] for x in resp.json['data']] == ['foo-bar@event:0', 'foo-bar@event:1', 'foo-bar@event:2'] + + # add shared two custody agendas + 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') + child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe') + + custody_agenda = SharedCustodyAgenda.objects.create( + first_guardian=father, second_guardian=mother, date_start=now() + ) + custody_agenda.children.add(child) + SharedCustodyRule.objects.create(agenda=custody_agenda, guardian=father, days=[0], weeks='even') + SharedCustodyRule.objects.create(agenda=custody_agenda, guardian=mother, days=[1, 2], weeks='odd') + + custody_agenda2 = SharedCustodyAgenda.objects.create( + first_guardian=father, second_guardian=mother, date_start=now() + datetime.timedelta(days=15) + ) + custody_agenda2.children.add(child) + SharedCustodyRule.objects.create(agenda=custody_agenda2, guardian=father, days=[1], weeks='even') + SharedCustodyRule.objects.create(agenda=custody_agenda2, guardian=mother, days=[0, 2], weeks='odd') + + resp = app.get( + '/api/agendas/recurring-events/', + params={'agendas': agenda.slug, 'user_external_id': 'child_id', 'guardian_external_id': 'father_id'}, + ) + assert [x['id'] for x in resp.json['data']] == ['foo-bar@event:0', 'foo-bar@event:1'] + + resp = app.get( + '/api/agendas/recurring-events/', + params={'agendas': agenda.slug, 'user_external_id': 'child_id', 'guardian_external_id': 'mother_id'}, + ) + assert [x['id'] for x in resp.json['data']] == ['foo-bar@event:0', 'foo-bar@event:1', 'foo-bar@event:2'] + + @pytest.mark.freeze_time('2021-09-06 12:00') def test_recurring_events_api_list_multiple_agendas(app): agenda = Agenda.objects.create(label='First Agenda', kind='events') @@ -345,7 +396,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) == 4 + assert len(ctx.captured_queries) == 5 @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 2eb8c862..d04994f3 100644 --- a/tests/api/fillslot/test_events_multiple_agendas.py +++ b/tests/api/fillslot/test_events_multiple_agendas.py @@ -652,6 +652,76 @@ def test_api_events_fillslots_multiple_agendas_shared_custody(app, user): assert resp.json['err_desc'] == 'Some events are outside guardian custody: first-agenda@event-thursday' +@pytest.mark.freeze_time('2022-03-07 14:00') # Monday of 10th week +def test_api_events_fillslots_multiple_agendas_shared_custody_date_start(app, user): + agenda = Agenda.objects.create(label='First agenda', kind='events') + Desk.objects.create(agenda=agenda, slug='_exceptions_holder') + Event.objects.create( + slug='event-wednesday', + start_datetime=make_aware(datetime.datetime(year=2022, month=3, day=9, hour=14, minute=0)), + places=5, + agenda=agenda, + ) + Event.objects.create( + slug='event-thursday', + start_datetime=make_aware(datetime.datetime(year=2022, month=3, day=10, hour=14, minute=0)), + places=5, + agenda=agenda, + ) + Subscription.objects.create( + agenda=agenda, + user_external_id='child_id', + date_start=now(), + date_end=now() + datetime.timedelta(days=14), + ) + + 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') + child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe') + + agenda = SharedCustodyAgenda.objects.create( + first_guardian=father, second_guardian=mother, date_start=now() + ) + agenda.children.add(child) + SharedCustodyRule.objects.create(agenda=agenda, guardian=father, days=list(range(7))) + + agenda2 = SharedCustodyAgenda.objects.create( + first_guardian=father, second_guardian=mother, date_start=datetime.date(year=2022, month=3, day=10) + ) + agenda2.children.add(child) + SharedCustodyRule.objects.create(agenda=agenda2, guardian=mother, days=list(range(7))) + + app.authorization = ('Basic', ('john.doe', 'password')) + params = {'user_external_id': 'child_id', 'slots': 'first-agenda@event-wednesday'} + resp = app.post_json( + '/api/agendas/events/fillslots/?subscribed=all&guardian_external_id=father_id', params=params + ) + assert resp.json['booking_count'] == 1 + + resp = app.post_json( + '/api/agendas/events/fillslots/?subscribed=all&guardian_external_id=mother_id', + params=params, + status=400, + ) + assert resp.json['err'] == 1 + assert resp.json['err_desc'] == 'Some events are outside guardian custody: first-agenda@event-wednesday' + + params['slots'] = 'first-agenda@event-thursday' + resp = app.post_json( + '/api/agendas/events/fillslots/?subscribed=all&guardian_external_id=mother_id', params=params + ) + assert resp.json['booking_count'] == 1 + + params['slots'] = 'first-agenda@event-thursday' + resp = app.post_json( + '/api/agendas/events/fillslots/?subscribed=all&guardian_external_id=father_id', + params=params, + status=400, + ) + assert resp.json['err'] == 1 + assert resp.json['err_desc'] == 'Some events are outside guardian custody: first-agenda@event-thursday' + + @pytest.mark.freeze_time('2021-09-06 12:00') def test_api_events_fillslots_multiple_agendas_overlapping_events(app, user, freezer): agenda = Agenda.objects.create(label='Foo bar', kind='events') diff --git a/tests/api/fillslot/test_recurring_events.py b/tests/api/fillslot/test_recurring_events.py index d767d55f..a00a1027 100644 --- a/tests/api/fillslot/test_recurring_events.py +++ b/tests/api/fillslot/test_recurring_events.py @@ -1312,7 +1312,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) == 13 + assert len(ctx.captured_queries) == 14 @pytest.mark.freeze_time('2022-03-07 14:00') # Monday of 10th week @@ -1377,6 +1377,37 @@ def test_recurring_events_api_fillslots_shared_custody(app, user, freezer): '2022-03-20', ] + # give father full custody from 14/03/2022 + agenda2 = SharedCustodyAgenda.objects.create( + first_guardian=father, second_guardian=mother, date_start=datetime.date(year=2022, month=3, day=14) + ) + agenda2.children.add(child) + + SharedCustodyRule.objects.create(agenda=agenda2, guardian=father, days=list(range(7))) + Booking.objects.all().delete() + + resp = app.post_json(fillslots_url % 'father_id', params=params) + assert [x['date'] for x in resp.json['booked_events']] == [ + '2022-03-10', + '2022-03-11', + '2022-03-12', + '2022-03-14', + '2022-03-15', + '2022-03-16', + '2022-03-17', + '2022-03-18', + '2022-03-19', + '2022-03-20', + ] + + resp = app.post_json(fillslots_url % 'mother_id', params=params) + assert [x['date'] for x in resp.json['booked_events']] == [ + '2022-03-07', + '2022-03-08', + '2022-03-09', + '2022-03-13', # last date before new agenda rules apply + ] + @pytest.mark.freeze_time('2021-09-06 12:00') def test_recurring_events_api_fillslots_overlapping_events(app, user): -- 2.30.2