From 9b5e68df90de7ab72eb7190df4bdb282ff679792 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Fri, 24 Apr 2020 14:55:31 +0200
Subject: [PATCH] api/backoffice: add (exact) filters on string, email and date
 fields (#41910)

---
 tests/test_api.py                   |   7 +-
 tests/test_backoffice_pages.py      | 102 ++++++++++++++++++++++++++++
 wcs/backoffice/management.py        |  25 ++++++-
 wcs/qommon/static/js/wcs.listing.js |   4 +-
 4 files changed, 131 insertions(+), 7 deletions(-)

diff --git a/tests/test_api.py b/tests/test_api.py
index 7871ab643..1349ac5a0 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -1665,10 +1665,10 @@ def test_api_list_formdata(pub, local_user):
     formdef.name = 'test'
     formdef.workflow_roles = {'_receiver': role.id}
     formdef.fields = [
-        fields.StringField(id='0', label='foobar', varname='foobar'),
+        fields.StringField(id='0', label='foobar', varname='foobar', type='string'),
         fields.ItemField(id='1', label='foobar3', varname='foobar3', type='item',
             items=['foo', 'bar', 'baz']),
-        fields.FileField(id='2', label='foobar4', varname='file'),
+        fields.FileField(id='2', label='foobar4', varname='file', type='file'),
         ]
     formdef.store()
 
@@ -1747,6 +1747,9 @@ 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))
+    assert len(resp.json) == 1
+
     # check filter on status
     resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter=pending', user=local_user))
     assert len(resp.json) == 20
diff --git a/tests/test_backoffice_pages.py b/tests/test_backoffice_pages.py
index 745ffa32f..23fba1c67 100644
--- a/tests/test_backoffice_pages.py
+++ b/tests/test_backoffice_pages.py
@@ -1011,6 +1011,108 @@ def test_backoffice_items_filter(pub):
         assert resp.text.count('data-link') == 0 # no rows
 
 
+def test_backoffice_string_filter(pub):
+    create_superuser(pub)
+    create_environment(pub)
+    formdef = FormDef.get_by_urlname('form-title')
+    formdef.fields.append(fields.StringField(id='4', label='4th field',
+        type='string',
+        display_locations=['validation', 'summary', 'listings']))
+    formdef.store()
+
+    for i, formdata in enumerate(formdef.data_class().select()):
+        formdata.data['4'] = 'a' if bool(i % 2) else 'b'
+        formdata.store()
+
+    app = login(get_app(pub))
+    resp = app.get('/backoffice/management/form-title/')
+    resp.forms['listing-settings']['filter-4'].checked = True
+    resp = resp.forms['listing-settings'].submit()
+
+    assert resp.forms['listing-settings']['filter-4-value'].value == ''
+
+    resp.forms['listing-settings']['filter-4-value'].value = 'a'
+    resp = resp.forms['listing-settings'].submit()
+    assert resp.text.count('<td>a</td>') > 0
+    assert resp.text.count('<td>b</td>') == 0
+
+    resp.forms['listing-settings']['filter-4-value'].value = 'b'
+    resp = resp.forms['listing-settings'].submit()
+    assert resp.text.count('<td>a</td>') == 0
+    assert resp.text.count('<td>b</td>') > 0
+
+
+def test_backoffice_email_filter(pub):
+    create_superuser(pub)
+    create_environment(pub)
+    formdef = FormDef.get_by_urlname('form-title')
+    formdef.fields.append(fields.EmailField(id='4', label='4th field',
+        type='email',
+        display_locations=['validation', 'summary', 'listings']))
+    formdef.store()
+
+    for i, formdata in enumerate(formdef.data_class().select()):
+        formdata.data['4'] = 'a@localhost' if bool(i % 2) else 'b@localhost'
+        formdata.store()
+
+    app = login(get_app(pub))
+    resp = app.get('/backoffice/management/form-title/')
+    resp.forms['listing-settings']['filter-4'].checked = True
+    resp = resp.forms['listing-settings'].submit()
+
+    assert resp.forms['listing-settings']['filter-4-value'].value == ''
+
+    resp.forms['listing-settings']['filter-4-value'].value = 'a@localhost'
+    resp = resp.forms['listing-settings'].submit()
+    assert resp.text.count('>a@localhost</') > 0
+    assert resp.text.count('>b@localhost</') == 0
+
+    resp.forms['listing-settings']['filter-4-value'].value = 'b@localhost'
+    resp = resp.forms['listing-settings'].submit()
+    assert resp.text.count('>a@localhost</') == 0
+    assert resp.text.count('>b@localhost</') > 0
+
+
+def test_backoffice_date_filter(pub):
+    if not pub.is_using_postgresql():
+        pytest.skip('this requires SQL')
+        return
+    create_superuser(pub)
+    create_environment(pub)
+    formdef = FormDef.get_by_urlname('form-title')
+    formdef.fields.append(fields.DateField(id='4', label='4th field',
+        type='date',
+        display_locations=['validation', 'summary', 'listings']))
+    formdef.store()
+
+    for i, formdata in enumerate(formdef.data_class().select()):
+        formdata.data['4'] = time.strptime('2020-04-24' if bool(i % 2) else '2015-05-12', '%Y-%m-%d')
+        formdata.store()
+
+    app = login(get_app(pub))
+    resp = app.get('/backoffice/management/form-title/')
+    resp.forms['listing-settings']['filter-4'].checked = True
+    resp = resp.forms['listing-settings'].submit()
+
+    assert resp.forms['listing-settings']['filter-4-value'].value == ''
+
+    resp.forms['listing-settings']['filter-4-value'].value = '2020-04-24'
+    resp = resp.forms['listing-settings'].submit()
+    assert resp.text.count('<td>2020-04-24</td>') > 0
+    assert resp.text.count('<td>2015-05-12</td>') == 0
+
+    resp.forms['listing-settings']['filter-4-value'].value = '2015-05-12'
+    resp = resp.forms['listing-settings'].submit()
+    assert resp.text.count('<td>2020-04-24</td>') == 0
+    assert resp.text.count('<td>2015-05-12</td>') > 0
+
+    # date in a different format
+    resp.forms['listing-settings']['filter-4-value'].value = '12/05/2015'
+    resp = resp.forms['listing-settings'].submit()
+    assert resp.text.count('<td>2020-04-24</td>') == 0
+    assert resp.text.count('<td>2015-05-12</td>') > 0
+
+
 def test_backoffice_csv(pub):
     create_superuser(pub)
     create_environment(pub)
