From fc58729b2f2a6a2baa79d474cfc2eb15234f22a1 Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Tue, 15 Jan 2013 18:47:05 +0100 Subject: [PATCH 01/35] [WIP] introduce a way to reference specific config options bzr revid: abo@openerp.com-20130115174705-qldpq3le1ul4nh0j --- openerp/addons/base/res/res_config.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index ff0c3bfe6ad..a3b168a8d82 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -570,17 +570,17 @@ class res_config_settings(osv.osv_memory): if action_ids: return act_window.read(cr, uid, action_ids[0], [], context=context) return {} - + def name_get(self, cr, uid, ids, context=None): """ Override name_get method to return an appropriate configuration wizard name, and not the generated name.""" - + if not ids: return [] # name_get may receive int id instead of an id list if isinstance(ids, (int, long)): ids = [ids] - + act_window = self.pool.get('ir.actions.act_window') action_ids = act_window.search(cr, uid, [('res_model', '=', self._name)], context=context) name = self._name @@ -588,4 +588,23 @@ class res_config_settings(osv.osv_memory): name = act_window.read(cr, uid, action_ids[0], ['name'], context=context)['name'] return [(record.id, name) for record in self.browse(cr, uid , ids, context=context)] + def get_path(self, cr, uid, module_name, menu_xml_id=None, field_name=None, context=None): + """ + Return a string representing the path to access a specific + configuration option through the interface. + """ + + config_path = '' + + # Fetch the path + if (menu_xml_id): + dummy, menu_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, module_name, menu_xml_id) + config_path += self.pool.get('ir.ui.menu').browse(cr, uid, menu_id, context=context).name + + # Fetch the exact config option name + #if (field_name): + # self.pool.get('mycfgobj')._get_all_columns().strings[field_name] + + return config_path + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From ff25f7a3d4d6dd15170692487de304a804060b0d Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Wed, 16 Jan 2013 18:14:11 +0100 Subject: [PATCH 02/35] [IMP] use a var to store the menuitem separator makes it easier to modify makes it possible to import anywhere bzr revid: abo@openerp.com-20130116171411-iugmyk4vqjewko2z --- openerp/addons/base/ir/ir_ui_menu.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openerp/addons/base/ir/ir_ui_menu.py b/openerp/addons/base/ir/ir_ui_menu.py index 5da85c43577..95379098cf4 100644 --- a/openerp/addons/base/ir/ir_ui_menu.py +++ b/openerp/addons/base/ir/ir_ui_menu.py @@ -30,6 +30,8 @@ from openerp.osv import fields, osv from openerp.tools.translate import _ from openerp import SUPERUSER_ID +MENU_ITEM_SEPARATOR = "/" + def one_in(setA, setB): """Check the presence of an element of setA in setB """ @@ -154,7 +156,7 @@ class ir_ui_menu(osv.osv): if level<=0: return '...' if elmt.parent_id: - parent_path = self._get_one_full_name(elmt.parent_id, level-1) + "/" + parent_path = self._get_one_full_name(elmt.parent_id, level-1) + MENU_ITEM_SEPARATOR else: parent_path = '' return parent_path + elmt.name From 70890bfa3250e33769e12a818780f90b3889be63 Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Wed, 16 Jan 2013 18:14:54 +0100 Subject: [PATCH 03/35] [WIP] introduce a way to reference specific config options bzr revid: abo@openerp.com-20130116171454-s4tt2f6le80oruc0 --- openerp/addons/base/res/res_config.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index a3b168a8d82..f7085f44000 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -25,6 +25,7 @@ from openerp import pooler from openerp.osv import osv, fields from openerp.tools import ustr from openerp.tools.translate import _ +from openerp.addons.base.ir.ir_ui_menu import MENU_ITEM_SEPARATOR _logger = logging.getLogger(__name__) @@ -588,22 +589,24 @@ class res_config_settings(osv.osv_memory): name = act_window.read(cr, uid, action_ids[0], ['name'], context=context)['name'] return [(record.id, name) for record in self.browse(cr, uid , ids, context=context)] - def get_path(self, cr, uid, module_name, menu_xml_id=None, field_name=None, context=None): + def get_config_path(self, cr, uid, module_name, menu_xml_id=None, model_name=None, field_name=None, context=None): """ Return a string representing the path to access a specific configuration option through the interface. + + :return string """ config_path = '' # Fetch the path - if (menu_xml_id): + if (module_name and menu_xml_id): dummy, menu_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, module_name, menu_xml_id) - config_path += self.pool.get('ir.ui.menu').browse(cr, uid, menu_id, context=context).name + config_path += self.pool.get('ir.ui.menu').browse(cr, uid, menu_id, context=context).complete_name # Fetch the exact config option name - #if (field_name): - # self.pool.get('mycfgobj')._get_all_columns().strings[field_name] + if (model_name and field_name): + config_path += MENU_ITEM_SEPARATOR + self.pool.get(model_name)._all_columns.get(field_name).column.string return config_path From 8214854b7996804bdfc8ec3d4ea2398f03d50370 Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Thu, 17 Jan 2013 15:47:55 +0100 Subject: [PATCH 04/35] [IMP] doc bzr revid: abo@openerp.com-20130117144755-r9oboeaw8rr1seqa --- openerp/osv/osv.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openerp/osv/osv.py b/openerp/osv/osv.py index 018976ac663..20dfe74b7ff 100644 --- a/openerp/osv/osv.py +++ b/openerp/osv/osv.py @@ -39,6 +39,7 @@ import openerp.exceptions _logger = logging.getLogger(__name__) # Deprecated. +# Have a look at exceptions.py instead. class except_osv(Exception): def __init__(self, name, value): self.name = name From 6b926883ccdb362b85be7b8e878f21775ad0375e Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Thu, 17 Jan 2013 16:50:03 +0100 Subject: [PATCH 05/35] [IMP] add a missing import (exceptions) bzr revid: abo@openerp.com-20130117155003-5akv67tylaz59s7l --- openerp/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openerp/__init__.py b/openerp/__init__.py index c9db7076e71..0afcaf0bfc0 100644 --- a/openerp/__init__.py +++ b/openerp/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- ############################################################################## -# +# # OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). # @@ -15,7 +15,7 @@ # 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 . +# along with this program. If not, see . # ############################################################################## @@ -28,6 +28,7 @@ SUPERUSER_ID = 1 import addons import cli import conf +import exceptions import loglevels import modules import netsvc From c588e762d4dde0680e9609202065e5bf739edcec Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Thu, 17 Jan 2013 17:05:46 +0100 Subject: [PATCH 06/35] [IMP] simplify the new config reference mechanism code bzr revid: abo@openerp.com-20130117160546-mg9g9zrbvp4poly6 --- openerp/addons/base/res/res_config.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index f7085f44000..cec30a6d303 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -589,24 +589,34 @@ class res_config_settings(osv.osv_memory): name = act_window.read(cr, uid, action_ids[0], ['name'], context=context)['name'] return [(record.id, name) for record in self.browse(cr, uid , ids, context=context)] - def get_config_path(self, cr, uid, module_name, menu_xml_id=None, model_name=None, field_name=None, context=None): + def get_path(self, cr, uid, menu_xml_id=None, field_name=None, context=None): """ Return a string representing the path to access a specific configuration option through the interface. - :return string + :return string: may vary depending of the given params + - if menu_xml_id and not field_name: the string will contain the + path to the config option. + e.g.: "Settings/Configuration/Sales" + - if not menu_xml_id and field_name: the string will contain the + config option's human readable name + e.g.: "Create leads from incoming mails" + - if menu_xml_id and field_name: the string will contain the path + and the config option's human readable name + e.g.: "Settings/Configuration/Sales/Create leads from incoming mails" """ config_path = '' - # Fetch the path - if (module_name and menu_xml_id): + # Fetch the path to the config option + if (menu_xml_id): + module_name, menu_xml_id = menu_xml_id.split('.') dummy, menu_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, module_name, menu_xml_id) config_path += self.pool.get('ir.ui.menu').browse(cr, uid, menu_id, context=context).complete_name # Fetch the exact config option name - if (model_name and field_name): - config_path += MENU_ITEM_SEPARATOR + self.pool.get(model_name)._all_columns.get(field_name).column.string + if (field_name): + config_path += MENU_ITEM_SEPARATOR + self._all_columns.get(field_name).column.string return config_path From fb5288e93cc68197400d2deaf7fa522273d73a5a Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Thu, 17 Jan 2013 19:33:42 +0100 Subject: [PATCH 07/35] [IMP] get_path code; return a tuple instead of a string also add a helper bzr revid: abo@openerp.com-20130117183342-rhfnxh4y777rruqt --- openerp/addons/base/res/res_config.py | 39 +++++++++++++-------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index cec30a6d303..910c473a4be 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -589,35 +589,34 @@ class res_config_settings(osv.osv_memory): name = act_window.read(cr, uid, action_ids[0], ['name'], context=context)['name'] return [(record.id, name) for record in self.browse(cr, uid , ids, context=context)] - def get_path(self, cr, uid, menu_xml_id=None, field_name=None, context=None): + def get_path(self, cr, uid, menu_xml_id, field_name, context=None): """ Return a string representing the path to access a specific configuration option through the interface. - :return string: may vary depending of the given params - - if menu_xml_id and not field_name: the string will contain the - path to the config option. - e.g.: "Settings/Configuration/Sales" - - if not menu_xml_id and field_name: the string will contain the - config option's human readable name - e.g.: "Create leads from incoming mails" - - if menu_xml_id and field_name: the string will contain the path - and the config option's human readable name - e.g.: "Settings/Configuration/Sales/Create leads from incoming mails" + Note that we'll prefer to find back the model's name through the + ui_menu and then its action than directly using self. It will + make the call to this method from a helper function possible. + + :return tuple t: t[0] contains the full path, t[1] contains + the "human readable" configuration option name """ - config_path = '' - # Fetch the path to the config option - if (menu_xml_id): - module_name, menu_xml_id = menu_xml_id.split('.') - dummy, menu_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, module_name, menu_xml_id) - config_path += self.pool.get('ir.ui.menu').browse(cr, uid, menu_id, context=context).complete_name + module_name, menu_xml_id = menu_xml_id.split('.') + dummy, menu_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, module_name, menu_xml_id) + ir_ui_menu = self.pool.get('ir.ui.menu').browse(cr, uid, menu_id, context=context) + res_name = ir_ui_menu.complete_name # Fetch the exact config option name - if (field_name): - config_path += MENU_ITEM_SEPARATOR + self._all_columns.get(field_name).column.string + res_path = self.pool.get(ir_ui_menu.action.res_model)._all_columns.get(field_name).column.string - return config_path + return (res_name, res_path) + +def get_config_path(cr, uid, menu_xml_id=None, field_name=None, context=None): + """ + Simple helper for res_config_settings.get_path(). + """ + return pooler.get_pool(cr.dbname).get('res.config.settings').get_path(cr, uid, menu_xml_id, field_name, context) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From a075983249df68cc9797b2c92f32f0039a4e68b9 Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Fri, 18 Jan 2013 12:03:00 +0100 Subject: [PATCH 08/35] [IMP] simplify the helper by using SUPERUSER_ID bzr revid: abo@openerp.com-20130118110300-d0z0xr8df0zygiv5 --- openerp/addons/base/res/res_config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index 910c473a4be..81e01e46f2f 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -21,7 +21,7 @@ import logging from operator import attrgetter -from openerp import pooler +from openerp import pooler, SUPERUSER_ID from openerp.osv import osv, fields from openerp.tools import ustr from openerp.tools.translate import _ @@ -613,10 +613,10 @@ class res_config_settings(osv.osv_memory): return (res_name, res_path) -def get_config_path(cr, uid, menu_xml_id=None, field_name=None, context=None): +def get_config_path(cr, menu_xml_id=None, field_name=None, context=None): """ Simple helper for res_config_settings.get_path(). """ - return pooler.get_pool(cr.dbname).get('res.config.settings').get_path(cr, uid, menu_xml_id, field_name, context) + return pooler.get_pool(cr.dbname).get('res.config.settings').get_path(cr, SUPERUSER_ID, menu_xml_id, field_name, context) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 8bd622202b123b4919ca7862b92d4fe19d5f464b Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Fri, 18 Jan 2013 16:23:46 +0100 Subject: [PATCH 09/35] [IMP] introduce a new type of exception: WarningConfig. Note that no corresponding RPC_FAULT_CODE has been added; WarningConfig will share the same RPC_FAULT_CODE than Warning (RPC_FAULT_CODE_WARNING = 2) Also remove a few whitespaces bzr revid: abo@openerp.com-20130118152346-5v4j94wjrq34hq4m --- openerp/exceptions.py | 8 ++++++++ openerp/netsvc.py | 4 +++- openerp/service/netrpc_server.py | 8 +++++--- openerp/service/wsgi_server.py | 4 +++- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/openerp/exceptions.py b/openerp/exceptions.py index fbf52c7f3a9..387e126b8c4 100644 --- a/openerp/exceptions.py +++ b/openerp/exceptions.py @@ -27,9 +27,17 @@ treated as a 'Server error'. """ +import re + class Warning(Exception): pass +class WarningConfig(Exception): + """ Warning bound to a misconfiguration. """ + def __init__(self, msg): + # todo: treat the msg (regex) + super(WarningConfig, self).__init__(msg) + class AccessDenied(Exception): """ Login/password error. No message, no traceback. """ def __init__(self): diff --git a/openerp/netsvc.py b/openerp/netsvc.py index 533b1659191..3362b86fac7 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -107,7 +107,7 @@ class ExportService(object): """ _services = {} - + def __init__(self, name): ExportService._services[name] = self self.__name = name @@ -306,6 +306,8 @@ def dispatch_rpc(service_name, method, params): raise except openerp.exceptions.Warning: raise + except openerp.exceptions.WarningConfig: + raise except openerp.exceptions.DeferredException, e: _logger.exception(tools.exception_to_unicode(e)) post_mortem(e.traceback) diff --git a/openerp/service/netrpc_server.py b/openerp/service/netrpc_server.py index af89a734f28..f87274ef6e6 100644 --- a/openerp/service/netrpc_server.py +++ b/openerp/service/netrpc_server.py @@ -15,7 +15,7 @@ # 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 . +# along with this program. If not, see . # ############################################################################## @@ -145,7 +145,7 @@ class TinySocketClientThread(threading.Thread): break except Exception, e: try: - valid_exception = Exception(netrpc_handle_exception_legacy(e)) + 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) @@ -164,12 +164,14 @@ class TinySocketClientThread(threading.Thread): def stop(self): self.running = False - + def netrpc_handle_exception_legacy(e): if isinstance(e, openerp.osv.osv.except_osv): 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.WarningConfig): + 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): diff --git a/openerp/service/wsgi_server.py b/openerp/service/wsgi_server.py index 67436a1c0bf..e83f06cc264 100644 --- a/openerp/service/wsgi_server.py +++ b/openerp/service/wsgi_server.py @@ -95,6 +95,8 @@ def xmlrpc_handle_exception(e): response = xmlrpclib.dumps(fault, allow_none=False, encoding=None) elif isinstance(e, openerp.exceptions.Warning): fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, str(e)) + elif isinstance(e, openerp.exceptions.WarningConfig): + fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, str(e)) response = xmlrpclib.dumps(fault, allow_none=False, encoding=None) elif isinstance (e, openerp.exceptions.AccessError): fault = xmlrpclib.Fault(RPC_FAULT_CODE_ACCESS_ERROR, str(e)) @@ -311,7 +313,7 @@ def http_to_wsgi(http_dir): handler.auth_provider.checkRequest(handler, path) except websrv_lib.AuthRequiredExc, ae: # Darwin 9.x.x webdav clients will report "HTTP/1.0" to us, while they support (and need) the - # authorisation features of HTTP/1.1 + # authorisation features of HTTP/1.1 if request_version != 'HTTP/1.1' and ('Darwin/9.' not in handler.headers.get('User-Agent', '')): start_response("403 Forbidden", []) return [] From 6f5c0e1a08a003087b3d61e8602d1c7ab02dd688 Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Fri, 18 Jan 2013 18:15:41 +0100 Subject: [PATCH 10/35] [REV] reintroduce the model's name in the get_path()'s signature, but in a different way bzr revid: abo@openerp.com-20130118171541-tyxl4k6w1wwa3kyl --- openerp/addons/base/res/res_config.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index 81e01e46f2f..197b60ebab0 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -589,15 +589,11 @@ class res_config_settings(osv.osv_memory): name = act_window.read(cr, uid, action_ids[0], ['name'], context=context)['name'] return [(record.id, name) for record in self.browse(cr, uid , ids, context=context)] - def get_path(self, cr, uid, menu_xml_id, field_name, context=None): + def get_path(self, cr, uid, menu_xml_id=None, full_field_name=None, context=None): """ Return a string representing the path to access a specific configuration option through the interface. - Note that we'll prefer to find back the model's name through the - ui_menu and then its action than directly using self. It will - make the call to this method from a helper function possible. - :return tuple t: t[0] contains the full path, t[1] contains the "human readable" configuration option name """ @@ -606,12 +602,13 @@ class res_config_settings(osv.osv_memory): module_name, menu_xml_id = menu_xml_id.split('.') dummy, menu_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, module_name, menu_xml_id) ir_ui_menu = self.pool.get('ir.ui.menu').browse(cr, uid, menu_id, context=context) - res_name = ir_ui_menu.complete_name + res_path = ir_ui_menu.complete_name # Fetch the exact config option name - res_path = self.pool.get(ir_ui_menu.action.res_model)._all_columns.get(field_name).column.string + model_name, field_name = full_field_name.rsplit('.', 1) + res_name = self.pool.get(model_name)._all_columns.get(field_name).column.string - return (res_name, res_path) + return (res_path, res_name) def get_config_path(cr, menu_xml_id=None, field_name=None, context=None): """ From 64eedb68166a418ce355e3aa01c4d0bcba6cd39a Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Fri, 18 Jan 2013 18:16:19 +0100 Subject: [PATCH 11/35] [WIP] start the implementation of WarningConfig bzr revid: abo@openerp.com-20130118171619-dx0yjwuun0j7m43p --- openerp/exceptions.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/openerp/exceptions.py b/openerp/exceptions.py index 387e126b8c4..f99ed433049 100644 --- a/openerp/exceptions.py +++ b/openerp/exceptions.py @@ -35,7 +35,20 @@ class Warning(Exception): class WarningConfig(Exception): """ Warning bound to a misconfiguration. """ def __init__(self, msg): - # todo: treat the msg (regex) + # WIP WIP WIP WIP WIP WIP WIP + def sub_path(g): + menu_xml_id = g.group(1) + + return '(group to convert: '+menu_xml_id+')' + + def sub_path(g): + menu_xml_id = g.group(1) + + return '(group to convert: '+menu_xml_id+')' + + # Treat the msg: find the menu_xml_id and the field_name + teststr = 'coucou [path:blabla_bla_bla]' + print ">>>>> sub: ", re.sub(r'\[path:([a-z_]*)\]', sub_path, teststr) super(WarningConfig, self).__init__(msg) class AccessDenied(Exception): From 9d8a2b9eb523d2676dd6ed06d550b96094086ffd Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Mon, 21 Jan 2013 16:07:48 +0100 Subject: [PATCH 12/35] [WIP] move the code to another place bzr revid: abo@openerp.com-20130121150748-3bw28jhvsj23s1zo --- openerp/addons/base/res/res_config.py | 44 ++++++++++++++++++--------- openerp/exceptions.py | 16 ---------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index 197b60ebab0..d96b2b5175c 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -20,12 +20,13 @@ ############################################################################## import logging from operator import attrgetter +import re from openerp import pooler, SUPERUSER_ID from openerp.osv import osv, fields from openerp.tools import ustr from openerp.tools.translate import _ -from openerp.addons.base.ir.ir_ui_menu import MENU_ITEM_SEPARATOR +from openerp import exceptions _logger = logging.getLogger(__name__) @@ -594,26 +595,41 @@ class res_config_settings(osv.osv_memory): Return a string representing the path to access a specific configuration option through the interface. - :return tuple t: t[0] contains the full path, t[1] contains + :return dict d: d['path'] contains the full path, d['name'] contains the "human readable" configuration option name """ + res = {'path': None, 'name': None} + # Fetch the path to the config option - module_name, menu_xml_id = menu_xml_id.split('.') - dummy, menu_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, module_name, menu_xml_id) - ir_ui_menu = self.pool.get('ir.ui.menu').browse(cr, uid, menu_id, context=context) - res_path = ir_ui_menu.complete_name + if (menu_xml_id): + module_name, menu_xml_id = menu_xml_id.split('.') + dummy, menu_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, module_name, menu_xml_id) + ir_ui_menu = self.pool.get('ir.ui.menu').browse(cr, uid, menu_id, context=context) + res['path'] = ir_ui_menu.complete_name # Fetch the exact config option name - model_name, field_name = full_field_name.rsplit('.', 1) - res_name = self.pool.get(model_name)._all_columns.get(field_name).column.string + if (full_field_name): + model_name, field_name = full_field_name.rsplit('.', 1) + res['name'] = self.pool.get(model_name)._all_columns.get(field_name).column.string - return (res_path, res_name) + return res -def get_config_path(cr, menu_xml_id=None, field_name=None, context=None): - """ - Simple helper for res_config_settings.get_path(). - """ - return pooler.get_pool(cr.dbname).get('res.config.settings').get_path(cr, SUPERUSER_ID, menu_xml_id, field_name, context) +def get_warning_config(cr, msg, context=None): + res_config_obj = pooler.get_pool(cr.dbname).get('res.config.settings') + + def sub_path(g): + s = g.group(1) + return res_config_obj.get_path(cr, SUPERUSER_ID, menu_xml_id=s, full_field_name=None, context=context)['path'] + + def sub_field(g): + s = g.group(1) + return res_config_obj.get_path(cr, SUPERUSER_ID, menu_xml_id=None, full_field_name=s, context=context)['name'] + + # Treat the msg: find the menu_xml_id and the field_name + msg = re.sub(r'\[path:([a-z_\.]*)\]', sub_path, msg) + msg = re.sub(r'\[field:([a-z_\.]*)\]', sub_field, msg) + + return exceptions.WarningConfig(msg) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/exceptions.py b/openerp/exceptions.py index f99ed433049..c43f706fa61 100644 --- a/openerp/exceptions.py +++ b/openerp/exceptions.py @@ -27,28 +27,12 @@ treated as a 'Server error'. """ -import re - class Warning(Exception): pass class WarningConfig(Exception): """ Warning bound to a misconfiguration. """ def __init__(self, msg): - # WIP WIP WIP WIP WIP WIP WIP - def sub_path(g): - menu_xml_id = g.group(1) - - return '(group to convert: '+menu_xml_id+')' - - def sub_path(g): - menu_xml_id = g.group(1) - - return '(group to convert: '+menu_xml_id+')' - - # Treat the msg: find the menu_xml_id and the field_name - teststr = 'coucou [path:blabla_bla_bla]' - print ">>>>> sub: ", re.sub(r'\[path:([a-z_]*)\]', sub_path, teststr) super(WarningConfig, self).__init__(msg) class AccessDenied(Exception): From 0a0e5e9fb02817a0a12c1019590e540ab4f9eb05 Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Mon, 21 Jan 2013 17:59:16 +0100 Subject: [PATCH 13/35] [WIP] heavy refactoring of the get option path feature bzr revid: abo@openerp.com-20130121165916-9oc7ox7ygmpb6lhm --- openerp/addons/base/res/res_config.py | 55 ++++++++++++--------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index d96b2b5175c..68f11d989c6 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -590,46 +590,39 @@ class res_config_settings(osv.osv_memory): name = act_window.read(cr, uid, action_ids[0], ['name'], context=context)['name'] return [(record.id, name) for record in self.browse(cr, uid , ids, context=context)] - def get_path(self, cr, uid, menu_xml_id=None, full_field_name=None, context=None): - """ - Return a string representing the path to access a specific - configuration option through the interface. + def get_option_path(self, cr, uid, menu_xml_id=None, context=None): + module_name, menu_xml_id = menu_xml_id.split('.') + dummy, menu_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, module_name, menu_xml_id) + ir_ui_menu = self.pool.get('ir.ui.menu').browse(cr, uid, menu_id, context=context) - :return dict d: d['path'] contains the full path, d['name'] contains - the "human readable" configuration option name - """ + return ir_ui_menu.complete_name - res = {'path': None, 'name': None} + def get_option_name(self, cr, uid, full_field_name=None, context=None): + model_name, field_name = full_field_name.rsplit('.', 1) - # Fetch the path to the config option - if (menu_xml_id): - module_name, menu_xml_id = menu_xml_id.split('.') - dummy, menu_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, module_name, menu_xml_id) - ir_ui_menu = self.pool.get('ir.ui.menu').browse(cr, uid, menu_id, context=context) - res['path'] = ir_ui_menu.complete_name - - # Fetch the exact config option name - if (full_field_name): - model_name, field_name = full_field_name.rsplit('.', 1) - res['name'] = self.pool.get(model_name)._all_columns.get(field_name).column.string - - return res + return self.pool.get(model_name)._all_columns.get(field_name).column.string def get_warning_config(cr, msg, context=None): res_config_obj = pooler.get_pool(cr.dbname).get('res.config.settings') - def sub_path(g): - s = g.group(1) - return res_config_obj.get_path(cr, SUPERUSER_ID, menu_xml_id=s, full_field_name=None, context=context)['path'] + # Treat the msg + # 1/ find the path and field references, put them in a list + references = re.findall(r'\[((?:path|field):[a-z_\.]*)\]', msg, flags=re.I) - def sub_field(g): - s = g.group(1) - return res_config_obj.get_path(cr, SUPERUSER_ID, menu_xml_id=None, full_field_name=s, context=context)['name'] + # 2/ fetch the path and field replacement values + # (full path and human readable field's name) + values = [] + for item in references: + ref_type, ref = item.split(':') + if ref_type == 'path': + values.append(res_config_obj.get_option_path(cr, SUPERUSER_ID, ref, context)) + elif ref_type == 'field': + values.append(res_config_obj.get_option_name(cr, SUPERUSER_ID, ref, context)) - # Treat the msg: find the menu_xml_id and the field_name - msg = re.sub(r'\[path:([a-z_\.]*)\]', sub_path, msg) - msg = re.sub(r'\[field:([a-z_\.]*)\]', sub_field, msg) + # 3/ replace the text references by the %s conversion symbol + msg = re.sub(r'\[((?:path|field):[a-z_\.]*)\]', '%s', msg, flags=re.I) - return exceptions.WarningConfig(msg) + # 4/ translate all the strings, substitute and return the result + return exceptions.WarningConfig(_(msg) % tuple(map(_, values))) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 8ab50cc0efbc52fd043e4252d32e845abfad00a7 Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Mon, 21 Jan 2013 19:16:50 +0100 Subject: [PATCH 14/35] [WIP] remove a useless translation phase bzr revid: abo@openerp.com-20130121181650-md0t80lokegr5kx1 --- openerp/addons/base/res/res_config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index 68f11d989c6..7f5dd7779e5 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -605,7 +605,7 @@ class res_config_settings(osv.osv_memory): def get_warning_config(cr, msg, context=None): res_config_obj = pooler.get_pool(cr.dbname).get('res.config.settings') - # Treat the msg + # Process the msg # 1/ find the path and field references, put them in a list references = re.findall(r'\[((?:path|field):[a-z_\.]*)\]', msg, flags=re.I) @@ -622,7 +622,7 @@ def get_warning_config(cr, msg, context=None): # 3/ replace the text references by the %s conversion symbol msg = re.sub(r'\[((?:path|field):[a-z_\.]*)\]', '%s', msg, flags=re.I) - # 4/ translate all the strings, substitute and return the result - return exceptions.WarningConfig(_(msg) % tuple(map(_, values))) + # 4/ substitute and return the result + return exceptions.WarningConfig(msg % tuple(values)) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 9419e59fefb9fce7450b95cb405623e8710a1d95 Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Tue, 22 Jan 2013 15:31:56 +0100 Subject: [PATCH 15/35] [IMP] change the warning string's pattern to something more familiar to translators bzr revid: abo@openerp.com-20130122143156-ffp79tdg1ryvr5y2 --- openerp/addons/base/res/res_config.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index 7f5dd7779e5..9580121e98f 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -604,25 +604,23 @@ class res_config_settings(osv.osv_memory): def get_warning_config(cr, msg, context=None): res_config_obj = pooler.get_pool(cr.dbname).get('res.config.settings') + regex_path = r'%\(((?:path|field):[a-z_\.]*)\)s' - # Process the msg - # 1/ find the path and field references, put them in a list - references = re.findall(r'\[((?:path|field):[a-z_\.]*)\]', msg, flags=re.I) + # Process the message + # 1/ find the path and/or field references, put them in a list + references = re.findall(regex_path, msg, flags=re.I) - # 2/ fetch the path and field replacement values + # 2/ fetch the path and/or field replacement values # (full path and human readable field's name) - values = [] + values = {} for item in references: ref_type, ref = item.split(':') if ref_type == 'path': - values.append(res_config_obj.get_option_path(cr, SUPERUSER_ID, ref, context)) + values[item] = res_config_obj.get_option_path(cr, SUPERUSER_ID, ref, context) elif ref_type == 'field': - values.append(res_config_obj.get_option_name(cr, SUPERUSER_ID, ref, context)) - - # 3/ replace the text references by the %s conversion symbol - msg = re.sub(r'\[((?:path|field):[a-z_\.]*)\]', '%s', msg, flags=re.I) + values[item] = res_config_obj.get_option_name(cr, SUPERUSER_ID, ref, context) # 4/ substitute and return the result - return exceptions.WarningConfig(msg % tuple(values)) + return exceptions.WarningConfig(msg % values) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 5cb047aa75922c2560f18fdf0f973e63a5f6c731 Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Tue, 22 Jan 2013 17:22:23 +0100 Subject: [PATCH 16/35] [IMP] add documentation also replace the %(path:)s by %(menu:)s (more cohesive) bzr revid: abo@openerp.com-20130122162223-no1wzf1gyxpdvzqk --- openerp/addons/base/res/res_config.py | 41 +++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index 9580121e98f..6ed28f88927 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -591,6 +591,15 @@ class res_config_settings(osv.osv_memory): return [(record.id, name) for record in self.browse(cr, uid , ids, context=context)] def get_option_path(self, cr, uid, menu_xml_id=None, context=None): + """ + Fetch the path to a specified configuration view. + + :param string menu_xml_id: the xml id of the menuitem where the view + is located, structured as follows: module_name.menuitem_xml_id + (e.g.: "base.menu_sale_config") + :return string: full path to the menuitem + (e.g.: "Settings/Configuration/Sales") + """ module_name, menu_xml_id = menu_xml_id.split('.') dummy, menu_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, module_name, menu_xml_id) ir_ui_menu = self.pool.get('ir.ui.menu').browse(cr, uid, menu_id, context=context) @@ -598,13 +607,41 @@ class res_config_settings(osv.osv_memory): return ir_ui_menu.complete_name def get_option_name(self, cr, uid, full_field_name=None, context=None): + """ + Fetch the human readable name of a specified configuration option. + + :param string full_field_name: the full name of the field, structured + as follows: model_name.field_name + (e.g.: "sale.config.settings.fetchmail_lead") + :return string: human readable name of the field + (e.g.: "Create leads from incoming mails") + """ model_name, field_name = full_field_name.rsplit('.', 1) return self.pool.get(model_name)._all_columns.get(field_name).column.string def get_warning_config(cr, msg, context=None): + """ + Helper: return a WarningConfig exception with the given message where the + %(field:)s and/or %(menu:)s are replaced by the human readable field's name + and/or menuitem's full path. + + Usage: + ------ + Just include in your error message %(field:model_name.field_name)s to + obtain the human readable field's name, and/or + %(menu:module_name.menuitem_xml_id)s to obtain the menuitem's full path. + + Example of use: + --------------- + from openerp.addons.base.res.res_config import get_warning_config + raise get_warning_config(cr, _("Error: this action is prohibited. You should check the field %(field:sale.config.settings.fetchmail_lead)s in %(menu:base.menu_sale_config)s."), context=context) + will return an exception containing the following message: + Error: this action is prohibited. You should check the field Create leads from incoming mails in Settings/Configuration/Sales. + """ + res_config_obj = pooler.get_pool(cr.dbname).get('res.config.settings') - regex_path = r'%\(((?:path|field):[a-z_\.]*)\)s' + regex_path = r'%\(((?:menu|field):[a-z_\.]*)\)s' # Process the message # 1/ find the path and/or field references, put them in a list @@ -615,7 +652,7 @@ def get_warning_config(cr, msg, context=None): values = {} for item in references: ref_type, ref = item.split(':') - if ref_type == 'path': + if ref_type == 'menu': values[item] = res_config_obj.get_option_path(cr, SUPERUSER_ID, ref, context) elif ref_type == 'field': values[item] = res_config_obj.get_option_name(cr, SUPERUSER_ID, ref, context) From 0b65aa2231e6aac97b3aae844bddfacf9a338c8c Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Wed, 23 Jan 2013 16:22:34 +0100 Subject: [PATCH 17/35] [IMP] fix a comment's indentation, remove a few whitespaces bzr revid: abo@openerp.com-20130123152234-g2ou48xajxsq8sqj --- openerp/osv/orm.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 6056e405b78..8b10d7da168 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -2287,7 +2287,7 @@ class BaseModel(object): if view_type == 'tree' or not action[2].get('multi')] resprint = [clean(print_) for print_ in resprint if view_type == 'tree' or not print_[2].get('multi')] - #When multi="True" set it will display only in More of the list view + #When multi="True" set it will display only in More of the list view resrelate = [clean(action) for action in resrelate if (action[2].get('multi') and view_type == 'tree') or (not action[2].get('multi') and view_type == 'form')] @@ -2682,17 +2682,17 @@ class BaseModel(object): order = orderby or groupby data_ids = self.search(cr, uid, [('id', 'in', alldata.keys())], order=order, context=context) - + # the IDs of records that have groupby field value = False or '' should be included too data_ids += set(alldata.keys()).difference(data_ids) - - if groupby: + + if groupby: data = self.read(cr, uid, data_ids, [groupby], context=context) # restore order of the search as read() uses the default _order (this is only for groups, so the footprint of data should be small): data_dict = dict((d['id'], d[groupby] ) for d in data) result = [{'id': i, groupby: data_dict[i]} for i in data_ids] else: - result = [{'id': i} for i in data_ids] + result = [{'id': i} for i in data_ids] for d in result: if groupby: @@ -2756,7 +2756,7 @@ class BaseModel(object): return _logger.info('Computing parent left and right for table %s...', self._table) def browse_rec(root, pos=0): -# TODO: set order + # TODO: set order where = self._parent_name+'='+str(root) if not root: where = self._parent_name+' IS NULL' @@ -3867,7 +3867,7 @@ class BaseModel(object): if operation in ('read','unlink'): # No need to warn about deleting an already deleted record. # And no error when reading a record that was deleted, to prevent spurious - # errors for non-transactional search/read sequences coming from clients + # errors for non-transactional search/read sequences coming from clients return _logger.warning('Failed operation on deleted record(s): %s, uid: %s, model: %s', operation, uid, self._name) raise except_orm(_('Missing document(s)'), @@ -4355,12 +4355,12 @@ class BaseModel(object): del vals[self._inherits[table]] record_id = tocreate[table].pop('id', None) - + # When linking/creating parent records, force context without 'no_store_function' key that - # defers stored functions computing, as these won't be computed in batch at the end of create(). + # defers stored functions computing, as these won't be computed in batch at the end of create(). parent_context = dict(context) parent_context.pop('no_store_function', None) - + if record_id is None or not record_id: record_id = self.pool.get(table).create(cr, user, tocreate[table], context=parent_context) else: @@ -4409,7 +4409,7 @@ class BaseModel(object): upd0 = upd0 + ',"' + field + '"' upd1 = upd1 + ',' + self._columns[field]._symbol_set[0] upd2.append(self._columns[field]._symbol_set[1](vals[field])) - #for the function fields that receive a value, we set them directly in the database + #for the function fields that receive a value, we set them directly in the database #(they may be required), but we also need to trigger the _fct_inv() if (hasattr(self._columns[field], '_fnct_inv')) and not isinstance(self._columns[field], fields.related): #TODO: this way to special case the related fields is really creepy but it shouldn't be changed at From 11dbed0fb7f9d5720c7e9b49c0df0d3f2a19c339 Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Wed, 23 Jan 2013 16:37:22 +0100 Subject: [PATCH 18/35] [IMP] only call ir.translation when necessary bzr revid: abo@openerp.com-20130123153722-iijbl8hvesstneq3 --- openerp/osv/orm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 8b10d7da168..471f72a82ac 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -3504,7 +3504,6 @@ class BaseModel(object): res = {} - translation_obj = self.pool.get('ir.translation') for parent in self._inherits: res.update(self.pool.get(parent).fields_get(cr, user, allfields, context)) @@ -3520,6 +3519,8 @@ class BaseModel(object): res[f]['states'] = {} if 'lang' in context: + translation_obj = self.pool.get('ir.translation') + if 'string' in res[f]: res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context['lang']) if res_trans: From 28960472df20d10024f2c45596a116dd87d81643 Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Wed, 23 Jan 2013 16:49:43 +0100 Subject: [PATCH 19/35] [IMP] use fields_get() instead of _all_columns.get(): this method has the advantage to provide translation support bzr revid: abo@openerp.com-20130123154943-2ux9vpts4pmvr7nq --- openerp/addons/base/res/res_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index 6ed28f88927..d2f2e8a265f 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -618,7 +618,7 @@ class res_config_settings(osv.osv_memory): """ model_name, field_name = full_field_name.rsplit('.', 1) - return self.pool.get(model_name)._all_columns.get(field_name).column.string + return self.pool.get(model_name).fields_get(cr, uid, allfields=[field_name], context=context)[field_name]['string'] def get_warning_config(cr, msg, context=None): """ From 59dc49061cf5e1520ef02df969e7c5bdf79a470b Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Thu, 24 Jan 2013 16:41:19 +0100 Subject: [PATCH 20/35] [IMP] we don't need a special type of exception anymore bzr revid: abo@openerp.com-20130124154119-5283u6x1it5xvt0r --- openerp/addons/base/res/res_config.py | 4 ++-- openerp/exceptions.py | 5 ----- openerp/netsvc.py | 2 -- openerp/service/netrpc_server.py | 2 -- openerp/service/wsgi_server.py | 3 --- 5 files changed, 2 insertions(+), 14 deletions(-) diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index d2f2e8a265f..33615333447 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -622,7 +622,7 @@ class res_config_settings(osv.osv_memory): def get_warning_config(cr, msg, context=None): """ - Helper: return a WarningConfig exception with the given message where the + Helper: return a Warning exception with the given message where the %(field:)s and/or %(menu:)s are replaced by the human readable field's name and/or menuitem's full path. @@ -658,6 +658,6 @@ def get_warning_config(cr, msg, context=None): values[item] = res_config_obj.get_option_name(cr, SUPERUSER_ID, ref, context) # 4/ substitute and return the result - return exceptions.WarningConfig(msg % values) + return exceptions.Warning(msg % values) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/exceptions.py b/openerp/exceptions.py index c43f706fa61..fbf52c7f3a9 100644 --- a/openerp/exceptions.py +++ b/openerp/exceptions.py @@ -30,11 +30,6 @@ treated as a 'Server error'. class Warning(Exception): pass -class WarningConfig(Exception): - """ Warning bound to a misconfiguration. """ - def __init__(self, msg): - super(WarningConfig, self).__init__(msg) - class AccessDenied(Exception): """ Login/password error. No message, no traceback. """ def __init__(self): diff --git a/openerp/netsvc.py b/openerp/netsvc.py index 3362b86fac7..47ee4d81e20 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -306,8 +306,6 @@ def dispatch_rpc(service_name, method, params): raise except openerp.exceptions.Warning: raise - except openerp.exceptions.WarningConfig: - raise except openerp.exceptions.DeferredException, e: _logger.exception(tools.exception_to_unicode(e)) post_mortem(e.traceback) diff --git a/openerp/service/netrpc_server.py b/openerp/service/netrpc_server.py index f87274ef6e6..34bb30d8f75 100644 --- a/openerp/service/netrpc_server.py +++ b/openerp/service/netrpc_server.py @@ -170,8 +170,6 @@ def netrpc_handle_exception_legacy(e): 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.WarningConfig): - 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): diff --git a/openerp/service/wsgi_server.py b/openerp/service/wsgi_server.py index 0b1cff68114..5da34e2e48a 100644 --- a/openerp/service/wsgi_server.py +++ b/openerp/service/wsgi_server.py @@ -95,9 +95,6 @@ def xmlrpc_handle_exception(e): response = xmlrpclib.dumps(fault, allow_none=False, encoding=None) elif isinstance(e, openerp.exceptions.Warning): fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, str(e)) - elif isinstance(e, openerp.exceptions.WarningConfig): - fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, str(e)) - response = xmlrpclib.dumps(fault, allow_none=False, encoding=None) elif isinstance (e, openerp.exceptions.AccessError): fault = xmlrpclib.Fault(RPC_FAULT_CODE_ACCESS_ERROR, str(e)) response = xmlrpclib.dumps(fault, allow_none=False, encoding=None) From 3d0b141406b397fa989c56af0946f11595951ff5 Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Wed, 30 Jan 2013 17:30:32 +0100 Subject: [PATCH 21/35] [IMP] remove default 'None' value (doesn't make sense anymore) bzr revid: abo@openerp.com-20130130163032-jsfm0fa5r54wmvpv --- openerp/addons/base/res/res_config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index 33615333447..0bbdf5b5e84 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -590,7 +590,7 @@ class res_config_settings(osv.osv_memory): name = act_window.read(cr, uid, action_ids[0], ['name'], context=context)['name'] return [(record.id, name) for record in self.browse(cr, uid , ids, context=context)] - def get_option_path(self, cr, uid, menu_xml_id=None, context=None): + def get_option_path(self, cr, uid, menu_xml_id, context=None): """ Fetch the path to a specified configuration view. @@ -606,7 +606,7 @@ class res_config_settings(osv.osv_memory): return ir_ui_menu.complete_name - def get_option_name(self, cr, uid, full_field_name=None, context=None): + def get_option_name(self, cr, uid, full_field_name, context=None): """ Fetch the human readable name of a specified configuration option. @@ -657,7 +657,7 @@ def get_warning_config(cr, msg, context=None): elif ref_type == 'field': values[item] = res_config_obj.get_option_name(cr, SUPERUSER_ID, ref, context) - # 4/ substitute and return the result + # 3/ substitute and return the result return exceptions.Warning(msg % values) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 2459399cac7426cf57fe9c31fbae43f3636bb221 Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Wed, 30 Jan 2013 17:56:42 +0100 Subject: [PATCH 22/35] [IMP] make get_config_warning() a method of res.config.settings, avoiding heavy import of res_config or long path calls like 'openerp.addons.base.res.res_config.get_config_warning()' just use self.pool.get('res.config.settings').get_config_warning() instead bzr revid: abo@openerp.com-20130130165642-pq5sbdluesxu7p6d --- openerp/addons/base/res/res_config.py | 66 +++++++++++++-------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index 0bbdf5b5e84..32e24238d52 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -620,44 +620,44 @@ class res_config_settings(osv.osv_memory): return self.pool.get(model_name).fields_get(cr, uid, allfields=[field_name], context=context)[field_name]['string'] -def get_warning_config(cr, msg, context=None): - """ - Helper: return a Warning exception with the given message where the - %(field:)s and/or %(menu:)s are replaced by the human readable field's name - and/or menuitem's full path. + def get_config_warning(self, cr, msg, context=None): + """ + Helper: return a Warning exception with the given message where the + %(field:)s and/or %(menu:)s are replaced by the human readable field's name + and/or menuitem's full path. - Usage: - ------ - Just include in your error message %(field:model_name.field_name)s to - obtain the human readable field's name, and/or - %(menu:module_name.menuitem_xml_id)s to obtain the menuitem's full path. + Usage: + ------ + Just include in your error message %(field:model_name.field_name)s to + obtain the human readable field's name, and/or + %(menu:module_name.menuitem_xml_id)s to obtain the menuitem's full path. - Example of use: - --------------- - from openerp.addons.base.res.res_config import get_warning_config - raise get_warning_config(cr, _("Error: this action is prohibited. You should check the field %(field:sale.config.settings.fetchmail_lead)s in %(menu:base.menu_sale_config)s."), context=context) - will return an exception containing the following message: - Error: this action is prohibited. You should check the field Create leads from incoming mails in Settings/Configuration/Sales. - """ + Example of use: + --------------- + from openerp.addons.base.res.res_config import get_warning_config + raise get_warning_config(cr, _("Error: this action is prohibited. You should check the field %(field:sale.config.settings.fetchmail_lead)s in %(menu:base.menu_sale_config)s."), context=context) + will return an exception containing the following message: + Error: this action is prohibited. You should check the field Create leads from incoming mails in Settings/Configuration/Sales. + """ - res_config_obj = pooler.get_pool(cr.dbname).get('res.config.settings') - regex_path = r'%\(((?:menu|field):[a-z_\.]*)\)s' + res_config_obj = pooler.get_pool(cr.dbname).get('res.config.settings') + regex_path = r'%\(((?:menu|field):[a-z_\.]*)\)s' - # Process the message - # 1/ find the path and/or field references, put them in a list - references = re.findall(regex_path, msg, flags=re.I) + # Process the message + # 1/ find the path and/or field references, put them in a list + references = re.findall(regex_path, msg, flags=re.I) - # 2/ fetch the path and/or field replacement values - # (full path and human readable field's name) - values = {} - for item in references: - ref_type, ref = item.split(':') - if ref_type == 'menu': - values[item] = res_config_obj.get_option_path(cr, SUPERUSER_ID, ref, context) - elif ref_type == 'field': - values[item] = res_config_obj.get_option_name(cr, SUPERUSER_ID, ref, context) + # 2/ fetch the path and/or field replacement values + # (full path and human readable field's name) + values = {} + for item in references: + ref_type, ref = item.split(':') + if ref_type == 'menu': + values[item] = res_config_obj.get_option_path(cr, SUPERUSER_ID, ref, context) + elif ref_type == 'field': + values[item] = res_config_obj.get_option_name(cr, SUPERUSER_ID, ref, context) - # 3/ substitute and return the result - return exceptions.Warning(msg % values) + # 3/ substitute and return the result + return exceptions.Warning(msg % values) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From a4af8609686386d3371289ea24e019a897ca13bd Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Thu, 31 Jan 2013 19:15:20 +0100 Subject: [PATCH 23/35] [IMP] introduce a new kind of exception: RedirectWarning (warning with an additional redirection button) bzr revid: abo@openerp.com-20130131181520-q5ol3ngsn93u7hg5 --- openerp/exceptions.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openerp/exceptions.py b/openerp/exceptions.py index fbf52c7f3a9..46f0a29b1e0 100644 --- a/openerp/exceptions.py +++ b/openerp/exceptions.py @@ -30,6 +30,19 @@ treated as a 'Server error'. class Warning(Exception): pass +class RedirectWarning(Exception): + """ Warning with a possibility to redirect the user instead of simply + discarding the warning message. + """ + def __init__(self, msg, action_id, button_text): + """ + :param int action_id: id of the action required to perform the + redirection + :param string button_text: text to put on the button which will trigger + the redirection + """ + super(RedirectWarning, self).__init__(msg, action_id, button_text) + class AccessDenied(Exception): """ Login/password error. No message, no traceback. """ def __init__(self): From a2d53ee33b23269a3c311daf696d3c919953e4e8 Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Thu, 31 Jan 2013 19:18:53 +0100 Subject: [PATCH 24/35] [IMP] the implementation of get_option_path(): return a tuple with the full path and the id of the action linked to the given menu xml id bzr revid: abo@openerp.com-20130131181853-mp7r238q0zf5go4l --- openerp/addons/base/res/res_config.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index 32e24238d52..35afafe1ad7 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -597,14 +597,16 @@ class res_config_settings(osv.osv_memory): :param string menu_xml_id: the xml id of the menuitem where the view is located, structured as follows: module_name.menuitem_xml_id (e.g.: "base.menu_sale_config") - :return string: full path to the menuitem - (e.g.: "Settings/Configuration/Sales") + :return tuple: + - t[0]: string: full path to the menuitem + (e.g.: "Settings/Configuration/Sales") + - t[1]: int: id of the menuitem's action """ module_name, menu_xml_id = menu_xml_id.split('.') dummy, menu_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, module_name, menu_xml_id) ir_ui_menu = self.pool.get('ir.ui.menu').browse(cr, uid, menu_id, context=context) - return ir_ui_menu.complete_name + return (ir_ui_menu.complete_name, ir_ui_menu.action.id) def get_option_name(self, cr, uid, full_field_name, context=None): """ @@ -644,20 +646,25 @@ class res_config_settings(osv.osv_memory): regex_path = r'%\(((?:menu|field):[a-z_\.]*)\)s' # Process the message - # 1/ find the path and/or field references, put them in a list + # 1/ find the menu and/or field references, put them in a list references = re.findall(regex_path, msg, flags=re.I) - # 2/ fetch the path and/or field replacement values - # (full path and human readable field's name) + # 2/ fetch the menu and/or field replacement values (full path and + # human readable field's name) and the action_id if any values = {} + action_id = None + buttontext = None for item in references: ref_type, ref = item.split(':') if ref_type == 'menu': - values[item] = res_config_obj.get_option_path(cr, SUPERUSER_ID, ref, context) + values[item], action_id = res_config_obj.get_option_path(cr, SUPERUSER_ID, ref, context) + buttontext = _('Go to the configuration panel') elif ref_type == 'field': values[item] = res_config_obj.get_option_name(cr, SUPERUSER_ID, ref, context) # 3/ substitute and return the result - return exceptions.Warning(msg % values) - + if (action_id): + return exceptions.RedirectWarning(msg % values, action_id, _('Go to the configuration panel')) + else: + return exceptions.Warning(msg % values) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 8d5340a27d818929a7ce3e008cff7328ac0d260d Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Fri, 1 Feb 2013 17:50:15 +0100 Subject: [PATCH 25/35] [IMP] fix a typo, even if it doesn't matter (odo would probably say something like 'stop acting nitpicky !' ;)) bzr revid: abo@openerp.com-20130201165015-l3dtvil3u59kqzf7 --- openerp/tests/addons/test_exceptions/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/tests/addons/test_exceptions/models.py b/openerp/tests/addons/test_exceptions/models.py index 45f0e6dbd47..03788159bf8 100644 --- a/openerp/tests/addons/test_exceptions/models.py +++ b/openerp/tests/addons/test_exceptions/models.py @@ -29,5 +29,5 @@ class m(openerp.osv.osv.Model): raise Exception('AccessDenied') def generate_undefined(self, cr, uid, ids, context=None): - self.surely_undefined_sumbol + self.surely_undefined_symbol # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 195be49479f93239849cd18c8807425e1fc983e7 Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Fri, 1 Feb 2013 18:17:42 +0100 Subject: [PATCH 26/35] [IMP] add a new test for the new exception type bzr revid: abo@openerp.com-20130201171742-axfcg7y99uxpersw --- openerp/exceptions.py | 2 ++ openerp/tests/addons/test_exceptions/models.py | 4 ++++ openerp/tests/addons/test_exceptions/view.xml | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/openerp/exceptions.py b/openerp/exceptions.py index 46f0a29b1e0..41fff85010c 100644 --- a/openerp/exceptions.py +++ b/openerp/exceptions.py @@ -25,6 +25,8 @@ This module defines a few exception types. Those types are understood by the RPC layer. Any other exception type bubbling until the RPC layer will be treated as a 'Server error'. +If you consider introducing new exceptions, check out the test_exceptions +addon. """ class Warning(Exception): diff --git a/openerp/tests/addons/test_exceptions/models.py b/openerp/tests/addons/test_exceptions/models.py index 03788159bf8..78f0e182b48 100644 --- a/openerp/tests/addons/test_exceptions/models.py +++ b/openerp/tests/addons/test_exceptions/models.py @@ -19,6 +19,9 @@ class m(openerp.osv.osv.Model): def generate_warning(self, cr, uid, ids, context=None): raise openerp.exceptions.Warning('description') + def generate_redirect_warning(self, cr, uid, ids, context=None): + raise openerp.exceptions.RedirectWarning('description', self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'action_client_base_menu'), 'go to the redirection') + def generate_access_denied(self, cr, uid, ids, context=None): raise openerp.exceptions.AccessDenied() @@ -30,4 +33,5 @@ class m(openerp.osv.osv.Model): def generate_undefined(self, cr, uid, ids, context=None): self.surely_undefined_symbol + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/tests/addons/test_exceptions/view.xml b/openerp/tests/addons/test_exceptions/view.xml index 180029e739a..0c8e373a278 100644 --- a/openerp/tests/addons/test_exceptions/view.xml +++ b/openerp/tests/addons/test_exceptions/view.xml @@ -21,6 +21,10 @@