From 25217be38d6b5d61940e0b32771f67db0614bd71 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Fri, 23 Apr 2021 15:20:59 +0200
Subject: [PATCH 1/2] wcs: add categories field to CareForms cell (#53213)

---
 combo/apps/wcs/forms.py                       | 12 +++
 .../migrations/0027_careforms_categories.py   | 17 ++++
 combo/apps/wcs/models.py                      | 57 +++++++++--
 .../wcs/templates/combo/wcs/care_forms.html   |  2 +-
 tests/test_wcs.py                             | 95 ++++++++++++++++++-
 5 files changed, 171 insertions(+), 12 deletions(-)
 create mode 100644 combo/apps/wcs/migrations/0027_careforms_categories.py

diff --git a/combo/apps/wcs/forms.py b/combo/apps/wcs/forms.py
index f4acaa5e..a9dc3f6b 100644
--- a/combo/apps/wcs/forms.py
+++ b/combo/apps/wcs/forms.py
@@ -23,6 +23,7 @@ from combo.utils.forms import MultiSortWidget
 from .models import (
     WcsCardInfosCell,
     WcsCardsCell,
+    WcsCareFormsCell,
     WcsCategoryCell,
     WcsCurrentDraftsCell,
     WcsCurrentFormsCell,
@@ -153,6 +154,17 @@ class WcsCurrentDraftsCellForm(WcsFormsMixin, forms.ModelForm):
         self._init_categories()
 
 
+class WcsCareFormsCellForm(WcsFormsMixin, forms.ModelForm):
+    class Meta:
+        model = WcsCareFormsCell
+        fields = ['wcs_site', 'categories']
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self._init_wcs_site()
+        self._init_categories()
+
+
 class BackofficeSubmissionCellForm(WcsFormsMixin, forms.ModelForm):
     class Meta:
         model = WcsCurrentDraftsCell
diff --git a/combo/apps/wcs/migrations/0027_careforms_categories.py b/combo/apps/wcs/migrations/0027_careforms_categories.py
new file mode 100644
index 00000000..07cd3910
--- /dev/null
+++ b/combo/apps/wcs/migrations/0027_careforms_categories.py
@@ -0,0 +1,17 @@
+import jsonfield.fields
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('wcs', '0026_text_to_jsonb'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='wcscareformscell',
+            name='categories',
+            field=jsonfield.fields.JSONField(blank=True, default=dict, verbose_name='Categories'),
+        ),
+    ]
diff --git a/combo/apps/wcs/models.py b/combo/apps/wcs/models.py
index 67b312b8..651fdbb0 100644
--- a/combo/apps/wcs/models.py
+++ b/combo/apps/wcs/models.py
@@ -16,6 +16,7 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import collections
 import copy
 import logging
 
@@ -279,6 +280,9 @@ class WcsBlurpMixin(object):
     def get_api_url(self, context):
         return self.api_url
 
+    def get_api_url_for_site(self, context, wcs_slug):
+        return self.get_api_url(context)
+
     def get_data(self, context):
         if context.get('placeholder_search_mode'):
             # don't call webservices when we're just looking for placeholders
@@ -292,13 +296,18 @@ class WcsBlurpMixin(object):
         else:
             wcs_sites = get_wcs_services()
 
-        wcs_sites = copy.deepcopy(wcs_sites)
+        result = {}
         returns = set([])
-        api_url = self.get_api_url(context)
-        for wcs_slug, wcs_site in wcs_sites.items():
-            url = wcs_site.get('url')
+        for wcs_slug, _wcs_site in wcs_sites.items():
+            api_url = self.get_api_url_for_site(context, wcs_slug)
+            if not api_url:
+                # nothing to call for this site
+                continue
+            url = _wcs_site.get('url')
             if not url.endswith('/'):
                 url += '/'
+            wcs_site = copy.deepcopy(_wcs_site)
+            result[wcs_slug] = wcs_site
             wcs_site['base_url'] = url
             wcs_site['slug'] = wcs_slug
 
@@ -335,7 +344,7 @@ class WcsBlurpMixin(object):
         else:
             self.mark_as_valid()
 
-        return wcs_sites
+        return result
 
     def get_cell_extra_context(self, context):
         return {self.variable_name: self.get_data(context)}
@@ -693,7 +702,9 @@ class WcsFormsOfCategoryCell(WcsCommonCategoryCell, WcsBlurpMixin):
 
 
 @register_cell_class
-class WcsCareFormsCell(WcsDataBaseCell):
+class WcsCareFormsCell(WcsDataBaseCell, CategoriesValidityMixin):
+    categories = JSONField(_('Categories'), blank=True)
+
     api_url = '/api/forms/?limit=10'
     variable_name = 'care_forms'
     template_name = 'combo/wcs/care_forms.html'
@@ -703,6 +714,40 @@ class WcsCareFormsCell(WcsDataBaseCell):
     class Meta:
         verbose_name = _('Forms to process')
 
+    def get_default_form_class(self):
+        from .forms import WcsCareFormsCellForm
+
+        return WcsCareFormsCellForm
+
+    def get_api_url_for_site(self, context, wcs_slug):
+        url = self.get_api_url(context)
+
+        if self.categories:
+            categories_by_site = collections.defaultdict(list)
+            for category in self.categories.get('data', []):
+                key, slug = category.split(':')
+                categories_by_site[key].append(slug)
+            if not categories_by_site.get(wcs_slug):
+                return None
+            url += '&category_slugs=%s' % ','.join(categories_by_site.get(wcs_slug))
+
+        return url
+
+    def get_cell_extra_context(self, context):
+        context = super().get_cell_extra_context(context)
+
+        categories_filter = []
+        if self.categories:
+            for category in self.categories.get('data', []):
+                categories_filter.append(tuple(category.split(':')))
+
+        for wcs_site in context['care_forms']:
+            if not context['care_forms'].get(wcs_site):
+                continue
+            context['care_forms'][wcs_site]['categories'] = [v for k, v in categories_filter if k == wcs_site]
+
+        return context
+
 
 @register_cell_class
 class CategoriesCell(WcsDataBaseCell):
diff --git a/combo/apps/wcs/templates/combo/wcs/care_forms.html b/combo/apps/wcs/templates/combo/wcs/care_forms.html
index ec8b14ea..48414899 100644
--- a/combo/apps/wcs/templates/combo/wcs/care_forms.html
+++ b/combo/apps/wcs/templates/combo/wcs/care_forms.html
@@ -26,6 +26,6 @@
     </tbody>
   </table>
   {% endif %}
-  <p><a class="pk-button" href="{{ forms.url }}backoffice/management/">{% trans "See all forms" %}</a></p>
+  <p><a class="pk-button" href="{{ forms.url }}backoffice/management/listing{% if forms.categories %}?category_slugs={{ forms.categories|join:"," }}{% endif %}">{% trans "See all forms" %}</a></p>
 {% endfor %}
 {% endblock %}
diff --git a/tests/test_wcs.py b/tests/test_wcs.py
index 95835eae..e47da907 100644
--- a/tests/test_wcs.py
+++ b/tests/test_wcs.py
@@ -719,15 +719,54 @@ def test_care_forms_cell_render(mock_send, context):
 
     assert 'http://127.0.0.1:8999/backoffice/management/foobar/1' in result
     assert 'http://127.0.0.1:8999/backoffice/management/foobar/2' in result
-    assert '"http://127.0.0.1:8999/backoffice/management/"' in result
+    assert '"http://127.0.0.1:8999/backoffice/management/listing"' in result
     assert 'http://127.0.0.2:8999/backoffice/management/foobar/1' in result
     assert 'http://127.0.0.2:8999/backoffice/management/foobar/2' in result
-    assert '"http://127.0.0.2:8999/backoffice/management/"' in result
+    assert '"http://127.0.0.2:8999/backoffice/management/listing"' in result
 
     data = cell.get_data(context)
     assert 'default' in data
     assert 'other' in data
 
+    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
+        mock_json = mock.Mock(status_code=200)
+        requests_get.return_value = mock_json
+        cell.render(context)
+    assert requests_get.call_args_list[0][0][0] == '/api/forms/?limit=10'
+    assert requests_get.call_args_list[1][0][0] == '/api/forms/?limit=10'
+
+    # limit to a list of categories
+    cell.categories = {'data': ['default:test-3', 'other:test-4']}
+
+    result = cell.render(context)
+    assert '"http://127.0.0.1:8999/backoffice/management/listing?category_slugs=test-3"' in result
+    assert '"http://127.0.0.2:8999/backoffice/management/listing?category_slugs=test-4"' in result
+
+    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
+        mock_json = mock.Mock(status_code=200)
+        requests_get.return_value = mock_json
+        cell.render(context)
+    assert len(requests_get.call_args_list) == 2
+    assert requests_get.call_args_list[0][0][0] == '/api/forms/?limit=10&category_slugs=test-3'
+    assert requests_get.call_args_list[0][1]['remote_service']['url'] == 'http://127.0.0.1:8999/'
+    assert requests_get.call_args_list[1][0][0] == '/api/forms/?limit=10&category_slugs=test-4'
+    assert requests_get.call_args_list[1][1]['remote_service']['url'] == 'http://127.0.0.2:8999/'
+
+    # limit to a single category
+    cell.categories = {'data': ['default:test-3']}
+
+    result = cell.render(context)
+    assert '"http://127.0.0.1:8999/backoffice/management/listing?category_slugs=test-3"' in result
+    assert '"http://127.0.0.2:8999/backoffice/management/listing' not in result
+
+    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
+        mock_json = mock.Mock(status_code=200)
+        requests_get.return_value = mock_json
+        cell.render(context)
+    assert len(requests_get.call_args_list) == 1
+    assert requests_get.call_args_list[0][0][0] == '/api/forms/?limit=10&category_slugs=test-3'
+    assert requests_get.call_args_list[0][1]['remote_service']['url'] == 'http://127.0.0.1:8999/'
+
 
 def test_care_forms_cell_validity(context):
     page = Page.objects.create(title='xxx', slug='test_care_forms_cell_render', template_name='standard')
@@ -767,6 +806,50 @@ def test_care_forms_cell_validity(context):
     assert validity_info.invalid_since is not None
 
 
+@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
+def test_care_forms_cell_check_validity(mock_send, context):
+    page = Page.objects.create(title='xxx', slug='test_care_forms_cell_render', template_name='standard')
+    cell = WcsCareFormsCell.objects.create(page=page, placeholder='content', order=0)
+
+    # no category
+    cell.check_validity()
+    assert ValidityInfo.objects.exists() is False
+
+    # valid categories
+    cell.categories = {'data': ['default:test-3', 'default:test-9']}
+    cell.save()
+    cell.check_validity()
+    assert ValidityInfo.objects.exists() is False
+
+    # can not retrieve data, don't set cell as invalid
+    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
+        mock_resp = Response()
+        mock_resp.status_code = 500
+        requests_get.return_value = mock_resp
+        cell.check_validity()
+    assert ValidityInfo.objects.exists() is False
+    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
+        requests_get.side_effect = ConnectionError()
+        cell.check_validity()
+    assert ValidityInfo.objects.exists() is False
+
+    # can not retrieve categories, don't set cell as invalid
+    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
+        mock_resp = Response()
+        mock_resp.status_code = 404
+        requests_get.return_value = mock_resp
+        cell.check_validity()
+    assert ValidityInfo.objects.exists() is False
+
+    # invalid category
+    cell.categories = {'data': ['default:foobar', 'default:test-9']}
+    cell.save()
+    cell.check_validity()
+    validity_info = ValidityInfo.objects.latest('pk')
+    assert validity_info.invalid_reason_code == 'wcs_category_not_found'
+    assert validity_info.invalid_since is not None
+
+
 @mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
 def test_care_forms_cell_render_single_site(mock_send, context):
     page = Page(title='xxx', slug='test_care_forms_cell_render', template_name='standard')
@@ -785,8 +868,8 @@ def test_care_forms_cell_render_single_site(mock_send, context):
     context['synchronous'] = True  # to get fresh content
 
     result = cell.render(context)
-    assert '"http://127.0.0.1:8999/backoffice/management/"' in result
-    assert '"http://127.0.0.2:8999/backoffice/management/"' not in result
+    assert '"http://127.0.0.1:8999/backoffice/management/listing"' in result
+    assert '"http://127.0.0.2:8999/backoffice/management/listing"' not in result
 
     data = cell.get_data(context)
     assert 'default' in data
@@ -1065,7 +1148,8 @@ def test_manager_forms_of_category_cell(mock_send, app, admin_user):
     assert resp.status_int == 302
 
 
-def test_manager_current_forms(app, admin_user):
+@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
+def test_manager_current_forms(mock_send, app, admin_user):
     page = Page(title='One', slug='one', template_name='standard')
     page.save()
     app = login(app)
@@ -2017,6 +2101,7 @@ def test_hourly():
         klass.objects.create(page=page, placeholder='content', order=0)
     for klass in cell_classes:
         if klass in [
+            WcsCareFormsCell,
             WcsCurrentFormsCell,
             WcsCurrentDraftsCell,
             WcsFormsOfCategoryCell,
-- 
2.30.2

