general: add support for backoffice fields (#8273)

This commit is contained in:
Frédéric Péters 2016-06-06 18:55:21 +02:00
parent 88380bc58e
commit 687a2456ce
15 changed files with 531 additions and 39 deletions

View File

@ -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()

View File

@ -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]

View File

@ -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

View File

@ -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'

View File

@ -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'}]

View File

@ -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'

View File

@ -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;"/>')

View File

@ -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':

View File

@ -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.

View File

@ -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,

View File

@ -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()

View File

@ -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;
}

View File

@ -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

114
wcs/wf/backoffice_fields.py Normal file
View File

@ -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)

View File

@ -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