0001-general-add-support-for-backoffice-fields-8273.patch
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 |
- |