From fca1b0ddd38a5d0c0a915aadd8035b93bf68ee14 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Thu, 26 Nov 2020 15:35:19 +0100 Subject: [PATCH] misc: remap statuses in a transaction (#38579) --- tests/admin_pages/test_form.py | 7 +++++ wcs/admin/forms.py | 42 ++++++++++++------------------ wcs/formdef.py | 47 ++++++++++++++++++++++++++++++++++ wcs/sql.py | 22 ++++++++++++++++ 4 files changed, 92 insertions(+), 26 deletions(-) diff --git a/tests/admin_pages/test_form.py b/tests/admin_pages/test_form.py index 9c292034..f6c77eb5 100644 --- a/tests/admin_pages/test_form.py +++ b/tests/admin_pages/test_form.py @@ -517,6 +517,10 @@ def test_form_workflow_remapping(pub): formdata2.status = 'draft' formdata2.store() + formdata3 = data_class() + formdata3.status = 'wf-1' + formdata3.store() + Workflow.wipe() workflow = Workflow(name='Workflow One') workflow.store() @@ -538,9 +542,11 @@ def test_form_workflow_remapping(pub): assert len(resp.forms[0]['mapping-%s' % status.id].options) == 1 assert data_class.get(formdata1.id).status == 'wf-new' assert data_class.get(formdata2.id).status == 'draft' + assert data_class.get(formdata3.id).status == 'wf-1' resp = resp.forms[0].submit() assert data_class.get(formdata1.id).status == 'wf-finished' assert data_class.get(formdata2.id).status == 'draft' + assert data_class.get(formdata3.id).status == 'wf-1-invalid-_default' # change to another workflow, with no mapping change workflow2 = workflow @@ -561,6 +567,7 @@ def test_form_workflow_remapping(pub): resp = resp.forms[0].submit() assert data_class.get(formdata1.id).status == 'wf-finished' assert data_class.get(formdata2.id).status == 'draft' + assert data_class.get(formdata3.id).status == 'wf-1-invalid-_default' def test_form_submitter_roles(pub): diff --git a/wcs/admin/forms.py b/wcs/admin/forms.py index 9ecab250..431a987c 100644 --- a/wcs/admin/forms.py +++ b/wcs/admin/forms.py @@ -1069,8 +1069,7 @@ class FormDefPage(Directory): if workflow_id is None: workflow_id = self.formdef_default_workflow return redirect('workflow-status-remapping?new=%s' % workflow_id) - self.formdef.workflow_id = workflow_id - self.formdef.store(comment=_('Workflow change')) + self.formdef.change_workflow(workflow_id) return redirect('.') def workflow_status_remapping(self): @@ -1109,33 +1108,24 @@ class FormDefPage(Directory): r += form.render() return r.getvalue() else: - get_logger().info( - 'admin - form "%s", workflow is now "%s" (was "%s")' - % (self.formdef.name, new_workflow.name, self.formdef.workflow.name) - ) - self.workflow_status_remapping_submit(form) - if new_workflow.id == self.formdef_default_workflow: - self.formdef.workflow_id = None - else: - self.formdef.workflow_id = new_workflow.id - self.formdef.store(comment=_('Workflow change')) - # instruct formdef to update its security rules - self.formdef.data_class().rebuild_security() - return redirect('.') + return self.workflow_status_remapping_submit(form, new_workflow) + + def workflow_status_remapping_submit(self, form, new_workflow): + get_logger().info( + 'admin - form "%s", workflow is now "%s" (was "%s")' + % (self.formdef.name, new_workflow.name, self.formdef.workflow.name) + ) - def workflow_status_remapping_submit(self, form): status_mapping = {} for status in self.formdef.workflow.possible_status: - status_mapping['wf-%s' % status.id] = 'wf-%s' % form.get_widget('mapping-%s' % status.id).parse() - if any([x[0] != x[1] for x in status_mapping.items()]): - # if there are status changes, update all formdatas (except drafts) - status_mapping.update({'draft': 'draft'}) - for item in self.formdef.data_class().select([NotEqual('status', 'draft')]): - item.status = status_mapping.get(item.status) - if item.evolution: - for evo in item.evolution: - evo.status = status_mapping.get(evo.status) - item.store() + status_mapping[status.id] = form.get_widget('mapping-%s' % status.id).parse() + + if new_workflow.id == self.formdef_default_workflow: + new_workflow_id = None + else: + new_workflow_id = new_workflow.id + self.formdef.change_workflow(new_workflow_id, status_mapping) + return redirect('.') def get_preview(self): form = Form(action='#', use_tokens=False) diff --git a/wcs/formdef.py b/wcs/formdef.py index e83d7cb3..b6e23e44 100644 --- a/wcs/formdef.py +++ b/wcs/formdef.py @@ -50,6 +50,7 @@ from .qommon.publisher import get_publisher_class from .qommon.storage import Equal from .qommon.storage import StorableObject from .qommon.storage import fix_key +from .qommon.storage import NotEqual from .qommon.substitution import Substitutions from .roles import logged_users_role @@ -1580,6 +1581,52 @@ class FormDef(StorableObject): # chunk contains the fields. return pickle.dumps(object, protocol=2) + pickle.dumps(object.fields, protocol=2) + def change_workflow(self, new_workflow_id, status_mapping=None): + old_workflow = self.get_workflow() + + formdata_count = self.data_class().count() + if formdata_count: + assert status_mapping, 'status mapping is required if there are formdatas' + assert all( + status.id in status_mapping for status in old_workflow.possible_status + ), 'a status was not mapped' + + mapping = {} + for old_status, new_status in status_mapping.items(): + mapping['wf-%s' % old_status] = 'wf-%s' % new_status + mapping['draft'] = 'draft' + + if any([x[0] != x[1] for x in mapping.items()]): + # if there are status changes, update all formdatas (except drafts) + if get_publisher().is_using_postgresql(): + from . import sql + + sql.formdef_remap_statuses(self, mapping) + else: + + def map_status(status): + if status is None: + return None + elif status in mapping: + return mapping[status] + elif '-invalid-' in status: + return status + else: + return '%s-invalid-%s' % (status, old_workflow.id) + + for formdata in self.data_class().select([NotEqual('status', 'draft')]): + formdata.status = map_status(formdata.status) + if formdata.evolution: + for evo in formdata.evolution: + evo.status = map_status(evo.status) + formdata.store() + + self.workflow_id = new_workflow_id + self.store(comment=_('Workflow change')) + if formdata_count: + # instruct formdef to update its security rules + self.data_class().rebuild_security() + from .qommon.admin.emails import EmailsDirectory diff --git a/wcs/sql.py b/wcs/sql.py index c7a6e0dd..aa9820eb 100644 --- a/wcs/sql.py +++ b/wcs/sql.py @@ -3511,3 +3511,25 @@ def reindex(): conn.commit() cur.close() + + +@guard_postgres +def formdef_remap_statuses(formdef, mapping): + conn, cur = get_connection_and_cursor() + table_name = get_formdef_table_name(formdef) + evolutions_table_name = table_name + '_evolutions' + old_workflow_id = formdef.workflow_id or '_default' + args = [] + case_expression = '(CASE ' + case_expression += 'WHEN status IS NULL THEN NULL ' + for old_id, new_id in mapping.items(): + case_expression += "WHEN status = %s THEN %s " + args.extend([old_id, new_id]) + case_expression += " WHEN status LIKE '%%-invalid-%%' THEN status " + case_expression += " ELSE (status || '-invalid-' || %s) END)" + args.append(old_workflow_id) + + cur.execute('BEGIN') + cur.execute('UPDATE %s SET status = %s WHERE status <> \'draft\'' % (table_name, case_expression), args) + cur.execute('UPDATE %s SET status = %s' % (evolutions_table_name, case_expression), args) + cur.execute('COMMIT') -- 2.30.1