Projet

Général

Profil

0001-general-add-handling-of-criticality-levels-10134.patch

Frédéric Péters, 08 mars 2016 11:43

Télécharger (42,4 ko)

Voir les différences:

Subject: [PATCH] general: add handling of criticality levels (#10134)

 tests/test_admin_pages.py           |  62 +++++++++++++++++
 tests/test_backoffice_pages.py      | 122 +++++++++++++++++++++++++++++++++-
 tests/test_workflow_import.py       |  15 ++++-
 tests/test_workflows.py             |  49 +++++++++++++-
 tests/utilities.py                  |   1 +
 wcs/admin/workflows.py              | 129 ++++++++++++++++++++++++++++++++++--
 wcs/backoffice/management.py        |  54 ++++++++++++++-
 wcs/formdata.py                     |   3 +
 wcs/forms/backoffice.py             |  24 ++++++-
 wcs/forms/common.py                 |  14 ----
 wcs/qommon/static/css/dc2/admin.css |  35 ++++++++++
 wcs/qommon/static/js/biglist.js     |   1 +
 wcs/sql.py                          |  16 +++--
 wcs/wf/criticality.py               |  84 +++++++++++++++++++++++
 wcs/workflows.py                    |  41 ++++++++++++
 15 files changed, 621 insertions(+), 29 deletions(-)
 create mode 100644 wcs/wf/criticality.py
tests/test_admin_pages.py
1378 1378
    assert Workflow.count() == 0
1379 1379

  
1380 1380
def test_workflows_add_all_actions(pub):
1381
    create_superuser(pub)
1382

  
1381 1383
    Workflow.wipe()
1382 1384
    workflow = Workflow(name='foo')
1383 1385
    workflow.add_status(name='baz')
......
1705 1707
    resp = resp.form.submit('submit')
1706 1708
    assert Workflow.get(workflow.id).global_actions[0].triggers[0].roles == ['_receiver']
1707 1709

  
1710
def test_workflows_criticality_levels(pub):
1711
    create_superuser(pub)
1712
    create_role()
1713

  
1714
    Workflow.wipe()
1715
    workflow = Workflow(name='foo')
1716
    workflow.store()
1717

  
1718
    app = login(get_app(pub))
1719
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
1720
    resp = resp.click('add criticality level')
1721
    resp = resp.forms[0].submit('cancel')
1722
    assert not Workflow.get(workflow.id).criticality_levels
1723

  
1724
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
1725
    resp = resp.click('add criticality level')
1726
    resp.forms[0]['name'] = 'vigilance'
1727
    resp = resp.forms[0].submit('submit')
1728
    assert len(Workflow.get(workflow.id).criticality_levels) == 1
1729
    assert Workflow.get(workflow.id).criticality_levels[0].name == 'vigilance'
1730

  
1731
    # test rename
1732
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
1733
    resp = resp.click('vigilance')
1734
    resp = resp.forms[0].submit('cancel')
1735

  
1736
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
1737
    resp = resp.click('vigilance')
1738
    resp.forms[0]['name'] = 'Vigilance'
1739
    resp = resp.forms[0].submit('submit')
1740
    assert len(Workflow.get(workflow.id).criticality_levels) == 1
1741
    assert Workflow.get(workflow.id).criticality_levels[0].name == 'Vigilance'
1742

  
1743
    # add a second level
1744
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
1745
    resp = resp.click('add criticality level')
1746
    resp.forms[0]['name'] = 'Alerte attentat'
1747
    resp = resp.forms[0].submit('submit')
1748
    assert len(Workflow.get(workflow.id).criticality_levels) == 2
1749
    assert Workflow.get(workflow.id).criticality_levels[0].name == 'Vigilance'
1750
    assert Workflow.get(workflow.id).criticality_levels[1].name == 'Alerte attentat'
1751

  
1752
    # test reorder
1753
    level1_id = Workflow.get(workflow.id).criticality_levels[0].id
1754
    level2_id = Workflow.get(workflow.id).criticality_levels[1].id
1755
    app.get('/backoffice/workflows/%s/update_criticality_levels_order?order=%s;%s;' % (
1756
            workflow.id, level1_id, level2_id))
1757
    assert Workflow.get(workflow.id).criticality_levels[0].id == level1_id
1758
    assert Workflow.get(workflow.id).criticality_levels[1].id == level2_id
1759
    app.get('/backoffice/workflows/%s/update_criticality_levels_order?order=%s;%s;' % (
1760
            workflow.id, level2_id, level1_id))
1761
    assert Workflow.get(workflow.id).criticality_levels[0].id == level2_id
1762
    assert Workflow.get(workflow.id).criticality_levels[1].id == level1_id
1763

  
1764
    # test removal
1765
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
1766
    resp = resp.click('Vigilance')
1767
    resp = resp.forms[0].submit('delete-level')
1768
    assert len(Workflow.get(workflow.id).criticality_levels) == 1
1769

  
1708 1770
def test_workflows_wscall_label(pub):
1709 1771
    create_superuser(pub)
1710 1772
    create_role()
tests/test_backoffice_pages.py
18 18
from wcs.roles import Role
19 19
from wcs.workflows import (Workflow, CommentableWorkflowStatusItem,
20 20
        ChoiceWorkflowStatusItem, EditableWorkflowStatusItem,
21
        JumpOnSubmitWorkflowStatusItem)
21
        JumpOnSubmitWorkflowStatusItem, WorkflowCriticalityLevel)
