From 19050af28d74236bff50cf8d227c8897c8c60852 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Fri, 8 Oct 2021 10:57:26 +0200
Subject: [PATCH] backoffice: duplicate page and subfields (#57036)

---
 tests/admin_pages/test_form.py | 117 ++++++++++++++++++++++++++++++++-
 wcs/admin/fields.py            |  59 ++++++++++++++++-
 2 files changed, 173 insertions(+), 3 deletions(-)

diff --git a/tests/admin_pages/test_form.py b/tests/admin_pages/test_form.py
index 56b9c3b2..1457b4c3 100644
--- a/tests/admin_pages/test_form.py
+++ b/tests/admin_pages/test_form.py
@@ -1414,8 +1414,7 @@ def test_form_duplicate_field(pub):
     formdef.store()
 
     app = login(get_app(pub))
-    resp = app.get('/backoffice/forms/1/')
-    resp = resp.click(href='fields/')
+    resp = app.get('/backoffice/forms/1/fields/')
     assert '1st field' in resp.text
 
     resp = resp.click(href='1/duplicate')
@@ -1426,6 +1425,120 @@ def test_form_duplicate_field(pub):
     assert FormDef.get(1).fields[1].label == '1st field'
 
 
+def test_form_duplicate_page_field(pub):
+    create_superuser(pub)
+    create_role(pub)
+
+    FormDef.wipe()
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.PageField(id='0', label='1st page', type='page'),
+        fields.StringField(id='1', label='1st field', type='string', varname='foobar'),
+        fields.PageField(id='2', label='2nd page', type='page'),
+        fields.StringField(id='3', label='2nd field', type='string', varname='baz'),
+    ]
+    formdef.store()
+
+    app = login(get_app(pub))
+    resp = app.get('/backoffice/forms/1/fields/')
+
+    # duplicate 1st page only
+    resp = resp.click(href='0/duplicate')
+    assert 'Also duplicate all fields of the page' in resp.text
+    resp = resp.form.submit().follow()
+    assert [f.label for f in FormDef.get(1).fields] == [
+        '1st page',
+        '1st field',
+        '1st page',
+        '2nd page',
+        '2nd field',
+    ]
+    assert [str(f.id) for f in FormDef.get(1).fields] == ['0', '1', '4', '2', '3']
+
+    # duplicate 1st page and fields
+    resp = resp.click(href='0/duplicate')
+    assert 'Also duplicate all fields of the page' in resp.text
+    resp.form['duplicate_fields'] = True
+    resp = resp.form.submit().follow()
+    assert [f.label for f in FormDef.get(1).fields] == [
+        '1st page',
+        '1st field',
+        '1st page',
+        '1st field',
+        '1st page',
+        '2nd page',
+        '2nd field',
+    ]
+    assert [str(f.id) for f in FormDef.get(1).fields] == ['0', '1', '5', '6', '4', '2', '3']
+
+    # duplicate copy of 1st page without fields
+    resp = resp.click(href='4/duplicate')
+    assert 'Also duplicate all fields of the page' not in resp.text
+    resp = resp.form.submit().follow()
+    assert [f.label for f in FormDef.get(1).fields] == [
+        '1st page',
+        '1st field',
+        '1st page',
+        '1st field',
+        '1st page',
+        '1st page',
+        '2nd page',
+        '2nd field',
+    ]
+    assert [str(f.id) for f in FormDef.get(1).fields] == ['0', '1', '5', '6', '4', '7', '2', '3']
+
+    # duplicate last page and fields
+    resp = resp.click(href='2/duplicate')
+    assert 'Also duplicate all fields of the page' in resp.text
+    resp.form['duplicate_fields'] = True
+    resp = resp.form.submit().follow()
+    assert [f.label for f in FormDef.get(1).fields] == [
+        '1st page',
+        '1st field',
+        '1st page',
+        '1st field',
+        '1st page',
+        '1st page',
+        '2nd page',
+        '2nd field',
+        '2nd page',
+        '2nd field',
+    ]
+    assert [str(f.id) for f in FormDef.get(1).fields] == ['0', '1', '5', '6', '4', '7', '2', '3', '8', '9']
+
+    # duplicate last page only
+    resp = resp.click(href='8/duplicate')
+    assert 'Also duplicate all fields of the page' in resp.text
+    resp = resp.form.submit().follow()
+    assert [f.label for f in FormDef.get(1).fields] == [
+        '1st page',
+        '1st field',
+        '1st page',
+        '1st field',
+        '1st page',
+        '1st page',
+        '2nd page',
+        '2nd field',
+        '2nd page',
+        '2nd field',
+        '2nd page',
+    ]
+    assert [str(f.id) for f in FormDef.get(1).fields] == [
+        '0',
+        '1',
+        '5',
+        '6',
+        '4',
+        '7',
+        '2',
+        '3',
+        '8',
+        '9',
+        '10',
+    ]
+
+
 def test_form_duplicate_file_field(pub):
     create_superuser(pub)
     create_role(pub)
