From 6353ed16ade0a14a2524dada1efc92e2e52ea127 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Fri, 1 Oct 2021 16:20:13 +0200
Subject: [PATCH] search: title for search cell (#57323)

---
 combo/apps/search/forms.py                    |   2 +-
 combo/apps/search/migrations/0011_title.py    |  16 ++
 combo/apps/search/models.py                   |   1 +
 .../search/templates/combo/search-cell.html   |   3 +
 tests/test_search.py                          | 185 +++++++++---------
 5 files changed, 113 insertions(+), 94 deletions(-)
 create mode 100644 combo/apps/search/migrations/0011_title.py

diff --git a/combo/apps/search/forms.py b/combo/apps/search/forms.py
index 44735c67..a3f47acf 100644
--- a/combo/apps/search/forms.py
+++ b/combo/apps/search/forms.py
@@ -27,7 +27,7 @@ from .models import SearchCell
 class SearchCellForm(forms.ModelForm):
     class Meta:
         model = SearchCell
-        fields = ('autofocus', 'input_placeholder')
+        fields = ('title', 'autofocus', 'input_placeholder')
 
 
 class SelectWithDisabled(forms.Select):
diff --git a/combo/apps/search/migrations/0011_title.py b/combo/apps/search/migrations/0011_title.py
new file mode 100644
index 00000000..24dadcef
--- /dev/null
+++ b/combo/apps/search/migrations/0011_title.py
@@ -0,0 +1,16 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('search', '0010_searchcell_template_name'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='searchcell',
+            name='title',
+            field=models.CharField(blank=True, max_length=150, verbose_name='Title'),
+        ),
+    ]
diff --git a/combo/apps/search/models.py b/combo/apps/search/models.py
index 0fae2505..c11ba844 100644
--- a/combo/apps/search/models.py
+++ b/combo/apps/search/models.py
@@ -53,6 +53,7 @@ class SearchCell(CellBase):
     exclude_from_search = True
 
     _search_services = JSONField(_('Search Services'), default=dict, blank=True)
+    title = models.CharField(_('Title'), max_length=150, blank=True)
     autofocus = models.BooleanField(_('Autofocus'), default=False)
     input_placeholder = models.CharField(_('Placeholder'), max_length=64, default="", blank=True)
 
diff --git a/combo/apps/search/templates/combo/search-cell.html b/combo/apps/search/templates/combo/search-cell.html
index ddf08298..68126676 100644
--- a/combo/apps/search/templates/combo/search-cell.html
+++ b/combo/apps/search/templates/combo/search-cell.html
@@ -1,5 +1,8 @@
 {% load i18n %}
 {% block cell-content %}
+{% if cell.title %}
+<h2>{{ cell.title }}</h2>
+{% endif %}
 
 {% block search-form %}
 <form id="combo-search-form-{{ cell.pk }}" class="combo-search-form">
diff --git a/tests/test_search.py b/tests/test_search.py
index 83ce2685..8b2f0797 100644
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -57,7 +57,8 @@ class SearchServices:
         settings.COMBO_SEARCH_SERVICES = {}
 
 
-def test_search_cell(app):
+def test_search_cell(settings, app):
+    settings.COMBO_SEARCH_SERVICES = SEARCH_SERVICES
     page = Page(title='Search', slug='search_page', template_name='standard')
     page.save()
 
@@ -69,105 +70,103 @@ def test_search_cell(app):
     # unknown cell pk
     resp = app.get('/ajax/search/0/search1/?q=foo', status=404)
 
-    with SearchServices(SEARCH_SERVICES):
-        resp = cell.render({})
-        assert 'input' in resp
-        assert 'id="combo-search-input-%s"' % cell.pk in resp
-        assert 'autofocus' not in resp
-        assert 'placeholder="my placeholder"' in resp
+    resp = cell.render({})
+    assert 'input' in resp
+    assert 'id="combo-search-input-%s"' % cell.pk in resp
+    assert 'autofocus' not in resp
+    assert 'placeholder="my placeholder"' in resp
 
-        cell.autofocus = True
-        cell.save()
-        resp = cell.render({})
-        assert 'autofocus' in resp
+    cell.autofocus = True
+    cell.save()
+    resp = cell.render({})
+    assert 'h2' not in resp
+    assert 'autofocus' in resp
 
-        cell.slug = 'var-name'
-        context = {'request': RequestFactory().get('/?q_var_name=searchme')}
-        resp = cell.render(context)
-        assert "$input.val('searchme');" in resp
+    cell.slug = 'var-name'
+    cell.title = 'Custom Title'
+    context = {'request': RequestFactory().get('/?q_var_name=searchme')}
+    resp = cell.render(context)
+    assert '<h2>Custom Title</h2>' in resp
+    assert "$input.val('searchme');" in resp
 
