0001-api-endpoint-to-get-a-list-of-serialized-events-6687.patch
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 |
- |