general: add support for backoffice fields (#8273)
This commit is contained in:
parent
88380bc58e
commit
687a2456ce
|
@ -1614,6 +1614,83 @@ def test_workflows_variables_edit(pub):
|
|||
assert Workflow.get(1).variables_formdef.fields[0].key == 'string'
|
||||
assert Workflow.get(1).variables_formdef.fields[0].varname == '1*1*message'
|
||||
|
||||
def test_workflows_backoffice_fields(pub):
|
||||
create_superuser(pub)
|
||||
create_role()
|
||||
|
||||
Workflow.wipe()
|
||||
workflow = Workflow(name='foo')
|
||||
workflow.add_status(name='baz')
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.workflow_id = workflow.id
|
||||
formdef.fields = []
|
||||
formdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/workflows/1/')
|
||||
resp = resp.click('baz')
|
||||
assert not 'Set Backoffice Field' in resp.body
|
||||
|
||||
resp = app.get('/backoffice/workflows/1/')
|
||||
resp = resp.click(href='backoffice-fields/')
|
||||
assert resp.location == 'http://example.net/backoffice/workflows/1/backoffice-fields/fields/'
|
||||
resp = resp.follow()
|
||||
|
||||
# makes sure we can't add page fields
|
||||
assert 'value="New Page"' not in resp.body
|
||||
|
||||
# add a simple field
|
||||
resp.forms[0]['label'] = 'foobar'
|
||||
resp.forms[0]['type'] = 'Text (line)'
|
||||
resp = resp.forms[0].submit()
|
||||
assert resp.location == 'http://example.net/backoffice/workflows/1/backoffice-fields/fields/'
|
||||
resp = resp.follow()
|
||||
|
||||
# check it's been saved correctly
|
||||
assert 'foobar' in resp.body
|
||||
assert len(Workflow.get(1).backoffice_fields_formdef.fields) == 1
|
||||
assert Workflow.get(1).backoffice_fields_formdef.fields[0].id.startswith('bo')
|
||||
assert Workflow.get(1).backoffice_fields_formdef.fields[0].key == 'string'
|
||||
assert Workflow.get(1).backoffice_fields_formdef.fields[0].label == 'foobar'
|
||||
|
||||
backoffice_field_id = Workflow.get(1).backoffice_fields_formdef.fields[0].id
|
||||
formdef = FormDef.get(formdef.id)
|
||||
data_class = formdef.data_class()
|
||||
data_class.wipe()
|
||||
formdata = data_class()
|
||||
formdata.data = {backoffice_field_id: 'HELLO'}
|
||||
formdata.status = 'wf-new'
|
||||
formdata.store()
|
||||
|
||||
assert data_class.get(formdata.id).data[backoffice_field_id] == 'HELLO'
|
||||
|
||||
# check the "set backoffice fields" action is now available
|
||||
resp = app.get('/backoffice/workflows/1/')
|
||||
resp = resp.click('baz')
|
||||
resp.forms[0]['type'] = 'Set Backoffice Fields'
|
||||
resp = resp.forms[0].submit()
|
||||
resp = resp.follow()
|
||||
|
||||
resp = resp.click('Set Backoffice Fields')
|
||||
|
||||
# add a second field
|
||||
resp = app.get('/backoffice/workflows/1/')
|
||||
resp = resp.click(href='backoffice-fields/', index=0)
|
||||
assert resp.location == 'http://example.net/backoffice/workflows/1/backoffice-fields/fields/'
|
||||
resp = resp.follow()
|
||||
resp.forms[0]['label'] = 'foobar2'
|
||||
resp.forms[0]['type'] = 'Text (line)'
|
||||
resp = resp.forms[0].submit()
|
||||
assert resp.location == 'http://example.net/backoffice/workflows/1/backoffice-fields/fields/'
|
||||
resp = resp.follow()
|
||||
workflow = Workflow.get(workflow.id)
|
||||
assert len(workflow.backoffice_fields_formdef.fields) == 2
|
||||
assert workflow.backoffice_fields_formdef.fields[0].id == 'bo1'
|
||||
assert workflow.backoffice_fields_formdef.fields[1].id == 'bo2'
|
||||
|
||||
def test_workflows_functions(pub):
|
||||
create_superuser(pub)
|
||||
create_role()
|
||||
|
|
|
@ -20,7 +20,7 @@ from wcs.formdef import FormDef
|
|||
from wcs.formdata import Evolution
|
||||
from wcs.categories import Category
|
||||
from wcs.data_sources import NamedDataSource
|
||||
from wcs.workflows import Workflow, EditableWorkflowStatusItem
|
||||
from wcs.workflows import Workflow, EditableWorkflowStatusItem, WorkflowBackofficeFieldsFormDef
|
||||
from wcs.wf.jump import JumpWorkflowStatusItem
|
||||
from wcs import fields, qommon
|
||||
from wcs.api_utils import sign_url
|
||||
|
@ -830,6 +830,24 @@ def test_formdata(pub, local_user):
|
|||
resp2 = get_app(pub).get(sign_uri('/test/%s/' % formdata.id,
|
||||
user=local_user), status=403)
|
||||
|
||||
def test_formdata_backoffice_fields(pub, local_user):
|
||||
test_formdata(pub, local_user)
|
||||
workflow = Workflow.get(2)
|
||||
workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
|
||||
workflow.backoffice_fields_formdef.fields = [
|
||||
fields.StringField(id='bo1', label='1st backoffice field',
|
||||
type='string', varname='backoffice_blah'),
|
||||
]
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef.select()[0]
|
||||
formdata = formdef.data_class().select()[0]
|
||||
formdata.data['bo1'] = 'Hello world'
|
||||
formdata.store()
|
||||
|
||||
resp = get_app(pub).get(sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user))
|
||||
assert resp.json['workflow']['fields']['backoffice_blah'] == 'Hello world'
|
||||
|
||||
def test_formdata_edit(pub, local_user):
|
||||
test_formdata(pub, local_user)
|
||||
formdef = FormDef.select()[0]
|
||||
|
|
|
@ -20,7 +20,8 @@ from wcs.qommon.http_request import HTTPRequest
|
|||
from wcs.roles import Role
|
||||
from wcs.workflows import (Workflow, CommentableWorkflowStatusItem,
|
||||
ChoiceWorkflowStatusItem, EditableWorkflowStatusItem,
|
||||
JumpOnSubmitWorkflowStatusItem, WorkflowCriticalityLevel)
|
||||
JumpOnSubmitWorkflowStatusItem, WorkflowCriticalityLevel,
|
||||
WorkflowBackofficeFieldsFormDef)
|
||||
from wcs.wf.dispatch import DispatchWorkflowStatusItem
|
||||
from wcs.wf.wscall import WebserviceCallStatusItem
|
||||
from wcs.wf.register_comment import RegisterCommenterWorkflowStatusItem
|
||||
|
@ -2437,3 +2438,37 @@ def test_inspect_page(pub):
|
|||
assert (pq('[title="form_var_foo_str_but_non_utf8"]')
|
||||
.parents('li').children('div.value span')
|
||||
.text() == '\'\\xed\\xa0\\x00\'')
|
||||
|
||||
def test_backoffice_fields(pub):
|
||||
user = create_user(pub)
|
||||
create_environment(pub)
|
||||
|
||||
wf = Workflow(name='bo fields')
|
||||
wf.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(wf)
|
||||
wf.backoffice_fields_formdef.fields = [
|
||||
fields.StringField(id='bo1', label='1st backoffice field',
|
||||
type='string', varname='backoffice_blah'),
|
||||
]
|
||||
st1 = wf.add_status('Status1')
|
||||
wf.store()
|
||||
|
||||
formdef = FormDef.get_by_urlname('form-title')
|
||||
formdef.workflow_id = wf.id
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get(formdata.get_url(backoffice=True))
|
||||
assert not 'Backoffice Data' in resp.body
|
||||
assert not '1st backoffice field' in resp.body
|
||||
|
||||
formdata.data = {'bo1': 'HELLO WORLD'}
|
||||
formdata.store()
|
||||
resp = app.get(formdata.get_url(backoffice=True))
|
||||
assert 'Backoffice Data' in resp.body
|
||||
assert '1st backoffice field' in resp.body
|
||||
assert 'HELLO WORLD' in resp.body
|
||||
|
|
|
@ -10,7 +10,7 @@ from wcs.qommon.http_request import HTTPRequest
|
|||
from wcs import fields, formdef
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.formdata import Evolution
|
||||
from wcs.workflows import Workflow, WorkflowCriticalityLevel
|
||||
from wcs.workflows import Workflow, WorkflowCriticalityLevel, WorkflowBackofficeFieldsFormDef
|
||||
from wcs.wf.anonymise import AnonymiseWorkflowStatusItem
|
||||
from wcs.wf.wscall import JournalWsCallErrorPart
|
||||
from wcs.wf.register_comment import JournalEvolutionPart
|
||||
|
@ -529,3 +529,22 @@ def test_field_bool_substvars(pub):
|
|||
variables = formdata.get_substitution_variables()
|
||||
assert variables.get('form_var_xxx') == 'True'
|
||||
assert variables.get('form_var_xxx_raw') is True
|
||||
|
||||
def test_backoffice_field_varname(pub):
|
||||
wf = Workflow(name='bo fields')
|
||||
wf.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(wf)
|
||||
wf.backoffice_fields_formdef.fields = [
|
||||
fields.StringField(id='bo1', label='1st backoffice field',
|
||||
type='string', varname='backoffice_blah'),
|
||||
]
|
||||
st1 = wf.add_status('Status1')
|
||||
wf.store()
|
||||
|
||||
formdef.workflow_id = wf.id
|
||||
formdef.data_class().wipe()
|
||||
formdef.fields = [fields.StringField(id='0', label='string', varname='foo')]
|
||||
formdef.store()
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'bo1': 'test'}
|
||||
substvars = formdata.get_substitution_variables()
|
||||
assert substvars.get('form_var_backoffice_blah') == 'test'
|
||||
|
|
|
@ -12,6 +12,7 @@ from wcs.wf.wscall import WebserviceCallStatusItem
|
|||
from wcs.wf.dispatch import DispatchWorkflowStatusItem
|
||||
from wcs.wf.register_comment import RegisterCommenterWorkflowStatusItem
|
||||
from wcs.wf.profile import UpdateUserProfileStatusItem
|
||||
from wcs.wf.backoffice_fields import SetBackofficeFieldsWorkflowStatusItem
|
||||
from wcs.roles import Role
|
||||
from wcs.fields import StringField
|
||||
|
||||
|
@ -468,3 +469,17 @@ def test_profile_action():
|
|||
wf2 = assert_import_export_works(wf)
|
||||
item2 = wf2.possible_status[0].items[0]
|
||||
assert item2.fields == [{'field_id': '__email', 'value': '=form_var_foo'}]
|
||||
|
||||
def test_set_backoffice_fields_action():
|
||||
wf = Workflow(name='status')
|
||||
st1 = wf.add_status('Status1', 'st1')
|
||||
|
||||
item = SetBackofficeFieldsWorkflowStatusItem()
|
||||
item.id = '_item'
|
||||
item.fields = [{'field_id': 'bo1', 'value': '=form_var_foo'}]
|
||||
st1.items.append(item)
|
||||
item.parent = st1
|
||||
|
||||
wf2 = assert_import_export_works(wf)
|
||||
item2 = wf2.possible_status[0].items[0]
|
||||
assert item2.fields == [{'field_id': 'bo1', 'value': '=form_var_foo'}]
|
||||
|
|
|
@ -20,7 +20,7 @@ from wcs.workflows import (Workflow, WorkflowStatusItem,
|
|||
SendmailWorkflowStatusItem, SendSMSWorkflowStatusItem,
|
||||
DisplayMessageWorkflowStatusItem,
|
||||
AbortActionException, WorkflowCriticalityLevel,
|
||||
AttachmentEvolutionPart)
|
||||
AttachmentEvolutionPart, WorkflowBackofficeFieldsFormDef)
|
||||
from wcs.wf.anonymise import AnonymiseWorkflowStatusItem
|
||||
from wcs.wf.criticality import ModifyCriticalityWorkflowStatusItem, MODE_INC, MODE_DEC, MODE_SET
|
||||
from wcs.wf.dispatch import DispatchWorkflowStatusItem
|
||||
|
@ -33,6 +33,7 @@ from wcs.wf.roles import AddRoleWorkflowStatusItem, RemoveRoleWorkflowStatusItem
|
|||
from wcs.wf.wscall import WebserviceCallStatusItem
|
||||
from wcs.wf.export_to_model import transform_to_pdf
|
||||
from wcs.wf.geolocate import GeolocateWorkflowStatusItem
|
||||
from wcs.wf.backoffice_fields import SetBackofficeFieldsWorkflowStatusItem
|
||||
|
||||
from utilities import (create_temporary_pub, MockSubstitutionVariables, emails,
|
||||
http_requests, clean_temporary_pub, sms_mocking)
|
||||
|
@ -1639,3 +1640,38 @@ def test_profile(pub):
|
|||
item.fields = [{'field_id': 'plop', 'value': '=form_var_foo'}]
|
||||
item.perform(formdata)
|
||||
assert pub.user_class.get(user.id).form_data == {'3': 'Plop'}
|
||||
|
||||
def test_set_backoffice_field(pub):
|
||||
Workflow.wipe()
|
||||
wf = Workflow(name='xxx')
|
||||
wf.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(wf)
|
||||
wf.backoffice_fields_formdef.fields = [
|
||||
StringField(id='bo1', label='1st backoffice field',
|
||||
type='string', varname='backoffice_blah'),
|
||||
]
|
||||
st1 = wf.add_status('Status1')
|
||||
wf.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'baz'
|
||||
formdef.fields = [
|
||||
StringField(id='1', label='String', type='string', varname='string'),
|
||||
]
|
||||
formdef.workflow_id = wf.id
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'1': 'HELLO'}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
pub.substitutions.feed(formdata)
|
||||
|
||||
item = SetBackofficeFieldsWorkflowStatusItem()
|
||||
item.perform(formdata)
|
||||
|
||||
item = SetBackofficeFieldsWorkflowStatusItem()
|
||||
item.fields = [{'field_id': 'bo1', 'value': '=form_var_string'}]
|
||||
item.perform(formdata)
|
||||
|
||||
formdata = formdef.data_class().get(formdata.id)
|
||||
assert formdata.data['bo1'] == 'HELLO'
|
||||
|
|
|
@ -832,6 +832,10 @@ class WorkflowVariablesFieldDefPage(FieldDefPage):
|
|||
return form
|
||||
|
||||
|
||||
class WorkflowBackofficeFieldDefPage(FieldDefPage):
|
||||
section = 'workflows'
|
||||
|
||||
|
||||
class WorkflowVariablesFieldsDirectory(FieldsDirectory):
|
||||
_q_exports = ['', 'update_order', 'new']
|
||||
|
||||
|
@ -853,6 +857,27 @@ class WorkflowVariablesFieldsDirectory(FieldsDirectory):
|
|||
pass
|
||||
|
||||
|
||||
class WorkflowBackofficeFieldsDirectory(FieldsDirectory):
|
||||
_q_exports = ['', 'update_order', 'new']
|
||||
|
||||
section = 'workflows'
|
||||
field_def_page_class = WorkflowBackofficeFieldDefPage
|
||||
support_import = False
|
||||
blacklisted_types = ['page']
|
||||
|
||||
def index_top(self):
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<h2>%s - %s - %s</h2>') % (_('Workflow'),
|
||||
self.objectdef.name, _('Backoffice Fields'))
|
||||
r += get_session().display_message()
|
||||
if not self.objectdef.fields:
|
||||
r += htmltext('<p>%s</p>') % _('There are not yet any backoffice fields.')
|
||||
return r.getvalue()
|
||||
|
||||
def index_bottom(self):
|
||||
pass
|
||||
|
||||
|
||||
class VariablesDirectory(Directory):
|
||||
_q_exports = ['', 'fields']
|
||||
|
||||
|
@ -869,6 +894,23 @@ class VariablesDirectory(Directory):
|
|||
return Directory._q_traverse(self, path)
|
||||
|
||||
|
||||
class BackofficeFieldsDirectory(Directory):
|
||||
_q_exports = ['', 'fields']
|
||||
|
||||
def __init__(self, workflow):
|
||||
self.workflow = workflow
|
||||
|
||||
def _q_index(self):
|
||||
return redirect('fields/')
|
||||
|
||||
def _q_traverse(self, path):
|
||||
get_response().breadcrumb.append(('backoffice-fields/', _('Backoffice Fields')))
|
||||
self.fields = WorkflowBackofficeFieldsDirectory(
|
||||
WorkflowBackofficeFieldsFormDef(self.workflow))
|
||||
return Directory._q_traverse(self, path)
|
||||
|
||||
|
||||
|
||||
class FunctionsDirectory(Directory):
|
||||
_q_exports = ['', 'new']
|
||||
|
||||
|
@ -1266,6 +1308,7 @@ class GlobalActionsDirectory(Directory):
|
|||
class WorkflowPage(Directory):
|
||||
_q_exports = ['', 'edit', 'delete', 'newstatus', ('status', 'status_dir'), 'update_order',
|
||||
'duplicate', 'export', 'svg', ('variables', 'variables_dir'),
|
||||
('backoffice-fields', 'backoffice_fields_dir'),
|
||||
'update_actions_order', 'update_criticality_levels_order',
|
||||
('functions', 'functions_dir'), ('global-actions', 'global_actions_dir'),
|
||||
('criticality-levels', 'criticality_levels_dir'),
|
||||
|
@ -1280,6 +1323,7 @@ class WorkflowPage(Directory):
|
|||
self.workflow_ui = WorkflowUI(self.workflow)
|
||||
self.status_dir = WorkflowStatusDirectory(self.workflow, html_top)
|
||||
self.variables_dir = VariablesDirectory(self.workflow)
|
||||
self.backoffice_fields_dir = BackofficeFieldsDirectory(self.workflow)
|
||||
self.functions_dir = FunctionsDirectory(self.workflow)
|
||||
self.global_actions_dir = GlobalActionsDirectory(self.workflow, html_top)
|
||||
self.criticality_levels_dir = CriticalityLevelsDirectory(self.workflow)
|
||||
|
@ -1425,6 +1469,21 @@ class WorkflowPage(Directory):
|
|||
r += htmltext('</ul>')
|
||||
r += htmltext('</div>')
|
||||
|
||||
if not str(self.workflow.id).startswith('_'):
|
||||
r += htmltext('<div class="bo-block">')
|
||||
r += htmltext('<h3>%s') % _('Backoffice Fields')
|
||||
r += htmltext(' <span class="change">(<a href="backoffice-fields/">%s</a>)</span></h3>') % _('change')
|
||||
if self.workflow.backoffice_fields_formdef:
|
||||
r += htmltext('<ul class="biglist">')
|
||||
for field in self.workflow.backoffice_fields_formdef.fields:
|
||||
r += htmltext('<li><a href="backoffice-fields/fields/%s/">%s') % (
|
||||
field.id, field.label)
|
||||
if field.varname:
|
||||
r += htmltext(' (<code>%s</code>)') % field.varname
|
||||
r += htmltext('</a></li>')
|
||||
r += htmltext('</ul>')
|
||||
r += htmltext('</div>')
|
||||
|
||||
r += htmltext('</div>') # .splitcontent-right
|
||||
|
||||
r += htmltext('<br style="clear:both;"/>')
|
||||
|
|
|
@ -183,7 +183,7 @@ class UserViewDirectory(Directory):
|
|||
r += htmltext('<h2>%s</h2>') % self.user.display_name
|
||||
formdef = UserFieldsFormDef()
|
||||
r += htmltext('<div class="form">')
|
||||
for field in formdef.fields:
|
||||
for field in formdef.get_all_fields():
|
||||
if not hasattr(field, str('get_view_value')):
|
||||
continue
|
||||
value = self.user.form_data.get(field.id)
|
||||
|
@ -321,7 +321,7 @@ class UsersViewDirectory(Directory):
|
|||
|
||||
formdef = UserFieldsFormDef()
|
||||
criteria_fields = [ILike('name', query), ILike('email', query)]
|
||||
for field in formdef.fields:
|
||||
for field in formdef.get_all_fields():
|
||||
if field.type in ('string', 'text', 'email'):
|
||||
criteria_fields.append(ILike('f%s' % field.id, query))
|
||||
if get_publisher().is_using_postgresql():
|
||||
|
@ -342,7 +342,7 @@ class UsersViewDirectory(Directory):
|
|||
r += htmltext('<th data-field-sort-key="name"><span>%s</span></th>') % _('Name')
|
||||
if include_email_column:
|
||||
r += htmltext('<th data-field-sort-key="email"><span>%s</span></th>') % _('Email')
|
||||
for field in formdef.fields:
|
||||
for field in formdef.get_all_fields():
|
||||
if field.in_listing:
|
||||
r += htmltext('<th data-field-sort-key="f%s"><span>%s</span></th>') % (
|
||||
field.id, field.label)
|
||||
|
@ -356,7 +356,7 @@ class UsersViewDirectory(Directory):
|
|||
r += htmltext('<td>%s</td>') % (user.name or '')
|
||||
if include_email_column:
|
||||
r += htmltext('<td>%s</td>') % (user.email or '')
|
||||
for field in formdef.fields:
|
||||
for field in formdef.get_all_fields():
|
||||
if field.in_listing:
|
||||
r += htmltext('<td>%s</td>') % (user.form_data.get(field.id) or '')
|
||||
r += htmltext('</tr>')
|
||||
|
@ -1031,7 +1031,7 @@ class FormPage(Directory):
|
|||
fields.append(FakeField('submission_channel', 'submission_channel', _('Channel')))
|
||||
fields.append(FakeField('time', 'time', _('Time')))
|
||||
fields.append(FakeField('user-label', 'user-label', _('User Label')))
|
||||
fields.extend(self.formdef.fields)
|
||||
fields.extend(self.formdef.get_all_fields())
|
||||
fields.append(FakeField('status', 'status', _('Status')))
|
||||
fields.append(FakeField('anonymised', 'anonymised', _('Anonymised')))
|
||||
|
||||
|
@ -1041,7 +1041,7 @@ class FormPage(Directory):
|
|||
field_ids = [x for x in get_request().form.keys()]
|
||||
if not field_ids or ignore_form:
|
||||
field_ids = ['id', 'time', 'user-label']
|
||||
for field in self.formdef.fields:
|
||||
for field in self.formdef.get_all_fields():
|
||||
if hasattr(field, str('get_view_value')) and field.in_listing:
|
||||
field_ids.append(field.id)
|
||||
field_ids.append('status')
|
||||
|
@ -1572,7 +1572,7 @@ class FormPage(Directory):
|
|||
had_page = False
|
||||
last_page = None
|
||||
last_title = None
|
||||
for f in self.formdef.fields:
|
||||
for f in self.formdef.get_all_fields():
|
||||
if excluded_fields and f.id in excluded_fields:
|
||||
continue
|
||||
if f.type == 'page':
|
||||
|
|
|
@ -463,7 +463,7 @@ class FormData(StorableObject):
|
|||
self.workflow_data.update(dict)
|
||||
|
||||
def get_as_dict(self):
|
||||
return get_dict_with_varnames(self.formdef.fields, self.data, self)
|
||||
return get_dict_with_varnames(self.formdef.get_all_fields(), self.data, self)
|
||||
|
||||
def get_substitution_variables(self, minimal=False):
|
||||
d = {}
|
||||
|
@ -742,6 +742,10 @@ class FormData(StorableObject):
|
|||
# Workflow data have unknown purpose, do not store them in anonymised export
|
||||
if self.workflow_data and not anonymise:
|
||||
data['workflow']['data'] = self.workflow_data
|
||||
if self.formdef.workflow.get_backoffice_fields():
|
||||
data['workflow']['fields'] = get_json_dict(
|
||||
self.formdef.workflow.get_backoffice_fields(),
|
||||
self.data, include_files=include_files, anonymise=anonymise)
|
||||
|
||||
# add a roles dictionary, with workflow functions and two special
|
||||
# entries for concerned/actions roles.
|
||||
|
|
|
@ -292,7 +292,10 @@ class FormDef(StorableObject):
|
|||
rebuild_global_views=True)
|
||||
return t
|
||||
|
||||
def rebuild_views(self):
|
||||
def get_all_fields(self):
|
||||
return (self.fields or []) + self.workflow.get_backoffice_fields()
|
||||
|
||||
def rebuild(self):
|
||||
if get_publisher().is_using_postgresql():
|
||||
import sql
|
||||
sql.do_formdef_tables(self, rebuild_views=True,
|
||||
|
|
|
@ -420,10 +420,25 @@ class FormStatusPage(Directory):
|
|||
r += htmltext('<div class="field"><span class="label">%s</span>') % _('User name')
|
||||
r += htmltext('<span class="value">%s</span></div>') % user.display_name
|
||||
|
||||
r += self.display_fields(self.formdef.fields, form_url)
|
||||
|
||||
if show_status and self.formdef.is_user_allowed_read_status_and_history(
|
||||
get_request().user, self.filled):
|
||||
wf_status = self.filled.get_visible_status()
|
||||
if wf_status:
|
||||
r += htmltext('<div><span class="label">%s</span> ') % _('Status')
|
||||
r += htmltext('<span class="value">%s</span></div>') % wf_status.name
|
||||
|
||||
r += htmltext('</div>') # .dataview
|
||||
r += htmltext('</div>') # .bo-block
|
||||
|
||||
return r.getvalue()
|
||||
|
||||
def display_fields(self, fields, form_url=''):
|
||||
r = TemplateIO(html=True)
|
||||
on_page = False
|
||||
on_disabled_page = False
|
||||
for f in self.formdef.fields:
|
||||
|
||||
for f in fields:
|
||||
if f.type == 'page':
|
||||
on_disabled_page = False
|
||||
if not f.is_visible(self.filled.data, self.formdef):
|
||||
|
@ -485,17 +500,23 @@ class FormStatusPage(Directory):
|
|||
if on_page:
|
||||
r += htmltext('</div></div>')
|
||||
|
||||
if show_status and self.formdef.is_user_allowed_read_status_and_history(
|
||||
get_request().user, self.filled):
|
||||
wf_status = self.filled.get_visible_status()
|
||||
if wf_status:
|
||||
r += htmltext('<p><span class="label">%s</span> ') % _('Status')
|
||||
r += htmltext('<span class="value">%s</span></p>') % wf_status.name
|
||||
|
||||
r += htmltext('</div>') # .dataview
|
||||
r += htmltext('</div>') # .bo-block
|
||||
return r.getvalue()
|
||||
|
||||
def backoffice_fields_section(self):
|
||||
backoffice_fields = self.formdef.workflow.get_backoffice_fields()
|
||||
if not backoffice_fields:
|
||||
return
|
||||
content = self.display_fields(backoffice_fields)
|
||||
if not len(content):
|
||||
return
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<div class="bo-block">')
|
||||
r += htmltext('<h2 class="foldable">%s</h2>') % _('Backoffice Data')
|
||||
r += htmltext('<div class="dataview">')
|
||||
r += content
|
||||
r += htmltext('</div>')
|
||||
r += htmltext('</div>')
|
||||
return r.getvalue()
|
||||
|
||||
def status(self):
|
||||
object_key = 'formdata-%s-%s' % (self.formdef.url_name, self.filled.id)
|
||||
|
@ -545,6 +566,7 @@ class FormStatusPage(Directory):
|
|||
break
|
||||
|
||||
r += self.receipt(always_include_user=True, folded=folded)
|
||||
r += self.backoffice_fields_section()
|
||||
|
||||
r += self.history()
|
||||
|
||||
|
|
|
@ -1186,6 +1186,36 @@ div.WidgetDict div.content div.content input {
|
|||
width: calc(100% - 1em);
|
||||
}
|
||||
|
||||
div.SetBackofficeFieldsTableWidget table {
|
||||
width: 100%;
|
||||
border-spacing: 1ex;
|
||||
}
|
||||
|
||||
div.SetBackofficeFieldsTableWidget th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.SetBackofficeFieldsTableWidget td {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
div.SetBackofficeFieldsTableWidget td + td {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
div.SetBackofficeFieldsTableWidget br {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.SetBackofficeFieldsTableWidget td select,
|
||||
div.SetBackofficeFieldsTableWidget td input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
form table div.widget {
|
||||
margin-bottom: 1ex;
|
||||
}
|
||||
|
||||
div.ComputedExpressionWidget div.content {
|
||||
position: relative;
|
||||
}
|
||||
|
|
16
wcs/sql.py
16
wcs/sql.py
|
@ -378,7 +378,7 @@ def do_formdef_tables(formdef, conn=None, cur=None, rebuild_views=False, rebuild
|
|||
cur.execute('''ALTER TABLE %s ADD COLUMN criticality_level integer NOT NULL DEFAULT(0)''' % table_name)
|
||||
|
||||
# add new fields
|
||||
for field in formdef.fields:
|
||||
for field in formdef.get_all_fields():
|
||||
assert field.id is not None
|
||||
sql_type = SQL_TYPE_MAPPING.get(field.key, 'varchar')
|
||||
if sql_type is None:
|
||||
|
@ -461,7 +461,7 @@ def do_user_table():
|
|||
from admin.settings import UserFieldsFormDef
|
||||
formdef = UserFieldsFormDef()
|
||||
|
||||
for field in formdef.fields:
|
||||
for field in formdef.get_all_fields():
|
||||
sql_type = SQL_TYPE_MAPPING.get(field.key, 'varchar')
|
||||
if sql_type is None:
|
||||
continue
|
||||
|
@ -602,7 +602,7 @@ def do_views(formdef, conn, cur, rebuild_global_views=True):
|
|||
view_fields = get_view_fields(formdef)
|
||||
|
||||
column_names = {}
|
||||
for field in formdef.fields:
|
||||
for field in formdef.get_all_fields():
|
||||
field_key = 'f%s' % field.id
|
||||
if field.type in ('page', 'title', 'subtitle', 'comment'):
|
||||
continue
|
||||
|
@ -900,7 +900,7 @@ class SqlMixin(object):
|
|||
|
||||
def get_sql_dict_from_data(self, data, formdef):
|
||||
sql_dict = {}
|
||||
for field in formdef.fields:
|
||||
for field in formdef.get_all_fields():
|
||||
sql_type = SQL_TYPE_MAPPING.get(field.key, 'varchar')
|
||||
if sql_type is None:
|
||||
continue
|
||||
|
@ -932,7 +932,7 @@ class SqlMixin(object):
|
|||
i = len(cls._table_static_fields)
|
||||
if formdef.geolocations:
|
||||
i += len(formdef.geolocations.keys())
|
||||
for field in formdef.fields:
|
||||
for field in formdef.get_all_fields():
|
||||
sql_type = SQL_TYPE_MAPPING.get(field.key, 'varchar')
|
||||
if sql_type is None:
|
||||
continue
|
||||
|
@ -1220,7 +1220,7 @@ class SqlFormData(SqlMixin, wcs.formdata.FormData):
|
|||
fts_strings = [str(self.id)]
|
||||
if self.tracking_code:
|
||||
fts_strings.append(self.tracking_code)
|
||||
for field in self._formdef.fields:
|
||||
for field in self._formdef.get_all_fields():
|
||||
if not self.data.get(field.id):
|
||||
continue
|
||||
value = None
|
||||
|
@ -1275,7 +1275,7 @@ class SqlFormData(SqlMixin, wcs.formdata.FormData):
|
|||
@classmethod
|
||||
def get_data_fields(cls):
|
||||
data_fields = ['geoloc_%s' % x for x in (cls._formdef.geolocations or {}).keys()]
|
||||
for field in cls._formdef.fields:
|
||||
for field in cls._formdef.get_all_fields():
|
||||
sql_type = SQL_TYPE_MAPPING.get(field.key, 'varchar')
|
||||
if sql_type is None:
|
||||
continue
|
||||
|
@ -1492,7 +1492,7 @@ class SqlUser(SqlMixin, wcs.users.User):
|
|||
@classmethod
|
||||
def get_data_fields(cls):
|
||||
data_fields = []
|
||||
for field in cls.get_formdef().fields:
|
||||
for field in cls.get_formdef().get_all_fields():
|
||||
sql_type = SQL_TYPE_MAPPING.get(field.key, 'varchar')
|
||||
if sql_type is None:
|
||||
continue
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
# w.c.s. - web application for online forms
|
||||
# Copyright (C) 2005-2016 Entr'ouvert
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from quixote import get_publisher
|
||||
|
||||
from qommon import get_logger
|
||||
from qommon.form import WidgetListAsTable, CompositeWidget, SingleSelectWidget, ComputedExpressionWidget
|
||||
from wcs.workflows import XmlSerialisable, WorkflowStatusItem, register_item_class
|
||||
from wcs.wf.profile import FieldNode
|
||||
|
||||
|
||||
class SetBackofficeFieldRowWidget(CompositeWidget):
|
||||
def __init__(self, name, value=None, workflow=None, **kwargs):
|
||||
CompositeWidget.__init__(self, name, value, **kwargs)
|
||||
if not value:
|
||||
value = {}
|
||||
|
||||
fields = [('', '', '')]
|
||||
fields.extend([(x.id, x.label, x.id) for x in workflow.get_backoffice_fields()])
|
||||
self.add(SingleSelectWidget, name='field_id', title=_('Field'),
|
||||
value=value.get('field_id'),
|
||||
options=fields, **kwargs)
|
||||
self.add(ComputedExpressionWidget, name='value', title=_('Value'),
|
||||
value=value.get('value'))
|
||||
|
||||
def _parse(self, request):
|
||||
if self.get('value') and self.get('field_id'):
|
||||
self.value = {
|
||||
'value': self.get('value'),
|
||||
'field_id': self.get('field_id')
|
||||
}
|
||||
else:
|
||||
self.value = None
|
||||
|
||||
|
||||
class SetBackofficeFieldsTableWidget(WidgetListAsTable):
|
||||
readonly = False
|
||||
def __init__(self, name, **kwargs):
|
||||
super(SetBackofficeFieldsTableWidget, self).__init__(name,
|
||||
element_type=SetBackofficeFieldRowWidget,
|
||||
element_kwargs={'workflow': kwargs.pop('workflow')},
|
||||
**kwargs)
|
||||
|
||||
|
||||
class SetBackofficeFieldsWorkflowStatusItem(WorkflowStatusItem):
|
||||
description = N_('Set Backoffice Fields')
|
||||
key = 'set-backoffice-fields'
|
||||
|
||||
fields = None
|
||||
|
||||
@classmethod
|
||||
def is_available(cls, workflow=None):
|
||||
return bool(workflow and getattr(workflow.backoffice_fields_formdef, 'fields', None))
|
||||
|
||||
def get_parameters(self):
|
||||
return ('fields',)
|
||||
|
||||
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
|
||||
if 'fields' in parameters:
|
||||
form.add(SetBackofficeFieldsTableWidget, '%sfields' % prefix,
|
||||
title=_('Fields Update'), value=self.fields,
|
||||
workflow=self.parent.parent)
|
||||
|
||||
def perform(self, formdata):
|
||||
if not self.fields:
|
||||
return
|
||||
for field in self.fields:
|
||||
try:
|
||||
formdata.data['%s' % field['field_id']] = self.compute(
|
||||
field['value'], raises=True)
|
||||
except:
|
||||
get_publisher().notify_of_exception(sys.exc_info())
|
||||
formdata.store()
|
||||
|
||||
def fields_export_to_xml(self, item, charset, include_id=False):
|
||||
if not self.fields:
|
||||
return
|
||||
|
||||
fields_node = ET.SubElement(item, 'fields')
|
||||
for field in self.fields:
|
||||
fields_node.append(FieldNode(field).export_to_xml(charset=charset,
|
||||
include_id=include_id))
|
||||
|
||||
return fields_node
|
||||
|
||||
def fields_init_with_xml(self, elem, charset, include_id=False):
|
||||
fields = []
|
||||
if elem is None:
|
||||
return
|
||||
for field_xml_node in elem.findall('field'):
|
||||
field_node = FieldNode()
|
||||
field_node.init_with_xml(field_xml_node, charset,
|
||||
include_id=include_id)
|
||||
fields.append(field_node.as_dict())
|
||||
if fields:
|
||||
self.fields = fields
|
||||
|
||||
register_item_class(SetBackofficeFieldsWorkflowStatusItem)
|
|
@ -249,12 +249,39 @@ class WorkflowVariablesFieldsFormDef(FormDef):
|
|||
self.workflow.store()
|
||||
|
||||
|
||||
class WorkflowBackofficeFieldsFormDef(FormDef):
|
||||
'''Class to handle workflow backoffice fields, it loads and saves from/to
|
||||
the workflow object 'backoffice_fields_formdef' attribute.'''
|
||||
|
||||
field_prefix = 'bo'
|
||||
|
||||
def __init__(self, workflow):
|
||||
self.id = None
|
||||
self.name = workflow.name
|
||||
self.workflow = workflow
|
||||
if workflow.backoffice_fields_formdef and workflow.backoffice_fields_formdef.fields:
|
||||
self.fields = self.workflow.backoffice_fields_formdef.fields
|
||||
self.max_field_id = max([int(x.id[len(self.field_prefix):]) for x in self.fields])
|
||||
else:
|
||||
self.fields = []
|
||||
self.max_field_id = 0
|
||||
|
||||
def get_new_field_id(self):
|
||||
self.max_field_id += 1
|
||||
return '%s%s' % (self.field_prefix, self.max_field_id)
|
||||
|
||||
def store(self):
|
||||
self.workflow.backoffice_fields_formdef = self
|
||||
self.workflow.store()
|
||||
|
||||
|
||||
class Workflow(StorableObject):
|
||||
_names = 'workflows'
|
||||
name = None
|
||||
possible_status = None
|
||||
roles = None
|
||||
variables_formdef = None
|
||||
backoffice_fields_formdef = None
|
||||
global_actions = None
|
||||
criticality_levels = None
|
||||
|
||||
|
@ -286,16 +313,28 @@ class Workflow(StorableObject):
|
|||
self.store()
|
||||
|
||||
def store(self):
|
||||
must_update_views = False
|
||||
must_update = False
|
||||
if self.id:
|
||||
old_self = self.get(self.id, ignore_errors=True, ignore_migration=True)
|
||||
if old_self:
|
||||
old_endpoints = set([x.id for x in old_self.get_endpoint_status()])
|
||||
if old_endpoints != set([x.id for x in self.get_endpoint_status()]):
|
||||
must_update_views = True
|
||||
must_update = True
|
||||
old_criticality_levels = len(old_self.criticality_levels or [0])
|
||||
if old_criticality_levels != len(self.criticality_levels or [0]):
|
||||
must_update_views = True
|
||||
must_update = True
|
||||
try:
|
||||
old_backoffice_fields = old_self.backoffice_fields_formdef.fields
|
||||
except AttributeError:
|
||||
old_backoffice_fields = []
|
||||
try:
|
||||
new_backoffice_fields = self.backoffice_fields_formdef.fields
|
||||
except AttributeError:
|
||||
new_backoffice_fields = []
|
||||
if len(old_backoffice_fields) != len(new_backoffice_fields):
|
||||
must_update = True
|
||||
elif self.backoffice_fields_formdef:
|
||||
must_update = True
|
||||
|
||||
self.last_modification_time = time.localtime()
|
||||
if get_request() and get_request().user:
|
||||
|
@ -304,12 +343,11 @@ class Workflow(StorableObject):
|
|||
self.last_modification_user_id = None
|
||||
StorableObject.store(self)
|
||||
|
||||
# instruct all related formdefs to update their security rules, and
|
||||
# their views if endpoints have changed.
|
||||
# instruct all related formdefs to update.
|
||||
for form in FormDef.select(lambda x: x.workflow_id == self.id, ignore_migration=True):
|
||||
form.data_class().rebuild_security()
|
||||
if must_update_views:
|
||||
form.rebuild_views()
|
||||
if must_update:
|
||||
form.rebuild()
|
||||
|
||||
@classmethod
|
||||
def get(cls, id, ignore_errors=False, ignore_migration=False):
|
||||
|
@ -340,6 +378,11 @@ class Workflow(StorableObject):
|
|||
return status
|
||||
raise KeyError()
|
||||
|
||||
def get_backoffice_fields(self):
|
||||
if self.backoffice_fields_formdef:
|
||||
return self.backoffice_fields_formdef.fields or []
|
||||
return []
|
||||
|
||||
def add_global_action(self, name, id=None):
|
||||
if [x for x in self.global_actions if x.name == name]:
|
||||
raise DuplicateGlobalActionNameError()
|
||||
|
@ -475,6 +518,14 @@ class Workflow(StorableObject):
|
|||
for field in self.variables_formdef.fields:
|
||||
fields.append(field.export_to_xml(charset=charset, include_id=include_id))
|
||||
|
||||
if self.backoffice_fields_formdef:
|
||||
variables = ET.SubElement(root, 'backoffice-fields')
|
||||
formdef = ET.SubElement(variables, 'formdef')
|
||||
ET.SubElement(formdef, 'name').text = '-' # required by formdef xml import
|
||||
fields = ET.SubElement(formdef, 'fields')
|
||||
for field in self.backoffice_fields_formdef.fields:
|
||||
fields.append(field.export_to_xml(charset=charset, include_id=include_id))
|
||||
|
||||
return root
|
||||
|
||||
@classmethod
|
||||
|
@ -546,6 +597,14 @@ class Workflow(StorableObject):
|
|||
imported_formdef = FormDef.import_from_xml_tree(formdef, include_id=True)
|
||||
workflow.variables_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow)
|
||||
workflow.variables_formdef.fields = imported_formdef.fields
|
||||
|
||||
variables = tree.find('backoffice-fields')
|
||||
if variables is not None:
|
||||
formdef = variables.find('backoffice-fields')
|
||||
imported_formdef = FormDef.import_from_xml_tree(formdef, include_id=True)
|
||||
workflow.backoffice_fields_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow)
|
||||
workflow.backoffice_fields_formdef.fields = imported_formdef.fields
|
||||
|
||||
return workflow
|
||||
|
||||
def get_list_of_roles(self, include_logged_in_users=True):
|
||||
|
@ -2233,5 +2292,6 @@ def load_extra():
|
|||
import wf.resubmit
|
||||
import wf.criticality
|
||||
import wf.profile
|
||||
import wf.backoffice_fields
|
||||
|
||||
from wf.export_to_model import ExportToModel
|
||||
|
|
Loading…
Reference in New Issue