diff --git a/wcs/backoffice/management.py b/wcs/backoffice/management.py
index f92f981fc..6e77c770b 100644
--- a/wcs/backoffice/management.py
+++ b/wcs/backoffice/management.py
@@ -1155,6 +1155,12 @@ class FormPage(Directory):
         return json.dumps({'err': 0, 'data': [{'id': x[0], 'text': x[1]} for x in options]},
                 cls=misc.JSONEncoder)
 
+    def get_filterable_field_types(self):
+        types = ['string', 'email', 'date', 'item', 'bool', 'items', 'period-date']
+        if get_publisher().is_using_postgresql():
+            types.append('date')
+        return types
+
     def get_filter_sidebar(self, selected_filter=None, mode='listing', query=None, criterias=None):
         r = TemplateIO(html=True)
 
@@ -1168,14 +1174,15 @@ class FormPage(Directory):
         filter_fields = []
         for field in period_fake_fields + self.get_formdef_fields():
             field.enabled = False
-            if field.type not in ('item', 'bool', 'items', 'period-date', 'status'):
+            if field.type not in self.get_filterable_field_types() + ['status']:
                 continue
             if field.type == 'status' and not waitpoint_status:
                 continue
             filter_fields.append(field)
 
             if get_request().form:
-                field.enabled = 'filter-%s' % field.id in get_request().form
+                field.enabled = ('filter-%s' % field.id in get_request().form) or (
+                        'filter-%s' % field.varname in get_request().form)
             else:
                 field.enabled = (field.id in default_filters)
                 if not self.view and field.type in ('item', 'items'):
@@ -1288,6 +1295,14 @@ class FormPage(Directory):
                         options=options, value=filter_field_value,
                         render_br=False).render()
 
+            elif filter_field.type in ('string', 'email'):
+                r += StringWidget(filter_field_key, title=filter_field.label,
+                        value=filter_field_value, render_br=False).render()
+
+            elif filter_field.type == 'date':
+                r += DateWidget(filter_field_key, title=filter_field.label,
+                        value=filter_field_value, render_br=False).render()
+
         # field filter dialog content
         r += htmltext('<div style="display: none;">')
         r += htmltext('<ul id="field-filter">')
@@ -1526,7 +1541,7 @@ class FormPage(Directory):
         filters_dict.update(get_request().form)
 
         for filter_field in period_fake_fields + self.get_formdef_fields():
-            if filter_field.type not in ('item', 'bool', 'items', 'period-date'):
+            if filter_field.type not in self.get_filterable_field_types():
                 continue
 
             filter_field_key = None
@@ -1581,6 +1596,10 @@ class FormPage(Directory):
                     criterias.append(Equal('f%s' % filter_field.id, True))
                 elif filter_field_value == 'false':
                     criterias.append(Equal('f%s' % filter_field.id, False))
+            elif filter_field.type in ('string', 'email') and filter_field_value not in (None, 'None'):
+                criterias.append(Equal('f%s' % filter_field.id, filter_field_value))
+            elif filter_field.type == 'date' and filter_field_value not in (None, 'None'):
+                criterias.append(Equal('f%s' % filter_field.id, filter_field_value))
 
         return criterias
 
diff --git a/wcs/qommon/static/js/wcs.listing.js b/wcs/qommon/static/js/wcs.listing.js
index faebd9b6c..c739065e5 100644
--- a/wcs/qommon/static/js/wcs.listing.js
+++ b/wcs/qommon/static/js/wcs.listing.js
@@ -314,8 +314,8 @@ $(function() {
     return false;
   });
 
-  /* automatically refresh on filter change */
-  $('form#listing-settings select').change(function() {
+  /* automatically refresh onfilter change */
+  $('form#listing-settings input[type=date], form#listing-settings input[type=text], form#listing-settings select').change(function() {
     $('form#listing-settings').submit();
   });
   /* partial table refresh */
-- 
2.26.2

