Projet

Général

Profil

0001-okina-handle-new-API-format-and-endpoint-40937.patch

Thomas Noël, 28 avril 2020 17:39

Télécharger (17,9 ko)

Voir les différences:

Subject: [PATCH] okina: handle new API format and endpoint (#40937)

 passerelle/apps/okina/models.py               |  50 ++-
 .../okina/templates/okina/okina_detail.html   |   8 +
 tests/test_okina.py                           | 370 +++++++++++++++++-
 3 files changed, 408 insertions(+), 20 deletions(-)
passerelle/apps/okina/models.py
18 18

  
19 19
from django.db import models
20 20
from django.http import HttpResponse
21
from django.utils.http import urlencode
21 22
from django.utils.translation import ugettext_lazy as _
22 23

  
23 24
from passerelle.base.models import BaseResource
......
78 79
        okina_cities = self.request('cities')
79 80
        cities = []
80 81
        for city in okina_cities:
81
            city = city['cityObject']
82
            city['id'] = '%s' % city['id']
83
            city['text'] = '%(nameCity)s (%(zipCode)s)' % city
82
            if 'name' in city:
83
                city['id'] = '%s' % city['id']
84
                city['text'] = '%(name)s (%(zipCode)s)' % city
85
            else:  # old API
86
                city = city['cityObject']
87
                city['id'] = '%s' % city['id']
88
                city['text'] = '%(nameCity)s (%(zipCode)s)' % city
84 89
            cities.append(city)
85 90
        cities.sort(key=lambda x: x['text'])
86 91
        return {'data': cities}
......
92 97
            'text': item['label']
93 98
        } for item in self.request('classes')]}
94 99

  
95
    def get_institutions(self, endpoint=''):
96
        okina_institutions = self.request('institutions' + endpoint)
100
    def get_institutions(self, query=''):
101
        okina_institutions = self.request('institutions' + query)
97 102
        institutions = []
98 103
        for institution in okina_institutions:
99 104
            institution.update(institution['schoolEstablishment'])
......
104 109
        institutions.sort(key=lambda x: x['text'])
105 110
        return institutions
106 111

  
107
    @endpoint()
108
    def institutions(self, request):
109
        return {'data': self.get_institutions()}
112
    @endpoint(parameters={
113
            'insee': {'description': _('INSEE City code'), 'example_value': '36005'},
114
        })
115
    def institutions(self, request, insee=None):
116
        if insee:
117
            query = '?' + urlencode({'inseeCode': insee})
118
        else:
119
            query = ''
120
        return {'data': self.get_institutions(query)}
110 121

  
111 122
    @endpoint(name='institutions', pattern='^from-city/(?P<city_insee_code>\d+)/*$')
112 123
    def institutions_from_city(self, request, city_insee_code):
113 124
        return {'data': self.get_institutions('/subscriberCity/%s' % city_insee_code)}
114 125

  
126
    @endpoint(
127
            description=_('Get stop points based on a starting position and an arrival institution (API 2020)'),
128
            parameters={
129
                'lat': {'description': _('Latitude (departure)'), 'example_value': '46.828652'},
130
                'lon': {'description': _('Longitude (departure)'), 'example_value': '1.701463'},
131
                'address': {'description': _('Address (departure)')},
132
                'institution': {'description': _('Institution ID (arrival)'), 'example_value': '277'},
133
                'mode': {'description': _('Search mode: CLOSE_SCHOLAR (default) = 3km, FAR_ALL = 15km')},
134
            })
135
    def search(self, request, lat, lon, institution, address='', mode='CLOSE_SCHOLAR'):
136
        payload = {
137
                'from-lat': lat,
138
                'from-long': lon,
139
                'from-address': address,
140
                'institution-id': institution,
141
                'type': mode,
142
        }
143
        stops = self.request('wishes/search', payload)
144
        for stop in stops:
145
            stop['id'] = '%s' % stop['id']
146
            stop['text'] = stop['commercial_name']
147
        return {'data': stops}
148

  
115 149
    @endpoint(name='stop-areas',
116 150
              pattern='^from-city/(?P<city_insee_code>\d+)/to-institution/(?P<institution_id>\d+)/*$')
117 151
    def stop_areas(self, request, city_insee_code, institution_id):
