Projet

Général

Profil

0002-api-add-support-for-subscriptions-in-multiple-agenda.patch

Valentin Deniaud, 02 décembre 2021 15:28

Télécharger (13,3 ko)

Voir les différences:

Subject: [PATCH 2/4] api: add support for subscriptions in multiple agendas
 fillslots (#58446)

 chrono/api/serializers.py  |  11 +--
 chrono/api/views.py        |  26 ++++++-
 tests/api/test_fillslot.py | 137 +++++++++++++++++++++++++++++++++++--
 3 files changed, 162 insertions(+), 12 deletions(-)
chrono/api/serializers.py
95 95
        extra_agendas = slots_agenda_slugs - set(allowed_agenda_slugs)
96 96
        if extra_agendas:
97 97
            extra_agendas = ', '.join(sorted(extra_agendas))
98
            raise ValidationError(
99
                _('Some events belong to agendas that are not present in querystring: %s' % extra_agendas)
100
            )
98
            raise ValidationError(_('Events from the following agendas cannot be booked: %s') % extra_agendas)
101 99
        return value
102 100

  
103 101

  
......
214 212
            raise ValidationError(_('Either "agendas" or "subscribed" parameter is required.'))
215 213
        if 'agendas' in attrs and 'subscribed' in attrs:
216 214
            raise ValidationError(_('"agendas" and "subscribed" parameters are mutually exclusive.'))
217
        user_external_id = attrs.get('user_external_id')
215
        user_external_id = attrs.get('user_external_id', self.context.get('user_external_id'))
218 216
        if 'subscribed' in attrs and not user_external_id:
219 217
            raise ValidationError(
220 218
                {'user_external_id': _('This field is required when using "subscribed" parameter.')}
......
225 223
            if attrs['subscribed'] != ['all']:
226 224
                agendas = agendas.filter(category__slug__in=attrs['subscribed'])
227 225
            attrs['agendas'] = agendas
226
            attrs['agenda_slugs'] = [agenda.slug for agenda in agendas]
228 227
        else:
229 228
            attrs['agenda_slugs'] = self.agenda_slugs
230 229
        return attrs
......
238 237
    show_past_events = serializers.BooleanField(default=False)
239 238

  
240 239

  
240
class AgendaOrSubscribedSlugsSerializer(AgendaOrSubscribedSlugsMixin, serializers.Serializer):
241
    pass
242

  
243

  
241 244
class AgendaSlugsSerializer(serializers.Serializer):
242 245
    agendas = CommaSeparatedStringField(
243 246
        required=True, child=serializers.SlugField(max_length=160, allow_blank=False)
chrono/api/views.py
1696 1696
    serializer_class = serializers.MultipleAgendasEventsSlotsSerializer
1697 1697

  
1698 1698
    def post(self, request):
1699
        self.agenda_slugs = get_agendas_from_request(request)
1700
        self.agendas = get_objects_from_slugs(self.agenda_slugs, qs=Agenda.objects.filter(kind='events'))
1699
        serializer = serializers.AgendaOrSubscribedSlugsSerializer(
1700
            data=request.query_params, context={'user_external_id': request.data.get('user_external_id')}
1701
        )
1702
        if not serializer.is_valid():
1703
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
1704
        data = serializer.validated_data
1705

  
1706
        self.agendas = data['agendas']
1707
        self.agenda_slugs = data['agenda_slugs']
1708

  
1701 1709
        return self.fillslots(request)
1702 1710

  
1703 1711
    def get_events(self, request, payload):
......
1714 1722
        for agenda_slug, event_slugs in events_by_agenda.items():
1715 1723
            events |= get_events_from_slots(event_slugs, request, agendas_by_slug[agenda_slug], payload)
1716 1724

  
1725
        if 'subscribed' in request.query_params:
1726
            events_outside_subscriptions = events.difference(
1727
                events.filter(
1728
                    agenda__subscriptions__user_external_id=payload['user_external_id'],
1729
                    agenda__subscriptions__date_start__lt=F('start_datetime'),
1730
                    agenda__subscriptions__date_end__gt=F('start_datetime'),
1731
                )
1732
            )  # workaround exclude method bug https://code.djangoproject.com/ticket/29697
1733
            if events_outside_subscriptions.exists():
1734
                event_slugs = ', '.join(
1735
                    '%s@%s' % (event.agenda.slug, event.slug) for event in events_outside_subscriptions
1736
                )
1737
                raise APIErrorBadRequest(N_('Some events are outside user subscriptions: %s'), event_slugs)
1738

  
1717 1739
        return events
1718 1740

  
1719 1741
    def get_already_booked_events(self, user_external_id):
tests/api/test_fillslot.py
11 11
    Agenda,
12 12
    Booking,
13 13
    BookingColor,
14
    Category,
14 15
    Desk,
15 16
    Event,
16 17
    MeetingType,
17 18
    Resource,
19
    Subscription,
18 20
    TimePeriod,
19 21
    VirtualMember,
20 22
)
......
2416 2418
    resp = app.post_json(fillslots_url % 'second-agenda', params=params, status=400)
2417 2419
    assert resp.json['err'] == 1
2418 2420
    assert resp.json['errors']['slots'] == [
2419
        'Some events belong to agendas that are not present in querystring: first-agenda'
2421
        'Events from the following agendas cannot be booked: first-agenda'
2420 2422
    ]
2421 2423

  
2422 2424

  
......
2671 2673
    resp = app.post_json(
2672 2674
        '/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params, status=400
2673 2675
    )
2674
    assert resp.json['errors']['slots'] == [
2675
        'Some events belong to agendas that are not present in querystring: xxx, yyy'
2676
    ]
2676
    assert resp.json['errors']['slots'] == ['Events from the following agendas cannot be booked: xxx, yyy']
2677 2677

  
2678 2678
    # missing agendas parameter
2679 2679
    resp = app.post_json('/api/agendas/events/fillslots/', params=params, status=400)
2680
    assert resp.json['errors']['agendas'] == ['This field is required.']
2680
    assert resp.json['errors']['non_field_errors'] == [
2681
        'Either "agendas" or "subscribed" parameter is required.'
2682
    ]
2681 2683

  
2682 2684
    # valid agendas parameter and event slugs, but mismatch between the two
2683 2685
    params = {'user_external_id': 'user_id_3', 'slots': event_slugs}
2684 2686
    resp = app.post_json('/api/agendas/events/fillslots/?agendas=first-agenda', params=params, status=400)
2685 2687
    assert resp.json['errors']['slots'] == [
2686
        'Some events belong to agendas that are not present in querystring: second-agenda'
2688
        'Events from the following agendas cannot be booked: second-agenda'
2687 2689
    ]
2688 2690

  
2689 2691
    # missing @ in slot
......
2754 2756
    assert resp.json['err'] == 0
2755 2757

  
2756 2758

  
2759
@pytest.mark.freeze_time('2021-09-06 12:00')
2760
def test_api_events_fillslots_multiple_agendas_subscribed(app, user):
2761
    category = Category.objects.create(label='Category A')
2762
    first_agenda = Agenda.objects.create(label='First agenda', kind='events', category=category)
2763
    second_agenda = Agenda.objects.create(label='Second agenda', kind='events', category=category)
2764
    category = Category.objects.create(label='Category B')
2765
    third_agenda = Agenda.objects.create(label='Third agenda', kind='events', category=category)
2766
    for agenda in Agenda.objects.all():
2767
        Event.objects.create(
2768
            slug='event',
2769
            start_datetime=now() + datetime.timedelta(days=5),
2770
            places=5,
2771
            agenda=agenda,
2772
        )
2773
        Event.objects.create(
2774
            slug='event-2',
2775
            start_datetime=now() + datetime.timedelta(days=20),
2776
            places=5,
2777
            agenda=agenda,
2778
        )
2779

  
2780
    # add subscriptions to first and second agenda
2781
    for agenda in (first_agenda, second_agenda):
2782
        Subscription.objects.create(
2783
            agenda=agenda,
2784
            user_external_id='xxx',
2785
            date_start=now(),
2786
            date_end=now() + datetime.timedelta(days=10),
2787
        )
2788

  
2789
    # book events
2790
    app.authorization = ('Basic', ('john.doe', 'password'))
2791
    params = {'user_external_id': 'xxx', 'slots': 'first-agenda@event,second-agenda@event'}
2792
    resp = app.post_json('/api/agendas/events/fillslots/?subscribed=category-a', params=params)
2793
    assert resp.json['booking_count'] == 2
2794
    assert Event.objects.get(agenda=first_agenda, slug='event').booking_set.count() == 1
2795
    assert Event.objects.get(agenda=second_agenda, slug='event').booking_set.count() == 1
2796
    assert Booking.objects.count() == 2
2797

  
2798
    # update bookings for category-a
2799
    params = {'user_external_id': 'xxx', 'slots': 'second-agenda@event'}
2800
    resp = app.post_json('/api/agendas/events/fillslots/?subscribed=category-a', params=params)
2801
    assert resp.json['booking_count'] == 0
2802
    assert resp.json['cancelled_booking_count'] == 1
2803
    assert Event.objects.get(agenda=first_agenda, slug='event').booking_set.count() == 0
2804
    assert Event.objects.get(agenda=second_agenda, slug='event').booking_set.count() == 1
2805
    assert Booking.objects.count() == 1
2806

  
2807
    # try to book event from agenda with no subscription TODO messages
2808
    params = {'user_external_id': 'xxx', 'slots': 'third-agenda@event'}
2809
    for slug in ('all', 'category-a', 'category-b'):
2810
        resp = app.post_json('/api/agendas/events/fillslots/?subscribed=%s' % slug, params=params, status=400)
2811
        assert (
2812
            resp.json['errors']['slots'][0]
2813
            == 'Events from the following agendas cannot be booked: third-agenda'
2814
        )
2815

  
2816
    # add subscription to third agenda
2817
    Subscription.objects.create(
2818
        agenda=third_agenda,
2819
        user_external_id='xxx',
2820
        date_start=now(),
2821
        date_end=now() + datetime.timedelta(days=10),
2822
    )
2823
    params = {'user_external_id': 'xxx', 'slots': 'third-agenda@event'}
2824
    resp = app.post_json('/api/agendas/events/fillslots/?subscribed=category-b', params=params)
2825
    assert resp.json['booking_count'] == 1
2826
    assert Event.objects.get(agenda=first_agenda, slug='event').booking_set.count() == 0
2827
    assert Event.objects.get(agenda=second_agenda, slug='event').booking_set.count() == 1
2828
    assert Event.objects.get(agenda=third_agenda, slug='event').booking_set.count() == 1
2829
    assert Booking.objects.count() == 2
2830

  
2831
    # add subscription to first agenda (disjoint) spanning event-2
2832
    for agenda in (first_agenda, second_agenda):
2833
        Subscription.objects.create(
2834
            agenda=agenda,
2835
            user_external_id='xxx',
2836
            date_start=now() + datetime.timedelta(days=15),
2837
            date_end=now() + datetime.timedelta(days=25),
2838
        )
2839
    # book event-2 while updating all bookings
2840
    params = {'user_external_id': 'xxx', 'slots': 'first-agenda@event,second-agenda@event-2'}
2841
    resp = app.post_json('/api/agendas/events/fillslots/?subscribed=all', params=params)
2842
    assert resp.json['booking_count'] == 2
2843
    assert resp.json['cancelled_booking_count'] == 2
2844
    assert Event.objects.get(agenda=first_agenda, slug='event').booking_set.count() == 1
2845
    assert Event.objects.get(agenda=second_agenda, slug='event-2').booking_set.count() == 1
2846
    assert Booking.objects.count() == 2
2847

  
2848
    # other user
2849
    for agenda in (first_agenda, second_agenda):
2850
        Subscription.objects.create(
2851
            agenda=agenda,
2852
            user_external_id='yyy',
2853
            date_start=now(),
2854
            date_end=now() + datetime.timedelta(days=25),
2855
        )
2856
    params = {'user_external_id': 'yyy', 'slots': 'first-agenda@event,second-agenda@event-2'}
2857
    resp = app.post_json('/api/agendas/events/fillslots/?subscribed=all', params=params)
2858
    assert resp.json['booking_count'] == 2
2859
    assert resp.json['cancelled_booking_count'] == 0
2860
    assert Event.objects.get(agenda=first_agenda, slug='event').booking_set.count() == 2
2861
    assert Event.objects.get(agenda=second_agenda, slug='event-2').booking_set.count() == 2
2862
    assert Booking.objects.count() == 4
2863

  
2864
    # try to book event outside subscription date range
2865
    params = {'user_external_id': 'xxx', 'slots': 'third-agenda@event-2'}
2866
    resp = app.post_json('/api/agendas/events/fillslots/?subscribed=all', params=params, status=400)
2867
    assert resp.json['err_class'] == 'Some events are outside user subscriptions: third-agenda@event-2'
2868

  
2869
    # mismatch between subscribed parameter and event
2870
    params = {'user_external_id': 'xxx', 'slots': 'third-agenda@event'}
2871
    resp = app.post_json('/api/agendas/events/fillslots/?subscribed=category-a', params=params, status=400)
2872
    assert (
2873
        resp.json['errors']['slots'][0] == 'Events from the following agendas cannot be booked: third-agenda'
2874
    )
2875

  
2876
    # missing user_external_id
2877
    params = {'slots': 'third-agenda@event'}
2878
    resp = app.post_json('/api/agendas/events/fillslots/?subscribed=all', params=params, status=400)
2879
    assert 'required' in resp.json['errors']['user_external_id'][0]
2880

  
2881

  
2757 2882
def test_url_translation(app, some_data, user):
2758 2883
    app.authorization = ('Basic', ('john.doe', 'password'))
2759 2884
    agenda_id = Agenda.objects.filter(label='Foo bar')[0].id
2760
-