Projet

Général

Profil

0001-api-endpoint-to-get-a-list-of-serialized-events-6687.patch

Lauréline Guérin, 01 juillet 2022 18:02

Télécharger (12,6 ko)

Voir les différences:

Subject: [PATCH] api: endpoint to get a list of serialized events (#66874)

 chrono/api/serializers.py |  14 ++--
 chrono/api/urls.py        |   5 ++
 chrono/api/views.py       |  54 ++++++++++++--
 tests/api/test_event.py   | 150 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 212 insertions(+), 11 deletions(-)
chrono/api/serializers.py
67 67
        return Agenda.objects.filter(kind='events').select_related('events_type')
68 68

  
69 69

  
70
class SlotSerializer(serializers.Serializer):
70
class FillSlotSerializer(serializers.Serializer):
71 71
    label = serializers.CharField(max_length=250, allow_blank=True)
72 72
    user_external_id = serializers.CharField(max_length=250, allow_blank=True)
73 73
    user_name = serializers.CharField(max_length=250, allow_blank=True)  # compatibility
......
95 95
    check_overlaps = serializers.BooleanField(default=False)
96 96

  
97 97

  
98
class SlotsSerializer(SlotSerializer):
98
class SlotsSerializer(serializers.Serializer):
99 99
    slots = StringOrListField(required=True, child=serializers.CharField(max_length=160, allow_blank=False))
100 100

  
101 101
    def validate(self, attrs):
......
105 105
        return attrs
106 106

  
107 107

  
108
class EventsSlotsSerializer(SlotSerializer):
108
class FillSlotsSerializer(FillSlotSerializer, SlotsSerializer):
109
    pass
110

  
111

  
112
class EventsFillSlotsSerializer(FillSlotSerializer):
109 113
    slots = StringOrListField(required=True, child=serializers.CharField(max_length=160))
110 114

  
111 115
    def validate(self, attrs):
......
117 121
        return attrs
118 122

  
119 123

  
120
class MultipleAgendasEventsSlotsSerializer(EventsSlotsSerializer):
124
class MultipleAgendasEventsFillSlotsSerializer(EventsFillSlotsSerializer):
121 125
    def validate_slots(self, value):
122 126
        allowed_agenda_slugs = self.context['allowed_agenda_slugs']
123 127
        slots_agenda_slugs = set()
......
151 155
        return get_objects_from_slugs(value, qs=self.get_agenda_qs())
152 156

  
153 157

  
154
class RecurringFillslotsSerializer(MultipleAgendasEventsSlotsSerializer):
158
class RecurringFillslotsSerializer(MultipleAgendasEventsFillSlotsSerializer):
155 159
    include_booked_events_detail = serializers.BooleanField(default=False)
156 160

  
157 161
    def validate_slots(self, value):
chrono/api/urls.py
23 23
    url(r'^agendas/datetimes/$', views.agendas_datetimes, name='api-agendas-datetimes'),
24 24
    url(r'^agendas/recurring-events/$', views.recurring_events_list, name='api-agenda-recurring-events'),
25 25
    url(r'^agendas/recurring-events/fillslots/$', views.recurring_fillslots, name='api-recurring-fillslots'),
26
    url(
27
        r'^agendas/events/$',
28
        views.agendas_events,
29
        name='api-agendas-events',
30
    ),
26 31
    url(
27 32
        r'^agendas/events/fillslots/$',
28 33
        views.agendas_events_fillslots,
chrono/api/views.py
676 676
    return list(get_objects_from_slugs(resources_slugs, qs=agenda.resources))
677 677

  
678 678

  
679
def get_objects_from_slugs(slugs, qs):
679
def get_objects_from_slugs(slugs, qs, prefix=''):
680 680
    slugs = set(slugs)
681 681
    objects = qs.filter(slug__in=slugs)
682 682
    if len(objects) != len(slugs):
683 683
        unknown_slugs = sorted(slugs - {obj.slug for obj in objects})
684
        unknown_slugs = ', '.join(unknown_slugs)
684
        unknown_slugs = ', '.join('%s%s' % (prefix, s) for s in unknown_slugs)
685 685
        raise APIErrorBadRequest(N_('invalid slugs: %s'), unknown_slugs)
686 686
    return objects
687 687

  
......
1314 1314

  
1315 1315
class Fillslots(APIView):
1316 1316
    permission_classes = (permissions.IsAuthenticated,)
1317
    serializer_class = serializers.SlotsSerializer
1317
    serializer_class = serializers.FillSlotsSerializer
1318 1318

  
1319 1319
    def post(self, request, agenda_identifier=None, event_identifier=None, format=None):
1320 1320
        return self.fillslot(request=request, agenda_identifier=agenda_identifier, format=format)
......
1633 1633

  
1634 1634

  
1635 1635
class Fillslot(Fillslots):
1636
    serializer_class = serializers.SlotSerializer
1636
    serializer_class = serializers.FillSlotSerializer
1637 1637

  
1638 1638
    def post(self, request, agenda_identifier=None, event_identifier=None, format=None):
1639 1639
        return self.fillslot(
......
1857 1857

  
1858 1858
class EventsFillslots(APIView):
1859 1859
    permission_classes = (permissions.IsAuthenticated,)
1860
    serializer_class = serializers.EventsSlotsSerializer
1860
    serializer_class = serializers.EventsFillSlotsSerializer
1861 1861
    serializer_extra_context = None
1862 1862
    multiple_agendas = False
1863 1863

  
......
2019 2019

  
2020 2020

  
2021 2021
class MultipleAgendasEventsFillslots(EventsFillslots):
2022
    serializer_class = serializers.MultipleAgendasEventsSlotsSerializer
2022
    serializer_class = serializers.MultipleAgendasEventsFillSlotsSerializer
2023 2023
    multiple_agendas = True
2024 2024

  
2025 2025
    def post(self, request):
......
2095 2095
agendas_events_fillslots = MultipleAgendasEventsFillslots.as_view()
2096 2096

  
2097 2097

  
2098
class MultipleAgendasEvents(APIView):
2099
    permission_classes = (permissions.IsAuthenticated,)
2100
    serializer_class = serializers.SlotsSerializer
2101

  
2102
    def get(self, request):
2103
        serializer = self.serializer_class(data=request.query_params)
2104

  
2105
        if not serializer.is_valid():
2106
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors, err=1)
2107

  
2108
        slots = serializer.validated_data['slots']
2109
        events_by_agenda = collections.defaultdict(list)
2110
        for slot in slots:
2111
            agenda, event = slot.split('@')
2112
            events_by_agenda[agenda].append(event)
2113

  
2114
        agendas = get_objects_from_slugs(events_by_agenda.keys(), qs=Agenda.objects.filter(kind='events'))
2115
        agendas_by_slug = {a.slug: a for a in agendas}
2116

  
2117
        events = []
2118
        for agenda_slug, event_slugs in events_by_agenda.items():
2119
            events += get_objects_from_slugs(
2120
                event_slugs,
2121
                qs=agendas_by_slug[agenda_slug]
2122
                .event_set.filter(cancelled=False, recurrence_days__isnull=True)
2123
                .prefetch_related(Prefetch('primary_event', queryset=Event.objects.all().order_by()))
2124
                .order_by(),
2125
                prefix='%s@' % agenda_slug,
2126
            )
2127

  
2128
        data = []
2129
        event_querystring_indexes = {event_slug: i for i, event_slug in enumerate(slots)}
2130
        events.sort(key=lambda event: (event_querystring_indexes['%s@%s' % (event.agenda.slug, event.slug)],))
2131
        for event in events:
2132
            data.append(serializers.EventSerializer(event).data)
2133

  
2134
        return Response({'err': 0, 'data': data})
2135

  
2136

  
2137
agendas_events = MultipleAgendasEvents.as_view()
2138

  
2139

  
2098 2140
class MultipleAgendasEventsCheckStatus(APIView):
2099 2141
    permission_classes = (permissions.IsAuthenticated,)
2100 2142
    serializer_class = serializers.MultipleAgendasEventsCheckStatusSerializer
tests/api/test_event.py
818 818
    assert not Event.objects.exists()
819 819

  
820 820

  
821
@pytest.mark.freeze_time('2022-07-01 14:00')
822
def test_events(app, user):
823
    app.authorization = ('Basic', ('john.doe', 'password'))
824

  
825
    # missing slots
826
    resp = app.get(
827
        '/api/agendas/events/',
828
        params={},
829
        status=400,
830
    )
831
    assert resp.json['err'] == 1
832
    assert resp.json['err_desc'] == 'invalid payload'
833
    assert resp.json['errors']['slots'] == ['This field is required.']
834

  
835
    agenda = Agenda.objects.create(label='Foo')
836
    agenda2 = Agenda.objects.create(label='Bar')
837
    start_datetime = now()
838
    # recurring event
839
    recurring_event = Event.objects.create(
840
        slug='recurring-event-slug',
841
        label='Recurring Event Label',
842
        start_datetime=start_datetime,
843
        recurrence_days=[start_datetime.weekday()],
844
        recurrence_end_date=start_datetime + datetime.timedelta(days=8),
845
        places=10,
846
        agenda=agenda,
847
        custom_fields={
848
            'text': 'foo',
849
            'textarea': 'foo bar',
850
            'bool': True,
851
        },
852
    )
853
    recurring_event.create_all_recurrences()
854
    Event.objects.create(
855
        slug='event-slug',
856
        label='Event Label',
857
        start_datetime=start_datetime + datetime.timedelta(days=1),
858
        places=10,
859
        agenda=agenda2,
860
        checked=True,
861
    )
862
    # cancelled event, not returned
863
    Event.objects.create(
864
        slug='cancelled',
865
        label='Cancelled',
866
        start_datetime=start_datetime,
867
        places=10,
868
        agenda=agenda,
869
        cancelled=True,
870
    )
871

  
872
    # unknown event in list
873
    resp = app.get(
874
        '/api/agendas/events/',
875
        params={'slots': ['foo@event-slug', 'foo@recurring-event-slug--2022-07-01-1600']},
876
        status=400,
877
    )
878
    assert resp.json['err'] == 1
879
    assert resp.json['err_desc'] == 'invalid slugs: foo@event-slug'
880

  
881
    # cancelled event in list
882
    resp = app.get(
883
        '/api/agendas/events/',
884
        params={'slots': ['bar@event-slug', 'foo@recurring-event-slug--2022-07-01-1600', 'foo@cancelled']},
885
        status=400,
886
    )
887
    assert resp.json['err'] == 1
888
    assert resp.json['err_desc'] == 'invalid slugs: foo@cancelled'
889

  
890
    # primary event in list
891
    resp = app.get(
892
        '/api/agendas/events/',
893
        params={
894
            'slots': [
895
                'bar@event-slug',
896
                'foo@recurring-event-slug--2022-07-01-1600',
897
                'foo@recurring-event-slug',
898
            ]
899
        },
900
        status=400,
901
    )
902
    assert resp.json['err'] == 1
903
    assert resp.json['err_desc'] == 'invalid slugs: foo@recurring-event-slug'
904

  
905
    # ok
906
    resp = app.get(
907
        '/api/agendas/events/',
908
        params={
909
            'slots': [
910
                'bar@event-slug',
911
                'foo@recurring-event-slug--2022-07-01-1600',
912
            ]
913
        },
914
    )
915
    assert resp.json['data'] == [
916
        {
917
            'agenda': 'bar',
918
            'description': None,
919
            'duration': None,
920
            'label': 'Event Label',
921
            'places': 10,
922
            'pricing': None,
923
            'primary_event': None,
924
            'publication_datetime': None,
925
            'recurrence_days': None,
926
            'recurrence_end_date': None,
927
            'recurrence_week_interval': 1,
928
            'slug': 'event-slug',
929
            'start_datetime': '2022-07-02T16:00:00+02:00',
930
            'url': None,
931
            'waiting_list_places': 0,
932
        },
933
        {
934
            'agenda': 'foo',
935
            'description': None,
936
            'duration': None,
937
            'label': 'Recurring Event Label',
938
            'places': 10,
939
            'pricing': None,
940
            'primary_event': 'recurring-event-slug',
941
            'publication_datetime': None,
942
            'recurrence_days': None,
943
            'recurrence_end_date': None,
944
            'recurrence_week_interval': 1,
945
            'slug': 'recurring-event-slug--2022-07-01-1600',
946
            'start_datetime': '2022-07-01T16:00:00+02:00',
947
            'url': None,
948
            'waiting_list_places': 0,
949
        },
950
    ]
951
    # result sorting ?
952
    with CaptureQueriesContext(connection) as ctx:
953
        resp = app.get(
954
            '/api/agendas/events/',
955
            params={
956
                'slots': [
957
                    'foo@recurring-event-slug--2022-07-01-1600',
958
                    'bar@event-slug',
959
                    'foo@recurring-event-slug--2022-07-08-1600',
960
                ]
961
            },
962
        )
963
        assert len(ctx.captured_queries) == 6
964
    assert [(d['agenda'], d['slug']) for d in resp.json['data']] == [
965
        ('foo', 'recurring-event-slug--2022-07-01-1600'),
966
        ('bar', 'event-slug'),
967
        ('foo', 'recurring-event-slug--2022-07-08-1600'),
968
    ]
969

  
970

  
821 971
def test_events_check_status_params(app, user):
822 972
    app.authorization = ('Basic', ('john.doe', 'password'))
823 973

  
824
-