0002-base_adresse-add-API-G-o-endpoints-11497.patch
passerelle/apps/base_adresse/migrations/0015_auto_20191128_1416.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.18 on 2019-11-28 13:16 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
from django.db import migrations, models |
|
6 | ||
7 | ||
8 |
class Migration(migrations.Migration): |
|
9 | ||
10 |
dependencies = [ |
|
11 |
('base_adresse', '0014_auto_20190207_0456'), |
|
12 |
] |
|
13 | ||
14 |
operations = [ |
|
15 |
migrations.AddField( |
|
16 |
model_name='baseadresse', |
|
17 |
name='api_geo_url', |
|
18 |
field=models.CharField(default=b'https://geo.api.gouv.fr/', help_text='Base Adresse API Geo URL', max_length=128, verbose_name='API Geo URL'), |
|
19 |
), |
|
20 |
migrations.AddField( |
|
21 |
model_name='baseadresse', |
|
22 |
name='county', |
|
23 |
field=models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='County code to get cities from'), |
|
24 |
), |
|
25 |
migrations.AddField( |
|
26 |
model_name='baseadresse', |
|
27 |
name='region', |
|
28 |
field=models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Region code to get cities and counties from'), |
|
29 |
), |
|
30 |
] |
passerelle/apps/base_adresse/models.py | ||
---|---|---|
23 | 23 |
verbose_name=_('Service URL'), |
24 | 24 |
help_text=_('Base Adresse Web Service URL')) |
25 | 25 | |
26 |
api_geo_url = models.CharField( |
|
27 |
max_length=128, blank=False, |
|
28 |
default='https://geo.api.gouv.fr/', |
|
29 |
verbose_name=_('API Geo URL'), |
|
30 |
help_text=_('Base Adresse API Geo URL')) |
|
31 | ||
26 | 32 |
category = _('Geographic information system') |
27 | 33 | |
28 |
api_description = _("The API is a partial view of OpenStreetMap's Nominatim " |
|
29 |
"own API; it currently doesn't support all parameters and " |
|
30 |
"is limited to the JSON format.") |
|
34 |
api_description = _("The geocoding endpoints are a partial view of OpenStreetMap's " |
|
35 |
"Nominatim own API; it currently doesn't support all parameters and " |
|
36 |
"is limited to the JSON format. The cities, counties and regions " |
|
37 |
"endpoints source data from API Geo.") |
|
31 | 38 | |
32 | 39 |
zipcode = models.CharField( |
33 | 40 |
max_length=600, |
34 | 41 |
blank=True, |
35 | 42 |
verbose_name=_('Postal codes or county number to get streets, separated with commas')) |
36 | 43 | |
44 |
county = models.PositiveSmallIntegerField( |
|
45 |
blank=True, |
|
46 |
null=True, |
|
47 |
verbose_name=_('County code to get cities from')) |
|
48 | ||
49 |
region = models.PositiveSmallIntegerField( |
|
50 |
blank=True, |
|
51 |
null=True, |
|
52 |
verbose_name=_('Region code to get cities and counties from')) |
|
53 | ||
37 | 54 |
class Meta: |
38 | 55 |
verbose_name = _('Base Adresse Web Service') |
39 | 56 | |
... | ... | |
152 | 169 | |
153 | 170 |
return {'data': result} |
154 | 171 | |
172 |
@endpoint(description=_('Cities list'), |
|
173 |
parameters={ |
|
174 |
'zipcode': {'description': _('Zipcode'), 'example_value': '75014'}, |
|
175 |
'q': {'description': _("City's name"), 'example_value': 'Paris'}, |
|
176 |
'code': {'description': _('City code (INSEE)'), 'example_value': '75056'}, |
|
177 |
'region_code': {'description': _('Region code'), 'example_value': '11'}, |
|
178 |
'county_code': {'description': _('County code'), 'example_value': '75'}, |
|
179 |
}) |
|
180 |
def cities(self, request, id=None, zipcode=None, q=None, code=None, |
|
181 |
region_code=None, county_code=None, **kwargs): |
|
182 |
if id is not None: |
|
183 |
code, zipcode = id.split('.') |
|
184 | ||
185 |
request_data = { |
|
186 |
'format': 'json', |
|
187 |
'boost': 'population', |
|
188 |
'fields': 'nom,code,codesPostaux,surface,codeDepartement,' |
|
189 |
'departement,codeRegion,region,population', |
|
190 |
} |
|
191 | ||
192 |
if code: |
|
193 |
request_data['code'] = code |
|
194 |
if zipcode: |
|
195 |
request_data['codePostal'] = zipcode |
|
196 |
if q: |
|
197 |
request_data['nom'] = q |
|
198 |
if self.county or county_code: |
|
199 |
request_data['codeDepartement'] = county_code or self.county |
|
200 |
if self.region or region_code: |
|
201 |
request_data['codeRegion'] = region_code or self.region |
|
202 | ||
203 |
scheme, netloc, path, params, query, fragment = urlparse.urlparse(self.api_geo_url) |
|
204 |
path = os.path.join(path, 'communes/') |
|
205 |
query = urlencode(request_data) |
|
206 |
url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) |
|
207 | ||
208 |
result_response = self.requests.get(url) |
|
209 | ||
210 |
data = [] |
|
211 |
for city in result_response.json(): |
|
212 |
zipcodes = [zipcode] if zipcode else city['codesPostaux'] |
|
213 |
for zipcode in zipcodes: |
|
214 |
new_city = city.copy() |
|
215 |
new_city['codePostal'] = zipcode |
|
216 |
new_city['id'] = '.'.join((city['code'], zipcode)) |
|
217 |
new_city['text'] = '%s (%s)' % (city['nom'], zipcode) |
|
218 |
data.append(new_city) |
|
219 | ||
220 |
return {'data': data} |
|
221 | ||
222 |
@endpoint(description=_('Counties list'), |
|
223 |
parameters={ |
|
224 |
'q': {'description': _('County name'), 'example_value': 'Nord'}, |
|
225 |
'code': {'description': _('County code'), 'example_value': '59'}, |
|
226 |
'region_code': {'description': _('Region code'), 'example_value': '32'}, |
|
227 |
}) |
|
228 |
def counties(self, request, id=None, q=None, code=None, region_code=None, **kwargs): |
|
229 |
if id is not None: |
|
230 |
code = id |
|
231 | ||
232 |
request_data = { |
|
233 |
'fields': 'nom,code,codeRegion,region', |
|
234 |
} |
|
235 | ||
236 |
if code: |
|
237 |
request_data['code'] = code |
|
238 |
if q: |
|
239 |
request_data['nom'] = q |
|
240 |
if self.region or region_code: |
|
241 |
request_data['codeRegion'] = region_code or self.region |
|
242 | ||
243 |
scheme, netloc, path, params, query, fragment = urlparse.urlparse(self.api_geo_url) |
|
244 |
path = os.path.join(path, 'departements/') |
|
245 |
query = urlencode(request_data) |
|
246 |
url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) |
|
247 | ||
248 |
result_response = self.requests.get(url) |
|
249 | ||
250 |
data = result_response.json() |
|
251 |
for county in data: |
|
252 |
county['id'] = county['code'] |
|
253 |
county['text'] = '%s (%s)' % (county['nom'], county['code']) |
|
254 | ||
255 |
return {'data': data} |
|
256 | ||
257 |
@endpoint(description=_('Regions list'), |
|
258 |
parameters={ |
|
259 |
'q': {'description': _('Region name'), 'example_value': 'Hauts-de-France'}, |
|
260 |
'code': {'description': _('Region code'), 'example_value': '32'}, |
|
261 |
}) |
|
262 |
def regions(self, request, id=None, q=None, code=None, **kwargs): |
|
263 |
if id is not None: |
|
264 |
code = id |
|
265 | ||
266 |
request_data = { |
|
267 |
'fields': 'nom,code', |
|
268 |
} |
|
269 | ||
270 |
if code: |
|
271 |
request_data['code'] = code |
|
272 |
if q: |
|
273 |
request_data['nom'] = q |
|
274 | ||
275 |
scheme, netloc, path, params, query, fragment = urlparse.urlparse(self.api_geo_url) |
|
276 |
path = os.path.join(path, 'regions/') |
|
277 |
query = urlencode(request_data) |
|
278 |
url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) |
|
279 | ||
280 |
result_response = self.requests.get(url) |
|
281 | ||
282 |
data = result_response.json() |
|
283 |
for region in data: |
|
284 |
region['id'] = region['code'] |
|
285 |
region['text'] = region['nom'] |
|
286 | ||
287 |
return {'data': data} |
|
288 | ||
155 | 289 |
def check_status(self): |
156 | 290 |
if self.service_url == 'https://api-adresse.data.gouv.fr/': |
157 | 291 |
result = self.search(None, '169 rue du chateau, paris') |
tests/test_base_adresse.py | ||
---|---|---|
7 | 7 |
import json |
8 | 8 | |
9 | 9 |
from django.core.management import call_command |
10 |
from django.utils.six.moves.urllib.parse import quote |
|
10 | 11 | |
11 | 12 |
from passerelle.apps.base_adresse.models import BaseAdresse, StreetModel |
12 | 13 | |
... | ... | |
41 | 42 | |
42 | 43 |
FAKE_DATA = '' |
43 | 44 | |
45 |
FAKE_API_GEO = json.dumps([{ |
|
46 |
"code": "75056", |
|
47 |
"codeDepartement": "75", |
|
48 |
"codeRegion": "11", |
|
49 |
"codesPostaux": [ |
|
50 |
"75001", |
|
51 |
"75002", |
|
52 |
], |
|
53 |
"departement": { |
|
54 |
"code": "75", |
|
55 |
"nom": "Paris" |
|
56 |
}, |
|
57 |
"nom": "Paris", |
|
58 |
"population": 2190327, |
|
59 |
"region": { |
|
60 |
"code": "11", |
|
61 |
"nom": "Île-de-France" |
|
62 |
}, |
|
63 |
"surface": 10528.81 |
|
64 |
}]) |
|
65 | ||
66 |
FAKE_API_GEO_COUNTIES = [ |
|
67 |
{ |
|
68 |
"code": "59", |
|
69 |
"codeRegion": "32", |
|
70 |
"nom": "Nord", |
|
71 |
"region": { |
|
72 |
"code": "32", |
|
73 |
"nom": "Hauts-de-France" |
|
74 |
} |
|
75 |
}, |
|
76 |
{ |
|
77 |
"code": "58", |
|
78 |
"codeRegion": "27", |
|
79 |
"nom": "Nievre", |
|
80 |
"region": { |
|
81 |
"code": "27", |
|
82 |
"nom": "Bourgogne-Franche-Comté" |
|
83 |
} |
|
84 |
} |
|
85 |
] |
|
86 | ||
87 |
FAKE_API_GEO_REGIONS = [ |
|
88 |
{ |
|
89 |
"code": "32", |
|
90 |
"nom": "Hauts-de-France" |
|
91 |
}, |
|
92 |
{ |
|
93 |
"code": "24", |
|
94 |
"nom": "Centre-Val de Loire" |
|
95 |
} |
|
96 |
] |
|
97 | ||
44 | 98 | |
45 | 99 |
@pytest.fixture |
46 | 100 |
def base_adresse(db): |
... | ... | |
258 | 312 |
mocked_get.assert_any_call('http://bano.openstreetmap.fr/BAN_odbl/BAN_odbl_2A-json.bz2') |
259 | 313 |
mocked_get.assert_any_call('http://bano.openstreetmap.fr/BAN_odbl/BAN_odbl_2B-json.bz2') |
260 | 314 |
assert StreetModel.objects.count() == 5 |
315 | ||
316 | ||
317 |
@mock.patch('passerelle.utils.Request.get') |
|
318 |
def test_base_adresse_cities(mocked_get, app, base_adresse): |
|
319 |
endpoint = utils.generic_endpoint_url('base-adresse', 'cities', |
|
320 |
slug=base_adresse.slug) |
|
321 |
assert endpoint == '/base-adresse/base-adresse/cities' |
|
322 |
mocked_get.return_value = utils.FakedResponse(content=FAKE_API_GEO, status_code=200) |
|
323 |
resp = app.get(endpoint, params={}, status=200) |
|
324 |
data = resp.json['data'] |
|
325 |
assert len(data) == 2 |
|
326 |
paris1, paris2 = data |
|
327 |
assert paris1.pop('id') == '75056.75001' |
|
328 |
assert paris2.pop('id') == '75056.75002' |
|
329 |
assert paris1.pop('codePostal') == '75001' |
|
330 |
assert paris2.pop('codePostal') == '75002' |
|
331 |
assert paris1.pop('text') == 'Paris (75001)' |
|
332 |
assert paris2.pop('text') == 'Paris (75002)' |
|
333 |
assert paris1 == paris2 |
|
334 | ||
335 | ||
336 |
@mock.patch('passerelle.utils.Request.get') |
|
337 |
def test_base_adresse_cities_zipcode(mocked_get, app, base_adresse): |
|
338 |
endpoint = utils.generic_endpoint_url('base-adresse', 'cities', |
|
339 |
slug=base_adresse.slug) |
|
340 |
mocked_get.return_value = utils.FakedResponse(content=FAKE_API_GEO, status_code=200) |
|
341 |
resp = app.get(endpoint, params={'zipcode': 75002}, status=200) |
|
342 |
data = resp.json['data'] |
|
343 |
assert len(data) == 1 |
|
344 |
assert data[0]['id'] == '75056.75002' |
|
345 | ||
346 | ||
347 |
@mock.patch('passerelle.utils.Request.get') |
|
348 |
def test_base_adresse_cities_id(mocked_get, app, base_adresse): |
|
349 |
endpoint = utils.generic_endpoint_url('base-adresse', 'cities', |
|
350 |
slug=base_adresse.slug) |
|
351 |
mocked_get.return_value = utils.FakedResponse(content=FAKE_API_GEO, status_code=200) |
|
352 |
resp = app.get(endpoint, params={'zipcode': 75002, 'code': 75056}, status=200) |
|
353 |
data = resp.json['data'] |
|
354 |
assert len(data) == 1 |
|
355 | ||
356 |
resp = app.get(endpoint, params={'id': data[0]['id']}, status=200) |
|
357 |
new_data = resp.json['data'] |
|
358 |
assert new_data == data |
|
359 | ||
360 | ||
361 |
@mock.patch('passerelle.utils.Request.get') |
|
362 |
def test_base_adresse_cities_path(mocked_get, app, base_adresse): |
|
363 |
base_adresse.api_geo_url = 'http://example.net/path/' |
|
364 |
base_adresse.save() |
|
365 |
endpoint = utils.generic_endpoint_url('base-adresse', 'cities', |
|
366 |
slug=base_adresse.slug) |
|
367 |
mocked_get.return_value = utils.FakedResponse(content=FAKE_API_GEO, status_code=200) |
|
368 |
resp = app.get(endpoint, params={}, status=200) |
|
369 |
assert mocked_get.call_args[0][0].startswith('http://example.net/path/communes/?') |
|
370 | ||
371 | ||
372 |
@mock.patch('passerelle.utils.Request.get') |
|
373 |
def test_base_adresse_cities_qs(mocked_get, app, base_adresse): |
|
374 |
endpoint = utils.generic_endpoint_url('base-adresse', 'cities', |
|
375 |
slug=base_adresse.slug) |
|
376 |
mocked_get.return_value = utils.FakedResponse(content=FAKE_API_GEO, status_code=200) |
|
377 |
resp = app.get(endpoint, params={}, status=200) |
|
378 |
assert 'format=json' in mocked_get.call_args[0][0] |
|
379 |
assert 'boost=population' in mocked_get.call_args[0][0] |
|
380 |
assert 'fields=' + quote('nom,code,codesPostaux,surface,codeDepartement,' |
|
381 |
'departement,codeRegion,region,population') in mocked_get.call_args[0][0] |
|
382 |
assert not 'codeRegion=' in mocked_get.call_args[0][0] |
|
383 |
assert not 'codeDepartement=' in mocked_get.call_args[0][0] |
|
384 | ||
385 | ||
386 |
@mock.patch('passerelle.utils.Request.get') |
|
387 |
def test_base_adresse_cities_qs_county_region(mocked_get, app, base_adresse): |
|
388 |
base_adresse.county = 75 |
|
389 |
base_adresse.region = 11 |
|
390 |
base_adresse.save() |
|
391 |
endpoint = utils.generic_endpoint_url('base-adresse', 'cities', |
|
392 |
slug=base_adresse.slug) |
|
393 |
mocked_get.return_value = utils.FakedResponse(content=FAKE_API_GEO, status_code=200) |
|
394 |
resp = app.get(endpoint, params={}, status=200) |
|
395 |
assert 'codeRegion=11' in mocked_get.call_args[0][0] |
|
396 |
assert 'codeDepartement=75' in mocked_get.call_args[0][0] |
|
397 | ||
398 |
resp = app.get(endpoint, params={'county_code': 42, 'region_code': 42}, status=200) |
|
399 |
assert 'codeRegion=42' in mocked_get.call_args[0][0] |
|
400 |
assert 'codeDepartement=42' in mocked_get.call_args[0][0] |
|
401 | ||
402 | ||
403 |
@mock.patch('passerelle.utils.Request.get') |
|
404 |
def test_base_adresse_cities_qs_filter(mocked_get, app, base_adresse): |
|
405 |
endpoint = utils.generic_endpoint_url('base-adresse', 'cities', |
|
406 |
slug=base_adresse.slug) |
|
407 |
mocked_get.return_value = utils.FakedResponse(content=FAKE_API_GEO, status_code=200) |
|
408 |
resp = app.get(endpoint, params={'q': 'Par', 'zipcode': 75001, 'code': 75056}, |
|
409 |
status=200) |
|
410 |
assert 'nom=Par' in mocked_get.call_args[0][0] |
|
411 |
assert 'codePostal=75001' in mocked_get.call_args[0][0] |
|
412 |
assert 'code=75056' in mocked_get.call_args[0][0] |
|
413 | ||
414 |
resp = app.get(endpoint, params={'id': '75056.75001'}, status=200) |
|
415 |
assert 'codePostal=75001' in mocked_get.call_args[0][0] |
|
416 |
assert 'code=75056' in mocked_get.call_args[0][0] |
|
417 | ||
418 | ||
419 |
@mock.patch('passerelle.utils.Request.get') |
|
420 |
def test_base_adresse_counties(mocked_get, app, base_adresse): |
|
421 |
endpoint = utils.generic_endpoint_url('base-adresse', 'counties', |
|
422 |
slug=base_adresse.slug) |
|
423 |
assert endpoint == '/base-adresse/base-adresse/counties' |
|
424 |
mocked_get.return_value = utils.FakedResponse( |
|
425 |
content=json.dumps(FAKE_API_GEO_COUNTIES), status_code=200) |
|
426 |
resp = app.get(endpoint, params={}, status=200) |
|
427 |
data = resp.json['data'] |
|
428 |
assert data[0]['id'] == '59' |
|
429 |
assert data[0]['text'] == 'Nord (59)' |
|
430 |
assert data[1]['id'] == '58' |
|
431 |
assert data[1]['text'] == 'Nievre (58)' |
|
432 | ||
433 | ||
434 |
@mock.patch('passerelle.utils.Request.get') |
|
435 |
def test_base_adresse_counties_qs(mocked_get, app, base_adresse): |
|
436 |
endpoint = utils.generic_endpoint_url('base-adresse', 'counties', |
|
437 |
slug=base_adresse.slug) |
|
438 |
mocked_get.return_value = utils.FakedResponse( |
|
439 |
content=json.dumps([FAKE_API_GEO_COUNTIES[0]]), status_code=200) |
|
440 |
resp = app.get(endpoint, params={'code': '59', 'q': 'N', 'region_code': '32'}, status=200) |
|
441 |
assert 'fields=' + quote('nom,code,codeRegion,region') in mocked_get.call_args[0][0] |
|
442 |
assert 'nom=N' in mocked_get.call_args[0][0] |
|
443 |
assert 'code=59' in mocked_get.call_args[0][0] |
|
444 |
assert 'codeRegion=32' in mocked_get.call_args[0][0] |
|
445 | ||
446 |
resp = app.get(endpoint, params={'id': '59'}, status=200) |
|
447 |
assert 'code=59' in mocked_get.call_args[0][0] |
|
448 | ||
449 | ||
450 |
@mock.patch('passerelle.utils.Request.get') |
|
451 |
def test_base_adresse_regions(mocked_get, app, base_adresse): |
|
452 |
endpoint = utils.generic_endpoint_url('base-adresse', 'regions', |
|
453 |
slug=base_adresse.slug) |
|
454 |
assert endpoint == '/base-adresse/base-adresse/regions' |
|
455 |
mocked_get.return_value = utils.FakedResponse( |
|
456 |
content=json.dumps(FAKE_API_GEO_REGIONS), status_code=200) |
|
457 |
resp = app.get(endpoint, params={}, status=200) |
|
458 |
data = resp.json['data'] |
|
459 |
assert data[0]['id'] == '32' |
|
460 |
assert data[0]['text'] == 'Hauts-de-France' |
|
461 |
assert data[1]['id'] == '24' |
|
462 |
assert data[1]['text'] == 'Centre-Val de Loire' |
|
463 | ||
464 | ||
465 |
@mock.patch('passerelle.utils.Request.get') |
|
466 |
def test_base_adresse_regions_qs(mocked_get, app, base_adresse): |
|
467 |
endpoint = utils.generic_endpoint_url('base-adresse', 'regions', |
|
468 |
slug=base_adresse.slug) |
|
469 |
mocked_get.return_value = utils.FakedResponse( |
|
470 |
content=json.dumps([FAKE_API_GEO_REGIONS[0]]), status_code=200) |
|
471 |
resp = app.get(endpoint, params={'code': '32', 'q': 'Hauts'}, status=200) |
|
472 |
assert 'nom=Hauts' in mocked_get.call_args[0][0] |
|
473 |
assert 'code=32' in mocked_get.call_args[0][0] |
|
474 | ||
475 |
resp = app.get(endpoint, params={'id': '32'}, status=200) |
|
476 |
assert 'code=32' in mocked_get.call_args[0][0] |
|
261 |
- |