From e1f3b733de1b63a7a39b49fd5d9782bf16ea7b3a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Tue, 3 May 2022 08:26:51 +0200
Subject: [PATCH] misc: limit computed field data source selection to cards
 (#64499)

---
 tests/admin_pages/test_datasource.py | 30 ++++++++++++++++++++
 tests/admin_pages/test_form.py       | 16 +++++++++--
 wcs/admin/data_sources.py            |  3 +-
 wcs/data_sources.py                  | 42 ++++++++++++++++++----------
 wcs/fields.py                        |  6 ++--
 5 files changed, 76 insertions(+), 21 deletions(-)

diff --git a/tests/admin_pages/test_datasource.py b/tests/admin_pages/test_datasource.py
index 5a222c3a1..e56141cdf 100644
--- a/tests/admin_pages/test_datasource.py
+++ b/tests/admin_pages/test_datasource.py
@@ -280,6 +280,36 @@ def test_data_sources_type_options_jsonp(pub):
     assert 'jsonp' in [x[0] for x in resp.form['data_source$type'].options]
 
 
+def test_data_sources_type_options_python(pub):
+    create_superuser(pub)
+
+    data_source = NamedDataSource(name='foobar')
+    data_source.store()
+
+    if not pub.site_options.has_section('options'):
+        pub.site_options.add_section('options')
+    pub.site_options.set('options', 'disable-python-expressions', 'false')
+    with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
+        pub.site_options.write(fd)
+
+    app = login(get_app(pub))
+    resp = app.get('/backoffice/settings/data-sources/%s/edit' % data_source.id)
+    assert 'python' in [x[0] for x in resp.form['data_source$type'].options]
+
+    pub.site_options.set('options', 'disable-python-expressions', 'true')
+    with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
+        pub.site_options.write(fd)
+
+    resp = app.get('/backoffice/settings/data-sources/%s/edit' % data_source.id)
+    assert 'python' not in [x[0] for x in resp.form['data_source$type'].options]
+
+    # make sure it's still displayed for sources using it.
+    data_source.data_source = {'type': 'formula', 'value': '[]'}
+    data_source.store()
+    resp = app.get('/backoffice/settings/data-sources/%s/edit' % data_source.id)
+    assert 'python' in [x[0] for x in resp.form['data_source$type'].options]
+
+
 def test_data_sources_agenda_manual_qs_data_type_options(pub):
     create_superuser(pub)
 
diff --git a/tests/admin_pages/test_form.py b/tests/admin_pages/test_form.py
index 50d6e99f0..591fd7a6a 100644
--- a/tests/admin_pages/test_form.py
+++ b/tests/admin_pages/test_form.py
@@ -3241,9 +3241,19 @@ def test_form_new_computed_field(pub):
     resp = resp.forms[0].submit().follow()
 
     assert len(FormDef.get(1).fields) == 1
-    assert FormDef.get(1).fields[0].key == 'computed'
-    assert FormDef.get(1).fields[0].label == 'foobar'
-    assert FormDef.get(1).fields[0].varname == 'foobar'
+    field = FormDef.get(1).fields[0]
+    assert field.key == 'computed'
+    assert field.label == 'foobar'
+    assert field.varname == 'foobar'
+
+    CardDef.wipe()
+    carddef = CardDef()
+    carddef.name = 'Baz'
+    carddef.digest_templates = {'default': 'plop'}
+    carddef.store()
+    resp = app.get('/backoffice/forms/%s/fields/%s/' % (formdef.id, field.id))
+    # only cards
+    assert resp.form['data_source$type'].options == [('None', True, 'None'), ('carddef:baz', False, 'Baz')]
 
 
 def test_form_category_management_roles(pub, backoffice_user, backoffice_role):
diff --git a/wcs/admin/data_sources.py b/wcs/admin/data_sources.py
index d61694f3b..05fff8827 100644
--- a/wcs/admin/data_sources.py
+++ b/wcs/admin/data_sources.py
@@ -88,8 +88,7 @@ class NamedDataSourceUI:
                 'data_source',
                 value=self.datasource.data_source,
                 title=_('Data Source'),
-                allow_geojson=True,
-                allow_named_sources=False,
+                allowed_source_types={'json', 'jsonp', 'geojson', 'python'},
                 required=True,
             )
         form.add(
diff --git a/wcs/data_sources.py b/wcs/data_sources.py
index 64c0afcad..5d6bb708b 100644
--- a/wcs/data_sources.py
+++ b/wcs/data_sources.py
@@ -54,9 +54,16 @@ def register_data_source_function(function, function_name=None):
 
 
 class DataSourceSelectionWidget(CompositeWidget):
-    def __init__(
-        self, name, value=None, allow_jsonp=True, allow_geojson=False, allow_named_sources=True, **kwargs
-    ):
+    def __init__(self, name, value=None, allowed_source_types=None, disallowed_source_types=None, **kwargs):
+        if allowed_source_types is None:
+            allowed_source_types = {'json', 'jsonp', 'geojson', 'named', 'cards', 'python'}
+        if get_publisher().has_site_option('disable-python-expressions'):
+            allowed_source_types.remove('python')
+        if get_publisher().has_site_option('disable-jsonp-sources'):
+            allowed_source_types.remove('jsonp')
+        if disallowed_source_types:
+            allowed_source_types = allowed_source_types.difference(disallowed_source_types)
+
         CompositeWidget.__init__(self, name, value, **kwargs)
 
         if not value:
@@ -64,7 +71,7 @@ class DataSourceSelectionWidget(CompositeWidget):
 
         options = [(None, _('None'), None)]
 
-        if allow_named_sources:
+        if 'cards' in allowed_source_types:
             from wcs.carddef import CardDef
 
             cards_options = [
@@ -78,6 +85,7 @@ class DataSourceSelectionWidget(CompositeWidget):
                 options.append(OptGroup(_('Cards')))
                 options.extend(cards_options)
 
+        if 'named' in allowed_source_types:
             admin_accessible = NamedDataSource.is_admin_accessible()
             nds_options = []
             nds_agenda_options = []
@@ -136,17 +144,23 @@ class DataSourceSelectionWidget(CompositeWidget):
                         options.append(OptGroup(name or _('Without category')))
                         options.extend(nds_by_category_names[name])
 
-        if len(options) > 1:
+        generic_options = []
+        if 'json' in allowed_source_types:
+            generic_options.append(('json', _('JSON URL'), 'json', {'data-maybe-datetimes': 'true'}))
+        if 'jsonp' in allowed_source_types:
+            generic_options.append(('jsonp', _('JSONP URL'), 'jsonp'))
+        elif value.get('type') == 'jsonp':
+            generic_options.append(('jsonp', _('JSONP URL (deprecated)'), 'jsonp'))
+        if 'geojson' in allowed_source_types:
+            generic_options.append(('geojson', _('GeoJSON URL'), 'geojson'))
+        if 'python' in allowed_source_types:
+            generic_options.append(('formula', _('Python Expression'), 'python'))
+        elif value.get('type') == 'formula':
+            generic_options.append(('formula', _('Python Expression (deprecated)'), 'python'))
+
+        if len(options) > 1 and generic_options:
             options.append(OptGroup(_('Generic Data Sources')))
-        options.append(('json', _('JSON URL'), 'json', {'data-maybe-datetimes': 'true'}))
-        if allow_jsonp and (
-            value.get('type') == 'jsonp' or not get_publisher().has_site_option('disable-jsonp-sources')
-        ):
-            options.append(('jsonp', _('JSONP URL'), 'jsonp'))
-        if allow_geojson:
-            options.append(('geojson', _('GeoJSON URL'), 'geojson'))
-        if not get_publisher().has_site_option('disable-python-expressions'):
-            options.append(('formula', _('Python Expression'), 'python'))
+        options.extend(generic_options)
 
         self.add(
             SingleSelectWidget,
diff --git a/wcs/fields.py b/wcs/fields.py
index bd0f1924e..218d27a37 100644
--- a/wcs/fields.py
+++ b/wcs/fields.py
@@ -1241,6 +1241,7 @@ class StringField(WidgetField):
             value=self.data_source,
             title=_('Data Source'),
             hint=_('This will allow autocompletion from an external source.'),
+            disallowed_source_types={'geojson'},
             advanced=True,
             required=False,
         )
@@ -2025,6 +2026,7 @@ class ItemFieldMixin:
             value=self.data_source,
             required=False,
             hint=_('This will get the available items from an external source.'),
+            disallowed_source_types={'geojson'},
             attrs={'data-dynamic-display-child-of': 'data_mode', 'data-dynamic-display-value': 'data-source'},
         )
 
@@ -3932,8 +3934,8 @@ class ComputedField(Field):
             data_sources.DataSourceSelectionWidget,
             'data_source',
             value=self.data_source,
-            allow_jsonp=False,
-            title=_('Data Source'),
+            allowed_source_types={'cards'},
+            title=_('Data Source (cards only)'),
             hint=_('This will make linked card data available for expressions.'),
             required=False,
         )
-- 
2.36.1

