Projet

Général

Profil

0001-workflows-add-possibility-to-mark-and-jump-to-marked.patch

Frédéric Péters, 16 juin 2017 18:23

Télécharger (18,2 ko)

Voir les différences:

Subject: [PATCH 1/6] workflows: add possibility to mark and jump to marked
 status (#16524)

This keeps a stack of marked status and allow to pop back to previous
ones.
 tests/test_admin_pages.py           |  32 +++++++++++
 tests/test_backoffice_pages.py      | 110 ++++++++++++++++++++++++++++++++++++
 wcs/admin/workflows.py              |   3 +-
 wcs/backoffice/management.py        |  11 +++-
 wcs/formdata.py                     |  28 +++++++--
 wcs/qommon/static/css/dc2/admin.css |   4 ++
 wcs/wf/jump.py                      |   4 +-
 wcs/workflows.py                    |  71 +++++++++++++++++++----
 8 files changed, 243 insertions(+), 20 deletions(-)
tests/test_admin_pages.py
1909 1909
    resp = resp.click('Edit')
1910 1910
    assert not 'in_listing' in resp.form.fields.keys()
1911 1911

  
1912
def test_workflows_edit_choice_action(pub):
1913
    create_superuser(pub)
1914
    role = create_role()
1915
    Workflow.wipe()
1916
    workflow = Workflow(name='foo')
1917
    workflow.add_status(name='baz')
1918
    workflow.store()
1919

  
1920
    app = login(get_app(pub))
1921
    resp = app.get('/backoffice/workflows/1/')
1922
    resp = resp.click('baz')
1923

  
1924
    resp.forms[0]['type'] = 'Change Status'
1925
    resp = resp.forms[0].submit()
1926
    resp = resp.follow()
1927

  
1928
    resp = resp.click(href='items/1/', index=0)
1929
    assert 'Previously Marked Status' not in [x[2] for x in resp.form['status'].options]
1930
    resp.form['status'].value = 'baz'
1931
    resp = resp.form.submit('submit')
1932
    resp = resp.follow()
1933
    resp = resp.follow()
1934

  
1935
    resp = resp.click(href='items/1/', index=0)
1936
    resp.form['set_marker_on_status'].value = True
1937
    resp = resp.form.submit('submit')
1938
    resp = resp.follow()
1939
    resp = resp.follow()
1940

  
1941
    resp = resp.click(href='items/1/', index=0)
1942
    assert 'Previously Marked Status' in [x[2] for x in resp.form['status'].options]
1943

  
1912 1944
def test_workflows_action_subpath(pub):
1913 1945
    create_superuser(pub)
1914 1946
    role = create_role()
tests/test_backoffice_pages.py
7 7
import StringIO
8 8
import time
9 9
import hashlib
10
import random
10 11

  
11 12
import pytest
12 13
from webtest import Upload
......
3210 3211
            .parents('li').children('div.value span')
3211 3212
            .text() == '\'\\xed\\xa0\\x00\'')
3212 3213

  
3214
def test_workflow_jump_previous(pub):
3215
    user = create_user(pub)
3216
    create_environment(pub)
3217

  
3218
    wf = Workflow(name='jump around')
3219
    #       North
3220
    #     /      \
3221
    # West <----> East
3222
    #   |          |
3223
    #   |         autojump
3224
    #    |         |
3225
    #     \       /
3226
    #       South
3227

  
3228
    st1 = wf.add_status('North')
3229
    st1.id = 'north'
3230
    st2 = wf.add_status('West')
3231
    st2.id = 'west'
3232
    st3 = wf.add_status('East')
3233
    st3.id = 'east'
3234
    st4 = wf.add_status('Autojump')
3235
    st4.id = 'autojump'
3236
    st5 = wf.add_status('South')
3237
    st5.id = 'south'
3238

  
3239
    button_by_id = {}
3240

  
3241
    def add_jump(label, src, dst_id):
3242
        jump = ChoiceWorkflowStatusItem()
3243
        jump.id = str(random.random())
3244
        jump.label = label
3245
        jump.by = ['logged-users']
3246
        jump.status = dst_id
3247
        src.items.append(jump)
3248
        jump.parent = src
3249
        if dst_id != '_previous':
3250
            jump.set_marker_on_status = True
3251
        button_by_id[label] = 'button%s' % jump.id
3252
        return jump
3253

  
3254
    add_jump('Go West', st1, st2.id)
3255
    add_jump('Go East', st1, st3.id)
3256
    add_jump('Go South', st2, st5.id)
3257
    add_jump('Go Autojump', st3, st4.id)
