From 8824d1b8b5f05e60dcad22900da41ee67eee9227 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Thu, 16 Jun 2022 16:49:37 +0200
Subject: [PATCH] manager: add a visual mark to cell tabs with configured
 settings (#64936)

---
 combo/apps/wcs/forms.py                       |  9 ++++++--
 combo/data/forms.py                           |  2 ++
 combo/data/models.py                          |  4 +++-
 combo/manager/forms.py                        | 14 ++++++++++++
 combo/manager/static/js/combo.manager.js      |  9 ++++++--
 .../combo/manager_edit_cell_block.html        | 11 +++++-----
 combo/manager/templatetags/cells.py           |  3 +++
 combo/manager/views.py                        |  7 +++++-
 tests/test_dataviz.py                         |  4 ++--
 tests/test_manager.py                         | 15 ++++++++++---
 tests/test_wcs.py                             | 22 +++++++++++++++----
 tests/utils.py                                |  4 +++-
 12 files changed, 83 insertions(+), 21 deletions(-)

diff --git a/combo/apps/wcs/forms.py b/combo/apps/wcs/forms.py
index 12425205..33608c6a 100644
--- a/combo/apps/wcs/forms.py
+++ b/combo/apps/wcs/forms.py
@@ -50,7 +50,9 @@ class WcsFormForLinkListCellForm(WcsFormCellForm):
 
 
 class WcsCardsCellForm(forms.ModelForm):
-    with_user = forms.BooleanField(label=_('Restrict to cards accessible to the user'), required=False)
+    with_user = forms.BooleanField(
+        label=_('Restrict to cards accessible to the user'), required=False, initial=True
+    )
 
     class Meta:
         model = WcsCardsCell
@@ -72,7 +74,9 @@ class WcsCardsCellForm(forms.ModelForm):
 
 
 class WcsCardInfoCellForm(forms.ModelForm):
-    with_user = forms.BooleanField(label=_('Restrict to cards accessible to the user'), required=False)
+    with_user = forms.BooleanField(
+        label=_('Restrict to cards accessible to the user'), required=False, initial=True
+    )
     customize_display = forms.BooleanField(label=_('Customize display'), required=False)
     related_card_path = forms.ChoiceField(label=_('Card Identifier'), required=False)
 
@@ -116,6 +120,7 @@ class WcsCardInfoCellForm(forms.ModelForm):
             self.fields['related_card_path'].choices += [
                 ('--', _('Identifier from page URL')),
             ]
+            self.fields['related_card_path'].initial = '--'
             if not self.instance.card_ids and not self.instance.related_card_path:
                 self.initial['related_card_path'] = '--'
         self.fields['related_card_path'].choices += [
diff --git a/combo/data/forms.py b/combo/data/forms.py
index 96a4059a..f26e69c4 100644
--- a/combo/data/forms.py
+++ b/combo/data/forms.py
@@ -62,6 +62,8 @@ class LinkListCellForm(forms.ModelForm):
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
+        if self.instance.get_items_with_prefetch():
+            self.is_not_default = True
 
 
 class ConfigJsonForm(forms.ModelForm):
diff --git a/combo/data/models.py b/combo/data/models.py
index c08f3f58..99bdb2a5 100644
--- a/combo/data/models.py
+++ b/combo/data/models.py
@@ -1784,7 +1784,9 @@ class LinkListCell(CellBase):
         )
 
     def get_items_with_prefetch(self):
-        return self.get_items(prefetch_validity_info=True)
+        if not hasattr(self, '_items_with_prefetch_cache'):
+            self._items_with_prefetch_cache = self.get_items(prefetch_validity_info=True)
+        return self._items_with_prefetch_cache
 
     def get_additional_label(self):
         title = self.title
diff --git a/combo/manager/forms.py b/combo/manager/forms.py
index ddd44514..949f92a0 100644
--- a/combo/manager/forms.py
+++ b/combo/manager/forms.py
@@ -60,6 +60,19 @@ def get_template_name_choices(with_models=False):
     return templates
 
 
+def build_tab_is_not_default(form):
+    if getattr(form, 'is_not_default', None) is True:
+        return True
+    for field_name, field in form.fields.items():
+        if field.initial is not None:
+            if field.initial != form.initial.get(field_name):
+                return True
+        else:
+            if bool(form.initial.get(field_name)):
+                return True
+    return False
+
+
 class PageDuplicateForm(forms.Form):
     title = forms.CharField(label=_('New title'), max_length=150, required=False)
 
@@ -274,6 +287,7 @@ class CellVisibilityForm(forms.Form):
             ('groups-any', _('Users with one of these groups')),
             ('groups-none', _('Users with none of these groups')),
         ],
