0001-general-add-support-for-backoffice-fields-8273.patch
tests/test_admin_pages.py | ||
---|---|---|
1556 | 1556 |
assert Workflow.get(1).variables_formdef.fields[0].key == 'string' |
1557 | 1557 |
assert Workflow.get(1).variables_formdef.fields[0].varname == '1*1*message' |
1558 | 1558 | |
1559 |
def test_workflows_backoffice_fields(pub): |
|
1560 |
create_superuser(pub) |
|
1561 |
create_role() |
|
1562 | ||
1563 |
Workflow.wipe() |
|
1564 |
workflow = Workflow(name='foo') |
|
1565 |
workflow.store() |
|
1566 | ||
1567 |
formdef = FormDef() |
|
1568 |
formdef.name = 'form title' |
|
1569 |
formdef.workflow_id = workflow.id |
|
1570 |
formdef.fields = [] |
|
1571 |
formdef.store() |
|
1572 | ||
1573 |
app = login(get_app(pub)) |
|
1574 |
resp = app.get('/backoffice/workflows/1/') |
|
1575 |
resp = resp.click(href='backoffice-fields/') |
|
1576 |
assert resp.location == 'http://example.net/backoffice/workflows/1/backoffice-fields/fields/' |
|
1577 |
resp = resp.follow() |
|
1578 | ||
1579 |
# makes sure we can't add page fields |
|
1580 |
assert 'value="New Page"' not in resp.body |
|
1581 | ||
1582 |
# add a simple field |
|
1583 |
resp.forms[0]['label'] = 'foobar' |
|
1584 |
resp.forms[0]['type'] = 'Text (line)' |
|
1585 |
resp = resp.forms[0].submit() |
|
1586 |
assert resp.location == 'http://example.net/backoffice/workflows/1/backoffice-fields/fields/' |
|
1587 |
resp = resp.follow() |
|
1588 | ||
1589 |
# check it's been saved correctly |
|
1590 |
assert 'foobar' in resp.body |
|
1591 |
assert len(Workflow.get(1).backoffice_fields_formdef.fields) == 1 |
|
1592 |
assert Workflow.get(1).backoffice_fields_formdef.fields[0].id.startswith('bo') |
|
1593 |
assert Workflow.get(1).backoffice_fields_formdef.fields[0].key == 'string' |
|
1594 |
assert Workflow.get(1).backoffice_fields_formdef.fields[0].label == 'foobar' |
|
1595 | ||
1596 |
backoffice_field_id = Workflow.get(1).backoffice_fields_formdef.fields[0].id |
|
1597 |
formdef = FormDef.get(formdef.id) |
|
1598 |
data_class = formdef.data_class() |
|
1599 |
data_class.wipe() |
|
1600 |
formdata = data_class() |
|
1601 |
formdata.data = {backoffice_field_id: 'HELLO'} |
|
1602 |
formdata.status = 'wf-new' |
|
1603 |
formdata.store() |
|
1604 | ||
1605 |
assert data_class.get(formdata.id).data[backoffice_field_id] == 'HELLO' |
|
1606 | ||
1607 | ||
1559 | 1608 |
def test_workflows_functions(pub): |
1560 | 1609 |
create_superuser(pub) |
1561 | 1610 |
create_role() |
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_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_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_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_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>') |
... | ... | |
1036 | 1036 |
fields.append(FakeField('submission_channel', 'submission_channel', _('Channel'))) |
1037 | 1037 |
fields.append(FakeField('time', 'time', _('Time'))) |
1038 | 1038 |
fields.append(FakeField('user-label', 'user-label', _('User Label'))) |
1039 |
fields.extend(self.formdef.fields)
|
|
1039 |
fields.extend(self.formdef.get_fields())
|
|
1040 | 1040 |
fields.append(FakeField('status', 'status', _('Status'))) |
1041 | 1041 |
fields.append(FakeField('anonymised', 'anonymised', _('Anonymised'))) |
1042 | 1042 | |
... | ... | |
1046 | 1046 |
field_ids = [x for x in get_request().form.keys()] |
1047 | 1047 |
if not field_ids or ignore_form: |
1048 | 1048 |
field_ids = ['id', 'time', 'user-label'] |
1049 |
for field in self.formdef.fields:
|
|
1049 |
for field in self.formdef.get_fields():
|
|
1050 | 1050 |
if hasattr(field, str('get_view_value')) and field.in_listing: |
1051 | 1051 |
field_ids.append(field.id) |
1052 | 1052 |
field_ids.append('status') |
... | ... | |
1636 | 1636 |
had_page = False |
1637 | 1637 |
last_page = None |
1638 | 1638 |
last_title = None |
1639 |
for f in self.formdef.fields:
|
|
1639 |
for f in self.formdef.get_fields():
|
|
1640 | 1640 |
if excluded_fields and f.id in excluded_fields: |
1641 | 1641 |
continue |
1642 | 1642 |
if f.type == 'page': |
wcs/formdef.py | ||
---|---|---|
292 | 292 |
rebuild_global_views=True) |
293 | 293 |
return t |
294 | 294 | |
295 |
def rebuild_views(self): |
|
295 |
def get_fields(self): |
|
296 |
workflow_fields = [] |
|
297 |
try: |
|
298 |
workflow_fields = self.workflow.backoffice_fields_formdef.fields |
|
299 |
except AttributeError: |
|
300 |
pass |
|
301 |
return (self.fields or []) + workflow_fields |
|
302 | ||
303 |
def rebuild(self): |
|
296 | 304 |
if get_publisher().is_using_postgresql(): |
297 | 305 |
import sql |
298 | 306 |
sql.do_formdef_tables(self, rebuild_views=True, |
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_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_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_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_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_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_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_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_fields():
|
|
1496 | 1496 |
sql_type = SQL_TYPE_MAPPING.get(field.key, 'varchar') |
1497 | 1497 |
if sql_type is None: |
1498 | 1498 |
continue |
wcs/workflows.py | ||
---|---|---|
246 | 246 |
self.workflow.store() |
247 | 247 | |
248 | 248 | |
249 |
class WorkflowBackofficeFieldsFormDef(FormDef): |
|
250 |
'''Class to handle workflow backoffice fields, it loads and saves from/to |
|
251 |
the workflow object 'backoffice_fields_formdef' attribute.''' |
|
252 | ||
253 |
def __init__(self, workflow): |
|
254 |
self.id = None |
|
255 |
self.name = workflow.name |
|
256 |
self.workflow = workflow |
|
257 |
if workflow.backoffice_fields_formdef and workflow.backoffice_fields_formdef.fields: |
|
258 |
self.fields = self.workflow.backoffice_fields_formdef.fields |
|
259 |
self.max_field_id = max([lax_int(x.id) for x in self.fields or []]) |
|
260 |
else: |
|
261 |
self.fields = [] |
|
262 | ||
263 |
def get_new_field_id(self): |
|
264 |
if self.max_field_id is None: |
|
265 |
field_id = 1 |
|
266 |
else: |
|
267 |
field_id = self.max_field_id + 1 |
|
268 |
self.max_field_id = field_id |
|
269 |
return 'bo%s' % field_id |
|
270 | ||
271 |
def store(self): |
|
272 |
self.workflow.backoffice_fields_formdef = self |
|
273 |
self.workflow.store() |
|
274 | ||
275 | ||
249 | 276 |
class Workflow(StorableObject): |
250 | 277 |
_names = 'workflows' |
251 | 278 |
name = None |
252 | 279 |
possible_status = None |
253 | 280 |
roles = None |
254 | 281 |
variables_formdef = None |
282 |
backoffice_fields_formdef = None |
|
255 | 283 |
global_actions = None |
256 | 284 |
criticality_levels = None |
257 | 285 | |
... | ... | |
283 | 311 |
self.store() |
284 | 312 | |
285 | 313 |
def store(self): |
286 |
must_update_views = False
|
|
314 |
must_update = False |
|
287 | 315 |
if self.id: |
288 | 316 |
old_self = self.get(self.id, ignore_errors=True, ignore_migration=True) |
289 | 317 |
if old_self: |
290 | 318 |
old_endpoints = set([x.id for x in old_self.get_endpoint_status()]) |
291 | 319 |
if old_endpoints != set([x.id for x in self.get_endpoint_status()]): |
292 |
must_update_views = True
|
|
320 |
must_update = True |
|
293 | 321 |
old_criticality_levels = len(old_self.criticality_levels or [0]) |
294 | 322 |
if old_criticality_levels != len(self.criticality_levels or [0]): |
295 |
must_update_views = True |
|
323 |
must_update = True |
|
324 |
try: |
|
325 |
old_backoffice_fields = old_self.backoffice_fields_formdef.fields |
|
326 |
except AttributeError: |
|
327 |
old_backoffice_fields = [] |
|
328 |
try: |
|
329 |
new_backoffice_fields = self.backoffice_fields_formdef.fields |
|
330 |
except AttributeError: |
|
331 |
new_backoffice_fields = [] |
|
332 |
if len(old_backoffice_fields) != len(new_backoffice_fields): |
|
333 |
must_update = True |
|
334 |
elif self.backoffice_fields_formdef: |
|
335 |
must_update = True |
|
296 | 336 | |
297 | 337 |
self.last_modification_time = time.localtime() |
298 | 338 |
if get_request() and get_request().user: |
... | ... | |
301 | 341 |
self.last_modification_user_id = None |
302 | 342 |
StorableObject.store(self) |
303 | 343 | |
304 |
# instruct all related formdefs to update their security rules, and |
|
305 |
# their views if endpoints have changed. |
|
344 |
# instruct all related formdefs to update. |
|
306 | 345 |
for form in FormDef.select(lambda x: x.workflow_id == self.id, ignore_migration=True): |
307 | 346 |
form.data_class().rebuild_security() |
308 |
if must_update_views:
|
|
309 |
form.rebuild_views()
|
|
347 |
if must_update: |
|
348 |
form.rebuild() |
|
310 | 349 | |
311 | 350 |
@classmethod |
312 | 351 |
def get(cls, id, ignore_errors=False, ignore_migration=False): |
... | ... | |
472 | 511 |
for field in self.variables_formdef.fields: |
473 | 512 |
fields.append(field.export_to_xml(charset=charset, include_id=include_id)) |
474 | 513 | |
514 |
if self.backoffice_fields_formdef: |
|
515 |
variables = ET.SubElement(root, 'backoffice-fields') |
|
516 |
formdef = ET.SubElement(variables, 'formdef') |
|
517 |
ET.SubElement(formdef, 'name').text = '-' # required by formdef xml import |
|
518 |
fields = ET.SubElement(formdef, 'fields') |
|
519 |
for field in self.backoffice_fields_formdef.fields: |
|
520 |
fields.append(field.export_to_xml(charset=charset, include_id=include_id)) |
|
521 | ||
475 | 522 |
return root |
476 | 523 | |
477 | 524 |
@classmethod |
... | ... | |
543 | 590 |
imported_formdef = FormDef.import_from_xml_tree(formdef, include_id=True) |
544 | 591 |
workflow.variables_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow) |
545 | 592 |
workflow.variables_formdef.fields = imported_formdef.fields |
593 | ||
594 |
variables = tree.find('backoffice-fields') |
|
595 |
if variables is not None: |
|
596 |
formdef = variables.find('backoffice-fields') |
|
597 |
imported_formdef = FormDef.import_from_xml_tree(formdef, include_id=True) |
|
598 |
workflow.backoffice_fields_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow) |
|
599 |
workflow.backoffice_fields_formdef.fields = imported_formdef.fields |
|
600 | ||
546 | 601 |
return workflow |
547 | 602 | |
548 | 603 |
def get_list_of_roles(self, include_logged_in_users=True): |
... | ... | |
2229 | 2284 |
import wf.resubmit |
2230 | 2285 |
import wf.criticality |
2231 | 2286 |
import wf.profile |
2287 |
import wf.set_backoffice_variable |
|
2232 | 2288 | |
2233 | 2289 |
from wf.export_to_model import ExportToModel |
2234 |
- |