From 19b0315f68d2630b17398439e81b9f8d66edc8f2 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 30 Jan 2013 12:36:46 +0100 Subject: [PATCH 01/34] [REF] model service: removed the unneeded object_service class. bzr revid: vmt@openerp.com-20130130113646-pviw5d6gnohk7wrb --- openerp/netsvc.py | 2 +- openerp/osv/osv.py | 185 +------------------------------- openerp/service/__init__.py | 2 - openerp/service/model.py | 172 +++++++++++++++++++++++++++++ openerp/service/web_services.py | 5 +- openerp/tests/__init__.py | 1 + 6 files changed, 178 insertions(+), 189 deletions(-) create mode 100644 openerp/service/model.py diff --git a/openerp/netsvc.py b/openerp/netsvc.py index 533b1659191..820df90e35d 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -94,7 +94,7 @@ def LocalService(name): # Special case for addons support, will be removed in a few days when addons # are updated to directly use openerp.osv.osv.service. if name == 'object_proxy': - return openerp.osv.osv.service + return openerp.service.model return Service._services[name] diff --git a/openerp/osv/osv.py b/openerp/osv/osv.py index 018976ac663..31919cc9cf6 100644 --- a/openerp/osv/osv.py +++ b/openerp/osv/osv.py @@ -21,22 +21,7 @@ #.apidoc title: Objects Services (OSV) -from functools import wraps -import logging -import threading - -from psycopg2 import IntegrityError, errorcodes - -import orm -import openerp -import openerp.netsvc as netsvc -import openerp.pooler as pooler -import openerp.sql_db as sql_db -from openerp.tools.translate import translate -from openerp.osv.orm import MetaModel, Model, TransientModel, AbstractModel -import openerp.exceptions - -_logger = logging.getLogger(__name__) +from openerp.osv.orm import Model, TransientModel, AbstractModel # Deprecated. class except_osv(Exception): @@ -45,177 +30,9 @@ class except_osv(Exception): self.value = value self.args = (name, value) -service = None - -class object_proxy(object): - def __init__(self): - global service - service = self - - def check(f): - @wraps(f) - def wrapper(self, dbname, *args, **kwargs): - """ Wraps around OSV functions and normalises a few exceptions - """ - - def tr(src, ttype): - # We try to do the same as the _(), but without the frame - # inspection, since we aready are wrapping an osv function - # trans_obj = self.get('ir.translation') cannot work yet :( - ctx = {} - if not kwargs: - if args and isinstance(args[-1], dict): - ctx = args[-1] - elif isinstance(kwargs, dict): - ctx = kwargs.get('context', {}) - - uid = 1 - if args and isinstance(args[0], (long, int)): - uid = args[0] - - lang = ctx and ctx.get('lang') - if not (lang or hasattr(src, '__call__')): - return src - - # We open a *new* cursor here, one reason is that failed SQL - # queries (as in IntegrityError) will invalidate the current one. - cr = False - - if hasattr(src, '__call__'): - # callable. We need to find the right parameters to call - # the orm._sql_message(self, cr, uid, ids, context) function, - # or we skip.. - # our signature is f(osv_pool, dbname [,uid, obj, method, args]) - try: - if args and len(args) > 1: - obj = self.get(args[1]) - if len(args) > 3 and isinstance(args[3], (long, int, list)): - ids = args[3] - else: - ids = [] - cr = sql_db.db_connect(dbname).cursor() - return src(obj, cr, uid, ids, context=(ctx or {})) - except Exception: - pass - finally: - if cr: cr.close() - - return False # so that the original SQL error will - # be returned, it is the best we have. - - try: - cr = sql_db.db_connect(dbname).cursor() - res = translate(cr, name=False, source_type=ttype, - lang=lang, source=src) - if res: - return res - else: - return src - finally: - if cr: cr.close() - - def _(src): - return tr(src, 'code') - - try: - if pooler.get_pool(dbname)._init: - raise except_osv('Database not ready', 'Currently, this database is not fully loaded and can not be used.') - return f(self, dbname, *args, **kwargs) - except orm.except_orm, inst: - raise except_osv(inst.name, inst.value) - except except_osv: - raise - except IntegrityError, inst: - osv_pool = pooler.get_pool(dbname) - for key in osv_pool._sql_error.keys(): - if key in inst[0]: - netsvc.abort_response(1, _('Constraint Error'), 'warning', - tr(osv_pool._sql_error[key], 'sql_constraint') or inst[0]) - if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION): - msg = _('The operation cannot be completed, probably due to the following:\n- deletion: you may be trying to delete a record while other records still reference it\n- creation/update: a mandatory field is not correctly set') - _logger.debug("IntegrityError", exc_info=True) - try: - errortxt = inst.pgerror.replace('«','"').replace('»','"') - if '"public".' in errortxt: - context = errortxt.split('"public".')[1] - model_name = table = context.split('"')[1] - else: - last_quote_end = errortxt.rfind('"') - last_quote_begin = errortxt.rfind('"', 0, last_quote_end) - model_name = table = errortxt[last_quote_begin+1:last_quote_end].strip() - model = table.replace("_",".") - model_obj = osv_pool.get(model) - if model_obj: - model_name = model_obj._description or model_obj._name - msg += _('\n\n[object with reference: %s - %s]') % (model_name, model) - except Exception: - pass - netsvc.abort_response(1, _('Integrity Error'), 'warning', msg) - else: - netsvc.abort_response(1, _('Integrity Error'), 'warning', inst[0]) - except Exception: - _logger.exception("Uncaught exception") - raise - - return wrapper - - def execute_cr(self, cr, uid, obj, method, *args, **kw): - object = pooler.get_pool(cr.dbname).get(obj) - if not object: - raise except_osv('Object Error', 'Object %s doesn\'t exist' % str(obj)) - return getattr(object, method)(cr, uid, *args, **kw) - - def execute_kw(self, db, uid, obj, method, args, kw=None): - return self.execute(db, uid, obj, method, *args, **kw or {}) - - @check - def execute(self, db, uid, obj, method, *args, **kw): - threading.currentThread().dbname = db - cr = pooler.get_db(db).cursor() - try: - try: - if method.startswith('_'): - raise except_osv('Access Denied', 'Private methods (such as %s) cannot be called remotely.' % (method,)) - res = self.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 - - def exec_workflow_cr(self, cr, uid, obj, signal, *args): - object = pooler.get_pool(cr.dbname).get(obj) - if not object: - raise except_osv('Object Error', 'Object %s doesn\'t exist' % str(obj)) - res_id = args[0] - return object._workflow_signal(cr, uid, [res_id], signal)[res_id] - - @check - def exec_workflow(self, db, uid, obj, signal, *args): - cr = pooler.get_db(db).cursor() - try: - try: - res = self.exec_workflow_cr(cr, uid, obj, signal, *args) - cr.commit() - except Exception: - cr.rollback() - raise - finally: - cr.close() - return res - # deprecated - for backward compatibility. osv = Model osv_memory = TransientModel osv_abstract = AbstractModel # ;-) - -def start_object_proxy(): - object_proxy() - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/openerp/service/__init__.py b/openerp/service/__init__.py index c95e35d8252..5e83ddd6e9c 100644 --- a/openerp/service/__init__.py +++ b/openerp/service/__init__.py @@ -75,8 +75,6 @@ def start_internal(): openerp.netsvc.init_logger() openerp.modules.loading.open_openerp_namespace() - # Instantiate local services (this is a legacy design). - openerp.osv.osv.start_object_proxy() # Export (for RPC) services. web_services.start_service() diff --git a/openerp/service/model.py b/openerp/service/model.py new file mode 100644 index 00000000000..204cad0c8f1 --- /dev/null +++ b/openerp/service/model.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- + +from functools import wraps +import logging +from psycopg2 import IntegrityError, errorcodes +import threading + +import openerp +from openerp.tools.translate import translate +from openerp.osv.orm import except_orm +from openerp.osv.osv import except_osv + +_logger = logging.getLogger(__name__) + +def check(f): + @wraps(f) + def wrapper(dbname, *args, **kwargs): + """ Wraps around OSV functions and normalises a few exceptions + """ + + def tr(src, ttype): + # We try to do the same as the _(), but without the frame + # inspection, since we aready are wrapping an osv function + # trans_obj = self.get('ir.translation') cannot work yet :( + ctx = {} + if not kwargs: + if args and isinstance(args[-1], dict): + ctx = args[-1] + elif isinstance(kwargs, dict): + ctx = kwargs.get('context', {}) + + uid = 1 + if args and isinstance(args[0], (long, int)): + uid = args[0] + + lang = ctx and ctx.get('lang') + if not (lang or hasattr(src, '__call__')): + return src + + # We open a *new* cursor here, one reason is that failed SQL + # queries (as in IntegrityError) will invalidate the current one. + cr = False + + if hasattr(src, '__call__'): + # callable. We need to find the right parameters to call + # the orm._sql_message(self, cr, uid, ids, context) function, + # or we skip.. + # our signature is f(osv_pool, dbname [,uid, obj, method, args]) + try: + if args and len(args) > 1: + # TODO self doesn't exist, but was already wrong before (it was not a registry but just the object_service. + obj = self.get(args[1]) + if len(args) > 3 and isinstance(args[3], (long, int, list)): + ids = args[3] + else: + ids = [] + cr = openerp.sql_db.db_connect(dbname).cursor() + return src(obj, cr, uid, ids, context=(ctx or {})) + except Exception: + pass + finally: + if cr: cr.close() + + return False # so that the original SQL error will + # be returned, it is the best we have. + + try: + cr = openerp.sql_db.db_connect(dbname).cursor() + res = translate(cr, name=False, source_type=ttype, + lang=lang, source=src) + if res: + return res + else: + return src + finally: + if cr: cr.close() + + def _(src): + return tr(src, 'code') + + try: + if openerp.pooler.get_pool(dbname)._init: + raise except_osv('Database not ready', 'Currently, this database is not fully loaded and can not be used.') + return f(dbname, *args, **kwargs) + except except_orm, inst: + raise except_osv(inst.name, inst.value) + except except_osv: + raise + except IntegrityError, inst: + osv_pool = openerp.pooler.get_pool(dbname) + for key in osv_pool._sql_error.keys(): + if key in inst[0]: + openerp.netsvc.abort_response(1, _('Constraint Error'), 'warning', + tr(osv_pool._sql_error[key], 'sql_constraint') or inst[0]) + if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION): + msg = _('The operation cannot be completed, probably due to the following:\n- deletion: you may be trying to delete a record while other records still reference it\n- creation/update: a mandatory field is not correctly set') + _logger.debug("IntegrityError", exc_info=True) + try: + errortxt = inst.pgerror.replace('«','"').replace('»','"') + if '"public".' in errortxt: + context = errortxt.split('"public".')[1] + model_name = table = context.split('"')[1] + else: + last_quote_end = errortxt.rfind('"') + last_quote_begin = errortxt.rfind('"', 0, last_quote_end) + model_name = table = errortxt[last_quote_begin+1:last_quote_end].strip() + model = table.replace("_",".") + model_obj = osv_pool.get(model) + if model_obj: + model_name = model_obj._description or model_obj._name + msg += _('\n\n[object with reference: %s - %s]') % (model_name, model) + except Exception: + pass + openerp.netsvc.abort_response(1, _('Integrity Error'), 'warning', msg) + else: + openerp.netsvc.abort_response(1, _('Integrity Error'), 'warning', inst[0]) + except Exception: + _logger.exception("Uncaught exception") + raise + + return wrapper + +def execute_cr(cr, uid, obj, method, *args, **kw): + object = openerp.pooler.get_pool(cr.dbname).get(obj) + if not object: + raise except_osv('Object Error', 'Object %s doesn\'t exist' % str(obj)) + return getattr(object, method)(cr, uid, *args, **kw) + +def execute_kw(db, uid, obj, method, args, kw=None): + return execute(db, uid, obj, method, *args, **kw or {}) + +@check +def execute(db, uid, obj, method, *args, **kw): + threading.currentThread().dbname = db + cr = openerp.pooler.get_db(db).cursor() + try: + try: + if method.startswith('_'): + raise except_osv('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 + +def exec_workflow_cr(cr, uid, obj, signal, *args): + object = openerp.pooler.get_pool(cr.dbname).get(obj) + if not object: + raise except_osv('Object Error', 'Object %s doesn\'t exist' % str(obj)) + res_id = args[0] + return object._workflow_signal(cr, uid, [res_id], signal)[res_id] + +@check +def exec_workflow(db, uid, obj, signal, *args): + cr = openerp.pooler.get_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 + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/service/web_services.py b/openerp/service/web_services.py index 940ecdb23e8..fb194b64694 100644 --- a/openerp/service/web_services.py +++ b/openerp/service/web_services.py @@ -32,10 +32,12 @@ import threading import time import traceback from cStringIO import StringIO + from openerp.tools.translate import _ import openerp.netsvc as netsvc import openerp.pooler as pooler import openerp.release as release +import openerp.service.model import openerp.sql_db as sql_db import openerp.tools as tools import openerp.modules @@ -608,9 +610,8 @@ class objects_proxy(netsvc.ExportService): if method not in ['execute', 'execute_kw', 'exec_workflow']: raise NameError("Method not available %s" % method) security.check(db,uid,passwd) - assert openerp.osv.osv.service, "The object_proxy class must be started with start_object_proxy." openerp.modules.registry.RegistryManager.check_registry_signaling(db) - fn = getattr(openerp.osv.osv.service, method) + fn = getattr(openerp.service.model, method) res = fn(db, uid, *params) openerp.modules.registry.RegistryManager.signal_caches_change(db) return res diff --git a/openerp/tests/__init__.py b/openerp/tests/__init__.py index 7f82c414fdd..73459318178 100644 --- a/openerp/tests/__init__.py +++ b/openerp/tests/__init__.py @@ -22,6 +22,7 @@ import test_osv import test_translate import test_uninstall import test_view_validation +import test_xmlrpc # TODO Remove this before merge, or change oe run-tests to only run fast_suite + checks by default. fast_suite = [ test_ir_sequence, From 674056c4a57e6ba0f5893afb621a85f8fcdb90af Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 30 Jan 2013 14:44:51 +0100 Subject: [PATCH 02/34] [REF] services: somewhat clean use of openerp exceptions. bzr revid: vmt@openerp.com-20130130134451-yft2y56hkpnoxgwa --- openerp/netsvc.py | 4 ---- openerp/osv/osv.py | 19 +++++++++++-------- openerp/service/model.py | 20 ++++++++------------ openerp/service/netrpc_server.py | 2 +- openerp/service/web_services.py | 25 +++++++------------------ openerp/service/wsgi_server.py | 4 ++-- 6 files changed, 29 insertions(+), 45 deletions(-) diff --git a/openerp/netsvc.py b/openerp/netsvc.py index 820df90e35d..7029d3264a5 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -67,10 +67,6 @@ def close_socket(sock): raise sock.close() -def abort_response(dummy_1, description, dummy_2, details): - # TODO Replace except_{osv,orm} with these directly. - raise openerp.osv.osv.except_osv(description, details) - class Service(object): """ Base class for Local services Functionality here is trusted, no authentication. diff --git a/openerp/osv/osv.py b/openerp/osv/osv.py index 31919cc9cf6..4f23b1df893 100644 --- a/openerp/osv/osv.py +++ b/openerp/osv/osv.py @@ -21,18 +21,21 @@ #.apidoc title: Objects Services (OSV) -from openerp.osv.orm import Model, TransientModel, AbstractModel +from openerp.osv.orm import except_orm, Model, TransientModel, AbstractModel -# Deprecated. -class except_osv(Exception): - def __init__(self, name, value): - self.name = name - self.value = value - self.args = (name, value) +# Deprecated, kept for backward compatibility. +# openerp.exceptions.Warning should be used instead. +except_osv = except_orm -# deprecated - for backward compatibility. +# Deprecated, kept for backward compatibility. osv = Model osv_memory = TransientModel osv_abstract = AbstractModel # ;-) +# Deprecated, kept for backward compatibility, in particular for audittrail. +class object_service(object): + def __init__(self): + import openerp.service.model + # TODO monkeypatch openerp.service.model to call the new object_service instance methods. + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/service/model.py b/openerp/service/model.py index 204cad0c8f1..10147fd799a 100644 --- a/openerp/service/model.py +++ b/openerp/service/model.py @@ -8,7 +8,6 @@ import threading import openerp from openerp.tools.translate import translate from openerp.osv.orm import except_orm -from openerp.osv.osv import except_osv _logger = logging.getLogger(__name__) @@ -80,18 +79,15 @@ def check(f): try: if openerp.pooler.get_pool(dbname)._init: - raise except_osv('Database not ready', 'Currently, this database is not fully loaded and can not be used.') + raise openerp.exceptions.Warning('Currently, this database is not fully loaded and can not be used.') return f(dbname, *args, **kwargs) - except except_orm, inst: - raise except_osv(inst.name, inst.value) - except except_osv: + except except_orm: raise except IntegrityError, inst: osv_pool = openerp.pooler.get_pool(dbname) for key in osv_pool._sql_error.keys(): if key in inst[0]: - openerp.netsvc.abort_response(1, _('Constraint Error'), 'warning', - tr(osv_pool._sql_error[key], 'sql_constraint') or inst[0]) + raise openerp.osv.orm.except_orm(_('Constraint Error'), tr(osv_pool._sql_error[key], 'sql_constraint') or inst[0]) if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION): msg = _('The operation cannot be completed, probably due to the following:\n- deletion: you may be trying to delete a record while other records still reference it\n- creation/update: a mandatory field is not correctly set') _logger.debug("IntegrityError", exc_info=True) @@ -111,9 +107,9 @@ def check(f): msg += _('\n\n[object with reference: %s - %s]') % (model_name, model) except Exception: pass - openerp.netsvc.abort_response(1, _('Integrity Error'), 'warning', msg) + raise openerp.osv.orm.except_orm(_('Integrity Error'), msg) else: - openerp.netsvc.abort_response(1, _('Integrity Error'), 'warning', inst[0]) + raise openerp.osv.orm.except_orm(_('Integrity Error'), inst[0]) except Exception: _logger.exception("Uncaught exception") raise @@ -123,7 +119,7 @@ def check(f): def execute_cr(cr, uid, obj, method, *args, **kw): object = openerp.pooler.get_pool(cr.dbname).get(obj) if not object: - raise except_osv('Object Error', 'Object %s doesn\'t exist' % str(obj)) + raise except_orm('Object Error', 'Object %s doesn\'t exist' % str(obj)) return getattr(object, method)(cr, uid, *args, **kw) def execute_kw(db, uid, obj, method, args, kw=None): @@ -136,7 +132,7 @@ def execute(db, uid, obj, method, *args, **kw): try: try: if method.startswith('_'): - raise except_osv('Access Denied', 'Private methods (such as %s) cannot be called remotely.' % (method,)) + 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) @@ -151,7 +147,7 @@ def execute(db, uid, obj, method, *args, **kw): def exec_workflow_cr(cr, uid, obj, signal, *args): object = openerp.pooler.get_pool(cr.dbname).get(obj) if not object: - raise except_osv('Object Error', 'Object %s doesn\'t exist' % str(obj)) + 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] diff --git a/openerp/service/netrpc_server.py b/openerp/service/netrpc_server.py index af89a734f28..a94c1993edc 100644 --- a/openerp/service/netrpc_server.py +++ b/openerp/service/netrpc_server.py @@ -166,7 +166,7 @@ class TinySocketClientThread(threading.Thread): self.running = False def netrpc_handle_exception_legacy(e): - if isinstance(e, openerp.osv.osv.except_osv): + if isinstance(e, openerp.osv.orm.except_orm): return 'warning -- ' + e.name + '\n\n' + e.value if isinstance(e, openerp.exceptions.Warning): return 'warning -- Warning\n\n' + str(e) diff --git a/openerp/service/web_services.py b/openerp/service/web_services.py index fb194b64694..9e42e458792 100644 --- a/openerp/service/web_services.py +++ b/openerp/service/web_services.py @@ -42,6 +42,7 @@ import openerp.sql_db as sql_db import openerp.tools as tools import openerp.modules import openerp.exceptions +import openerp.osv.orm # TODO use openerp.exceptions from openerp.service import http_server from openerp import SUPERUSER_ID @@ -388,22 +389,10 @@ class db(netsvc.ExportService): return release.version def exp_migrate_databases(self,databases): - - from openerp.osv.orm import except_orm - from openerp.osv.osv import except_osv - for db in databases: - try: - _logger.info('migrate database %s', db) - tools.config['update']['base'] = True - pooler.restart_pool(db, force_demo=False, update_module=True) - except except_orm, inst: - netsvc.abort_response(1, inst.name, 'warning', inst.value) - except except_osv, inst: - netsvc.abort_response(1, inst.name, 'warning', inst.value) - except Exception: - _logger.exception('Exception in migrate_databases:') - raise + _logger.info('migrate database %s', db) + tools.config['update']['base'] = True + pooler.restart_pool(db, force_demo=False, update_module=True) return True class common(netsvc.ExportService): @@ -474,7 +463,7 @@ GNU Public Licence. return rc.get_available_updates(rc.id, openerp.modules.get_modules_with_version()) except tm.RemoteContractException, e: - netsvc.abort_response(1, 'Migration Error', 'warning', str(e)) + raise openerp.osv.orm.except_orm('Migration Error', str(e)) def exp_get_migration_scripts(self, contract_id, contract_password): @@ -540,7 +529,7 @@ GNU Public Licence. return True except tm.RemoteContractException, e: - netsvc.abort_response(1, 'Migration Error', 'warning', str(e)) + raise openerp.osv.orm.except_orm('Migration Error', str(e)) except Exception, e: _logger.exception('Exception in get_migration_script:') raise @@ -724,7 +713,7 @@ class report_spool(netsvc.ExportService): result = self._reports[report_id] exc = result['exception'] if exc: - netsvc.abort_response(exc, exc.message, 'warning', exc.traceback) + raise openerp.osv.orm.except_orm(exc.message, exc.traceback) res = {'state': result['state']} if res['state']: if tools.config['reportgz']: diff --git a/openerp/service/wsgi_server.py b/openerp/service/wsgi_server.py index c9294365d71..b76ac95e4b8 100644 --- a/openerp/service/wsgi_server.py +++ b/openerp/service/wsgi_server.py @@ -90,7 +90,7 @@ def xmlrpc_return(start_response, service, method, params, legacy_exceptions=Fal return [response] def xmlrpc_handle_exception(e): - if isinstance(e, openerp.osv.osv.except_osv): # legacy + if isinstance(e, openerp.osv.orm.except_orm): # legacy fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, openerp.tools.ustr(e.value)) response = xmlrpclib.dumps(fault, allow_none=False, encoding=None) elif isinstance(e, openerp.exceptions.Warning): @@ -123,7 +123,7 @@ def xmlrpc_handle_exception(e): return response def xmlrpc_handle_exception_legacy(e): - if isinstance(e, openerp.osv.osv.except_osv): + if isinstance(e, openerp.osv.orm.except_orm): fault = xmlrpclib.Fault('warning -- ' + e.name + '\n\n' + e.value, '') response = xmlrpclib.dumps(fault, allow_none=False, encoding=None) elif isinstance(e, openerp.exceptions.Warning): From fc27a7b3a4ebabc586498ea2c91ccafde5b46a67 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 30 Jan 2013 14:57:35 +0100 Subject: [PATCH 03/34] [REF] netrpc: removed. bzr revid: vmt@openerp.com-20130130135735-pskmwbkmgfotjx1e --- openerp/service/__init__.py | 6 +- openerp/service/netrpc_server.py | 245 ------------------------------- openerp/service/netrpc_socket.py | 99 ------------- 3 files changed, 1 insertion(+), 349 deletions(-) delete mode 100644 openerp/service/netrpc_server.py delete mode 100644 openerp/service/netrpc_socket.py diff --git a/openerp/service/__init__.py b/openerp/service/__init__.py index 5e83ddd6e9c..9f921340586 100644 --- a/openerp/service/__init__.py +++ b/openerp/service/__init__.py @@ -29,7 +29,6 @@ import threading import time import cron -import netrpc_server import web_services import web_services import wsgi_server @@ -82,10 +81,8 @@ def start_internal(): start_internal_done = True def start_services(): - """ Start all services including http, netrpc and cron """ + """ Start all services including http, and cron """ start_internal() - # Initialize the NETRPC server. - netrpc_server.start_service() # Start the WSGI server. wsgi_server.start_service() # Start the main cron thread. @@ -95,7 +92,6 @@ def stop_services(): """ Stop all services. """ # stop services cron.stop_service() - netrpc_server.stop_service() wsgi_server.stop_service() _logger.info("Initiating shutdown") diff --git a/openerp/service/netrpc_server.py b/openerp/service/netrpc_server.py deleted file mode 100644 index a94c1993edc..00000000000 --- a/openerp/service/netrpc_server.py +++ /dev/null @@ -1,245 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# Copyright P. Christeas 2008,2009 -# Copyright (C) 2004-2009 Tiny SPRL (). -# -# 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 . -# -############################################################################## - -#.apidoc title: NET-RPC Server - -""" This file contains instance of the net-rpc server -""" -import logging -import select -import socket -import sys -import threading -import traceback -import openerp -import openerp.service.netrpc_socket -import openerp.netsvc as netsvc -import openerp.tools as tools - -_logger = logging.getLogger(__name__) - -class Server: - """ Generic interface for all servers with an event loop etc. - Override this to impement http, net-rpc etc. servers. - - Servers here must have threaded behaviour. start() must not block, - there is no run(). - """ - __is_started = False - __servers = [] - __starter_threads = [] - - # we don't want blocking server calls (think select()) to - # wait forever and possibly prevent exiting the process, - # but instead we want a form of polling/busy_wait pattern, where - # _server_timeout should be used as the default timeout for - # all I/O blocking operations - _busywait_timeout = 0.5 - - def __init__(self): - Server.__servers.append(self) - if Server.__is_started: - # raise Exception('All instances of servers must be inited before the startAll()') - # Since the startAll() won't be called again, allow this server to - # init and then start it after 1sec (hopefully). Register that - # timer thread in a list, so that we can abort the start if quitAll - # is called in the meantime - t = threading.Timer(1.0, self._late_start) - t.name = 'Late start timer for %s' % str(self.__class__) - Server.__starter_threads.append(t) - t.start() - - def start(self): - _logger.debug("called stub Server.start") - - def _late_start(self): - self.start() - for thr in Server.__starter_threads: - if thr.finished.is_set(): - Server.__starter_threads.remove(thr) - - def stop(self): - _logger.debug("called stub Server.stop") - - def stats(self): - """ This function should return statistics about the server """ - return "%s: No statistics" % str(self.__class__) - - @classmethod - def startAll(cls): - if cls.__is_started: - return - _logger.info("Starting %d services" % len(cls.__servers)) - for srv in cls.__servers: - srv.start() - cls.__is_started = True - - @classmethod - def quitAll(cls): - if not cls.__is_started: - return - _logger.info("Stopping %d services" % len(cls.__servers)) - for thr in cls.__starter_threads: - if not thr.finished.is_set(): - thr.cancel() - cls.__starter_threads.remove(thr) - - for srv in cls.__servers: - srv.stop() - cls.__is_started = False - - @classmethod - def allStats(cls): - res = ["Servers %s" % ('stopped', 'started')[cls.__is_started]] - res.extend(srv.stats() for srv in cls.__servers) - return '\n'.join(res) - - def _close_socket(self): - netsvc.close_socket(self.socket) - -class TinySocketClientThread(threading.Thread): - def __init__(self, sock, threads): - spn = sock and sock.getpeername() - spn = 'netrpc-client-%s:%s' % spn[0:2] - threading.Thread.__init__(self, name=spn) - self.sock = sock - # Only at the server side, use a big timeout: close the - # clients connection when they're idle for 20min. - self.sock.settimeout(1200) - self.threads = threads - - def run(self): - self.running = True - try: - ts = openerp.server.netrpc_socket.mysocket(self.sock) - except Exception: - self.threads.remove(self) - self.running = False - return False - - while self.running: - try: - msg = ts.myreceive() - result = netsvc.dispatch_rpc(msg[0], msg[1], msg[2:]) - ts.mysend(result) - except socket.timeout: - #terminate this channel because other endpoint is gone - break - except Exception, e: - try: - valid_exception = Exception(netrpc_handle_exception_legacy(e)) - valid_traceback = getattr(e, 'traceback', sys.exc_info()) - formatted_traceback = "".join(traceback.format_exception(*valid_traceback)) - _logger.debug("netrpc: communication-level exception", exc_info=True) - ts.mysend(valid_exception, exception=True, traceback=formatted_traceback) - break - except Exception, ex: - #terminate this channel if we can't properly send back the error - _logger.exception("netrpc: cannot deliver exception message to client") - break - - netsvc.close_socket(self.sock) - self.sock = None - self.threads.remove(self) - self.running = False - return True - - def stop(self): - self.running = False - -def netrpc_handle_exception_legacy(e): - if isinstance(e, openerp.osv.orm.except_orm): - return 'warning -- ' + e.name + '\n\n' + e.value - if isinstance(e, openerp.exceptions.Warning): - return 'warning -- Warning\n\n' + str(e) - if isinstance(e, openerp.exceptions.AccessError): - return 'warning -- AccessError\n\n' + str(e) - if isinstance(e, openerp.exceptions.AccessDenied): - return 'AccessDenied ' + str(e) - return openerp.tools.exception_to_unicode(e) - -class TinySocketServerThread(threading.Thread,Server): - def __init__(self, interface, port, secure=False): - threading.Thread.__init__(self, name="NetRPCDaemon-%d"%port) - Server.__init__(self) - self.__port = port - self.__interface = interface - self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self.socket.bind((self.__interface, self.__port)) - self.socket.listen(5) - self.threads = [] - _logger.info("starting NET-RPC service on %s:%s", interface or '0.0.0.0', port) - - def run(self): - try: - self.running = True - while self.running: - fd_sets = select.select([self.socket], [], [], self._busywait_timeout) - if not fd_sets[0]: - continue - (clientsocket, address) = self.socket.accept() - ct = TinySocketClientThread(clientsocket, self.threads) - clientsocket = None - self.threads.append(ct) - ct.start() - lt = len(self.threads) - if (lt > 10) and (lt % 10 == 0): - # Not many threads should be serving at the same time, so log - # their abuse. - _logger.debug("Netrpc: %d threads", len(self.threads)) - self.socket.close() - except Exception, e: - _logger.warning("Netrpc: closing because of exception %s", e) - self.socket.close() - return False - - def stop(self): - self.running = False - for t in self.threads: - t.stop() - self._close_socket() - - def stats(self): - res = "Net-RPC: " + ( (self.running and "running") or "stopped") - i = 0 - for t in self.threads: - i += 1 - res += "\nNet-RPC #%d: %s " % (i, t.name) - if t.isAlive(): - res += "running" - else: - res += "finished" - if t.sock: - res += ", socket" - return res - -netrpcd = None - -def start_service(): - global netrpcd - if tools.config.get('netrpc', False): - netrpcd = TinySocketServerThread(tools.config.get('netrpc_interface', ''), int(tools.config.get('netrpc_port', 8070))) - -def stop_service(): - Server.quitAll() - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/service/netrpc_socket.py b/openerp/service/netrpc_socket.py deleted file mode 100644 index c49c8f7c2a2..00000000000 --- a/openerp/service/netrpc_socket.py +++ /dev/null @@ -1,99 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (). -# -# 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 . -# -############################################################################## - -import socket -import cPickle -import cStringIO - -import openerp.netsvc as netsvc - -# Pickle protocol version 2 is optimized compared to default (version 0) -PICKLE_PROTOCOL = 2 - -class Myexception(Exception): - """ - custom exception object store - * faultcode - * faulestring - * args - """ - - def __init__(self, faultCode, faultString): - self.faultCode = faultCode - self.faultString = faultString - self.args = (faultCode, faultString) - -class mysocket: - - def __init__(self, sock=None): - if sock is None: - self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - else: - self.sock = sock - # self.sock.settimeout(120) - # prepare this socket for long operations: it may block for infinite - # time, but should exit as soon as the net is down - self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - - def connect(self, host, port=False): - if not port: - protocol, buf = host.split('//') - host, port = buf.split(':') - self.sock.connect((host, int(port))) - - def disconnect(self): - netsvc.close_socket(self.sock) - - def mysend(self, msg, exception=False, traceback=None): - msg = cPickle.dumps([msg, traceback], PICKLE_PROTOCOL) - self.sock.sendall('%8d%d%s' % (len(msg), bool(exception), msg)) - - def myreceive(self): - buf='' - while len(buf) < 9: - chunk = self.sock.recv(9 - len(buf)) - if not chunk: - raise socket.timeout - buf += chunk - size = int(buf[:8]) - if buf[8] != "0": - exception = buf[8] - else: - exception = False - msg = '' - while len(msg) < size: - chunk = self.sock.recv(size-len(msg)) - if not chunk: - raise socket.timeout - msg = msg + chunk - msgio = cStringIO.StringIO(msg) - unpickler = cPickle.Unpickler(msgio) - unpickler.find_global = None - res = unpickler.load() - - if isinstance(res[0],Exception): - if exception: - raise Myexception(str(res[0]), str(res[1])) - raise res[0] - else: - return res[0] - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From e86f90b0c75bac80bbbda1c01bb5abfa95af55a4 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 30 Jan 2013 14:58:59 +0100 Subject: [PATCH 04/34] [FIX] osv.object_service must be called osv.object_proxy. bzr revid: vmt@openerp.com-20130130135859-vy96niq19fjsp8g4 --- openerp/osv/osv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/osv/osv.py b/openerp/osv/osv.py index 4f23b1df893..16acbbb7ae6 100644 --- a/openerp/osv/osv.py +++ b/openerp/osv/osv.py @@ -33,7 +33,7 @@ osv_memory = TransientModel osv_abstract = AbstractModel # ;-) # Deprecated, kept for backward compatibility, in particular for audittrail. -class object_service(object): +class object_proxy(object): def __init__(self): import openerp.service.model # TODO monkeypatch openerp.service.model to call the new object_service instance methods. From 52f1ad3509a52c3216ee21d447616a6712716e1d Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 30 Jan 2013 15:05:28 +0100 Subject: [PATCH 05/34] [FIX] ir_property: correct import of orm module. bzr revid: vmt@openerp.com-20130130140528-6n8s18szifzztvpy --- openerp/addons/base/res/ir_property.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openerp/addons/base/res/ir_property.py b/openerp/addons/base/res/ir_property.py index 07875e5efd0..5ec4f98c3b8 100644 --- a/openerp/addons/base/res/ir_property.py +++ b/openerp/addons/base/res/ir_property.py @@ -21,7 +21,8 @@ import time -from openerp.osv import osv,fields +from openerp.osv import osv, fields +from openerp.osv.orm import browse_record from openerp.tools.misc import attrgetter # ------------------------------------------------------------------------- @@ -111,7 +112,7 @@ class ir_property(osv.osv): raise osv.except_osv('Error', 'Invalid type') if field == 'value_reference': - if isinstance(value, osv.orm.browse_record): + if isinstance(value, browse_record): value = '%s,%d' % (value._name, value.id) elif isinstance(value, (int, long)): field_id = values.get('fields_id') From 8d23b8a63af5c457b17bd1ff9433c25c9f60a1c7 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 30 Jan 2013 15:56:16 +0100 Subject: [PATCH 06/34] [REF] services: moved the db service to openerp.service.db. bzr revid: vmt@openerp.com-20130130145616-5hijxp9qib7i1uip --- openerp/netsvc.py | 4 + openerp/service/__init__.py | 3 + openerp/service/db.py | 347 ++++++++++++++++++++++++++++++++ openerp/service/web_services.py | 332 ------------------------------ 4 files changed, 354 insertions(+), 332 deletions(-) create mode 100644 openerp/service/db.py diff --git a/openerp/netsvc.py b/openerp/netsvc.py index 7029d3264a5..63eddaaab6c 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -91,6 +91,8 @@ def LocalService(name): # are updated to directly use openerp.osv.osv.service. if name == 'object_proxy': return openerp.service.model + if name == 'db': + return openerp.service.db return Service._services[name] @@ -111,6 +113,8 @@ class ExportService(object): @classmethod def getService(cls,name): + if name == 'db': + return openerp.service.db return cls._services[name] # Dispatch a RPC call w.r.t. the method name. The dispatching diff --git a/openerp/service/__init__.py b/openerp/service/__init__.py index 9f921340586..f57b8322b92 100644 --- a/openerp/service/__init__.py +++ b/openerp/service/__init__.py @@ -39,6 +39,9 @@ import openerp.osv from openerp.release import nt_service_name import openerp.tools +import model +import db + #.apidoc title: RPC Services """ Classes of this module implement the network protocols that the diff --git a/openerp/service/db.py b/openerp/service/db.py new file mode 100644 index 00000000000..2bc14084f59 --- /dev/null +++ b/openerp/service/db.py @@ -0,0 +1,347 @@ +# -*- coding: utf-8 -*- + +import contextlib +import logging +import threading +import traceback + +from openerp import SUPERUSER_ID +import openerp.pooler +import openerp.sql_db +import openerp.tools + +import security + +_logger = logging.getLogger(__name__) + +self_actions = {} +self_id = 0 +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): + cr = None + try: + self_actions[id]['progress'] = 0 + cr = openerp.sql_db.db_connect(db_name).cursor() + openerp.modules.db.initialize(cr) # TODO this should be removed as it is done by pooler.restart_pool. + openerp.tools.config['lang'] = lang + cr.commit() + cr.close() + + pool = openerp.pooler.restart_pool(db_name, demo, self_actions[id], + update_module=True)[1] + + cr = openerp.sql_db.db_connect(db_name).cursor() + + if lang: + modobj = pool.get('ir.module.module') + mids = modobj.search(cr, SUPERUSER_ID, [('state', '=', 'installed')]) + modobj.update_translations(cr, SUPERUSER_ID, mids, lang) + + # update admin's password and lang + values = {'password': user_password, 'lang': lang} + pool.get('res.users').write(cr, SUPERUSER_ID, [SUPERUSER_ID], values) + + cr.execute('SELECT login, password FROM res_users ORDER BY login') + self_actions[id].update(users=cr.dictfetchall(), clean=True) + cr.commit() + cr.close() + except Exception, e: + self_actions[id].update(clean=False, exception=e) + _logger.exception('CREATE DATABASE failed:') + self_actions[id]['traceback'] = traceback.format_exc() + if cr: + cr.close() + +def dispatch(method, params): + if method in [ 'create', 'get_progress', 'drop', 'dump', + 'restore', 'rename', + 'change_admin_password', 'migrate_databases', + 'create_database', 'duplicate_database' ]: + passwd = params[0] + params = params[1:] + security.check_super(passwd) + elif method in [ 'db_exist', 'list', 'list_lang', 'server_version' ]: + # params = params + # No security check for these methods + pass + else: + raise KeyError("Method not found: %s" % method) + fn = globals()['exp_' + method] + return fn(*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() + +def exp_create(db_name, demo, lang, user_password='admin'): + self_id_protect.acquire() + global self_id + self_id += 1 + id = self_id + self_id_protect.release() + + self_actions[id] = {'clean': False} + + _create_empty_database(db_name) + + _logger.info('CREATE DATABASE %s', db_name.lower()) + create_thread = threading.Thread(target=_initialize_db, + args=(id, db_name, demo, lang, user_password)) + create_thread.start() + self_actions[id]['thread'] = create_thread + return id + +def exp_create_database(db_name, demo, lang, user_password='admin'): + """ Similar to exp_create but blocking.""" + self_id_protect.acquire() + global self_id + self_id += 1 + id = self_id + self_id_protect.release() + + self_actions[id] = {'clean': False} + + _logger.info('Create database `%s`.', db_name) + _create_empty_database(db_name) + _initialize_db(id, db_name, demo, lang, user_password) + return True + +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: + 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): + if self_actions[id]['thread'].isAlive(): +# return openerp.modules.init_progress[db_name] + return min(self_actions[id].get('progress', 0),0.95), [] + else: + clean = self_actions[id]['clean'] + if clean: + users = self_actions[id]['users'] + self_actions.pop(id) + return 1.0, users + else: + e = self_actions[id]['exception'] # TODO this seems wrong: actions[id]['traceback'] is set, but not 'exception'. + self_actions.pop(id) + raise Exception, e + +def exp_drop(db_name): + if not exp_db_exist(db_name): + return False + openerp.modules.registry.RegistryManager.delete(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: + # Try to terminate all other connections that might prevent + # dropping the database + try: + + # PostgreSQL 9.2 renamed pg_stat_activity.procpid to pid: + # http://www.postgresql.org/docs/9.2/static/release-9-2.html#AEN110389 + pid_col = 'pid' if cr._cnx.server_version >= 90200 else 'procpid' + + cr.execute("""SELECT pg_terminate_backend(%(pid_col)s) + FROM pg_stat_activity + WHERE datname = %%s AND + %(pid_col)s != pg_backend_pid()""" % {'pid_col': pid_col}, + (db_name,)) + except Exception: + pass + + try: + cr.execute('DROP DATABASE "%s"' % db_name) + except Exception, e: + _logger.error('DROP DB: %s failed:\n%s', db_name, e) + 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 +def _set_pg_password_in_environment(): + """ On Win32, pg_dump (and pg_restore) require that + :envvar:`PGPASSWORD` be set + + This context management method handles setting + :envvar:`PGPASSWORD` iif win32 and the envvar is not already + set, and removing it afterwards. + """ + if os.name != 'nt' or os.environ.get('PGPASSWORD'): + yield + else: + os.environ['PGPASSWORD'] = openerp.tools.config['db_password'] + try: + yield + finally: + del os.environ['PGPASSWORD'] + + +def exp_dump(db_name): + logger = logging.getLogger('openerp.service.web_services.db.dump') + with _set_pg_password_in_environment(): + cmd = ['pg_dump', '--format=c', '--no-owner'] + if openerp.tools.config['db_user']: + cmd.append('--username=' + openerp.tools.config['db_user']) + if openerp.tools.config['db_host']: + cmd.append('--host=' + openerp.tools.config['db_host']) + if openerp.tools.config['db_port']: + cmd.append('--port=' + str(openerp.tools.config['db_port'])) + cmd.append(db_name) + + stdin, stdout = openerp.tools.exec_pg_command_pipe(*tuple(cmd)) + stdin.close() + data = stdout.read() + res = stdout.close() + + if not data or res: + logger.error( + 'DUMP DB: %s failed! Please verify the configuration of the database password on the server. ' + 'It should be provided as a -w command-line option, or as `db_password` in the ' + 'server configuration file.\n %s', db_name, data) + raise Exception, "Couldn't dump database" + logger.info('DUMP DB successful: %s', db_name) + + return base64.encodestring(data) + +def exp_restore(db_name, data): + logger = logging.getLogger('openerp.service.web_services.db.restore') + with _set_pg_password_in_environment(): + if exp_db_exist(db_name): + logger.warning('RESTORE DB: %s already exists', db_name) + raise Exception, "Database already exists" + + _create_empty_database(db_name) + + cmd = ['pg_restore', '--no-owner'] + if openerp.tools.config['db_user']: + cmd.append('--username=' + openerp.tools.config['db_user']) + if openerp.tools.config['db_host']: + cmd.append('--host=' + openerp.tools.config['db_host']) + if openerp.tools.config['db_port']: + cmd.append('--port=' + str(openerp.tools.config['db_port'])) + cmd.append('--dbname=' + db_name) + args2 = tuple(cmd) + + buf=base64.decodestring(data) + if os.name == "nt": + tmpfile = (os.environ['TMP'] or 'C:\\') + os.tmpnam() + file(tmpfile, 'wb').write(buf) + args2=list(args2) + args2.append(tmpfile) + args2=tuple(args2) + stdin, stdout = openerp.tools.exec_pg_command_pipe(*args2) + if not os.name == "nt": + stdin.write(base64.decodestring(data)) + stdin.close() + res = stdout.close() + if res: + raise Exception, "Couldn't restore database" + logger.info('RESTORE DB: %s', db_name) + + return True + +def exp_rename(old_name, new_name): + openerp.modules.registry.RegistryManager.delete(old_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: + try: + cr.execute('ALTER DATABASE "%s" RENAME TO "%s"' % (old_name, new_name)) + except Exception, e: + _logger.error('RENAME DB: %s -> %s failed:\n%s', old_name, new_name, e) + raise Exception("Couldn't rename database %s to %s: %s" % (old_name, new_name, e)) + else: + fs = os.path.join(openerp.tools.config['root_path'], 'filestore') + if os.path.exists(os.path.join(fs, old_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): + ## Not True: in fact, check if connection to database is possible. The database may exists + return bool(openerp.sql_db.db_connect(db_name)) + +def exp_list(document=False): + if not openerp.tools.config['list_db'] and not document: + raise openerp.exceptions.AccessDenied() + 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: + try: + db_user = openerp.tools.config["db_user"] + if not db_user and os.name == 'posix': + import pwd + db_user = pwd.getpwuid(os.getuid())[0] + if not db_user: + cr.execute("select usename from pg_user where usesysid=(select datdba from pg_database where datname=%s)", (openerp.tools.config["db_name"],)) + res = cr.fetchone() + db_user = res and str(res[0]) + if db_user: + cr.execute("select datname from pg_database where datdba=(select usesysid from pg_user where usename=%s) and datname not in %s order by datname", (db_user, templates_list)) + else: + cr.execute("select datname from pg_database where datname not in %s order by datname", (templates_list,)) + res = [str(name) for (name,) in cr.fetchall()] + except Exception: + res = [] + finally: + cr.close() + res.sort() + return res + +def exp_change_admin_password(new_password): + openerp.tools.config['admin_passwd'] = new_password + openerp.tools.config.save() + return True + +def exp_list_lang(): + return openerp.tools.scan_languages() + +def exp_server_version(): + """ Return the version of the server + Used by the client to verify the compatibility with its own version + """ + return release.version + +def exp_migrate_databases(databases): + for db in databases: + _logger.info('migrate database %s', db) + openerp.tools.config['update']['base'] = True + openerp.pooler.restart_pool(db, force_demo=False, update_module=True) + return True + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/service/web_services.py b/openerp/service/web_services.py index 9e42e458792..dbcbcef34ef 100644 --- a/openerp/service/web_services.py +++ b/openerp/service/web_services.py @@ -64,337 +64,6 @@ RPC_VERSION_1 = { 'protocol_version': 1, } -# This should be moved to openerp.modules.db, along side initialize(). -def _initialize_db(serv, id, db_name, demo, lang, user_password): - cr = None - try: - serv.actions[id]['progress'] = 0 - cr = sql_db.db_connect(db_name).cursor() - openerp.modules.db.initialize(cr) # TODO this should be removed as it is done by pooler.restart_pool. - tools.config['lang'] = lang - cr.commit() - cr.close() - - pool = pooler.restart_pool(db_name, demo, serv.actions[id], - update_module=True)[1] - - cr = sql_db.db_connect(db_name).cursor() - - if lang: - modobj = pool.get('ir.module.module') - mids = modobj.search(cr, SUPERUSER_ID, [('state', '=', 'installed')]) - modobj.update_translations(cr, SUPERUSER_ID, mids, lang) - - # update admin's password and lang - values = {'password': user_password, 'lang': lang} - pool.get('res.users').write(cr, SUPERUSER_ID, [SUPERUSER_ID], values) - - cr.execute('SELECT login, password FROM res_users ORDER BY login') - serv.actions[id].update(users=cr.dictfetchall(), clean=True) - cr.commit() - cr.close() - except Exception, e: - serv.actions[id].update(clean=False, exception=e) - _logger.exception('CREATE DATABASE failed:') - serv.actions[id]['traceback'] = traceback.format_exc() - if cr: - cr.close() - -class db(netsvc.ExportService): - def __init__(self, name="db"): - netsvc.ExportService.__init__(self, name) - self.actions = {} - self.id = 0 - self.id_protect = threading.Semaphore() - - def dispatch(self, method, params): - if method in [ 'create', 'get_progress', 'drop', 'dump', - 'restore', 'rename', - 'change_admin_password', 'migrate_databases', - 'create_database', 'duplicate_database' ]: - passwd = params[0] - params = params[1:] - security.check_super(passwd) - elif method in [ 'db_exist', 'list', 'list_lang', 'server_version' ]: - # params = params - # No security check for these methods - pass - else: - raise KeyError("Method not found: %s" % method) - fn = getattr(self, 'exp_'+method) - return fn(*params) - - def _create_empty_database(self, name): - db = sql_db.db_connect('postgres') - cr = db.cursor() - chosen_template = 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() - - def exp_create(self, db_name, demo, lang, user_password='admin'): - self.id_protect.acquire() - self.id += 1 - id = self.id - self.id_protect.release() - - self.actions[id] = {'clean': False} - - self._create_empty_database(db_name) - - _logger.info('CREATE DATABASE %s', db_name.lower()) - create_thread = threading.Thread(target=_initialize_db, - args=(self, id, db_name, demo, lang, user_password)) - create_thread.start() - self.actions[id]['thread'] = create_thread - return id - - def exp_create_database(self, db_name, demo, lang, user_password='admin'): - """ Similar to exp_create but blocking.""" - self.id_protect.acquire() - self.id += 1 - id = self.id - self.id_protect.release() - - self.actions[id] = {'clean': False} - - _logger.info('Create database `%s`.', db_name) - self._create_empty_database(db_name) - _initialize_db(self, id, db_name, demo, lang, user_password) - return True - - def exp_duplicate_database(self, db_original_name, db_name): - _logger.info('Duplicate database `%s` to `%s`.', db_original_name, db_name) - sql_db.close_db(db_original_name) - db = sql_db.db_connect('postgres') - cr = db.cursor() - try: - 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(self, id): - if self.actions[id]['thread'].isAlive(): -# return openerp.modules.init_progress[db_name] - return min(self.actions[id].get('progress', 0),0.95), [] - else: - clean = self.actions[id]['clean'] - if clean: - users = self.actions[id]['users'] - self.actions.pop(id) - return 1.0, users - else: - e = self.actions[id]['exception'] # TODO this seems wrong: actions[id]['traceback'] is set, but not 'exception'. - self.actions.pop(id) - raise Exception, e - - def exp_drop(self, db_name): - if not self.exp_db_exist(db_name): - return False - openerp.modules.registry.RegistryManager.delete(db_name) - sql_db.close_db(db_name) - - db = sql_db.db_connect('postgres') - cr = db.cursor() - cr.autocommit(True) # avoid transaction block - try: - # Try to terminate all other connections that might prevent - # dropping the database - try: - - # PostgreSQL 9.2 renamed pg_stat_activity.procpid to pid: - # http://www.postgresql.org/docs/9.2/static/release-9-2.html#AEN110389 - pid_col = 'pid' if cr._cnx.server_version >= 90200 else 'procpid' - - cr.execute("""SELECT pg_terminate_backend(%(pid_col)s) - FROM pg_stat_activity - WHERE datname = %%s AND - %(pid_col)s != pg_backend_pid()""" % {'pid_col': pid_col}, - (db_name,)) - except Exception: - pass - - try: - cr.execute('DROP DATABASE "%s"' % db_name) - except Exception, e: - _logger.error('DROP DB: %s failed:\n%s', db_name, e) - 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 - def _set_pg_password_in_environment(self): - """ On Win32, pg_dump (and pg_restore) require that - :envvar:`PGPASSWORD` be set - - This context management method handles setting - :envvar:`PGPASSWORD` iif win32 and the envvar is not already - set, and removing it afterwards. - """ - if os.name != 'nt' or os.environ.get('PGPASSWORD'): - yield - else: - os.environ['PGPASSWORD'] = tools.config['db_password'] - try: - yield - finally: - del os.environ['PGPASSWORD'] - - - def exp_dump(self, db_name): - logger = logging.getLogger('openerp.service.web_services.db.dump') - with self._set_pg_password_in_environment(): - cmd = ['pg_dump', '--format=c', '--no-owner'] - if tools.config['db_user']: - cmd.append('--username=' + tools.config['db_user']) - if tools.config['db_host']: - cmd.append('--host=' + tools.config['db_host']) - if tools.config['db_port']: - cmd.append('--port=' + str(tools.config['db_port'])) - cmd.append(db_name) - - stdin, stdout = tools.exec_pg_command_pipe(*tuple(cmd)) - stdin.close() - data = stdout.read() - res = stdout.close() - - if not data or res: - logger.error( - 'DUMP DB: %s failed! Please verify the configuration of the database password on the server. ' - 'It should be provided as a -w command-line option, or as `db_password` in the ' - 'server configuration file.\n %s', db_name, data) - raise Exception, "Couldn't dump database" - logger.info('DUMP DB successful: %s', db_name) - - return base64.encodestring(data) - - def exp_restore(self, db_name, data): - logger = logging.getLogger('openerp.service.web_services.db.restore') - with self._set_pg_password_in_environment(): - if self.exp_db_exist(db_name): - logger.warning('RESTORE DB: %s already exists', db_name) - raise Exception, "Database already exists" - - self._create_empty_database(db_name) - - cmd = ['pg_restore', '--no-owner'] - if tools.config['db_user']: - cmd.append('--username=' + tools.config['db_user']) - if tools.config['db_host']: - cmd.append('--host=' + tools.config['db_host']) - if tools.config['db_port']: - cmd.append('--port=' + str(tools.config['db_port'])) - cmd.append('--dbname=' + db_name) - args2 = tuple(cmd) - - buf=base64.decodestring(data) - if os.name == "nt": - tmpfile = (os.environ['TMP'] or 'C:\\') + os.tmpnam() - file(tmpfile, 'wb').write(buf) - args2=list(args2) - args2.append(tmpfile) - args2=tuple(args2) - stdin, stdout = tools.exec_pg_command_pipe(*args2) - if not os.name == "nt": - stdin.write(base64.decodestring(data)) - stdin.close() - res = stdout.close() - if res: - raise Exception, "Couldn't restore database" - logger.info('RESTORE DB: %s', db_name) - - return True - - def exp_rename(self, old_name, new_name): - openerp.modules.registry.RegistryManager.delete(old_name) - sql_db.close_db(old_name) - - db = sql_db.db_connect('postgres') - cr = db.cursor() - cr.autocommit(True) # avoid transaction block - try: - try: - cr.execute('ALTER DATABASE "%s" RENAME TO "%s"' % (old_name, new_name)) - except Exception, e: - _logger.error('RENAME DB: %s -> %s failed:\n%s', old_name, new_name, e) - raise Exception("Couldn't rename database %s to %s: %s" % (old_name, new_name, e)) - else: - fs = os.path.join(tools.config['root_path'], 'filestore') - if os.path.exists(os.path.join(fs, old_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(self, db_name): - ## Not True: in fact, check if connection to database is possible. The database may exists - return bool(sql_db.db_connect(db_name)) - - def exp_list(self, document=False): - if not tools.config['list_db'] and not document: - raise openerp.exceptions.AccessDenied() - chosen_template = tools.config['db_template'] - templates_list = tuple(set(['template0', 'template1', 'postgres', chosen_template])) - db = sql_db.db_connect('postgres') - cr = db.cursor() - try: - try: - db_user = tools.config["db_user"] - if not db_user and os.name == 'posix': - import pwd - db_user = pwd.getpwuid(os.getuid())[0] - if not db_user: - cr.execute("select usename from pg_user where usesysid=(select datdba from pg_database where datname=%s)", (tools.config["db_name"],)) - res = cr.fetchone() - db_user = res and str(res[0]) - if db_user: - cr.execute("select datname from pg_database where datdba=(select usesysid from pg_user where usename=%s) and datname not in %s order by datname", (db_user, templates_list)) - else: - cr.execute("select datname from pg_database where datname not in %s order by datname", (templates_list,)) - res = [str(name) for (name,) in cr.fetchall()] - except Exception: - res = [] - finally: - cr.close() - res.sort() - return res - - def exp_change_admin_password(self, new_password): - tools.config['admin_passwd'] = new_password - tools.config.save() - return True - - def exp_list_lang(self): - return tools.scan_languages() - - def exp_server_version(self): - """ Return the version of the server - Used by the client to verify the compatibility with its own version - """ - return release.version - - def exp_migrate_databases(self,databases): - for db in databases: - _logger.info('migrate database %s', db) - tools.config['update']['base'] = True - pooler.restart_pool(db, force_demo=False, update_module=True) - return True - class common(netsvc.ExportService): def __init__(self,name="common"): @@ -743,7 +412,6 @@ class report_spool(netsvc.ExportService): def start_service(): - db() common() objects_proxy() report_spool() From 93cdfeb86a28b6c822b334a293c35b4a6930c100 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 30 Jan 2013 16:02:07 +0100 Subject: [PATCH 07/34] [REF] services: moved the object service to openerp.service.model. bzr revid: vmt@openerp.com-20130130150207-fz1ueou8prul05qa --- openerp/netsvc.py | 2 ++ openerp/service/model.py | 17 +++++++++++++++++ openerp/service/web_services.py | 21 --------------------- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/openerp/netsvc.py b/openerp/netsvc.py index 63eddaaab6c..1051a761af3 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -113,6 +113,8 @@ class ExportService(object): @classmethod def getService(cls,name): + if name == 'object': + return openerp.service.model if name == 'db': return openerp.service.db return cls._services[name] diff --git a/openerp/service/model.py b/openerp/service/model.py index 10147fd799a..a78f5e94018 100644 --- a/openerp/service/model.py +++ b/openerp/service/model.py @@ -9,8 +9,25 @@ import openerp from openerp.tools.translate import translate from openerp.osv.orm import except_orm +import security + _logger = logging.getLogger(__name__) +def dispatch(method, params): + (db, uid, passwd ) = params[0:3] + threading.current_thread().uid = uid + params = params[3:] + if method == 'obj_list': + raise NameError("obj_list has been discontinued via RPC as of 6.0, please query ir.model directly!") + if method not in ['execute', 'execute_kw', 'exec_workflow']: + raise NameError("Method not available %s" % method) + security.check(db,uid,passwd) + openerp.modules.registry.RegistryManager.check_registry_signaling(db) + fn = globals()[method] + res = fn(db, uid, *params) + openerp.modules.registry.RegistryManager.signal_caches_change(db) + return res + def check(f): @wraps(f) def wrapper(dbname, *args, **kwargs): diff --git a/openerp/service/web_services.py b/openerp/service/web_services.py index dbcbcef34ef..3a89f3fb615 100644 --- a/openerp/service/web_services.py +++ b/openerp/service/web_services.py @@ -255,26 +255,6 @@ GNU Public Licence. return sql_db.sql_counter -class objects_proxy(netsvc.ExportService): - def __init__(self, name="object"): - netsvc.ExportService.__init__(self,name) - - def dispatch(self, method, params): - (db, uid, passwd ) = params[0:3] - threading.current_thread().uid = uid - params = params[3:] - if method == 'obj_list': - raise NameError("obj_list has been discontinued via RPC as of 6.0, please query ir.model directly!") - if method not in ['execute', 'execute_kw', 'exec_workflow']: - raise NameError("Method not available %s" % method) - security.check(db,uid,passwd) - openerp.modules.registry.RegistryManager.check_registry_signaling(db) - fn = getattr(openerp.service.model, method) - res = fn(db, uid, *params) - openerp.modules.registry.RegistryManager.signal_caches_change(db) - return res - - # # TODO: set a maximum report number per user to avoid DOS attacks # @@ -413,7 +393,6 @@ class report_spool(netsvc.ExportService): def start_service(): common() - objects_proxy() report_spool() From 796bb7442615d9c0e3c3ca761eff3e1225c4560b Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 30 Jan 2013 16:14:34 +0100 Subject: [PATCH 08/34] [REF] services: moved the common service to openerp.service.common. bzr revid: vmt@openerp.com-20130130151434-1qoe9jhefge48ag3 --- openerp/netsvc.py | 2 + openerp/service/__init__.py | 3 +- openerp/service/common.py | 200 ++++++++++++++++++++++++++++++++ openerp/service/web_services.py | 199 ------------------------------- 4 files changed, 204 insertions(+), 200 deletions(-) create mode 100644 openerp/service/common.py diff --git a/openerp/netsvc.py b/openerp/netsvc.py index 1051a761af3..374fc9a2f4d 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -117,6 +117,8 @@ class ExportService(object): return openerp.service.model if name == 'db': return openerp.service.db + if name == 'common': + return openerp.service.common return cls._services[name] # Dispatch a RPC call w.r.t. the method name. The dispatching diff --git a/openerp/service/__init__.py b/openerp/service/__init__.py index f57b8322b92..06bf53503d7 100644 --- a/openerp/service/__init__.py +++ b/openerp/service/__init__.py @@ -39,8 +39,9 @@ import openerp.osv from openerp.release import nt_service_name import openerp.tools -import model +import common import db +import model #.apidoc title: RPC Services diff --git a/openerp/service/common.py b/openerp/service/common.py new file mode 100644 index 00000000000..f06ab3fe5aa --- /dev/null +++ b/openerp/service/common.py @@ -0,0 +1,200 @@ +# -*- coding: utf-8 -*- + +import logging +import threading + +import openerp.osv.orm # TODO use openerp.exceptions +import openerp.pooler +import openerp.release +import openerp.tools + +import security + +_logger = logging.getLogger(__name__) + +RPC_VERSION_1 = { + 'server_version': openerp.release.version, + 'server_version_info': openerp.release.version_info, + 'server_serie': openerp.release.serie, + 'protocol_version': 1, +} + +def dispatch(method, params): + if method in ['login', 'about', 'timezone_get', 'get_server_environment', + 'login_message','get_stats', 'check_connectivity', + 'list_http_services', 'version', 'authenticate']: + pass + elif method in ['get_available_updates', 'get_migration_scripts', 'set_loglevel', 'get_os_time', 'get_sqlcount']: + passwd = params[0] + params = params[1:] + security.check_super(passwd) + else: + raise Exception("Method not found: %s" % method) + + fn = globals()['exp_' + method] + return fn(*params) + +def exp_login(db, login, password): + # TODO: legacy indirection through 'security', should use directly + # the res.users model + res = security.login(db, login, password) + msg = res and 'successful login' or 'bad login or password' + _logger.info("%s from '%s' using database '%s'", msg, login, db.lower()) + return res or False + +def exp_authenticate(db, login, password, user_agent_env): + res_users = openerp.pooler.get_pool(db).get('res.users') + return res_users.authenticate(db, login, password, user_agent_env) + +def exp_version(): + return RPC_VERSION_1 + +def exp_about(extended=False): + """Return information about the OpenERP Server. + + @param extended: if True then return version info + @return string if extended is False else tuple + """ + + info = _('See http://openerp.com') + + if extended: + return info, openerp.release.version + return info + +def exp_timezone_get(db, login, password): + return openerp.tools.misc.get_server_timezone() + +def exp_get_available_updates(contract_id, contract_password): + import openerp.tools.maintenance as tm + try: + rc = tm.remote_contract(contract_id, contract_password) + if not rc.id: + raise tm.RemoteContractException('This contract does not exist or is not active') + + return rc.get_available_updates(rc.id, openerp.modules.get_modules_with_version()) + + except tm.RemoteContractException, e: + raise openerp.osv.orm.except_orm('Migration Error', str(e)) + + +def exp_get_migration_scripts(contract_id, contract_password): + import openerp.tools.maintenance as tm + try: + rc = tm.remote_contract(contract_id, contract_password) + if not rc.id: + raise tm.RemoteContractException('This contract does not exist or is not active') + if rc.status != 'full': + raise tm.RemoteContractException('Can not get updates for a partial contract') + + _logger.info('starting migration with contract %s', rc.name) + + zips = rc.retrieve_updates(rc.id, openerp.modules.get_modules_with_version()) + + from shutil import rmtree, copytree, copy + + backup_directory = os.path.join(openerp.tools.config['root_path'], 'backup', time.strftime('%Y-%m-%d-%H-%M')) + if zips and not os.path.isdir(backup_directory): + _logger.info('create a new backup directory to store the old modules: %s', backup_directory) + os.makedirs(backup_directory) + + for module in zips: + _logger.info('upgrade module %s', module) + mp = openerp.modules.get_module_path(module) + if mp: + if os.path.isdir(mp): + copytree(mp, os.path.join(backup_directory, module)) + if os.path.islink(mp): + os.unlink(mp) + else: + rmtree(mp) + else: + copy(mp + 'zip', backup_directory) + os.unlink(mp + '.zip') + + try: + try: + base64_decoded = base64.decodestring(zips[module]) + except Exception: + _logger.error('unable to read the module %s', module) + raise + + zip_contents = StringIO(base64_decoded) + zip_contents.seek(0) + try: + try: + openerp.tools.extract_zip_file(zip_contents, openerp.tools.config['addons_path'] ) + except Exception: + _logger.error('unable to extract the module %s', module) + rmtree(module) + raise + finally: + zip_contents.close() + except Exception: + _logger.error('restore the previous version of the module %s', module) + nmp = os.path.join(backup_directory, module) + if os.path.isdir(nmp): + copytree(nmp, openerp.tools.config['addons_path']) + else: + copy(nmp+'.zip', openerp.tools.config['addons_path']) + raise + + return True + except tm.RemoteContractException, e: + raise openerp.osv.orm.except_orm('Migration Error', str(e)) + except Exception, e: + _logger.exception('Exception in get_migration_script:') + raise + +def exp_get_server_environment(): + os_lang = '.'.join( [x for x in locale.getdefaultlocale() if x] ) + if not os_lang: + os_lang = 'NOT SET' + environment = '\nEnvironment Information : \n' \ + 'System : %s\n' \ + 'OS Name : %s\n' \ + %(platform.platform(), platform.os.name) + if os.name == 'posix': + if platform.system() == 'Linux': + lsbinfo = os.popen('lsb_release -a').read() + environment += '%s'% lsbinfo + else: + environment += 'Your System is not lsb compliant\n' + environment += 'Operating System Release : %s\n' \ + 'Operating System Version : %s\n' \ + 'Operating System Architecture : %s\n' \ + 'Operating System Locale : %s\n'\ + 'Python Version : %s\n'\ + 'OpenERP-Server Version : %s'\ + %(platform.release(), platform.version(), platform.architecture()[0], + os_lang, platform.python_version(), openerp.release.version) + return environment + +def exp_login_message(): + return openerp.tools.config.get('login_message', False) + +def exp_set_loglevel(loglevel, logger=None): + # TODO Previously, the level was set on the now deprecated + # `openerp.netsvc.Logger` class. + return True + +def exp_get_stats(): + res = "OpenERP server: %d threads\n" % threading.active_count() + res += netsvc.Server.allStats() + return res + +def exp_list_http_services(): + return http_server.list_http_services() + +def exp_check_connectivity(): + return bool(sql_db.db_connect('postgres')) + +def exp_get_os_time(): + return os.times() + +def exp_get_sqlcount(): + if not logging.getLogger('openerp.sql_db').isEnabledFor(logging.DEBUG): + _logger.warning("Counters of SQL will not be reliable unless logger openerp.sql_db is set to level DEBUG or higer.") + return sql_db.sql_counter + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/service/web_services.py b/openerp/service/web_services.py index 3a89f3fb615..8c61c696723 100644 --- a/openerp/service/web_services.py +++ b/openerp/service/web_services.py @@ -36,7 +36,6 @@ from cStringIO import StringIO from openerp.tools.translate import _ import openerp.netsvc as netsvc import openerp.pooler as pooler -import openerp.release as release import openerp.service.model import openerp.sql_db as sql_db import openerp.tools as tools @@ -57,203 +56,6 @@ from openerp import SUPERUSER_ID _logger = logging.getLogger(__name__) -RPC_VERSION_1 = { - 'server_version': release.version, - 'server_version_info': release.version_info, - 'server_serie': release.serie, - 'protocol_version': 1, -} - -class common(netsvc.ExportService): - - def __init__(self,name="common"): - netsvc.ExportService.__init__(self,name) - - def dispatch(self, method, params): - if method in ['login', 'about', 'timezone_get', 'get_server_environment', - 'login_message','get_stats', 'check_connectivity', - 'list_http_services', 'version', 'authenticate']: - pass - elif method in ['get_available_updates', 'get_migration_scripts', 'set_loglevel', 'get_os_time', 'get_sqlcount']: - passwd = params[0] - params = params[1:] - security.check_super(passwd) - else: - raise Exception("Method not found: %s" % method) - - fn = getattr(self, 'exp_'+method) - return fn(*params) - - def exp_login(self, db, login, password): - # TODO: legacy indirection through 'security', should use directly - # the res.users model - res = security.login(db, login, password) - msg = res and 'successful login' or 'bad login or password' - _logger.info("%s from '%s' using database '%s'", msg, login, db.lower()) - return res or False - - def exp_authenticate(self, db, login, password, user_agent_env): - res_users = pooler.get_pool(db).get('res.users') - return res_users.authenticate(db, login, password, user_agent_env) - - def exp_version(self): - return RPC_VERSION_1 - - def exp_about(self, extended=False): - """Return information about the OpenERP Server. - - @param extended: if True then return version info - @return string if extended is False else tuple - """ - - info = _(''' - -OpenERP is an ERP+CRM program for small and medium businesses. - -The whole source code is distributed under the terms of the -GNU Public Licence. - -(c) 2003-TODAY - OpenERP SA''') - - if extended: - return info, release.version - return info - - def exp_timezone_get(self, db, login, password): - return tools.misc.get_server_timezone() - - def exp_get_available_updates(self, contract_id, contract_password): - import openerp.tools.maintenance as tm - try: - rc = tm.remote_contract(contract_id, contract_password) - if not rc.id: - raise tm.RemoteContractException('This contract does not exist or is not active') - - return rc.get_available_updates(rc.id, openerp.modules.get_modules_with_version()) - - except tm.RemoteContractException, e: - raise openerp.osv.orm.except_orm('Migration Error', str(e)) - - - def exp_get_migration_scripts(self, contract_id, contract_password): - import openerp.tools.maintenance as tm - try: - rc = tm.remote_contract(contract_id, contract_password) - if not rc.id: - raise tm.RemoteContractException('This contract does not exist or is not active') - if rc.status != 'full': - raise tm.RemoteContractException('Can not get updates for a partial contract') - - _logger.info('starting migration with contract %s', rc.name) - - zips = rc.retrieve_updates(rc.id, openerp.modules.get_modules_with_version()) - - from shutil import rmtree, copytree, copy - - backup_directory = os.path.join(tools.config['root_path'], 'backup', time.strftime('%Y-%m-%d-%H-%M')) - if zips and not os.path.isdir(backup_directory): - _logger.info('create a new backup directory to store the old modules: %s', backup_directory) - os.makedirs(backup_directory) - - for module in zips: - _logger.info('upgrade module %s', module) - mp = openerp.modules.get_module_path(module) - if mp: - if os.path.isdir(mp): - copytree(mp, os.path.join(backup_directory, module)) - if os.path.islink(mp): - os.unlink(mp) - else: - rmtree(mp) - else: - copy(mp + 'zip', backup_directory) - os.unlink(mp + '.zip') - - try: - try: - base64_decoded = base64.decodestring(zips[module]) - except Exception: - _logger.error('unable to read the module %s', module) - raise - - zip_contents = StringIO(base64_decoded) - zip_contents.seek(0) - try: - try: - tools.extract_zip_file(zip_contents, tools.config['addons_path'] ) - except Exception: - _logger.error('unable to extract the module %s', module) - rmtree(module) - raise - finally: - zip_contents.close() - except Exception: - _logger.error('restore the previous version of the module %s', module) - nmp = os.path.join(backup_directory, module) - if os.path.isdir(nmp): - copytree(nmp, tools.config['addons_path']) - else: - copy(nmp+'.zip', tools.config['addons_path']) - raise - - return True - except tm.RemoteContractException, e: - raise openerp.osv.orm.except_orm('Migration Error', str(e)) - except Exception, e: - _logger.exception('Exception in get_migration_script:') - raise - - def exp_get_server_environment(self): - os_lang = '.'.join( [x for x in locale.getdefaultlocale() if x] ) - if not os_lang: - os_lang = 'NOT SET' - environment = '\nEnvironment Information : \n' \ - 'System : %s\n' \ - 'OS Name : %s\n' \ - %(platform.platform(), platform.os.name) - if os.name == 'posix': - if platform.system() == 'Linux': - lsbinfo = os.popen('lsb_release -a').read() - environment += '%s'% lsbinfo - else: - environment += 'Your System is not lsb compliant\n' - environment += 'Operating System Release : %s\n' \ - 'Operating System Version : %s\n' \ - 'Operating System Architecture : %s\n' \ - 'Operating System Locale : %s\n'\ - 'Python Version : %s\n'\ - 'OpenERP-Server Version : %s'\ - %(platform.release(), platform.version(), platform.architecture()[0], - os_lang, platform.python_version(),release.version) - return environment - - def exp_login_message(self): - return tools.config.get('login_message', False) - - def exp_set_loglevel(self, loglevel, logger=None): - # TODO Previously, the level was set on the now deprecated - # `openerp.netsvc.Logger` class. - return True - - def exp_get_stats(self): - res = "OpenERP server: %d threads\n" % threading.active_count() - res += netsvc.Server.allStats() - return res - - def exp_list_http_services(self): - return http_server.list_http_services() - - def exp_check_connectivity(self): - return bool(sql_db.db_connect('postgres')) - - def exp_get_os_time(self): - return os.times() - - def exp_get_sqlcount(self): - if not logging.getLogger('openerp.sql_db').isEnabledFor(logging.DEBUG): - _logger.warning("Counters of SQL will not be reliable unless logger openerp.sql_db is set to level DEBUG or higer.") - return sql_db.sql_counter - # # TODO: set a maximum report number per user to avoid DOS attacks @@ -392,7 +194,6 @@ class report_spool(netsvc.ExportService): def start_service(): - common() report_spool() From 4c201d5f99e025c686c44d472d6a7ce0339b28df Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 30 Jan 2013 16:55:01 +0100 Subject: [PATCH 09/34] [IMP] base_calendar: use the new openerp.service.report instead of the report_spool class bzr revid: vmt@openerp.com-20130130155501-6hk2v8p7myde6xc8 --- addons/base_calendar/base_calendar.py | 39 +++++++++++++-------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/addons/base_calendar/base_calendar.py b/addons/base_calendar/base_calendar.py index ab0d8d324b4..ca75c9e20fc 100644 --- a/addons/base_calendar/base_calendar.py +++ b/addons/base_calendar/base_calendar.py @@ -24,12 +24,13 @@ from dateutil import parser from dateutil import rrule from dateutil.relativedelta import relativedelta from openerp.osv import fields, osv -from openerp.service import web_services from openerp.tools.translate import _ import pytz import re import time + from openerp import tools, SUPERUSER_ID +import openerp.service.report months = { 1: "January", 2: "February", 3: "March", 4: "April", \ @@ -1729,27 +1730,25 @@ class ir_model(osv.osv): ir_model() -class virtual_report_spool(web_services.report_spool): +original_exp_report = openerp.service.report.exp_report - def exp_report(self, db, uid, object, ids, data=None, context=None): - """ - Export Report - @param self: The object pointer - @param db: get the current database, - @param uid: the current user's ID for security checks, - @param context: A standard dictionary for contextual values - """ +def exp_report(db, uid, object, ids, data=None, context=None): + """ + Export Report + @param db: get the current database, + @param uid: the current user's ID for security checks, + @param context: A standard dictionary for contextual values + """ - if object == 'printscreen.list': - return super(virtual_report_spool, self).exp_report(db, uid, \ - object, ids, data, context) - new_ids = [] - for id in ids: - new_ids.append(base_calendar_id2real_id(id)) - if data.get('id', False): - data['id'] = base_calendar_id2real_id(data['id']) - return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, data, context) + if object == 'printscreen.list': + original_exp_report(db, uid, object, ids, data, context) + new_ids = [] + for id in ids: + new_ids.append(base_calendar_id2real_id(id)) + if data.get('id', False): + data['id'] = base_calendar_id2real_id(data['id']) + return original_exp_report(db, uid, object, new_ids, data, context) -virtual_report_spool() +openerp.service.report.exp_report = exp_report # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 4aacad4abf042ce706d1c8e99e734084df7163d8 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 30 Jan 2013 16:56:24 +0100 Subject: [PATCH 10/34] [REF] services: moved the report service to openerp.service.report. bzr revid: vmt@openerp.com-20130130155624-u3vfqwd3komo3wfs --- openerp/loglevels.py | 4 +- openerp/netsvc.py | 2 + openerp/service/__init__.py | 6 +- openerp/service/db.py | 10 +- openerp/service/report.py | 143 +++++++++++++++++++++++ openerp/service/web_services.py | 200 -------------------------------- 6 files changed, 152 insertions(+), 213 deletions(-) create mode 100644 openerp/service/report.py delete mode 100644 openerp/service/web_services.py diff --git a/openerp/loglevels.py b/openerp/loglevels.py index 75dca53bb6a..3172a2b2042 100644 --- a/openerp/loglevels.py +++ b/openerp/loglevels.py @@ -46,7 +46,7 @@ class Logger(object): _logger.warning( "notifyChannel API shouldn't be used anymore, please use " "the standard `logging` module instead.") - from service.web_services import common + from service import common log = logging.getLogger(__name__ + '.deprecated.' + ustr(name)) @@ -63,7 +63,7 @@ class Logger(object): try: msg = ustr(msg).strip() if level in (LOG_ERROR, LOG_CRITICAL): # and tools.config.get_misc('debug','env_info',False): - msg = common().exp_get_server_environment() + "\n" + msg + msg = common.exp_get_server_environment() + "\n" + msg result = msg.split('\n') except UnicodeDecodeError: diff --git a/openerp/netsvc.py b/openerp/netsvc.py index 374fc9a2f4d..fe0dfd06344 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -119,6 +119,8 @@ class ExportService(object): return openerp.service.db if name == 'common': return openerp.service.common + if name == 'report': + return openerp.service.report return cls._services[name] # Dispatch a RPC call w.r.t. the method name. The dispatching diff --git a/openerp/service/__init__.py b/openerp/service/__init__.py index 06bf53503d7..9db73223029 100644 --- a/openerp/service/__init__.py +++ b/openerp/service/__init__.py @@ -29,8 +29,6 @@ import threading import time import cron -import web_services -import web_services import wsgi_server import openerp.modules @@ -42,6 +40,7 @@ import openerp.tools import common import db import model +import report #.apidoc title: RPC Services @@ -78,9 +77,6 @@ def start_internal(): openerp.netsvc.init_logger() openerp.modules.loading.open_openerp_namespace() - # Export (for RPC) services. - web_services.start_service() - load_server_wide_modules() start_internal_done = True diff --git a/openerp/service/db.py b/openerp/service/db.py index 2bc14084f59..2e03a56e3c3 100644 --- a/openerp/service/db.py +++ b/openerp/service/db.py @@ -204,7 +204,6 @@ def _set_pg_password_in_environment(): def exp_dump(db_name): - logger = logging.getLogger('openerp.service.web_services.db.dump') with _set_pg_password_in_environment(): cmd = ['pg_dump', '--format=c', '--no-owner'] if openerp.tools.config['db_user']: @@ -221,20 +220,19 @@ def exp_dump(db_name): res = stdout.close() if not data or res: - logger.error( + _logger.error( 'DUMP DB: %s failed! Please verify the configuration of the database password on the server. ' 'It should be provided as a -w command-line option, or as `db_password` in the ' 'server configuration file.\n %s', db_name, data) raise Exception, "Couldn't dump database" - logger.info('DUMP DB successful: %s', db_name) + _logger.info('DUMP DB successful: %s', db_name) return base64.encodestring(data) def exp_restore(db_name, data): - logger = logging.getLogger('openerp.service.web_services.db.restore') with _set_pg_password_in_environment(): if exp_db_exist(db_name): - logger.warning('RESTORE DB: %s already exists', db_name) + _logger.warning('RESTORE DB: %s already exists', db_name) raise Exception, "Database already exists" _create_empty_database(db_name) @@ -263,7 +261,7 @@ def exp_restore(db_name, data): res = stdout.close() if res: raise Exception, "Couldn't restore database" - logger.info('RESTORE DB: %s', db_name) + _logger.info('RESTORE DB: %s', db_name) return True diff --git a/openerp/service/report.py b/openerp/service/report.py new file mode 100644 index 00000000000..520faa8ead5 --- /dev/null +++ b/openerp/service/report.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -*- + +import logging +import threading + +import openerp.netsvc +import openerp.pooler + +_logger = logging.getLogger(__name__) + +# TODO: set a maximum report number per user to avoid DOS attacks +# +# Report state: +# False -> True + +self_reports = {} +self_id = 0 +self_id_protect = threading.Semaphore() + +def dispatch(method, params): + (db, uid, passwd ) = params[0:3] + threading.current_thread().uid = uid + params = params[3:] + if method not in ['report', 'report_get', 'render_report']: + raise KeyError("Method not supported %s" % method) + security.check(db,uid,passwd) + openerp.modules.registry.RegistryManager.check_registry_signaling(db) + fn = globals()['exp_' + method] + res = fn(db, uid, *params) + openerp.modules.registry.RegistryManager.signal_caches_change(db) + return res + +def exp_render_report(db, uid, object, ids, datas=None, context=None): + if not datas: + datas={} + if not context: + context={} + + self_id_protect.acquire() + global self_id + self_id += 1 + id = self_id + self_id_protect.release() + + self_reports[id] = {'uid': uid, 'result': False, 'state': False, 'exception': None} + + cr = openerp.pooler.get_db(db).cursor() + try: + obj = openerp.netsvc.LocalService('report.'+object) + (result, format) = obj.create(cr, uid, ids, datas, context) + if not result: + tb = sys.exc_info() + self_reports[id]['exception'] = openerp.exceptions.DeferredException('RML is not available at specified location or not enough data to print!', tb) + self_reports[id]['result'] = result + self_reports[id]['format'] = format + self_reports[id]['state'] = True + except Exception, exception: + + _logger.exception('Exception: %s\n', exception) + if hasattr(exception, 'name') and hasattr(exception, 'value'): + self_reports[id]['exception'] = openerp.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value)) + else: + tb = sys.exc_info() + self_reports[id]['exception'] = openerp.exceptions.DeferredException(tools.exception_to_unicode(exception), tb) + self_reports[id]['state'] = True + cr.commit() + cr.close() + + return _check_report(id) + +def exp_report(db, uid, object, ids, datas=None, context=None): + if not datas: + datas={} + if not context: + context={} + + self_id_protect.acquire() + global self_id + self_id += 1 + id = self_id + self_id_protect.release() + + self_reports[id] = {'uid': uid, 'result': False, 'state': False, 'exception': None} + + def go(id, uid, ids, datas, context): + cr = openerp.pooler.get_db(db).cursor() + try: + obj = openerp.netsvc.LocalService('report.'+object) + (result, format) = obj.create(cr, uid, ids, datas, context) + if not result: + tb = sys.exc_info() + self_reports[id]['exception'] = openerp.exceptions.DeferredException('RML is not available at specified location or not enough data to print!', tb) + self_reports[id]['result'] = result + self_reports[id]['format'] = format + self_reports[id]['state'] = True + except Exception, exception: + _logger.exception('Exception: %s\n', exception) + if hasattr(exception, 'name') and hasattr(exception, 'value'): + self_reports[id]['exception'] = openerp.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value)) + else: + tb = sys.exc_info() + self_reports[id]['exception'] = openerp.exceptions.DeferredException(tools.exception_to_unicode(exception), tb) + self_reports[id]['state'] = True + cr.commit() + cr.close() + return True + + thread.start_new_thread(go, (id, uid, ids, datas, context)) + return id + +def _check_report(report_id): + result = self_reports[report_id] + exc = result['exception'] + if exc: + raise openerp.osv.orm.except_orm(exc.message, exc.traceback) + res = {'state': result['state']} + if res['state']: + if tools.config['reportgz']: + import zlib + res2 = zlib.compress(result['result']) + res['code'] = 'zlib' + else: + #CHECKME: why is this needed??? + if isinstance(result['result'], unicode): + res2 = result['result'].encode('latin1', 'replace') + else: + res2 = result['result'] + if res2: + res['result'] = base64.encodestring(res2) + res['format'] = result['format'] + del self_reports[report_id] + return res + +def exp_report_get(db, uid, report_id): + if report_id in self_reports: + if self_reports[report_id]['uid'] == uid: + return _check_report(report_id) + else: + raise Exception, 'AccessDenied' + else: + raise Exception, 'ReportNotFound' + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/service/web_services.py b/openerp/service/web_services.py deleted file mode 100644 index 8c61c696723..00000000000 --- a/openerp/service/web_services.py +++ /dev/null @@ -1,200 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (). -# -# 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 . -# -############################################################################## -from __future__ import with_statement -import contextlib -import base64 -import locale -import logging -import os -import platform -import security -import sys -import thread -import threading -import time -import traceback -from cStringIO import StringIO - -from openerp.tools.translate import _ -import openerp.netsvc as netsvc -import openerp.pooler as pooler -import openerp.service.model -import openerp.sql_db as sql_db -import openerp.tools as tools -import openerp.modules -import openerp.exceptions -import openerp.osv.orm # TODO use openerp.exceptions -from openerp.service import http_server -from openerp import SUPERUSER_ID - -#.apidoc title: Exported Service methods -#.apidoc module-mods: member-order: bysource - -""" This python module defines the RPC methods available to remote clients. - - Each 'Export Service' is a group of 'methods', which in turn are RPC - procedures to be called. Each method has its own arguments footprint. -""" - -_logger = logging.getLogger(__name__) - - -# -# TODO: set a maximum report number per user to avoid DOS attacks -# -# Report state: -# False -> True -# - -class report_spool(netsvc.ExportService): - def __init__(self, name='report'): - netsvc.ExportService.__init__(self, name) - self._reports = {} - self.id = 0 - self.id_protect = threading.Semaphore() - - def dispatch(self, method, params): - (db, uid, passwd ) = params[0:3] - threading.current_thread().uid = uid - params = params[3:] - if method not in ['report', 'report_get', 'render_report']: - raise KeyError("Method not supported %s" % method) - security.check(db,uid,passwd) - openerp.modules.registry.RegistryManager.check_registry_signaling(db) - fn = getattr(self, 'exp_' + method) - res = fn(db, uid, *params) - openerp.modules.registry.RegistryManager.signal_caches_change(db) - return res - - def exp_render_report(self, db, uid, object, ids, datas=None, context=None): - if not datas: - datas={} - if not context: - context={} - - self.id_protect.acquire() - self.id += 1 - id = self.id - self.id_protect.release() - - self._reports[id] = {'uid': uid, 'result': False, 'state': False, 'exception': None} - - cr = pooler.get_db(db).cursor() - try: - obj = netsvc.LocalService('report.'+object) - (result, format) = obj.create(cr, uid, ids, datas, context) - if not result: - tb = sys.exc_info() - self._reports[id]['exception'] = openerp.exceptions.DeferredException('RML is not available at specified location or not enough data to print!', tb) - self._reports[id]['result'] = result - self._reports[id]['format'] = format - self._reports[id]['state'] = True - except Exception, exception: - - _logger.exception('Exception: %s\n', exception) - if hasattr(exception, 'name') and hasattr(exception, 'value'): - self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value)) - else: - tb = sys.exc_info() - self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.exception_to_unicode(exception), tb) - self._reports[id]['state'] = True - cr.commit() - cr.close() - - return self._check_report(id) - - def exp_report(self, db, uid, object, ids, datas=None, context=None): - if not datas: - datas={} - if not context: - context={} - - self.id_protect.acquire() - self.id += 1 - id = self.id - self.id_protect.release() - - self._reports[id] = {'uid': uid, 'result': False, 'state': False, 'exception': None} - - def go(id, uid, ids, datas, context): - cr = pooler.get_db(db).cursor() - try: - obj = netsvc.LocalService('report.'+object) - (result, format) = obj.create(cr, uid, ids, datas, context) - if not result: - tb = sys.exc_info() - self._reports[id]['exception'] = openerp.exceptions.DeferredException('RML is not available at specified location or not enough data to print!', tb) - self._reports[id]['result'] = result - self._reports[id]['format'] = format - self._reports[id]['state'] = True - except Exception, exception: - _logger.exception('Exception: %s\n', exception) - if hasattr(exception, 'name') and hasattr(exception, 'value'): - self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value)) - else: - tb = sys.exc_info() - self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.exception_to_unicode(exception), tb) - self._reports[id]['state'] = True - cr.commit() - cr.close() - return True - - thread.start_new_thread(go, (id, uid, ids, datas, context)) - return id - - def _check_report(self, report_id): - result = self._reports[report_id] - exc = result['exception'] - if exc: - raise openerp.osv.orm.except_orm(exc.message, exc.traceback) - res = {'state': result['state']} - if res['state']: - if tools.config['reportgz']: - import zlib - res2 = zlib.compress(result['result']) - res['code'] = 'zlib' - else: - #CHECKME: why is this needed??? - if isinstance(result['result'], unicode): - res2 = result['result'].encode('latin1', 'replace') - else: - res2 = result['result'] - if res2: - res['result'] = base64.encodestring(res2) - res['format'] = result['format'] - del self._reports[report_id] - return res - - def exp_report_get(self, db, uid, report_id): - if report_id in self._reports: - if self._reports[report_id]['uid'] == uid: - return self._check_report(report_id) - else: - raise Exception, 'AccessDenied' - else: - raise Exception, 'ReportNotFound' - - -def start_service(): - report_spool() - - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 05cb1f04eb700837de42b94590c437543dec3b02 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 30 Jan 2013 17:31:03 +0100 Subject: [PATCH 11/34] [REF] edi: kill the edi service (need some refactoring later). bzr revid: vmt@openerp.com-20130130163103-6xawlt5774rbl1f2 --- addons/edi/edi_service.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/addons/edi/edi_service.py b/addons/edi/edi_service.py index fd911139170..0de44537f55 100644 --- a/addons/edi/edi_service.py +++ b/addons/edi/edi_service.py @@ -21,14 +21,15 @@ import logging import openerp -import openerp.netsvc as netsvc _logger = logging.getLogger(__name__) -class edi(netsvc.ExportService): - - def __init__(self, name="edi"): - netsvc.ExportService.__init__(self, name) +# TODO this is not needed anymore: +# - the exposed new service just forward to the model service +# - the service is called by the web controller, which can +# now directly call into openerp as the web server is always +# embedded in openerp. +class edi(): def _edi_dispatch(self, db_name, method_name, *method_args): try: @@ -61,5 +62,4 @@ class edi(netsvc.ExportService): fn = getattr(self, 'exp_'+method) return fn(*params) -edi() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From f2238e06f8c307b96306c9fb2815535f61486916 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 30 Jan 2013 17:45:58 +0100 Subject: [PATCH 12/34] [REF] document_ftp, document_webdav: kill the ExportService concept. bzr revid: vmt@openerp.com-20130130164558-3i13omizumvo9f13 --- addons/document_ftp/ftpserver/abstracted_fs.py | 3 ++- addons/document_webdav/dav_fs.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index a9efac5819c..b746f189700 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -10,6 +10,7 @@ import glob import fnmatch from openerp import pooler, netsvc, sql_db +import openerp.service from openerp.service import security from openerp.osv import osv @@ -60,7 +61,7 @@ class abstracted_fs(object): def db_list(self): """Get the list of available databases, with FTPd support """ - s = netsvc.ExportService.getService('db') + s = openerp.service.db result = s.exp_list(document=True) self.db_name_list = [] for db_name in result: diff --git a/addons/document_webdav/dav_fs.py b/addons/document_webdav/dav_fs.py index e5756f5df91..1781a02d76e 100644 --- a/addons/document_webdav/dav_fs.py +++ b/addons/document_webdav/dav_fs.py @@ -38,6 +38,7 @@ except ImportError: import openerp from openerp import pooler, sql_db, netsvc +import openerp.service from openerp.tools import misc from cache import memoize @@ -372,7 +373,7 @@ class openerp_dav_handler(dav_interface): @memoize(4) def _all_db_list(self): """return all databases who have module document_webdav installed""" - s = netsvc.ExportService.getService('db') + s = openerp.service.db result = s.exp_list() self.db_name_list=[] for db_name in result: From 2c2b6d9506721c44a49860b1b619d82d453ecd0f Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 30 Jan 2013 17:58:12 +0100 Subject: [PATCH 13/34] [REF] service: killed the ExportService concept. bzr revid: vmt@openerp.com-20130130165812-0hzfnfudi8im0usb --- openerp/netsvc.py | 43 +++++++++----------------------------- openerp/service/workers.py | 2 +- 2 files changed, 11 insertions(+), 34 deletions(-) diff --git a/openerp/netsvc.py b/openerp/netsvc.py index fe0dfd06344..434b5443ac5 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -96,38 +96,6 @@ def LocalService(name): return Service._services[name] -class ExportService(object): - """ Proxy for exported services. - - Note that this class has no direct proxy, capable of calling - eservice.method(). Rather, the proxy should call - dispatch(method, params) - """ - - _services = {} - - def __init__(self, name): - ExportService._services[name] = self - self.__name = name - _logger.debug("Registered an exported service: %s" % name) - - @classmethod - def getService(cls,name): - if name == 'object': - return openerp.service.model - if name == 'db': - return openerp.service.db - if name == 'common': - return openerp.service.common - if name == 'report': - return openerp.service.report - return cls._services[name] - - # Dispatch a RPC call w.r.t. the method name. The dispatching - # w.r.t. the service (this class) is done by OpenERPDispatcher. - def dispatch(self, method, params): - raise Exception("stub dispatch at %s" % self.__name) - BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, _NOTHING, DEFAULT = range(10) #The background is set with 40 plus the number of the color, and the foreground with 30 #These are the sequences need to get colored ouput @@ -292,7 +260,16 @@ def dispatch_rpc(service_name, method, params): threading.current_thread().uid = None threading.current_thread().dbname = None - result = ExportService.getService(service_name).dispatch(method, params) + service = None + if service_name == 'object': + service = openerp.service.model + if service_name == 'db': + service = openerp.service.db + if service_name == 'common': + service = openerp.service.common + if service_name == 'report': + service = openerp.service.report + result = service.dispatch(method, params) if rpc_request_flag or rpc_response_flag: end_time = time.time() diff --git a/openerp/service/workers.py b/openerp/service/workers.py index ed77b28cf1b..65cdd5258d7 100644 --- a/openerp/service/workers.py +++ b/openerp/service/workers.py @@ -361,7 +361,7 @@ class WorkerCron(Worker): if config['db_name']: db_names = config['db_name'].split(',') else: - db_names = openerp.netsvc.ExportService._services['db'].exp_list(True) + db_names = openerp.service.db.exp_list(True) for db_name in db_names: if rpc_request_flag: start_time = time.time() From 8b5e317eabd3de78b87640317374c28306ab8829 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 30 Jan 2013 18:37:59 +0100 Subject: [PATCH 14/34] [REF] service: workflow is not a service - simply use openerp.workflow instead of the funky netsvc.LocalService - the funky netsvc.LocalService is still available though - all the methods are now simple function (the self was unnecessary) bzr revid: vmt@openerp.com-20130130173759-xxgyafxqzp3okz3c --- openerp/netsvc.py | 4 +- openerp/workflow/__init__.py | 119 +++++++++++++++++++++++- openerp/workflow/wkf_service.py | 156 -------------------------------- 3 files changed, 118 insertions(+), 161 deletions(-) delete mode 100644 openerp/workflow/wkf_service.py diff --git a/openerp/netsvc.py b/openerp/netsvc.py index 434b5443ac5..a59ba85cfd4 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -91,8 +91,8 @@ def LocalService(name): # are updated to directly use openerp.osv.osv.service. if name == 'object_proxy': return openerp.service.model - if name == 'db': - return openerp.service.db + if name == 'workflow': + return openerp.workflow return Service._services[name] diff --git a/openerp/workflow/__init__.py b/openerp/workflow/__init__.py index 959a878f208..f823a63fd42 100644 --- a/openerp/workflow/__init__.py +++ b/openerp/workflow/__init__.py @@ -19,10 +19,123 @@ # ############################################################################## -import wkf_service +import instance -#.apidoc title: Workflow objects +wkf_on_create_cache = {} +def clear_cache(cr, uid): + wkf_on_create_cache[cr.dbname]={} + +def trg_write(uid, res_type, res_id, cr): + """ + Reevaluates the specified workflow instance. Thus if any condition for + a transition have been changed in the backend, then running ``trg_write`` + will move the workflow over that transition. + + :param res_type: the model name + :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) + +def trg_trigger(uid, res_type, res_id, cr): + """ + Activate a trigger. + + If a workflow instance is waiting for a trigger from another model, then this + trigger can be activated if its conditions are met. + + :param res_type: the model name + :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) + +def trg_delete(uid, res_type, res_id, cr): + """ + Delete a workflow instance + + :param res_type: the model name + :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) + +def trg_create(uid, res_type, res_id, cr): + """ + Create a new workflow instance + + :param res_type: the model name + :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) + +def trg_validate(uid, res_type, res_id, signal, cr): + """ + Fire a signal on a given workflow instance + + :param res_type: the model name + :param res_id: the model instance id the workflow belongs to + :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 + +def trg_redirect(uid, res_type, res_id, new_rid, cr): + """ + Re-bind a workflow instance to another instance of the same model. + + Make all workitems which are waiting for a (subflow) workflow instance + for the old resource point to the (first active) workflow instance for + the new resource. + + :param res_type: the model name + :param res_id: the model instance id the workflow belongs to + :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)) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/openerp/workflow/wkf_service.py b/openerp/workflow/wkf_service.py deleted file mode 100644 index 2b3ce435e60..00000000000 --- a/openerp/workflow/wkf_service.py +++ /dev/null @@ -1,156 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (). -# -# 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 . -# -############################################################################## -import instance -import openerp.netsvc as netsvc - -class workflow_service(netsvc.Service): - """ - Sometimes you might want to fire a signal or re-evaluate the current state - of a workflow using the service's API. You can access the workflow services - using: - - >>> import netsvc - >>> wf_service = netsvc.LocalService("workflow") - - """ - - def __init__(self, name='workflow'): - netsvc.Service.__init__(self, name) - self.wkf_on_create_cache={} - - def clear_cache(self, cr, uid): - self.wkf_on_create_cache[cr.dbname]={} - - def trg_write(self, uid, res_type, res_id, cr): - """ - Reevaluates the specified workflow instance. Thus if any condition for - a transition have been changed in the backend, then running ``trg_write`` - will move the workflow over that transition. - - :param res_type: the model name - :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) - - def trg_trigger(self, uid, res_type, res_id, cr): - """ - Activate a trigger. - - If a workflow instance is waiting for a trigger from another model, then this - trigger can be activated if its conditions are met. - - :param res_type: the model name - :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) - - def trg_delete(self, uid, res_type, res_id, cr): - """ - Delete a workflow instance - - :param res_type: the model name - :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) - - def trg_create(self, uid, res_type, res_id, cr): - """ - Create a new workflow instance - - :param res_type: the model name - :param res_id: the model instance id to own the created worfklow instance - :param cr: a database cursor - """ - ident = (uid,res_type,res_id) - self.wkf_on_create_cache.setdefault(cr.dbname, {}) - if res_type in self.wkf_on_create_cache[cr.dbname]: - wkf_ids = self.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() - self.wkf_on_create_cache[cr.dbname][res_type] = wkf_ids - for (wkf_id,) in wkf_ids: - instance.create(cr, ident, wkf_id) - - def trg_validate(self, uid, res_type, res_id, signal, cr): - """ - Fire a signal on a given workflow instance - - :param res_type: the model name - :param res_id: the model instance id the workflow belongs to - :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 - - def trg_redirect(self, uid, res_type, res_id, new_rid, cr): - """ - Re-bind a workflow instance to another instance of the same model. - - Make all workitems which are waiting for a (subflow) workflow instance - for the old resource point to the (first active) workflow instance for - the new resource. - - :param res_type: the model name - :param res_id: the model instance id the workflow belongs to - :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)) -workflow_service() - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - From 2910d5b3f6e519165628d6b255be22d67ced9f6b Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 30 Jan 2013 18:42:29 +0100 Subject: [PATCH 15/34] [REF] netsvc: close_socket is only used in wsgi_server, so move it there. bzr revid: vmt@openerp.com-20130130174229-myvtydv03iikt6k3 --- openerp/netsvc.py | 18 ------------------ openerp/service/wsgi_server.py | 21 ++++++++++++++++++++- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/openerp/netsvc.py b/openerp/netsvc.py index a59ba85cfd4..14ee0d67215 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -49,24 +49,6 @@ import openerp _logger = logging.getLogger(__name__) -def close_socket(sock): - """ Closes a socket instance cleanly - - :param sock: the network socket to close - :type sock: socket.socket - """ - try: - sock.shutdown(socket.SHUT_RDWR) - except socket.error, e: - # On OSX, socket shutdowns both sides if any side closes it - # causing an error 57 'Socket is not connected' on shutdown - # of the other side (or something), see - # http://bugs.python.org/issue4397 - # note: stdlib fixed test, not behavior - if e.errno != errno.ENOTCONN or platform.system() not in ['Darwin', 'Windows']: - raise - sock.close() - class Service(object): """ Base class for Local services Functionality here is trusted, no authentication. diff --git a/openerp/service/wsgi_server.py b/openerp/service/wsgi_server.py index b76ac95e4b8..f55f9de6669 100644 --- a/openerp/service/wsgi_server.py +++ b/openerp/service/wsgi_server.py @@ -439,6 +439,25 @@ def stop_service(): """ if httpd: httpd.shutdown() - openerp.netsvc.close_socket(httpd.socket) + close_socket(httpd.socket) + +def close_socket(sock): + """ Closes a socket instance cleanly + + :param sock: the network socket to close + :type sock: socket.socket + """ + try: + sock.shutdown(socket.SHUT_RDWR) + except socket.error, e: + # On OSX, socket shutdowns both sides if any side closes it + # causing an error 57 'Socket is not connected' on shutdown + # of the other side (or something), see + # http://bugs.python.org/issue4397 + # note: stdlib fixed test, not behavior + if e.errno != errno.ENOTCONN or platform.system() not in ['Darwin', 'Windows']: + raise + sock.close() + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 7b789f0865494ef3b4b475393bfda19e19160b9a Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Thu, 31 Jan 2013 14:04:47 +0100 Subject: [PATCH 16/34] [FIX] At the end of the database creation polling, None passwords are no more returned (for e.g. the portaluser). bzr revid: vmt@openerp.com-20130131130447-3obyq2ch2omerypo --- openerp/service/db.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openerp/service/db.py b/openerp/service/db.py index 2e03a56e3c3..7272b82bc83 100644 --- a/openerp/service/db.py +++ b/openerp/service/db.py @@ -140,6 +140,10 @@ def exp_get_progress(id): clean = self_actions[id]['clean'] if clean: users = self_actions[id]['users'] + for user in users: + # Remove the None passwords as they can't be marshalled by XML-RPC. + if user['password'] is None: + user['password'] = '' self_actions.pop(id) return 1.0, users else: From b303bfe5a4bacc5f75cc5710ee9435a6110cf1bb Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Thu, 31 Jan 2013 14:10:51 +0100 Subject: [PATCH 17/34] [REF] service: A new openerp.service.rpc decorator is provided to replace the ExportService class. bzr revid: vmt@openerp.com-20130131131051-5189susswxlshp29 --- doc/index.rst | 1 + doc/routing.rst | 18 ++++++++++++++++++ openerp/netsvc.py | 20 ++++++++++---------- openerp/service/__init__.py | 18 ++++++++++++++++++ openerp/service/wsgi_server.py | 8 ++++++++ openerp/tests/common.py | 1 + openerp/tests/test_xmlrpc.py | 8 ++++++++ 7 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 doc/routing.rst diff --git a/doc/index.rst b/doc/index.rst index 33474e2da57..daa212d07e0 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -35,6 +35,7 @@ OpenERP Server API :maxdepth: 1 api_models.rst + routing.rst Concepts '''''''' diff --git a/doc/routing.rst b/doc/routing.rst new file mode 100644 index 00000000000..1e4f76d25b8 --- /dev/null +++ b/doc/routing.rst @@ -0,0 +1,18 @@ +.. _routing: + +Routing +======= + +The OpenERP framework, as an HTTP server, serves a few hard-coded URLs +(``models``, ``db``, ...) to expose RPC endpoints. When running the web addons +(which is almost always the case), it also serves URLs without them being RPC +endpoints. + +In older version of OpenERP, adding RPC endpoints was done by subclassing the +``openerp.netsvc.ExportService`` class. Adding WSGI handlers was done by +registering them with the ``openerp.wsgi.register_wsgi_handler()`` function. + +Starting with OpenERP 7.1, exposing a new arbitrary WSGI handler is done with +the ``openerp.service.handler`` decorator while adding an RPC endpoint is done +with the ``openerp.service.rpc`` decorator. + diff --git a/openerp/netsvc.py b/openerp/netsvc.py index 14ee0d67215..6b70dc972a8 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -29,7 +29,6 @@ import logging.handlers import os import platform import release -import socket import sys import threading import time @@ -242,16 +241,17 @@ def dispatch_rpc(service_name, method, params): threading.current_thread().uid = None threading.current_thread().dbname = None - service = None - if service_name == 'object': - service = openerp.service.model - if service_name == 'db': - service = openerp.service.db if service_name == 'common': - service = openerp.service.common - if service_name == 'report': - service = openerp.service.report - result = service.dispatch(method, params) + dispatch = openerp.service.common.dispatch + elif service_name == 'db': + dispatch = openerp.service.db.dispatch + elif service_name == 'object': + dispatch = openerp.service.model.dispatch + elif service_name == 'report': + dispatch = openerp.service.report.dispatch + else: + dispatch = openerp.service.wsgi_server.rpc_handlers.get(service_name) + result = dispatch(method, params) if rpc_request_flag or rpc_response_flag: end_time = time.time() diff --git a/openerp/service/__init__.py b/openerp/service/__init__.py index 9db73223029..db5d4542bf4 100644 --- a/openerp/service/__init__.py +++ b/openerp/service/__init__.py @@ -150,4 +150,22 @@ def restart_server(): openerp.phoenix = True os.kill(os.getpid(), signal.SIGINT) +def handler(): + """TODO""" + def decorator(f): + wsgi_server.register_wsgi_handler(f) + return decorator + +def route(url): + """TODO""" + def decorator(f): + pass # TODO Same as handler() but register the handler under a specific url. + return decorator + +def rpc(endpoint): + """TODO""" + def decorator(f): + wsgi_server.register_rpc_endpoint(endpoint, f) + return decorator + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/service/wsgi_server.py b/openerp/service/wsgi_server.py index f55f9de6669..8d9d7b4a83c 100644 --- a/openerp/service/wsgi_server.py +++ b/openerp/service/wsgi_server.py @@ -34,6 +34,7 @@ import errno import logging import os import signal +import socket import sys import threading import traceback @@ -373,6 +374,8 @@ def parse_http_response(s): # WSGI handlers registered through the register_wsgi_handler() function below. module_handlers = [] +# RPC endpoints registered through the register_rpc_endpoint() function below. +rpc_handlers = {} def register_wsgi_handler(handler): """ Register a WSGI handler. @@ -382,6 +385,11 @@ def register_wsgi_handler(handler): """ module_handlers.append(handler) +def register_rpc_endpoint(endpoint, handler): + """ Register a handler for a given RPC enpoint. + """ + rpc_handlers[endpoint] = handler + def application_unproxied(environ, start_response): """ WSGI entry point.""" openerp.service.start_internal() diff --git a/openerp/tests/common.py b/openerp/tests/common.py index 3f0143ef9df..f8472e81fcf 100644 --- a/openerp/tests/common.py +++ b/openerp/tests/common.py @@ -133,6 +133,7 @@ class RpcCase(unittest2.TestCase): self.proxy.common_60 = xmlrpclib.ServerProxy(url_60 + 'common') self.proxy.db_60 = xmlrpclib.ServerProxy(url_60 + 'db') self.proxy.object_60 = xmlrpclib.ServerProxy(url_60 + 'object') + #self.proxy.edi_60 = xmlrpclib.ServerProxy(url_60 + 'edi') # Use the new (6.1) API. self.proxy.url_61 = url_61 = 'http://%s:%d/openerp/xmlrpc/1/' % (HOST, PORT) diff --git a/openerp/tests/test_xmlrpc.py b/openerp/tests/test_xmlrpc.py index 1198c0fa2f2..8c9d6911e7b 100644 --- a/openerp/tests/test_xmlrpc.py +++ b/openerp/tests/test_xmlrpc.py @@ -64,6 +64,14 @@ class test_xmlrpc(common.RpcCase): ids = proxy.execute(ADMIN_USER_ID, ADMIN_PASSWORD, 'search', [], {}) assert ids + # This test was written to test the creation of a new RPC endpoint, not + # really for the EDI itself. + #def test_xmlrpc_import_edi_document(self): + # """ Try to call an EDI method. """ + # msg_re = 'EDI Document is empty!' + # with self.assertRaisesRegexp(Exception, msg_re): + # self.proxy.edi_60.import_edi_document(DB, ADMIN_USER_ID, ADMIN_PASSWORD, {}) + def test_zz_xmlrpc_drop_database(self): """ Simulate a OpenERP client requesting the deletion of a database. From d2d87796576923f35cecdcc49c0a7525a441be20 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Thu, 31 Jan 2013 16:45:15 +0100 Subject: [PATCH 18/34] [REF] edi: use openerp.service.rpc instead of ExportService. bzr revid: vmt@openerp.com-20130131154515-hb2ginvg8f0fvek2 --- addons/edi/edi_service.py | 57 ++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/addons/edi/edi_service.py b/addons/edi/edi_service.py index 0de44537f55..3bd562ac0bb 100644 --- a/addons/edi/edi_service.py +++ b/addons/edi/edi_service.py @@ -29,37 +29,38 @@ _logger = logging.getLogger(__name__) # - the service is called by the web controller, which can # now directly call into openerp as the web server is always # embedded in openerp. -class edi(): - def _edi_dispatch(self, db_name, method_name, *method_args): - try: - registry = openerp.modules.registry.RegistryManager.get(db_name) - assert registry, 'Unknown database %s' % db_name - edi = registry['edi.edi'] - cr = registry.db.cursor() - res = None - res = getattr(edi, method_name)(cr, *method_args) - cr.commit() - except Exception: - _logger.exception('Failed to execute EDI method %s with args %r.', method_name, method_args) - raise - finally: - cr.close() - return res +def _edi_dispatch(db_name, method_name, *method_args): + try: + registry = openerp.modules.registry.RegistryManager.get(db_name) + assert registry, 'Unknown database %s' % db_name + edi = registry['edi.edi'] + cr = registry.db.cursor() + res = None + res = getattr(edi, method_name)(cr, *method_args) + cr.commit() + except Exception, e: + _logger.exception('Failed to execute EDI method %s with args %r.', + method_name, method_args) + raise + finally: + cr.close() + return res - def exp_import_edi_document(self, db_name, uid, passwd, edi_document, context=None): - return self._edi_dispatch(db_name, 'import_edi', uid, edi_document, None) +def exp_import_edi_document(db_name, uid, passwd, edi_document, context=None): + return _edi_dispatch(db_name, 'import_edi', uid, edi_document, None) - def exp_import_edi_url(self, db_name, uid, passwd, edi_url, context=None): - return self._edi_dispatch(db_name, 'import_edi', uid, None, edi_url) +def exp_import_edi_url(db_name, uid, passwd, edi_url, context=None): + return _edi_dispatch(db_name, 'import_edi', uid, None, edi_url) - def dispatch(self, method, params): - if method in ['import_edi_document', 'import_edi_url']: - (db, uid, passwd ) = params[0:3] - openerp.service.security.check(db, uid, passwd) - else: - raise KeyError("Method not found: %s." % method) - fn = getattr(self, 'exp_'+method) - return fn(*params) +@openerp.service.rpc('edi') +def dispatch(method, params): + if method in ['import_edi_document', 'import_edi_url']: + (db, uid, passwd) = params[0:3] + openerp.service.security.check(db, uid, passwd) + else: + raise KeyError("Method not found: %s." % method) + fn = globals()['exp_' + method] + return fn(*params) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 0d350a9112130714b0989effaec9a5387be2510f Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Thu, 31 Jan 2013 17:06:25 +0100 Subject: [PATCH 19/34] [REF] audittrail: monkeypatch the openerp.service.model functions. Instead of inheriting from object_proxy, audittrail now wraps the original openerp.service.model functions. This potentially means it is possible to further wrap them (whil inheriting from the object_proxy was feasible by only one module at a time). bzr revid: vmt@openerp.com-20130131160625-dd43wk7q7y1jbwxg --- addons/audittrail/audittrail.py | 646 ++++++++++++++++---------------- 1 file changed, 326 insertions(+), 320 deletions(-) diff --git a/addons/audittrail/audittrail.py b/addons/audittrail/audittrail.py index 7364ea505f6..857f5ff64d0 100644 --- a/addons/audittrail/audittrail.py +++ b/addons/audittrail/audittrail.py @@ -21,6 +21,7 @@ from openerp.osv import fields, osv from openerp.osv.osv import object_proxy +import openerp.service.model from openerp.tools.translate import _ from openerp import pooler import time @@ -171,355 +172,360 @@ class audittrail_log_line(osv.osv): 'field_description': fields.char('Field Description', size=64), } -class audittrail_objects_proxy(object_proxy): - """ Uses Object proxy for auditing changes on object of subscribed Rules""" +# Monkeypatch the model RPC endpoint for auditing changes. - def get_value_text(self, cr, uid, pool, resource_pool, method, field, value): - """ - Gets textual values for the fields. - If the field is a many2one, it returns the name. - If it's a one2many or a many2many, it returns a list of name. - In other cases, it just returns the value. - :param cr: the current row, from the database cursor, - :param uid: the current user’s ID for security checks, - :param pool: current db's pooler object. - :param resource_pool: pooler object of the model which values are being changed. - :param field: for which the text value is to be returned. - :param value: value of the field. - :param recursive: True or False, True will repeat the process recursively - :return: string value or a list of values(for O2M/M2M) - """ +def get_value_text(cr, uid, pool, resource_pool, method, field, value): + """ + Gets textual values for the fields. + If the field is a many2one, it returns the name. + If it's a one2many or a many2many, it returns a list of name. + In other cases, it just returns the value. + :param cr: the current row, from the database cursor, + :param uid: the current user’s ID for security checks, + :param pool: current db's pooler object. + :param resource_pool: pooler object of the model which values are being changed. + :param field: for which the text value is to be returned. + :param value: value of the field. + :param recursive: True or False, True will repeat the process recursively + :return: string value or a list of values(for O2M/M2M) + """ - field_obj = (resource_pool._all_columns.get(field)).column - if field_obj._type in ('one2many','many2many'): - data = pool.get(field_obj._obj).name_get(cr, uid, value) - #return the modifications on x2many fields as a list of names - res = map(lambda x:x[1], data) - elif field_obj._type == 'many2one': - #return the modifications on a many2one field as its value returned by name_get() - res = value and value[1] or value - else: - res = value - return res + field_obj = (resource_pool._all_columns.get(field)).column + if field_obj._type in ('one2many','many2many'): + data = pool.get(field_obj._obj).name_get(cr, uid, value) + #return the modifications on x2many fields as a list of names + res = map(lambda x:x[1], data) + elif field_obj._type == 'many2one': + #return the modifications on a many2one field as its value returned by name_get() + res = value and value[1] or value + else: + res = value + return res - def create_log_line(self, cr, uid, log_id, model, lines=None): - """ - Creates lines for changed fields with its old and new values +def create_log_line(cr, uid, log_id, model, lines=None): + """ + Creates lines for changed fields with its old and new values - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param model: Object which values are being changed - @param lines: List of values for line is to be created - """ - if lines is None: - lines = [] - pool = pooler.get_pool(cr.dbname) - obj_pool = pool.get(model.model) - model_pool = pool.get('ir.model') - field_pool = pool.get('ir.model.fields') - log_line_pool = pool.get('audittrail.log.line') - for line in lines: - field_obj = obj_pool._all_columns.get(line['name']) - assert field_obj, _("'%s' field does not exist in '%s' model" %(line['name'], model.model)) - field_obj = field_obj.column - old_value = line.get('old_value', '') - new_value = line.get('new_value', '') - search_models = [model.id] - if obj_pool._inherits: - search_models += model_pool.search(cr, uid, [('model', 'in', obj_pool._inherits.keys())]) - field_id = field_pool.search(cr, uid, [('name', '=', line['name']), ('model_id', 'in', search_models)]) - if field_obj._type == 'many2one': - old_value = old_value and old_value[0] or old_value - new_value = new_value and new_value[0] or new_value - vals = { - "log_id": log_id, - "field_id": field_id and field_id[0] or False, - "old_value": old_value, - "new_value": new_value, - "old_value_text": line.get('old_value_text', ''), - "new_value_text": line.get('new_value_text', ''), - "field_description": field_obj.string - } - line_id = log_line_pool.create(cr, uid, vals) - return True - - def log_fct(self, cr, uid_orig, model, method, fct_src, *args, **kw): - """ - Logging function: This function is performing the logging operation - @param model: Object whose values are being changed - @param method: method to log: create, read, write, unlink, action or workflow action - @param fct_src: execute method of Object proxy - - @return: Returns result as per method of Object proxy - """ - pool = pooler.get_pool(cr.dbname) - resource_pool = pool.get(model) - model_pool = pool.get('ir.model') - model_ids = model_pool.search(cr, SUPERUSER_ID, [('model', '=', model)]) - model_id = model_ids and model_ids[0] or False - assert model_id, _("'%s' Model does not exist..." %(model)) - model = model_pool.browse(cr, SUPERUSER_ID, model_id) - - # fields to log. currently only used by log on read() - field_list = [] - old_values = new_values = {} - - if method == 'create': - res = fct_src(cr, uid_orig, model.model, method, *args, **kw) - if res: - res_ids = [res] - new_values = self.get_data(cr, uid_orig, pool, res_ids, model, method) - elif method == 'read': - res = fct_src(cr, uid_orig, model.model, method, *args, **kw) - # build the res_ids and the old_values dict. Here we don't use get_data() to - # avoid performing an additional read() - res_ids = [] - for record in res: - res_ids.append(record['id']) - old_values[(model.id, record['id'])] = {'value': record, 'text': record} - # log only the fields read - field_list = args[1] - elif method == 'unlink': - res_ids = args[0] - old_values = self.get_data(cr, uid_orig, pool, res_ids, model, method) - res = fct_src(cr, uid_orig, model.model, method, *args, **kw) - else: # method is write, action or workflow action - res_ids = [] - if args: - res_ids = args[0] - if isinstance(res_ids, (long, int)): - res_ids = [res_ids] - if res_ids: - # store the old values into a dictionary - old_values = self.get_data(cr, uid_orig, pool, res_ids, model, method) - # process the original function, workflow trigger... - res = fct_src(cr, uid_orig, model.model, method, *args, **kw) - if method == 'copy': - res_ids = [res] - if res_ids: - # check the new values and store them into a dictionary - new_values = self.get_data(cr, uid_orig, pool, res_ids, model, method) - # compare the old and new values and create audittrail log if needed - self.process_data(cr, uid_orig, pool, res_ids, model, method, old_values, new_values, field_list) - return res - - def get_data(self, cr, uid, pool, res_ids, model, method): - """ - This function simply read all the fields of the given res_ids, and also recurisvely on - all records of a x2m fields read that need to be logged. Then it returns the result in - convenient structure that will be used as comparison basis. - - :param cr: the current row, from the database cursor, - :param uid: the current user’s ID. This parameter is currently not used as every - operation to get data is made as super admin. Though, it could be usefull later. - :param pool: current db's pooler object. - :param res_ids: Id's of resource to be logged/compared. - :param model: Object whose values are being changed - :param method: method to log: create, read, unlink, write, actions, workflow actions - :return: dict mapping a tuple (model_id, resource_id) with its value and textual value - { (model_id, resource_id): { 'value': ... - 'textual_value': ... - }, + @param cr: the current row, from the database cursor, + @param uid: the current user’s ID for security checks, + @param model: Object which values are being changed + @param lines: List of values for line is to be created + """ + if lines is None: + lines = [] + pool = pooler.get_pool(cr.dbname) + obj_pool = pool.get(model.model) + model_pool = pool.get('ir.model') + field_pool = pool.get('ir.model.fields') + log_line_pool = pool.get('audittrail.log.line') + for line in lines: + field_obj = obj_pool._all_columns.get(line['name']) + assert field_obj, _("'%s' field does not exist in '%s' model" %(line['name'], model.model)) + field_obj = field_obj.column + old_value = line.get('old_value', '') + new_value = line.get('new_value', '') + search_models = [model.id] + if obj_pool._inherits: + search_models += model_pool.search(cr, uid, [('model', 'in', obj_pool._inherits.keys())]) + field_id = field_pool.search(cr, uid, [('name', '=', line['name']), ('model_id', 'in', search_models)]) + if field_obj._type == 'many2one': + old_value = old_value and old_value[0] or old_value + new_value = new_value and new_value[0] or new_value + vals = { + "log_id": log_id, + "field_id": field_id and field_id[0] or False, + "old_value": old_value, + "new_value": new_value, + "old_value_text": line.get('old_value_text', ''), + "new_value_text": line.get('new_value_text', ''), + "field_description": field_obj.string } - """ - data = {} - resource_pool = pool.get(model.model) - # read all the fields of the given resources in super admin mode - for resource in resource_pool.read(cr, SUPERUSER_ID, res_ids): - values = {} - values_text = {} - resource_id = resource['id'] - # loop on each field on the res_ids we just have read - for field in resource: - if field in ('__last_update', 'id'): - continue - values[field] = resource[field] - # get the textual value of that field for this record - values_text[field] = self.get_value_text(cr, SUPERUSER_ID, pool, resource_pool, method, field, resource[field]) + line_id = log_line_pool.create(cr, uid, vals) + return True - field_obj = resource_pool._all_columns.get(field).column - if field_obj._type in ('one2many','many2many'): - # check if an audittrail rule apply in super admin mode - if self.check_rules(cr, SUPERUSER_ID, field_obj._obj, method): - # check if the model associated to a *2m field exists, in super admin mode - x2m_model_ids = pool.get('ir.model').search(cr, SUPERUSER_ID, [('model', '=', field_obj._obj)]) - x2m_model_id = x2m_model_ids and x2m_model_ids[0] or False - assert x2m_model_id, _("'%s' Model does not exist..." %(field_obj._obj)) - x2m_model = pool.get('ir.model').browse(cr, SUPERUSER_ID, x2m_model_id) - field_resource_ids = list(set(resource[field])) - if model.model == x2m_model.model: - # we need to remove current resource_id from the many2many to prevent an infinit loop - if resource_id in field_resource_ids: - field_resource_ids.remove(resource_id) - data.update(self.get_data(cr, SUPERUSER_ID, pool, field_resource_ids, x2m_model, method)) - - data[(model.id, resource_id)] = {'text':values_text, 'value': values} - return data +def log_fct(cr, uid_orig, model, method, fct_src, *args, **kw): + """ + Logging function: This function is performing the logging operation + @param model: Object whose values are being changed + @param method: method to log: create, read, write, unlink, action or workflow action + @param fct_src: execute method of Object proxy - def prepare_audittrail_log_line(self, cr, uid, pool, model, resource_id, method, old_values, new_values, field_list=None): - """ - This function compares the old data (i.e before the method was executed) and the new data - (after the method was executed) and returns a structure with all the needed information to - log those differences. + @return: Returns result as per method of Object proxy + """ + pool = pooler.get_pool(cr.dbname) + resource_pool = pool.get(model) + model_pool = pool.get('ir.model') + model_ids = model_pool.search(cr, SUPERUSER_ID, [('model', '=', model)]) + model_id = model_ids and model_ids[0] or False + assert model_id, _("'%s' Model does not exist..." %(model)) + model = model_pool.browse(cr, SUPERUSER_ID, model_id) + + # fields to log. currently only used by log on read() + field_list = [] + old_values = new_values = {} + + if method == 'create': + res = fct_src(cr, uid_orig, model.model, method, *args, **kw) + if res: + res_ids = [res] + new_values = get_data(cr, uid_orig, pool, res_ids, model, method) + elif method == 'read': + res = fct_src(cr, uid_orig, model.model, method, *args, **kw) + # build the res_ids and the old_values dict. Here we don't use get_data() to + # avoid performing an additional read() + res_ids = [] + for record in res: + res_ids.append(record['id']) + old_values[(model.id, record['id'])] = {'value': record, 'text': record} + # log only the fields read + field_list = args[1] + elif method == 'unlink': + res_ids = args[0] + old_values = get_data(cr, uid_orig, pool, res_ids, model, method) + res = fct_src(cr, uid_orig, model.model, method, *args, **kw) + else: # method is write, action or workflow action + res_ids = [] + if args: + res_ids = args[0] + if isinstance(res_ids, (long, int)): + res_ids = [res_ids] + if res_ids: + # store the old values into a dictionary + old_values = get_data(cr, uid_orig, pool, res_ids, model, method) + # process the original function, workflow trigger... + res = fct_src(cr, uid_orig, model.model, method, *args, **kw) + if method == 'copy': + res_ids = [res] + if res_ids: + # check the new values and store them into a dictionary + new_values = get_data(cr, uid_orig, pool, res_ids, model, method) + # compare the old and new values and create audittrail log if needed + process_data(cr, uid_orig, pool, res_ids, model, method, old_values, new_values, field_list) + return res + +def get_data(cr, uid, pool, res_ids, model, method): + """ + This function simply read all the fields of the given res_ids, and also recurisvely on + all records of a x2m fields read that need to be logged. Then it returns the result in + convenient structure that will be used as comparison basis. :param cr: the current row, from the database cursor, :param uid: the current user’s ID. This parameter is currently not used as every operation to get data is made as super admin. Though, it could be usefull later. :param pool: current db's pooler object. - :param model: model object which values are being changed - :param resource_id: ID of record to which values are being changed + :param res_ids: Id's of resource to be logged/compared. + :param model: Object whose values are being changed :param method: method to log: create, read, unlink, write, actions, workflow actions - :param old_values: dict of values read before execution of the method - :param new_values: dict of values read after execution of the method - :param field_list: optional argument containing the list of fields to log. Currently only - used when performing a read, it could be usefull later on if we want to log the write - on specific fields only. - - :return: dictionary with - * keys: tuples build as ID of model object to log and ID of resource to log - * values: list of all the changes in field values for this couple (model, resource) - return { - (model.id, resource_id): [] - } - - The reason why the structure returned is build as above is because when modifying an existing - record, we may have to log a change done in a x2many field of that object - """ - if field_list is None: - field_list = [] - key = (model.id, resource_id) - lines = { - key: [] - } - # loop on all the fields - for field_name, field_definition in pool.get(model.model)._all_columns.items(): - if field_name in ('__last_update', 'id'): + :return: dict mapping a tuple (model_id, resource_id) with its value and textual value + { (model_id, resource_id): { 'value': ... + 'textual_value': ... + }, + } + """ + data = {} + resource_pool = pool.get(model.model) + # read all the fields of the given resources in super admin mode + for resource in resource_pool.read(cr, SUPERUSER_ID, res_ids): + values = {} + values_text = {} + resource_id = resource['id'] + # loop on each field on the res_ids we just have read + for field in resource: + if field in ('__last_update', 'id'): continue - #if the field_list param is given, skip all the fields not in that list - if field_list and field_name not in field_list: - continue - field_obj = field_definition.column + values[field] = resource[field] + # get the textual value of that field for this record + values_text[field] = get_value_text(cr, SUPERUSER_ID, pool, resource_pool, method, field, resource[field]) + + field_obj = resource_pool._all_columns.get(field).column if field_obj._type in ('one2many','many2many'): - # checking if an audittrail rule apply in super admin mode - if self.check_rules(cr, SUPERUSER_ID, field_obj._obj, method): - # checking if the model associated to a *2m field exists, in super admin mode + # check if an audittrail rule apply in super admin mode + if check_rules(cr, SUPERUSER_ID, field_obj._obj, method): + # check if the model associated to a *2m field exists, in super admin mode x2m_model_ids = pool.get('ir.model').search(cr, SUPERUSER_ID, [('model', '=', field_obj._obj)]) x2m_model_id = x2m_model_ids and x2m_model_ids[0] or False assert x2m_model_id, _("'%s' Model does not exist..." %(field_obj._obj)) x2m_model = pool.get('ir.model').browse(cr, SUPERUSER_ID, x2m_model_id) - # the resource_ids that need to be checked are the sum of both old and previous values (because we - # need to log also creation or deletion in those lists). - x2m_old_values_ids = old_values.get(key, {'value': {}})['value'].get(field_name, []) - x2m_new_values_ids = new_values.get(key, {'value': {}})['value'].get(field_name, []) - # We use list(set(...)) to remove duplicates. - res_ids = list(set(x2m_old_values_ids + x2m_new_values_ids)) + field_resource_ids = list(set(resource[field])) if model.model == x2m_model.model: # we need to remove current resource_id from the many2many to prevent an infinit loop - if resource_id in res_ids: - res_ids.remove(resource_id) - for res_id in res_ids: - lines.update(self.prepare_audittrail_log_line(cr, SUPERUSER_ID, pool, x2m_model, res_id, method, old_values, new_values, field_list)) - # if the value value is different than the old value: record the change - if key not in old_values or key not in new_values or old_values[key]['value'][field_name] != new_values[key]['value'][field_name]: - data = { - 'name': field_name, - 'new_value': key in new_values and new_values[key]['value'].get(field_name), - 'old_value': key in old_values and old_values[key]['value'].get(field_name), - 'new_value_text': key in new_values and new_values[key]['text'].get(field_name), - 'old_value_text': key in old_values and old_values[key]['text'].get(field_name) - } - lines[key].append(data) - return lines + if resource_id in field_resource_ids: + field_resource_ids.remove(resource_id) + data.update(get_data(cr, SUPERUSER_ID, pool, field_resource_ids, x2m_model, method)) - def process_data(self, cr, uid, pool, res_ids, model, method, old_values=None, new_values=None, field_list=None): - """ - This function processes and iterates recursively to log the difference between the old - data (i.e before the method was executed) and the new data and creates audittrail log - accordingly. + data[(model.id, resource_id)] = {'text':values_text, 'value': values} + return data - :param cr: the current row, from the database cursor, - :param uid: the current user’s ID, - :param pool: current db's pooler object. - :param res_ids: Id's of resource to be logged/compared. - :param model: model object which values are being changed - :param method: method to log: create, read, unlink, write, actions, workflow actions - :param old_values: dict of values read before execution of the method - :param new_values: dict of values read after execution of the method - :param field_list: optional argument containing the list of fields to log. Currently only - used when performing a read, it could be usefull later on if we want to log the write - on specific fields only. - :return: True - """ - if field_list is None: - field_list = [] - # loop on all the given ids - for res_id in res_ids: - # compare old and new values and get audittrail log lines accordingly - lines = self.prepare_audittrail_log_line(cr, uid, pool, model, res_id, method, old_values, new_values, field_list) +def prepare_audittrail_log_line(cr, uid, pool, model, resource_id, method, old_values, new_values, field_list=None): + """ + This function compares the old data (i.e before the method was executed) and the new data + (after the method was executed) and returns a structure with all the needed information to + log those differences. - # if at least one modification has been found - for model_id, resource_id in lines: - name = pool.get(model.model).name_get(cr, uid, [resource_id])[0][1] - vals = { - 'method': method, - 'object_id': model_id, - 'user_id': uid, - 'res_id': resource_id, - 'name': name, - } - if (model_id, resource_id) not in old_values and method not in ('copy', 'read'): - # the resource was not existing so we are forcing the method to 'create' - # (because it could also come with the value 'write' if we are creating - # new record through a one2many field) - vals.update({'method': 'create'}) - if (model_id, resource_id) not in new_values and method not in ('copy', 'read'): - # the resource is not existing anymore so we are forcing the method to 'unlink' - # (because it could also come with the value 'write' if we are deleting the - # record through a one2many field) - vals.update({'method': 'unlink'}) - # create the audittrail log in super admin mode, only if a change has been detected - if lines[(model_id, resource_id)]: - log_id = pool.get('audittrail.log').create(cr, SUPERUSER_ID, vals) - model = pool.get('ir.model').browse(cr, uid, model_id) - self.create_log_line(cr, SUPERUSER_ID, log_id, model, lines[(model_id, resource_id)]) - return True + :param cr: the current row, from the database cursor, + :param uid: the current user’s ID. This parameter is currently not used as every + operation to get data is made as super admin. Though, it could be usefull later. + :param pool: current db's pooler object. + :param model: model object which values are being changed + :param resource_id: ID of record to which values are being changed + :param method: method to log: create, read, unlink, write, actions, workflow actions + :param old_values: dict of values read before execution of the method + :param new_values: dict of values read after execution of the method + :param field_list: optional argument containing the list of fields to log. Currently only + used when performing a read, it could be usefull later on if we want to log the write + on specific fields only. - def check_rules(self, cr, uid, model, method): - """ - Checks if auditrails is installed for that db and then if one rule match - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID, - @param model: value of _name of the object which values are being changed - @param method: method to log: create, read, unlink,write,actions,workflow actions - @return: True or False - """ - pool = pooler.get_pool(cr.dbname) - if 'audittrail.rule' in pool.models: - model_ids = pool.get('ir.model').search(cr, SUPERUSER_ID, [('model', '=', model)]) - model_id = model_ids and model_ids[0] or False - if model_id: - rule_ids = pool.get('audittrail.rule').search(cr, SUPERUSER_ID, [('object_id', '=', model_id), ('state', '=', 'subscribed')]) - for rule in pool.get('audittrail.rule').read(cr, SUPERUSER_ID, rule_ids, ['user_id','log_read','log_write','log_create','log_unlink','log_action','log_workflow']): - if len(rule['user_id']) == 0 or uid in rule['user_id']: - if rule.get('log_'+method,0): + :return: dictionary with + * keys: tuples build as ID of model object to log and ID of resource to log + * values: list of all the changes in field values for this couple (model, resource) + return { + (model.id, resource_id): [] + } + + The reason why the structure returned is build as above is because when modifying an existing + record, we may have to log a change done in a x2many field of that object + """ + if field_list is None: + field_list = [] + key = (model.id, resource_id) + lines = { + key: [] + } + # loop on all the fields + for field_name, field_definition in pool.get(model.model)._all_columns.items(): + if field_name in ('__last_update', 'id'): + continue + #if the field_list param is given, skip all the fields not in that list + if field_list and field_name not in field_list: + continue + field_obj = field_definition.column + if field_obj._type in ('one2many','many2many'): + # checking if an audittrail rule apply in super admin mode + if check_rules(cr, SUPERUSER_ID, field_obj._obj, method): + # checking if the model associated to a *2m field exists, in super admin mode + x2m_model_ids = pool.get('ir.model').search(cr, SUPERUSER_ID, [('model', '=', field_obj._obj)]) + x2m_model_id = x2m_model_ids and x2m_model_ids[0] or False + assert x2m_model_id, _("'%s' Model does not exist..." %(field_obj._obj)) + x2m_model = pool.get('ir.model').browse(cr, SUPERUSER_ID, x2m_model_id) + # the resource_ids that need to be checked are the sum of both old and previous values (because we + # need to log also creation or deletion in those lists). + x2m_old_values_ids = old_values.get(key, {'value': {}})['value'].get(field_name, []) + x2m_new_values_ids = new_values.get(key, {'value': {}})['value'].get(field_name, []) + # We use list(set(...)) to remove duplicates. + res_ids = list(set(x2m_old_values_ids + x2m_new_values_ids)) + if model.model == x2m_model.model: + # we need to remove current resource_id from the many2many to prevent an infinit loop + if resource_id in res_ids: + res_ids.remove(resource_id) + for res_id in res_ids: + lines.update(prepare_audittrail_log_line(cr, SUPERUSER_ID, pool, x2m_model, res_id, method, old_values, new_values, field_list)) + # if the value value is different than the old value: record the change + if key not in old_values or key not in new_values or old_values[key]['value'][field_name] != new_values[key]['value'][field_name]: + data = { + 'name': field_name, + 'new_value': key in new_values and new_values[key]['value'].get(field_name), + 'old_value': key in old_values and old_values[key]['value'].get(field_name), + 'new_value_text': key in new_values and new_values[key]['text'].get(field_name), + 'old_value_text': key in old_values and old_values[key]['text'].get(field_name) + } + lines[key].append(data) + return lines + +def process_data(cr, uid, pool, res_ids, model, method, old_values=None, new_values=None, field_list=None): + """ + This function processes and iterates recursively to log the difference between the old + data (i.e before the method was executed) and the new data and creates audittrail log + accordingly. + + :param cr: the current row, from the database cursor, + :param uid: the current user’s ID, + :param pool: current db's pooler object. + :param res_ids: Id's of resource to be logged/compared. + :param model: model object which values are being changed + :param method: method to log: create, read, unlink, write, actions, workflow actions + :param old_values: dict of values read before execution of the method + :param new_values: dict of values read after execution of the method + :param field_list: optional argument containing the list of fields to log. Currently only + used when performing a read, it could be usefull later on if we want to log the write + on specific fields only. + :return: True + """ + if field_list is None: + field_list = [] + # loop on all the given ids + for res_id in res_ids: + # compare old and new values and get audittrail log lines accordingly + lines = prepare_audittrail_log_line(cr, uid, pool, model, res_id, method, old_values, new_values, field_list) + + # if at least one modification has been found + for model_id, resource_id in lines: + name = pool.get(model.model).name_get(cr, uid, [resource_id])[0][1] + vals = { + 'method': method, + 'object_id': model_id, + 'user_id': uid, + 'res_id': resource_id, + 'name': name, + } + if (model_id, resource_id) not in old_values and method not in ('copy', 'read'): + # the resource was not existing so we are forcing the method to 'create' + # (because it could also come with the value 'write' if we are creating + # new record through a one2many field) + vals.update({'method': 'create'}) + if (model_id, resource_id) not in new_values and method not in ('copy', 'read'): + # the resource is not existing anymore so we are forcing the method to 'unlink' + # (because it could also come with the value 'write' if we are deleting the + # record through a one2many field) + vals.update({'method': 'unlink'}) + # create the audittrail log in super admin mode, only if a change has been detected + if lines[(model_id, resource_id)]: + log_id = pool.get('audittrail.log').create(cr, SUPERUSER_ID, vals) + model = pool.get('ir.model').browse(cr, uid, model_id) + create_log_line(cr, SUPERUSER_ID, log_id, model, lines[(model_id, resource_id)]) + return True + +def check_rules(cr, uid, model, method): + """ + Checks if auditrails is installed for that db and then if one rule match + @param cr: the current row, from the database cursor, + @param uid: the current user’s ID, + @param model: value of _name of the object which values are being changed + @param method: method to log: create, read, unlink,write,actions,workflow actions + @return: True or False + """ + pool = pooler.get_pool(cr.dbname) + if 'audittrail.rule' in pool.models: + model_ids = pool.get('ir.model').search(cr, SUPERUSER_ID, [('model', '=', model)]) + model_id = model_ids and model_ids[0] or False + if model_id: + rule_ids = pool.get('audittrail.rule').search(cr, SUPERUSER_ID, [('object_id', '=', model_id), ('state', '=', 'subscribed')]) + for rule in pool.get('audittrail.rule').read(cr, SUPERUSER_ID, rule_ids, ['user_id','log_read','log_write','log_create','log_unlink','log_action','log_workflow']): + if len(rule['user_id']) == 0 or uid in rule['user_id']: + if rule.get('log_'+method,0): + return True + elif method not in ('default_get','read','fields_view_get','fields_get','search','search_count','name_search','name_get','get','request_get', 'get_sc', 'unlink', 'write', 'create', 'read_group', 'import_data'): + if rule['log_action']: return True - elif method not in ('default_get','read','fields_view_get','fields_get','search','search_count','name_search','name_get','get','request_get', 'get_sc', 'unlink', 'write', 'create', 'read_group', 'import_data'): - if rule['log_action']: - return True - def execute_cr(self, cr, uid, model, method, *args, **kw): - fct_src = super(audittrail_objects_proxy, self).execute_cr - if self.check_rules(cr,uid,model,method): - return self.log_fct(cr, uid, model, method, fct_src, *args, **kw) - return fct_src(cr, uid, model, method, *args, **kw) +# Replace the openerp.service.model functions. - def exec_workflow_cr(self, cr, uid, model, method, *args, **kw): - fct_src = super(audittrail_objects_proxy, self).exec_workflow_cr - if self.check_rules(cr,uid,model,'workflow'): - return self.log_fct(cr, uid, model, method, fct_src, *args, **kw) - return fct_src(cr, uid, model, method, *args, **kw) +original_execute_cr = openerp.service.model.execute_cr +original_exec_workflow_cr = openerp.service.model.exec_workflow_cr -audittrail_objects_proxy() +def execute_cr(cr, uid, model, method, *args, **kw): + fct_src = original_execute_cr + if check_rules(cr,uid,model,method): + return log_fct(cr, uid, model, method, fct_src, *args, **kw) + return fct_src(cr, uid, model, method, *args, **kw) + +def exec_workflow_cr(cr, uid, model, method, *args, **kw): + fct_src = original_exec_workflow_cr + if check_rules(cr,uid,model,'workflow'): + return log_fct(cr, uid, model, method, fct_src, *args, **kw) + return fct_src(cr, uid, model, method, *args, **kw) + +openerp.service.model.execute_cr = execute_cr +openerp.service.model.exec_workflow_cr = exec_workflow_cr # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From b6531577d789c4b00bcb251b4946ea680c22cfeb Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Thu, 31 Jan 2013 17:13:46 +0100 Subject: [PATCH 20/34] [FIX] audittrail: forgot to remove the import for object_proxy (made unused by the previous patch). bzr revid: vmt@openerp.com-20130131161346-pduol7fvrozbm25z --- addons/audittrail/audittrail.py | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/audittrail/audittrail.py b/addons/audittrail/audittrail.py index 857f5ff64d0..0debdf11492 100644 --- a/addons/audittrail/audittrail.py +++ b/addons/audittrail/audittrail.py @@ -20,7 +20,6 @@ ############################################################################## from openerp.osv import fields, osv -from openerp.osv.osv import object_proxy import openerp.service.model from openerp.tools.translate import _ from openerp import pooler From a57b655e43227e551c7f88f2a50ed99467503007 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Thu, 31 Jan 2013 17:14:42 +0100 Subject: [PATCH 21/34] [REF] object_proxy is no longer used by audittrail (which patches directly openerp.service.model). bzr revid: vmt@openerp.com-20130131161442-17rwinwygte6usfg --- openerp/osv/osv.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/openerp/osv/osv.py b/openerp/osv/osv.py index 16acbbb7ae6..9634f9ce615 100644 --- a/openerp/osv/osv.py +++ b/openerp/osv/osv.py @@ -32,10 +32,4 @@ osv = Model osv_memory = TransientModel osv_abstract = AbstractModel # ;-) -# Deprecated, kept for backward compatibility, in particular for audittrail. -class object_proxy(object): - def __init__(self): - import openerp.service.model - # TODO monkeypatch openerp.service.model to call the new object_service instance methods. - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 665fc8d8f18c839935d48e84c9943952f78936f6 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 1 Feb 2013 10:37:27 +0100 Subject: [PATCH 22/34] [REF] openerp.service.{rpc,route,handler} decorators moved to openerp.http. bzr revid: vmt@openerp.com-20130201093727-pgcvauuo87o2a3p6 --- doc/routing.rst | 6 ++++-- openerp/__init__.py | 1 + openerp/http.py | 37 +++++++++++++++++++++++++++++++++++++ openerp/service/__init__.py | 18 ------------------ 4 files changed, 42 insertions(+), 20 deletions(-) create mode 100644 openerp/http.py diff --git a/doc/routing.rst b/doc/routing.rst index 1e4f76d25b8..b2e93de8ef9 100644 --- a/doc/routing.rst +++ b/doc/routing.rst @@ -3,6 +3,8 @@ Routing ======= +.. versionchanged:: 7.1 + The OpenERP framework, as an HTTP server, serves a few hard-coded URLs (``models``, ``db``, ...) to expose RPC endpoints. When running the web addons (which is almost always the case), it also serves URLs without them being RPC @@ -13,6 +15,6 @@ In older version of OpenERP, adding RPC endpoints was done by subclassing the registering them with the ``openerp.wsgi.register_wsgi_handler()`` function. Starting with OpenERP 7.1, exposing a new arbitrary WSGI handler is done with -the ``openerp.service.handler`` decorator while adding an RPC endpoint is done -with the ``openerp.service.rpc`` decorator. +the ``openerp.http.handler`` decorator while adding an RPC endpoint is done +with the ``openerp.http.rpc`` decorator. diff --git a/openerp/__init__.py b/openerp/__init__.py index 307170b9033..dc09113adff 100644 --- a/openerp/__init__.py +++ b/openerp/__init__.py @@ -28,6 +28,7 @@ SUPERUSER_ID = 1 import addons import cli import conf +import http import loglevels import modules import netsvc diff --git a/openerp/http.py b/openerp/http.py new file mode 100644 index 00000000000..004542f3100 --- /dev/null +++ b/openerp/http.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- + +""" +Decorators to register WSGI and RPC endpoints handlers. See doc/routing.rst. +# TODO use sphinx ref to doc/routing.rst. +""" + +from . import service + +def handler(): + """ + Decorator to register a WSGI handler. The handler must return None if it + does not handle the request. + """ + def decorator(f): + service.wsgi_server.register_wsgi_handler(f) + return decorator + +def route(url): + """ + Same as then handler() decorator but register the handler under a specific + url. Not yet implemented. + """ + def decorator(f): + pass # TODO + return decorator + +def rpc(endpoint): + """ + Decorator to register a RPC endpoint handler. The handler will receive + already unmarshalled RCP arguments. + """ + def decorator(f): + service.wsgi_server.register_rpc_endpoint(endpoint, f) + return decorator + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/service/__init__.py b/openerp/service/__init__.py index db5d4542bf4..9db73223029 100644 --- a/openerp/service/__init__.py +++ b/openerp/service/__init__.py @@ -150,22 +150,4 @@ def restart_server(): openerp.phoenix = True os.kill(os.getpid(), signal.SIGINT) -def handler(): - """TODO""" - def decorator(f): - wsgi_server.register_wsgi_handler(f) - return decorator - -def route(url): - """TODO""" - def decorator(f): - pass # TODO Same as handler() but register the handler under a specific url. - return decorator - -def rpc(endpoint): - """TODO""" - def decorator(f): - wsgi_server.register_rpc_endpoint(endpoint, f) - return decorator - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From f0e4a9b01ea44768d9ebeb9c4110a617adcc31f9 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 1 Feb 2013 12:20:01 +0100 Subject: [PATCH 23/34] [REF] tools.translate: removed dead code (wizard service do not exist anymore). bzr revid: vmt@openerp.com-20130201112001-9db40vy8rhef6wj4 --- openerp/tools/translate.py | 44 +------------------------------------- 1 file changed, 1 insertion(+), 43 deletions(-) diff --git a/openerp/tools/translate.py b/openerp/tools/translate.py index 41113b8ccd9..45688ae76e0 100644 --- a/openerp/tools/translate.py +++ b/openerp/tools/translate.py @@ -674,49 +674,7 @@ def trans_generate(lang, modules, cr): for t in trans_parse_view(d): push_translation(module, 'view', encode(obj.model), 0, t) elif model=='ir.actions.wizard': - service_name = 'wizard.'+encode(obj.wiz_name) - import openerp.netsvc as netsvc - if netsvc.Service._services.get(service_name): - obj2 = netsvc.Service._services[service_name] - for state_name, state_def in obj2.states.iteritems(): - if 'result' in state_def: - result = state_def['result'] - if result['type'] != 'form': - continue - name = "%s,%s" % (encode(obj.wiz_name), state_name) - - def_params = { - 'string': ('wizard_field', lambda s: [encode(s)]), - 'selection': ('selection', lambda s: [encode(e[1]) for e in ((not callable(s)) and s or [])]), - 'help': ('help', lambda s: [encode(s)]), - } - - # export fields - if not result.has_key('fields'): - _logger.warning("res has no fields: %r", result) - continue - for field_name, field_def in result['fields'].iteritems(): - res_name = name + ',' + field_name - - for fn in def_params: - if fn in field_def: - transtype, modifier = def_params[fn] - for val in modifier(field_def[fn]): - push_translation(module, transtype, res_name, 0, val) - - # export arch - arch = result['arch'] - if arch and not isinstance(arch, UpdateableStr): - d = etree.XML(arch) - for t in trans_parse_view(d): - push_translation(module, 'wizard_view', name, 0, t) - - # export button labels - for but_args in result['state']: - button_name = but_args[0] - button_label = but_args[1] - res_name = name + ',' + button_name - push_translation(module, 'wizard_button', res_name, 0, button_label) + pass # TODO Can model really be 'ir.actions.wizard' ? elif model=='ir.model.fields': try: From c8ffd1cedce3b80317687f3929697222c9d79e4e Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 1 Feb 2013 12:22:32 +0100 Subject: [PATCH 24/34] [REF] service: netsvc.Service is removed. LocalService(object_proxy) is never used so it is removed. bzr revid: vmt@openerp.com-20130201112232-2k8geufmryx6p249 --- openerp/addons/base/ir/ir_actions.py | 5 +++-- openerp/netsvc.py | 24 +----------------------- openerp/report/interface.py | 12 ++++++++---- 3 files changed, 12 insertions(+), 29 deletions(-) diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py index 4404b0a8a50..beb4e27200a 100644 --- a/openerp/addons/base/ir/ir_actions.py +++ b/openerp/addons/base/ir/ir_actions.py @@ -28,6 +28,7 @@ import time from openerp import SUPERUSER_ID from openerp import netsvc, tools from openerp.osv import fields, osv +import openerp.report.interface from openerp.report.report_sxw import report_sxw, report_rml from openerp.tools.config import config from openerp.tools.safe_eval import safe_eval as eval @@ -93,9 +94,9 @@ class report_xml(osv.osv): opj = os.path.join cr.execute("SELECT * FROM ir_act_report_xml WHERE auto=%s ORDER BY id", (True,)) result = cr.dictfetchall() - svcs = netsvc.Service._services + reports = openerp.report.interface.report_int._reports for r in result: - if svcs.has_key('report.'+r['report_name']): + if reports.has_key('report.'+r['report_name']): continue if r['report_rml'] or r['report_rml_content_data']: report_sxw('report.'+r['report_name'], r['model'], diff --git a/openerp/netsvc.py b/openerp/netsvc.py index 6b70dc972a8..5aee8e53cc1 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -47,35 +47,13 @@ import openerp _logger = logging.getLogger(__name__) - -class Service(object): - """ Base class for Local services - Functionality here is trusted, no authentication. - Workflow engine and reports subclass this. - """ - _services = {} - def __init__(self, name): - Service._services[name] = self - self.__name = name - - @classmethod - def exists(cls, name): - return name in cls._services - - @classmethod - def remove(cls, name): - if cls.exists(name): - cls._services.pop(name) - def LocalService(name): # Special case for addons support, will be removed in a few days when addons # are updated to directly use openerp.osv.osv.service. - if name == 'object_proxy': - return openerp.service.model if name == 'workflow': return openerp.workflow - return Service._services[name] + return openerp.report.interface.report_int._reports[name] BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, _NOTHING, DEFAULT = range(10) #The background is set with 40 plus the number of the color, and the foreground with 30 diff --git a/openerp/report/interface.py b/openerp/report/interface.py index 88331db1f4d..f0de9631800 100644 --- a/openerp/report/interface.py +++ b/openerp/report/interface.py @@ -23,7 +23,6 @@ import os import re from lxml import etree -import openerp.netsvc as netsvc import openerp.pooler as pooler import openerp.tools as tools @@ -40,12 +39,17 @@ def toxml(value): unicode_value = tools.ustr(value) return unicode_value.replace('&', '&').replace('<','<').replace('>','>') -class report_int(netsvc.Service): +class report_int(object): + + _reports = {} + def __init__(self, name): - assert not self.exists(name), 'The report "%s" already exists!' % name - super(report_int, self).__init__(name) if not name.startswith('report.'): raise Exception('ConceptionError, bad report name, should start with "report."') + assert name not in self._reports, 'The report "%s" already exists!' % name + self._reports[name] = self + self.__name = name + self.name = name self.id = 0 self.name2 = '.'.join(name.split('.')[1:]) From 69b23c9936c9ea987c8b3f1e4e8f688d81734303 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 1 Feb 2013 12:23:09 +0100 Subject: [PATCH 25/34] [REF] service: some code was touching the internal of netsvc.Service so it has to be updated. bzr revid: vmt@openerp.com-20130201112309-bjhu7jems82uk6bg --- addons/document/document.py | 4 ++-- addons/edi/edi_service.py | 2 +- addons/report_webkit/ir_report.py | 11 ++++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/addons/document/document.py b/addons/document/document.py index 1671b6bc1ba..85468e00509 100644 --- a/addons/document/document.py +++ b/addons/document/document.py @@ -31,11 +31,11 @@ from StringIO import StringIO import psycopg2 import openerp -from openerp import netsvc from openerp import pooler from openerp import tools from openerp.osv import fields, osv from openerp.osv.orm import except_orm +import openerp.report.interface from openerp.tools.misc import ustr from openerp.tools.translate import _ from openerp.tools.safe_eval import safe_eval @@ -456,7 +456,7 @@ class document_directory_content(osv.osv): if node.extension != '.pdf': raise Exception("Invalid content: %s" % node.extension) report = self.pool.get('ir.actions.report.xml').browse(cr, uid, node.report_id, context=context) - srv = netsvc.Service._services['report.'+report.report_name] + srv = openerp.report.interface.report_int._reports['report.'+report.report_name] ctx = node.context.context.copy() ctx.update(node.dctx) pdf,pdftype = srv.create(cr, uid, [node.act_id,], {}, context=ctx) diff --git a/addons/edi/edi_service.py b/addons/edi/edi_service.py index 3bd562ac0bb..d5dc5c09f74 100644 --- a/addons/edi/edi_service.py +++ b/addons/edi/edi_service.py @@ -53,7 +53,7 @@ def exp_import_edi_document(db_name, uid, passwd, edi_document, context=None): def exp_import_edi_url(db_name, uid, passwd, edi_url, context=None): return _edi_dispatch(db_name, 'import_edi', uid, None, edi_url) -@openerp.service.rpc('edi') +@openerp.http.rpc('edi') def dispatch(method, params): if method in ['import_edi_document', 'import_edi_url']: (db, uid, passwd) = params[0:3] diff --git a/addons/report_webkit/ir_report.py b/addons/report_webkit/ir_report.py index 1385f504928..01b65987a8e 100644 --- a/addons/report_webkit/ir_report.py +++ b/addons/report_webkit/ir_report.py @@ -30,21 +30,22 @@ ############################################################################## from openerp.osv import fields, osv -from openerp import netsvc -from webkit_report import WebKitParser +import openerp.report.interface from openerp.report.report_sxw import rml_parse +from webkit_report import WebKitParser + def register_report(name, model, tmpl_path, parser=rml_parse): """Register the report into the services""" name = 'report.%s' % name - if netsvc.Service._services.get(name, False): - service = netsvc.Service._services[name] + if name in openerp.report.interface.report_int._reports: + service = openerp.report.interface.report_int[name] if isinstance(service, WebKitParser): #already instantiated properly, skip it return if hasattr(service, 'parser'): parser = service.parser - del netsvc.Service._services[name] + del openerp.report.interface.report_int[name] WebKitParser(name, model, tmpl_path, parser=parser) From 2f4fe9cf8cadd065ae4b75ee282068d7652f5443 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 1 Feb 2013 14:53:30 +0100 Subject: [PATCH 26/34] [FIX] crm: fixed demo data with hardcoded %Y-%m-30 (same fix than in trunk). bzr revid: vmt@openerp.com-20130201135330-kjdfv2b9fjz2oweg --- addons/crm/crm_lead_demo.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/crm/crm_lead_demo.xml b/addons/crm/crm_lead_demo.xml index 99fb0e895c2..21d60ad2961 100644 --- a/addons/crm/crm_lead_demo.xml +++ b/addons/crm/crm_lead_demo.xml @@ -360,7 +360,7 @@ Andrew 2 - + Convert to quote From 56df6d6ecc3fc4c6cefb4aa47225d78f2dc2e630 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 1 Feb 2013 16:13:54 +0100 Subject: [PATCH 27/34] [DOC] openerp.http: use sphinx domains. bzr revid: vmt@openerp.com-20130201151354-uyxgyt0aw101a2mq --- doc/routing.rst | 13 ++++++++++--- openerp/http.py | 4 ++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/doc/routing.rst b/doc/routing.rst index b2e93de8ef9..6a4af3374a9 100644 --- a/doc/routing.rst +++ b/doc/routing.rst @@ -12,9 +12,16 @@ endpoints. In older version of OpenERP, adding RPC endpoints was done by subclassing the ``openerp.netsvc.ExportService`` class. Adding WSGI handlers was done by -registering them with the ``openerp.wsgi.register_wsgi_handler()`` function. +registering them with the :py:func:`openerp.wsgi.register_wsgi_handler` +function. Starting with OpenERP 7.1, exposing a new arbitrary WSGI handler is done with -the ``openerp.http.handler`` decorator while adding an RPC endpoint is done -with the ``openerp.http.rpc`` decorator. +the :py:func:`openerp.http.handler` decorator while adding an RPC endpoint is +done with the :py:func:`openerp.http.rpc` decorator. +Routing decorators +------------------ + +.. automodule:: openerp.http + :members: + :undoc-members: diff --git a/openerp/http.py b/openerp/http.py index 004542f3100..e2f3024110d 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- """ -Decorators to register WSGI and RPC endpoints handlers. See doc/routing.rst. -# TODO use sphinx ref to doc/routing.rst. +``openerp.http`` offers decorators to register WSGI and RPC endpoints handlers. +See :ref:`routing`. """ from . import service From ff31ff5ca9ea571250482d24523bb9a7cf111d0e Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 1 Feb 2013 17:15:53 +0100 Subject: [PATCH 28/34] [REF] tests: removed the temporary import for test_xmlrpc. bzr revid: vmt@openerp.com-20130201161553-0cqsnv9l7962wwls --- openerp/tests/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openerp/tests/__init__.py b/openerp/tests/__init__.py index 73459318178..e6a2eacf386 100644 --- a/openerp/tests/__init__.py +++ b/openerp/tests/__init__.py @@ -22,7 +22,8 @@ import test_osv import test_translate import test_uninstall import test_view_validation -import test_xmlrpc # TODO Remove this before merge, or change oe run-tests to only run fast_suite + checks by default. +# This need a change in `oe run-tests` to only run fast_suite + checks by default. +# import test_xmlrpc fast_suite = [ test_ir_sequence, From f84535944fa8c7de666b190a799adffc7004a492 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Tue, 12 Feb 2013 15:14:26 +0100 Subject: [PATCH 29/34] [REM] base_quality_interrogation: seems unused for a long time. Seems full of good ideas though. Sadness ensues. RIP. bzr revid: vmt@openerp.com-20130212141426-athz3br7enhynlwf --- openerp/addons/base_quality_interrogation.py | 352 ------------------- 1 file changed, 352 deletions(-) delete mode 100755 openerp/addons/base_quality_interrogation.py diff --git a/openerp/addons/base_quality_interrogation.py b/openerp/addons/base_quality_interrogation.py deleted file mode 100755 index 1795db7e13f..00000000000 --- a/openerp/addons/base_quality_interrogation.py +++ /dev/null @@ -1,352 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (). -# -# 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 . -# -############################################################################## - -import xmlrpclib -import optparse -import sys -import threading -import os -import time -import base64 -import socket -import string - -admin_passwd = 'admin' -waittime = 10 -wait_count = 0 -wait_limit = 12 - -def to_decode(s): - try: - return s.encode('utf-8') - except UnicodeError: - try: - return s.encode('latin') - except UnicodeError: - try: - return s.decode('ascii') - except UnicodeError: - return s - -def start_server(root_path, port, netport, addons_path): - os.system('python2.5 %sopenerp-server --pidfile=openerp.pid --no-xmlrpcs --xmlrpc-port=%s --netrpc-port=%s --addons-path=%s' %(root_path, str(port),str(netport),addons_path)) -def clean(): - if os.path.isfile('openerp.pid'): - ps = open('openerp.pid') - if ps: - pid = int(ps.read()) - ps.close() - if pid: - os.kill(pid,9) - -def execute(connector, method, *args): - global wait_count - res = False - try: - res = getattr(connector,method)(*args) - except socket.error,e: - if e.args[0] == 111: - if wait_count > wait_limit: - print "Server is taking too long to start, it has exceeded the maximum limit of %d seconds." % wait_limit - clean() - sys.exit(1) - print 'Please wait %d sec to start server....' % waittime - wait_count += 1 - time.sleep(waittime) - res = execute(connector, method, *args) - else: - raise e - wait_count = 0 - return res - -def login(uri, dbname, user, pwd): - conn = xmlrpclib.ServerProxy(uri + '/xmlrpc/common') - uid = execute(conn,'login',dbname, user, pwd) - return uid - -def import_translate(uri, user, pwd, dbname, translate_in): - uid = login(uri, dbname, user, pwd) - if uid: - conn = xmlrpclib.ServerProxy(uri + '/xmlrpc/wizard') - wiz_id = execute(conn,'create',dbname, uid, pwd, 'base.language.import') - for trans_in in translate_in: - lang,ext = os.path.splitext(trans_in.split('/')[-1]) - state = 'init' - datas = {'form':{}} - while state!='end': - res = execute(conn,'execute',dbname, uid, pwd, wiz_id, datas, state, {}) - if 'datas' in res: - datas['form'].update( res['datas'].get('form',{}) ) - if res['type']=='form': - for field in res['fields'].keys(): - datas['form'][field] = res['fields'][field].get('value', False) - state = res['state'][-1][0] - trans_obj = open(trans_in) - datas['form'].update({ - 'name': lang, - 'code': lang, - 'data' : base64.encodestring(trans_obj.read()) - }) - trans_obj.close() - elif res['type']=='action': - state = res['state'] - - -def check_quality(uri, user, pwd, dbname, modules, quality_logs): - uid = login(uri, dbname, user, pwd) - quality_logs += 'quality-logs' - if uid: - conn = xmlrpclib.ServerProxy(uri + '/xmlrpc/object') - final = {} - for module in modules: - qualityresult = {} - test_detail = {} - quality_result = execute(conn,'execute', dbname, uid, pwd,'module.quality.check','check_quality',module) - detail_html = '' - html = '''''' - html +="