22 22
from wcs.wf.dispatch import DispatchWorkflowStatusItem
23 23
from wcs.wf.wscall import WebserviceCallStatusItem
24 24
from wcs.wf.register_comment import RegisterCommenterWorkflowStatusItem
......
1835 1835
    assert resp.form['f2'].value == 'XXX'
1836 1836
    assert 'Original form' in resp.body
1837 1837
    assert formdata.get_url(backoffice=True) in resp.body
1838

  
1839
def test_backoffice_criticality_in_formdef_listing(pub):
1840
    if not pub.is_using_postgresql():
1841
        pytest.skip('this requires SQL')
1842
        return
1843
    user = create_user(pub)
1844
    create_environment(pub)
1845

  
1846
    wf = Workflow.get_default_workflow()
1847
    wf.id = '2'
1848
    wf.criticality_levels = [
1849
        WorkflowCriticalityLevel(name='green'),
1850
        WorkflowCriticalityLevel(name='yellow'),
1851
        WorkflowCriticalityLevel(name='red'),
1852
    ]
1853
    wf.store()
1854
    formdef = FormDef.get_by_urlname('form-title')
1855
    formdef.workflow_id = wf.id
1856
    formdef.store()
1857

  
1858
    formdata1, formdata2, formdata3, formdata4 = [
1859
            x for x in formdef.data_class().select() if x.status == 'wf-new'][:4]
1860

  
1861
    formdata1.criticality_level = 1
1862
    formdata1.store()
1863
    formdata1_str = '>%s<' % formdata1.get_display_id()
1864
    formdata2.criticality_level = 2
1865
    formdata2.store()
1866
    formdata2_str = '>%s<' % formdata2.get_display_id()
1867
    formdata3.criticality_level = 2
1868
    formdata3.store()
1869
    formdata3_str = '>%s<' % formdata3.get_display_id()
1870
    formdata4_str = '>%s<' % formdata4.get_display_id()
1871

  
1872
    app = login(get_app(pub))
1873
    resp = app.get('/backoffice/management/form-title/?order_by=-criticality_level&limit=100')
1874
    assert resp.body.index(formdata1_str) > resp.body.index(formdata2_str)
1875
    assert resp.body.index(formdata1_str) > resp.body.index(formdata3_str)
1876
    assert resp.body.index(formdata1_str) < resp.body.index(formdata4_str)
1877

  
1878
    resp = app.get('/backoffice/management/form-title/?order_by=criticality_level&limit=100')
1879
    assert resp.body.index(formdata1_str) < resp.body.index(formdata2_str)
1880
    assert resp.body.index(formdata1_str) < resp.body.index(formdata3_str)
1881
    assert resp.body.index(formdata1_str) > resp.body.index(formdata4_str)
1882

  
1883
def test_backoffice_criticality_in_global_listing(pub):
1884
    if not pub.is_using_postgresql():
1885
        pytest.skip('this requires SQL')
1886
        return
1887

  
1888
    user = create_user(pub)
1889
    create_environment(pub)
1890

  
1891
    wf = Workflow.get_default_workflow()
1892
    wf.id = '2'
1893
    wf.criticality_levels = [
1894
        WorkflowCriticalityLevel(name='green'),
1895
        WorkflowCriticalityLevel(name='yellow'),
1896
        WorkflowCriticalityLevel(name='red'),
1897
    ]
1898
    wf.store()
1899
    formdef = FormDef.get_by_urlname('form-title')
1900
    formdef.workflow_id = wf.id
1901
    formdef.store()
1902

  
1903
    formdata1, formdata2, formdata3, formdata4 = [
1904
            x for x in formdef.data_class().select() if x.status == 'wf-new'][:4]
1905

  
1906
    formdata1.criticality_level = 1
1907
    formdata1.store()
1908
    formdata1_str = '>%s<' % formdata1.get_display_id()
1909
    formdata2.criticality_level = 2
1910
    formdata2.store()
1911
    formdata2_str = '>%s<' % formdata2.get_display_id()
1912
    formdata3.criticality_level = 2
1913
    formdata3.store()
1914
    formdata3_str = '>%s<' % formdata3.get_display_id()
1915
    formdata4_str = '>%s<' % formdata4.get_display_id()
1916

  
1917
    app = login(get_app(pub))
1918
    resp = app.get('/backoffice/management/listing?order_by=-criticality_level&limit=100')
1919
    assert resp.body.index(formdata1_str) > resp.body.index(formdata2_str)
1920
    assert resp.body.index(formdata1_str) > resp.body.index(formdata3_str)
1921
    assert resp.body.index(formdata1_str) < resp.body.index(formdata4_str)
1922

  
1923
    resp = app.get('/backoffice/management/listing?order_by=criticality_level&limit=100')
1924
    assert resp.body.index(formdata1_str) < resp.body.index(formdata2_str)
1925
    assert resp.body.index(formdata1_str) < resp.body.index(formdata3_str)
1926
    assert resp.body.index(formdata1_str) > resp.body.index(formdata4_str)
1927

  
1928
def test_backoffice_criticality_formdata_view(pub):
1929
    user = create_user(pub)
1930
    create_environment(pub)
1931

  
1932
    wf = Workflow.get_default_workflow()
1933
    wf.id = '2'
1934
    wf.criticality_levels = [
1935
        WorkflowCriticalityLevel(name='green'),
1936
        WorkflowCriticalityLevel(name='yellow'),
1937
        WorkflowCriticalityLevel(name='red'),
1938
    ]
1939
    wf.store()
1940
    formdef = FormDef.get_by_urlname('form-title')
