Projet

Général

Profil

0003-api-exclude-slots-already-booked-by-user-meetings-51.patch

Lauréline Guérin, 25 février 2021 10:58

Télécharger (10,7 ko)

Voir les différences:

Subject: [PATCH 3/3] api: exclude slots already booked by user - meetings
 (#51341)

 chrono/api/views.py |  42 +++++++++++++-
 tests/test_api.py   | 138 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 178 insertions(+), 2 deletions(-)
chrono/api/views.py
90 90

  
91 91

  
92 92
def get_all_slots(
93
    base_agenda, meeting_type, resources=None, unique=False, start_datetime=None, end_datetime=None
93
    base_agenda,
94
    meeting_type,
95
    resources=None,
96
    unique=False,
97
    start_datetime=None,
98
    end_datetime=None,
99
    excluded_user_external_id=None,
94 100
):
95 101
    """Get all occupation state of all possible slots for the given agenda (of
96 102
    its real agendas for a virtual agenda) and the given meeting_type.
......
243 249
            for event_start_datetime, event_duration in booked_events
244 250
        )
245 251

  
252
    # aggregate already booked time intervals by excluded_user_external_id
253
    user_bookings = IntervalSet()
254
    if excluded_user_external_id:
255
        used_min_datetime, used_max_datetime = (
256
            min([v[0] for v in agenda_id_min_max_datetime.values()]),
257
            max([v[1] for v in agenda_id_min_max_datetime.values()]),
258
        )
259
        booked_events = (
260
            Event.objects.filter(
261
                agenda__in=agenda_ids,
262
                start_datetime__gte=used_min_datetime,
263
                start_datetime__lte=used_max_datetime + meeting_duration_td,
264
                booking__user_external_id=excluded_user_external_id,
265
            )
266
            .exclude(booking__cancellation_datetime__isnull=False)
267
            # ordering is important for the later groupby, it works like sort | uniq
268
            .order_by('start_datetime', 'meeting_type__duration')
269
            .values_list('start_datetime', 'meeting_type__duration')
270
        )
271
        # compute exclusion set by desk from all bookings, using
272
        # itertools.groupby() to group them by desk_id
273
        user_bookings = IntervalSet.from_ordered(
274
            (event_start_datetime, event_start_datetime + datetime.timedelta(minutes=event_duration))
275
            for event_start_datetime, event_duration in booked_events
276
        )
277

  
246 278
    unique_booked = {}
247 279
    for time_period in base_agenda.get_effective_time_periods():
248 280
        duration = (
......
290 322
                    # slot is full if an already booked event overlaps it
291 323
                    # check resources first
292 324
                    booked = resources_bookings.overlaps(start_datetime, end_datetime)
325
                    # then check user boookings
326
                    if not booked:
327
                        booked = user_bookings.overlaps(start_datetime, end_datetime)
328
                    # then bookings if resources are free
293 329
                    if not booked:
294
                        # then bookings if resources are free
295 330
                        booked = desk.id in bookings and bookings[desk.id].overlaps(
296 331
                            start_datetime, end_datetime
297 332
                        )
......
624 659
                    http_status=status.HTTP_400_BAD_REQUEST,
625 660
                )
626 661

  
662
        user_external_id = request.GET.get('exclude_user_external_id') or None
663

  
627 664
        # Generate an unique slot for each possible meeting [start_datetime,
628 665
        # end_datetime] range.
629 666
        # First use get_all_slots() to get each possible meeting by desk and
......
645 682
                    unique=True,
646 683
                    start_datetime=start_datetime,
647 684
                    end_datetime=end_datetime,
685
                    excluded_user_external_id=user_external_id,
648 686
                )
649 687
            )
650 688
            for slot in sorted(all_slots, key=lambda slot: slot[:3]):
tests/test_api.py
986 986
    assert resp.json['err_desc'] == 'no more desk available'
987 987

  
988 988

  
989
@pytest.mark.freeze_time('2021-02-25')
990
def test_datetimes_api_meetings_agenda_exclude_slots(app):
991
    tomorrow = now() + datetime.timedelta(days=1)
992
    agenda = Agenda.objects.create(
993
        label='Agenda', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=10
994
    )
995
    desk = Desk.objects.create(agenda=agenda, slug='desk')
996
    meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo-bar')
997
    TimePeriod.objects.create(
998
        weekday=tomorrow.date().weekday(),
999
        start_time=datetime.time(9, 0),
1000
        end_time=datetime.time(17, 00),
1001
        desk=desk,
1002
    )
1003
    desk.duplicate()
1004
    event = Event.objects.create(
1005
        agenda=agenda,
1006
        meeting_type=meeting_type,
1007
        places=1,
1008
        start_datetime=localtime(tomorrow).replace(hour=9, minute=0),
1009
        desk=desk,
1010
    )
1011
    Booking.objects.create(event=event, user_external_id='42')
1012
    event2 = Event.objects.create(
1013
        agenda=agenda,
1014
        meeting_type=meeting_type,
1015
        places=1,
1016
        start_datetime=localtime(tomorrow).replace(hour=10, minute=0),
1017
        desk=desk,
1018
    )
1019
    cancelled = Booking.objects.create(event=event2, user_external_id='35')
1020
    cancelled.cancel()
1021

  
1022
    resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug))
1023
    assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
1024
    assert resp.json['data'][0]['disabled'] is False
1025
    assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
1026
    assert resp.json['data'][2]['disabled'] is False
1027

  
1028
    resp = app.get(
1029
        '/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug),
1030
        params={'exclude_user_external_id': '35'},
1031
    )
1032
    assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
1033
    assert resp.json['data'][0]['disabled'] is False
1034
    assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
1035
    assert resp.json['data'][2]['disabled'] is False
1036

  
1037
    with CaptureQueriesContext(connection) as ctx:
1038
        resp = app.get(
1039
            '/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug),
1040
            params={'exclude_user_external_id': '42'},
1041
        )
1042
        assert len(ctx.captured_queries) == 9
1043
    assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
1044
    assert resp.json['data'][0]['disabled'] is True
1045
    assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
1046
    assert resp.json['data'][2]['disabled'] is False
1047

  
1048

  
989 1049
def test_booking_api(app, some_data, user):
990 1050
    agenda = Agenda.objects.filter(label=u'Foo bar')[0]
991 1051
    event = [x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()][0]
......
4540 4600
    assert len(resp.json['data']) == 20
4541 4601

  
4542 4602

  
4603
@pytest.mark.freeze_time('2021-02-25')
4604
def test_virtual_agendas_meetings_datetimes_exclude_slots(app):
4605
    tomorrow = now() + datetime.timedelta(days=1)
4606
    agenda = Agenda.objects.create(
4607
        label='Agenda', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=10
4608
    )
4609
    desk = Desk.objects.create(agenda=agenda, slug='desk')
4610
    meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo-bar')
4611
    TimePeriod.objects.create(
4612
        weekday=tomorrow.date().weekday(),
4613
        start_time=datetime.time(9, 0),
4614
        end_time=datetime.time(17, 00),
4615
        desk=desk,
4616
    )
4617
    agenda2 = agenda.duplicate()
4618
    virt_agenda = Agenda.objects.create(
4619
        label='Virtual Agenda', kind='virtual', minimal_booking_delay=1, maximal_booking_delay=10
4620
    )
4621
    VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=agenda)
4622
    VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=agenda2)
4623

  
4624
    event = Event.objects.create(
4625
        agenda=agenda,
4626
        meeting_type=meeting_type,
4627
        places=1,
4628
        start_datetime=localtime(tomorrow).replace(hour=9, minute=0),
4629
        desk=desk,
4630
    )
4631
    Booking.objects.create(event=event, user_external_id='42')
4632
    event2 = Event.objects.create(
4633
        agenda=agenda,
4634
        meeting_type=meeting_type,
4635
        places=1,
4636
        start_datetime=localtime(tomorrow).replace(hour=10, minute=0),
4637
        desk=desk,
4638
    )
4639
    cancelled = Booking.objects.create(event=event2, user_external_id='35')
4640
    cancelled.cancel()
4641

  
4642
    resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, meeting_type.slug))
4643
    assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
4644
    assert resp.json['data'][0]['disabled'] is False
4645
    assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
4646
    assert resp.json['data'][2]['disabled'] is False
4647

  
4648
    resp = app.get(
4649
        '/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, meeting_type.slug),
4650
        params={'exclude_user_external_id': '35'},
4651
    )
4652
    assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
4653
    assert resp.json['data'][0]['disabled'] is False
4654
    assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
4655
    assert resp.json['data'][2]['disabled'] is False
4656

  
4657
    with CaptureQueriesContext(connection) as ctx:
4658
        resp = app.get(
4659
            '/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, meeting_type.slug),
4660
            params={'exclude_user_external_id': '42'},
4661
        )
4662
        assert len(ctx.captured_queries) == 13
4663
    assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
4664
    assert resp.json['data'][0]['disabled'] is True
4665
    assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
4666
    assert resp.json['data'][2]['disabled'] is False
4667

  
4668
    virt_agenda.minimal_booking_delay = None
4669
    virt_agenda.maximal_booking_delay = None
4670
    virt_agenda.save()
4671
    resp = app.get(
4672
        '/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, meeting_type.slug),
4673
        params={'exclude_user_external_id': '42'},
4674
    )
4675
    assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
4676
    assert resp.json['data'][0]['disabled'] is True
4677
    assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
4678
    assert resp.json['data'][2]['disabled'] is False
4679

  
4680

  
4543 4681
def test_virtual_agendas_meetings_booking(app, mock_now, user):
4544 4682
    foo_agenda = Agenda.objects.create(
4545 4683
        label='Foo Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=5
4546
-