Projet

Général

Profil

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

Benjamin Dauvergne, 21 octobre 2021 19:20

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
......
505 505
        return workflow
506 506

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

  
514 515
    workflow = property(get_workflow, set_workflow)
515 516

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

  
1670
    def change_workflow(self, new_workflow, status_mapping=None):
1671
        old_workflow = self.get_workflow()
1672

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

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

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

  
1691
                    sql.formdef_remap_statuses(self, mapping)
1692
                else:
1693

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

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

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

  
1669 1717

  
1670 1718
EmailsDirectory.register(
1671 1719
    'new_user',
wcs/sql.py
25 25
import psycopg2
26 26
import psycopg2.extensions
27 27
import psycopg2.extras
28
from psycopg2.sql import SQL, Identifier, Literal
28 29
import unidecode
29 30

  
30 31
try:
......
3669 3670

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

  
3674

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

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

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