Projet

Général

Profil

0001-general-add-support-for-backoffice-fields-8273.patch

Frédéric Péters, 20 juin 2016 14:12

Télécharger (38,5 ko)

Voir les différences:

Subject: [PATCH 01/10] general: add support for backoffice fields (#8273)

 tests/test_admin_pages.py           |  62 ++++++++++++++++++++
 tests/test_api.py                   |  20 ++++++-
 tests/test_backoffice_pages.py      |  37 +++++++++++-
 tests/test_formdata.py              |  21 ++++++-
 tests/test_workflow_import.py       |  15 +++++
 tests/test_workflows.py             |  38 +++++++++++-
 wcs/admin/workflows.py              |  59 +++++++++++++++++++
 wcs/backoffice/management.py        |  14 ++---
 wcs/formdata.py                     |   6 +-
 wcs/formdef.py                      |   5 +-
 wcs/forms/common.py                 |  44 ++++++++++----
 wcs/qommon/static/css/dc2/admin.css |  30 ++++++++++
 wcs/sql.py                          |  16 ++---
 wcs/wf/backoffice_fields.py         | 114 ++++++++++++++++++++++++++++++++++++
 wcs/workflows.py                    |  75 +++++++++++++++++++++---
 15 files changed, 517 insertions(+), 39 deletions(-)
 create mode 100644 wcs/wf/backoffice_fields.py
tests/test_admin_pages.py
1603 1603
    assert Workflow.get(1).variables_formdef.fields[0].key == 'string'
1604 1604
    assert Workflow.get(1).variables_formdef.fields[0].varname == '1*1*message'
1605 1605

  
1606
def test_workflows_backoffice_fields(pub):
1607
    create_superuser(pub)
1608
    create_role()
1609

  
1610
    Workflow.wipe()
1611
    workflow = Workflow(name='foo')
1612
    workflow.add_status(name='baz')
1613
    workflow.store()
1614

  
1615
    formdef = FormDef()
1616
    formdef.name = 'form title'
1617
    formdef.workflow_id = workflow.id
1618
    formdef.fields = []
1619
    formdef.store()
1620

  
1621
    app = login(get_app(pub))
1622
    resp = app.get('/backoffice/workflows/1/')
1623
    resp = resp.click('baz')
1624
    assert not 'Set Backoffice Field' in resp.body
1625

  
1626
    resp = app.get('/backoffice/workflows/1/')
1627
    resp = resp.click(href='backoffice-fields/')
1628
    assert resp.location == 'http://example.net/backoffice/workflows/1/backoffice-fields/fields/'
1629
    resp = resp.follow()
1630

  
1631
    # makes sure we can't add page fields
1632
    assert 'value="New Page"' not in resp.body
1633

  
1634
    # add a simple field
1635
    resp.forms[0]['label'] = 'foobar'
1636
    resp.forms[0]['type'] = 'Text (line)'
1637
    resp = resp.forms[0].submit()
1638
    assert resp.location == 'http://example.net/backoffice/workflows/1/backoffice-fields/fields/'
1639
    resp = resp.follow()
1640

  
1641
    # check it's been saved correctly
1642
    assert 'foobar' in resp.body
1643
    assert len(Workflow.get(1).backoffice_fields_formdef.fields) == 1
1644
    assert Workflow.get(1).backoffice_fields_formdef.fields[0].id.startswith('bo')
1645
    assert Workflow.get(1).backoffice_fields_formdef.fields[0].key == 'string'
1646
    assert Workflow.get(1).backoffice_fields_formdef.fields[0].label == 'foobar'
1647

  
1648
    backoffice_field_id = Workflow.get(1).backoffice_fields_formdef.fields[0].id
1649
    formdef = FormDef.get(formdef.id)
1650
    data_class = formdef.data_class()
1651
    data_class.wipe()
1652
    formdata = data_class()
1653
    formdata.data = {backoffice_field_id: 'HELLO'}
1654
    formdata.status = 'wf-new'
1655
    formdata.store()
1656

  
1657
    assert data_class.get(formdata.id).data[backoffice_field_id] == 'HELLO'
1658

  
1659
    # check the "set backoffice fields" action is now available
1660
    resp = app.get('/backoffice/workflows/1/')
1661
    resp = resp.click('baz')
1662
    resp.forms[0]['type'] = 'Set Backoffice Fields'
1663
    resp = resp.forms[0].submit()
1664
    resp = resp.follow()
1665

  
1666
    resp = resp.click('Set Backoffice Fields')
1667

  
1606 1668
def test_workflows_functions(pub):
1607 1669
    create_superuser(pub)
1608 1670
    create_role()
tests/test_api.py
20 20
from wcs.formdata import Evolution
21 21
from wcs.categories import Category
22 22
from wcs.data_sources import NamedDataSource
23
from wcs.workflows import Workflow, EditableWorkflowStatusItem
23
from wcs.workflows import Workflow, EditableWorkflowStatusItem, WorkflowBackofficeFieldsFormDef
24 24
from wcs.wf.jump import JumpWorkflowStatusItem
25 25
from wcs import fields, qommon
26 26
from wcs.api_utils import sign_url
......
830 830
    resp2 = get_app(pub).get(sign_uri('/test/%s/' % formdata.id,
831 831
        user=local_user), status=403)
832 832

  
833
def test_formdata_backoffice_fields(pub, local_user):
834
    test_formdata(pub, local_user)
835
    workflow = Workflow.get(2)
836
    workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
837
    workflow.backoffice_fields_formdef.fields = [
838
        fields.StringField(id='bo1', label='1st backoffice field',
839
            type='string', varname='backoffice_blah'),
840
    ]
841
    workflow.store()
842

  
843
    formdef = FormDef.select()[0]
844
    formdata = formdef.data_class().select()[0]
845
    formdata.data['bo1'] = 'Hello world'
846
    formdata.store()
847

  
848
    resp = get_app(pub).get(sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user))
