From d276aac1c4c376b297e0bca7b01f7277b45070fe Mon Sep 17 00:00:00 2001
From: Paul Marillonnet <pmarillonnet@entrouvert.com>
Date: Mon, 5 Dec 2022 10:08:00 +0100
Subject: [PATCH] wf: add user unlinking option to anonymise action (#71777)

---
 tests/workflow/test_carddata.py | 37 +++++++++++++++++++++++++++++++++
 tests/workflow/test_formdata.py | 37 +++++++++++++++++++++++++++++++++
 wcs/formdata.py                 |  5 +++++
 wcs/wf/anonymise.py             | 21 ++++++++++++++++++-
 4 files changed, 99 insertions(+), 1 deletion(-)

diff --git a/tests/workflow/test_carddata.py b/tests/workflow/test_carddata.py
index e4f6c60f3..6d45abc9b 100644
--- a/tests/workflow/test_carddata.py
+++ b/tests/workflow/test_carddata.py
@@ -1619,3 +1619,40 @@ def test_create_carddata_with_workflow_deleting_it(pub):
     formdata.just_created()
     formdata.perform_workflow()
     assert carddef.data_class().count() == 0
+
+
+def test_anonymise_action_unlink_user(pub):
+    CardDef.wipe()
+    FormDef.wipe()
+    pub.user_class.wipe()
+
+    user = pub.user_class()
+    user.email = 'test@example.net'
+    user.name_identifiers = ['xyz']
+    user.store()
+
+    wf = Workflow(name='test-unlink-user')
+    wf.possible_status = Workflow.get_default_workflow().possible_status[:]
+    anonymise = wf.possible_status[1].add_action('anonymise', id='_anonymise', prepend=True)
+    anonymise.label = 'Unlink User'
+    anonymise.varname = 'mycard'
+    anonymise.unlink_user = True
+    wf.store()
+
+    carddef = CardDef()
+    carddef.name = 'Person'
+    carddef.workflow_id = wf.id
+    carddef.store()
+    carddef.data_class().wipe()
+
+    carddata = carddef.data_class()()
+    carddata.data = {'0': 'Foo', '1': 'Bar'}
+    carddata.user_id = user.id
+    carddata.just_created()
+    carddata.store()
+
+    assert carddef.data_class().select()[0].user.id == user.id
+
+    carddata.perform_workflow()
+
+    assert carddef.data_class().select()[0].user is None
diff --git a/tests/workflow/test_formdata.py b/tests/workflow/test_formdata.py
index 60fa1fed1..be70f0bbb 100644
--- a/tests/workflow/test_formdata.py
+++ b/tests/workflow/test_formdata.py
@@ -398,3 +398,40 @@ def test_create_formdata_does_not_overwrite_initial_submission_context(pub):
     formdata.perform_workflow()
     assert target_formdef.data_class().count() == 2
     assert formdata.submission_context == {'a': 'b'}
+
+
+def test_anonymise_action_unlink_user(pub):
+    CardDef.wipe()
+    FormDef.wipe()
+    pub.user_class.wipe()
+
+    user = pub.user_class()
+    user.email = 'test@example.net'
+    user.name_identifiers = ['xyz']
+    user.store()
+
+    wf = Workflow(name='test-unlink-user')
+    wf.possible_status = Workflow.get_default_workflow().possible_status[:]
+    anonymise = wf.possible_status[1].add_action('anonymise', id='_anonymise', prepend=True)
+    anonymise.label = 'Unlink User'
+    anonymise.varname = 'mycard'
+    anonymise.unlink_user = True
+    wf.store()
+
+    formdef = FormDef()
+    formdef.name = 'Person'
+    formdef.workflow_id = wf.id
+    formdef.store()
+    formdef.data_class().wipe()
+
+    formdata = formdef.data_class()()
+    formdata.data = {'0': 'Foo', '1': 'Bar'}
+    formdata.user_id = user.id
+    formdata.just_created()
+    formdata.store()
+
+    assert formdef.data_class().select()[0].user.id == user.id
+
+    formdata.perform_workflow()
+
+    assert formdef.data_class().select()[0].user is None
diff --git a/wcs/formdata.py b/wcs/formdata.py
index 07b50f8dc..0856dcb4a 100644
--- a/wcs/formdata.py
+++ b/wcs/formdata.py
@@ -1266,6 +1266,11 @@ class FormData(StorableObject):
         self._store_all_evolution = True
         self.store()
 
+    def unlink_user(self):
+        get_session().mark_anonymous_formdata(self)
+        self.user_id = None
+        self.store()
+
     def get_display_name(self):
         return _('%(name)s #%(id)s') % {'name': self.formdef.name, 'id': self.get_display_id()}
 
diff --git a/wcs/wf/anonymise.py b/wcs/wf/anonymise.py
index 65a8ad4d4..18dee0c35 100644
--- a/wcs/wf/anonymise.py
+++ b/wcs/wf/anonymise.py
@@ -15,6 +15,7 @@
 # along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from wcs.qommon import _
+from wcs.qommon.form import CheckboxWidget
 from wcs.workflows import WorkflowStatusItem, register_item_class
 
 
@@ -22,9 +23,27 @@ class AnonymiseWorkflowStatusItem(WorkflowStatusItem):
     description = _('Anonymisation')
     key = 'anonymise'
     category = 'formdata-action'
+    unlink_user = False
 
     def perform(self, formdata):
-        formdata.anonymise()
+        if self.unlink_user is True:
+            formdata.unlink_user()
+        else:
+            formdata.anonymise()
+
+    def get_parameters(self):
+        return ('unlink_user',)
+
+    def add_parameters_widgets(self, form, parameters, prefix='', formdef=None, **kwargs):
+        super().add_parameters_widgets(form, parameters, prefix, formdef, **kwargs)
+        if 'unlink_user' in parameters:
+            form.add(
+                CheckboxWidget,
+                '%sunlink_user' % prefix,
+                title=_('Only perform form/card user unlinking'),
+                hint=_('If checked, this action will only unlink user from the form/card.'),
+                value=self.unlink_user,
+            )
 
 
 register_item_class(AnonymiseWorkflowStatusItem)
-- 
2.38.1

