From 14544f6b692d9892839fc009cfcdcfdefcc91523 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Tue, 30 Mar 2021 10:16:20 +0200
Subject: [PATCH] settings: configure user search result template (#51343)

---
 tests/admin_pages/test_settings.py   | 26 ++++++++++++----
 tests/api/test_user.py               | 45 ++++++++++++++++++++++++++--
 wcs/admin/settings.py                | 30 ++++++++++++++++++-
 wcs/api.py                           | 13 ++++++++
 wcs/qommon/static/js/qommon.admin.js | 32 +++++---------------
 wcs/users.py                         |  8 +++++
 6 files changed, 121 insertions(+), 33 deletions(-)

diff --git a/tests/admin_pages/test_settings.py b/tests/admin_pages/test_settings.py
index 61a614aa..76b8b473 100644
--- a/tests/admin_pages/test_settings.py
+++ b/tests/admin_pages/test_settings.py
@@ -354,8 +354,8 @@ def test_settings_user(pub):
     resp = app.get('/backoffice/settings/users').follow().follow()
 
     # add a field
-    resp.forms[2]['label'] = 'foobar'
-    resp = resp.forms[2].submit()
+    resp.forms[3]['label'] = 'foobar'
+    resp = resp.forms[3].submit()
     assert resp.location == 'http://example.net/backoffice/settings/users/fields/'
     resp = resp.follow()
     assert b'foobar' in pub.cfg['users']['formdef']
@@ -376,9 +376,9 @@ def test_settings_user(pub):
     assert pub.cfg['users']['field_email'] is None
 
     # add a comment field
-    resp.forms[2]['label'] = 'barfoo'
-    resp.forms[2]['type'] = 'comment'
-    resp = resp.forms[2].submit()
+    resp.forms[3]['label'] = 'barfoo'
+    resp.forms[3]['type'] = 'comment'
+    resp = resp.forms[3].submit()
     assert resp.location == 'http://example.net/backoffice/settings/users/fields/'
     resp = resp.follow()
     assert b'barfoo' in pub.cfg['users']['formdef']
@@ -409,6 +409,22 @@ def test_settings_user(pub):
     assert pub.cfg['users']['sidebar_template'] == 'hello {{ form_user_display_name }}'
     assert 'syntax error in Django template' in resp
 
+    # set a search result template
+    resp = app.get('/backoffice/settings/users/fields/')
+    assert 'search_result_template' not in pub.cfg['users']
+    assert resp.forms['search_result_template']['search_result_template'].value.replace('\n', '') == (
+        '{{ user_email|default:"" }}'
+        '{% if user_var_phone %} 📞 {{ user_var_phone }}{% endif %}'
+        '{% if user_var_mobile %} 📱 {{ user_var_mobile }}{% endif %}'
+        '{% if user_var_address or user_var_zipcode or user_var_city %} 📨{% endif %}'
+        '{% if user_var_address %} {{ user_var_address }}{% endif %}'
+        '{% if user_var_zipcode %} {{ user_var_zipcode }}{% endif %}'
+        '{% if user_var_city %} {{ user_var_city }}{% endif %}'
+    )
+    resp.forms['search_result_template']['search_result_template'] = '{{ user_email|default:"" }} Foo Bar'
+    resp = resp.forms['search_result_template'].submit().follow()
+    assert pub.cfg['users']['search_result_template'] == '{{ user_email|default:"" }} Foo Bar'
+
     # disable users screen
     if not pub.site_options.has_section('options'):
         pub.site_options.add_section('options')
diff --git a/tests/api/test_user.py b/tests/api/test_user.py
index e90f1a4f..ed8859b7 100644
--- a/tests/api/test_user.py
+++ b/tests/api/test_user.py
@@ -8,6 +8,7 @@ from quixote import get_publisher
 from utilities import clean_temporary_pub, create_temporary_pub, get_app
 
 from wcs import fields
+from wcs.admin.settings import UserFieldsFormDef
 from wcs.formdef import FormDef
 from wcs.qommon.http_request import HTTPRequest
 from wcs.qommon.ident.password_accounts import PasswordAccount
@@ -121,8 +122,6 @@ def test_users(pub, local_user):
     resp = get_app(pub).get(sign_uri('/api/users/?q=foobar'))
     assert len(resp.json['data']) == 0
 
-    from wcs.admin.settings import UserFieldsFormDef
-
     formdef = UserFieldsFormDef(pub)
     formdef.fields.append(fields.StringField(id='3', label='test', type='string'))
     formdef.store()
@@ -157,6 +156,48 @@ def test_users_unaccent(pub, local_user):
     assert len(resp.json['data']) == 0
 
 
+def test_users_description(pub, local_user):
+    assert 'users' not in pub.cfg
+
+    formdef = UserFieldsFormDef(pub)
+    formdef.fields = [
+        fields.StringField(id='1', label='phone', type='string', varname='phone'),
+        fields.StringField(id='2', label='mobile', type='string', varname='mobile'),
+        fields.StringField(id='3', label='address', type='string', varname='address'),
+        fields.StringField(id='4', label='zipcode', type='string', varname='zipcode'),
+        fields.StringField(id='5', label='city', type='string', varname='city'),
+    ]
+    formdef.store()
+
+    local_user.form_data = {
+        '1': '0505050505',
+        '2': '0606060606',
+        '3': 'rue du Chateau',
+        '4': '75014',
+        '5': 'PARIS',
+    }
+    local_user.set_attributes_from_formdata(local_user.form_data)
+    local_user.store()
+
+    resp = get_app(pub).get(sign_uri('/api/users/'))
+    assert resp.json['data'][0]['user_id'] == local_user.id
+    assert (
+        resp.json['data'][0]['description'].replace('\n', '')
+        == 'jean.darmette@triffouilis.fr 📞 0505050505 📱 0606060606 📨 rue du Chateau 75014 PARIS'
+    )
+
+    pub.cfg['users'][
+        'search_result_template'
+    ] = """{{ user_email|default:"" }}{% if user_var_phone %} 📞 {{ user_var_phone }}{% endif %} foo bar"""
+    pub.write_cfg()
+    resp = get_app(pub).get(sign_uri('/api/users/'))
+    assert resp.json['data'][0]['user_id'] == local_user.id
+    assert (
+        resp.json['data'][0]['description'].replace('\n', '')
+        == 'jean.darmette@triffouilis.fr 📞 0505050505 foo bar'
+    )
+
+
 def test_user_by_nameid(pub, local_user):
     resp = get_app(pub).get(sign_uri('/api/users/xyz/', user=local_user), status=404)
     local_user.name_identifiers = ['xyz']
diff --git a/wcs/admin/settings.py b/wcs/admin/settings.py
index 3c9b6c3b..25f456b2 100644
--- a/wcs/admin/settings.py
+++ b/wcs/admin/settings.py
@@ -136,7 +136,7 @@ class UserFieldDefPage(FieldDefPage):
 
 
 class UserFieldsDirectory(FieldsDirectory):
-    _q_exports = ['', 'update_order', 'new', 'mapping', 'template']
+    _q_exports = ['', 'update_order', 'new', 'mapping', 'template', 'search_result_template']
 
     section = 'settings'
     field_def_page_class = UserFieldDefPage
@@ -158,6 +158,10 @@ class UserFieldsDirectory(FieldsDirectory):
         r += htmltext('<h2>%s</h2>') % _('Sidebar Template')
         r += self.sidebar_template_form().render()
         r += htmltext('</div>')
+        r += htmltext('<div class="bo-block">')
+        r += htmltext('<h2>%s</h2>') % _('User search result template')
+        r += self.search_result_template_form().render()
+        r += htmltext('</div>')
         return r.getvalue()
 
     def mapping_form(self):
@@ -214,6 +218,30 @@ class UserFieldsDirectory(FieldsDirectory):
         cfg_submit(form, 'users', ['sidebar_template'])
         return redirect('.')
 
+    @classmethod
+    def search_result_template_form(cls, action='search_result_template'):
+        users_cfg = get_cfg('users', {})
+        form = Form(action=action, id='search_result_template')
+        form.add(
+            TextWidget,
+            'search_result_template',
+            value=users_cfg.get('search_result_template')
+            or get_publisher().user_class.default_search_result_template,
+            required=False,
+            validation_function=ComputedExpressionWidget.validate_template,
+            rows=7,
+        )
+        form.add_submit('submit', _('Submit'))
+        return form
+
+    def search_result_template(self):
+        form = self.search_result_template_form()
+        if form.has_errors():
+            get_session().message = ('error', form.get_widget('search_result_template').get_error())
+            return redirect('.')
+        cfg_submit(form, 'users', ['search_result_template'])
+        return redirect('.')
+
 
 class UserFieldsFormDef(FormDef):
     """Class to handle custom user fields, it loads and saves from/to
diff --git a/wcs/api.py b/wcs/api.py
index 54074117..ae1f999c 100644
--- a/wcs/api.py
+++ b/wcs/api.py
@@ -37,6 +37,7 @@ from wcs.data_sources import NamedDataSource
 from wcs.data_sources import get_object as get_data_source_object
 from wcs.formdef import FormDef
 from wcs.forms.common import FormStatusPage
+from wcs.qommon import get_cfg
 from wcs.qommon.afterjobs import AfterJob
 from wcs.roles import logged_users_role
 
@@ -53,6 +54,7 @@ from .qommon.errors import (
 )
 from .qommon.form import ComputedExpressionWidget
 from .qommon.storage import Equal, NotEqual
+from .qommon.template import Template, TemplateError
 
 
 def posted_json_data_to_formdata_data(formdef, data):
@@ -970,6 +972,12 @@ class ApiUsersDirectory(Directory):
             criterias.append(st.Or(criteria_fields))
 
         def as_dict(user):
+            users_cfg = get_cfg('users', {})
+            template = (
+                users_cfg.get('search_result_template')
+                or get_publisher().user_class.default_search_result_template
+            )
+
             user_info = user.get_substitution_variables(prefix='')
             del user_info['user']
             user_info['user_id'] = user.id
@@ -978,6 +986,11 @@ class ApiUsersDirectory(Directory):
             # add attributes to be usable as datasource
             user_info['id'] = user.id
             user_info['text'] = user_info['user_display_name']
+            if Template.is_template_string(template):
+                try:
+                    user_info['description'] = Template(template).render(user_info)
+                except TemplateError:
+                    pass
             return user_info
 
         limit = misc.get_int_or_400(get_request().form.get('limit'))
diff --git a/wcs/qommon/static/js/qommon.admin.js b/wcs/qommon/static/js/qommon.admin.js
index 2315c82e..4da8cd21 100644
--- a/wcs/qommon/static/js/qommon.admin.js
+++ b/wcs/qommon/static/js/qommon.admin.js
@@ -159,33 +159,15 @@ $(function() {
       },
       placeholder: '-',
       templateResult: function (state) {
-        console.log(state);
-        if (!state.id) {
+        if (!state.description) {
           return state.text;
         }
-        var details = '';
-        if (state.user_email) {
-          details += ' ' + state.user_email;
-        }
-        if (state.user_var_phone) {
-          details += ' 📞 ' + state.user_var_phone;
-        }
-        if (state.user_var_mobile) {
-          details += ' 📱 ' + state.user_var_mobile;
-        }
-        if (state.user_var_address || state.user_var_zipcode || state.user_var_city) {
-          details += ' 📨';
-        }
-        if (state.user_var_address) {
-          details += ' ' + state.user_var_address;
-        }
-        if (state.user_var_zipcode) {
-          details += ' ' + state.user_var_zipcode;
-        }
-        if (state.user_var_city) {
-          details += ' ' + state.user_var_city;
-        }
-        return $('<span>' + state.text + '</span><br /><span>' + details + '</span>');
+        var $template_string = $('<span>');
+        $template_string.append(
+                $('<span>', {text: state.text})).append(
+                $('<br>')).append(
+                $('<span>' + state.description + '</span>'));
+        return $template_string;
       }
     }
     if ($('div.submit-user-selection').length) {
diff --git a/wcs/users.py b/wcs/users.py
index 0f63dd0a..e4f4fb9f 100644
--- a/wcs/users.py
+++ b/wcs/users.py
@@ -44,6 +44,14 @@ class User(StorableObject):
 
     last_seen = None
 
+    default_search_result_template = """{{ user_email|default:"" }}
+{% if user_var_phone %} 📞 {{ user_var_phone }}{% endif %}
+{% if user_var_mobile %} 📱 {{ user_var_mobile }}{% endif %}
+{% if user_var_address or user_var_zipcode or user_var_city %} 📨{% endif %}
+{% if user_var_address %} {{ user_var_address }}{% endif %}
+{% if user_var_zipcode %} {{ user_var_zipcode }}{% endif %}
+{% if user_var_city %} {{ user_var_city }}{% endif %}"""
+
     def __init__(self, name=None):
         StorableObject.__init__(self)
         self.name = name
-- 
2.30.2