849
    assert resp.json['workflow']['fields']['backoffice_blah'] == 'Hello world'
850

  
833 851
def test_formdata_edit(pub, local_user):
834 852
    test_formdata(pub, local_user)
835 853
    formdef = FormDef.select()[0]
tests/test_backoffice_pages.py
20 20
from wcs.roles import Role
21 21
from wcs.workflows import (Workflow, CommentableWorkflowStatusItem,
22 22
        ChoiceWorkflowStatusItem, EditableWorkflowStatusItem,
23
        JumpOnSubmitWorkflowStatusItem, WorkflowCriticalityLevel)
23
        JumpOnSubmitWorkflowStatusItem, WorkflowCriticalityLevel,
24
        WorkflowBackofficeFieldsFormDef)
24 25
from wcs.wf.dispatch import DispatchWorkflowStatusItem
25 26
from wcs.wf.wscall import WebserviceCallStatusItem
26 27
from wcs.wf.register_comment import RegisterCommenterWorkflowStatusItem
......
2437 2438
    assert (pq('[title="form_var_foo_str_but_non_utf8"]')
2438 2439
            .parents('li').children('div.value span')
2439 2440
            .text() == '\'\\xed\\xa0\\x00\'')
2441

  
2442
def test_backoffice_fields(pub):
2443
    user = create_user(pub)
2444
    create_environment(pub)
2445

  
2446
    wf = Workflow(name='bo fields')
2447
    wf.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(wf)
2448
    wf.backoffice_fields_formdef.fields = [
2449
        fields.StringField(id='bo1', label='1st backoffice field',
2450
            type='string', varname='backoffice_blah'),
2451
    ]
2452
    st1 = wf.add_status('Status1')
2453
    wf.store()
2454

  
2455
    formdef = FormDef.get_by_urlname('form-title')
2456
    formdef.workflow_id = wf.id
2457
    formdef.store()
2458

  
2459
    formdata = formdef.data_class()()
2460
    formdata.data = {}
2461
    formdata.just_created()
2462
    formdata.store()
2463

  
2464
    app = login(get_app(pub))
2465
    resp = app.get(formdata.get_url(backoffice=True))
2466
    assert not 'Backoffice Data' in resp.body
2467
    assert not '1st backoffice field' in resp.body
2468

  
2469
    formdata.data = {'bo1': 'HELLO WORLD'}
2470
    formdata.store()
2471
    resp = app.get(formdata.get_url(backoffice=True))
2472
    assert 'Backoffice Data' in resp.body
2473
    assert '1st backoffice field' in resp.body
2474
    assert 'HELLO WORLD' in resp.body
tests/test_formdata.py
10 10
from wcs import fields, formdef
11 11
from wcs.formdef import FormDef
12 12
from wcs.formdata import Evolution
13
from wcs.workflows import Workflow, WorkflowCriticalityLevel
13
from wcs.workflows import Workflow, WorkflowCriticalityLevel, WorkflowBackofficeFieldsFormDef
14 14
from wcs.wf.anonymise import AnonymiseWorkflowStatusItem
15 15
from wcs.wf.wscall import JournalWsCallErrorPart
16 16
from wcs.wf.register_comment import JournalEvolutionPart
......
529 529
    variables = formdata.get_substitution_variables()
530 530
    assert variables.get('form_var_xxx') == 'True'
531 531
    assert variables.get('form_var_xxx_raw') is True
532

  
533
def test_backoffice_field_varname(pub):
534
    wf = Workflow(name='bo fields')
535
    wf.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(wf)
536
    wf.backoffice_fields_formdef.fields = [
537
        fields.StringField(id='bo1', label='1st backoffice field',
538
            type='string', varname='backoffice_blah'),
539
    ]
540
    st1 = wf.add_status('Status1')
541
    wf.store()
542

  
543
    formdef.workflow_id = wf.id
544
    formdef.data_class().wipe()
545
    formdef.fields = [fields.StringField(id='0', label='string', varname='foo')]
546
    formdef.store()
547
    formdata = formdef.data_class()()
