From 7553154d4ac58342ee0afb5fd087890d641ffe7d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Thu, 7 Jul 2022 10:27:37 +0200
Subject: [PATCH] fields: fix live prefill for items with carddef datasource
 (#59052)

---
 tests/form_pages/test_live.py                 | 83 +++++++++++++++++++
 wcs/fields.py                                 |  7 ++
 wcs/forms/common.py                           |  4 +
 wcs/qommon/static/js/qommon.forms.js          |  8 +-
 .../qommon/forms/widgets/checkboxes.html      |  1 +
 5 files changed, 102 insertions(+), 1 deletion(-)

diff --git a/tests/form_pages/test_live.py b/tests/form_pages/test_live.py
index c4a847be8..47c342f46 100644
--- a/tests/form_pages/test_live.py
+++ b/tests/form_pages/test_live.py
@@ -1184,6 +1184,89 @@ def test_field_live_item_datasource_prefill_with_request_with_q(urlopen, pub):
     assert urlopen.call_args[0][0] == 'http://remote.example.net/json?plop=&q=deux'
 
 
+def test_field_live_items_datasource_carddef_prefill(pub, http_requests):
+    CardDef.wipe()
+
+    carddef_related = CardDef()
+    carddef_related.name = 'bar'
+    carddef_related.digest_templates = {'default': '{{ form_var_bar }}'}
+    carddef_related.fields = [
+        fields.StringField(id='1', type='string', label='string', varname='bar'),
+    ]
+    carddef_related.store()
+    carddef_related.data_class().wipe()
+    for value in ['A', 'B', 'C']:
+        carddata = carddef_related.data_class()()
+        carddata.data = {
+            '1': value,
+        }
+        carddata.just_created()
+        carddata.store()
+
+    carddef = CardDef()
+    carddef.name = 'foo'
+    carddef.digest_templates = {'default': '{{ form_var_foo }}'}
+    carddef.fields = [
+        fields.StringField(id='1', type='string', label='string', varname='foo'),
+        fields.ItemsField(
+            type='items',
+            id='2',
+            varname='items',
+            data_source={'type': 'carddef:bar'},
+        ),
+    ]
+    carddef.store()
+    carddef.data_class().wipe()
+    carddata1 = carddef.data_class()()
+    carddata1.data = {
+        '1': 'bar',
+        '2': ['1'],
+        '2_display': ['A'],
+    }
+    carddata1.just_created()
+    carddata1.store()
+    carddata2 = carddef.data_class()()
+    carddata2.data = {
+        '1': 'baz',
+        '2': ['2', '3'],
+        '2_display': 'B,C',
+    }
+    carddata2.just_created()
+    carddata2.store()
+
+    FormDef.wipe()
+    formdef = FormDef()
+    formdef.name = 'Foo'
+    formdef.fields = [
+        fields.ItemField(
+            type='item', id='1', label='foo', varname='foo', data_source={'type': 'carddef:foo'}
+        ),
+        fields.ItemsField(
+            type='items',
+            id='2',
+            label='items',
+            varname='items',
+            prefill={'type': 'string', 'value': '{{ form_var_foo_live_var_items }}'},
+            data_source={'type': 'carddef:bar'},
+        ),
+    ]
+    formdef.store()
+    formdef.data_class().wipe()
+
+    app = get_app(pub)
+    resp = app.get('/foo/')
+    assert resp.html.find('div', {'data-field-id': '1'}).attrs['data-live-source'] == 'true'
+    assert resp.pyquery('#var_items.widget-prefilled')  # second field is marked as prefilled
+    resp.form['f1'] = str(carddata2.id)
+    live_resp = app.post('/foo/live?modified_field_id=1&prefilled_2=on', params=resp.form.submit_fields())
+    assert live_resp.json['result']['2'] == {'visible': True, 'content': ['2', '3']}
+
+    resp.form['f2$element1'] = True  # manually changed -> widget-prefilled class will be removed
+    resp.form['f1'] = str(carddata1.id)
+    live_resp = app.post('/foo/live?modified_field_id=1', params=resp.form.submit_fields())
+    assert live_resp.json['result']['2'] == {'visible': True}
+
+
 def test_field_live_block_string_prefill(pub, http_requests):
     FormDef.wipe()
     BlockDef.wipe()
diff --git a/wcs/fields.py b/wcs/fields.py
index bd0f1924e..719eb75b1 100644
--- a/wcs/fields.py
+++ b/wcs/fields.py
@@ -2483,6 +2483,13 @@ class ItemsField(WidgetField, ItemFieldMixin):
         else:
             return []
 
+    def get_ids_by_option_text(self, text_values):
+        if self.data_source:
+            ids = [data_sources.get_id_by_option_text(self.data_source, v) for v in text_values]
+            ids = [i for i in ids if i]
+            return ids
+        return text_values
+
     def perform_more_widget_changes(self, form, kwargs, edit=True):
         kwargs['options'] = self.get_options()
         kwargs['min_choices'] = self.min_choices
diff --git a/wcs/forms/common.py b/wcs/forms/common.py
index 9bb14cd3c..96b2e8c99 100644
--- a/wcs/forms/common.py
+++ b/wcs/forms/common.py
@@ -864,6 +864,10 @@ class FormStatusPage(Directory, FormTemplateMixin):
                         id_value = field.get_id_by_option_text(value)
                         if id_value:
                             value = id_value
+                    elif field.key == 'items' and value:
+                        id_values = field.get_ids_by_option_text([v.strip() for v in value.split(',')])
+                        if id_values:
+                            value = id_values
                     entry['content'] = value
             elif field.prefill and field.prefill.get('type') == 'user':
                 update_prefill = bool(get_request().form.get('modified_field_id') == 'user')
diff --git a/wcs/qommon/static/js/qommon.forms.js b/wcs/qommon/static/js/qommon.forms.js
index 82cfa574a..1b8571664 100644
--- a/wcs/qommon/static/js/qommon.forms.js
+++ b/wcs/qommon/static/js/qommon.forms.js
@@ -588,8 +588,14 @@ $(function() {
                     // replace checkbox input value
                     $widget.find('input[type=checkbox]').prop('checked', value.content);
                   }
+                  if ($widget.hasClass('CheckboxesWidget') && Array.isArray(value.content)) {
+                    $($widget).find('input[type=checkbox]').prop('checked', false);
+                    $.each(value.content, function() {
+                      $($widget).find('input[type=checkbox][data-value="' + this + '"]').prop('checked', true);
+                    });
+                  }
                   // replace select value
-                  $(widget).find('select').val(value.content);
+                  $(widget).find('select').val(value.content).trigger('change.select2');
                   // replace radio value
                   $(widget).find('input[type=radio]').prop('checked', false);
                   $(widget).find('input[type=radio][value="'+value.content+'"]').prop('checked', true);
diff --git a/wcs/qommon/templates/qommon/forms/widgets/checkboxes.html b/wcs/qommon/templates/qommon/forms/widgets/checkboxes.html
index c17993b3d..83d60ccf2 100644
--- a/wcs/qommon/templates/qommon/forms/widgets/checkboxes.html
+++ b/wcs/qommon/templates/qommon/forms/widgets/checkboxes.html
@@ -8,6 +8,7 @@
    {% if option.selected %}checked="checked"
    {% elif widget.readonly or option.disabled %}disabled{% endif %}
    {% for attr in widget.attrs.items %}{{attr.0}}="{{attr.1}}" {% endfor %}
+   data-value="{{ option.value }}"
    type="checkbox"
    {% if widget.readonly %}value="yes" disabled {% else %}name="{{ option.name }}" value="yes"{% endif %}
    ><span>{{ option.label }}</span></label>
-- 
2.35.1

