From 1e5b0bc979057072eb880a6602fc7a30a3575493 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Thu, 7 Jan 2021 14:42:01 +0100
Subject: [PATCH] admin: option to move fields when a page is moved (#49585)

---
 tests/admin_pages/test_form.py  | 74 ++++++++++++++++++++++++++++--
 wcs/admin/fields.py             | 81 ++++++++++++++++++++++++++++++---
 wcs/qommon/static/js/biglist.js | 12 ++++-
 3 files changed, 157 insertions(+), 10 deletions(-)

diff --git a/tests/admin_pages/test_form.py b/tests/admin_pages/test_form.py
index 7d0574c8..d90f249e 100644
--- a/tests/admin_pages/test_form.py
+++ b/tests/admin_pages/test_form.py
@@ -2033,16 +2033,84 @@ def test_form_fields_reorder(pub):
     formdef.store()
 
     app = login(get_app(pub))
-    app.get('/backoffice/forms/%s/fields/update_order?order=0;3;1;2;' % formdef.id)
+
+    # missing element in params: do nothing
+    resp = app.get('/backoffice/forms/%s/fields/update_order?order=0;3;1;2;' % formdef.id)
+    assert resp.json == {'success': 'ko'}
+    # missing order in params: do nothing
+    resp = app.get('/backoffice/forms/%s/fields/update_order?element=0' % formdef.id)
+    assert resp.json == {'success': 'ko'}
+
+    resp = app.get('/backoffice/forms/%s/fields/update_order?order=0;3;1;2;&element=3' % formdef.id)
+    assert resp.json == {'success': 'ok'}
     formdef = FormDef.get(formdef.id)
     assert [x.id for x in formdef.fields] == ['0', '3', '1', '2']
 
     # unknown id: ignored
-    app.get('/backoffice/forms/%s/fields/update_order?order=0;1;2;3;4;' % formdef.id)
+    resp = app.get('/backoffice/forms/%s/fields/update_order?order=0;1;2;3;4;&element=3' % formdef.id)
+    assert resp.json == {'success': 'ok'}
     formdef = FormDef.get(formdef.id)
     assert [x.id for x in formdef.fields] == ['0', '1', '2', '3']
     # missing id: do nothing
-    app.get('/backoffice/forms/%s/fields/update_order?order=0;3;1;' % formdef.id)
+    resp = app.get('/backoffice/forms/%s/fields/update_order?order=0;3;1;&element=3' % formdef.id)
+    assert resp.json == {'success': 'ko'}
+    formdef = FormDef.get(formdef.id)
+    assert [x.id for x in formdef.fields] == ['0', '1', '2', '3']
+
+    # move a page
+    resp = app.get('/backoffice/forms/%s/fields/update_order?order=2;0;1;3;&element=2' % formdef.id)
+    assert resp.json == {'success': 'ok', 'additional-action': {
+        'message': 'Also move the fields of the page?', 'url': 'move_page_fields?fields=3&page=2'}}
+    # reset
+    resp = app.get('/backoffice/forms/%s/fields/update_order?order=0;1;2;3;&element=2' % formdef.id)
+    assert resp.json == {'success': 'ok'}
+    # move the first page
+    resp = app.get('/backoffice/forms/%s/fields/update_order?order=1;2;3;0;&element=0' % formdef.id)
+    assert resp.json == {'success': 'ok', 'additional-action': {
+        'message': 'Also move the fields of the page?', 'url': 'move_page_fields?fields=1&page=0'}}
+
+
+def test_form_move_page_fields(pub):
+    create_superuser(pub)
+    create_role()
+
+    FormDef.wipe()
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.PageField(id='2', label='2nd page', type='page'),
+        fields.PageField(id='0', label='1st page', type='page'),
+        fields.StringField(id='1', label='string', type='string'),
+        fields.StringField(id='3', label='string 2', type='string')
+    ]
+    formdef.store()
+
+    app = login(get_app(pub))
+    # missing element in params: do nothing
+    app.get('/backoffice/forms/%s/fields/move_page_fields?fields=3' % formdef.id)
+    formdef = FormDef.get(formdef.id)
+    assert [x.id for x in formdef.fields] == ['2', '0', '1', '3']
+    # missing order in params: do nothing
+    app.get('/backoffice/forms/%s/fields/move_page_fields?page=2' % formdef.id)
+    formdef = FormDef.get(formdef.id)
+    assert [x.id for x in formdef.fields] == ['2', '0', '1', '3']
+
+    # unknown id: do nothing
+    app.get('/backoffice/forms/%s/fields/move_page_fields?fields=4&page=2' % formdef.id)
+    formdef = FormDef.get(formdef.id)
+    assert [x.id for x in formdef.fields] == ['2', '0', '1', '3']
+
+    # move the fields of the page
+    app.get('/backoffice/forms/%s/fields/move_page_fields?fields=3&page=2' % formdef.id)
+    formdef = FormDef.get(formdef.id)
+    assert [x.id for x in formdef.fields] == ['2', '3', '0', '1']
+
+    # move the new first page
+    app.get('/backoffice/forms/%s/fields/update_order?order=3;0;1;2&element=2' % formdef.id)
+    formdef = FormDef.get(formdef.id)
+    assert [x.id for x in formdef.fields] == ['3', '0', '1', '2']
+    # and the fields
+    app.get('/backoffice/forms/%s/fields/move_page_fields?fields=3&page=2' % formdef.id)
     formdef = FormDef.get(formdef.id)
     assert [x.id for x in formdef.fields] == ['0', '1', '2', '3']
 
