misc: remap statuses in a transaction (#38579)
This commit is contained in:
parent
8f8d4bf481
commit
dfbe3123ef
|
@ -602,6 +602,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()
|
||||
|
@ -627,6 +631,7 @@ 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()
|
||||
|
||||
# run a SQL SELECT and we known all columns are defined.
|
||||
|
@ -634,6 +639,7 @@ def test_form_workflow_remapping(pub):
|
|||
|
||||
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
|
||||
|
@ -658,6 +664,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'
|
||||
|
||||
# run a SQL SELECT and we known all columns are defined.
|
||||
FormDef.get(formdef.id).data_class().select()
|
||||
|
|
|
@ -1100,14 +1100,11 @@ class FormDefPage(Directory):
|
|||
r += form.render()
|
||||
return r.getvalue()
|
||||
else:
|
||||
workflow_id = form.get_widget('workflow_id').parse()
|
||||
if self.formdef.data_class().keys():
|
||||
workflow_id = form.get_widget('workflow_id').parse() or self.formdef_default_workflow
|
||||
if self.formdef.data_class().count():
|
||||
# there are existing formdata, status will have to be mapped
|
||||
if workflow_id is None:
|
||||
workflow_id = self.formdef_default_workflow
|
||||
return redirect('workflow-status-remapping?new=%s' % workflow_id)
|
||||
self.formdef.workflow = Workflow.get(workflow_id) if workflow_id else None
|
||||
self.formdef.store(comment=_('Workflow change'))
|
||||
self.formdef.change_workflow(Workflow.get(workflow_id))
|
||||
return redirect('.')
|
||||
|
||||
def workflow_status_remapping(self):
|
||||
|
@ -1146,33 +1143,18 @@ 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 = None
|
||||
else:
|
||||
self.formdef.workflow = Workflow.get(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):
|
||||
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)
|
||||
)
|
||||
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()
|
||||
self.formdef.change_workflow(new_workflow, status_mapping)
|
||||
return redirect('.')
|
||||
|
||||
def get_preview(self):
|
||||
form = Form(action='#', use_tokens=False)
|
||||
|
|
|
@ -42,7 +42,7 @@ from .qommon.cron import CronJob
|
|||
from .qommon.form import Form, HtmlWidget, UploadedFile
|
||||
from .qommon.misc import JSONEncoder, get_as_datetime, simplify, xml_node_text
|
||||
from .qommon.publisher import get_publisher_class
|
||||
from .qommon.storage import Equal, StorableObject, fix_key
|
||||
from .qommon.storage import Equal, NotEqual, StorableObject, fix_key
|
||||
from .qommon.substitution import Substitutions
|
||||
from .qommon.template import Template
|
||||
from .roles import logged_users_role
|
||||
|
@ -506,11 +506,12 @@ class FormDef(StorableObject):
|
|||
return workflow
|
||||
|
||||
def set_workflow(self, workflow):
|
||||
if workflow:
|
||||
if workflow and workflow.id not in ['_carddef_default', '_default']:
|
||||
self.workflow_id = workflow.id
|
||||
self._workflow = workflow
|
||||
elif self.workflow_id:
|
||||
self.workflow_id = None
|
||||
self._workflow = None
|
||||
|
||||
workflow = property(get_workflow, set_workflow)
|
||||
|
||||
|
@ -1667,6 +1668,53 @@ 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, 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'
|
||||
|
||||
unmapped_status_suffix = '-invalid-%s' % str(self.workflow_id or 'default')
|
||||
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%s' % (status, unmapped_status_suffix)
|
||||
|
||||
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 = new_workflow
|
||||
self.store(comment=_('Workflow change'))
|
||||
if formdata_count:
|
||||
# instruct formdef to update its security rules
|
||||
self.data_class().rebuild_security()
|
||||
|
||||
|
||||
EmailsDirectory.register(
|
||||
'new_user',
|
||||
|
|
45
wcs/sql.py
45
wcs/sql.py
|
@ -25,6 +25,7 @@ import uuid
|
|||
import psycopg2
|
||||
import psycopg2.extensions
|
||||
import psycopg2.extras
|
||||
from psycopg2.sql import SQL, Identifier, Literal
|
||||
import unidecode
|
||||
|
||||
try:
|
||||
|
@ -3672,3 +3673,47 @@ def reindex():
|
|||
|
||||
conn.commit()
|
||||
cur.close()
|
||||
|
||||
|
||||
@guard_postgres
|
||||
def formdef_remap_statuses(formdef, mapping):
|
||||
table_name = get_formdef_table_name(formdef)
|
||||
evolutions_table_name = table_name + '_evolutions'
|
||||
unmapped_status_suffix = str(formdef.workflow_id or 'default')
|
||||
|
||||
# build the case expression
|
||||
status_cases = []
|
||||
for old_id, new_id in mapping.items():
|
||||
status_cases.append(
|
||||
SQL('WHEN status = {old_status} THEN {new_status}').format(
|
||||
old_status=Literal(old_id), new_status=Literal(new_id)
|
||||
)
|
||||
)
|
||||
case_expression = SQL(
|
||||
'(CASE WHEN status IS NULL THEN NULL '
|
||||
'{status_cases} '
|
||||
# keep status alread marked as invalid
|
||||
'WHEN status LIKE {pattern} THEN status '
|
||||
# mark unknown statuses as invalid
|
||||
'ELSE (status || {suffix}) END)'
|
||||
).format(
|
||||
status_cases=SQL('').join(status_cases),
|
||||
pattern=Literal('%-invalid-%'),
|
||||
suffix=Literal('-invalid-' + unmapped_status_suffix),
|
||||
)
|
||||
|
||||
conn, cur = get_connection_and_cursor()
|
||||
# update formdatas statuses
|
||||
cur.execute(
|
||||
SQL('UPDATE {table_name} SET status = {case_expression} WHERE status <> {draft_status}').format(
|
||||
table_name=Identifier(table_name), case_expression=case_expression, draft_status=Literal('draft')
|
||||
)
|
||||
)
|
||||
# update evolutions statuses
|
||||
cur.execute(
|
||||
SQL('UPDATE {table_name} SET status = {case_expression}').format(
|
||||
table_name=Identifier(evolutions_table_name), case_expression=case_expression
|
||||
)
|
||||
)
|
||||
conn.commit()
|
||||
cur.close()
|
||||
|
|
Loading…
Reference in New Issue