Projet

Général

Profil

0004-api-account-for-time-period-weekday-indexes-45159.patch

Valentin Deniaud, 15 mars 2022 13:39

Télécharger (12,6 ko)

Voir les différences:

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 ++++++++++++++++++++++++++
 tests/test_time_periods.py           | 71 ++++++++++++++++++++++++++++
 4 files changed, 200 insertions(+), 8 deletions(-)
chrono/agendas/models.py
53 53
from django.utils.translation import ungettext
54 54

  
55 55
from chrono.interval import Interval, IntervalSet
56
from chrono.utils.date import get_weekday_index
56 57
from chrono.utils.db import SumCardinality
57 58
from chrono.utils.publik_urls import translate_from_publik_url
58 59
from chrono.utils.requests_wrapper import requests as requests_wrapper
......
1181 1182
WEEKDAYS_LIST = sorted(WEEKDAYS.items(), key=lambda x: x[0])
1182 1183

  
1183 1184

  
1184
class WeekTime(collections.namedtuple('WeekTime', ['weekday', 'time'])):
1185
class WeekTime(collections.namedtuple('WeekTime', ['weekday', 'weekday_indexes', 'time'])):
1185 1186
    """Representation of a time point in a weekday, ex.: Monday at 5 o'clock."""
1186 1187

  
1187 1188
    def __repr__(self):
......
1269 1270

  
1270 1271
    def as_weektime_interval(self):
1271 1272
        return Interval(
1272
            WeekTime(self.weekday, self.start_time),
1273
            WeekTime(self.weekday, self.end_time),
1273
            WeekTime(self.weekday, self.weekday_indexes, self.start_time),
1274
            WeekTime(self.weekday, self.weekday_indexes, self.end_time),
1274 1275
        )
1275 1276

  
1276 1277
    def as_shared_timeperiods(self):
1277 1278
        return SharedTimePeriod(
1278 1279
            weekday=self.weekday,
1280
            weekday_indexes=self.weekday_indexes,
1279 1281
            start_time=self.start_time,
1280 1282
            end_time=self.end_time,
1281 1283
            desks=[self.desk],
......
1303 1305
    of get_all_slots() for details).