1941
    formdef.workflow_id = wf.id
1942
    formdef.store()
1943

  
1944
    formdef = FormDef.get_by_urlname('form-title')
1945
    formdata = [x for x in formdef.data_class().select() if x.status == 'wf-new'][0]
1946

  
1947
    formdata.criticality_level = 1
1948
    formdata.store()
1949

  
1950
    app = login(get_app(pub))
1951
    resp = app.get(formdata.get_url(backoffice=True))
1952
    assert 'Criticality Level: yellow' in resp.body
1953

  
1954
    formdata.criticality_level = 2
1955
    formdata.store()
1956
    resp = app.get(formdata.get_url(backoffice=True))
1957
    assert 'Criticality Level: red' in resp.body
tests/test_workflow_import.py
6 6
from quixote import cleanup
7 7
from wcs import publisher
8 8

  
9
from wcs.workflows import Workflow, CommentableWorkflowStatusItem
9
from wcs.workflows import (Workflow, CommentableWorkflowStatusItem,
10
        WorkflowCriticalityLevel)
10 11
from wcs.wf.wscall import WebserviceCallStatusItem
11 12
from wcs.wf.dispatch import DispatchWorkflowStatusItem
12 13
from wcs.wf.register_comment import RegisterCommenterWorkflowStatusItem
......
426 427
        assert role_id in wf2.possible_status[0].items[0].to
427 428

  
428 429
    wf2 = assert_import_export_works(wf, include_id=True)
430

  
431
def test_criticality_level():
432
    wf = Workflow(name='criticality level')
433
    wf.criticality_levels = [
434
        WorkflowCriticalityLevel(name='green'),
435
        WorkflowCriticalityLevel(name='yellow'),
436
        WorkflowCriticalityLevel(name='red', colour='FF0000'),
437
    ]
438

  
439
    wf2 = assert_import_export_works(wf)
440
    assert wf2.criticality_levels[0].name == 'green'
441
    assert wf2.criticality_levels[1].name == 'yellow'
tests/test_workflows.py
14 14
from wcs.workflows import (Workflow, WorkflowStatusItem,
15 15
        SendmailWorkflowStatusItem, SendSMSWorkflowStatusItem,
16 16
        DisplayMessageWorkflowStatusItem,
17
        AbortActionException)
17
        AbortActionException, WorkflowCriticalityLevel)
18 18
from wcs.wf.anonymise import AnonymiseWorkflowStatusItem
19
from wcs.wf.criticality import ModifyCriticalityWorkflowStatusItem, MODE_INC, MODE_DEC, MODE_SET
19 20
from wcs.wf.dispatch import DispatchWorkflowStatusItem
20 21
from wcs.wf.form import FormWorkflowStatusItem, WorkflowFormFieldsFormDef
21 22
from wcs.wf.jump import JumpWorkflowStatusItem, _apply_timeouts
......
943 944
    assert emails.get('Foobar')
944 945
    assert set(emails.get('Foobar')['email_rcpt']) == set(
945 946
            ['foo@localhost', 'bar@localhost', 'baz@localhost'])
947

  
948
def test_criticality(pub):
949
    FormDef.wipe()
950

  
951
    workflow = Workflow(name='criticality')
952
    workflow.criticality_levels = [
953
        WorkflowCriticalityLevel(name='green'),
954
        WorkflowCriticalityLevel(name='yellow'),
955
        WorkflowCriticalityLevel(name='red'),
956
    ]
957
    workflow.store()
958

  
959
    formdef = FormDef()
960
    formdef.name = 'baz'
961
    formdef.workflow_id = workflow.id
962
    formdef.store()
963

  
964
    item = ModifyCriticalityWorkflowStatusItem()
965

  
966
    formdata = formdef.data_class()()
967
    item.perform(formdata)
968
    assert not formdata.criticality_level
969

  
970
    item.mode = MODE_INC
971
    item.perform(formdata)
972
    assert formdata.criticality_level == 1
973
    item.perform(formdata)
974
    assert formdata.criticality_level == 2
975
    item.perform(formdata)
976
    assert formdata.criticality_level == 2
977

  
978
    item.mode = MODE_DEC
979
    item.perform(formdata)
980
    assert formdata.criticality_level == 1
981
    item.perform(formdata)
982
    assert formdata.criticality_level == 0
983
    item.perform(formdata)
984
    assert formdata.criticality_level == 0
985

  
986
    item.mode = MODE_SET
987
    item.absolute_value = 2
988
    item.perform(formdata)
989
    assert formdata.criticality_level == 2
990
    item.absolute_value = 0
991
    item.perform(formdata)
992
    assert formdata.criticality_level == 0
tests/utilities.py
99 99
    fd.write('formdef-captcha-option = true\n')
100 100
    fd.write('workflow-resubmit-action = true\n')
101 101
    fd.write('workflow-global-actions = true\n')
102
    fd.write('workflow-criticality-levels = true\n')
102 103
    if sql_mode:
103 104
        fd.write('postgresql = true\n')
104 105
        conn = psycopg2.connect(user=os.environ['USER'])
wcs/admin/workflows.py
949 949
        return redirect('..')
950 950

  
951 951

  
952
class CriticalityLevelsDirectory(Directory):
953
    _q_exports = ['', 'new']
954

  
955
    def __init__(self, workflow):
956
        self.workflow = workflow
957

  
958
    def _q_traverse(self, path):
959
        get_response().breadcrumb.append(
960
                ('criticality-levels/', _('Criticality Levels')))
