Projet

Général

Profil

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

Emmanuel Cazenave, 16 mars 2020 19:21

Télécharger (15,3 ko)

Voir les différences:

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

 chrono/agendas/models.py |  41 ++++++++++++++
 chrono/api/views.py      |  26 +++++----
 tests/test_agendas.py    |  90 ++++++++++++++++++++++++++++++
 tests/test_api.py        | 117 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 263 insertions(+), 11 deletions(-)
chrono/agendas/models.py
333 333
            'end_time': self.end_time.strftime('%H:%M'),
334 334
        }
335 335

  
336
    def get_effective_timeperiods(self, excluded_timeperiods):
337
        effective_timeperiods = [self]
338
        for excluded_timeperiod in excluded_timeperiods:
339
            res = []
340
            for effective_timeperiod in effective_timeperiods:
341
                if (
342
                    excluded_timeperiod.weekday != effective_timeperiod.weekday
343
                    or excluded_timeperiod.start_time >= effective_timeperiod.end_time
344
                    or excluded_timeperiod.end_time <= effective_timeperiod.start_time
345
                ):
346
                    res.append(effective_timeperiod)
347
                    continue
348
                if (
349
                    excluded_timeperiod.start_time <= effective_timeperiod.start_time
350
                    and excluded_timeperiod.end_time >= effective_timeperiod.end_time
351
                ):
352
                    # completely exclude
353
                    continue
354
                if excluded_timeperiod.start_time > effective_timeperiod.start_time:
355
                    res.append(
356
                        TimePeriod(
357
                            weekday=effective_timeperiod.weekday,
358
                            start_time=effective_timeperiod.start_time,
359
                            end_time=excluded_timeperiod.start_time,
360
                            desk=effective_timeperiod.desk,
361
                        )
362
                    )
363
                if excluded_timeperiod.end_time < effective_timeperiod.end_time:
364
                    res.append(
365
                        TimePeriod(
366
                            weekday=effective_timeperiod.weekday,
367
                            start_time=excluded_timeperiod.end_time,
368
                            end_time=effective_timeperiod.end_time,
369
                            desk=effective_timeperiod.desk,
370
                        )
371
                    )
372

  
373
            effective_timeperiods = res
374

  
375
        return effective_timeperiods
376

  
336 377
    def get_time_slots(self, min_datetime, max_datetime, meeting_type):
337 378
        meeting_duration = datetime.timedelta(minutes=meeting_type.duration)
338 379
        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,
......
594 595
    meeting_type = MeetingType(agenda=agenda2, label='Bar', duration=60)
595 596
    meeting_type.save()
596 597
    assert virt_agenda.get_base_meeting_duration() == 60
598

  
599

  
600
def test_get_effective_timeperiods():
601
    time_period = TimePeriod(weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0))
602
    # empty exclusion set
603
    effective_timeperiods = time_period.get_effective_timeperiods(TimePeriod.objects.none())
604
    assert len(effective_timeperiods) == 1
605
    effective_timeperiod = effective_timeperiods[0]
606
    assert effective_timeperiod.weekday == time_period.weekday
607
    assert effective_timeperiod.start_time == time_period.start_time
608
    assert effective_timeperiod.end_time == time_period.end_time
609

  
610
    # exclusions are on a different day
611
    excluded_timeperiods = [
612
        TimePeriod(weekday=1, start_time=datetime.time(17, 0), end_time=datetime.time(18, 0)),
613
        TimePeriod(weekday=2, start_time=datetime.time(17, 0), end_time=datetime.time(18, 0)),
614
    ]
615
    effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods)
616
    assert len(effective_timeperiods) == 1
617
    effective_timeperiod = effective_timeperiods[0]
618
    assert effective_timeperiod.weekday == time_period.weekday
619
    assert effective_timeperiod.start_time == time_period.start_time
620
    assert effective_timeperiod.end_time == time_period.end_time
621

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

  
633
    # one exclusion, start_time should be later
