From a7f1eda6e5b88ea37fe7b2c24e9f58495fd11116 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Mon, 8 Aug 2022 17:23:00 +0200
Subject: [PATCH] wcs: for card cell with multiple ids, get all cards in one
 request (#68015)

---
 combo/apps/wcs/models.py | 46 ++++++++++++++++++++++-
 tests/test_wcs.py        | 81 +++++++++++++++++++++++++---------------
 2 files changed, 95 insertions(+), 32 deletions(-)

diff --git a/combo/apps/wcs/models.py b/combo/apps/wcs/models.py
index 7f673a49..23d58e64 100644
--- a/combo/apps/wcs/models.py
+++ b/combo/apps/wcs/models.py
@@ -1024,6 +1024,50 @@ class WcsCardInfosCell(CardMixin, CellBase):
     def get_repeat_template(self, context):
         return len(context.get(self.global_context_key) or [])
 
+    def get_card_data_from_ids(self, card_id, context):
+        card_ids = context.get(self.global_context_key)
+        if not card_ids:
+            return None
+
+        api_url = '/api/cards/%s/list?full=on' % (self.card_slug)
+        if self.card_custom_view:
+            api_url = '/api/cards/%s/list/%s?full=on' % (
+                self.card_slug,
+                self.card_custom_view,
+            )
+            user = self.get_concerned_user(context)
+            if user and not user.is_anonymous:
+                user_name_id = user.get_name_id()
+                if user_name_id:
+                    api_url += '&filter-user-uuid=%s' % user_name_id
+        api_url += '&%s' % '&'.join(['filter-internal-id=%s' % cid for cid in card_ids])
+        synchronous = bool(context.get('synchronous'))
+        wcs_site = get_wcs_services().get(self.wcs_site)
+        try:
+            response = requests.get(
+                api_url,
+                remote_service=wcs_site,
+                user=None if self.without_user else getattr(context.get('request'), 'user', None),
+                without_user=self.without_user,
+                cache_duration=5,
+                raise_if_not_cached=not synchronous,
+                log_errors=False,
+            )
+            response.raise_for_status()
+        except RequestException:
+            if self.card_custom_view:
+                # if there's a custom view consider the error is a 404 because
+                # the card was not found in that view, and mark it so.
+                context['card_not_found'] = True
+            return None
+
+        if response.status_code == 200:
+            data = response.json()
+            for card_data in data.get('data') or []:
+                if str(card_data.get('id')) == str(card_id):
+                    return card_data
+        return None
+
     def get_card_data(self, card_slug, card_custom_view, card_id, context, synchronous=False):
         api_url = '/api/cards/%s/%s/?include-files-content=off' % (card_slug, card_id)
         if card_custom_view:
@@ -1320,7 +1364,7 @@ class WcsCardInfosCell(CardMixin, CellBase):
                 extra_context['card_not_found'] = True
             return extra_context
 
-        card_data = self.get_card_data(self.card_slug, self.card_custom_view, card_id, context)
+        card_data = self.get_card_data_from_ids(card_id, context)
         if not card_data:
             return extra_context
 
diff --git a/tests/test_wcs.py b/tests/test_wcs.py
index f453784f..01900cda 100644
--- a/tests/test_wcs.py
+++ b/tests/test_wcs.py
@@ -3206,7 +3206,8 @@ def test_card_cell_render_identifier(mock_send, nocache, app):
     assert 'Card Model 1' in cell_resp
     assert '<p>Unknown Card</p>' not in cell_resp
     assert len(mock_send.call_args_list) == 1
-    assert '/api/cards/card_model_1/11/' in mock_send.call_args_list[0][0][0].url
+    assert '/api/cards/card_model_1/list' in mock_send.call_args_list[0][0][0].url
+    assert '&filter-internal-id=11&' in mock_send.call_args_list[0][0][0].url
 
     # with identifiers
     page.sub_slug = ''
@@ -3224,7 +3225,8 @@ def test_card_cell_render_identifier(mock_send, nocache, app):
     assert 'Card Model 1' in cell_resp
     assert '<p>Unknown Card</p>' in cell_resp
     assert len(mock_send.call_args_list) == 1
-    assert '/api/cards/card_model_1/42/' in mock_send.call_args_list[0][0][0].url
+    assert '/api/cards/card_model_1/list' in mock_send.call_args_list[0][0][0].url
+    assert '&filter-internal-id=42&' in mock_send.call_args_list[0][0][0].url
 
     cell.card_ids = '42, , 35'
     cell.save()
@@ -3240,8 +3242,9 @@ def test_card_cell_render_identifier(mock_send, nocache, app):
         assert 'Card Model 1' in cell_resp
         assert '<p>Unknown Card</p>' in cell_resp
     assert len(mock_send.call_args_list) == 2
-    assert '/api/cards/card_model_1/42/' in mock_send.call_args_list[0][0][0].url
-    assert '/api/cards/card_model_1/35/' in mock_send.call_args_list[1][0][0].url
+    for i in range(0, 2):
+        assert '/api/cards/card_model_1/list' in mock_send.call_args_list[i][0][0].url
+        assert '&filter-internal-id=42&filter-internal-id=35&' in mock_send.call_args_list[i][0][0].url
 
     cell.card_ids = '{% cards|objects:"card_model_1"|last|get:"id" %}'  # syntax error
     cell.save()
@@ -3267,7 +3270,8 @@ def test_card_cell_render_identifier(mock_send, nocache, app):
     assert '/api/cards/card_model_1/list' in mock_send.call_args_list[0][0][0].url
     # cell rendering
     assert '/api/cards/card_model_1/list' in mock_send.call_args_list[1][0][0].url
-    assert '/api/cards/card_model_1/13/' in mock_send.call_args_list[2][0][0].url
+    assert '/api/cards/card_model_1/list' in mock_send.call_args_list[2][0][0].url
+    assert '&filter-internal-id=13&' in mock_send.call_args_list[2][0][0].url
 
     def test_card_ids():
         mock_send.reset_mock()
@@ -3283,12 +3287,12 @@ def test_card_cell_render_identifier(mock_send, nocache, app):
         # page rendering
         assert '/api/cards/card_model_1/list' in mock_send.call_args_list[0][0][0].url
         # cell rendering
+        card_ids = [c['id'] for c in WCS_CARDS_DATA['card_model_1']]
+        filters = '&%s&' % '&'.join('filter-internal-id=%s' % cid for cid in card_ids)
         for i in range(0, 3):
             assert '/api/cards/card_model_1/list' in mock_send.call_args_list[i * 2 + 1][0][0].url
-            assert (
-                '/api/cards/card_model_1/%s/' % WCS_CARDS_DATA['card_model_1'][i]['id']
-                in mock_send.call_args_list[i * 2 + 2][0][0].url
-            )
+            assert '/api/cards/card_model_1/list' in mock_send.call_args_list[i * 2 + 2][0][0].url
+            assert filters in mock_send.call_args_list[i * 2 + 2][0][0].url
 
     for card_ids in [
         '{% for card in cards|objects:"card_model_1" %}{{ card.id }},{% endfor %}',
@@ -3361,8 +3365,11 @@ def test_card_cell_render_identifier_from_related(mock_send, nocache, app):
         cell_resp = app.get(cell2_url + '?ctx=' + extra_ctx[1])
         assert cell_resp.context['repeat_index'] == 0
         assert len(mock_send.call_args_list) == len(urls)
-        for j, url in enumerate(urls):
-            assert url in mock_send.call_args_list[j][0][0].url
+        for j, url_parts in enumerate(urls):
+            if not isinstance(url_parts, tuple):
+                url_parts = (url_parts,)
+            for url_part in url_parts:
+                assert url_part in mock_send.call_args_list[j][0][0].url
 
     def multiple(urls):
         resp = app.get(page.get_online_url())
@@ -3376,11 +3383,11 @@ def test_card_cell_render_identifier_from_related(mock_send, nocache, app):
             cell_resp = app.get(cell2_url + '?ctx=' + extra_ctx[i + 1])
             assert cell_resp.context['repeat_index'] == i
             assert len(mock_send.call_args_list) == len(urls)
-            for j, url in enumerate(urls):
-                if isinstance(url, list):
-                    assert url[i] in mock_send.call_args_list[j][0][0].url
-                else:
-                    assert url in mock_send.call_args_list[j][0][0].url
+            for j, url_parts in enumerate(urls):
+                if not isinstance(url_parts, tuple):
+                    url_parts = (url_parts,)
+                for url_part in url_parts:
+                    assert url_part in mock_send.call_args_list[j][0][0].url
 
     # no cell with this slug
     cell2.related_card_path = 'slugz/cardb'
@@ -3430,7 +3437,7 @@ def test_card_cell_render_identifier_from_related(mock_send, nocache, app):
             # get first cell data
             '/api/cards/card_a/1/',
             # and follow cardb relation
-            '/api/cards/card_b/1/',
+            ('/api/cards/card_b/list', '&filter-internal-id=1&'),
         ]
     )
     cell3.delete()  # reset
