Projet

Général

Profil

0007-api-book-also-resources-in-fillslot-s-endpoint-38942.patch

Lauréline Guérin, 29 mai 2020 17:10

Télécharger (11,1 ko)

Voir les différences:

Subject: [PATCH 7/8] api: book also resources in fillslot(s) endpoint (#38942)

 chrono/api/views.py |  82 ++++++++++++++++++++++----------
 tests/test_api.py   | 112 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 169 insertions(+), 25 deletions(-)
chrono/api/views.py
40 40
from ..interval import IntervalSet
41 41

  
42 42

  
43
class APIError(Exception):
44
    err = 1
45
    http_status = 200
46

  
47
    def __init__(self, *args, **kwargs):
48
        self.__dict__.update(kwargs)
49
        super(APIError, self).__init__(*args)
50

  
51
    def to_response(self):
52
        data = {
53
            'err': self.err,
54
            'err_class': self.err_class,
55
            'err_desc': force_text(self),
56
        }
57
        if hasattr(self, 'errors'):
58
            data['errors'] = self.errors
59
        return Response(data, status=self.http_status)
60

  
61

  
43 62
def format_response_datetime(dt):
44 63
    return localtime(dt).strftime('%Y-%m-%d %H:%M:%S')
45 64

  
......
310 329
    return places
311 330

  
312 331

  
332
def get_resources_from_request(request, agenda):
333
    if agenda.kind != 'meetings' or 'resources' not in request.GET:
334
        return []
335
    resources_slugs = [s for s in request.GET['resources'].split(',') if s]
336
    resources = list(agenda.resources.filter(slug__in=resources_slugs))
337
    if len(resources) != len(resources_slugs):
338
        unknown_slugs = set(resources_slugs) - set([r.slug for r in resources])
339
        unknown_slugs = sorted(list(unknown_slugs))
340
        raise APIError(
341
            _('invalid resource: %s') % ', '.join(unknown_slugs),
342
            err_class='invalid resource: %s' % ', '.join(unknown_slugs),
343
            http_status=status.HTTP_400_BAD_REQUEST,
344
        )
345
    return resources
346

  
347

  
313 348
class Agendas(APIView):
314 349
    permission_classes = ()
315 350

  
......
447 482

  
448 483
        now_datetime = now()
449 484

  
450
        resources = None
451
        if agenda.kind == 'meetings' and 'resources' in request.GET:
452
            resources_slugs = [s for s in request.GET['resources'].split(',') if s]
453
            resources = list(agenda.resources.filter(slug__in=resources_slugs))
454
            if len(resources) != len(resources_slugs):
455
                unknown_slugs = set(resources_slugs) - set([r.slug for r in resources])
456
                unknown_slugs = sorted(list(unknown_slugs))
457
                return Response(
458
                    {
459
                        'err': 1,
460
                        'err_class': 'invalid resource: %s' % ', '.join(unknown_slugs),
461
                        'err_desc': _('invalid resource: %s') % ', '.join(unknown_slugs),
462
                    },
463
                    status=status.HTTP_400_BAD_REQUEST,
464
                )
485
        try:
486
            resources = get_resources_from_request(request, agenda)
487
        except APIError as e:
488
            return e.to_response()
465 489

  
466 490
        # Generate an unique slot for each possible meeting [start_datetime,
467 491
        # end_datetime] range.
......
756 780
                    )
757 781
                datetimes.add(make_aware(datetime.datetime.strptime(datetime_str, '%Y-%m-%d-%H%M')))
758 782

  
783
            try:
784
                resources = get_resources_from_request(request, agenda)
785
            except APIError as e:
786
                return e.to_response()
787

  
759 788
            # get all free slots and separate them by desk
760 789
            try:
761 790
                all_slots = sorted(
762
                    get_all_slots(agenda, agenda.get_meetingtype(id_=meeting_type_id)),
791
                    get_all_slots(agenda, agenda.get_meetingtype(id_=meeting_type_id), resources=resources),
763 792
                    key=lambda slot: slot.start_datetime,
764 793
                )
765 794
            except (MeetingType.DoesNotExist, ValueError):