-        with mock.patch('combo.apps.search.models.requests.get') as requests_get:
+    with mock.patch('combo.apps.search.models.requests.get') as requests_get:
 
-            response = {'err': 0, 'data': []}
-            mock_json = mock.Mock()
-            mock_json.json.return_value = response
-            requests_get.return_value = mock_json
-            resp = app.get('/ajax/search/%s/search1/?q=foo' % cell.pk, status=200)
-            assert requests_get.call_args[0][0] == 'http://www.example.net/search/?q=foo'
-            assert '<li>' not in resp.text
-            assert 'no result found' in resp.text
-
-            resp = app.get('/ajax/search/%s/search1/?q=foo%%23bar' % cell.pk, status=200)
-            assert requests_get.call_args[0][0] == 'http://www.example.net/search/?q=foo%23bar'
-            assert '<li>' not in resp.text
-            assert 'no result found' in resp.text
-
-            response['data'] = [{'url': 'http://test', 'text': 'barbarbar'}]
-            resp = app.get('/ajax/search/%s/search1/?q=foo' % cell.pk, status=200)
-            assert resp.text.count('<li>') == 1
-            assert '<li><a href="http://test">barbarbar</a>' in resp.text
-            assert 'no result found' not in resp.text
+        response = {'err': 0, 'data': []}
+        mock_json = mock.Mock()
+        mock_json.json.return_value = response
+        requests_get.return_value = mock_json
+        resp = app.get('/ajax/search/%s/search1/?q=foo' % cell.pk, status=200)
+        assert requests_get.call_args[0][0] == 'http://www.example.net/search/?q=foo'
+        assert '<li>' not in resp.text
+        assert 'no result found' in resp.text
+
+        resp = app.get('/ajax/search/%s/search1/?q=foo%%23bar' % cell.pk, status=200)
+        assert requests_get.call_args[0][0] == 'http://www.example.net/search/?q=foo%23bar'
+        assert '<li>' not in resp.text
+        assert 'no result found' in resp.text
+
+        response['data'] = [{'url': 'http://test', 'text': 'barbarbar'}]
+        resp = app.get('/ajax/search/%s/search1/?q=foo' % cell.pk, status=200)
+        assert resp.text.count('<li>') == 1
+        assert '<li><a href="http://test">barbarbar</a>' in resp.text
+        assert 'no result found' not in resp.text
 
-            response['data'] = [
-                {'url': 'http://test', 'text': 'barbarbar', 'description': 'this is <b>html</b>'}
-            ]
-            resp = app.get('/ajax/search/%s/search1/?q=foo' % cell.pk, status=200)
-            assert resp.text.count('<li>') == 1
-            assert '<li><a href="http://test">barbarbar</a>' in resp.text
-            assert 'this is <b>html</b>' in resp.text
-            assert 'no result found' not in resp.text
+        response['data'] = [{'url': 'http://test', 'text': 'barbarbar', 'description': 'this is <b>html</b>'}]
+        resp = app.get('/ajax/search/%s/search1/?q=foo' % cell.pk, status=200)
+        assert resp.text.count('<li>') == 1
+        assert '<li><a href="http://test">barbarbar</a>' in resp.text
+        assert 'this is <b>html</b>' in resp.text
+        assert 'no result found' not in resp.text
 
-            resp = app.get('/ajax/search/%s/search1/?q=' % cell.pk, status=200)
-            assert '<li>' not in resp.text
-            assert 'no result found' not in resp.text
+        resp = app.get('/ajax/search/%s/search1/?q=' % cell.pk, status=200)
+        assert '<li>' not in resp.text
+        assert 'no result found' not in resp.text
 
