From 3c20ad911842e7668e767632da5d1eda2c9d9049 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Corentin=20S=C3=A9chet?= <csechet@entrouvert.com>
Date: Wed, 27 Apr 2022 11:41:49 +0200
Subject: [PATCH] misc: add title field to text cells (#42968)

---
 combo/data/migrations/0058_textcell_title.py | 66 ++++++++++++++++++++
 combo/data/models.py                         |  5 +-
 combo/public/templates/combo/text-cell.html  |  1 +
 tests/test_cells.py                          | 19 ++++++
 4 files changed, 90 insertions(+), 1 deletion(-)
 create mode 100644 combo/data/migrations/0058_textcell_title.py

diff --git a/combo/data/migrations/0058_textcell_title.py b/combo/data/migrations/0058_textcell_title.py
new file mode 100644
index 00000000..4064e30a
--- /dev/null
+++ b/combo/data/migrations/0058_textcell_title.py
@@ -0,0 +1,66 @@
+# Generated by Django 2.2.26 on 2022-08-10 16:32
+
+from io import StringIO
+
+import lxml
+import lxml.html
+from django.db import migrations, models
+
+from combo.data.models import TextCell
+
+
+def forward_title_migration(apps, schema_editor):
+    TextCell = apps.get_model('data', 'TextCell')
+    for cell in TextCell.objects.all():
+        if not cell.text:
+            continue
+
+        parser = lxml.etree.HTMLParser()
+        html_document = lxml.etree.parse(StringIO(cell.text), parser)
+        root = html_document.getroot()
+
+        if root is None:
+            continue
+
+        title_nodes = root.xpath('/html/body/h2[1]')
+        if not title_nodes:
+            continue
+
+        assert len(title_nodes) == 1
+        title_node = title_nodes[0]
+        if len(title_node) != 0 or len(title_node.attrib) != 0:
+            continue
+
+        body = title_node.getparent()
+        title = title_node.text or ''
+        text = title_node.tail or ''
+        text += body.text or ''
+        text += ''.join([lxml.html.tostring(child).decode() for child in body[1:]])
+        cell.title = title
+        cell.text = text
+        print(title)
+        cell.save()
+
+
+def reverse_title_migration(apps, schema_editor):
+    TextCell = apps.get_model('data', 'TextCell')
+    for cell in TextCell.objects.all():
+        if cell.title:
+            cell.text = f'<h2>{cell.title}</h2>{cell.text}'
+        cell.save()
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('data', '0057_pagesnapshot_label'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='textcell',
+            name='title',
+            field=models.CharField(blank=True, max_length=150, null=True, verbose_name='Title'),
+        ),
+        migrations.RunPython(forward_title_migration, reverse_title_migration),
+    ]
diff --git a/combo/data/models.py b/combo/data/models.py
index cd1fc976..a32a9ba9 100644
--- a/combo/data/models.py
+++ b/combo/data/models.py
@@ -1475,6 +1475,7 @@ class CellBase(models.Model, metaclass=CellMeta):
 
 @register_cell_class
 class TextCell(CellBase):
+    title = models.CharField(_('Title'), max_length=150, blank=True, null=True)
     text = RichTextField(_('Text'), blank=True, null=True)
 
     default_template_name = 'combo/text-cell.html'
@@ -1533,7 +1534,9 @@ class TextCell(CellBase):
 
             text = re.sub(r'src="(.*?)"', sub_src, text)
             text = re.sub(r'href="(.*?)"', sub_href, text)
-        extra_context['text'] = mark_safe(text)
+        extra_context["text"] = mark_safe(text)
+        if self.title:
+            extra_context["title"] = mark_safe(self.title)
         return extra_context
 
 
diff --git a/combo/public/templates/combo/text-cell.html b/combo/public/templates/combo/text-cell.html
index 846995ab..c01cdcc6 100644
--- a/combo/public/templates/combo/text-cell.html
+++ b/combo/public/templates/combo/text-cell.html
@@ -1,4 +1,5 @@
 {% block cell-content %}
 {% include "combo/asset_picture_fragment.html" %}
+{% if title %}<h2 class="cell--title text-cell--title">{{ title }}</h2>{% endif %}
 {{text}}
 {% endblock %}
diff --git a/tests/test_cells.py b/tests/test_cells.py
index a93339fe..2ffc7455 100644
--- a/tests/test_cells.py
+++ b/tests/test_cells.py
@@ -122,6 +122,25 @@ def test_text_cell_variadic_url():
     assert 'href="/plop"' in cell.render(ctx)
 
 
+def test_text_cell_title():
+    page = Page()
+    page.save()
+
+    cell = TextCell(page=page, order=0)
+    cell.text = '<p>body</p>'
+    cell.title = 'Cell Title'
+    cell.save()
+    ctx = {}
+    assert re.search(r'<h2[^>]*>Cell Title</h2>', cell.render(ctx))
+
+    cell = TextCell(page=page, order=0)
+    cell.text = '<p>body</p>'
+    cell.title = None
+    cell.save()
+    ctx = {}
+    assert not re.search(r'<h2[^>]*>.*</h2>', cell.render(ctx))
+
+
 def test_link_cell():
     page = Page(title='example page', slug='example-page')
     page.save()
-- 
2.35.1