Module: %s

"%(quality_result['name']) - html += "

Final score: %s

"%(quality_result['final_score']) - html += "
" - html += "
    " - for x,y,detail in quality_result['check_detail_ids']: - test = detail.get('name') - msg = detail.get('message','') - score = round(float(detail.get('score',0)),2) - html += "
  • %s
  • "%(test.replace(' ','-'),test) - detail_html +='''

    %s (Score : %s)

    %s
    %s
    '''%(test.replace(' ', '-'), test, score, msg, detail.get('detail', '')) - test_detail[test] = (score,msg,detail.get('detail','')) - html += "
" - html += "%s"% detail_html - html += "
" - if not os.path.isdir(quality_logs): - os.mkdir(quality_logs) - fp = open('%s/%s.html'%(quality_logs,module),'wb') - fp.write(to_decode(html)) - fp.close() - #final[quality_result['name']] = (quality_result['final_score'],html,test_detail) - - #fp = open('quality_log.pck','wb') - #pck_obj = pickle.dump(final,fp) - #fp.close() - #print "LOG PATH%s"%(os.path.realpath('quality_log.pck')) - return True - else: - print 'Login Failed...' - clean() - sys.exit(1) - - - -def wait(id,url=''): - progress=0.0 - sock2 = xmlrpclib.ServerProxy(url+'/xmlrpc/db') - while not progress==1.0: - progress,users = execute(sock2,'get_progress',admin_passwd, id) - return True - - -def create_db(uri, dbname, user='admin', pwd='admin', lang='en_US'): - conn = xmlrpclib.ServerProxy(uri + '/xmlrpc/db') - obj_conn = xmlrpclib.ServerProxy(uri + '/xmlrpc/object') - wiz_conn = xmlrpclib.ServerProxy(uri + '/xmlrpc/wizard') - login_conn = xmlrpclib.ServerProxy(uri + '/xmlrpc/common') - db_list = execute(conn, 'list') - if dbname in db_list: - drop_db(uri, dbname) - id = execute(conn,'create',admin_passwd, dbname, True, lang) - wait(id,uri) - install_module(uri, dbname, ['base_module_quality'],user=user,pwd=pwd) - return True - -def drop_db(uri, dbname): - conn = xmlrpclib.ServerProxy(uri + '/xmlrpc/db') - db_list = execute(conn,'list') - if dbname in db_list: - execute(conn, 'drop', admin_passwd, dbname) - return True - -def make_links(uri, uid, dbname, source, destination, module, user, pwd): - if module in ('base','quality_integration_server'): - return True - if os.path.islink(destination + '/' + module): - os.unlink(destination + '/' + module) - for path in source: - if os.path.isdir(path + '/' + module): - os.symlink(path + '/' + module, destination + '/' + module) - obj_conn = xmlrpclib.ServerProxy(uri + '/xmlrpc/object') - execute(obj_conn, 'execute', dbname, uid, pwd, 'ir.module.module', 'update_list') - module_ids = execute(obj_conn, 'execute', dbname, uid, pwd, 'ir.module.module', 'search', [('name','=',module)]) - if len(module_ids): - data = execute(obj_conn, 'execute', dbname, uid, pwd, 'ir.module.module', 'read', module_ids[0],['name','dependencies_id']) - dep_datas = execute(obj_conn, 'execute', dbname, uid, pwd, 'ir.module.module.dependency', 'read', data['dependencies_id'],['name']) - for dep_data in dep_datas: - make_links(uri, uid, dbname, source, destination, dep_data['name'], user, pwd) - return False - -def install_module(uri, dbname, modules, addons='', extra_addons='', user='admin', pwd='admin'): - uid = login(uri, dbname, user, pwd) - if extra_addons: - extra_addons = extra_addons.split(',') - if uid: - if addons and extra_addons: - for module in modules: - make_links(uri, uid, dbname, extra_addons, addons, module, user, pwd) - - obj_conn = xmlrpclib.ServerProxy(uri + '/xmlrpc/object') - wizard_conn = xmlrpclib.ServerProxy(uri + '/xmlrpc/wizard') - module_ids = execute(obj_conn, 'execute', dbname, uid, pwd, 'ir.module.module', 'search', [('name','in',modules)]) - execute(obj_conn, 'execute', dbname, uid, pwd, 'ir.module.module', 'button_install', module_ids) - wiz_id = execute(wizard_conn, 'create', dbname, uid, pwd, 'module.upgrade.simple') - state = 'init' - datas = {} - #while state!='menu': - while state!='end': - res = execute(wizard_conn, 'execute', dbname, uid, pwd, wiz_id, datas, state, {}) - if state == 'init': - state = 'start' - elif state == 'start': - state = 'end' - return True - -def upgrade_module(uri, dbname, modules, user='admin', pwd='admin'): - uid = login(uri, dbname, user, pwd) - if uid: - obj_conn = xmlrpclib.ServerProxy(uri + '/xmlrpc/object') - wizard_conn = xmlrpclib.ServerProxy(uri + '/xmlrpc/wizard') - module_ids = execute(obj_conn, 'execute', dbname, uid, pwd, 'ir.module.module', 'search', [('name','in',modules)]) - execute(obj_conn, 'execute', dbname, uid, pwd, 'ir.module.module', 'button_upgrade', module_ids) - wiz_id = execute(wizard_conn, 'create', dbname, uid, pwd, 'module.upgrade.simple') - state = 'init' - datas = {} - #while state!='menu': - while state!='end': - res = execute(wizard_conn, 'execute', dbname, uid, pwd, wiz_id, datas, state, {}) - if state == 'init': - state = 'start' - elif state == 'start': - state = 'end' - - return True - - - - - -usage = """%prog command [options] - -Basic Commands: - start-server Start Server - create-db Create new database - drop-db Drop database - install-module Install module - upgrade-module Upgrade module - install-translation Install translation file - check-quality Calculate quality and dump quality result into quality_log.pck using pickle -""" -parser = optparse.OptionParser(usage) -parser.add_option("--modules", dest="modules", - help="specify modules to install or check quality") -parser.add_option("--addons-path", dest="addons_path", help="specify the addons path") -parser.add_option("--quality-logs", dest="quality_logs", help="specify the path of quality logs files which has to stores") -parser.add_option("--root-path", dest="root_path", help="specify the root path") -parser.add_option("-p", "--port", dest="port", help="specify the TCP port", type="int") -parser.add_option("--net_port", dest="netport",help="specify the TCP port for netrpc") -parser.add_option("-d", "--database", dest="db_name", help="specify the database name") -parser.add_option("--login", dest="login", help="specify the User Login") -parser.add_option("--password", dest="pwd", help="specify the User Password") -parser.add_option("--translate-in", dest="translate_in", - help="specify .po files to import translation terms") -parser.add_option("--extra-addons", dest="extra_addons", - help="specify extra_addons and trunkCommunity modules path ") - -(opt, args) = parser.parse_args() -if len(args) != 1: - parser.error("incorrect number of arguments") -command = args[0] -if command not in ('start-server','create-db','drop-db','install-module','upgrade-module','check-quality','install-translation'): - parser.error("incorrect command") - -def die(cond, msg): - if cond: - print msg - sys.exit(1) - -die(opt.modules and (not opt.db_name), - "the modules option cannot be used without the database (-d) option") - -die(opt.translate_in and (not opt.db_name), - "the translate-in option cannot be used without the database (-d) option") - -options = { - 'addons-path' : opt.addons_path or 'addons', - 'quality-logs' : opt.quality_logs or '', - 'root-path' : opt.root_path or '', - 'translate-in': [], - 'port' : opt.port or 8069, - 'netport':opt.netport or 8070, - 'database': opt.db_name or 'terp', - 'modules' : map(string.strip, opt.modules.split(',')) if opt.modules else [], - 'login' : opt.login or 'admin', - 'pwd' : opt.pwd or '', - 'extra-addons':opt.extra_addons or [] -} -# Hint:i18n-import=purchase:ar_AR.po+sale:fr_FR.po,nl_BE.po -if opt.translate_in: - translate = opt.translate_in - for module_name,po_files in map(lambda x:tuple(x.split(':')),translate.split('+')): - for po_file in po_files.split(','): - if module_name == 'base': - po_link = '%saddons/%s/i18n/%s'%(options['root-path'],module_name,po_file) - else: - po_link = '%s/%s/i18n/%s'%(options['addons-path'], module_name, po_file) - options['translate-in'].append(po_link) - -uri = 'http://localhost:' + str(options['port']) - -server_thread = threading.Thread(target=start_server, - args=(options['root-path'], options['port'],options['netport'], options['addons-path'])) -try: - server_thread.start() - if command == 'create-db': - create_db(uri, options['database'], options['login'], options['pwd']) - if command == 'drop-db': - drop_db(uri, options['database']) - if command == 'install-module': - install_module(uri, options['database'], options['modules'],options['addons-path'],options['extra-addons'],options['login'], options['pwd']) - if command == 'upgrade-module': - upgrade_module(uri, options['database'], options['modules'], options['login'], options['pwd']) - if command == 'check-quality': - check_quality(uri, options['login'], options['pwd'], options['database'], options['modules'], options['quality-logs']) - if command == 'install-translation': - import_translate(uri, options['login'], options['pwd'], options['database'], options['translate-in']) - clean() - sys.exit(0) - -except xmlrpclib.Fault, e: - print e.faultString - clean() - sys.exit(1) -except Exception, e: - print e - clean() - sys.exit(1) - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From c99c4091ce6154e3af3428bbbb9ce7fd8b2516d8 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Tue, 12 Feb 2013 15:24:10 +0100 Subject: [PATCH 30/34] [REM] Deleted .apidoc lines. They were probably used by some tools. How sad. bzr revid: vmt@openerp.com-20130212142410-zqdjd8jw3gtvxab0 --- openerp/.apidoc | 1 - openerp/netsvc.py | 2 -- openerp/osv/__init__.py | 1 - openerp/osv/expression.py | 1 - openerp/osv/orm.py | 2 -- openerp/osv/osv.py | 1 - openerp/osv/query.py | 1 - openerp/report/__init__.py | 1 - openerp/report/printscreen/__init__.py | 1 - openerp/report/printscreen/ps_form.py | 1 - openerp/report/printscreen/ps_list.py | 1 - openerp/report/pyPdf/__init__.py | 1 - openerp/report/render/__init__.py | 1 - openerp/report/render/html2html/__init__.py | 1 - openerp/report/render/makohtml2html/__init__.py | 1 - openerp/report/render/odt2odt/__init__.py | 1 - openerp/report/render/rml2html/__init__.py | 1 - openerp/report/render/rml2pdf/__init__.py | 1 - openerp/report/render/rml2pdf/customfonts.py | 1 - openerp/report/render/rml2txt/__init__.py | 1 - openerp/service/__init__.py | 1 - openerp/service/http_server.py | 1 - openerp/service/netrpc_server.py | 1 - openerp/service/security.py | 1 - openerp/service/web_services.py | 2 -- openerp/service/websrv_lib.py | 1 - openerp/sql_db.py | 3 --- openerp/tools/__init__.py | 1 - openerp/tools/config.py | 1 - openerp/tools/misc.py | 1 - openerp/workflow/__init__.py | 1 - 31 files changed, 36 deletions(-) delete mode 100644 openerp/.apidoc diff --git a/openerp/.apidoc b/openerp/.apidoc deleted file mode 100644 index 9109e4388fe..00000000000 --- a/openerp/.apidoc +++ /dev/null @@ -1 +0,0 @@ -excludes: pychart release openerp-server test run_tests addons/base_quality_interrogation \ No newline at end of file diff --git a/openerp/netsvc.py b/openerp/netsvc.py index d6f9dc49d84..5280531b0f1 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -20,8 +20,6 @@ # ############################################################################## -#.apidoc title: Common Services: netsvc -#.apidoc module-mods: member-order: bysource import errno import logging diff --git a/openerp/osv/__init__.py b/openerp/osv/__init__.py index b32d81d659e..630090954e6 100644 --- a/openerp/osv/__init__.py +++ b/openerp/osv/__init__.py @@ -22,7 +22,6 @@ import osv import fields -#.apidoc title: Object Services and Relational Mapping # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/osv/expression.py b/openerp/osv/expression.py index 899279c8015..6989be2ccb3 100644 --- a/openerp/osv/expression.py +++ b/openerp/osv/expression.py @@ -141,7 +141,6 @@ from openerp.osv import fields from openerp.osv.orm import MAGIC_COLUMNS import openerp.tools as tools -#.apidoc title: Domain Expressions # Domain operators. NOT_OPERATOR = '!' diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 8a432240567..8e0c6c7376a 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -19,8 +19,6 @@ # ############################################################################## -#.apidoc title: Object Relational Mapping -#.apidoc module-mods: member-order: bysource """ Object relational mapping to database (postgresql) module diff --git a/openerp/osv/osv.py b/openerp/osv/osv.py index 018976ac663..b7742169f2f 100644 --- a/openerp/osv/osv.py +++ b/openerp/osv/osv.py @@ -19,7 +19,6 @@ # ############################################################################## -#.apidoc title: Objects Services (OSV) from functools import wraps import logging diff --git a/openerp/osv/query.py b/openerp/osv/query.py index 04a0ec5509b..8ddce368220 100644 --- a/openerp/osv/query.py +++ b/openerp/osv/query.py @@ -19,7 +19,6 @@ # ############################################################################## -#.apidoc title: Query object def _quote(to_quote): diff --git a/openerp/report/__init__.py b/openerp/report/__init__.py index 48b654fdd86..6b56f15b6b5 100644 --- a/openerp/report/__init__.py +++ b/openerp/report/__init__.py @@ -30,7 +30,6 @@ import report_sxw import printscreen -#.apidoc title: Reporting Support and Engines # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/report/printscreen/__init__.py b/openerp/report/printscreen/__init__.py index e940a7e467c..deb270b561b 100644 --- a/openerp/report/printscreen/__init__.py +++ b/openerp/report/printscreen/__init__.py @@ -22,7 +22,6 @@ import ps_list import ps_form -#.apidoc title: Printscreen Support """ A special report, that is automatically formatted to look like the screen contents of Form/List Views. diff --git a/openerp/report/printscreen/ps_form.py b/openerp/report/printscreen/ps_form.py index 299e0466d8f..e585176d515 100644 --- a/openerp/report/printscreen/ps_form.py +++ b/openerp/report/printscreen/ps_form.py @@ -28,7 +28,6 @@ from lxml import etree import time, os -#.apidoc title: Printscreen for Form Views class report_printscreen_list(report_int): def __init__(self, name): diff --git a/openerp/report/printscreen/ps_list.py b/openerp/report/printscreen/ps_list.py index 51882b6c81a..c26597c8cdf 100644 --- a/openerp/report/printscreen/ps_list.py +++ b/openerp/report/printscreen/ps_list.py @@ -31,7 +31,6 @@ import time, os from operator import itemgetter from datetime import datetime -#.apidoc title: Printscreen for List Views class report_printscreen_list(report_int): def __init__(self, name): diff --git a/openerp/report/pyPdf/__init__.py b/openerp/report/pyPdf/__init__.py index 9e4f84d3863..e6fe66e1b6a 100644 --- a/openerp/report/pyPdf/__init__.py +++ b/openerp/report/pyPdf/__init__.py @@ -1,5 +1,4 @@ from pdf import PdfFileReader, PdfFileWriter -#.apidoc title: pyPdf Engine __all__ = ["pdf"] diff --git a/openerp/report/render/__init__.py b/openerp/report/render/__init__.py index 89432fa9bdd..6f31e560973 100644 --- a/openerp/report/render/__init__.py +++ b/openerp/report/render/__init__.py @@ -23,7 +23,6 @@ from simple import simple from rml import rml, rml2html, rml2txt, odt2odt , html2html, makohtml2html from render import render -#.apidoc title: Report Rendering try: from PIL import Image diff --git a/openerp/report/render/html2html/__init__.py b/openerp/report/render/html2html/__init__.py index 40917030f45..ac040a37989 100644 --- a/openerp/report/render/html2html/__init__.py +++ b/openerp/report/render/html2html/__init__.py @@ -21,7 +21,6 @@ from html2html import parseString -#.apidoc title: HTML to HTML engine # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/report/render/makohtml2html/__init__.py b/openerp/report/render/makohtml2html/__init__.py index 8ce38649762..d5e97470556 100644 --- a/openerp/report/render/makohtml2html/__init__.py +++ b/openerp/report/render/makohtml2html/__init__.py @@ -21,7 +21,6 @@ from makohtml2html import parseNode -#.apidoc title: MAKO to HTML engine # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/report/render/odt2odt/__init__.py b/openerp/report/render/odt2odt/__init__.py index 7155923b6b6..a0d2126ee35 100644 --- a/openerp/report/render/odt2odt/__init__.py +++ b/openerp/report/render/odt2odt/__init__.py @@ -21,6 +21,5 @@ from odt2odt import parseNode -#.apidoc title: ODT to ODT engine # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file diff --git a/openerp/report/render/rml2html/__init__.py b/openerp/report/render/rml2html/__init__.py index 96b9afc6e70..8bb6fbb507b 100644 --- a/openerp/report/render/rml2html/__init__.py +++ b/openerp/report/render/rml2html/__init__.py @@ -21,7 +21,6 @@ from rml2html import parseString -#.apidoc title: RML to HTML engine # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/report/render/rml2pdf/__init__.py b/openerp/report/render/rml2pdf/__init__.py index fdbb5f77b89..cc5c898bbf7 100644 --- a/openerp/report/render/rml2pdf/__init__.py +++ b/openerp/report/render/rml2pdf/__init__.py @@ -21,7 +21,6 @@ from trml2pdf import parseString, parseNode -#.apidoc title: RML to PDF engine # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/report/render/rml2pdf/customfonts.py b/openerp/report/render/rml2pdf/customfonts.py index ff46018a5e5..0c0095b8818 100644 --- a/openerp/report/render/rml2pdf/customfonts.py +++ b/openerp/report/render/rml2pdf/customfonts.py @@ -28,7 +28,6 @@ from reportlab import rl_config from openerp.tools import config -#.apidoc title: TTF Font Table """This module allows the mapping of some system-available TTF fonts to the reportlab engine. diff --git a/openerp/report/render/rml2txt/__init__.py b/openerp/report/render/rml2txt/__init__.py index cc0530eedb6..c80d8376272 100644 --- a/openerp/report/render/rml2txt/__init__.py +++ b/openerp/report/render/rml2txt/__init__.py @@ -21,7 +21,6 @@ from rml2txt import parseString, parseNode -#.apidoc title: RML to TXT engine """ This engine is the minimalistic renderer of RML documents into text files, using spaces and newlines to format. diff --git a/openerp/service/__init__.py b/openerp/service/__init__.py index bb37cf2cf4f..16a8596cb6f 100644 --- a/openerp/service/__init__.py +++ b/openerp/service/__init__.py @@ -40,7 +40,6 @@ import openerp.osv from openerp.release import nt_service_name import openerp.tools -#.apidoc title: RPC Services """ Classes of this module implement the network protocols that the OpenERP server uses to communicate with remote clients. diff --git a/openerp/service/http_server.py b/openerp/service/http_server.py index 6134f75f93e..63e8ce8338e 100644 --- a/openerp/service/http_server.py +++ b/openerp/service/http_server.py @@ -26,7 +26,6 @@ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ############################################################################### -#.apidoc title: HTTP and XML-RPC Server """ This module offers the family of HTTP-based servers. These are not a single class/functionality, but a set of network stack layers, implementing diff --git a/openerp/service/netrpc_server.py b/openerp/service/netrpc_server.py index af89a734f28..b3f37bd771d 100644 --- a/openerp/service/netrpc_server.py +++ b/openerp/service/netrpc_server.py @@ -19,7 +19,6 @@ # ############################################################################## -#.apidoc title: NET-RPC Server """ This file contains instance of the net-rpc server """ diff --git a/openerp/service/security.py b/openerp/service/security.py index d327efe5443..849796da8bf 100644 --- a/openerp/service/security.py +++ b/openerp/service/security.py @@ -23,7 +23,6 @@ import openerp.exceptions import openerp.pooler as pooler import openerp.tools as tools -#.apidoc title: Authentication helpers def login(db, login, password): pool = pooler.get_pool(db) diff --git a/openerp/service/web_services.py b/openerp/service/web_services.py index 940ecdb23e8..c11b1fc74b8 100644 --- a/openerp/service/web_services.py +++ b/openerp/service/web_services.py @@ -43,8 +43,6 @@ import openerp.exceptions from openerp.service import http_server from openerp import SUPERUSER_ID -#.apidoc title: Exported Service methods -#.apidoc module-mods: member-order: bysource """ This python module defines the RPC methods available to remote clients. diff --git a/openerp/service/websrv_lib.py b/openerp/service/websrv_lib.py index 2ce6d3fa09d..7767cff2b30 100644 --- a/openerp/service/websrv_lib.py +++ b/openerp/service/websrv_lib.py @@ -24,7 +24,6 @@ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ############################################################################### -#.apidoc title: HTTP Layer library (websrv_lib) """ Framework for generic http servers diff --git a/openerp/sql_db.py b/openerp/sql_db.py index 337964f3368..77c873b3482 100644 --- a/openerp/sql_db.py +++ b/openerp/sql_db.py @@ -20,7 +20,6 @@ # ############################################################################## -#.apidoc title: PostgreSQL interface """ The PostgreSQL connector is a connectivity layer between the OpenERP code and @@ -30,8 +29,6 @@ the ORM does, in fact. See also: the `pooler` module """ -#.apidoc add-functions: print_stats -#.apidoc add-classes: Cursor Connection ConnectionPool __all__ = ['db_connect', 'close_db'] diff --git a/openerp/tools/__init__.py b/openerp/tools/__init__.py index af14189bbae..18dab088440 100644 --- a/openerp/tools/__init__.py +++ b/openerp/tools/__init__.py @@ -35,7 +35,6 @@ from sql import * from float_utils import * from mail import * -#.apidoc title: Tools # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/tools/config.py b/openerp/tools/config.py index bca0f8dc603..3b04881048c 100644 --- a/openerp/tools/config.py +++ b/openerp/tools/config.py @@ -47,7 +47,6 @@ class MyOption (optparse.Option, object): self.my_default = attrs.pop('my_default', None) super(MyOption, self).__init__(*opts, **attrs) -#.apidoc title: Server Configuration Loader def check_ssl(): try: diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py index 47a2ae4faac..c145209e2f1 100644 --- a/openerp/tools/misc.py +++ b/openerp/tools/misc.py @@ -20,7 +20,6 @@ # ############################################################################## -#.apidoc title: Utilities: tools.misc """ Miscellaneous tools used by OpenERP. diff --git a/openerp/workflow/__init__.py b/openerp/workflow/__init__.py index 959a878f208..87622df7be6 100644 --- a/openerp/workflow/__init__.py +++ b/openerp/workflow/__init__.py @@ -21,7 +21,6 @@ import wkf_service -#.apidoc title: Workflow objects # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 96c8a54d223aa43ab5d8358b0cdf8950a9cfd24c Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Tue, 12 Feb 2013 15:53:09 +0100 Subject: [PATCH 31/34] [REM] Removed unused base_module_scan wizard. bzr revid: vmt@openerp.com-20130212145309-2wspc6y8f03074cn --- .../base/module/wizard/base_module_scan.py | 74 ------------------- 1 file changed, 74 deletions(-) delete mode 100644 openerp/addons/base/module/wizard/base_module_scan.py diff --git a/openerp/addons/base/module/wizard/base_module_scan.py b/openerp/addons/base/module/wizard/base_module_scan.py deleted file mode 100644 index 7ddd580d284..00000000000 --- a/openerp/addons/base/module/wizard/base_module_scan.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (). -# -# 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 . -# -############################################################################## -import os -import glob -import imp -import zipfile - -from openerp import tools -from openerp.osv import osv - -class base_module_scan(osv.osv_memory): - """ scan module """ - - _name = "base.module.scan" - _description = "scan module" - - def watch_dir(self, cr, uid, ids, context): - mod_obj = self.pool.get('ir.module.module') - all_mods = mod_obj.read(cr, uid, mod_obj.search(cr, uid, []), ['name', 'state']) - known_modules = [x['name'] for x in all_mods] - ls_ad = glob.glob(os.path.join(tools.config['addons_path'], '*', '__terp__.py')) - modules = [module_name_re.match(name).group(1) for name in ls_ad] - for fname in os.listdir(tools.config['addons_path']): - if zipfile.is_zipfile(fname): - modules.append( fname.split('.')[0]) - for module in modules: - if module in known_modules: - continue - terp = mod_obj.get_module_info(module) - if not terp.get('installable', True): - continue - - # XXX check if this code is correct... - fm = imp.find_module(module) - try: - imp.load_module(module, *fm) - finally: - if fm[0]: - fm[0].close() - - values = mod_obj.get_values_from_terp(terp) - mod_id = mod_obj.create(cr, uid, dict(name=module, state='uninstalled', **values)) - dependencies = terp.get('depends', []) - for d in dependencies: - cr.execute('insert into ir_module_module_dependency (module_id,name) values (%s, %s)', (mod_id, d)) - for module in known_modules: - terp = mod_obj.get_module_info(module) - if terp.get('installable', True): - for mod in all_mods: - if mod['name'] == module and mod['state'] == 'uninstallable': - mod_obj.write(cr, uid, [mod['id']], {'state': 'uninstalled'}) - return {} - -base_module_scan() - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 9f9c9585a1ad65a462939f03bb263e67cafca6f1 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Tue, 12 Feb 2013 16:23:28 +0100 Subject: [PATCH 32/34] [REF] Removed support for __terp__.py files and `terp` root elements in XML files. bzr revid: vmt@openerp.com-20130212152328-flpn1tbz75lhi2m2 --- doc/changelog.rst | 10 ++++++++++ doc/index.rst | 8 ++++++++ openerp/import_xml.rng | 17 +++++------------ openerp/modules/module.py | 7 ++----- openerp/tools/convert.py | 8 ++------ 5 files changed, 27 insertions(+), 23 deletions(-) create mode 100644 doc/changelog.rst diff --git a/doc/changelog.rst b/doc/changelog.rst new file mode 100644 index 00000000000..19b01a3da02 --- /dev/null +++ b/doc/changelog.rst @@ -0,0 +1,10 @@ +.. _changelog: + +Changelog +========= + +`trunk` +------- + +- Removed support for `__terp__.py` descriptor files. +- Removed support for `` root element in XML files. diff --git a/doc/index.rst b/doc/index.rst index e5918e055c4..a3d32805165 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -37,6 +37,14 @@ OpenERP Server API api_models.rst +Changelog +''''''''' + +.. toctree:: + :maxdepth: 1 + + changelog.rst + Concepts '''''''' diff --git a/openerp/import_xml.rng b/openerp/import_xml.rng index 5dd789c6e95..97b2c4a13c0 100644 --- a/openerp/import_xml.rng +++ b/openerp/import_xml.rng @@ -246,17 +246,10 @@ - - - - - - - - - - - - + + + + + diff --git a/openerp/modules/module.py b/openerp/modules/module.py index ec1c6a1708b..9f716099b56 100644 --- a/openerp/modules/module.py +++ b/openerp/modules/module.py @@ -226,7 +226,7 @@ def zip_directory(directory, b64enc=True, src=True): base = os.path.basename(path) for f in osutil.listdir(path, True): bf = os.path.basename(f) - if not RE_exclude.search(bf) and (src or bf in ('__openerp__.py', '__terp__.py') or not bf.endswith('.py')): + if not RE_exclude.search(bf) and (src or bf == '__openerp__.py' or not bf.endswith('.py')): archive.write(os.path.join(path, f), os.path.join(base, f)) archname = StringIO() @@ -310,8 +310,6 @@ def load_information_from_description_file(module): """ terp_file = get_module_resource(module, '__openerp__.py') - if not terp_file: - terp_file = get_module_resource(module, '__terp__.py') mod_path = get_module_path(module) if terp_file: info = {} @@ -354,8 +352,7 @@ def load_information_from_description_file(module): #TODO: refactor the logger in this file to follow the logging guidelines # for 6.0 - _logger.debug('module %s: no descriptor file' - ' found: __openerp__.py or __terp__.py (deprecated)', module) + _logger.debug('module %s: no __openerp__.py file found.', module) return {} diff --git a/openerp/tools/convert.py b/openerp/tools/convert.py index a64aabf2df6..00b8bf96340 100644 --- a/openerp/tools/convert.py +++ b/openerp/tools/convert.py @@ -833,12 +833,8 @@ form: module.record_id""" % (xml_id,) return model_data_obj.get_object_reference(cr, self.uid, mod, id_str) def parse(self, de): - if not de.tag in ['terp', 'openerp']: - _logger.error("Mismatch xml format") - raise Exception( "Mismatch xml format: only terp or openerp as root tag" ) - - if de.tag == 'terp': - _logger.warning("The tag is deprecated, use ") + if de.tag != 'openerp': + raise Exception("Mismatch xml format: root tag must be `openerp`.") for n in de.findall('./data'): for rec in n: From 53152d7bf43e6ac2f406981461ebebf9bb35f791 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Tue, 12 Feb 2013 17:13:30 +0100 Subject: [PATCH 33/34] [FIX] openerp.service: remove call to open_openerp_namespace bzr revid: rco@openerp.com-20130212161330-kij5s3v35zzp1536 --- openerp/service/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openerp/service/__init__.py b/openerp/service/__init__.py index 9db73223029..2eb80e545ff 100644 --- a/openerp/service/__init__.py +++ b/openerp/service/__init__.py @@ -75,7 +75,6 @@ def start_internal(): if start_internal_done: return openerp.netsvc.init_logger() - openerp.modules.loading.open_openerp_namespace() load_server_wide_modules() start_internal_done = True From 856429efd31d956f80a5ef48a3004ee225fcebdb Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Tue, 12 Feb 2013 17:21:16 +0100 Subject: [PATCH 34/34] [DOC] Updated changelog. bzr revid: vmt@openerp.com-20130212162116-0ly3zelhywn4fqvu --- doc/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.rst b/doc/changelog.rst index 19b01a3da02..464237543d5 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -8,3 +8,5 @@ Changelog - 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 + of `openerp.tools` in an addons).