......
847 876
            # create them now, with data from the slots and the desk we found.
848 877
            events = []
849 878
            for start_datetime in datetimes:
850
                events.append(
851
                    Event.objects.create(
852
                        agenda=available_desk.agenda,
853
                        meeting_type_id=meeting_type_id,
854
                        start_datetime=start_datetime,
855
                        full=False,
856
                        places=1,
857
                        desk=available_desk,
858
                    )
879
                event = Event.objects.create(
880
                    agenda=available_desk.agenda,
881
                    meeting_type_id=meeting_type_id,
882
                    start_datetime=start_datetime,
883
                    full=False,
884
                    places=1,
885
                    desk=available_desk,
859 886
                )
887
                if resources:
888
                    event.resources.add(*resources)
889
                events.append(event)
860 890
        else:
861 891
            try:
862 892
                events = Event.objects.filter(id__in=[int(s) for s in slots]).order_by('start_datetime')
......
973 1003
                }
974 1004
                for x in events
975 1005
            ]
1006
        if agenda.kind == 'meetings':
1007
            response['resources'] = [r.slug for r in resources]
976 1008

  
977 1009
        return Response(response)
978 1010

  
tests/test_api.py
1080 1080
    assert Booking.objects.count() == 2
1081 1081

  
1082 1082

  
1083
def test_booking_api_meeting_with_resources(app, user):
1084
    tomorrow = datetime.date.today() + datetime.timedelta(days=1)
1085
    tomorrow_str = tomorrow.isoformat()
1086
    agenda = Agenda.objects.create(
1087
        label='Agenda', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=10
1088
    )
1089
    resource1 = Resource.objects.create(label='Resource 1')
1090
    resource2 = Resource.objects.create(label='Resource 2')
1091
    resource3 = Resource.objects.create(label='Resource 3')
1092
    agenda.resources.add(resource1, resource2, resource3)
1093
    desk = Desk.objects.create(agenda=agenda, slug='desk')
1094
    meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo-bar')
1095
    TimePeriod.objects.create(
1096
        weekday=tomorrow.weekday(), start_time=datetime.time(9, 0), end_time=datetime.time(17, 00), desk=desk,
1097
    )
1098

  
1099
    app.authorization = ('Basic', ('john.doe', 'password'))
1100

  
1101
    # make a booking without resource
1102
    resp = app.post('/api/agenda/%s/fillslot/%s:%s-1000/' % (agenda.pk, meeting_type.pk, tomorrow_str))
1103
    assert resp.json['datetime'] == '%s 10:00:00' % tomorrow_str
1104
    assert resp.json['end_datetime'] == '%s 10:30:00' % tomorrow_str
1105
    assert resp.json['duration'] == 30
1106
    assert resp.json['resources'] == []
1107
    booking = Booking.objects.latest('pk')
1108
    assert list(booking.event.resources.all()) == []
1109

  
1110
    # now try to book also a resource - slot not free
1111
    resp = app.post(
1112
        '/api/agenda/%s/fillslot/%s:%s-1000/?resources=%s'
1113
        % (agenda.pk, meeting_type.pk, tomorrow_str, resource1.slug)
1114
    )
1115
    assert resp.json['err'] == 1
1116
    assert resp.json['reason'] == 'no more desk available'  # legacy
1117
    assert resp.json['err_class'] == 'no more desk available'
1118
    assert resp.json['err_desc'] == 'no more desk available'
1119

  
1120
    # slot is free
1121
    resp = app.post(
1122
        '/api/agenda/%s/fillslot/%s:%s-0900/?resources=%s'
1123
        % (agenda.pk, meeting_type.pk, tomorrow_str, resource1.slug)
1124
    )
1125
    assert resp.json['datetime'] == '%s 09:00:00' % tomorrow_str
1126
    assert resp.json['end_datetime'] == '%s 09:30:00' % tomorrow_str
1127
    assert resp.json['duration'] == 30
1128
    assert resp.json['resources'] == [resource1.slug]
1129
    booking = Booking.objects.latest('pk')
1130
    assert list(booking.event.resources.all()) == [resource1]
