Projet

Général

Profil

0003-api-restrict-slots-with-exluded-timeperiods-40058.patch

Emmanuel Cazenave, 26 février 2020 18:30

Télécharger (15,4 ko)

Voir les différences:

Subject: [PATCH 3/3] api: restrict slots with exluded timeperiods (#40058)

 chrono/agendas/models.py |  46 +++++++++++++++
 chrono/api/views.py      |  26 +++++----
 tests/test_agendas.py    |  90 ++++++++++++++++++++++++++++++
 tests/test_api.py        | 117 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 268 insertions(+), 11 deletions(-)
chrono/agendas/models.py
62 62
    return slug
63 63

  
64 64

  
65
def compute_effective_timeperiods(effective_timeperiods, excluded_timeperiods):
66
    if not excluded_timeperiods:
67
        return effective_timeperiods
68

  
69
    res = []
70
    excluded_timeperiod = excluded_timeperiods[0]
71

  
72
    for effective_timeperiod in effective_timeperiods:
73
        if (
74
            excluded_timeperiod.weekday != effective_timeperiod.weekday
75
            or excluded_timeperiod.start_time >= effective_timeperiod.end_time
76
            or excluded_timeperiod.end_time <= effective_timeperiod.start_time
77
        ):
78
            res.append(effective_timeperiod)
79
            continue
80
        if (
81
            excluded_timeperiod.start_time <= effective_timeperiod.start_time
82
            and excluded_timeperiod.end_time >= effective_timeperiod.end_time
83
        ):
84
            # completely exclude
85
            continue
86
        if excluded_timeperiod.start_time > effective_timeperiod.start_time:
87
            res.append(
88
                TimePeriod(
89
                    weekday=effective_timeperiod.weekday,
90
                    start_time=effective_timeperiod.start_time,
91
                    end_time=excluded_timeperiod.start_time,
92
                    desk=effective_timeperiod.desk,
93
                )
94
            )
95
        if excluded_timeperiod.end_time < effective_timeperiod.end_time:
96
            res.append(
97
                TimePeriod(
98
                    weekday=effective_timeperiod.weekday,
99
                    start_time=excluded_timeperiod.end_time,
100
                    end_time=effective_timeperiod.end_time,
101
                    desk=effective_timeperiod.desk,
102
                )
103
            )
104

  
105
    return compute_effective_timeperiods(res, excluded_timeperiods[1:])
106

  
107

  
65 108
class ICSError(Exception):
66 109
    pass
67 110

  
......
331 374
            'end_time': self.end_time.strftime('%H:%M'),
332 375
        }
333 376

  
377
    def get_effective_timeperiods(self, excluded_timeperiods):
378
        return compute_effective_timeperiods([self], excluded_timeperiods)
379

  
334 380
    def get_time_slots(self, min_datetime, max_datetime, meeting_type):
335 381
        meeting_duration = datetime.timedelta(minutes=meeting_type.duration)
336 382
        duration = datetime.timedelta(minutes=self.desk.agenda.get_base_meeting_duration())
chrono/api/views.py
76 76
    base_date = now().date()
77 77

  
78 78
    agendas = agenda.get_real_agendas()
79
    base_agenda = agenda
79 80

  
80 81
    open_slots = {}
81 82
    for agenda in agendas:
......
88 89
        if used_time_period_filters['max_datetime'] is None:
89 90
            used_time_period_filters['max_datetime'] = get_max_datetime(agenda)
90 91

  
91
        for time_period in TimePeriod.objects.filter(desk__agenda=agenda):
92
            duration = (
93
                datetime.datetime.combine(base_date, time_period.end_time)
94
                - datetime.datetime.combine(base_date, time_period.start_time)
95
            ).seconds / 60
96
            if duration < meeting_type.duration:
97
                # skip time period that can't even hold a single meeting
98
                continue
99
            for slot in time_period.get_time_slots(**used_time_period_filters):
100
                slot.full = False
101
                open_slots[agenda][time_period.desk_id].add(slot.start_datetime, slot.end_datetime, slot)
92
        for raw_time_period in TimePeriod.objects.filter(desk__agenda=agenda):
93
            for time_period in raw_time_period.get_effective_timeperiods(
94
                base_agenda.excluded_timeperiods.all()
95
            ):
96
                duration = (
97
                    datetime.datetime.combine(base_date, time_period.end_time)
98
                    - datetime.datetime.combine(base_date, time_period.start_time)
99
                ).seconds / 60
100
                if duration < meeting_type.duration:
101
                    # skip time period that can't even hold a single meeting
102
                    continue
103
                for slot in time_period.get_time_slots(**used_time_period_filters):
104
                    slot.full = False
105
                    open_slots[agenda][time_period.desk_id].add(slot.start_datetime, slot.end_datetime, slot)
102 106

  
103 107
    # remove excluded slot
104 108
    for agenda in agendas:
tests/test_agendas.py
16 16
    Event,
17 17
    ICSError,
18 18
    MeetingType,
19
    TimePeriod,
19 20
    TimePeriodException,
20 21
    TimePeriodExceptionSource,
