2010-04-21 10:50:16 +02:00
|
|
|
# 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
|
2012-01-26 17:32:10 +01:00
|
|
|
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
2010-04-21 10:50:16 +02:00
|
|
|
|
2019-09-29 20:53:23 +02:00
|
|
|
from .qommon import ezt
|
2016-04-04 22:05:06 +02:00
|
|
|
import collections
|
2013-01-21 16:24:21 +01:00
|
|
|
import copy
|
2016-04-04 22:05:06 +02:00
|
|
|
import datetime
|
2014-08-28 12:15:52 +02:00
|
|
|
import xml.etree.ElementTree as ET
|
2014-09-17 21:29:23 +02:00
|
|
|
import random
|
|
|
|
import os
|
2016-04-04 22:05:06 +02:00
|
|
|
import sys
|
|
|
|
import time
|
|
|
|
import uuid
|
2012-01-26 13:59:22 +01:00
|
|
|
|
2019-11-12 19:44:52 +01:00
|
|
|
from django.utils import six
|
2019-11-11 20:10:57 +01:00
|
|
|
from django.utils.encoding import force_text
|
2019-02-28 15:17:35 +01:00
|
|
|
|
2018-05-18 10:34:18 +02:00
|
|
|
from quixote import get_request, get_response, redirect
|
2006-06-09 08:45:36 +02:00
|
|
|
|
2020-03-03 09:36:42 +01:00
|
|
|
from .qommon import _, N_, force_str
|
2020-01-24 14:25:43 +01:00
|
|
|
from .qommon.misc import C_, get_as_datetime, file_digest, get_foreground_colour, xml_node_text
|
2019-11-26 18:15:08 +01:00
|
|
|
from .qommon.storage import StorableObject, atomic_write, NotEqual, Contains, Null, pickle_2to3_conversion
|
2019-09-29 20:53:23 +02:00
|
|
|
from .qommon.form import *
|
|
|
|
from .qommon.humantime import seconds2humanduration
|
|
|
|
from .qommon import emails, get_cfg, get_logger
|
2011-06-21 22:05:47 +02:00
|
|
|
from quixote.html import htmltext
|
2019-09-29 20:53:23 +02:00
|
|
|
from .qommon import errors
|
|
|
|
from .qommon.template import Template, TemplateError
|
2006-03-27 17:07:55 +02:00
|
|
|
|
2019-09-29 20:53:23 +02:00
|
|
|
from .conditions import Condition
|
|
|
|
from .roles import Role, logged_users_role, get_user_roles
|
|
|
|
from .fields import FileField
|
|
|
|
from .formdef import FormDef
|
|
|
|
from .formdata import Evolution
|
2020-01-21 09:55:20 +01:00
|
|
|
from .mail_templates import MailTemplate
|
2006-06-08 23:26:17 +02:00
|
|
|
|
2009-11-21 15:13:47 +01:00
|
|
|
if not __name__.startswith('wcs.') and not __name__ == "__main__":
|
2007-12-28 23:29:10 +01:00
|
|
|
raise ImportError('Import of workflows module must be absolute (import wcs.workflows)')
|
|
|
|
|
2020-01-18 20:33:44 +01:00
|
|
|
|
2006-06-19 09:56:02 +02:00
|
|
|
def lax_int(s):
|
|
|
|
try:
|
|
|
|
return int(s)
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
return -1
|
|
|
|
|
|
|
|
|
2015-12-12 10:46:24 +01:00
|
|
|
def perform_items(items, formdata, depth=20):
|
|
|
|
if depth == 0: # prevents infinite loops
|
|
|
|
return
|
|
|
|
url = None
|
|
|
|
old_status = formdata.status
|
|
|
|
for item in items:
|
2018-03-23 17:08:24 +01:00
|
|
|
if not item.check_condition(formdata):
|
|
|
|
continue
|
2015-12-12 10:46:24 +01:00
|
|
|
try:
|
|
|
|
url = item.perform(formdata) or url
|
|
|
|
except AbortActionException:
|
|
|
|
break
|
|
|
|
if formdata.status != old_status:
|
|
|
|
break
|
|
|
|
if formdata.status != old_status:
|
|
|
|
if not formdata.evolution:
|
|
|
|
formdata.evolution = []
|
|
|
|
evo = Evolution()
|
|
|
|
evo.time = time.localtime()
|
|
|
|
evo.status = formdata.status
|
|
|
|
formdata.evolution.append(evo)
|
|
|
|
formdata.store()
|
|
|
|
# performs the items of the new status
|
|
|
|
wf_status = formdata.get_status()
|
|
|
|
url = perform_items(wf_status.items, formdata, depth=depth-1) or url
|
|
|
|
return url
|
|
|
|
|
|
|
|
|
2014-10-30 11:17:38 +01:00
|
|
|
class WorkflowImportError(Exception):
|
2016-12-21 10:10:06 +01:00
|
|
|
def __init__(self, msg, msg_args=None):
|
2019-03-15 09:16:12 +01:00
|
|
|
super(WorkflowImportError, self).__init__(msg)
|
2016-12-21 10:10:06 +01:00
|
|
|
self.msg_args = msg_args or ()
|
2014-10-30 11:17:38 +01:00
|
|
|
|
|
|
|
|
2015-09-18 13:03:02 +02:00
|
|
|
class AbortActionException(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2015-11-02 11:44:35 +01:00
|
|
|
class AttachmentSubstitutionProxy(object):
|
|
|
|
def __init__(self, formdata, attachment_evolution_part):
|
|
|
|
self.formdata = formdata
|
|
|
|
self.attachment_evolution_part = attachment_evolution_part
|
|
|
|
|
|
|
|
@property
|
|
|
|
def filename(self):
|
|
|
|
return self.attachment_evolution_part.orig_filename
|
|
|
|
|
|
|
|
@property
|
|
|
|
def content_type(self):
|
|
|
|
return self.attachment_evolution_part.content_type
|
|
|
|
|
|
|
|
@property
|
|
|
|
def content(self):
|
|
|
|
return self.attachment_evolution_part.get_file_pointer().read()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def b64_content(self):
|
|
|
|
return base64.b64encode(self.content)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def url(self):
|
|
|
|
return '%sattachment?f=%s' % (self.formdata.get_url(),
|
|
|
|
os.path.basename(self.attachment_evolution_part.filename))
|
|
|
|
|
|
|
|
|
|
|
|
class NamedAttachmentsSubstitutionProxy(object):
|
2019-05-17 20:08:42 +02:00
|
|
|
def __init__(self, formdata, parts):
|
2015-11-02 11:44:35 +01:00
|
|
|
self.formdata = formdata
|
2019-05-17 20:08:42 +02:00
|
|
|
self.parts = parts[:]
|
|
|
|
self.parts.reverse()
|
2015-11-02 11:44:35 +01:00
|
|
|
|
|
|
|
def __len__(self):
|
2019-05-17 20:08:42 +02:00
|
|
|
return len(self.parts)
|
2015-11-02 11:44:35 +01:00
|
|
|
|
|
|
|
def __getattr__(self, name):
|
|
|
|
return getattr(self[0], name)
|
|
|
|
|
|
|
|
def __getitem__(self, i):
|
2019-05-17 20:08:42 +02:00
|
|
|
return AttachmentSubstitutionProxy(self.formdata, self.parts[i])
|
2015-11-02 11:44:35 +01:00
|
|
|
|
|
|
|
|
|
|
|
class AttachmentsSubstitutionProxy(object):
|
|
|
|
def __init__(self, formdata):
|
|
|
|
self.formdata = formdata
|
|
|
|
|
|
|
|
def __getattr__(self, name):
|
2019-11-13 14:27:19 +01:00
|
|
|
if name.startswith('__'):
|
|
|
|
raise AttributeError(name)
|
2019-05-17 20:08:42 +02:00
|
|
|
def has_varname_attachment(part):
|
|
|
|
return isinstance(part, AttachmentEvolutionPart) and getattr(part, 'varname', None) == name
|
|
|
|
|
|
|
|
parts = [part for part in self.formdata.iter_evolution_parts()
|
|
|
|
if has_varname_attachment(part)]
|
|
|
|
if parts:
|
|
|
|
return NamedAttachmentsSubstitutionProxy(self.formdata, parts)
|
2015-11-02 11:44:35 +01:00
|
|
|
raise AttributeError(name)
|
|
|
|
|
|
|
|
|
2014-12-20 19:50:40 +01:00
|
|
|
class AttachmentEvolutionPart: #pylint: disable=C1001
|
2014-09-17 21:29:23 +02:00
|
|
|
orig_filename = None
|
|
|
|
base_filename = None
|
|
|
|
content_type = None
|
|
|
|
charset = None
|
2015-10-31 16:26:31 +01:00
|
|
|
varname = None
|
2014-09-17 21:29:23 +02:00
|
|
|
|
2014-02-12 11:25:53 +01:00
|
|
|
def __init__(self, base_filename, fp, orig_filename=None, content_type=None,
|
2015-10-31 16:26:31 +01:00
|
|
|
charset=None, varname=None):
|
2014-02-12 11:25:53 +01:00
|
|
|
self.base_filename = base_filename
|
|
|
|
self.orig_filename = orig_filename or base_filename
|
|
|
|
self.content_type = content_type
|
|
|
|
self.charset = charset
|
|
|
|
self.fp = fp
|
2015-10-31 16:26:31 +01:00
|
|
|
self.varname = varname
|
2014-02-12 11:25:53 +01:00
|
|
|
|
2016-03-11 16:40:16 +01:00
|
|
|
@classmethod
|
2015-10-31 16:26:31 +01:00
|
|
|
def from_upload(cls, upload, varname=None):
|
2014-02-12 11:25:53 +01:00
|
|
|
return AttachmentEvolutionPart(
|
|
|
|
upload.base_filename,
|
|
|
|
upload.fp,
|
|
|
|
upload.orig_filename,
|
|
|
|
upload.content_type,
|
2015-10-31 16:26:31 +01:00
|
|
|
upload.charset,
|
|
|
|
varname=varname)
|
2014-09-17 21:29:23 +02:00
|
|
|
|
2015-08-20 11:21:25 +02:00
|
|
|
def get_file_pointer(self):
|
2019-11-15 14:27:29 +01:00
|
|
|
return open(self.filename, 'rb')
|
2015-08-20 11:21:25 +02:00
|
|
|
|
2014-09-17 21:29:23 +02:00
|
|
|
def __getstate__(self):
|
|
|
|
odict = self.__dict__.copy()
|
2019-11-12 11:54:16 +01:00
|
|
|
if not 'fp' in odict:
|
2014-09-17 21:29:23 +02:00
|
|
|
return odict
|
|
|
|
|
|
|
|
del odict['fp']
|
|
|
|
dirname = os.path.join(get_publisher().app_dir, 'attachments')
|
|
|
|
if not os.path.exists(dirname):
|
|
|
|
os.mkdir(dirname)
|
|
|
|
|
2016-06-14 13:33:33 +02:00
|
|
|
if not 'filename' in odict:
|
2017-01-21 23:25:12 +01:00
|
|
|
filename = file_digest(self.fp)
|
2018-06-01 10:58:15 +02:00
|
|
|
dirname = os.path.join(dirname, filename[:4])
|
|
|
|
if not os.path.exists(dirname):
|
|
|
|
os.mkdir(dirname)
|
2017-01-21 23:25:12 +01:00
|
|
|
odict['filename'] = os.path.join(dirname, filename)
|
2016-06-14 13:33:33 +02:00
|
|
|
self.filename = odict['filename']
|
|
|
|
self.fp.seek(0)
|
2017-01-21 23:25:12 +01:00
|
|
|
atomic_write(self.filename, self.fp)
|
2014-09-17 21:29:23 +02:00
|
|
|
return odict
|
|
|
|
|
|
|
|
def view(self):
|
2017-07-05 09:10:09 +02:00
|
|
|
return htmltext('<p class="wf-attachment"><a href="attachment?f=%s">%s</a>' % (
|
2014-09-17 21:29:23 +02:00
|
|
|
os.path.basename(self.filename), self.orig_filename))
|
|
|
|
|
2015-11-02 11:44:35 +01:00
|
|
|
@classmethod
|
|
|
|
def get_substitution_variables(cls, formdata):
|
2017-06-01 08:46:49 +02:00
|
|
|
return {'attachments': AttachmentsSubstitutionProxy(formdata),
|
|
|
|
'form_attachments': AttachmentsSubstitutionProxy(formdata)}
|
2015-11-02 11:44:35 +01:00
|
|
|
|
2020-02-05 12:28:36 +01:00
|
|
|
# mimic PicklableUpload methods:
|
|
|
|
|
|
|
|
def can_thumbnail(self):
|
|
|
|
return True
|
|
|
|
|
|
|
|
def has_redirect_url(self):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def get_redirect_url(self, upload, backoffice=False):
|
|
|
|
# should never be called, has_redirect_url is False
|
|
|
|
raise AssertionError('no get_redirect_url on AttachmentEvolutionPart object')
|
|
|
|
|
2014-09-17 21:29:23 +02:00
|
|
|
|
2015-12-12 10:46:24 +01:00
|
|
|
class DuplicateGlobalActionNameError(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2012-01-25 14:56:07 +01:00
|
|
|
class DuplicateStatusNameError(Exception):
|
|
|
|
pass
|
|
|
|
|
2011-06-21 22:06:28 +02:00
|
|
|
|
2015-02-02 16:51:45 +01:00
|
|
|
class WorkflowVariablesFieldsFormDef(FormDef):
|
|
|
|
'''Class to handle workflow variables, it loads and saves from/to
|
|
|
|
the workflow object 'variables_formdef' attribute.'''
|
|
|
|
|
2018-04-09 12:21:44 +02:00
|
|
|
lightweight = False
|
|
|
|
|
2015-02-02 16:51:45 +01:00
|
|
|
def __init__(self, workflow):
|
|
|
|
self.id = None
|
|
|
|
self.workflow = workflow
|
2016-04-15 10:30:17 +02:00
|
|
|
if workflow.variables_formdef and workflow.variables_formdef.fields:
|
2015-02-02 16:51:45 +01:00
|
|
|
self.fields = self.workflow.variables_formdef.fields
|
2016-04-05 21:54:13 +02:00
|
|
|
self.max_field_id = max([lax_int(x.id) for x in self.fields or []])
|
2015-02-02 16:51:45 +01:00
|
|
|
else:
|
|
|
|
self.fields = []
|
|
|
|
|
2019-10-21 21:28:01 +02:00
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
return _('Options of workflow "%s"') % self.workflow.name
|
|
|
|
|
|
|
|
def get_admin_url(self):
|
|
|
|
base_url = get_publisher().get_backoffice_url()
|
|
|
|
return '%s/workflows/%s/variables/fields/' % (base_url, self.workflow.id)
|
|
|
|
|
2015-02-02 16:51:45 +01:00
|
|
|
def store(self):
|
|
|
|
for field in self.fields:
|
|
|
|
if hasattr(field, 'widget_class'):
|
|
|
|
if not field.varname:
|
|
|
|
field.varname = misc.simplify(field.label, space='_')
|
|
|
|
self.workflow.variables_formdef = self
|
|
|
|
self.workflow.store()
|
|
|
|
|
|
|
|
|
2016-06-06 18:55:21 +02:00
|
|
|
class WorkflowBackofficeFieldsFormDef(FormDef):
|
|
|
|
'''Class to handle workflow backoffice fields, it loads and saves from/to
|
|
|
|
the workflow object 'backoffice_fields_formdef' attribute.'''
|
|
|
|
|
2018-04-09 12:21:44 +02:00
|
|
|
lightweight = False
|
|
|
|
|
2016-06-06 18:55:21 +02:00
|
|
|
field_prefix = 'bo'
|
|
|
|
|
|
|
|
def __init__(self, workflow):
|
|
|
|
self.id = None
|
|
|
|
self.workflow = workflow
|
|
|
|
if workflow.backoffice_fields_formdef and workflow.backoffice_fields_formdef.fields:
|
|
|
|
self.fields = self.workflow.backoffice_fields_formdef.fields
|
|
|
|
else:
|
|
|
|
self.fields = []
|
|
|
|
|
2019-10-21 21:28:01 +02:00
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
return _('Backoffice fields of workflow "%s"') % self.workflow.name
|
|
|
|
|
|
|
|
def get_admin_url(self):
|
|
|
|
base_url = get_publisher().get_backoffice_url()
|
|
|
|
return '%s/workflows/%s/backoffice-fields/fields/' % (base_url, self.workflow.id)
|
|
|
|
|
2016-06-06 18:55:21 +02:00
|
|
|
def get_new_field_id(self):
|
2019-04-08 19:19:35 +02:00
|
|
|
return '%s%s' % (self.field_prefix, str(uuid.uuid4()))
|
2016-06-06 18:55:21 +02:00
|
|
|
|
|
|
|
def store(self):
|
|
|
|
self.workflow.backoffice_fields_formdef = self
|
|
|
|
self.workflow.store()
|
|
|
|
|
|
|
|
|
2006-03-27 17:07:55 +02:00
|
|
|
class Workflow(StorableObject):
|
|
|
|
_names = 'workflows'
|
|
|
|
name = None
|
2006-06-08 21:14:31 +02:00
|
|
|
possible_status = None
|
2013-01-21 16:24:21 +01:00
|
|
|
roles = None
|
2015-02-02 16:51:45 +01:00
|
|
|
variables_formdef = None
|
2016-06-06 18:55:21 +02:00
|
|
|
backoffice_fields_formdef = None
|
2015-12-12 10:46:24 +01:00
|
|
|
global_actions = None
|
2016-03-07 21:28:20 +01:00
|
|
|
criticality_levels = None
|
2006-03-27 17:07:55 +02:00
|
|
|
|
2013-03-27 17:12:44 +01:00
|
|
|
last_modification_time = None
|
|
|
|
last_modification_user_id = None
|
|
|
|
|
2006-03-27 17:07:55 +02:00
|
|
|
def __init__(self, name = None):
|
|
|
|
StorableObject.__init__(self)
|
|
|
|
self.name = name
|
2006-06-08 21:14:31 +02:00
|
|
|
self.possible_status = []
|
2015-09-12 14:20:07 +02:00
|
|
|
self.roles = {'_receiver': _('Recipient')}
|
2015-12-12 10:46:24 +01:00
|
|
|
self.global_actions = []
|
2016-03-07 21:28:20 +01:00
|
|
|
self.criticality_levels = []
|
2006-06-08 21:14:31 +02:00
|
|
|
|
2013-01-21 16:24:21 +01:00
|
|
|
def migrate(self):
|
|
|
|
changed = False
|
|
|
|
|
2019-11-12 11:54:16 +01:00
|
|
|
if not 'roles' in self.__dict__ or self.roles is None:
|
2013-01-21 16:24:21 +01:00
|
|
|
self.roles = {'_receiver': _('Recipient')}
|
|
|
|
changed = True
|
|
|
|
|
2015-06-17 14:58:40 +02:00
|
|
|
for status in self.possible_status:
|
|
|
|
changed |= status.migrate()
|
2019-10-14 17:17:29 +02:00
|
|
|
|
|
|
|
if self.backoffice_fields_formdef and self.backoffice_fields_formdef.fields:
|
|
|
|
for field in self.backoffice_fields_formdef.fields:
|
|
|
|
changed |= field.migrate()
|
2015-06-17 14:58:40 +02:00
|
|
|
|
2015-12-12 10:46:24 +01:00
|
|
|
if not self.global_actions:
|
|
|
|
self.global_actions = []
|
|
|
|
|
2013-01-21 16:24:21 +01:00
|
|
|
if changed:
|
|
|
|
self.store()
|
|
|
|
|
2013-03-27 17:12:44 +01:00
|
|
|
def store(self):
|
2016-06-06 18:55:21 +02:00
|
|
|
must_update = False
|
2015-07-23 18:11:20 +02:00
|
|
|
if self.id:
|
|
|
|
old_self = self.get(self.id, ignore_errors=True, ignore_migration=True)
|
|
|
|
if old_self:
|
|
|
|
old_endpoints = set([x.id for x in old_self.get_endpoint_status()])
|
|
|
|
if old_endpoints != set([x.id for x in self.get_endpoint_status()]):
|
2016-06-06 18:55:21 +02:00
|
|
|
must_update = True
|
2016-03-07 21:28:20 +01:00
|
|
|
old_criticality_levels = len(old_self.criticality_levels or [0])
|
|
|
|
if old_criticality_levels != len(self.criticality_levels or [0]):
|
2016-06-06 18:55:21 +02:00
|
|
|
must_update = True
|
|
|
|
try:
|
|
|
|
old_backoffice_fields = old_self.backoffice_fields_formdef.fields
|
|
|
|
except AttributeError:
|
|
|
|
old_backoffice_fields = []
|
|
|
|
try:
|
|
|
|
new_backoffice_fields = self.backoffice_fields_formdef.fields
|
|
|
|
except AttributeError:
|
|
|
|
new_backoffice_fields = []
|
|
|
|
if len(old_backoffice_fields) != len(new_backoffice_fields):
|
|
|
|
must_update = True
|
|
|
|
elif self.backoffice_fields_formdef:
|
|
|
|
must_update = True
|
2015-07-23 18:11:20 +02:00
|
|
|
|
2013-03-27 17:12:44 +01:00
|
|
|
self.last_modification_time = time.localtime()
|
2013-07-26 16:18:57 +02:00
|
|
|
if get_request() and get_request().user:
|
2015-07-23 19:09:21 +02:00
|
|
|
self.last_modification_user_id = str(get_request().user.id)
|
2013-03-27 17:12:44 +01:00
|
|
|
else:
|
|
|
|
self.last_modification_user_id = None
|
2014-04-05 20:26:11 +02:00
|
|
|
StorableObject.store(self)
|
|
|
|
|
2018-05-18 10:34:18 +02:00
|
|
|
def update(job=None):
|
|
|
|
# instruct all related formdefs to update.
|
2020-02-11 18:06:10 +01:00
|
|
|
for form in self.formdefs(ignore_migration=True, order_by='id'):
|
2018-05-18 10:34:18 +02:00
|
|
|
form.data_class().rebuild_security()
|
|
|
|
if must_update:
|
|
|
|
form.rebuild()
|
|
|
|
|
|
|
|
if get_response():
|
|
|
|
get_response().add_after_job(
|
|
|
|
N_('Reindexing forms after workflow change'), update)
|
|
|
|
else:
|
|
|
|
update()
|
2013-03-27 17:12:44 +01:00
|
|
|
|
2016-03-11 16:40:16 +01:00
|
|
|
@classmethod
|
2012-08-13 10:02:22 +02:00
|
|
|
def get(cls, id, ignore_errors=False, ignore_migration=False):
|
|
|
|
if id == '_default':
|
|
|
|
return cls.get_default_workflow()
|
2019-09-24 17:59:07 +02:00
|
|
|
elif id == '_carddef_default':
|
|
|
|
from wcs.carddef import CardDef
|
|
|
|
return CardDef.get_default_workflow()
|
2012-08-13 10:02:22 +02:00
|
|
|
return super(Workflow, cls).get (id, ignore_errors=ignore_errors,
|
|
|
|
ignore_migration=ignore_migration)
|
|
|
|
|
2012-06-20 13:02:28 +02:00
|
|
|
def add_status(self, name, id=None):
|
2012-01-25 14:56:07 +01:00
|
|
|
if [x for x in self.possible_status if x.name == name]:
|
|
|
|
raise DuplicateStatusNameError()
|
2006-06-08 21:14:31 +02:00
|
|
|
status = WorkflowStatus(name)
|
2012-06-20 13:02:28 +02:00
|
|
|
status.parent = self
|
2006-06-08 21:14:31 +02:00
|
|
|
|
2012-06-20 13:02:28 +02:00
|
|
|
if id is None:
|
|
|
|
if self.possible_status:
|
|
|
|
status.id = str(max([lax_int(x.id) for x in self.possible_status]) + 1)
|
|
|
|
else:
|
|
|
|
status.id = '1'
|
2006-06-08 21:14:31 +02:00
|
|
|
else:
|
2012-06-20 13:02:28 +02:00
|
|
|
status.id = id
|
2006-06-08 21:14:31 +02:00
|
|
|
self.possible_status.append(status)
|
2010-09-18 16:52:21 +02:00
|
|
|
return status
|
2006-06-08 21:14:31 +02:00
|
|
|
|
2011-04-17 19:38:25 +02:00
|
|
|
def get_status(self, id):
|
2019-06-30 15:53:31 +02:00
|
|
|
if id.startswith('wf-'):
|
|
|
|
id = id[3:]
|
2011-04-17 19:38:25 +02:00
|
|
|
for status in self.possible_status:
|
|
|
|
if status.id == id:
|
|
|
|
return status
|
|
|
|
raise KeyError()
|
|
|
|
|
2016-06-06 18:55:21 +02:00
|
|
|
def get_backoffice_fields(self):
|
|
|
|
if self.backoffice_fields_formdef:
|
|
|
|
return self.backoffice_fields_formdef.fields or []
|
|
|
|
return []
|
|
|
|
|
2020-01-21 09:55:20 +01:00
|
|
|
def get_all_items(self):
|
|
|
|
for status in self.possible_status or []:
|
|
|
|
for item in status.items or []:
|
|
|
|
yield item
|
|
|
|
for action in self.global_actions or []:
|
|
|
|
for item in action.items or []:
|
|
|
|
yield item
|
|
|
|
|
2015-12-12 10:46:24 +01:00
|
|
|
def add_global_action(self, name, id=None):
|
|
|
|
if [x for x in self.global_actions if x.name == name]:
|
|
|
|
raise DuplicateGlobalActionNameError()
|
|
|
|
action = WorkflowGlobalAction(name)
|
|
|
|
action.parent = self
|
|
|
|
action.append_trigger('manual')
|
|
|
|
|
|
|
|
if id is None:
|
|
|
|
if self.global_actions:
|
|
|
|
action.id = str(max([lax_int(x.id) for x in self.global_actions]) + 1)
|
|
|
|
else:
|
|
|
|
action.id = '1'
|
|
|
|
else:
|
|
|
|
action.id = id
|
|
|
|
self.global_actions.append(action)
|
|
|
|
return action
|
|
|
|
|
2019-08-31 10:38:14 +02:00
|
|
|
def get_global_manual_actions(self):
|
|
|
|
actions = []
|
|
|
|
for action in self.global_actions or []:
|
|
|
|
roles = []
|
|
|
|
for trigger in action.triggers or []:
|
|
|
|
if not isinstance(trigger, WorkflowGlobalActionManualTrigger):
|
|
|
|
continue
|
|
|
|
roles.extend(trigger.roles or [])
|
|
|
|
functions = [x for x in roles if x in self.roles]
|
|
|
|
roles = [x for x in roles if x not in self.roles]
|
|
|
|
if functions or roles:
|
|
|
|
actions.append({'action': action, 'roles': roles, 'functions': functions})
|
|
|
|
return actions
|
|
|
|
|
|
|
|
|
2015-12-12 10:46:24 +01:00
|
|
|
def get_global_actions_for_user(self, formdata, user):
|
|
|
|
if not user:
|
|
|
|
return []
|
|
|
|
actions = []
|
|
|
|
for action in self.global_actions or []:
|
|
|
|
for trigger in action.triggers or []:
|
|
|
|
if isinstance(trigger, WorkflowGlobalActionManualTrigger):
|
2018-03-03 16:05:46 +01:00
|
|
|
if '_submitter' in (trigger.roles or []) and formdata.is_submitter(user):
|
|
|
|
actions.append(action)
|
|
|
|
break
|
2015-12-12 10:46:24 +01:00
|
|
|
roles = [get_role_translation(formdata, x)
|
2018-03-03 16:05:46 +01:00
|
|
|
for x in (trigger.roles or []) if x != '_submitter']
|
2019-09-03 10:55:53 +02:00
|
|
|
if set(roles).intersection(user.get_roles()):
|
2015-12-12 10:46:24 +01:00
|
|
|
actions.append(action)
|
|
|
|
break
|
|
|
|
return actions
|
|
|
|
|
2019-04-11 10:47:18 +02:00
|
|
|
def get_subdirectories(self, formdata):
|
|
|
|
wf_status = formdata.get_status()
|
|
|
|
if not wf_status: # draft
|
|
|
|
return []
|
2019-04-11 10:48:03 +02:00
|
|
|
directories = []
|
|
|
|
for action in self.global_actions:
|
|
|
|
for trigger in action.triggers or []:
|
|
|
|
directories.extend(trigger.get_subdirectories(formdata))
|
|
|
|
directories.extend(wf_status.get_subdirectories(formdata))
|
|
|
|
return directories
|
2019-04-11 10:47:18 +02:00
|
|
|
|
2006-06-09 13:07:36 +02:00
|
|
|
def __setstate__(self, dict):
|
|
|
|
self.__dict__.update(dict)
|
2019-11-26 18:15:08 +01:00
|
|
|
pickle_2to3_conversion(self)
|
2015-12-12 10:46:24 +01:00
|
|
|
for s in self.possible_status + (self.global_actions or []):
|
2006-06-09 13:07:36 +02:00
|
|
|
s.parent = self
|
2015-12-12 10:46:24 +01:00
|
|
|
triggers = getattr(s, 'triggers', None) or []
|
|
|
|
for i, item in enumerate(s.items + triggers):
|
2006-06-09 13:07:36 +02:00
|
|
|
item.parent = s
|
2006-06-11 18:33:20 +02:00
|
|
|
if not item.id:
|
|
|
|
item.id = '%d' % (i+1)
|
2019-10-21 21:28:01 +02:00
|
|
|
if self.variables_formdef:
|
|
|
|
self.variables_formdef.workflow = self
|
2019-10-21 21:02:49 +02:00
|
|
|
if self.backoffice_fields_formdef:
|
2019-10-21 21:28:01 +02:00
|
|
|
self.backoffice_fields_formdef.workflow = self
|
2019-10-21 21:02:49 +02:00
|
|
|
self.backoffice_fields_formdef.__class__ = WorkflowBackofficeFieldsFormDef
|
2006-06-09 13:07:36 +02:00
|
|
|
|
2012-08-17 10:01:51 +02:00
|
|
|
def get_waitpoint_status(self):
|
2017-05-28 20:00:45 +02:00
|
|
|
return [x for x in self.possible_status if x.is_waitpoint()]
|
2012-08-17 10:01:51 +02:00
|
|
|
|
2006-12-16 10:52:19 +01:00
|
|
|
def get_endpoint_status(self):
|
2017-11-11 16:21:02 +01:00
|
|
|
return [x for x in self.possible_status if x.is_endpoint()]
|
2006-12-16 10:52:19 +01:00
|
|
|
|
|
|
|
def get_not_endpoint_status(self):
|
2017-11-11 16:21:02 +01:00
|
|
|
return [x for x in self.possible_status if not x.is_endpoint()]
|
2006-12-16 10:52:19 +01:00
|
|
|
|
2011-02-10 15:11:14 +01:00
|
|
|
def has_options(self):
|
|
|
|
for status in self.possible_status:
|
|
|
|
for item in status.items:
|
|
|
|
for parameter in item.get_parameters():
|
|
|
|
if not getattr(item, parameter):
|
|
|
|
return True
|
|
|
|
return False
|
2006-12-16 10:52:19 +01:00
|
|
|
|
2011-12-15 18:38:08 +01:00
|
|
|
def remove_self(self):
|
2020-02-11 18:06:10 +01:00
|
|
|
for form in self.formdefs():
|
2011-12-15 18:38:08 +01:00
|
|
|
form.workflow_id = None
|
|
|
|
form.store()
|
2012-01-25 18:24:44 +01:00
|
|
|
StorableObject.remove_self(self)
|
2011-12-15 18:38:08 +01:00
|
|
|
|
2014-04-25 15:50:25 +02:00
|
|
|
def export_to_xml(self, include_id=False):
|
2012-01-26 13:59:22 +01:00
|
|
|
charset = get_publisher().site_charset
|
|
|
|
root = ET.Element('workflow')
|
2014-04-25 15:50:25 +02:00
|
|
|
if include_id and self.id and not str(self.id).startswith('_'):
|
2014-07-22 15:36:36 +02:00
|
|
|
root.attrib['id'] = str(self.id)
|
2019-11-11 20:10:57 +01:00
|
|
|
ET.SubElement(root, 'name').text = force_text(self.name, charset)
|
2014-04-25 15:50:25 +02:00
|
|
|
|
|
|
|
roles_node = ET.SubElement(root, 'roles')
|
|
|
|
if self.roles:
|
2019-11-17 09:27:26 +01:00
|
|
|
for role_id, role_label in sorted(self.roles.items()):
|
2014-04-25 15:50:25 +02:00
|
|
|
role_node = ET.SubElement(roles_node, 'role')
|
|
|
|
role_node.attrib['id'] = role_id
|
2019-11-11 20:10:57 +01:00
|
|
|
role_node.text = force_text(role_label, charset)
|
2014-04-25 15:50:25 +02:00
|
|
|
|
|
|
|
if self.last_modification_time:
|
|
|
|
elem = ET.SubElement(root, 'last_modification')
|
|
|
|
elem.text = time.strftime('%Y-%m-%d %H:%M:%S', self.last_modification_time)
|
|
|
|
if include_id:
|
|
|
|
elem.attrib['user_id'] = str(self.last_modification_user_id)
|
|
|
|
|
2012-01-26 13:59:22 +01:00
|
|
|
possible_status = ET.SubElement(root, 'possible_status')
|
|
|
|
for status in self.possible_status:
|
2014-09-17 14:36:08 +02:00
|
|
|
possible_status.append(status.export_to_xml(charset=charset,
|
|
|
|
include_id=include_id))
|
2015-02-02 16:51:45 +01:00
|
|
|
|
2015-12-12 10:46:24 +01:00
|
|
|
if self.global_actions:
|
|
|
|
global_actions = ET.SubElement(root, 'global_actions')
|
|
|
|
for action in self.global_actions:
|
|
|
|
global_actions.append(action.export_to_xml(charset=charset,
|
|
|
|
include_id=include_id))
|
|
|
|
|
2016-03-07 21:28:20 +01:00
|
|
|
if self.criticality_levels:
|
|
|
|
criticality_levels = ET.SubElement(root, 'criticality_levels')
|
|
|
|
for level in self.criticality_levels:
|
|
|
|
criticality_levels.append(level.export_to_xml(charset=charset))
|
|
|
|
|
2015-02-02 16:51:45 +01:00
|
|
|
if self.variables_formdef:
|
|
|
|
variables = ET.SubElement(root, 'variables')
|
|
|
|
formdef = ET.SubElement(variables, 'formdef')
|
|
|
|
ET.SubElement(formdef, 'name').text = '-' # required by formdef xml import
|
|
|
|
fields = ET.SubElement(formdef, 'fields')
|
|
|
|
for field in self.variables_formdef.fields:
|
|
|
|
fields.append(field.export_to_xml(charset=charset, include_id=include_id))
|
|
|
|
|
2016-06-06 18:55:21 +02:00
|
|
|
if self.backoffice_fields_formdef:
|
|
|
|
variables = ET.SubElement(root, 'backoffice-fields')
|
|
|
|
formdef = ET.SubElement(variables, 'formdef')
|
|
|
|
ET.SubElement(formdef, 'name').text = '-' # required by formdef xml import
|
|
|
|
fields = ET.SubElement(formdef, 'fields')
|
|
|
|
for field in self.backoffice_fields_formdef.fields:
|
|
|
|
fields.append(field.export_to_xml(charset=charset, include_id=include_id))
|
|
|
|
|
2012-01-26 13:59:22 +01:00
|
|
|
return root
|
|
|
|
|
2016-03-11 16:40:16 +01:00
|
|
|
@classmethod
|
2014-04-25 15:50:25 +02:00
|
|
|
def import_from_xml(cls, fd, include_id=False):
|
2012-01-26 13:59:22 +01:00
|
|
|
try:
|
|
|
|
tree = ET.parse(fd)
|
|
|
|
except:
|
|
|
|
raise ValueError()
|
2014-04-25 15:50:25 +02:00
|
|
|
return cls.import_from_xml_tree(tree, include_id=include_id)
|
2012-01-26 13:59:22 +01:00
|
|
|
|
2016-03-11 16:40:16 +01:00
|
|
|
@classmethod
|
2014-04-25 15:50:25 +02:00
|
|
|
def import_from_xml_tree(cls, tree, include_id=False):
|
2012-01-26 13:59:22 +01:00
|
|
|
charset = get_publisher().site_charset
|
|
|
|
workflow = cls()
|
|
|
|
if tree.find('name') is None or not tree.find('name').text:
|
2014-10-30 11:17:38 +01:00
|
|
|
raise WorkflowImportError(N_('Missing name'))
|
2012-01-26 13:59:22 +01:00
|
|
|
|
2014-09-17 14:36:08 +02:00
|
|
|
# if the tree we get is actually a ElementTree for real, we get its
|
|
|
|
# root element and go on happily.
|
|
|
|
if not ET.iselement(tree):
|
|
|
|
tree = tree.getroot()
|
|
|
|
|
2014-10-30 11:17:38 +01:00
|
|
|
if tree.tag != 'workflow':
|
|
|
|
raise WorkflowImportError(N_('Not a workflow'))
|
|
|
|
|
2014-04-25 15:50:25 +02:00
|
|
|
if include_id and tree.attrib.get('id'):
|
|
|
|
workflow.id = tree.attrib.get('id')
|
|
|
|
|
2020-01-24 14:25:43 +01:00
|
|
|
workflow.name = xml_node_text(tree.find('name'))
|
2014-04-25 15:50:25 +02:00
|
|
|
|
|
|
|
if tree.find('roles') is not None:
|
|
|
|
workflow.roles = {}
|
|
|
|
for role_node in tree.findall('roles/role'):
|
2020-01-24 14:25:43 +01:00
|
|
|
workflow.roles[role_node.attrib['id']] = xml_node_text(role_node)
|
2014-04-25 15:50:25 +02:00
|
|
|
|
|
|
|
if tree.find('last_modification') is not None:
|
|
|
|
node = tree.find('last_modification')
|
2014-05-07 17:14:21 +02:00
|
|
|
workflow.last_modification_time = time.strptime(node.text, '%Y-%m-%d %H:%M:%S')
|
|
|
|
if include_id and node.attrib.get('user_id'):
|
|
|
|
workflow.last_modification_user_id = node.attrib.get('user_id')
|
2014-04-25 15:50:25 +02:00
|
|
|
|
2012-01-26 13:59:22 +01:00
|
|
|
workflow.possible_status = []
|
|
|
|
for status in tree.find('possible_status'):
|
|
|
|
status_o = WorkflowStatus()
|
2014-02-04 11:11:10 +01:00
|
|
|
status_o.parent = workflow
|
2014-09-17 14:36:08 +02:00
|
|
|
status_o.init_with_xml(status, charset, include_id=include_id)
|
2012-01-26 13:59:22 +01:00
|
|
|
workflow.possible_status.append(status_o)
|
2015-02-02 16:51:45 +01:00
|
|
|
|
2015-12-12 10:46:24 +01:00
|
|
|
workflow.global_actions = []
|
|
|
|
global_actions = tree.find('global_actions')
|
|
|
|
if global_actions is not None:
|
|
|
|
for action in global_actions:
|
|
|
|
action_o = WorkflowGlobalAction()
|
|
|
|
action_o.parent = workflow
|
|
|
|
action_o.init_with_xml(action, charset, include_id=include_id)
|
|
|
|
workflow.global_actions.append(action_o)
|
|
|
|
|
2016-03-07 21:28:20 +01:00
|
|
|
workflow.criticality_levels = []
|
|
|
|
criticality_levels = tree.find('criticality_levels')
|
|
|
|
if criticality_levels is not None:
|
|
|
|
for level in criticality_levels:
|
|
|
|
level_o = WorkflowCriticalityLevel()
|
|
|
|
level_o.init_with_xml(level, charset)
|
|
|
|
workflow.criticality_levels.append(level_o)
|
|
|
|
|
2015-02-02 16:51:45 +01:00
|
|
|
variables = tree.find('variables')
|
|
|
|
if variables is not None:
|
|
|
|
formdef = variables.find('formdef')
|
|
|
|
imported_formdef = FormDef.import_from_xml_tree(formdef, include_id=True)
|
|
|
|
workflow.variables_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow)
|
|
|
|
workflow.variables_formdef.fields = imported_formdef.fields
|
2016-06-06 18:55:21 +02:00
|
|
|
|
|
|
|
variables = tree.find('backoffice-fields')
|
|
|
|
if variables is not None:
|
2016-07-11 14:55:43 +02:00
|
|
|
formdef = variables.find('formdef')
|
2016-06-06 18:55:21 +02:00
|
|
|
imported_formdef = FormDef.import_from_xml_tree(formdef, include_id=True)
|
2019-10-21 21:02:49 +02:00
|
|
|
workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow=workflow)
|
2016-06-06 18:55:21 +02:00
|
|
|
workflow.backoffice_fields_formdef.fields = imported_formdef.fields
|
|
|
|
|
2012-01-26 13:59:22 +01:00
|
|
|
return workflow
|
|
|
|
|
2013-01-21 16:24:21 +01:00
|
|
|
def get_list_of_roles(self, include_logged_in_users=True):
|
|
|
|
t = []
|
2015-09-25 14:44:33 +02:00
|
|
|
t.append(('_submitter', C_('role|User'), '_submitter'))
|
2013-01-21 16:24:21 +01:00
|
|
|
for workflow_role in self.roles.items():
|
2015-09-25 14:44:33 +02:00
|
|
|
t.append(list(workflow_role) + [workflow_role[0]])
|
2013-01-21 16:24:21 +01:00
|
|
|
if include_logged_in_users:
|
2015-09-25 14:44:33 +02:00
|
|
|
t.append((logged_users_role().id, logged_users_role().name, logged_users_role().id))
|
2019-03-06 08:51:43 +01:00
|
|
|
include_roles = not(get_publisher().has_site_option('workflow-functions-only'))
|
|
|
|
if include_roles and get_user_roles():
|
2015-09-25 14:44:33 +02:00
|
|
|
t.append((None, '----', None))
|
2013-01-21 16:24:21 +01:00
|
|
|
t.extend(get_user_roles())
|
|
|
|
return t
|
|
|
|
|
|
|
|
def render_list_of_roles(self, roles):
|
2015-12-12 10:46:24 +01:00
|
|
|
return render_list_of_roles(self, roles)
|
2013-01-21 16:24:21 +01:00
|
|
|
|
2016-02-02 19:16:43 +01:00
|
|
|
def get_json_export_dict(self, include_id=False):
|
|
|
|
charset = get_publisher().site_charset
|
|
|
|
root = {}
|
2019-11-11 20:10:57 +01:00
|
|
|
root['name'] = force_text(self.name, charset)
|
2016-02-02 19:16:43 +01:00
|
|
|
if include_id and self.id:
|
|
|
|
root['id'] = str(self.id)
|
|
|
|
if self.last_modification_time:
|
|
|
|
root['last_modification_time'] = time.strftime('%Y-%m-%dT%H:%M:%S',
|
|
|
|
self.last_modification_time)
|
|
|
|
roles = root['functions'] = {}
|
2019-11-12 19:47:26 +01:00
|
|
|
for role, label in self.roles.items():
|
2019-11-11 20:10:57 +01:00
|
|
|
roles[role] = force_text(label, charset)
|
2016-05-03 12:13:53 +02:00
|
|
|
statuses = root['statuses'] = []
|
2016-05-10 19:22:35 +02:00
|
|
|
endpoint_status_ids = [s.id for s in self.get_endpoint_status()]
|
2019-09-24 17:28:16 +02:00
|
|
|
waitpoint_status_ids = [s.id for s in self.get_waitpoint_status()]
|
2016-02-02 19:16:43 +01:00
|
|
|
for status in self.possible_status:
|
2016-05-03 12:13:53 +02:00
|
|
|
statuses.append({
|
|
|
|
'id': status.id,
|
2019-11-11 20:10:57 +01:00
|
|
|
'name': force_text(status.name, charset),
|
2016-02-02 19:16:43 +01:00
|
|
|
'forced_endpoint': status.forced_endpoint,
|
2016-05-10 19:22:35 +02:00
|
|
|
'endpoint': status.id in endpoint_status_ids,
|
2019-09-24 17:28:16 +02:00
|
|
|
'waitpoint': status.id in waitpoint_status_ids,
|
2016-05-03 12:13:53 +02:00
|
|
|
})
|
2016-08-24 08:25:51 +02:00
|
|
|
root['fields'] = []
|
|
|
|
for field in self.get_backoffice_fields():
|
|
|
|
root['fields'].append(field.export_to_json(include_id=include_id))
|
2016-02-02 19:16:43 +01:00
|
|
|
return root
|
|
|
|
|
2016-03-11 16:40:16 +01:00
|
|
|
@classmethod
|
2012-08-13 10:02:22 +02:00
|
|
|
def get_unknown_workflow(cls):
|
|
|
|
workflow = Workflow(name=_('Unknown'))
|
|
|
|
workflow.id = '_unknown'
|
|
|
|
return workflow
|
|
|
|
|
2016-03-11 16:40:16 +01:00
|
|
|
@classmethod
|
2012-08-13 10:02:22 +02:00
|
|
|
def get_default_workflow(cls):
|
2019-09-29 20:53:23 +02:00
|
|
|
from .qommon.admin.emails import EmailsDirectory
|
2012-08-13 10:02:22 +02:00
|
|
|
|
|
|
|
workflow = Workflow(name=_('Default'))
|
|
|
|
workflow.id = '_default'
|
2013-01-21 16:24:21 +01:00
|
|
|
workflow.roles = {'_receiver': _('Recipient')}
|
2012-08-16 13:44:36 +02:00
|
|
|
just_submitted_status = workflow.add_status(_('Just Submitted'), 'just_submitted')
|
2012-10-03 10:22:56 +02:00
|
|
|
just_submitted_status.visibility = ['_receiver']
|
2012-08-13 10:02:22 +02:00
|
|
|
new_status = workflow.add_status(_('New'), 'new')
|
2014-01-13 17:23:31 +01:00
|
|
|
new_status.colour = '66FF00'
|
2012-08-13 10:02:22 +02:00
|
|
|
rejected_status = workflow.add_status(_('Rejected'), 'rejected')
|
2014-01-13 17:23:31 +01:00
|
|
|
rejected_status.colour = 'FF3300'
|
2012-08-13 10:02:22 +02:00
|
|
|
accepted_status = workflow.add_status(_('Accepted'), 'accepted')
|
2014-01-13 17:23:31 +01:00
|
|
|
accepted_status.colour = '66CCFF'
|
2012-08-13 10:02:22 +02:00
|
|
|
finished_status = workflow.add_status(_('Finished'), 'finished')
|
2014-01-13 17:23:31 +01:00
|
|
|
finished_status.colour = 'CCCCCC'
|
2012-08-13 10:02:22 +02:00
|
|
|
|
|
|
|
commentable = CommentableWorkflowStatusItem()
|
|
|
|
commentable.id = '_commentable'
|
2012-08-16 13:47:12 +02:00
|
|
|
commentable.by = ['_submitter', '_receiver']
|
2012-08-13 10:02:22 +02:00
|
|
|
|
2019-11-12 13:47:56 +01:00
|
|
|
from wcs.wf.jump import JumpWorkflowStatusItem
|
|
|
|
jump_to_new = JumpWorkflowStatusItem()
|
2012-08-16 13:44:36 +02:00
|
|
|
jump_to_new.id = '_jump_to_new'
|
|
|
|
jump_to_new.status = new_status.id
|
|
|
|
jump_to_new.parent = just_submitted_status
|
|
|
|
|
2012-08-13 10:02:22 +02:00
|
|
|
notify_new_receiver_email = SendmailWorkflowStatusItem()
|
|
|
|
notify_new_receiver_email.id = '_notify_new_receiver_email'
|
|
|
|
notify_new_receiver_email.to = ['_receiver']
|
|
|
|
notify_new_receiver_email.subject = EmailsDirectory.get_subject('new_receiver')
|
|
|
|
notify_new_receiver_email.body = EmailsDirectory.get_body('new_receiver')
|
2012-08-16 13:25:02 +02:00
|
|
|
if not EmailsDirectory.is_enabled('new_receiver'):
|
|
|
|
notify_new_receiver_email = None
|
2012-08-13 10:02:22 +02:00
|
|
|
|
|
|
|
notify_new_user_email = SendmailWorkflowStatusItem()
|
|
|
|
notify_new_user_email.id = '_notify_new_user_email'
|
|
|
|
notify_new_user_email.to = ['_submitter']
|
|
|
|
notify_new_user_email.subject = EmailsDirectory.get_subject('new_user')
|
|
|
|
notify_new_user_email.body = EmailsDirectory.get_body('new_user')
|
2012-08-16 13:25:02 +02:00
|
|
|
if not EmailsDirectory.is_enabled('new_user'):
|
|
|
|
notify_change_user_email = None
|
2012-08-13 10:02:22 +02:00
|
|
|
|
|
|
|
notify_change_receiver_email = SendmailWorkflowStatusItem()
|
|
|
|
notify_change_receiver_email.id = '_notify_change_receiver_email'
|
|
|
|
notify_change_receiver_email.to = ['_receiver']
|
|
|
|
notify_change_receiver_email.subject = EmailsDirectory.get_subject('change_receiver')
|
|
|
|
notify_change_receiver_email.body = EmailsDirectory.get_body('change_receiver')
|
2012-08-16 13:25:02 +02:00
|
|
|
if not EmailsDirectory.is_enabled('change_receiver'):
|
|
|
|
notify_change_receiver_email = None
|
2012-08-13 10:02:22 +02:00
|
|
|
|
|
|
|
notify_change_user_email = SendmailWorkflowStatusItem()
|
|
|
|
notify_change_user_email.id = '_notify_change_user_email'
|
|
|
|
notify_change_user_email.to = ['_submitter']
|
|
|
|
notify_change_user_email.subject = EmailsDirectory.get_subject('change_user')
|
|
|
|
notify_change_user_email.body = EmailsDirectory.get_body('change_user')
|
2012-08-16 13:25:02 +02:00
|
|
|
if not EmailsDirectory.is_enabled('change_user'):
|
|
|
|
notify_change_user_email = None
|
|
|
|
|
|
|
|
if notify_new_receiver_email:
|
2013-01-21 16:24:21 +01:00
|
|
|
notify_new_receiver_email.parent = just_submitted_status
|
2012-08-16 13:44:36 +02:00
|
|
|
just_submitted_status.items.append(notify_new_receiver_email)
|
2012-08-16 13:25:02 +02:00
|
|
|
if notify_new_user_email:
|
2013-01-21 16:24:21 +01:00
|
|
|
notify_new_user_email.parent = just_submitted_status
|
2012-08-16 13:44:36 +02:00
|
|
|
just_submitted_status.items.append(notify_new_user_email)
|
|
|
|
|
|
|
|
just_submitted_status.items.append(jump_to_new)
|
2012-08-16 13:25:02 +02:00
|
|
|
|
|
|
|
if notify_change_receiver_email:
|
|
|
|
accepted_status.items.append(notify_change_receiver_email)
|
2013-01-21 16:24:21 +01:00
|
|
|
notify_change_receiver_email.parent = accepted_status
|
|
|
|
notify_change_receiver_email = copy.copy(notify_change_receiver_email)
|
|
|
|
|
2012-08-16 13:25:02 +02:00
|
|
|
rejected_status.items.append(notify_change_receiver_email)
|
2013-01-21 16:24:21 +01:00
|
|
|
notify_change_receiver_email.parent = rejected_status
|
|
|
|
notify_change_receiver_email = copy.copy(notify_change_receiver_email)
|
|
|
|
|
2012-08-16 13:25:02 +02:00
|
|
|
finished_status.items.append(notify_change_receiver_email)
|
2013-01-21 16:24:21 +01:00
|
|
|
notify_change_receiver_email.parent = finished_status
|
|
|
|
|
2012-08-16 13:25:02 +02:00
|
|
|
if notify_change_user_email:
|
|
|
|
accepted_status.items.append(notify_change_user_email)
|
2013-01-21 16:24:21 +01:00
|
|
|
notify_change_user_email.parent = accepted_status
|
|
|
|
notify_change_user_email = copy.copy(notify_change_user_email)
|
|
|
|
|
2012-08-16 13:25:02 +02:00
|
|
|
rejected_status.items.append(notify_change_user_email)
|
2013-01-21 16:24:21 +01:00
|
|
|
notify_change_user_email.parent = rejected_status
|
|
|
|
notify_change_user_email = copy.copy(notify_change_user_email)
|
|
|
|
|
2012-08-16 13:25:02 +02:00
|
|
|
finished_status.items.append(notify_change_user_email)
|
2013-01-21 16:24:21 +01:00
|
|
|
notify_change_user_email.parent = finished_status
|
2012-08-13 10:02:22 +02:00
|
|
|
|
2012-08-16 14:17:51 +02:00
|
|
|
new_status.items.append(commentable)
|
2013-01-21 16:24:21 +01:00
|
|
|
commentable.parent = new_status
|
|
|
|
|
|
|
|
commentable = copy.copy(commentable)
|
2012-08-16 14:17:51 +02:00
|
|
|
accepted_status.items.append(commentable)
|
2013-01-21 16:24:21 +01:00
|
|
|
commentable.parent = accepted_status
|
2012-08-16 14:17:51 +02:00
|
|
|
|
2012-08-13 10:02:22 +02:00
|
|
|
accept = ChoiceWorkflowStatusItem()
|
|
|
|
accept.id = '_accept'
|
|
|
|
accept.label = _('Accept')
|
|
|
|
accept.by = ['_receiver']
|
|
|
|
accept.status = accepted_status.id
|
|
|
|
accept.parent = new_status
|
|
|
|
new_status.items.append(accept)
|
|
|
|
|
|
|
|
reject = ChoiceWorkflowStatusItem()
|
|
|
|
reject.id = '_reject'
|
|
|
|
reject.label = _('Reject')
|
|
|
|
reject.by = ['_receiver']
|
|
|
|
reject.status = rejected_status.id
|
|
|
|
reject.parent = new_status
|
|
|
|
new_status.items.append(reject)
|
|
|
|
|
|
|
|
finish = ChoiceWorkflowStatusItem()
|
|
|
|
finish.id = '_finish'
|
|
|
|
finish.label = _('Finish')
|
|
|
|
finish.by = ['_receiver']
|
|
|
|
finish.status = finished_status.id
|
|
|
|
finish.parent = accepted_status
|
|
|
|
accepted_status.items.append(finish)
|
|
|
|
|
|
|
|
return workflow
|
|
|
|
|
2020-02-11 18:06:10 +01:00
|
|
|
def formdefs(self, **kwargs):
|
|
|
|
return list(FormDef.select(lambda x: x.workflow_id == self.id, **kwargs))
|
|
|
|
|
2012-08-13 10:02:22 +02:00
|
|
|
|
2015-12-12 10:46:24 +01:00
|
|
|
class XmlSerialisable(object):
|
|
|
|
node_name = None
|
2016-01-04 16:04:22 +01:00
|
|
|
key = None
|
2015-12-12 10:46:24 +01:00
|
|
|
|
|
|
|
def export_to_xml(self, charset, include_id=False):
|
|
|
|
node = ET.Element(self.node_name)
|
2016-01-04 16:04:22 +01:00
|
|
|
if self.key:
|
|
|
|
node.attrib['type'] = self.key
|
2016-04-04 22:05:06 +02:00
|
|
|
if include_id and getattr(self, 'id', None):
|
|
|
|
node.attrib['id'] = self.id
|
2015-12-12 10:46:24 +01:00
|
|
|
for attribute in self.get_parameters():
|
2017-09-30 11:44:48 +02:00
|
|
|
if getattr(self, '%s_export_to_xml' % attribute, None):
|
2015-12-12 10:46:24 +01:00
|
|
|
getattr(self, '%s_export_to_xml' % attribute)(node, charset,
|
|
|
|
include_id=include_id)
|
|
|
|
continue
|
|
|
|
if hasattr(self, attribute) and getattr(self, attribute) is not None:
|
|
|
|
el = ET.SubElement(node, attribute)
|
|
|
|
val = getattr(self, attribute)
|
|
|
|
if type(val) is dict:
|
|
|
|
for k, v in val.items():
|
2019-11-11 20:10:57 +01:00
|
|
|
ET.SubElement(el, k).text = force_text(v, charset, errors='replace')
|
2015-12-12 10:46:24 +01:00
|
|
|
elif type(val) is list:
|
|
|
|
if attribute[-1] == 's':
|
|
|
|
atname = attribute[:-1]
|
|
|
|
else:
|
|
|
|
atname = 'item'
|
|
|
|
for v in val:
|
2019-11-11 20:10:57 +01:00
|
|
|
ET.SubElement(el, atname).text = force_text(str(v), charset, errors='replace')
|
2019-11-12 19:44:52 +01:00
|
|
|
elif isinstance(val, six.string_types):
|
|
|
|
el.text = force_text(val, charset, errors='replace')
|
2015-12-12 10:46:24 +01:00
|
|
|
else:
|
|
|
|
el.text = str(val)
|
|
|
|
return node
|
|
|
|
|
|
|
|
def init_with_xml(self, elem, charset, include_id=False):
|
2016-04-04 22:05:06 +02:00
|
|
|
if include_id and elem.attrib.get('id'):
|
|
|
|
self.id = elem.attrib.get('id')
|
2015-12-12 10:46:24 +01:00
|
|
|
for attribute in self.get_parameters():
|
|
|
|
el = elem.find(attribute)
|
2017-09-30 11:44:48 +02:00
|
|
|
if getattr(self, '%s_init_with_xml' % attribute, None):
|
2015-12-12 10:46:24 +01:00
|
|
|
getattr(self, '%s_init_with_xml' % attribute)(el, charset,
|
|
|
|
include_id=include_id)
|
|
|
|
continue
|
|
|
|
if el is None:
|
|
|
|
continue
|
2018-10-04 23:26:48 +02:00
|
|
|
if list(el):
|
2015-12-12 10:46:24 +01:00
|
|
|
if type(getattr(self, attribute)) is list:
|
2020-01-24 14:25:43 +01:00
|
|
|
v = [xml_node_text(x) or '' for x in el]
|
2015-12-12 10:46:24 +01:00
|
|
|
elif type(getattr(self, attribute)) is dict:
|
|
|
|
v = {}
|
2018-10-04 23:26:48 +02:00
|
|
|
for e in el:
|
2020-01-24 14:25:43 +01:00
|
|
|
v[e.tag] = xml_node_text(e)
|
2015-12-12 10:46:24 +01:00
|
|
|
else:
|
|
|
|
# ???
|
|
|
|
raise AssertionError
|
|
|
|
setattr(self, attribute, v)
|
|
|
|
else:
|
|
|
|
if el.text is None:
|
|
|
|
setattr(self, attribute, None)
|
2020-01-29 10:36:29 +01:00
|
|
|
elif el.text in ('False', 'True') and not isinstance(getattr(self, attribute), six.string_types):
|
|
|
|
# booleans
|
2016-04-18 23:07:40 +02:00
|
|
|
setattr(self, attribute, el.text == 'True')
|
2015-12-12 10:46:24 +01:00
|
|
|
elif type(getattr(self, attribute)) is int:
|
2019-11-13 09:29:33 +01:00
|
|
|
setattr(self, attribute, int(el.text))
|
2015-12-12 10:46:24 +01:00
|
|
|
else:
|
2020-01-24 14:25:43 +01:00
|
|
|
setattr(self, attribute, xml_node_text(el))
|
2015-12-12 10:46:24 +01:00
|
|
|
|
|
|
|
def _roles_export_to_xml(self, attribute, item, charset, include_id=False):
|
|
|
|
if not hasattr(self, attribute) or not getattr(self, attribute):
|
|
|
|
return
|
|
|
|
el = ET.SubElement(item, attribute)
|
|
|
|
for role_id in getattr(self, attribute):
|
|
|
|
if role_id is None:
|
|
|
|
continue
|
|
|
|
role_id = str(role_id)
|
|
|
|
if role_id.startswith('_') or role_id == 'logged-users':
|
2019-11-11 20:10:57 +01:00
|
|
|
role = force_text(role_id, charset)
|
2015-12-12 10:46:24 +01:00
|
|
|
else:
|
|
|
|
try:
|
2019-11-11 20:10:57 +01:00
|
|
|
role = force_text(Role.get(role_id).name, charset)
|
2015-12-12 10:46:24 +01:00
|
|
|
except KeyError:
|
2019-11-11 20:10:57 +01:00
|
|
|
role = force_text(role_id, charset)
|
2015-12-12 10:46:24 +01:00
|
|
|
sub = ET.SubElement(el, 'item')
|
|
|
|
sub.attrib['role_id'] = role_id
|
|
|
|
sub.text = role
|
|
|
|
|
|
|
|
def _roles_init_with_xml(self, attribute, elem, charset, include_id=False):
|
|
|
|
if elem is None:
|
|
|
|
setattr(self, attribute, [])
|
|
|
|
else:
|
|
|
|
imported_roles = []
|
2018-10-04 23:26:48 +02:00
|
|
|
for child in elem:
|
2015-12-12 10:46:24 +01:00
|
|
|
imported_roles.append(self._get_role_id_from_xml(child,
|
|
|
|
charset, include_id=include_id))
|
|
|
|
setattr(self, attribute, imported_roles)
|
|
|
|
|
|
|
|
def _role_export_to_xml(self, attribute, item, charset, include_id=False):
|
|
|
|
if not hasattr(self, attribute) or not getattr(self, attribute):
|
|
|
|
return
|
|
|
|
role_id = str(getattr(self, attribute))
|
|
|
|
if role_id.startswith('_') or role_id == 'logged-users':
|
2019-11-11 20:10:57 +01:00
|
|
|
role = force_text(role_id, charset)
|
2015-12-12 10:46:24 +01:00
|
|
|
else:
|
|
|
|
try:
|
2019-11-11 20:10:57 +01:00
|
|
|
role = force_text(Role.get(role_id).name, charset)
|
2015-12-12 10:46:24 +01:00
|
|
|
except KeyError:
|
2019-11-11 20:10:57 +01:00
|
|
|
role_id = role = force_text(role_id, charset)
|
2015-12-12 10:46:24 +01:00
|
|
|
sub = ET.SubElement(item, attribute)
|
|
|
|
if include_id:
|
|
|
|
sub.attrib['role_id'] = role_id
|
|
|
|
sub.text = role
|
|
|
|
|
|
|
|
def _get_role_id_from_xml(self, elem, charset, include_id=False):
|
|
|
|
if elem is None:
|
|
|
|
return None
|
|
|
|
|
2020-01-24 14:25:43 +01:00
|
|
|
value = xml_node_text(elem) or ''
|
2015-12-12 10:46:24 +01:00
|
|
|
|
|
|
|
# look for known static values
|
|
|
|
if value.startswith('_') or value == 'logged-users':
|
|
|
|
return value
|
|
|
|
|
2019-07-17 15:40:35 +02:00
|
|
|
# if we import using id, look at the role_id attribute
|
2019-07-17 07:02:14 +02:00
|
|
|
if include_id and 'role_id' in elem.attrib:
|
2019-11-13 09:29:33 +01:00
|
|
|
role_id = force_str(elem.attrib['role_id'])
|
2015-12-12 10:46:24 +01:00
|
|
|
if Role.has_key(role_id):
|
|
|
|
return role_id
|
2018-07-17 08:20:38 +02:00
|
|
|
if WorkflowStatusItem.get_expression(role_id)['type'] in ('python', 'template'):
|
|
|
|
return role_id
|
2015-12-12 10:46:24 +01:00
|
|
|
|
|
|
|
# if not using id, look up on the name
|
|
|
|
for role in Role.select(ignore_errors=True):
|
|
|
|
if role.name == value:
|
|
|
|
return role.id
|
|
|
|
|
2017-11-28 14:15:46 +01:00
|
|
|
# if a computed value is possible and value looks like
|
|
|
|
# an expression, use it
|
2018-05-28 14:39:59 +02:00
|
|
|
if WorkflowStatusItem.get_expression(value)['type'] in ('python', 'template'):
|
2017-11-28 14:15:46 +01:00
|
|
|
return value
|
|
|
|
|
2016-12-21 10:10:06 +01:00
|
|
|
# if the roles are managed by the idp, don't try further.
|
|
|
|
if get_publisher() and get_cfg('sp', {}).get('idp-manage-roles') is True:
|
|
|
|
raise WorkflowImportError(N_('Unknown referenced role (%s)'), (value,))
|
|
|
|
|
2015-12-12 10:46:24 +01:00
|
|
|
# and if there's no match, create a new role
|
|
|
|
role = Role()
|
|
|
|
role.name = value
|
|
|
|
role.store()
|
|
|
|
return role.id
|
|
|
|
|
|
|
|
def _role_init_with_xml(self, attribute, elem, charset, include_id=False):
|
|
|
|
setattr(self, attribute, self._get_role_id_from_xml(elem, charset,
|
|
|
|
include_id=include_id))
|
|
|
|
|
|
|
|
|
|
|
|
class WorkflowGlobalActionTrigger(XmlSerialisable):
|
|
|
|
node_name = 'trigger'
|
|
|
|
|
2016-04-04 22:05:06 +02:00
|
|
|
def submit_admin_form(self, form):
|
|
|
|
for f in self.get_parameters():
|
|
|
|
widget = form.get_widget(f)
|
|
|
|
if widget:
|
|
|
|
value = widget.parse()
|
|
|
|
if hasattr(self, '%s_parse' % f):
|
|
|
|
value = getattr(self, '%s_parse' % f)(value)
|
|
|
|
setattr(self, f, value)
|
|
|
|
|
2019-04-11 10:48:03 +02:00
|
|
|
def get_subdirectories(self, formdata):
|
|
|
|
return []
|
|
|
|
|
2015-12-12 10:46:24 +01:00
|
|
|
|
|
|
|
class WorkflowGlobalActionManualTrigger(WorkflowGlobalActionTrigger):
|
|
|
|
key = 'manual'
|
|
|
|
roles = None
|
|
|
|
|
|
|
|
def get_parameters(self):
|
|
|
|
return ('roles',)
|
|
|
|
|
|
|
|
def render_as_line(self):
|
|
|
|
if self.roles:
|
|
|
|
return _('Manual by %s') % render_list_of_roles(
|
|
|
|
self.parent.parent, self.roles)
|
|
|
|
else:
|
|
|
|
return _('Manual (not assigned)')
|
|
|
|
|
|
|
|
def form(self, workflow):
|
|
|
|
form = Form(enctype='multipart/form-data')
|
|
|
|
options = [(None, '---', None)]
|
|
|
|
options += workflow.get_list_of_roles(include_logged_in_users=False)
|
|
|
|
form.add(WidgetList, 'roles', title=_('Roles'),
|
|
|
|
element_type=SingleSelectWidget,
|
|
|
|
value=self.roles,
|
|
|
|
add_element_label=_('Add Role'),
|
|
|
|
element_kwargs={'render_br': False,
|
|
|
|
'options': options})
|
|
|
|
return form
|
|
|
|
|
|
|
|
def roles_export_to_xml(self, item, charset, include_id=False):
|
|
|
|
self._roles_export_to_xml('roles', item, charset, include_id=include_id)
|
|
|
|
|
|
|
|
def roles_init_with_xml(self, elem, charset, include_id=False):
|
|
|
|
self._roles_init_with_xml('roles', elem, charset, include_id=include_id)
|
|
|
|
|
|
|
|
|
2016-04-04 22:05:06 +02:00
|
|
|
class WorkflowGlobalActionTimeoutTriggerMarker(object):
|
|
|
|
def __init__(self, timeout_id):
|
|
|
|
self.timeout_id = timeout_id
|
|
|
|
|
2020-01-18 20:33:44 +01:00
|
|
|
|
2016-04-04 22:05:06 +02:00
|
|
|
class WorkflowGlobalActionTimeoutTrigger(WorkflowGlobalActionTrigger):
|
|
|
|
key = 'timeout'
|
|
|
|
anchor = None
|
2020-01-29 10:36:29 +01:00
|
|
|
anchor_expression = ''
|
2016-04-04 22:05:06 +02:00
|
|
|
anchor_status_first = None
|
|
|
|
anchor_status_latest = None
|
|
|
|
timeout = None
|
|
|
|
|
|
|
|
def get_parameters(self):
|
|
|
|
return ('anchor', 'anchor_expression', 'anchor_status_first',
|
|
|
|
'anchor_status_latest', 'timeout')
|
|
|
|
|
|
|
|
def get_anchor_labels(self):
|
|
|
|
return collections.OrderedDict([
|
|
|
|
('creation', _('Creation')),
|
|
|
|
('1st-arrival', _('First arrival in status')),
|
|
|
|
('latest-arrival', _('Latest arrival in status')),
|
2019-06-30 15:53:31 +02:00
|
|
|
('finalized', _('Arrival in final status')),
|
2016-04-04 22:05:06 +02:00
|
|
|
('python', _('Python expression')),
|
|
|
|
])
|
|
|
|
|
|
|
|
def properly_configured(self):
|
2019-07-05 00:11:06 +02:00
|
|
|
workflow = self.parent.parent
|
|
|
|
if not (self.anchor and self.timeout):
|
|
|
|
return False
|
|
|
|
if self.anchor == '1st-arrival' and self.anchor_status_first:
|
|
|
|
try:
|
|
|
|
workflow.get_status(self.anchor_status_first)
|
|
|
|
except KeyError:
|
|
|
|
return False
|
|
|
|
if self.anchor == 'latest-arrival' and self.anchor_status_latest:
|
|
|
|
try:
|
|
|
|
workflow.get_status(self.anchor_status_latest)
|
|
|
|
except KeyError:
|
|
|
|
return False
|
|
|
|
return True
|
2016-04-04 22:05:06 +02:00
|
|
|
|
|
|
|
def render_as_line(self):
|
|
|
|
if self.properly_configured():
|
2018-07-18 09:24:49 +02:00
|
|
|
return _('Automatic, %(timeout)s, relative to: %(anchor)s') % {
|
2016-04-04 22:05:06 +02:00
|
|
|
'anchor': self.get_anchor_labels().get(self.anchor).lower(),
|
|
|
|
'timeout': _('%s days') % self.timeout}
|
|
|
|
else:
|
2018-07-18 09:24:49 +02:00
|
|
|
return _('Automatic (not configured)')
|
2016-04-04 22:05:06 +02:00
|
|
|
|
|
|
|
def form(self, workflow):
|
|
|
|
form = Form(enctype='multipart/form-data')
|
2019-11-13 20:05:14 +01:00
|
|
|
options = list(self.get_anchor_labels().items())
|
2018-07-18 09:24:49 +02:00
|
|
|
form.add(SingleSelectWidget, 'anchor', title=_('Reference Date'),
|
2016-04-04 22:05:06 +02:00
|
|
|
options=options, value=self.anchor, required=True,
|
|
|
|
attrs={'data-dynamic-display-parent': 'true'})
|
|
|
|
|
|
|
|
form.add(StringWidget, 'anchor_expression', title=_('Expression'), size=80,
|
|
|
|
value=self.anchor_expression,
|
2019-06-30 15:53:31 +02:00
|
|
|
hint=_('This will only apply to open forms.'),
|
2016-04-04 22:05:06 +02:00
|
|
|
attrs={'data-dynamic-display-child-of': 'anchor',
|
|
|
|
'data-dynamic-display-value': _('Python expression')})
|
|
|
|
possible_status = [(None, _('Current Status'), None)]
|
|
|
|
possible_status.extend([('wf-%s' % x.id, x.name, x.id) for x in workflow.possible_status])
|
|
|
|
form.add(SingleSelectWidget, 'anchor_status_first', title=_('Status'),
|
|
|
|
options=possible_status,
|
|
|
|
value=self.anchor_status_first,
|
|
|
|
attrs={'data-dynamic-display-child-of': 'anchor',
|
|
|
|
'data-dynamic-display-value': _('First arrival in status')}
|
|
|
|
)
|
|
|
|
form.add(SingleSelectWidget, 'anchor_status_latest', title=_('Status'),
|
|
|
|
options=possible_status,
|
|
|
|
value=self.anchor_status_latest,
|
|
|
|
attrs={'data-dynamic-display-child-of': 'anchor',
|
|
|
|
'data-dynamic-display-value': _('Latest arrival in status')}
|
|
|
|
)
|
|
|
|
|
2018-07-18 09:24:49 +02:00
|
|
|
form.add(ValidatedStringWidget, 'timeout', title=_('Delay (in days)'),
|
2016-04-04 22:05:06 +02:00
|
|
|
value=self.timeout,
|
2018-07-09 21:08:03 +02:00
|
|
|
regex=r'^-?\d+$',
|
|
|
|
required=True,
|
2018-07-18 09:24:49 +02:00
|
|
|
hint=_('''
|
|
|
|
Number of days relative to the reference date. If the
|
|
|
|
reference date is computed from an expression, a negative
|
|
|
|
delay is accepted to trigger the action before the
|
|
|
|
date.'''))
|
2016-04-04 22:05:06 +02:00
|
|
|
|
|
|
|
return form
|
|
|
|
|
2019-06-30 15:53:31 +02:00
|
|
|
def must_trigger(self, formdata, endpoint_status_ids):
|
|
|
|
if formdata.status in endpoint_status_ids:
|
|
|
|
if not ((self.anchor == '1st-arrival' and self.anchor_status_first in endpoint_status_ids) or (
|
|
|
|
self.anchor == 'latest-arrival' and self.anchor_status_latest in endpoint_status_ids) or (
|
|
|
|
self.anchor == 'finalized')):
|
|
|
|
# don't trigger on finalized formdata (unless explicit anchor point)
|
|
|
|
return False
|
2016-04-04 22:05:06 +02:00
|
|
|
anchor_date = None
|
|
|
|
if self.anchor == 'creation':
|
|
|
|
anchor_date = formdata.receipt_time
|
|
|
|
elif self.anchor == '1st-arrival':
|
|
|
|
anchor_status = self.anchor_status_first or formdata.status
|
|
|
|
for evolution in formdata.evolution:
|
|
|
|
if evolution.status == anchor_status:
|
2018-05-01 12:22:19 +02:00
|
|
|
anchor_date = evolution.last_jump_datetime or evolution.time
|
2016-04-04 22:05:06 +02:00
|
|
|
break
|
|
|
|
elif self.anchor == 'latest-arrival':
|
|
|
|
anchor_status = self.anchor_status_latest or formdata.status
|
2020-01-20 10:22:38 +01:00
|
|
|
latest_no_status_evolution = None
|
2016-04-04 22:05:06 +02:00
|
|
|
for evolution in reversed(formdata.evolution):
|
|
|
|
if evolution.status == anchor_status:
|
2020-01-20 10:22:38 +01:00
|
|
|
if latest_no_status_evolution:
|
|
|
|
evolution = latest_no_status_evolution
|
2018-05-01 12:22:19 +02:00
|
|
|
anchor_date = evolution.last_jump_datetime or evolution.time
|
2016-04-04 22:05:06 +02:00
|
|
|
break
|
2020-01-20 10:22:38 +01:00
|
|
|
elif evolution.status:
|
|
|
|
latest_no_status_evolution = None
|
|
|
|
elif latest_no_status_evolution is None:
|
|
|
|
latest_no_status_evolution = evolution
|
2019-06-30 15:53:31 +02:00
|
|
|
elif self.anchor == 'finalized':
|
|
|
|
if formdata.status in endpoint_status_ids:
|
|
|
|
for evolution in reversed(formdata.evolution):
|
|
|
|
if not evolution.status:
|
|
|
|
continue
|
|
|
|
if evolution.status in endpoint_status_ids:
|
|
|
|
anchor_date = evolution.time
|
|
|
|
else:
|
|
|
|
break
|
2016-04-04 22:05:06 +02:00
|
|
|
elif self.anchor == 'python':
|
|
|
|
variables = get_publisher().substitutions.get_context_variables()
|
|
|
|
try:
|
|
|
|
anchor_date = eval(self.anchor_expression,
|
|
|
|
get_publisher().get_global_eval_dict(), variables)
|
|
|
|
except:
|
|
|
|
# get the variables in the locals() namespace so they are
|
|
|
|
# displayed within the trace.
|
|
|
|
expression = self.anchor_expression
|
|
|
|
global_variables = get_publisher().get_global_eval_dict()
|
|
|
|
get_publisher().notify_of_exception(sys.exc_info(), context='[TIMEOUTS]')
|
|
|
|
|
|
|
|
# convert anchor_date to datetime.datetime()
|
|
|
|
if isinstance(anchor_date, datetime.datetime):
|
|
|
|
pass
|
|
|
|
elif isinstance(anchor_date, datetime.date):
|
2019-01-23 01:14:52 +01:00
|
|
|
anchor_date = datetime.datetime(year=anchor_date.year,
|
|
|
|
month=anchor_date.month,
|
|
|
|
day=anchor_date.day)
|
2016-04-04 22:05:06 +02:00
|
|
|
elif isinstance(anchor_date, time.struct_time):
|
|
|
|
anchor_date = datetime.datetime.fromtimestamp(time.mktime(anchor_date))
|
2019-11-12 19:53:06 +01:00
|
|
|
elif isinstance(anchor_date, six.string_types) and anchor_date:
|
2016-04-04 22:05:06 +02:00
|
|
|
try:
|
|
|
|
anchor_date = get_as_datetime(anchor_date)
|
|
|
|
except ValueError:
|
|
|
|
get_publisher().notify_of_exception(sys.exc_info(), context='[TIMEOUTS]')
|
|
|
|
anchor_date = None
|
|
|
|
elif anchor_date:
|
|
|
|
# timestamp
|
|
|
|
try:
|
|
|
|
anchor_date = datetime.datetime.fromtimestamp(anchor_date)
|
|
|
|
except TypeError:
|
|
|
|
get_publisher().notify_of_exception(sys.exc_info(), context='[TIMEOUTS]')
|
|
|
|
anchor_date = None
|
|
|
|
|
2016-04-13 12:54:48 +02:00
|
|
|
if not anchor_date:
|
2016-04-04 22:05:06 +02:00
|
|
|
return False
|
|
|
|
|
|
|
|
anchor_date = anchor_date + datetime.timedelta(days=int(self.timeout))
|
|
|
|
|
|
|
|
return bool(datetime.datetime.now() > anchor_date)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def apply(cls, workflow):
|
|
|
|
triggers = []
|
|
|
|
for action in workflow.global_actions or []:
|
|
|
|
triggers.extend([(action, x) for x in action.triggers or [] if
|
|
|
|
isinstance(x, WorkflowGlobalActionTimeoutTrigger)
|
|
|
|
and x.properly_configured()])
|
|
|
|
if not triggers:
|
|
|
|
return
|
|
|
|
|
|
|
|
not_endpoint_status = workflow.get_not_endpoint_status()
|
|
|
|
not_endpoint_status_ids = ['wf-%s' % x.id for x in not_endpoint_status]
|
2019-06-30 15:53:31 +02:00
|
|
|
endpoint_status = workflow.get_endpoint_status()
|
|
|
|
endpoint_status_ids = ['wf-%s' % x.id for x in endpoint_status]
|
|
|
|
|
|
|
|
# check if triggers are defined relative to terminal status
|
|
|
|
run_on_finalized = False
|
|
|
|
for action, trigger in triggers:
|
|
|
|
if trigger.anchor == 'finalized':
|
|
|
|
run_on_finalized = True
|
|
|
|
elif (trigger.anchor == 'creation' and
|
|
|
|
workflow.possible_status and
|
|
|
|
workflow.possible_status[0] in endpoint_status):
|
|
|
|
run_on_finalized = True
|
|
|
|
elif (trigger.anchor == '1st-arrival' and
|
|
|
|
trigger.anchor_status_first and
|
|
|
|
workflow.get_status(trigger.anchor_status_first) in endpoint_status):
|
|
|
|
run_on_finalized = True
|
|
|
|
elif (trigger.anchor == 'latest-arrival' and
|
|
|
|
trigger.anchor_status_latest and
|
|
|
|
workflow.get_status(trigger.anchor_status_latest) in endpoint_status):
|
|
|
|
run_on_finalized = True
|
|
|
|
|
|
|
|
criterias = [NotEqual('status', 'draft'), Null('anonymised')]
|
|
|
|
if not run_on_finalized:
|
|
|
|
# limit to formdata that are not finalized
|
|
|
|
criterias.append(Contains('status', not_endpoint_status_ids))
|
|
|
|
|
2020-02-11 18:06:10 +01:00
|
|
|
for formdef in workflow.formdefs():
|
2016-04-04 22:05:06 +02:00
|
|
|
open_formdata_ids = []
|
|
|
|
data_class = formdef.data_class()
|
2019-06-30 15:53:31 +02:00
|
|
|
for formdata in data_class.select(criterias, iterator=True):
|
2016-04-04 22:05:06 +02:00
|
|
|
get_publisher().substitutions.reset()
|
|
|
|
get_publisher().substitutions.feed(get_publisher())
|
|
|
|
get_publisher().substitutions.feed(formdef)
|
|
|
|
get_publisher().substitutions.feed(formdata)
|
|
|
|
|
|
|
|
seen_triggers = []
|
2019-05-17 20:08:42 +02:00
|
|
|
for part in formdata.iter_evolution_parts():
|
|
|
|
if not isinstance(part, WorkflowGlobalActionTimeoutTriggerMarker):
|
|
|
|
continue
|
|
|
|
seen_triggers.append(part.timeout_id)
|
2016-04-04 22:05:06 +02:00
|
|
|
|
|
|
|
for action, trigger in triggers:
|
|
|
|
if trigger.id in seen_triggers:
|
|
|
|
continue # already triggered
|
2019-06-30 15:53:31 +02:00
|
|
|
if trigger.must_trigger(formdata, endpoint_status_ids):
|
2016-04-04 22:05:06 +02:00
|
|
|
if not formdata.evolution:
|
|
|
|
continue
|
|
|
|
formdata.evolution[-1].add_part(
|
|
|
|
WorkflowGlobalActionTimeoutTriggerMarker(trigger.id))
|
|
|
|
formdata.store()
|
|
|
|
perform_items(action.items, formdata)
|
|
|
|
break
|
|
|
|
|
|
|
|
|
2019-04-11 10:48:03 +02:00
|
|
|
class WorkflowGlobalActionWebserviceTrigger(WorkflowGlobalActionManualTrigger):
|
|
|
|
key = 'webservice'
|
|
|
|
identifier = None
|
|
|
|
roles = None
|
|
|
|
|
|
|
|
def get_parameters(self):
|
|
|
|
return ('identifier', 'roles')
|
|
|
|
|
|
|
|
def render_as_line(self):
|
|
|
|
if self.identifier:
|
|
|
|
return _('Webservice (%s)') % self.identifier
|
|
|
|
else:
|
|
|
|
return _('Webservice (not configured)')
|
|
|
|
|
|
|
|
def form(self, workflow):
|
|
|
|
form = Form(enctype='multipart/form-data')
|
|
|
|
form.add(StringWidget, 'identifier', title=_('Identifier'),
|
|
|
|
required=True, value=self.identifier)
|
|
|
|
options = [(None, '---', None)]
|
|
|
|
options += workflow.get_list_of_roles(include_logged_in_users=True)
|
|
|
|
form.add(WidgetList, 'roles', title=_('Roles'),
|
|
|
|
element_type=SingleSelectWidget,
|
|
|
|
value=self.roles,
|
|
|
|
add_element_label=_('Add Role'),
|
|
|
|
element_kwargs={'render_br': False,
|
|
|
|
'options': options})
|
|
|
|
return form
|
|
|
|
|
|
|
|
def get_subdirectories(self, formdata):
|
|
|
|
from wcs.forms.workflows import WorkflowGlobalActionWebserviceHooksDirectory
|
|
|
|
return [('hooks', WorkflowGlobalActionWebserviceHooksDirectory(formdata))]
|
|
|
|
|
|
|
|
|
2015-12-12 10:46:24 +01:00
|
|
|
class WorkflowGlobalAction(object):
|
|
|
|
id = None
|
|
|
|
name = None
|
|
|
|
items = None
|
|
|
|
triggers = None
|
|
|
|
backoffice_info_text = 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)
|
|
|
|
return o
|
|
|
|
else:
|
|
|
|
raise KeyError()
|
|
|
|
|
|
|
|
def append_trigger(self, type):
|
|
|
|
trigger_types = {
|
2016-04-04 22:05:06 +02:00
|
|
|
'manual': WorkflowGlobalActionManualTrigger,
|
2019-04-11 10:48:03 +02:00
|
|
|
'timeout': WorkflowGlobalActionTimeoutTrigger,
|
|
|
|
'webservice': WorkflowGlobalActionWebserviceTrigger,
|
2015-12-12 10:46:24 +01:00
|
|
|
}
|
|
|
|
o = trigger_types.get(type)()
|
|
|
|
if not self.triggers:
|
|
|
|
self.triggers = []
|
2016-04-04 22:05:06 +02:00
|
|
|
o.id = str(uuid.uuid4())
|
2015-12-12 10:46:24 +01:00
|
|
|
self.triggers.append(o)
|
|
|
|
return o
|
|
|
|
|
|
|
|
def export_to_xml(self, charset, include_id=False):
|
|
|
|
status = ET.Element('action')
|
2019-11-11 20:10:57 +01:00
|
|
|
ET.SubElement(status, 'id').text = force_text(self.id, charset)
|
|
|
|
ET.SubElement(status, 'name').text = force_text(self.name, charset)
|
2015-12-12 10:46:24 +01:00
|
|
|
|
|
|
|
if self.backoffice_info_text:
|
2019-11-11 20:10:57 +01:00
|
|
|
ET.SubElement(status, 'backoffice_info_text').text = force_text(
|
2015-12-12 10:46:24 +01:00
|
|
|
self.backoffice_info_text, charset)
|
|
|
|
|
|
|
|
items = ET.SubElement(status, 'items')
|
|
|
|
for item in self.items:
|
|
|
|
items.append(item.export_to_xml(charset=charset,
|
|
|
|
include_id=include_id))
|
|
|
|
|
|
|
|
triggers = ET.SubElement(status, 'triggers')
|
2019-07-17 07:01:11 +02:00
|
|
|
for trigger in self.triggers or []:
|
2015-12-12 10:46:24 +01:00
|
|
|
triggers.append(trigger.export_to_xml(charset=charset,
|
|
|
|
include_id=include_id))
|
|
|
|
|
|
|
|
return status
|
|
|
|
|
|
|
|
def init_with_xml(self, elem, charset, include_id=False):
|
2020-01-24 14:25:43 +01:00
|
|
|
self.id = xml_node_text(elem.find('id'))
|
|
|
|
self.name = xml_node_text(elem.find('name'))
|
2015-12-12 10:46:24 +01:00
|
|
|
if elem.find('backoffice_info_text') is not None:
|
2020-01-24 14:25:43 +01:00
|
|
|
self.backoffice_info_text = xml_node_text(elem.find('backoffice_info_text'))
|
2015-12-12 10:46:24 +01:00
|
|
|
|
|
|
|
self.items = []
|
|
|
|
for item in elem.find('items'):
|
|
|
|
item_type = item.attrib['type']
|
|
|
|
self.append_item(item_type)
|
|
|
|
item_o = self.items[-1]
|
|
|
|
item_o.parent = self
|
|
|
|
item_o.init_with_xml(item, charset, include_id=include_id)
|
|
|
|
|
|
|
|
self.triggers = []
|
|
|
|
for trigger in elem.find('triggers'):
|
|
|
|
trigger_type = trigger.attrib['type']
|
|
|
|
self.append_trigger(trigger_type)
|
|
|
|
trigger_o = self.triggers[-1]
|
|
|
|
trigger_o.parent = self
|
|
|
|
trigger_o.init_with_xml(trigger, charset, include_id=include_id)
|
|
|
|
|
2006-12-16 10:52:19 +01:00
|
|
|
|
2016-03-07 21:28:20 +01:00
|
|
|
class WorkflowCriticalityLevel(object):
|
|
|
|
id = None
|
|
|
|
name = None
|
|
|
|
colour = None
|
|
|
|
|
|
|
|
def __init__(self, name=None, colour=None):
|
|
|
|
self.name = name
|
|
|
|
self.colour = colour
|
|
|
|
self.id = str(random.randint(0, 100000))
|
|
|
|
|
|
|
|
def export_to_xml(self, charset, include_id=False):
|
|
|
|
level = ET.Element('criticality-level')
|
2019-11-11 20:10:57 +01:00
|
|
|
ET.SubElement(level, 'id').text = force_text(self.id, charset) if self.id else ''
|
|
|
|
ET.SubElement(level, 'name').text = force_text(self.name, charset)
|
2016-03-07 21:28:20 +01:00
|
|
|
if self.colour:
|
2019-11-11 20:10:57 +01:00
|
|
|
ET.SubElement(level, 'colour').text = force_text(self.colour, charset)
|
2016-03-07 21:28:20 +01:00
|
|
|
return level
|
|
|
|
|
|
|
|
def init_with_xml(self, elem, charset, include_id=False):
|
2020-01-24 14:25:43 +01:00
|
|
|
self.id = xml_node_text(elem.find('id'))
|
|
|
|
self.name = xml_node_text(elem.find('name'))
|
2016-03-07 21:28:20 +01:00
|
|
|
if elem.find('colour') is not None:
|
2020-01-24 14:25:43 +01:00
|
|
|
self.colour = xml_node_text(elem.find('colour'))
|
2016-03-07 21:28:20 +01:00
|
|
|
|
|
|
|
|
2014-12-20 19:50:40 +01:00
|
|
|
class WorkflowStatus(object):
|
2006-06-08 21:14:31 +02:00
|
|
|
id = None
|
|
|
|
name = None
|
|
|
|
items = None
|
2012-10-03 10:19:06 +02:00
|
|
|
visibility = None
|
2012-11-16 17:34:48 +01:00
|
|
|
forced_endpoint = False
|
2014-01-13 17:23:31 +01:00
|
|
|
colour = 'FFFFFF'
|
2015-08-26 20:28:07 +02:00
|
|
|
backoffice_info_text = None
|
2017-01-24 14:44:47 +01:00
|
|
|
extra_css_class = ''
|
2006-06-08 21:14:31 +02:00
|
|
|
|
|
|
|
def __init__(self, name = None):
|
|
|
|
self.name = name
|
|
|
|
self.items = []
|
|
|
|
|
2012-10-12 09:40:26 +02:00
|
|
|
def __eq__(self, other):
|
|
|
|
if other is None:
|
|
|
|
return False
|
|
|
|
# this assumes both status are from the same workflow
|
2013-05-07 10:47:18 +02:00
|
|
|
if type(other) is str:
|
|
|
|
other_id = other
|
|
|
|
else:
|
|
|
|
other_id = other.id
|
|
|
|
return self.id == other_id
|
2012-10-12 09:40:26 +02:00
|
|
|
|
2015-06-17 14:58:40 +02:00
|
|
|
def migrate(self):
|
|
|
|
changed = False
|
|
|
|
for item in self.items:
|
|
|
|
changed |= item.migrate()
|
|
|
|
return changed
|
|
|
|
|
2006-06-08 21:14:31 +02:00
|
|
|
def append_item(self, type):
|
2006-06-08 23:26:17 +02:00
|
|
|
for klass in item_classes:
|
|
|
|
if klass.key == type:
|
2006-06-11 18:33:20 +02:00
|
|
|
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)
|
2006-06-08 23:26:17 +02:00
|
|
|
break
|
2006-06-08 21:14:31 +02:00
|
|
|
else:
|
|
|
|
raise KeyError()
|
|
|
|
|
2011-04-17 19:38:25 +02:00
|
|
|
def get_item(self, id):
|
|
|
|
for item in self.items:
|
|
|
|
if item.id == id:
|
|
|
|
return item
|
|
|
|
raise KeyError()
|
|
|
|
|
2019-06-04 08:44:13 +02:00
|
|
|
def get_action_form(self, filled, user, displayed_fields=None):
|
2016-11-16 13:26:28 +01:00
|
|
|
form = Form(enctype='multipart/form-data', use_tokens=False)
|
|
|
|
form.attrs['id'] = 'wf-actions'
|
2006-06-09 13:07:36 +02:00
|
|
|
for item in self.items:
|
2006-07-21 23:52:35 +02:00
|
|
|
if not item.check_auth(filled, user):
|
|
|
|
continue
|
2018-03-23 17:08:24 +01:00
|
|
|
if not item.check_condition(filled):
|
|
|
|
continue
|
2019-06-04 08:44:13 +02:00
|
|
|
item.fill_form(form, filled, user, displayed_fields=displayed_fields)
|
2006-06-09 13:07:36 +02:00
|
|
|
|
2015-12-12 10:46:24 +01:00
|
|
|
for action in filled.formdef.workflow.get_global_actions_for_user(filled, user):
|
|
|
|
form.add_submit('button-action-%s' % action.id, action.name)
|
|
|
|
if form.get_widget('button-action-%s' % action.id):
|
|
|
|
form.get_widget('button-action-%s' % action.id).backoffice_info_text = action.backoffice_info_text
|
|
|
|
|
2006-06-09 13:07:36 +02:00
|
|
|
if form.widgets or form.submit_widgets:
|
|
|
|
return form
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
2019-06-04 08:44:13 +02:00
|
|
|
def get_active_items(self, form, filled, user):
|
2006-06-09 14:09:43 +02:00
|
|
|
for item in self.items:
|
|
|
|
if hasattr(item, 'by'):
|
|
|
|
for role in item.by or []:
|
|
|
|
if role == logged_users_role().id:
|
|
|
|
break
|
2008-03-20 14:50:59 +01:00
|
|
|
if role == '_submitter':
|
2012-01-10 14:11:04 +01:00
|
|
|
if filled.is_submitter(user):
|
2008-03-20 14:50:59 +01:00
|
|
|
break
|
|
|
|
else:
|
|
|
|
continue
|
2015-02-24 17:07:18 +01:00
|
|
|
if user is None:
|
|
|
|
continue
|
2013-01-21 16:49:49 +01:00
|
|
|
role = get_role_translation(filled, role)
|
2019-09-03 10:55:53 +02:00
|
|
|
if role in user.get_roles():
|
2006-06-09 14:09:43 +02:00
|
|
|
break
|
|
|
|
else:
|
|
|
|
continue
|
2018-03-23 17:08:24 +01:00
|
|
|
if not item.check_condition(filled):
|
|
|
|
continue
|
2019-06-04 08:44:13 +02:00
|
|
|
yield item
|
|
|
|
|
|
|
|
def evaluate_live_form(self, form, filled, user):
|
|
|
|
for item in self.get_active_items(form, filled, user):
|
|
|
|
item.evaluate_live_form(form, filled, user)
|
|
|
|
|
|
|
|
def handle_form(self, form, filled, user):
|
|
|
|
# check for global actions
|
|
|
|
for action in filled.formdef.workflow.get_global_actions_for_user(filled, user):
|
|
|
|
if 'button-action-%s' % action.id in get_request().form:
|
|
|
|
url = perform_items(action.items, filled)
|
|
|
|
if url:
|
|
|
|
return url
|
|
|
|
return
|
|
|
|
|
|
|
|
evo = Evolution()
|
|
|
|
evo.time = time.localtime()
|
|
|
|
if user:
|
|
|
|
if filled.is_submitter(user):
|
|
|
|
evo.who = '_submitter'
|
|
|
|
else:
|
|
|
|
evo.who = user.id
|
|
|
|
if not filled.evolution:
|
|
|
|
filled.evolution = []
|
|
|
|
|
|
|
|
for item in self.get_active_items(form, filled, user):
|
2006-07-21 22:54:03 +02:00
|
|
|
next_url = item.submit_form(form, filled, user, evo)
|
|
|
|
if next_url is True:
|
|
|
|
break
|
|
|
|
if next_url:
|
2011-01-06 15:30:54 +01:00
|
|
|
if not form.has_errors():
|
|
|
|
filled.evolution.append(evo)
|
|
|
|
if evo.status:
|
|
|
|
filled.status = evo.status
|
|
|
|
filled.store()
|
2006-07-21 22:54:03 +02:00
|
|
|
return next_url
|
2006-06-09 14:09:43 +02:00
|
|
|
|
2006-06-19 13:33:10 +02:00
|
|
|
if form.has_errors():
|
|
|
|
return
|
|
|
|
|
|
|
|
filled.evolution.append(evo)
|
|
|
|
|
2006-06-09 14:09:43 +02:00
|
|
|
if evo.status:
|
|
|
|
filled.status = evo.status
|
|
|
|
filled.store()
|
2011-01-06 15:30:54 +01:00
|
|
|
url = filled.perform_workflow()
|
|
|
|
if url:
|
|
|
|
return url
|
2006-06-09 14:09:43 +02:00
|
|
|
|
2011-01-06 15:30:54 +01:00
|
|
|
def get_subdirectories(self, formdata):
|
|
|
|
subdirectories = []
|
|
|
|
for item in self.items:
|
|
|
|
if item.directory_name:
|
|
|
|
subdirectories.append((item.directory_name,
|
|
|
|
item.directory_class(formdata, item, self)))
|
|
|
|
return subdirectories
|
2006-06-09 13:07:36 +02:00
|
|
|
|
2015-09-12 14:20:07 +02:00
|
|
|
def get_visibility_restricted_roles(self):
|
|
|
|
if not self.visibility: # no restriction -> visible
|
|
|
|
return []
|
|
|
|
return self.visibility
|
|
|
|
|
2012-10-03 10:19:06 +02:00
|
|
|
def is_visible(self, formdata, user):
|
|
|
|
if not self.visibility: # no restriction -> visible
|
|
|
|
return True
|
2019-11-07 11:22:40 +01:00
|
|
|
if get_request() and get_request().is_in_frontoffice():
|
|
|
|
# always hide in front
|
|
|
|
return False
|
2012-10-03 10:19:06 +02:00
|
|
|
if user and user.is_admin:
|
|
|
|
return True
|
|
|
|
|
|
|
|
if user:
|
2019-09-03 10:55:53 +02:00
|
|
|
user_roles = set(user.get_roles())
|
2012-10-03 10:19:06 +02:00
|
|
|
user_roles.add(logged_users_role().id)
|
|
|
|
else:
|
|
|
|
user_roles = set([])
|
|
|
|
|
2014-09-11 14:36:44 +02:00
|
|
|
visibility_roles = self.visibility[:]
|
|
|
|
for item in self.items or []:
|
|
|
|
if not hasattr(item, 'by') or not item.by:
|
|
|
|
continue
|
|
|
|
visibility_roles.extend(item.by)
|
|
|
|
|
|
|
|
for role in visibility_roles:
|
2013-01-21 16:24:21 +01:00
|
|
|
if role != '_submitter':
|
2013-01-21 16:49:49 +01:00
|
|
|
role = get_role_translation(formdata, role)
|
2012-10-03 10:19:06 +02:00
|
|
|
if role in user_roles:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2017-11-11 16:21:02 +01:00
|
|
|
def is_endpoint(self):
|
|
|
|
# an endpoint status is a status that marks the end of the workflow; it
|
|
|
|
# can either be computed automatically (if there's no way out of the
|
|
|
|
# status) or be set manually (to mark the expected end while still
|
|
|
|
# allowing to go back and re-enter the workflow).
|
|
|
|
if self.forced_endpoint:
|
|
|
|
return True
|
|
|
|
endpoint = True
|
|
|
|
for item in self.items:
|
|
|
|
endpoint = endpoint and item.endpoint
|
|
|
|
if endpoint is False:
|
|
|
|
break
|
|
|
|
return endpoint
|
|
|
|
|
2017-05-28 20:00:45 +02:00
|
|
|
def is_waitpoint(self):
|
|
|
|
# a waitpoint status is a status waiting for an event (be it user
|
|
|
|
# interaction or something else), but can also be an endpoint (where
|
|
|
|
# the user would wait, infinitely).
|
|
|
|
waitpoint = False
|
|
|
|
endpoint = True
|
|
|
|
if self.forced_endpoint:
|
|
|
|
endpoint = True
|
|
|
|
else:
|
|
|
|
for item in self.items:
|
|
|
|
endpoint = item.endpoint and endpoint
|
|
|
|
waitpoint = item.waitpoint or waitpoint
|
|
|
|
return bool(endpoint or waitpoint)
|
|
|
|
|
2017-07-13 11:29:45 +02:00
|
|
|
def get_contrast_color(self):
|
|
|
|
colour = self.colour or 'ffffff'
|
|
|
|
return misc.get_foreground_colour(colour)
|
|
|
|
|
2006-06-09 13:07:36 +02:00
|
|
|
def __getstate__(self):
|
|
|
|
odict = self.__dict__.copy()
|
2019-11-12 11:54:16 +01:00
|
|
|
if 'parent' in odict:
|
2006-06-11 10:07:51 +02:00
|
|
|
del odict['parent']
|
2006-06-09 13:07:36 +02:00
|
|
|
return odict
|
|
|
|
|
2014-09-17 14:36:08 +02:00
|
|
|
def export_to_xml(self, charset, include_id=False):
|
2012-01-26 13:59:22 +01:00
|
|
|
status = ET.Element('status')
|
2019-11-11 20:10:57 +01:00
|
|
|
ET.SubElement(status, 'id').text = force_text(self.id, charset)
|
|
|
|
ET.SubElement(status, 'name').text = force_text(self.name, charset)
|
|
|
|
ET.SubElement(status, 'colour').text = force_text(self.colour, charset)
|
2017-01-24 16:06:34 +01:00
|
|
|
if self.extra_css_class:
|
2019-11-11 20:10:57 +01:00
|
|
|
ET.SubElement(status, 'extra_css_class').text = force_text(self.extra_css_class, charset)
|
2014-04-25 15:50:25 +02:00
|
|
|
|
|
|
|
if self.forced_endpoint:
|
|
|
|
ET.SubElement(status, 'forced_endpoint').text = 'true'
|
|
|
|
|
2015-08-26 20:28:07 +02:00
|
|
|
if self.backoffice_info_text:
|
2019-11-11 20:10:57 +01:00
|
|
|
ET.SubElement(status, 'backoffice_info_text').text = force_text(
|
2015-08-26 20:28:07 +02:00
|
|
|
self.backoffice_info_text, charset)
|
|
|
|
|
2014-04-25 15:50:25 +02:00
|
|
|
visibility_node = ET.SubElement(status, 'visibility')
|
|
|
|
for role in self.visibility or []:
|
|
|
|
ET.SubElement(visibility_node, 'role').text = str(role)
|
|
|
|
|
2012-01-26 13:59:22 +01:00
|
|
|
items = ET.SubElement(status, 'items')
|
2012-05-27 21:49:22 +02:00
|
|
|
for item in self.items:
|
2014-09-17 14:36:08 +02:00
|
|
|
items.append(item.export_to_xml(charset=charset,
|
|
|
|
include_id=include_id))
|
2012-01-26 13:59:22 +01:00
|
|
|
return status
|
|
|
|
|
|
|
|
def init_with_xml(self, elem, charset, include_id=False):
|
2020-01-24 14:25:43 +01:00
|
|
|
self.id = xml_node_text(elem.find('id'))
|
|
|
|
self.name = xml_node_text(elem.find('name'))
|
2014-04-25 15:50:25 +02:00
|
|
|
if elem.find('colour') is not None:
|
2020-01-24 14:25:43 +01:00
|
|
|
self.colour = xml_node_text(elem.find('colour'))
|
2017-01-24 14:44:47 +01:00
|
|
|
if elem.find('extra_css_class') is not None:
|
2020-01-24 14:25:43 +01:00
|
|
|
self.extra_css_class = xml_node_text(elem.find('extra_css_class'))
|
2014-04-25 15:50:25 +02:00
|
|
|
if elem.find('forced_endpoint') is not None:
|
|
|
|
self.forced_endpoint = (elem.find('forced_endpoint').text == 'true')
|
2015-08-26 20:28:07 +02:00
|
|
|
if elem.find('backoffice_info_text') is not None:
|
2020-01-24 14:25:43 +01:00
|
|
|
self.backoffice_info_text = xml_node_text(elem.find('backoffice_info_text'))
|
2014-04-25 15:50:25 +02:00
|
|
|
|
|
|
|
self.visibility = []
|
|
|
|
for visibility_role in elem.findall('visibility/role'):
|
|
|
|
self.visibility.append(visibility_role.text)
|
|
|
|
|
2012-01-26 13:59:22 +01:00
|
|
|
self.items = []
|
|
|
|
for item in elem.find('items'):
|
|
|
|
item_type = item.attrib['type']
|
|
|
|
self.append_item(item_type)
|
|
|
|
item_o = self.items[-1]
|
2014-02-04 11:11:10 +01:00
|
|
|
item_o.parent = self
|
2014-09-17 14:36:08 +02:00
|
|
|
item_o.init_with_xml(item, charset, include_id=include_id)
|
2006-06-09 13:07:36 +02:00
|
|
|
|
2019-07-12 19:36:40 +02:00
|
|
|
def __repr__(self):
|
|
|
|
return '<%s %s %r>' % (self.__class__.__name__, self.id, self.name)
|
|
|
|
|
2015-12-12 10:46:24 +01:00
|
|
|
|
|
|
|
class WorkflowStatusItem(XmlSerialisable):
|
|
|
|
node_name = 'item'
|
2006-10-10 18:30:39 +02:00
|
|
|
description = 'XX'
|
2012-11-14 16:31:35 +01:00
|
|
|
category = None # (key, label)
|
2006-06-09 13:07:36 +02:00
|
|
|
id = None
|
2018-03-23 17:08:24 +01:00
|
|
|
condition = None
|
2012-11-14 16:31:35 +01:00
|
|
|
|
2012-08-17 10:01:51 +02:00
|
|
|
endpoint = True # means it's not possible to interact, and/or cause a status change
|
|
|
|
waitpoint = False # means it's possible to wait (user interaction, or other event)
|
2015-12-12 10:46:24 +01:00
|
|
|
ok_in_global_action = True # means it can be used in a global action
|
2011-01-06 15:30:54 +01:00
|
|
|
directory_name = None
|
|
|
|
directory_class = None
|
2012-01-03 13:52:04 +01:00
|
|
|
support_substitution_variables = False
|
2006-06-08 21:14:31 +02:00
|
|
|
|
2016-03-11 16:40:16 +01:00
|
|
|
@classmethod
|
2006-06-19 11:48:30 +02:00
|
|
|
def init(cls):
|
|
|
|
pass
|
|
|
|
|
2016-03-11 16:40:16 +01:00
|
|
|
@classmethod
|
2016-06-06 19:17:27 +02:00
|
|
|
def is_available(cls, workflow=None):
|
2012-05-27 21:55:06 +02:00
|
|
|
return True
|
|
|
|
|
2015-06-17 14:58:40 +02:00
|
|
|
def migrate(self):
|
|
|
|
changed = False
|
|
|
|
for roles_attribute in ('to', 'by'):
|
|
|
|
attribute_value = getattr(self, roles_attribute, []) or []
|
|
|
|
if any((x for x in attribute_value if type(x) is int)):
|
|
|
|
setattr(self, roles_attribute, [str(x) for x in attribute_value])
|
|
|
|
changed = True
|
|
|
|
return changed
|
|
|
|
|
2006-06-08 21:14:31 +02:00
|
|
|
def render_as_line(self):
|
2018-03-23 17:08:24 +01:00
|
|
|
label = _(self.description)
|
2017-10-21 13:05:04 +02:00
|
|
|
details = self.get_line_details()
|
|
|
|
if details:
|
2018-03-23 17:08:24 +01:00
|
|
|
label += ' (%s)' % details
|
|
|
|
if self.condition and self.condition.get('value'):
|
|
|
|
label += ' (%s)' % _('conditional')
|
|
|
|
return label
|
2006-06-08 21:14:31 +02:00
|
|
|
|
2017-10-21 13:05:04 +02:00
|
|
|
def get_line_details(self):
|
|
|
|
return None
|
|
|
|
|
2013-01-21 16:24:21 +01:00
|
|
|
def render_list_of_roles(self, roles):
|
|
|
|
return self.parent.parent.render_list_of_roles(roles)
|
|
|
|
|
|
|
|
def get_list_of_roles(self, include_logged_in_users=True):
|
|
|
|
return self.parent.parent.get_list_of_roles(include_logged_in_users=include_logged_in_users)
|
|
|
|
|
2006-06-09 08:45:36 +02:00
|
|
|
def perform(self, formdata):
|
|
|
|
pass
|
2006-06-08 21:14:31 +02:00
|
|
|
|
2019-06-04 08:44:13 +02:00
|
|
|
def fill_form(self, form, formdata, user, **kwargs):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def evaluate_live_form(self, form, formdata, user):
|
2006-06-09 13:07:36 +02:00
|
|
|
pass
|
|
|
|
|
2006-07-21 22:54:03 +02:00
|
|
|
def submit_form(self, form, formdata, user, evo):
|
2006-06-09 14:09:43 +02:00
|
|
|
pass
|
|
|
|
|
2006-07-21 23:52:35 +02:00
|
|
|
def check_auth(self, formdata, user):
|
|
|
|
if not hasattr(self, 'by'):
|
|
|
|
return True
|
|
|
|
|
|
|
|
for role in self.by or []:
|
2006-10-11 14:45:48 +02:00
|
|
|
if user and role == logged_users_role().id:
|
2006-07-21 23:52:35 +02:00
|
|
|
return True
|
2008-03-19 16:59:39 +01:00
|
|
|
if role == '_submitter':
|
2012-11-11 17:38:07 +01:00
|
|
|
t = formdata.is_submitter(user)
|
|
|
|
if t is True:
|
|
|
|
return True
|
|
|
|
continue
|
2015-02-24 15:13:47 +01:00
|
|
|
if not user:
|
|
|
|
continue
|
2013-01-21 16:49:49 +01:00
|
|
|
role = get_role_translation(formdata, role)
|
2019-09-03 10:55:53 +02:00
|
|
|
if role in user.get_roles():
|
2006-07-21 23:52:35 +02:00
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
2018-03-23 17:08:24 +01:00
|
|
|
def check_condition(self, formdata):
|
2018-06-12 16:49:12 +02:00
|
|
|
context = {'formdata': formdata, 'status_item': self}
|
2018-03-23 17:08:24 +01:00
|
|
|
try:
|
2018-06-12 16:49:12 +02:00
|
|
|
return Condition(self.condition, context).evaluate()
|
2018-03-23 17:08:24 +01:00
|
|
|
except RuntimeError:
|
|
|
|
return False
|
|
|
|
|
2011-09-05 17:33:08 +02:00
|
|
|
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
|
2018-03-23 17:08:24 +01:00
|
|
|
if 'condition' in parameters:
|
2019-02-04 15:08:30 +01:00
|
|
|
form.add(ConditionWidget, '%scondition' % prefix,
|
|
|
|
title=_('Condition of execution of the action'),
|
2018-03-23 17:08:24 +01:00
|
|
|
value=self.condition, size=40,
|
|
|
|
advanced=not(self.condition))
|
2011-09-05 17:33:08 +02:00
|
|
|
|
2019-05-15 14:44:18 +02:00
|
|
|
if 'attachments' in parameters:
|
|
|
|
attachments_options, attachments = self.get_attachments_options()
|
|
|
|
if len(attachments_options) > 1:
|
|
|
|
form.add(WidgetList, '%sattachments' % prefix, title=_('Attachments'),
|
|
|
|
element_type=SingleSelectWidgetWithOther,
|
|
|
|
value=attachments,
|
|
|
|
add_element_label=_('Add attachment'),
|
|
|
|
element_kwargs={'render_br': False, 'options': attachments_options})
|
|
|
|
else:
|
|
|
|
form.add(WidgetList, '%sattachments' % prefix,
|
|
|
|
title=_('Attachments (Python expressions)'),
|
|
|
|
element_type=StringWidget,
|
|
|
|
value=attachments,
|
|
|
|
add_element_label=_('Add attachment'),
|
|
|
|
element_kwargs={'render_br': False, 'size': 50},
|
|
|
|
advanced=not(bool(attachments)))
|
|
|
|
|
2011-02-10 13:48:27 +01:00
|
|
|
def get_parameters(self):
|
2018-03-23 17:08:24 +01:00
|
|
|
return ('condition',)
|
2011-02-10 13:48:27 +01:00
|
|
|
|
2016-08-30 17:24:08 +02:00
|
|
|
def get_parameters_view(self):
|
|
|
|
r = TemplateIO(html=True)
|
|
|
|
form = Form()
|
|
|
|
parameters = [x for x in self.get_parameters() if getattr(self, x, None) is not None]
|
2018-03-23 17:08:24 +01:00
|
|
|
for parameter in parameters:
|
|
|
|
self.add_parameters_widgets(form, [parameter])
|
2016-08-30 17:24:08 +02:00
|
|
|
r += htmltext('<ul>')
|
|
|
|
for parameter in parameters:
|
|
|
|
widget = form.get_widget(parameter)
|
2020-03-04 12:41:29 +01:00
|
|
|
if not widget:
|
|
|
|
continue
|
|
|
|
r += htmltext('<li>')
|
2016-08-30 17:24:08 +02:00
|
|
|
r += htmltext('<span class="parameter">%s</span> ') % _('%s:') % widget.get_title()
|
|
|
|
r += self.get_parameter_view_value(widget, parameter)
|
|
|
|
r += htmltext('</li>')
|
|
|
|
r += htmltext('</ul>')
|
|
|
|
return r.getvalue()
|
|
|
|
|
|
|
|
def get_backoffice_info_text_parameter_view_value(self):
|
|
|
|
return htmltext('<pre>%s</pre>') % self.backoffice_info_text
|
|
|
|
|
|
|
|
def get_by_parameter_view_value(self):
|
|
|
|
return self.render_list_of_roles(self.by)
|
|
|
|
|
|
|
|
def get_timeout_parameter_view_value(self):
|
2018-07-05 17:36:28 +02:00
|
|
|
try:
|
|
|
|
return seconds2humanduration(int(self.timeout or 0))
|
|
|
|
except ValueError:
|
|
|
|
return self.timeout # probably an expression
|
2016-08-30 17:24:08 +02:00
|
|
|
|
|
|
|
def get_status_parameter_view_value(self):
|
|
|
|
for status in self.parent.parent.possible_status:
|
|
|
|
if status.id == self.status:
|
|
|
|
return htmltext('<a href="#status-%s">%s</a>') % (status.id, status.name)
|
2016-11-24 14:25:18 +01:00
|
|
|
return _('Unknown (%s)') % self.status
|
2016-08-30 17:24:08 +02:00
|
|
|
|
|
|
|
def get_parameter_view_value(self, widget, parameter):
|
|
|
|
if hasattr(self, 'get_%s_parameter_view_value' % parameter):
|
|
|
|
return getattr(self, 'get_%s_parameter_view_value' % parameter)()
|
|
|
|
value = getattr(self, parameter)
|
2016-09-02 15:13:57 +02:00
|
|
|
if type(value) is bool:
|
2016-08-30 17:24:08 +02:00
|
|
|
return _('Yes') if value else _('No')
|
|
|
|
elif hasattr(widget, 'options') and value:
|
|
|
|
for option in widget.options:
|
|
|
|
if isinstance(option, tuple):
|
|
|
|
if option[0] == value:
|
|
|
|
return option[1]
|
|
|
|
else:
|
|
|
|
if option == value:
|
|
|
|
return option
|
|
|
|
return '-'
|
|
|
|
else:
|
|
|
|
return str(value)
|
|
|
|
|
2011-02-10 15:11:14 +01:00
|
|
|
def fill_admin_form(self, form):
|
2018-03-23 17:08:24 +01:00
|
|
|
for parameter in self.get_parameters():
|
|
|
|
self.add_parameters_widgets(form, [parameter])
|
2011-02-10 15:11:14 +01:00
|
|
|
|
2011-02-10 13:48:27 +01:00
|
|
|
def submit_admin_form(self, form):
|
|
|
|
for f in self.get_parameters():
|
|
|
|
widget = form.get_widget(f)
|
|
|
|
if widget:
|
2012-08-13 12:41:38 +02:00
|
|
|
value = widget.parse()
|
|
|
|
if hasattr(self, '%s_parse' % f):
|
|
|
|
value = getattr(self, '%s_parse' % f)(value)
|
|
|
|
setattr(self, f, value)
|
2011-02-10 13:48:27 +01:00
|
|
|
|
2018-05-28 14:39:59 +02:00
|
|
|
@classmethod
|
|
|
|
def get_expression(cls, var):
|
|
|
|
if not var:
|
|
|
|
expression_type = 'text'
|
|
|
|
expression_value = ''
|
|
|
|
elif var.startswith('='):
|
|
|
|
expression_type = 'python'
|
|
|
|
expression_value = var[1:]
|
|
|
|
elif '{{' in var or '{%' in var or '[' in var:
|
|
|
|
expression_type = 'template'
|
|
|
|
expression_value = var
|
|
|
|
else:
|
|
|
|
expression_type = 'text'
|
|
|
|
expression_value = var
|
|
|
|
return {'type': expression_type, 'value': expression_value}
|
|
|
|
|
2016-06-17 12:26:47 +02:00
|
|
|
@classmethod
|
2018-06-19 15:04:19 +02:00
|
|
|
def compute(cls, var, render=True, raises=False, context=None, formdata=None, status_item=None):
|
2019-11-12 19:53:06 +01:00
|
|
|
if not isinstance(var, six.string_types):
|
2011-06-27 10:49:29 +02:00
|
|
|
return var
|
2016-05-21 09:25:10 +02:00
|
|
|
|
2018-05-28 14:39:59 +02:00
|
|
|
expression = cls.get_expression(var)
|
|
|
|
|
|
|
|
if expression['type'] != 'python' and not render:
|
2011-06-27 10:49:29 +02:00
|
|
|
return var
|
2016-05-21 09:25:10 +02:00
|
|
|
|
2018-05-28 14:39:59 +02:00
|
|
|
if expression['type'] == 'text':
|
|
|
|
return expression['value']
|
|
|
|
|
2019-12-09 18:23:39 +01:00
|
|
|
vars = get_publisher().substitutions.get_context_variables(
|
|
|
|
'lazy' if expression['type'] == 'template' else None)
|
2017-05-02 15:21:28 +02:00
|
|
|
vars.update(context or {})
|
|
|
|
|
2018-06-19 15:04:19 +02:00
|
|
|
def log_exception(exception):
|
|
|
|
from wcs.logged_errors import LoggedError
|
|
|
|
if expression['type'] == 'template':
|
|
|
|
summary = _('Failed to compute template')
|
|
|
|
else:
|
|
|
|
summary = _('Failed to compute Python expression')
|
|
|
|
LoggedError.record(summary, formdata=formdata, status_item=status_item,
|
|
|
|
expression=expression['value'],
|
|
|
|
expression_type=expression['type'],
|
|
|
|
exception=exception)
|
|
|
|
|
2018-05-28 14:39:59 +02:00
|
|
|
if expression['type'] == 'template':
|
2016-05-21 09:25:10 +02:00
|
|
|
try:
|
2018-05-28 14:39:59 +02:00
|
|
|
return Template(expression['value'], raises=raises, autoescape=False).render(vars)
|
2018-06-19 15:04:19 +02:00
|
|
|
except TemplateError as e:
|
|
|
|
log_exception(e)
|
2016-05-21 09:25:10 +02:00
|
|
|
if raises:
|
|
|
|
raise
|
|
|
|
return var
|
|
|
|
|
2011-06-27 10:49:29 +02:00
|
|
|
try:
|
2018-05-28 14:39:59 +02:00
|
|
|
return eval(expression['value'], get_publisher().get_global_eval_dict(), vars)
|
2018-06-19 15:04:19 +02:00
|
|
|
except Exception as e:
|
|
|
|
log_exception(e)
|
2015-08-31 17:42:41 +02:00
|
|
|
if raises:
|
|
|
|
raise
|
2011-06-27 10:49:29 +02:00
|
|
|
return var
|
|
|
|
|
2018-10-14 10:03:02 +02:00
|
|
|
def get_computed_role_id(self, role_id):
|
|
|
|
new_role_id = self.compute(str(role_id))
|
|
|
|
if Role.has_key(new_role_id):
|
|
|
|
return new_role_id
|
|
|
|
# computed value, not an id, try to get role by slug
|
|
|
|
new_role = Role.get_on_index(new_role_id, 'slug', ignore_errors=True)
|
|
|
|
if new_role:
|
|
|
|
return new_role.id
|
|
|
|
# fallback to role label
|
|
|
|
for role in Role.select():
|
|
|
|
if role.name == new_role_id:
|
|
|
|
return role.id
|
|
|
|
return None
|
|
|
|
|
2011-09-05 17:33:08 +02:00
|
|
|
def get_substitution_variables(self, formdata):
|
|
|
|
return {}
|
|
|
|
|
2017-05-28 20:03:14 +02:00
|
|
|
def get_target_status(self, formdata=None):
|
2014-10-30 10:30:59 +01:00
|
|
|
"""Returns a list of status this item can lead to."""
|
2015-12-13 17:02:25 +01:00
|
|
|
if not getattr(self, 'status', None):
|
|
|
|
return []
|
|
|
|
|
2017-05-28 20:03:14 +02:00
|
|
|
if self.status == '_previous':
|
|
|
|
if formdata is None:
|
|
|
|
# must be in a formdata to compute destination, just give a
|
|
|
|
# fake status for presentation purpose
|
|
|
|
return [WorkflowStatus(_('Previously Marked Status'))]
|
|
|
|
previous_status = formdata.pop_previous_marked_status()
|
|
|
|
if previous_status:
|
|
|
|
return [previous_status]
|
|
|
|
return []
|
|
|
|
|
2018-05-17 15:16:55 +02:00
|
|
|
targets = [x for x in self.parent.parent.possible_status if x.id == self.status]
|
2018-06-07 12:16:20 +02:00
|
|
|
if not targets and formdata: # do not log in presentation context: formdata is needed
|
|
|
|
from wcs.logged_errors import LoggedError
|
2018-06-07 18:05:13 +02:00
|
|
|
message = _('reference to invalid status %(target)s in status %(status)s, '
|
|
|
|
'action %(status_item)s') % {
|
|
|
|
'target': self.status,
|
|
|
|
'status': self.parent.name,
|
|
|
|
'status_item': _(self.description)
|
|
|
|
}
|
2018-06-07 12:16:20 +02:00
|
|
|
LoggedError.record(message, formdata=formdata, status_item=self)
|
|
|
|
|
2018-05-17 15:16:55 +02:00
|
|
|
return targets
|
2014-10-30 10:30:59 +01:00
|
|
|
|
2018-10-16 07:19:04 +02:00
|
|
|
def get_jump_label(self, target_id):
|
2015-11-03 14:55:40 +01:00
|
|
|
'''Return the label to use on a workflow graph arrow'''
|
|
|
|
if getattr(self, 'label', None):
|
|
|
|
label = self.label
|
|
|
|
if getattr(self, 'by', None):
|
|
|
|
roles = self.parent.parent.render_list_of_roles(self.by)
|
|
|
|
label += ' %s %s' % (_('by'), roles)
|
2017-06-20 15:00:35 +02:00
|
|
|
if getattr(self, 'status', None) == '_previous':
|
2017-05-28 20:03:14 +02:00
|
|
|
label += ' ' + _('(to last marker)')
|
2017-06-20 15:00:35 +02:00
|
|
|
if getattr(self, 'set_marker_on_status', False):
|
2017-05-28 20:03:14 +02:00
|
|
|
label += ' ' + _('(and set marker)')
|
2015-11-03 14:55:40 +01:00
|
|
|
else:
|
|
|
|
label = self.render_as_line()
|
|
|
|
return label
|
|
|
|
|
2017-10-23 08:48:52 +02:00
|
|
|
def get_backoffice_filefield_options(self):
|
|
|
|
options = []
|
|
|
|
for field in self.parent.parent.get_backoffice_fields():
|
|
|
|
if field.key == 'file':
|
|
|
|
options.append((field.id, field.label, field.id))
|
|
|
|
return options
|
|
|
|
|
|
|
|
def store_in_backoffice_filefield(self, formdata, backoffice_filefield_id,
|
|
|
|
filename, content_type, content):
|
|
|
|
filefield = [x for x in self.parent.parent.get_backoffice_fields()
|
|
|
|
if x.id == backoffice_filefield_id and x.key == 'file']
|
|
|
|
if filefield:
|
|
|
|
upload = PicklableUpload(filename, content_type)
|
|
|
|
upload.receive([content])
|
|
|
|
formdata.data[backoffice_filefield_id] = upload
|
|
|
|
formdata.store()
|
|
|
|
|
2014-09-17 14:36:08 +02:00
|
|
|
def by_export_to_xml(self, item, charset, include_id=False):
|
|
|
|
self._roles_export_to_xml('by', item, charset, include_id=include_id)
|
2013-12-12 16:20:12 +01:00
|
|
|
|
2014-09-17 14:36:08 +02:00
|
|
|
def by_init_with_xml(self, elem, charset, include_id=False):
|
|
|
|
self._roles_init_with_xml('by', elem, charset, include_id=include_id)
|
2013-12-12 16:20:12 +01:00
|
|
|
|
2014-09-17 14:36:08 +02:00
|
|
|
def to_export_to_xml(self, item, charset, include_id=False):
|
|
|
|
self._roles_export_to_xml('to', item, charset, include_id=include_id)
|
2013-12-12 16:20:12 +01:00
|
|
|
|
2014-09-17 14:36:08 +02:00
|
|
|
def to_init_with_xml(self, elem, charset, include_id=False):
|
|
|
|
self._roles_init_with_xml('to', elem, charset, include_id)
|
2013-10-28 10:09:31 +01:00
|
|
|
|
2018-03-23 17:08:24 +01:00
|
|
|
def condition_init_with_xml(self, node, charset, include_id=False):
|
2018-05-16 09:38:24 +02:00
|
|
|
self.condition = None
|
2018-03-23 17:08:24 +01:00
|
|
|
if node is None:
|
2018-05-16 09:38:24 +02:00
|
|
|
return
|
|
|
|
if node.findall('type'):
|
2018-03-23 17:08:24 +01:00
|
|
|
self.condition = {
|
2020-01-24 14:25:43 +01:00
|
|
|
'type': xml_node_text(node.find('type')),
|
|
|
|
'value': xml_node_text(node.find('value')),
|
2018-03-23 17:08:24 +01:00
|
|
|
}
|
2018-05-16 09:38:24 +02:00
|
|
|
elif node.text:
|
2018-03-23 17:08:24 +01:00
|
|
|
# backward compatibility
|
2020-01-24 14:25:43 +01:00
|
|
|
self.condition = {'type': 'python', 'value': xml_node_text(node)}
|
2018-03-23 17:08:24 +01:00
|
|
|
|
2016-07-27 12:21:47 +02:00
|
|
|
def q_admin_lookup(self, workflow, status, component, html_top):
|
2013-04-09 14:24:57 +02:00
|
|
|
return None
|
|
|
|
|
2006-06-09 13:07:36 +02:00
|
|
|
def __getstate__(self):
|
|
|
|
odict = self.__dict__.copy()
|
2019-11-12 11:54:16 +01:00
|
|
|
if 'parent' in odict:
|
2006-06-09 13:07:36 +02:00
|
|
|
del odict['parent']
|
|
|
|
return odict
|
|
|
|
|
2019-05-15 14:44:18 +02:00
|
|
|
def attachments_init_with_xml(self, elem, charset, include_id=False):
|
|
|
|
if elem is None:
|
|
|
|
self.attachments = None
|
|
|
|
else:
|
2020-01-24 14:25:43 +01:00
|
|
|
self.attachments = [xml_node_text(item) for item in elem.findall('attachment')]
|
2019-05-15 14:44:18 +02:00
|
|
|
|
|
|
|
def get_attachments_options(self):
|
|
|
|
attachments_options = [(None, '---', None)]
|
|
|
|
varnameless = []
|
|
|
|
for field in self.parent.parent.get_backoffice_fields():
|
|
|
|
if field.key != 'file':
|
|
|
|
continue
|
|
|
|
if field.varname:
|
|
|
|
codename = 'form_var_%s_raw' % field.varname
|
|
|
|
else:
|
|
|
|
codename = 'form_f%s' % field.id.replace('-', '_') # = form_fbo<...>
|
|
|
|
varnameless.append(codename)
|
|
|
|
attachments_options.append((codename, field.label, codename))
|
|
|
|
# filter: do not consider removed fields without varname
|
|
|
|
attachments = [attachment for attachment in self.attachments or []
|
|
|
|
if ((not attachment.startswith('form_fbo')) or
|
|
|
|
(attachment in varnameless))]
|
|
|
|
return attachments_options, attachments
|
|
|
|
|
|
|
|
def convert_attachments_to_uploads(self):
|
|
|
|
uploads = []
|
|
|
|
|
|
|
|
if self.attachments:
|
|
|
|
global_eval_dict = get_publisher().get_global_eval_dict()
|
|
|
|
local_eval_dict = get_publisher().substitutions.get_context_variables()
|
|
|
|
for attachment in self.attachments:
|
|
|
|
if attachment.startswith('form_fbo') and '-' in attachment:
|
|
|
|
# detect varname-less backoffice fields that were set
|
|
|
|
# before #33366 was fixed, and fix them.
|
|
|
|
attachment = attachment.replace('-', '_')
|
|
|
|
try:
|
|
|
|
# execute any Python expression
|
|
|
|
# and magically convert string like 'form_var_*_raw' to a PicklableUpload
|
|
|
|
picklableupload = eval(attachment, global_eval_dict, local_eval_dict)
|
|
|
|
except:
|
|
|
|
get_publisher().notify_of_exception(sys.exc_info(),
|
|
|
|
context='[workflow/attachments]')
|
|
|
|
continue
|
|
|
|
|
|
|
|
if not picklableupload:
|
|
|
|
continue
|
|
|
|
|
|
|
|
try:
|
|
|
|
# convert any value to a PicklableUpload; it will ususally
|
|
|
|
# be a dict like one provided by qommon/evalutils:attachment()
|
|
|
|
picklableupload = FileField.convert_value_from_anything(picklableupload)
|
|
|
|
except ValueError:
|
|
|
|
get_publisher().notify_of_exception(sys.exc_info(),
|
|
|
|
context='[workflow/attachments]')
|
|
|
|
continue
|
|
|
|
|
|
|
|
uploads.append(picklableupload)
|
|
|
|
|
|
|
|
return uploads
|
|
|
|
|
2019-07-12 19:36:40 +02:00
|
|
|
def __repr__(self):
|
|
|
|
return '<%s %s>' % (self.__class__.__name__, self.id)
|
|
|
|
|
2007-12-28 23:29:10 +01:00
|
|
|
|
2012-08-13 12:41:38 +02:00
|
|
|
class WorkflowStatusJumpItem(WorkflowStatusItem):
|
|
|
|
status = None
|
2012-08-17 10:01:51 +02:00
|
|
|
endpoint = False
|
2017-05-28 20:03:14 +02:00
|
|
|
set_marker_on_status = False
|
2017-10-21 13:05:04 +02:00
|
|
|
category = 'status-change'
|
2012-08-13 12:41:38 +02:00
|
|
|
|
|
|
|
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
|
2018-03-23 17:08:24 +01:00
|
|
|
super(WorkflowStatusJumpItem, self).add_parameters_widgets(
|
|
|
|
form, parameters, prefix=prefix, formdef=formdef)
|
2012-08-13 12:41:38 +02:00
|
|
|
if 'status' in parameters:
|
2017-05-28 20:03:14 +02:00
|
|
|
destinations = [(x.id, x.name) for x in self.parent.parent.possible_status]
|
|
|
|
|
|
|
|
# look for existing jumps that are dropping a mark
|
2017-11-19 20:27:31 +01:00
|
|
|
workflow = self.parent.parent
|
|
|
|
statuses = getattr(workflow, 'possible_status') or []
|
|
|
|
global_actions = getattr(workflow, 'global_actions') or []
|
|
|
|
for status in statuses + global_actions:
|
2017-05-28 20:03:14 +02:00
|
|
|
for item in status.items:
|
|
|
|
if getattr(item, 'set_marker_on_status', False):
|
|
|
|
destinations.append(('_previous', _('Previously Marked Status')))
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
continue
|
|
|
|
break
|
|
|
|
|
2012-08-13 12:41:38 +02:00
|
|
|
form.add(SingleSelectWidget, '%sstatus' % prefix, title = _('Status'), value = self.status,
|
2017-05-28 20:03:14 +02:00
|
|
|
options = [(None, '---')] + destinations)
|
|
|
|
|
|
|
|
if 'set_marker_on_status' in parameters:
|
|
|
|
form.add(CheckboxWidget, '%sset_marker_on_status' % prefix,
|
|
|
|
title=_('Set marker to jump back to current status'),
|
|
|
|
value=self.set_marker_on_status,
|
|
|
|
advanced=not(self.set_marker_on_status))
|
2012-08-13 12:41:38 +02:00
|
|
|
|
2017-06-29 13:31:19 +02:00
|
|
|
def handle_markers_stack(self, formdata):
|
|
|
|
if self.set_marker_on_status:
|
|
|
|
if formdata.workflow_data and '_markers_stack' in formdata.workflow_data:
|
|
|
|
markers_stack = formdata.workflow_data.get('_markers_stack')
|
|
|
|
else:
|
|
|
|
markers_stack = []
|
|
|
|
markers_stack.append({'status_id': formdata.status[3:]})
|
|
|
|
formdata.update_workflow_data({'_markers_stack': markers_stack})
|
|
|
|
|
2012-08-13 12:41:38 +02:00
|
|
|
def get_parameters(self):
|
2018-03-23 17:08:24 +01:00
|
|
|
return ('status', 'set_marker_on_status', 'condition')
|
2012-08-13 12:41:38 +02:00
|
|
|
|
|
|
|
|
2013-01-21 16:49:49 +01:00
|
|
|
def get_role_translation(formdata, role_name):
|
2013-01-21 16:24:21 +01:00
|
|
|
if role_name == '_submitter':
|
2007-12-28 23:29:10 +01:00
|
|
|
raise Exception('_submitter is not a valid role')
|
2013-01-21 16:24:21 +01:00
|
|
|
elif str(role_name).startswith('_'):
|
2013-01-21 16:49:49 +01:00
|
|
|
role_id = None
|
|
|
|
if formdata.workflow_roles:
|
|
|
|
role_id = formdata.workflow_roles.get(role_name)
|
|
|
|
if not role_id and formdata.formdef.workflow_roles:
|
|
|
|
role_id = formdata.formdef.workflow_roles.get(role_name)
|
2015-09-12 14:20:07 +02:00
|
|
|
if role_id is None:
|
|
|
|
return role_id
|
2014-12-31 10:08:18 +01:00
|
|
|
return str(role_id)
|
2007-12-28 23:29:10 +01:00
|
|
|
else:
|
2014-12-31 10:08:18 +01:00
|
|
|
return str(role_name)
|
2007-12-28 23:29:10 +01:00
|
|
|
|
2020-01-18 20:33:44 +01:00
|
|
|
|
2013-01-21 16:24:21 +01:00
|
|
|
def get_role_translation_label(workflow, role_id):
|
|
|
|
if role_id == logged_users_role().id:
|
|
|
|
return logged_users_role().name
|
|
|
|
if role_id == '_submitter':
|
2015-09-08 14:48:27 +02:00
|
|
|
return C_('role|User')
|
2013-01-21 16:24:21 +01:00
|
|
|
if str(role_id).startswith('_'):
|
|
|
|
return workflow.roles.get(role_id)
|
|
|
|
else:
|
|
|
|
try:
|
2013-04-15 20:31:59 +02:00
|
|
|
return Role.get(role_id).name
|
2013-01-21 16:24:21 +01:00
|
|
|
except KeyError:
|
2013-04-15 20:31:59 +02:00
|
|
|
return
|
2006-06-11 21:10:35 +02:00
|
|
|
|
2020-01-18 20:33:44 +01:00
|
|
|
|
2015-12-12 10:46:24 +01:00
|
|
|
def render_list_of_roles(workflow, roles):
|
|
|
|
t = []
|
|
|
|
for r in roles:
|
|
|
|
role_label = get_role_translation_label(workflow, r)
|
|
|
|
if role_label:
|
|
|
|
t.append(role_label)
|
|
|
|
return ', '.join(t)
|
|
|
|
|
|
|
|
|
2006-06-15 09:58:14 +02:00
|
|
|
item_classes = []
|
|
|
|
|
2020-01-18 20:33:44 +01:00
|
|
|
|
2006-06-15 09:58:14 +02:00
|
|
|
def register_item_class(klass):
|
2017-08-18 10:12:05 +02:00
|
|
|
if not klass.key in [x.key for x in item_classes]:
|
2006-06-15 09:58:14 +02:00
|
|
|
item_classes.append(klass)
|
2006-06-19 11:48:30 +02:00
|
|
|
klass.init()
|
2006-06-15 09:58:14 +02:00
|
|
|
|
2006-06-11 21:10:35 +02:00
|
|
|
|
2006-06-08 21:14:31 +02:00
|
|
|
class CommentableWorkflowStatusItem(WorkflowStatusItem):
|
2017-10-21 13:05:04 +02:00
|
|
|
description = N_('Comment')
|
2006-06-08 23:26:17 +02:00
|
|
|
key = 'commentable'
|
2017-10-21 13:05:04 +02:00
|
|
|
category = 'interaction'
|
2006-07-16 22:51:02 +02:00
|
|
|
endpoint = False
|
2012-08-17 10:01:51 +02:00
|
|
|
waitpoint = True
|
2015-12-12 10:46:24 +01:00
|
|
|
ok_in_global_action = False
|
2006-06-08 23:26:17 +02:00
|
|
|
|
2019-01-24 16:47:24 +01:00
|
|
|
required = False
|
2013-10-28 10:38:16 +01:00
|
|
|
varname = None
|
2012-01-23 14:32:42 +01:00
|
|
|
label = None
|
|
|
|
button_label = 0 # hack to handle legacy commentable items
|
|
|
|
hint = None
|
2012-01-26 13:59:22 +01:00
|
|
|
by = []
|
2015-08-26 20:28:07 +02:00
|
|
|
backoffice_info_text = None
|
2006-06-08 21:14:31 +02:00
|
|
|
|
2017-10-21 13:05:04 +02:00
|
|
|
def get_line_details(self):
|
2006-06-08 21:14:31 +02:00
|
|
|
if self.by:
|
2017-10-21 13:05:04 +02:00
|
|
|
return _('by %s') % self.render_list_of_roles(self.by)
|
2006-06-08 21:14:31 +02:00
|
|
|
else:
|
2017-10-21 13:05:04 +02:00
|
|
|
return _('not completed')
|
2006-06-08 21:14:31 +02:00
|
|
|
|
2019-06-04 08:44:13 +02:00
|
|
|
def fill_form(self, form, formdata, user, **kwargs):
|
2007-01-03 17:48:34 +01:00
|
|
|
if not 'comment' in [x.name for x in form.widgets]:
|
2012-01-23 14:32:42 +01:00
|
|
|
if self.label is None:
|
|
|
|
title = _('Comment')
|
|
|
|
else:
|
|
|
|
title = self.label
|
2019-01-24 16:47:24 +01:00
|
|
|
form.add(TextWidget, 'comment', title=title, required=self.required,
|
2012-01-23 14:32:42 +01:00
|
|
|
cols=40, rows=10, hint=self.hint)
|
2007-06-26 11:19:09 +02:00
|
|
|
form.get_widget('comment').attrs['class'] = 'comment'
|
2012-01-23 14:32:42 +01:00
|
|
|
if self.button_label == 0:
|
2012-04-04 15:59:31 +02:00
|
|
|
form.add_submit('button%s' % self.id, _('Add Comment'))
|
2012-01-23 14:32:42 +01:00
|
|
|
elif self.button_label:
|
2012-04-04 15:59:31 +02:00
|
|
|
form.add_submit('button%s' % self.id, self.button_label)
|
2015-09-11 11:37:37 +02:00
|
|
|
if form.get_widget('button%s' % self.id):
|
|
|
|
form.get_widget('button%s' % self.id).backoffice_info_text = self.backoffice_info_text
|
2006-06-09 13:07:36 +02:00
|
|
|
|
2006-07-21 22:54:03 +02:00
|
|
|
def submit_form(self, form, formdata, user, evo):
|
2008-05-29 23:52:00 +02:00
|
|
|
if form.get_widget('comment'):
|
|
|
|
evo.comment = form.get_widget('comment').parse()
|
2013-10-28 10:38:16 +01:00
|
|
|
if self.varname:
|
|
|
|
workflow_data = {'comment_%s' % self.varname: evo.comment}
|
|
|
|
formdata.update_workflow_data(workflow_data)
|
2006-06-09 14:09:43 +02:00
|
|
|
|
2012-01-23 14:32:42 +01:00
|
|
|
def submit_admin_form(self, form):
|
|
|
|
for f in self.get_parameters():
|
|
|
|
widget = form.get_widget(f)
|
|
|
|
if widget:
|
|
|
|
setattr(self, f, widget.parse())
|
|
|
|
|
2006-06-08 21:14:31 +02:00
|
|
|
def fill_admin_form(self, form):
|
2006-10-27 14:42:34 +02:00
|
|
|
if self.by and not type(self.by) is list:
|
|
|
|
self.by = None
|
2019-06-19 17:33:35 +02:00
|
|
|
return super(CommentableWorkflowStatusItem, self).fill_admin_form(form)
|
2011-02-10 15:11:14 +01:00
|
|
|
|
|
|
|
def get_parameters(self):
|
2018-03-23 17:08:24 +01:00
|
|
|
return ('label', 'button_label', 'hint', 'by', 'varname',
|
2019-01-24 16:47:24 +01:00
|
|
|
'backoffice_info_text', 'required', 'condition')
|
2011-02-10 15:11:14 +01:00
|
|
|
|
2011-06-21 22:05:37 +02:00
|
|
|
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
|
2018-03-23 17:08:24 +01:00
|
|
|
super(CommentableWorkflowStatusItem, self).add_parameters_widgets(
|
|
|
|
form, parameters, prefix=prefix, formdef=formdef)
|
2012-01-23 14:32:42 +01:00
|
|
|
if 'label' in parameters:
|
|
|
|
if self.label is None:
|
|
|
|
self.label = _('Comment')
|
|
|
|
form.add(StringWidget, '%slabel' % prefix, size=40, title=_('Label'), value=self.label)
|
|
|
|
if 'button_label' in parameters:
|
|
|
|
if self.button_label == 0:
|
|
|
|
self.button_label = _('Add Comment')
|
|
|
|
form.add(StringWidget, '%sbutton_label' % prefix, title=_('Button Label'),
|
|
|
|
hint=_('(empty to disable the button)'),
|
|
|
|
value=self.button_label)
|
|
|
|
if 'hint' in parameters:
|
|
|
|
form.add(StringWidget, '%shint' % prefix, size=40, title=_('Hint'), value=self.hint)
|
2011-02-10 15:11:14 +01:00
|
|
|
if 'by' in parameters:
|
|
|
|
form.add(WidgetList, '%sby' % prefix, title = _('By'),
|
|
|
|
element_type=SingleSelectWidget,
|
|
|
|
value=self.by,
|
|
|
|
add_element_label=_('Add Role'),
|
|
|
|
element_kwargs={'render_br': False,
|
2015-09-25 14:44:33 +02:00
|
|
|
'options': [(None, '---', None)] + self.get_list_of_roles()})
|
2013-10-28 10:38:16 +01:00
|
|
|
if 'varname' in parameters:
|
2013-12-12 12:28:03 +01:00
|
|
|
form.add(VarnameWidget, '%svarname' % prefix,
|
2017-06-19 09:43:15 +02:00
|
|
|
title=_('Identifier'), value=self.varname,
|
|
|
|
hint=_('This will make the comment available in a variable named comment_ + identifier.'))
|
2019-01-24 16:47:24 +01:00
|
|
|
if 'required' in parameters:
|
|
|
|
form.add(CheckboxWidget, '%srequired' % prefix,
|
|
|
|
title=_('Required'), value=self.required)
|
2015-08-26 20:28:07 +02:00
|
|
|
if 'backoffice_info_text' in parameters:
|
2015-09-11 19:17:12 +02:00
|
|
|
form.add(WysiwygTextWidget, '%sbackoffice_info_text' % prefix,
|
2015-08-26 20:28:07 +02:00
|
|
|
title=_('Information Text for Backoffice'),
|
|
|
|
value=self.backoffice_info_text)
|
2006-06-08 23:26:17 +02:00
|
|
|
|
2014-09-17 14:36:08 +02:00
|
|
|
def button_label_export_to_xml(self, xml_item, charset, include_id=False):
|
2013-03-25 14:40:49 +01:00
|
|
|
if self.button_label == 0:
|
|
|
|
pass
|
|
|
|
elif self.button_label is None:
|
2014-07-23 16:47:43 +02:00
|
|
|
# button_label being None is a special case meaning "no button", it
|
|
|
|
# should be handled differently than the "not filled" case
|
2013-03-25 14:40:49 +01:00
|
|
|
el = ET.SubElement(xml_item, 'button_label')
|
|
|
|
else:
|
|
|
|
el = ET.SubElement(xml_item, 'button_label')
|
2020-01-28 09:35:35 +01:00
|
|
|
el.text = self.button_label
|
2013-03-25 14:40:49 +01:00
|
|
|
|
2014-09-17 14:36:08 +02:00
|
|
|
def button_label_init_with_xml(self, element, charset, include_id=False):
|
2014-07-23 16:47:43 +02:00
|
|
|
if element is None:
|
2013-03-25 14:40:49 +01:00
|
|
|
return
|
2020-01-24 14:25:43 +01:00
|
|
|
# this can be None if element is self-closing, <button_label />, which
|
|
|
|
# then maps to None, meaning "no button".
|
|
|
|
self.button_label = xml_node_text(element)
|
2013-03-25 14:40:49 +01:00
|
|
|
|
2006-06-15 09:58:14 +02:00
|
|
|
register_item_class(CommentableWorkflowStatusItem)
|
2006-06-08 21:14:31 +02:00
|
|
|
|
|
|
|
|
2012-08-13 12:41:38 +02:00
|
|
|
class ChoiceWorkflowStatusItem(WorkflowStatusJumpItem):
|
2017-10-21 13:05:04 +02:00
|
|
|
description = N_('Manual Jump')
|
2006-06-08 23:26:17 +02:00
|
|
|
key = 'choice'
|
2006-07-16 22:51:02 +02:00
|
|
|
endpoint = False
|
2012-08-17 10:01:51 +02:00
|
|
|
waitpoint = True
|
2015-12-12 10:46:24 +01:00
|
|
|
ok_in_global_action = False
|
2006-06-08 21:14:31 +02:00
|
|
|
|
|
|
|
label = None
|
2018-08-08 13:34:43 +02:00
|
|
|
identifier = None
|
2012-01-26 13:59:22 +01:00
|
|
|
by = []
|
2015-08-26 20:28:07 +02:00
|
|
|
backoffice_info_text = None
|
2016-04-14 11:01:24 +02:00
|
|
|
require_confirmation = False
|
2019-11-03 09:23:32 +01:00
|
|
|
ignore_form_errors = False
|
2006-06-08 21:14:31 +02:00
|
|
|
|
2018-05-28 14:39:59 +02:00
|
|
|
def get_label(self):
|
|
|
|
expression = self.get_expression(self.label)
|
|
|
|
if expression['type'] == 'text':
|
|
|
|
return expression['value']
|
|
|
|
return _('computed label')
|
|
|
|
|
2017-10-21 13:05:04 +02:00
|
|
|
def get_line_details(self):
|
2006-06-08 21:14:31 +02:00
|
|
|
if self.label:
|
2017-05-28 20:03:14 +02:00
|
|
|
more = ''
|
|
|
|
if self.set_marker_on_status:
|
|
|
|
more += ' ' + _('(and set marker)')
|
2010-12-03 14:56:22 +01:00
|
|
|
if self.by:
|
2017-10-21 13:05:04 +02:00
|
|
|
return _('"%(label)s" by %(by)s%(more)s') % {
|
2018-05-28 14:39:59 +02:00
|
|
|
'label' : self.get_label(),
|
2017-05-28 20:03:14 +02:00
|
|
|
'by' : self.render_list_of_roles(self.by),
|
|
|
|
'more': more
|
|
|
|
}
|
2010-12-03 14:56:22 +01:00
|
|
|
else:
|
2017-10-21 13:05:04 +02:00
|
|
|
return _('"%(label)s"%(more)s') % {
|
2018-05-28 14:39:59 +02:00
|
|
|
'label': self.get_label(),
|
2017-05-28 20:03:14 +02:00
|
|
|
'more': more
|
|
|
|
}
|
2006-06-08 21:14:31 +02:00
|
|
|
else:
|
2017-10-21 13:05:04 +02:00
|
|
|
return _('not completed')
|
2006-06-08 21:14:31 +02:00
|
|
|
|
2019-06-04 08:44:13 +02:00
|
|
|
def fill_form(self, form, formdata, user, **kwargs):
|
2017-04-14 17:00:45 +02:00
|
|
|
label = self.compute(self.label)
|
|
|
|
if not label:
|
|
|
|
return
|
2018-08-08 13:34:43 +02:00
|
|
|
widget = form.add_submit('button%s' % self.id, label)
|
|
|
|
if self.identifier:
|
|
|
|
widget.extra_css_class = 'button-%s' % self.identifier
|
2016-04-14 11:01:24 +02:00
|
|
|
if self.require_confirmation:
|
|
|
|
get_response().add_javascript(['jquery.js', '../../i18n.js', 'qommon.js'])
|
|
|
|
widget.attrs = {'data-ask-for-confirmation': 'true'}
|
2019-11-03 09:23:32 +01:00
|
|
|
widget.backoffice_info_text = self.backoffice_info_text
|
|
|
|
widget.ignore_form_errors = self.ignore_form_errors
|
|
|
|
if self.ignore_form_errors:
|
|
|
|
widget.attrs['formnovalidate'] = 'formnovalidate'
|
2006-06-08 21:14:31 +02:00
|
|
|
|
2006-07-21 22:54:03 +02:00
|
|
|
def submit_form(self, form, formdata, user, evo):
|
2012-04-04 15:59:31 +02:00
|
|
|
if form.get_submit() == 'button%s' % self.id:
|
2017-05-28 20:03:14 +02:00
|
|
|
wf_status = self.get_target_status(formdata)
|
2012-08-13 12:41:38 +02:00
|
|
|
if wf_status:
|
2014-10-30 10:30:59 +01:00
|
|
|
evo.status = 'wf-%s' % wf_status[0].id
|
2017-06-29 13:31:19 +02:00
|
|
|
self.handle_markers_stack(formdata)
|
2012-08-13 12:41:38 +02:00
|
|
|
form.clear_errors()
|
|
|
|
return True # get out of processing loop
|
2006-06-09 14:09:43 +02:00
|
|
|
|
2011-06-21 22:05:37 +02:00
|
|
|
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
|
2018-03-23 17:08:24 +01:00
|
|
|
super(ChoiceWorkflowStatusItem, self).add_parameters_widgets(
|
|
|
|
form, parameters, prefix=prefix, formdef=formdef)
|
2011-02-10 15:11:14 +01:00
|
|
|
if 'label' in parameters:
|
2016-06-01 09:28:11 +02:00
|
|
|
form.add(ComputedExpressionWidget, '%slabel' % prefix,
|
|
|
|
title=_('Label'), value=self.label)
|
2011-02-10 15:11:14 +01:00
|
|
|
if 'by' in parameters:
|
|
|
|
form.add(WidgetList, '%sby' % prefix, title = _('By'), element_type = SingleSelectWidget,
|
2006-06-08 23:26:17 +02:00
|
|
|
value = self.by,
|
|
|
|
add_element_label = _('Add Role'),
|
2013-01-21 16:24:21 +01:00
|
|
|
element_kwargs={'render_br': False,
|
2015-09-25 14:44:33 +02:00
|
|
|
'options': [(None, '---', None)] + self.get_list_of_roles()})
|
2016-04-14 11:01:24 +02:00
|
|
|
if 'require_confirmation' in parameters:
|
|
|
|
form.add(CheckboxWidget, '%srequire_confirmation' % prefix,
|
|
|
|
title=_('Require confirmation'),
|
|
|
|
value=self.require_confirmation)
|
2015-08-26 20:28:07 +02:00
|
|
|
if 'backoffice_info_text' in parameters:
|
2015-09-11 19:17:12 +02:00
|
|
|
form.add(WysiwygTextWidget, '%sbackoffice_info_text' % prefix,
|
2015-08-26 20:28:07 +02:00
|
|
|
title=_('Information Text for Backoffice'),
|
|
|
|
value=self.backoffice_info_text)
|
2018-08-08 13:34:43 +02:00
|
|
|
if 'identifier' in parameters:
|
|
|
|
form.add(VarnameWidget, '%sidentifier' % prefix,
|
|
|
|
title=_('Identifier'), value=self.identifier,
|
|
|
|
advanced=True)
|
2019-11-03 09:23:32 +01:00
|
|
|
if 'ignore_form_errors' in parameters:
|
|
|
|
form.add(CheckboxWidget, '%signore_form_errors' % prefix,
|
|
|
|
title=_('Ignore form errors'),
|
|
|
|
value=self.ignore_form_errors,
|
|
|
|
advanced=True)
|
2006-06-08 23:26:17 +02:00
|
|
|
|
2011-02-10 13:48:27 +01:00
|
|
|
def get_parameters(self):
|
2018-03-23 17:08:24 +01:00
|
|
|
return ('label', 'by', 'status',
|
|
|
|
'require_confirmation',
|
|
|
|
'backoffice_info_text',
|
2019-11-03 09:23:32 +01:00
|
|
|
'ignore_form_errors',
|
2018-03-23 17:08:24 +01:00
|
|
|
'set_marker_on_status',
|
2018-08-08 13:34:43 +02:00
|
|
|
'condition', 'identifier',)
|
2006-06-08 21:14:31 +02:00
|
|
|
|
2006-06-15 09:58:14 +02:00
|
|
|
register_item_class(ChoiceWorkflowStatusItem)
|
2006-03-27 17:07:55 +02:00
|
|
|
|
2006-06-08 23:26:17 +02:00
|
|
|
|
2012-08-13 12:41:38 +02:00
|
|
|
class JumpOnSubmitWorkflowStatusItem(WorkflowStatusJumpItem):
|
2017-10-21 13:05:04 +02:00
|
|
|
description = N_('On Submit Jump')
|
2006-06-19 13:33:10 +02:00
|
|
|
key = 'jumponsubmit'
|
2015-12-12 10:46:24 +01:00
|
|
|
ok_in_global_action = False
|
2006-06-19 13:33:10 +02:00
|
|
|
|
2017-10-21 13:05:04 +02:00
|
|
|
def get_line_details(self):
|
2012-01-10 14:24:26 +01:00
|
|
|
if self.status:
|
2014-10-30 10:30:59 +01:00
|
|
|
if self.get_target_status():
|
2017-10-21 13:05:04 +02:00
|
|
|
return _('to %s') % self.get_target_status()[0].name
|
2012-08-13 12:41:38 +02:00
|
|
|
else:
|
2017-10-21 13:05:04 +02:00
|
|
|
return _('broken')
|
2012-01-10 14:24:26 +01:00
|
|
|
else:
|
2017-10-21 13:05:04 +02:00
|
|
|
return _('not completed')
|
2012-01-10 14:24:26 +01:00
|
|
|
|
2006-07-21 22:54:03 +02:00
|
|
|
def submit_form(self, form, formdata, user, evo):
|
2006-06-19 13:33:10 +02:00
|
|
|
if form.is_submitted() and not form.has_errors():
|
2017-05-28 20:03:14 +02:00
|
|
|
wf_status = self.get_target_status(formdata)
|
2012-08-13 12:41:38 +02:00
|
|
|
if wf_status:
|
2014-10-30 10:30:59 +01:00
|
|
|
evo.status = 'wf-%s' % wf_status[0].id
|
2017-06-29 13:31:19 +02:00
|
|
|
self.handle_markers_stack(formdata)
|
2006-06-19 13:33:10 +02:00
|
|
|
|
2011-02-10 13:48:27 +01:00
|
|
|
def get_parameters(self):
|
2018-03-23 17:08:24 +01:00
|
|
|
return ('status', 'set_marker_on_status', 'condition')
|
2006-06-19 13:33:10 +02:00
|
|
|
register_item_class(JumpOnSubmitWorkflowStatusItem)
|
|
|
|
|
|
|
|
|
2006-06-08 23:26:17 +02:00
|
|
|
class SendmailWorkflowStatusItem(WorkflowStatusItem):
|
2017-10-21 13:05:04 +02:00
|
|
|
description = N_('Email')
|
2006-06-08 23:26:17 +02:00
|
|
|
key = 'sendmail'
|
2017-10-21 13:05:04 +02:00
|
|
|
category = 'interaction'
|
2012-01-03 13:52:04 +01:00
|
|
|
support_substitution_variables = True
|
2006-06-08 23:26:17 +02:00
|
|
|
|
2012-01-26 13:59:22 +01:00
|
|
|
to = []
|
2006-06-08 23:26:17 +02:00
|
|
|
subject = None
|
2020-01-21 09:55:20 +01:00
|
|
|
mail_template = None
|
2006-06-08 23:26:17 +02:00
|
|
|
body = None
|
2017-03-02 15:25:56 +01:00
|
|
|
custom_from = None
|
2017-09-26 17:13:47 +02:00
|
|
|
attachments = None
|
2006-06-08 23:26:17 +02:00
|
|
|
|
2006-11-13 17:36:21 +01:00
|
|
|
comment = None
|
|
|
|
|
2016-11-12 17:11:16 +01:00
|
|
|
def _get_role_id_from_xml(self, elem, charset, include_id=False):
|
|
|
|
# override to allow for destination set with computed values.
|
|
|
|
if elem is None:
|
|
|
|
return None
|
2020-01-24 14:25:43 +01:00
|
|
|
value = xml_node_text(elem)
|
2016-11-12 17:11:16 +01:00
|
|
|
|
2018-05-28 14:39:59 +02:00
|
|
|
if self.get_expression(value)['type'] != 'text' or '@' in value:
|
2016-11-12 17:11:16 +01:00
|
|
|
return value
|
|
|
|
|
|
|
|
return super(SendmailWorkflowStatusItem, self)._get_role_id_from_xml(
|
|
|
|
elem, charset, include_id=include_id)
|
|
|
|
|
2016-04-05 10:50:13 +02:00
|
|
|
def render_list_of_roles_or_emails(self, roles):
|
|
|
|
t = []
|
|
|
|
for r in roles:
|
2018-05-28 14:39:59 +02:00
|
|
|
expression = self.get_expression(r)
|
|
|
|
if expression['type'] in ('python', 'template'):
|
2016-04-05 10:50:13 +02:00
|
|
|
t.append(_('computed value'))
|
2018-05-28 14:39:59 +02:00
|
|
|
elif '@' in expression['value']:
|
|
|
|
t.append(expression['value'])
|
2016-04-05 10:50:13 +02:00
|
|
|
else:
|
|
|
|
role_label = get_role_translation_label(self.parent.parent, r)
|
|
|
|
if role_label:
|
|
|
|
t.append(role_label)
|
|
|
|
return ', '.join(t)
|
|
|
|
|
2016-08-30 17:24:08 +02:00
|
|
|
def get_to_parameter_view_value(self):
|
|
|
|
return self.render_list_of_roles_or_emails(self.to)
|
|
|
|
|
2017-10-21 13:05:04 +02:00
|
|
|
def get_line_details(self):
|
2006-06-08 23:26:17 +02:00
|
|
|
if self.to:
|
2017-10-21 13:05:04 +02:00
|
|
|
return _('to %s') % self.render_list_of_roles_or_emails(self.to)
|
2006-06-08 23:26:17 +02:00
|
|
|
else:
|
2017-10-21 13:05:04 +02:00
|
|
|
return _('not completed')
|
2006-06-08 23:26:17 +02:00
|
|
|
|
2011-02-10 13:48:27 +01:00
|
|
|
def get_parameters(self):
|
2020-01-21 09:55:20 +01:00
|
|
|
return ('to', 'mail_template', 'subject', 'body', 'attachments', 'custom_from', 'condition')
|
2011-02-10 13:48:27 +01:00
|
|
|
|
2011-06-21 22:05:37 +02:00
|
|
|
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
|
2018-03-23 17:08:24 +01:00
|
|
|
super(SendmailWorkflowStatusItem, self).add_parameters_widgets(
|
|
|
|
form, parameters, prefix=prefix, formdef=formdef)
|
2020-01-21 09:55:20 +01:00
|
|
|
subject_body_attrs = {}
|
|
|
|
if 'subject' in parameters or 'body' in parameters:
|
|
|
|
if get_publisher().has_site_option('mail-templates') and MailTemplate.count():
|
|
|
|
subject_body_attrs = {
|
|
|
|
'data-dynamic-display-value': '',
|
|
|
|
'data-dynamic-display-child-of': '%smail_template' % prefix,
|
|
|
|
}
|
|
|
|
|
2011-02-10 15:11:14 +01:00
|
|
|
if 'to' in parameters:
|
|
|
|
form.add(WidgetList, '%sto' % prefix, title=_('To'),
|
2016-04-05 10:50:13 +02:00
|
|
|
element_type=SingleSelectWidgetWithOther,
|
2011-02-10 15:11:14 +01:00
|
|
|
value=self.to,
|
|
|
|
add_element_label=_('Add Role'),
|
|
|
|
element_kwargs={'render_br': False,
|
2015-09-25 14:44:33 +02:00
|
|
|
'options': [(None, '---', None)] +
|
2013-01-21 16:24:21 +01:00
|
|
|
self.get_list_of_roles(include_logged_in_users=False)})
|
2011-02-10 15:11:14 +01:00
|
|
|
if 'subject' in parameters:
|
|
|
|
form.add(StringWidget, '%ssubject' % prefix, title=_('Subject'),
|
2017-10-15 02:48:32 +02:00
|
|
|
validation_function=ComputedExpressionWidget.validate_template,
|
2020-01-21 09:55:20 +01:00
|
|
|
value=self.subject, size=40,
|
|
|
|
attrs=subject_body_attrs)
|
|
|
|
if 'mail_template' in parameters and get_publisher().has_site_option('mail-templates') and MailTemplate.count():
|
|
|
|
form.add(SingleSelectWidget,
|
|
|
|
'%smail_template' % prefix,
|
|
|
|
title=_('Mail Template'),
|
|
|
|
value=self.mail_template,
|
|
|
|
options=[(None, '', '')] + MailTemplate.get_as_options_list(),
|
|
|
|
attrs={'data-dynamic-display-parent': 'true'}
|
|
|
|
)
|
2011-02-10 15:11:14 +01:00
|
|
|
if 'body' in parameters:
|
|
|
|
form.add(TextWidget, '%sbody' % prefix, title=_('Body'),
|
|
|
|
value=self.body, cols=80, rows=10,
|
2020-01-21 09:55:20 +01:00
|
|
|
validation_function=ComputedExpressionWidget.validate_template,
|
|
|
|
attrs=subject_body_attrs)
|
2017-09-26 17:13:47 +02:00
|
|
|
|
2017-03-02 15:25:56 +01:00
|
|
|
if 'custom_from' in parameters:
|
|
|
|
form.add(ComputedExpressionWidget, '%scustom_from' % prefix,
|
|
|
|
title=_('Custom From Address'), value=self.custom_from,
|
|
|
|
advanced=not(bool(self.custom_from)))
|
2011-02-10 15:11:14 +01:00
|
|
|
|
2016-08-30 17:24:08 +02:00
|
|
|
def get_body_parameter_view_value(self):
|
|
|
|
return htmltext('<pre class="wrapping-pre">%s</pre>') % self.body
|
|
|
|
|
2006-06-09 08:45:36 +02:00
|
|
|
def perform(self, formdata):
|
2006-06-09 20:24:47 +02:00
|
|
|
if not self.to:
|
|
|
|
return
|
2020-01-21 09:55:20 +01:00
|
|
|
if not (self.subject and self.body) and not self.mail_template:
|
2015-01-03 10:10:35 +01:00
|
|
|
return
|
2006-06-09 20:24:47 +02:00
|
|
|
|
2011-06-21 22:05:52 +02:00
|
|
|
url = formdata.get_url()
|
2020-01-21 09:55:20 +01:00
|
|
|
body = self.body
|
|
|
|
subject = self.subject
|
|
|
|
if self.mail_template:
|
|
|
|
mail_template = MailTemplate.get_by_slug(self.mail_template)
|
|
|
|
if mail_template:
|
|
|
|
body = mail_template.body
|
|
|
|
subject = mail_template.subject
|
|
|
|
else:
|
|
|
|
from wcs.logged_errors import LoggedError
|
|
|
|
message = _('reference to invalid mail template %(mail_template)s in status %(status)s') % {
|
|
|
|
'status': self.parent.name,
|
|
|
|
'mail_template': self.mail_template,
|
|
|
|
}
|
|
|
|
LoggedError.record(message, formdata=formdata, status_item=self)
|
|
|
|
return
|
|
|
|
|
2008-01-21 12:03:01 +01:00
|
|
|
try:
|
2018-01-11 09:33:42 +01:00
|
|
|
mail_body = template_on_formdata(formdata,
|
2020-01-21 09:55:20 +01:00
|
|
|
self.compute(body, render=False),
|
|
|
|
autoescape=body.startswith('<'))
|
2017-10-18 00:02:29 +02:00
|
|
|
except TemplateError as e:
|
|
|
|
get_logger().error('error in template for email body [%s], '
|
|
|
|
'mail could not be generated: %s' % (url, str(e)))
|
2008-01-21 12:03:01 +01:00
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
2018-01-11 09:33:42 +01:00
|
|
|
mail_subject = template_on_formdata(formdata,
|
2020-01-21 09:55:20 +01:00
|
|
|
self.compute(subject, render=False),
|
2018-01-11 09:33:42 +01:00
|
|
|
autoescape=False)
|
2017-10-18 00:02:29 +02:00
|
|
|
except TemplateError as e:
|
|
|
|
get_logger().error('error in template for email subject [%s], '
|
|
|
|
'mail could not be generated: %s' % (url, str(e)))
|
2008-01-21 12:03:01 +01:00
|
|
|
return
|
|
|
|
|
2007-03-28 11:52:28 +02:00
|
|
|
users_cfg = get_cfg('users', {})
|
|
|
|
|
2011-04-17 19:57:01 +02:00
|
|
|
# this works around the fact that parametric workflows only support
|
|
|
|
# string values, so if we get set a string, we convert it here to an
|
|
|
|
# array.
|
2019-11-12 19:53:06 +01:00
|
|
|
if isinstance(self.to, six.string_types):
|
2011-04-17 19:57:01 +02:00
|
|
|
self.to = [self.to]
|
|
|
|
|
2006-06-09 20:24:47 +02:00
|
|
|
addresses = []
|
|
|
|
for dest in self.to:
|
2016-04-12 11:21:34 +02:00
|
|
|
try:
|
|
|
|
dest = self.compute(dest, raises=True)
|
|
|
|
except:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if not dest:
|
|
|
|
continue
|
2011-06-27 10:49:29 +02:00
|
|
|
|
2016-12-10 17:16:10 +01:00
|
|
|
if isinstance(dest, list):
|
|
|
|
addresses.extend(dest)
|
|
|
|
continue
|
2019-11-12 19:53:06 +01:00
|
|
|
elif isinstance(dest, six.string_types) and ',' in dest:
|
2018-02-26 14:44:46 +01:00
|
|
|
# if the email contains a comma consider it as a serie of
|
|
|
|
# emails
|
|
|
|
addresses.extend([x.strip() for x in dest.split(',')])
|
|
|
|
continue
|
2016-12-10 17:16:10 +01:00
|
|
|
|
2011-04-17 19:57:01 +02:00
|
|
|
if '@' in str(dest):
|
|
|
|
addresses.append(dest)
|
|
|
|
continue
|
|
|
|
|
2006-06-09 20:24:47 +02:00
|
|
|
if dest == '_submitter':
|
2014-12-28 15:44:29 +01:00
|
|
|
submitter_email = formdata.formdef.get_submitter_email(formdata)
|
|
|
|
if submitter_email:
|
|
|
|
addresses.append(submitter_email)
|
2006-06-09 20:24:47 +02:00
|
|
|
continue
|
|
|
|
|
2013-01-21 16:49:49 +01:00
|
|
|
dest = get_role_translation(formdata, dest)
|
2006-06-09 20:24:47 +02:00
|
|
|
|
|
|
|
try:
|
|
|
|
role = Role.get(dest)
|
|
|
|
except KeyError:
|
|
|
|
continue
|
2011-01-09 16:56:49 +01:00
|
|
|
addresses.extend(role.get_emails())
|
2009-01-15 10:50:37 +01:00
|
|
|
|
2006-06-09 20:24:47 +02:00
|
|
|
if not addresses:
|
|
|
|
return
|
|
|
|
|
2017-03-02 15:25:56 +01:00
|
|
|
email_from = None
|
|
|
|
if self.custom_from:
|
|
|
|
email_from = self.compute(self.custom_from)
|
|
|
|
|
2019-05-15 14:44:18 +02:00
|
|
|
attachments = self.convert_attachments_to_uploads()
|
2017-09-26 17:13:47 +02:00
|
|
|
|
2011-08-31 16:54:57 +02:00
|
|
|
if len(addresses) > 1:
|
|
|
|
emails.email(mail_subject, mail_body, email_rcpt=None,
|
2017-03-02 15:25:56 +01:00
|
|
|
bcc=addresses, email_from=email_from,
|
2011-08-31 16:54:57 +02:00
|
|
|
exclude_current_user=False,
|
2017-09-26 17:13:47 +02:00
|
|
|
attachments=attachments,
|
2011-08-31 16:54:57 +02:00
|
|
|
fire_and_forget=True)
|
|
|
|
else:
|
|
|
|
emails.email(mail_subject, mail_body, email_rcpt=addresses,
|
2017-03-02 15:25:56 +01:00
|
|
|
email_from=email_from, exclude_current_user=False,
|
2017-09-26 17:13:47 +02:00
|
|
|
attachments=attachments,
|
2011-08-31 16:54:57 +02:00
|
|
|
fire_and_forget=True)
|
2006-06-15 09:58:14 +02:00
|
|
|
register_item_class(SendmailWorkflowStatusItem)
|
2006-06-09 20:24:47 +02:00
|
|
|
|
2011-06-21 22:08:04 +02:00
|
|
|
|
2018-07-28 14:59:12 +02:00
|
|
|
def get_formdata_template_context(formdata=None):
|
2019-12-09 18:23:39 +01:00
|
|
|
ctx = get_publisher().substitutions.get_context_variables('lazy')
|
2015-10-13 21:55:07 +02:00
|
|
|
if formdata:
|
|
|
|
ctx['url'] = formdata.get_url()
|
|
|
|
ctx['url_status'] = '%sstatus' % formdata.get_url()
|
|
|
|
ctx['details'] = formdata.formdef.get_detailed_email_form(formdata, ctx['url'])
|
|
|
|
ctx['name'] = formdata.formdef.name
|
|
|
|
ctx['number'] = formdata.id
|
|
|
|
if formdata.evolution and formdata.evolution[-1].comment:
|
|
|
|
ctx['comment'] = formdata.evolution[-1].comment
|
|
|
|
else:
|
|
|
|
ctx['comment'] = ''
|
|
|
|
ctx.update(formdata.get_as_dict())
|
|
|
|
|
|
|
|
# compatibility vars
|
|
|
|
ctx['before'] = ctx.get('form_previous_status')
|
|
|
|
ctx['after'] = ctx.get('form_status')
|
|
|
|
ctx['evolution'] = ctx.get('form_evolution')
|
|
|
|
|
|
|
|
return ctx
|
|
|
|
|
|
|
|
|
2017-10-18 00:02:29 +02:00
|
|
|
def template_on_html_string(template):
|
|
|
|
return template_on_formdata(None, template, ezt_format=ezt.FORMAT_HTML)
|
2014-11-17 18:45:56 +01:00
|
|
|
|
|
|
|
|
2018-07-28 14:59:12 +02:00
|
|
|
def template_on_formdata(formdata=None, template=None, **kwargs):
|
2014-11-17 18:45:56 +01:00
|
|
|
assert template is not None
|
2017-10-18 00:02:29 +02:00
|
|
|
if not Template.is_template_string(template):
|
|
|
|
# no tags, no variables: don't even process formdata
|
2014-11-17 18:45:56 +01:00
|
|
|
return template
|
2018-07-28 14:59:12 +02:00
|
|
|
context = get_formdata_template_context(formdata)
|
2018-01-11 09:33:42 +01:00
|
|
|
return template_on_context(context, template, **kwargs)
|
2011-06-21 22:06:02 +02:00
|
|
|
|
2013-06-25 16:01:06 +02:00
|
|
|
|
2018-01-11 09:33:42 +01:00
|
|
|
def template_on_context(context=None, template=None, **kwargs):
|
2017-09-20 14:41:27 +02:00
|
|
|
assert template is not None
|
2017-10-18 00:02:29 +02:00
|
|
|
if not Template.is_template_string(template):
|
2017-09-20 14:41:27 +02:00
|
|
|
return template
|
2018-01-11 09:33:42 +01:00
|
|
|
return Template(template, **kwargs).render(context)
|
2006-06-09 20:24:47 +02:00
|
|
|
|
2016-01-06 17:09:55 +01:00
|
|
|
|
2011-01-09 17:21:39 +01:00
|
|
|
class SendSMSWorkflowStatusItem(WorkflowStatusItem):
|
2017-10-21 13:05:04 +02:00
|
|
|
description = N_('SMS')
|
2011-01-09 17:21:39 +01:00
|
|
|
key = 'sendsms'
|
2017-10-21 13:05:04 +02:00
|
|
|
category = 'interaction'
|
2012-01-03 13:52:04 +01:00
|
|
|
support_substitution_variables = True
|
2011-01-09 17:21:39 +01:00
|
|
|
|
2012-01-26 13:59:22 +01:00
|
|
|
to = []
|
2011-01-09 17:21:39 +01:00
|
|
|
body = None
|
|
|
|
|
2017-09-30 11:44:48 +02:00
|
|
|
# don't use roles (de)serializer for "to" field
|
|
|
|
to_export_to_xml = None
|
|
|
|
to_init_with_xml = None
|
|
|
|
|
2016-12-04 17:26:49 +01:00
|
|
|
@classmethod
|
|
|
|
def is_available(cls, workflow=None):
|
|
|
|
sms_mode = get_cfg('sms', {}).get('mode') or 'none'
|
|
|
|
return sms_mode != 'none'
|
|
|
|
|
2011-02-10 13:48:27 +01:00
|
|
|
def get_parameters(self):
|
2018-03-23 17:08:24 +01:00
|
|
|
return ('to', 'body', 'condition')
|
2011-01-09 17:21:39 +01:00
|
|
|
|
2011-06-21 22:05:37 +02:00
|
|
|
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
|
2018-03-23 17:08:24 +01:00
|
|
|
super(SendSMSWorkflowStatusItem, self).add_parameters_widgets(
|
|
|
|
form, parameters, prefix=prefix, formdef=formdef)
|
2011-02-10 15:11:14 +01:00
|
|
|
if 'to' in parameters:
|
2016-06-07 12:14:51 +02:00
|
|
|
form.add(WidgetList, '%sto' % prefix, title=_('To'),
|
|
|
|
element_type=ComputedExpressionWidget,
|
2011-02-10 15:11:14 +01:00
|
|
|
value=self.to, add_element_label=_('Add Number'),
|
|
|
|
element_kwargs = {'render_br': False})
|
|
|
|
if 'body' in parameters:
|
2018-09-22 12:47:21 +02:00
|
|
|
form.add(TextWidget, '%sbody' % prefix, title=_('Body'), value=self.body, cols=80, rows=10)
|
2011-02-10 15:11:14 +01:00
|
|
|
|
2011-01-09 17:21:39 +01:00
|
|
|
def perform(self, formdata):
|
2017-10-16 09:27:03 +02:00
|
|
|
if not self.is_available():
|
|
|
|
return
|
2011-01-09 17:21:39 +01:00
|
|
|
if not self.to:
|
|
|
|
return
|
|
|
|
if not self.body:
|
|
|
|
return
|
|
|
|
|
2015-04-11 14:01:39 +02:00
|
|
|
destinations = [self.compute(x) for x in self.to]
|
|
|
|
destinations = [x for x in destinations if x] # ignore empty elements
|
|
|
|
if not destinations:
|
|
|
|
return
|
|
|
|
|
2011-01-09 17:21:39 +01:00
|
|
|
try:
|
2017-10-15 02:48:32 +02:00
|
|
|
sms_body = template_on_formdata(formdata, self.compute(self.body, render=False))
|
2017-10-18 00:02:29 +02:00
|
|
|
except TemplateError as e:
|
2011-06-21 22:05:52 +02:00
|
|
|
url = formdata.get_url()
|
2017-10-18 00:02:29 +02:00
|
|
|
get_logger().error('error in template for sms [%s], '
|
|
|
|
'sms could not be generated' % (url, str(e)))
|
2011-01-09 17:21:39 +01:00
|
|
|
return
|
|
|
|
|
2019-09-29 20:53:23 +02:00
|
|
|
from .qommon import sms
|
2011-01-09 17:21:39 +01:00
|
|
|
|
|
|
|
sms_cfg = get_cfg('sms', {})
|
|
|
|
sender = sms_cfg.get('sender', 'AuQuotidien')[:11]
|
2013-03-05 15:37:35 +01:00
|
|
|
mode = sms_cfg.get('mode', 'none')
|
2011-01-09 17:21:39 +01:00
|
|
|
try:
|
2020-03-28 17:40:21 +01:00
|
|
|
sms.SMS.get_sms_class(mode).send(sender, destinations, sms_body)
|
2019-09-29 20:53:23 +02:00
|
|
|
except errors.SMSError as e:
|
2011-01-09 17:21:39 +01:00
|
|
|
get_logger().error(e)
|
|
|
|
|
|
|
|
register_item_class(SendSMSWorkflowStatusItem)
|
2006-06-09 08:45:36 +02:00
|
|
|
|
2006-06-08 23:26:17 +02:00
|
|
|
|
|
|
|
class DisplayMessageWorkflowStatusItem(WorkflowStatusItem):
|
2018-02-11 11:44:28 +01:00
|
|
|
description = N_('Alert')
|
2006-06-08 23:26:17 +02:00
|
|
|
key = 'displaymsg'
|
2017-10-21 13:05:04 +02:00
|
|
|
category = 'interaction'
|
2012-01-03 13:52:04 +01:00
|
|
|
support_substitution_variables = True
|
2015-12-12 10:46:24 +01:00
|
|
|
ok_in_global_action = False
|
2006-06-08 23:26:17 +02:00
|
|
|
|
2016-01-22 19:16:09 +01:00
|
|
|
to = None
|
2018-02-11 11:44:28 +01:00
|
|
|
position = 'top'
|
2018-02-24 19:19:55 +01:00
|
|
|
level = None
|
2006-06-08 23:26:17 +02:00
|
|
|
message = None
|
|
|
|
|
2017-10-21 13:05:04 +02:00
|
|
|
def get_line_details(self):
|
2018-02-11 11:44:28 +01:00
|
|
|
parts = []
|
|
|
|
if self.position == 'top':
|
|
|
|
parts.append(_('top of page'))
|
2018-02-21 13:37:20 +01:00
|
|
|
elif self.position == 'bottom':
|
|
|
|
parts.append(_('bottom of page'))
|
2018-02-11 11:44:28 +01:00
|
|
|
elif self.position == 'actions':
|
2018-02-21 13:37:20 +01:00
|
|
|
parts.append(_('with actions'))
|
2016-01-22 19:16:09 +01:00
|
|
|
if self.to:
|
2018-02-21 13:26:22 +01:00
|
|
|
parts.append(_('for %s') % self.render_list_of_roles(self.to))
|
2018-02-11 11:44:28 +01:00
|
|
|
return ', '.join(parts)
|
2016-01-22 19:16:09 +01:00
|
|
|
|
2018-02-11 11:44:28 +01:00
|
|
|
def is_for_current_user(self, filled):
|
|
|
|
if not self.to:
|
|
|
|
return True
|
|
|
|
if not get_request():
|
|
|
|
return False
|
|
|
|
user = get_request().user
|
|
|
|
for role in self.to or []:
|
|
|
|
if role == '_submitter':
|
|
|
|
if filled.is_submitter(user):
|
|
|
|
return True
|
|
|
|
elif user:
|
|
|
|
role = get_role_translation(filled, role)
|
2019-09-03 10:55:53 +02:00
|
|
|
if role in user.get_roles():
|
2018-02-11 11:44:28 +01:00
|
|
|
return True
|
|
|
|
return False
|
2016-01-22 19:16:09 +01:00
|
|
|
|
2018-02-11 11:44:28 +01:00
|
|
|
def get_message(self, filled, position='top'):
|
|
|
|
if not (self.message and self.position == position and self.is_for_current_user(filled)):
|
|
|
|
return ''
|
2016-01-22 19:16:09 +01:00
|
|
|
|
2019-12-09 18:23:39 +01:00
|
|
|
dict = copy.copy(get_publisher().substitutions.get_context_variables('lazy'))
|
2006-06-09 08:45:36 +02:00
|
|
|
dict['date'] = misc.localstrftime(filled.receipt_time)
|
|
|
|
dict['number'] = filled.id
|
2013-02-12 11:07:13 +01:00
|
|
|
handling_role = filled.get_handling_role()
|
|
|
|
if handling_role and handling_role.details:
|
|
|
|
dict['receiver'] = handling_role.details.replace('\n', '<br />')
|
2006-06-09 08:45:36 +02:00
|
|
|
|
2018-02-24 19:19:55 +01:00
|
|
|
message = self.message
|
|
|
|
if self.level:
|
|
|
|
message = '<div class="%snotice">%s</div>' % (self.level, message)
|
|
|
|
|
|
|
|
return Template(message, ezt_format=ezt.FORMAT_HTML).render(dict)
|
2006-06-09 08:45:36 +02:00
|
|
|
|
2011-06-21 22:05:37 +02:00
|
|
|
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
|
2018-03-23 17:08:24 +01:00
|
|
|
super(DisplayMessageWorkflowStatusItem, self).add_parameters_widgets(
|
|
|
|
form, parameters, prefix=prefix, formdef=formdef)
|
2011-02-10 15:11:14 +01:00
|
|
|
if 'message' in parameters:
|
|
|
|
form.add(TextWidget, '%smessage' % prefix, title = _('Message'),
|
2016-09-19 09:02:14 +02:00
|
|
|
value=self.message, cols=80, rows=10,
|
2017-10-15 02:48:32 +02:00
|
|
|
validation_function=ComputedExpressionWidget.validate_template)
|
2018-02-24 19:19:55 +01:00
|
|
|
if 'level' in parameters:
|
|
|
|
form.add(SingleSelectWidget, '%slevel' % prefix, title=_('Level'),
|
|
|
|
value=self.level,
|
|
|
|
options=[(None, ''),
|
|
|
|
('success', _('Success')),
|
|
|
|
('info', _('Information')),
|
|
|
|
('warning', _('Warning')),
|
|
|
|
('error', _('Error'))])
|
2018-02-11 11:44:28 +01:00
|
|
|
if 'position' in parameters:
|
|
|
|
form.add(SingleSelectWidget, '%sposition' % prefix, title=_('Position'),
|
|
|
|
value=self.position,
|
2018-02-21 13:37:20 +01:00
|
|
|
options=[('top', _('Top of page')),
|
|
|
|
('bottom', _('Bottom of page')),
|
2018-02-21 14:02:40 +01:00
|
|
|
#('actions', _('With actions')) "too complicated"
|
|
|
|
])
|
2016-01-22 19:16:09 +01:00
|
|
|
if 'to' in parameters:
|
|
|
|
form.add(WidgetList, '%sto' % prefix, title=_('To'),
|
|
|
|
element_type=SingleSelectWidget,
|
|
|
|
value=self.to or [],
|
|
|
|
add_element_label=_('Add Role'),
|
|
|
|
element_kwargs={'render_br': False,
|
|
|
|
'options': [(None, '---', None)] +
|
|
|
|
self.get_list_of_roles(include_logged_in_users=False)})
|
2006-06-08 23:26:17 +02:00
|
|
|
|
2011-02-10 13:48:27 +01:00
|
|
|
def get_parameters(self):
|
2018-03-23 17:08:24 +01:00
|
|
|
return ('message', 'level', 'position', 'to', 'condition')
|
2006-06-08 23:26:17 +02:00
|
|
|
|
2016-08-30 17:24:08 +02:00
|
|
|
def get_message_parameter_view_value(self):
|
|
|
|
if self.message.startswith('<'):
|
|
|
|
return htmltext(self.message)
|
|
|
|
return htmltext('<pre>%s</pre>') % self.message
|
|
|
|
|
|
|
|
|
2006-06-15 09:58:14 +02:00
|
|
|
register_item_class(DisplayMessageWorkflowStatusItem)
|
2006-06-08 23:26:17 +02:00
|
|
|
|
2006-07-16 18:33:03 +02:00
|
|
|
|
|
|
|
class RedirectToStatusWorkflowStatusItem(WorkflowStatusItem):
|
2017-10-21 13:05:04 +02:00
|
|
|
description = N_('Status Page Redirection')
|
2006-07-16 18:33:03 +02:00
|
|
|
key = 'redirectstatus'
|
2015-12-12 10:46:24 +01:00
|
|
|
ok_in_global_action = False
|
2006-07-16 18:33:03 +02:00
|
|
|
|
|
|
|
backoffice = False
|
|
|
|
|
|
|
|
def perform(self, formdata):
|
|
|
|
return formdata.get_url(self.backoffice)
|
|
|
|
|
2011-06-21 22:05:37 +02:00
|
|
|
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
|
2018-03-23 17:08:24 +01:00
|
|
|
super(RedirectToStatusWorkflowStatusItem, self).add_parameters_widgets(
|
|
|
|
form, parameters, prefix=prefix, formdef=formdef)
|
2011-02-10 15:11:14 +01:00
|
|
|
if 'backoffice' in parameters:
|
|
|
|
form.add(CheckboxWidget, '%sbackoffice' % prefix,
|
|
|
|
title = _('Redirect to backoffice page'),
|
2006-07-16 18:33:03 +02:00
|
|
|
value = self.backoffice)
|
|
|
|
|
2011-02-10 13:48:27 +01:00
|
|
|
def get_parameters(self):
|
|
|
|
return ('backoffice',)
|
2006-07-16 18:33:03 +02:00
|
|
|
|
2014-01-03 16:02:00 +01:00
|
|
|
# RedirectToStatusWorkflowStatusItem is not registered as the class kept for
|
|
|
|
# backward compatibility only and should not be exposed to the user. (#3031)
|
2006-07-16 18:33:03 +02:00
|
|
|
|
|
|
|
|
2006-07-21 22:54:03 +02:00
|
|
|
class EditableWorkflowStatusItem(WorkflowStatusItem):
|
2017-10-21 13:05:04 +02:00
|
|
|
description = N_('Edition')
|
2006-07-21 22:54:03 +02:00
|
|
|
key = 'editable'
|
2017-10-21 13:05:04 +02:00
|
|
|
category = 'formdata-action'
|
2006-07-21 22:54:03 +02:00
|
|
|
endpoint = False
|
2012-08-17 10:01:51 +02:00
|
|
|
waitpoint = True
|
2015-12-12 10:46:24 +01:00
|
|
|
ok_in_global_action = False
|
2006-07-21 22:54:03 +02:00
|
|
|
|
2012-01-26 13:59:22 +01:00
|
|
|
by = []
|
2006-07-21 22:54:03 +02:00
|
|
|
status = None
|
2006-09-10 17:31:08 +02:00
|
|
|
label = None
|
2015-08-26 20:28:07 +02:00
|
|
|
backoffice_info_text = None
|
2006-07-21 22:54:03 +02:00
|
|
|
|
2017-10-21 13:05:04 +02:00
|
|
|
def get_line_details(self):
|
2006-07-21 22:54:03 +02:00
|
|
|
if self.by:
|
2017-10-21 13:05:04 +02:00
|
|
|
return _('by %s') % self.render_list_of_roles(self.by)
|
2006-07-21 22:54:03 +02:00
|
|
|
else:
|
2017-10-21 13:05:04 +02:00
|
|
|
return _('not completed')
|
2006-07-21 22:54:03 +02:00
|
|
|
|
2019-06-04 08:44:13 +02:00
|
|
|
def fill_form(self, form, formdata, user, **kwargs):
|
2006-09-10 17:31:08 +02:00
|
|
|
label = self.label
|
|
|
|
if not label:
|
|
|
|
label = _('Edit Form')
|
2012-04-04 15:59:31 +02:00
|
|
|
form.add_submit('button%s' % self.id, label)
|
2015-08-26 20:28:07 +02:00
|
|
|
form.get_widget('button%s' % self.id).backoffice_info_text = self.backoffice_info_text
|
2006-07-21 22:54:03 +02:00
|
|
|
|
|
|
|
def submit_form(self, form, formdata, user, evo):
|
2012-04-04 15:59:31 +02:00
|
|
|
if form.get_submit() == 'button%s' % self.id:
|
2015-12-20 12:29:48 +01:00
|
|
|
return formdata.get_url(backoffice=get_request().is_in_backoffice()) + 'wfedit-%s' % self.id
|
2006-07-21 22:54:03 +02:00
|
|
|
|
2011-06-21 22:05:37 +02:00
|
|
|
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
|
2018-03-23 17:08:24 +01:00
|
|
|
super(EditableWorkflowStatusItem, self).add_parameters_widgets(
|
|
|
|
form, parameters, prefix=prefix, formdef=formdef)
|
2011-02-10 15:11:14 +01:00
|
|
|
if 'by' in parameters:
|
|
|
|
form.add(WidgetList, '%sby' % prefix, title = _('By'), element_type = SingleSelectWidget,
|
2006-07-21 22:54:03 +02:00
|
|
|
value = self.by,
|
|
|
|
add_element_label = _('Add Role'),
|
2013-01-21 16:24:21 +01:00
|
|
|
element_kwargs={'render_br': False,
|
2015-09-25 14:44:33 +02:00
|
|
|
'options': [(None, '---', None)] + self.get_list_of_roles()})
|
2011-02-10 15:11:14 +01:00
|
|
|
if 'status' in parameters:
|
|
|
|
form.add(SingleSelectWidget, '%sstatus' % prefix, title = _('Status After Edit'), value = self.status,
|
2006-07-21 22:54:03 +02:00
|
|
|
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])
|
2011-02-10 15:11:14 +01:00
|
|
|
if 'label' in parameters:
|
|
|
|
form.add(StringWidget, '%slabel' % prefix, title = _('Button Label'), value = self.label)
|
2015-08-26 20:28:07 +02:00
|
|
|
if 'backoffice_info_text' in parameters:
|
2015-09-11 19:17:12 +02:00
|
|
|
form.add(WysiwygTextWidget, '%sbackoffice_info_text' % prefix,
|
2015-08-26 20:28:07 +02:00
|
|
|
title=_('Information Text for Backoffice'),
|
|
|
|
value=self.backoffice_info_text)
|
2006-07-21 22:54:03 +02:00
|
|
|
|
2011-02-10 13:48:27 +01:00
|
|
|
def get_parameters(self):
|
2018-03-23 17:08:24 +01:00
|
|
|
return ('by', 'status', 'label', 'backoffice_info_text', 'condition')
|
2011-02-10 13:48:27 +01:00
|
|
|
|
2006-07-21 22:54:03 +02:00
|
|
|
register_item_class(EditableWorkflowStatusItem)
|
|
|
|
|
2006-07-16 18:33:03 +02:00
|
|
|
|
2011-09-05 17:33:08 +02:00
|
|
|
def load_extra():
|
2019-09-29 20:53:23 +02:00
|
|
|
from .wf import aggregation_email
|
|
|
|
from .wf import timeout_jump
|
|
|
|
from .wf import jump
|
|
|
|
from .wf import attachment
|
|
|
|
from .wf import remove
|
|
|
|
from .wf import roles
|
|
|
|
from .wf import dispatch
|
|
|
|
from .wf import geolocate
|
|
|
|
from .wf import wscall
|
|
|
|
from .wf import form
|
|
|
|
from .wf import register_comment
|
|
|
|
from .wf import anonymise
|
|
|
|
from .wf import export_to_model
|
|
|
|
from .wf import resubmit
|
|
|
|
from .wf import criticality
|
|
|
|
from .wf import profile
|
|
|
|
from .wf import backoffice_fields
|
|
|
|
from .wf import redirect_to_url
|
|
|
|
from .wf import notification
|
2019-05-18 14:17:33 +02:00
|
|
|
from .wf import create_formdata
|
2019-11-26 11:23:50 +01:00
|
|
|
from .wf import create_carddata
|
2019-09-29 20:53:23 +02:00
|
|
|
|
|
|
|
from .wf.export_to_model import ExportToModel
|