From b0f2ced22ac9700fb5dae12aa05f910dbd29e1ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Fri, 12 Nov 2021 10:06:16 +0100
Subject: [PATCH] blocks: default_items configuration (#58450)

---
 tests/form_pages/test_block.py | 144 +++++++++++++++++++++++++++++++++
 wcs/blocks.py                  |  15 +++-
 wcs/fields.py                  |  17 +++-
 wcs/qommon/form.py             |   5 ++
 4 files changed, 177 insertions(+), 4 deletions(-)

diff --git a/tests/form_pages/test_block.py b/tests/form_pages/test_block.py
index 711c058f..d6a15861 100644
--- a/tests/form_pages/test_block.py
+++ b/tests/form_pages/test_block.py
@@ -895,6 +895,57 @@ def test_block_repeated(pub):
     assert '>bar2<' in resp
 
 
+def test_block_repeated_with_default(pub):
+    FormDef.wipe()
+    BlockDef.wipe()
+
+    block = BlockDef()
+    block.name = 'foobar'
+    block.fields = [
+        fields.StringField(id='123', required=True, label='Test', type='string'),
+        fields.StringField(id='234', required=True, label='Test2', type='string'),
+    ]
+    block.store()
+
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.PageField(id='0', label='1st page', type='page'),
+        fields.BlockField(
+            id='1', label='test', type='block:foobar', default_items=2, max_items=3, hint='hintblock'
+        ),
+        fields.PageField(id='2', label='2nd page', type='page'),
+    ]
+    formdef.store()
+
+    app = get_app(pub)
+    resp = app.get(formdef.get_url())
+    assert resp.text.count('>Test<') == 2
+    assert resp.text.count('>hintblock<') == 1
+    assert 'wcs-block-add-clicked' not in resp
+    assert 'Add another' in resp
+    assert resp.html.find('div', {'class': 'list-add'})
+    resp = resp.form.submit('f1$add_element')
+    assert resp.text.count('>Test<') == 3
+    assert resp.text.count('>hintblock<') == 1
+    assert resp.pyquery('.list-add').attr['style'] == 'display: none'
+
+    formdef.fields[1].default_items = 3
+    formdef.store()
+
+    app = get_app(pub)
+    resp = app.get(formdef.get_url())
+    assert resp.text.count('>Test<') == 3
+    assert resp.text.count('>hintblock<') == 1
+    assert resp.pyquery('.list-add').attr['style'] == 'display: none'
+
+    formdef.fields[1].default_items = 4
+    formdef.store()
+    app = get_app(pub)
+    resp = app.get(formdef.get_url())
+    assert resp.text.count('>Test<') == 3
+
+
 def test_block_repeated_over_limit(pub):
     FormDef.wipe()
     BlockDef.wipe()
@@ -943,6 +994,99 @@ def test_block_repeated_over_limit(pub):
     assert 'Too many elements (maximum: 2)' in resp
 
 
