From 08fe3ca5c703362a084199016ab0fc93d3ff6f8e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Tue, 11 May 2021 10:23:39 +0200
Subject: [PATCH] datasource: data attribute can be "data/foo/bar/results"
 (#53911)

---
 tests/test_datasource.py  | 12 ++++++++++++
 wcs/admin/data_sources.py |  5 ++++-
 wcs/data_sources.py       | 16 ++++++++++++++--
 3 files changed, 30 insertions(+), 3 deletions(-)

diff --git a/tests/test_datasource.py b/tests/test_datasource.py
index e40162c0..9385b3d4 100644
--- a/tests/test_datasource.py
+++ b/tests/test_datasource.py
@@ -338,6 +338,18 @@ def test_json_datasource(pub, requests_pub, http_requests):
     get_request().datasources_cache = {}
     assert data_sources.get_structured_items(datasource) == []
 
+    get_request().datasources_cache = {}
+    with open(json_file_path, 'w') as json_file:
+        json.dump({'data': {'results': [{'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}]}}, json_file)
+    assert data_sources.get_structured_items(datasource) == []
+
+    datasource = {'type': 'json', 'value': ' {{ json_url }}', 'data_attribute': 'data.results'}
+    get_request().datasources_cache = {}
+    assert data_sources.get_structured_items(datasource) == [
+        {'id': '1', 'text': 'foo'},
+        {'id': '2', 'text': 'bar'},
+    ]
+
     # specify id_attribute
     datasource = {'type': 'json', 'value': ' {{ json_url }}', 'id_attribute': 'pk'}
     get_request().datasources_cache = {}
diff --git a/wcs/admin/data_sources.py b/wcs/admin/data_sources.py
index c3d408f8..96673239 100644
--- a/wcs/admin/data_sources.py
+++ b/wcs/admin/data_sources.py
@@ -181,7 +181,10 @@ class NamedDataSourceUI:
                 'data_attribute',
                 value=self.datasource.data_attribute,
                 title=_('Data Attribute'),
-                hint=_('Name of the attribute containing the list of results (default: data)'),
+                hint=_(
+                    'Name of the attribute containing the list of results (default: data). '
+                    'Possibility to chain attributes with a dot separator (example: data.results)'
+                ),
                 required=False,
                 advanced=True,
                 attrs={
diff --git a/wcs/data_sources.py b/wcs/data_sources.py
index 7b25ecb4..1bcf7015 100644
--- a/wcs/data_sources.py
+++ b/wcs/data_sources.py
@@ -171,6 +171,7 @@ def get_json_from_url(url, data_source=None, log_message_part='JSON data source'
     geojson = data_source.get('type') == 'geojson'
     error_summary = None
     exc = None
+
     try:
         entries = misc.json_loads(misc.urlopen(url).read())
         if not isinstance(entries, dict):
@@ -181,7 +182,14 @@ def get_json_from_url(url, data_source=None, log_message_part='JSON data source'
             if not isinstance(entries.get('features'), list):
                 raise ValueError('bad geojson format')
         else:
-            if not isinstance(entries.get(data_key), list):
+            # data_key can be "data.foo.bar.results"
+            keys = data_key.split('.')
+            data = entries
+            for key in keys[:-1]:
+                if not isinstance(data.get(key), dict):
+                    raise ValueError('not a json dict with a %s list attribute' % data_key)
+                data = data[key]
+            if not isinstance(data.get(keys[-1]), list):
                 raise ValueError('not a json dict with a %s list attribute' % data_key)
         return entries
     except misc.ConnectionError as e:
@@ -213,8 +221,12 @@ def request_json_items(url, data_source):
     data_key = data_source.get('data_attribute') or 'data'
     id_attribute = data_source.get('id_attribute') or 'id'
     text_attribute = data_source.get('text_attribute') or 'text'
+    # data_key can be "data.foo.bar.results"
+    keys = data_key.split('.')
+    for key in keys:
+        entries = entries[key]
     items = []
-    for item in entries.get(data_key):
+    for item in entries:
         # skip malformed items
         if not isinstance(item, dict):
             continue
-- 
2.30.2

