From b53730d49e686e33ffa6e2e7929ddfb752c12f11 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Sat, 24 Sep 2022 11:10:56 +0200
Subject: [PATCH] api: extend form/card roles support in applification API
 (#69252)

---
 tests/api/test_export_import.py | 45 +++++++++++++++++++++++++++++----
 wcs/api_export_import.py        | 24 +++++-------------
 wcs/formdef.py                  |  5 ++++
 3 files changed, 51 insertions(+), 23 deletions(-)

diff --git a/tests/api/test_export_import.py b/tests/api/test_export_import.py
index 50e2932e1..badcc0c20 100644
--- a/tests/api/test_export_import.py
+++ b/tests/api/test_export_import.py
@@ -95,6 +95,10 @@ def test_export_import_form_dependencies_404(pub):
 def test_export_import_dependencies(pub):
     role = pub.role_class(name='Test role')
     role.store()
+    role2 = pub.role_class(name='Second role')
+    role2.store()
+    role3 = pub.role_class(name='Third role')
+    role3.store()
 
     formdef = FormDef()
     formdef.name = 'Test'
@@ -104,6 +108,11 @@ def test_export_import_dependencies(pub):
     resp = get_app(pub).get(sign_uri(resp.json['data'][0]['urls']['dependencies']))
     assert not resp.json['data']
 
+    formdef.roles = ['logged-users']
+    formdef.backoffice_submission_roles = [role2.id]
+    formdef.workflow_roles = {'_receiver': role3.id}
+    formdef.store()
+
     block = BlockDef(name='test')
     block.store()
 
@@ -144,7 +153,12 @@ def test_export_import_dependencies(pub):
 
     resp = get_app(pub).get(sign_uri('/api/export-import/forms/'))
     resp = get_app(pub).get(sign_uri(resp.json['data'][0]['urls']['dependencies']))
-    assert {(x['id'], x['type']) for x in resp.json['data']} == {('test', 'workflows'), ('test', 'blocks')}
+    assert {(x['id'], x['type']) for x in resp.json['data']} == {
+        ('test', 'workflows'),
+        ('test', 'blocks'),
+        ('second-role', 'roles'),
+        ('third-role', 'roles'),
+    }
     for dependency in resp.json['data']:
         get_app(pub).get(sign_uri(dependency['urls']['export']))
 
@@ -238,6 +252,7 @@ def create_bundle(*args):
             'elements': [
                 {'type': 'forms', 'slug': 'test', 'name': 'test'},
                 {'type': 'blocks', 'slug': 'test', 'name': 'test'},
+                {'type': 'roles', 'slug': 'test', 'name': 'test'},
                 {'type': 'workflows', 'slug': 'test', 'name': 'test'},
                 {'type': 'forms-categories', 'slug': 'test', 'name': 'test'},
                 {'type': 'mail-templates-categories', 'slug': 'test', 'name': 'test'},
@@ -253,26 +268,35 @@ def create_bundle(*args):
 
         for (path, obj) in args:
             tarinfo = tarfile.TarInfo(path)
-            xml_export = ET.tostring(obj.export_to_xml(include_id=True))
-            tarinfo.size = len(xml_export)
-            tar.addfile(tarinfo, fileobj=io.BytesIO(xml_export))
+            if hasattr(obj, 'export_for_application'):
+                export, _ = obj.export_for_application()
+                export = export.encode()
+            else:
+                export = ET.tostring(obj.export_to_xml(include_id=True))
+            tarinfo.size = len(export)
+            tar.addfile(tarinfo, fileobj=io.BytesIO(export))
 
     return tar_io.getvalue()
 
 
 def test_export_import_bundle_import(pub):
     workflow = Workflow(name='test')
+    workflow.roles = {'_receiver': 'Receiver'}
     workflow.store()
 
     block = BlockDef(name='test')
     block.store()
 
+    role = pub.role_class(name='test')
+    role.store()
+
     formdef = FormDef()
     formdef.name = 'Test'
     formdef.fields = [
         BlockField(id='1', label='test', type='block:test'),
     ]
     formdef.workflow = workflow
+    formdef.workflow_roles = {'_receiver': role.id}
     formdef.disabled = False
     formdef.store()
 
@@ -306,6 +330,7 @@ def test_export_import_bundle_import(pub):
         ('data-sources-categories/test', ds_category),
         ('mail-templates/test', mail_template),
         ('mail-templates-categories/test', mail_template_category),
+        ('roles/test', role),
     )
     Category.wipe()
     FormDef.wipe()
@@ -315,8 +340,15 @@ def test_export_import_bundle_import(pub):
     MailTemplateCategory.wipe()
     NamedDataSource.wipe()
     DataSourceCategory.wipe()
+    pub.role_class.wipe()
     pub.custom_view_class.wipe()
 
+    # roles will be created beforehand with authentic provisionning
+    extra_role = pub.role_class(name='not this one')
+    extra_role.store()
+    role = pub.role_class(name='test')
+    role.store()
+
     resp = get_app(pub).put(sign_uri('/api/export-import/bundle-import/'), bundle)
     afterjob_url = resp.json['url']
     resp = get_app(pub).put(sign_uri(afterjob_url))
@@ -333,6 +365,7 @@ def test_export_import_bundle_import(pub):
     assert DataSourceCategory.count() == 1
     assert FormDef.select()[0].fields[0].type == 'block:test'
     assert pub.custom_view_class().count() == 1
+    assert FormDef.select()[0].workflow_roles == {'_receiver': role.id}
 
     # run new import to check it doesn't duplicate objects
     resp = get_app(pub).put(sign_uri('/api/export-import/bundle-import/'), bundle)
@@ -350,8 +383,9 @@ def test_export_import_bundle_import(pub):
     assert DataSourceCategory.count() == 1
     assert pub.custom_view_class().count() == 1
 
-    # change immutable attribute and check it's not reset
+    # change immutable attributes and check they are not reset
     formdef = FormDef.select()[0]
+    formdef.workflow_roles = {'_receiver': extra_role.id}
     formdef.disabled = True
     formdef.store()
 
@@ -362,3 +396,4 @@ def test_export_import_bundle_import(pub):
 
     formdef = FormDef.select()[0]
     assert formdef.disabled is True
+    assert formdef.workflow_roles == {'_receiver': extra_role.id}
diff --git a/wcs/api_export_import.py b/wcs/api_export_import.py
index 4ab2b7416..1011e833a 100644
--- a/wcs/api_export_import.py
+++ b/wcs/api_export_import.py
@@ -204,6 +204,8 @@ class BundleImportJob(AfterJob):
         self.tar_content = tar_content
 
     def execute(self):
+        object_types = [x for x in klasses if x != 'roles']
+
         tar_io = io.BytesIO(self.tar_content)
         with tarfile.open(fileobj=tar_io) as self.tar:
             manifest = json.loads(self.tar.extractfile('manifest.json').read().decode())
@@ -218,7 +220,7 @@ class BundleImportJob(AfterJob):
                     if x.get('type') in ('forms', 'cards', 'blocks', 'workflows')
                 ]
             )
