From 7716113a51160e5611db1c9667c8c566ab72673f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= Date: Thu, 11 Aug 2022 16:46:26 +0200 Subject: [PATCH] wcs: render card cell with table mode (#68063) --- combo/apps/wcs/models.py | 55 +++- combo/apps/wcs/templates/combo/wcs/cards.html | 2 +- tests/wcs/test_card.py | 267 +++++++++++++++++- 3 files changed, 317 insertions(+), 7 deletions(-) diff --git a/combo/apps/wcs/models.py b/combo/apps/wcs/models.py index 42c55ef9..7556fcdb 100644 --- a/combo/apps/wcs/models.py +++ b/combo/apps/wcs/models.py @@ -34,7 +34,7 @@ from requests.exceptions import RequestException from combo.data.library import register_cell_class from combo.data.models import CellBase, Page -from combo.utils import requests +from combo.utils import NothingInCacheException, requests from combo.utils.misc import is_portal_agent from .utils import get_wcs_json, get_wcs_services, is_wcs_enabled @@ -979,7 +979,6 @@ class WcsCardInfosCell(CardMixin, CellBase): is_enabled = classmethod(is_wcs_enabled) - default_template_name = 'combo/wcs/card.html' manager_appearance_template = 'combo/wcs/manager/card-infos-cell-form-appearance.html' class Meta: @@ -1035,16 +1034,28 @@ class WcsCardInfosCell(CardMixin, CellBase): self.mark_as_valid() + @property + def default_template_name(self): + if self.display_mode == 'table': + return 'combo/wcs/cards.html' + return 'combo/wcs/card.html' + @property def global_context_key(self): return '%s-card-ids' % self.get_reference() def modify_global_context(self, context, request): + if self.display_mode == 'table' and not context.get('synchronous'): + # don't call wcs on page loading + return if self.carddef_reference and self.global_context_key not in context: card_ids = self.get_card_ids(context, request) context[self.global_context_key] = card_ids def get_repeat_template(self, context): + if self.display_mode == 'table': + # don't repeat cell if table display mode + return [] return len(context.get(self.global_context_key) or []) def get_related_card_path(self): @@ -1406,6 +1417,9 @@ class WcsCardInfosCell(CardMixin, CellBase): return self.filter_card_ids(card_ids, original_context) if self.must_get_all(): + if self.display_mode == 'table': + # don't call wcs if table mode with all cards + return [] # get all cards return [c['id'] for c in self.get_cards_from_ids([], original_context, synchronous=True)] @@ -1442,13 +1456,44 @@ class WcsCardInfosCell(CardMixin, CellBase): def get_cell_extra_context(self, context): extra_context = super().get_cell_extra_context(context) + if self.title_type in ['auto', 'manual']: + # card mode: default value used if card is not found + extra_context['title'] = self.cached_title + return getattr(self, 'get_cell_extra_context_%s_mode' % self.display_mode)(context, extra_context) + + def get_cell_extra_context_table_mode(self, context, extra_context): + if not context.get('synchronous'): + raise NothingInCacheException() + + extra_context['paginate_by'] = self.limit or 10 + if self.title_type == 'manual': + extra_context['title'] = self.custom_title or extra_context['title'] + if not self.carddef_reference: + # not configured + return extra_context + + pages_with_sub_slug = Page.objects.exclude(sub_slug='') + card_id = '%s_id' % self.carddef_reference.split(':')[1] + matching_pages = [ + p for p in pages_with_sub_slug if '<%s>' % card_id in p.sub_slug or p.sub_slug == card_id + ] + if matching_pages: + card_page = matching_pages[0] + extra_context['card_page_base_url'] = card_page.get_online_url() + + card_ids = context.get(self.global_context_key) + if not card_ids and self.related_card_path != '__all__': + extra_context['card_objects'] = [] + else: + extra_context['card_objects'] = self.get_cards_from_ids(card_ids, context) + + return extra_context + + def get_cell_extra_context_card_mode(self, context, extra_context): extra_context['schema'] = self.cached_json extra_context['fields_by_varnames'] = { i['varname']: i for i in (self.cached_json.get('fields') or []) if i.get('varname') } - # default value used if card is not found - if self.title_type in ['auto', 'manual']: - extra_context['title'] = self.cached_title card_id = self.get_card_id(context) if not card_id: diff --git a/combo/apps/wcs/templates/combo/wcs/cards.html b/combo/apps/wcs/templates/combo/wcs/cards.html index 4168cc31..a7edd9f0 100644 --- a/combo/apps/wcs/templates/combo/wcs/cards.html +++ b/combo/apps/wcs/templates/combo/wcs/cards.html @@ -3,7 +3,7 @@ {% block cell-content %} {% block cell-header %} -

{{ title }}

+{% if title %}

{{ title|force_escape }}

{% endif %} {% include "combo/asset_picture_fragment.html" %} {% endblock %} diff --git a/tests/wcs/test_card.py b/tests/wcs/test_card.py index 3cee2796..c1c17094 100644 --- a/tests/wcs/test_card.py +++ b/tests/wcs/test_card.py @@ -18,7 +18,7 @@ from combo.data.models import Page, TextCell, ValidityInfo from tests.test_manager import login from tests.utils import manager_submit_cell -from .utils import WCS_CARDS_DATA, MockUser, MockUserWithNameId, mocked_requests_send +from .utils import WCS_CARDS_DATA, MockedRequestResponse, MockUser, MockUserWithNameId, mocked_requests_send pytestmark = pytest.mark.django_db @@ -633,6 +633,271 @@ def test_card_cell_load(mock_send): assert cell.cached_title == 'Card Model 1' +@mock.patch('requests.Session.send', side_effect=mocked_requests_send) +def test_card_cell_table_mode_render(mock_send, context, app): + page = Page.objects.create(title='xxx', template_name='standard') + cell = WcsCardInfosCell.objects.create( + page=page, + placeholder='content', + order=0, + display_mode='table', + carddef_reference='default:card_model_1', + ) + + context['card_model_1_id'] = 11 + request = RequestFactory().get('/') + context['synchronous'] = True # to get fresh content + cell.modify_global_context(context, request) + + result = cell.render(context) + assert '

Card Model 1

' in result + assert 'cards-card_model_1' in result + assert ( + 'aa' + in result + ) + assert ( + 'bb' + in result + ) + assert ( + 'cc' + in result + ) + assert 'data-paginate-by="10"' in result + + # create a page with the correct subslug + page = Page.objects.create(slug='foo', title='Foo', sub_slug='(?P[a-z0-9]+)') + + result = cell.render(context) + assert '

Card Model 1

' in result + assert 'aa' in result + assert 'bb' in result + assert 'cc' in result + + cell.carddef_reference = 'default:card_model_1:foo' + cell.limit = 42 + cell.save() + page.sub_slug = 'card_model_1_id' + page.save() + + result = cell.render(context) + assert '

Card Model 1

' in result + assert 'aa' in result + assert 'bb' in result + assert 'cc' not in result + assert 'data-paginate-by="42"' in result + + with mock.patch('combo.apps.wcs.models.requests.get') as requests_get: + requests_get.return_value = MockedRequestResponse(content=json.dumps({'data': []})) + cell.render(context) + assert len(requests_get.call_args_list) == 1 + assert ( + requests_get.call_args_list[0][0][0] + == '/api/cards/card_model_1/list/foo?full=on&filter-internal-id=11' + ) + assert requests_get.call_args_list[0][1]['remote_service']['url'] == 'http://127.0.0.1:8999/' + + +@mock.patch('requests.Session.send', side_effect=mocked_requests_send) +def test_card_cell_table_mode_render_all_cards(mock_send, nocache, app): + page = Page.objects.create(title='xxx', slug='foo', template_name='standard') + cell = WcsCardInfosCell.objects.create( + page=page, + placeholder='content', + order=0, + display_mode='table', + carddef_reference='default:card_model_1', + related_card_path='__all__', + ) + + cell_url = reverse( + 'combo-public-ajax-page-cell', + kwargs={'page_pk': page.pk, 'cell_reference': cell.get_reference()}, + ) + + # check url called + mock_send.reset_mock() + resp = app.get(page.get_online_url()) + assert len(resp.context['cells']) == 1 + extra_ctx = re.findall(r'data-extra-context="(.*)"', resp.text) + app.get(cell_url + '?ctx=' + extra_ctx[0]) + assert len(mock_send.call_args_list) == 1 + # cell rendering + assert '/api/cards/card_model_1/list' in mock_send.call_args_list[0][0][0].url + assert 'filter-internal-id' not in mock_send.call_args_list[0][0][0].url + + +@mock.patch('requests.Session.send', side_effect=mocked_requests_send) +def test_card_cell_table_mode_render_identifier(mock_send, nocache, app): + page = Page.objects.create( + title='xxx', slug='foo', template_name='standard', sub_slug='(?P[a-z0-9]+)' + ) + cell = WcsCardInfosCell.objects.create( + page=page, + placeholder='content', + order=0, + display_mode='table', + carddef_reference='default:card_model_1', + ) + + cell_url = reverse( + 'combo-public-ajax-page-cell', + kwargs={'page_pk': page.pk, 'cell_reference': cell.get_reference()}, + ) + + # check url called + mock_send.reset_mock() + resp = app.get(page.get_online_url() + '11/') + assert len(resp.context['cells']) == 1 + extra_ctx = re.findall(r'data-extra-context="(.*)"', resp.text) + cell_resp = app.get(cell_url + '?ctx=' + extra_ctx[0]) + assert len(mock_send.call_args_list) == 1 + 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 = '' + page.save() + cell.card_ids = '42' + cell.save() + mock_send.reset_mock() + resp = app.get(page.get_online_url()) + assert len(resp.context['cells']) == 1 + extra_ctx = re.findall(r'data-extra-context="(.*)"', resp.text) + cell_resp = app.get(cell_url + '?ctx=' + extra_ctx[0]) + assert len(mock_send.call_args_list) == 1 + 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() + mock_send.reset_mock() + resp = app.get(page.get_online_url()) + assert len(resp.context['cells']) == 1 + extra_ctx = re.findall(r'data-extra-context="(.*)"', resp.text) + cell_resp = app.get(cell_url + '?ctx=' + extra_ctx[0]) + assert len(mock_send.call_args_list) == 1 + assert '/api/cards/card_model_1/list' in mock_send.call_args_list[0][0][0].url + assert '&filter-internal-id=42&filter-internal-id=35&' in mock_send.call_args_list[0][0][0].url + + cell.card_ids = '{% cards|objects:"card_model_1"|last|get:"id" %}' # syntax error + cell.save() + mock_send.reset_mock() + resp = app.get(page.get_online_url()) + assert len(resp.context['cells']) == 1 + extra_ctx = re.findall(r'data-extra-context="(.*)"', resp.text) + cell_resp = app.get(cell_url + '?ctx=' + extra_ctx[0]) + assert 'empty-message' in cell_resp + + cell.card_ids = '{{ cards|objects:"card_model_1"|last|get:"id" }}' + cell.save() + mock_send.reset_mock() + resp = app.get(page.get_online_url()) + assert len(resp.context['cells']) == 1 + extra_ctx = re.findall(r'data-extra-context="(.*)"', resp.text) + cell_resp = app.get(cell_url + '?ctx=' + extra_ctx[0]) + assert len(mock_send.call_args_list) == 2 + # cell rendering + assert '/api/cards/card_model_1/list' in mock_send.call_args_list[0][0][0].url + assert '/api/cards/card_model_1/list' in mock_send.call_args_list[1][0][0].url + assert '&filter-internal-id=13&' in mock_send.call_args_list[1][0][0].url + + +@mock.patch('requests.Session.send', side_effect=mocked_requests_send) +def test_card_cell_table_mode_render_identifier_from_related(mock_send, nocache, app): + page = Page.objects.create(title='xxx', slug='foo', template_name='standard') + WcsCardInfosCell.objects.create( + page=page, + placeholder='content', + order=0, + display_mode='table', + slug='sluga', + carddef_reference='default:card_a', + card_ids='1', + ) + cell2 = WcsCardInfosCell.objects.create( + page=page, placeholder='content', order=1, slug='slugb', carddef_reference='default:card_b' + ) + + cell2_url = reverse( + 'combo-public-ajax-page-cell', + kwargs={'page_pk': page.pk, 'cell_reference': cell2.get_reference()}, + ) + + # just do a simple test to check url calls + def check(urls): + resp = app.get(page.get_online_url()) + assert len(resp.context['cells']) == 2 + extra_ctx = re.findall(r'data-extra-context="(.*)"', resp.text) + mock_send.reset_mock() + app.get(cell2_url + '?ctx=' + extra_ctx[1]) + assert len(mock_send.call_args_list) == len(urls) + 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 + + # direct and single relation (item) + cell2.related_card_path = 'sluga/cardb' + cell2.save() + urls = [ + # get first cell data + '/api/cards/card_a/1/', + # and follow cardb relation + '/api/cards/card_b/1/', # get card + ] + check(urls) + + # direct and multiple relation (items) + cell2.carddef_reference = 'default:card_b' # reset + cell2.related_card_path = 'sluga/cardsb' + cell2.save() + urls = [ + # get first cell data + '/api/cards/card_a/1/', + # and follow cardb relation + ('/api/cards/card_b/list', '&filter-internal-id=2&filter-internal-id=3&'), # get cards + ] + check(urls) + + +@mock.patch('requests.Session.send', side_effect=mocked_requests_send) +def test_card_cell_table_mode_render_title(mock_send, context): + page = Page.objects.create(title='xxx', template_name='standard') + cell = WcsCardInfosCell.objects.create( + page=page, + placeholder='content', + order=0, + display_mode='table', + carddef_reference='default:card_model_1', + title_type='auto', + ) + + context['synchronous'] = True # to get fresh content + + result = cell.render(context) + assert '

Card Model 1

' in result + + context.pop('title') + cell.title_type = 'empty' + cell.save() + result = cell.render(context) + assert '

' not in result + + cell.title_type = 'manual' + cell.save() + result = cell.render(context) + assert '

Card Model 1

' in result + + context.pop('title') + cell.custom_title = 'Foo bar !' + cell.save() + result = cell.render(context) + assert '

Foo bar !

' in result + + @mock.patch('requests.Session.send', side_effect=mocked_requests_send) def test_card_cell_card_mode_render(mock_send, context, app): page = Page.objects.create(title='xxx', template_name='standard')