From 17cf3a8133269fc755e7a1193e7ea7161c59cc50 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Fri, 16 Dec 2022 12:09:18 +0100
Subject: [PATCH 2/4] misc: convert formula datasource into jsonvalue in some
 tests (#72096)

---
 tests/admin_pages/test_datasource.py        | 50 +++++++++++---
 tests/api/test_carddef.py                   |  8 +--
 tests/api/test_formdata.py                  | 21 +++---
 tests/api/test_formdef.py                   | 10 +--
 tests/backoffice_pages/test_all.py          |  5 +-
 tests/backoffice_pages/test_carddata.py     | 12 ++--
 tests/backoffice_pages/test_columns.py      |  5 +-
 tests/backoffice_pages/test_custom_view.py  |  5 +-
 tests/backoffice_pages/test_export.py       |  5 +-
 tests/backoffice_pages/test_filters.py      |  9 ++-
 tests/backoffice_pages/test_form_inspect.py |  5 +-
 tests/backoffice_pages/test_submission.py   |  5 +-
 tests/form_pages/test_all.py                | 72 ++++++++++++---------
 tests/test_content_snapshots.py             | 32 +++++++--
 tests/test_fields.py                        | 46 ++++++++++---
 tests/test_formdata.py                      | 41 ++++++------
 tests/test_snapshots.py                     |  5 +-
 tests/workflow/test_all.py                  |  4 +-
 tests/workflow/test_backoffice_field.py     | 41 +++++++++---
 tests/workflow/test_carddata.py             |  5 +-
 20 files changed, 270 insertions(+), 116 deletions(-)

diff --git a/tests/admin_pages/test_datasource.py b/tests/admin_pages/test_datasource.py
index 4d87500bc..738e5dff4 100644
--- a/tests/admin_pages/test_datasource.py
+++ b/tests/admin_pages/test_datasource.py
@@ -1,4 +1,5 @@
 import io
+import json
 import os
 import re
 import xml.etree.ElementTree as ET
@@ -427,6 +428,32 @@ def test_data_sources_view(pub):
     data_source = NamedDataSource(name='foobar')
     app = login(get_app(pub))
 
+    data_source.data_source = {'type': 'jsonvalue', 'value': '[]'}
+    data_source.store()
+    resp = app.get('/backoffice/settings/data-sources/%s/' % data_source.id)
+    assert 'This data source is readonly.' not in resp
+    assert 'href="edit"' in resp
+    assert 'href="delete"' in resp
+    assert 'href="duplicate"' in resp
+    assert 'Type of source: JSON Expression' in resp.text
+    assert 'JSON Expression' in resp.text
+    assert 'Preview' not in resp.text
+
+    data_source.data_source = {
+        'type': 'jsonvalue',
+        'value': json.dumps(
+            [
+                {'id': 'AAA', 'text': 'AAA'},
+                {'id': 'BBB', 'text': 'BBB'},
+            ]
+        ),
+    }
+    data_source.store()
+    resp = app.get('/backoffice/settings/data-sources/%s/' % data_source.id)
+    assert 'Preview' in resp.text
+    assert resp.text.count('AAA') == 4  # expression (id, text) + preview (id, text)
+
+    # check python
     data_source.data_source = {'type': 'formula', 'value': '[]'}
     data_source.store()
     resp = app.get('/backoffice/settings/data-sources/%s/' % data_source.id)
@@ -516,7 +543,10 @@ def test_data_sources_view(pub):
     assert 'bar' in resp.text
     assert 'Additional keys are available: geometry_coordinates, geometry_type, properties_label' in resp.text
 
-    data_source.data_source = {'type': 'formula', 'value': '[str(x) for x in range(100)]'}
+    data_source.data_source = {
+        'type': 'jsonvalue',
+        'value': json.dumps([{'id': x, 'text': x} for x in range(100)]),
+    }
     data_source.store()
     resp = app.get('/backoffice/settings/data-sources/%s/' % data_source.id)
     assert 'Preview' in resp.text
@@ -524,8 +554,8 @@ def test_data_sources_view(pub):
     assert '<li>...</li>' in resp.text
 
     data_source.data_source = {
-        'type': 'formula',
-        'value': repr(
+        'type': 'jsonvalue',
+        'value': json.dumps(
             [
                 {'id': 'a', 'text': 'BBB', 'foo': 'bar1'},
                 {'id': 'b', 'text': 'BBB', 'foo': 'bar2'},
@@ -803,10 +833,10 @@ def test_data_sources_edit_duplicate_name(pub):
     create_superuser(pub)
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
-    data_source.data_source = {'type': 'formula', 'value': '[]'}
+    data_source.data_source = {'type': 'jsonvalue', 'value': '[]'}
     data_source.store()
     data_source = NamedDataSource(name='foobar2')
-    data_source.data_source = {'type': 'formula', 'value': '[]'}
+    data_source.data_source = {'type': 'jsonvalue', 'value': '[]'}
     data_source.store()
 
     app = login(get_app(pub))
@@ -895,7 +925,7 @@ def test_data_sources_export(pub):
 
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
-    data_source.data_source = {'type': 'formula', 'value': '[]'}
+    data_source.data_source = {'type': 'jsonvalue', 'value': '[]'}
     data_source.store()
 
     app = login(get_app(pub))
@@ -915,7 +945,7 @@ def test_data_sources_import(pub):
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
     data_source.slug = 'baaaz'
-    data_source.data_source = {'type': 'formula', 'value': '[]'}
+    data_source.data_source = {'type': 'jsonvalue', 'value': '[]'}
     data_source.store()
     data_source_xml = ET.tostring(data_source.export_to_xml(include_id=True))
 
@@ -959,7 +989,7 @@ def test_data_sources_edit_slug(pub):
     create_superuser(pub)
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
-    data_source.data_source = {'type': 'formula', 'value': '[]'}
+    data_source.data_source = {'type': 'jsonvalue', 'value': '[]'}
     data_source.store()
     assert NamedDataSource.get(1).slug == 'foobar'
 
@@ -975,7 +1005,7 @@ def test_data_sources_edit_slug(pub):
     assert NamedDataSource.get(1).slug == 'foo_bar'
 
     data_source = NamedDataSource(name='barfoo')
-    data_source.data_source = {'type': 'formula', 'value': '[]'}
+    data_source.data_source = {'type': 'jsonvalue', 'value': '[]'}
     data_source.store()
 
     resp = app.get('/backoffice/settings/data-sources/1/')
@@ -994,7 +1024,7 @@ def test_data_sources_in_use_edit_slug(pub):
     create_superuser(pub)
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
-    data_source.data_source = {'type': 'formula', 'value': '[]'}
+    data_source.data_source = {'type': 'jsonvalue', 'value': '[]'}
     data_source.store()
     assert NamedDataSource.get(1).slug == 'foobar'
 
diff --git a/tests/api/test_carddef.py b/tests/api/test_carddef.py
index 3a60b5c84..04797cbe2 100644
--- a/tests/api/test_carddef.py
+++ b/tests/api/test_carddef.py
@@ -1031,7 +1031,7 @@ def test_carddef_submit_with_varname(pub, local_user):
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
     source = [{'id': '1', 'text': 'foo', 'more': 'XXX'}, {'id': '2', 'text': 'bar', 'more': 'YYY'}]
-    data_source.data_source = {'type': 'formula', 'value': repr(source)}
+    data_source.data_source = {'type': 'jsonvalue', 'value': json.dumps(source)}
     data_source.store()
 
     data_source = NamedDataSource(name='foobar_jsonp')
@@ -1123,7 +1123,7 @@ def test_carddef_submit_from_wscall(pub, local_user):
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
     source = [{'id': '1', 'text': 'foo', 'more': 'XXX'}, {'id': '2', 'text': 'bar', 'more': 'YYY'}]
-    data_source.data_source = {'type': 'formula', 'value': repr(source)}
+    data_source.data_source = {'type': 'jsonvalue', 'value': json.dumps(source)}
     data_source.store()
 
     data_source = NamedDataSource(name='foobar_jsonp')
@@ -1267,8 +1267,8 @@ def test_formdef_submit_structured(pub, local_user):
             label='foobar1',
             varname='foobar1',
             data_source={
-                'type': 'formula',
-                'value': '[dict(id=i, text=\'label %s\' % i, foo=i) for i in range(10)]',
+                'type': 'jsonvalue',
+                'value': json.dumps([{'id': i, 'text': 'label %s' % i, 'foo': i} for i in range(10)]),
             },
         ),
     ]
diff --git a/tests/api/test_formdata.py b/tests/api/test_formdata.py
index 39d867942..c6a3a81a4 100644
--- a/tests/api/test_formdata.py
+++ b/tests/api/test_formdata.py
@@ -1,6 +1,7 @@
 import base64
 import datetime
 import io
+import json
 import os
 import re
 import time
@@ -108,8 +109,10 @@ def test_formdata(pub, local_user):
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
     data_source.data_source = {
-        'type': 'formula',
-        'value': repr([{'id': '1', 'text': 'foo', 'more': 'XXX'}, {'id': '2', 'text': 'bar', 'more': 'YYY'}]),
+        'type': 'jsonvalue',
+        'value': json.dumps(
+            [{'id': '1', 'text': 'foo', 'more': 'XXX'}, {'id': '2', 'text': 'bar', 'more': 'YYY'}]
+        ),
     }
     data_source.store()
 
@@ -1302,8 +1305,10 @@ def test_api_list_formdata_item_filter(pub, local_user):
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
     data_source.data_source = {
-        'type': 'formula',
-        'value': repr([{'id': '9', 'text': 'foo'}, {'id': '10', 'text': 'bar'}, {'id': '11', 'text': 'baz'}]),
+        'type': 'jsonvalue',
+        'value': json.dumps(
+            [{'id': '9', 'text': 'foo'}, {'id': '10', 'text': 'bar'}, {'id': '11', 'text': 'baz'}]
+        ),
     }
     data_source.store()
 
@@ -1388,8 +1393,8 @@ def test_api_list_formdata_items_filter(pub, local_user):
     # use large numbers as identifiers as they are concatenated in SQL and it should
     # not trigger any out-of-bounds SQL checks or Python pre-checks.
     data_source.data_source = {
-        'type': 'formula',
-        'value': repr(
+        'type': 'jsonvalue',
+        'value': json.dumps(
             [{'id': '9000', 'text': 'foo'}, {'id': '10000', 'text': 'bar'}, {'id': '11000', 'text': 'baz'}]
         ),
     }
@@ -1772,8 +1777,8 @@ def test_api_list_formdata_block_field_filter(pub, local_user):
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
     data_source.data_source = {
-        'type': 'formula',
-        'value': repr([{'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}]),
+        'type': 'jsonvalue',
+        'value': json.dumps([{'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}]),
     }
     data_source.store()
 
diff --git a/tests/api/test_formdef.py b/tests/api/test_formdef.py
index bcf07cd4e..1bd786b69 100644
--- a/tests/api/test_formdef.py
+++ b/tests/api/test_formdef.py
@@ -421,8 +421,8 @@ def test_formdef_schema(pub, access):
             label='foobar2',
             varname='foobar2',
             data_source={
-                'type': 'formula',
-                'value': '[dict(id=i, text=\'label %s\' % i, foo=i) for i in range(10)]',
+                'type': 'jsonvalue',
+                'value': json.dumps([{'id': i, 'text': 'label %s' % i, 'foo': i} for i in range(10)]),
             },
         ),
     ]
@@ -689,7 +689,7 @@ def test_formdef_submit_with_varname(pub, local_user):
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
     source = [{'id': '1', 'text': 'foo', 'more': 'XXX'}, {'id': '2', 'text': 'bar', 'more': 'YYY'}]
-    data_source.data_source = {'type': 'formula', 'value': repr(source)}
+    data_source.data_source = {'type': 'jsonvalue', 'value': json.dumps(source)}
     data_source.store()
 
     data_source = NamedDataSource(name='foobar_jsonp')
@@ -891,8 +891,8 @@ def test_formdef_submit_structured(pub, local_user):
             label='foobar1',
             varname='foobar1',
             data_source={
-                'type': 'formula',
-                'value': '[dict(id=i, text=\'label %s\' % i, foo=i) for i in range(10)]',
+                'type': 'jsonvalue',
+                'value': json.dumps([{'id': i, 'text': 'label %s' % i, 'foo': i} for i in range(10)]),
             },
         ),
         fields.ItemField(
diff --git a/tests/backoffice_pages/test_all.py b/tests/backoffice_pages/test_all.py
index 6c1c19bd1..956181121 100644
--- a/tests/backoffice_pages/test_all.py
+++ b/tests/backoffice_pages/test_all.py
@@ -100,7 +100,10 @@ def create_environment(pub, set_receiver=True):
     if set_receiver:
         formdef.workflow_roles = {'_receiver': 1}
 
-    datasource = {'type': 'formula', 'value': repr([('A', 'aa'), ('B', 'bb'), ('C', 'cc')])}
+    datasource = {
+        'type': 'jsonvalue',
+        'value': '[{"id": "A", "text": "aa"}, {"id": "B", "text": "bb"}, {"id": "C", "text": "cc"}]',
+    }
 
     formdef.fields = []
     formdef.store()  # make sure sql columns are removed
diff --git a/tests/backoffice_pages/test_carddata.py b/tests/backoffice_pages/test_carddata.py
index b1e05dd17..d7d0d23b4 100644
--- a/tests/backoffice_pages/test_carddata.py
+++ b/tests/backoffice_pages/test_carddata.py
@@ -347,8 +347,10 @@ def test_backoffice_cards_import_data_from_csv(pub):
     user = create_user(pub)
 
     data_source = {
-        'type': 'formula',
-        'value': repr([{'id': '1', 'text': 'un', 'more': 'foo'}, {'id': '2', 'text': 'deux', 'more': 'bar'}]),
+        'type': 'jsonvalue',
+        'value': json.dumps(
+            [{'id': '1', 'text': 'un', 'more': 'foo'}, {'id': '2', 'text': 'deux', 'more': 'bar'}]
+        ),
     }
 
     CardDef.wipe()
@@ -597,8 +599,10 @@ def test_backoffice_cards_import_data_from_json(pub):
     user = create_user(pub)
 
     data_source = {
-        'type': 'formula',
-        'value': repr([{'id': '1', 'text': 'un', 'more': 'foo'}, {'id': '2', 'text': 'deux', 'more': 'bar'}]),
+        'type': 'jsonvalue',
+        'value': json.dumps(
+            [{'id': '1', 'text': 'un', 'more': 'foo'}, {'id': '2', 'text': 'deux', 'more': 'bar'}]
+        ),
     }
 
     CardDef.wipe()
diff --git a/tests/backoffice_pages/test_columns.py b/tests/backoffice_pages/test_columns.py
index 0246fc279..8b368f21b 100644
--- a/tests/backoffice_pages/test_columns.py
+++ b/tests/backoffice_pages/test_columns.py
@@ -322,7 +322,10 @@ def test_backoffice_card_field_columns(pub):
     role = pub.role_class(name='test')
     role.store()
 
-    datasource = {'type': 'formula', 'value': repr([('A', 'aa'), ('B', 'bb'), ('C', 'cc')])}
+    datasource = {
+        'type': 'jsonvalue',
+        'value': '[{"id": "A", "text": "aa"}, {"id": "B", "text": "bb"}, {"id": "C", "text": "cc"}]',
+    }
 
     CardDef.wipe()
     carddef = CardDef()
diff --git a/tests/backoffice_pages/test_custom_view.py b/tests/backoffice_pages/test_custom_view.py
index 268530d9b..d14cd0095 100644
--- a/tests/backoffice_pages/test_custom_view.py
+++ b/tests/backoffice_pages/test_custom_view.py
@@ -1017,7 +1017,10 @@ def test_carddata_custom_view_digest_template(pub):
 def test_backoffice_custom_view_keep_filters(pub):
     user = create_superuser(pub)
 
-    datasource = {'type': 'formula', 'value': repr([('A', 'aa'), ('B', 'bb'), ('C', 'cc')])}
+    datasource = {
+        'type': 'jsonvalue',
+        'value': '[{"id": "A", "text": "aa"}, {"id": "B", "text": "bb"}, {"id": "C", "text": "cc"}]',
+    }
     FormDef.wipe()
     pub.custom_view_class.wipe()
     formdef = FormDef()
diff --git a/tests/backoffice_pages/test_export.py b/tests/backoffice_pages/test_export.py
index 1bf8416dd..cf7e4cf22 100644
--- a/tests/backoffice_pages/test_export.py
+++ b/tests/backoffice_pages/test_export.py
@@ -49,7 +49,10 @@ def teardown_module(module):
 def test_backoffice_csv(pub):
     create_superuser(pub)
 
-    datasource = {'type': 'formula', 'value': repr([('A', 'aa'), ('B', 'bb'), ('C', 'cc')])}
+    datasource = {
+        'type': 'jsonvalue',
+        'value': '[{"id": "A", "text": "aa"}, {"id": "B", "text": "bb"}, {"id": "C", "text": "cc"}]',
+    }
     FormDef.wipe()
     formdef = FormDef()
     formdef.name = 'form title'
diff --git a/tests/backoffice_pages/test_filters.py b/tests/backoffice_pages/test_filters.py
index 477d3edc5..262ae6e06 100644
--- a/tests/backoffice_pages/test_filters.py
+++ b/tests/backoffice_pages/test_filters.py
@@ -1274,7 +1274,10 @@ def test_backoffice_table_varname_filter(pub):
     role = pub.role_class(name='test')
     role.store()
 
-    datasource = {'type': 'formula', 'value': repr([('A', 'aa'), ('B', 'bb'), ('C', 'cc')])}
+    datasource = {
+        'type': 'jsonvalue',
+        'value': '[{"id": "A", "text": "aa"}, {"id": "B", "text": "bb"}, {"id": "C", "text": "cc"}]',
+    }
     FormDef.wipe()
     formdef = FormDef()
     formdef.name = 'form-title'
@@ -1338,8 +1341,8 @@ def test_backoffice_block_field_filter(pub):
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
     data_source.data_source = {
-        'type': 'formula',
-        'value': repr([{'id': '1', 'text': 'foo', 'more': 'XXX'}, {'id': '2', 'text': 'bar', 'more': 'YYY'}]),
+        'type': 'jsonvalue',
+        'value': '[{"id": "1", "text": "foo", "more": "XXX"}, {"id": "2", "text": "bar", "more": "YYY"}]',
     }
     data_source.store()
 
diff --git a/tests/backoffice_pages/test_form_inspect.py b/tests/backoffice_pages/test_form_inspect.py
index 620a4c6b2..c13a42edd 100644
--- a/tests/backoffice_pages/test_form_inspect.py
+++ b/tests/backoffice_pages/test_form_inspect.py
@@ -55,7 +55,10 @@ def test_inspect_page(pub, local_user):
 
     formdef = FormDef()
     formdef.name = 'form title'
-    datasource = {'type': 'formula', 'value': repr([('A', 'aa'), ('B', 'bb'), ('C', 'cc')])}
+    datasource = {
+        'type': 'jsonvalue',
+        'value': '[{"id": "A", "text": "aa"}, {"id": "B", "text": "bb"}, {"id": "C", "text": "cc"}]',
+    }
     formdef.fields = [
         fields.StringField(
             id='1', label='1st field', type='string', display_locations=['validation', 'summary', 'listings']
diff --git a/tests/backoffice_pages/test_submission.py b/tests/backoffice_pages/test_submission.py
index bea15ef13..8d1bf9fb2 100644
--- a/tests/backoffice_pages/test_submission.py
+++ b/tests/backoffice_pages/test_submission.py
@@ -53,7 +53,10 @@ def teardown_module(module):
 def test_backoffice_submission(pub):
     user = create_user(pub)
 
-    datasource = {'type': 'formula', 'value': repr([('A', 'aa'), ('B', 'bb'), ('C', 'cc')])}
+    datasource = {
+        'type': 'jsonvalue',
+        'value': '[{"id": "A", "text": "aa"}, {"id": "B", "text": "bb"}, {"id": "C", "text": "cc"}]',
+    }
     FormDef.wipe()
     formdef = FormDef()
     formdef.name = 'form title'
diff --git a/tests/form_pages/test_all.py b/tests/form_pages/test_all.py
index cabba2d9b..d80800935 100644
--- a/tests/form_pages/test_all.py
+++ b/tests/form_pages/test_all.py
@@ -1031,7 +1031,7 @@ def test_form_multi_page_condition_data_source(pub):
     # add the named data source, empty
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
-    data_source.data_source = {'type': 'formula', 'value': repr([])}
+    data_source.data_source = {'type': 'jsonvalue', 'value': '[]'}
     data_source.store()
 
     resp = get_app(pub).get('/test/')
@@ -1045,7 +1045,10 @@ def test_form_multi_page_condition_data_source(pub):
     # replace the named data source with one with items
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
-    data_source.data_source = {'type': 'formula', 'value': repr(['un', 'deux'])}
+    data_source.data_source = {
+        'type': 'jsonvalue',
+        'value': '[{"id": "un", "text": "un"}, {"id": "deux", "text": "deux"}]',
+    }
     data_source.store()
 
     resp = resp.forms[0].submit('submit')  # should go to second page
@@ -3189,15 +3192,17 @@ def test_form_item_data_source_field_submit(pub):
         return formdef.data_class().get(data_id).data
 
     ds = {
-        'type': 'formula',
-        'value': repr([('1', 'un'), ('2', 'deux')]),
+        'type': 'jsonvalue',
+        'value': '[{"id": "1", "text": "un"}, {"id": "2", "text": "deux"}]',
     }
     assert submit_item_data_source_field(ds) == {'0': '1', '0_display': 'un'}
 
-    ds['value'] = repr([{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}])
+    ds['value'] = json.dumps([{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}])
     assert submit_item_data_source_field(ds) == {'0': '1', '0_display': 'un'}
 
-    ds['value'] = repr([{'id': '1', 'text': 'un', 'more': 'foo'}, {'id': '2', 'text': 'deux', 'more': 'bar'}])
+    ds['value'] = json.dumps(
+        [{'id': '1', 'text': 'un', 'more': 'foo'}, {'id': '2', 'text': 'deux', 'more': 'bar'}]
+    )
     assert submit_item_data_source_field(ds) == {
         '0': '1',
         '0_display': 'un',
@@ -3205,7 +3210,7 @@ def test_form_item_data_source_field_submit(pub):
     }
 
     # numeric identifiers
-    ds['value'] = repr([{'id': 1, 'text': 'un'}, {'id': 2, 'text': 'deux'}])
+    ds['value'] = json.dumps([{'id': 1, 'text': 'un'}, {'id': 2, 'text': 'deux'}])
     assert submit_item_data_source_field(ds) == {'0': '1', '0_display': 'un'}
 
     # json source
@@ -3399,8 +3404,8 @@ def test_form_items_data_source_field_submit(pub):
         return formdef.data_class().get(data_id).data
 
     ds = {
-        'type': 'formula',
-        'value': repr([('1', 'un'), ('2', 'deux'), ('3', 'trois')]),
+        'type': 'jsonvalue',
+        'value': '[{"id": "1", "text": "un"}, {"id": "2", "text": "deux"}, {"id": "3", "text": "trois"}]',
     }
     assert submit_items_data_source_field(ds) == {
         '0': ['1', '3'],
@@ -3408,14 +3413,16 @@ def test_form_items_data_source_field_submit(pub):
         '0_structured': [{'id': '1', 'text': 'un'}, {'id': '3', 'text': 'trois'}],
     }
 
-    ds['value'] = repr([{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}, {'id': '3', 'text': 'trois'}])
+    ds['value'] = json.dumps(
+        [{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}, {'id': '3', 'text': 'trois'}]
+    )
     assert submit_items_data_source_field(ds) == {
         '0': ['1', '3'],
         '0_display': 'un, trois',
         '0_structured': [{'id': '1', 'text': 'un'}, {'id': '3', 'text': 'trois'}],
     }
 
-    ds['value'] = repr(
+    ds['value'] = json.dumps(
         [
             {'id': '1', 'text': 'un', 'more': 'foo'},
             {'id': '2', 'text': 'deux', 'more': 'bar'},
@@ -3660,8 +3667,8 @@ def test_form_page_query_string_list_prefill(pub):
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
     data_source.data_source = {
-        'type': 'formula',
-        'value': repr(
+        'type': 'jsonvalue',
+        'value': json.dumps(
             [
                 {'id': '1', 'text': 'un'},
                 {'id': '2', 'text': 'deux'},
@@ -4769,7 +4776,7 @@ def test_form_items_datasource(pub):
     # add the named data source
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
-    data_source.data_source = {'type': 'formula', 'value': repr([])}
+    data_source.data_source = {'type': 'jsonvalue', 'value': '[]'}
     data_source.store()
 
     resp = get_app(pub).get('/test/')
@@ -4782,7 +4789,10 @@ def test_form_items_datasource(pub):
     # replace the named data source with one with items
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
-    data_source.data_source = {'type': 'formula', 'value': repr(['un', 'deux'])}
+    data_source.data_source = {
+        'type': 'jsonvalue',
+        'value': '[{"id": "un", "text": "un"}, {"id": "deux", "text": "deux"}]',
+    }
     data_source.store()
 
     resp = get_app(pub).get('/test/')
@@ -4802,8 +4812,8 @@ def test_form_items_datasource(pub):
     }
 
     data_source.data_source = {
-        'type': 'formula',
-        'value': repr([{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}]),
+        'type': 'jsonvalue',
+        'value': json.dumps([{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}]),
     }
     data_source.store()
 
@@ -4825,8 +4835,10 @@ def test_form_items_datasource(pub):
     }
 
     data_source.data_source = {
-        'type': 'formula',
-        'value': repr([{'id': '1', 'text': 'un', 'foo': 'bar1'}, {'id': '2', 'text': 'deux', 'foo': 'bar2'}]),
+        'type': 'jsonvalue',
+        'value': json.dumps(
+            [{'id': '1', 'text': 'un', 'foo': 'bar1'}, {'id': '2', 'text': 'deux', 'foo': 'bar2'}]
+        ),
     }
     data_source.store()
 
@@ -8029,7 +8041,7 @@ def test_backoffice_fields_just_after_conditional_form_submit(pub):
             varname='listA',
             type='item',
             label='list A',
-            data_source={'type': 'formula', 'value': str(items_A)},
+            data_source={'type': 'jsonvalue', 'value': json.dumps(items_A)},
             condition={'type': 'python', 'value': 'form_var_choice_raw == "A"'},
         ),
         fields.ItemField(
@@ -8037,7 +8049,7 @@ def test_backoffice_fields_just_after_conditional_form_submit(pub):
             varname='listB',
             type='item',
             label='list B',
-            data_source={'type': 'formula', 'value': str(items_B)},
+            data_source={'type': 'jsonvalue', 'value': json.dumps(items_B)},
             condition={'type': 'python', 'value': 'form_var_choice_raw == "B"'},
         ),
     ]
@@ -8099,7 +8111,7 @@ def test_backoffice_fields_just_after_conditional_form_edit_action(pub):
             varname='listA',
             type='item',
             label='list A',
-            data_source={'type': 'formula', 'value': str(items_A)},
+            data_source={'type': 'jsonvalue', 'value': json.dumps(items_A)},
             condition={'type': 'python', 'value': 'form_var_choice_raw == "A"'},
         ),
         fields.ItemField(
@@ -8107,7 +8119,7 @@ def test_backoffice_fields_just_after_conditional_form_edit_action(pub):
             varname='listB',
             type='item',
             label='list B',
-            data_source={'type': 'formula', 'value': str(items_B)},
+            data_source={'type': 'jsonvalue', 'value': json.dumps(items_B)},
             condition={'type': 'python', 'value': 'form_var_choice_raw == "B"'},
         ),
     ]
@@ -8650,8 +8662,8 @@ def test_frontoffice_workflow_form_with_attachment_and_python_datasource(pub):
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
     data_source.data_source = {
-        'type': 'formula',
-        'value': repr([{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}]),
+        'type': 'jsonvalue',
+        'value': json.dumps([{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}]),
     }
     data_source.store()
 
@@ -8877,8 +8889,8 @@ def create_formdata(pub):
         {'id': '2', 'text': 'deux', 'more': 'bar'},
     ]
     ds = {
-        'type': 'formula',
-        'value': repr(data),
+        'type': 'jsonvalue',
+        'value': json.dumps(data),
     }
     source_formdef = FormDef()
     source_formdef.name = 'source form'
@@ -9188,8 +9200,10 @@ def test_structured_workflow_options(pub):
     workflow = Workflow(name='test')
     workflow.variables_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow)
     data_source = {
-        'type': 'formula',
-        'value': repr([{'id': '1', 'text': 'un', 'more': 'foo'}, {'id': '2', 'text': 'deux', 'more': 'bar'}]),
+        'type': 'jsonvalue',
+        'value': json.dumps(
+            [{'id': '1', 'text': 'un', 'more': 'foo'}, {'id': '2', 'text': 'deux', 'more': 'bar'}]
+        ),
     }
     workflow.variables_formdef.fields = [
         fields.StringField(id='1', label='Test', type='string', varname='foo'),
diff --git a/tests/test_content_snapshots.py b/tests/test_content_snapshots.py
index 707d0593a..83c8394fe 100644
--- a/tests/test_content_snapshots.py
+++ b/tests/test_content_snapshots.py
@@ -352,10 +352,22 @@ def test_backoffice_show_history(pub, user, formdef_class):
         fields.FileField(id='5', label='File', type='file'),
         fields.DateField(id='6', label='Date', type='date'),
         fields.ItemField(
-            id='7', label='Item', type='item', data_source={'type': 'formula', 'value': repr(['a', 'b'])}
+            id='7',
+            label='Item',
+            type='item',
+            data_source={
+                'type': 'jsonvalue',
+                'value': '[{"id": "a", "text": "a"}, {"id": "b", "text": "b"}]',
+            },
         ),
         fields.ItemsField(
-            id='8', label='Items', type='items', data_source={'type': 'formula', 'value': repr(['a', 'b'])}
+            id='8',
+            label='Items',
+            type='items',
+            data_source={
+                'type': 'jsonvalue',
+                'value': '[{"id": "a", "text": "a"}, {"id": "b", "text": "b"}]',
+            },
         ),
         fields.MapField(id='9', label='Map', type='map'),
         fields.PasswordField(id='10', label='Password', type='password'),
@@ -382,10 +394,22 @@ def test_backoffice_show_history(pub, user, formdef_class):
         fields.FileField(id='5', label='File', type='file'),
         fields.DateField(id='6', label='Date', type='date'),
         fields.ItemField(
-            id='7', label='Item', type='item', data_source={'type': 'formula', 'value': repr(['a', 'b'])}
+            id='7',
+            label='Item',
+            type='item',
+            data_source={
+                'type': 'jsonvalue',
+                'value': '[{"id": "a", "text": "a"}, {"id": "b", "text": "b"}]',
+            },
         ),
         fields.ItemsField(
-            id='8', label='Items', type='items', data_source={'type': 'formula', 'value': repr(['a', 'b'])}
+            id='8',
+            label='Items',
+            type='items',
+            data_source={
+                'type': 'jsonvalue',
+                'value': '[{"id": "a", "text": "a"}, {"id": "b", "text": "b"}]',
+            },
         ),
         fields.MapField(id='9', label='Map', type='map'),
         fields.PasswordField(id='10', label='Password', type='password'),
diff --git a/tests/test_fields.py b/tests/test_fields.py
index e15e40757..162d4be02 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -162,7 +162,10 @@ def test_items():
     assert len(fields.ItemsField(items=['a', 'b', 'c'], max_choices=2).get_csv_heading()) == 2
 
     field = fields.ItemsField()
-    field.data_source = {'type': 'formula', 'value': '''[('1', 'foo'), ('2', 'bar')]'''}
+    field.data_source = {
+        'type': 'jsonvalue',
+        'value': '[{"id": "1", "text": "foo"}, {"id": "2", "text": "bar"}]',
+    }
     assert field.get_options() == [('1', 'foo', '1'), ('2', 'bar', '2')]
     assert field.get_options() == [('1', 'foo', '1'), ('2', 'bar', '2')]  # twice for cached behaviour
 
@@ -185,7 +188,10 @@ def test_items_get_value_info():
 
     # data source with structured : labels are available
     field = fields.ItemsField(id='1')
-    field.data_source = {'type': 'formula', 'value': '''[('1', 'un'), ('2', 'deux')]'''}
+    field.data_source = {
+        'type': 'jsonvalue',
+        'value': '[{"id": "1", "text": "foo"}, {"id": "2", "text": "bar"}]',
+    }
     assert field.get_value_info(
         {
             '1': ['un', 'deux'],
@@ -199,7 +205,10 @@ def test_items_get_value_info():
 
     # data source with no strucured : no labels
     field = fields.ItemsField(id='1')
-    field.data_source = {'type': 'formula', 'value': '''[('1', 'un'), ('2', 'deux')]'''}
+    field.data_source = {
+        'type': 'jsonvalue',
+        'value': '[{"id": "1", "text": "foo"}, {"id": "2", "text": "bar"}]',
+    }
     assert field.get_value_info({'1': ['un', 'deux'], '1_display': 'un, deux'}) == (
         'un, deux',
         {'value_id': ['un', 'deux'], 'labels': []},
@@ -419,7 +428,14 @@ def test_map_set_value(pub):
 def test_item_render():
     items_kwargs = []
     items_kwargs.append({'items': ['aa', 'ab', 'ac']})
-    items_kwargs.append({'data_source': {'type': 'formula', 'value': '''['aa', 'ab', 'ac']'''}})
+    items_kwargs.append(
+        {
+            'data_source': {
+                'type': 'jsonvalue',
+                'value': '[{"id": "aa", "text": "aa"}, {"id": "ab", "text": "ab"}, {"id": "ac", "text": "ac"}]',
+            }
+        }
+    )
 
     for item_kwargs in items_kwargs:
         field = fields.ItemField(id='1', label='Foobar', **item_kwargs)
@@ -451,7 +467,7 @@ def test_item_render():
     items_kwargs = []
     items_kwargs.append({'items': None})
     items_kwargs.append({'items': []})
-    items_kwargs.append({'data_source': {'type': 'formula', 'value': '''[]'''}})
+    items_kwargs.append({'data_source': {'type': 'jsonvalue', 'value': '[]'}})
     for item_kwargs in items_kwargs:
         field = fields.ItemField(id='1', label='Foobar', **item_kwargs)
         form = Form(use_tokens=False)
@@ -509,7 +525,14 @@ def test_item_get_display_value(pub):
 def test_item_render_as_radio():
     items_kwargs = []
     items_kwargs.append({'items': ['aa', 'ab', 'ac']})
-    items_kwargs.append({'data_source': {'type': 'formula', 'value': '''['aa', 'ab', 'ac']'''}})
+    items_kwargs.append(
+        {
+            'data_source': {
+                'type': 'jsonvalue',
+                'value': '[{"id": "aa", "text": "aa"}, {"id": "ab", "text": "ab"}, {"id": "ac", "text": "ac"}]',
+            }
+        }
+    )
 
     for item_kwargs in items_kwargs:
         field = fields.ItemField(id='1', label='Foobar', display_mode='radio', **item_kwargs)
@@ -539,7 +562,7 @@ def test_item_render_as_radio():
     items_kwargs = []
     items_kwargs.append({'items': None})
     items_kwargs.append({'items': []})
-    items_kwargs.append({'data_source': {'type': 'formula', 'value': '''[]'''}})
+    items_kwargs.append({'data_source': {'type': 'jsonvalue', 'value': '[]'}})
     for item_kwargs in items_kwargs:
         field = fields.ItemField(id='1', label='Foobar', display_mode='radio', **item_kwargs)
         form = Form(use_tokens=False)
@@ -595,7 +618,14 @@ def test_item_radio_lengths():
 def test_items_render():
     items_kwargs = []
     items_kwargs.append({'items': ['aa', 'ab', 'ac']})
-    items_kwargs.append({'data_source': {'type': 'formula', 'value': '''['aa', 'ab', 'ac']'''}})
+    items_kwargs.append(
+        {
+            'data_source': {
+                'type': 'jsonvalue',
+                'value': '[{"id": "aa", "text": "aa"}, {"id": "ab", "text": "ab"}, {"id": "ac", "text": "ac"}]',
+            }
+        }
+    )
 
     for item_kwargs in items_kwargs:
         field = fields.ItemsField(id='1', label='Foobar', **item_kwargs)
diff --git a/tests/test_formdata.py b/tests/test_formdata.py
index 03b47a561..79b5b88e0 100644
--- a/tests/test_formdata.py
+++ b/tests/test_formdata.py
@@ -1,6 +1,7 @@
 import collections
 import datetime
 import io
+import json
 import os.path
 import time
 import uuid
@@ -451,8 +452,8 @@ def test_criticality_levels(pub):
 
 def test_field_item_substvars(pub):
     ds = {
-        'type': 'formula',
-        'value': repr([('1', 'un'), ('2', 'deux')]),
+        'type': 'jsonvalue',
+        'value': '[{"id": "1", "text": "un"}, {"id": "2", "text": "deux"}]',
     }
 
     formdef = FormDef()
@@ -3140,8 +3141,8 @@ def test_rounding_and_abs_conditions_django(pub, variable_test_data):
 
 def test_lazy_url_suffix(pub, variable_test_data):
     ds = {
-        'type': 'formula',
-        'value': repr(
+        'type': 'jsonvalue',
+        'value': json.dumps(
             [
                 {'id': '1', 'text': 'un', 'more': 'foo', 'url': 'xxx'},
                 {'id': '2', 'text': 'deux', 'more': 'bar', 'url': 'yyy'},
@@ -3176,8 +3177,8 @@ def test_lazy_url_suffix(pub, variable_test_data):
 
 def test_lazy_structured_items(pub, variable_test_data):
     ds = {
-        'type': 'formula',
-        'value': repr(
+        'type': 'jsonvalue',
+        'value': json.dumps(
             [
                 {'id': '1', 'text': 'un', 'more': 'foo', 'url': 'xxx', 'invalid:key': 'xxx'},
                 {'id': '2', 'text': 'deux', 'more': 'bar', 'url': 'yyy', 'invalid:key': 'yyy'},
@@ -3186,8 +3187,8 @@ def test_lazy_structured_items(pub, variable_test_data):
     }
 
     ds2 = {
-        'type': 'formula',
-        'value': repr([{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}]),
+        'type': 'jsonvalue',
+        'value': json.dumps([{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}]),
     }
 
     FormDef.wipe()
@@ -3586,16 +3587,16 @@ def test_formdata_filtering_on_fields(pub):
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
     data_source.data_source = {
-        'type': 'formula',
-        'value': repr([{'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}]),
+        'type': 'jsonvalue',
+        'value': json.dumps([{'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}]),
     }
     data_source.store()
     data_source2 = NamedDataSource(name='foobar2')
     # use large numbers as identifiers as they are concatenated in SQL and it should
     # not trigger any out-of-bounds SQL checks or Python pre-checks.
     data_source2.data_source = {
-        'type': 'formula',
-        'value': repr(
+        'type': 'jsonvalue',
+        'value': json.dumps(
             [{'id': '9000', 'text': 'foo'}, {'id': '10000', 'text': 'bar'}, {'id': '11000', 'text': 'baz'}]
         ),
     }
@@ -3877,8 +3878,8 @@ def test_formdata_filtering_on_block_fields(pub):
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
     data_source.data_source = {
-        'type': 'formula',
-        'value': repr([{'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}]),
+        'type': 'jsonvalue',
+        'value': json.dumps([{'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}]),
     }
     data_source.store()
 
@@ -4093,8 +4094,8 @@ def test_items_field_getlist(pub):
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
     data_source.data_source = {
-        'type': 'formula',
-        'value': repr(
+        'type': 'jsonvalue',
+        'value': json.dumps(
             [
                 {'id': '1', 'text': 'un'},
                 {'id': '2', 'text': 'deux'},
@@ -4175,8 +4176,8 @@ def test_items_field_contains(pub):
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
     data_source.data_source = {
-        'type': 'formula',
-        'value': repr(
+        'type': 'jsonvalue',
+        'value': json.dumps(
             [
                 {'id': '1', 'text': 'un'},
                 {'id': '2', 'text': 'deux'},
@@ -4227,8 +4228,8 @@ def test_items_field_split(pub):
     NamedDataSource.wipe()
     data_source = NamedDataSource(name='foobar')
     data_source.data_source = {
-        'type': 'formula',
-        'value': repr(
+        'type': 'jsonvalue',
+        'value': json.dumps(
             [
                 {'id': '1', 'text': 'un'},
                 {'id': '2', 'text': 'deux'},
diff --git a/tests/test_snapshots.py b/tests/test_snapshots.py
index 2115296a1..b579a5c4d 100644
--- a/tests/test_snapshots.py
+++ b/tests/test_snapshots.py
@@ -501,7 +501,10 @@ def test_datasource_snapshot_browse(pub):
 
     NamedDataSource.wipe()
     datasource = NamedDataSource(name='test')
-    datasource.data_source = {'type': 'formula', 'value': repr([('1', 'un'), ('2', 'deux')])}
+    datasource.data_source = {
+        'type': 'jsonvalue',
+        'value': '[{"id": "1", "text": "un"}, {"id": "2", "text": "deux"}]',
+    }
     datasource.store()
     assert pub.snapshot_class.count() == 1
     # check calling .store() without changes doesn't create snapshots
diff --git a/tests/workflow/test_all.py b/tests/workflow/test_all.py
index 003fdb8eb..dbce53426 100644
--- a/tests/workflow/test_all.py
+++ b/tests/workflow/test_all.py
@@ -2292,8 +2292,8 @@ def test_webservice_with_complex_data(http_requests, pub):
     wf.store()
 
     datasource = {
-        'type': 'formula',
-        'value': repr(
+        'type': 'jsonvalue',
+        'value': json.dumps(
             [
                 {'id': 'a', 'text': 'aa', 'more': 'aaa'},
                 {'id': 'b', 'text': 'bb', 'more': 'bbb'},
diff --git a/tests/workflow/test_backoffice_field.py b/tests/workflow/test_backoffice_field.py
index 7acf7d3c1..fed815a2c 100644
--- a/tests/workflow/test_backoffice_field.py
+++ b/tests/workflow/test_backoffice_field.py
@@ -1,5 +1,6 @@
 import base64
 import datetime
+import json
 import os
 
 import pytest
@@ -606,7 +607,16 @@ def test_set_backoffice_field_item(pub):
     assert formdata.data['bo1'] == 'a'
     assert formdata.data['bo1_display'] == 'a'
 
-    datasource = {'type': 'formula', 'value': repr([('a', 'aa'), ('b', 'bb'), ('c', 'cc')])}
+    datasource = {
+        'type': 'jsonvalue',
+        'value': json.dumps(
+            [
+                {'id': 'a', 'text': 'aa'},
+                {'id': 'b', 'text': 'bb'},
+                {'id': 'c', 'text': 'cc'},
+            ]
+        ),
+    }
 
     wf.backoffice_fields_formdef.fields = [
         ItemField(
@@ -629,8 +639,10 @@ def test_set_backoffice_field_item(pub):
     assert formdata.data['bo1_display'] == 'aa'
 
     datasource = {
-        'type': 'formula',
-        'value': repr([{'id': 'a', 'text': 'aa', 'more': 'aaa'}, {'id': 'b', 'text': 'bb', 'more': 'bbb'}]),
+        'type': 'jsonvalue',
+        'value': json.dumps(
+            [{'id': 'a', 'text': 'aa', 'more': 'aaa'}, {'id': 'b', 'text': 'bb', 'more': 'bbb'}]
+        ),
     }
 
     wf.backoffice_fields_formdef.fields = [
@@ -836,7 +848,16 @@ def test_set_backoffice_field_items(pub):
     assert formdata.data['bo1'] == ['a', 'b']
     assert formdata.data['bo1_display'] == 'a, b'
 
-    datasource = {'type': 'formula', 'value': repr([('a', 'aa'), ('b', 'bb'), ('c', 'cc')])}
+    datasource = {
+        'type': 'jsonvalue',
+        'value': json.dumps(
+            [
+                {'id': 'a', 'text': 'aa'},
+                {'id': 'b', 'text': 'bb'},
+                {'id': 'c', 'text': 'cc'},
+            ]
+        ),
+    }
 
     wf.backoffice_fields_formdef.fields = [
         ItemsField(
@@ -859,8 +880,8 @@ def test_set_backoffice_field_items(pub):
     assert formdata.data['bo1_display'] == 'aa, bb'
 
     datasource = {
-        'type': 'formula',
-        'value': repr(
+        'type': 'jsonvalue',
+        'value': json.dumps(
             [
                 {'id': 'a', 'text': 'aa', 'more': 'aaa'},
                 {'id': 'b', 'text': 'bb', 'more': 'bbb'},
@@ -932,8 +953,8 @@ def test_set_backoffice_field_items(pub):
 
     # using a single int
     datasource = {
-        'type': 'formula',
-        'value': repr(
+        'type': 'jsonvalue',
+        'value': json.dumps(
             [
                 {'id': 1, 'text': 'aa', 'more': 'aaa'},
                 {'id': 2, 'text': 'bb', 'more': 'bbb'},
@@ -1337,8 +1358,8 @@ def test_set_backoffice_field_block_template_tag(pub):
 
     # "item" subfield, make sure raw and display and structured values are stored
     datasource = {
-        'type': 'formula',
-        'value': repr(
+        'type': 'jsonvalue',
+        'value': json.dumps(
             [
                 {'id': 'a', 'text': 'aa', 'more': 'aaa'},
                 {'id': 'b', 'text': 'bb', 'more': 'bbb'},
diff --git a/tests/workflow/test_carddata.py b/tests/workflow/test_carddata.py
index e4f6c60f3..e34067fce 100644
--- a/tests/workflow/test_carddata.py
+++ b/tests/workflow/test_carddata.py
@@ -1,4 +1,5 @@
 import datetime
+import json
 import os
 
 import pytest
@@ -588,8 +589,8 @@ def test_edit_carddata_with_data_sourced_object(pub):
     CardDef.wipe()
 
     datasource = {
-        'type': 'formula',
-        'value': repr(
+        'type': 'jsonvalue',
+        'value': json.dumps(
             [
                 {'id': 'b', 'text': 'baker', 'extra': 'plop'},
                 {'id': 'c', 'text': 'cook', 'extra': 'plop2'},
-- 
2.35.1