961
        return Directory._q_traverse(self, path)
962

  
963
    def new(self):
964
        currentlevels = self.workflow.criticality_levels or []
965
        default_colours = ['FFFFFF', 'FFFF00', 'FF9900', 'FF6600', 'FF0000']
966
        try:
967
            default_colour = default_colours[len(currentlevels)]
968
        except IndexError:
969
            default_colour = '000000'
970
        form = Form(enctype='multipart/form-data')
971
        form.add(StringWidget, 'name', title=_('Name'), required=True, size=50)
972
        form.add(ColourWidget, 'colour', title=_('Colour'), required=False,
973
                value=default_colour)
974
        form.add_submit('submit', _('Add'))
975
        form.add_submit('cancel', _('Cancel'))
976
        if form.get_widget('cancel').parse():
977
            return redirect('..')
978

  
979
        if form.is_submitted() and not form.has_errors():
980
            if not self.workflow.criticality_levels:
981
                self.workflow.criticality_levels = []
982
            level = WorkflowCriticalityLevel()
983
            level.name = form.get_widget('name').parse()
984
            level.colour = form.get_widget('colour').parse()
985
            self.workflow.criticality_levels.append(level)
986
            self.workflow.store()
987
            return redirect('..')
988

  
989
        get_response().breadcrumb.append(('new', _('New Criticality Level')))
990
        html_top('workflows', title=_('New Criticality level'))
991
        r = TemplateIO(html=True)
992
        r += htmltext('<h2>%s</h2>') % _('New Criticality Level')
993
        r += form.render()
994
        return r.getvalue()
995

  
996
    def _q_lookup(self, component):
997
        for level in (self.workflow.criticality_levels or []):
998
            if level.id == component:
999
                break
1000
        else:
1001
            raise errors.TraversalError()
1002

  
1003
        form = Form(enctype='multipart/form-data')
1004
        form.add(StringWidget, 'name', title=_('Name'), required=True, size=50,
1005
                value=level.name)
1006
        form.add(ColourWidget, 'colour', title=_('Colour'), required=False,
1007
                value=level.colour)
1008
        form.add_submit('submit', _('Submit'))
1009
        form.add_submit('cancel', _('Cancel'))
1010
        form.add_submit('delete-level', _('Delete'))
1011

  
1012
        if form.get_widget('cancel').parse():
1013
            return redirect('..')
1014

  
1015
        if form.get_submit() == 'delete-level':
1016
            self.workflow.criticality_levels.remove(level)
1017
            self.workflow.store()
1018
            return redirect('..')
1019

  
1020
        if form.is_submitted() and not form.has_errors():
1021
            level.name = form.get_widget('name').parse()
1022
            level.colour = form.get_widget('colour').parse()
1023
            self.workflow.store()
1024
            return redirect('..')
1025

  
1026
        get_response().breadcrumb.append(('new', _('Edit Criticality Level')))
1027
        html_top('workflows', title=_('Edit Criticality Level'))
1028
        r = TemplateIO(html=True)
1029
        r += htmltext('<h2>%s</h2>') % _('Edit Criticality Level')
1030
        r += form.render()
1031
        return r.getvalue()
1032

  
1033
    def _q_index(self):
1034
        return redirect('..')
1035

  
1036

  
952 1037
class GlobalActionPage(WorkflowStatusPage):
953 1038
    _q_exports = ['', 'new', 'delete', 'newitem', ('items', 'items_dir'),
954 1039
                  'edit', ('triggers', 'triggers_dir'),
......
1139 1224
class WorkflowPage(Directory):
1140 1225
    _q_exports = ['', 'edit', 'delete', 'newstatus', ('status', 'status_dir'), 'update_order',
1141 1226
            'duplicate', 'export', 'svg', ('variables', 'variables_dir'),
1142
            'update_actions_order',
1143
            ('functions', 'functions_dir'), ('global-actions', 'global_actions_dir')]
1227
            'update_actions_order', 'update_criticality_levels_order',
1228
            ('functions', 'functions_dir'), ('global-actions', 'global_actions_dir'),
1229
            ('criticality-levels', 'criticality_levels_dir'),
1230
            ]
1144 1231

  
1145 1232
    def __init__(self, component, html_top):
1146 1233
        try:
......
1153 1240
        self.variables_dir = VariablesDirectory(self.workflow)
1154 1241
        self.functions_dir = FunctionsDirectory(self.workflow)
1155 1242
        self.global_actions_dir = GlobalActionsDirectory(self.workflow, html_top)
1243
        self.criticality_levels_dir = CriticalityLevelsDirectory(self.workflow)
1156 1244
        get_response().breadcrumb.append((component + '/', self.workflow.name))
1157 1245

  
1158 1246
    def _q_index(self):
1159 1247
        self.html_top(title = _('Workflow - %s') % self.workflow.name)
1160 1248
        r = TemplateIO(html=True)
1161
        get_response().add_javascript(['jquery.js', 'jquery-ui.js', 'biglist.js', 'svg-pan-zoom.js'])
1249
        get_response().add_javascript(['jquery.js', 'jquery-ui.js', 'biglist.js',
1250
            'svg-pan-zoom.js', 'jquery.colourpicker.js'])
1162 1251
        get_response().add_javascript_code('$(function () { svgPanZoom("svg", {controlIconsEnabled: true}); });')
1163 1252
        r += htmltext('<h2>%s - ') % _('Workflow')
1164 1253
        r += htmltext('%s') % self.workflow.name
