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')