@@ -3446,7 +3453,7 @@ def test_card_cell_render_identifier_from_related(mock_send, nocache, app):
             # follow cardc relation
             '/api/cards/card_c/6/',
             # and follow cardb relation
-            '/api/cards/card_b/7/',
+            ('/api/cards/card_b/list', '&filter-internal-id=7&'),
         ]
     )
 
@@ -3466,7 +3473,7 @@ def test_card_cell_render_identifier_from_related(mock_send, nocache, app):
         # follow cardc relation
         '/api/cards/card_c/6/',
         # and follow cardb relation
-        '/api/cards/card_b/7/',
+        ('/api/cards/card_b/list', '&filter-internal-id=7&'),
     ]
     resp = app.get(page.get_online_url())
     assert len(resp.context['cells']) == 2
@@ -3478,8 +3485,11 @@ def test_card_cell_render_identifier_from_related(mock_send, nocache, app):
     cell_resp = app.get(cell2_url + '?ctx=' + extra_ctx[0])
     assert cell_resp.context['repeat_index'] == 0
     assert len(mock_send.call_args_list) == len(urls)
-    for j, url in enumerate(urls):
-        assert url in mock_send.call_args_list[j][0][0].url
+    for j, url_parts in enumerate(urls):
+        if not isinstance(url_parts, tuple):
+            url_parts = (url_parts,)
+        for url_part in url_parts:
+            assert url_part in mock_send.call_args_list[j][0][0].url
 
     # reset
     cell.order = 0  # reset