548
    formdata.data = {'bo1': 'test'}
549
    substvars = formdata.get_substitution_variables()
550
    assert substvars.get('form_var_backoffice_blah') == 'test'
tests/test_workflow_import.py
12 12
from wcs.wf.dispatch import DispatchWorkflowStatusItem
13 13
from wcs.wf.register_comment import RegisterCommenterWorkflowStatusItem
14 14
from wcs.wf.profile import UpdateUserProfileStatusItem
15
from wcs.wf.backoffice_fields import SetBackofficeFieldsWorkflowStatusItem
15 16
from wcs.roles import Role
16 17
from wcs.fields import StringField
17 18

  
......
468 469
    wf2 = assert_import_export_works(wf)
469 470
    item2 = wf2.possible_status[0].items[0]
470 471
    assert item2.fields == [{'field_id': '__email', 'value': '=form_var_foo'}]
472

  
473
def test_set_backoffice_fields_action():
474
    wf = Workflow(name='status')
475
    st1 = wf.add_status('Status1', 'st1')
476

  
477
    item = SetBackofficeFieldsWorkflowStatusItem()
478
    item.id = '_item'
479
    item.fields = [{'field_id': 'bo1', 'value': '=form_var_foo'}]
480
    st1.items.append(item)
481
    item.parent = st1
482

  
483
    wf2 = assert_import_export_works(wf)
484
    item2 = wf2.possible_status[0].items[0]
485
    assert item2.fields == [{'field_id': 'bo1', 'value': '=form_var_foo'}]
tests/test_workflows.py
20 20
        SendmailWorkflowStatusItem, SendSMSWorkflowStatusItem,
21 21
        DisplayMessageWorkflowStatusItem,
22 22
        AbortActionException, WorkflowCriticalityLevel,
23
        AttachmentEvolutionPart)
23
        AttachmentEvolutionPart, WorkflowBackofficeFieldsFormDef)
24 24
from wcs.wf.anonymise import AnonymiseWorkflowStatusItem
25 25
from wcs.wf.criticality import ModifyCriticalityWorkflowStatusItem, MODE_INC, MODE_DEC, MODE_SET
26 26
from wcs.wf.dispatch import DispatchWorkflowStatusItem
......
33 33
from wcs.wf.wscall import WebserviceCallStatusItem
34 34
from wcs.wf.export_to_model import transform_to_pdf
35 35
from wcs.wf.geolocate import GeolocateWorkflowStatusItem
36
from wcs.wf.backoffice_fields import SetBackofficeFieldsWorkflowStatusItem
36 37

  
37 38
from utilities import (create_temporary_pub, MockSubstitutionVariables, emails,
38 39
        http_requests, clean_temporary_pub, sms_mocking)
......
1639 1640
    item.fields = [{'field_id': 'plop', 'value': '=form_var_foo'}]
1640 1641
    item.perform(formdata)
1641 1642
    assert pub.user_class.get(user.id).form_data == {'3': 'Plop'}
1643

  
1644
def test_set_backoffice_field(pub):
1645
    Workflow.wipe()
1646
    wf = Workflow(name='xxx')
1647
    wf.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(wf)
1648
    wf.backoffice_fields_formdef.fields = [
1649
        StringField(id='bo1', label='1st backoffice field',
1650
            type='string', varname='backoffice_blah'),
1651
    ]
1652
    st1 = wf.add_status('Status1')
1653
    wf.store()
1654

  
1655
    formdef = FormDef()
1656
    formdef.name = 'baz'
1657
    formdef.fields = [
1658
        StringField(id='1', label='String', type='string', varname='string'),
1659
    ]
1660
    formdef.workflow_id = wf.id
1661
    formdef.store()
1662

  
1663
    formdata = formdef.data_class()()
1664
    formdata.data = {'1': 'HELLO'}
1665
    formdata.just_created()
1666
    formdata.store()
1667
    pub.substitutions.feed(formdata)
1668

  
1669
    item = SetBackofficeFieldsWorkflowStatusItem()
1670
    item.perform(formdata)
1671

  
1672
    item = SetBackofficeFieldsWorkflowStatusItem()
1673
    item.fields = [{'field_id': 'bo1', 'field_value': '=form_var_string'}]
1674
    item.perform(formdata)
1675

  
1676
    formdata = formdef.data_class().get(formdata.id)
1677
    assert formdata.data['bo1'] == 'HELLO'
wcs/admin/workflows.py
832 832
        return form
833 833

  
834 834

  
835
class WorkflowBackofficeFieldDefPage(FieldDefPage):
836
    section = 'workflows'
837

  
838

  
835 839
class WorkflowVariablesFieldsDirectory(FieldsDirectory):
836 840
    _q_exports = ['', 'update_order', 'new']
837 841

  
......
853 857
        pass
854 858

  
855 859

  
860
class WorkflowBackofficeFieldsDirectory(FieldsDirectory):
861
    _q_exports = ['', 'update_order', 'new']