3258
    add_jump('Go Back', st5, '_previous')
3259

  
3260
    add_jump('Jump West', st3, st2.id)
3261
    add_jump('Jump East', st2, st3.id)
3262

  
3263
    jump = JumpWorkflowStatusItem()
3264
    jump.id = '_auto-jump'
3265
    jump.status = st5.id
3266
    st4.items.append(jump)
3267
    jump.parent = st4
3268

  
3269
    wf.store()
3270

  
3271
    formdef = FormDef.get_by_urlname('form-title')
3272
    formdef.data_class().wipe()
3273
    formdef.workflow = wf
3274
    formdef.store()
3275

  
3276
    formdata = formdef.data_class()()
3277
    formdata.data = {}
3278
    formdata.just_created()
3279
    formdata.store()
3280

  
3281
    app = login(get_app(pub))
3282
    resp = app.get('/backoffice/management/form-title/%s/' % formdata.id)
3283

  
3284
    # jump around using buttons
3285
    resp = resp.form.submit(button_by_id['Go West']).follow()  # push (north)
3286
    assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st2.id
3287
    resp = resp.form.submit(button_by_id['Go South']).follow() # push (north, west)
3288
    assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st5.id
3289
    resp = resp.form.submit(button_by_id['Go Back']).follow()  # pop (north)
3290
    assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st2.id
3291
    resp = resp.form.submit(button_by_id['Go South']).follow() # push (north, west)
3292
    assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st5.id
3293
    resp = resp.form.submit(button_by_id['Go Back']).follow()  # pop (north)
3294
    assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st2.id
3295
    resp = resp.form.submit(button_by_id['Jump East']).follow() # push (north, west)
3296
    assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st3.id
3297
    resp = resp.form.submit(button_by_id['Go Autojump']).follow() # push (north, west, east)
3298
    assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st5.id
3299

  
3300
    # check markers are displayed in /inspect page
3301
    user.is_admin = True
3302
    user.store()
3303
    resp2 = app.get('/backoffice/management/form-title/%s/inspect' % formdata.id)
3304
    assert 'Markers Stack' in resp2.body
3305
    assert '<span class="status">East</span>' in resp2.body
3306
    assert '<span class="status">West</span>' in resp2.body
3307
    assert '<span class="status">North</span>' in resp2.body
3308
    assert resp2.body.find('<span class="status">East</span>') < resp2.body.find('<span class="status">West</span>')
3309
    assert resp2.body.find('<span class="status">West</span>') < resp2.body.find('<span class="status">North</span>')
3310

  
3311
    resp = resp.form.submit(button_by_id['Go Back']).follow() # pop (north, west)
3312
    assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st3.id
3313

  
3314
    # and do a last jump using the API
3315
    formdata = formdef.data_class().get(formdata.id)
3316
    formdata.jump_status('_previous') # pop (north)
3317
    assert formdata.status == 'wf-%s' % st2.id
3318

  
3319
    formdata = formdef.data_class().get(formdata.id)
3320
    formdata.jump_status('_previous') # pop ()
3321
    assert formdata.status == 'wf-%s' % st1.id
3322

  
3213 3323
def test_backoffice_fields(pub):
3214 3324
    user = create_user(pub)
3215 3325
    create_environment(pub)
wcs/admin/workflows.py
153 153
    for status in workflow.possible_status:
154 154
        i = status.id
155 155
        for item in status.items:
156
            next_status_ids = [x.id for x in item.get_target_status() if x.id != status.id]
156
            next_status_ids = [x.id for x in item.get_target_status()
157
                               if x.id and x.id != status.id]
157 158
            if not next_status_ids:
158 159
                next_status_ids = [status.id]
159 160
            done = {}
wcs/backoffice/management.py
2088 2088
            if not isinstance(v, basestring):
2089 2089
                r += htmltext(' <span class="type">(%r)</span>') % type(v)
2090 2090
            r += htmltext('</div></li>')
2091

  
2092
        if '_markers_stack' in (self.filled.workflow_data or {}):
2093
            r += htmltext('<li><h3>%s</h3></li>') % _('Markers Stack')
2094
            for marker in reversed(self.filled.workflow_data['_markers_stack']):
2095
                status = self.filled.get_status(marker['status_id'])
2096
                if status:
2097
                    r += htmltext('<li><span class="status">%s</span></li>') % status.name
2098
                else:
2099
                    r += htmltext('<li><span class="status">%s</span></li>') % _('Unknown')
2100

  
2091 2101
        r += htmltext('</ul>')
2092 2102
        r += htmltext('</div>')
2093

  
2094 2103
        return r.getvalue()