-            self.total_count += len(manifest.get('elements'))
+            self.total_count += len([x for x in manifest.get('elements') if x.get('type') in object_types])
 
             # first pass on formdef/carddef/blockdef/workflows to create them empty
             # (name and slug); so they can be found for sure in import pass
@@ -226,21 +228,7 @@ class BundleImportJob(AfterJob):
                 self.pre_install([x for x in manifest.get('elements') if x.get('type') == type])
 
             # real installation pass
-            for type in (
-                'blocks-categories',
-                'cards-categories',
-                'forms-categories',
-                'workflows-categories',
-                'mail-templates-categories',
-                'data-sources-categories',
-                'data-sources',
-                'wscalls',
-                'mail-templates',
-                'forms',
-                'cards',
-                'blocks',
-                'workflows',
-            ):
+            for type in object_types:
                 self.install([x for x in manifest.get('elements') if x.get('type') == type])
 
     def pre_install(self, elements):
@@ -283,8 +271,8 @@ class BundleImportJob(AfterJob):
                 continue
             # replace
             new_object.id = existing_object.id
-            if element['type'] in ('forms', 'cards'):
-                # keep some settings
+            if element['type'] in ('forms', 'cards') and not existing_object.name.startswith('[pre-import]'):
+                # keep some settings when updating
                 for attr in (
                     'workflow_options',
                     'workflow_roles',
diff --git a/wcs/formdef.py b/wcs/formdef.py
index e00e52164..bf01df0f3 100644
--- a/wcs/formdef.py
+++ b/wcs/formdef.py
@@ -691,6 +691,11 @@ class FormDef(StorableObject):
             yield self.workflow
         for field in self.fields or []:
             yield from field.get_dependencies()
+        role_class = get_publisher().role_class
+        for role_id in itertools.chain(self.roles or [], self.backoffice_submission_roles or []):
+            yield role_class.get(role_id, ignore_errors=True)
+        for role_id in (self.workflow_roles or {}).values():
+            yield role_class.get(role_id, ignore_errors=True)
 
     @property
     def keywords_list(self):
-- 
2.37.2