21 22
    VirtualMember,
......
584 585
    meeting_type = MeetingType(agenda=agenda2, label='Bar', duration=60)
585 586
    meeting_type.save()
586 587
    assert virt_agenda.get_base_meeting_duration() == 60
588

  
589

  
590
def test_get_effective_timeperiods():
591
    time_period = TimePeriod(weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0))
592
    # empty exclusion set
593
    effective_timeperiods = time_period.get_effective_timeperiods(TimePeriod.objects.none())
594
    assert len(effective_timeperiods) == 1
595
    effective_timeperiod = effective_timeperiods[0]
596
    assert effective_timeperiod.weekday == time_period.weekday
597
    assert effective_timeperiod.start_time == time_period.start_time
598
    assert effective_timeperiod.end_time == time_period.end_time
599

  
600
    # exclusions are on a different day
601
    excluded_timeperiods = [
602
        TimePeriod(weekday=1, start_time=datetime.time(17, 0), end_time=datetime.time(18, 0)),
603
        TimePeriod(weekday=2, start_time=datetime.time(17, 0), end_time=datetime.time(18, 0)),
604
    ]
605
    effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods)
606
    assert len(effective_timeperiods) == 1
607
    effective_timeperiod = effective_timeperiods[0]
608
    assert effective_timeperiod.weekday == time_period.weekday
609
    assert effective_timeperiod.start_time == time_period.start_time
610
    assert effective_timeperiod.end_time == time_period.end_time
611

  
612
    # one exclusion, end_time should be earlier
613
    excluded_timeperiods = [
614
        TimePeriod(weekday=0, start_time=datetime.time(17, 0), end_time=datetime.time(18, 0))
615
    ]
616
    effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods)
617
    assert len(effective_timeperiods) == 1
618
    effective_timeperiod = effective_timeperiods[0]
619
    assert effective_timeperiod.weekday == time_period.weekday
620
    assert effective_timeperiod.start_time == datetime.time(10, 0)
621
    assert effective_timeperiod.end_time == datetime.time(17, 0)
622

  
623
    # one exclusion, start_time should be later
624
    excluded_timeperiods = [
625
        TimePeriod(weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(16, 0))
626
    ]
627
    effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods)
628
    assert len(effective_timeperiods) == 1
629
    effective_timeperiod = effective_timeperiods[0]
630
    assert effective_timeperiod.weekday == time_period.weekday
631
    assert effective_timeperiod.start_time == datetime.time(16, 0)
632
    assert effective_timeperiod.end_time == datetime.time(18, 0)
633

  
634
    # one exclusion, splits effective timeperiod in two
635
    excluded_timeperiods = [
636
        TimePeriod(weekday=0, start_time=datetime.time(12, 0), end_time=datetime.time(16, 0))
637
    ]
638
    effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods)
639
    assert len(effective_timeperiods) == 2
640
    effective_timeperiod = effective_timeperiods[0]
641
    assert effective_timeperiod.weekday == time_period.weekday
642
    assert effective_timeperiod.start_time == datetime.time(10, 0)
643
    assert effective_timeperiod.end_time == datetime.time(12, 0)
644
    effective_timeperiod = effective_timeperiods[1]
645
    assert effective_timeperiod.weekday == time_period.weekday
646
    assert effective_timeperiod.start_time == datetime.time(16, 0)
647
    assert effective_timeperiod.end_time == datetime.time(18, 0)
648

  
649
    # several exclusion, splits effective timeperiod into pieces
650
    excluded_timeperiods = [
651
        TimePeriod(weekday=0, start_time=datetime.time(12, 0), end_time=datetime.time(13, 0)),
652
        TimePeriod(weekday=0, start_time=datetime.time(10, 30), end_time=datetime.time(11, 30)),
653
        TimePeriod(weekday=0, start_time=datetime.time(16, 30), end_time=datetime.time(17, 00)),
654
    ]
655
    effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods)
656
    assert len(effective_timeperiods) == 4
657

  
658
    effective_timeperiod = effective_timeperiods[0]
659
    assert effective_timeperiod.weekday == time_period.weekday
660
    assert effective_timeperiod.start_time == datetime.time(10, 0)
661
    assert effective_timeperiod.end_time == datetime.time(10, 30)
662

  
663
    effective_timeperiod = effective_timeperiods[1]
664
    assert effective_timeperiod.weekday == time_period.weekday
665
    assert effective_timeperiod.start_time == datetime.time(11, 30)
666
    assert effective_timeperiod.end_time == datetime.time(12, 0)
667

  
668
    effective_timeperiod = effective_timeperiods[2]
669
    assert effective_timeperiod.weekday == time_period.weekday
670
    assert effective_timeperiod.start_time == datetime.time(13, 0)
671
    assert effective_timeperiod.end_time == datetime.time(16, 30)
672

  
673
    effective_timeperiod = effective_timeperiods[3]
674
    assert effective_timeperiod.weekday == time_period.weekday
675
    assert effective_timeperiod.start_time == datetime.time(17, 0)
676
    assert effective_timeperiod.end_time == datetime.time(18, 0)