......
1257 1346
                r += htmltext('<ul id="status-list" class="biglist sortable" '
1258 1347
                              'data-order-function="update_actions_order">')
1259 1348
            else:
1260
                r += htmltext('<ul id="status-list" class="biglist">')
1349
                r += htmltext('<ul class="biglist">')
1261 1350

  
1262 1351
            for action in (self.workflow.global_actions or []):
1263 1352
                r += htmltext('<li class="biglistitem" id="itemId_%s">' % action.id)
......
1270 1359
            r += htmltext('</ul>')
1271 1360
            r += htmltext('</div>')
1272 1361

  
1362
        if not str(self.workflow.id).startswith('_') and get_publisher().has_site_option('workflow-criticality-levels'):
1363
            r += htmltext('<div class="bo-block">')
1364
            r += htmltext('<h3>%s') % _('Criticality Levels')
1365
            if not str(self.workflow.id).startswith('_'):
1366
                r += htmltext(' <span class="change">'
1367
                              '(<a rel="popup" href="criticality-levels/new">'
1368
                              '%s</a>)</span>') % _('add criticality level')
1369
            r += htmltext('</h3>')
1370
            r += htmltext('<ul class="biglist sortable criticality-levels" '
1371
                              'data-order-function="update_criticality_levels_order">')
1372
            for level in (self.workflow.criticality_levels or []):
1373
                style = ''
1374
                if level.colour:
1375
                    style = 'style="border-left-color: #%s"' % level.colour
1376
                r += htmltext('<li class="biglistitem" id="itemId_%s" %s>' % (level.id, style))
1377
                if not str(self.workflow.id).startswith('_'):
1378
                    r += htmltext('<a rel="popup" href="criticality-levels/%s">%s</a>') % (
1379
                            level.id, level.name)
1380
                else:
1381
                    r += htmltext('<a>%s</a>') % level.name
1382
                r += htmltext('</li>')
1383
            r += htmltext('</ul>')
1384
            r += htmltext('</div>')
1385

  
1273 1386
        r += htmltext('</div>') # .splitcontent-right
1274 1387

  
1275 1388
        r += htmltext('<br style="clear:both;"/>')
......
1355 1468
        self.workflow.store()
1356 1469
        return 'ok'
1357 1470

  
1471
    def update_criticality_levels_order(self):
1472
        request = get_request()
1473
        new_order = request.form['order'].strip(';').split(';')
1474
        self.workflow.criticality_levels = [
1475
                [x for x in self.workflow.criticality_levels if x.id == y][0] for y in new_order]
1476
        self.workflow.store()
1477
        return 'ok'
1478

  
1358 1479
    def newstatus(self):
1359 1480
        form = Form(enctype='multipart/form-data', action = 'newstatus')
1360 1481
        form.add(StringWidget, 'name', title = _('Name'), size = 50)
wcs/backoffice/management.py
688 688
                order_by=order_by, limit=limit, offset=offset)
689 689
        include_submission_channel = bool(
690 690
                get_publisher().get_site_option('welco_url', 'variables'))
691
        include_criticality_level = get_publisher().has_site_option('workflow-criticality-levels')
691 692

  
692 693
        r = TemplateIO(html=True)
693 694
        r += htmltext('<table id="listing" class="main">')
694 695
        r += htmltext('<thead>')
695
        r += htmltext('<th></th>') # lock
696
        if include_criticality_level:
697
            r += htmltext('<th data-field-sort-key="criticality_level"><span></span></th>')
698
        else:
699
            r += htmltext('<th></th>') # lock
696 700
        if include_submission_channel:
697 701
            r += htmltext('<th data-field-sort-key="submission_channel"><span>%s</span></th>') % _('Channel')
698 702
        r += htmltext('<th data-field-sort-key="formdef_name"><span>%s</span></th>') % _('Form')
......
713 717
            object_key = 'formdata-%s-%s' % (formdata.formdef.url_name, formdata.id)
714 718
            if object_key in visited_objects:
715 719
                classes.append('advisory-lock')
720
            style = ''
721
            if include_criticality_level:
722
                try:
723
                    level = formdata.formdef.workflow.criticality_levels[formdata.criticality_level]
724
                except (IndexError, TypeError):
725
                    pass
726
                else:
727
                    classes.append('criticality-level')
728
                    style = 'style="border-left-color: #%s;"' % level.colour
716 729
            r += htmltext('<tr class="%s" data-link="%s">' % (
717 730
                    ' '.join(classes),
718 731
                    formdata.get_url(backoffice=True)))
719
            r += htmltext('<td></td>') # lock
732
            r += htmltext('<td %s></td>' % style) # lock
720 733
            if include_submission_channel:
721 734
                r += htmltext('<td>%s</td>') % formdata.get_submission_channel_label()
722 735
            r += htmltext('<td>%s</td>') % formdata.formdef.name
......
1642 1655
        formdata = self.filled
1643 1656

  
1644 1657
        r = TemplateIO(html=True)
1658

  
1659
        if formdata.receipt_time:
1660
            r += htmltext('<div class="extra-context">')
1661
            r += htmltext('<h3>%s</h3>') % _('General Information')
1662
            r += htmltext('<p>')
1663
            tm = misc.localstrftime(formdata.receipt_time)
1664
            r += _('The form has been recorded on %(date)s with the number %(number)s.') % {
1665
                    'date': tm, 'number': formdata.get_display_id()}
1666
            r += htmltext('</p>')
1667
            try:
1668
                status_colour = formdata.get_status().colour
1669
            except AttributeError:
1670
                status_colour = 'ffffff'
1671
            fg_colour = misc.get_foreground_colour(status_colour)
1672

  
1673
            r += htmltext('<p class="current-status"><span class="item" style="background: #%s; color: %s;"></span>' %
1674
                    (status_colour, fg_colour))
1675
            r += htmltext('<span>%s %s</span></p>') % (_('Status:'), formdata.get_status_label())
1676
            if formdata.formdef.workflow.criticality_levels:
1677
                try:
1678
                    level = formdata.formdef.workflow.criticality_levels[formdata.criticality_level]
1679
                except IndexError:
1680
                    pass
1681
                else:
1682
                    r += htmltext('<p class="current-level">')
1683
                    if level.colour:
1684
                        r += htmltext('<span class="item" style="background: #%s;"></span>' % level.colour)
1685
                    r += htmltext('<span>%s %s</span></p>') % (_('Criticality Level:'), level.name)
1686

  
1687
            if formdata.anonymised:
1688
                r += htmltext('<div class="infonotice">')
1689
                r += htmltext(_('This form has been anonymised on %(date)s.')) % {
1690
                        'date': formdata.anonymised.strftime(misc.date_format())}
1691
                r += htmltext('</div>')
1692

  
1693
            r += htmltext('</div>')
1694

  
1645 1695
        if formdata.submission_context or formdata.submission_channel:
1646 1696
            extra_context = formdata.submission_context or {}
1647 1697
            r += htmltext('<div class="extra-context">')
wcs/formdata.py
171 171
    backoffice_submission = False
172 172
    submission_context = None
173 173
    submission_channel = None
174
    criticality_level = 0
174 175

  
175 176
    workflow_data = None
176 177
    workflow_roles = None
......
408 409
                'form_url': self.get_url(),
409 410
                'form_url_backoffice': self.get_url(backoffice=True),
410 411
                'form_uri': '%s/%s/' % (self.formdef.url_name, self.id),
412
                'form_criticality_level': self.criticality_level,
411 413
            })
412 414

  
413 415
        d['form_status'] = self.get_status_label()
......
641 643
                'id': data['display_id']}
642 644
        data['receipt_time'] = self.receipt_time
643 645
        data['last_update_time'] = self.last_update_time
646
        data['criticality_level'] = self.criticality_level
644 647
        data['url'] = self.get_url()
645 648

  
646 649
        try:
wcs/forms/backoffice.py
75 75
            r += htmltext('<col />')
76 76
        r += htmltext('</colgroup>')
77 77

  
78
        include_criticality_level = get_publisher().has_site_option('workflow-criticality-levels')
78 79
        r += htmltext('<thead><tr>')
79
        r += htmltext('<td></td>') # lock
80
        if include_criticality_level:
81
            r += htmltext('<th style="width: 4ex;" data-field-sort-key="criticality_level"><span></span>')
82
        else:
83
            r += htmltext('<td></td>') # lock
80 84
        for f in fields:
81 85
            field_sort_key = None
82 86
            if getattr(f, 'fake', False):
......
209 213
            url_action = ''
210 214
        root_url = get_publisher().get_root_url()
211 215
        visited_objects = get_publisher().get_visited_objects(exclude_user=get_session().user)
216
        include_criticality_level = get_publisher().has_site_option('workflow-criticality-levels')
217
        if include_criticality_level:
218
            include_criticality_level = bool(self.formdef.workflow.criticality_levels)
212 219
        for i, filled in enumerate(items):
213 220
            classes = ['status-%s-%s' % (filled.formdef.workflow.id, filled.status)]
214 221
            if i%2:
......
220 227
            if object_key in visited_objects:
221 228
                classes.append('advisory-lock')
222 229

  
230
            style = ''
231
            if include_criticality_level:
232
                try:
233
                    level = filled.formdef.workflow.criticality_levels[filled.criticality_level]
234
                except (IndexError, TypeError):
235
                    style = ''
236
                else:
237
                    classes.append('criticality-level')
238
                    style = ' style="border-color: #%s;"' % level.colour
239

  
223 240
            link = str(filled.id) + '/'
224 241
            data = ' data-link="%s"' % link
225 242
            if filled.anonymised:
226 243
                data += ' data-anonymised="true"'
227 244
            r += htmltext('<tr class="%s"%s>' % (' '.join(classes), data))
228
            r += htmltext('<td></td>') # lock
245
            if include_criticality_level:
246
                r += htmltext('<td %s></td>' % style) # criticality_level
247
            else:
248
                r += htmltext('<td></td>') # lock
229 249
            for i, f in enumerate(fields):
230 250
                if f.type == 'id':
