From bc672cf8fe131636bf1a68bf5fec43d62323548b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Sun, 29 Nov 2020 15:06:22 +0100
Subject: [PATCH] forms: use a template to render checkboxes widget (#48387)

---
 wcs/admin/workflows.py                        |  6 +-
 wcs/qommon/form.py                            | 91 +++++--------------
 .../qommon/forms/widgets/checkboxes.html      | 18 ++++
 3 files changed, 46 insertions(+), 69 deletions(-)
 create mode 100644 wcs/qommon/templates/qommon/forms/widgets/checkboxes.html

diff --git a/wcs/admin/workflows.py b/wcs/admin/workflows.py
index 81d1e1ddf..7f1ff352c 100644
--- a/wcs/admin/workflows.py
+++ b/wcs/admin/workflows.py
@@ -873,7 +873,8 @@ class WorkflowVariablesFieldDefPage(FieldDefPage):
                 workflow=self.objectdef.workflow)
         display_locations = form.get_widget('display_locations')
         if display_locations:
-            del display_locations.widgets[0]  # validation page
+            # remove validation page from choices
+            display_locations.options = display_locations.options[1:]
         return form
 
 
@@ -886,7 +887,8 @@ class WorkflowBackofficeFieldDefPage(FieldDefPage):
         form.remove('prefill')
         display_locations = form.get_widget('display_locations')
         if display_locations:
-            del display_locations.widgets[0]  # validation page
+            # remove validation page from choices
+            display_locations.options = display_locations.options[1:]
         return form
 
 
diff --git a/wcs/qommon/form.py b/wcs/qommon/form.py
index 543cee7ed..c404c8397 100644
--- a/wcs/qommon/form.py
+++ b/wcs/qommon/form.py
@@ -1334,62 +1334,51 @@ class RegexStringWidget(StringWidget):
                 self.value = None
 
 
-class CheckboxesWidget(CompositeWidget):
+class CheckboxesWidget(Widget):
     readonly = False
     template_name = 'qommon/forms/widgets/checkboxes.html'
     has_inside_labels = True
 
     def __init__(self, name, value=None, options=None, **kwargs):
-        CompositeWidget.__init__(self, name, value, **kwargs)
-        self.element_names = collections.OrderedDict()
-
+        self.options = options
+        self.options_with_attributes = kwargs.pop('options_with_attributes', None)
+        self.inline = kwargs.pop('inline', True)
+        self.max_choices = int(kwargs.pop('max_choices', 0) or 0)
         if 'title' in kwargs:
             del kwargs['title']
         if 'readonly' in kwargs:
             del kwargs['readonly']
             self.readonly = True
-        self.inline = kwargs.get('inline', True)
-        self.max_choices = int(kwargs.get('max_choices', 0) or 0)
+        super().__init__(name, value, **kwargs)
 
-        self.options_with_attributes = kwargs.pop('options_with_attributes', None)
-        self.disabled_options = []
-        if self.options_with_attributes:
-            options = self.options_with_attributes
+    def is_selected(self, value):
+        return bool(self.value and value in self.value)
 
+    def get_options(self):
+        options = self.options_with_attributes or self.options
         for i, option in enumerate(options):
             if len(option) == 2:
-                key, title = option[:2]
-                name = 'element%d' % i
+                obj, description, key = (option[0], option[1], str(i))
             else:
-                _, title, key  = option[:3]
-                name = 'element%s' % str(key)
-
-            key = str(key)
-
-            element_kwargs = kwargs.copy()
-            if self.options_with_attributes and option[-1].get('disabled'):
-                element_kwargs['disabled'] = 'disabled'
-                self.disabled_options.append(name)
-            # don't mark individual checkboxes as required
-            element_kwargs.pop('required', None)
-
-            if value and key in value:
-                checked = True
-            else:
-                checked = False
-            self.add(CheckboxWidget, name, title=title, value=checked, **element_kwargs)
-            self.element_names[name] = key
+                obj, description, key = option[:3]
+            yield {
+                'name': self.name + '$element%s' % key,
+                'value': obj,
+                'label': description,
+                'disabled': bool(self.options_with_attributes and option[-1].get('disabled')),
+                'selected': self.is_selected(obj)
+            }
 
     def _parse(self, request):
         if self.readonly:
             return
         values = []
-        for name in self.element_names:
-            if name in self.disabled_options:
+        for option in self.get_options():
+            if option.get('disabled'):
                 continue
-            value = self.get(name)
-            if value is True:
-                values.append(self.element_names[name])
+            name = option['name']
+            if name in request.form and not request.form[name] in (False, '', 'False'):
+                values.append(option['value'])
         self.value = values
         if self.required and not self.value:
             self.set_error(self.REQUIRED_ERROR)
@@ -1398,40 +1387,8 @@ class CheckboxesWidget(CompositeWidget):
 
     def set_value(self, value):
         self.value = value
-        for name in self.element_names:
-            widget = self.get_widget(name)
-            if self.element_names[name] in self.value:
-                widget.set_value(True)
-            else:
-                widget.set_value(False)
         self._parsed = True
 
-    def has_error(self, request=None):
-        return Widget.has_error(self, request=request)
-
-    def render_content(self):
-        r = TemplateIO(html=True)
-        if self.inline:
-            r += htmltext('<ul class="inline">')
-        else:
-            r += htmltext('<ul>')
-        for widget in self.get_widgets():
-            if widget.attrs and 'disabled' in widget.attrs:
-                r += htmltext('<li class="disabled"><label>')
-            else:
-                r += htmltext('<li><label>')
-            if self.readonly:
-                widget.attrs['disabled'] = 'disabled'
-                if widget.value:
-                    r += htmltext('<input type="hidden" name="%s" value="yes" >') % widget.name
-                widget.name = widget.name + 'xx'
-            r += widget.render_content(standalone=False)
-            r += htmltext('<span>%s</span>') % widget.title
-            r += htmltext('</label>')
-            r += htmltext('</li>')
-        r += htmltext('</ul>')
-        return r.getvalue()
-
 
 class ValidatedStringWidget(StringWidget):
     '''StringWidget which checks the value entered is correct according to a regex'''
diff --git a/wcs/qommon/templates/qommon/forms/widgets/checkboxes.html b/wcs/qommon/templates/qommon/forms/widgets/checkboxes.html
new file mode 100644
index 000000000..c1e689925
--- /dev/null
+++ b/wcs/qommon/templates/qommon/forms/widgets/checkboxes.html
@@ -0,0 +1,18 @@
+{% extends "qommon/forms/widget.html" %}
+
+{% block widget-control %}
+<ul {% if widget.inline %}class="inline"{% endif %}>
+  {% for option in widget.get_options %}
+  <li {% if option.disabled %}class="disabled"{% endif %}><label><input
+   {% if option.selected %}checked="checked"
+   {% elif widget.readonly or option.disabled %}disabled{% endif %}
+   {% for attr in widget.attrs.items %}{{attr.0}}="{{attr.1}}"{% endfor %}
+   type="checkbox"
+   {% if widget.readonly %}value="yes" disabled
+   {% else %}name="{{ option.name }}" value="yes">
+   {% endif %}<span>{{ option.label }}</span></label>
+   {% if widget.readonly and option.selected %}<input type="hidden" name="{{ option.name }}" value="yes">{% endif %}
+   </li>
+{% endfor %}
+</ul>
+{% endblock %}
-- 
2.29.2