634
    excluded_timeperiods = [
635
        TimePeriod(weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(16, 0))
636
    ]
637
    effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods)
638
    assert len(effective_timeperiods) == 1
639
    effective_timeperiod = effective_timeperiods[0]
640
    assert effective_timeperiod.weekday == time_period.weekday
641
    assert effective_timeperiod.start_time == datetime.time(16, 0)
642
    assert effective_timeperiod.end_time == datetime.time(18, 0)
643

  
644
    # one exclusion, splits effective timeperiod in two
645
    excluded_timeperiods = [
646
        TimePeriod(weekday=0, start_time=datetime.time(12, 0), end_time=datetime.time(16, 0))
647
    ]
648
    effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods)
649
    assert len(effective_timeperiods) == 2
650
    effective_timeperiod = effective_timeperiods[0]
651
    assert effective_timeperiod.weekday == time_period.weekday
652
    assert effective_timeperiod.start_time == datetime.time(10, 0)
653
    assert effective_timeperiod.end_time == datetime.time(12, 0)
654
    effective_timeperiod = effective_timeperiods[1]
655
    assert effective_timeperiod.weekday == time_period.weekday
656
    assert effective_timeperiod.start_time == datetime.time(16, 0)
657
    assert effective_timeperiod.end_time == datetime.time(18, 0)
658

  
659
    # several exclusion, splits effective timeperiod into pieces
660
    excluded_timeperiods = [
661
        TimePeriod(weekday=0, start_time=datetime.time(12, 0), end_time=datetime.time(13, 0)),
662
        TimePeriod(weekday=0, start_time=datetime.time(10, 30), end_time=datetime.time(11, 30)),
663
        TimePeriod(weekday=0, start_time=datetime.time(16, 30), end_time=datetime.time(17, 00)),
664
    ]
665
    effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods)
666
    assert len(effective_timeperiods) == 4
667

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

  
673
    effective_timeperiod = effective_timeperiods[1]
674
    assert effective_timeperiod.weekday == time_period.weekday
675
    assert effective_timeperiod.start_time == datetime.time(11, 30)
676
    assert effective_timeperiod.end_time == datetime.time(12, 0)
677

  
678
    effective_timeperiod = effective_timeperiods[2]
679
    assert effective_timeperiod.weekday == time_period.weekday
680
    assert effective_timeperiod.start_time == datetime.time(13, 0)
681
    assert effective_timeperiod.end_time == datetime.time(16, 30)
682

  
683
    effective_timeperiod = effective_timeperiods[3]
684
    assert effective_timeperiod.weekday == time_period.weekday
685
    assert effective_timeperiod.start_time == datetime.time(17, 0)
686
    assert effective_timeperiod.end_time == datetime.time(18, 0)
tests/test_api.py
2505 2505
    assert len(resp.json['data']) == 12
2506 2506

  
2507 2507

  
2508
def test_virtual_agendas_meetings_datetimes_exluded_periods(app, mock_now):
2509
    foo_agenda = Agenda.objects.create(label='Foo Meeting', kind='meetings', maximal_booking_delay=7)
2510
    MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30)
2511
    foo_desk_1 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1')
2512
    TimePeriod.objects.create(
2513
        weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_1,
2514
    )
2515
    TimePeriod.objects.create(
2516
        weekday=1, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_1,
2517
    )
2518
    virt_agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual')
2519
    VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda)
2520

  
2521
    api_url = '/api/agenda/%s/meetings/meeting-type/datetimes/' % (virt_agenda.slug)
2522
    resp = app.get(api_url)
2523
    # 8 slots
2524
    data = resp.json['data']
2525
    assert len(data) == 8
2526
    assert data[0]['datetime'] == '2017-05-22 10:00:00'
2527
    assert data[1]['datetime'] == '2017-05-22 10:30:00'
2528
    assert data[2]['datetime'] == '2017-05-22 11:00:00'