862

  
863
    section = 'workflows'
864
    field_def_page_class = WorkflowBackofficeFieldDefPage
865
    support_import = False
866
    blacklisted_types = ['page']
867

  
868
    def index_top(self):
869
        r = TemplateIO(html=True)
870
        r += htmltext('<h2>%s - %s - %s</h2>') % (_('Workflow'),
871
                self.objectdef.name, _('Backoffice Fields'))
872
        r += get_session().display_message()
873
        if not self.objectdef.fields:
874
            r += htmltext('<p>%s</p>') % _('There are not yet any backoffice fields.')
875
        return r.getvalue()
876

  
877
    def index_bottom(self):
878
        pass
879

  
880

  
856 881
class VariablesDirectory(Directory):
857 882
    _q_exports = ['', 'fields']
858 883

  
......
869 894
        return Directory._q_traverse(self, path)
870 895

  
871 896

  
897
class BackofficeFieldsDirectory(Directory):
898
    _q_exports = ['', 'fields']
899

  
900
    def __init__(self, workflow):
901
        self.workflow = workflow
902

  
903
    def _q_index(self):
904
        return redirect('fields/')
905

  
906
    def _q_traverse(self, path):
907
        get_response().breadcrumb.append(('backoffice-fields/', _('Backoffice Fields')))
908
        self.fields = WorkflowBackofficeFieldsDirectory(
909
                WorkflowBackofficeFieldsFormDef(self.workflow))
910
        return Directory._q_traverse(self, path)
911

  
912

  
913

  
872 914
class FunctionsDirectory(Directory):
873 915
    _q_exports = ['', 'new']
874 916

  
......
1266 1308
class WorkflowPage(Directory):
1267 1309
    _q_exports = ['', 'edit', 'delete', 'newstatus', ('status', 'status_dir'), 'update_order',
1268 1310
            'duplicate', 'export', 'svg', ('variables', 'variables_dir'),
1311
            ('backoffice-fields', 'backoffice_fields_dir'),
1269 1312
            'update_actions_order', 'update_criticality_levels_order',
1270 1313
            ('functions', 'functions_dir'), ('global-actions', 'global_actions_dir'),
1271 1314
            ('criticality-levels', 'criticality_levels_dir'),
......
1280 1323
        self.workflow_ui = WorkflowUI(self.workflow)
1281 1324
        self.status_dir = WorkflowStatusDirectory(self.workflow, html_top)
1282 1325
        self.variables_dir = VariablesDirectory(self.workflow)
1326
        self.backoffice_fields_dir = BackofficeFieldsDirectory(self.workflow)
1283 1327
        self.functions_dir = FunctionsDirectory(self.workflow)
1284 1328
        self.global_actions_dir = GlobalActionsDirectory(self.workflow, html_top)
1285 1329
        self.criticality_levels_dir = CriticalityLevelsDirectory(self.workflow)
......
1425 1469
            r += htmltext('</ul>')
1426 1470
            r += htmltext('</div>')
1427 1471

  
1472
        if not str(self.workflow.id).startswith('_'):
1473
            r += htmltext('<div class="bo-block">')
1474
            r += htmltext('<h3>%s') % _('Backoffice Fields')
1475
            r += htmltext(' <span class="change">(<a href="backoffice-fields/">%s</a>)</span></h3>') % _('change')
1476
            if self.workflow.backoffice_fields_formdef:
1477
                r += htmltext('<ul class="biglist">')
1478
                for field in self.workflow.backoffice_fields_formdef.fields:
1479
                    r += htmltext('<li><a href="backoffice-fields/fields/%s/">%s') % (
1480
                            field.id, field.label)
1481
                    if field.varname:
1482
                        r += htmltext(' (<code>%s</code>)') % field.varname
1483
                    r += htmltext('</a></li>')
1484
                r += htmltext('</ul>')
1485
            r += htmltext('</div>')
1486

  
1428 1487
        r += htmltext('</div>') # .splitcontent-right
1429 1488

  
1430 1489
        r += htmltext('<br style="clear:both;"/>')
wcs/backoffice/management.py
183 183
        r += htmltext('<h2>%s</h2>') % self.user.display_name
184 184
        formdef = UserFieldsFormDef()
185 185
        r += htmltext('<div class="form">')
186
        for field in formdef.fields:
186
        for field in formdef.get_all_fields():
187 187
            if not hasattr(field, str('get_view_value')):
188 188
                continue
189 189
            value = self.user.form_data.get(field.id)
......
321 321

  
322 322
        formdef = UserFieldsFormDef()
323 323
        criteria_fields = [ILike('name', query), ILike('email', query)]
324
        for field in formdef.fields:
324
        for field in formdef.get_all_fields():
325 325
            if field.type in ('string', 'text', 'email'):
326 326
                criteria_fields.append(ILike('f%s' % field.id, query))
327 327
        if get_publisher().is_using_postgresql():
......
342 342
            r += htmltext('<th data-field-sort-key="name"><span>%s</span></th>') % _('Name')
343 343
        if include_email_column:
344 344
            r += htmltext('<th data-field-sort-key="email"><span>%s</span></th>') % _('Email')
345
        for field in formdef.fields:
345
        for field in formdef.get_all_fields():
346 346
            if field.in_listing:
347 347
                r += htmltext('<th data-field-sort-key="f%s"><span>%s</span></th>') % (
348 348
                        field.id, field.label)
......
356 356
                r += htmltext('<td>%s</td>') % (user.name or '')
357 357
            if include_email_column:
358 358
                r += htmltext('<td>%s</td>') % (user.email or '')
359
            for field in formdef.fields:
359
            for field in formdef.get_all_fields():
360 360
                if field.in_listing:
361 361
                    r += htmltext('<td>%s</td>') % (user.form_data.get(field.id) or '')
362 362
            r += htmltext('</tr>')
......
1031 1031
            fields.append(FakeField('submission_channel', 'submission_channel', _('Channel')))
1032 1032
        fields.append(FakeField('time', 'time', _('Time')))
1033 1033
        fields.append(FakeField('user-label', 'user-label', _('User Label')))
1034
        fields.extend(self.formdef.fields)
1034
        fields.extend(self.formdef.get_all_fields())
1035 1035
        fields.append(FakeField('status', 'status', _('Status')))
1036 1036
        fields.append(FakeField('anonymised', 'anonymised', _('Anonymised')))
1037 1037

  
......
1041 1041
        field_ids = [x for x in get_request().form.keys()]
1042 1042
        if not field_ids or ignore_form:
1043 1043
            field_ids = ['id', 'time', 'user-label']
1044
            for field in self.formdef.fields:
1044
            for field in self.formdef.get_all_fields():
1045 1045
                if hasattr(field, str('get_view_value')) and field.in_listing:
1046 1046
                    field_ids.append(field.id)
1047 1047
            field_ids.append('status')
......
1572 1572
        had_page = False
1573 1573
        last_page = None
1574 1574
        last_title = None
1575
        for f in self.formdef.fields:
1575
        for f in self.formdef.get_all_fields():
1576 1576
            if excluded_fields and f.id in excluded_fields:
1577 1577
                continue
1578 1578
            if f.type == 'page':
wcs/formdata.py
463 463
        self.workflow_data.update(dict)
464 464

  
465 465
    def get_as_dict(self):
466
        return get_dict_with_varnames(self.formdef.fields, self.data, self)
466
        return get_dict_with_varnames(self.formdef.get_all_fields(), self.data, self)
467 467

  
468 468
    def get_substitution_variables(self, minimal=False):
469 469
        d = {}
......
742 742
        # Workflow data have unknown purpose, do not store them in anonymised export
743 743
        if self.workflow_data and not anonymise:
744 744
            data['workflow']['data'] = self.workflow_data
745
        if self.formdef.workflow.get_backoffice_fields():
746
            data['workflow']['fields'] = get_json_dict(
747
                    self.formdef.workflow.get_backoffice_fields(),
748
                    self.data, include_files=include_files, anonymise=anonymise)
745 749

  
746 750
        # add a roles dictionary, with workflow functions and two special
747 751
        # entries for concerned/actions roles.
wcs/formdef.py
292 292
                    rebuild_global_views=True)
