opengis: use correct json format only when available (#41224)

This commit is contained in:
Valentin Deniaud 2020-04-01 15:37:06 +02:00
parent 8b62667df0
commit d47ed7717c
2 changed files with 85 additions and 47 deletions

View File

@ -73,18 +73,21 @@ class OpenGIS(BaseResource):
class Meta:
verbose_name = _('OpenGIS')
def get_service_version(self, service_type, service_url, renew=False):
def get_capabilities(self, service_type, service_url):
if not service_url:
raise APIError('no %s URL declared' % service_type)
return self.requests.get(
service_url,
params={'request': 'GetCapabilities', 'service': service_type.upper()},
)
def get_service_version(self, service_type, service_url, renew=False):
cache_key = 'opengis-%s-%s-version' % (service_type, self.id)
if not renew:
service_version = cache.get(cache_key)
if service_version:
return service_version
response = self.requests.get(
service_url,
params={'request': 'GetCapabilities', 'service': service_type.upper()},
)
response = self.get_capabilities(service_type, service_url)
element = ET.fromstring(response.content)
service_version = element.attrib.get('version')
# cache version number for an hour
@ -97,6 +100,23 @@ class OpenGIS(BaseResource):
def get_wfs_service_version(self, renew=False):
return self.get_service_version('wfs', self.wfs_service_url, renew=renew)
def get_output_format(self):
cache_key = 'opengis-%s-output-format' % self.id
output_format = cache.get(cache_key)
if output_format:
return output_format
response = self.get_capabilities('wfs', self.wfs_service_url)
element = ET.fromstring(response.content)
ns = {'ows': 'http://www.opengis.net/ows/1.1'}
formats = element.findall('.//ows:Operation[@name="GetFeature"]/'
'ows:Parameter[@name="outputFormat"]/'
'ows:AllowedValues/ows:Value', ns)
for output_format in formats:
if 'json' in output_format.text.lower():
cache.set(cache_key, output_format.text, 3600)
return output_format.text
raise APIError('WFS server doesn\'t support json output format for GetFeature request')
def get_typename_label(self):
version_str = self.get_wfs_service_version()
version_tuple = tuple(int(x) for x in version_str.split('.'))
@ -123,6 +143,19 @@ class OpenGIS(BaseResource):
})
response.raise_for_status()
def build_get_features_params(self, typename=None, property_name=None, cql_filter=None):
params = {
'version': self.get_wfs_service_version(),
'service': 'WFS',
'request': 'GetFeature',
self.get_typename_label(): typename,
'propertyName': property_name,
'outputFormat': self.get_output_format(),
}
if cql_filter:
params['cql_filter'] = cql_filter
return params
@endpoint(perm='can_access', description='Get features',
parameters={
'type_names': {
@ -152,22 +185,14 @@ class OpenGIS(BaseResource):
})
def features(self, request, type_names, property_name, cql_filter=None,
filter_property_name=None, q=None, **kwargs):
params = {
'VERSION': self.get_wfs_service_version(),
'SERVICE': 'WFS',
'REQUEST': 'GetFeature',
self.get_typename_label(): type_names,
'PROPERTYNAME': property_name,
'OUTPUTFORMAT': 'json',
}
if cql_filter:
params.update({'CQL_FILTER': cql_filter})
if filter_property_name and q:
if 'case-insensitive' in kwargs:
operator = 'ILIKE'
else:
operator = 'LIKE'
params['CQL_FILTER'] += ' AND %s %s \'%%%s%%\'' % (filter_property_name, operator, q)
cql_filter += ' AND %s %s \'%%%s%%\'' % (filter_property_name, operator, q)
params = self.build_get_features_params(type_names, property_name, cql_filter)
response = self.requests.get(self.wfs_service_url, params=params)
data = []
try:
@ -297,14 +322,7 @@ class OpenGIS(BaseResource):
lon, lat = self.convert_coordinates(lon, lat)
cql_filter = 'DWITHIN(the_geom,Point(%.6f %.6f),%s,meters)' % (lon, lat, self.search_radius)
params = {
'VERSION': self.get_wfs_service_version(),
'SERVICE': 'WFS',
'REQUEST': 'GetFeature',
self.get_typename_label(): self.query_layer,
'OUTPUTFORMAT': 'json',
'CQL_FILTER': cql_filter
}
params = self.build_get_features_params(typename=self.query_layer, cql_filter=cql_filter)
response = self.requests.get(self.wfs_service_url, params=params)
if not response.ok:
raise APIError('Webservice returned status code %s' % response.status_code)

View File

@ -39,6 +39,16 @@ FAKE_SERVICE_CAPABILITIES = '''<?xml version="1.0" encoding="UTF-8"?>
<ows:ServiceType>WFS</ows:ServiceType><ows:ServiceTypeVersion>2.0.0</ows:ServiceTypeVersion><ows:Fees/>
<ows:AccessConstraints/>
</ows:ServiceIdentification>
<ows:OperationsMetadata>
<ows:Operation name="GetFeature">
<ows:Parameter name="outputFormat">
<ows:AllowedValues>
<ows:Value>application/gml+xml; version=3.2</ows:Value>
<ows:Value>application/json; subtype=geojson</ows:Value>
</ows:AllowedValues>
</ows:Parameter>
</ows:Operation>
</ows:OperationsMetadata>
</wfs:WFS_Capabilities>'''
FAKE_SERVICE_CAPABILITIES_V1_0_0 = '''<?xml version="1.0" encoding="UTF-8"?>
@ -50,6 +60,16 @@ FAKE_SERVICE_CAPABILITIES_V1_0_0 = '''<?xml version="1.0" encoding="UTF-8"?>
<ows:ServiceType>WFS</ows:ServiceType><ows:ServiceTypeVersion>1.0.0</ows:ServiceTypeVersion><ows:Fees/>
<ows:AccessConstraints/>
</ows:ServiceIdentification>
<ows:OperationsMetadata>
<ows:Operation name="GetFeature">
<ows:Parameter name="outputFormat">
<ows:AllowedValues>
<ows:Value>application/gml+xml; version=3.2</ows:Value>
<ows:Value>application/json; subtype=geojson</ows:Value>
</ows:AllowedValues>
</ows:Parameter>
</ows:Operation>
</ows:OperationsMetadata>
</wfs:WFS_Capabilities>'''
FAKE_FEATURES_JSON = '''
@ -326,12 +346,12 @@ def test_get_feature(mocked_get, app, connector):
assert endpoint == '/opengis/test/features'
mocked_get.side_effect = geoserver_responses
resp = app.get(endpoint, params={'type_names': 'ref_metro_limites_communales', 'property_name': 'nom'})
assert mocked_get.call_args[1]['params']['REQUEST'] == 'GetFeature'
assert mocked_get.call_args[1]['params']['PROPERTYNAME'] == 'nom'
assert mocked_get.call_args[1]['params']['request'] == 'GetFeature'
assert mocked_get.call_args[1]['params']['propertyName'] == 'nom'
assert mocked_get.call_args[1]['params']['TYPENAMES'] == 'ref_metro_limites_communales'
assert mocked_get.call_args[1]['params']['OUTPUTFORMAT'] == 'json'
assert mocked_get.call_args[1]['params']['SERVICE'] == 'WFS'
assert mocked_get.call_args[1]['params']['VERSION'] == connector.get_wfs_service_version()
assert 'json' in mocked_get.call_args[1]['params']['outputFormat']
assert mocked_get.call_args[1]['params']['service'] == 'WFS'
assert mocked_get.call_args[1]['params']['version'] == connector.get_wfs_service_version()
assert len(resp.json['data']) == 7
for item in resp.json['data']:
assert 'text' in item
@ -347,7 +367,7 @@ def test_get_filtered_feature(mocked_get, app, connector):
'property_name': 'nom',
'cql_filter': 'nom=\'Fontaine\''
})
assert mocked_get.call_args[1]['params']['CQL_FILTER'] == 'nom=\'Fontaine\''
assert mocked_get.call_args[1]['params']['cql_filter'] == 'nom=\'Fontaine\''
@mock.patch('passerelle.utils.Request.get')
@ -358,16 +378,16 @@ def test_get_filtered_by_property_feature(mocked_get, app, connector):
'property_name': 'nom', 'cql_filter': 'nom=\'Fontaine\'',
'filter_property_name': 'nom'}
app.get(endpoint, params=params)
assert mocked_get.call_args[1]['params']['CQL_FILTER'] == 'nom=\'Fontaine\''
assert mocked_get.call_args[1]['params']['cql_filter'] == 'nom=\'Fontaine\''
params['q'] = 'bens'
app.get(endpoint, params=params)
assert mocked_get.call_args[1]['params']['CQL_FILTER'] == 'nom=\'Fontaine\' AND nom LIKE \'%bens%\''
assert mocked_get.call_args[1]['params']['cql_filter'] == 'nom=\'Fontaine\' AND nom LIKE \'%bens%\''
params['case-insensitive'] = True
app.get(endpoint, params=params)
assert mocked_get.call_args[1]['params']['CQL_FILTER'] == 'nom=\'Fontaine\' AND nom ILIKE \'%bens%\''
assert mocked_get.call_args[1]['params']['cql_filter'] == 'nom=\'Fontaine\' AND nom ILIKE \'%bens%\''
params.pop('cql_filter')
app.get(endpoint, params=params)
assert 'CQL_FILTER' not in mocked_get.call_args[1]['params']
assert 'cql_filter' not in mocked_get.call_args[1]['params']
@mock.patch('passerelle.utils.Request.get')
@ -379,12 +399,12 @@ def test_get_feature_error(mocked_get, app, connector):
'type_names': 'ref_metro_limites_communales',
'property_name': 'nom'
})
assert mocked_get.call_args[1]['params']['REQUEST'] == 'GetFeature'
assert mocked_get.call_args[1]['params']['PROPERTYNAME'] == 'nom'
assert mocked_get.call_args[1]['params']['request'] == 'GetFeature'
assert mocked_get.call_args[1]['params']['propertyName'] == 'nom'
assert mocked_get.call_args[1]['params']['TYPENAMES'] == 'ref_metro_limites_communales'
assert mocked_get.call_args[1]['params']['OUTPUTFORMAT'] == 'json'
assert mocked_get.call_args[1]['params']['SERVICE'] == 'WFS'
assert mocked_get.call_args[1]['params']['VERSION'] == connector.get_wfs_service_version()
assert 'json' in mocked_get.call_args[1]['params']['outputFormat']
assert mocked_get.call_args[1]['params']['service'] == 'WFS'
assert mocked_get.call_args[1]['params']['version'] == connector.get_wfs_service_version()
result = resp.json
assert result['err'] == 1
assert result['err_desc'] == 'OpenGIS Error: NoApplicableCode'
@ -400,12 +420,12 @@ def test_get_feature_error2(mocked_get, app, connector):
'type_names': 'ref_metro_limites_communales',
'property_name': 'nom'
})
assert mocked_get.call_args[1]['params']['REQUEST'] == 'GetFeature'
assert mocked_get.call_args[1]['params']['PROPERTYNAME'] == 'nom'
assert mocked_get.call_args[1]['params']['request'] == 'GetFeature'
assert mocked_get.call_args[1]['params']['propertyName'] == 'nom'
assert mocked_get.call_args[1]['params']['TYPENAMES'] == 'ref_metro_limites_communales'
assert mocked_get.call_args[1]['params']['OUTPUTFORMAT'] == 'json'
assert mocked_get.call_args[1]['params']['SERVICE'] == 'WFS'
assert mocked_get.call_args[1]['params']['VERSION'] == connector.get_wfs_service_version()
assert 'json' in mocked_get.call_args[1]['params']['outputFormat']
assert mocked_get.call_args[1]['params']['service'] == 'WFS'
assert mocked_get.call_args[1]['params']['version'] == connector.get_wfs_service_version()
result = resp.json
assert result['err'] == 1
assert result['err_desc'] == 'OpenGIS Error: unparsable error'
@ -421,8 +441,8 @@ def test_typename_parameter_upgrade(mocked_get, server_responses, version, typen
assert endpoint == '/opengis/test/features'
mocked_get.side_effect = server_responses
resp = app.get(endpoint, params={'type_names': '...', 'property_name': '...'})
assert mocked_get.call_args[1]['params']['REQUEST'] == 'GetFeature'
assert mocked_get.call_args[1]['params']['VERSION'] == version
assert mocked_get.call_args[1]['params']['request'] == 'GetFeature'
assert mocked_get.call_args[1]['params']['version'] == version
assert typename_label in mocked_get.call_args[1]['params'].keys()
@ -445,7 +465,7 @@ def test_reverse_geocoding(mocked_get, app, connector):
'lat': '45.1893469606986',
'lon': '5.72462060798'
})
assert (mocked_get.call_args[1]['params']['CQL_FILTER']
assert (mocked_get.call_args[1]['params']['cql_filter']
== 'DWITHIN(the_geom,Point(1914061.486036 4224640.457791),45,meters)')
assert resp.json['lon'] == '5.724077'
assert resp.json['lat'] == '45.189397'
@ -464,7 +484,7 @@ def test_reverse_geocoding(mocked_get, app, connector):
'lat': '45.183784',
'lon': '5.714885'
})
assert mocked_get.call_args[1]['params']['CQL_FILTER'] == 'DWITHIN(the_geom,Point(5.714885 45.183784),10,meters)'
assert mocked_get.call_args[1]['params']['cql_filter'] == 'DWITHIN(the_geom,Point(5.714885 45.183784),10,meters)'
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'Unable to geocode'