diff --git a/passerelle/apps/opengis/models.py b/passerelle/apps/opengis/models.py
index 9beb0e3e..f5ceb8f3 100644
--- a/passerelle/apps/opengis/models.py
+++ b/passerelle/apps/opengis/models.py
@@ -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)
diff --git a/tests/test_opengis.py b/tests/test_opengis.py
index 07d3fdfb..fa5e2de7 100644
--- a/tests/test_opengis.py
+++ b/tests/test_opengis.py
@@ -39,6 +39,16 @@ FAKE_SERVICE_CAPABILITIES = '''
WFS2.0.0
+
+
+
+
+ application/gml+xml; version=3.2
+ application/json; subtype=geojson
+
+
+
+
'''
FAKE_SERVICE_CAPABILITIES_V1_0_0 = '''
@@ -50,6 +60,16 @@ FAKE_SERVICE_CAPABILITIES_V1_0_0 = '''
WFS1.0.0
+
+
+
+
+ application/gml+xml; version=3.2
+ application/json; subtype=geojson
+
+
+
+
'''
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'