@@ -3499,7 +3509,7 @@ def test_card_cell_render_identifier_from_related(mock_send, nocache, app):
             # follow cardc relation
             '/api/cards/card_c/6/',
             # and follow cardb relation
-            '/api/cards/card_b/a-custom-view/7/',
+            ('/api/cards/card_b/list/a-custom-view', '&filter-internal-id=7&'),
         ]
     )
 
@@ -3512,7 +3522,7 @@ def test_card_cell_render_identifier_from_related(mock_send, nocache, app):
             # get first cell data
             '/api/cards/card_a/1/',
             # and follow cardb relation
-            ['/api/cards/card_b/2/', '/api/cards/card_b/3/'],
+            ('/api/cards/card_b/list', '&filter-internal-id=2&filter-internal-id=3&'),
         ]
     )
 
@@ -3527,7 +3537,7 @@ def test_card_cell_render_identifier_from_related(mock_send, nocache, app):
             # follow cardc relation
             '/api/cards/card_c/6/',
             # and follow cardb relation
-            ['/api/cards/card_b/8/', '/api/cards/card_b/9/'],
+            ('/api/cards/card_b/list', '&filter-internal-id=8&filter-internal-id=9&'),
         ]
     )
 
@@ -3539,7 +3549,7 @@ def test_card_cell_render_identifier_from_related(mock_send, nocache, app):
             # get first cell data
             '/api/cards/card_a/1/',
             # and follow cardb relation
-            ['/api/cards/card_b/4/', '/api/cards/card_b/5/'],
+            ('/api/cards/card_b/list', '&filter-internal-id=4&filter-internal-id=5&'),
         ]
     )
 
@@ -3554,7 +3564,7 @@ def test_card_cell_render_identifier_from_related(mock_send, nocache, app):
             # follow cardc relation
             '/api/cards/card_c/6/',
             # and follow cardb relation
-            ['/api/cards/card_b/10/', '/api/cards/card_b/11/'],
+            ('/api/cards/card_b/list', '&filter-internal-id=10&filter-internal-id=11&'),
         ]
     )
 
@@ -3716,7 +3726,10 @@ def test_card_cell_render_identifier_from_related(mock_send, nocache, app):
             # get list of card_a with cardb=1
             '/api/cards/card_a/list?orig=combo&filter-cardb=1',
             # and follow carda reverse relation
-            ['/api/cards/card_a/%s/' % i for i in range(1, 5)],
+            (
+                '/api/cards/card_a/list',
+                '&filter-internal-id=1&filter-internal-id=2&filter-internal-id=3&filter-internal-id=4&',
+            ),
         ]
     )
 
@@ -3730,7 +3743,10 @@ def test_card_cell_render_identifier_from_related(mock_send, nocache, app):
             # get list of card_a with cardsb=1
             '/api/cards/card_a/list?orig=combo&filter-cardsb=1',
             # and follow carda reverse relation
-            ['/api/cards/card_a/%s/' % i for i in range(1, 5)],
+            (
+                '/api/cards/card_a/list',
+                '&filter-internal-id=1&filter-internal-id=2&filter-internal-id=3&filter-internal-id=4&',
+            ),
         ]
     )
 
@@ -3744,7 +3760,10 @@ def test_card_cell_render_identifier_from_related(mock_send, nocache, app):
             # get list of card_a with cardsb=1
             '/api/cards/card_a/list?orig=combo&filter-blockb_cardb=1',
             # and follow carda reverse relation
-            ['/api/cards/card_a/%s/' % i for i in range(1, 5)],
+            (
+                '/api/cards/card_a/list',
+                '&filter-internal-id=1&filter-internal-id=2&filter-internal-id=3&filter-internal-id=4&',
+            ),
         ]
     )
 
@@ -3786,7 +3805,7 @@ def test_card_cell_render_identifier_from_related(mock_send, nocache, app):
             # get list of card_f with cardf=42
             '/api/cards/card_f/list?orig=combo&filter-cardh=42',
             # and follow cardf reverse relation
-            ['/api/cards/card_f/41/'],
+            ('/api/cards/card_f/list', '&filter-internal-id=41&'),
         ]
     )
 
@@ -3805,7 +3824,7 @@ def test_card_cell_render_identifier_from_related(mock_send, nocache, app):
             # get list of card_g with cardf=44
             '/api/cards/card_g/list?orig=combo&filter-cardh=44',
             # and follow cardf reverse relation
-            ['/api/cards/card_g/43/'],
+            ('/api/cards/card_g/list', '&filter-internal-id=43&'),
         ]
     )
 
-- 
2.35.1