-            cell._search_services = {'data': ['search_alternate_key']}
-            cell.save()
-            response = {'results': [{'url': 'http://test', 'text': 'barbarbar'}]}
-            mock_json.json.return_value = response
-            resp = app.get('/ajax/search/%s/search_alternate_key/?q=foo' % cell.pk, status=200)
-            assert resp.text.count('<li>') == 1
-            assert '<li><a href="http://test">barbarbar</a>' in resp.text
-
-            # search engine does not return valid JSON
-            class FakedResponse(mock.Mock):
-                def json(self):
-                    return json.loads(self.content)
-
-            requests_get.return_value = FakedResponse(content='notjson', status_code=200)
-            resp = app.get('/ajax/search/%s/search_alternate_key/?q=bar' % cell.pk, status=200)
-            assert requests_get.call_args[0][0] == 'http://www.example.net/search/?q=bar'
-            assert '<li>' not in resp.text
-            assert 'no result found' in resp.text
-            requests_get.return_value = FakedResponse(content='500withbadjson', status_code=500)
-            resp = app.get('/ajax/search/%s/search_alternate_key/?q=foo' % cell.pk, status=200)
-            assert requests_get.call_args[0][0] == 'http://www.example.net/search/?q=foo'
-            assert '<li>' not in resp.text
-            assert 'no result found' in resp.text
-
-        with override_settings(TEMPLATE_VARS=TEMPLATE_VARS):
-            cell._search_services = {'data': ['search_tmpl']}
-            cell.save()
-            with mock.patch('combo.apps.search.models.requests.get') as requests_get:
-                response = {'err': 0, 'data': []}
-                mock_json = mock.Mock()
-                mock_json.json.return_value = response
-                requests_get.return_value = mock_json
-                resp = app.get('/ajax/search/%s/search_tmpl/?q=foo' % cell.pk, status=200)
-                assert requests_get.call_args[0][0] == 'http://search.example.net/?q=foo'
-
-                # TEMPLATE_VARS are accessible in template
-                cell.slug = 'searchfoo'
-                cell.save()
-                templates_settings = [settings.TEMPLATES[0].copy()]
-                templates_settings[0]['DIRS'] = [
-                    '%s/templates-1' % os.path.abspath(os.path.dirname(__file__))
-                ]
-                with override_settings(TEMPLATES=templates_settings):
-                    resp = app.get('/ajax/search/%s/search_tmpl/?q=bar' % cell.pk, status=200)
-                    assert requests_get.call_args[0][0] == 'http://search.example.net/?q=bar'
-                    assert 'searchfoo results.data=[]' in resp.text
-                    assert 'search_url=http://search.example.net/' in resp.text
+        cell._search_services = {'data': ['search_alternate_key']}
+        cell.save()
+        response = {'results': [{'url': 'http://test', 'text': 'barbarbar'}]}
+        mock_json.json.return_value = response
+        resp = app.get('/ajax/search/%s/search_alternate_key/?q=foo' % cell.pk, status=200)
+        assert resp.text.count('<li>') == 1
+        assert '<li><a href="http://test">barbarbar</a>' in resp.text
+
+        # search engine does not return valid JSON
+        class FakedResponse(mock.Mock):
+            def json(self):
+                return json.loads(self.content)
+
+        requests_get.return_value = FakedResponse(content='notjson', status_code=200)
+        resp = app.get('/ajax/search/%s/search_alternate_key/?q=bar' % cell.pk, status=200)
+        assert requests_get.call_args[0][0] == 'http://www.example.net/search/?q=bar'
+        assert '<li>' not in resp.text
+        assert 'no result found' in resp.text
+        requests_get.return_value = FakedResponse(content='500withbadjson', status_code=500)
+        resp = app.get('/ajax/search/%s/search_alternate_key/?q=foo' % cell.pk, status=200)
+        assert requests_get.call_args[0][0] == 'http://www.example.net/search/?q=foo'
+        assert '<li>' not in resp.text
+        assert 'no result found' in resp.text
+
+    settings.TEMPLATE_VARS = TEMPLATE_VARS
+    cell._search_services = {'data': ['search_tmpl']}
+    cell.save()
+    with mock.patch('combo.apps.search.models.requests.get') as requests_get:
+        response = {'err': 0, 'data': []}
+        mock_json = mock.Mock()
+        mock_json.json.return_value = response
+        requests_get.return_value = mock_json
+        resp = app.get('/ajax/search/%s/search_tmpl/?q=foo' % cell.pk, status=200)
+        assert requests_get.call_args[0][0] == 'http://search.example.net/?q=foo'
+
+        # TEMPLATE_VARS are accessible in template
+        cell.slug = 'searchfoo'
+        cell.save()
+        templates_settings = [settings.TEMPLATES[0].copy()]
+        templates_settings[0]['DIRS'] = ['%s/templates-1' % os.path.abspath(os.path.dirname(__file__))]
+        with override_settings(TEMPLATES=templates_settings):
+            resp = app.get('/ajax/search/%s/search_tmpl/?q=bar' % cell.pk, status=200)
+            assert requests_get.call_args[0][0] == 'http://search.example.net/?q=bar'
+            assert 'searchfoo results.data=[]' in resp.text
+            assert 'search_url=http://search.example.net/' in resp.text
 
 
 def test_search_cell_urlsplit(settings, app):
-- 
2.33.0