+def test_block_repeated_under_default(pub):
+    FormDef.wipe()
+    BlockDef.wipe()
+
+    block = BlockDef()
+    block.name = 'foobar'
+    block.fields = [
+        fields.StringField(id='123', required=True, label='Test', type='string'),
+        fields.StringField(id='234', required=True, label='Test2', type='string'),
+    ]
+    block.store()
+
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.PageField(id='0', label='1st page', type='page'),
+        fields.BlockField(
+            id='1', label='test', type='block:foobar', default_items=2, max_items=2, remove_button=True
+        ),
+        fields.PageField(id='2', label='2nd page', type='page'),
+        fields.BlockField(
+            id='3', label='test', type='block:foobar', default_items=2, max_items=2, remove_button=True
+        ),
+    ]
+    formdef.store()
+    formdef.data_class().wipe()
+
+    app = get_app(pub)
+    resp = app.get(formdef.get_url())
+    assert resp.text.count('>Test<') == 2
+
+    # fill items
+    resp.form['f1$element0$f123'] = 'foo1'
+    resp.form['f1$element0$f234'] = 'bar1'
+    resp.form['f1$element1$f123'] = 'foo2'
+    resp.form['f1$element1$f234'] = 'bar2'
+
+    resp = resp.form.submit('submit')  # -> 2nd page
+    resp.form['f3$element0$f123'] = 'fooo1'
+    resp.form['f3$element0$f234'] = 'barr1'
+    resp.form['f3$element1$f123'] = 'fooo2'
+    resp.form['f3$element1$f234'] = 'barr2'
+    resp = resp.form.submit('submit')  # -> validation page
+    assert 'Check values then click submit.' in resp.text
+    assert resp.form['f1$element0$f123'].value == 'foo1'
+    assert resp.form['f1$element0$f234'].value == 'bar1'
+    assert resp.form['f1$element1$f123'].value == 'foo2'
+    assert resp.form['f1$element1$f234'].value == 'bar2'
+    assert resp.form['f3$element0$f123'].value == 'fooo1'
+    assert resp.form['f3$element0$f234'].value == 'barr1'
+    assert resp.form['f3$element1$f123'].value == 'fooo2'
+    assert resp.form['f3$element1$f234'].value == 'barr2'
+
+    resp = resp.form.submit('previous')  # -> 2nd page
+    # simulate javascript removing of block elements from DOM
+    resp.form.field_order.remove(('f3$element0$f123', resp.form.fields['f3$element0$f123'][0]))
+    del resp.form.fields['f3$element0$f123']
+    resp.form.field_order.remove(('f3$element0$f234', resp.form.fields['f3$element0$f234'][0]))
+    del resp.form.fields['f3$element0$f234']
+    resp = resp.form.submit('previous')  # -> 1st page
+    # simulate javascript removing of block elements from DOM
+    resp.form.field_order.remove(('f1$element0$f123', resp.form.fields['f1$element0$f123'][0]))
+    del resp.form.fields['f1$element0$f123']
+    resp.form.field_order.remove(('f1$element0$f234', resp.form.fields['f1$element0$f234'][0]))
+    del resp.form.fields['f1$element0$f234']
+
+    resp = resp.form.submit('submit')  # -> 2nd page
+    assert resp.form['f3$element0$f123'].value == 'fooo2'
+    assert resp.form['f3$element0$f234'].value == 'barr2'
+    assert 'f3$element1$f123' in resp.form.fields
+    assert 'f3$element1$f234' in resp.form.fields
+    resp = resp.form.submit('previous')  # -> 1st page
+    assert resp.form['f1$element0$f123'].value == 'foo2'
+    assert resp.form['f1$element0$f234'].value == 'bar2'
+    assert 'f1$element1$f123' in resp.form.fields
+    assert 'f1$element1$f234' in resp.form.fields
+
+    resp = resp.form.submit('submit')  # -> 2nd page
+    resp = resp.form.submit('submit')  # -> validation page
+    assert resp.form['f1$element0$f123'].value == 'foo2'
+    assert resp.form['f1$element0$f234'].value == 'bar2'
+    assert 'f1$element1$f123' not in resp.form.fields
+    assert 'f1$element1$f234' not in resp.form.fields
+    assert resp.form['f3$element0$f123'].value == 'fooo2'
+    assert resp.form['f3$element0$f234'].value == 'barr2'
+    assert 'f3$element1$f123' not in resp.form.fields
+    assert 'f3$element1$f234' not in resp.form.fields
+
+    resp = resp.form.submit('submit')  # -> submit
+    assert len(formdef.data_class().select()[0].data['1']['data']) == 1
+    assert len(formdef.data_class().select()[0].data['3']['data']) == 1
+
+
 def test_block_repeated_files(pub):
     FormDef.wipe()
     BlockDef.wipe()
