From 6f08999b81bfa14a6824103f29f7f03d5431705b Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Thu, 20 Jan 2022 16:34:43 +0100 Subject: [PATCH 4/5] api: account for time period weekday indexes (#45159) --- chrono/agendas/models.py | 25 ++++++---- tests/api/test_fillslot.py | 44 ++++++++++++++++++ tests/api/test_meetings_datetimes.py | 68 ++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 8 deletions(-) diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py index 352cdccf..8ee5d8be 100644 --- a/chrono/agendas/models.py +++ b/chrono/agendas/models.py @@ -51,6 +51,7 @@ from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ungettext from chrono.interval import Interval, IntervalSet +from chrono.utils.date import get_weekday_index from chrono.utils.publik_urls import translate_from_publik_url from chrono.utils.requests_wrapper import requests as requests_wrapper @@ -1148,7 +1149,7 @@ class VirtualMember(models.Model): WEEKDAYS_LIST = sorted(WEEKDAYS.items(), key=lambda x: x[0]) -class WeekTime(collections.namedtuple('WeekTime', ['weekday', 'time'])): +class WeekTime(collections.namedtuple('WeekTime', ['weekday', 'weekday_indexes', 'time'])): """Representation of a time point in a weekday, ex.: Monday at 5 o'clock.""" def __repr__(self): @@ -1236,13 +1237,14 @@ class TimePeriod(models.Model): def as_weektime_interval(self): return Interval( - WeekTime(self.weekday, self.start_time), - WeekTime(self.weekday, self.end_time), + WeekTime(self.weekday, self.weekday_indexes, self.start_time), + WeekTime(self.weekday, self.weekday_indexes, self.end_time), ) def as_shared_timeperiods(self): return SharedTimePeriod( weekday=self.weekday, + weekday_indexes=self.weekday_indexes, start_time=self.start_time, end_time=self.end_time, desks=[self.desk], @@ -1270,10 +1272,11 @@ class SharedTimePeriod: of get_all_slots() for details). """ - __slots__ = ['weekday', 'start_time', 'end_time', 'desks'] + __slots__ = ['weekday', 'weekday_indexes', 'start_time', 'end_time', 'desks'] - def __init__(self, weekday, start_time, end_time, desks): + def __init__(self, weekday, weekday_indexes, start_time, end_time, desks): self.weekday = weekday + self.weekday_indexes = weekday_indexes self.start_time = start_time self.end_time = end_time self.desks = set(desks) @@ -1343,7 +1346,11 @@ class SharedTimePeriod: while event_datetime < max_datetime: end_time = event_datetime + meeting_duration next_time = event_datetime + duration - if end_time.time() > self.end_time or event_datetime.date() != next_time.date(): + if ( + end_time.time() > self.end_time + or event_datetime.date() != next_time.date() + or (self.weekday_indexes and get_weekday_index(event_datetime) not in self.weekday_indexes) + ): # switch to naive time for day/week changes event_datetime = make_naive(event_datetime) # back to morning @@ -1352,9 +1359,10 @@ class SharedTimePeriod: ) # but next week event_datetime += datetime.timedelta(days=7) + # and re-align to timezone afterwards event_datetime = make_aware(event_datetime) - next_time = event_datetime + duration + continue # don't end after max_datetime if event_datetime > max_datetime: @@ -1366,9 +1374,10 @@ class SharedTimePeriod: @classmethod def from_weektime_interval(cls, weektime_interval, desks=()): begin, end = weektime_interval - assert begin.weekday == end.weekday + assert begin.weekday == end.weekday and begin.weekday_indexes == end.weekday_indexes return cls( weekday=begin.weekday, + weekday_indexes=begin.weekday_indexes, start_time=begin.time, end_time=end.time, desks=desks, diff --git a/tests/api/test_fillslot.py b/tests/api/test_fillslot.py index 88f981f9..af29d75a 100644 --- a/tests/api/test_fillslot.py +++ b/tests/api/test_fillslot.py @@ -889,6 +889,50 @@ def test_booking_api_meeting_across_daylight_saving_time(app, meetings_agenda, u assert resp.json['data'][event_index]['disabled'] +@pytest.mark.freeze_time('2022-01-20 14:00') # Thursday +def test_booking_api_meeting_weekday_indexes(app, user): + agenda = Agenda.objects.create( + label='Foo bar', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=60 + ) + meeting_type = MeetingType.objects.create(agenda=agenda, label='Plop', duration=30) + desk = Desk.objects.create(agenda=agenda, label='desk') + + time_period = TimePeriod.objects.create( + weekday=3, # Thursday + weekday_indexes=[1, 3], + start_time=datetime.time(11, 0), + end_time=datetime.time(12, 0), + desk=desk, + ) + datetimes_resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug)) + slot = datetimes_resp.json['data'][0]['id'] + assert slot == 'plop:2022-02-03-1100' + + app.authorization = ('Basic', ('john.doe', 'password')) + + # single booking + resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, slot)) + assert Booking.objects.count() == 1 + assert resp.json['duration'] == 30 + + # multiple slots + slots = [datetimes_resp.json['data'][1]['id'], datetimes_resp.json['data'][2]['id']] + assert slots == ['plop:2022-02-03-1130', 'plop:2022-02-17-1100'] + resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots}) + assert Booking.objects.count() == 3 + + # try to book slot on a skipped week + slot = datetimes_resp.json['data'][3]['id'] + time_period.weekday_indexes = [1] + time_period.save() + resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug)) + assert slot not in {slot['id'] for slot in resp.json['data']} + + resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, slot)) + assert resp.json['err'] == 1 + assert resp.json['err_desc'] == 'no more desk available' + + def test_booking_api_with_data(app, user): agenda = Agenda.objects.create(label='Foo bar', kind='events') event = Event.objects.create( diff --git a/tests/api/test_meetings_datetimes.py b/tests/api/test_meetings_datetimes.py index 90624528..98cdfa82 100644 --- a/tests/api/test_meetings_datetimes.py +++ b/tests/api/test_meetings_datetimes.py @@ -2300,3 +2300,71 @@ def test_virtual_agendas_time_change(app, freezer): assert ( False ), 'slot should not appear due to maximal_booking_delay of the real agenda (and no maximal_booking_delay) is defined on the real agenda' + + +@pytest.mark.freeze_time('2022-01-20 14:00') # Thursday +def test_datetimes_api_meetings_agenda_weekday_indexes(app): + agenda = Agenda.objects.create( + label='Foo bar', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=60 + ) + meeting_type = MeetingType.objects.create(agenda=agenda, label='Plop', duration=30) + desk = Desk.objects.create(agenda=agenda, label='desk') + + time_period = TimePeriod.objects.create( + weekday=3, # Thursday + start_time=datetime.time(11, 0), + end_time=datetime.time(12, 0), + desk=desk, + ) + api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug) + + resp = app.get(api_url) + assert len(resp.json['data']) == 16 + assert [x['datetime'] for x in resp.json['data']][:6] == [ + '2022-01-27 11:00:00', + '2022-01-27 11:30:00', + '2022-02-03 11:00:00', + '2022-02-03 11:30:00', + '2022-02-10 11:00:00', + '2022-02-10 11:30:00', + ] + every_weeks_resp = resp + + time_period.weekday_indexes = [1] + time_period.save() + resp = app.get(api_url) + assert len(resp.json['data']) == 4 + assert [x['datetime'] for x in resp.json['data']] == [ + '2022-02-03 11:00:00', + '2022-02-03 11:30:00', + '2022-03-03 11:00:00', + '2022-03-03 11:30:00', + ] + + time_period.weekday_indexes = [1, 3] + time_period.save() + resp = app.get(api_url) + assert len(resp.json['data']) == 8 + assert [x['datetime'] for x in resp.json['data']] == [ + '2022-02-03 11:00:00', + '2022-02-03 11:30:00', + '2022-02-17 11:00:00', + '2022-02-17 11:30:00', + '2022-03-03 11:00:00', + '2022-03-03 11:30:00', + '2022-03-17 11:00:00', + '2022-03-17 11:30:00', + ] + + time_period.weekday_indexes = [1, 2, 3, 4, 5] + time_period.save() + resp = app.get(api_url) + assert resp.json == every_weeks_resp.json + + # there are five Mondays this month + time_period.weekday = 0 + time_period.weekday_indexes = [5] + time_period.save() + resp = app.get(api_url) + assert len(resp.json['data']) == 2 + assert [x['datetime'] for x in resp.json['data']] == ['2022-01-31 11:00:00', '2022-01-31 11:30:00'] -- 2.30.2