293 293
        return t
294 294

  
295
    def rebuild_views(self):
295
    def get_all_fields(self):
296
        return (self.fields or []) + self.workflow.get_backoffice_fields()
297

  
298
    def rebuild(self):
296 299
        if get_publisher().is_using_postgresql():
297 300
            import sql
298 301
            sql.do_formdef_tables(self, rebuild_views=True,
wcs/forms/common.py
420 420
            r += htmltext('<div class="field"><span class="label">%s</span>') % _('User name')
421 421
            r += htmltext('<span class="value">%s</span></div>') % user.display_name
422 422

  
423
        r += self.display_fields(self.formdef.fields, form_url)
424

  
425
        if show_status and self.formdef.is_user_allowed_read_status_and_history(
426
                        get_request().user, self.filled):
427
            wf_status = self.filled.get_visible_status()
428
            if wf_status:
429
                r += htmltext('<div><span class="label">%s</span> ') % _('Status')
430
                r += htmltext('<span class="value">%s</span></div>') % wf_status.name
431

  
432
        r += htmltext('</div>') # .dataview
433
        r += htmltext('</div>') # .bo-block
434

  
435
        return r.getvalue()
436

  
437
    def display_fields(self, fields, form_url=''):
438
        r = TemplateIO(html=True)
423 439
        on_page = False
424 440
        on_disabled_page = False
425
        for f in self.formdef.fields:
426

  
441
        for f in fields:
427 442
            if f.type == 'page':
428 443
                on_disabled_page = False
429 444
                if not f.is_visible(self.filled.data, self.formdef):
......
485 500
        if on_page:
486 501
            r += htmltext('</div></div>')
487 502

  
488
        if show_status and self.formdef.is_user_allowed_read_status_and_history(
489
                        get_request().user, self.filled):
490
            wf_status = self.filled.get_visible_status()
491
            if wf_status:
492
                r += htmltext('<p><span class="label">%s</span> ') % _('Status')
493
                r += htmltext('<span class="value">%s</span></p>') % wf_status.name
494

  
495
        r += htmltext('</div>') # .dataview
496
        r += htmltext('</div>') # .bo-block
497 503
        return r.getvalue()
498 504

  
505
    def backoffice_fields_section(self):
506
        backoffice_fields = self.formdef.workflow.get_backoffice_fields()
507
        if not backoffice_fields:
508
            return
509
        content = self.display_fields(backoffice_fields)
510
        if not len(content):
511
            return
512
        r = TemplateIO(html=True)
513
        r += htmltext('<div class="bo-block">')
514
        r += htmltext('<h2 class="foldable">%s</h2>') % _('Backoffice Data')
515
        r += htmltext('<div class="dataview">')
516
        r += content
517
        r += htmltext('</div>')
518
        r += htmltext('</div>')
519
        return r.getvalue()
499 520

  
500 521
    def status(self):
501 522
        object_key = 'formdata-%s-%s' % (self.formdef.url_name, self.filled.id)
......
545 566
                    break
546 567

  
547 568
        r += self.receipt(always_include_user=True, folded=folded)
569
        r += self.backoffice_fields_section()
548 570

  
549 571
        r += self.history()
550 572

  
wcs/qommon/static/css/dc2/admin.css
1186 1186
	width: calc(100% - 1em);
1187 1187
}
1188 1188

  
1189
div.SetBackofficeFieldsTableWidget table {
1190
	width: 100%;
1191
	border-spacing: 1ex;
1192
}
1193

  
1194
div.SetBackofficeFieldsTableWidget th {
1195
	text-align: left;
1196
}
1197

  
1198
div.SetBackofficeFieldsTableWidget td {
1199
	width: 20%;
1200
}
1201

  
1202
div.SetBackofficeFieldsTableWidget td + td {
1203
	width: 80%;
1204
}
1205

  
1206
div.SetBackofficeFieldsTableWidget br {
1207
	display: none;
1208
}
1209

  
1210
div.SetBackofficeFieldsTableWidget td select,
1211
div.SetBackofficeFieldsTableWidget td input {
1212
	width: 100%;
1213
}
1214

  
1215
form table div.widget {
1216
	margin-bottom: 1ex;
1217
}
1218

  
1189 1219
div.ComputedExpressionWidget div.content {
1190 1220
	position: relative;
1191 1221
}
wcs/sql.py
378 378
        cur.execute('''ALTER TABLE %s ADD COLUMN criticality_level integer NOT NULL DEFAULT(0)''' % table_name)