diff --git a/wcs/blocks.py b/wcs/blocks.py
index a1ec62ce..263e6aa3 100644
--- a/wcs/blocks.py
+++ b/wcs/blocks.py
@@ -245,7 +245,15 @@ class BlockWidget(WidgetList):
     always_include_add_button = True
 
     def __init__(
-        self, name, value=None, title=None, block=None, max_items=None, add_element_label=None, **kwargs
+        self,
+        name,
+        value=None,
+        title=None,
+        block=None,
+        default_items=None,
+        max_items=None,
+        add_element_label=None,
+        **kwargs,
     ):
         self.block = block
         self.readonly = kwargs.get('readonly')
@@ -254,8 +262,8 @@ class BlockWidget(WidgetList):
         element_values = None
         if value:
             element_values = value.get('data')
-        if not max_items:
-            max_items = 1
+        max_items = max_items or 1
+        default_items = min(default_items or 1, max_items)
         hint = kwargs.pop('hint', None)
         element_kwargs = {'block': self.block, 'render_br': False, 'remove_button': self.remove_button}
         element_kwargs.update(kwargs)
@@ -263,6 +271,7 @@ class BlockWidget(WidgetList):
             name,
             value=element_values,
             title=title,
+            default_items=default_items,
             max_items=max_items,
             element_type=BlockSubWidget,
             element_kwargs=element_kwargs,
diff --git a/wcs/fields.py b/wcs/fields.py
index 3ddd95ed..5432d36b 100644
--- a/wcs/fields.py
+++ b/wcs/fields.py
@@ -3323,8 +3323,16 @@ class BlockField(WidgetField):
     allow_complex = True
 
     widget_class = BlockWidget
+    default_items = 1
     max_items = 1
-    extra_attributes = ['block', 'max_items', 'add_element_label', 'label_display', 'remove_button']
+    extra_attributes = [
+        'block',
+        'default_items',
+        'max_items',
+        'add_element_label',
+        'label_display',
+        'remove_button',
+    ]
     add_element_label = ''
     label_display = 'normal'
     remove_button = False
@@ -3346,6 +3354,12 @@ class BlockField(WidgetField):
         super().fill_admin_form(form)
         if form.get_widget('prefill'):
             form.remove('prefill')
+        form.add(
+            IntWidget,
+            'default_items',
+            title=_('Number of items to display by default'),
+            value=self.default_items,
+        )
         form.add(IntWidget, 'max_items', title=_('Maximum number of items'), value=self.max_items)
         form.add(
             StringWidget, 'add_element_label', title=_('Label of "Add" button'), value=self.add_element_label
@@ -3366,6 +3380,7 @@ class BlockField(WidgetField):
 
     def get_admin_attributes(self):
         return super().get_admin_attributes() + [
+            'default_items',
             'max_items',
             'add_element_label',
             'label_display',
diff --git a/wcs/qommon/form.py b/wcs/qommon/form.py
index 82aa5c17..01039c06 100644
--- a/wcs/qommon/form.py
+++ b/wcs/qommon/form.py
@@ -1706,6 +1706,7 @@ class WidgetList(quixote.form.widget.WidgetList):
         element_type=StringWidget,
         element_kwargs=None,
         add_element_label="Add row",
+        default_items=None,
         max_items=None,
         **kwargs,
     ):
@@ -1719,6 +1720,7 @@ class WidgetList(quixote.form.widget.WidgetList):
         self.element_type = element_type
         self.element_kwargs = element_kwargs or {}
         self.element_names = []
+        self.default_items = default_items or 1
         self.max_items = max_items
 
         # Add element widgets for initial value
@@ -1752,6 +1754,9 @@ class WidgetList(quixote.form.widget.WidgetList):
             if self.get('add_element') and (not max_items or current_len < max_items):
                 # add an empty row
                 self.add_element()
+            # Add elements until default_items
+            while len(self.element_names) < self.default_items:
+                self.add_element()
 
     def add_element(self, value=None, element_name=None):
         if element_name:
-- 
2.33.0

