Projet

Général

Profil

0001-api-add-a-meta-dict-on-datetimes-endpoints-50278.patch

Nicolas Roche, 20 janvier 2021 09:27

Télécharger (15,5 ko)

Voir les différences:

Subject: [PATCH] api: add a meta dict on datetimes endpoints (#50278)

 chrono/agendas/models.py |   4 +-
 chrono/api/views.py      |  69 +++++++++----
 tests/test_api.py        | 209 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 260 insertions(+), 22 deletions(-)
chrono/agendas/models.py
744 744
        Then we iterate, advancing by base_duration minutes each time.
745 745

  
746 746
        If we cross the end_time of the period or end of the current_day
747 747
        (means end_time is midnight), it advance time to self.start_time on
748 748
        the next week (same weekday, same start, one week in the future).
749 749

  
750 750
        When it crosses end_datetime it stops.
751 751

  
752
        Generated start_datetime MUST be in the local timezone as the API
753
        needs it to generate stable ids.
752
        Generated start_datetime MUST be in the local timezone, and the local
753
        timezone must not change, as the API needs it to generate stable ids.
754 754
        """
755 755
        meeting_duration = datetime.timedelta(minutes=meeting_duration)
756 756
        duration = datetime.timedelta(minutes=base_duration)
757 757

  
758 758
        real_min_datetime = min_datetime + datetime.timedelta(days=self.weekday - min_datetime.weekday())
759 759
        if real_min_datetime < min_datetime:
760 760
            real_min_datetime += datetime.timedelta(days=7)
761 761

  
chrono/api/views.py
393 393
                    kwargs={'agenda_identifier': agenda.slug, 'event_identifier': event.slug},
394 394
                )
395 395
            ),
396 396
        },
397 397
        'places': get_event_places(event),
398 398
    }
399 399

  
400 400

  
401
def get_events_meta_detail(events):
402
    bookable_datetimes_number_total = 0
403
    bookable_datetimes_number_available = 0
404
    bookable_datetimes_first = None
405
    for event in events:
406
        bookable_datetimes_number_total += 1
407
        if not bool(event.full):
408
            bookable_datetimes_number_available += 1
409
            if not bookable_datetimes_first:
410
                bookable_datetimes_first = format_response_datetime(event.start_datetime)
411
    return {
412
        'no_bookable_datetimes': bool(bookable_datetimes_number_available == 0),
413
        'bookable_datetimes_number_total': bookable_datetimes_number_total,
414
        'bookable_datetimes_number_available': bookable_datetimes_number_available,
415
        'bookable_datetimes_first': bookable_datetimes_first,
416
    }
417

  
418

  
401 419
def get_resources_from_request(request, agenda):
402 420
    if agenda.kind != 'meetings' or 'resources' not in request.GET:
403 421
        return []
404 422
    resources_slugs = [s for s in request.GET['resources'].split(',') if s]
405 423
    resources = list(agenda.resources.filter(slug__in=resources_slugs))
406 424
    if len(resources) != len(resources_slugs):
407 425
        unknown_slugs = set(resources_slugs) - set([r.slug for r in resources])
408 426
        unknown_slugs = sorted(list(unknown_slugs))
......
497 515

  
498 516
        if 'date_end' in request.GET:
499 517
            entries = entries.filter(
500 518
                start_datetime__lt=make_aware(
501 519
                    datetime.datetime.combine(parse_date(request.GET['date_end']), datetime.time(0, 0))
502 520
                )
503 521
            )
504 522

  
505
        response = {'data': [get_event_detail(request, x, agenda=agenda) for x in entries]}
523
        response = {
524
            'data': [get_event_detail(request, x, agenda=agenda) for x in entries],
525
            'meta': get_events_meta_detail(entries),
526
        }
506 527
        return Response(response)
507 528

  
508 529

  
509 530
datetimes = Datetimes.as_view()
510 531

  
511 532

  
512 533
class MeetingDatetimes(APIView):
513 534
    permission_classes = ()
......
598 619
            reverse(
599 620
                'api-fillslot',
600 621
                kwargs={'agenda_identifier': agenda.slug, 'event_identifier': fake_event_identifier},
601 622
            )
602 623
        )
603 624
        if resources:
604 625
            fillslot_url += '?resources=%s' % ','.join(r.slug for r in resources)
605 626

  
606
        def make_id(start_datetime, meeting_type):
607
            """Make virtual id for a slot, combining meeting_type.id and
608
            iso-format of date and time.
609
            !!! The datetime must always be in the local timezone and the local
610
            timezone must not change if we want the id to be stable.
611
            It MUST be a garanty of SharedTimePeriod.get_time_slots(),
612
            !!!
613
            """
614
            return '%s:%s' % (
615
                meeting_type.slug,
616
                start_datetime.strftime('%Y-%m-%d-%H%M'),
617
            )
618

  
619
        response = {
620
            'data': [
627
        bookable_datetimes_number_total = 0
628
        bookable_datetimes_number_available = 0
629
        bookable_datetimes_first = None
630
        data = []
631
        for slot in generator_of_unique_slots:
632
            bookable_datetimes_number_total += 1
633
            if not bool(slot.full):
634
                bookable_datetimes_number_available += 1
635
                if not bookable_datetimes_first:
636
                    bookable_datetimes_first = format_response_datetime(slot.start_datetime)
637

  
638
            # Make virtual id for a slot, combining meeting_type.id and
639
            # iso-format of date and time.
640
            # (SharedTimePeriod.get_time_slots() generate datetime in fixed local timezone,
641
            # in order to make slot_id stable.)
642
            slot_id = '%s:%s' % (meeting_type.slug, slot.start_datetime.strftime('%Y-%m-%d-%H%M'))
643
            data.append(
621 644
                {
622 645
                    'id': slot_id,
623 646
                    'datetime': format_response_datetime(slot.start_datetime),
624 647
                    'text': date_format(slot.start_datetime, format='DATETIME_FORMAT'),
625 648
                    'disabled': bool(slot.full),
626 649
                    'api': {'fillslot_url': fillslot_url.replace(fake_event_identifier, slot_id)},
627 650
                }
628
                for slot in generator_of_unique_slots
629
                # we do not have the := operator, so we do that
630
                for slot_id in [make_id(slot.start_datetime, meeting_type)]
631
            ]
651
            )
652

  
653
        response = {
654
            'data': data,
655
            'meta': {
656
                'no_bookable_datetimes': bool(bookable_datetimes_number_available == 0),
657
                'bookable_datetimes_number_total': bookable_datetimes_number_total,
658
                'bookable_datetimes_number_available': bookable_datetimes_number_available,
659
                'bookable_datetimes_first': bookable_datetimes_first,
660
            },
632 661
        }
633 662
        return Response(response)
634 663

  
635 664

  
636 665
meeting_datetimes = MeetingDatetimes.as_view()
637 666

  
638 667

  
639 668
class MeetingList(APIView):
tests/test_api.py
4812 4812
    agenda_foo.save()
4813 4813
    resp = app.get(foo_api_url, params=params)
4814 4814
    assert len(resp.json['data']) == 16
4815 4815
    # also on virtual agenda
4816 4816
    virtual_agenda.maximal_booking_delay = 5
4817 4817
    virtual_agenda.save()
4818 4818
    resp = app.get(virtual_api_url, params=params)
4819 4819
    assert len(resp.json['data']) == 16
4820

  
4821

  
4822
def test_datetimes_api_meta(app, some_data):
4823
    agenda = Agenda.objects.filter(label=u'Foo bar')[0]
4824
    events = Event.objects.filter(agenda_id=agenda.id).exclude(start_datetime__lt=now())
4825
    assert len(events) == 3
4826
    api_url = '/api/agenda/%s/datetimes/' % agenda.slug
4827
    resp = app.get(api_url)
4828
    assert len(resp.json['data']) == 3
4829
    assert resp.json['meta'] == {
4830
        'no_bookable_datetimes': False,
4831
        'bookable_datetimes_number_total': 3,
4832
        'bookable_datetimes_number_available': 3,
4833
        'bookable_datetimes_first': '2017-05-21 17:00:00',
4834
    }
4835

  
4836
    def simulate_booking(event, nb_places):
4837
        for i in range(nb_places):
4838
            Booking(event=event).save()
4839

  
4840
    simulate_booking(events[0], 10)
4841
    resp = app.get(api_url)
4842
    assert len(resp.json['data']) == 3
4843
    assert resp.json['meta'] == {
4844
        'no_bookable_datetimes': False,
4845
        'bookable_datetimes_number_total': 3,
4846
        'bookable_datetimes_number_available': 3,
4847
        'bookable_datetimes_first': '2017-05-21 17:00:00',
4848
    }
4849

  
4850
    simulate_booking(events[0], 10)
4851
    resp = app.get(api_url)
4852
    assert len(resp.json['data']) == 3
4853
    assert resp.json['meta'] == {
4854
        'no_bookable_datetimes': False,
4855
        'bookable_datetimes_number_total': 3,
4856
        'bookable_datetimes_number_available': 2,
4857
        'bookable_datetimes_first': '2017-05-22 17:00:00',
4858
    }
4859

  
4860
    simulate_booking(events[1], 20)
4861
    simulate_booking(events[2], 20)
4862
    resp = app.get(api_url)
4863
    assert len(resp.json['data']) == 3
4864
    assert resp.json['meta'] == {
4865
        'no_bookable_datetimes': True,
4866
        'bookable_datetimes_number_total': 3,
4867
        'bookable_datetimes_number_available': 0,
4868
        'bookable_datetimes_first': None,
4869
    }
4870

  
4871

  
4872
def test_datetimes_api_meetings_agenda_meta(app, mock_now):
4873
    meetings_agenda = Agenda.objects.create(
4874
        label=u'Foo bar Meeting', kind='meetings', maximal_booking_delay=3
4875
    )
4876
    meeting_type = MeetingType(agenda=meetings_agenda, label='Blah', duration=30)
4877
    meeting_type.save()
4878

  
4879
    desk1 = Desk.objects.create(agenda=meetings_agenda, label='Desk 1')
4880
    desk2 = Desk.objects.create(agenda=meetings_agenda, label='Desk 2')
4881
    test_1st_weekday = (localtime(now()).weekday() + 2) % 7
4882
    for desk in desk1, desk2:
4883
        TimePeriod(
4884
            weekday=test_1st_weekday,
4885
            start_time=datetime.time(10, 0),
4886
            end_time=datetime.time(12, 0),
4887
            desk=desk,
4888
        ).save()
4889

  
4890
    meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
4891
    api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (meeting_type.agenda.slug, meeting_type.slug)
4892
    resp = app.get(api_url)
4893
    assert len(resp.json['data']) == 4
4894
    assert resp.json['data'][2]['disabled'] is False
4895
    assert resp.json['meta'] == {
4896
        'no_bookable_datetimes': False,
4897
        'bookable_datetimes_number_total': 4,
4898
        'bookable_datetimes_number_available': 4,
4899
        'bookable_datetimes_first': '2017-05-22 10:00:00',
4900
    }
4901

  
4902
    def simulate_booking(slot, desk):
4903
        dt = datetime.datetime.strptime(slot['id'].split(':')[1], '%Y-%m-%d-%H%M')
4904
        ev = Event(
4905
            agenda=meetings_agenda,
4906
            meeting_type=meeting_type,
4907
            places=1,
4908
            full=False,
4909
            start_datetime=make_aware(dt),
4910
            desk=desk,
4911
        )
4912
        ev.save()
4913
        booking = Booking(event=ev)
4914
        booking.save()
4915

  
4916
    simulate_booking(resp.json['data'][0], desk1)
4917
    resp = app.get(api_url)
4918
    assert len(resp.json['data']) == 4
4919
    assert resp.json['data'][0]['disabled'] is False
4920
    assert resp.json['meta'] == {
4921
        'no_bookable_datetimes': False,
4922
        'bookable_datetimes_number_total': 4,
4923
        'bookable_datetimes_number_available': 4,
4924
        'bookable_datetimes_first': '2017-05-22 10:00:00',
4925
    }
4926

  
4927
    simulate_booking(resp.json['data'][0], desk2)
4928
    resp = app.get(api_url)
4929
    assert len(resp.json['data']) == 4
4930
    assert resp.json['data'][0]['disabled'] is True
4931
    assert resp.json['meta'] == {
4932
        'no_bookable_datetimes': False,
4933
        'bookable_datetimes_number_total': 4,
4934
        'bookable_datetimes_number_available': 3,
4935
        'bookable_datetimes_first': '2017-05-22 10:30:00',
4936
    }
4937

  
4938
    for idx in range(1, 4):
4939
        simulate_booking(resp.json['data'][idx], desk1)
4940
        simulate_booking(resp.json['data'][idx], desk2)
4941
    resp = app.get(api_url)
4942
    assert len(resp.json['data']) == 4
4943
    assert resp.json['meta'] == {
4944
        'no_bookable_datetimes': True,
4945
        'bookable_datetimes_number_total': 4,
4946
        'bookable_datetimes_number_available': 0,
4947
        'bookable_datetimes_first': None,
4948
    }
4949

  
4950

  
4951
def test_datetimes_api_virtual_meetings_agenda_meta(app, virtual_meetings_agenda):
4952
    meetings_agenda1 = Agenda.objects.create(label=u'Foo Meeting', kind='meetings', maximal_booking_delay=3)
4953
    meetings_agenda2 = Agenda.objects.create(label=u'Bar Meeting', kind='meetings', maximal_booking_delay=3)
4954
    virtual_agenda = Agenda.objects.create(label=u'Agenda Virtual', kind='virtual', maximal_booking_delay=3)
4955
    VirtualMember.objects.create(virtual_agenda=virtual_agenda, real_agenda=meetings_agenda1)
4956
    VirtualMember.objects.create(virtual_agenda=virtual_agenda, real_agenda=meetings_agenda2)
4957
    desk1 = Desk.objects.create(agenda=meetings_agenda1, label='Desk 1')
4958
    desk2 = Desk.objects.create(agenda=meetings_agenda2, label='Desk 2')
4959

  
4960
    test_1st_weekday = (localtime(now()).weekday() + 2) % 7
4961
    for agenda, desk in zip((meetings_agenda1, meetings_agenda2), (desk1, desk2)):
4962
        meeting_type = MeetingType.objects.create(agenda=agenda, label='Blah', duration=30)
4963
        TimePeriod(
4964
            weekday=test_1st_weekday,
4965
            start_time=datetime.time(10, 0),
4966
            end_time=datetime.time(12, 0),
4967
            desk=desk,
4968
        ).save()
4969

  
4970
    virt_meeting_type = virtual_agenda.iter_meetingtypes()[0]
4971
    api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virtual_agenda.slug, virt_meeting_type.slug)
4972
    resp = app.get(api_url)
4973
    assert len(resp.json['data']) == 4
4974
    assert resp.json['data'][2]['disabled'] is False
4975
    assert resp.json['meta'] == {
4976
        'no_bookable_datetimes': False,
4977
        'bookable_datetimes_number_total': 4,
4978
        'bookable_datetimes_number_available': 4,
4979
        'bookable_datetimes_first': '2017-05-22 10:00:00',
4980
    }
4981

  
4982
    def simulate_booking(slot, agenda, desk):
4983
        dt = datetime.datetime.strptime(slot['id'].split(':')[1], '%Y-%m-%d-%H%M')
4984
        ev = Event(
4985
            agenda=agenda,
4986
            meeting_type=meeting_type,
4987
            places=1,
4988
            full=False,
4989
            start_datetime=make_aware(dt),
4990
            desk=desk,
4991
        )
4992
        ev.save()
4993
        booking = Booking(event=ev)
4994
        booking.save()
4995

  
4996
    simulate_booking(resp.json['data'][0], meetings_agenda1, desk1)
4997
    resp = app.get(api_url)
4998
    assert len(resp.json['data']) == 4
4999
    assert resp.json['data'][0]['disabled'] is False
5000
    assert resp.json['meta'] == {
5001
        'no_bookable_datetimes': False,
5002
        'bookable_datetimes_number_total': 4,
5003
        'bookable_datetimes_number_available': 4,
5004
        'bookable_datetimes_first': '2017-05-22 10:00:00',
5005
    }
5006

  
5007
    simulate_booking(resp.json['data'][0], meetings_agenda2, desk2)
5008
    resp = app.get(api_url)
5009
    assert len(resp.json['data']) == 4
5010
    assert resp.json['data'][0]['disabled'] is True
5011
    assert resp.json['meta'] == {
5012
        'no_bookable_datetimes': False,
5013
        'bookable_datetimes_number_total': 4,
5014
        'bookable_datetimes_number_available': 3,
5015
        'bookable_datetimes_first': '2017-05-22 10:30:00',
5016
    }
5017

  
5018
    for idx in range(1, 4):
5019
        simulate_booking(resp.json['data'][idx], meetings_agenda1, desk1)
5020
        simulate_booking(resp.json['data'][idx], meetings_agenda2, desk2)
5021
    resp = app.get(api_url)
5022
    assert len(resp.json['data']) == 4
5023
    assert resp.json['meta'] == {
5024
        'no_bookable_datetimes': True,
5025
        'bookable_datetimes_number_total': 4,
5026
        'bookable_datetimes_number_available': 0,
5027
        'bookable_datetimes_first': None,
5028
    }
4820
-