231 251
                    r += htmltext('<td class="cell-id"><a href="%s%s">%s</a></td>') % (link, url_action,
wcs/forms/common.py
497 497
        get_logger().info('form %s - id: %s - view status' % (self.formdef.name, self.filled.id))
498 498
        self.html_top('%s - %s' % (self.formdef.name, self.filled.id))
499 499
        r = TemplateIO(html=True)
500
        if self.filled.receipt_time is not None:
501
            tm = misc.localstrftime(self.filled.receipt_time)
502
        else:
503
            tm = '???'
504
        r += htmltext("<p>")
505
        r += _('The form has been recorded on %(date)s with the number %(number)s.') % {
506
                'date': tm, 'number': self.filled.get_display_id()}
507
        r += htmltext("</p>")
508

  
509
        if self.filled.anonymised:
510
            r += htmltext('<div class="infonotice">')
511
            r += htmltext(_('This form has been anonymised on %(date)s.')) % {
512
                    'date': self.filled.anonymised.strftime(misc.date_format())}
513
            r += htmltext('</div>')
514 500

  
515 501
        r += htmltext(self.workflow_messages())
516 502

  
wcs/qommon/static/css/dc2/admin.css
1251 1251
.c-360-user-view li > span {
1252 1252
	padding-left: 1ex;
1253 1253
}
1254

  
1255
p.current-level span,
1256
p.current-status span {
1257
	line-height: 40px;
1258
}
1259

  
1260
p.current-level .item,
1261
p.current-status .item {
1262
	float: left;
1263
	display: inline-block;
1264
	width: 40px;
1265
	height: 40px;
1266
	border-radius: 20px;
1267
	border: 1px solid #aaa;
1268
	margin-right: 1ex;
1269
	margin-bottom: 1ex;
1270
}
1271

  
1272
p.current-level .item {
1273
	border-radius: 0;
1274
}
1275

  
1276
tr.criticality-level {
1277
	border-left-width: 5px;
1278
	border-left-style: solid;
1279
}
1280

  
1281
div.bo-block ul.biglist.criticality-levels li {
1282
	border-left-width: 5px;
1283
}
1284

  
1285
table#listing tr.criticality-level td:first-child {
1286
	border-left-width: 5px;
1287
	border-left-style: solid;
1288
}
wcs/qommon/static/js/biglist.js
9 9
            {
10 10
                accept: 'biglistitem',
11 11
                scroll: true,
12
                helper: 'clone',
12 13
                helperclass: 'sortHelper',
13 14
                activeclass :     'sortableactive',
14 15
                hoverclass :     'sortablehover',
wcs/sql.py
325 325
        'anonymised', 'workflow_roles', 'workflow_roles_array',
326 326
        'concerned_roles_array', 'tracking_code',
327 327
        'actions_roles_array', 'backoffice_submission',
328
        'submission_context', 'submission_channel'])
328
        'submission_context', 'submission_channel',
329
        'criticality_level'])
329 330

  
330 331
    # migrations
331 332
    if not 'fts' in existing_fields:
......
360 361
    if not 'submission_channel' in existing_fields:
361 362
        cur.execute('''ALTER TABLE %s ADD COLUMN submission_channel varchar''' % table_name)
362 363

  
364
    if not 'criticality_level' in existing_fields:
365
        cur.execute('''ALTER TABLE %s ADD COLUMN criticality_level integer NOT NULL DEFAULT(0)''' % table_name)
366

  
363 367
    # add new fields
364 368
    for field in formdef.fields:
365 369
        assert field.id is not None
......
565 569
    view_fields.append(("int '%s'" % (formdef.category_id or 0), 'category_id'))
566 570
    view_fields.append(("int '%s'" % (formdef.id or 0), 'formdef_id'))
567 571
    for field in ('id', 'user_id', 'user_hash', 'receipt_time', 'status',
568
            'id_display', 'submission_channel', 'backoffice_submission'):
572
            'id_display', 'submission_channel', 'backoffice_submission',
573
            'criticality_level'):
569 574
        view_fields.append((field, field))
570 575
    return view_fields
571 576

  
......
994 999
        ('backoffice_submission', 'boolean'),
995 1000
        ('submission_context', 'bytea'),
996 1001
        ('submission_channel', 'varchar'),
1002
        ('criticality_level', 'int'),
997 1003
    ]
998 1004

  
999 1005
    def __init__(self, id=None):
......
1088 1094
                'backoffice_submission': self.backoffice_submission,
1089 1095
                'submission_context': self.submission_context,
1090 1096
                'submission_channel': self.submission_channel,
1097
                'criticality_level': self.criticality_level,
1091 1098
        }
1092 1099
        if self.receipt_time:
1093 1100
            sql_dict['receipt_time'] = datetime.datetime.fromtimestamp(time.mktime(self.receipt_time)),
......
1786 1793
    return result
