Projet

Général

Profil

0001-misc-remap-statuses-in-a-transaction-38579.patch

Benjamin Dauvergne, 20 octobre 2021 10:29

Télécharger (10,6 ko)

Voir les différences:

Subject: [PATCH] misc: remap statuses in a transaction (#38579)

 tests/admin_pages/test_form.py |  7 +++++
 wcs/admin/forms.py             | 42 ++++++++-------------------
 wcs/formdef.py                 | 52 ++++++++++++++++++++++++++++++++--
 wcs/sql.py                     | 45 +++++++++++++++++++++++++++++
 4 files changed, 114 insertions(+), 32 deletions(-)
tests/admin_pages/test_form.py
602 602
    formdata2.status = 'draft'
603 603
    formdata2.store()
604 604

  
605
    formdata3 = data_class()
606
    formdata3.status = 'wf-1'
607
    formdata3.store()
608

  
605 609
    Workflow.wipe()
606 610
    workflow = Workflow(name='Workflow One')
607 611
    workflow.store()
......
627 631
        assert len(resp.forms[0]['mapping-%s' % status.id].options) == 1
628 632
    assert data_class.get(formdata1.id).status == 'wf-new'
629 633
    assert data_class.get(formdata2.id).status == 'draft'
634
    assert data_class.get(formdata3.id).status == 'wf-1'
630 635
    resp = resp.forms[0].submit()
631 636

  
632 637
    # run a SQL SELECT and we known all columns are defined.
......
634 639

  
635 640
    assert data_class.get(formdata1.id).status == 'wf-finished'
636 641
    assert data_class.get(formdata2.id).status == 'draft'
642
    assert data_class.get(formdata3.id).status == 'wf-1-invalid-default'
637 643

  
638 644
    # change to another workflow, with no mapping change
639 645
    workflow2 = workflow
......
658 664
    resp = resp.forms[0].submit()
659 665
    assert data_class.get(formdata1.id).status == 'wf-finished'
660 666
    assert data_class.get(formdata2.id).status == 'draft'
667
    assert data_class.get(formdata3.id).status == 'wf-1-invalid-default'
661 668

  
662 669
    # run a SQL SELECT and we known all columns are defined.
663 670
    FormDef.get(formdef.id).data_class().select()
wcs/admin/forms.py
1100 1100
            r += form.render()
1101 1101
            return r.getvalue()
1102 1102
        else:
1103
            workflow_id = form.get_widget('workflow_id').parse()
1104
            if self.formdef.data_class().keys():
1103
            workflow_id = form.get_widget('workflow_id').parse() or self.formdef_default_workflow
1104
            if self.formdef.data_class().count():
1105 1105
                # there are existing formdata, status will have to be mapped
1106
                if workflow_id is None:
1107
                    workflow_id = self.formdef_default_workflow
1108 1106
                return redirect('workflow-status-remapping?new=%s' % workflow_id)
1109
            self.formdef.workflow = Workflow.get(workflow_id) if workflow_id else None
1110
            self.formdef.store(comment=_('Workflow change'))
1107
            self.formdef.change_workflow(Workflow.get(workflow_id))
1111 1108
            return redirect('.')
1112 1109

  
1113 1110
    def workflow_status_remapping(self):
......
1146 1143
            r += form.render()
1147 1144
            return r.getvalue()
1148 1145
        else:
1149
            get_logger().info(
1150
                'admin - form "%s", workflow is now "%s" (was "%s")'
1151
                % (self.formdef.name, new_workflow.name, self.formdef.workflow.name)
1152
            )
1153
            self.workflow_status_remapping_submit(form)
1154
            if new_workflow.id == self.formdef_default_workflow:
1155
                self.formdef.workflow = None
1156
            else:
1157
                self.formdef.workflow = Workflow.get(new_workflow.id)
1158
            self.formdef.store(comment=_('Workflow change'))
1159
            # instruct formdef to update its security rules
1160
            self.formdef.data_class().rebuild_security()
1161
            return redirect('.')
1146
            return self.workflow_status_remapping_submit(form, new_workflow)
1162 1147

  
1163
    def workflow_status_remapping_submit(self, form):
1148
    def workflow_status_remapping_submit(self, form, new_workflow):
1149
        get_logger().info(
1150
            'admin - form "%s", workflow is now "%s" (was "%s")'
1151
            % (self.formdef.name, new_workflow.name, self.formdef.workflow.name)
1152
        )
1164 1153
        status_mapping = {}
1165 1154
        for status in self.formdef.workflow.possible_status:
1166
            status_mapping['wf-%s' % status.id] = 'wf-%s' % form.get_widget('mapping-%s' % status.id).parse()
1167
        if any(x[0] != x[1] for x in status_mapping.items()):
1168
            # if there are status changes, update all formdatas (except drafts)
1169
            status_mapping.update({'draft': 'draft'})
1170
            for item in self.formdef.data_class().select([NotEqual('status', 'draft')]):
1171
                item.status = status_mapping.get(item.status)
1172
                if item.evolution:
1173
                    for evo in item.evolution:
1174
                        evo.status = status_mapping.get(evo.status)
1175
                item.store()
1155
            status_mapping[status.id] = form.get_widget('mapping-%s' % status.id).parse()
1156
        self.formdef.change_workflow(new_workflow, status_mapping)
1157
        return redirect('.')
1176 1158

  
1177 1159
    def get_preview(self):
1178 1160
        form = Form(action='#', use_tokens=False)
wcs/formdef.py
41 41
from .qommon.form import Form, HtmlWidget, UploadedFile
42 42
from .qommon.misc import JSONEncoder, get_as_datetime, simplify, xml_node_text
43 43
from .qommon.publisher import get_publisher_class
44
from .qommon.storage import Equal, StorableObject, fix_key
44
from .qommon.storage import Equal, NotEqual, StorableObject, fix_key
45 45
from .qommon.substitution import Substitutions
46 46
from .qommon.template import Template
47 47
from .roles import logged_users_role
......
503 503
        return workflow
504 504

  
505 505
    def set_workflow(self, workflow):
506
        if workflow:
506
        if workflow and workflow.id not in ['_carddef_default', '_default']:
507 507
            self.workflow_id = workflow.id
508 508
            self._workflow = workflow
509 509
        elif self.workflow_id:
510 510
            self.workflow_id = None
511
            self._workflow = None
511 512

  
512 513
    workflow = property(get_workflow, set_workflow)
513 514

  
......
1664 1665
        # chunk contains the fields.
1665 1666
        return pickle.dumps(object, protocol=2) + pickle.dumps(object.fields, protocol=2)
1666 1667

  
1668
    def change_workflow(self, new_workflow, status_mapping=None):
1669
        old_workflow = self.get_workflow()
1670

  
1671
        formdata_count = self.data_class().count()
1672
        if formdata_count:
1673
            assert status_mapping, 'status mapping is required if there are formdatas'
1674
            assert all(
1675
                status.id in status_mapping for status in old_workflow.possible_status
1676
            ), 'a status was not mapped'
1677

  
1678
            unmmaped_status_suffix = '-invalid-%s' % str(self.workflow_id or 'default')
1679
            mapping = {}
1680
            for old_status, new_status in status_mapping.items():
1681
                mapping['wf-%s' % old_status] = 'wf-%s' % new_status
1682
            mapping['draft'] = 'draft'
1683

  
1684
            if any(x[0] != x[1] for x in mapping.items()):
1685
                # if there are status changes, update all formdatas (except drafts)
1686
                if get_publisher().is_using_postgresql():
1687
                    from . import sql
1688

  
1689
                    sql.formdef_remap_statuses(self, mapping)
1690
                else:
1691

  
1692
                    def map_status(status):
1693
                        if status is None:
1694
                            return None
1695
                        elif status in mapping:
1696
                            return mapping[status]
1697
                        elif '-invalid-' in status:
1698
                            return status
1699
                        else:
1700
                            return '%s%s' % (status, unmmaped_status_suffix)
1701

  
1702
                    for formdata in self.data_class().select([NotEqual('status', 'draft')]):
1703
                        formdata.status = map_status(formdata.status)
1704
                        if formdata.evolution:
1705
                            for evo in formdata.evolution:
1706
                                evo.status = map_status(evo.status)
1707
                        formdata.store()
1708

  
1709
        self.workflow = new_workflow
1710
        self.store(comment=_('Workflow change'))
1711
        if formdata_count:
1712
            # instruct formdef to update its security rules
1713
            self.data_class().rebuild_security()
1714

  
1667 1715

  
1668 1716
EmailsDirectory.register(
1669 1717
    'new_user',
wcs/sql.py
26 26
import psycopg2
27 27
import psycopg2.extensions
28 28
import psycopg2.extras
29
from psycopg2.sql import SQL, Identifier, Literal
29 30

  
30 31
try:
31 32
    import cPickle as pickle
......
3668 3669

  
3669 3670
    conn.commit()
3670 3671
    cur.close()
3672

  
3673

  
3674
@guard_postgres
3675
def formdef_remap_statuses(formdef, mapping):
3676
    table_name = get_formdef_table_name(formdef)
3677
    evolutions_table_name = table_name + '_evolutions'
3678
    unmmaped_status_suffix = str(formdef.workflow_id or 'default')
3679

  
3680
    # build the case expression
3681
    status_cases = []
3682
    for old_id, new_id in mapping.items():
3683
        status_cases.append(
3684
            SQL('WHEN status = {old_status} THEN {new_status}').format(
3685
                old_status=Literal(old_id), new_status=Literal(new_id)
3686
            )
3687
        )
3688
    case_expression = SQL(
3689
        '(CASE WHEN status IS NULL THEN NULL '
3690
        '{status_cases} '
3691
        # keep status alread marked as invalid
3692
        'WHEN status LIKE {pattern} THEN status '
3693
        # mark unknown statuses as invalid
3694
        'ELSE (status || {suffix}) END)'
3695
    ).format(
3696
        status_cases=SQL('').join(status_cases),
3697
        pattern=Literal('%-invalid-%'),
3698
        suffix=Literal('-invalid-' + unmmaped_status_suffix),
3699
    )
3700

  
3701
    conn, cur = get_connection_and_cursor()
3702
    # update formdatas statuses
3703
    cur.execute(
3704
        SQL('UPDATE {table_name} SET status = {case_expression} WHERE status <> {draft_status}').format(
3705
            table_name=Identifier(table_name), case_expression=case_expression, draft_status=Literal('draft')
3706
        )
3707
    )
3708
    # update evolutions statuses
3709
    cur.execute(
3710
        SQL('UPDATE {table_name} SET status = {case_expression}').format(
3711
            table_name=Identifier(evolutions_table_name), case_expression=case_expression
3712
        )
3713
    )
3714
    conn.commit()
3715
    cur.close()
3671
-