From 27ceb72998f6a10ddfe3c26221e3126e37b2067c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Tue, 18 Jan 2022 14:28:30 +0100
Subject: [PATCH 7/8] api: allow formdata sorting on varname (#60706)

---
 tests/api/test_formdata.py          | 53 +++++++++++++++++++++++---
 tests/backoffice_pages/test_sort.py | 58 ++++++++++++++++++++++++++++-
 wcs/forms/backoffice.py             | 15 ++++++++
 3 files changed, 119 insertions(+), 7 deletions(-)

diff --git a/tests/api/test_formdata.py b/tests/api/test_formdata.py
index 77338372..a18e923f 100644
--- a/tests/api/test_formdata.py
+++ b/tests/api/test_formdata.py
@@ -627,7 +627,7 @@ def test_api_list_formdata(pub, local_user):
         formdata = data_class()
         upload = PicklableUpload('test.txt', 'text/plain', 'ascii')
         upload.receive([b'base64me'])
-        formdata.data = {'0': 'FOO BAR %d' % i, '2': upload}
+        formdata.data = {'0': 'FOO BAR %02d' % i, '2': upload}
         formdata.user_id = local_user.id
         if i % 4 == 0:
             formdata.data['1'] = 'foo'
@@ -683,16 +683,18 @@ def test_api_list_formdata(pub, local_user):
     assert resp.json[0]['evolution'][0]['who']['id'] == local_user.id
 
     assert all('status' in x['workflow'] for x in resp.json)
-    assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 0'][0]['submission'][
+    assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 00'][0]['submission'][
         'backoffice'
     ] is True
-    assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 0'][0]['submission'][
+    assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 00'][0]['submission'][
         'channel'
     ] == 'mail'
-    assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 1'][0]['submission'][
+    assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 01'][0]['submission'][
         'backoffice'
     ] is False
-    assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 1'][0]['submission']['channel'] == 'web'
+    assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 01'][0]['submission'][
+        'channel'
+    ] == 'web'
 
     # check filtered results
     resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter-foobar3=foo', user=local_user))
@@ -702,7 +704,7 @@ def test_api_list_formdata(pub, local_user):
     resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter-foobar3=baz', user=local_user))
     assert len(resp.json) == 14
 
-    resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter-foobar=FOO BAR 3', user=local_user))
+    resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter-foobar=FOO BAR 03', user=local_user))
     assert len(resp.json) == 1
 
     # check filter on status
@@ -765,6 +767,20 @@ def test_api_list_formdata(pub, local_user):
     get_app(pub).get(sign_uri('/api/forms/test/list?filter=all&offset=plop', user=local_user), status=400)
     get_app(pub).get(sign_uri('/api/forms/test/list?filter=all&limit=plop', user=local_user), status=400)
 
+    if pub.is_using_postgresql():
+        # just check ordering
+        resp = get_app(pub).get(sign_uri('/api/forms/test/list?full=on&order_by=f0', user=local_user))
+        assert [d['fields']['foobar'] for d in resp.json] == ['FOO BAR %02d' % i for i in range(0, 30)]
+
+        resp = get_app(pub).get(sign_uri('/api/forms/test/list?full=on&order_by=-f0', user=local_user))
+        assert [d['fields']['foobar'] for d in resp.json] == ['FOO BAR %02d' % i for i in range(29, -1, -1)]
+
+        resp = get_app(pub).get(sign_uri('/api/forms/test/list?full=on&order_by=foobar', user=local_user))
+        assert [d['fields']['foobar'] for d in resp.json] == ['FOO BAR %02d' % i for i in range(0, 30)]
+
+        resp = get_app(pub).get(sign_uri('/api/forms/test/list?full=on&order_by=-foobar', user=local_user))
+        assert [d['fields']['foobar'] for d in resp.json] == ['FOO BAR %02d' % i for i in range(29, -1, -1)]
+
 
 def test_api_list_formdata_date_filter(pub, local_user):
     if not pub.is_using_postgresql():
@@ -993,6 +1009,31 @@ def test_api_list_formdata_block_field_filter(pub, local_user):
     )
     assert len(resp.json) == 1
 
+    # just check ordering
+    resp = get_app(pub).get(sign_uri('/api/forms/test/list?full=on&order_by=f0-1', user=local_user))
+    assert [d['fields']['blockdata_raw'][0]['string'] for d in resp.json] == [
+        'plop%s' % i for i in range(0, 10)
+    ]
+
+    resp = get_app(pub).get(sign_uri('/api/forms/test/list?full=on&order_by=-f0-1', user=local_user))
+    assert [d['fields']['blockdata_raw'][0]['string'] for d in resp.json] == [
+        'plop%s' % i for i in range(9, -1, -1)
+    ]
+
+    resp = get_app(pub).get(
+        sign_uri('/api/forms/test/list?full=on&order_by=blockdata_string', user=local_user)
+    )
+    assert [d['fields']['blockdata_raw'][0]['string'] for d in resp.json] == [
+        'plop%s' % i for i in range(0, 10)
+    ]
+
+    resp = get_app(pub).get(
+        sign_uri('/api/forms/test/list?full=on&order_by=-blockdata_string', user=local_user)
+    )
+    assert [d['fields']['blockdata_raw'][0]['string'] for d in resp.json] == [
+        'plop%s' % i for i in range(9, -1, -1)
+    ]
+
 
 def test_api_anonymized_formdata(pub, local_user, admin_user):
     pub.role_class.wipe()
