From b7a4e4532ba377f74992d94568645207434cc445 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Mon, 11 Feb 2019 12:25:50 +0100
Subject: [PATCH] forms: switch comment field to render using a template
 (#30530)

---
 tests/test_backoffice_pages.py | 12 ++++++------
 tests/test_fields.py           | 15 +++++++--------
 tests/test_form_pages.py       |  8 ++++----
 wcs/fields.py                  | 32 ++++++++++++++------------------
 wcs/qommon/form.py             | 19 +++++++++++++++++++
 5 files changed, 50 insertions(+), 36 deletions(-)

diff --git a/tests/test_backoffice_pages.py b/tests/test_backoffice_pages.py
index 76c308bb9..b95084f4c 100644
--- a/tests/test_backoffice_pages.py
+++ b/tests/test_backoffice_pages.py
@@ -4257,7 +4257,7 @@ def test_backoffice_formdata_named_wscall(http_requests, pub):
     app = login(get_app(pub))
 
     resp = app.get('/backoffice/submission/test/')
-    assert '<p data-field-id="7" class="comment-field ">XbarY</p>' in resp.body
+    assert resp.html.find('div', {'data-field-id': '7'}).text.strip() == 'XbarY'
 
     # check with publisher variable in named webservice call
     if not pub.site_options.has_section('variables'):
@@ -4272,13 +4272,13 @@ def test_backoffice_formdata_named_wscall(http_requests, pub):
     wscall.store()
 
     resp = app.get('/backoffice/submission/test/')
-    assert '<p data-field-id="7" class="comment-field ">XbarY</p>' in resp.body
+    assert resp.html.find('div', {'data-field-id': '7'}).text.strip() == 'XbarY'
 
     # django-templated URL
     wscall.request = {'url': '{{ example_url }}json'}
     wscall.store()
     resp = app.get('/backoffice/submission/test/')
-    assert '<p data-field-id="7" class="comment-field ">XbarY</p>' in resp.body
+    assert resp.html.find('div', {'data-field-id': '7'}).text.strip() == 'XbarY'
 
     # webservice call in django template
     formdef.fields = [
@@ -4287,7 +4287,7 @@ def test_backoffice_formdata_named_wscall(http_requests, pub):
     formdef.store()
     formdef.data_class().wipe()
     resp = app.get('/backoffice/submission/test/')
-    assert '<p data-field-id="7" class="comment-field ">dja-bar-ngo</p>' in resp.body
+    assert resp.html.find('div', {'data-field-id': '7'}).text.strip() == 'dja-bar-ngo'
 
 def test_backoffice_session_var(pub):
     open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w').write('''[options]
@@ -4311,7 +4311,7 @@ query_string_allowed_vars = foo,bar
     resp = app.get('/backoffice/submission/test/?session_var_foo=bar')
     assert resp.location.endswith('/backoffice/submission/test/')
     resp = resp.follow()
-    assert '<p data-field-id="7" class="comment-field ">XbarY</p>' in resp.body
+    assert resp.html.find('div', {'data-field-id': '7'}).text.strip() == 'XbarY'
 
     # django template
     formdef.fields = [
@@ -4322,7 +4322,7 @@ query_string_allowed_vars = foo,bar
     resp = app.get('/backoffice/submission/test/?session_var_foo=jang')
     assert resp.location.endswith('/backoffice/submission/test/')
     resp = resp.follow()
-    assert '<p data-field-id="7" class="comment-field ">django</p>' in resp.body
+    assert resp.html.find('div', {'data-field-id': '7'}).text.strip() == 'django'
 
 def test_backoffice_display_message(pub):
     user = create_user(pub)
diff --git a/tests/test_fields.py b/tests/test_fields.py
index b069d9ab6..12f22ee00 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -3,6 +3,7 @@
 import re
 import shutil
 
+from bs4 import BeautifulSoup
 import pytest
 
 from quixote import cleanup
@@ -155,43 +156,41 @@ def test_comment():
     field = fields.CommentField(label='Foobar')
     form = Form(use_tokens=False)
     field.add_to_form(form)
-    assert '<p data-field-id="None" class="comment-field ">Foobar</p>' in str(form.render())
+    assert BeautifulSoup(str(form.render())).find('div').text.strip() == 'Foobar'
 
     field = fields.CommentField(label='Foo\n\nBar\n\nBaz')
     form = Form(use_tokens=False)
     field.add_to_form(form)
-    assert '<p>Foo</p>\n<p>Bar</p>\n<p>Baz</p>' in str(form.render())
-    assert '<div data-field-id="None" class="comment-field "' in str(form.render())
+    assert str(BeautifulSoup(str(form.render())).find('div')) == '<div class="comment-field">\n<p>Foo</p>\n<p>Bar</p>\n<p>Baz</p>\n</div>'
 
     # test for variable substitution
     pub.substitutions.feed(MockSubstitutionVariables())
     field = fields.CommentField(label='{{ bar }}')
     form = Form(use_tokens=False)
     field.add_to_form(form)
-    assert '<p data-field-id="None" class="comment-field ">Foobar</p>' in str(form.render())
+    assert BeautifulSoup(str(form.render())).find('div').text.strip() == 'Foobar'
 
     field = fields.CommentField(label='[bar]')
     form = Form(use_tokens=False)
     field.add_to_form(form)
-    assert '<p data-field-id="None" class="comment-field ">Foobar</p>' in str(form.render())
+    assert BeautifulSoup(str(form.render())).find('div').text.strip() == 'Foobar'
 
     # test for proper escaping of substitution variables
     field = fields.CommentField(label='{{ foo }}')
     form = Form(use_tokens=False)
     field.add_to_form(form)
-    assert '<p data-field-id="None" class="comment-field ">1 &lt; 3</p>' in str(form.render())
+    assert '1 &lt; 3' in str(form.render())
 
     field = fields.CommentField(label='[foo]')
     form = Form(use_tokens=False)
     field.add_to_form(form)
-    assert '<p data-field-id="None" class="comment-field ">1 &lt; 3</p>' in str(form.render())
+    assert '1 &lt; 3' in str(form.render())
 
     # test for html content
     field = fields.CommentField(label='<p>Foobar</p>')
     form = Form(use_tokens=False)
     field.add_to_form(form)
     assert '<p>Foobar</p>' in str(form.render())
-    assert '<div data-field-id="None" class="comment-field "' in str(form.render())
     assert field.unhtmled_label == 'Foobar'
 
     field = fields.CommentField(label='<p>Foobar&eacute;</p>')
diff --git a/tests/test_form_pages.py b/tests/test_form_pages.py
index 9d1c9a202..f89870914 100644
--- a/tests/test_form_pages.py
+++ b/tests/test_form_pages.py
@@ -5476,13 +5476,13 @@ def test_field_unicode_condition_in_array(pub):
             required=True, varname='baz',
             condition={'type': 'django', 'value': '"Pêche" in form_var_foo'}),
         fields.CommentField(type='comment', id='4', label='{{form_var_foo}}'),
-        fields.CommentField(type='comment', id='5', label='{% if "Pêche" in form_var_foo %}OK{% endif %}'),
+        fields.CommentField(type='comment', id='5', label='{% if "Pêche" in form_var_foo %}CHECK OK{% endif %}'),
     ]
 
     workflow = Workflow(name='test')
     st1 = workflow.add_status('Status1', 'st1')
     display1 = DisplayMessageWorkflowStatusItem()
-    display1.message = 'Message {% if "Pêche" in form_var_foo %}OK{% endif %}'
+    display1.message = 'Message {% if "Pêche" in form_var_foo %}CHECK OK{% endif %}'
     display1.to = []
     st1.items.append(display1)
     display1.parent = st1
@@ -5503,11 +5503,11 @@ def test_field_unicode_condition_in_array(pub):
     assert 'f3' in resp.form.fields  # check it's ok in field condition
     resp.form['f3'] = 'hop'
     assert '>Poire, Pêche<' in resp.body  # check it's displayed correctly
-    assert '>OK<' in resp.body  # check it's ok in template condition
+    assert 'CHECK OK' in resp.body  # check it's ok in template condition
     resp = resp.form.submit('submit')
     assert 'Check values then click submit.' in resp.body
     resp = resp.form.submit('submit').follow()
-    assert '<p>Message OK</p>' in resp.body  # check it's ok in workflow template
+    assert '<p>Message CHECK OK</p>' in resp.body  # check it's ok in workflow template
 
 def test_field_live_condition(pub):
     FormDef.wipe()
diff --git a/wcs/fields.py b/wcs/fields.py
index 5c8434850..67aa7f9d3 100644
--- a/wcs/fields.py
+++ b/wcs/fields.py
@@ -607,23 +607,13 @@ class CommentField(Field):
     key = 'comment'
     description = N_('Comment')
 
-    def add_to_form(self, form, value = None):
-        tag_attributes = 'data-field-id="%s" class="comment-field %s"' % (
-                self.id, self.extra_css_class or '')
-
-        label = self.get_html_content()
-
+    def add_to_form(self, form, value=None):
         import wcs.workflows
+        label = self.get_html_content()
         label = wcs.workflows.template_on_html_string(label)
-
-        enclosing_tag = 'p'
-        for block_level in ('p', 'div', 'ul', 'ol', 'hr'):
-            if '<' + block_level in label:
-                enclosing_tag = 'div'
-                break
-
-        widget = HtmlWidget(htmltext('<%s %s>%s</%s>' % (
-            enclosing_tag, tag_attributes, label, enclosing_tag)))
+        widget = CommentWidget(
+                content=label,
+                extra_css_class=self.extra_css_class)
         form.widgets.append(widget)
         return widget
 
@@ -631,9 +621,15 @@ class CommentField(Field):
         pass
 
     def get_html_content(self):
-        if self.label and '\n\n' in self.label and self.label[0] != '<':
-            return '<p>' + re.sub('\n\n+', '</p>\n<p>', self.label) + '</p>'
-        return self.label
+        if not self.label:
+            return ''
+        if self.label.startswith('<'):
+            return self.label
+        if '\n\n' in self.label:
+            # blank lines to paragraphs
+            label = '</p>\n<p>'.join([str(htmlescape(x)) for x in re.split('\n\n+', self.label)])
+            return '<p>' + label + '</p>'
+        return '<p>%s</p>' % str(htmlescape(self.label))
 
     def fill_admin_form(self, form):
         if self.label and (self.label[0] != '<' and '[end]' in self.label):
diff --git a/wcs/qommon/form.py b/wcs/qommon/form.py
index 9a4dc13d4..67541bc41 100644
--- a/wcs/qommon/form.py
+++ b/wcs/qommon/form.py
@@ -431,6 +431,25 @@ class HtmlWidget(object):
     def clear_error(self, request=None):
         pass
 
+
+class CommentWidget(Widget):
+    template_name = 'qommon/forms/widgets/comment.html'
+    name = None
+
+    def __init__(self, content, extra_css_class):
+        self.content = content
+        self.extra_css_class = extra_css_class
+
+    def has_error(self, request):
+        return False
+
+    def parse(self, *args):
+        pass
+
+    def clear_error(self, request=None):
+        pass
+
+
 class CompositeWidget(quixote.form.CompositeWidget):
     def render_as_thead(self):
         r = TemplateIO(html=True)
-- 
2.20.1