2529

  
2530
    # exclude one hour the first day
2531
    tp1 = TimePeriod.objects.create(
2532
        weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda
2533
    )
2534
    resp = app.get(api_url)
2535
    data = resp.json['data']
2536
    assert len(data) == 6
2537
    assert data[0]['datetime'] == '2017-05-22 10:00:00'
2538
    assert data[1]['datetime'] == '2017-05-22 10:30:00'
2539
    # no more slots the 22 thanks to the exclusion period
2540
    assert data[2]['datetime'] == '2017-05-23 10:00:00'
2541

  
2542
    # exclude the second day
2543
    tp2 = TimePeriod.objects.create(
2544
        weekday=1, start_time=datetime.time(9, 0), end_time=datetime.time(18, 0), agenda=virt_agenda
2545
    )
2546
    resp = app.get(api_url)
2547
    data = resp.json['data']
2548
    assert len(data) == 2
2549
    assert data[0]['datetime'] == '2017-05-22 10:00:00'
2550
    assert data[1]['datetime'] == '2017-05-22 10:30:00'
2551

  
2552
    # go back to no restriction
2553
    tp1.delete()
2554
    tp2.delete()
2555
    resp = app.get(api_url)
2556
    data = resp.json['data']
2557
    assert len(data) == 8
2558

  
2559
    # excluded period applies to every desk
2560
    foo_desk_2 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 2')
2561
    TimePeriod.objects.create(
2562
        weekday=3, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_2,
2563
    )
2564
    TimePeriod.objects.create(
2565
        weekday=4, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_2,
2566
    )
2567
    resp = app.get(api_url)
2568
    data = resp.json['data']
2569
    assert len(data) == 16
2570

  
2571
    # exclude one hour the first day
2572
    tp1 = TimePeriod.objects.create(
2573
        weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda
2574
    )
2575
    resp = app.get(api_url)
2576
    data = resp.json['data']
2577
    assert len(data) == 14
2578

  
2579
    # exclude one hour the last day
2580
    tp2 = TimePeriod.objects.create(
2581
        weekday=4, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda
2582
    )
2583
    resp = app.get(api_url)
2584
    data = resp.json['data']
2585
    assert len(data) == 12
2586

  
2587
    # go back to no restriction
2588
    tp1.delete()
2589
    tp2.delete()
2590
    resp = app.get(api_url)
2591
    data = resp.json['data']
2592
    assert len(data) == 16
2593

  
2594
    # add a second real agenda
2595
    bar_agenda = Agenda.objects.create(label='Bar Meeting', kind='meetings', maximal_booking_delay=7)
2596
    VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda)
2597
    MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type', duration=30)
2598
    bar_desk_1 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 1')
2599
    bar_desk_2 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 2')
2600
    TimePeriod.objects.create(
2601
        weekday=0, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0), desk=bar_desk_1,
2602
    )
2603
    TimePeriod.objects.create(
2604
        weekday=1, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0), desk=bar_desk_1,
2605
    )
2606
    TimePeriod.objects.create(
2607
        weekday=2, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0), desk=bar_desk_2,
2608
    )
2609
    TimePeriod.objects.create(
2610
        weekday=3, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0), desk=bar_desk_2,
2611
    )
2612
    resp = app.get(api_url)
2613
    data = resp.json['data']
2614
    assert len(data) == 32
2615

  
2616
    # exclude the first day, 11 to 15 : 4 slots
2617
    tp1 = TimePeriod.objects.create(
2618
        weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(15, 0), agenda=virt_agenda
2619
    )
2620
    resp = app.get(api_url)
2621
    data = resp.json['data']
2622
    assert len(data) == 28
2623

  
2624

  
2508 2625
def test_virtual_agendas_meetings_exception(app, user, virtual_meetings_agenda):
2509 2626
    app.authorization = ('Basic', ('john.doe', 'password'))
2510 2627
    real_agenda = virtual_meetings_agenda.real_agendas.first()
2511
-