Projet

Général

Profil

0001-api-add-endpoint-for-geojson-data-sources-47066.patch

Frédéric Péters, 24 novembre 2020 16:19

Télécharger (11 ko)

Voir les différences:

Subject: [PATCH 1/3] api: add endpoint for geojson data sources (#47066)

 tests/test_datasource.py | 12 +++---
 wcs/api.py               | 14 +++++-
 wcs/data_sources.py      | 93 ++++++++++++++++++++++++++++++----------
 3 files changed, 90 insertions(+), 29 deletions(-)
tests/test_datasource.py
588 588
def test_geojson_datasource_bad_url(http_requests, caplog):
589 589
    datasource = {'type': 'geojson', 'value': 'http://remote.example.net/404'}
590 590
    assert data_sources.get_items(datasource) == []
591
    assert 'Error loading GeoJSON data source' in caplog.records[-1].message
591
    assert 'Error loading JSON data source' in caplog.records[-1].message
592 592
    assert 'status: 404' in caplog.records[-1].message
593 593

  
594 594
    datasource = {'type': 'geojson', 'value': 'http://remote.example.net/xml'}
595 595
    assert data_sources.get_items(datasource) == []
596
    assert 'Error reading GeoJSON data source output' in caplog.records[-1].message
596
    assert 'Error reading JSON data source output' in caplog.records[-1].message
597 597
    assert 'Expecting value:' in caplog.records[-1].message
598 598

  
599 599
    datasource = {'type': 'geojson', 'value': 'http://remote.example.net/connection-error'}
600 600
    assert data_sources.get_items(datasource) == []
601
    assert 'Error loading GeoJSON data source' in caplog.records[-1].message
601
    assert 'Error loading JSON data source' in caplog.records[-1].message
602 602
    assert 'error' in caplog.records[-1].message
603 603

  
604 604
    datasource = {'type': 'geojson', 'value': 'http://remote.example.net/json-list-err1'}
605 605
    assert data_sources.get_items(datasource) == []
606
    assert 'Error reading GeoJSON data source output (err 1)' in caplog.records[-1].message
606
    assert 'Error reading JSON data source output (err 1)' in caplog.records[-1].message
607 607

  
608 608

  
609 609
def test_geojson_datasource_bad_url_scheme(caplog):
......
613 613

  
614 614
    datasource = {'type': 'geojson', 'value': 'foo://bar'}
615 615
    assert data_sources.get_items(datasource) == []
616
    assert 'Error loading GeoJSON data source' in caplog.records[-1].message
616
    assert 'Error loading JSON data source' in caplog.records[-1].message
617 617
    assert 'invalid scheme in URL' in caplog.records[-1].message
618 618

  
619 619
    datasource = {'type': 'geojson', 'value': '/bla/blo'}
620 620
    assert data_sources.get_items(datasource) == []
621
    assert 'Error loading GeoJSON data source' in caplog.records[-1].message
621
    assert 'Error loading JSON data source' in caplog.records[-1].message
622 622
    assert 'invalid scheme in URL' in caplog.records[-1].message
623 623

  
624 624

  
wcs/api.py
36 36
from wcs.conditions import Condition, ValidationError
37 37
from wcs.carddef import CardDef
38 38
from wcs.formdef import FormDef
39
from wcs.data_sources import get_object as get_data_source_object
39 40
from wcs.roles import Role, logged_users_role
40 41
from wcs.forms.common import FormStatusPage
41 42
import wcs.qommon.storage as st
......
912 913
        return json.dumps({'data': [{'id': x['id'], 'text': x['text']} for x in values]})
913 914

  
914 915

  
916
class GeoJsonDirectory(Directory):
917
    def _q_lookup(self, component):
918
        try:
919
            data_source = get_data_source_object({'type': component}, ignore_errors=False)
920
        except KeyError:
921
            raise TraversalError()
922
        get_response().set_content_type('application/json')
923
        return json.dumps(data_source.get_geojson_data())
924

  
925

  
915 926
class ApiDirectory(Directory):
916 927
    _q_exports = ['forms', 'roles', ('reverse-geocoding', 'reverse_geocoding'),
917 928
            'formdefs', 'categories', 'user', 'users', 'code', 'autocomplete',
918
            'cards']
929
            'cards', 'geojson']
919 930

  
920 931
    cards = ApiCardsDirectory()
921 932
    forms = ApiFormsDirectory()
......
925 936
    users = ApiUsersDirectory()
926 937
    code = ApiTrackingCodeDirectory()
927 938
    autocomplete = AutocompleteDirectory()
939
    geojson = GeoJsonDirectory()
928 940

  
929 941
    def roles(self):
930 942
        get_response().set_content_type('application/json')
wcs/data_sources.py
126 126
    return tupled_items
127 127

  
128 128

  
129
def request_json_items(url, data_source):
129
def get_json_from_url(url, data_source):
130 130
    url = sign_url_auto_orig(url)
131 131
    data_key = data_source.get('data_attribute') or 'data'
132
    geojson = data_source.get('type') == 'geojson'
132 133
    try:
133 134
        entries = misc.json_loads(misc.urlopen(url).read())
134 135
        if not isinstance(entries, dict):
135 136
            raise ValueError('not a json dict')
136 137
        if entries.get('err') not in (None, 0, "0"):
137 138
            raise ValueError('err %s' % entries['err'])
138
        if not isinstance(entries.get(data_key), list):
139
            raise ValueError('not a json dict with a %s list attribute' % data_key)
139
        if geojson:
140
            if not isinstance(entries.get('features'), list):
141
                raise ValueError('bad geojson format')
