Projet

Général

Profil

0001-datasources-json-datasource-attributes-47218.patch

Lauréline Guérin, 09 octobre 2020 15:36

Télécharger (13,9 ko)

Voir les différences:

Subject: [PATCH] datasources: json datasource attributes (#47218)

 tests/test_datasource.py              |  40 +++++++++
 tests/test_datasources_admin_pages.py |  17 ++++
 wcs/admin/data_sources.py             |  33 +++++++
 wcs/data_sources.py                   | 122 ++++++++++++++++----------
 4 files changed, 167 insertions(+), 45 deletions(-)
tests/test_datasource.py
283 283
    assert data_sources.get_items(datasource) == []
284 284
    assert data_sources.get_structured_items(datasource) == []
285 285

  
286
    # specify data_attribute
287
    datasource = {'type': 'json', 'value': ' {{ json_url }}', 'data_attribute': 'results'}
288
    get_request().datasources_cache = {}
289
    json_file = open(json_file_path, 'w')
290
    json.dump({'results': [{'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}]}, json_file)
291
    json_file.close()
292
    assert data_sources.get_structured_items(datasource) == [
293
            {'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}]
294

  
295
    datasource = {'type': 'json', 'value': ' {{ json_url }}', 'data_attribute': 'data'}
296
    get_request().datasources_cache = {}
297
    assert data_sources.get_structured_items(datasource) == []
298

  
299
    # specify id_attribute
300
    datasource = {'type': 'json', 'value': ' {{ json_url }}', 'id_attribute': 'pk'}
301
    get_request().datasources_cache = {}
302
    json_file = open(json_file_path, 'w')
303
    json.dump({'data': [{'pk': '1', 'text': 'foo'}, {'pk': '2', 'text': 'bar'}]}, json_file)
304
    json_file.close()
305
    assert data_sources.get_structured_items(datasource) == [
306
            {'id': '1', 'text': 'foo', 'pk': '1'}, {'id': '2', 'text': 'bar', 'pk': '2'}]
307

  
308
    datasource = {'type': 'json', 'value': ' {{ json_url }}', 'id_attribute': 'id'}
309
    get_request().datasources_cache = {}
310
    assert data_sources.get_structured_items(datasource) == []
311

  
312
    # specify text_attribute
313
    datasource = {'type': 'json', 'value': ' {{ json_url }}', 'text_attribute': 'label'}
314
    get_request().datasources_cache = {}
315
    json_file = open(json_file_path, 'w')
316
    json.dump({'data': [{'id': '1', 'label': 'foo'}, {'id': '2', 'label': 'bar'}]}, json_file)
317
    json_file.close()
318
    assert data_sources.get_structured_items(datasource) == [
319
            {'id': '1', 'text': 'foo', 'label': 'foo'}, {'id': '2', 'text': 'bar', 'label': 'bar'}]
320

  
321
    datasource = {'type': 'json', 'value': ' {{ json_url }}', 'text_attribute': 'text'}
322
    get_request().datasources_cache = {}
323
    assert data_sources.get_structured_items(datasource) == [
324
            {'id': '1', 'text': '1', 'label': 'foo'}, {'id': '2', 'text': '2', 'label': 'bar'}]
325

  
286 326

  
287 327
def test_json_datasource_bad_url(http_requests, caplog):
288 328
    datasource = {'type': 'json', 'value': 'http://remote.example.net/404'}
tests/test_datasources_admin_pages.py
161 161
    assert 'Preview' in resp.text
162 162
    assert 'foo' in resp.text
163 163

  
164
    # with other attributes
165
    json_file = open(json_file_path, 'w')
166
    json.dump({'results': [{'pk': '1', 'label': 'foo'}, {'pk': '2'}]}, json_file)
167
    json_file.close()
168

  
169
    data_source.data_attribute = 'results'
170
    data_source.id_attribute = 'pk'
171
    data_source.text_attribute = 'label'
172
    data_source.store()
173
    with HttpRequestsMocking():
174
        resp = app.get('/backoffice/settings/data-sources/%s/' % data_source.id)
175
    assert 'Preview' in resp.text
176
    assert '<tt>1</tt>: foo</li>' in resp.text
177
    assert '<tt>2</tt>: 2</li>' in resp.text
178
    assert '<p>Additional keys are available: label, pk</p>' in resp.text
179

  
164 180
    # variadic url
181
    data_source.data_attribute = None
165 182
    data_source.data_source = {'type': 'json', 'value': '{{ site_url }}/foo/bar'}
166 183
    data_source.store()
167 184
    with HttpRequestsMocking():
wcs/admin/data_sources.py
104 104
                     'data-dynamic-display-child-of': 'data_source$type',
105 105
                     'data-dynamic-display-value': 'geojson',
106 106
                 })
107
        form.add(StringWidget, 'data_attribute',
108
                 value=self.datasource.data_attribute,
109
                 title=_('Data Attribute'),
110
                 hint=_('Name of the attribute containing the list of results (default: data)'),
111
                 required=False,
112
                 advanced=False,
113
                 attrs={
114
                     'data-dynamic-display-child-of': 'data_source$type',
115
                     'data-dynamic-display-value': 'json',
116
                 })
117
        form.add(StringWidget, 'id_attribute',
118
                 value=self.datasource.id_attribute,
119
                 title=_('Id Attribute'),
120
                 hint=_('Name of the attribute containing the identifier of an entry (default: id)'),
121
                 required=False,
122
                 advanced=False,
123
                 attrs={
124
                     'data-dynamic-display-child-of': 'data_source$type',
125
                     'data-dynamic-display-value-in': 'json|geojson',
126
                 })
127
        form.add(StringWidget, 'text_attribute',
128
                 value=self.datasource.text_attribute,
129
                 title=_('Text Attribute'),
130
                 hint=_('Name of the attribute containing the label of an entry (default: text)'),
131
                 required=False,
132
                 advanced=False,
133
                 attrs={
134
                     'data-dynamic-display-child-of': 'data_source$type',
135
                     'data-dynamic-display-value': 'json',
136
                 })
107 137
        form.add(StringWidget, 'label_template_property',
108 138
                 value=self.datasource.label_template_property,
109 139
                 title=_('Label template'),
......
148 178
        self.datasource.cache_duration = form.get_widget('cache_duration').parse()
149 179
        self.datasource.query_parameter = form.get_widget('query_parameter').parse()
150 180
        self.datasource.id_parameter = form.get_widget('id_parameter').parse()
181
        self.datasource.data_attribute = form.get_widget('data_attribute').parse()
182
        self.datasource.id_attribute = form.get_widget('id_attribute').parse()
183
        self.datasource.text_attribute = form.get_widget('text_attribute').parse()
151 184
        self.datasource.id_property = form.get_widget('id_property').parse()
152 185
        self.datasource.label_template_property = form.get_widget('label_template_property').parse()
153 186
        if slug_widget:
wcs/data_sources.py
118 118

  
119 119
def request_json_items(url, data_source):
120 120
    url = sign_url_auto_orig(url)
121
    geojson = data_source.get('type') == 'geojson'
121
    data_key = data_source.get('data_attribute') or 'data'
122 122
    try:
123 123
        entries = misc.json_loads(misc.urlopen(url).read())
124 124
        if not isinstance(entries, dict):
125 125
            raise ValueError('not a json dict')
126 126
        if entries.get('err') not in (None, 0, "0"):
127 127
            raise ValueError('err %s' % entries['err'])
128
        if not geojson and not isinstance(entries.get('data'), list):
129
            raise ValueError('not a json dict with a data list attribute')
130
        if geojson and not isinstance(entries.get('features'), list):
131
            raise ValueError('bad geojson format')
128
        if not isinstance(entries.get(data_key), list):
129
            raise ValueError('not a json dict with a %s list attribute' % data_key)
132 130
    except misc.ConnectionError as e:
133
        if geojson:
134
            get_logger().warning('Error loading GeoJSON data source (%s)' % str(e))
135
        else:
136
            get_logger().warning('Error loading JSON data source (%s)' % str(e))
131
        get_logger().warning('Error loading JSON data source (%s)' % str(e))
137 132
        return None
138 133
    except (ValueError, TypeError) as e:
139
        if geojson:
140
            get_logger().warning('Error reading GeoJSON data source output (%s)' % str(e))
134
        get_logger().warning('Error reading JSON data source output (%s)' % str(e))
135
        return None
136
    items = []
137
    id_attribute = data_source.get('id_attribute') or 'id'
138
    text_attribute = data_source.get('text_attribute') or 'text'
139
    for item in entries.get(data_key):
140
        # skip malformed items
141
        if not isinstance(item, dict):
142
            continue
143
        if item.get(id_attribute) is None or item.get(id_attribute) == '':
144
            continue
145
        item['id'] = item[id_attribute]
146
        if text_attribute not in item:
147
            item['text'] = item['id']
141 148
        else:
142
            get_logger().warning('Error reading JSON data source output (%s)' % str(e))
149
            item['text'] = item[text_attribute]
150
        items.append(item)
151
    return items
152

  
153

  
154
def request_geojson_items(url, data_source):
155
    url = sign_url_auto_orig(url)
156
    try:
157
        entries = misc.json_loads(misc.urlopen(url).read())
158
        if not isinstance(entries, dict):
159
            raise ValueError('not a json dict')
160
        if entries.get('err') not in (None, 0, "0"):
161
            raise ValueError('err %s' % entries['err'])
162
        if not isinstance(entries.get('features'), list):
163
            raise ValueError('bad geojson format')
164
    except misc.ConnectionError as e:
165
        get_logger().warning('Error loading GeoJSON data source (%s)' % str(e))
166
        return None
167
    except (ValueError, TypeError) as e:
168
        get_logger().warning('Error reading GeoJSON data source output (%s)' % str(e))
143 169
        return None
144 170
    items = []
145
    if geojson:
146
        id_property = data_source.get('id_property') or 'id'
147
        for item in entries.get('features'):
148
            if not item.get('properties', {}).get(id_property):
149
                continue
150
            item['id'] = item['properties'][id_property]
151
            try:
152
                item['text'] = Template(
153
                    data_source.get('label_template_property') or '{{ text }}').render(item['properties'])
154
            except (TemplateSyntaxError, VariableDoesNotExist):
155
                pass
156
            if not item.get('text'):
157
                item['text'] = item['id']
158
            items.append(item)
159
    else:
160
        for item in entries.get('data'):
161
            # skip malformed items
162
            if not isinstance(item, dict):
163
                continue
164
            if item.get('id') is None or item.get('id') == '':
165
                continue
166
            if 'text' not in item:
167
                item['text'] = item['id']
168
            items.append(item)
171
    id_property = data_source.get('id_property') or 'id'
172
    for item in entries.get('features'):
173
        if not item.get('properties', {}).get(id_property):
174
            continue
175
        item['id'] = item['properties'][id_property]
176
        try:
177
            item['text'] = Template(
178
                data_source.get('label_template_property') or '{{ text }}').render(item['properties'])
179
        except (TemplateSyntaxError, VariableDoesNotExist):
180
            pass
181
        if not item.get('text'):
182
            item['text'] = item['id']
183
        items.append(item)
169 184
    return items
170 185

  
171 186

  
......
247 262
            if items is not None:
248 263
                return items
249 264

  
250
        items = request_json_items(url, data_source)
265
        if geojson:
266
            items = request_geojson_items(url, data_source)
267
        else:
268
            items = request_json_items(url, data_source)
251 269
        if items is None:
252 270
            return []
253 271
        if hasattr(request, 'datasources_cache'):
......
298 316
    cache_duration = None
299 317
    query_parameter = None
300 318
    id_parameter = None
319
    data_attribute = None
320
    id_attribute = None
321
    text_attribute = None
301 322
    id_property = None
302 323
    label_template_property = None
303 324

  
......
306 327
            ('cache_duration', 'str'),
307 328
            ('query_parameter', 'str'),
308 329
            ('id_parameter', 'str'),
330
            ('data_attribute', 'str'),
331
            ('id_attribute', 'str'),
332
            ('text_attribute', 'str'),
309 333
            ('id_property', 'str'),
310 334
            ('label_template_property', 'str'),
311 335
            ('data_source', 'data_source'),
......
321 345

  
322 346
    @property
323 347
    def extended_data_source(self):
324
        if self.type != 'geojson':
325
            return self.data_source
326
        data_source = self.data_source.copy()
327
        data_source.update({
328
            'id_property': self.id_property,
329
            'label_template_property': self.label_template_property,
330
        })
331
        return data_source
348
        if self.type == 'geojson':
349
            data_source = self.data_source.copy()
350
            data_source.update({
351
                'id_property': self.id_property,
352
                'label_template_property': self.label_template_property,
353
            })
354
            return data_source
355
        if self.type == 'json':
356
            data_source = self.data_source.copy()
357
            data_source.update({
358
                'data_attribute': self.data_attribute,
359
                'id_attribute': self.id_attribute,
360
                'text_attribute': self.text_attribute,
361
            })
362
            return data_source
363
        return self.data_source
332 364

  
333 365
    def can_jsonp(self):
334 366
        if self.type == 'jsonp':
335
-