606 lines
19 KiB
Python
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
|
|
|