diff --git a/wcs/admin/fields.py b/wcs/admin/fields.py
index 1dde3496..afe72c6d 100644
--- a/wcs/admin/fields.py
+++ b/wcs/admin/fields.py
@@ -185,6 +185,8 @@ class FieldDefPage(Directory):
             return self.redirect_field_anchor(above_field)
 
     def duplicate(self):
+        if self.field.type == 'page':
+            return self.duplicate_page()
         field_pos = self.objectdef.fields.index(self.field)
         fields = self.objectdef.fields
         new_field = copy.deepcopy(self.field)
@@ -194,6 +196,61 @@ class FieldDefPage(Directory):
         self.objectdef.store(comment=_('Duplication of field "%s"') % self.field.unhtmled_label)
         return self.redirect_field_anchor(new_field)
 
+    def duplicate_page(self):
+        form = Form(enctype='multipart/form-data')
+        ellipsized_field_label = misc.ellipsize(self.field.unhtmled_label, 60)
+        duplicate_top_title = _('Duplicate Page')
+        duplicate_title = _('Duplicating Page: %s') % ellipsized_field_label
+        duplicate_message = _("You are about to duplicate the \"%s\" page.") % ellipsized_field_label
+        form.widgets.append(HtmlWidget('<p>%s</p>' % duplicate_message))
+        current_field_index = self.objectdef.fields.index(self.field)
+        page_fields = []
+        # get fields of the page
+        for index in range(current_field_index + 1, len(self.objectdef.fields)):
+            field = self.objectdef.fields[index]
+            if field.type == 'page':
+                # next page found; break
+                break
+            page_fields.append(field)
+        # add duplicate_fields checkbox only if the page has fields
+        if page_fields:
+            form.add(CheckboxWidget, 'duplicate_fields', title=_('Also duplicate all fields of the page'))
+        form.add_submit('submit', _('Duplicate'))
+        form.add_submit("cancel", _("Cancel"))
+        if form.get_widget('cancel').parse():
+            return self.redirect_field_anchor(self.field)
+        if not form.is_submitted() or form.has_errors():
+            get_response().breadcrumb.append(('duplicate', _('Duplicate')))
+            self.html_top(title=duplicate_top_title)
+            r = TemplateIO(html=True)
+            r += htmltext('<h2>%s</h2>') % duplicate_title
+            r += form.render()
+            return r.getvalue()
+        else:
+            duplicate_fields = form.get_widget('duplicate_fields')
+            to_be_duplicated = [self.field]
+            if duplicate_fields and duplicate_fields.parse():
+                # duplicate page fields if requested
+                to_be_duplicated += page_fields
+            new_fields = []
+            # duplicate fields
+            for field in to_be_duplicated:
+                new_field = copy.deepcopy(field)
+                # allocate a new id
+                new_field.id = self.objectdef.get_new_field_id()
+                new_fields.append(new_field)
+            # insert new fields
+            last_duplicated_field_index = self.objectdef.fields.index(([self.field] + page_fields)[-1])
+            self.objectdef.fields = (
+                self.objectdef.fields[: last_duplicated_field_index + 1]
+                + new_fields
+                + self.objectdef.fields[last_duplicated_field_index + 1 :]
+            )
+            # and store them
+            self.objectdef.store(comment=_('Duplication of field "%s"') % self.field.ellipsized_label)
+            # redirect to the new page field
+            return self.redirect_field_anchor(new_fields[0])
+
 
 class FieldsPagesDirectory(Directory):
     def __init__(self, parent):
@@ -342,7 +399,7 @@ class FieldsDirectory(Directory):
                         )
                     r += command_icon('%s/' % field.id, 'edit')
                 if not self.objectdef.is_readonly():
-                    r += command_icon('%s/duplicate' % field.id, 'duplicate')
+                    r += command_icon('%s/duplicate' % field.id, 'duplicate', popup=(field.type == 'page'))
                     r += command_icon('%s/delete' % field.id, 'remove', popup=True)
                 r += htmltext('</p></li>')
             r += htmltext('</ul>')
-- 
2.33.0

