0001-opengis-add-text-search-for-queries-40743.patch
passerelle/apps/opengis/migrations/0009_auto_20200407_1544.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.18 on 2020-04-07 13:44 |
|
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 |
('opengis', '0008_featurecache'), |
|
12 |
] |
|
13 | ||
14 |
operations = [ |
|
15 |
migrations.AddField( |
|
16 |
model_name='featurecache', |
|
17 |
name='text', |
|
18 |
field=models.CharField(default='', max_length=2048), |
|
19 |
preserve_default=False, |
|
20 |
), |
|
21 |
migrations.AddField( |
|
22 |
model_name='query', |
|
23 |
name='index_properties', |
|
24 |
field=models.CharField(blank=True, help_text='Comma separated list such as property1,property2', max_length=1024, verbose_name='Properties for searching'), |
|
25 |
), |
|
26 |
] |
passerelle/apps/opengis/models.py | ||
---|---|---|
34 | 34 | |
35 | 35 |
from passerelle.base.models import BaseResource, BaseQuery |
36 | 36 |
from passerelle.utils.api import endpoint |
37 |
from passerelle.utils.conversion import num2deg |
|
37 |
from passerelle.utils.conversion import num2deg, simplify
|
|
38 | 38 |
from passerelle.utils.jsonresponse import APIError |
39 | 39 | |
40 | 40 | |
... | ... | |
380 | 380 |
'bbox': { |
381 | 381 |
'description': _('Only include results inside bounding box'), |
382 | 382 |
'example_value': '-0.489,51.28,0.236,51.686' |
383 |
}, |
|
384 |
'q': { |
|
385 |
'description': _('Text search for specified properties'), |
|
386 |
'example_value': 'library' |
|
383 | 387 |
} |
384 | 388 |
}, |
385 | 389 |
show=False) |
386 |
def query(self, request, query_slug, bbox=None): |
|
390 |
def query(self, request, query_slug, bbox=None, q=None):
|
|
387 | 391 |
query = get_object_or_404(Query, resource=self, slug=query_slug) |
388 |
return query.q(request, bbox) |
|
392 |
return query.q(request, bbox, q)
|
|
389 | 393 | |
390 | 394 |
def export_json(self): |
391 | 395 |
d = super(OpenGIS, self).export_json() |
... | ... | |
435 | 439 | |
436 | 440 |
typename = models.CharField(_('Feature type'), max_length=256) |
437 | 441 |
filter_expression = models.TextField(_('XML filter'), blank=True) |
442 |
index_properties = models.CharField( |
|
443 |
verbose_name=_('Properties for searching'), |
|
444 |
help_text=_('Comma separated list such as property1,property2'), |
|
445 |
max_length=1024, |
|
446 |
blank=True) |
|
438 | 447 | |
439 | 448 |
delete_view = 'opengis-query-delete' |
440 | 449 |
edit_view = 'opengis-query-edit' |
... | ... | |
448 | 457 |
endpoint.parameters = resource_endpoint.parameters |
449 | 458 |
return endpoint |
450 | 459 | |
451 |
def q(self, request, bbox): |
|
460 |
def q(self, request, bbox, q):
|
|
452 | 461 |
features = self.features.all() |
453 | 462 |
if not features.exists(): |
454 | 463 |
raise APIError('Data is not synchronized yet. Retry in a few minutes.') |
... | ... | |
460 | 469 |
'floating point numbers of the form lonmin,latmin,lonmax,latmax') |
461 | 470 |
features = features.filter(lon__gte=lonmin, lon__lte=lonmax, lat__gte=latmin, |
462 | 471 |
lat__lte=latmax) |
472 |
if q: |
|
473 |
features = features.filter(text__icontains=simplify(q)) |
|
463 | 474 |
data = { |
464 | 475 |
'type': 'FeatureCollection', |
465 | 476 |
'name': self.typename |
... | ... | |
477 | 488 |
except (KeyError, TypeError): |
478 | 489 |
self.resource.logger.warning('invalid coordinates in geometry: %s', geometry) |
479 | 490 |
continue |
480 |
features.append(FeatureCache(query=self, lat=lat, lon=lon, data=feature)) |
|
491 |
text = '' |
|
492 |
if self.index_properties: |
|
493 |
properties = [x.strip() for x in self.index_properties.split(',') if x.strip()] |
|
494 |
values = [val for prop, val in feature.get('properties', {}).items() |
|
495 |
if prop in properties] |
|
496 |
text = simplify(' '.join(values)) |
|
497 |
features.append(FeatureCache(query=self, lat=lat, lon=lon, text=text, data=feature)) |
|
481 | 498 |
with transaction.atomic(): |
482 | 499 |
self.features.all().delete() |
483 | 500 |
FeatureCache.objects.bulk_create(features) |
... | ... | |
495 | 512 |
verbose_name=_('Query')) |
496 | 513 |
lat = models.FloatField() |
497 | 514 |
lon = models.FloatField() |
515 |
text = models.CharField(max_length=2048) |
|
498 | 516 |
data = JSONField() |
tests/test_opengis.py | ||
---|---|---|
641 | 641 |
resp = app.get(connector.get_absolute_url()) |
642 | 642 |
assert query.name in resp.text |
643 | 643 |
assert query.description in resp.text |
644 |
assert '/opengis/test/query/test_query/?bbox=' in resp.text |
|
644 |
assert '/opengis/test/query/test_query/' in resp.text |
|
645 |
assert 'bbox=-0.489,51.28,0.236,51.686' in resp.text |
|
646 |
assert 'q=library' in resp.text |
|
647 | ||
648 | ||
649 |
@mock.patch('passerelle.utils.Request.get') |
|
650 |
def test_opengis_query_text_search(mocked_get, app, connector, query): |
|
651 |
endpoint = utils.generic_endpoint_url('opengis', 'query/test_query/', slug=connector.slug) |
|
652 |
mocked_get.side_effect = geoserver_geolocated_responses |
|
653 |
query.update_cache() |
|
654 | ||
655 |
resp = app.get(endpoint + '?q=grenoble') |
|
656 |
assert len(resp.json['features']) == 0 |
|
657 | ||
658 |
query.index_properties = 'nom_commune,nom_voie' |
|
659 |
query.save() |
|
660 |
query.update_cache() |
|
661 | ||
662 |
resp = app.get(endpoint + '?q=grenoble') |
|
663 |
assert len(resp.json['features']) == 4 |
|
664 | ||
665 |
resp = app.get(endpoint + '?q=victor') |
|
666 |
assert len(resp.json['features']) == 2 |
|
667 | ||
668 |
resp = app.get(endpoint + '?q=nomatch') |
|
669 |
assert len(resp.json['features']) == 0 |
|
645 | 670 | |
646 | 671 | |
647 | 672 |
def test_opengis_export_import(query): |
648 |
- |