base_adresse: add parameter type=housenumber to prevent users from picking street addresses (#76376)
gitea/passerelle/pipeline/head This commit looks good Details

This commit is contained in:
Benjamin Dauvergne 2023-04-14 16:40:01 +02:00
parent 8e215185ec
commit 84cd51957e
2 changed files with 70 additions and 7 deletions

View File

@ -125,10 +125,24 @@ class BaseAdresse(AddressResource):
'Prioritize results according to coordinates. "lat" parameter must also be present.' 'Prioritize results according to coordinates. "lat" parameter must also be present.'
) )
}, },
'type': {
'description': _(
'Type of address to return, housenumber, street, locality, municipality or all. Default is all.'
)
},
}, },
) )
def addresses( def addresses(
self, request, id=None, q=None, zipcode='', citycode=None, lat=None, lon=None, page_limit=5 self,
request,
id=None,
q=None,
zipcode='',
citycode=None,
lat=None,
lon=None,
page_limit=5,
type=None,
): ):
if id is not None: if id is not None:
return self.get_by_id(request, id=id, citycode=citycode) return self.get_by_id(request, id=id, citycode=citycode)
@ -156,6 +170,8 @@ class BaseAdresse(AddressResource):
if self.latitude and self.longitude or lat and lon: if self.latitude and self.longitude or lat and lon:
query_args['lat'] = lat or self.latitude query_args['lat'] = lat or self.latitude
query_args['lon'] = lon or self.longitude query_args['lon'] = lon or self.longitude
if type in ('housenumber', 'street', 'locality', 'municipality'):
query_args['type'] = type
query = urlencode(query_args) query = urlencode(query_args)
url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
@ -167,7 +183,8 @@ class BaseAdresse(AddressResource):
result = [] result = []
for feature in result_response.json().get('features'): features = result_response.json().get('features')
for feature in features:
if not feature['geometry']['type'] == 'Point': if not feature['geometry']['type'] == 'Point':
continue # skip unknown continue # skip unknown
data = self.format_address_data(feature) data = self.format_address_data(feature)
@ -177,7 +194,6 @@ class BaseAdresse(AddressResource):
) )
if not created: if not created:
address.update_timestamp() address.update_timestamp()
return {'data': result} return {'data': result}
def get_by_id(self, request, id, citycode=None): def get_by_id(self, request, id, citycode=None):
@ -236,13 +252,25 @@ class BaseAdresse(AddressResource):
'Prioritize results according to coordinates. "lon" parameter must be present.' 'Prioritize results according to coordinates. "lon" parameter must be present.'
) )
}, },
'type': {
'description': _(
'Type of address to return, housenumber, street, locality, municipality or all. Default is all.'
)
},
}, },
) )
def search(self, request, q, zipcode='', citycode=None, lat=None, lon=None, **kwargs): def search(self, request, q, zipcode='', citycode=None, lat=None, lon=None, type=None, **kwargs):
if kwargs.get('format', 'json') != 'json': if kwargs.get('format', 'json') != 'json':
raise NotImplementedError() raise NotImplementedError()
result = self.addresses( result = self.addresses(
request, q=q, zipcode=zipcode, citycode=citycode, lat=lat, lon=lon, page_limit=1 request,
q=q,
zipcode=zipcode,
citycode=citycode,
lat=lat,
lon=lon,
page_limit=1,
type=type,
) )
return result['data'] return result['data']
@ -251,15 +279,23 @@ class BaseAdresse(AddressResource):
parameters={ parameters={
'lat': {'description': _('Latitude'), 'example_value': 48.833708}, 'lat': {'description': _('Latitude'), 'example_value': 48.833708},
'lon': {'description': _('Longitude'), 'example_value': 2.323349}, 'lon': {'description': _('Longitude'), 'example_value': 2.323349},
'type': {
'description': _(
'Type of address to return, housenumber, street, locality, municipality or all. Default is all.'
)
},
}, },
) )
def reverse(self, request, lat, lon, **kwargs): def reverse(self, request, lat, lon, type=None, **kwargs):
if kwargs.get('format', 'json') != 'json': if kwargs.get('format', 'json') != 'json':
raise NotImplementedError() raise NotImplementedError()
scheme, netloc, path, params, query, fragment = urlparse.urlparse(self.service_url) scheme, netloc, path, params, query, fragment = urlparse.urlparse(self.service_url)
path = urlparse.urljoin(path, 'reverse/') path = urlparse.urljoin(path, 'reverse/')
query = urlencode({'lat': lat, 'lon': lon}) query_dict = {'lat': lat, 'lon': lon}
if type in ('housenumber', 'street', 'locality', 'municipality'):
query_dict['type'] = type
query = urlencode(query_dict)
url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
try: try:

View File

@ -211,6 +211,21 @@ def test_base_adresse_search(mocked_get, app, base_adresse):
assert data['display_name'] == 'Rue Roger Halope 49000 Angers' assert data['display_name'] == 'Rue Roger Halope 49000 Angers'
@mock.patch('passerelle.utils.Request.get')
def test_base_adresse_search_type_housenumber(mocked_get, app, base_adresse):
endpoint = tests.utils.generic_endpoint_url('base-adresse', 'search', slug=base_adresse.slug)
mocked_get.return_value = tests.utils.FakedResponse(content=FAKED_CONTENT, status_code=200)
app.get(endpoint, params={'q': 'plop'}, status=200)
assert 'type=' not in mocked_get.call_args[0][0]
for type in ['housenumber', 'street', 'locality', 'municipality']:
app.get(endpoint, params={'q': 'plop', 'type': type}, status=200)
assert f'type={type}' in mocked_get.call_args[0][0]
app.get(endpoint, params={'q': 'plop', 'type': 'foo'}, status=200)
assert 'type=foo' not in mocked_get.call_args[0][0]
@mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.get')
def test_base_adresse_search_limit_to_200(mocked_get, app, base_adresse): def test_base_adresse_search_limit_to_200(mocked_get, app, base_adresse):
endpoint = tests.utils.generic_endpoint_url('base-adresse', 'search', slug=base_adresse.slug) endpoint = tests.utils.generic_endpoint_url('base-adresse', 'search', slug=base_adresse.slug)
@ -366,6 +381,18 @@ def test_base_adresse_reverse_path(mocked_get, app, base_adresse):
assert mocked_get.call_args[0][0].startswith('http://example.net/path/reverse/?') assert mocked_get.call_args[0][0].startswith('http://example.net/path/reverse/?')
@mock.patch('passerelle.utils.Request.get')
def test_base_adresse_reverse_type_housenumber(mocked_get, app, base_adresse):
mocked_get.return_value = tests.utils.FakedResponse(content=json.dumps({'features': []}), status_code=200)
app.get('/base-adresse/%s/reverse?lon=-0.593775&lat=47.474633' % base_adresse.slug)
assert 'type=' not in mocked_get.call_args[0][0]
app.get('/base-adresse/%s/reverse?lon=-0.593775&lat=47.474633&type=truc' % base_adresse.slug)
assert 'type=' not in mocked_get.call_args[0][0]
for type in ['housenumber', 'street', 'locality', 'municipality']:
app.get(f'/base-adresse/%s/reverse?lon=-0.593775&lat=47.474633&type={type}' % base_adresse.slug)
assert f'type={type}' in mocked_get.call_args[0][0]
@mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.get')
def test_base_adresse_reverse_api_timeout(mocked_get, app, base_adresse): def test_base_adresse_reverse_api_timeout(mocked_get, app, base_adresse):
mocked_get.side_effect = ConnectionError('Remote end closed connection without response') mocked_get.side_effect = ConnectionError('Remote end closed connection without response')