diff --git a/doc/changelog.rst b/doc/changelog.rst index 184a783e3c0..84a3cdcb12e 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -6,6 +6,8 @@ Changelog `trunk` ------- +- Added :ref:`orm-workflows` to the ORM. +- Added :ref:`routing-decorators` to the RPC and WSGI stack. - Removed support for `__terp__.py` descriptor files. - Removed support for `` root element in XML files. - Removed support for the non-openerp namespace (e.g. importing `tools` instead diff --git a/doc/index.rst b/doc/index.rst index 83b3837f73f..5dbea31f02e 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -35,6 +35,7 @@ OpenERP Server API .. toctree:: :maxdepth: 1 + orm-methods.rst api_models.rst routing.rst diff --git a/doc/orm-methods.rst b/doc/orm-methods.rst new file mode 100644 index 00000000000..fb8c461761e --- /dev/null +++ b/doc/orm-methods.rst @@ -0,0 +1,61 @@ +.. _orm-methods: + +ORM methods +=========== + +.. _orm-workflows: + +Workflow-related methods +------------------------ + +.. versionadded:: 7.1 + +Creating, deleting, or otherwise manipulating workflow instances is possible +right from a Model instance. (Previously, workflows were handled throught a +call to ``LocalService('workflow')``. Using the ORM methods is now the preferred +way.) + +.. currentmodule:: openerp.osv.orm + +.. automethod:: BaseModel.create_workflow + :noindex: + + This is used instead of ``LocalService('workflow').trg_create()``. + +.. automethod:: BaseModel.delete_workflow + :noindex: + + This is used instead of ``LocalService('workflow').trg_delete()``. + +.. automethod:: BaseModel.step_workflow + :noindex: + + This is used instead of ``LocalService('workflow').trg_write()``. + +.. automethod:: BaseModel.redirect_workflow + :noindex: + +.. automethod:: BaseModel.signal_workflow + :noindex: + + This is used instead of ``LocalService('workflow').trg_validate()``. + +.. method:: BaseModel.signal_xxx(cr, uid, ids) + :noindex: + + Sends a signal ``xxx`` to the workflow instances bound to the given record + IDs. (This is implemented using ``__getattr__`` so no source link is + rendered on the right.) + + This is used instead of ``LocalService('workflow').trg_validate()``. + + +.. note:: + Low-level access to the workflows is still possible by using the + ``openerp.workflow`` module, that is, in a similar way to what was possible + with the previous ``LocalService('workflow')`` access. This may be useful + when looking-up a model in the registry and/or its records is not necessary. + For instance when working with raw model names and record IDs is preferred (to + avoid hitting unnecessarily the database). But this is something that should be + carefully considered as it would bypass the ORM methods (and thus any inherited + behaviors). diff --git a/doc/routing.rst b/doc/routing.rst index 6a4af3374a9..76ef859c48b 100644 --- a/doc/routing.rst +++ b/doc/routing.rst @@ -19,6 +19,8 @@ Starting with OpenERP 7.1, exposing a new arbitrary WSGI handler is done with the :py:func:`openerp.http.handler` decorator while adding an RPC endpoint is done with the :py:func:`openerp.http.rpc` decorator. +.. _routing-decorators: + Routing decorators ------------------ diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 836456136ce..cbc56bf834d 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -3911,20 +3911,44 @@ class BaseModel(object): returned_ids = [x['id'] for x in cr.dictfetchall()] self._check_record_rules_result_count(cr, uid, sub_ids, returned_ids, operation, context=context) - def _workflow_trigger(self, cr, uid, ids, trigger, context=None): - """Call given workflow trigger as a result of a CRUD operation""" - wf_service = netsvc.LocalService("workflow") + def create_workflow(self, cr, uid, ids, context=None): + """Create a workflow instance for each given record IDs.""" + from openerp import workflow for res_id in ids: - getattr(wf_service, trigger)(uid, self._name, res_id, cr) + workflow.trg_create(uid, self._name, res_id, cr) + return True - def _workflow_signal(self, cr, uid, ids, signal, context=None): + def delete_workflow(self, cr, uid, ids, context=None): + """Delete the workflow instances bound to the given record IDs.""" + from openerp import workflow + for res_id in ids: + workflow.trg_delete(uid, self._name, res_id, cr) + return True + + def step_workflow(self, cr, uid, ids, context=None): + """Reevaluate the workflow instances of the given record IDs.""" + from openerp import workflow + for res_id in ids: + workflow.trg_write(uid, self._name, res_id, cr) + return True + + def signal_workflow(self, cr, uid, ids, signal, context=None): """Send given workflow signal and return a dict mapping ids to workflow results""" - wf_service = netsvc.LocalService("workflow") + from openerp import workflow result = {} for res_id in ids: - result[res_id] = wf_service.trg_validate(uid, self._name, res_id, signal, cr) + result[res_id] = workflow.trg_validate(uid, self._name, res_id, signal, cr) return result + def redirect_workflow(self, cr, uid, old_new_ids, context=None): + """ Rebind the workflow instance bound to the given 'old' record IDs to + the given 'new' IDs. (``old_new_ids`` is a list of pairs ``(old, new)``. + """ + from openerp import workflow + for old_id, new_id in old_new_ids: + workflow.trg_redirect(uid, self._name, old_id, new_id, cr) + return True + def unlink(self, cr, uid, ids, context=None): """ Delete records with given ids @@ -3963,7 +3987,7 @@ class BaseModel(object): property_ids = ir_property.search(cr, uid, [('res_id', 'in', ['%s,%s' % (self._name, i) for i in ids])], context=context) ir_property.unlink(cr, uid, property_ids, context=context) - self._workflow_trigger(cr, uid, ids, 'trg_delete', context=context) + self.delete_workflow(cr, uid, ids, context=context) self.check_access_rule(cr, uid, ids, 'unlink', context=context) pool_model_data = self.pool.get('ir.model.data') @@ -4268,7 +4292,7 @@ class BaseModel(object): todo.append(id) self.pool.get(object)._store_set_values(cr, user, todo, fields_to_recompute, context) - self._workflow_trigger(cr, user, ids, 'trg_write', context=context) + self.step_workflow(cr, user, ids, context=context) return True # @@ -4484,7 +4508,7 @@ class BaseModel(object): self.name_get(cr, user, [id_new], context=context)[0][1] + \ "' " + _("created.") self.log(cr, user, id_new, message, True, context=context) - self._workflow_trigger(cr, user, [id_new], 'trg_create', context=context) + self.create_workflow(cr, user, [id_new], context=context) return id_new def browse(self, cr, uid, select, context=None, list_class=None, fields_process=None): @@ -5248,6 +5272,14 @@ class BaseModel(object): """ stuff to do right after the registry is built """ pass + def __getattr__(self, name): + if name.startswith('signal_'): + signal_name = name[len('signal_'):] + assert signal_name + return (lambda *args, **kwargs: + self.signal_workflow(*args, signal=signal_name, **kwargs)) + return super(BaseModel, self).__getattr__(name) + # keep this import here, at top it will cause dependency cycle errors import expression diff --git a/openerp/service/model.py b/openerp/service/model.py index a78f5e94018..ac37264ae2f 100644 --- a/openerp/service/model.py +++ b/openerp/service/model.py @@ -166,7 +166,7 @@ def exec_workflow_cr(cr, uid, obj, signal, *args): if not object: raise except_orm('Object Error', 'Object %s doesn\'t exist' % str(obj)) res_id = args[0] - return object._workflow_signal(cr, uid, [res_id], signal)[res_id] + return object.signal_workflow(cr, uid, [res_id], signal)[res_id] @check def exec_workflow(db, uid, obj, signal, *args): diff --git a/openerp/service/report.py b/openerp/service/report.py index 520faa8ead5..c20c7f0d7ec 100644 --- a/openerp/service/report.py +++ b/openerp/service/report.py @@ -1,10 +1,15 @@ # -*- coding: utf-8 -*- +import base64 import logging +import sys import threading import openerp.netsvc import openerp.pooler +from openerp import tools + +import security _logger = logging.getLogger(__name__) @@ -105,7 +110,7 @@ def exp_report(db, uid, object, ids, datas=None, context=None): cr.close() return True - thread.start_new_thread(go, (id, uid, ids, datas, context)) + threading.Thread(target=go, args=(id, uid, ids, datas, context)).start() return id def _check_report(report_id):