Projet

Général

Profil

0001-backoffice-add-multi-action-support-for-status-manua.patch

Frédéric Péters, 05 mai 2020 14:17

Télécharger (19,4 ko)

Voir les différences:

Subject: [PATCH] backoffice: add multi-action support for status manual jump
 action (#37983)

 tests/test_backoffice_pages.py      | 123 +++++++++++++++++++---------
 wcs/backoffice/management.py        |  70 ++++++++++------
 wcs/qommon/static/js/wcs.listing.js |  17 +++-
 wcs/workflows.py                    |  27 +++++-
 4 files changed, 170 insertions(+), 67 deletions(-)
tests/test_backoffice_pages.py
317 317

  
318 318
    # check status filter <select>
319 319
    resp = app.get('/backoffice/management/form-title/')
320
    resp.forms[0]['filter'] = 'all'
321
    resp = resp.forms[0].submit()
320
    resp.forms['listing-settings']['filter'] = 'all'
321
    resp = resp.forms['listing-settings'].submit()
322 322
    if pub.is_using_postgresql():
323 323
        assert resp.text.count('data-link') == 20
324 324
    else:
......
327 327

  
328 328
    # check status filter <select>
329 329
    resp = app.get('/backoffice/management/form-title/')
330
    resp.forms[0]['filter'] = 'done'
331
    resp = resp.forms[0].submit()
330
    resp.forms['listing-settings']['filter'] = 'done'
331
    resp = resp.forms['listing-settings'].submit()
332 332
    if pub.is_using_postgresql():
333 333
        assert resp.text.count('data-link') == 20
334 334
        resp = resp.click('Next Page')
......
361 361

  
362 362
    resp = app.get('/backoffice/management/form-title/')
363 363
    assert resp.text.count('data-link') == 9
364
    resp.forms[0]['filter'] = 'pending'
365
    resp = resp.forms[0].submit()
364
    resp.forms['listing-settings']['filter'] = 'pending'
365
    resp = resp.forms['listing-settings'].submit()
366 366
    assert resp.text.count('data-link') == 17
367 367

  
368 368
    # check status forced as endpoints are not part of the "actionable" list.
......
392 392

  
393 393
    resp = app.get('/backoffice/management/form-title/')
394 394
    assert resp.text.count('data-link') == 17
395
    resp.forms[0]['filter'] = 'pending'
396
    resp = resp.forms[0].submit()
395
    resp.forms['listing-settings']['filter'] = 'pending'
396
    resp = resp.forms['listing-settings'].submit()
397 397
    assert resp.text.count('data-link') == 17
398 398

  
399 399
    # mark status as an endpoint
......
403 403

  
404 404
    resp = app.get('/backoffice/management/form-title/')
405 405
    assert resp.text.count('data-link') == 9
406
    resp.forms[0]['filter'] = 'pending'
407
    resp = resp.forms[0].submit()
406
    resp.forms['listing-settings']['filter'] = 'pending'
407
    resp = resp.forms['listing-settings'].submit()
408 408
    assert resp.text.count('data-link') == 9
409 409

  
410 410

  
......
542 542
    app = login(get_app(pub))
543 543
    resp = app.get('/backoffice/management/form-title/')
544 544
    assert resp.text.count('</th>') == 8 # six columns
545
    resp.forms[0]['1'].checked = False
546
    assert not 'submission_channel' in resp.forms[0].fields
547
    assert 'last_update_time' in resp.forms[0].fields
548
    resp = resp.forms[0].submit()
545
    resp.forms['listing-settings']['1'].checked = False
546
    assert not 'submission_channel' in resp.forms['listing-settings'].fields
547
    assert 'last_update_time' in resp.forms['listing-settings'].fields
548
    resp = resp.forms['listing-settings'].submit()
549 549
    assert resp.text.count('</th>') == 7 # fixe columns
550 550
    assert resp.text.count('data-link') == 17 # 17 rows
551 551
    assert resp.text.count('FOO BAR') == 0 # no field 1 column
......
570 570
    app = login(get_app(pub))
571 571
    resp = app.get('/backoffice/management/form-title/')
572 572
    assert resp.text.count('</th>') == 8 # six columns
573
    resp.forms[0]['submission_channel'].checked = True
574
    resp = resp.forms[0].submit()
573
    resp.forms['listing-settings']['submission_channel'].checked = True
574
    resp = resp.forms['listing-settings'].submit()
575 575
    assert resp.text.count('</th>') == 9 # seven columns
576 576
    assert resp.text.count('data-link') == 17 # 17 rows
577 577
    assert resp.text.count('<td>Web</td>') == 17
......
640 640
    create_environment(pub)
641 641
    app = login(get_app(pub))
642 642
    resp = app.get('/backoffice/management/form-title/')
643
    assert resp.forms[0]['filter-status'].checked == True
644
    resp.forms[0]['filter-status'].checked = False
645
    resp.forms[0]['filter-2'].checked = True
646
    resp = resp.forms[0].submit()
643
    assert resp.forms['listing-settings']['filter-status'].checked == True
644
    resp.forms['listing-settings']['filter-status'].checked = False
645
    resp.forms['listing-settings']['filter-2'].checked = True
646
    resp = resp.forms['listing-settings'].submit()
647 647
    assert '<select name="filter">' not in resp.text
648 648

  
649
    resp.forms[0]['filter-2-value'] = 'baz'
650
    resp = resp.forms[0].submit()
649
    resp.forms['listing-settings']['filter-2-value'] = 'baz'
650
    resp = resp.forms['listing-settings'].submit()
651 651
    assert resp.text.count('<td>baz</td>') == 8
652 652
    assert resp.text.count('<td>foo</td>') == 0
653 653
    assert resp.text.count('<td>bar</td>') == 0
654 654

  
655
    resp.forms[0]['filter-start'].checked = True
656
    resp = resp.forms[0].submit()
657
    resp.forms[0]['filter-start-value'] = datetime.datetime(2015, 2, 1).strftime('%Y-%m-%d')
658
    resp = resp.forms[0].submit()
655
    resp.forms['listing-settings']['filter-start'].checked = True
656
    resp = resp.forms['listing-settings'].submit()
657
    resp.forms['listing-settings']['filter-start-value'] = datetime.datetime(2015, 2, 1).strftime('%Y-%m-%d')
658
    resp = resp.forms['listing-settings'].submit()
659 659
    assert resp.text.count('<td>baz</td>') == 0
660
    resp.forms[0]['filter-start-value'] = datetime.datetime(2014, 2, 1).strftime('%Y-%m-%d')
661
    resp = resp.forms[0].submit()
660
    resp.forms['listing-settings']['filter-start-value'] = datetime.datetime(2014, 2, 1).strftime('%Y-%m-%d')
661
    resp = resp.forms['listing-settings'].submit()
662 662
    assert resp.text.count('<td>baz</td>') == 8
663 663

  
664 664
    # check there's no crash on invalid date values
665
    resp.forms[0]['filter-start-value'] = 'whatever'
666
    resp = resp.forms[0].submit()
665
    resp.forms['listing-settings']['filter-start-value'] = 'whatever'
666
    resp = resp.forms['listing-settings'].submit()
667 667
    assert resp.text.count('<td>baz</td>') == 8
668 668

  
669 669
    # check two-digit years are handled correctly
670
    resp.forms[0]['filter-start-value'] = datetime.datetime(2014, 2, 1).strftime('%y-%m-%d')
671
    resp = resp.forms[0].submit()
670
    resp.forms['listing-settings']['filter-start-value'] = datetime.datetime(2014, 2, 1).strftime('%y-%m-%d')
671
    resp = resp.forms['listing-settings'].submit()
672 672
    assert resp.text.count('<td>baz</td>') == 8
673 673

  
674 674
    # check it's also ok for end filter
675
    resp.forms[0]['filter-end'].checked = True
676
    resp = resp.forms[0].submit()
677
    resp.forms[0]['filter-end-value'] = datetime.datetime(2014, 2, 2).strftime('%y-%m-%d')
678
    resp = resp.forms[0].submit()
675
    resp.forms['listing-settings']['filter-end'].checked = True
676
    resp = resp.forms['listing-settings'].submit()
677
    resp.forms['listing-settings']['filter-end-value'] = datetime.datetime(2014, 2, 2).strftime('%y-%m-%d')
678
    resp = resp.forms['listing-settings'].submit()
679 679
    assert resp.text.count('<td>baz</td>') == 0
680 680

  
681 681

  
......
1447 1447

  
1448 1448
    app = login(get_app(pub))
1449 1449
    resp = app.get('/backoffice/management/form-title/')
1450
    assert not 'id="multi-actions"' in resp.text
1450
    assert 'id="multi-actions"' in resp.text  # always there
1451 1451

  
1452 1452
    workflow = Workflow.get_default_workflow()
1453 1453
    workflow.id = '2'
......
1462 1462
    formdef.store()
1463 1463

  
1464 1464
    resp = app.get('/backoffice/management/form-title/')
1465
    assert not 'id="multi-actions"' in resp.text
1465
    assert 'id="multi-actions"' in resp.text
1466 1466

  
1467 1467
    trigger.roles = [x.id for x in Role.select() if x.name == 'foobar']
1468 1468
    workflow.store()
......
1550 1550
            assert formdata.status != 'wf-accepted'
1551 1551

  
1552 1552

  
1553
def test_backoffice_multi_actions_jump(pub):
1554
    create_superuser(pub)
1555
    create_environment(pub)
1556
    formdef = FormDef.get_by_urlname('form-title')
1557

  
1558
    app = login(get_app(pub))
1559
    resp = app.get('/backoffice/management/form-title/')
1560

  
1561
    workflow = Workflow.get_default_workflow()
1562
    workflow.id = '2'
1563
    workflow.store()
1564
    formdef.workflow_id = workflow.id
1565
    formdef.store()
1566

  
1567
    resp = app.get('/backoffice/management/form-title/')
1568
    assert 'select[]' not in resp.forms['multi-actions'].fields
1569

  
1570
    resp.forms['listing-settings']['filter'] = 'new'
1571
    resp = resp.forms['listing-settings'].submit()
1572
    assert 'select[]' not in resp.forms['multi-actions'].fields
1573

  
1574
    # add identifier to jumps
1575
    workflow.get_status('new').items[1].identifier = 'accept'
1576
    workflow.get_status('new').items[2].identifier = 'reject'
1577
    workflow.get_status('new').items[2].require_confirmation = True
1578
    workflow.store()
1579

  
1580
    resp = resp.forms['listing-settings'].submit()
1581
    assert 'select[]' in resp.forms['multi-actions'].fields
1582
    assert len(resp.pyquery.find('#multi-actions div.buttons button')) == 2
1583
    assert len(resp.pyquery.find('#multi-actions div.buttons button[data-ask-for-confirmation]')) == 1
1584

  
1585
    ids = []
1586
    for checkbox in resp.forms[0].fields['select[]'][1:6]:
1587
        ids.append(checkbox._value)
1588
        checkbox.checked = True
1589
    resp = resp.forms['multi-actions'].submit('button-action-st-accept')
1590
    assert '?job=' in resp.location
1591
    resp = resp.follow()
1592
    assert 'Executing task &quot;Accept&quot; on forms' in resp.text
1593
    assert '>completed<' in resp.text
1594
    for id in ids:
1595
        assert formdef.data_class().get(id).status == 'wf-accepted'
1596

  
1597

  
1553 1598
def test_backoffice_statistics_status_filter(pub):
1554 1599
    create_superuser(pub)
1555 1600
    create_environment(pub)
......
6386 6431
    resp.forms['listing-settings']['user-label'].checked = False
6387 6432
    resp = resp.forms['listing-settings'].submit()
6388 6433
    # filters
6389
    resp.forms[0]['filter-2'].checked = True
6434
    resp.forms['listing-settings']['filter-2'].checked = True
6390 6435
    resp = resp.forms['listing-settings'].submit()
6391 6436

  
6392 6437
    resp.forms['listing-settings']['filter-2-value'] = 'baz'
wcs/backoffice/management.py
1634 1634
    def listing_top_actions(self):
1635 1635
        return ''
1636 1636

  
1637
    def get_multi_actions(self, user):
1638
        # filter global manual actions to get those that can be run by the
1639
        # user, either because of actual roles, or because the action is
1640
        # accessible to functions.
1637
    def get_multi_actions(self, user, status_filter):
1641 1638
        global_actions = self.formdef.workflow.get_global_manual_actions()
1639

  
1640
        if status_filter not in ('open', 'waiting', 'done', 'all'):
1641
            # when the listing is filtered on a specific status, include
1642
            # manual jumps with identifiers
1643
            try:
1644
                status = self.formdef.workflow.get_status(status_filter)
1645
            except KeyError:
1646
                status = None
1647
            else:
1648
                global_actions.extend(status.get_status_manual_actions())
1649

  
1642 1650
        mass_actions = []
1643 1651
        for action_dict in global_actions:
1644
            action_dict['roles'] = [x for x in user.get_roles() if x in action_dict.get('roles') or []]
1652
            # filter actions to get those that can be run by the user,
1653
            # either because of actual roles, or because the action is
1654
            # accessible to functions.
1655
            if not logged_users_role().id in action_dict.get('roles') or []:
1656
                action_dict['roles'] = [x for x in user.get_roles() if x in action_dict.get('roles') or []]
1645 1657
            if action_dict['roles']:
1646 1658
                # action is accessible with user roles, remove mentions of functions
1647 1659
                action_dict['functions'] = []
1648 1660
            if action_dict['functions'] or action_dict['roles']:
1649 1661
                mass_actions.append(action_dict)
1662

  
1650 1663
        return mass_actions
1651 1664

  
1652 1665
    def _q_index(self):
......
1680 1693
        if get_request().get_query():
1681 1694
            qs = '?' + get_request().get_query()
1682 1695

  
1683
        multi_actions = self.get_multi_actions(get_request().user)
1696
        multi_actions = self.get_multi_actions(get_request().user,
1697
                status_filter=selected_filter)
1698
        multi_form = Form(id='multi-actions')
1699
        for action in multi_actions:
1700
            attrs = {}
1701
            if action.get('functions'):
1702
                for function in action.get('functions'):
1703
                    # dashes are replaced by underscores to prevent HTML5
1704
                    # normalization to CamelCase.
1705
                    attrs['data-visible_for_%s' % function.replace('-', '_')] = 'true'
1706
            else:
1707
                attrs['data-visible_for_all'] = 'true'
1708
            if getattr(action['action'], 'require_confirmation', False):
1709
                attrs['data-ask-for-confirmation'] = 'true'
1710
            multi_form.add_submit('button-action-%s' % action['action'].id, action['action'].name, attrs=attrs)
1684 1711
        if not get_request().form.get('ajax') == 'true':
1685
            multi_form = Form(id='multi-actions')
1686
            for action in multi_actions:
1687
                attrs = {}
1688
                if action.get('functions'):
1689
                    for function in action.get('functions'):
1690
                        # dashes are replaced by underscores to prevent HTML5
1691
                        # normalization to CamelCase.
1692
                        attrs['data-visible_for_%s' % function.replace('-', '_')] = 'true'
1693
                else:
1694
                    attrs['data-visible_for_all'] = 'true'
1695
                multi_form.add_submit('button-action-%s' % action['action'].id, action['action'].name, attrs=attrs)
1696 1712
            if multi_form.is_submitted() and get_request().form.get('select[]'):
1697 1713
                for action in multi_actions:
1698 1714
                    if multi_form.get_submit() == 'button-action-%s' % action['action'].id:
......
1707 1723
                        limit=int(limit), offset=int(offset), query=query,
1708 1724
                        order_by=order_by, criterias=criterias,
1709 1725
                        include_checkboxes=bool(multi_actions))
