From 7007f2d685fb848fce60acb9155d4c7d98541bb8 Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Wed, 9 Oct 2019 12:27:53 +0200
Subject: [PATCH] datasources: avoid raising on lost datasource (#18431)

---
 tests/test_form_pages.py | 19 +++++++++++++++++++
 wcs/data_sources.py      | 13 +++++++++----
 wcs/fields.py            |  2 ++
 wcs/formdef.py           |  6 +++---
 wcs/forms/common.py      |  2 ++
 5 files changed, 35 insertions(+), 7 deletions(-)

diff --git a/tests/test_form_pages.py b/tests/test_form_pages.py
index 1f9a122d..9327ceb3 100644
--- a/tests/test_form_pages.py
+++ b/tests/test_form_pages.py
@@ -4238,6 +4238,25 @@ def test_form_text_field_submit(pub):
     resp = resp.forms[0].submit('submit')
     assert 'Check values then click submit.' in resp.body
 
+def test_unknown_datasource(pub):
+    formdef = create_formdef()
+    formdef.fields = [
+        fields.StringField(id='1', label='string',
+                varname='string', required=False, data_source={'type': 'foobar'}),
+        fields.ItemField(id='2', label='item',
+                varname='item', required=False, data_source={'type': 'foobar'}),
+        fields.ItemsField(id='3', label='items',
+                varname='items', required=False, data_source={'type': 'foobar'}),
+    ]
+
+    formdef.store()
+    data_class = formdef.data_class()
+    data_class.wipe()
+    resp = get_app(pub).get('/test/')
+    formdef.data_class().wipe()
+    resp = resp.forms[0].submit('submit') # should go straight to validation
+    assert 'Check values then click submit.' in resp.body
+
 def test_form_items_datasource(pub):
     formdef = create_formdef()
     formdef.fields = [fields.ItemsField(id='1', label='items',
diff --git a/wcs/data_sources.py b/wcs/data_sources.py
index 1f1384e2..005e6981 100644
--- a/wcs/data_sources.py
+++ b/wcs/data_sources.py
@@ -102,7 +102,7 @@ class DataSourceSelectionWidget(CompositeWidget):
 
 
 def get_items(data_source, include_disabled=False, mode=None):
-    structured_items = get_structured_items(data_source, mode=mode)
+    structured_items = get_structured_items(data_source, mode=mode) or []
     tupled_items = []
     for item in structured_items:
         if item.get('disabled') and not include_disabled:
@@ -130,6 +130,8 @@ def get_structured_items(data_source, mode=None):
     if data_source.get('type') not in ('json', 'jsonp', 'formula'):
         # named data source
         named_data_source = NamedDataSource.get_by_slug(data_source['type'])
+        if not named_data_source:
+            return
         if named_data_source.cache_duration:
             cache_duration = int(named_data_source.cache_duration)
         data_source = named_data_source.data_source
@@ -231,7 +233,8 @@ def get_real(data_source):
         return data_source
     if ds_type and ds_type.startswith('carddef:'):
         return data_source
-    return NamedDataSource.get_by_slug(ds_type).data_source
+    named_data_source = NamedDataSource.get_by_slug(ds_type)
+    return named_data_source.data_source if named_data_source else None
 
 
 def get_object(data_source):
@@ -330,7 +333,8 @@ class NamedDataSource(XmlStorableObject):
         objects = [x for x in cls.select() if x.slug == slug]
         if objects:
             return objects[0]
-        raise KeyError("data source '%s' does not exist" % slug)
+        get_logger().warn("data source '%s' does not exist" % slug)
+        return None
 
     def get_json_query_url(self):
         url = self.data_source.get('value').strip()
@@ -422,4 +426,5 @@ class NamedDataSource(XmlStorableObject):
 
 class DataSourcesSubstitutionProxy(object):
     def __getattr__(self, attr):
-        return get_structured_items(NamedDataSource.get_by_slug(attr).data_source)
+        named_data_source = NamedDataSource.get_by_slug(attr)
+        return get_structured_items(named_data_source.data_source) if named_data_source else []
diff --git a/wcs/fields.py b/wcs/fields.py
index bdf3f1bb..7f54744f 100644
--- a/wcs/fields.py
+++ b/wcs/fields.py
@@ -725,6 +725,8 @@ class StringField(WidgetField):
     def perform_more_widget_changes(self, form, kwargs, edit=True):
         if self.data_source:
             real_data_source = data_sources.get_real(self.data_source)
+            if not real_data_source:
+                return
             if real_data_source.get('type') == 'jsonp':
                 kwargs['url'] = real_data_source.get('value')
                 self.widget_class = AutocompleteStringWidget
diff --git a/wcs/formdef.py b/wcs/formdef.py
index 473cb7a4..4de07e79 100644
--- a/wcs/formdef.py
+++ b/wcs/formdef.py
@@ -668,6 +668,8 @@ class FormDef(StorableObject):
                     live_condition_fields[varname].append(field)
             if field.key == 'item' and field.data_source:
                 real_data_source = data_sources.get_real(field.data_source)
+                if not real_data_source:
+                    continue
                 if real_data_source.get('type') != 'json':
                     continue
                 varnames = field.get_referenced_varnames(
@@ -1010,9 +1012,7 @@ class FormDef(StorableObject):
         for field in formdef.fields:
             data_source = getattr(field, 'data_source', None)
             if data_source:
-                try:
-                    data_sources.get_real(data_source)
-                except KeyError:
+                if not data_sources.get_real(data_source):
                     unknown_datasources.add(data_source.get('type'))
         if unknown_datasources:
             raise FormdefImportError(N_('Unknown datasources'),
diff --git a/wcs/forms/common.py b/wcs/forms/common.py
index e4c60adb..1e50688d 100644
--- a/wcs/forms/common.py
+++ b/wcs/forms/common.py
@@ -652,6 +652,8 @@ class FormStatusPage(Directory, FormTemplateMixin):
         for field in displayed_fields:
             if field.key == 'item' and field.data_source:
                 data_source = data_sources.get_object(field.data_source)
+                if not data_source:
+                    continue
                 if data_source.type != 'json':
                     continue
                 varnames = field.get_referenced_varnames(
-- 
2.23.0