2095 2104

  
2096 2105

  
wcs/formdata.py
404 404
            return None
405 405
        if not self.formdef:
406 406
            return None
407
        if status.startswith('wf-'):
408
            status = status[3:]
407 409
        try:
408
            status_id = status.split('-')[1]
409
            wf_status = [x for x in self.formdef.workflow.possible_status if x.id == status_id][0]
410
            wf_status = [x for x in self.formdef.workflow.possible_status if x.id == status][0]
410 411
        except IndexError:
411 412
            return None
412 413
        return wf_status
......
453 454
            return None
454 455
        return wf_status.handle_form(form, self, user)
455 456

  
457
    def pop_previous_marked_status(self):
458
        if not self.workflow_data or not '_markers_stack' in self.workflow_data:
459
            return None
460
        try:
461
            marker_data = self.workflow_data['_markers_stack'].pop()
462
            status_id = marker_data['status_id']
463
        except IndexError:
464
            return None
465
        return self.formdef.workflow.get_status(status_id)
466

  
456 467
    def jump_status(self, status_id):
468
        if status_id == '_previous':
469
            previous_status = self.pop_previous_marked_status()
470
            assert previous_status, 'failed to compute previous status'
471
            status_id = previous_status.id
457 472
        evo = Evolution()
458 473
        evo.time = time.localtime()
459 474
        evo.status = 'wf-%s' % status_id
......
631 646
                d.update(klass.get_substitution_variables(self))
632 647

  
633 648
        if self.workflow_data:
634
            d.update(self.workflow_data)
635
            # pass over uploaded files and attach an extra attribute with the
636
            # url to the file.
649
            # pass over workflow data to:
650
            #  - attach an extra url attribute to uploaded files
651
            #  - ignore "private" attributes
637 652
            for k, v in self.workflow_data.items():
653
                if k[0] == '_':
654
                    continue
655
                d[k] = v
638 656
                if isinstance(v, Upload):
639 657
                    try:
640 658
                        formvar, fieldvar = re.match('(.*)_var_(.*)_raw$', k).groups()
wcs/qommon/static/css/dc2/admin.css
1525 1525
	font-size: 100%;
1526 1526
}
1527 1527

  
1528
ul.form-inspector li span.status {
1529
	padding: 0 1ex;
1530
}
1531

  
1528 1532
ul.form-inspector li div.value {
1529 1533
	display: block;
1530 1534
	padding: 0 0 0.5ex 1em;
wcs/wf/jump.py
145 145
            return _('Change Status Automatically (to %s)') % wf_status[0].name
146 146

  
147 147
    def get_parameters(self):
148
        return ('status', 'condition', 'trigger', 'by', 'timeout')
148
        return ('status', 'set_marker_on_status', 'condition', 'trigger', 'by', 'timeout')
149 149

  
150 150
    def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
151 151
        WorkflowStatusJumpItem.add_parameters_widgets(self, form, parameters, prefix, formdef)
......
190 190
            return
191 191

  
192 192
        if self.must_jump(formdata):
193
            wf_status = self.get_target_status()
193
            wf_status = self.get_target_status(formdata)
194 194
            if wf_status:
195 195
                formdata.status = 'wf-%s' % wf_status[0].id
196 196

  
wcs/workflows.py
1634 1634
    def get_substitution_variables(self, formdata):
1635 1635
        return {}
1636 1636

  
1637
    def get_target_status(self):
1637
    def get_target_status(self, formdata=None):
1638 1638
        """Returns a list of status this item can lead to."""
1639 1639
        if not getattr(self, 'status', None):
1640 1640
            return []
1641 1641

  
1642
        if self.status == '_previous':
1643
            if formdata is None:
1644
                # must be in a formdata to compute destination, just give a
1645
                # fake status for presentation purpose
1646
                return [WorkflowStatus(_('Previously Marked Status'))]
1647
            previous_status = formdata.pop_previous_marked_status()
1648
            if previous_status:
1649
                return [previous_status]
1650
            return []
1651

  
1642 1652
        try:
1643 1653
            return [x for x in self.parent.parent.possible_status if x.id == self.status]
1644 1654
        except IndexError:
......
1656 1666
            if getattr(self, 'by', None):
1657 1667
                roles = self.parent.parent.render_list_of_roles(self.by)
1658 1668
                label += ' %s %s' % (_('by'), roles)
1669
            if self.status == '_previous':
1670
                label += ' ' + _('(to last marker)')
1671
            if self.set_marker_on_status:
1672
                label += ' ' + _('(and set marker)')
1659 1673
        else:
1660 1674
            label = self.render_as_line()
1661 1675
        return label
......
1685 1699
class WorkflowStatusJumpItem(WorkflowStatusItem):
1686 1700
    status = None
1687 1701
    endpoint = False
1702
    set_marker_on_status = False
1688 1703

  
1689 1704
    def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
1690 1705
        if 'status' in parameters:
1706
            destinations = [(x.id, x.name) for x in self.parent.parent.possible_status]
1707

  
1708
            # look for existing jumps that are dropping a mark
1709
            for status in self.parent.parent.possible_status:
1710
                for item in status.items:
1711
                    if getattr(item, 'set_marker_on_status', False):
1712
                        destinations.append(('_previous', _('Previously Marked Status')))
1713
                        break
1714
                else:
1715
                    continue
1716
                break
1717

  
1691 1718
            form.add(SingleSelectWidget, '%sstatus' % prefix, title = _('Status'), value = self.status,
1692
                options = [(None, '---')] + [(x.id, x.name) for x in self.parent.parent.possible_status])
1719
                options = [(None, '---')] + destinations)