379 379

  
380 380
    # add new fields
381
    for field in formdef.fields:
381
    for field in formdef.get_all_fields():
382 382
        assert field.id is not None
383 383
        sql_type = SQL_TYPE_MAPPING.get(field.key, 'varchar')
384 384
        if sql_type is None:
......
461 461
    from admin.settings import UserFieldsFormDef
462 462
    formdef = UserFieldsFormDef()
463 463

  
464
    for field in formdef.fields:
464
    for field in formdef.get_all_fields():
465 465
        sql_type = SQL_TYPE_MAPPING.get(field.key, 'varchar')
466 466
        if sql_type is None:
467 467
            continue
......
602 602
    view_fields = get_view_fields(formdef)
603 603

  
604 604
    column_names = {}
605
    for field in formdef.fields:
605
    for field in formdef.get_all_fields():
606 606
        field_key = 'f%s' % field.id
607 607
        if field.type in ('page', 'title', 'subtitle', 'comment'):
608 608
            continue
......
900 900

  
901 901
    def get_sql_dict_from_data(self, data, formdef):
902 902
        sql_dict = {}
903
        for field in formdef.fields:
903
        for field in formdef.get_all_fields():
904 904
            sql_type = SQL_TYPE_MAPPING.get(field.key, 'varchar')
905 905
            if sql_type is None:
906 906
                continue
......
932 932
        i = len(cls._table_static_fields)
933 933
        if formdef.geolocations:
934 934
            i += len(formdef.geolocations.keys())
935
        for field in formdef.fields:
935
        for field in formdef.get_all_fields():
936 936
            sql_type = SQL_TYPE_MAPPING.get(field.key, 'varchar')
937 937
            if sql_type is None:
938 938
                continue
......
1220 1220
        fts_strings = [str(self.id)]
1221 1221
        if self.tracking_code:
1222 1222
            fts_strings.append(self.tracking_code)
1223
        for field in self._formdef.fields:
1223
        for field in self._formdef.get_all_fields():
1224 1224
            if not self.data.get(field.id):
1225 1225
                continue
1226 1226
            value = None
......
1275 1275
    @classmethod
1276 1276
    def get_data_fields(cls):