+        initial='all',
     )
     groups = forms.MultipleChoiceField(label=_('Groups'), required=False, choices=get_groups_as_choices)
 
diff --git a/combo/manager/static/js/combo.manager.js b/combo/manager/static/js/combo.manager.js
index caf2e59d..00a44cc4 100644
--- a/combo/manager/static/js/combo.manager.js
+++ b/combo/manager/static/js/combo.manager.js
@@ -275,10 +275,10 @@ $(function() {
             $button.attr('disabled', null);
             for (const tab_slug in data.tabs) {
               var $tab_content = $form.find('[data-tab-slug="' + tab_slug + '"]');
-              if (data.tabs[tab_slug].indexOf('ckeditortype') == -1) {
+              if (data.tabs[tab_slug].form.indexOf('ckeditortype') == -1) {
                 /* update form with new content, unless it has a ckeditor, as
                  * this causes an unpleasant flickering */
-                $tab_content.html(data.tabs[tab_slug]);
+                $tab_content.html(data.tabs[tab_slug].form);
               } else {
                 for (const error in data.errorlist[tab_slug]) {
                   var $widget_p = $('[name="' + data.prefix + '-' + error).closest('p');
@@ -289,6 +289,11 @@ $(function() {
                   $widget_p.before($widget_ul);
                 }
               }
+              if (data.tabs[tab_slug].is_not_default) {
+                $('#tab-' + $cell.data('cell-reference') + '-' + tab_slug).addClass('pk-tabs--button-marker');
+              } else {
+                $('#tab-' + $cell.data('cell-reference') + '-' + tab_slug).removeClass('pk-tabs--button-marker');
+              }
             }
             // update title labels
             $cell.find('.visibility-summary').removeClass(
diff --git a/combo/manager/templates/combo/manager_edit_cell_block.html b/combo/manager/templates/combo/manager_edit_cell_block.html
index 48ef634a..d3e7b7fe 100644
--- a/combo/manager/templates/combo/manager_edit_cell_block.html
+++ b/combo/manager/templates/combo/manager_edit_cell_block.html
@@ -5,19 +5,20 @@
    {% for tab in manager_tabs %}
    <button role="tab"
       aria-selected="{{ forloop.first|yesno:"true,false" }}"
-      aria-controls="panel-{{cell.get_reference}}-{{ forloop.counter }}"
-      id="tab-{{cell.get_reference}}-{{ forloop.counter }}"
-      tabindex="{{ forloop.first|yesno:"0,-1" }}">{{ tab.name }}</button>
+      aria-controls="panel-{{ cell.get_reference }}-{{ tab.slug }}"
+      id="tab-{{ cell.get_reference }}-{{ tab.slug }}"
+      tabindex="{{ forloop.first|yesno:"0,-1" }}"
+      {% if tab.is_not_default %}class="pk-tabs--button-marker"{% endif %}>{{ tab.name }}</button>
    {% endfor %}
   </div>
 
 <form class="pk-tabs--container" action="{{ url }}" method="post">
 {% csrf_token %}
    {% for tab in manager_tabs %}
-   <div id="panel-{{cell.get_reference}}-{{forloop.counter}}"
+   <div id="panel-{{ cell.get_reference }}-{{ tab.slug }}"
         role="tabpanel" tabindex="0" {% if not forloop.first %}hidden{% endif %}
         data-tab-slug="{{ tab.slug }}"
-        aria-labelledby="tab-{{cell.get_reference}}-{{ forloop.counter }}">
+        aria-labelledby="tab-{{ cell.get_reference }}-{{ tab.slug }}">
      {% if tab.template %}{% include tab.template %}{% else %}{{ tab.form_instance.as_p }}{% endif %}
    </div>
    {% endfor %}
diff --git a/combo/manager/templatetags/cells.py b/combo/manager/templatetags/cells.py
index 5b232b98..665e0fa5 100644
--- a/combo/manager/templatetags/cells.py
+++ b/combo/manager/templatetags/cells.py
@@ -17,6 +17,8 @@
 from django import forms, template
 from django.urls import reverse
 
+from combo.manager.forms import build_tab_is_not_default
+
 register = template.Library()
 
 
@@ -37,6 +39,7 @@ def cell_form(context, cell):
         if tab.get('fields'):
             tab['form'] = forms.models.modelform_factory(cell.__class__, fields=tab['fields'])
         tab['form_instance'] = tab['form'](initial={}, instance=cell, prefix='c%s' % cell.get_reference())
+        tab['is_not_default'] = build_tab_is_not_default(tab['form_instance'])
         context[form_name] = tab['form_instance']
     context['cell'] = cell
     cell_form_template = template.loader.get_template('combo/manager_edit_cell_block.html')
diff --git a/combo/manager/views.py b/combo/manager/views.py
index d5035ca7..a789008e 100644
--- a/combo/manager/views.py
+++ b/combo/manager/views.py
@@ -88,6 +88,7 @@ from .forms import (
     SiteExportForm,
     SiteImportForm,
     SiteSettingsForm,
+    build_tab_is_not_default,
 )
 
 
@@ -736,6 +737,7 @@ class PageEditCellView(ManagedPageMixin, UpdateView):
             form = tab_error_forms.get(tab['slug']) or tab['form'](
                 instance=self.object, prefix=self.get_prefix(), initial={}
             )
+            is_not_default = build_tab_is_not_default(form)
             if tab['slug'] == 'general':
                 form_name = 'form'
             else:
@@ -745,7 +747,10 @@ class PageEditCellView(ManagedPageMixin, UpdateView):
                 cell_form_template = template.loader.get_template(tab['template'])
             else:
                 cell_form_template = engines['django'].from_string('{{ %s.as_p }}' % form_name)
-            response['tabs'][tab['slug']] = cell_form_template.render(context)
+            response['tabs'][tab['slug']] = {
+                'form': cell_form_template.render(context),
+                'is_not_default': is_not_default,
+            }
 
         response['visibility_css_class'] = self.object.get_manager_visibility_css_class()
         response['visibility_content'] = self.object.get_manager_visibility_content()
diff --git a/tests/test_dataviz.py b/tests/test_dataviz.py
index 13b3a8e5..88d00ac4 100644
--- a/tests/test_dataviz.py
+++ b/tests/test_dataviz.py
@@ -1624,8 +1624,8 @@ def test_chartng_cell_manager_new_api_dynamic_fields(app, admin_user, new_api_st
     field_prefix = 'cdataviz_chartngcell-%s-' % cell.id
     resp.form[field_prefix + 'statistic'] = statistic.pk
     resp = app.post(resp.form.action, params=resp.form.submit_fields(), xhr=True)
-    assert 'time_interval' in resp.json['tabs']['general']
-    assert 'This field is required.' not in resp.json['tabs']['general']
+    assert 'time_interval' in resp.json['tabs']['general']['form']
+    assert 'This field is required.' not in resp.json['tabs']['general']['form']
 
 
 @with_httmock(new_api_mock)
diff --git a/tests/test_manager.py b/tests/test_manager.py
index 2b41ac85..d76bc0db 100644
--- a/tests/test_manager.py
+++ b/tests/test_manager.py
@@ -1778,7 +1778,7 @@ def test_edit_config_json_cell(app, admin_user):
         assert resp.form['c%s-test4' % cells[0].get_reference()].tag == 'textarea'
         resp.forms[0]['c%s-test4' % cells[0].get_reference()].value = 'Text Area'
         resp = manager_submit_cell(resp.form)
-        assert resp.json['tabs']['general'].strip().startswith('<p><label')
+        assert resp.json['tabs']['general']['form'].strip().startswith('<p><label')
 
         resp = app.get('/manage/pages/%s/' % page.id)
         assert resp.form['c%s-test' % cells[0].get_reference()].value == 'World Hello'
@@ -2628,13 +2628,22 @@ def test_link_cell_setup():
 
 def test_manager_link_cell_tabs(app, admin_user):
     page = Page.objects.create(title='One', slug='one', template_name='standard')
-    LinkCell.objects.create(order=0, placeholder='content', page=page)
+    cell = LinkCell.objects.create(order=0, placeholder='content', page=page)
+
     app = login(app)
     resp = app.get('/manage/pages/%s/' % page.pk)
-
+    assert not resp.pyquery('#tab-%s-general.pk-tabs--button-marker' % cell.get_reference())
     assert resp.pyquery('[data-tab-slug="general"] input[name$="title"]')
+    assert not resp.pyquery('#tab-%s-visibility.pk-tabs--button-marker' % cell.get_reference())
     assert not resp.pyquery('[data-tab-slug="appearance"] input[name$="title"]')
 
+    cell.title = "Custom"
+    cell.public = False
+    cell.save()
+    resp = app.get('/manage/pages/%s/' % page.pk)
+    assert resp.pyquery('#tab-%s-general.pk-tabs--button-marker' % cell.get_reference())
+    assert resp.pyquery('#tab-%s-visibility.pk-tabs--button-marker' % cell.get_reference())
+
 
 def test_link_cell_validation():
     form = LinkCellForm(data={'url': 'http://example.com'})
diff --git a/tests/test_wcs.py b/tests/test_wcs.py
index e03bc54e..ccb08b3f 100644
--- a/tests/test_wcs.py
+++ b/tests/test_wcs.py
@@ -35,7 +35,7 @@ from combo.apps.wcs.models import (
     WcsFormsOfCategoryCell,
 )
 from combo.data.library import get_cell_classes
-from combo.data.models import CellBase, LinkListCell, Page, ValidityInfo
+from combo.data.models import CellBase, LinkCell, LinkListCell, Page, ValidityInfo
 from combo.utils import NothingInCacheException
 
 from .test_manager import login
@@ -1649,12 +1649,13 @@ def test_manager_cards_cell(mock_send, app, admin_user):
 
 def test_manager_cards_cell_tabs(app, admin_user):
     page = Page.objects.create(title='xxx', slug='test_cards_cell_save_cache', template_name='standard')
-    WcsCardsCell.objects.create(page=page, placeholder='content', order=0)
+    cell = WcsCardsCell.objects.create(page=page, placeholder='content', order=0)
     login(app)
 
     resp = app.get('/manage/pages/%s/' % page.pk)
 
     assert not resp.pyquery('[data-tab-slug="general"] input[name$="custom_title"]')
+    assert not resp.pyquery('#tab-%s-general.pk-tabs--button-marker' % cell.get_reference())
     assert resp.pyquery('[data-tab-slug="appearance"] input[name$="custom_title"]')
 
 
@@ -2481,13 +2482,14 @@ def test_manager_card_cell(mock_send, app, admin_user):
 
 def test_manager_card_cell_tabs(app, admin_user):
     page = Page.objects.create(title='xxx', slug='test_cards', template_name='standard', sub_slug='foobar')
-    WcsCardInfosCell.objects.create(page=page, placeholder='content', order=0)
+    cell = WcsCardInfosCell.objects.create(page=page, placeholder='content', order=0)
 
     app = login(app)
     resp = app.get('/manage/pages/%s/' % page.pk)
 
     assert not resp.pyquery('[data-tab-slug="general"] select[name$="title_type"]')
     assert not resp.pyquery('[data-tab-slug="general"] input[name$="custom_title"]')
+    assert not resp.pyquery('#tab-%s-general.pk-tabs--button-marker' % cell.get_reference())
     assert resp.pyquery('[data-tab-slug="appearance"] select[name$="title_type"]')
     assert resp.pyquery('[data-tab-slug="appearance"] input[name$="custom_title"]')
 
@@ -4247,13 +4249,25 @@ def test_manager_add_edit_delete_list_link_item(mock_send, app, admin_user):
 
 def test_manager_link_list_tabs(app, admin_user):
     page = Page.objects.create(title='One', slug='one', template_name='standard')
-    LinkListCell.objects.create(order=0, placeholder='content', page=page)
+    cell = LinkListCell.objects.create(order=0, placeholder='content', page=page)
     app = login(app)
     resp = app.get('/manage/pages/%s/' % page.pk)
 
     assert not resp.pyquery('[data-tab-slug="general"] input[name$="title"]')
+    assert not resp.pyquery('#tab-%s-general.pk-tabs--button-marker' % cell.get_reference())
     assert resp.pyquery('[data-tab-slug="appearance"] input[name$="title"]')
 
+    LinkCell.objects.create(
+        page=page,
+        placeholder=cell.link_placeholder,
+        title='Example Site',
+        url='http://example.net/',
+        link_page=page,
+        order=1,
+    )
+    resp = app.get('/manage/pages/%s/' % page.id)
+    assert resp.pyquery('#tab-%s-general.pk-tabs--button-marker' % cell.get_reference())
+
 
 def test_import_export_pages_with_links():
     page = Page(title='bar', slug='bar', order=1)
diff --git a/tests/utils.py b/tests/utils.py
index f676c964..61c91321 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -9,7 +9,9 @@ def manager_submit_cell(form, expect_errors=False):
     pq = PyQuery(resp.body)
     resp2 = form.submit()
     for tab in resp2.json['tabs']:
-        pq.find('form[action="%s"] [data-tab-slug="%s"]' % (action, tab)).html(resp2.json['tabs'][tab])
+        pq.find('form[action="%s"] [data-tab-slug="%s"]' % (action, tab)).html(
+            resp2.json['tabs'][tab]['form']
+        )
     resp.text = pq.html()
     resp._forms_indexed = None
     assert expect_errors or not resp2.json['errorlist'], 'got unexpected errors'
-- 
2.35.1

