wcs/wcs/workflows.py

606 lines
19 KiB
Python

from qommon import ezt
from cStringIO import StringIO
from quixote import get_request
from qommon.storage import StorableObject
from qommon.form import *
from qommon import emails, get_cfg, get_logger
from wcs.roles import Role, logged_users_role, get_user_roles
from wcs.formdata import Evolution
if not __name__.startswith('wcs.'):
raise ImportError('Import of workflows module must be absolute (import wcs.workflows)')
def lax_int(s):
try:
return int(s)
except (ValueError, TypeError):
return -1
class Workflow(StorableObject):
_names = 'workflows'
name = None
details = None
possible_status = None
def __init__(self, name = None):
StorableObject.__init__(self)
self.name = name
self.possible_status = []
def add_status(self, name):
status = WorkflowStatus(name)
if self.possible_status:
status.id = str(max([lax_int(x.id) for x in self.possible_status]) + 1)
else:
status.id = '1'
self.possible_status.append(status)
def __setstate__(self, dict):
self.__dict__.update(dict)
for s in self.possible_status:
s.parent = self
for i, item in enumerate(s.items):
item.parent = s
if not item.id:
item.id = '%d' % (i+1)
def get_endpoint_status(self):
not_endpoint_status = self.get_not_endpoint_status()
endpoint_status = [x for x in self.possible_status if x not in not_endpoint_status]
return endpoint_status
def get_not_endpoint_status(self):
not_endpoint_status = []
for status in self.possible_status:
endpoint = True
for item in status.items:
endpoint = item.endpoint and endpoint
if endpoint is False:
not_endpoint_status.append(status)
break
return not_endpoint_status
class WorkflowStatus:
id = None
name = None
items = None
def __init__(self, name = None):
self.name = name
self.items = []
def append_item(self, type):
for klass in item_classes:
if klass.key == type:
o = klass()
if self.items:
o.id = str(max([lax_int(x.id) for x in self.items]) + 1)
else:
o.id = '1'
self.items.append(o)
break
else:
raise KeyError()
def perform_items(self, formdata):
url = None
for item in self.items:
url = item.perform(formdata) or url
return url
def get_action_form(self, filled, user):
form = Form(enctype='multipart/form-data', use_tokens = False)
for item in self.items:
if not item.check_auth(filled, user):
continue
item.fill_form(form, filled, user)
if form.widgets or form.submit_widgets:
return form
else:
return None
def handle_form(self, form, filled, user):
#form = self.get_action_form(filled, user)
evo = Evolution()
evo.time = time.localtime()
if user:
evo.who = user.id
if not filled.evolution:
filled.evolution = []
for item in self.items:
if hasattr(item, 'by'):
for role in item.by or []:
if role == logged_users_role().id:
break
if role == '_submitter':
if filled.user_id == user.id:
break
else:
continue
role = get_role_translation(filled.formdef, role)
if role in (user.roles or []):
break
else:
continue
next_url = item.submit_form(form, filled, user, evo)
if next_url is True:
break
if next_url:
return next_url
if form.has_errors():
return
filled.evolution.append(evo)
if evo.status:
filled.status = evo.status
filled.store()
filled.perform_workflow()
def __getstate__(self):
odict = self.__dict__.copy()
if odict.has_key('parent'):
del odict['parent']
return odict
class WorkflowStatusItem:
description = 'XX'
id = None
endpoint = True
def init(cls):
pass
init = classmethod(init)
def render_as_line(self):
return _(self.description)
def perform(self, formdata):
pass
def fill_form(self, form, formdata, user):
pass
def submit_form(self, form, formdata, user, evo):
pass
def check_auth(self, formdata, user):
if not hasattr(self, 'by'):
return True
for role in self.by or []:
if user and role == logged_users_role().id:
return True
if not user:
continue
if role == '_submitter':
return (formdata.user_id == user.id)
role = get_role_translation(formdata.formdef, role)
if role in (user.roles or []):
return True
return False
def __getstate__(self):
odict = self.__dict__.copy()
if odict.has_key('parent'):
del odict['parent']
return odict
def get_role_translation(formdef, role_name):
if role_name == '_receiver':
return formdef.receiver_id
elif role_name == '_submitter':
raise Exception('_submitter is not a valid role')
else:
return role_name
def render_list_of_roles(roles):
t = []
for r in roles:
if r == logged_users_role().id:
t.append(logged_users_role().name)
elif r == '_submitter':
t.append(_('sender'))
elif r == '_receiver':
t.append(_('receiver'))
else:
try:
t.append(Role.get(r).name)
except KeyError:
pass
return ', '.join(t)
item_classes = []
item_types = []
def register_item_class(klass):
if not klass in item_classes:
item_classes.append(klass)
item_types.append((klass.key, klass.description))
klass.init()
class CommentableWorkflowStatusItem(WorkflowStatusItem):
description = N_('Commentable')
key = 'commentable'
endpoint = False
by = None
def render_as_line(self):
if self.by:
return _('Commentable by %s') % render_list_of_roles(self.by)
else:
return _('Commentable (not completed)')
def fill_form(self, form, formdata, user):
if not 'comment' in [x.name for x in form.widgets]:
form.add(TextWidget, 'comment', title = _('Comment'), required = False,
cols = 40, rows = 10)
form.get_widget('comment').attrs['class'] = 'comment'
form.add_submit(self.id, _('Add Comment'))
def submit_form(self, form, formdata, user, evo):
if form.get_widget('comment'):
evo.comment = form.get_widget('comment').parse()
def fill_admin_form(self, form):
if self.by and not type(self.by) is list:
self.by = None
form.add(WidgetList, 'by', title = _('By'), element_type = SingleSelectWidget,
value = self.by,
add_element_label = _('Add Role'),
element_kwargs = {'render_br': False,
'options': [(None, '---'),
('_submitter', _('Sender')),
('_receiver', _('Receiver')),
(logged_users_role().id, logged_users_role().name),
(None, '----')] + get_user_roles()})
def submit_admin_form(self, form):
for f in ('by',):
widget = form.get_widget(f)
if widget:
setattr(self, f, widget.parse())
register_item_class(CommentableWorkflowStatusItem)
class ChoiceWorkflowStatusItem(WorkflowStatusItem):
description = N_('Choice')
key = 'choice'
endpoint = False
label = None
status = None
by = None
def render_as_line(self):
if self.label:
return _('Choice "%s"') % self.label
else:
return _('Choice (not completed)')
def fill_form(self, form, formdata, user):
form.add_submit(self.id, self.label)
def submit_form(self, form, formdata, user, evo):
if form.get_submit() == self.id:
wf_status = [x for x in self.parent.parent.possible_status \
if str(x.id) == str(self.status)][0]
evo.status = 'wf-%s' % wf_status.id
form.clear_errors()
return True # get out of processing loop
def fill_admin_form(self, form):
form.add(StringWidget, 'label', title = _('Label'), value = self.label)
form.add(SingleSelectWidget, 'status', title = _('Status'), value = self.status,
options = [(None, '---')] + [(x.id, x.name) for x in self.parent.parent.possible_status])
form.add(WidgetList, 'by', title = _('By'), element_type = SingleSelectWidget,
value = self.by,
add_element_label = _('Add Role'),
element_kwargs = {'render_br': False,
'options': [(None, '---'),
('_submitter', _('Sender')),
('_receiver', _('Receiver')),
(logged_users_role().id, logged_users_role().name),
(None, '----')] + get_user_roles()})
def submit_admin_form(self, form):
for f in ('by', 'status', 'label'):
widget = form.get_widget(f)
if widget:
setattr(self, f, widget.parse())
register_item_class(ChoiceWorkflowStatusItem)
class JumpOnSubmitWorkflowStatusItem(WorkflowStatusItem):
description = N_('Jump on Submit')
key = 'jumponsubmit'
status = None
def submit_form(self, form, formdata, user, evo):
if form.is_submitted() and not form.has_errors():
wf_status = [x for x in self.parent.parent.possible_status if x.id == self.status][0]
evo.status = 'wf-%s' % wf_status.id
def fill_admin_form(self, form):
form.add(SingleSelectWidget, 'status', title = _('Status'), value = self.status,
options = [(None, '---')] + [(x.id, x.name) for x in self.parent.parent.possible_status])
def submit_admin_form(self, form):
for f in ('status',):
widget = form.get_widget(f)
if widget:
setattr(self, f, widget.parse())
register_item_class(JumpOnSubmitWorkflowStatusItem)
class SendmailWorkflowStatusItem(WorkflowStatusItem):
description = N_('Send mail')
key = 'sendmail'
to = None
subject = None
body = None
comment = None
def render_as_line(self):
if self.to:
return _('Send mail to %s') % render_list_of_roles(self.to)
else:
return _('Send mail (not completed)')
def fill_admin_form(self, form):
form.add(WidgetList, 'to', title = _('To'), element_type = SingleSelectWidget,
value = self.to,
add_element_label = _('Add Role'),
element_kwargs = {'render_br': False,
'options': [(None, '---'),
('_submitter', _('Sender')),
('_receiver', _('Receiver')), ] + get_user_roles()})
form.add(StringWidget, 'subject', title = _('Subject'), value = self.subject,
size = 40)
form.add(TextWidget, 'body', title = _('Body'), value = self.body, cols = 80, rows = 10,
hint = _('Available variables: url, url_status, details, name, number, comment, field_NAME'))
def submit_admin_form(self, form):
for f in ('to', 'subject', 'body'):
widget = form.get_widget(f)
if widget:
setattr(self, f, widget.parse())
def perform(self, formdata):
if not self.to:
return
if not self.subject:
return
dict = {}
dict['url'] = formdata.get_url()
dict['url_status'] = '%sstatus' % formdata.get_url()
dict['details'] = formdata.formdef.get_detailed_email_form(formdata, dict['url'])
dict['name'] = formdata.formdef.name
dict['number'] = formdata.id
if formdata.evolution and formdata.evolution[-1].comment:
dict['comment'] = formdata.evolution[-1].comment
else:
dict['comment'] = ''
dict.update(formdata.get_as_dict(prefix = 'field_'))
mail_body_template = ezt.Template(compress_whitespace = False)
mail_body_template.parse(self.body or '')
fd = StringIO()
try:
mail_body_template.generate(fd, dict)
except ezt.EZTException:
url = dict['url']
get_logger().error('error in template for email [%s], mail could not be generated' % url)
return
mail_body = fd.getvalue()
mail_subject_template = ezt.Template(compress_whitespace = False)
mail_subject_template.parse(self.subject)
fd = StringIO()
try:
mail_subject_template.generate(fd, dict)
except ezt.EZTException:
get_logger().error('error in template for email [%s], mail could not be generated' % url)
return
mail_subject = fd.getvalue()
users_cfg = get_cfg('users', {})
addresses = []
for dest in self.to:
if dest == '_submitter':
field_email = users_cfg.get('field_email') or 'email'
done = False
if formdata.user:
if field_email == 'email' and formdata.user.email:
addresses.append(formdata.user.email)
done = True
elif formdata.user.form_data.get(field_email):
addresses.append(formdata.user.form_data.get(field_email))
done = True
if not done:
# if there is no user, or user has no email address, look
# up in submitted form for one that would hold the user
# email (the one set to be prefilled by user email)
fields = formdata.formdef.fields
for field in fields:
if not hasattr(field, 'prefill'):
continue
if field.prefill and field.prefill.get('type') == 'user':
if field.prefill.get('value') == field_email:
v = formdata.data.get(field.id)
if v:
addresses.append(v)
done = True
break
continue
dest = get_role_translation(formdata.formdef, dest)
try:
role = Role.get(dest)
except KeyError:
continue
if role.emails:
addresses.extend(role.emails)
if not addresses:
return
emails.email(mail_subject, mail_body, email_rcpt = addresses[0],
bcc = addresses[1:],
exclude_current_user = False,
fire_and_forget = True)
register_item_class(SendmailWorkflowStatusItem)
class DisplayMessageWorkflowStatusItem(WorkflowStatusItem):
description = N_('Display message')
key = 'displaymsg'
message = None
def get_message(self, filled):
if not self.message:
return ''
tmpl = ezt.Template()
tmpl.parse(self.message)
dict = {}
dict['date'] = misc.localstrftime(filled.receipt_time)
dict['number'] = filled.id
if filled.formdef.receiver and filled.formdef.receiver.details:
dict['receiver'] = filled.formdef.receiver.details.replace('\n', '<br />')
fd = StringIO()
tmpl.generate(fd, dict)
msg = fd.getvalue()
return msg
def fill_admin_form(self, form):
form.add(TextWidget, 'message', title = _('Message'),
value = self.message, cols = 80, rows = 10)
def submit_admin_form(self, form):
for f in ('message',):
widget = form.get_widget(f)
if widget:
setattr(self, f, widget.parse())
register_item_class(DisplayMessageWorkflowStatusItem)
class RedirectToStatusWorkflowStatusItem(WorkflowStatusItem):
description = N_('Redirect to Status Page')
key = 'redirectstatus'
backoffice = False
def perform(self, formdata):
if not get_request().user:
return None
return formdata.get_url(self.backoffice)
def fill_admin_form(self, form):
form.add(CheckboxWidget, 'backoffice', title = _('Redirect to backoffice page'),
value = self.backoffice)
def submit_admin_form(self, form):
for f in ('backoffice',):
widget = form.get_widget(f)
if widget:
setattr(self, f, widget.parse())
register_item_class(RedirectToStatusWorkflowStatusItem)
class EditableWorkflowStatusItem(WorkflowStatusItem):
description = N_('Editable')
key = 'editable'
endpoint = False
by = None
status = None
label = None
def render_as_line(self):
if self.by:
return _('Editable by %s') % render_list_of_roles(self.by)
else:
return _('Editable (not completed)')
def fill_form(self, form, formdata, user):
label = self.label
if not label:
label = _('Edit Form')
form.add_submit(self.id, label)
def submit_form(self, form, formdata, user, evo):
if form.get_submit() == self.id:
return formdata.get_url() + 'wfedit'
def fill_admin_form(self, form):
form.add(WidgetList, 'by', title = _('By'), element_type = SingleSelectWidget,
value = self.by,
add_element_label = _('Add Role'),
element_kwargs = {'render_br': False,
'options': [(None, '---'),
('_submitter', _('Sender')),
('_receiver', _('Receiver')),
(logged_users_role().id, logged_users_role().name),
(None, '----')] + get_user_roles()})
form.add(SingleSelectWidget, 'status', title = _('Status After Edit'), value = self.status,
hint = _("Don't select any if you don't want status change processing"),
options = [(None, '---')] + [(x.id, x.name) for x in self.parent.parent.possible_status])
form.add(StringWidget, 'label', title = _('Button Label'), value = self.label)
def submit_admin_form(self, form):
for f in ('by', 'status', 'label'):
widget = form.get_widget(f)
if widget:
setattr(self, f, widget.parse())
register_item_class(EditableWorkflowStatusItem)
import wf.aggregation_email