1720

  
1721
        if 'set_marker_on_status' in parameters:
1722
            form.add(CheckboxWidget, '%sset_marker_on_status' % prefix,
1723
                title=_('Set marker to jump back to current status'),
1724
                value=self.set_marker_on_status,
1725
                advanced=not(self.set_marker_on_status))
1693 1726

  
1694 1727
    def get_parameters(self):
1695
        return ('status',)
1728
        return ('status', 'set_marker_on_status')
1696 1729

  
1697 1730

  
1698 1731
def get_role_translation(formdata, role_name):
......
1870 1903

  
1871 1904
    def render_as_line(self):
1872 1905
        if self.label:
1906
            more = ''
1907
            if self.set_marker_on_status:
1908
                more += ' ' + _('(and set marker)')
1873 1909
            if self.by:
1874
                return _('Change Status "%(label)s" by %(by)s') % \
1875
                        { 'label' : self.label,
1876
                          'by' : self.render_list_of_roles(self.by) }
1910
                return _('Change Status "%(label)s" by %(by)s%(more)s') % {
1911
                        'label' : self.label,
1912
                        'by' : self.render_list_of_roles(self.by),
1913
                        'more': more
1914
                    }
1877 1915
            else:
1878
                return _('Change Status "%s"') % self.label
1916
                return _('Change Status "%(label)s"%(more)s') % {
1917
                        'label': self.label,
1918
                        'more': more
1919
                    }
1879 1920
        else:
1880 1921
            return _('Change Status (not completed)')
1881 1922

  
......
1892 1933

  
1893 1934
    def submit_form(self, form, formdata, user, evo):
1894 1935
        if form.get_submit() == 'button%s' % self.id:
1895
            wf_status = self.get_target_status()
1936
            wf_status = self.get_target_status(formdata)
1896 1937
            if wf_status:
1897 1938
                evo.status = 'wf-%s' % wf_status[0].id
1939
                if self.set_marker_on_status:
1940
                    if formdata.workflow_data and '_markers_stack' in formdata.workflow_data:
1941
                        markers_stack = formdata.workflow_data.get('_markers_stack')
1942
                    else:
1943
                        markers_stack = []
1944
                    markers_stack.append({'status_id': formdata.status[3:]})
1945
                    formdata.update_workflow_data({'_markers_stack': markers_stack})
1898 1946
                form.clear_errors()
1899 1947
                return True # get out of processing loop
1900 1948

  
......
1919 1967
                     value=self.backoffice_info_text)
1920 1968

  
1921 1969
    def get_parameters(self):
1922
        return ('by', 'status', 'label', 'backoffice_info_text', 'require_confirmation')
1970
        return ('by', 'status', 'label', 'backoffice_info_text',
1971
                'require_confirmation', 'set_marker_on_status')
1923 1972

  
1924 1973
register_item_class(ChoiceWorkflowStatusItem)
1925 1974

  
......
1940 1989

  
1941 1990
    def submit_form(self, form, formdata, user, evo):
1942 1991
        if form.is_submitted() and not form.has_errors():
1943
            wf_status = self.get_target_status()
1992
            wf_status = self.get_target_status(formdata)
1944 1993
            if wf_status:
1945 1994
                evo.status = 'wf-%s' % wf_status[0].id
1946 1995

  
1947 1996
    def get_parameters(self):
1948
        return ('status',)
1997
        return ('status', 'set_marker_on_status')
1949 1998
register_item_class(JumpOnSubmitWorkflowStatusItem)
1950 1999

  
1951 2000

  
1952
-