0001-backoffice-add-multi-action-support-for-status-manua.patch
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 "Accept" 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 |
- |