0001-opendatasoft-add-facet-filters-50212.patch
passerelle/apps/opendatasoft/forms.py | ||
---|---|---|
10 | 10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | 11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | 12 |
# GNU Affero General Public License for more details. |
13 | 13 |
# |
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 | 17 |
from django import forms |
18 |
from django.utils.six.moves.urllib import parse as urlparse |
|
18 | 19 | |
19 | 20 |
from passerelle.base.forms import BaseQueryFormMixin |
20 | 21 |
from . import models |
21 | 22 | |
22 | 23 | |
23 | 24 |
class QueryForm(BaseQueryFormMixin, forms.ModelForm): |
24 | 25 |
class Meta: |
25 | 26 |
model = models.Query |
passerelle/apps/opendatasoft/migrations/0002_auto_20210409_1143.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.18 on 2021-04-09 09:43 |
|
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 |
('opendatasoft', '0001_initial'), |
|
12 |
] |
|
13 | ||
14 |
operations = [ |
|
15 |
migrations.AddField( |
|
16 |
model_name='query', |
|
17 |
name='filter_expression', |
|
18 |
field=models.TextField( |
|
19 |
blank=True, |
|
20 |
help_text='Specify refine and exclude facet expressions separated by &', |
|
21 |
verbose_name='filter', |
|
22 |
), |
|
23 |
), |
|
24 |
migrations.AlterField( |
|
25 |
model_name='opendatasoft', |
|
26 |
name='service_url', |
|
27 |
field=models.CharField( |
|
28 |
help_text='URL without ending "api/records/1.0/search/"', |
|
29 |
max_length=256, |
|
30 |
verbose_name='Site URL', |
|
31 |
), |
|
32 |
), |
|
33 |
] |
passerelle/apps/opendatasoft/models.py | ||
---|---|---|
88 | 88 |
params = { |
89 | 89 |
'dataset': dataset, |
90 | 90 |
'q': query, |
91 | 91 |
} |
92 | 92 |
if self.api_key: |
93 | 93 |
params.update({'apikey': self.api_key}) |
94 | 94 |
if limit: |
95 | 95 |
params.update({'rows': limit}) |
96 |
params.update(kwargs) # filter expressions |
|
96 | 97 | |
97 | 98 |
result_response = self.requests.get(url, params=params) |
98 | 99 |
err_desc = result_response.json().get('error') |
99 | 100 |
if err_desc: |
100 | 101 |
return {'err': 1, 'err_desc': err_desc} |
101 | 102 | |
102 | 103 |
result = [] |
103 | 104 |
for record in result_response.json().get('records'): |
... | ... | |
137 | 138 |
help_text=_('dataset to query'), |
138 | 139 |
) |
139 | 140 |
text_template = models.TextField( |
140 | 141 |
verbose_name=_('Text template'), |
141 | 142 |
help_text=_("Use Django's template syntax. Attributes can be accessed through {{ attributes.name }}"), |
142 | 143 |
validators=[validate_template], |
143 | 144 |
blank=True, |
144 | 145 |
) |
146 |
filter_expression = models.TextField( |
|
147 |
verbose_name=_('filter'), |
|
148 |
help_text=_('Specify refine and exclude facet expressions separated by &'), |
|
149 |
blank=True, |
|
150 |
) |
|
145 | 151 | |
146 | 152 |
delete_view = 'opendatasoft-query-delete' |
147 | 153 |
edit_view = 'opendatasoft-query-edit' |
148 | 154 | |
149 | 155 |
def q(self, request, **kwargs): |
156 |
filters = urlparse.parse_qs(self.filter_expression) |
|
157 |
kwargs.update(filters) |
|
150 | 158 |
return self.resource.search(request, dataset=self.dataset, text_template=self.text_template, **kwargs) |
151 | 159 | |
152 | 160 |
def as_endpoint(self): |
153 | 161 |
endpoint = super(Query, self).as_endpoint(path=self.resource.q.endpoint_info.name) |
154 | 162 | |
155 | 163 |
search_endpoint = self.resource.search.endpoint_info |
156 | 164 |
endpoint.func = search_endpoint.func |
157 | 165 |
endpoint.show_undocumented_params = False |
tests/test_opendatasoft.py | ||
---|---|---|
140 | 140 |
def query(connector): |
141 | 141 |
return Query.objects.create( |
142 | 142 |
resource=connector, |
143 | 143 |
name='Référenciel adresses de test', |
144 | 144 |
slug='my_query', |
145 | 145 |
description='Rechercher une adresse', |
146 | 146 |
dataset='referentiel-adresse-test', |
147 | 147 |
text_template='{{numero}} {{nom_rue|safe}} {{nom_commun}}', |
148 |
filter_expression='refine.source=Ville et Eurométropole de Strasbourg&exclude.numero=42&exclude.numero=43', |
|
148 | 149 |
) |
149 | 150 | |
150 | 151 | |
151 | 152 |
def test_views(db, admin_user, app, connector): |
152 | 153 |
app = login(app) |
153 | 154 |
resp = app.get('/opendatasoft/my_connector/', status=200) |
154 | 155 |
resp = resp.click('New Query') |
155 | 156 |
resp.form['name'] = 'my query' |
... | ... | |
187 | 188 | |
188 | 189 |
@mock.patch('passerelle.utils.Request.get') |
189 | 190 |
def test_search_using_q(mocked_get, app, connector): |
190 | 191 |
endpoint = utils.generic_endpoint_url('opendatasoft', 'search', slug=connector.slug) |
191 | 192 |
assert endpoint == '/opendatasoft/my_connector/search' |
192 | 193 |
params = { |
193 | 194 |
'dataset': 'referentiel-adresse-test', |
194 | 195 |
'text_template': '{{numero}} {{nom_rue|safe}} {{nom_commun}}', |
196 |
'refine.source': 'Ville et Eurométropole de Strasbourg', |
|
197 |
'exclude.numero': ['42', '43'], |
|
195 | 198 |
'q': "rue de l'aubepine", |
196 | 199 |
'rows': 3, |
197 | 200 |
} |
198 | 201 |
mocked_get.return_value = utils.FakedResponse(content=FAKED_CONTENT_Q_SEARCH, status_code=200) |
199 | 202 |
resp = app.get(endpoint, params=params, status=200) |
200 | 203 |
assert not resp.json['err'] |
201 | 204 |
assert len(resp.json['data']) == 3 |
202 | 205 |
# check order is kept |
... | ... | |
217 | 220 | |
218 | 221 |
@mock.patch('passerelle.utils.Request.get') |
219 | 222 |
def test_search_using_id(mocked_get, app, connector): |
220 | 223 |
endpoint = utils.generic_endpoint_url('opendatasoft', 'search', slug=connector.slug) |
221 | 224 |
assert endpoint == '/opendatasoft/my_connector/search' |
222 | 225 |
params = { |
223 | 226 |
'dataset': 'referentiel-adresse-test', |
224 | 227 |
'text_template': '{{numero}} {{nom_rue|safe}} {{nom_commun}}', |
228 |
'refine.source': 'Ville et Eurométropole de Strasbourg', |
|
229 |
'exclude.numero': ['42', '43'], |
|
225 | 230 |
'id': '7cafcd5c692773e8b863587b2d38d6be82e023d8', |
226 | 231 |
} |
227 | 232 |
mocked_get.return_value = utils.FakedResponse(content=FAKED_CONTENT_ID_SEARCH, status_code=200) |
228 | 233 |
resp = app.get(endpoint, params=params, status=200) |
229 | 234 |
assert len(resp.json['data']) == 1 |
230 | 235 |
assert resp.json['data'][0]['text'] == "19 RUE DE L'AUBEPINE Lipsheim" |
231 | 236 | |
232 | 237 | |
... | ... | |
234 | 239 |
def test_query_q_using_q(mocked_get, app, query): |
235 | 240 |
endpoint = '/opendatasoft/my_connector/q/my_query/' |
236 | 241 |
params = { |
237 | 242 |
'q': "rue de l'aubepine", |
238 | 243 |
'rows': 3, |
239 | 244 |
} |
240 | 245 |
mocked_get.return_value = utils.FakedResponse(content=FAKED_CONTENT_Q_SEARCH, status_code=200) |
241 | 246 |
resp = app.get(endpoint, params=params, status=200) |
247 |
assert mocked_get.call_args[1]['params'] == { |
|
248 |
'dataset': 'referentiel-adresse-test', |
|
249 |
'q': "rue de l'aubepine", |
|
250 |
'apikey': 'my_secret', |
|
251 |
'rows': '3', |
|
252 |
'refine.source': ['Ville et Eurométropole de Strasbourg'], |
|
253 |
'exclude.numero': ['42', '43'], |
|
254 |
} |
|
242 | 255 |
assert not resp.json['err'] |
243 | 256 |
assert len(resp.json['data']) == 3 |
244 | 257 |
# check order is kept |
245 | 258 |
assert [x['id'] for x in resp.json['data']] == [ |
246 | 259 |
'e00cf6161e52a4c8fe510b2b74d4952036cb3473', |
247 | 260 |
'7cafcd5c692773e8b863587b2d38d6be82e023d8', |
248 | 261 |
'0984a5e1745701f71c91af73ce764e1f7132e0ff', |
249 | 262 |
] |
... | ... | |
260 | 273 |
@mock.patch('passerelle.utils.Request.get') |
261 | 274 |
def test_query_q_using_id(mocked_get, app, query): |
262 | 275 |
endpoint = '/opendatasoft/my_connector/q/my_query/' |
263 | 276 |
params = { |
264 | 277 |
'id': '7cafcd5c692773e8b863587b2d38d6be82e023d8', |
265 | 278 |
} |
266 | 279 |
mocked_get.return_value = utils.FakedResponse(content=FAKED_CONTENT_ID_SEARCH, status_code=200) |
267 | 280 |
resp = app.get(endpoint, params=params, status=200) |
281 |
assert mocked_get.call_args[1]['params'] == { |
|
282 |
'dataset': 'referentiel-adresse-test', |
|
283 |
'q': 'recordid:7cafcd5c692773e8b863587b2d38d6be82e023d8', |
|
284 |
'apikey': 'my_secret', |
|
285 |
'refine.source': ['Ville et Eurométropole de Strasbourg'], |
|
286 |
'exclude.numero': ['42', '43'], |
|
287 |
} |
|
268 | 288 |
assert len(resp.json['data']) == 1 |
269 | 289 |
assert resp.json['data'][0]['text'] == "19 RUE DE L'AUBEPINE Lipsheim" |
270 | 290 | |
271 | 291 | |
272 | 292 |
def test_opendatasoft_query_unicity(admin_user, app, connector, query): |
273 | 293 |
connector2 = OpenDataSoft.objects.create( |
274 | 294 |
slug='my_connector2', |
275 | 295 |
api_key='my_secret', |
276 |
- |