1726

  
1710 1727
        if get_response().status_code == 302:
1711 1728
            # catch early redirect
1712 1729
            return table
1713 1730

  
1731
        multi_form.widgets.append(HtmlWidget(table))
1732
        if not multi_actions:
1733
            multi_form.widgets.append(HtmlWidget('<div class="buttons"></div>'))
1714 1734
        if get_request().form.get('ajax') == 'true':
1715 1735
            get_response().filter = {'raw': True}
1716
            return table
1736
            return multi_form.render()
1717 1737

  
1718 1738
        view_name = self.view.title if self.view else _('Listing')
1719 1739
        html_top('management', '%s - %s' % (view_name, self.formdef.name))
......
1723 1743
        r += get_session().display_message()
1724 1744
        r += self.listing_top_actions()
1725 1745
        r += htmltext('</div>')
1726
        if multi_actions:
1727
            multi_form.widgets.append(HtmlWidget(table))
1728
            r += multi_form.render()
1729
        else:
1730
            r += table
1746
        r += multi_form.render()
1731 1747

  
1732 1748
        get_response().filter['sidebar'] = self.get_formdata_sidebar(qs) + \
1733 1749
                self.get_fields_sidebar(selected_filter, fields, limit=limit,
......
1753 1769
                    publisher.substitutions.feed(publisher)
1754 1770
                    publisher.substitutions.feed(self.formdef)
1755 1771
                    publisher.substitutions.feed(formdata)
1756
                    formdata.perform_global_action(self.action['action'].id, self.user)
1772
                    if getattr(self.action['action'], 'status_action', False):
1773
                        # manual jump action
1774
                        from wcs.wf.jump import jump_and_perform
1775
                        jump_and_perform(formdata, self.action['action'].action)
1776
                    else:
1777
                        # global action
1778
                        formdata.perform_global_action(self.action['action'].id, self.user)
1757 1779

  
1758 1780
        item_ids = get_request().form['select[]']
1759 1781
        if '_all' in item_ids:
wcs/qommon/static/js/wcs.listing.js
151 151
    beforeSend: function() { $('#more-user-links, #listing, #statistics').addClass('activity'); },
152 152
    complete: function() { $('#more-user-links, #listing, #statistics').removeClass('activity'); },
153 153
    success: function(html) {
154
      $('#page-links').remove();
155
      $('#listing').replaceWith(html);
156
      $('#statistics').replaceWith(html);
154
      var $html = $(html);
155
      var $listing = $html;
156
      console.log('hello', $html.find('div.buttons'));
157
      if ($listing.is('form')) {
158
        // mass action
159
        $listing = $listing.find('#listing');
160
        $('#multi-actions div.buttons').replaceWith($html.find('div.buttons'));
161
        $('#listing').replaceWith($listing);
162
        $('#page-links').replaceWith($html.find('#page-links'));
163
      } else {
164
        $('#page-links').remove();
165
        $('#listing').replaceWith($listing);
166
      }
167
      $('#statistics').replaceWith($html);
157 168
      if (typeof(wcs_draw_graphs) !== 'undefined') {
158 169
        wcs_draw_graphs();
159 170
      }
wcs/workflows.py
468 468
                actions.append({'action': action, 'roles': roles, 'functions': functions})
469 469
        return actions
470 470

  
471

  
472 471
    def get_global_actions_for_user(self, formdata, user):
473 472
        if not user:
474 473
            return []
......
1634 1633
                waitpoint = item.waitpoint or waitpoint
1635 1634
        return bool(endpoint or waitpoint)
1636 1635

  
1636
    def get_status_manual_actions(self):
1637
        actions = []
1638
        status_id = self.id
1639

  
1640
        class StatusAction:
1641
            def __init__(self, action):
1642
                self.id = 'st-%s' % action.identifier
1643
                self.status_id = status_id
1644
                self.action_id = action.identifier
1645
                self.name = action.get_label()
1646
                self.status_action = True
1647
                self.require_confirmation = action.require_confirmation
1648
                self.action = action
1649

  
1650
        for action in self.items or []:
1651
            if not isinstance(action, ChoiceWorkflowStatusItem):
1652
                continue
1653
            if not action.identifier:
1654
                continue
1655
            roles = action.by
1656
            functions = [x for x in roles if x in self.parent.roles]
1657
            roles = [x for x in roles if x not in self.parent.roles]
1658
            if functions or roles:
1659
                actions.append({'action': StatusAction(action), 'roles': roles, 'functions': functions})
1660
        return actions
1661

  
1637 1662
    def get_contrast_color(self):
1638 1663
        colour = self.colour or 'ffffff'
1639 1664
        return misc.get_foreground_colour(colour)
1640
-