From ad42d43b30cea0eed26a93d717debab294604a57 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Thu, 9 Dec 2021 16:11:53 +0100
Subject: [PATCH] management: add link to form/carddef page in sidebar (#58671)

---
 tests/admin_pages/test_workflow.py      |  1 +
 tests/backoffice_pages/test_all.py      | 18 ++++++++++++++++--
 tests/backoffice_pages/test_carddata.py | 16 +++++++++++++++-
 wcs/admin/forms.py                      |  2 +-
 wcs/admin/workflows.py                  | 14 ++++----------
 wcs/api.py                              |  4 ++--
 wcs/backoffice/data_management.py       |  1 +
 wcs/backoffice/management.py            | 12 ++++++++++++
 wcs/formdef.py                          |  2 +-
 wcs/workflows.py                        | 11 +++++++++++
 10 files changed, 64 insertions(+), 17 deletions(-)

diff --git a/tests/admin_pages/test_workflow.py b/tests/admin_pages/test_workflow.py
index c326c55c..3bfa30d7 100644
--- a/tests/admin_pages/test_workflow.py
+++ b/tests/admin_pages/test_workflow.py
@@ -3165,6 +3165,7 @@ def test_workflow_category_management_roles(pub, backoffice_user, backoffice_rol
     app.get('/backoffice/workflows/categories/', status=403)
     app.get('/backoffice/workflows/data-sources/', status=403)
     app.get('/backoffice/workflows/mail-templates/', status=403)
+    app.get('/backoffice/workflows/%s/' % workflow.id, status=403)
 
     # no import into other category
     workflow_xml = ET.tostring(workflow.export_to_xml(include_id=True))
diff --git a/tests/backoffice_pages/test_all.py b/tests/backoffice_pages/test_all.py
index 71709ecb..4278b740 100644
--- a/tests/backoffice_pages/test_all.py
+++ b/tests/backoffice_pages/test_all.py
@@ -331,6 +331,20 @@ def test_backoffice_management_css_class(pub):
     assert resp.pyquery.find('body.section-management')
 
 
+def test_admin_form_page(pub):
+    create_superuser(pub)
+    FormDef.wipe()
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = []
+    formdef.store()
+
+    app = login(get_app(pub))
+    resp = app.get('/backoffice/management/form-title/')
+    assert 'backoffice/forms/1/' in resp
+    assert 'backoffice/workflows/_default/' in resp
+
+
 def test_backoffice_listing(pub):
     create_superuser(pub)
     create_environment(pub)
@@ -4575,7 +4589,7 @@ def test_backoffice_create_formdata_backoffice_submission(pub, create_formdata):
     resp = app.get(create_formdata['source_formdef'].get_url(backoffice=True))
 
     # click on first available formdata
-    resp = resp.click(href='%s/' % formdata.id)
+    resp = resp.click('%s-%s' % (create_formdata['source_formdef'].id, formdata.id))
     target_data_class = create_formdata['target_formdef'].data_class()
     assert target_data_class.count() == 0
     # resubmit it through backoffice submission
@@ -4693,7 +4707,7 @@ def test_backoffice_create_formdata_map_fields_by_varname(pub, create_formdata):
     resp = app.get(create_formdata['source_formdef'].get_url(backoffice=True))
 
     # click on first available formdata
-    resp = resp.click(href='%s/' % formdata.id)
+    resp = resp.click('%s-%s' % (create_formdata['source_formdef'].id, formdata.id))
     target_data_class = create_formdata['target_formdef'].data_class()
     assert target_data_class.count() == 0
     # resubmit it through backoffice submission
diff --git a/tests/backoffice_pages/test_carddata.py b/tests/backoffice_pages/test_carddata.py
index fc6422ea..b8fc349f 100644
--- a/tests/backoffice_pages/test_carddata.py
+++ b/tests/backoffice_pages/test_carddata.py
@@ -15,7 +15,7 @@ from wcs.wf.wscall import WebserviceCallStatusItem
 from wcs.workflows import ChoiceWorkflowStatusItem, Workflow
 
 from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login
-from .test_all import create_user
+from .test_all import create_superuser, create_user
 
 
 def pytest_generate_tests(metafunc):
@@ -47,6 +47,20 @@ def teardown_module(module):
     clean_temporary_pub()
 
 
+def test_admin_card_page(pub):
+    create_superuser(pub)
+    CardDef.wipe()
+    carddef = CardDef()
+    carddef.name = 'foo'
+    carddef.fields = []
+    carddef.store()
+
+    app = login(get_app(pub))
+    resp = app.get('/backoffice/data/foo/')
+    assert 'backoffice/cards/1/' in resp
+    assert 'backoffice/workflows/_carddef_default/' in resp
+
+
 def test_carddata_management(pub):
     CardDef.wipe()
     user = create_user(pub)
diff --git a/wcs/admin/forms.py b/wcs/admin/forms.py
index f2551f8b..6372cdfb 100644
--- a/wcs/admin/forms.py
+++ b/wcs/admin/forms.py
@@ -1822,7 +1822,7 @@ class FormsDirectory(AccessControlled, Directory):
 
     def _q_lookup(self, component):
         directory = self.formdef_page_class(component)
-        if not directory.formdef.is_managed_by(get_request().user):
+        if not directory.formdef.has_user_access(get_request().user):
             raise AccessForbiddenError()
         return directory
 
diff --git a/wcs/admin/workflows.py b/wcs/admin/workflows.py
index bf384d3f..2f7b8898 100644
--- a/wcs/admin/workflows.py
+++ b/wcs/admin/workflows.py
@@ -2044,16 +2044,10 @@ class WorkflowsDirectory(Directory):
 
     def _q_lookup(self, component):
         directory = WorkflowPage(component)
-        global_access = is_global_accessible()
-        if directory.workflow.id not in ('_default', '_carddef_default') and not global_access:
-            user_roles = set(get_request().user.get_roles())
-            management_roles = set()
-            if directory.workflow.category:
-                management_roles = {
-                    x.id for x in getattr(directory.workflow.category, 'management_roles') or []
-                }
-            if not management_roles.intersection(user_roles):
-                raise errors.AccessForbiddenError()
+        if directory.workflow.id in ('_default', '_carddef_default'):
+            return directory
+        if not directory.workflow.has_user_access(get_request().user):
+            raise errors.AccessForbiddenError()
         return directory
 
     def p_import(self):
diff --git a/wcs/api.py b/wcs/api.py
index 9eae1a3b..18bdddc4 100644
--- a/wcs/api.py
+++ b/wcs/api.py
@@ -277,7 +277,7 @@ class ApiCardPage(ApiFormPageMixin, BackofficeCardPage):
         return super().check_access(api_name=api_name)
 
     def schema(self):
-        if is_url_signed() or self.formdef.is_managed_by(get_user_from_api_query_string()):
+        if is_url_signed() or self.formdef.has_user_access(get_user_from_api_query_string()):
             get_response().set_content_type('application/json')
             return self.formdef.export_to_json()
         raise AccessForbiddenError()
@@ -515,7 +515,7 @@ class ApiFormdefDirectory(Directory):
         self.formdef = formdef
 
     def schema(self):
-        if is_url_signed() or self.formdef.is_managed_by(get_user_from_api_query_string()):
+        if is_url_signed() or self.formdef.has_user_access(get_user_from_api_query_string()):
             get_response().set_content_type('application/json')
             return self.formdef.export_to_json()
         raise AccessForbiddenError()
diff --git a/wcs/backoffice/data_management.py b/wcs/backoffice/data_management.py
index 868a93b8..7c4da912 100644
--- a/wcs/backoffice/data_management.py
+++ b/wcs/backoffice/data_management.py
@@ -103,6 +103,7 @@ class CardPage(FormPage):
     admin_permission = 'cards'
     formdef_class = CardDef
     search_label = _('Search in card content')
+    formdef_view_label = _('View Card')
 
     @property
     def add(self):
diff --git a/wcs/backoffice/management.py b/wcs/backoffice/management.py
index bf9718fe..c2ed0278 100644
--- a/wcs/backoffice/management.py
+++ b/wcs/backoffice/management.py
@@ -792,6 +792,7 @@ class FormPage(Directory):
     admin_permission = 'forms'
     formdef_class = FormDef
     search_label = _('Search in form content')
+    formdef_view_label = _('View Form')
     WCS_SYNC_EXPORT_LIMIT = 100  # Arbitrary threshold
 
     def __init__(self, component=None, formdef=None, view=None, update_breadcrumbs=True):
@@ -875,6 +876,17 @@ class FormPage(Directory):
             or self.formdef.category.has_permission('statistics', get_request().user)
         ):
             r += htmltext(' <li class="stats"><a href="stats">%s</a></li>') % _('Statistics')
+
+        if self.formdef.has_user_access(get_request().user):
+            r += htmltext(' <li><a href="%s">%s</a></li>') % (
+                self.formdef.get_admin_url(),
+                self.formdef_view_label,
+            )
+        if self.formdef.workflow.has_user_access(get_request().user):
+            r += htmltext(' <li><a href="%s">%s</a></li>') % (
+                self.formdef.workflow.get_admin_url(),
+                _('View Workflow'),
+            )
         return r.getvalue()
 
     def get_formdata_sidebar(self, qs=''):
diff --git a/wcs/formdef.py b/wcs/formdef.py
index 8d092048..ac41834a 100644
--- a/wcs/formdef.py
+++ b/wcs/formdef.py
@@ -423,7 +423,7 @@ class FormDef(StorableObject):
 
         return sql.get_formdef_new_id(id_start=id_start)
 
-    def is_managed_by(self, user):
+    def has_user_access(self, user):
         if get_publisher().get_backoffice_root().is_global_accessible(self.backoffice_section):
             return True
         if not user:
diff --git a/wcs/workflows.py b/wcs/workflows.py
index 3e3e8350..bca64d3c 100644
--- a/wcs/workflows.py
+++ b/wcs/workflows.py
@@ -1103,6 +1103,17 @@ class Workflow(StorableObject):
         criterias = [Contains('slug', slugs)]
         return list(MailTemplate.select(criterias, order_by='name'))
 
+    def has_user_access(self, user):
+        if get_publisher().get_backoffice_root().is_global_accessible('workflows'):
+            return True
+        if not user:
+            return False
+        if not self.category_id:
+            return False
+        management_roles = {x.id for x in getattr(self.category, 'management_roles') or []}
+        user_roles = set(user.get_roles())
+        return management_roles.intersection(user_roles)
+
 
 class XmlSerialisable:
     node_name = None
-- 
2.33.0