1277 1277
        data_fields = ['geoloc_%s' % x for x in (cls._formdef.geolocations or {}).keys()]
1278
        for field in cls._formdef.fields:
1278
        for field in cls._formdef.get_all_fields():
1279 1279
            sql_type = SQL_TYPE_MAPPING.get(field.key, 'varchar')
1280 1280
            if sql_type is None:
1281 1281
                continue
......
1492 1492
    @classmethod
1493 1493
    def get_data_fields(cls):
1494 1494
        data_fields = []
1495
        for field in cls.get_formdef().fields:
1495
        for field in cls.get_formdef().get_all_fields():
1496 1496
            sql_type = SQL_TYPE_MAPPING.get(field.key, 'varchar')
1497 1497
            if sql_type is None:
1498 1498
                continue
wcs/wf/backoffice_fields.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
import sys
18
import xml.etree.ElementTree as ET
19

  
20
from quixote import get_publisher
21

  
22
from qommon import get_logger
23
from qommon.form import WidgetListAsTable, CompositeWidget, SingleSelectWidget, ComputedExpressionWidget
24
from wcs.workflows import XmlSerialisable, WorkflowStatusItem, register_item_class
25
from wcs.wf.profile import FieldNode
26

  
27

  
28
class SetBackofficeFieldRowWidget(CompositeWidget):
29
    def __init__(self, name, value=None, workflow=None, **kwargs):
30
        CompositeWidget.__init__(self, name, value, **kwargs)
31
        if not value:
32
            value = {}
33

  
34
        fields = [('', '', '')]
35
        fields.extend([(x.id, x.label, x.id) for x in workflow.get_backoffice_fields()])
36
        self.add(SingleSelectWidget, name='field_id', title=_('Field'),
37
                value=value.get('field_id'),
38
                options=fields, **kwargs)
39
        self.add(ComputedExpressionWidget, name='value', title=_('Value'),
40
                value=value.get('value'))
41

  
42
    def _parse(self, request):
43
        if self.get('value') and self.get('field_id'):
44
            self.value = {
45
                'value': self.get('value'),
46
                'field_id': self.get('field_id')
47
            }
48
        else:
49
            self.value = None
50

  
51

  
52
class SetBackofficeFieldsTableWidget(WidgetListAsTable):
53
    readonly = False
54
    def __init__(self, name, **kwargs):
55
        super(SetBackofficeFieldsTableWidget, self).__init__(name,
56
                element_type=SetBackofficeFieldRowWidget,
57
                element_kwargs={'workflow': kwargs.pop('workflow')},
58
                **kwargs)
59

  
60

  
61
class SetBackofficeFieldsWorkflowStatusItem(WorkflowStatusItem):
62
    description = N_('Set Backoffice Fields')
63
    key = 'set-backoffice-fields'
64

  
65
    fields = None
66

  
67
    @classmethod
68
    def is_available(cls, workflow=None):
69
        return bool(workflow and getattr(workflow.backoffice_fields_formdef, 'fields', None))
70

  
71
    def get_parameters(self):
72
        return ('fields',)
73

  
74
    def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
75
        if 'fields' in parameters:
76
            form.add(SetBackofficeFieldsTableWidget, '%sfields' % prefix,
77
                    title=_('Fields Update'), value=self.fields,
78
                    workflow=self.parent.parent)
79

  
80
    def perform(self, formdata):
81
        if not self.fields:
82
            return
83
        for field in self.fields:
84
            try:
85
                formdata.data['%s' % field['field_id']] = self.compute(
86
                        field['field_value'], raises=True)
87
            except:
88
                get_publisher().notify_of_exception(sys.exc_info())
89
        formdata.store()
90

  
91
    def fields_export_to_xml(self, item, charset, include_id=False):
92
        if not self.fields:
93
            return
94

  
95
        fields_node = ET.SubElement(item, 'fields')
96
        for field in self.fields:
97
            fields_node.append(FieldNode(field).export_to_xml(charset=charset,
98
                include_id=include_id))
99

  
100
        return fields_node
101

  
102
    def fields_init_with_xml(self, elem, charset, include_id=False):
103
        fields = []
104
        if elem is None:
105
            return
106
        for field_xml_node in elem.findall('field'):
107
            field_node = FieldNode()
108
            field_node.init_with_xml(field_xml_node, charset,
109
                    include_id=include_id)
110
            fields.append(field_node.as_dict())
111
        if fields:
112
            self.fields = fields
113

  
114
register_item_class(SetBackofficeFieldsWorkflowStatusItem)
wcs/workflows.py
249 249
        self.workflow.store()
250 250

  
251 251

  
252
class WorkflowBackofficeFieldsFormDef(FormDef):
253
    '''Class to handle workflow backoffice fields, it loads and saves from/to
254
       the workflow object 'backoffice_fields_formdef' attribute.'''
255

  
256
    def __init__(self, workflow):
257
        self.id = None
258
        self.name = workflow.name
259
        self.workflow = workflow