passerelle/apps/okina/templates/okina/okina_detail.html
26 26

  
27 27
<h4><span class="description">{% trans 'Journeys' %}</h4>
28 28
<ul class="endpoints">
29
<li class="get-method"><span class="description">{% trans 'Get stop points based on a starting position and an arrival institution (API 2020)' %}</span>
30
  <a class="example-url" href="search?lat=46.828652&lon=1.701463&institution=277"
31
  >{{ site_base_uri }}{{ object.get_absolute_url }}search?lat=...&lon=...&institution=<i>institution_id</i></a></li>
32

  
33
<li class="get-method"><span class="description">{% trans 'Institutions in a city (API 2020)' %}</span>
34
  <a class="example-url" href="institutions?insee=36005"
35
  >{{ site_base_uri }}{{ object.get_absolute_url }}institutions?insee=<i>city-insee-code</i></a></li>
36

  
29 37
<li class="get-method"><span class="description">{% trans 'Institutions accessible from a city:' %}</span>
30 38
  <a class="example-url" href="institutions/from-city/36005/"
31 39
  >{{ site_base_uri }}{{ object.get_absolute_url }}institutions/from-city/<i>city-insee-code</i>/</a></li>
tests/test_okina.py
1 1
# -*- coding: utf-8 -*-
2
import json
2 3
import pytest
3 4
import mock
4 5
import utils
......
33 34

  
34 35
'''
35 36

  
37
# new API (march 2020)
38
CITIES_NEW = '''[
39
  {
40
    "id": 83355,
41
    "insee": "36005",
42
    "name": "ARDENTES",
43
    "zipCode": "36120",
44
    "nameCityRouting": null,
45
    "line5": null,
46
    "latitude": null,
47
    "longitude": null,
48
    "geoJson": null
49
  }
50
]
51
'''
52

  
36 53
CLASSES = '''
37 54
[
38 55
  {
......
94 111

  
95 112
'''
96 113

  
114
# new API (march 2020)
115
INSTITUTIONS_NEW = '''[ {
116
  "id" : 276,
117
  "schoolEstablishment" : {
118
    "uai" : "0360155Y",
119
    "uaiLabel" : "Ecole élémentaire Saint-Martin",
120
    "type" : "Public",
121
    "address" : "Rue Calmette et Guérin",
122
    "zipCode" : "36120",
123
    "codeCity" : "36005",
124
    "cityName" : "Ardentes",
125
    "locationX" : "1.8296541547236063",
126
    "locationY" : "46.74024796567764",
127
    "nature" : "1000",
128
    "natureTitle" : "Ecole",
129
    "restoration" : true,
130
    "accommodation" : false,
131
    "ulis" : false,
132
    "segpa" : false,
133
    "sirenSiret" : "21360005900033",
134
    "webSite" : null,
135
    "phone" : "254362184",
136
    "fax" : "",
137
    "email" : "ec-saint-martin-ardentes@ac-orleans-tours.fr",
138
    "departmentLabel" : "Indre",
139
    "academyLabel" : "Orléans-Tours",
140
    "regionLabel" : "Centre-Val de Loire",
141
    "schoolSectionList" : [ ],
142
    "schoolEstablishmentTypeInfos" : [ {
143
      "key" : "ecole_elementaire",
144
      "value" : "École élémentaire"
145
    } ]
146
  },
147
  "schoolEstablishmentDisplayName" : "Ecole élémentaire Saint-Martin",
148
  "schoolEstablishmentUai" : "0360155Y",
149
  "zipAndCity" : "36120 - Ardentes",
150
  "withContract" : true,
151
  "institutionContacts" : [ ],
152
  "institutionTimes" : [ {
153
    "id" : 27,
154
    "day" : "MONDAY",
155
    "way" : "WAY_TO",
156
    "time" : "08:46:00",
157
    "institutionId" : 0,
158
    "timeString" : null
159
  }, {
160
    "id" : 28,
161
    "day" : "MONDAY",
162
    "way" : "WAY_BACK",
163
    "time" : "16:30:00",
164
    "institutionId" : 0,
165
    "timeString" : null
166
  }, {
167
    "id" : 29,
168
    "day" : "TUESDAY",
169
    "way" : "WAY_TO",
170
    "time" : "08:46:00",
171
    "institutionId" : 0,
172
    "timeString" : null
173
  }, {
174
    "id" : 30,
175
    "day" : "TUESDAY",
176
    "way" : "WAY_BACK",
177
    "time" : "16:30:00",
178
    "institutionId" : 0,
179
    "timeString" : null
180
  }, {
181
    "id" : 31,
182
    "day" : "THURSDAY",
183
    "way" : "WAY_TO",
184
    "time" : "08:46:00",
185
    "institutionId" : 0,
186
    "timeString" : null
187
  }, {
188
    "id" : 32,
189
    "day" : "THURSDAY",
190
    "way" : "WAY_BACK",
191
    "time" : "16:30:00",
192
    "institutionId" : 0,
193
    "timeString" : null
194
  }, {
195
    "id" : 33,
196
    "day" : "FRIDAY",
197
    "way" : "WAY_TO",
198
    "time" : "08:46:00",
199
    "institutionId" : 0,
200
    "timeString" : null
201
  }, {
202
    "id" : 34,
203
    "day" : "FRIDAY",
204
    "way" : "WAY_BACK",
205
    "time" : "16:30:00",
206
    "institutionId" : 0,
207
    "timeString" : null
208
  } ],
209
  "cities" : null,
210
  "optionsDerogatory" : [ ],
211
  "institutionCenterName" : "ARDENTES",
212
  "institutionCenterId" : 3,
213
  "customLocationX" : "1.8296541547236063",
214
  "customLocationY" : "46.74024796567764"
215
}, {
216
  "id" : 277,
217
  "schoolEstablishment" : {
218
    "uai" : "0360048G",
219
    "uaiLabel" : "Collège Touvent",
220
    "type" : "Public",
221
    "address" : "4 allée des Lauriers",
222
    "zipCode" : "36000",
223
    "codeCity" : "36044",
224
    "cityName" : "Châteauroux",
225
    "locationX" : "1.6926355997775921",
226
    "locationY" : "46.791332586468016",
227
    "nature" : "340",
228
    "natureTitle" : "Collège",
229
    "restoration" : false,
230
    "accommodation" : false,
231
    "ulis" : true,
232
    "segpa" : false,
233
    "sirenSiret" : "19360048300013",
234
    "webSite" : null,
235
    "phone" : "02 54 34 03 30",
236
    "fax" : "02 54 08 01 09",
237
    "email" : "ce.0360048g@ac-orleans-tours.fr",
238
    "departmentLabel" : "Indre",
239
    "academyLabel" : "Orléans-Tours",
240
    "regionLabel" : "Centre-Val de Loire",
241
    "schoolSectionList" : [ {
242
      "key" : "section_sport",
243
      "name" : "Section sport"
244
    } ],
245
    "schoolEstablishmentTypeInfos" : [ ]
246
  },
247
  "schoolEstablishmentDisplayName" : "Collège Touvent",
248
  "schoolEstablishmentUai" : "0360048G",
249
  "zipAndCity" : "36000 - Châteauroux",
250
  "withContract" : true,
251
  "institutionContacts" : [ ],
252
  "institutionTimes" : [ {
253
    "id" : 249,
254
    "day" : "MONDAY",
255
    "way" : "WAY_TO",
256
    "time" : "08:20:00",
257
    "institutionId" : 0,
258
    "timeString" : null
259
  }, {
260
    "id" : 250,
261
    "day" : "MONDAY",
262
    "way" : "WAY_BACK",
263
    "time" : "17:05:00",
264
    "institutionId" : 0,
265
    "timeString" : null
266
  }, {
267
    "id" : 251,
268
    "day" : "TUESDAY",
269
    "way" : "WAY_TO",
270
    "time" : "08:20:00",
271
    "institutionId" : 0,
272
    "timeString" : null
273
  }, {
274
    "id" : 252,
275
    "day" : "TUESDAY",
276
    "way" : "WAY_BACK",
277
    "time" : "17:05:00",
278
    "institutionId" : 0,
279
    "timeString" : null
280
  }, {
281
    "id" : 253,
282
    "day" : "WEDNESDAY",
283
    "way" : "WAY_TO",
284
    "time" : "07:50:00",
285
    "institutionId" : 0,
286
    "timeString" : null
287
  }, {
288
    "id" : 254,
289
    "day" : "WEDNESDAY",
290
    "way" : "WAY_BACK",
291
    "time" : "12:05:00",
292
    "institutionId" : 0,
293
    "timeString" : null
294
  }, {
295
    "id" : 255,
296
    "day" : "THURSDAY",
297
    "way" : "WAY_TO",
298
    "time" : "08:20:00",
299
    "institutionId" : 0,
300
    "timeString" : null
301
  }, {
302
    "id" : 256,
303
    "day" : "THURSDAY",
304
    "way" : "WAY_BACK",
305
    "time" : "17:05:00",
306
    "institutionId" : 0,
307
    "timeString" : null
308
  }, {
309
    "id" : 257,
310
    "day" : "FRIDAY",
311
    "way" : "WAY_TO",
312
    "time" : "08:20:00",
313
    "institutionId" : 0,
314
    "timeString" : null
315
  }, {
316
    "id" : 258,
317
    "day" : "FRIDAY",
318
    "way" : "WAY_BACK",
319
    "time" : "17:05:00",
320
    "institutionId" : 0,
321
    "timeString" : null
322
  } ],
323
  "cities" : null,
324
  "optionsDerogatory" : [ ],
325
  "institutionCenterName" : "CHATEAUROUX",
326
  "institutionCenterId" : 2,
327
  "customLocationX" : "1.6926355997775921",
328
  "customLocationY" : "46.791332586468016"
329
} ]
330
'''
331

  
332
SEARCH = '''[ {
333
  "id" : 3312,
334
  "stop_point_id" : 1498,
335
  "name" : "Le Grand Verger",
336
  "latitude" : 46.8444186,
337
  "longitude" : 1.708197,
338
  "bearing" : 0.0,
339
  "position" : 6,
340
  "forBoarding" : null,
341
  "forAlighting" : null,
342
  "colorUsage" : null,
343
  "isRecommended" : false,
344
  "titreRelaisId" : null,
345
  "networkType" : "SCHOLAR",
346
  "commercial_stop_area" : 3312,
347
  "commercial_name" : "Le Grand Verger",
348
  "liaisons" : [ ],
349
  "registrationNumber" : "36063_009",
350
  "cityCode" : "36063",
351
  "cityName" : "Déols",
352
  "zipCode" : "36130",
353
  "isDuplicatedCompute" : 0,
354
  "nbClosedStopAreas" : null,
355
  "isValidated" : null,
356
  "isUnique" : null,
357
  "isDuplicated" : null,
358
  "network" : {
359
    "id" : 2,
360
    "name" : "réseau scolaire",
361
    "supprime" : false,
362
    "networkType" : "SCHOLAR"
363
  },
364
  "external_ref" : null,
365
  "distanceBetweenPreviousStopCalculated" : 0.0,
366
  "distanceBetweenPreviousStopReal" : null
367
}, {
368
  "id" : 3311,
369
  "stop_point_id" : 1499,
370
  "name" : "Jean Bizet U",
371
  "latitude" : 46.8359017,
372
  "longitude" : 1.7045139,
373
  "bearing" : 0.0,
374
  "position" : 7,
375
  "forBoarding" : null,
376
  "forAlighting" : null,
377
  "colorUsage" : null,
378
  "isRecommended" : true,
379
  "titreRelaisId" : null,
380
  "networkType" : "SCHOLAR",
381
  "commercial_stop_area" : 3311,
382
  "commercial_name" : "Jean Bizet U",
383
  "liaisons" : [ ],
384
  "registrationNumber" : "36063_008",
385
  "cityCode" : "36063",
386
  "cityName" : "Déols",
387
  "zipCode" : "36130",
388
  "isDuplicatedCompute" : 0,
389
  "nbClosedStopAreas" : null,
390
  "isValidated" : null,
391
  "isUnique" : null,
392
  "isDuplicated" : null,
393
  "network" : {
394
    "id" : 2,
395
    "name" : "réseau scolaire",
396
    "supprime" : false,
397
    "networkType" : "SCHOLAR"
398
  },
399
  "external_ref" : null,
400
  "distanceBetweenPreviousStopCalculated" : 0.0,
401
  "distanceBetweenPreviousStopReal" : null
402
} ]
403
'''
404

  
97 405
STOPS = '''
98 406
[ {
99 407
  "id" : 3281,
......
1356 1664
        assert resp.json['data'][0]['insee'] == '36005'
1357 1665
        assert resp.json['data'][0]['text'] == 'ARDENTES (36120)'
1358 1666

  
1667
        requests_get.return_value = utils.FakedResponse(content=CITIES_NEW,
1668
                                                        status_code=200)
1669
        resp = app.get(endpoint, status=200)
1670
        assert requests_get.call_count == 2
1671
        assert 'data' in resp.json
1672
        assert resp.json['err'] == 0
1673
        assert len(resp.json['data']) == 1
1674
        assert resp.json['data'][0]['id'] == '83355'
1675
        assert resp.json['data'][0]['insee'] == '36005'
1676
        assert resp.json['data'][0]['text'] == 'ARDENTES (36120)'
1677

  
1359 1678
def test_okina_classes(app, okina):
1360 1679
    endpoint = utils.generic_endpoint_url('okina', 'classes', slug=okina.slug)
1361 1680
    assert endpoint == '/okina/test/classes'
......
1374 1693
    endpoint = utils.generic_endpoint_url('okina', 'institutions', slug=okina.slug)
1375 1694
    assert endpoint == '/okina/test/institutions'
1376 1695
    with mock.patch('passerelle.utils.Request.get') as requests_get:
1377
        requests_get.return_value = utils.FakedResponse(content=INSTITUTIONS,
1378
                                                        status_code=200)
1379
        resp = app.get(endpoint, status=200)
1380
        assert requests_get.call_args[0][0] == 'https://okina.example.net/b2b/institutions'
1381
        assert resp.json['err'] == 0
1382
        assert len(resp.json['data']) == 2
1383
        assert resp.json['data'][0]['id'] == '277'
1384
        assert resp.json['data'][0]['text'] == u'Collège Touvent'
1696
        for content in (INSTITUTIONS, INSTITUTIONS_NEW):
1697
            requests_get.return_value = utils.FakedResponse(content=content, status_code=200)
1698
            resp = app.get(endpoint, status=200)
1699
            assert requests_get.call_args[0][0] == 'https://okina.example.net/b2b/institutions'
1700
            assert resp.json['err'] == 0
1701
            assert len(resp.json['data']) == 2
1702
            assert resp.json['data'][0]['id'] == '277'
1703
            assert resp.json['data'][0]['text'] == u'Collège Touvent'
1385 1704

  
1386
        resp = app.get(endpoint + '/from-city/36005/', status=200)
1387
        assert requests_get.call_args[0][0] == 'https://okina.example.net/b2b/institutions/subscriberCity/36005'
1705
            resp = app.get(endpoint, params={'insee': '36005'}, status=200)
1706
            assert requests_get.call_args[0][0] == 'https://okina.example.net/b2b/institutions?inseeCode=36005'
1707
            assert resp.json['err'] == 0
1708
            assert len(resp.json['data']) == 2
1709
            assert resp.json['data'][0]['id'] == '277'
1710
            assert resp.json['data'][0]['text'] == u'Collège Touvent'
1711

  
1712
            resp = app.get(endpoint + '/from-city/36005/', status=200)
1713
            assert requests_get.call_args[0][0] == 'https://okina.example.net/b2b/institutions/subscriberCity/36005'
1714
            assert resp.json['err'] == 0
1715
            assert len(resp.json['data']) == 2
1716
            assert resp.json['data'][0]['id'] == '277'
1717
            assert resp.json['data'][0]['text'] == u'Collège Touvent'
1718

  
1719
def test_okina_search(app, okina):
1720
    endpoint = utils.generic_endpoint_url('okina', 'search', slug=okina.slug)
1721
    assert endpoint == '/okina/test/search'
1722
    with mock.patch('passerelle.utils.Request.post') as requests_post:
1723
        requests_post.return_value = utils.FakedResponse(content=SEARCH, status_code=200)
1724
        app.get(endpoint + '?lat=46.828652', status=400)  # missing argument
1725
        resp = app.get(endpoint + '?lat=46.828652&lon=1.701463&institution=277', status=200)
1726
        assert requests_post.call_args[0][0] == 'https://okina.example.net/b2b/wishes/search'
1727
        assert json.loads(requests_post.call_args[1]['data']) == {
1728
            "type": "CLOSE_SCHOLAR",
1729
            "from-address": "",
1730
            "from-lat": "46.828652",
1731
            "from-long": "1.701463",
1732
            "institution-id": "277"
1733
        }
1388 1734
        assert resp.json['err'] == 0
1389 1735
        assert len(resp.json['data']) == 2
1390
        assert resp.json['data'][0]['id'] == '277'
1391
        assert resp.json['data'][0]['text'] == u'Collège Touvent'
1736
        assert resp.json['data'][0]['id'] == '3312'
1737
        assert resp.json['data'][0]['text'] == 'Le Grand Verger'
1392 1738

  
1393 1739
def test_okina_stops_area(app, okina):
1394 1740
    endpoint = utils.generic_endpoint_url('okina', 'stop-areas', slug=okina.slug)
1395
-