diff --git a/tests/backoffice_pages/test_sort.py b/tests/backoffice_pages/test_sort.py
index a7e2d1a0..e464624f 100644
--- a/tests/backoffice_pages/test_sort.py
+++ b/tests/backoffice_pages/test_sort.py
@@ -232,6 +232,52 @@ def test_backoffice_criticality_in_global_listing_order(pub):
     assert resp.text.index(formdata1_str) > resp.text.index(formdata4_str)
 
 
+def test_backoffice_varname_order(pub):
+    pub.user_class.wipe()
+    create_superuser(pub)
+    pub.role_class.wipe()
+    role = pub.role_class(name='test')
+    role.store()
+
+    FormDef.wipe()
+    formdef = FormDef()
+    formdef.name = 'form-title'
+    formdef.fields = [
+        fields.StringField(id='1', label='Foo', type='string', varname='foo'),
+    ]
+    formdef.workflow_roles = {'_receiver': role.id}
+    formdef.store()
+
+    data_class = formdef.data_class()
+    data_class.wipe()
+
+    formdata_ids = []
+    for i in range(0, 2):
+        formdata = data_class()
+        formdata.data = {'1': 'Bar %s' % i}
+        formdata.just_created()
+        formdata.jump_status('new')
+        formdata.store()
+        formdata_ids.append(formdata.id)
+
+    app = login(get_app(pub))
+    resp = app.get('/backoffice/management/form-title/?order_by=f1')
+    ids = [int(x.strip('/')) for x in re.findall(r'data-link="(.*?)"', resp.text)]
+    assert ids == formdata_ids
+
+    resp = app.get('/backoffice/management/form-title/?order_by=-f1')
+    ids = [int(x.strip('/')) for x in re.findall(r'data-link="(.*?)"', resp.text)]
+    assert ids == list(reversed(formdata_ids))
+
+    resp = app.get('/backoffice/management/form-title/?order_by=foo')
+    ids = [int(x.strip('/')) for x in re.findall(r'data-link="(.*?)"', resp.text)]
+    assert ids == formdata_ids
+
+    resp = app.get('/backoffice/management/form-title/?order_by=-foo')
+    ids = [int(x.strip('/')) for x in re.findall(r'data-link="(.*?)"', resp.text)]
+    assert ids == list(reversed(formdata_ids))
+
+
 def test_backoffice_block_columns_order(pub):
     pub.user_class.wipe()
     create_superuser(pub)
@@ -267,7 +313,9 @@ def test_backoffice_block_columns_order(pub):
     block.name = 'foobar'
     block.fields = [
         fields.StringField(id='123', required=True, label='Test', type='string'),
-        fields.ItemField(id='456', label='card field', type='item', data_source={'type': 'carddef:foo'}),
+        fields.ItemField(
+            id='456', label='card field', type='item', data_source={'type': 'carddef:foo'}, varname='item'
+        ),
     ]
     block.store()
 
@@ -328,3 +376,11 @@ def test_backoffice_block_columns_order(pub):
     resp = app.get('/backoffice/management/form-title/?order_by=-f8-456')
     ids = [int(x.strip('/')) for x in re.findall(r'data-link="(.*?)"', resp.text)]
     assert ids == [formdata3.id, formdata2.id, formdata1.id]
+
+    resp = app.get('/backoffice/management/form-title/?order_by=data_item')
+    ids = [int(x.strip('/')) for x in re.findall(r'data-link="(.*?)"', resp.text)]
+    assert ids == [formdata1.id, formdata2.id, formdata3.id]
+
+    resp = app.get('/backoffice/management/form-title/?order_by=-data_item')
+    ids = [int(x.strip('/')) for x in re.findall(r'data-link="(.*?)"', resp.text)]
+    assert ids == [formdata3.id, formdata2.id, formdata1.id]
diff --git a/wcs/forms/backoffice.py b/wcs/forms/backoffice.py
index 420894fd..e39f8ca0 100644
--- a/wcs/forms/backoffice.py
+++ b/wcs/forms/backoffice.py
@@ -220,12 +220,20 @@ class FormDefUI:
         def iter_fields(fields, block_field=None):
             for field in fields:
                 field.contextual_id = field.id
+                field.contextual_varname = None
                 if block_field:
                     if field.key == 'items':
                         # not yet
                         continue
                     field.block_field = block_field
                     field.contextual_id = '%s-%s' % (field.block_field.id, field.id)
+                    if field.varname and field.block_field.varname:
+                        field.contextual_varname = '%s_%s' % (
+                            field.block_field.varname,
+                            field.varname,
+                        )
+                else:
+                    field.contextual_varname = field.varname
                 yield field
                 if field.key == 'block':
                     yield from iter_fields(field.block.fields, block_field=field)
@@ -236,10 +244,17 @@ class FormDefUI:
             if order_by.startswith('-'):
                 order_by = order_by[1:]
                 direction = '-'
+            field_found = False
             for field in iter_fields(self.formdef.get_all_fields()):
+                if order_by in [field.contextual_varname, 'f%s' % field.contextual_id]:
+                    field_found = True
+                if field.contextual_varname == order_by:
+                    order_by = "f%s" % field.contextual_id
                 if getattr(field, 'block_field', None) and 'f%s' % field.contextual_id == order_by:
                     # field of block field, sort on the first element
                     order_by = "f%s->'data'->0->>'%s'" % (field.block_field.id, field.id)
+                if field_found:
+                    break
             order_by = '%s%s' % (direction, order_by)
             ordered_ids = formdata_class.get_sorted_ids(order_by, clause=criterias)
             item_ids_dict = {x: True for x in item_ids}
-- 
2.34.1