260
        if workflow.backoffice_fields_formdef and workflow.backoffice_fields_formdef.fields:
261
            self.fields = self.workflow.backoffice_fields_formdef.fields
262
            self.max_field_id = max([lax_int(x.id) for x in self.fields or []])
263
        else:
264
            self.fields = []
265

  
266
    def get_new_field_id(self):
267
        if self.max_field_id is None:
268
            field_id = 1
269
        else:
270
            field_id = self.max_field_id + 1
271
        self.max_field_id = field_id
272
        return 'bo%s' % field_id
273

  
274
    def store(self):
275
        self.workflow.backoffice_fields_formdef = self
276
        self.workflow.store()
277

  
278

  
252 279
class Workflow(StorableObject):
253 280
    _names = 'workflows'
254 281
    name = None
255 282
    possible_status = None
256 283
    roles = None
257 284
    variables_formdef = None
285
    backoffice_fields_formdef = None
258 286
    global_actions = None
259 287
    criticality_levels = None
260 288

  
......
286 314
            self.store()
287 315

  
288 316
    def store(self):
289
        must_update_views = False
317
        must_update = False
290 318
        if self.id:
291 319
            old_self = self.get(self.id, ignore_errors=True, ignore_migration=True)
292 320
            if old_self:
293 321
                old_endpoints = set([x.id for x in old_self.get_endpoint_status()])
294 322
                if old_endpoints != set([x.id for x in self.get_endpoint_status()]):
295
                    must_update_views = True
323
                    must_update = True
296 324
                old_criticality_levels = len(old_self.criticality_levels or [0])
297 325
                if old_criticality_levels != len(self.criticality_levels or [0]):
298
                    must_update_views = True
326
                    must_update = True
327
                try:
328
                    old_backoffice_fields = old_self.backoffice_fields_formdef.fields
329
                except AttributeError:
330
                    old_backoffice_fields = []
331
                try:
332
                    new_backoffice_fields = self.backoffice_fields_formdef.fields
333
                except AttributeError:
334
                    new_backoffice_fields = []
335
                if len(old_backoffice_fields) != len(new_backoffice_fields):
336
                    must_update = True
337
        elif self.backoffice_fields_formdef:
338
            must_update = True
299 339

  
300 340
        self.last_modification_time = time.localtime()
301 341
        if get_request() and get_request().user:
......
304 344
            self.last_modification_user_id = None
305 345
        StorableObject.store(self)
306 346

  
307
        # instruct all related formdefs to update their security rules, and
308
        # their views if endpoints have changed.
347
        # instruct all related formdefs to update.
309 348
        for form in FormDef.select(lambda x: x.workflow_id == self.id, ignore_migration=True):
310 349
            form.data_class().rebuild_security()
311
            if must_update_views:
312
                form.rebuild_views()
350
            if must_update:
351
                form.rebuild()
313 352

  
314 353
    @classmethod
315 354
    def get(cls, id, ignore_errors=False, ignore_migration=False):
......
340 379
                return status
341 380
        raise KeyError()
342 381

  
382
    def get_backoffice_fields(self):
383
        if self.backoffice_fields_formdef:
384
            return self.backoffice_fields_formdef.fields or []
385
        return []
386

  
343 387
    def add_global_action(self, name, id=None):
344 388
        if [x for x in self.global_actions if x.name == name]:
345 389
            raise DuplicateGlobalActionNameError()
......
475 519
            for field in self.variables_formdef.fields:
476 520
                fields.append(field.export_to_xml(charset=charset, include_id=include_id))
477 521

  
522
        if self.backoffice_fields_formdef:
523
            variables = ET.SubElement(root, 'backoffice-fields')
524
            formdef = ET.SubElement(variables, 'formdef')
525
            ET.SubElement(formdef, 'name').text = '-' # required by formdef xml import
526
            fields = ET.SubElement(formdef, 'fields')
527
            for field in self.backoffice_fields_formdef.fields:
528
                fields.append(field.export_to_xml(charset=charset, include_id=include_id))
529

  
478 530
        return root
479 531

  
480 532
    @classmethod
......
546 598
            imported_formdef = FormDef.import_from_xml_tree(formdef, include_id=True)
547 599
            workflow.variables_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow)
548 600
            workflow.variables_formdef.fields = imported_formdef.fields
601

  
602
        variables = tree.find('backoffice-fields')
603
        if variables is not None:
604
            formdef = variables.find('backoffice-fields')
605
            imported_formdef = FormDef.import_from_xml_tree(formdef, include_id=True)
606
            workflow.backoffice_fields_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow)
607
            workflow.backoffice_fields_formdef.fields = imported_formdef.fields
608

  
549 609
        return workflow
550 610

  
551 611
    def get_list_of_roles(self, include_logged_in_users=True):
......
2233 2293
    import wf.resubmit
2234 2294
    import wf.criticality
2235 2295
    import wf.profile
2296
    import wf.backoffice_fields
2236 2297

  
2237 2298
from wf.export_to_model import ExportToModel
2238
-