From bead5a6361fba59b7e794dfdb835251838f084a7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Tue, 26 Oct 2021 10:16:14 +0200
Subject: [PATCH] wcs: custom field for card cell (#57134)

---
 combo/apps/wcs/models.py                      | 17 +++++-
 combo/apps/wcs/templates/combo/wcs/card.html  | 12 +++++
 .../wcs/manager/card-infos-cell-form.html     |  6 +++
 combo/manager/static/js/combo.manager.js      | 36 +++++++++++--
 tests/test_wcs.py                             | 54 ++++++++++++++++++-
 5 files changed, 119 insertions(+), 6 deletions(-)

diff --git a/combo/apps/wcs/models.py b/combo/apps/wcs/models.py
index d468194d..56ecca43 100644
--- a/combo/apps/wcs/models.py
+++ b/combo/apps/wcs/models.py
@@ -1016,15 +1016,30 @@ class WcsCardInfosCell(CardMixin, CellBase):
 
         if response.status_code == 200:
             extra_context['card'] = response.json()
+            context = Context(extra_context)
             if self.title_type == 'manual':
                 try:
-                    extra_context['title'] = Template(self.custom_title).render(Context(extra_context))
+                    extra_context['title'] = Template(self.custom_title).render(context)
                 except (VariableDoesNotExist, TemplateSyntaxError):
                     extra_context['title'] = ''
             # auto title or custom_title gives an empty title, use default value + card text
             if self.title_type == 'auto' or self.title_type == 'manual' and not extra_context['title']:
                 extra_context['title'] = '%s - %s' % (self.cached_title, extra_context['card'].get('text'))
 
+            extra_context['card']['custom_fields'] = {}
+            if self.custom_schema:
+                for item in self.custom_schema.get('cells') or []:
+                    if item.get('varname') != '@custom@':
+                        continue
+                    if not item.get('template'):
+                        continue
+                    try:
+                        extra_context['card']['custom_fields'][item['template']] = Template(
+                            item['template']
+                        ).render(context)
+                    except (VariableDoesNotExist, TemplateSyntaxError):
+                        pass
+
         return extra_context
 
     def get_default_form_class(self):
diff --git a/combo/apps/wcs/templates/combo/wcs/card.html b/combo/apps/wcs/templates/combo/wcs/card.html
index 73109461..8870fde0 100644
--- a/combo/apps/wcs/templates/combo/wcs/card.html
+++ b/combo/apps/wcs/templates/combo/wcs/card.html
@@ -14,6 +14,17 @@
   <div class="{{ cell.custom_schema.grid_class }}">
     {% for item in cell.custom_schema.cells %}
       <div class="{{ item.cell_size|default:"" }}">
+        {% if item.varname == "@custom@" and item.template %}
+          {% with card.custom_fields|get:item.template as value %}
+          {% if item.display_mode == "title" %}
+          <h3>{{ value }}</h3>
+          {% elif item.display_mode == "label" %}
+            <p class="label">{{ value }}</p>
+          {% elif item.display_mode == "value" %}
+            <div class="value">{{ value }}</div>
+          {% endif %}
+          {% endwith %}
+        {% else %}
         {% for field in schema.fields %}
           {% if field.varname == item.varname %}
           {% with card.fields|get:item.varname as value %}
@@ -29,6 +40,7 @@
           {% endwith %}
           {% endif %}
         {% endfor%}
+        {% endif %}
       </div>
     {% endfor%}
   </div>
diff --git a/combo/apps/wcs/templates/combo/wcs/manager/card-infos-cell-form.html b/combo/apps/wcs/templates/combo/wcs/manager/card-infos-cell-form.html
index adaeab53..a72e13b1 100644
--- a/combo/apps/wcs/templates/combo/wcs/manager/card-infos-cell-form.html
+++ b/combo/apps/wcs/templates/combo/wcs/manager/card-infos-cell-form.html
@@ -44,6 +44,12 @@
         <select name="card_field_varname"></select>
       </label>
     </p>
+    <p>
+      <label>
+        {% trans "Value template" %}
+        <input name="card_field_template" />
+      </label>
+    </p>
     <p>
       <label>
         {% trans "Display" %}
diff --git a/combo/manager/static/js/combo.manager.js b/combo/manager/static/js/combo.manager.js
index 318ce7fc..de952123 100644
--- a/combo/manager/static/js/combo.manager.js
+++ b/combo/manager/static/js/combo.manager.js
@@ -472,8 +472,9 @@ Card_cell_custom.prototype = {
   //  Grid cell methods
   grid_cell__init_form: function() {
     this.grid_cell_form.varname = this.grid_cell_form[0];
-    this.grid_cell_form.display_mode = this.grid_cell_form[1];
-    this.grid_cell_form.size = this.grid_cell_form[2];
+    this.grid_cell_form.template = this.grid_cell_form[1];
+    this.grid_cell_form.display_mode = this.grid_cell_form[2];
+    this.grid_cell_form.size = this.grid_cell_form[3];
     const varname_select = this.grid_cell_form.varname;
     this.cardSchema.fields.forEach(function(el, id) {
       if (el.varname) {
@@ -483,12 +484,32 @@ Card_cell_custom.prototype = {
           .appendTo(varname_select);
       }
     })
+    // add a field 'Custom', so the user can define a template
+    $('<option />')
+      .attr('value', '@custom@')
+      .text(gettext('Custom'))
+      .appendTo(varname_select);
+
+    // hide or show template, change mode options, depending on selected field
+    const _label_and_value_option = this.grid_cell_form.display_mode[0];
+    $(document).on('change', 'select[name="card_field_varname"]', function() {
+      if ($(this).val() == '@custom@') {
+        $('input[name="card_field_template"]').parent().show();
+        $('select[name="display_mode"] option[value="label-and-value"]').remove();
+      } else {
+        $('input[name="card_field_template"]').parent().hide();
+        $(_label_and_value_option).prependTo($('select[name="display_mode"]'));
+      }
+    });
   },
   grid_cell__form_dialog: function(button_label, callback){
     const _self = this;
     $(this.grid_cell_form).dialog({
       modal: true,
       width: 'auto',
+      open: function( event, ui ) {
+        $('select[name="card_field_varname"]').change();
+      },
       buttons: [
         {
           text: gettext('Cancel'),
@@ -532,14 +553,19 @@ Card_cell_custom.prototype = {
     // set cell text
     let schema_field = _self.field_with_varname(schema_cell.varname);
     let cell_text = "";
-    if (schema_field) {
-      cell_text += '<span class="' + schema_cell.display_mode + '">' + schema_field.label + '</span>';
+    if (schema_field || schema_cell.varname == '@custom@') {
+      if (schema_cell.varname == '@custom@') {
+        cell_text += '<span class="' + schema_cell.display_mode + '">' + schema_cell.template + ' (' + gettext('Custom') + ')</span>';
+      } else {
+        cell_text += '<span class="' + schema_cell.display_mode + '">' + schema_field.label + '</span>';
+      }
       cell_text += '<span class="cell-meta">';
       let cell_display_mode_label = $(this.grid_cell_form).find('select[name="display_mode"] option[value="' + schema_cell.display_mode + '"]').text();
       cell_text += '<span class="cell-display-mode-label">' + cell_display_mode_label + '</span>';
       let cell_size_label = $(this.grid_cell_form).find('select[name="cell_size"] option[value="' + schema_cell.cell_size + '"]').text();
       cell_text += '<span class="cell-size-label">' + gettext('Size:') + ' ' + cell_size_label + '</span>';
       cell_text += '</span>';
+    } else if (schema_cell.varname == '@custom@') {
     } else {
       cell_text += '<span class="warning">' + gettext('Deleted field:') + ' ' + schema_cell.varname + '</span>';
     }
@@ -564,6 +590,7 @@ Card_cell_custom.prototype = {
   grid_cell__edit: function(grid_cell) {
     const _self = this;
     this.grid_cell_form.varname.value = grid_cell.dataset.varname;
+    this.grid_cell_form.template.value = grid_cell.dataset.template;
     this.grid_cell_form.size.value = grid_cell.dataset.cell_size;
     this.grid_cell_form.display_mode.value = grid_cell.dataset.display_mode;
     const cell_id = $(grid_cell).index();
@@ -576,6 +603,7 @@ Card_cell_custom.prototype = {
   },
   grid_cell__set_schema: function(form_datas, schema_cell) {
     schema_cell.varname = form_datas.card_field_varname;
+    schema_cell.template = form_datas.card_field_template;
     schema_cell.cell_size = form_datas.cell_size;
     schema_cell.display_mode = form_datas.display_mode;
     return schema_cell
diff --git a/tests/test_wcs.py b/tests/test_wcs.py
index c6cdb6a6..8d64cd1b 100644
--- a/tests/test_wcs.py
+++ b/tests/test_wcs.py
@@ -1876,7 +1876,6 @@ def test_card_cell_render_text_field(mock_send, context):
     page = Page.objects.create(title='xxx', template_name='standard')
     cell = WcsCardInfosCell(page=page, placeholder='content', order=0)
     cell.carddef_reference = 'default:card_model_1'
-    cell.custom_title = 'Foo bar {{ card.fields.title }}'
     cell.save()
 
     context['card_model_1_id'] = 11
@@ -1901,6 +1900,59 @@ def test_card_cell_render_text_field(mock_send, context):
     )
 
 
+@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
+def test_card_cell_render_custom_schema(mock_send, context):
+    page = Page.objects.create(title='xxx', template_name='standard')
+    cell = WcsCardInfosCell(page=page, placeholder='content', order=0)
+    cell.carddef_reference = 'default:card_model_1'
+    cell.custom_schema = {
+        'cells': [
+            {'varname': 'fielda', 'display_mode': 'title'},
+        ]
+    }
+    cell.save()
+
+    context['card_model_1_id'] = 11
+    context['synchronous'] = True  # to get fresh content
+
+    result = cell.render(context)
+    assert PyQuery(result).find('h3').text() == 'a'
+
+    # custom field
+    cell.custom_schema = {
+        'cells': [
+            {'varname': '@custom@', 'template': 'Foo bar baz', 'display_mode': 'title'},
+        ]
+    }
+    cell.save()
+    result = cell.render(context)
+    assert PyQuery(result).find('h3').text() == 'Foo bar baz'
+
+    # test context
+    cell.custom_schema['cells'][0][
+        'template'
+    ] = '{{ card.fields.fielda }} - {{ card.fields.related }} ({{ card.fields.related_structured.id }})'
+    cell.save()
+    result = cell.render(context)
+    assert PyQuery(result).find('h3').text() == 'a - Foo Bar (42)'
+
+    # test display_mode & filters in template
+    cell.custom_schema = {
+        'cells': [
+            {'varname': '@custom@', 'template': 'Foo bar baz', 'display_mode': 'label'},
+            {
+                'varname': '@custom@',
+                'template': '{{ card.fields.related|split:" "|join:"," }}',
+                'display_mode': 'value',
+            },
+        ]
+    }
+    cell.save()
+    result = cell.render(context)
+    assert PyQuery(result).find('p.label').text() == 'Foo bar baz'
+    assert PyQuery(result).find('div.value').text() == 'Foo,Bar'
+
+
 @mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
 def test_card_cell_render_identifier(mock_send, context, nocache):
     page = Page.objects.create(title='xxx', template_name='standard')
-- 
2.33.0