142
        else:
143
            if not isinstance(entries.get(data_key), list):
144
                raise ValueError('not a json dict with a %s list attribute' % data_key)
140 145
    except misc.ConnectionError as e:
141 146
        get_logger().warning('Error loading JSON data source (%s)' % str(e))
142 147
        return None
143 148
    except (ValueError, TypeError) as e:
144 149
        get_logger().warning('Error reading JSON data source output (%s)' % str(e))
145 150
        return None
146
    items = []
151
    return entries
152

  
153

  
154
def request_json_items(url, data_source):
155
    entries = get_json_from_url(url, data_source)
156
    if entries is None:
157
        return None
158
    data_key = data_source.get('data_attribute') or 'data'
147 159
    id_attribute = data_source.get('id_attribute') or 'id'
148 160
    text_attribute = data_source.get('text_attribute') or 'text'
161
    items = []
149 162
    for item in entries.get(data_key):
150 163
        # skip malformed items
151 164
        if not isinstance(item, dict):
......
162 175

  
163 176

  
164 177
def request_geojson_items(url, data_source):
165
    url = sign_url_auto_orig(url)
166
    try:
167
        entries = misc.json_loads(misc.urlopen(url).read())
168
        if not isinstance(entries, dict):
169
            raise ValueError('not a json dict')
170
        if entries.get('err') not in (None, 0, "0"):
171
            raise ValueError('err %s' % entries['err'])
172
        if not isinstance(entries.get('features'), list):
173
            raise ValueError('bad geojson format')
174
    except misc.ConnectionError as e:
175
        get_logger().warning('Error loading GeoJSON data source (%s)' % str(e))
176
        return None
177
    except (ValueError, TypeError) as e:
178
        get_logger().warning('Error reading GeoJSON data source output (%s)' % str(e))
178
    entries = get_json_from_url(url, data_source)
179
    if entries is None:
179 180
        return None
180 181
    items = []
181 182
    id_property = data_source.get('id_property') or 'id'
......
306 307
    return NamedDataSource.get_by_slug(ds_type).data_source
307 308

  
308 309

  
309
def get_object(data_source):
310
def get_object(data_source, ignore_errors=True):
310 311
    if not data_source:
311 312
        return None
312 313
    ds_type = data_source.get('type')
......
320 321
        named_data_source = NamedDataSource()
321 322
        named_data_source.data_source = data_source
322 323
        return named_data_source
323
    return NamedDataSource.get_by_slug(ds_type)
324
    return NamedDataSource.get_by_slug(ds_type, ignore_errors=ignore_errors)
324 325

  
325 326

  
326 327
class NamedDataSource(XmlStorableObject):
......
444 445
            }
445 446

  
446 447
    @classmethod
447
    def get_by_slug(cls, slug):
448
    def get_by_slug(cls, slug, ignore_errors=True):
448 449
        objects = [x for x in cls.select() if x.slug == slug]
449 450
        if objects:
450 451
            return objects[0]
452
        if not ignore_errors:
453
            raise KeyError(slug)
451 454
        get_logger().warning("data source '%s' does not exist" % slug)
452 455
        return StubNamedDataSource(name=slug)
453 456

  
......
499 502
                    }))
500 503
        return None
501 504

  
505
    def get_geojson_url(self):
506
        assert self.type == 'geojson'
507
        return '/api/geojson/%s' % self.slug
508

  
509
    def get_geojson_data(self):
510
        url = self.data_source.get('value').strip()
511
        if Template.is_template_string(url):
512
            vars = get_publisher().substitutions.get_context_variables(mode='lazy')
513
            url = get_variadic_url(url, vars)
514

  
515
        request = get_request()
516
        if hasattr(request, 'datasources_cache') and url in request.datasources_cache:
517
            return request.datasources_cache[url]
518

  
519
        cache_duration = 0
520
        if self.cache_duration:
521
            cache_duration = int(self.cache_duration)
522

  
523
        if cache_duration:
524
            cache_key = 'geojson-data-source-%s' % force_str(hashlib.md5(force_bytes(url)).hexdigest())
525
            from django.core.cache import cache
526
            data = cache.get(cache_key)
527
            if data is not None:
528
                return data
529

  
530
        data = get_json_from_url(url, self.data_source)
531
        id_property = self.id_property or 'id'
532
        label_template_property = self.label_template_property or '{{ text }}'
533

  
534
        for feature in data['features']:
535
            feature['properties']['_id'] = feature['properties'][id_property]
536
            try:
537
                feature['properties']['_text'] = Template(
538
                        label_template_property).render(feature['properties'])
539
            except (TemplateSyntaxError, VariableDoesNotExist):
540
                pass
541
            if not feature['properties'].get('_text'):
542
                feature['properties']['_text'] = feature['properties']['_id']
543

  
544
        if hasattr(request, 'datasources_cache'):
545
            request.datasources_cache[url] = data
546
        if cache_duration:
547
            cache.set(cache_key, data, cache_duration)
548

  
549
        return data
550

  
502 551
    def get_value_by_id(self, param_name, param_value):
503 552
        url = self.data_source.get('value').strip()
504 553
        if Template.is_template_string(url):
......
562 611
        elif self.type == 'json' and self.id_parameter:
563 612
            value = self.get_value_by_id(self.id_parameter, option_id)
564 613
        else:
565
            structured_items = get_structured_items(self.data_source, mode='lazy')
614
            structured_items = get_structured_items(self.extended_data_source, mode='lazy')
566 615
            for item in structured_items:
567 616
                if str(item['id']) == str(option_id):
568 617
                    value = item
569
-