0001-general-add-support-for-global-actions-3659.patch
tests/test_admin_pages.py | ||
---|---|---|
1469 | 1469 |
assert set(Workflow.get(workflow.id).possible_status[2].visibility) == set( |
1470 | 1470 |
['_receiver', '_other-function']) |
1471 | 1471 | |
1472 |
def test_workflows_global_actions(pub): |
|
1473 |
create_superuser(pub) |
|
1474 |
create_role() |
|
1475 | ||
1476 |
Workflow.wipe() |
|
1477 |
workflow = Workflow(name='foo') |
|
1478 |
workflow.store() |
|
1479 | ||
1480 |
app = login(get_app(pub)) |
|
1481 |
resp = app.get('/backoffice/workflows/%s/' % workflow.id) |
|
1482 |
resp = resp.click('add global action') |
|
1483 |
resp = resp.forms[0].submit('cancel') |
|
1484 |
assert not Workflow.get(workflow.id).global_actions |
|
1485 | ||
1486 |
resp = app.get('/backoffice/workflows/%s/' % workflow.id) |
|
1487 |
resp = resp.click('add global action') |
|
1488 |
resp.forms[0]['name'] = 'Global Action' |
|
1489 |
resp = resp.forms[0].submit('submit') |
|
1490 |
assert Workflow.get(workflow.id).global_actions[0].name == 'Global Action' |
|
1491 | ||
1492 |
# test rename |
|
1493 |
resp = app.get('/backoffice/workflows/%s/' % workflow.id) |
|
1494 |
resp = resp.click('Global Action') |
|
1495 |
resp = resp.click('Change Action Name') |
|
1496 |
resp.forms[0]['name'] = 'Renamed Action' |
|
1497 |
resp = resp.forms[0].submit('submit') |
|
1498 |
assert Workflow.get(workflow.id).global_actions[0].name == 'Renamed Action' |
|
1499 | ||
1500 |
# test removal |
|
1501 |
resp = app.get('/backoffice/workflows/%s/' % workflow.id) |
|
1502 |
resp = resp.click('Renamed Action') |
|
1503 |
resp = resp.click('Delete') |
|
1504 |
resp = resp.forms[0].submit('delete') |
|
1505 |
assert not Workflow.get(workflow.id).global_actions |
|
1506 | ||
1507 |
def test_workflows_global_actions_edit(pub): |
|
1508 |
create_superuser(pub) |
|
1509 |
create_role() |
|
1510 | ||
1511 |
Workflow.wipe() |
|
1512 |
workflow = Workflow(name='foo') |
|
1513 |
workflow.store() |
|
1514 | ||
1515 |
app = login(get_app(pub)) |
|
1516 |
resp = app.get('/backoffice/workflows/%s/' % workflow.id) |
|
1517 |
resp = resp.click('add global action') |
|
1518 |
resp.forms[0]['name'] = 'Global Action' |
|
1519 |
resp = resp.forms[0].submit('submit') |
|
1520 |
resp = resp.follow() |
|
1521 | ||
1522 |
resp = resp.click('Global Action') |
|
1523 | ||
1524 |
# test adding all actions |
|
1525 |
for action in [x[0] for x in resp.forms[0]['type'].options]: |
|
1526 |
resp.forms[0]['type'] = action |
|
1527 |
resp = resp.forms[0].submit() |
|
1528 |
resp = resp.follow() |
|
1529 | ||
1530 |
# test visiting |
|
1531 |
action_id = Workflow.get(workflow.id).global_actions[0].id |
|
1532 |
for item in Workflow.get(workflow.id).global_actions[0].items: |
|
1533 |
resp = app.get('/backoffice/workflows/%s/global-actions/%s/items/%s/' % ( |
|
1534 |
workflow.id, action_id, item.id)) |
|
1535 | ||
1536 |
# test modifying a trigger |
|
1537 |
resp = app.get('/backoffice/workflows/%s/' % workflow.id) |
|
1538 |
resp = resp.click('Global Action') |
|
1539 |
assert len(Workflow.get(workflow.id).global_actions[0].triggers) == 1 |
|
1540 |
resp = resp.click(href='triggers/1/', index=0) |
|
1541 |
assert resp.form['roles$element0'].value == 'None' |
|
1542 |
resp.form['roles$element0'].value = '_receiver' |
|
1543 |
resp = resp.form.submit('submit') |
|
1544 |
assert Workflow.get(workflow.id).global_actions[0].triggers[0].roles == ['_receiver'] |
|
1545 | ||
1546 |
resp = app.get('/backoffice/workflows/%s/' % workflow.id) |
|
1547 |
resp = resp.click('Global Action') |
|
1548 |
resp = resp.click(href='triggers/1/', index=0) |
|
1549 |
assert resp.form['roles$element0'].value == '_receiver' |
|
1550 |
resp.form['roles$element1'].value = '_submitter' |
|
1551 |
resp = resp.form.submit('submit') |
|
1552 |
assert Workflow.get(workflow.id).global_actions[0].triggers[0].roles == [ |
|
1553 |
'_receiver', '_submitter'] |
|
1554 | ||
1555 |
resp = app.get('/backoffice/workflows/%s/' % workflow.id) |
|
1556 |
resp = resp.click('Global Action') |
|
1557 |
resp = resp.click(href='triggers/1/', index=0) |
|
1558 |
resp.form['roles$element1'].value = 'None' |
|
1559 |
resp = resp.form.submit('submit') |
|
1560 |
assert Workflow.get(workflow.id).global_actions[0].triggers[0].roles == ['_receiver'] |
|
1561 | ||
1472 | 1562 |
def test_users(pub): |
1473 | 1563 |
create_superuser(pub) |
1474 | 1564 |
app = login(get_app(pub)) |
tests/test_backoffice_pages.py | ||
---|---|---|
18 | 18 |
from wcs.roles import Role |
19 | 19 |
from wcs.workflows import (Workflow, CommentableWorkflowStatusItem, |
20 | 20 |
ChoiceWorkflowStatusItem, EditableWorkflowStatusItem) |
21 |
from wcs.wf.jump import JumpWorkflowStatusItem |
|
22 |
from wcs.wf.register_comment import RegisterCommenterWorkflowStatusItem |
|
21 | 23 |
from wcs.wf.wscall import WebserviceCallStatusItem |
22 | 24 |
from wcs.categories import Category |
23 | 25 |
from wcs.formdef import FormDef |
... | ... | |
477 | 479 |
assert FormDef.get_by_urlname('form-title').data_class().get(number31).status == 'wf-accepted' |
478 | 480 |
assert 'HELLO WORLD' in resp.body |
479 | 481 | |
482 |
def test_backoffice_handling_global_action(pub): |
|
483 |
create_user(pub) |
|
484 |
create_environment(pub) |
|
485 | ||
486 |
formdef = FormDef() |
|
487 |
formdef.name = 'test global action' |
|
488 |
formdef.fields = [] |
|
489 | ||
490 |
workflow = Workflow.get_default_workflow() |
|
491 |
workflow.id = '2' |
|
492 |
action = workflow.add_global_action('FOOBAR') |
|
493 |
register_comment = action.append_item('register-comment') |
|
494 |
register_comment.comment = 'HELLO WORLD GLOBAL ACTION' |
|
495 |
jump = action.append_item('jump') |
|
496 |
jump.status = 'finished' |
|
497 |
trigger = action.triggers[0] |
|
498 |
trigger.roles = [x.id for x in Role.select() if x.name == 'foobar'] |
|
499 | ||
500 |
workflow.store() |
|
501 |
formdef.workflow_id = workflow.id |
|
502 |
formdef.workflow_roles = {'_receiver': 1} |
|
503 |
formdef.store() |
|
504 | ||
505 |
formdata = formdef.data_class()() |
|
506 |
formdata.just_created() |
|
507 |
formdata.store() |
|
508 | ||
509 |
app = login(get_app(pub)) |
|
510 |
resp = app.get('/backoffice/management/%s/%s/' % (formdef.url_name, formdata.id)) |
|
511 |
assert 'button-action-1' in resp.form.fields |
|
512 |
resp = resp.form.submit('button-action-1') |
|
513 | ||
514 |
resp = app.get('/backoffice/management/%s/%s/' % (formdef.url_name, formdata.id)) |
|
515 |
assert 'HELLO WORLD GLOBAL ACTION' in resp.body |
|
516 |
assert formdef.data_class().get(formdata.id).status == 'wf-finished' |
|
517 | ||
480 | 518 |
def test_backoffice_submission_context(pub): |
481 | 519 |
user = create_user(pub) |
482 | 520 |
create_environment(pub) |
tests/test_workflow_import.py | ||
---|---|---|
8 | 8 | |
9 | 9 |
from wcs.workflows import Workflow, CommentableWorkflowStatusItem |
10 | 10 |
from wcs.wf.wscall import WebserviceCallStatusItem |
11 |
from wcs.wf.register_comment import RegisterCommenterWorkflowStatusItem |
|
11 | 12 |
from wcs.roles import Role |
12 | 13 |
from wcs.fields import StringField |
13 | 14 | |
... | ... | |
316 | 317 |
wf2 = assert_import_export_works(wf) |
317 | 318 |
assert wf2.possible_status[0].backoffice_info_text == '<p>Foo</p>' |
318 | 319 |
assert wf2.possible_status[0].items[0].backoffice_info_text == '<p>Bar</p>' |
320 | ||
321 |
def test_global_actions(): |
|
322 |
role = Role() |
|
323 |
role.id = '5' |
|
324 |
role.name = 'Test Role' |
|
325 |
role.store() |
|
326 | ||
327 |
wf = Workflow(name='global actions') |
|
328 |
ac1 = wf.add_global_action('Action', 'ac1') |
|
329 |
ac1.backoffice_info_text = '<p>Foo</p>' |
|
330 | ||
331 |
add_to_journal = RegisterCommenterWorkflowStatusItem() |
|
332 |
add_to_journal.id = '_add_to_journal' |
|
333 |
add_to_journal.comment = 'HELLO WORLD' |
|
334 |
ac1.items.append(add_to_journal) |
|
335 |
add_to_journal.parent = ac1 |
|
336 | ||
337 |
trigger = ac1.triggers[0] |
|
338 |
assert trigger.key == 'manual' |
|
339 |
trigger.roles = [role.id] |
|
340 | ||
341 |
wf2 = assert_import_export_works(wf) |
|
342 |
assert wf2.global_actions[0].triggers[0].roles == [role.id] |
|
343 | ||
344 |
wf2 = assert_import_export_works(wf, True) |
wcs/admin/workflows.py | ||
---|---|---|
229 | 229 |
class WorkflowItemPage(Directory): |
230 | 230 |
_q_exports = ['', 'delete'] |
231 | 231 | |
232 |
def __init__(self, workflow, status, component, html_top):
|
|
232 |
def __init__(self, workflow, parent, component, html_top):
|
|
233 | 233 |
try: |
234 |
self.item = [x for x in status.items if x.id == component][0]
|
|
234 |
self.item = [x for x in parent.items if x.id == component][0]
|
|
235 | 235 |
except (IndexError, ValueError): |
236 | 236 |
raise errors.TraversalError() |
237 | 237 |
self.workflow = workflow |
238 |
self.status = status
|
|
238 |
self.parent = parent
|
|
239 | 239 |
self.html_top = html_top |
240 | 240 |
get_response().breadcrumb.append(('items/%s/' % component, _(self.item.description))) |
241 | 241 | |
... | ... | |
259 | 259 |
if not form.get_submit() == 'submit' or form.has_errors(): |
260 | 260 |
self.html_top('%s - %s' % (_('Workflow'), self.workflow.name)) |
261 | 261 |
r = TemplateIO(html=True) |
262 |
r += htmltext('<h2>%s - %s</h2>') % (self.workflow.name, self.status.name)
|
|
262 |
r += htmltext('<h2>%s - %s</h2>') % (self.workflow.name, self.parent.name)
|
|
263 | 263 |
r += htmltext('<h3>%s</h3>') % _(self.item.description) |
264 | 264 |
r += form.render() |
265 | 265 |
if self.item.support_substitution_variables: |
... | ... | |
286 | 286 |
r += form.render() |
287 | 287 |
return r.getvalue() |
288 | 288 |
else: |
289 |
del self.status.items[self.status.items.index(self.item)]
|
|
289 |
del self.parent.items[self.parent.items.index(self.item)]
|
|
290 | 290 |
self.workflow.store() |
291 | 291 |
return redirect('../../') |
292 | 292 | |
293 | 293 |
def _q_lookup(self, component): |
294 |
t = self.item.q_admin_lookup(self.workflow, self.status, component,
|
|
294 |
t = self.item.q_admin_lookup(self.workflow, self.parent, component,
|
|
295 | 295 |
self.html_top) |
296 | 296 |
if t: |
297 | 297 |
return t |
298 | 298 |
return Directory._q_lookup(self, component) |
299 | 299 | |
300 | 300 | |
301 |
class WorkflowItemsDir(Directory): |
|
301 |
class GlobalActionTriggerPage(Directory): |
|
302 |
_q_exports = ['', 'delete'] |
|
303 | ||
304 |
def __init__(self, workflow, action, component, html_top): |
|
305 |
try: |
|
306 |
self.trigger = [x for x in action.triggers if x.id == component][0] |
|
307 |
except (IndexError, ValueError): |
|
308 |
raise errors.TraversalError() |
|
309 |
self.workflow = workflow |
|
310 |
self.action = action |
|
311 |
self.status = action |
|
312 |
self.html_top = html_top |
|
313 |
get_response().breadcrumb.append(('triggers/%s/' % component, _('Trigger'))) |
|
314 | ||
315 |
def _q_index(self): |
|
316 |
form = self.trigger.form(self.workflow) |
|
317 |
form.add_submit('submit', _('Save')) |
|
318 |
form.add_submit('cancel', _('Cancel')) |
|
319 | ||
320 |
if form.get_widget('cancel').parse(): |
|
321 |
return redirect('..') |
|
322 | ||
323 |
if form.get_submit() == 'submit' and not form.has_errors(): |
|
324 |
self.trigger.submit(form) |
|
325 |
if not form.has_errors(): |
|
326 |
self.workflow.store() |
|
327 |
return redirect('../../') |
|
328 | ||
329 |
self.html_top('%s - %s' % (_('Workflow'), self.workflow.name)) |
|
330 |
r = TemplateIO(html=True) |
|
331 |
r += htmltext('<h2>%s - %s</h2>') % (self.workflow.name, self.action.name) |
|
332 |
r += form.render() |
|
333 |
return r.getvalue() |
|
334 | ||
335 |
def delete(self): |
|
336 |
form = Form(enctype='multipart/form-data') |
|
337 |
form.widgets.append(HtmlWidget('<p>%s</p>' % _('You are about to remove a trigger.'))) |
|
338 |
form.add_submit('delete', _('Submit')) |
|
339 |
form.add_submit('cancel', _('Cancel')) |
|
340 |
if form.get_widget('cancel').parse(): |
|
341 |
return redirect('../../') |
|
342 |
if not form.is_submitted() or form.has_errors(): |
|
343 |
get_response().breadcrumb.append(('delete', _('Delete'))) |
|
344 |
self.html_top(title=_('Delete Trigger')) |
|
345 |
r = TemplateIO(html=True) |
|
346 |
r += htmltext('<h2>%s</h2>') % _('Deleting Trigger') |
|
347 |
r += form.render() |
|
348 |
return r.getvalue() |
|
349 |
else: |
|
350 |
del self.action.triggers[self.action.triggers.index(self.trigger)] |
|
351 |
self.workflow.store() |
|
352 |
return redirect('../../') |
|
353 | ||
354 |
class ToChildDirectory(Directory): |
|
302 | 355 |
_q_exports = [''] |
356 |
klass = None |
|
303 | 357 | |
304 | 358 |
def __init__(self, workflow, status, html_top): |
305 | 359 |
self.workflow = workflow |
... | ... | |
307 | 361 |
self.html_top = html_top |
308 | 362 | |
309 | 363 |
def _q_lookup(self, component): |
310 |
return WorkflowItemPage(self.workflow, self.status, component, |
|
311 |
self.html_top) |
|
364 |
return self.klass(self.workflow, self.status, component, self.html_top) |
|
312 | 365 | |
313 | 366 |
def _q_index(self): |
314 | 367 |
return redirect('..') |
315 | 368 | |
369 | ||
370 |
class WorkflowItemsDir(ToChildDirectory): |
|
371 |
klass = WorkflowItemPage |
|
372 | ||
373 | ||
374 |
class GlobalActionTriggersDir(ToChildDirectory): |
|
375 |
klass = GlobalActionTriggerPage |
|
376 | ||
377 | ||
378 |
class GlobalActionItemsDir(ToChildDirectory): |
|
379 |
klass = WorkflowItemPage |
|
380 | ||
381 | ||
316 | 382 |
class WorkflowStatusPage(Directory): |
317 | 383 |
_q_exports = ['', 'delete', 'newitem', ('items', 'items_dir'), |
318 | 384 |
'update_order', 'edit', 'reassign', 'visibility', |
... | ... | |
341 | 407 |
r += htmltext('%s - %s</h2>') % (self.workflow.name, self.status.name) |
342 | 408 |
r += get_session().display_message() |
343 | 409 | |
344 |
r += htmltext('<div class="bo-block">') |
|
345 |
r += htmltext('<h3>%s ') % _('Possible Status:') |
|
346 |
r += htmltext('%s</h3>') % self.status.name |
|
347 |
r += htmltext('</div>') |
|
348 | ||
349 | 410 |
if self.status.get_visibility_restricted_roles(): |
350 | 411 |
r += htmltext('<div class="bo-block">') |
351 | 412 |
r += _('This status is hidden from the user.') |
... | ... | |
411 | 472 | |
412 | 473 |
return r.getvalue() |
413 | 474 | |
414 | ||
415 | 475 |
def get_sidebar(self): |
416 | 476 |
get_response().add_javascript(['jquery.js', 'jquery-ui.js', 'popup.js', |
417 | 477 |
'jquery.colourpicker.js']) |
... | ... | |
437 | 497 |
r += htmltext('</div>') |
438 | 498 |
return r.getvalue() |
439 | 499 | |
500 |
def is_item_available(self, item): |
|
501 |
return item.is_available() |
|
502 | ||
440 | 503 |
def get_new_item_form(self): |
441 | 504 |
form = Form(enctype='multipart/form-data', action = 'newitem') |
442 |
available_items = [x for x in item_classes if x.is_available()]
|
|
505 |
available_items = [x for x in item_classes if self.is_item_available(x)]
|
|
443 | 506 |
def cmp_items(x, y): |
444 | 507 |
t = cmp(x.category and x.category[0], y.category and y.category[0]) |
445 | 508 |
if t: |
... | ... | |
460 | 523 |
self.workflow.store() |
461 | 524 |
return 'ok' |
462 | 525 | |
463 | ||
464 | 526 |
def newitem(self): |
465 | 527 |
form = self.get_new_item_form() |
466 | 528 | |
... | ... | |
478 | 540 | |
479 | 541 |
return redirect('.') |
480 | 542 | |
481 | ||
482 | 543 |
def delete(self): |
483 | 544 |
form = Form(enctype="multipart/form-data") |
484 | 545 |
form.widgets.append(HtmlWidget('<p>%s</p>' % _( |
... | ... | |
888 | 949 |
return redirect('..') |
889 | 950 | |
890 | 951 | |
952 |
class GlobalActionPage(WorkflowStatusPage): |
|
953 |
_q_exports = ['', 'new', 'delete', 'newitem', ('items', 'items_dir'), |
|
954 |
'edit', ('triggers', 'triggers_dir'), |
|
955 |
('backoffice-info-text', 'backoffice_info_text'),] |
|
956 | ||
957 |
def __init__(self, workflow, action_id, html_top): |
|
958 |
self.html_top = html_top |
|
959 |
self.workflow = workflow |
|
960 |
try: |
|
961 |
self.action = [x for x in self.workflow.global_actions if x.id == action_id][0] |
|
962 |
except IndexError: |
|
963 |
raise errors.TraversalError() |
|
964 |
self.status = self.action |
|
965 |
self.items_dir = GlobalActionItemsDir(workflow, self.action, html_top) |
|
966 |
self.triggers_dir = GlobalActionTriggersDir(workflow, self.action, html_top) |
|
967 | ||
968 |
def _q_traverse(self, path): |
|
969 |
get_response().breadcrumb.append( |
|
970 |
('global-actions/%s/' % self.action.id, _('Global Action: %s') % self.action.name)) |
|
971 |
return Directory._q_traverse(self, path) |
|
972 | ||
973 |
def is_item_available(self, item): |
|
974 |
return item.is_available() and item.ok_in_global_action |
|
975 | ||
976 |
def _q_index(self): |
|
977 |
self.html_top('%s - %s' % (_('Workflow'), self.workflow.name)) |
|
978 |
r = TemplateIO(html=True) |
|
979 |
get_response().add_javascript(['jquery.js', 'jquery-ui.js', 'biglist.js', |
|
980 |
'ckeditor/ckeditor.js', 'qommon.wysiwyg.js', 'ckeditor/adapters/jquery.js']) |
|
981 | ||
982 |
r += htmltext('<h2>%s - ') % _('Workflow') |
|
983 |
r += htmltext('%s - %s</h2>') % (self.workflow.name, self.action.name) |
|
984 |
r += get_session().display_message() |
|
985 | ||
986 |
r += htmltext('<div class="bo-block">') |
|
987 |
r += htmltext('<h2>%s</h2>') % _('Actions') |
|
988 |
if not self.action.items: |
|
989 |
r += htmltext('<p>%s</p>') % _('There are not yet any items in this action.') |
|
990 |
else: |
|
991 |
if str(self.workflow.id).startswith('_'): |
|
992 |
r += htmltext('<ul id="items-list" class="biglist">') |
|
993 |
else: |
|
994 |
r += htmltext('<p>') |
|
995 |
r += _('Use drag and drop to reorder items.') |
|
996 |
r += htmltext('</p>') |
|
997 |
r += htmltext('<ul id="items-list" class="biglist sortable">') |
|
998 |
for i, item in enumerate(self.action.items): |
|
999 |
r += htmltext('<li class="biglistitem" id="itemId_%s">') % item.id |
|
1000 |
if str(self.workflow.id).startswith('_'): |
|
1001 |
r += item.render_as_line() |
|
1002 |
else: |
|
1003 |
if hasattr(item, 'fill_admin_form'): |
|
1004 |
r += htmltext('<a href="items/%s/">%s</a>') % (item.id, item.render_as_line()) |
|
1005 |
else: |
|
1006 |
r += item.render_as_line() |
|
1007 |
r += htmltext('<p class="commands">') |
|
1008 |
if hasattr(item, 'fill_admin_form'): |
|
1009 |
r += command_icon('items/%s/' % item.id, 'edit') |
|
1010 |
r += command_icon('items/%s/delete' % item.id, 'remove', popup = True) |
|
1011 |
r += htmltext('</p>') |
|
1012 |
r += htmltext('</li>') |
|
1013 |
r += htmltext('</ul>') |
|
1014 |
r += htmltext('</div>') # bo-block |
|
1015 | ||
1016 |
r += htmltext('<div class="bo-block">') |
|
1017 |
r += htmltext('<h2>%s</h2>') % _('Triggers') |
|
1018 |
r += htmltext('<ul id="items-list" class="biglist sortable">') |
|
1019 |
for trigger in self.action.triggers: |
|
1020 |
r += htmltext('<li>') |
|
1021 |
r += htmltext('<a rel="popup" href="triggers/%s/">%s</a>') % ( |
|
1022 |
trigger.id, trigger.render_as_line()) |
|
1023 |
r += htmltext('<p class="commands">') |
|
1024 |
r += command_icon('triggers/%s/' % trigger.id, 'edit', popup=True) |
|
1025 |
r += htmltext('</li>') |
|
1026 |
r+= htmltext('</ul>') |
|
1027 |
r += htmltext('</div>') # bo-block |
|
1028 | ||
1029 |
r += htmltext('<p><a href="../../">%s</a></p>') % _('Back to workflow main page') |
|
1030 | ||
1031 |
get_response().filter['sidebar'] = self.get_sidebar() |
|
1032 | ||
1033 |
return r.getvalue() |
|
1034 | ||
1035 |
def delete(self): |
|
1036 |
form = Form(enctype='multipart/form-data') |
|
1037 |
form.widgets.append(HtmlWidget('<p>%s</p>' % _( |
|
1038 |
'You are about to remove an action.'))) |
|
1039 |
form.add_submit('delete', _('Submit')) |
|
1040 |
form.add_submit('cancel', _('Cancel')) |
|
1041 |
if form.get_widget('cancel').parse(): |
|
1042 |
return redirect('../../') |
|
1043 |
if not form.is_submitted() or form.has_errors(): |
|
1044 |
get_response().breadcrumb.append(('delete', _('Delete'))) |
|
1045 |
self.html_top(title = _('Delete Action')) |
|
1046 |
r = TemplateIO(html=True) |
|
1047 |
r += htmltext('<h2>%s %s</h2>') % (_('Deleting Action:'), self.action.name) |
|
1048 |
r += form.render() |
|
1049 |
return r.getvalue() |
|
1050 | ||
1051 |
del self.workflow.global_actions[self.workflow.global_actions.index(self.action)] |
|
1052 |
self.workflow.store() |
|
1053 |
return redirect('../../') |
|
1054 | ||
1055 |
def edit(self): |
|
1056 |
form = Form(enctype = 'multipart/form-data') |
|
1057 |
form.add(StringWidget, 'name', title=_('Action Name'), required=True, |
|
1058 |
size=30, value=self.action.name) |
|
1059 |
form.add_submit('submit', _('Submit')) |
|
1060 |
form.add_submit('cancel', _('Cancel')) |
|
1061 |
if form.get_widget('cancel').parse(): |
|
1062 |
return redirect('..') |
|
1063 | ||
1064 |
if form.is_submitted() and not form.has_errors(): |
|
1065 |
new_name = str(form.get_widget('name').parse()) |
|
1066 |
if [x for x in self.workflow.global_actions if x.name == new_name]: |
|
1067 |
form.get_widget('name').set_error( |
|
1068 |
_('There is already an action with that name.')) |
|
1069 |
else: |
|
1070 |
self.action.name = new_name |
|
1071 |
self.workflow.store() |
|
1072 |
return redirect('.') |
|
1073 | ||
1074 |
self.html_top(title=_('Edit Action Name')) |
|
1075 |
get_response().breadcrumb.append( ('edit', _('Edit')) ) |
|
1076 |
r = TemplateIO(html=True) |
|
1077 |
r += htmltext('<h2>%s</h2>') % _('Edit Action Name') |
|
1078 |
r += form.render() |
|
1079 |
return r.getvalue() |
|
1080 | ||
1081 |
def get_sidebar(self): |
|
1082 |
get_response().add_javascript(['jquery.js', 'jquery-ui.js', 'popup.js', |
|
1083 |
'jquery.colourpicker.js']) |
|
1084 |
r = TemplateIO(html=True) |
|
1085 |
if str(self.workflow.id).startswith('_'): |
|
1086 |
r += htmltext('<p>') |
|
1087 |
r += _('''This is the default workflow, you cannot edit it but you can |
|
1088 |
duplicate it to base your own workflow on it.''') |
|
1089 |
r += htmltext('</p>') |
|
1090 |
else: |
|
1091 |
r += htmltext('<ul id="sidebar-actions">') |
|
1092 |
r += htmltext('<li><a href="edit" rel="popup">%s</a></li>') % _('Change Action Name') |
|
1093 |
r += htmltext('<li><a href="backoffice-info-text" rel="popup">%s</a></li>' |
|
1094 |
) % _('Change Backoffice Information Text') |
|
1095 |
r += htmltext('<li><a href="delete" rel="popup">%s</a></li>') % _('Delete') |
|
1096 |
r += htmltext('</ul>') |
|
1097 |
r += htmltext('<div id="new-field">') |
|
1098 |
r += htmltext('<h3>%s</h3>') % _('New Item') |
|
1099 |
r += self.get_new_item_form().render() |
|
1100 |
r += htmltext('</div>') |
|
1101 |
return r.getvalue() |
|
1102 | ||
1103 | ||
1104 |
class GlobalActionsDirectory(Directory): |
|
1105 |
_q_exports = ['', 'new'] |
|
1106 | ||
1107 |
def __init__(self, workflow, html_top): |
|
1108 |
self.workflow = workflow |
|
1109 |
self.html_top = html_top |
|
1110 | ||
1111 |
def _q_lookup(self, component): |
|
1112 |
return GlobalActionPage(self.workflow, component, self.html_top) |
|
1113 | ||
1114 |
def _q_index(self): |
|
1115 |
return redirect('..') |
|
1116 | ||
1117 |
def new(self): |
|
1118 |
form = Form(enctype='multipart/form-data') |
|
1119 |
form.add(StringWidget, 'name', title=_('Name'), required=True, size=50) |
|
1120 |
form.add_submit('submit', _('Add')) |
|
1121 |
form.add_submit('cancel', _('Cancel')) |
|
1122 |
if form.get_widget('cancel').parse(): |
|
1123 |
return redirect('..') |
|
1124 | ||
1125 |
if form.is_submitted() and not form.has_errors(): |
|
1126 |
name = form.get_widget('name').parse() |
|
1127 |
action = self.workflow.add_global_action(name) |
|
1128 |
self.workflow.store() |
|
1129 |
return redirect('%s/' % action.id) |
|
1130 | ||
1131 |
get_response().breadcrumb.append(('new', _('New Global Action'))) |
|
1132 |
html_top('workflows', title=_('New Global Action')) |
|
1133 |
r = TemplateIO(html=True) |
|
1134 |
r += htmltext('<h2>%s</h2>') % _('New Global Action') |
|
1135 |
r += form.render() |
|
1136 |
return r.getvalue() |
|
1137 | ||
1138 | ||
891 | 1139 |
class WorkflowPage(Directory): |
892 | 1140 |
_q_exports = ['', 'edit', 'delete', 'newstatus', ('status', 'status_dir'), 'update_order', |
893 | 1141 |
'duplicate', 'export', 'svg', ('variables', 'variables_dir'), |
894 |
('functions', 'functions_dir')] |
|
1142 |
'update_actions_order', |
|
1143 |
('functions', 'functions_dir'), ('global-actions', 'global_actions_dir')] |
|
895 | 1144 | |
896 | 1145 |
def __init__(self, component, html_top): |
897 | 1146 |
try: |
... | ... | |
903 | 1152 |
self.status_dir = WorkflowStatusDirectory(self.workflow, html_top) |
904 | 1153 |
self.variables_dir = VariablesDirectory(self.workflow) |
905 | 1154 |
self.functions_dir = FunctionsDirectory(self.workflow) |
1155 |
self.global_actions_dir = GlobalActionsDirectory(self.workflow, html_top) |
|
906 | 1156 |
get_response().breadcrumb.append((component + '/', self.workflow.name)) |
907 | 1157 | |
908 | 1158 |
def _q_index(self): |
... | ... | |
992 | 1242 |
field.id, field.label) |
993 | 1243 |
if not '*' in field.varname: |
994 | 1244 |
r += htmltext(' (<code>%s</code>)') % field.varname |
995 |
r += htmltext('</li>') |
|
1245 |
r += htmltext('</a></li>')
|
|
996 | 1246 |
r += htmltext('</ul>') |
997 | 1247 |
r += htmltext('</div>') |
998 | 1248 | |
1249 |
if not str(self.workflow.id).startswith('_') or self.workflow.global_actions: |
|
1250 |
r += htmltext('<div class="bo-block">') |
|
1251 |
r += htmltext('<h3>%s') % _('Global Actions') |
|
1252 |
if not str(self.workflow.id).startswith('_'): |
|
1253 |
r += htmltext(' <span class="change">(<a rel="popup" href="global-actions/new">%s</a>)</span>') % _('add global action') |
|
1254 |
r += htmltext('</h3>') |
|
1255 | ||
1256 |
if not str(self.workflow.id).startswith('_'): |
|
1257 |
r += htmltext('<ul id="status-list" class="biglist sortable" ' |
|
1258 |
'data-order-function="update_actions_order">') |
|
1259 |
else: |
|
1260 |
r += htmltext('<ul id="status-list" class="biglist">') |
|
1261 | ||
1262 |
for action in (self.workflow.global_actions or []): |
|
1263 |
r += htmltext('<li class="biglistitem" id="itemId_%s">' % action.id) |
|
1264 |
if not str(self.workflow.id).startswith('_'): |
|
1265 |
r += htmltext('<a href="global-actions/%s/">%s</a>') % ( |
|
1266 |
action.id, action.name) |
|
1267 |
else: |
|
1268 |
r += htmltext('<a>%s</a>') % action.name |
|
1269 |
r += htmltext('</li>') |
|
1270 |
r += htmltext('</ul>') |
|
1271 |
r += htmltext('</div>') |
|
1272 | ||
999 | 1273 |
r += htmltext('</div>') # .splitcontent-right |
1000 | 1274 | |
1001 | 1275 |
r += htmltext('<br style="clear:both;"/>') |
... | ... | |
1073 | 1347 |
self.workflow.store() |
1074 | 1348 |
return 'ok' |
1075 | 1349 | |
1350 |
def update_actions_order(self): |
|
1351 |
request = get_request() |
|
1352 |
new_order = request.form['order'].strip(';').split(';') |
|
1353 |
self.workflow.global_actions = [ [x for x in self.workflow.global_actions if |
|
1354 |
x.id == y][0] for y in new_order] |
|
1355 |
self.workflow.store() |
|
1356 |
return 'ok' |
|
1076 | 1357 | |
1077 | 1358 |
def newstatus(self): |
1078 | 1359 |
form = Form(enctype='multipart/form-data', action = 'newstatus') |
wcs/backoffice/management.py | ||
---|---|---|
1580 | 1580 | |
1581 | 1581 | |
1582 | 1582 |
class FormBackOfficeStatusPage(FormStatusPage): |
1583 |
_q_exports = ['', 'download', 'json', 'wfedit'] |
|
1583 |
_q_exports = ['', 'download', 'json', 'wfedit', 'action']
|
|
1584 | 1584 | |
1585 | 1585 |
def html_top(self, title = None): |
1586 | 1586 |
return html_top('management', title) |
wcs/formdata.py | ||
---|---|---|
260 | 260 |
url = None |
261 | 261 |
get_publisher().substitutions.feed(self) |
262 | 262 |
wf_status = self.get_status() |
263 |
url = wf_status.perform_items(self) |
|
263 |
from wcs.workflows import perform_items |
|
264 |
url = perform_items(wf_status.items, self) |
|
264 | 265 |
return url |
265 | 266 | |
266 | 267 |
def display_workflow_message(self): |
wcs/forms/common.py | ||
---|---|---|
89 | 89 | |
90 | 90 | |
91 | 91 |
class FormStatusPage(Directory): |
92 |
_q_exports = ['', 'download', 'json'] |
|
92 |
_q_exports = ['', 'download', 'json', 'action']
|
|
93 | 93 |
_q_extra_exports = [] |
94 | 94 | |
95 | 95 |
def html_top(self, title = None): |
wcs/qommon/static/js/biglist.js | ||
---|---|---|
16 | 16 |
update : function(event, ui) |
17 | 17 |
{ |
18 | 18 |
result = ''; |
19 |
items = $('ul.biglist li');
|
|
19 |
items = $(ui.item).parent().find('li');
|
|
20 | 20 |
for (i=0; i < items.length; i++) { |
21 | 21 |
var item = items[i]; |
22 | 22 |
var item_id = item.id.substr(7, 50); |
... | ... | |
24 | 24 |
result += item_id + ';'; |
25 | 25 |
} |
26 | 26 |
} |
27 |
$.post('update_order', {'order': result}); |
|
27 |
var order_function = $(this).data('order-function') || 'update_order'; |
|
28 |
$.post(order_function, {'order': result}); |
|
28 | 29 |
}, |
29 | 30 |
} |
30 | 31 |
); |
wcs/wf/aggregation_email.py | ||
---|---|---|
28 | 28 |
class AggregationEmailWorkflowStatusItem(WorkflowStatusItem): |
29 | 29 |
description = N_('Aggregate to summary email') |
30 | 30 |
key = 'aggregationemail' |
31 |
ok_in_global_action = False |
|
31 | 32 | |
32 | 33 |
to = [] |
33 | 34 |
wcs/wf/attachment.py | ||
---|---|---|
68 | 68 |
key = 'addattachment' |
69 | 69 |
endpoint = False |
70 | 70 |
waitpoint = True |
71 |
ok_in_global_action = False |
|
71 | 72 | |
72 | 73 |
title = None |
73 | 74 |
display_title = True |
wcs/wf/export_to_model.py | ||
---|---|---|
131 | 131 |
description = N_('Create Document') |
132 | 132 |
key = 'export_to_model' |
133 | 133 |
support_substitution_variables = True |
134 |
ok_in_global_action = False |
|
134 | 135 | |
135 | 136 |
endpoint = False |
136 | 137 |
waitpoint = True |
wcs/wf/form.py | ||
---|---|---|
63 | 63 |
class FormWorkflowStatusItem(WorkflowStatusItem): |
64 | 64 |
description = N_('Display a form') |
65 | 65 |
key = 'form' |
66 |
ok_in_global_action = False |
|
66 | 67 | |
67 | 68 |
by = [] |
68 | 69 |
formdef = None |
wcs/wf/timeout_jump.py | ||
---|---|---|
24 | 24 |
description = N_('Change Status on Timeout') |
25 | 25 |
key = 'timeout' |
26 | 26 |
waitpoint = True |
27 |
ok_in_global_action = False |
|
27 | 28 | |
28 | 29 |
timeout = None |
29 | 30 |
wcs/workflows.py | ||
---|---|---|
48 | 48 |
return -1 |
49 | 49 | |
50 | 50 | |
51 |
def perform_items(items, formdata, depth=20): |
|
52 |
if depth == 0: # prevents infinite loops |
|
53 |
return |
|
54 |
url = None |
|
55 |
old_status = formdata.status |
|
56 |
for item in items: |
|
57 |
try: |
|
58 |
url = item.perform(formdata) or url |
|
59 |
except AbortActionException: |
|
60 |
break |
|
61 |
if formdata.status != old_status: |
|
62 |
break |
|
63 |
if formdata.status != old_status: |
|
64 |
if not formdata.evolution: |
|
65 |
formdata.evolution = [] |
|
66 |
evo = Evolution() |
|
67 |
evo.time = time.localtime() |
|
68 |
evo.status = formdata.status |
|
69 |
formdata.evolution.append(evo) |
|
70 |
formdata.store() |
|
71 |
# performs the items of the new status |
|
72 |
wf_status = formdata.get_status() |
|
73 |
url = perform_items(wf_status.items, formdata, depth=depth-1) or url |
|
74 |
return url |
|
75 | ||
76 | ||
51 | 77 |
class WorkflowImportError(Exception): |
52 | 78 |
pass |
53 | 79 | |
... | ... | |
187 | 213 |
return {'attachments': AttachmentsSubstitutionProxy(formdata) } |
188 | 214 | |
189 | 215 | |
216 |
class DuplicateGlobalActionNameError(Exception): |
|
217 |
pass |
|
218 | ||
219 | ||
190 | 220 |
class DuplicateStatusNameError(Exception): |
191 | 221 |
pass |
192 | 222 | |
... | ... | |
220 | 250 |
possible_status = None |
221 | 251 |
roles = None |
222 | 252 |
variables_formdef = None |
253 |
global_actions = None |
|
223 | 254 | |
224 | 255 |
last_modification_time = None |
225 | 256 |
last_modification_user_id = None |
... | ... | |
229 | 260 |
self.name = name |
230 | 261 |
self.possible_status = [] |
231 | 262 |
self.roles = {'_receiver': _('Recipient')} |
263 |
self.global_actions = [] |
|
232 | 264 | |
233 | 265 |
def migrate(self): |
234 | 266 |
changed = False |
... | ... | |
240 | 272 |
for status in self.possible_status: |
241 | 273 |
changed |= status.migrate() |
242 | 274 | |
275 |
if not self.global_actions: |
|
276 |
self.global_actions = [] |
|
277 | ||
243 | 278 |
if changed: |
244 | 279 |
self.store() |
245 | 280 | |
... | ... | |
295 | 330 |
return status |
296 | 331 |
raise KeyError() |
297 | 332 | |
333 |
def add_global_action(self, name, id=None): |
|
334 |
if [x for x in self.global_actions if x.name == name]: |
|
335 |
raise DuplicateGlobalActionNameError() |
|
336 |
action = WorkflowGlobalAction(name) |
|
337 |
action.parent = self |
|
338 |
action.append_trigger('manual') |
|
339 | ||
340 |
if id is None: |
|
341 |
if self.global_actions: |
|
342 |
action.id = str(max([lax_int(x.id) for x in self.global_actions]) + 1) |
|
343 |
else: |
|
344 |
action.id = '1' |
|
345 |
else: |
|
346 |
action.id = id |
|
347 |
self.global_actions.append(action) |
|
348 |
return action |
|
349 | ||
350 |
def get_global_actions_for_user(self, formdata, user): |
|
351 |
if not user: |
|
352 |
return [] |
|
353 |
actions = [] |
|
354 |
for action in self.global_actions or []: |
|
355 |
for trigger in action.triggers or []: |
|
356 |
if isinstance(trigger, WorkflowGlobalActionManualTrigger): |
|
357 |
roles = [get_role_translation(formdata, x) |
|
358 |
for x in trigger.roles] |
|
359 |
if set(roles).intersection(user.roles or []): |
|
360 |
actions.append(action) |
|
361 |
break |
|
362 |
return actions |
|
363 | ||
298 | 364 |
def __setstate__(self, dict): |
299 | 365 |
self.__dict__.update(dict) |
300 |
for s in self.possible_status: |
|
366 |
for s in self.possible_status + (self.global_actions or []):
|
|
301 | 367 |
s.parent = self |
302 |
for i, item in enumerate(s.items): |
|
368 |
triggers = getattr(s, 'triggers', None) or [] |
|
369 |
for i, item in enumerate(s.items + triggers): |
|
303 | 370 |
item.parent = s |
304 | 371 |
if not item.id: |
305 | 372 |
item.id = '%d' % (i+1) |
... | ... | |
379 | 446 |
possible_status.append(status.export_to_xml(charset=charset, |
380 | 447 |
include_id=include_id)) |
381 | 448 | |
449 |
if self.global_actions: |
|
450 |
global_actions = ET.SubElement(root, 'global_actions') |
|
451 |
for action in self.global_actions: |
|
452 |
global_actions.append(action.export_to_xml(charset=charset, |
|
453 |
include_id=include_id)) |
|
454 | ||
382 | 455 |
if self.variables_formdef: |
383 | 456 |
variables = ET.SubElement(root, 'variables') |
384 | 457 |
formdef = ET.SubElement(variables, 'formdef') |
... | ... | |
434 | 507 |
status_o.init_with_xml(status, charset, include_id=include_id) |
435 | 508 |
workflow.possible_status.append(status_o) |
436 | 509 | |
510 |
workflow.global_actions = [] |
|
511 |
global_actions = tree.find('global_actions') |
|
512 |
if global_actions is not None: |
|
513 |
for action in global_actions: |
|
514 |
action_o = WorkflowGlobalAction() |
|
515 |
action_o.parent = workflow |
|
516 |
action_o.init_with_xml(action, charset, include_id=include_id) |
|
517 |
workflow.global_actions.append(action_o) |
|
518 | ||
437 | 519 |
variables = tree.find('variables') |
438 | 520 |
if variables is not None: |
439 | 521 |
formdef = variables.find('formdef') |
... | ... | |
456 | 538 |
return t |
457 | 539 | |
458 | 540 |
def render_list_of_roles(self, roles): |
459 |
t = [] |
|
460 |
for r in roles: |
|
461 |
role_label = get_role_translation_label(self, r) |
|
462 |
if role_label: |
|
463 |
t.append(role_label) |
|
464 |
return ', '.join(t) |
|
541 |
return render_list_of_roles(self, roles) |
|
465 | 542 | |
466 | 543 |
def get_unknown_workflow(cls): |
467 | 544 |
workflow = Workflow(name=_('Unknown')) |
... | ... | |
596 | 673 |
get_default_workflow = classmethod(get_default_workflow) |
597 | 674 | |
598 | 675 | |
676 |
class XmlSerialisable(object): |
|
677 |
node_name = None |
|
678 | ||
679 |
def export_to_xml(self, charset, include_id=False): |
|
680 |
node = ET.Element(self.node_name) |
|
681 |
node.attrib['type'] = self.key |
|
682 |
for attribute in self.get_parameters(): |
|
683 |
if hasattr(self, '%s_export_to_xml' % attribute): |
|
684 |
getattr(self, '%s_export_to_xml' % attribute)(node, charset, |
|
685 |
include_id=include_id) |
|
686 |
continue |
|
687 |
if hasattr(self, attribute) and getattr(self, attribute) is not None: |
|
688 |
el = ET.SubElement(node, attribute) |
|
689 |
val = getattr(self, attribute) |
|
690 |
if type(val) is dict: |
|
691 |
for k, v in val.items(): |
|
692 |
ET.SubElement(el, k).text = unicode(v, charset, 'replace') |
|
693 |
elif type(val) is list: |
|
694 |
if attribute[-1] == 's': |
|
695 |
atname = attribute[:-1] |
|
696 |
else: |
|
697 |
atname = 'item' |
|
698 |
for v in val: |
|
699 |
ET.SubElement(el, atname).text = unicode(str(v), charset, 'replace') |
|
700 |
elif type(val) in (str, unicode): |
|
701 |
if type(val) is unicode: |
|
702 |
el.text = val |
|
703 |
else: |
|
704 |
el.text = unicode(val, charset, 'replace') |
|
705 |
else: |
|
706 |
el.text = str(val) |
|
707 |
return node |
|
708 | ||
709 |
def init_with_xml(self, elem, charset, include_id=False): |
|
710 |
for attribute in self.get_parameters(): |
|
711 |
el = elem.find(attribute) |
|
712 |
if hasattr(self, '%s_init_with_xml' % attribute): |
|
713 |
getattr(self, '%s_init_with_xml' % attribute)(el, charset, |
|
714 |
include_id=include_id) |
|
715 |
continue |
|
716 |
if el is None: |
|
717 |
continue |
|
718 |
if el.getchildren(): |
|
719 |
if type(getattr(self, attribute)) is list: |
|
720 |
v = [x.text.encode(charset) for x in el.getchildren()] |
|
721 |
elif type(getattr(self, attribute)) is dict: |
|
722 |
v = {} |
|
723 |
for e in el.getchildren(): |
|
724 |
v[e.tag] = e.text.encode(charset) |
|
725 |
else: |
|
726 |
# ??? |
|
727 |
raise AssertionError |
|
728 |
setattr(self, attribute, v) |
|
729 |
else: |
|
730 |
if el.text is None: |
|
731 |
setattr(self, attribute, None) |
|
732 |
elif el.text in ('False', 'True'): # bools |
|
733 |
setattr(self, attribute, eval(el.text)) |
|
734 |
elif type(getattr(self, attribute)) is int: |
|
735 |
setattr(self, attribute, int(el.text.encode(charset))) |
|
736 |
else: |
|
737 |
setattr(self, attribute, el.text.encode(charset)) |
|
738 | ||
739 |
def _roles_export_to_xml(self, attribute, item, charset, include_id=False): |
|
740 |
if not hasattr(self, attribute) or not getattr(self, attribute): |
|
741 |
return |
|
742 |
el = ET.SubElement(item, attribute) |
|
743 |
for role_id in getattr(self, attribute): |
|
744 |
if role_id is None: |
|
745 |
continue |
|
746 |
role_id = str(role_id) |
|
747 |
if role_id.startswith('_') or role_id == 'logged-users': |
|
748 |
role = unicode(role_id, charset) |
|
749 |
else: |
|
750 |
try: |
|
751 |
role = unicode(Role.get(role_id).name, charset) |
|
752 |
except KeyError: |
|
753 |
role = unicode(role_id, charset) |
|
754 |
sub = ET.SubElement(el, 'item') |
|
755 |
sub.attrib['role_id'] = role_id |
|
756 |
sub.text = role |
|
757 | ||
758 |
def _roles_init_with_xml(self, attribute, elem, charset, include_id=False): |
|
759 |
if elem is None: |
|
760 |
setattr(self, attribute, []) |
|
761 |
else: |
|
762 |
imported_roles = [] |
|
763 |
for child in elem.getchildren(): |
|
764 |
imported_roles.append(self._get_role_id_from_xml(child, |
|
765 |
charset, include_id=include_id)) |
|
766 |
setattr(self, attribute, imported_roles) |
|
767 | ||
768 |
def _role_export_to_xml(self, attribute, item, charset, include_id=False): |
|
769 |
if not hasattr(self, attribute) or not getattr(self, attribute): |
|
770 |
return |
|
771 |
role_id = str(getattr(self, attribute)) |
|
772 |
if role_id.startswith('_') or role_id == 'logged-users': |
|
773 |
role = unicode(role_id, charset) |
|
774 |
else: |
|
775 |
try: |
|
776 |
role = unicode(Role.get(role_id).name, charset) |
|
777 |
except KeyError: |
|
778 |
role = unicode(role_id, charset) |
|
779 |
sub = ET.SubElement(item, attribute) |
|
780 |
if include_id: |
|
781 |
sub.attrib['role_id'] = role_id |
|
782 |
sub.text = role |
|
783 | ||
784 |
def _get_role_id_from_xml(self, elem, charset, include_id=False): |
|
785 |
if elem is None: |
|
786 |
return None |
|
787 | ||
788 |
value = elem.text.encode(charset) |
|
789 | ||
790 |
# look for known static values |
|
791 |
if value.startswith('_') or value == 'logged-users': |
|
792 |
return value |
|
793 | ||
794 |
# if we import using id, only look at the role_id attribute |
|
795 |
if include_id: |
|
796 |
if not 'role_id' in elem.attrib: |
|
797 |
return None |
|
798 |
role_id = str(elem.attrib['role_id']) |
|
799 |
if Role.has_key(role_id): |
|
800 |
return role_id |
|
801 |
else: |
|
802 |
return None |
|
803 | ||
804 |
# if not using id, look up on the name |
|
805 |
for role in Role.select(ignore_errors=True): |
|
806 |
if role.name == value: |
|
807 |
return role.id |
|
808 | ||
809 |
# and if there's no match, create a new role |
|
810 |
role = Role() |
|
811 |
role.name = value |
|
812 |
role.store() |
|
813 |
return role.id |
|
814 | ||
815 |
def _role_init_with_xml(self, attribute, elem, charset, include_id=False): |
|
816 |
setattr(self, attribute, self._get_role_id_from_xml(elem, charset, |
|
817 |
include_id=include_id)) |
|
818 | ||
819 | ||
820 |
class WorkflowGlobalActionTrigger(XmlSerialisable): |
|
821 |
node_name = 'trigger' |
|
822 | ||
823 | ||
824 |
class WorkflowGlobalActionManualTrigger(WorkflowGlobalActionTrigger): |
|
825 |
key = 'manual' |
|
826 |
roles = None |
|
827 | ||
828 |
def get_parameters(self): |
|
829 |
return ('roles',) |
|
830 | ||
831 |
def render_as_line(self): |
|
832 |
if self.roles: |
|
833 |
return _('Manual by %s') % render_list_of_roles( |
|
834 |
self.parent.parent, self.roles) |
|
835 |
else: |
|
836 |
return _('Manual (not assigned)') |
|
837 | ||
838 |
def form(self, workflow): |
|
839 |
form = Form(enctype='multipart/form-data') |
|
840 |
options = [(None, '---', None)] |
|
841 |
options += workflow.get_list_of_roles(include_logged_in_users=False) |
|
842 |
form.add(WidgetList, 'roles', title=_('Roles'), |
|
843 |
element_type=SingleSelectWidget, |
|
844 |
value=self.roles, |
|
845 |
add_element_label=_('Add Role'), |
|
846 |
element_kwargs={'render_br': False, |
|
847 |
'options': options}) |
|
848 |
return form |
|
849 | ||
850 |
def submit(self, form): |
|
851 |
self.roles = form.get_widget('roles').parse() |
|
852 | ||
853 |
def roles_export_to_xml(self, item, charset, include_id=False): |
|
854 |
self._roles_export_to_xml('roles', item, charset, include_id=include_id) |
|
855 | ||
856 |
def roles_init_with_xml(self, elem, charset, include_id=False): |
|
857 |
self._roles_init_with_xml('roles', elem, charset, include_id=include_id) |
|
858 | ||
859 | ||
860 |
class WorkflowGlobalAction(object): |
|
861 |
id = None |
|
862 |
name = None |
|
863 |
items = None |
|
864 |
triggers = None |
|
865 |
backoffice_info_text = None |
|
866 | ||
867 |
def __init__(self, name=None): |
|
868 |
self.name = name |
|
869 |
self.items = [] |
|
870 | ||
871 |
def append_item(self, type): |
|
872 |
for klass in item_classes: |
|
873 |
if klass.key == type: |
|
874 |
o = klass() |
|
875 |
if self.items: |
|
876 |
o.id = str(max([lax_int(x.id) for x in self.items]) + 1) |
|
877 |
else: |
|
878 |
o.id = '1' |
|
879 |
self.items.append(o) |
|
880 |
return o |
|
881 |
else: |
|
882 |
raise KeyError() |
|
883 | ||
884 |
def append_trigger(self, type): |
|
885 |
trigger_types = { |
|
886 |
'manual': WorkflowGlobalActionManualTrigger |
|
887 |
} |
|
888 |
o = trigger_types.get(type)() |
|
889 |
if not self.triggers: |
|
890 |
self.triggers = [] |
|
891 |
if self.triggers: |
|
892 |
o.id = str(max([lax_int(x.id) for x in self.triggers]) + 1) |
|
893 |
else: |
|
894 |
o.id = '1' |
|
895 |
self.triggers.append(o) |
|
896 |
return o |
|
897 | ||
898 |
def export_to_xml(self, charset, include_id=False): |
|
899 |
status = ET.Element('action') |
|
900 |
ET.SubElement(status, 'id').text = unicode(self.id, charset) |
|
901 |
ET.SubElement(status, 'name').text = unicode(self.name, charset) |
|
902 | ||
903 |
if self.backoffice_info_text: |
|
904 |
ET.SubElement(status, 'backoffice_info_text').text = unicode( |
|
905 |
self.backoffice_info_text, charset) |
|
906 | ||
907 |
items = ET.SubElement(status, 'items') |
|
908 |
for item in self.items: |
|
909 |
items.append(item.export_to_xml(charset=charset, |
|
910 |
include_id=include_id)) |
|
911 | ||
912 |
triggers = ET.SubElement(status, 'triggers') |
|
913 |
for trigger in self.triggers: |
|
914 |
triggers.append(trigger.export_to_xml(charset=charset, |
|
915 |
include_id=include_id)) |
|
916 | ||
917 |
return status |
|
918 | ||
919 |
def init_with_xml(self, elem, charset, include_id=False): |
|
920 |
self.id = elem.find('id').text.encode(charset) |
|
921 |
self.name = elem.find('name').text.encode(charset) |
|
922 |
if elem.find('backoffice_info_text') is not None: |
|
923 |
self.backoffice_info_text = elem.find('backoffice_info_text').text.encode(charset) |
|
924 | ||
925 |
self.items = [] |
|
926 |
for item in elem.find('items'): |
|
927 |
item_type = item.attrib['type'] |
|
928 |
self.append_item(item_type) |
|
929 |
item_o = self.items[-1] |
|
930 |
item_o.parent = self |
|
931 |
item_o.init_with_xml(item, charset, include_id=include_id) |
|
932 | ||
933 |
self.triggers = [] |
|
934 |
for trigger in elem.find('triggers'): |
|
935 |
trigger_type = trigger.attrib['type'] |
|
936 |
self.append_trigger(trigger_type) |
|
937 |
trigger_o = self.triggers[-1] |
|
938 |
trigger_o.parent = self |
|
939 |
trigger_o.init_with_xml(trigger, charset, include_id=include_id) |
|
940 | ||
599 | 941 | |
600 | 942 |
class WorkflowStatus(object): |
601 | 943 |
id = None |
... | ... | |
645 | 987 |
return item |
646 | 988 |
raise KeyError() |
647 | 989 | |
648 |
def perform_items(self, formdata, depth=20): |
|
649 |
if depth == 0: # prevents infinite loops |
|
650 |
return |
|
651 |
url = None |
|
652 |
old_status = formdata.status |
|
653 |
for item in self.items: |
|
654 |
try: |
|
655 |
url = item.perform(formdata) or url |
|
656 |
except AbortActionException: |
|
657 |
break |
|
658 |
if formdata.status != old_status: |
|
659 |
break |
|
660 |
if formdata.status != old_status: |
|
661 |
if not formdata.evolution: |
|
662 |
formdata.evolution = [] |
|
663 |
evo = Evolution() |
|
664 |
evo.time = time.localtime() |
|
665 |
evo.status = formdata.status |
|
666 |
formdata.evolution.append(evo) |
|
667 |
formdata.store() |
|
668 |
# performs the items of the new status |
|
669 |
wf_status = formdata.get_status() |
|
670 |
url = wf_status.perform_items(formdata, depth=depth-1) or url |
|
671 |
return url |
|
672 | ||
673 | 990 |
def get_action_form(self, filled, user): |
674 | 991 |
form = Form(enctype='multipart/form-data', use_tokens = False) |
675 | 992 |
for item in self.items: |
... | ... | |
677 | 994 |
continue |
678 | 995 |
item.fill_form(form, filled, user) |
679 | 996 | |
997 |
for action in filled.formdef.workflow.get_global_actions_for_user(filled, user): |
|
998 |
form.add_submit('button-action-%s' % action.id, action.name) |
|
999 |
if form.get_widget('button-action-%s' % action.id): |
|
1000 |
form.get_widget('button-action-%s' % action.id).backoffice_info_text = action.backoffice_info_text |
|
1001 | ||
680 | 1002 |
if form.widgets or form.submit_widgets: |
681 | 1003 |
return form |
682 | 1004 |
else: |
683 | 1005 |
return None |
684 | 1006 | |
685 | 1007 |
def handle_form(self, form, filled, user): |
1008 |
# check for global actions |
|
1009 |
for action in filled.formdef.workflow.get_global_actions_for_user(filled, user): |
|
1010 |
if 'button-action-%s' % action.id in get_request().form: |
|
1011 |
url = perform_items(action.items, filled) |
|
1012 |
if url: |
|
1013 |
return redirect(url) |
|
1014 |
return |
|
1015 | ||
686 | 1016 |
evo = Evolution() |
687 | 1017 |
evo.time = time.localtime() |
688 | 1018 |
if user: |
... | ... | |
822 | 1152 |
item_o.parent = self |
823 | 1153 |
item_o.init_with_xml(item, charset, include_id=include_id) |
824 | 1154 | |
825 |
class WorkflowStatusItem(object): |
|
1155 | ||
1156 |
class WorkflowStatusItem(XmlSerialisable): |
|
1157 |
node_name = 'item' |
|
826 | 1158 |
description = 'XX' |
827 | 1159 |
category = None # (key, label) |
828 | 1160 |
id = None |
829 | 1161 | |
830 | 1162 |
endpoint = True # means it's not possible to interact, and/or cause a status change |
831 | 1163 |
waitpoint = False # means it's possible to wait (user interaction, or other event) |
1164 |
ok_in_global_action = True # means it can be used in a global action |
|
832 | 1165 |
directory_name = None |
833 | 1166 |
directory_class = None |
834 | 1167 |
support_substitution_variables = False |
... | ... | |
937 | 1270 |
label = self.render_as_line() |
938 | 1271 |
return label |
939 | 1272 | |
940 |
def export_to_xml(self, charset, include_id=False): |
|
941 |
item = ET.Element('item') |
|
942 |
item.attrib['type'] = self.key |
|
943 |
for attribute in self.get_parameters(): |
|
944 |
if hasattr(self, '%s_export_to_xml' % attribute): |
|
945 |
getattr(self, '%s_export_to_xml' % attribute)(item, charset, |
|
946 |
include_id=include_id) |
|
947 |
continue |
|
948 |
if hasattr(self, attribute) and getattr(self, attribute) is not None: |
|
949 |
el = ET.SubElement(item, attribute) |
|
950 |
val = getattr(self, attribute) |
|
951 |
if type(val) is dict: |
|
952 |
for k, v in val.items(): |
|
953 |
ET.SubElement(el, k).text = unicode(v, charset, 'replace') |
|
954 |
elif type(val) is list: |
|
955 |
if attribute[-1] == 's': |
|
956 |
atname = attribute[:-1] |
|
957 |
else: |
|
958 |
atname = 'item' |
|
959 |
for v in val: |
|
960 |
ET.SubElement(el, atname).text = unicode(str(v), charset, 'replace') |
|
961 |
elif type(val) in (str, unicode): |
|
962 |
if type(val) is unicode: |
|
963 |
el.text = val |
|
964 |
else: |
|
965 |
el.text = unicode(val, charset, 'replace') |
|
966 |
else: |
|
967 |
el.text = str(val) |
|
968 |
return item |
|
969 | ||
970 |
def init_with_xml(self, elem, charset, include_id=False): |
|
971 |
for attribute in self.get_parameters(): |
|
972 |
el = elem.find(attribute) |
|
973 |
if hasattr(self, '%s_init_with_xml' % attribute): |
|
974 |
getattr(self, '%s_init_with_xml' % attribute)(el, charset, |
|
975 |
include_id=include_id) |
|
976 |
continue |
|
977 |
if el is None: |
|
978 |
continue |
|
979 |
if el.getchildren(): |
|
980 |
if type(getattr(self, attribute)) is list: |
|
981 |
v = [x.text.encode(charset) for x in el.getchildren()] |
|
982 |
elif type(getattr(self, attribute)) is dict: |
|
983 |
v = {} |
|
984 |
for e in el.getchildren(): |
|
985 |
v[e.tag] = e.text.encode(charset) |
|
986 |
else: |
|
987 |
# ??? |
|
988 |
raise AssertionError |
|
989 |
setattr(self, attribute, v) |
|
990 |
else: |
|
991 |
if el.text is None: |
|
992 |
setattr(self, attribute, None) |
|
993 |
elif el.text in ('False', 'True'): # bools |
|
994 |
setattr(self, attribute, eval(el.text)) |
|
995 |
elif type(getattr(self, attribute)) is int: |
|
996 |
setattr(self, attribute, int(el.text.encode(charset))) |
|
997 |
else: |
|
998 |
setattr(self, attribute, el.text.encode(charset)) |
|
999 | ||
1000 |
def _roles_export_to_xml(self, attribute, item, charset, include_id=False): |
|
1001 |
if not hasattr(self, attribute) or not getattr(self, attribute): |
|
1002 |
return |
|
1003 |
el = ET.SubElement(item, attribute) |
|
1004 |
for role_id in getattr(self, attribute): |
|
1005 |
if role_id is None: |
|
1006 |
continue |
|
1007 |
role_id = str(role_id) |
|
1008 |
if role_id.startswith('_') or role_id == 'logged-users': |
|
1009 |
role = unicode(role_id, charset) |
|
1010 |
else: |
|
1011 |
try: |
|
1012 |
role = unicode(Role.get(role_id).name, charset) |
|
1013 |
except KeyError: |
|
1014 |
role = unicode(role_id, charset) |
|
1015 |
sub = ET.SubElement(el, 'item') |
|
1016 |
sub.attrib['role_id'] = role_id |
|
1017 |
sub.text = role |
|
1018 | ||
1019 |
def _roles_init_with_xml(self, attribute, elem, charset, include_id=False): |
|
1020 |
if elem is None: |
|
1021 |
setattr(self, attribute, []) |
|
1022 |
else: |
|
1023 |
imported_roles = [] |
|
1024 |
for child in elem.getchildren(): |
|
1025 |
imported_roles.append(self._get_role_id_from_xml(child, |
|
1026 |
charset, include_id=include_id)) |
|
1027 |
setattr(self, attribute, imported_roles) |
|
1028 | ||
1029 |
def _role_export_to_xml(self, attribute, item, charset, include_id=False): |
|
1030 |
if not hasattr(self, attribute) or not getattr(self, attribute): |
|
1031 |
return |
|
1032 |
role_id = str(getattr(self, attribute)) |
|
1033 |
if role_id.startswith('_') or role_id == 'logged-users': |
|
1034 |
role = unicode(role_id, charset) |
|
1035 |
else: |
|
1036 |
try: |
|
1037 |
role = unicode(Role.get(role_id).name, charset) |
|
1038 |
except KeyError: |
|
1039 |
role = unicode(role_id, charset) |
|
1040 |
sub = ET.SubElement(item, attribute) |
|
1041 |
if include_id: |
|
1042 |
sub.attrib['role_id'] = role_id |
|
1043 |
sub.text = role |
|
1044 | ||
1045 |
def _get_role_id_from_xml(self, elem, charset, include_id=False): |
|
1046 |
if elem is None: |
|
1047 |
return None |
|
1048 | ||
1049 |
value = elem.text.encode(charset) |
|
1050 | ||
1051 |
# look for known static values |
|
1052 |
if value.startswith('_') or value == 'logged-users': |
|
1053 |
return value |
|
1054 | ||
1055 |
# if we import using id, only look at the role_id attribute |
|
1056 |
if include_id: |
|
1057 |
if not 'role_id' in elem.attrib: |
|
1058 |
return None |
|
1059 |
role_id = str(elem.attrib['role_id']) |
|
1060 |
if Role.has_key(role_id): |
|
1061 |
return role_id |
|
1062 |
else: |
|
1063 |
return None |
|
1064 | ||
1065 |
# if not using id, look up on the name |
|
1066 |
for role in Role.select(ignore_errors=True): |
|
1067 |
if role.name == value: |
|
1068 |
return role.id |
|
1069 | ||
1070 |
# and if there's no match, create a new role |
|
1071 |
role = Role() |
|
1072 |
role.name = value |
|
1073 |
role.store() |
|
1074 |
return role.id |
|
1075 | ||
1076 |
def _role_init_with_xml(self, attribute, elem, charset, include_id=False): |
|
1077 |
setattr(self, attribute, self._get_role_id_from_xml(elem, charset, |
|
1078 |
include_id=include_id)) |
|
1079 | ||
1080 | 1273 |
def by_export_to_xml(self, item, charset, include_id=False): |
1081 | 1274 |
self._roles_export_to_xml('by', item, charset, include_id=include_id) |
1082 | 1275 | |
... | ... | |
1153 | 1346 |
except KeyError: |
1154 | 1347 |
return |
1155 | 1348 | |
1349 |
def render_list_of_roles(workflow, roles): |
|
1350 |
t = [] |
|
1351 |
for r in roles: |
|
1352 |
role_label = get_role_translation_label(workflow, r) |
|
1353 |
if role_label: |
|
1354 |
t.append(role_label) |
|
1355 |
return ', '.join(t) |
|
1356 | ||
1357 | ||
1156 | 1358 |
item_classes = [] |
1157 | 1359 | |
1158 | 1360 |
def register_item_class(klass): |
... | ... | |
1166 | 1368 |
key = 'commentable' |
1167 | 1369 |
endpoint = False |
1168 | 1370 |
waitpoint = True |
1371 |
ok_in_global_action = False |
|
1169 | 1372 | |
1170 | 1373 |
varname = None |
1171 | 1374 |
label = None |
... | ... | |
1281 | 1484 |
key = 'choice' |
1282 | 1485 |
endpoint = False |
1283 | 1486 |
waitpoint = True |
1487 |
ok_in_global_action = False |
|
1284 | 1488 | |
1285 | 1489 |
label = None |
1286 | 1490 |
by = [] |
... | ... | |
1333 | 1537 |
class JumpOnSubmitWorkflowStatusItem(WorkflowStatusJumpItem): |
1334 | 1538 |
description = N_('Change Status on Submit') |
1335 | 1539 |
key = 'jumponsubmit' |
1540 |
ok_in_global_action = False |
|
1336 | 1541 | |
1337 | 1542 |
def render_as_line(self): |
1338 | 1543 |
if self.status: |
... | ... | |
1574 | 1779 |
description = N_('Display message') |
1575 | 1780 |
key = 'displaymsg' |
1576 | 1781 |
support_substitution_variables = True |
1782 |
ok_in_global_action = False |
|
1577 | 1783 | |
1578 | 1784 |
message = None |
1579 | 1785 | |
... | ... | |
1611 | 1817 |
class RedirectToStatusWorkflowStatusItem(WorkflowStatusItem): |
1612 | 1818 |
description = N_('Redirect to Status Page') |
1613 | 1819 |
key = 'redirectstatus' |
1820 |
ok_in_global_action = False |
|
1614 | 1821 | |
1615 | 1822 |
backoffice = False |
1616 | 1823 | |
... | ... | |
1635 | 1842 |
key = 'editable' |
1636 | 1843 |
endpoint = False |
1637 | 1844 |
waitpoint = True |
1845 |
ok_in_global_action = False |
|
1638 | 1846 | |
1639 | 1847 |
by = [] |
1640 | 1848 |
status = None |
1641 |
- |