[MERGE] Merge with trunk
bzr revid: jke@openerp.com-20140113193949-39t5hssln2u5s899
|
@ -9,70 +9,7 @@
|
|||
</record>
|
||||
|
||||
<record id="main_partner" model="res.partner">
|
||||
<field name="image">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
|
||||
BZrmED0AAAAASUVORK5CYII=
|
||||
</field>
|
||||
<field name="image" type="base64" file="base/static/img/main_partner-image.png"/>
|
||||
</record>
|
||||
|
||||
<record id="user_demo" model="res.users">
|
||||
|
@ -83,160 +20,13 @@ BZrmED0AAAAASUVORK5CYII=
|
|||
Mr Demo</field>
|
||||
<field name="company_id" ref="main_company"/>
|
||||
<field name="groups_id" eval="[(6,0,[ref('base.group_user'), ref('base.group_partner_manager')])]"/>
|
||||
<field name="image">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
|
||||
gk//2Q==</field>
|
||||
<field name="image" type="base64" file="base/static/img/user_demo-image.jpg"/>
|
||||
</record>
|
||||
|
||||
<record model="res.partner" id="base.partner_root">
|
||||
<field name="email">admin@example.com</field>
|
||||
<field name="tz">Europe/Brussels</field>
|
||||
<field name="image">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
|
||||
kALR5oiAU7jnCuj0RFIHQbIiKAf/2Q==</field>
|
||||
<field name="image" type="base64" file="base/static/img/partner_root-image.jpg"/>
|
||||
</record>
|
||||
|
||||
<!-- new rate for demo transactions in multi currency -->
|
||||
|
|
|
@ -81,6 +81,15 @@ class res_font(osv.Model):
|
|||
font = ttfonts.TTFontFile(font_path)
|
||||
_logger.debug("Found font %s at %s", font.name, font_path)
|
||||
found_fonts.append((font.familyName, font.name, font_path, font.styleName))
|
||||
except KeyError, ex:
|
||||
if ex.args and ex.args[0] == 'head':
|
||||
# Sometimes, the system can have a lot of Bitmap fonts, and
|
||||
# in this case, Reportlab can't load the 'head' table from
|
||||
# the structure of the TTF file (ex: NISC18030.ttf)
|
||||
# In this case, we have to bypass the loading of this font!
|
||||
_logger.warning("Could not register Fond %s (Old Bitmap font)", font_path)
|
||||
else:
|
||||
raise
|
||||
except ttfonts.TTFError:
|
||||
_logger.warning("Could not register Font %s", font_path)
|
||||
|
||||
|
@ -112,4 +121,4 @@ class res_font(osv.Model):
|
|||
def clear_caches(self):
|
||||
"""Force worker to resync at next report loading by setting an empty font list"""
|
||||
customfonts.CustomTTFonts = []
|
||||
return super(res_font, self).clear_caches()
|
||||
return super(res_font, self).clear_caches()
|
||||
|
|
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 8.3 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 3.8 KiB |
|
@ -85,10 +85,9 @@ def setup_pid_file():
|
|||
"""
|
||||
config = openerp.tools.config
|
||||
if config['pidfile']:
|
||||
fd = open(config['pidfile'], 'w')
|
||||
pidtext = "%d" % (os.getpid())
|
||||
fd.write(pidtext)
|
||||
fd.close()
|
||||
with open(config['pidfile'], 'w') as fd:
|
||||
pidtext = "%d" % (os.getpid())
|
||||
fd.write(pidtext)
|
||||
|
||||
def preload_registry(dbname):
|
||||
""" Preload a registry, and start the cron."""
|
||||
|
@ -173,7 +172,7 @@ def main(args):
|
|||
if config['workers']:
|
||||
openerp.multi_process = True
|
||||
|
||||
# preload registryies, needed for -u --stop_after_init
|
||||
# preload registries, needed for -u --stop_after_init
|
||||
rc = 0
|
||||
if config['db_name']:
|
||||
for dbname in config['db_name'].split(','):
|
||||
|
|
|
@ -130,11 +130,18 @@
|
|||
<rng:group>
|
||||
<rng:attribute name="type">
|
||||
<rng:choice>
|
||||
<rng:value>base64</rng:value>
|
||||
<rng:value>char</rng:value>
|
||||
<rng:value>file</rng:value>
|
||||
</rng:choice>
|
||||
</rng:attribute>
|
||||
<rng:text/>
|
||||
<rng:choice>
|
||||
<rng:group>
|
||||
<rng:attribute name="file"/>
|
||||
<rng:empty/>
|
||||
</rng:group>
|
||||
<rng:text/>
|
||||
</rng:choice>
|
||||
</rng:group>
|
||||
<rng:group>
|
||||
<rng:attribute name="type"><rng:value>int</rng:value></rng:attribute>
|
||||
|
@ -182,9 +189,7 @@
|
|||
<rng:optional><rng:attribute name="use"/></rng:optional>
|
||||
<rng:empty/>
|
||||
</rng:group>
|
||||
<rng:group>
|
||||
<rng:text/>
|
||||
</rng:group>
|
||||
<rng:text/>
|
||||
</rng:choice>
|
||||
</rng:element>
|
||||
</rng:define>
|
||||
|
|
|
@ -4412,7 +4412,15 @@ class BaseModel(object):
|
|||
tocreate[v] = {}
|
||||
else:
|
||||
tocreate[v] = {'id': vals[self._inherits[v]]}
|
||||
(upd0, upd1, upd2) = ('', '', [])
|
||||
|
||||
columns = [
|
||||
# columns will contain a list of field defined as a tuple
|
||||
# tuple(field_name, format_string, field_value)
|
||||
# the tuple will be used by the string formatting for the INSERT
|
||||
# statement.
|
||||
('id', "nextval('%s')" % self._sequence),
|
||||
]
|
||||
|
||||
upd_todo = []
|
||||
unknown_fields = []
|
||||
for v in vals.keys():
|
||||
|
@ -4429,15 +4437,12 @@ class BaseModel(object):
|
|||
'No such field(s) in model %s: %s.',
|
||||
self._name, ', '.join(unknown_fields))
|
||||
|
||||
# Try-except added to filter the creation of those records whose filds are readonly.
|
||||
# Example : any dashboard which has all the fields readonly.(due to Views(database views))
|
||||
try:
|
||||
cr.execute("SELECT nextval('"+self._sequence+"')")
|
||||
except:
|
||||
raise except_orm(_('UserError'),
|
||||
_('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
|
||||
if not self._sequence:
|
||||
raise except_orm(
|
||||
_('UserError'),
|
||||
_('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.')
|
||||
)
|
||||
|
||||
id_new = cr.fetchone()[0]
|
||||
for table in tocreate:
|
||||
if self._inherits[table] in vals:
|
||||
del vals[self._inherits[table]]
|
||||
|
@ -4454,9 +4459,7 @@ class BaseModel(object):
|
|||
else:
|
||||
self.pool[table].write(cr, user, [record_id], tocreate[table], context=parent_context)
|
||||
|
||||
upd0 += ',' + self._inherits[table]
|
||||
upd1 += ',%s'
|
||||
upd2.append(record_id)
|
||||
columns.append((self._inherits[table], '%s', record_id))
|
||||
|
||||
#Start : Set bool fields to be False if they are not touched(to make search more powerful)
|
||||
bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
|
||||
|
@ -4493,13 +4496,13 @@ class BaseModel(object):
|
|||
if not edit:
|
||||
vals.pop(field)
|
||||
for field in vals:
|
||||
if self._columns[field]._classic_write:
|
||||
upd0 = upd0 + ',"' + field + '"'
|
||||
upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
|
||||
upd2.append(self._columns[field]._symbol_set[1](vals[field]))
|
||||
current_field = self._columns[field]
|
||||
if current_field._classic_write:
|
||||
columns.append((field, '%s', current_field._symbol_set[1](vals[field])))
|
||||
|
||||
#for the function fields that receive a value, we set them directly in the database
|
||||
#(they may be required), but we also need to trigger the _fct_inv()
|
||||
if (hasattr(self._columns[field], '_fnct_inv')) and not isinstance(self._columns[field], fields.related):
|
||||
if (hasattr(current_field, '_fnct_inv')) and not isinstance(current_field, fields.related):
|
||||
#TODO: this way to special case the related fields is really creepy but it shouldn't be changed at
|
||||
#one week of the release candidate. It seems the only good way to handle correctly this is to add an
|
||||
#attribute to make a field `really readonly´ and thus totally ignored by the create()... otherwise
|
||||
|
@ -4511,17 +4514,33 @@ class BaseModel(object):
|
|||
else:
|
||||
#TODO: this `if´ statement should be removed because there is no good reason to special case the fields
|
||||
#related. See the above TODO comment for further explanations.
|
||||
if not isinstance(self._columns[field], fields.related):
|
||||
if not isinstance(current_field, fields.related):
|
||||
upd_todo.append(field)
|
||||
if field in self._columns \
|
||||
and hasattr(self._columns[field], 'selection') \
|
||||
and hasattr(current_field, 'selection') \
|
||||
and vals[field]:
|
||||
self._check_selection_field_value(cr, user, field, vals[field], context=context)
|
||||
if self._log_access:
|
||||
upd0 += ',create_uid,create_date,write_uid,write_date'
|
||||
upd1 += ",%s,(now() at time zone 'UTC'),%s,(now() at time zone 'UTC')"
|
||||
upd2.extend((user, user))
|
||||
cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
|
||||
columns.append(('create_uid', '%s', user))
|
||||
columns.append(('write_uid', '%s', user))
|
||||
columns.append(('create_date', "(now() at time zone 'UTC')"))
|
||||
columns.append(('write_date', "(now() at time zone 'UTC')"))
|
||||
|
||||
# the list of tuples used in this formatting corresponds to
|
||||
# tuple(field_name, format, value)
|
||||
# In some case, for example (id, create_date, write_date) we does not
|
||||
# need to read the third value of the tuple, because the real value is
|
||||
# encoded in the second value (the format).
|
||||
cr.execute(
|
||||
"""INSERT INTO "%s" (%s) VALUES(%s) RETURNING id""" % (
|
||||
self._table,
|
||||
', '.join('"%s"' % f[0] for f in columns),
|
||||
', '.join(f[1] for f in columns)
|
||||
),
|
||||
tuple([f[2] for f in columns if len(f) > 2])
|
||||
)
|
||||
|
||||
id_new, = cr.fetchone()
|
||||
upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
|
||||
|
||||
if self._parent_store and not context.get('defer_parent_store_computation'):
|
||||
|
|
|
@ -6,6 +6,7 @@ import logging
|
|||
import os
|
||||
import threading
|
||||
import traceback
|
||||
from contextlib import contextmanager, closing
|
||||
|
||||
import openerp
|
||||
from openerp import SUPERUSER_ID
|
||||
|
@ -24,24 +25,17 @@ self_id_protect = threading.Semaphore()
|
|||
# This should be moved to openerp.modules.db, along side initialize().
|
||||
def _initialize_db(id, db_name, demo, lang, user_password):
|
||||
try:
|
||||
cr = None
|
||||
try:
|
||||
self_actions[id]['progress'] = 0
|
||||
cr = openerp.sql_db.db_connect(db_name).cursor()
|
||||
self_actions[id]['progress'] = 0
|
||||
db = openerp.sql_db.db_connect(db_name)
|
||||
with closing(db.cursor()) as cr:
|
||||
openerp.modules.db.initialize(cr) # TODO this should be removed as it is done by RegistryManager.new().
|
||||
openerp.tools.config['lang'] = lang
|
||||
cr.commit()
|
||||
finally:
|
||||
if cr:
|
||||
cr.close()
|
||||
cr = None
|
||||
|
||||
registry = openerp.modules.registry.RegistryManager.new(
|
||||
db_name, demo, self_actions[id], update_module=True)
|
||||
|
||||
try:
|
||||
cr = openerp.sql_db.db_connect(db_name).cursor()
|
||||
|
||||
with closing(db.cursor()) as cr:
|
||||
if lang:
|
||||
modobj = registry['ir.module.module']
|
||||
mids = modobj.search(cr, SUPERUSER_ID, [('state', '=', 'installed')])
|
||||
|
@ -54,9 +48,7 @@ def _initialize_db(id, db_name, demo, lang, user_password):
|
|||
cr.execute('SELECT login, password FROM res_users ORDER BY login')
|
||||
self_actions[id].update(users=cr.dictfetchall(), clean=True)
|
||||
cr.commit()
|
||||
finally:
|
||||
if cr:
|
||||
cr.close()
|
||||
|
||||
except Exception, e:
|
||||
self_actions[id].update(clean=False, exception=e)
|
||||
_logger.exception('CREATE DATABASE failed:')
|
||||
|
@ -81,19 +73,15 @@ def dispatch(method, params):
|
|||
|
||||
def _create_empty_database(name):
|
||||
db = openerp.sql_db.db_connect('postgres')
|
||||
cr = db.cursor()
|
||||
chosen_template = openerp.tools.config['db_template']
|
||||
cr.execute("""SELECT datname
|
||||
FROM pg_database
|
||||
WHERE datname = %s """,
|
||||
(name,))
|
||||
if cr.fetchall():
|
||||
raise openerp.exceptions.Warning(" %s database already exists!" % name )
|
||||
try:
|
||||
cr.autocommit(True) # avoid transaction block
|
||||
cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (name, chosen_template))
|
||||
finally:
|
||||
cr.close()
|
||||
with closing(db.cursor()) as cr:
|
||||
chosen_template = openerp.tools.config['db_template']
|
||||
cr.execute("SELECT datname FROM pg_database WHERE datname = %s",
|
||||
(name,))
|
||||
if cr.fetchall():
|
||||
raise openerp.exceptions.Warning(" %s database already exists!" % name )
|
||||
else:
|
||||
cr.autocommit(True) # avoid transaction block
|
||||
cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (name, chosen_template))
|
||||
|
||||
def exp_create(db_name, demo, lang, user_password='admin'):
|
||||
self_id_protect.acquire()
|
||||
|
@ -132,12 +120,9 @@ def exp_duplicate_database(db_original_name, db_name):
|
|||
_logger.info('Duplicate database `%s` to `%s`.', db_original_name, db_name)
|
||||
openerp.sql_db.close_db(db_original_name)
|
||||
db = openerp.sql_db.db_connect('postgres')
|
||||
cr = db.cursor()
|
||||
try:
|
||||
with closing(db.cursor()) as cr:
|
||||
cr.autocommit(True) # avoid transaction block
|
||||
cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (db_name, db_original_name))
|
||||
finally:
|
||||
cr.close()
|
||||
return True
|
||||
|
||||
def exp_get_progress(id):
|
||||
|
@ -166,9 +151,8 @@ def exp_drop(db_name):
|
|||
openerp.sql_db.close_db(db_name)
|
||||
|
||||
db = openerp.sql_db.db_connect('postgres')
|
||||
cr = db.cursor()
|
||||
cr.autocommit(True) # avoid transaction block
|
||||
try:
|
||||
with closing(db.cursor()) as cr:
|
||||
cr.autocommit(True) # avoid transaction block
|
||||
# Try to terminate all other connections that might prevent
|
||||
# dropping the database
|
||||
try:
|
||||
|
@ -192,8 +176,6 @@ def exp_drop(db_name):
|
|||
raise Exception("Couldn't drop database %s: %s" % (db_name, e))
|
||||
else:
|
||||
_logger.info('DROP DB: %s', db_name)
|
||||
finally:
|
||||
cr.close()
|
||||
return True
|
||||
|
||||
@contextlib.contextmanager
|
||||
|
@ -290,9 +272,8 @@ def exp_rename(old_name, new_name):
|
|||
openerp.sql_db.close_db(old_name)
|
||||
|
||||
db = openerp.sql_db.db_connect('postgres')
|
||||
cr = db.cursor()
|
||||
cr.autocommit(True) # avoid transaction block
|
||||
try:
|
||||
with closing(db.cursor()) as cr:
|
||||
cr.autocommit(True) # avoid transaction block
|
||||
try:
|
||||
cr.execute('ALTER DATABASE "%s" RENAME TO "%s"' % (old_name, new_name))
|
||||
except Exception, e:
|
||||
|
@ -304,8 +285,6 @@ def exp_rename(old_name, new_name):
|
|||
os.rename(os.path.join(fs, old_name), os.path.join(fs, new_name))
|
||||
|
||||
_logger.info('RENAME DB: %s -> %s', old_name, new_name)
|
||||
finally:
|
||||
cr.close()
|
||||
return True
|
||||
|
||||
def exp_db_exist(db_name):
|
||||
|
@ -318,8 +297,7 @@ def exp_list(document=False):
|
|||
chosen_template = openerp.tools.config['db_template']
|
||||
templates_list = tuple(set(['template0', 'template1', 'postgres', chosen_template]))
|
||||
db = openerp.sql_db.db_connect('postgres')
|
||||
cr = db.cursor()
|
||||
try:
|
||||
with closing(db.cursor()) as cr:
|
||||
try:
|
||||
db_user = openerp.tools.config["db_user"]
|
||||
if not db_user and os.name == 'posix':
|
||||
|
@ -336,8 +314,6 @@ def exp_list(document=False):
|
|||
res = [str(name) for (name,) in cr.fetchall()]
|
||||
except Exception:
|
||||
res = []
|
||||
finally:
|
||||
cr.close()
|
||||
res.sort()
|
||||
return res
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import time
|
|||
import openerp
|
||||
from openerp.tools.translate import translate
|
||||
from openerp.osv.orm import except_orm
|
||||
from contextlib import contextmanager
|
||||
|
||||
import security
|
||||
|
||||
|
@ -159,41 +160,36 @@ def execute_cr(cr, uid, obj, method, *args, **kw):
|
|||
def execute_kw(db, uid, obj, method, args, kw=None):
|
||||
return execute(db, uid, obj, method, *args, **kw or {})
|
||||
|
||||
@contextmanager
|
||||
def closing_cr_and_commit(cr):
|
||||
try:
|
||||
yield cr
|
||||
cr.commit()
|
||||
except Exception:
|
||||
cr.rollback()
|
||||
raise
|
||||
finally:
|
||||
cr.close()
|
||||
|
||||
@check
|
||||
def execute(db, uid, obj, method, *args, **kw):
|
||||
threading.currentThread().dbname = db
|
||||
cr = openerp.registry(db).db.cursor()
|
||||
try:
|
||||
try:
|
||||
if method.startswith('_'):
|
||||
raise except_orm('Access Denied', 'Private methods (such as %s) cannot be called remotely.' % (method,))
|
||||
res = execute_cr(cr, uid, obj, method, *args, **kw)
|
||||
if res is None:
|
||||
_logger.warning('The method %s of the object %s can not return `None` !', method, obj)
|
||||
cr.commit()
|
||||
except Exception:
|
||||
cr.rollback()
|
||||
raise
|
||||
finally:
|
||||
cr.close()
|
||||
return res
|
||||
with closing_cr_and_commit(openerp.registry(db).db.cursor()) as cr:
|
||||
if method.startswith('_'):
|
||||
raise except_orm('Access Denied', 'Private methods (such as %s) cannot be called remotely.' % (method,))
|
||||
res = execute_cr(cr, uid, obj, method, *args, **kw)
|
||||
if res is None:
|
||||
_logger.warning('The method %s of the object %s can not return `None` !', method, obj)
|
||||
return res
|
||||
|
||||
def exec_workflow_cr(cr, uid, obj, signal, *args):
|
||||
res_id = args[0]
|
||||
return execute_cr(cr, uid, obj, 'signal_workflow', [res_id], signal)[res_id]
|
||||
|
||||
|
||||
@check
|
||||
def exec_workflow(db, uid, obj, signal, *args):
|
||||
cr = openerp.registry(db).db.cursor()
|
||||
try:
|
||||
try:
|
||||
res = exec_workflow_cr(cr, uid, obj, signal, *args)
|
||||
cr.commit()
|
||||
except Exception:
|
||||
cr.rollback()
|
||||
raise
|
||||
finally:
|
||||
cr.close()
|
||||
return res
|
||||
with closing_cr_and_commit(openerp.registry(db).db.cursor()) as cr:
|
||||
return exec_workflow_cr(cr, uid, obj, signal, *args)
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -152,11 +152,22 @@ def _eval_xml(self, node, pool, cr, uid, idref, context=None):
|
|||
'Could not eval(%s) for %s in %s', a_eval, node.get('name'), context)
|
||||
raise
|
||||
def _process(s, idref):
|
||||
m = re.findall('[^%]%\((.*?)\)[ds]', s)
|
||||
for id in m:
|
||||
matches = re.finditer('[^%]%\((.*?)\)[ds]', s)
|
||||
done = []
|
||||
for m in matches:
|
||||
found = m.group()[1:]
|
||||
if found in done:
|
||||
continue
|
||||
done.append(found)
|
||||
id = m.groups()[0]
|
||||
if not id in idref:
|
||||
idref[id]=self.id_get(cr, id)
|
||||
return s % idref
|
||||
idref[id] = self.id_get(cr, id)
|
||||
s = s.replace(found, str(idref[id]))
|
||||
|
||||
s = s.replace('%%', '%') # Quite wierd but it's for (somewhat) backward compatibility sake
|
||||
|
||||
return s
|
||||
|
||||
if t == 'xml':
|
||||
_fix_multiple_roots(node)
|
||||
return '<?xml version="1.0"?>\n'\
|
||||
|
@ -165,20 +176,38 @@ def _eval_xml(self, node, pool, cr, uid, idref, context=None):
|
|||
if t == 'html':
|
||||
return _process("".join([etree.tostring(n, encoding='utf-8')
|
||||
for n in node]), idref)
|
||||
if t in ('char', 'int', 'float'):
|
||||
d = node.text
|
||||
if t == 'int':
|
||||
d = d.strip()
|
||||
if d == 'None':
|
||||
return None
|
||||
else:
|
||||
return int(d.strip())
|
||||
elif t == 'float':
|
||||
return float(d.strip())
|
||||
return d
|
||||
elif t in ('list','tuple'):
|
||||
|
||||
data = node.text
|
||||
if node.get('file'):
|
||||
with openerp.tools.file_open(node.get('file'), 'rb') as f:
|
||||
data = f.read()
|
||||
|
||||
if t == 'file':
|
||||
from ..modules import module
|
||||
path = data.strip()
|
||||
if not module.get_module_resource(self.module, path):
|
||||
raise IOError("No such file or directory: '%s' in %s" % (
|
||||
path, self.module))
|
||||
return '%s,%s' % (self.module, path)
|
||||
|
||||
if t == 'char':
|
||||
return data
|
||||
|
||||
if t == 'base64':
|
||||
return data.encode('base64')
|
||||
|
||||
if t == 'int':
|
||||
d = data.strip()
|
||||
if d == 'None':
|
||||
return None
|
||||
return int(d)
|
||||
|
||||
if t == 'float':
|
||||
return float(data.strip())
|
||||
|
||||
if t in ('list','tuple'):
|
||||
res=[]
|
||||
for n in node.findall('./value'):
|
||||
for n in node.iterchildren(tag='value'):
|
||||
res.append(_eval_xml(self,n,pool,cr,uid,idref))
|
||||
if t=='tuple':
|
||||
return tuple(res)
|
||||
|
@ -609,9 +638,8 @@ form: module.record_id""" % (xml_id,)
|
|||
"Verify that this is a window action or add a type argument." % (a_action,)
|
||||
action_type,action_mode,action_name,view_id,target = rrres
|
||||
if view_id:
|
||||
cr.execute('SELECT arch FROM ir_ui_view WHERE id=%s', (int(view_id),))
|
||||
arch, = cr.fetchone()
|
||||
action_mode = etree.fromstring(arch.encode('utf8')).tag
|
||||
view_arch = self.pool['ir.ui.view'].read(cr, 1, [view_id], ['arch'])
|
||||
action_mode = etree.fromstring(view_arch[0]['arch'].encode('utf8')).tag
|
||||
cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (int(a_id),))
|
||||
if cr.rowcount:
|
||||
action_mode, = cr.fetchone()
|
||||
|
@ -745,6 +773,9 @@ form: module.record_id""" % (xml_id,)
|
|||
if rec_context:
|
||||
rec_context = unsafe_eval(rec_context)
|
||||
self._test_xml_id(rec_id)
|
||||
# in update mode, the record won't be updated if the data node explicitely
|
||||
# opt-out using @noupdate="1". A second check will be performed in
|
||||
# ir.model.data#_update() using the record's ir.model.data `noupdate` field.
|
||||
if self.isnoupdate(data_node) and self.mode != 'init':
|
||||
# check if the xml record has an id string
|
||||
if rec_id:
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2004-2014 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -15,16 +15,17 @@
|
|||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import instance
|
||||
from openerp.workflow.service import WorkflowService
|
||||
|
||||
wkf_on_create_cache = {}
|
||||
# The new API is in openerp.workflow.workflow_service
|
||||
# OLD API of the Workflow
|
||||
|
||||
def clear_cache(cr, uid):
|
||||
wkf_on_create_cache[cr.dbname]={}
|
||||
WorkflowService.clear_cache(cr.dbname)
|
||||
|
||||
def trg_write(uid, res_type, res_id, cr):
|
||||
"""
|
||||
|
@ -36,10 +37,7 @@ def trg_write(uid, res_type, res_id, cr):
|
|||
:param res_id: the model instance id the workflow belongs to
|
||||
:param cr: a database cursor
|
||||
"""
|
||||
ident = (uid,res_type,res_id)
|
||||
cr.execute('select id from wkf_instance where res_id=%s and res_type=%s and state=%s', (res_id or None,res_type or None, 'active'))
|
||||
for (id,) in cr.fetchall():
|
||||
instance.update(cr, id, ident)
|
||||
return WorkflowService.new(cr, uid, res_type, res_id).write()
|
||||
|
||||
def trg_trigger(uid, res_type, res_id, cr):
|
||||
"""
|
||||
|
@ -52,12 +50,7 @@ def trg_trigger(uid, res_type, res_id, cr):
|
|||
:param res_id: the model instance id the workflow belongs to
|
||||
:param cr: a database cursor
|
||||
"""
|
||||
cr.execute('select instance_id from wkf_triggers where res_id=%s and model=%s', (res_id,res_type))
|
||||
res = cr.fetchall()
|
||||
for (instance_id,) in res:
|
||||
cr.execute('select %s,res_type,res_id from wkf_instance where id=%s', (uid, instance_id,))
|
||||
ident = cr.fetchone()
|
||||
instance.update(cr, instance_id, ident)
|
||||
return WorkflowService.new(cr, uid, res_type, res_id).trigger()
|
||||
|
||||
def trg_delete(uid, res_type, res_id, cr):
|
||||
"""
|
||||
|
@ -67,8 +60,7 @@ def trg_delete(uid, res_type, res_id, cr):
|
|||
:param res_id: the model instance id the workflow belongs to
|
||||
:param cr: a database cursor
|
||||
"""
|
||||
ident = (uid,res_type,res_id)
|
||||
instance.delete(cr, ident)
|
||||
return WorkflowService.new(cr, uid, res_type, res_id).delete()
|
||||
|
||||
def trg_create(uid, res_type, res_id, cr):
|
||||
"""
|
||||
|
@ -78,16 +70,7 @@ def trg_create(uid, res_type, res_id, cr):
|
|||
:param res_id: the model instance id to own the created worfklow instance
|
||||
:param cr: a database cursor
|
||||
"""
|
||||
ident = (uid,res_type,res_id)
|
||||
wkf_on_create_cache.setdefault(cr.dbname, {})
|
||||
if res_type in wkf_on_create_cache[cr.dbname]:
|
||||
wkf_ids = wkf_on_create_cache[cr.dbname][res_type]
|
||||
else:
|
||||
cr.execute('select id from wkf where osv=%s and on_create=True', (res_type,))
|
||||
wkf_ids = cr.fetchall()
|
||||
wkf_on_create_cache[cr.dbname][res_type] = wkf_ids
|
||||
for (wkf_id,) in wkf_ids:
|
||||
instance.create(cr, ident, wkf_id)
|
||||
return WorkflowService.new(cr, uid, res_type, res_id).create()
|
||||
|
||||
def trg_validate(uid, res_type, res_id, signal, cr):
|
||||
"""
|
||||
|
@ -98,14 +81,8 @@ def trg_validate(uid, res_type, res_id, signal, cr):
|
|||
:signal: the signal name to be fired
|
||||
:param cr: a database cursor
|
||||
"""
|
||||
result = False
|
||||
ident = (uid,res_type,res_id)
|
||||
# ids of all active workflow instances for a corresponding resource (id, model_nam)
|
||||
cr.execute('select id from wkf_instance where res_id=%s and res_type=%s and state=%s', (res_id, res_type, 'active'))
|
||||
for (id,) in cr.fetchall():
|
||||
res2 = instance.validate(cr, id, ident, signal)
|
||||
result = result or res2
|
||||
return result
|
||||
assert isinstance(signal, basestring)
|
||||
return WorkflowService.new(cr, uid, res_type, res_id).validate(signal)
|
||||
|
||||
def trg_redirect(uid, res_type, res_id, new_rid, cr):
|
||||
"""
|
||||
|
@ -120,22 +97,7 @@ def trg_redirect(uid, res_type, res_id, new_rid, cr):
|
|||
:param new_rid: the model instance id to own the worfklow instance
|
||||
:param cr: a database cursor
|
||||
"""
|
||||
# get ids of wkf instances for the old resource (res_id)
|
||||
#CHECKME: shouldn't we get only active instances?
|
||||
cr.execute('select id, wkf_id from wkf_instance where res_id=%s and res_type=%s', (res_id, res_type))
|
||||
for old_inst_id, wkf_id in cr.fetchall():
|
||||
# first active instance for new resource (new_rid), using same wkf
|
||||
cr.execute(
|
||||
'SELECT id '\
|
||||
'FROM wkf_instance '\
|
||||
'WHERE res_id=%s AND res_type=%s AND wkf_id=%s AND state=%s',
|
||||
(new_rid, res_type, wkf_id, 'active'))
|
||||
new_id = cr.fetchone()
|
||||
if new_id:
|
||||
# select all workitems which "wait" for the old instance
|
||||
cr.execute('select id from wkf_workitem where subflow_id=%s', (old_inst_id,))
|
||||
for (item_id,) in cr.fetchall():
|
||||
# redirect all those workitems to the wkf instance of the new resource
|
||||
cr.execute('update wkf_workitem set subflow_id=%s where id=%s', (new_id[0], item_id))
|
||||
assert isinstance(new_rid, (long, int))
|
||||
return WorkflowService.new(cr, uid, res_type, res_id).redirect(new_rid)
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import openerp.sql_db
|
||||
|
||||
class Session(object):
|
||||
def __init__(self, cr, uid):
|
||||
assert isinstance(cr, openerp.sql_db.Cursor)
|
||||
assert isinstance(uid, (int, long))
|
||||
self.cr = cr
|
||||
self.uid = uid
|
||||
|
||||
class Record(object):
|
||||
def __init__(self, model, record_id):
|
||||
assert isinstance(model, basestring)
|
||||
assert isinstance(record_id, (int, long))
|
||||
self.model = model
|
||||
self.id = record_id
|
||||
|
||||
class WorkflowActivity(object):
|
||||
KIND_FUNCTION = 'function'
|
||||
KIND_DUMMY = 'dummy'
|
||||
KIND_STOPALL = 'stopall'
|
||||
KIND_SUBFLOW = 'subflow'
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2004-2014 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -19,58 +19,115 @@
|
|||
#
|
||||
##############################################################################
|
||||
import workitem
|
||||
from openerp.workflow.helpers import Session
|
||||
from openerp.workflow.helpers import Record
|
||||
from openerp.workflow.workitem import WorkflowItem
|
||||
|
||||
def create(cr, ident, wkf_id):
|
||||
(uid,res_type,res_id) = ident
|
||||
cr.execute('insert into wkf_instance (res_type,res_id,uid,wkf_id) values (%s,%s,%s,%s) RETURNING id', (res_type,res_id,uid,wkf_id))
|
||||
id_new = cr.fetchone()[0]
|
||||
cr.execute('select * from wkf_activity where flow_start=True and wkf_id=%s', (wkf_id,))
|
||||
res = cr.dictfetchall()
|
||||
stack = []
|
||||
workitem.create(cr, res, id_new, ident, stack=stack)
|
||||
update(cr, id_new, ident)
|
||||
return id_new
|
||||
class WorkflowInstance(object):
|
||||
def __init__(self, session, record, values):
|
||||
assert isinstance(session, Session)
|
||||
assert isinstance(record, Record)
|
||||
self.session = session
|
||||
self.record = record
|
||||
|
||||
def delete(cr, ident):
|
||||
(uid,res_type,res_id) = ident
|
||||
cr.execute('delete from wkf_instance where res_id=%s and res_type=%s', (res_id,res_type))
|
||||
if not values:
|
||||
values = {}
|
||||
|
||||
def validate(cr, inst_id, ident, signal, force_running=False):
|
||||
cr.execute("select * from wkf_workitem where inst_id=%s", (inst_id,))
|
||||
stack = []
|
||||
for witem in cr.dictfetchall():
|
||||
assert isinstance(values, dict)
|
||||
self.instance = values
|
||||
|
||||
@classmethod
|
||||
def create(cls, session, record, workflow_id):
|
||||
assert isinstance(session, Session)
|
||||
assert isinstance(record, Record)
|
||||
assert isinstance(workflow_id, (int, long))
|
||||
|
||||
cr = session.cr
|
||||
cr.execute('insert into wkf_instance (res_type,res_id,uid,wkf_id) values (%s,%s,%s,%s) RETURNING id', (record.model, record.id, session.uid, workflow_id))
|
||||
instance_id = cr.fetchone()[0]
|
||||
|
||||
cr.execute('select * from wkf_activity where flow_start=True and wkf_id=%s', (workflow_id,))
|
||||
stack = []
|
||||
workitem.process(cr, witem, ident, signal, force_running, stack=stack)
|
||||
# An action is returned
|
||||
_update_end(cr, inst_id, ident)
|
||||
return stack and stack[0] or False
|
||||
|
||||
def update(cr, inst_id, ident):
|
||||
cr.execute("select * from wkf_workitem where inst_id=%s", (inst_id,))
|
||||
for witem in cr.dictfetchall():
|
||||
activities = cr.dictfetchall()
|
||||
for activity in activities:
|
||||
WorkflowItem.create(session, record, activity, instance_id, stack)
|
||||
|
||||
cr.execute('SELECT * FROM wkf_instance WHERE id = %s', (instance_id,))
|
||||
values = cr.dictfetchone()
|
||||
wi = WorkflowInstance(session, record, values)
|
||||
wi.update()
|
||||
|
||||
return wi
|
||||
|
||||
def delete(self):
|
||||
self.session.cr.execute('delete from wkf_instance where res_id=%s and res_type=%s', (self.record.id, self.record.model))
|
||||
|
||||
def validate(self, signal, force_running=False):
|
||||
assert isinstance(signal, basestring)
|
||||
assert isinstance(force_running, bool)
|
||||
|
||||
cr = self.session.cr
|
||||
cr.execute("select * from wkf_workitem where inst_id=%s", (self.instance['id'],))
|
||||
stack = []
|
||||
workitem.process(cr, witem, ident, stack=stack)
|
||||
return _update_end(cr, inst_id, ident)
|
||||
for work_item_values in cr.dictfetchall():
|
||||
wi = WorkflowItem(self.session, self.record, work_item_values)
|
||||
wi.process(signal=signal, force_running=force_running, stack=stack)
|
||||
# An action is returned
|
||||
self._update_end()
|
||||
return stack and stack[0] or False
|
||||
|
||||
def _update_end(cr, inst_id, ident):
|
||||
cr.execute('select wkf_id from wkf_instance where id=%s', (inst_id,))
|
||||
wkf_id = cr.fetchone()[0]
|
||||
cr.execute('select state,flow_stop from wkf_workitem w left join wkf_activity a on (a.id=w.act_id) where w.inst_id=%s', (inst_id,))
|
||||
ok=True
|
||||
for r in cr.fetchall():
|
||||
if (r[0]<>'complete') or not r[1]:
|
||||
ok=False
|
||||
break
|
||||
if ok:
|
||||
cr.execute('select distinct a.name from wkf_activity a left join wkf_workitem w on (a.id=w.act_id) where w.inst_id=%s', (inst_id,))
|
||||
act_names = cr.fetchall()
|
||||
cr.execute("update wkf_instance set state='complete' where id=%s", (inst_id,))
|
||||
cr.execute("update wkf_workitem set state='complete' where subflow_id=%s", (inst_id,))
|
||||
cr.execute("select i.id,w.osv,i.res_id from wkf_instance i left join wkf w on (i.wkf_id=w.id) where i.id IN (select inst_id from wkf_workitem where subflow_id=%s)", (inst_id,))
|
||||
for i in cr.fetchall():
|
||||
for act_name in act_names:
|
||||
validate(cr, i[0], (ident[0],i[1],i[2]), 'subflow.'+act_name[0])
|
||||
return ok
|
||||
def update(self):
|
||||
cr = self.session.cr
|
||||
|
||||
cr.execute("select * from wkf_workitem where inst_id=%s", (self.instance['id'],))
|
||||
for work_item_values in cr.dictfetchall():
|
||||
stack = []
|
||||
WorkflowItem(self.session, self.record, work_item_values).process(stack=stack)
|
||||
return self._update_end()
|
||||
|
||||
def _update_end(self):
|
||||
cr = self.session.cr
|
||||
instance_id = self.instance['id']
|
||||
cr.execute('select wkf_id from wkf_instance where id=%s', (instance_id,))
|
||||
wkf_id = cr.fetchone()[0]
|
||||
cr.execute('select state,flow_stop from wkf_workitem w left join wkf_activity a on (a.id=w.act_id) where w.inst_id=%s', (instance_id,))
|
||||
ok=True
|
||||
for r in cr.fetchall():
|
||||
if (r[0]<>'complete') or not r[1]:
|
||||
ok=False
|
||||
break
|
||||
if ok:
|
||||
cr.execute('select distinct a.name from wkf_activity a left join wkf_workitem w on (a.id=w.act_id) where w.inst_id=%s', (instance_id,))
|
||||
act_names = cr.fetchall()
|
||||
cr.execute("update wkf_instance set state='complete' where id=%s", (instance_id,))
|
||||
cr.execute("update wkf_workitem set state='complete' where subflow_id=%s", (instance_id,))
|
||||
cr.execute("select i.id,w.osv,i.res_id from wkf_instance i left join wkf w on (i.wkf_id=w.id) where i.id IN (select inst_id from wkf_workitem where subflow_id=%s)", (instance_id,))
|
||||
for cur_instance_id, cur_model_name, cur_record_id in cr.fetchall():
|
||||
cur_record = Record(cur_model_name, cur_record_id)
|
||||
for act_name in act_names:
|
||||
WorkflowInstance(self.session, cur_record, {'id':cur_instance_id}).validate('subflow.{}'.format(act_name[0]))
|
||||
|
||||
return ok
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def create(session, record, workflow_id):
|
||||
return WorkflowInstance(session, record).create(workflow_id)
|
||||
|
||||
def delete(session, record):
|
||||
return WorkflowInstance(session, record).delete()
|
||||
|
||||
def validate(session, record, instance_id, signal, force_running=False):
|
||||
return WorkflowInstance(session, record).validate(instance_id, signal, force_running)
|
||||
|
||||
def update(session, record, instance_id):
|
||||
return WorkflowInstance(session, record).update(instance_id)
|
||||
|
||||
def _update_end(session, record, instance_id):
|
||||
return WorkflowInstance(session, record)._update_end(instance_id)
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-TODAY OpenERP S.A. (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from helpers import Session
|
||||
from helpers import Record
|
||||
|
||||
from openerp.workflow.instance import WorkflowInstance
|
||||
# import instance
|
||||
|
||||
|
||||
class WorkflowService(object):
|
||||
CACHE = {}
|
||||
|
||||
@classmethod
|
||||
def clear_cache(cls, dbname):
|
||||
cls.CACHE[dbname] = {}
|
||||
|
||||
@classmethod
|
||||
def new(cls, cr, uid, model_name, record_id):
|
||||
return cls(Session(cr, uid), Record(model_name, record_id))
|
||||
|
||||
def __init__(self, session, record):
|
||||
assert isinstance(session, Session)
|
||||
assert isinstance(record, Record)
|
||||
|
||||
self.session = session
|
||||
self.record = record
|
||||
|
||||
self.cr = self.session.cr
|
||||
|
||||
def write(self):
|
||||
self.cr.execute('select id from wkf_instance where res_id=%s and res_type=%s and state=%s',
|
||||
(self.record.id or None, self.record.model or None, 'active')
|
||||
)
|
||||
for (instance_id,) in self.cr.fetchall():
|
||||
WorkflowInstance(self.session, self.record, {'id': instance_id}).update()
|
||||
|
||||
def trigger(self):
|
||||
self.cr.execute('select instance_id from wkf_triggers where res_id=%s and model=%s', (self.record.id, self.record.model))
|
||||
res = self.cr.fetchall()
|
||||
for (instance_id,) in res:
|
||||
self.cr.execute('select %s,res_type,res_id from wkf_instance where id=%s', (self.session.uid, instance_id,))
|
||||
current_uid, current_model_name, current_record_id = self.cr.fetchone()
|
||||
|
||||
current_session = Session(self.session.cr, current_uid)
|
||||
current_record = Record(current_model_name, current_record_id)
|
||||
|
||||
WorkflowInstance(current_session, current_record, {'id': instance_id}).update()
|
||||
|
||||
def delete(self):
|
||||
WorkflowInstance(self.session, self.record, {}).delete()
|
||||
|
||||
def create(self):
|
||||
WorkflowService.CACHE.setdefault(self.cr.dbname, {})
|
||||
|
||||
wkf_ids = WorkflowService.CACHE[self.cr.dbname].get(self.record.model, None)
|
||||
|
||||
if not wkf_ids:
|
||||
self.cr.execute('select id from wkf where osv=%s and on_create=True', (self.record.model,))
|
||||
wkf_ids = self.cr.fetchall()
|
||||
WorkflowService.CACHE[self.cr.dbname][self.record.model] = wkf_ids
|
||||
|
||||
for (wkf_id, ) in wkf_ids:
|
||||
WorkflowInstance.create(self.session, self.record, wkf_id)
|
||||
|
||||
def validate(self, signal):
|
||||
result = False
|
||||
# ids of all active workflow instances for a corresponding resource (id, model_nam)
|
||||
self.cr.execute('select id from wkf_instance where res_id=%s and res_type=%s and state=%s', (self.record.id, self.record.model, 'active'))
|
||||
# TODO: Refactor the workflow instance object
|
||||
for (instance_id,) in self.cr.fetchall():
|
||||
wi = WorkflowInstance(self.session, self.record, {'id': instance_id})
|
||||
|
||||
res2 = wi.validate(signal)
|
||||
|
||||
result = result or res2
|
||||
return result
|
||||
|
||||
def redirect(self, new_rid):
|
||||
# get ids of wkf instances for the old resource (res_id)
|
||||
# CHECKME: shouldn't we get only active instances?
|
||||
self.cr.execute('select id, wkf_id from wkf_instance where res_id=%s and res_type=%s', (self.record.id, self.record.model))
|
||||
|
||||
for old_inst_id, workflow_id in self.cr.fetchall():
|
||||
# first active instance for new resource (new_rid), using same wkf
|
||||
self.cr.execute(
|
||||
'SELECT id '\
|
||||
'FROM wkf_instance '\
|
||||
'WHERE res_id=%s AND res_type=%s AND wkf_id=%s AND state=%s',
|
||||
(new_rid, self.record.model, workflow_id, 'active'))
|
||||
new_id = self.cr.fetchone()
|
||||
if new_id:
|
||||
# select all workitems which "wait" for the old instance
|
||||
self.cr.execute('select id from wkf_workitem where subflow_id=%s', (old_inst_id,))
|
||||
for (item_id,) in self.cr.fetchall():
|
||||
# redirect all those workitems to the wkf instance of the new resource
|
||||
self.cr.execute('update wkf_workitem set subflow_id=%s where id=%s', (new_id[0], item_id))
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""
|
||||
Evaluate workflow code found in activity actions and transition conditions.
|
||||
"""
|
||||
|
||||
import openerp
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
|
||||
class Env(dict):
|
||||
"""
|
||||
Dictionary class used as an environment to evaluate workflow code (such as
|
||||
the condition on transitions).
|
||||
|
||||
This environment provides sybmols for cr, uid, id, model name, model
|
||||
instance, column names, and all the record (the one obtained by browsing
|
||||
the provided ID) attributes.
|
||||
"""
|
||||
def __init__(self, cr, uid, model, id):
|
||||
self.cr = cr
|
||||
self.uid = uid
|
||||
self.model = model
|
||||
self.id = id
|
||||
self.ids = [id]
|
||||
self.obj = openerp.registry(cr.dbname)[model]
|
||||
self.columns = self.obj._columns.keys() + self.obj._inherit_fields.keys()
|
||||
|
||||
def __getitem__(self, key):
|
||||
if (key in self.columns) or (key in dir(self.obj)):
|
||||
res = self.obj.browse(self.cr, self.uid, self.id)
|
||||
return res[key]
|
||||
else:
|
||||
return super(Env, self).__getitem__(key)
|
||||
|
||||
def _eval_expr(cr, ident, workitem, lines):
|
||||
"""
|
||||
Evaluate each line of ``lines`` with the ``Env`` environment, returning
|
||||
the value of the last line.
|
||||
"""
|
||||
assert lines, 'You used a NULL action in a workflow, use dummy node instead.'
|
||||
uid, model, id = ident
|
||||
result = False
|
||||
for line in lines.split('\n'):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
if line == 'True':
|
||||
result = True
|
||||
elif line == 'False':
|
||||
result = False
|
||||
else:
|
||||
env = Env(cr, uid, model, id)
|
||||
result = eval(line, env, nocopy=True)
|
||||
return result
|
||||
|
||||
def execute_action(cr, ident, workitem, activity):
|
||||
"""
|
||||
Evaluate the ir.actions.server action specified in the activity.
|
||||
"""
|
||||
uid, model, id = ident
|
||||
ir_actions_server = openerp.registry(cr.dbname)['ir.actions.server']
|
||||
context = { 'active_model': model, 'active_id': id, 'active_ids': [id] }
|
||||
result = ir_actions_server.run(cr, uid, [activity['action_id']], context)
|
||||
return result
|
||||
|
||||
def execute(cr, ident, workitem, activity):
|
||||
"""
|
||||
Evaluate the action specified in the activity.
|
||||
"""
|
||||
return _eval_expr(cr, ident, workitem, activity['action'])
|
||||
|
||||
def check(cr, workitem, ident, transition, signal):
|
||||
"""
|
||||
Test if a transition can be taken. The transition can be taken if:
|
||||
|
||||
- the signal name matches,
|
||||
- the uid is SUPERUSER_ID or the user groups contains the transition's
|
||||
group,
|
||||
- the condition evaluates to a truish value.
|
||||
"""
|
||||
if transition['signal'] and signal != transition['signal']:
|
||||
return False
|
||||
|
||||
uid = ident[0]
|
||||
if uid != openerp.SUPERUSER_ID and transition['group_id']:
|
||||
registry = openerp.registry(cr.dbname)
|
||||
user_groups = registry['res.users'].read(cr, uid, [uid], ['groups_id'])[0]['groups_id']
|
||||
if transition['group_id'] not in user_groups:
|
||||
return False
|
||||
|
||||
return _eval_expr(cr, ident, workitem, transition['condition'])
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2004-2014 OpenERP S.A. (<http://openerp.com).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -24,174 +24,314 @@
|
|||
# cr.execute('delete from wkf_triggers where model=%s and res_id=%s', (res_type,res_id))
|
||||
#
|
||||
import logging
|
||||
|
||||
import instance
|
||||
|
||||
import wkf_expr
|
||||
from openerp.workflow.helpers import Session
|
||||
from openerp.workflow.helpers import Record
|
||||
from openerp.workflow.helpers import WorkflowActivity
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def create(cr, act_datas, inst_id, ident, stack):
|
||||
for act in act_datas:
|
||||
import openerp
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
|
||||
class Environment(dict):
|
||||
"""
|
||||
Dictionary class used as an environment to evaluate workflow code (such as
|
||||
the condition on transitions).
|
||||
|
||||
This environment provides sybmols for cr, uid, id, model name, model
|
||||
instance, column names, and all the record (the one obtained by browsing
|
||||
the provided ID) attributes.
|
||||
"""
|
||||
def __init__(self, session, record):
|
||||
self.cr = session.cr
|
||||
self.uid = session.uid
|
||||
self.model = record.model
|
||||
self.id = record.id
|
||||
self.ids = [record.id]
|
||||
self.obj = openerp.registry(self.cr.dbname)[self.model]
|
||||
self.columns = self.obj._columns.keys() + self.obj._inherit_fields.keys()
|
||||
|
||||
def __getitem__(self, key):
|
||||
if (key in self.columns) or (key in dir(self.obj)):
|
||||
res = self.obj.browse(self.cr, self.uid, self.id)
|
||||
return res[key]
|
||||
else:
|
||||
return super(Environment, self).__getitem__(key)
|
||||
|
||||
|
||||
class WorkflowItem(object):
|
||||
def __init__(self, session, record, work_item_values):
|
||||
assert isinstance(session, Session)
|
||||
assert isinstance(record, Record)
|
||||
self.session = session
|
||||
self.record = record
|
||||
|
||||
if not work_item_values:
|
||||
work_item_values = {}
|
||||
|
||||
assert isinstance(work_item_values, dict)
|
||||
self.workitem = work_item_values
|
||||
|
||||
@classmethod
|
||||
def create(cls, session, record, activity, instance_id, stack):
|
||||
assert isinstance(session, Session)
|
||||
assert isinstance(record, Record)
|
||||
assert isinstance(activity, dict)
|
||||
assert isinstance(instance_id, (long, int))
|
||||
assert isinstance(stack, list)
|
||||
|
||||
cr = session.cr
|
||||
cr.execute("select nextval('wkf_workitem_id_seq')")
|
||||
id_new = cr.fetchone()[0]
|
||||
cr.execute("insert into wkf_workitem (id,act_id,inst_id,state) values (%s,%s,%s,'active')", (id_new, act['id'], inst_id))
|
||||
cr.execute("insert into wkf_workitem (id,act_id,inst_id,state) values (%s,%s,%s,'active')", (id_new, activity['id'], instance_id))
|
||||
cr.execute('select * from wkf_workitem where id=%s',(id_new,))
|
||||
res = cr.dictfetchone()
|
||||
work_item_values = cr.dictfetchone()
|
||||
logger.info('Created workflow item in activity %s',
|
||||
act['id'], extra={'ident': ident})
|
||||
process(cr, res, ident, stack=stack)
|
||||
activity['id'],
|
||||
extra={'ident': (session.uid, record.model, record.id)})
|
||||
|
||||
def process(cr, workitem, ident, signal=None, force_running=False, stack=None):
|
||||
assert stack is not None
|
||||
workflow_item = WorkflowItem(session, record, work_item_values)
|
||||
workflow_item.process(stack=stack)
|
||||
|
||||
cr.execute('select * from wkf_activity where id=%s', (workitem['act_id'],))
|
||||
activity = cr.dictfetchone()
|
||||
@classmethod
|
||||
def create_all(cls, session, record, activities, instance_id, stack):
|
||||
assert isinstance(activities, list)
|
||||
|
||||
triggers = False
|
||||
if workitem['state'] == 'active':
|
||||
triggers = True
|
||||
if not _execute(cr, workitem, activity, ident, stack):
|
||||
for activity in activities:
|
||||
cls.create(session, record, activity, instance_id, stack)
|
||||
|
||||
def process(self, signal=None, force_running=False, stack=None):
|
||||
assert isinstance(force_running, bool)
|
||||
assert stack is not None
|
||||
|
||||
cr = self.session.cr
|
||||
|
||||
cr.execute('select * from wkf_activity where id=%s', (self.workitem['act_id'],))
|
||||
activity = cr.dictfetchone()
|
||||
|
||||
triggers = False
|
||||
if self.workitem['state'] == 'active':
|
||||
triggers = True
|
||||
if not self._execute(activity, stack):
|
||||
return False
|
||||
|
||||
if force_running or self.workitem['state'] == 'complete':
|
||||
ok = self._split_test(activity['split_mode'], signal, stack)
|
||||
triggers = triggers and not ok
|
||||
|
||||
if triggers:
|
||||
cr.execute('select * from wkf_transition where act_from=%s', (self.workitem['act_id'],))
|
||||
for trans in cr.dictfetchall():
|
||||
if trans['trigger_model']:
|
||||
ids = self.wkf_expr_eval_expr(trans['trigger_expr_id'])
|
||||
for res_id in ids:
|
||||
cr.execute('select nextval(\'wkf_triggers_id_seq\')')
|
||||
id =cr.fetchone()[0]
|
||||
cr.execute('insert into wkf_triggers (model,res_id,instance_id,workitem_id,id) values (%s,%s,%s,%s,%s)', (trans['trigger_model'],res_id, self.workitem['inst_id'], self.workitem['id'], id))
|
||||
|
||||
return True
|
||||
|
||||
def _execute(self, activity, stack):
|
||||
"""Send a signal to parenrt workflow (signal: subflow.signal_name)"""
|
||||
result = True
|
||||
cr = self.session.cr
|
||||
signal_todo = []
|
||||
|
||||
if (self.workitem['state']=='active') and activity['signal_send']:
|
||||
# signal_send']:
|
||||
cr.execute("select i.id,w.osv,i.res_id from wkf_instance i left join wkf w on (i.wkf_id=w.id) where i.id IN (select inst_id from wkf_workitem where subflow_id=%s)", (self.workitem['inst_id'],))
|
||||
for instance_id, model_name, record_id in cr.fetchall():
|
||||
record = Record(model_name, record_id)
|
||||
signal_todo.append((instance_id, record, activity['signal_send']))
|
||||
|
||||
|
||||
if activity['kind'] == WorkflowActivity.KIND_DUMMY:
|
||||
if self.workitem['state']=='active':
|
||||
self._state_set(activity, 'complete')
|
||||
if activity['action_id']:
|
||||
res2 = self.wkf_expr_execute_action(activity)
|
||||
if res2:
|
||||
stack.append(res2)
|
||||
result=res2
|
||||
|
||||
elif activity['kind'] == WorkflowActivity.KIND_FUNCTION:
|
||||
|
||||
if self.workitem['state']=='active':
|
||||
self._state_set(activity, 'running')
|
||||
returned_action = self.wkf_expr_execute(activity)
|
||||
if type(returned_action) in (dict,):
|
||||
stack.append(returned_action)
|
||||
if activity['action_id']:
|
||||
res2 = self.wkf_expr_execute_action(activity)
|
||||
# A client action has been returned
|
||||
if res2:
|
||||
stack.append(res2)
|
||||
result=res2
|
||||
self._state_set(activity, 'complete')
|
||||
|
||||
elif activity['kind'] == WorkflowActivity.KIND_STOPALL:
|
||||
if self.workitem['state']=='active':
|
||||
self._state_set(activity, 'running')
|
||||
cr.execute('delete from wkf_workitem where inst_id=%s and id<>%s', (self.workitem['inst_id'], self.workitem['id']))
|
||||
if activity['action']:
|
||||
self.wkf_expr_execute(activity)
|
||||
self._state_set(activity, 'complete')
|
||||
|
||||
elif activity['kind'] == WorkflowActivity.KIND_SUBFLOW:
|
||||
|
||||
if self.workitem['state']=='active':
|
||||
|
||||
self._state_set(activity, 'running')
|
||||
if activity.get('action', False):
|
||||
id_new = self.wkf_expr_execute(activity)
|
||||
if not id_new:
|
||||
cr.execute('delete from wkf_workitem where id=%s', (self.workitem['id'],))
|
||||
return False
|
||||
assert type(id_new)==type(1) or type(id_new)==type(1L), 'Wrong return value: '+str(id_new)+' '+str(type(id_new))
|
||||
cr.execute('select id from wkf_instance where res_id=%s and wkf_id=%s', (id_new, activity['subflow_id']))
|
||||
id_new = cr.fetchone()[0]
|
||||
else:
|
||||
inst = instance.WorkflowInstance(self.session, self.record)
|
||||
id_new = inst.create(activity['subflow_id'])
|
||||
|
||||
cr.execute('update wkf_workitem set subflow_id=%s where id=%s', (id_new, self.workitem['id']))
|
||||
self.workitem['subflow_id'] = id_new
|
||||
|
||||
if self.workitem['state']=='running':
|
||||
cr.execute("select state from wkf_instance where id=%s", (self.workitem['subflow_id'],))
|
||||
state = cr.fetchone()[0]
|
||||
if state=='complete':
|
||||
self._state_set(activity, 'complete')
|
||||
|
||||
for instance_id, record, signal_send in signal_todo:
|
||||
wi = instance.WorkflowInstance(self.session, record, {'id': instance_id})
|
||||
wi.validate(signal_send, force_running=True)
|
||||
|
||||
return result
|
||||
|
||||
def _state_set(self, activity, state):
|
||||
self.session.cr.execute('update wkf_workitem set state=%s where id=%s', (state, self.workitem['id']))
|
||||
self.workitem['state'] = state
|
||||
logger.info('Changed state of work item %s to "%s" in activity %s',
|
||||
self.workitem['id'], state, activity['id'],
|
||||
extra={'ident': (self.session.uid, self.record.model, self.record.id)})
|
||||
|
||||
def _split_test(self, split_mode, signal, stack):
|
||||
cr = self.session.cr
|
||||
cr.execute('select * from wkf_transition where act_from=%s', (self.workitem['act_id'],))
|
||||
test = False
|
||||
transitions = []
|
||||
alltrans = cr.dictfetchall()
|
||||
|
||||
if split_mode in ('XOR', 'OR'):
|
||||
for transition in alltrans:
|
||||
if self.wkf_expr_check(transition,signal):
|
||||
test = True
|
||||
transitions.append((transition['id'], self.workitem['inst_id']))
|
||||
if split_mode=='XOR':
|
||||
break
|
||||
else:
|
||||
test = True
|
||||
for transition in alltrans:
|
||||
if not self.wkf_expr_check(transition, signal):
|
||||
test = False
|
||||
break
|
||||
cr.execute('select count(*) from wkf_witm_trans where trans_id=%s and inst_id=%s', (transition['id'], self.workitem['inst_id']))
|
||||
if not cr.fetchone()[0]:
|
||||
transitions.append((transition['id'], self.workitem['inst_id']))
|
||||
|
||||
if test and transitions:
|
||||
cr.executemany('insert into wkf_witm_trans (trans_id,inst_id) values (%s,%s)', transitions)
|
||||
cr.execute('delete from wkf_workitem where id=%s', (self.workitem['id'],))
|
||||
for t in transitions:
|
||||
self._join_test(t[0], t[1], stack)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _join_test(self, trans_id, inst_id, stack):
|
||||
cr = self.session.cr
|
||||
cr.execute('select * from wkf_activity where id=(select act_to from wkf_transition where id=%s)', (trans_id,))
|
||||
activity = cr.dictfetchone()
|
||||
if activity['join_mode']=='XOR':
|
||||
WorkflowItem.create(self.session, self.record, activity, inst_id, stack=stack)
|
||||
cr.execute('delete from wkf_witm_trans where inst_id=%s and trans_id=%s', (inst_id,trans_id))
|
||||
else:
|
||||
cr.execute('select id from wkf_transition where act_to=%s', (activity['id'],))
|
||||
trans_ids = cr.fetchall()
|
||||
ok = True
|
||||
for (id,) in trans_ids:
|
||||
cr.execute('select count(*) from wkf_witm_trans where trans_id=%s and inst_id=%s', (id,inst_id))
|
||||
res = cr.fetchone()[0]
|
||||
if not res:
|
||||
ok = False
|
||||
break
|
||||
if ok:
|
||||
for (id,) in trans_ids:
|
||||
cr.execute('delete from wkf_witm_trans where trans_id=%s and inst_id=%s', (id,inst_id))
|
||||
WorkflowItem.create(self.session, self.record, activity, inst_id, stack=stack)
|
||||
|
||||
def wkf_expr_eval_expr(self, lines):
|
||||
"""
|
||||
Evaluate each line of ``lines`` with the ``Environment`` environment, returning
|
||||
the value of the last line.
|
||||
"""
|
||||
assert lines, 'You used a NULL action in a workflow, use dummy node instead.'
|
||||
result = False
|
||||
for line in lines.split('\n'):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
if line == 'True':
|
||||
result = True
|
||||
elif line == 'False':
|
||||
result = False
|
||||
else:
|
||||
env = Environment(self.session, self.record)
|
||||
result = eval(line, env, nocopy=True)
|
||||
return result
|
||||
|
||||
def wkf_expr_execute_action(self, activity):
|
||||
"""
|
||||
Evaluate the ir.actions.server action specified in the activity.
|
||||
"""
|
||||
context = {
|
||||
'active_model': self.record.model,
|
||||
'active_id': self.record.id,
|
||||
'active_ids': [self.record.id]
|
||||
}
|
||||
|
||||
ir_actions_server = openerp.registry(self.session.cr.dbname)['ir.actions.server']
|
||||
result = ir_actions_server.run(self.session.cr, self.session.uid, [activity['action_id']], context)
|
||||
|
||||
return result
|
||||
|
||||
def wkf_expr_execute(self, activity):
|
||||
"""
|
||||
Evaluate the action specified in the activity.
|
||||
"""
|
||||
return self.wkf_expr_eval_expr(activity['action'])
|
||||
|
||||
def wkf_expr_check(self, transition, signal):
|
||||
"""
|
||||
Test if a transition can be taken. The transition can be taken if:
|
||||
|
||||
- the signal name matches,
|
||||
- the uid is SUPERUSER_ID or the user groups contains the transition's
|
||||
group,
|
||||
- the condition evaluates to a truish value.
|
||||
"""
|
||||
if transition['signal'] and signal != transition['signal']:
|
||||
return False
|
||||
|
||||
if force_running or workitem['state'] == 'complete':
|
||||
ok = _split_test(cr, workitem, activity['split_mode'], ident, signal, stack)
|
||||
triggers = triggers and not ok
|
||||
if self.session.uid != openerp.SUPERUSER_ID and transition['group_id']:
|
||||
registry = openerp.registry(self.session.cr.dbname)
|
||||
user_groups = registry['res.users'].read(self.session.cr, self.session.uid, [self.session.uid], ['groups_id'])[0]['groups_id']
|
||||
if transition['group_id'] not in user_groups:
|
||||
return False
|
||||
|
||||
if triggers:
|
||||
cr.execute('select * from wkf_transition where act_from=%s', (workitem['act_id'],))
|
||||
for trans in cr.dictfetchall():
|
||||
if trans['trigger_model']:
|
||||
ids = wkf_expr._eval_expr(cr,ident,workitem,trans['trigger_expr_id'])
|
||||
for res_id in ids:
|
||||
cr.execute('select nextval(\'wkf_triggers_id_seq\')')
|
||||
id =cr.fetchone()[0]
|
||||
cr.execute('insert into wkf_triggers (model,res_id,instance_id,workitem_id,id) values (%s,%s,%s,%s,%s)', (trans['trigger_model'],res_id,workitem['inst_id'], workitem['id'], id))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# ---------------------- PRIVATE FUNCS --------------------------------
|
||||
|
||||
def _state_set(cr, workitem, activity, state, ident):
|
||||
cr.execute('update wkf_workitem set state=%s where id=%s', (state,workitem['id']))
|
||||
workitem['state'] = state
|
||||
logger.info("Changed state of work item %s to \"%s\" in activity %s",
|
||||
workitem['id'], state, activity['id'], extra={'ident': ident})
|
||||
|
||||
def _execute(cr, workitem, activity, ident, stack):
|
||||
result = True
|
||||
#
|
||||
# send a signal to parent workflow (signal: subflow.signal_name)
|
||||
#
|
||||
signal_todo = []
|
||||
if (workitem['state']=='active') and activity['signal_send']:
|
||||
cr.execute("select i.id,w.osv,i.res_id from wkf_instance i left join wkf w on (i.wkf_id=w.id) where i.id IN (select inst_id from wkf_workitem where subflow_id=%s)", (workitem['inst_id'],))
|
||||
for i in cr.fetchall():
|
||||
signal_todo.append((i[0], (ident[0],i[1],i[2]), activity['signal_send']))
|
||||
|
||||
if activity['kind']=='dummy':
|
||||
if workitem['state']=='active':
|
||||
_state_set(cr, workitem, activity, 'complete', ident)
|
||||
if activity['action_id']:
|
||||
res2 = wkf_expr.execute_action(cr, ident, workitem, activity)
|
||||
if res2:
|
||||
stack.append(res2)
|
||||
result=res2
|
||||
elif activity['kind']=='function':
|
||||
if workitem['state']=='active':
|
||||
_state_set(cr, workitem, activity, 'running', ident)
|
||||
returned_action = wkf_expr.execute(cr, ident, workitem, activity)
|
||||
if type(returned_action) in (dict,):
|
||||
stack.append(returned_action)
|
||||
if activity['action_id']:
|
||||
res2 = wkf_expr.execute_action(cr, ident, workitem, activity)
|
||||
# A client action has been returned
|
||||
if res2:
|
||||
stack.append(res2)
|
||||
result=res2
|
||||
_state_set(cr, workitem, activity, 'complete', ident)
|
||||
elif activity['kind']=='stopall':
|
||||
if workitem['state']=='active':
|
||||
_state_set(cr, workitem, activity, 'running', ident)
|
||||
cr.execute('delete from wkf_workitem where inst_id=%s and id<>%s', (workitem['inst_id'], workitem['id']))
|
||||
if activity['action']:
|
||||
wkf_expr.execute(cr, ident, workitem, activity)
|
||||
_state_set(cr, workitem, activity, 'complete', ident)
|
||||
elif activity['kind']=='subflow':
|
||||
if workitem['state']=='active':
|
||||
_state_set(cr, workitem, activity, 'running', ident)
|
||||
if activity.get('action', False):
|
||||
id_new = wkf_expr.execute(cr, ident, workitem, activity)
|
||||
if not id_new:
|
||||
cr.execute('delete from wkf_workitem where id=%s', (workitem['id'],))
|
||||
return False
|
||||
assert type(id_new)==type(1) or type(id_new)==type(1L), 'Wrong return value: '+str(id_new)+' '+str(type(id_new))
|
||||
cr.execute('select id from wkf_instance where res_id=%s and wkf_id=%s', (id_new,activity['subflow_id']))
|
||||
id_new = cr.fetchone()[0]
|
||||
else:
|
||||
id_new = instance.create(cr, ident, activity['subflow_id'])
|
||||
cr.execute('update wkf_workitem set subflow_id=%s where id=%s', (id_new, workitem['id']))
|
||||
workitem['subflow_id'] = id_new
|
||||
if workitem['state']=='running':
|
||||
cr.execute("select state from wkf_instance where id=%s", (workitem['subflow_id'],))
|
||||
state= cr.fetchone()[0]
|
||||
if state=='complete':
|
||||
_state_set(cr, workitem, activity, 'complete', ident)
|
||||
for t in signal_todo:
|
||||
instance.validate(cr, t[0], t[1], t[2], force_running=True)
|
||||
|
||||
return result
|
||||
|
||||
def _split_test(cr, workitem, split_mode, ident, signal=None, stack=None):
|
||||
cr.execute('select * from wkf_transition where act_from=%s', (workitem['act_id'],))
|
||||
test = False
|
||||
transitions = []
|
||||
alltrans = cr.dictfetchall()
|
||||
if split_mode=='XOR' or split_mode=='OR':
|
||||
for transition in alltrans:
|
||||
if wkf_expr.check(cr, workitem, ident, transition,signal):
|
||||
test = True
|
||||
transitions.append((transition['id'], workitem['inst_id']))
|
||||
if split_mode=='XOR':
|
||||
break
|
||||
else:
|
||||
test = True
|
||||
for transition in alltrans:
|
||||
if not wkf_expr.check(cr, workitem, ident, transition,signal):
|
||||
test = False
|
||||
break
|
||||
cr.execute('select count(*) from wkf_witm_trans where trans_id=%s and inst_id=%s', (transition['id'], workitem['inst_id']))
|
||||
if not cr.fetchone()[0]:
|
||||
transitions.append((transition['id'], workitem['inst_id']))
|
||||
if test and len(transitions):
|
||||
cr.executemany('insert into wkf_witm_trans (trans_id,inst_id) values (%s,%s)', transitions)
|
||||
cr.execute('delete from wkf_workitem where id=%s', (workitem['id'],))
|
||||
for t in transitions:
|
||||
_join_test(cr, t[0], t[1], ident, stack)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _join_test(cr, trans_id, inst_id, ident, stack):
|
||||
cr.execute('select * from wkf_activity where id=(select act_to from wkf_transition where id=%s)', (trans_id,))
|
||||
activity = cr.dictfetchone()
|
||||
if activity['join_mode']=='XOR':
|
||||
create(cr,[activity], inst_id, ident, stack)
|
||||
cr.execute('delete from wkf_witm_trans where inst_id=%s and trans_id=%s', (inst_id,trans_id))
|
||||
else:
|
||||
cr.execute('select id from wkf_transition where act_to=%s', (activity['id'],))
|
||||
trans_ids = cr.fetchall()
|
||||
ok = True
|
||||
for (id,) in trans_ids:
|
||||
cr.execute('select count(*) from wkf_witm_trans where trans_id=%s and inst_id=%s', (id,inst_id))
|
||||
res = cr.fetchone()[0]
|
||||
if not res:
|
||||
ok = False
|
||||
break
|
||||
if ok:
|
||||
for (id,) in trans_ids:
|
||||
cr.execute('delete from wkf_witm_trans where trans_id=%s and inst_id=%s', (id,inst_id))
|
||||
create(cr, [activity], inst_id, ident, stack)
|
||||
return self.wkf_expr_eval_expr(transition['condition'])
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#!/usr/bin/env bash
|
||||
DATABASE=trunk
|
||||
dropdb ${DATABASE}
|
||||
REPOSITORIES=../../addons/trunk
|
||||
|
|
3
setup.py
|
@ -130,12 +130,15 @@ setuptools.setup(
|
|||
'psycopg2 >= 2.2',
|
||||
'pydot',
|
||||
'pyparsing < 2',
|
||||
'pyserial',
|
||||
'python-dateutil < 2',
|
||||
'python-ldap', # optional
|
||||
'python-openid',
|
||||
'pytz',
|
||||
'pyusb >= 1.0.0b1',
|
||||
'pywebdav',
|
||||
'pyyaml',
|
||||
'qrcode',
|
||||
'reportlab', # windows binary pypi.python.org/pypi/reportlab
|
||||
'simplejson',
|
||||
'unittest2',
|
||||
|
|