diff --git a/wcs/admin/fields.py b/wcs/admin/fields.py
index 6a6b32dc..c491313d 100644
--- a/wcs/admin/fields.py
+++ b/wcs/admin/fields.py
@@ -17,6 +17,7 @@
 # along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 import copy
+import json
 
 from quixote import redirect
 from quixote.directory import Directory
@@ -212,7 +213,7 @@ class FieldsPagesDirectory(Directory):
 
 
 class FieldsDirectory(Directory):
-    _q_exports = ['', 'update_order', 'new', 'pages']
+    _q_exports = ['', 'update_order', 'move_page_fields', 'new', 'pages']
     field_def_page_class = FieldDefPage
     blacklisted_types = []
     page_id = None
@@ -393,19 +394,87 @@ class FieldsDirectory(Directory):
         pass
 
     def update_order(self):
+        get_response().set_content_type('application/json')
         request = get_request()
+
+        if 'element' not in request.form:
+            return json.dumps({'success': 'ko'})
+        if 'order' not in request.form:
+            return json.dumps({'success': 'ko'})
+
+        dropped_element = request.form['element']
+        dropped_page_index = None
+
         new_order = request.form['order'].strip(';').split(';')
         new_fields = []
+
+        # build new ordered field list
         for y in new_order:
-            for x in self.objectdef.fields:
-                if x.id == y:
-                    new_fields.append(x)
+            for i, x in enumerate(self.objectdef.fields):
+                if x.id != y:
+                    continue
+                new_fields.append(x)
+                # if dropped field is a page, keep it's old index
+                if x.id == dropped_element and x.type == 'page':
+                    dropped_page_index = i
+                break
+
+        # get the list of dropped page fields from old field list
+        page_field_ids = []
+        if dropped_page_index is not None:
+            for field in self.objectdef.fields[dropped_page_index + 1:]:
+                if field.type == 'page':
+                    # next page found; break
                     break
+                page_field_ids.append(field.id)
+
+        # check new field list composition
+        if set(self.objectdef.fields) != set(new_fields):
+            return json.dumps({'success': 'ko'})
+
+        self.objectdef.fields = new_fields
+        self.objectdef.store(comment=_('Change in order of fields'))
+
+        if not page_field_ids:
+            return json.dumps({'success': 'ok'})
+
+        # propose to move also page fields
+        return json.dumps({
+            'success': 'ok',
+            'additional-action': {
+                'message': _('Also move the fields of the page?'),
+                'url': 'move_page_fields?fields=%s&page=%s' % (';'.join(page_field_ids), dropped_element)
+            }
+        })
+
+    def move_page_fields(self):
+        request = get_request()
+
+        if 'fields' not in request.form:
+            return redirect('.')
+        if 'page' not in request.form:
+            return redirect('.')
+
+        field_ids = request.form['fields'].strip(';').split(';')
+        # keep all fields except page fields
+        new_fields = [f for f in self.objectdef.fields if f.id not in field_ids]
+        # find page fields
+        page_fields = [f for f in self.objectdef.fields if f.id in field_ids]
+        # find page in new fields, and insert page_fields
+        for i, field in enumerate(new_fields):
+            if field.id != request.form['page']:
+                continue
+            new_fields = new_fields[:i+1] + page_fields + new_fields[i+1:]
+            break
+
+        # check new field list composition
         if set(self.objectdef.fields) != set(new_fields):
-            return 'ko'
+            return redirect('.')
+
         self.objectdef.fields = new_fields
         self.objectdef.store(comment=_('Change in order of fields'))
-        return 'ok'
+
+        return redirect('.')
 
     def new(self):
         form = Form(enctype='multipart/form-data', action = 'new')
diff --git a/wcs/qommon/static/js/biglist.js b/wcs/qommon/static/js/biglist.js
index e3a06268..a62ceb18 100644
--- a/wcs/qommon/static/js/biglist.js
+++ b/wcs/qommon/static/js/biglist.js
@@ -35,8 +35,18 @@ $(document).ready(
                           page_index += 1;
                         }
                     }
+                    $('.move-page-fields').remove();
                     var order_function = $(this).data('order-function') || 'update_order';
-                    $.post(order_function, {'order': result});
+                    $.post(order_function, {'order': result, 'element': $(ui.item)[0].id.substr(7, 50)})
+                    .done(function(data) {
+                        if (data['success'] != "ok") return;
+                        if (!data['additional-action']) return;
+                        $div = $('<div class="move-page-fields"></div>');
+                        $div.append('<a href="' + data['additional-action']['url'] + '">' + data['additional-action']['message'] + '</a>')
+                        $div.dialog({
+                            position: { my: "left top", at: "left bottom", of: $(ui.item) },
+                        });
+                    });
                 },
             }
         );
-- 
2.29.2