1304 1306
    """
1305 1307

  
1306
    __slots__ = ['weekday', 'start_time', 'end_time', 'desks']
1308
    __slots__ = ['weekday', 'weekday_indexes', 'start_time', 'end_time', 'desks']
1307 1309

  
1308
    def __init__(self, weekday, start_time, end_time, desks):
1310
    def __init__(self, weekday, weekday_indexes, start_time, end_time, desks):
1309 1311
        self.weekday = weekday
1312
        self.weekday_indexes = weekday_indexes
1310 1313
        self.start_time = start_time
1311 1314
        self.end_time = end_time
1312 1315
        self.desks = set(desks)
......
1376 1379
        while event_datetime < max_datetime:
1377 1380
            end_time = event_datetime + meeting_duration
1378 1381
            next_time = event_datetime + duration
1379
            if end_time.time() > self.end_time or event_datetime.date() != next_time.date():
1382
            if (
1383
                end_time.time() > self.end_time
1384
                or event_datetime.date() != next_time.date()
1385
                or (self.weekday_indexes and get_weekday_index(event_datetime) not in self.weekday_indexes)
1386
            ):
1380 1387
                # switch to naive time for day/week changes
1381 1388
                event_datetime = make_naive(event_datetime)
1382 1389
                # back to morning
......
1385 1392
                )
1386 1393
                # but next week
1387 1394
                event_datetime += datetime.timedelta(days=7)
1395

  
1388 1396
                # and re-align to timezone afterwards
1389 1397
                event_datetime = make_aware(event_datetime)
1390
                next_time = event_datetime + duration
1398
                continue
1391 1399

  
1392 1400
            # don't end after max_datetime
1393 1401
            if event_datetime > max_datetime:
......
1399 1407
    @classmethod
1400 1408
    def from_weektime_interval(cls, weektime_interval, desks=()):
1401 1409
        begin, end = weektime_interval
1402
        assert begin.weekday == end.weekday
1410
        assert begin.weekday == end.weekday and begin.weekday_indexes == end.weekday_indexes
1403 1411
        return cls(
1404 1412
            weekday=begin.weekday,
1413
            weekday_indexes=begin.weekday_indexes,
1405 1414
            start_time=begin.time,
1406 1415
            end_time=end.time,
1407 1416
            desks=desks,
tests/api/test_fillslot.py
889 889
    assert resp.json['data'][event_index]['disabled']
890 890

  
891 891

  
892
@pytest.mark.freeze_time('2022-01-20 14:00')  # Thursday
893
def test_booking_api_meeting_weekday_indexes(app, user):
894
    agenda = Agenda.objects.create(
895
        label='Foo bar', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=60
896
    )
897
    meeting_type = MeetingType.objects.create(agenda=agenda, label='Plop', duration=30)
898
    desk = Desk.objects.create(agenda=agenda, label='desk')
899

  
900
    time_period = TimePeriod.objects.create(
901
        weekday=3,  # Thursday
902
        weekday_indexes=[1, 3],
903
        start_time=datetime.time(11, 0),
904
        end_time=datetime.time(12, 0),
905
        desk=desk,
906
    )
907
    datetimes_resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug))
908
    slot = datetimes_resp.json['data'][0]['id']
909
    assert slot == 'plop:2022-02-03-1100'
910

  
911
    app.authorization = ('Basic', ('john.doe', 'password'))
912

  
913
    # single booking
914
    resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, slot))
915
    assert Booking.objects.count() == 1
916
    assert resp.json['duration'] == 30
917

  
918
    # multiple slots
919
    slots = [datetimes_resp.json['data'][1]['id'], datetimes_resp.json['data'][2]['id']]
920
    assert slots == ['plop:2022-02-03-1130', 'plop:2022-02-17-1100']
921
    resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots})
922
    assert Booking.objects.count() == 3
923

  
924
    # try to book slot on a skipped week
925
    slot = datetimes_resp.json['data'][3]['id']
926
    time_period.weekday_indexes = [1]
927
    time_period.save()
928
    resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug))
929
    assert slot not in {slot['id'] for slot in resp.json['data']}
930

  
931
    resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, slot))
932
    assert resp.json['err'] == 1
933
    assert resp.json['err_desc'] == 'no more desk available'
934

  
935

  
892 936
def test_booking_api_with_data(app, user):
893 937
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
894 938
    event = Event.objects.create(
tests/api/test_meetings_datetimes.py
2300 2300
            assert (
2301 2301
                False
2302 2302
            ), 'slot should not appear due to maximal_booking_delay of the real agenda (and no maximal_booking_delay) is defined on the real agenda'
2303

  
2304

  
2305
@pytest.mark.freeze_time('2022-01-20 14:00')  # Thursday
2306
def test_datetimes_api_meetings_agenda_weekday_indexes(app):
2307
    agenda = Agenda.objects.create(
2308
        label='Foo bar', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=60
2309
    )
2310
    meeting_type = MeetingType.objects.create(agenda=agenda, label='Plop', duration=30)
2311
    desk = Desk.objects.create(agenda=agenda, label='desk')
2312

  
2313
    time_period = TimePeriod.objects.create(
2314
        weekday=3,  # Thursday
2315
        start_time=datetime.time(11, 0),
2316
        end_time=datetime.time(12, 0),
2317
        desk=desk,
2318
    )
2319
    api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug)
2320

  
2321
    resp = app.get(api_url)
2322
    assert len(resp.json['data']) == 16
2323
    assert [x['datetime'] for x in resp.json['data']][:6] == [
2324
        '2022-01-27 11:00:00',
2325
        '2022-01-27 11:30:00',
2326
        '2022-02-03 11:00:00',
2327
        '2022-02-03 11:30:00',
2328
        '2022-02-10 11:00:00',
2329
        '2022-02-10 11:30:00',
2330
    ]
2331
    every_weeks_resp = resp
2332

  
2333
    time_period.weekday_indexes = [1]
2334
    time_period.save()
2335
    resp = app.get(api_url)
2336
    assert len(resp.json['data']) == 4
2337
    assert [x['datetime'] for x in resp.json['data']] == [
2338
        '2022-02-03 11:00:00',
2339
        '2022-02-03 11:30:00',
2340
        '2022-03-03 11:00:00',
2341
        '2022-03-03 11:30:00',
2342
    ]
2343

  
2344
    time_period.weekday_indexes = [1, 3]
2345
    time_period.save()
2346
    resp = app.get(api_url)
2347
    assert len(resp.json['data']) == 8
2348
    assert [x['datetime'] for x in resp.json['data']] == [
2349
        '2022-02-03 11:00:00',
2350
        '2022-02-03 11:30:00',
2351
        '2022-02-17 11:00:00',
2352
        '2022-02-17 11:30:00',
2353
        '2022-03-03 11:00:00',
2354
        '2022-03-03 11:30:00',
2355
        '2022-03-17 11:00:00',
2356
        '2022-03-17 11:30:00',
2357
    ]
2358

  
2359
    time_period.weekday_indexes = [1, 2, 3, 4, 5]
2360
    time_period.save()
2361
    resp = app.get(api_url)
2362
    assert resp.json == every_weeks_resp.json
2363

  
2364
    # there are five Mondays this month
2365
    time_period.weekday = 0
2366
    time_period.weekday_indexes = [5]
2367
    time_period.save()
2368
    resp = app.get(api_url)
2369
    assert len(resp.json['data']) == 2
2370
    assert [x['datetime'] for x in resp.json['data']] == ['2022-01-31 11:00:00', '2022-01-31 11:30:00']
tests/test_time_periods.py
255 255
    assert events[2].timetuple()[:5] == (2016, 9, 19, 21, 0)
256 256
    assert events[3].timetuple()[:5] == (2016, 9, 26, 21, 0)
257 257
    assert len(events) == 4
258

  
259

  
260
def test_timeperiod_weekday_indexes():
261
    agenda = Agenda.objects.create(
262
        label='Foo bar', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=60
263
    )
264
    meeting_type = MeetingType.objects.create(agenda=agenda, label='Plop', duration=60)
265
    desk = Desk.objects.create(agenda=agenda, label='desk')
266

  
267
    timeperiod = TimePeriod.objects.create(
268
        weekday=0,  # Monday
269
        start_time=datetime.time(11, 0),
270
        end_time=datetime.time(12, 0),
271
        desk=desk,
272
    )
273

  
274
    def get_events(min_datetime, max_datetime):
275
        return sorted(
276
            timeperiod.as_shared_timeperiods().get_time_slots(
277
                min_datetime=make_aware(min_datetime),
278
                max_datetime=make_aware(max_datetime),
279
                meeting_duration=meeting_type.duration,
280
                base_duration=agenda.get_base_meeting_duration(),
281
            )
282
        )
283

  
284
    events = get_events(datetime.datetime(2022, 3, 1), datetime.datetime(2022, 4, 1))
285
    assert events[0].timetuple()[:5] == (2022, 3, 7, 11, 0)
286
    assert events[1].timetuple()[:5] == (2022, 3, 14, 11, 0)
287
    assert events[2].timetuple()[:5] == (2022, 3, 21, 11, 0)
288
    assert events[3].timetuple()[:5] == (2022, 3, 28, 11, 0)
289
    assert len(events) == 4
290

  
291
    timeperiod.weekday_indexes = [1]
292
    timeperiod.save()
293
    events = get_events(datetime.datetime(2022, 3, 1), datetime.datetime(2022, 4, 1))
294
    assert events[0].timetuple()[:5] == (2022, 3, 7, 11, 0)
295
    assert len(events) == 1
296

  
297
    timeperiod.weekday_indexes = [3, 4]
298
    timeperiod.save()
299
    events = get_events(datetime.datetime(2022, 3, 1), datetime.datetime(2022, 4, 1))
300
    assert events[0].timetuple()[:5] == (2022, 3, 21, 11, 0)
301
    assert events[1].timetuple()[:5] == (2022, 3, 28, 11, 0)
302
    assert len(events) == 2
303

  
304
    timeperiod.weekday_indexes = [5]
305
    timeperiod.save()
306
    assert get_events(datetime.datetime(2022, 3, 1), datetime.datetime(2022, 4, 1)) == []
307

  
308
    # month with five Mondays
309
    events = get_events(datetime.datetime(2022, 5, 1), datetime.datetime(2022, 6, 1))
310
    assert events[0].timetuple()[:5] == (2022, 5, 30, 11, 0)
311
    assert len(events) == 1
312

  
313
    # reduce ranges
314
    events = get_events(datetime.datetime(2022, 5, 30), datetime.datetime(2022, 5, 31))
315
    assert events[0].timetuple()[:5] == (2022, 5, 30, 11, 0)
316
    assert len(events) == 1
317

  
318
    assert get_events(datetime.datetime(2022, 5, 29), datetime.datetime(2022, 5, 30)) == []
319
    assert get_events(datetime.datetime(2022, 5, 1), datetime.datetime(2022, 5, 20)) == []
320

  
321
    # midnight overlap
322
    timeperiod.start_time = datetime.time(22, 0)
323
    timeperiod.end_time = datetime.time(23, 0)
324
    timeperiod.save()
325

  
326
    events = get_events(datetime.datetime(2022, 5, 1), datetime.datetime(2022, 6, 1))
327
    assert events[0].timetuple()[:5] == (2022, 5, 30, 22, 0)
328
    assert len(events) == 1
258
-