wcs/wcs/admin/workflows.ptl

870 lines
33 KiB
Plaintext

# w.c.s. - web application for online forms
# Copyright (C) 2005-2010 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 time
from StringIO import StringIO
from subprocess import Popen, PIPE
import textwrap
import xml.etree.ElementTree as etree
import re
from quixote import redirect, get_publisher
from quixote.directory import Directory
from qommon import errors
from qommon import misc
from qommon.form import *
from qommon.admin.menu import html_top, command_icon, error_page
from qommon import get_logger
from wcs.workflows import *
from wcs.formdef import FormDef
from wcs.formdata import Evolution
from wcs.admin.forms import ET, indent
def svg(tag):
return '{http://www.w3.org/2000/svg}%s' % tag
def xlink(tag):
return '{http://www.w3.org/1999/xlink}%s' % tag
TITLE = svg('title')
POLYGON = svg('polygon')
XLINK_TITLE = xlink('title')
def remove_tag(node, tag):
for child in node:
if child.tag == tag:
node.remove(child)
def remove_attribute(node, att):
if att in node.attrib:
del node.attrib[att]
def remove_style(node, top):
remove_tag(node, TITLE)
if node.get('fill') == 'white' and node.get('stroke') == 'white':
# this is the general white background, wipe it to be transparent
node.attrib['fill'] = 'transparent'
node.attrib['stroke'] = 'transparent'
for child in node:
remove_attribute(child, XLINK_TITLE)
style = child.get('style', None)
# Beware ! HACK ! salmon is matched and converted to class="page-subject"
if style:
m = re.search('(?:stroke|fill):salmon', style)
if m:
top.set('class', top.get('class','') + ' page-subject')
if child.get('fill') == 'salmon':
top.set('class', top.get('class', '') + ' page-subject')
if child.get('font-family'):
del child.attrib['font-family']
if child.get('font-size'):
child.attrib['font-size'] = str(float(child.attrib['font-size'])*0.8)
remove_attribute(child, 'style')
remove_style(child, top)
def graphviz_post_treatment(content):
''' Remove all svg:title and top-level svg:polygon nodes, remove style
attributes and xlink:title attributes.
If a color style is set to a name matching class-\w+, set the second
part on as class selector on the top level svg:g element.
'''
tree = etree.fromstring(content)
for root in tree:
remove_tag(root, TITLE)
# remove_tag(root, POLYGON)
for child in root:
remove_style(child, child)
return etree.tostring(tree)
def graphviz(workflow, url_prefix='', select=None, svg=True,
include=False):
out = StringIO()
print >>out, 'digraph main {'
# print >>out, 'graph [ rankdir=LR ];'
print >>out, 'node [shape=box,style=filled];'
print >>out, 'edge [];'
for status in workflow.possible_status:
i = status.id
print >>out, 'status%s' % i,
print >>out, '[label="%s"' % status.name,
if select == str(i):
print >>out, ',color=salmon'
print >>out, ' URL="%sstatus/%s/"];' % (url_prefix, i)
for status in workflow.possible_status:
i = status.id
for item in status.items:
next_status_ids = []
for status_key in item.__dict__:
if status_key == 'status' or \
status_key.startswith('status_') or \
status_key.endswith('_status'):
next_id = getattr(item, status_key, None)
if next_id:
next_status_ids.append(next_id)
if not next_status_ids:
next_status_ids = [status.id]
for next_id in next_status_ids:
print >>out, 'status%s -> status%s' % (i, next_id)
url = 'status/%s/items/%s/' % (i, item.id)
if getattr(item, 'label', None):
label = item.label
if getattr(item, 'by', None):
roles = render_list_of_roles(item.by)
label += ' %s %s' % (_('by'), roles)
else:
label = item.render_as_line()
label = label.replace('"', '\\"')
label = label.decode('utf8')
label = textwrap.fill(label, 20, break_long_words=False)
label = label.encode('utf8')
label = label.replace('\n', '\\n')
print >>out, '[label="%s"' % label,
if select == '%s-%s' % (i, item.id):
print >>out, ',color=salmon'
print >>out, ',URL="%s%s"]' % (url_prefix, url)
print >>out, '}'
out = out.getvalue()
if svg:
try:
process = Popen(['dot', '-Tsvg', '/dev/stdin'], stdin=PIPE, stdout=PIPE)
out, err = process.communicate(out)
except OSError:
return ''
if include:
out = graphviz_post_treatment(out)
# It seems webkit refuse to accept SVG when using its proper namespace,
# and xlink namespace prefix must be xlink: to be acceptable
out = out.replace('ns0:', '')
out = out.replace('xmlns:ns0', 'xmlns:svg')
out = out.replace('ns1:', 'xlink:')
out = out.replace(':ns1', ':xlink')
return out
class WorkflowUI:
def __init__(self, workflow):
self.workflow = workflow
def form_new(self):
form = Form(enctype='multipart/form-data')
form.add(StringWidget, 'name', title = _('Workflow Name'), required = True, size=30)
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
return form
def form_edit(self):
form = Form(enctype='multipart/form-data')
form.add_hidden('id', value = self.workflow.id)
form.add(StringWidget, 'name', title = _('Workflow Name'), required = True, size=30,
value = self.workflow.name)
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
return form
def submit_form(self, form):
if self.workflow:
workflow = self.workflow
else:
workflow = Workflow(name = form.get_widget('name').parse())
name = form.get_widget('name').parse()
workflows_name = [x.name for x in Workflow.select() if x.id != workflow.id]
if name in workflows_name:
form.get_widget('name').set_error(_('This name is already used'))
raise ValueError()
for f in ('name',):
setattr(workflow, f, form.get_widget(f).parse())
workflow.store()
return workflow
class WorkflowItemPage(Directory):
_q_exports = ['', 'delete']
def __init__(self, workflow, status, component):
try:
self.item = [x for x in status.items if x.id == component][0]
except (IndexError, ValueError):
raise errors.TraversalError()
self.workflow = workflow
self.status = status
get_response().breadcrumb.append(('items/%s/' % component, _(self.item.description)))
def _q_index [html] (self):
request = get_request()
if request.get_method() == 'GET' and request.form.get('file'):
value = getattr(self.item, request.form.get('file'), None)
if value:
return value.build_response()
form = Form(enctype='multipart/form-data')
self.item.fill_admin_form(form)
if not str(self.workflow.id).startswith(str('_')):
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
return redirect('..')
if not form.get_submit() == 'submit' or form.has_errors():
html_top('workflows', '%s - %s' % (_('Workflow'), self.workflow.name))
'<h2>%s - %s</h2>' % (self.workflow.name, self.status.name)
'<h3>%s</h3>' % _(self.item.description)
form.render()
if self.item.support_substitution_variables:
'<h3>%s</h3>' % _('Substitution Variables')
get_publisher().substitutions.get_substitution_html_table()
else:
self.item.submit_admin_form(form)
self.workflow.store()
return redirect('..')
get_response().filter['sidebar'] = self.get_sidebar()
def get_sidebar [html] (self):
if str(self.workflow.id).startswith(str('_')):
'<p>'
_('''This is the default workflow, you cannot edit it but you can
duplicate it to base your own workflow on it.''')
'</p>'
def delete [html] (self):
form = Form(enctype='multipart/form-data')
form.widgets.append(HtmlWidget('<p>%s</p>' % _('You are about to remove an item.')))
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
return redirect('../../')
if not form.is_submitted() or form.has_errors():
get_response().breadcrumb.append(('delete', _('Delete')))
html_top('workflows', title = _('Delete Item'))
'<h2>%s</h2>' % _('Deleting Item')
form.render()
else:
del self.status.items[self.status.items.index(self.item)]
self.workflow.store()
return redirect('../../')
class WorkflowItemsDir(Directory):
_q_exports = ['']
def __init__(self, workflow, status):
self.workflow = workflow
self.status = status
def _q_lookup(self, component):
return WorkflowItemPage(self.workflow, self.status, component)
def _q_index(self):
return redirect('..')
class WorkflowStatusPage(Directory):
_q_exports = ['', 'delete', 'newitem', ('items', 'items_dir'),
'update_order', 'edit', 'reassign', 'visibility',
'endpoint']
def __init__(self, workflow, status_id):
self.workflow = workflow
try:
self.status = [x for x in self.workflow.possible_status if x.id == status_id][0]
except IndexError:
raise errors.TraversalError()
self.items_dir = WorkflowItemsDir(workflow, self.status)
get_response().breadcrumb.append(('status/%s/' % status_id, _('Status: %s') % self.status.name))
def _q_index [html] (self):
html_top('workflows', '%s - %s' % (_('Workflow'), self.workflow.name))
get_response().add_javascript(['jquery.js', 'interface.js', 'biglist.js'])
'<h2>%s - ' % _('Workflow')
'%s - %s</h2>' % (self.workflow.name, self.status.name)
get_session().display_message()
'<div class="bo-block">'
'<h3>%s ' % _('Possible Status:')
'%s</h3>' % self.status.name
'</div>'
if self.status.visibility == ['_receiver']:
'<div class="bo-block">'
_('This status is hidden from the user.')
if not str(self.workflow.id).startswith(str('_')):
' '
'(<a href="visibility" rel="popup">%s</a>)' % _('change')
'</div>'
'<div class="bo-block">'
if not self.status.items:
'<p>%s</p>' % _('There are not yet any items in this status.')
else:
if str(self.workflow.id).startswith(str('_')):
'<ul id="items-list" class="biglist">'
else:
'<p>'
_('Use drag and drop to reorder items.')
'</p>'
'<ul id="items-list" class="biglist sortable">'
for i, item in enumerate(self.status.items):
'<li class="biglistitem" id="itemId_%s">' % item.id
if hasattr(item, str('fill_admin_form')):
'<a href="items/%s/">%s</a>' % (item.id, item.render_as_line())
else:
item.render_as_line()
if not str(self.workflow.id).startswith(str('_')):
'<p class="commands">'
if hasattr(item, str('fill_admin_form')):
command_icon('items/%s/' % item.id, 'edit')
command_icon('items/%s/delete' % item.id, 'remove', popup = True)
'</p>'
'</li>'
'</ul>'
'</div>' # bo-block
'<p><a href="../../">%s</a></p>' % _('Back to workflow main page')
'<div class="bo-block">'
htmltext(graphviz(self.workflow, url_prefix='../../', include=True,
select='%s' % self.status.id))
'</div>'
get_response().filter['sidebar'] = self.get_sidebar()
def get_sidebar [html] (self):
if str(self.workflow.id).startswith(str('_')):
'<p>'
_('''This is the default workflow, you cannot edit it but you can
duplicate it to base your own workflow on it.''')
'</p>'
else:
'<ul>'
'<li><a href="edit">%s</a></li>' % _('Change Status Name')
'<li><a href="visibility" rel="popup">%s</a></li>' % _('Change Status Visibility')
'<li><a href="endpoint" rel="popup">%s</a></li>' % _('Change Terminal Status')
'<li><a href="delete" rel="popup">%s</a></li>' % _('Delete')
'</ul>'
'<div id="new-field">'
'<h3>%s</h3>' % _('New Item')
self.get_new_item_form().render()
'</div>'
def get_new_item_form [html] (self):
form = Form(enctype='multipart/form-data', action = 'newitem')
options = [(x.key, _(x.description)) for x in item_classes if x.is_available()]
options.sort(cmp=lambda a, b: cmp(a[1],b[1]))
form.add(SingleSelectWidget, 'type', title = _('Type'),
required=True, options = options)
form.add_submit('submit', _('Add'))
return form
def update_order(self):
request = get_request()
new_order = request.form['order'].strip(';').split(';')
self.status.items = [ [x for x in self.status.items if x.id == y][0] for y in new_order]
self.workflow.store()
return 'ok'
def newitem(self):
form = self.get_new_item_form()
if not form.is_submitted() or form.has_errors():
get_session().message = ('error', _('Submitted form was not filled properly.'))
return redirect('.')
if form.get_widget('type').parse():
self.status.append_item(form.get_widget('type').parse())
else:
get_session().message = ('error', _('Submitted form was not filled properly.'))
return redirect('.')
self.workflow.store()
return redirect('.')
def delete [html] (self):
form = Form(enctype="multipart/form-data")
form.widgets.append(HtmlWidget('<p>%s</p>' % _(
"You are about to remove a status.")))
form.add_submit("submit", _("Submit"))
form.add_submit("cancel", _("Cancel"))
if form.get_widget('cancel').parse():
return redirect('../../')
if not form.is_submitted() or form.has_errors():
get_response().breadcrumb.append(('delete', _('Delete')))
html_top('workflows', title = _('Delete Status'))
'<h2>%s %s</h2>' % (_('Deleting Status:'), self.status.name)
form.render()
else:
# Before removing the status, scan formdata to know if it's in use.
for formdef in FormDef.select():
if formdef.workflow_id != self.workflow.id:
continue
if formdef.data_class().get_with_indexed_value(
str('status'), 'wf-%s' % self.status.id):
return redirect('reassign')
del self.workflow.possible_status[ self.workflow.possible_status.index(self.status) ]
self.workflow.store()
return redirect('../../')
def edit [html] (self):
form = Form(enctype = 'multipart/form-data')
form.add(StringWidget, 'name', title = _('Status Name'), required = True, size=30,
value = self.status.name)
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
return redirect('..')
if form.is_submitted() and not form.has_errors():
new_name = str(form.get_widget('name').parse())
if [x for x in self.workflow.possible_status if x.name == new_name]:
form.get_widget('name').set_error(
_('There is already a status with that name.'))
else:
self.status.name = new_name
self.workflow.store()
return redirect('.')
html_top('workflows', title = _('Edit Workflow Status'))
get_response().breadcrumb.append( ('edit', _('Edit')) )
'<h2>%s</h2>' % _('Edit Workflow Status')
form.render()
def reassign [html] (self):
options = [(None, 'Do nothing'),
('remove', _('Remove these forms'))]
for status in self.workflow.get_waitpoint_status():
if status.id == self.status.id:
continue
options.append(('reassign-%s' % status.id,
_('Change these forms status to "%s"') % status.name))
form = Form(enctype='multipart/form-data')
form.add(SingleSelectWidget, 'action', title=_('Pick an Action'),
options=options)
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
if form.get_submit() == 'cancel':
return redirect('.')
if form.is_submitted() and not form.get_widget('action').parse():
return redirect('.')
if not form.is_submitted() or form.has_errors():
get_response().breadcrumb.append(('reassign', _('Delete / Reassign')))
html_top('workflows', title = _('Delete Status'))
html_top('workflows', title = _('Delete Status / Reassign'))
'<h2>%s %s</h2>' % (_('Deleting Status:'), self.status.name)
'<p>'
_('''There are forms set to this status, they need to be changed before
this status can be deleted.''')
'</p>'
'<ul>'
for formdef in FormDef.select():
if formdef.workflow_id != self.workflow.id:
continue
items = formdef.data_class().get_with_indexed_value(
str('status'), 'wf-%s' % self.status.id)
'<li>%s: %s</li>' % (formdef.name, _('%s items') % len(items))
'</ul>'
form.render()
else:
self.submit_reassign(form)
del self.workflow.possible_status[ self.workflow.possible_status.index(self.status) ]
self.workflow.store()
return redirect('../..')
def submit_reassign(self, form):
nb_forms = 0
action = form.get_widget('action').parse()
if action.startswith(str('reassign-')):
new_status = 'wf-%s' % str(action)[9:]
for formdef in FormDef.select():
if formdef.workflow_id != self.workflow.id:
continue
for item in formdef.data_class().get_with_indexed_value(
str('status'), 'wf-%s' % self.status.id):
nb_forms += 1
if action == 'remove':
item.remove_self()
else:
item.status = new_status
evo = Evolution()
evo.time = time.localtime()
evo.status = new_status
evo.comment = _('Administrator reassigned status')
if not item.evolution:
item.evolution = []
item.evolution.append(evo)
item.store()
# delete all (old) status references in evolutions
for item in formdef.data_class().select():
if item.evolution:
modified = False
for evo in item.evolution:
if evo.status == self.status:
evo.status = None
modified = True
if modified:
item.store()
if action == 'remove':
get_logger().info('admin - delete status "%s" in workflow "%s": %d forms deleted' % (
self.status.name, self.workflow.name, nb_forms))
def visibility [html] (self):
form = Form(enctype = 'multipart/form-data')
form.add(CheckboxWidget, 'hide_status_from_user',
title=_('Hide status from user'),
value=(self.status.visibility == ['_receiver']))
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
return redirect('..')
if form.is_submitted() and not form.has_errors():
hide_status = form.get_widget('hide_status_from_user').parse()
if hide_status:
self.status.visibility = [str('_receiver')]
else:
self.status.visibility = None
self.workflow.store()
return redirect('.')
html_top('workflows', title = _('Edit Workflow Status Visibility'))
get_response().breadcrumb.append( ('visibility', _('Visibility')) )
'<h2>%s</h2>' % _('Edit Workflow Status Visibility')
form.render()
def endpoint [html] (self):
form = Form(enctype = 'multipart/form-data')
form.add(CheckboxWidget, 'force_terminal_status',
title=_('Force Terminal Status'),
value=(self.status.forced_endpoint == True))
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
return redirect('..')
if form.is_submitted() and not form.has_errors():
self.status.forced_endpoint = form.get_widget('force_terminal_status').parse()
self.workflow.store()
return redirect('.')
html_top('workflows', title = _('Edit Terminal Status'))
get_response().breadcrumb.append( ('endpoint', _('Terminal Status')) )
form.render()
class WorkflowStatusDirectory(Directory):
_q_exports = ['']
def __init__(self, workflow):
self.workflow = workflow
def _q_lookup(self, component):
return WorkflowStatusPage(self.workflow, component)
def _q_index(self):
return redirect('..')
class WorkflowPage(Directory):
_q_exports = ['', 'edit', 'delete', 'newstatus', ('status', 'status_dir'), 'update_order',
'duplicate', 'export', 'svg']
def __init__(self, component):
try:
self.workflow = Workflow.get(component)
except KeyError:
raise errors.TraversalError()
self.workflow_ui = WorkflowUI(self.workflow)
self.status_dir = WorkflowStatusDirectory(self.workflow)
get_response().breadcrumb.append((component + '/', self.workflow.name))
def _q_index [html] (self):
html_top('workflows', title = _('Workflow - %s') % self.workflow.name)
get_response().add_javascript(['jquery.js', 'interface.js', 'biglist.js'])
'<h2>%s - ' % _('Workflow')
'%s</h2>'% self.workflow.name
get_session().display_message()
'<div class="bo-block">'
'<h3>%s</h3>' % _('Possible Status')
if not self.workflow.possible_status:
'<p>%s</p>' % _('There are not yet any status defined in this workflow.')
else:
if not str(self.workflow.id).startswith(str('_')):
'<p>'
_('Use drag and drop to reorder status.')
'</p>'
'<ul id="status-list" class="biglist sortable">'
else:
'<ul id="status-list" class="biglist">'
for status in self.workflow.possible_status:
'<li class="biglistitem" id="itemId_%s">' % status.id
'<a href="status/%s/">%s</a>' % (status.id, status.name)
'</li>'
'</ul>'
'</div>'
'<div class="bo-block">'
htmltext(graphviz(self.workflow, include=True))
'</div>' # bo-block
formdefs = [x for x in FormDef.select() if x.workflow_id == self.workflow.id]
if formdefs:
'<div class="bo-block">'
'<h3>%s</h3>' % _('Forms')
'<p>%s ' % _('This workflow is used for the following forms:')
', '.join(['<a href="../../forms/%s/">%s</a>' % (x.id, x.name) for x in formdefs])
'</p>'
'</div>'
get_response().filter['sidebar'] = self.get_sidebar()
def get_sidebar [html] (self):
if str(self.workflow.id).startswith(str('_')):
'<p>'
_('''This is the default workflow, you cannot edit it but you can
duplicate it to base your own workflow on it.''')
'</p>'
'<ul>'
if not str(self.workflow.id).startswith(str('_')):
'<li><a href="edit">%s</a></li>' % _('Change Title')
'<li><a href="delete" rel="popup">%s</a></li>' % _('Delete')
if ET:
'<li><a href="duplicate">%s</a></li>' % _('Duplicate')
'<li><a href="export">%s</a></li>' % _('Export')
'</ul>'
if not str(self.workflow.id).startswith(str('_')):
self.get_new_status_form()
def svg(self):
response = get_response()
response.set_content_type('image/svg+xml')
return graphviz(self.workflow, include=False)
def export(self):
x = self.workflow.export_to_xml()
indent(x)
response = get_response()
response.set_content_type('application/x-wcs-form')
response.set_header('content-disposition',
'attachment; filename=%s.wcs' % misc.simplify(self.workflow.name))
return '<?xml version="1.0" encoding="utf-8"?>\n' + ET.tostring(x)
def get_new_status_form [html] (self):
'<div id="new-field">'
'<h3>%s</h3>' % _('New Status')
form = Form(enctype='multipart/form-data', action = 'newstatus')
form.add(StringWidget, 'name', title = _('Name'), required = True, size = 50)
form.add_submit('submit', _('Add'))
form.render()
'</div>'
def update_order(self):
request = get_request()
new_order = request.form['order'].strip(';').split(';')
self.workflow.possible_status = [ [x for x in self.workflow.possible_status if \
x.id == y][0] for y in new_order]
self.workflow.store()
return 'ok'
def newstatus(self):
form = Form(enctype='multipart/form-data', action = 'newstatus')
form.add(StringWidget, 'name', title = _('Name'), size = 50)
if not form.is_submitted() or form.has_errors():
get_session().message = ('error', _('Submitted form was not filled properly.'))
return redirect('.')
if form.get_widget('name').parse():
try:
self.workflow.add_status(form.get_widget('name').parse())
except DuplicateStatusNameError:
get_session().message = ('error', _('There is already a status with that name.'))
return redirect('.')
else:
get_session().message = ('error', _('Submitted form was not filled properly.'))
return redirect('.')
self.workflow.store()
return redirect('.')
def edit [html] (self, duplicate = False):
form = self.workflow_ui.form_edit()
if form.get_widget('cancel').parse():
return redirect('..')
if form.is_submitted() and not form.has_errors():
try:
self.workflow_ui.submit_form(form)
except ValueError:
pass
else:
return redirect('..')
html_top('workflows', title = _('Edit Workflow'))
if duplicate:
get_response().breadcrumb.append( ('edit', _('Duplicate')) )
'<h2>%s</h2>' % _('Duplicate Workflow')
else:
get_response().breadcrumb.append( ('edit', _('Edit')) )
'<h2>%s</h2>' % _('Edit Workflow')
form.render()
def delete [html] (self):
form = Form(enctype="multipart/form-data")
for formdef in FormDef.select():
if formdef.workflow_id == self.workflow.id:
form.widgets.append(HtmlWidget('<p>%s</p>' % _(
"This workflow is currently in use, you cannot remove it.")))
form.add_submit("cancel", _("Cancel"))
break
else:
form.widgets.append(HtmlWidget('<p>%s</p>' % _(
"You are about to irrevocably delete this workflow.")))
form.add_submit("submit", _("Submit"))
form.add_submit("cancel", _("Cancel"))
if form.get_widget('cancel').parse():
return redirect('..')
if not form.is_submitted() or form.has_errors():
get_response().breadcrumb.append(('delete', _('Delete')))
html_top('workflows', title = _('Delete Workflow'))
'<h2>%s %s</h2>' % (_('Deleting Workflow:'), self.workflow.name)
form.render()
else:
self.workflow.remove_self()
return redirect('..')
def duplicate [html] (self):
self.workflow_ui.workflow.id = None
original_name = self.workflow_ui.workflow.name
self.workflow_ui.workflow.name = self.workflow_ui.workflow.name + _(' (copy)')
workflow_names = [x.name for x in Workflow.select()]
no = 2
while self.workflow_ui.workflow.name in workflow_names:
self.workflow_ui.workflow.name = _('%(name)s (copy %(no)d)') % {
'name': original_name, 'no': no}
no += 1
self.workflow_ui.workflow.store()
return redirect('../%s/' % self.workflow_ui.workflow.id)
class WorkflowsDirectory(Directory):
_q_exports = ['', 'new', ('import', 'p_import')]
def _q_index [html] (self):
get_response().breadcrumb.append( ('workflows/', _('Workflows')) )
html_top('workflows', title = _('Workflows'))
'<ul id="main-actions">'
' <li><a class="new-item" href="new">%s</a></li>' % _('New Workflow')
' <li><a href="import" rel="popup">%s</a></li>' % _('Import')
'</ul>'
'<ul class="biglist">'
for workflow in [Workflow.get_default_workflow()] + Workflow.select(order_by = 'name'):
'<li>'
'<strong class="label"><a href="%s/">%s</a></strong>' % (workflow.id, workflow.name)
'<p class="details">'
'</p></li>'
'</ul>'
def new [html] (self):
get_response().breadcrumb.append( ('workflows/', _('Workflows')) )
get_response().breadcrumb.append( ('new', _('New')) )
workflow_ui = WorkflowUI(None)
form = workflow_ui.form_new()
if form.get_widget('cancel').parse():
return redirect('.')
if form.is_submitted() and not form.has_errors():
try:
workflow = workflow_ui.submit_form(form)
except ValueError:
pass
else:
return redirect('%s/' % workflow.id)
html_top('workflows', title = _('New Workflow'))
'<h2>%s</h2>' % _('New Workflow')
form.render()
def _q_lookup(self, component):
get_response().breadcrumb.append( ('workflows/', _('Workflows')) )
return WorkflowPage(component)
def p_import [html] (self):
form = Form(enctype = 'multipart/form-data')
form.add(FileWidget, 'file', title = _('File'), required = True)
form.add_submit('submit', _('Import Workflow'))
form.add_submit('cancel', _('Cancel'))
if form.get_submit() == 'cancel':
return redirect('.')
if form.is_submitted() and not form.has_errors():
try:
return self.import_submit(form)
except ValueError:
form.get_widget('file').set_error(_('Invalid File'))
get_response().breadcrumb.append( ('import', _('Import')) )
html_top('forms', title = _('Import Workflow'))
'<h2>%s</h2>' % _('Import Workflow')
form.render()
def import_submit(self, form):
workflow = Workflow.import_from_xml(form.get_widget('file').parse().fp)
initial_workflow_name = workflow.name
workflow_names = [x.name for x in Workflow.select()]
copy_no = 1
while workflow.name in workflow_names:
if copy_no == 1:
workflow.name = _('Copy of %s') % initial_workflow_name
else:
workflow.name = _('Copy of %(name)s (%(no)d)') % {
'name': initial_workflow_name, 'no': copy_no}
copy_no += 1
workflow.store()
return redirect('%s/' % workflow.id)