1787 1794

  
1788 1795

  
1789
SQL_LEVEL = 13
1796
SQL_LEVEL = 14
1790 1797

  
1791 1798
def migrate_global_views(conn, cur):
1792 1799
    cur.execute('''SELECT COUNT(*) FROM information_schema.tables
......
1820 1827
        raise RuntimeError()
1821 1828
    if sql_level < 1: # 1: introduction of tracking_code table
1822 1829
        do_tracking_code_table()
1823
    if sql_level < 13:
1830
    if sql_level < 14:
1824 1831
        # 2: introduction of formdef_id in views
1825 1832
        # 5: add concerned_roles_array, is_at_endpoint and fts to views
1826 1833
        # 7: add backoffice_submission to tables
......
1829 1836
        # 10: add submission_channel to tables
1830 1837
        # 11: add formdef_name and user_name to views
1831 1838
        # 13: add backoffice_submission to views
1839
        # 14: add criticality_level to tables & views
1832 1840
        migrate_views(conn, cur)
1833 1841
    if sql_level < 12:
1834 1842
        # 3: introduction of _structured for user fields
wcs/wf/criticality.py
1
# w.c.s. - web application for online forms
2
# Copyright (C) 2005-2016  Entr'ouvert
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, see <http://www.gnu.org/licenses/>.
16

  
17
from quixote import get_publisher
18

  
19
from wcs.qommon.form import SingleSelectWidget
20
from wcs.workflows import WorkflowStatusItem, register_item_class
21

  
22
MODE_INC = '1'
23
MODE_DEC = '2'
24
MODE_SET = '3'
25

  
26
class ModifyCriticalityWorkflowStatusItem(WorkflowStatusItem):
27
    description = N_('Modify Criticality')
28
    key = 'modify_criticality'
29

  
30
    mode = None
31
    absolute_value = None
32

  
33
    def get_parameters(self):
34
        return ('mode', 'absolute_value')
35

  
36
    @classmethod
37
    def is_available(cls):
38
        # TODO: if we had per-workflow availability we could show this action
39
        # only on the workflow where criticality levels are set.
40
        return get_publisher().has_site_option('workflow-criticality-levels')
41

  
42
    def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
43
        if 'mode' in parameters:
44
            form.add(SingleSelectWidget, '%smode' % prefix,
45
                    title=_('Modification Mode'), value=self.mode,
46
                    required=True,
47
                    options=[
48
                        (MODE_INC, _('Increase Level')),
49
                        (MODE_DEC, _('Decrease Level')),
50
                        (MODE_SET, _('Set Level'))],
51
                    attrs={'data-dynamic-display-parent': 'true'})
52
        if 'absolute_value' in parameters:
53
            if self.parent.parent.criticality_levels:
54
                options = [(str(i), x.name) for i, x in enumerate(self.parent.parent.criticality_levels)]
55
            else:
56
                options = [('0', '---')]
57
            form.add(SingleSelectWidget, '%sabsolute_value' % prefix,
58
                     title=_('Value'), value=self.absolute_value,
59
                     options=options,
60
                     attrs={
61
                         'data-dynamic-display-child-of': '%smode' % prefix,
62
                         'data-dynamic-display-value': _('Set Level'),
63
                         }
64
                     )
65

  
66
    def perform(self, formdata):
67
        levels = formdata.formdef.workflow.criticality_levels
68
        current_level = formdata.criticality_level or 0
69
        new_level = current_level
70
        if self.mode == MODE_INC:
71
            new_level = current_level + 1
72
        elif self.mode == MODE_DEC:
73
            new_level = current_level - 1
74
        elif self.mode == MODE_SET:
75
            new_level = int(self.absolute_value)
76
        if new_level < 0:
77
            new_level = 0
78
        if new_level >= len(levels):
79
            new_level = len(levels) - 1
80
        if new_level != formdata.criticality_level:
81
            formdata.criticality_level = new_level
82
            formdata.store()
83

  
84
register_item_class(ModifyCriticalityWorkflowStatusItem)
wcs/workflows.py
251 251
    roles = None
252 252
    variables_formdef = None
253 253
    global_actions = None
254
    criticality_levels = None
254 255

  
255 256
    last_modification_time = None
256 257
    last_modification_user_id = None
......
261 262
        self.possible_status = []
262 263
        self.roles = {'_receiver': _('Recipient')}
263 264
        self.global_actions = []
265
        self.criticality_levels = []
264 266

  
265 267
    def migrate(self):
266 268
        changed = False
......
452 454
                global_actions.append(action.export_to_xml(charset=charset,
453 455
                    include_id=include_id))
454 456

  
457
        if self.criticality_levels:
458
            criticality_levels = ET.SubElement(root, 'criticality_levels')
459
            for level in self.criticality_levels:
460
                criticality_levels.append(level.export_to_xml(charset=charset))
461

  
455 462
        if self.variables_formdef:
456 463
            variables = ET.SubElement(root, 'variables')
457 464
            formdef = ET.SubElement(variables, 'formdef')
......
516 523
                action_o.init_with_xml(action, charset, include_id=include_id)
517 524
                workflow.global_actions.append(action_o)
518 525

  
526
        workflow.criticality_levels = []
527
        criticality_levels = tree.find('criticality_levels')
528
        if criticality_levels is not None:
529
            for level in criticality_levels:
530
                level_o = WorkflowCriticalityLevel()
531
                level_o.init_with_xml(level, charset)
532
                workflow.criticality_levels.append(level_o)
533

  
519 534
        variables = tree.find('variables')
520 535
        if variables is not None:
521 536
            formdef = variables.find('formdef')
......
941 956
            trigger_o.init_with_xml(trigger, charset, include_id=include_id)
942 957

  
943 958

  
959
class WorkflowCriticalityLevel(object):
960
    id = None
961
    name = None
962
    colour = None
963

  
964
    def __init__(self, name=None, colour=None):
965
        self.name = name
966
        self.colour = colour
967
        self.id = str(random.randint(0, 100000))
968

  
969
    def export_to_xml(self, charset, include_id=False):
970
        level = ET.Element('criticality-level')
971
        ET.SubElement(level, 'id').text = unicode(self.id, charset)
972
        ET.SubElement(level, 'name').text = unicode(self.name, charset)
973
        if self.colour:
974
            ET.SubElement(level, 'colour').text = unicode(self.colour, charset)
975
        return level
976

  
977
    def init_with_xml(self, elem, charset, include_id=False):
978
        self.id = elem.find('id').text.encode(charset)
979
        self.name = elem.find('name').text.encode(charset)
980
        if elem.find('colour') is not None:
981
            self.colour = elem.find('colour').text.encode(charset)
982

  
983

  
944 984
class WorkflowStatus(object):
945 985
    id = None
946 986
    name = None
......
1961 2001
    import wf.anonymise
1962 2002
    import wf.export_to_model
1963 2003
    import wf.resubmit
2004
    import wf.criticality
1964 2005

  
1965 2006
from wf.export_to_model import ExportToModel
1966
-