From bb35f7bbfa47cf25de2017cf0fd86b3ffdf7421f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Fri, 31 Jul 2020 11:28:02 +0200
Subject: [PATCH] backoffice: list related forms and cards in inspect view
 (#43359)

---
 tests/test_backoffice_pages.py | 104 +++++++++++++++++++++++++++++++++
 wcs/backoffice/management.py   |  29 +++++++--
 wcs/formdata.py                |  19 ++++++
 3 files changed, 146 insertions(+), 6 deletions(-)

diff --git a/tests/test_backoffice_pages.py b/tests/test_backoffice_pages.py
index 930c4872..fd0b26f5 100644
--- a/tests/test_backoffice_pages.py
+++ b/tests/test_backoffice_pages.py
@@ -5282,6 +5282,110 @@ def test_inspect_page(pub, local_user):
     assert 'Invalid block tag' in resp.text
 
 
+def test_inspect_page_with_related_objects(pub):
+    if not pub.is_using_postgresql():
+        pytest.skip('this requires SQL')
+        return
+
+    user = create_user(pub, is_admin=True)
+
+    formdef1 = FormDef()
+    formdef1.name = 'form 1'
+    formdef1.fields = []
+    formdef1.store()
+    formdef1.data_class().wipe()
+    formdata = formdef1.data_class()()
+    formdata.store()
+    formdef1.data_class()().store()
+
+    formdef2 = FormDef()
+    formdef2.name = 'form 2'
+    formdef2.fields = []
+    formdef2.store()
+    formdef2.data_class().wipe()
+    formdef2.data_class()().store()
+
+    carddef1 = CardDef()
+    carddef1.name = 'card 1'
+    carddef1.fields = []
+    carddef1.store()
+    carddef1.data_class().wipe()
+    carddata = carddef1.data_class()()
+    carddata.store()
+
+    carddef2 = CardDef()
+    carddef2.name = 'card 2'
+    carddef2.fields = []
+    carddef2.backoffice_submission_roles = user.roles
+    carddef2.store()
+    carddef2.data_class().wipe()
+    carddef2.data_class()().store()
+
+    related_formdata1 = formdef2.data_class()()
+    related_formdata1.submission_context = {
+        'orig_object_type': 'formdef',
+        'orig_formdata_id': str(formdata.id),
+        'orig_formdef_id': str(formdef1.id),
+    }
+    related_formdata1.store()
+    related_formdata2 = formdef2.data_class()()
+    related_formdata2.submission_context = {
+        'orig_object_type': 'carddef',
+        'orig_formdata_id': str(carddata.id),
+        'orig_formdef_id': str(carddef1.id),
+    }
+    related_formdata2.store()
+
+    related_carddata1 = carddef2.data_class()()
+    related_carddata1.submission_context = {
+        'orig_object_type': 'carddef',
+        'orig_formdata_id': str(carddata.id),
+        'orig_formdef_id': str(carddef1.id),
+    }
+    related_carddata1.store()
+    related_carddata2 = carddef2.data_class()()
+    related_carddata2.submission_context = {
+        'orig_object_type': 'formdef',
+        'orig_formdata_id': str(formdata.id),
+        'orig_formdef_id': str(formdef1.id),
+    }
+    related_carddata2.store()
+
+    assert [f.get_display_id() for f in formdata.get_children()] == [related_formdata1.get_display_id(), related_carddata2.get_display_id()]
+    assert [c.get_display_id() for c in carddata.get_children()] == [related_formdata2.get_display_id(), related_carddata1.get_display_id()]
+
+    app = login(get_app(pub))
+    resp = app.get('%sinspect' % formdata.get_url(backoffice=True), status=200)
+    assert 'Related Forms/Cards' in resp.text
+    assert related_formdata1.get_url(backoffice=True) in resp.text
+    assert related_carddata2.get_url(backoffice=True) in resp.text
+
+    resp = app.get('%sinspect' % carddata.get_url(backoffice=True), status=200)
+    assert 'Related Forms/Cards' in resp.text
+    assert related_formdata2.get_url(backoffice=True) in resp.text
+    assert related_carddata1.get_url(backoffice=True) in resp.text
+
+    resp = app.get(related_formdata1.get_url(backoffice=True), status=200)
+    assert 'Original form' in resp.text
+    assert '(deleted)' not in resp.text
+    assert formdata.get_url(backoffice=True) in resp.text
+
+    resp = app.get(related_formdata2.get_url(backoffice=True), status=200)
+    assert 'Original card' in resp.text
+    assert '(deleted)' not in resp.text
+    assert carddata.get_url(backoffice=True) in resp.text
+
+    resp = app.get(related_carddata1.get_url(backoffice=True), status=200)
+    assert 'Original card' in resp.text
+    assert '(deleted)' not in resp.text
+    assert carddata.get_url(backoffice=True) in resp.text
+
+    resp = app.get(related_carddata2.get_url(backoffice=True), status=200)
+    assert 'Original form' in resp.text
+    assert '(deleted)' not in resp.text
+    assert formdata.get_url(backoffice=True) in resp.text
+
+
 def test_workflow_jump_previous(pub):
     user = create_user(pub)
     create_environment(pub)
diff --git a/wcs/backoffice/management.py b/wcs/backoffice/management.py
index 9ca3a567..3fad96d5 100644
--- a/wcs/backoffice/management.py
+++ b/wcs/backoffice/management.py
@@ -2729,17 +2729,23 @@ class FormBackOfficeStatusPage(FormStatusPage):
             extra_context = formdata.submission_context or {}
             r += htmltext('<div class="extra-context">')
             if extra_context.get('orig_formdef_id'):
-                r += htmltext('<h3>%s</h3>') % _('Original form')
+                object_type = extra_context.get('orig_object_type', 'formdef')
+                if object_type == 'formdef':
+                    r += htmltext('<h3>%s</h3>') % _('Original form')
+                    object_class = FormDef
+                else:
+                    r += htmltext('<h3>%s</h3>') % _('Original card')
+                    object_class = CardDef
                 try:
-                    orig_formdata = FormDef.get(extra_context.get('orig_formdef_id')
-                            ).data_class().get(extra_context.get('orig_formdata_id'))
+                    orig_formdata = object_class.get(
+                        extra_context.get('orig_formdef_id')
+                    ).data_class().get(extra_context.get('orig_formdata_id'))
                 except KeyError:
                     r += htmltext('<p>%s</p>') % _('(deleted)')
                 else:
-                    r += htmltext('<p><a href="%s">%s %s</a></p>') % (
+                    r += htmltext('<p><a href="%s">%s</a></p>') % (
                              orig_formdata.get_url(backoffice=True),
-                             orig_formdata.formdef.name,
-                             orig_formdata.get_display_id())
+                             orig_formdata.get_display_name())
             if formdata.submission_channel:
                 r += htmltext('<h3>%s</h3>') % '%s: %s' % (
                         _('Channel'), formdata.get_submission_channel_label())
@@ -3130,6 +3136,17 @@ class FormBackOfficeStatusPage(FormStatusPage):
             r += htmltext('</ul>')
             r += htmltext('</div>')
 
+        children = self.filled.get_children()
+        if children:
+            r += htmltext('<div id="inspect-related" class="section">')
+            r += htmltext('<h2>%s</h2>') % _('Related Forms/Cards')
+            r += htmltext('<ul class="form-inspector biglist">')
+            for child in children:
+                r += htmltext('<li><a href="%s">%s</a></li>') % (
+                    child.get_url(backoffice=True), child.get_display_name())
+            r += htmltext('</ul>')
+            r += htmltext('</div>')
+
         return r.getvalue()
 
     def inspect_tool(self):
diff --git a/wcs/formdata.py b/wcs/formdata.py
index 17c35d34..f790228c 100644
--- a/wcs/formdata.py
+++ b/wcs/formdata.py
@@ -382,6 +382,25 @@ class FormData(StorableObject):
         except KeyError:
             return None
 
+    def get_children(self):
+        from .carddef import CardDef
+        from .formdef import FormDef
+        from .sql import NotNull
+        children = []
+
+        for objclass in [FormDef, CardDef]:
+            for objdef in objclass.select():
+                for objdata in objdef.data_class().select(clause=[NotNull('submission_context')]):
+                    if objdata.submission_context.get('orig_object_type', 'formdef') != self.formdef.__class__.__name__.lower():
+                        continue
+                    if objdata.submission_context.get('orig_formdef_id') != str(self.formdef.id):
+                        continue
+                    if objdata.submission_context.get('orig_formdata_id') != str(self.id):
+                        continue
+                    children.append(objdata)
+
+        return children
+
     def just_created(self):
         self.receipt_time = time.localtime()
         self.status = 'wf-%s' % self.formdef.workflow.possible_status[0].id
-- 
2.27.0

