[MERGE] merged the branch with the RedirectWarnings
bzr revid: qdp-launchpad@openerp.com-20130215151416-p27n59r9wri2gp9f
This commit is contained in:
@@ -12,3 +12,7 @@ Changelog
|
||||
- Removed support for `<terp>` root element in XML files.
|
||||
- Removed support for the non-openerp namespace (e.g. importing `tools` instead
|
||||
of `openerp.tools` in an addons).
|
||||
- Add a new type of exception that allows redirections:
|
||||
openerp.exceptions.RedirectWarning.
|
||||
- Give a pair of new methods to res.config.settings and a helper to make them
|
||||
easier to use: get_config_warning()
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -20,12 +20,14 @@
|
||||
##############################################################################
|
||||
import logging
|
||||
from operator import attrgetter
|
||||
import re
|
||||
|
||||
import openerp
|
||||
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 _
|
||||
from openerp import exceptions
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -572,17 +574,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
|
||||
@@ -590,4 +592,75 @@ 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, context=None):
|
||||
"""
|
||||
Fetch the path to a specified configuration view and the action id to access it.
|
||||
|
||||
: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 tuple:
|
||||
- t[0]: string: full path to the menuitem (e.g.: "Settings/Configuration/Sales")
|
||||
- t[1]: long: 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, ir_ui_menu.action.id)
|
||||
|
||||
def get_option_name(self, cr, uid, full_field_name, 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).fields_get(cr, uid, allfields=[field_name], context=context)[field_name]['string']
|
||||
|
||||
def get_config_warning(self, cr, msg, context=None):
|
||||
"""
|
||||
Helper: return a Warning exception with the given message where the %(field:xxx)s
|
||||
and/or %(menu:yyy)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)
|
||||
|
||||
This 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'
|
||||
|
||||
# Process the message
|
||||
# 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 menu and/or field replacement values (full path and
|
||||
# human readable field's name) and the action_id if any
|
||||
values = {}
|
||||
action_id = None
|
||||
for item in references:
|
||||
ref_type, ref = item.split(':')
|
||||
if ref_type == 'menu':
|
||||
values[item], action_id = res_config_obj.get_option_path(cr, SUPERUSER_ID, ref, context=context)
|
||||
elif ref_type == 'field':
|
||||
values[item] = res_config_obj.get_option_name(cr, SUPERUSER_ID, ref, context=context)
|
||||
|
||||
# 3/ substitute and return the result
|
||||
if (action_id):
|
||||
return exceptions.RedirectWarning(msg % values, action_id, _('Go to the configuration panel'))
|
||||
return exceptions.Warning(msg % values)
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -3,6 +3,7 @@ import test_expression
|
||||
import test_ir_attachment
|
||||
import test_ir_values
|
||||
import test_menu
|
||||
import test_res_config
|
||||
import test_res_lang
|
||||
import test_search
|
||||
|
||||
@@ -12,6 +13,7 @@ checks = [
|
||||
test_ir_attachment,
|
||||
test_ir_values,
|
||||
test_menu,
|
||||
test_res_config,
|
||||
test_res_lang,
|
||||
test_search,
|
||||
]
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import unittest2
|
||||
|
||||
import openerp
|
||||
import openerp.tests.common as common
|
||||
|
||||
class test_res_config(common.TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(test_res_config, self).setUp()
|
||||
self.res_config = self.registry('res.config.settings')
|
||||
|
||||
# Define the test values
|
||||
self.menu_xml_id = 'base.menu_action_res_users'
|
||||
self.full_field_name = 'res.partner.lang'
|
||||
self.error_msg = "WarningRedirect test string: %(field:res.partner.lang)s - %(menu:base.menu_action_res_users)s."
|
||||
self.error_msg_wo_menu = "WarningRedirect test string: %(field:res.partner.lang)s."
|
||||
# Note: see the get_config_warning() doc for a better example
|
||||
|
||||
# Fetch the expected values
|
||||
module_name, menu_xml_id = self.menu_xml_id.split('.')
|
||||
dummy, menu_id = self.registry('ir.model.data').get_object_reference(self.cr, self.uid, module_name, menu_xml_id)
|
||||
ir_ui_menu = self.registry('ir.ui.menu').browse(self.cr, self.uid, menu_id, context=None)
|
||||
|
||||
model_name, field_name = self.full_field_name.rsplit('.', 1)
|
||||
|
||||
self.expected_path = ir_ui_menu.complete_name
|
||||
self.expected_action_id = ir_ui_menu.action.id
|
||||
self.expected_name = self.registry(model_name).fields_get(self.cr, self.uid, allfields=[field_name], context=None)[field_name]['string']
|
||||
self.expected_final_error_msg = self.error_msg % {
|
||||
'field:res.partner.lang': self.expected_name,
|
||||
'menu:base.menu_action_res_users': self.expected_path
|
||||
}
|
||||
self.expected_final_error_msg_wo_menu = self.error_msg_wo_menu % {
|
||||
'field:res.partner.lang': self.expected_name,
|
||||
}
|
||||
|
||||
def test_00_get_option_path(self):
|
||||
""" The get_option_path() method should return a tuple containing a string and an integer """
|
||||
res = self.res_config.get_option_path(self.cr, self.uid, self.menu_xml_id, context=None)
|
||||
|
||||
# Check types
|
||||
self.assertTrue(isinstance(res, tuple)), "The result of get_option_path() should be a tuple (got %s)" % type(res)
|
||||
self.assertTrue(len(res) == 2), "The tuple should contain 2 elements (got %s)" % len(res)
|
||||
self.assertTrue(isinstance(res[0], basestring)), "The first element of the tuple should be a string (got %s)" % type(res[0])
|
||||
self.assertTrue(isinstance(res[1], long)), "The second element of the tuple should be an long (got %s)" % type(res[1])
|
||||
|
||||
# Check returned values
|
||||
self.assertTrue(res[0] == self.expected_path), "Result mismatch: expected %s, got %s" % (self.expected_path, res[0])
|
||||
self.assertTrue(res[1] == self.expected_action_id), "Result mismatch: expected %s, got %s" % (self.expected_action_id, res[1])
|
||||
|
||||
def test_10_get_option_name(self):
|
||||
""" The get_option_name() method should return a string """
|
||||
res = self.res_config.get_option_name(self.cr, self.uid, self.full_field_name, context=None)
|
||||
|
||||
# Check type
|
||||
self.assertTrue(isinstance(res, basestring)), "Result type mismatch: expected basestring, got %s" % type(res)
|
||||
|
||||
# Check returned value
|
||||
self.assertTrue(res == self.expected_name), "Result mismatch: expected %s, got %s" % (self.expected_name, res)
|
||||
|
||||
def test_20_get_config_warning(self):
|
||||
""" The get_config_warning() method should return a RedirectWarning """
|
||||
res = self.res_config.get_config_warning(self.cr, self.error_msg, context=None)
|
||||
|
||||
# Check type
|
||||
self.assertTrue(isinstance(res, openerp.exceptions.RedirectWarning)), "Result type mismatch: expected openerp.exceptions.RedirectWarning, got %s" % type(res)
|
||||
|
||||
# Check returned value
|
||||
self.assertTrue(res.args[0], self.expected_final_error_msg), "Result mismatch: expected %s, got %s" % (self.expected_final_error_msg, res.args[0])
|
||||
self.assertTrue(res.args[1], self.expected_action_id), "Result mismatch: expected %s, got %s" % (self.expected_action_id, res.args[1])
|
||||
|
||||
def test_30_get_config_warning_wo_menu(self):
|
||||
""" The get_config_warning() method should return a Warning exception """
|
||||
res = self.res_config.get_config_warning(self.cr, self.error_msg_wo_menu, context=None)
|
||||
|
||||
# Check type
|
||||
self.assertTrue(isinstance(res, openerp.exceptions.Warning)), "Result type mismatch: expected openerp.exceptions.Warning, got %s" % type(res)
|
||||
|
||||
# Check returned value
|
||||
self.assertTrue(res.args[0], self.expected_final_error_msg), "Result mismatch: expected %s, got %s" % (self.expected_final_error_msg_wo_menu, res.args[0])
|
||||
@@ -7,20 +7,20 @@ class test_res_lang(common.TransactionCase):
|
||||
def test_00_intersperse(self):
|
||||
from openerp.addons.base.res.res_lang import intersperse
|
||||
|
||||
assert intersperse("", []) == ("", 0), "Assert passed"
|
||||
assert intersperse("0", []) == ("0", 0), "Assert passed"
|
||||
assert intersperse("012", []) == ("012", 0), "Assert passed"
|
||||
assert intersperse("1", []) == ("1", 0), "Assert passed"
|
||||
assert intersperse("12", []) == ("12", 0), "Assert passed"
|
||||
assert intersperse("123", []) == ("123", 0), "Assert passed"
|
||||
assert intersperse("1234", []) == ("1234", 0), "Assert passed"
|
||||
assert intersperse("123456789", []) == ("123456789", 0), "Assert passed"
|
||||
assert intersperse("&ab%#@1", []) == ("&ab%#@1", 0), "Assert passed"
|
||||
assert intersperse("", []) == ("", 0)
|
||||
assert intersperse("0", []) == ("0", 0)
|
||||
assert intersperse("012", []) == ("012", 0)
|
||||
assert intersperse("1", []) == ("1", 0)
|
||||
assert intersperse("12", []) == ("12", 0)
|
||||
assert intersperse("123", []) == ("123", 0)
|
||||
assert intersperse("1234", []) == ("1234", 0)
|
||||
assert intersperse("123456789", []) == ("123456789", 0)
|
||||
assert intersperse("&ab%#@1", []) == ("&ab%#@1", 0)
|
||||
|
||||
assert intersperse("0", []) == ("0", 0), "Assert passed"
|
||||
assert intersperse("0", [1]) == ("0", 0), "Assert passed"
|
||||
assert intersperse("0", [2]) == ("0", 0), "Assert passed"
|
||||
assert intersperse("0", [200]) == ("0", 0), "Assert passed"
|
||||
assert intersperse("0", []) == ("0", 0)
|
||||
assert intersperse("0", [1]) == ("0", 0)
|
||||
assert intersperse("0", [2]) == ("0", 0)
|
||||
assert intersperse("0", [200]) == ("0", 0)
|
||||
|
||||
assert intersperse("12345678", [1], '.') == ('1234567.8', 1)
|
||||
assert intersperse("12345678", [1], '.') == ('1234567.8', 1)
|
||||
|
||||
@@ -25,11 +25,22 @@ 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):
|
||||
pass
|
||||
|
||||
class RedirectWarning(Exception):
|
||||
""" Warning with a possibility to redirect the user instead of simply
|
||||
diplaying the warning message.
|
||||
|
||||
Should receive as parameters:
|
||||
:param int action_id: id of the action where to perform the redirection
|
||||
:param string button_text: text to put on the button that will trigger
|
||||
the redirection.
|
||||
"""
|
||||
|
||||
class AccessDenied(Exception):
|
||||
""" Login/password error. No message, no traceback. """
|
||||
def __init__(self):
|
||||
|
||||
+11
-11
@@ -2283,7 +2283,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')]
|
||||
|
||||
@@ -2678,17 +2678,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:
|
||||
@@ -2752,7 +2752,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'
|
||||
@@ -3863,7 +3863,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)'),
|
||||
@@ -4375,12 +4375,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:
|
||||
@@ -4429,7 +4429,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
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
|
||||
from openerp.osv.orm import except_orm, Model, TransientModel, AbstractModel
|
||||
|
||||
# Deprecated, kept for backward compatibility.
|
||||
|
||||
@@ -94,7 +94,7 @@ def xmlrpc_handle_exception(e):
|
||||
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):
|
||||
elif isinstance(e, openerp.exceptions.Warning) or isinstance(e, openerp.exceptions.RedirectWarning):
|
||||
fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, str(e))
|
||||
response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
|
||||
elif isinstance (e, openerp.exceptions.AccessError):
|
||||
@@ -312,7 +312,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 []
|
||||
|
||||
@@ -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', 'menu_action_res_users'), 'go to the redirection')
|
||||
|
||||
def generate_access_denied(self, cr, uid, ids, context=None):
|
||||
raise openerp.exceptions.AccessDenied()
|
||||
|
||||
@@ -29,5 +32,6 @@ 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:
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
<button name="generate_warning" string="Warning" type="object" icon="gtk-ok" colspan="1"/>
|
||||
<label string="Warning-description"/>
|
||||
</group>
|
||||
<group colspan="8" col="8">
|
||||
<button name="generate_redirect_warning" string="RedirectWarning" type="object" icon="gtk-ok" colspan="1"/>
|
||||
<label string="Warning-description-redirection button"/>
|
||||
</group>
|
||||
<group colspan="8" col="8">
|
||||
<button name="generate_access_denied" string="AccessDenied" type="object" icon="gtk-ok" colspan="1"/>
|
||||
<label string="Access denied-traceback"/>
|
||||
|
||||
Reference in New Issue
Block a user