1131
    resp = app.post(
1132
        '/api/agenda/%s/fillslot/%s:%s-0930/?resources=%s,%s'
1133
        % (agenda.pk, meeting_type.pk, tomorrow_str, resource1.slug, resource2.slug)
1134
    )
1135
    assert resp.json['datetime'] == '%s 09:30:00' % tomorrow_str
1136
    assert resp.json['end_datetime'] == '%s 10:00:00' % tomorrow_str
1137
    assert resp.json['duration'] == 30
1138
    assert resp.json['resources'] == [resource1.slug, resource2.slug]
1139
    booking = Booking.objects.latest('pk')
1140
    assert list(booking.event.resources.all()) == [resource1, resource2]
1141

  
1142
    # resource is unknown or not valid for this agenda
1143
    resp = app.post(
1144
        '/api/agenda/%s/fillslot/%s:%s-0900/?resources=foobarblah'
1145
        % (agenda.pk, meeting_type.pk, tomorrow_str),
1146
        status=400,
1147
    )
1148
    assert resp.json['err'] == 1
1149
    assert resp.json['reason'] == 'invalid resource: foobarblah'  # legacy
1150
    assert resp.json['err_class'] == 'invalid resource: foobarblah'
1151
    assert resp.json['err_desc'] == 'invalid resource: foobarblah'
1152
    agenda.resources.remove(resource3)
1153
    resp = app.post(
1154
        '/api/agenda/%s/fillslot/%s:%s-0900/?resources=%s'
1155
        % (agenda.pk, meeting_type.pk, tomorrow_str, resource3.slug),
1156
        status=400,
1157
    )
1158
    assert resp.json['err'] == 1
1159
    assert resp.json['reason'] == 'invalid resource: resource-3'  # legacy
1160
    assert resp.json['err_class'] == 'invalid resource: resource-3'
1161
    assert resp.json['err_desc'] == 'invalid resource: resource-3'
1162
    resp = app.post(
1163
        '/api/agenda/%s/fillslot/%s:%s-0900/?resources=%s,foobarblah'
1164
        % (agenda.pk, meeting_type.pk, tomorrow_str, resource3.slug),
1165
        status=400,
1166
    )
1167
    assert resp.json['err'] == 1
1168
    assert resp.json['reason'] == 'invalid resource: foobarblah, resource-3'  # legacy
1169
    assert resp.json['err_class'] == 'invalid resource: foobarblah, resource-3'
1170
    assert resp.json['err_desc'] == 'invalid resource: foobarblah, resource-3'
1171
    resp = app.post(
1172
        '/api/agenda/%s/fillslot/%s:%s-0900/?resources=%s,foobarblah'
1173
        % (agenda.pk, meeting_type.pk, tomorrow_str, resource1.slug),
1174
        status=400,
1175
    )
1176
    assert resp.json['err'] == 1
1177
    assert resp.json['reason'] == 'invalid resource: foobarblah'  # legacy
1178
    assert resp.json['err_class'] == 'invalid resource: foobarblah'
1179
    assert resp.json['err_desc'] == 'invalid resource: foobarblah'
1180

  
1181
    # booking is canceled: slot is free
1182
    booking.cancel()
1183
    resp = app.post(
1184
        '/api/agenda/%s/fillslot/%s:%s-0930/?resources=%s,%s'
1185
        % (agenda.pk, meeting_type.pk, tomorrow_str, resource1.slug, resource2.slug)
1186
    )
1187
    assert resp.json['datetime'] == '%s 09:30:00' % tomorrow_str
1188
    assert resp.json['end_datetime'] == '%s 10:00:00' % tomorrow_str
1189
    assert resp.json['duration'] == 30
1190
    assert resp.json['resources'] == [resource1.slug, resource2.slug]
1191
    booking = Booking.objects.latest('pk')
1192
    assert list(booking.event.resources.all()) == [resource1, resource2]
1193

  
1194

  
1083 1195
def test_booking_api_meeting_fillslots(app, meetings_agenda, user):
1084 1196
    agenda_id = meetings_agenda.slug
1085 1197
    meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
1086
-