tests/test_api.py
2391 2391
    assert len(resp.json['data']) == 12
2392 2392

  
2393 2393

  
2394
def test_virtual_agendas_meetings_datetimes_exluded_periods(app, mock_now):
2395
    foo_agenda = Agenda.objects.create(label='Foo Meeting', kind='meetings', maximal_booking_delay=7)
2396
    MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30)
2397
    foo_desk_1 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1')
2398
    TimePeriod.objects.create(
2399
        weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_1,
2400
    )
2401
    TimePeriod.objects.create(
2402
        weekday=1, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_1,
2403
    )
2404
    virt_agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual')
2405
    VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda)
2406

  
2407
    api_url = '/api/agenda/%s/meetings/meeting-type/datetimes/' % (virt_agenda.slug)
2408
    resp = app.get(api_url)
2409
    # 8 slots
2410
    data = resp.json['data']
2411
    assert len(data) == 8
2412
    assert data[0]['datetime'] == '2017-05-22 10:00:00'
2413
    assert data[1]['datetime'] == '2017-05-22 10:30:00'
2414
    assert data[2]['datetime'] == '2017-05-22 11:00:00'
2415

  
2416
    # exclude one hour the first day
2417
    tp1 = TimePeriod.objects.create(
2418
        weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda
2419
    )
2420
    resp = app.get(api_url)
2421
    data = resp.json['data']
2422
    assert len(data) == 6
2423
    assert data[0]['datetime'] == '2017-05-22 10:00:00'
2424
    assert data[1]['datetime'] == '2017-05-22 10:30:00'
2425
    # no more slots the 22 thanks to the exclusion period
2426
    assert data[2]['datetime'] == '2017-05-23 10:00:00'
2427

  
2428
    # exclude the second day
2429
    tp2 = TimePeriod.objects.create(
2430
        weekday=1, start_time=datetime.time(9, 0), end_time=datetime.time(18, 0), agenda=virt_agenda
2431
    )
2432
    resp = app.get(api_url)
2433
    data = resp.json['data']
2434
    assert len(data) == 2
2435
    assert data[0]['datetime'] == '2017-05-22 10:00:00'
2436
    assert data[1]['datetime'] == '2017-05-22 10:30:00'
2437

  
2438
    # go back to no restriction
2439
    tp1.delete()
2440
    tp2.delete()
2441
    resp = app.get(api_url)
2442
    data = resp.json['data']
2443
    assert len(data) == 8
2444

  
2445
    # excluded period applies to every desk
2446
    foo_desk_2 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 2')
2447
    TimePeriod.objects.create(
2448
        weekday=3, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_2,
2449
    )
2450
    TimePeriod.objects.create(
2451
        weekday=4, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_2,
2452
    )
2453
    resp = app.get(api_url)
2454
    data = resp.json['data']
2455
    assert len(data) == 16
2456

  
2457
    # exclude one hour the first day
2458
    tp1 = TimePeriod.objects.create(
2459
        weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda
2460
    )
2461
    resp = app.get(api_url)
2462
    data = resp.json['data']
2463
    assert len(data) == 14
2464

  
2465
    # exclude one hour the last day
2466
    tp2 = TimePeriod.objects.create(
2467
        weekday=4, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda
2468
    )
2469
    resp = app.get(api_url)
2470
    data = resp.json['data']
2471
    assert len(data) == 12
2472

  
2473
    # go back to no restriction
2474
    tp1.delete()
2475
    tp2.delete()
2476
    resp = app.get(api_url)
2477
    data = resp.json['data']
2478
    assert len(data) == 16
2479

  
2480
    # add a second real agenda
2481
    bar_agenda = Agenda.objects.create(label='Bar Meeting', kind='meetings', maximal_booking_delay=7)
2482
    VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda)
2483
    MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type', duration=30)
2484
    bar_desk_1 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 1')
2485
    bar_desk_2 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 2')
2486
    TimePeriod.objects.create(
2487
        weekday=0, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0), desk=bar_desk_1,
2488
    )
2489
    TimePeriod.objects.create(
2490
        weekday=1, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0), desk=bar_desk_1,
2491
    )
2492
    TimePeriod.objects.create(
2493
        weekday=2, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0), desk=bar_desk_2,
2494
    )
2495
    TimePeriod.objects.create(
2496
        weekday=3, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0), desk=bar_desk_2,
2497
    )
2498
    resp = app.get(api_url)
2499
    data = resp.json['data']
2500
    assert len(data) == 32
2501

  
2502
    # exclude the first day, 11 to 15 : 4 slots
2503
    tp1 = TimePeriod.objects.create(
2504
        weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(15, 0), agenda=virt_agenda
2505
    )
2506
    resp = app.get(api_url)
2507
    data = resp.json['data']
2508
    assert len(data) == 28
2509

  
2510

  
2394 2511
def test_virtual_agendas_meetings_exception(app, user, virtual_meetings_agenda):
2395 2512
    app.authorization = ('Basic', ('john.doe', 'password'))
2396 2513
    real_agenda = virtual_meetings_agenda.real_agendas.first()
2397
-