0001-generalize-ArcGIS-connector-17763.patch
passerelle/contrib/arcgis/migrations/0003_auto_20181102_1550.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.16 on 2018-11-02 14:50 |
|
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 |
('arcgis', '0002_auto_20170920_0951'), |
|
12 |
] |
|
13 | ||
14 |
operations = [ |
|
15 |
migrations.AlterModelOptions( |
|
16 |
name='arcgis', |
|
17 |
options={'verbose_name': 'ArcGIS REST API'}, |
|
18 |
), |
|
19 |
migrations.AddField( |
|
20 |
model_name='arcgis', |
|
21 |
name='basic_auth_password', |
|
22 |
field=models.CharField(blank=True, max_length=128, verbose_name='Basic authentication password'), |
|
23 |
), |
|
24 |
migrations.AddField( |
|
25 |
model_name='arcgis', |
|
26 |
name='basic_auth_username', |
|
27 |
field=models.CharField(blank=True, max_length=128, verbose_name='Basic authentication username'), |
|
28 |
), |
|
29 |
migrations.AddField( |
|
30 |
model_name='arcgis', |
|
31 |
name='client_certificate', |
|
32 |
field=models.FileField(blank=True, null=True, upload_to=b'', verbose_name='TLS client certificate'), |
|
33 |
), |
|
34 |
migrations.AddField( |
|
35 |
model_name='arcgis', |
|
36 |
name='http_proxy', |
|
37 |
field=models.CharField(blank=True, max_length=128, verbose_name='HTTP and HTTPS proxy'), |
|
38 |
), |
|
39 |
migrations.AddField( |
|
40 |
model_name='arcgis', |
|
41 |
name='trusted_certificate_authorities', |
|
42 |
field=models.FileField(blank=True, null=True, upload_to=b'', verbose_name='TLS trusted CAs'), |
|
43 |
), |
|
44 |
migrations.AddField( |
|
45 |
model_name='arcgis', |
|
46 |
name='verify_cert', |
|
47 |
field=models.BooleanField(default=True, verbose_name='TLS verify certificates'), |
|
48 |
), |
|
49 |
migrations.AlterField( |
|
50 |
model_name='arcgis', |
|
51 |
name='base_url', |
|
52 |
field=models.URLField(verbose_name='Webservice Base URL'), |
|
53 |
), |
|
54 |
migrations.AlterField( |
|
55 |
model_name='arcgis', |
|
56 |
name='log_level', |
|
57 |
field=models.CharField(choices=[(b'NOTSET', b'NOTSET'), (b'DEBUG', b'DEBUG'), (b'INFO', b'INFO'), (b'WARNING', b'WARNING'), (b'ERROR', b'ERROR'), (b'CRITICAL', b'CRITICAL')], default=b'INFO', max_length=10, verbose_name='Log Level'), |
|
58 |
), |
|
59 |
] |
passerelle/contrib/arcgis/models.py | ||
---|---|---|
1 |
# Copyright (C) 2016 Entr'ouvert |
|
1 |
# passerelle - uniform access to multiple data sources and services |
|
2 |
# Copyright (C) 2018 Entr'ouvert |
|
2 | 3 |
# |
3 | 4 |
# This program is free software: you can redistribute it and/or modify it |
4 | 5 |
# under the terms of the GNU Affero General Public License as published |
... | ... | |
13 | 14 |
# You should have received a copy of the GNU Affero General Public License |
14 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 16 | |
17 |
import urlparse |
|
18 | ||
16 | 19 |
from django.db import models |
20 |
from django.template import Template, Context |
|
17 | 21 |
from django.utils.translation import ugettext_lazy as _ |
18 | 22 | |
19 |
from passerelle.base.models import BaseResource |
|
20 |
from passerelle.utils.api import endpoint |
|
21 | 23 |
from passerelle.utils.jsonresponse import APIError |
24 |
from passerelle.utils.api import endpoint |
|
25 |
from passerelle.base.models import BaseResource, HTTPResource |
|
22 | 26 | |
23 | 27 | |
24 |
class ParameterTypeError(Exception): |
|
25 |
http_status = 400 |
|
26 |
log_error = False |
|
27 | ||
28 |
class ArcGISError(APIError): |
|
29 |
pass |
|
28 | 30 | |
29 |
class Arcgis(BaseResource): |
|
30 |
base_url = models.CharField(_('SIG Url'), max_length=256) |
|
31 | 31 | |
32 |
class ArcGIS(BaseResource, HTTPResource): |
|
32 | 33 |
category = _('Geographic information system') |
33 | 34 | |
35 |
base_url = models.URLField(_('Webservice Base URL')) |
|
36 | ||
34 | 37 |
class Meta: |
35 |
verbose_name = _('Arcgis Webservice')
|
|
38 |
verbose_name = _('ArcGIS REST API')
|
|
36 | 39 | |
37 |
@endpoint() |
|
38 |
def district(self, request, lon=None, lat=None): |
|
39 |
if lon and lat: |
|
40 |
try: |
|
41 |
lon, lat = float(lon), float(lat) |
|
42 |
geometry = '{}, {}'.format(lon, lat) |
|
43 |
geometryType = 'esriGeometryPoint' |
|
44 |
except(ValueError,): |
|
45 |
raise ParameterTypeError('<lon> and <lat> must be floats') |
|
46 |
else: |
|
47 |
geometry = '' |
|
48 |
geometryType = 'esriGeometryEnvelope' |
|
40 |
@endpoint(name='mapservice-query', |
|
41 |
description=_('Map Service Query'), |
|
42 |
perm='can_access', |
|
43 |
parameters={ |
|
44 |
'folder': { |
|
45 |
'description': _('Folder name'), |
|
46 |
'example_value': 'Specialty', |
|
47 |
}, |
|
48 |
'service': { |
|
49 |
'description': _('Service name'), |
|
50 |
'example_value': 'ESRI_StateCityHighway_USA', |
|
51 |
}, |
|
52 |
'layer': { |
|
53 |
'description': _('Layer or table name'), |
|
54 |
'example_value': '1', |
|
55 |
}, |
|
56 |
'lat': {'description': _('Latitude')}, |
|
57 |
'lon': {'description': _('Longitude')}, |
|
58 |
'latmin': {'description': _('Minimal latitude (envelope)')}, |
|
59 |
'lonmin': {'description': _('Minimal longitude (envelope)')}, |
|
60 |
'latmax': {'description': _('Minimal atitude (envelope)')}, |
|
61 |
'lonmax': {'description': _('Minimal Longitude (envelope)')}, |
|
62 |
'q': {'description': _('Search text in display field')}, |
|
63 |
'template': { |
|
64 |
'description': _('Django template for text attribute'), |
|
65 |
'example_value': '{{ attributes.STATE_NAME }} ({{ attributes.STATE_ABBR }})', |
|
66 |
}, |
|
67 |
'id_template': { |
|
68 |
'description': _('Django template for id attribute'), |
|
69 |
}, |
|
70 |
'full': { |
|
71 |
'description': _('Returns all ArcGIS informations (geometry, metadata)'), |
|
72 |
'type': 'bool', |
|
73 |
}, |
|
74 |
}) |
|
75 |
def mapservice_query(self, request, service, layer='0', folder='', lat=None, lon=None, |
|
76 |
latmin=None, lonmin=None, latmax=None, lonmax=None, q=None, |
|
77 |
template=None, id_template=None, full=False, **kwargs): |
|
78 |
url = urlparse.urljoin(self.base_url, 'services/') |
|
79 |
if folder: |
|
80 |
url = urlparse.urljoin(url, folder + '/') |
|
81 |
url = urlparse.urljoin(url, service + '/MapServer/' + layer + '/query') |
|
49 | 82 | |
83 |
# build query params |
|
84 |
# cf https://developers.arcgis.com/rest/services-reference/query-map-service-layer-.htm |
|
50 | 85 |
params = { |
51 |
'where': '1=1', |
|
52 |
'f': 'pjson', |
|
53 |
'geometry': geometry, |
|
54 |
'geometryType': geometryType, |
|
86 |
'f': 'json', |
|
55 | 87 |
'inSR': '4326', |
56 |
'outFields': '*', |
|
57 | 88 |
'outSR': '4326', |
58 |
'returnCountOnly': 'false', |
|
59 |
'returnDistinctValues': 'false', |
|
60 |
'returnExtentOnly': 'false', |
|
61 |
'returnGeometry': 'false', |
|
62 |
'returnIdsOnly': 'false', |
|
63 |
'returnM': 'false', |
|
64 |
'returnZ': 'false', |
|
65 |
'spatialRel': 'esriSpatialRelIntersects', |
|
66 |
'units': 'esriSRUnit_Foot', |
|
89 |
'outFields': '*', |
|
67 | 90 |
} |
68 | ||
69 |
url = '%s/query' % self.base_url |
|
91 |
if lat and lon: |
|
92 |
try: |
|
93 |
lon, lat = float(lon), float(lat) |
|
94 |
except (ValueError,): |
|
95 |
raise APIError('<lon> and <lat> must be floats', http_status=400) |
|
96 |
params['geometry'] = '{},{}'.format(lon, lat) |
|
97 |
params['geometryType'] = 'esriGeometryPoint' |
|
98 |
elif latmin and lonmin and latmax and lonmax: |
|
99 |
try: |
|
100 |
lonmin, latmin = float(lonmin), float(latmin) |
|
101 |
lonmax, latmax = float(lonmax), float(latmax) |
|
102 |
except (ValueError,): |
|
103 |
raise APIError('<lonmin> <latmin> <lonmax> and <latmax> must be floats', |
|
104 |
http_status=400) |
|
105 |
params['geometry'] = '{},{},{},{}'.format(lonmin, latmin, lonmax, latmax) |
|
106 |
params['geometryType'] = 'esriGeometryEnvelope' |
|
107 |
if q is not None: |
|
108 |
params['text'] = q |
|
109 |
# consider all remaining parameters as ArcGIS ones |
|
110 |
params.update(kwargs) |
|
111 |
if 'where' not in params and 'text' not in params: |
|
112 |
params['where'] = '1=1' |
|
113 |
if 'distance' in params and 'units' not in params: |
|
114 |
params['units'] = 'esriSRUnit_Meter' |
|
70 | 115 | |
71 | 116 |
response = self.requests.get(url, params=params) |
72 |
data = response.json() |
|
73 |
features = data['features'] |
|
74 |
if not features: |
|
75 |
raise APIError('No features found.') |
|
76 | 117 | |
77 |
data = [ |
|
78 |
{'id': feature['attributes'].get('NUMERO'), 'text': feature['attributes'].get('NOM')} for feature in features] |
|
118 |
# errors |
|
119 |
if response.status_code // 100 != 2: |
|
120 |
raise ArcGISError('ArcGIS returned status code %s' % response.status_code) |
|
121 |
try: |
|
122 |
infos = response.json() |
|
123 |
except (ValueError,): |
|
124 |
raise ArcGISError('ArcGIS returned invalid JSON content: %r' % response.content) |
|
125 |
if 'error' in infos: |
|
126 |
err_desc = infos['error'].get('message') or 'unknown ArcGIS error' |
|
127 |
raise ArcGISError(err_desc, data=infos) |
|
128 | ||
129 |
features = infos.pop('features', []) |
|
130 |
id_fieldname = infos.get('objectIdFieldName') or 'OBJECTID' |
|
131 |
text_fieldname = infos.get('displayFieldName') |
|
132 |
if infos.get('fieldAliases'): |
|
133 |
aliases = {v: k for k, v in infos['fieldAliases'].items()} |
|
134 |
else: |
|
135 |
aliases = {} |
|
136 | ||
137 |
# data is the features list, with 'id' and 'text' entries |
|
138 |
data = [] |
|
79 | 139 | |
80 |
if len(data) == 1: |
|
81 |
return {'data': data[0]} |
|
140 |
def get_feature_attribute(feature, attribute): |
|
141 |
if attribute in feature['attributes']: |
|
142 |
return feature['attributes'][attribute] |
|
143 |
return feature['attributes'].get(aliases.get(attribute)) |
|
144 | ||
145 |
if template: |
|
146 |
template = Template(template) |
|
147 |
if id_template: |
|
148 |
id_template = Template(id_template) |
|
149 |
for n, feature in enumerate(features): |
|
150 |
if 'attributes' in feature: |
|
151 |
feature['id'] = '%s' % get_feature_attribute(feature, id_fieldname) |
|
152 |
feature['text'] = '%s' % get_feature_attribute(feature, text_fieldname) |
|
153 |
else: |
|
154 |
feature['id'] = feature['text'] = '%d' % (n+1) |
|
155 |
if template: |
|
156 |
feature['text'] = template.render(Context(feature)) |
|
157 |
if id_template: |
|
158 |
feature['id'] = id_template.render(Context(feature)) |
|
159 |
if not full and 'geometry' in feature: |
|
160 |
del feature['geometry'] |
|
161 |
data.append(feature) |
|
162 | ||
163 |
if full: |
|
164 |
return {'data': data, 'metadata': infos} |
|
82 | 165 |
return {'data': data} |
166 | ||
167 |
@endpoint(name='district', |
|
168 |
description=_('Districts in Nancy Town (deprecated)'), |
|
169 |
parameters={ |
|
170 |
'lat': {'description': _('Latitude')}, |
|
171 |
'lon': {'description': _('Longitude')}, |
|
172 |
}) |
|
173 |
def district(self, request, lon=None, lat=None): |
|
174 |
if 'NANCY_Grc' in self.base_url: |
|
175 |
# Nancy URL used to contains folder, service and layer, remove them |
|
176 |
self.base_url = 'https://geoservices.grand-nancy.org/arcgis/rest/' |
|
177 |
features = self.mapservice_query(request, folder='public', service='NANCY_Grc', layer='0', |
|
178 |
template='{{ attributes.NOM }}', |
|
179 |
id_template='{{ attributes.NUMERO }}', |
|
180 |
lon=lon, lat=lat)['data'] |
|
181 |
if not features: |
|
182 |
raise APIError('No features found.') |
|
183 |
for feature in features: |
|
184 |
del feature['attributes'] |
|
185 |
feature['id'] = int(feature['id']) |
|
186 |
if len(features) == 1: |
|
187 |
return {'data': features[0]} |
|
188 |
return {'data': features} |
passerelle/contrib/arcgis/templates/arcgis/arcgis_detail.html | ||
---|---|---|
1 |
{% extends "passerelle/manage/service_view.html" %} |
|
2 |
{% load i18n passerelle %} |
|
3 | ||
4 |
{% block endpoints %} |
|
5 |
<ul> |
|
6 |
<li> |
|
7 |
{%trans "Request : "%} |
|
8 |
{% url "generic-endpoint" connector="arcgis" slug=object.slug endpoint="district" as get_district %} |
|
9 |
<a href="{{get_district}}?lat=48.695243&lon=6.178921">{{get_district}}?lat=48.695243&lon=6.178921</a> |
|
10 |
</li> |
|
11 |
<li> |
|
12 |
{%trans "Response : "%} |
|
13 |
<samp> |
|
14 |
{ |
|
15 |
"data": { |
|
16 |
"id": 7, |
|
17 |
"name": "SAINT NICOLAS / CHARLES III / VIEILLE VILLE / TROIS MAISONS/ LEOPOLD" |
|
18 |
}, |
|
19 |
"err": 0 |
|
20 |
} |
|
21 |
</samp> |
|
22 |
</li> |
|
23 |
</ul> |
|
24 |
{% endblock %} |
tests/test_arcgis.py | ||
---|---|---|
1 |
import os |
|
2 |
import json |
|
3 | ||
1 |
# -*- coding: utf-8 -*- |
|
4 | 2 |
import pytest |
5 | 3 |
import mock |
4 |
import utils |
|
6 | 5 | |
7 | ||
8 |
from django.core.urlresolvers import reverse |
|
9 | 6 |
from django.contrib.contenttypes.models import ContentType |
10 | 7 | |
8 |
from passerelle.contrib.arcgis.models import ArcGIS |
|
11 | 9 |
from passerelle.base.models import ApiUser, AccessRight |
12 |
from passerelle.contrib.arcgis.models import Arcgis |
|
13 | ||
14 | ||
15 |
TEST_BASE_DIR = os.path.join(os.path.dirname(__file__), 'data', 'nancy_arcgis') |
|
16 | ||
17 | ||
18 |
def get_file_content(filename): |
|
19 |
with open(os.path.join(TEST_BASE_DIR, filename), 'rb') as fd: |
|
20 |
return fd.read() |
|
21 | 10 | |
22 | ||
23 |
class MockedRequestsResponse(mock.Mock): |
|
24 | ||
25 |
def json(self): |
|
26 |
return json.loads(self.content) |
|
27 | ||
28 | ||
29 |
@pytest.fixture |
|
30 |
def setup(db): |
|
31 |
api = ApiUser.objects.create(username='all', |
|
32 |
keytype='', key='') |
|
33 |
arcgis = Arcgis.objects.create(base_url='https://whatevever.org/layer/0', slug='test') |
|
34 |
obj_type = ContentType.objects.get_for_model(arcgis) |
|
35 |
AccessRight.objects.create(codename='can_access', apiuser=api, |
|
36 |
resource_type=obj_type, resource_pk=arcgis.pk) |
|
37 |
return arcgis |
|
11 |
# from http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/fold/serv/MapServer/1 |
|
12 |
STATES = '''{ |
|
13 |
"fieldAliases" : { |
|
14 |
"OBJECTID" : "OBJECTID", |
|
15 |
"STATE_NAME" : "STATE_NAME", |
|
16 |
"STATE_ABBR" : "STATE_ABBR" |
|
17 |
}, |
|
18 |
"features" : [ |
|
19 |
{ |
|
20 |
"attributes" : { |
|
21 |
"STATE_NAME" : "Texas", |
|
22 |
"STATE_ABBR" : "TX", |
|
23 |
"OBJECTID" : 40 |
|
24 |
}, |
|
25 |
"geometry" : { |
|
26 |
"rings" : [ |
|
27 |
[ |
|
28 |
[-105.998886788462, 31.3939400524361], |
|
29 |
[-106.21328556164, 31.4782464373727] |
|
30 |
] |
|
31 |
] |
|
32 |
} |
|
33 |
}, |
|
34 |
{ |
|
35 |
"geometry" : { |
|
36 |
"rings" : [ |
|
37 |
[ |
|
38 |
[-111.475425113078, 44.7021622250113], |
|
39 |
[-111.480804007084, 44.6914159859524] |
|
40 |
] |
|
41 |
] |
|
42 |
}, |
|
43 |
"attributes" : { |
|
44 |
"STATE_NAME" : "Montana", |
|
45 |
"STATE_ABBR" : "MT", |
|
46 |
"OBJECTID" : 2 |
|
47 |
} |
|
48 |
} |
|
49 |
], |
|
50 |
"spatialReference" : { |
|
51 |
"wkid" : 4326 |
|
52 |
}, |
|
53 |
"fields" : [ |
|
54 |
{ |
|
55 |
"alias" : "OBJECTID", |
|
56 |
"type" : "esriFieldTypeOID", |
|
57 |
"name" : "OBJECTID" |
|
58 |
}, |
|
59 |
{ |
|
60 |
"type" : "esriFieldTypeString", |
|
61 |
"alias" : "STATE_NAME", |
|
62 |
"length" : 25, |
|
63 |
"name" : "STATE_NAME" |
|
64 |
}, |
|
65 |
{ |
|
66 |
"length" : 2, |
|
67 |
"alias" : "STATE_ABBR", |
|
68 |
"type" : "esriFieldTypeString", |
|
69 |
"name" : "STATE_ABBR" |
|
70 |
} |
|
71 |
], |
|
72 |
"geometryType" : "esriGeometryPolygon", |
|
73 |
"displayFieldName" : "STATE_NAME" |
|
74 |
}''' |
|
38 | 75 | |
39 | 76 | |
40 | 77 |
@pytest.fixture |
41 |
def url(): |
|
42 |
return reverse('generic-endpoint', kwargs={ |
|
43 |
'connector': 'arcgis', 'slug': 'test', 'endpoint': 'district'}) |
|
44 | ||
45 | ||
46 |
def test_get_district_parameters_error(app, setup, url): |
|
47 |
resp = app.get(url, params={'lon': 'lon', 'lat': 'lat'}, status=400) |
|
48 |
assert resp.json['err_desc'] == '<lon> and <lat> must be floats' |
|
49 | ||
50 | ||
51 |
@mock.patch('passerelle.utils.Request.get') |
|
52 |
def test_get_district(mocked_get, app, setup, url): |
|
53 |
mocked_get.return_value = MockedRequestsResponse( |
|
54 |
content=get_file_content('sigresponse.json'), |
|
55 |
status_code=200) |
|
56 | ||
57 |
resp = app.get(url, params={'lon': 6.172122, 'lat': 48.673836}, status=200) |
|
58 |
data = resp.json['data'] |
|
59 |
assert data['id'] == 4 |
|
60 |
assert data['text'] == 'HAUSSONVILLE / BLANDAN / MON DESERT / SAURUPT' |
|
61 | ||
62 | ||
63 |
@mock.patch('passerelle.utils.Request.get') |
|
64 |
def test_get_all_district(mocked_get, app, setup, url): |
|
65 |
mocked_get.return_value = MockedRequestsResponse( |
|
66 |
content=get_file_content('all_districts.json'), |
|
67 |
status_code=200) |
|
68 | ||
69 |
resp = app.get(url, status=200) |
|
70 |
data = resp.json['data'] |
|
71 |
assert len(data) == 7 |
|
72 | ||
73 |
@mock.patch('passerelle.utils.Request.get') |
|
74 |
def test_no_district(mocked_get, app, setup, url): |
|
75 |
mocked_get.return_value = MockedRequestsResponse( |
|
76 |
content='{"features": []}', |
|
77 |
status_code=200) |
|
78 | ||
79 |
resp = app.get(url, status=200) |
|
80 |
assert resp.json['err'] == 1 |
|
81 |
assert resp.json['err_class'] == 'passerelle.utils.jsonresponse.APIError' |
|
82 |
assert resp.json['err_desc'] == 'No features found.' |
|
78 |
def arcgis(db): |
|
79 |
return ArcGIS.objects.create(slug='test', |
|
80 |
base_url='https://arcgis.example.net/') |
|
81 | ||
82 | ||
83 |
def test_arcgis_mapservice_query(app, arcgis): |
|
84 |
endpoint = utils.generic_endpoint_url('arcgis', 'mapservice-query', slug=arcgis.slug) |
|
85 |
assert endpoint == '/arcgis/test/mapservice-query' |
|
86 |
params = { |
|
87 |
'folder': 'fold', |
|
88 |
'service': 'serv', |
|
89 |
'layer': '1' |
|
90 |
} |
|
91 | ||
92 |
with mock.patch('passerelle.utils.Request.get') as requests_get: |
|
93 |
requests_get.return_value = utils.FakedResponse(content=STATES, |
|
94 |
status_code=200) |
|
95 | ||
96 |
resp = app.get(endpoint, params=params, status=403) |
|
97 |
assert requests_get.call_count == 0 |
|
98 |
assert resp.json['err'] == 1 |
|
99 |
assert resp.json['err_class'] == 'django.core.exceptions.PermissionDenied' |
|
100 | ||
101 |
# open access |
|
102 |
api = ApiUser.objects.create(username='all', keytype='', key='') |
|
103 |
obj_type = ContentType.objects.get_for_model(arcgis) |
|
104 |
AccessRight.objects.create(codename='can_access', apiuser=api, resource_type=obj_type, |
|
105 |
resource_pk=arcgis.pk) |
|
106 |
resp = app.get(endpoint, params=params, status=200) |
|
107 |
assert requests_get.call_count == 1 |
|
108 |
assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/fold/serv/MapServer/1/query' |
|
109 |
args = requests_get.call_args[1]['params'] |
|
110 |
assert args['f'] == 'json' |
|
111 |
assert args['outFields'] == '*' |
|
112 |
assert args['where'] == '1=1' |
|
113 |
assert 'data' in resp.json |
|
114 |
assert resp.json['err'] == 0 |
|
115 |
assert len(resp.json['data']) == 2 |
|
116 |
assert resp.json['data'][0]['id'] == '40' |
|
117 |
assert resp.json['data'][0]['text'] == 'Texas' |
|
118 |
assert 'geometry' not in resp.json['data'][0] |
|
119 |
assert 'metadata' not in resp.json |
|
120 | ||
121 |
params['full'] = 'on' |
|
122 |
resp = app.get(endpoint, params=params, status=200) |
|
123 |
assert requests_get.call_count == 2 |
|
124 |
assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/fold/serv/MapServer/1/query' |
|
125 |
args = requests_get.call_args[1]['params'] |
|
126 |
assert args['f'] == 'json' |
|
127 |
assert args['outFields'] == '*' |
|
128 |
assert args['where'] == '1=1' |
|
129 |
assert 'data' in resp.json |
|
130 |
assert resp.json['err'] == 0 |
|
131 |
assert len(resp.json['data']) == 2 |
|
132 |
assert resp.json['data'][0]['id'] == '40' |
|
133 |
assert resp.json['data'][0]['text'] == 'Texas' |
|
134 |
assert resp.json['data'][0]['geometry'] |
|
135 |
assert resp.json['metadata'] |
|
136 | ||
137 |
params['q'] = 'Texas' |
|
138 |
resp = app.get(endpoint, params=params, status=200) |
|
139 |
assert requests_get.call_count == 3 |
|
140 |
assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/fold/serv/MapServer/1/query' |
|
141 |
args = requests_get.call_args[1]['params'] |
|
142 |
assert args['text'] == 'Texas' |
|
143 |
assert 'where' not in args |
|
144 | ||
145 |
params['lat'] = '9.87654' |
|
146 |
params['lon'] = '1.12345' |
|
147 |
resp = app.get(endpoint, params=params, status=200) |
|
148 |
assert requests_get.call_count == 4 |
|
149 |
assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/fold/serv/MapServer/1/query' |
|
150 |
args = requests_get.call_args[1]['params'] |
|
151 |
assert args['geometry'] == '1.12345,9.87654' |
|
152 |
assert args['geometryType'] == 'esriGeometryPoint' |
|
153 | ||
154 |
del params['lat'] # missing lat, do not search by geometry |
|
155 |
resp = app.get(endpoint, params=params, status=200) |
|
156 |
assert requests_get.call_count == 5 |
|
157 |
assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/fold/serv/MapServer/1/query' |
|
158 |
args = requests_get.call_args[1]['params'] |
|
159 |
assert 'geometry' not in args |
|
160 |
assert 'geometryType' not in args |
|
161 | ||
162 |
params.update({'latmin': '1', 'lonmin': '2', 'latmax': '3', 'lonmax': '4'}) |
|
163 |
resp = app.get(endpoint, params=params, status=200) |
|
164 |
assert requests_get.call_count == 6 |
|
165 |
assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/fold/serv/MapServer/1/query' |
|
166 |
args = requests_get.call_args[1]['params'] |
|
167 |
assert args['geometry'] == '2.0,1.0,4.0,3.0' |
|
168 |
assert args['geometryType'] == 'esriGeometryEnvelope' |
|
169 | ||
170 |
del params['latmin'] # incomplete box, do not search by geometry |
|
171 |
resp = app.get(endpoint, params=params, status=200) |
|
172 |
assert requests_get.call_count == 7 |
|
173 |
assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/fold/serv/MapServer/1/query' |
|
174 |
args = requests_get.call_args[1]['params'] |
|
175 |
assert 'geometry' not in args |
|
176 |
assert 'geometryType' not in args |
|
177 | ||
178 |
# others params are directly sent to ArcGIS |
|
179 |
params['spatialRel'] = 'esriSpatialRelContains' |
|
180 |
params.update({'latmin': '1', 'lonmin': '2', 'latmax': '3', 'lonmax': '4'}) |
|
181 |
resp = app.get(endpoint, params=params, status=200) |
|
182 |
assert requests_get.call_count == 8 |
|
183 |
assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/fold/serv/MapServer/1/query' |
|
184 |
args = requests_get.call_args[1]['params'] |
|
185 |
assert args['geometry'] == '2.0,1.0,4.0,3.0' |
|
186 |
assert args['geometryType'] == 'esriGeometryEnvelope' |
|
187 |
assert args['spatialRel'] == 'esriSpatialRelContains' |
|
188 | ||
189 |
# folder |
|
190 |
params['folder'] = 'foo/bar' |
|
191 |
resp = app.get(endpoint, params=params, status=200) |
|
192 |
assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/foo/bar/serv/MapServer/1/query' |
|
193 |
del params['folder'] |
|
194 |
resp = app.get(endpoint, params=params, status=200) |
|
195 |
assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/serv/MapServer/1/query' |
|
196 | ||
197 |
# minimal call |
|
198 |
resp = app.get(endpoint, params={'service': 'srv'}, status=200) |
|
199 |
assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/srv/MapServer/0/query' |
|
200 |
args = requests_get.call_args[1]['params'] |
|
201 |
assert args == {'f': 'json', 'inSR': '4326', 'outSR': '4326', |
|
202 |
'outFields': '*', 'where': '1=1'} |
|
203 | ||
204 |
# distance |
|
205 |
resp = app.get(endpoint, params={'service': 'srv', 'distance': '100'}, status=200) |
|
206 |
assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/srv/MapServer/0/query' |
|
207 |
args = requests_get.call_args[1]['params'] |
|
208 |
assert args['distance'] == '100' |
|
209 |
assert args['units'] == 'esriSRUnit_Meter' # default unit |
|
210 |
resp = app.get(endpoint, params={'service': 'srv', 'distance': '5', 'units': |
|
211 |
'esriSRUnit_NauticalMile'}, status=200) |
|
212 |
assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/srv/MapServer/0/query' |
|
213 |
args = requests_get.call_args[1]['params'] |
|
214 |
assert args['distance'] == '5' |
|
215 |
assert args['units'] == 'esriSRUnit_NauticalMile' |
|
216 | ||
217 |
# call errors |
|
218 |
with mock.patch('passerelle.utils.Request.get') as requests_get: |
|
219 |
requests_get.return_value = utils.FakedResponse(content=STATES, |
|
220 |
status_code=200) |
|
221 |
resp = app.get(endpoint, params={}, status=400) |
|
222 |
assert requests_get.call_count == 0 |
|
223 |
assert resp.json['err'] == 1 |
|
224 |
assert resp.json['err_class'] == 'passerelle.views.WrongParameter' |
|
225 |
assert resp.json['err_desc'] == "missing parameters: 'service'." |
|
226 | ||
227 |
resp = app.get(endpoint, params={'service': 'src', 'lat': '0', 'lon': 'y'}, status=400) |
|
228 |
assert requests_get.call_count == 0 |
|
229 |
assert resp.json['err'] == 1 |
|
230 |
assert resp.json['err_class'] == 'passerelle.utils.jsonresponse.APIError' |
|
231 |
assert resp.json['err_desc'] == '<lon> and <lat> must be floats' |
|
232 | ||
233 |
resp = app.get(endpoint, params={'service': 'src', 'latmin': '0', 'lonmin': 'y', |
|
234 |
'latmax': '0', 'lonmax': '1'}, status=400) |
|
235 |
assert requests_get.call_count == 0 |
|
236 |
assert resp.json['err'] == 1 |
|
237 |
assert resp.json['err_class'] == 'passerelle.utils.jsonresponse.APIError' |
|
238 |
assert resp.json['err_desc'] == '<lonmin> <latmin> <lonmax> and <latmax> must be floats' |
tests/test_arcgis_nancy.py | ||
---|---|---|
1 |
import os |
|
2 |
import json |
|
3 | ||
4 |
import pytest |
|
5 |
import mock |
|
6 | ||
7 | ||
8 |
from django.core.urlresolvers import reverse |
|
9 |
from django.contrib.contenttypes.models import ContentType |
|
10 | ||
11 |
from passerelle.base.models import ApiUser, AccessRight |
|
12 |
from passerelle.contrib.arcgis.models import ArcGIS |
|
13 | ||
14 | ||
15 |
TEST_BASE_DIR = os.path.join(os.path.dirname(__file__), 'data', 'nancy_arcgis') |
|
16 | ||
17 | ||
18 |
def get_file_content(filename): |
|
19 |
with open(os.path.join(TEST_BASE_DIR, filename), 'rb') as fd: |
|
20 |
return fd.read() |
|
21 | ||
22 | ||
23 |
class MockedRequestsResponse(mock.Mock): |
|
24 | ||
25 |
def json(self): |
|
26 |
return json.loads(self.content) |
|
27 | ||
28 | ||
29 |
@pytest.fixture |
|
30 |
def setup(db): |
|
31 |
api = ApiUser.objects.create(username='all', |
|
32 |
keytype='', key='') |
|
33 |
arcgis = ArcGIS.objects.create(base_url='https://example.net/layer/0', slug='test') |
|
34 |
obj_type = ContentType.objects.get_for_model(arcgis) |
|
35 |
AccessRight.objects.create(codename='can_access', apiuser=api, |
|
36 |
resource_type=obj_type, resource_pk=arcgis.pk) |
|
37 |
return arcgis |
|
38 | ||
39 | ||
40 |
@pytest.fixture |
|
41 |
def url(): |
|
42 |
return reverse('generic-endpoint', kwargs={ |
|
43 |
'connector': 'arcgis', 'slug': 'test', 'endpoint': 'district'}) |
|
44 | ||
45 | ||
46 |
def test_get_district_parameters_error(app, setup, url): |
|
47 |
resp = app.get(url, params={'lon': 'lon', 'lat': 'lat'}, status=400) |
|
48 |
assert resp.json['err_desc'] == '<lon> and <lat> must be floats' |
|
49 | ||
50 | ||
51 |
@mock.patch('passerelle.utils.Request.get') |
|
52 |
def test_get_district(mocked_get, app, setup, url): |
|
53 |
mocked_get.return_value = MockedRequestsResponse( |
|
54 |
content=get_file_content('sigresponse.json'), |
|
55 |
status_code=200) |
|
56 | ||
57 |
resp = app.get(url, params={'lon': 6.172122, 'lat': 48.673836}, status=200) |
|
58 |
data = resp.json['data'] |
|
59 |
assert data['id'] == 4 |
|
60 |
assert data['text'] == 'HAUSSONVILLE / BLANDAN / MON DESERT / SAURUPT' |
|
61 | ||
62 | ||
63 |
@mock.patch('passerelle.utils.Request.get') |
|
64 |
def test_get_all_district(mocked_get, app, setup, url): |
|
65 |
mocked_get.return_value = MockedRequestsResponse( |
|
66 |
content=get_file_content('all_districts.json'), |
|
67 |
status_code=200) |
|
68 | ||
69 |
resp = app.get(url, status=200) |
|
70 |
data = resp.json['data'] |
|
71 |
assert len(data) == 7 |
|
72 | ||
73 |
@mock.patch('passerelle.utils.Request.get') |
|
74 |
def test_no_district(mocked_get, app, setup, url): |
|
75 |
mocked_get.return_value = MockedRequestsResponse( |
|
76 |
content='{"features": []}', |
|
77 |
status_code=200) |
|
78 | ||
79 |
resp = app.get(url, status=200) |
|
80 |
assert resp.json['err'] == 1 |
|
81 |
assert resp.json['err_class'] == 'passerelle.utils.jsonresponse.APIError' |
|
82 |
assert resp.json['err_desc'] == 'No features found.' |
tests/test_generic_endpoint.py | ||
---|---|---|
28 | 28 | |
29 | 29 |
from passerelle.base.models import BaseResource, ResourceLog, ProxyLogger |
30 | 30 |
from passerelle.contrib.mdel.models import MDEL |
31 |
from passerelle.contrib.arcgis.models import Arcgis
|
|
31 |
from passerelle.contrib.arcgis.models import ArcGIS
|
|
32 | 32 |
from passerelle.contrib.stub_invoices.models import StubInvoicesConnector |
33 | 33 |
from passerelle.utils.api import endpoint |
34 | 34 | |
... | ... | |
40 | 40 | |
41 | 41 |
@pytest.fixture |
42 | 42 |
def arcgis(db): |
43 |
return utils.setup_access_rights(Arcgis.objects.create(slug='test', log_level='DEBUG'))
|
|
43 |
return utils.setup_access_rights(ArcGIS.objects.create(slug='test', log_level='DEBUG'))
|
|
44 | 44 | |
45 | 45 | |
46 | 46 |
DEMAND_STATUS = { |
47 |
- |