diff options
35 files changed, 148 insertions, 312 deletions
diff --git a/addons/account_facturx/models/account_move.py b/addons/account_facturx/models/account_move.py index 68020ea6c90..92d3f6c225c 100644 --- a/addons/account_facturx/models/account_move.py +++ b/addons/account_facturx/models/account_move.py @@ -3,7 +3,7 @@ from odoo import api, models, fields, tools, _ from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, float_repr from odoo.tests.common import Form -from odoo.exceptions import UserError, except_orm +from odoo.exceptions import UserError from datetime import datetime from lxml import etree @@ -271,7 +271,7 @@ class AccountMove(models.Model): self._import_facturx_invoice(tree) self._remove_ocr_option() buffer.close() - except except_orm as e: + except UserError as e: raise e except Exception as e: # Malformed pdf diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py index 6fc0c58f919..82219be6930 100644 --- a/addons/auth_signup/controllers/main.py +++ b/addons/auth_signup/controllers/main.py @@ -46,7 +46,7 @@ class AuthSignupHome(Home): template.sudo().send_mail(user_sudo.id, force_send=True) return self.web_login(*args, **kw) except UserError as e: - qcontext['error'] = e.name or e.value + qcontext['error'] = e.args[0] except (SignupError, AssertionError) as e: if request.env["res.users"].sudo().search([("login", "=", qcontext.get("login"))]): qcontext["error"] = _("Another user is already registered using this email address.") @@ -79,7 +79,7 @@ class AuthSignupHome(Home): request.env['res.users'].sudo().reset_password(login) qcontext['message'] = _("An email has been sent with credentials to reset your password") except UserError as e: - qcontext['error'] = e.name or e.value + qcontext['error'] = e.args[0] except SignupError: qcontext['error'] = _("Could not reset your password") _logger.exception('error when resetting password') diff --git a/addons/delivery/models/delivery_grid.py b/addons/delivery/models/delivery_grid.py index 5cef40ac210..17bc32a4f3b 100644 --- a/addons/delivery/models/delivery_grid.py +++ b/addons/delivery/models/delivery_grid.py @@ -58,7 +58,7 @@ class ProviderGrid(models.Model): except UserError as e: return {'success': False, 'price': 0.0, - 'error_message': e.name, + 'error_message': e.args[0], 'warning_message': False} if order.company_id.currency_id.id != order.pricelist_id.currency_id.id: price_unit = order.company_id.currency_id._convert( diff --git a/addons/http_routing/models/ir_http.py b/addons/http_routing/models/ir_http.py index c0fd4a46a19..064665d5d15 100644 --- a/addons/http_routing/models/ir_http.py +++ b/addons/http_routing/models/ir_http.py @@ -572,9 +572,8 @@ class IrHttp(models.AbstractModel): exception=exception, traceback=traceback.format_exc(), ) - # only except_orm exceptions contain a message - if isinstance(exception, exceptions.except_orm): - values['error_message'] = exception.name + if isinstance(exception, exceptions.UserError): + values['error_message'] = exception.args[0] code = 400 if isinstance(exception, exceptions.AccessError): code = 403 diff --git a/addons/mail/models/mail_mail.py b/addons/mail/models/mail_mail.py index 598c92d999f..49abe210f7a 100644 --- a/addons/mail/models/mail_mail.py +++ b/addons/mail/models/mail_mail.py @@ -416,10 +416,8 @@ class MailMail(models.Model): if isinstance(e, UnicodeEncodeError): value = "Invalid text: %s" % e.object else: - # get the args of the original error, wrap into a value and throw a MailDeliveryException - # that is an except_orm, with name and value as arguments value = '. '.join(e.args) - raise MailDeliveryException(_("Mail Delivery Failed"), value) + raise MailDeliveryException(value) raise if auto_commit is True: diff --git a/addons/mail/wizard/mail_template_preview.py b/addons/mail/wizard/mail_template_preview.py index 7cedde9b5e5..03062beee74 100644 --- a/addons/mail/wizard/mail_template_preview.py +++ b/addons/mail/wizard/mail_template_preview.py @@ -73,7 +73,7 @@ class MailTemplatePreview(models.TransientModel): self.error_msg = False except UserError as user_error: self._set_mail_attributes() - self.error_msg = user_error.name + self.error_msg = user_error.args[0] finally: # Avoid to be change by a invalidate_cache call (in generate_mail), e.g. Quotation / Order report for key, value in copy_depends_values.items(): diff --git a/addons/mrp/tests/test_stock.py b/addons/mrp/tests/test_stock.py index 2c03f3234c1..f2c012737c7 100644 --- a/addons/mrp/tests/test_stock.py +++ b/addons/mrp/tests/test_stock.py @@ -2,7 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import common -from odoo.exceptions import except_orm +from odoo.exceptions import UserError from odoo.tests import Form @@ -150,7 +150,7 @@ class TestWarehouse(common.TestMrpCommon): # Scrap Product Wood without lot to check assert raise ?. scrap_id = self.env['stock.scrap'].with_context(active_model='mrp.production', active_id=production_3.id).create({'product_id': self.product_2.id, 'scrap_qty': 1.0, 'product_uom_id': self.product_2.uom_id.id, 'location_id': location_id, 'production_id': production_3.id}) - with self.assertRaises(except_orm): + with self.assertRaises(UserError): scrap_id.do_scrap() # Scrap Product Wood with lot. diff --git a/addons/payment_authorize/controllers/main.py b/addons/payment_authorize/controllers/main.py index 49f919f4198..4417f0408bb 100644 --- a/addons/payment_authorize/controllers/main.py +++ b/addons/payment_authorize/controllers/main.py @@ -57,7 +57,7 @@ class AuthorizeController(http.Controller): } except UserError as e: return { - 'error': e.name, + 'error': e.args[0], } if not token: diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 12c488793d0..a318641fe92 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -1022,11 +1022,6 @@ exports.PosModel = Backbone.Model.extend({ }).catch(function (reason){ var error = reason.message; if(error.code === 200 ){ // Business Logic Error, not a connection problem - //if warning do not need to display traceback!! - if (error.data.exception_type == 'warning') { - delete error.data.debug; - } - // Hide error if already shown before ... if ((!self.get('failed') || options.show_error) && !options.to_invoice) { self.gui.show_popup('error-traceback',{ @@ -1071,13 +1066,6 @@ exports.PosModel = Backbone.Model.extend({ self.db.set_ids_removed_from_server(server_ids); return server_ids; }).catch(function (reason){ - var error = reason.message; - if(error.code === 200 ){ // Business Logic Error, not a connection problem - //if warning do not need to display traceback!! - if (error.data.exception_type == 'warning') { - delete error.data.debug; - } - } self.gui.show_sync_error_popup(); console.error('Failed to remove orders:', server_ids); }); diff --git a/addons/test_mail/tests/test_mail_channel.py b/addons/test_mail/tests/test_mail_channel.py index 3fb07f010ce..cb9d949c7ef 100644 --- a/addons/test_mail/tests/test_mail_channel.py +++ b/addons/test_mail/tests/test_mail_channel.py @@ -4,7 +4,7 @@ from odoo.tests import tagged from odoo.addons.mail.tests.common import mail_new_test_user from odoo.addons.test_mail.tests.common import TestMailCommon -from odoo.exceptions import AccessError, except_orm, ValidationError, UserError +from odoo.exceptions import AccessError, ValidationError, UserError from odoo.tools import mute_logger, formataddr @@ -39,10 +39,7 @@ class TestChannelAccessRights(TestMailCommon): self.group_public.with_user(self.user_public).read() # Read Pigs -> ko, restricted to employees - # TODO: Change the except_orm to Warning ( Because here it's call check_access_rule - # which still generate exception in except_orm.So we need to change all - # except_orm to warning in mail module.) - with self.assertRaises(except_orm): + with self.assertRaises(AccessError): self.group_pigs.with_user(self.user_public).read() # Read a private group when being a member: ok @@ -64,7 +61,6 @@ class TestChannelAccessRights(TestMailCommon): @mute_logger('odoo.addons.base.models.ir_model', 'odoo.models', 'odoo.models.unlink') def test_access_rights_groups(self): # Employee read employee-based group: ok - # TODO Change the except_orm to Warning self.group_pigs.with_user(self.user_employee).read() # Employee can create a group @@ -77,7 +73,7 @@ class TestChannelAccessRights(TestMailCommon): self.group_pigs.with_user(self.user_employee).unlink() # Employee cannot read a private group - with self.assertRaises(except_orm): + with self.assertRaises(AccessError): self.group_private.with_user(self.user_employee).read() # Employee cannot write on private diff --git a/addons/test_mail/tests/test_mail_message.py b/addons/test_mail/tests/test_mail_message.py index 1487597f56a..42c48aa960f 100644 --- a/addons/test_mail/tests/test_mail_message.py +++ b/addons/test_mail/tests/test_mail_message.py @@ -7,7 +7,7 @@ from unittest.mock import patch from odoo.addons.mail.tests.common import mail_new_test_user from odoo.addons.test_mail.tests.common import TestMailCommon from odoo.addons.test_mail.models.test_mail_models import MailTestSimple -from odoo.exceptions import AccessError, except_orm +from odoo.exceptions import AccessError from odoo.tools import mute_logger, formataddr from odoo.tests import tagged @@ -244,15 +244,12 @@ class TestMessageAccess(TestMailCommon): @mute_logger('odoo.addons.base.models.ir_model', 'odoo.models') def test_mail_message_access_read_crash(self): - # TODO: Change the except_orm to Warning ( Because here it's call check_access_rule - # which still generate exception in except_orm.So we need to change all - # except_orm to warning in mail module.) - with self.assertRaises(except_orm): + with self.assertRaises(AccessError): self.message.with_user(self.user_employee).read() @mute_logger('odoo.models') def test_mail_message_access_read_crash_portal(self): - with self.assertRaises(except_orm): + with self.assertRaises(AccessError): self.message.with_user(self.user_portal).read(['body', 'message_type', 'subtype_id']) def test_mail_message_access_read_ok_portal(self): @@ -302,17 +299,16 @@ class TestMessageAccess(TestMailCommon): @mute_logger('odoo.models') def test_mail_message_access_create_crash(self): # Do: Bert create a private message -> ko, no creation rights - with self.assertRaises(except_orm): + with self.assertRaises(AccessError): self.env['mail.message'].with_user(self.user_employee).create({'model': 'mail.channel', 'res_id': self.group_private.id, 'body': 'Test'}) @mute_logger('odoo.models') def test_mail_message_access_create_doc(self): - # TODO Change the except_orm to Warning Message = self.env['mail.message'].with_user(self.user_employee) # Do: Raoul creates a message on Jobs -> ok, write access to the related document Message.create({'model': 'mail.channel', 'res_id': self.group_public.id, 'body': 'Test'}) # Do: Raoul creates a message on Priv -> ko, no write access to the related document - with self.assertRaises(except_orm): + with self.assertRaises(AccessError): Message.create({'model': 'mail.channel', 'res_id': self.group_private.id, 'body': 'Test'}) def test_mail_message_access_create_private(self): @@ -338,11 +334,11 @@ class TestMessageAccess(TestMailCommon): body='<p>This is First Message</p>', subject='Subject', message_type='comment', subtype_xmlid='mail.mt_note') # portal user have no rights to read the message - with self.assertRaises(except_orm): + with self.assertRaises(AccessError): message.with_user(self.user_portal).read(['subject, body']) with patch.object(MailTestSimple, 'check_access_rights', return_value=True): - with self.assertRaises(except_orm): + with self.assertRaises(AccessError): message.with_user(self.user_portal).read(['subject, body']) # parent message is accessible to references notification mail values diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index b450663cd46..c68966af4bc 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -1198,12 +1198,12 @@ class Session(http.Controller): try: if request.env['res.users'].change_password(old_password, new_password): return {'new_password':new_password} - except UserError as e: - msg = e.name except AccessDenied as e: msg = e.args[0] if msg == AccessDenied().args[0]: msg = _('The old password you provided is incorrect, your password was not changed.') + except UserError as e: + msg = e.args[0] return {'title': _('Change Password'), 'error': msg} @http.route('/web/session/get_lang_list', type='json', auth="none") diff --git a/addons/web/static/src/js/services/crash_manager.js b/addons/web/static/src/js/services/crash_manager.js index 19db73e1da5..b653aaa1c8d 100644 --- a/addons/web/static/src/js/services/crash_manager.js +++ b/addons/web/static/src/js/services/crash_manager.js @@ -87,6 +87,13 @@ var CrashManager = AbstractService.extend({ var self = this; active = true; this.isConnected = true; + this.odooExceptionTitleMap = { + 'odoo.exceptions.AccessDenied': _lt("Access Denied"), + 'odoo.exceptions.AccessError': _lt("Access Error"), + 'odoo.exceptions.MissingError': _lt("Missing Record"), + 'odoo.exceptions.UserError': _lt("User Error"), + 'odoo.exceptions.ValidationError': _lt("Validation Error"), + }; this._super.apply(this, arguments); @@ -180,70 +187,35 @@ var CrashManager = AbstractService.extend({ rpc_error: function(error) { // Some qunit tests produces errors before the DOM is set. // This produces an error loop as the modal/toast has no DOM to attach to. - if (!document.body) { - return; - } - var map_title = { - access_denied: _lt("Access Denied"), - access_error: _lt("Access Error"), - except_orm: _lt("Global Business Error"), - missing_error: _lt("Missing Record"), - user_error: _lt("User Error"), - validation_error: _lt("Validation Error"), - warning: _lt("Warning"), - }; - if (!active) { - return; - } - if (this.connection_lost) { - return; - } + if (!document.body || !active || this.connection_lost) return; + + // Connection lost error if (error.code === -32098) { this.handleLostConnection(); return; } + + // Special exception handlers, see crash_registry bellow var handler = core.crash_registry.get(error.data.name, true); if (handler) { new (handler)(this, error).display(); return; } - if (_.has(map_title, error.data.exception_type)) { - if (error.data.exception_type === 'except_orm') { - if (error.data.arguments[1]) { - error = _.extend({}, error, - { - data: _.extend({}, error.data, - { - message: error.data.arguments[1], - title: error.data.arguments[0] !== 'Warning' ? error.data.arguments[0] : '', - }) - }); - } - else { - error = _.extend({}, error, - { - data: _.extend({}, error.data, - { - message: error.data.arguments[0], - title: '', - }) - }); - } - } - else { - error = _.extend({}, error, - { - data: _.extend({}, error.data, - { - message: error.data.arguments[0], - title: map_title[error.data.exception_type] !== 'Warning' ? map_title[error.data.exception_type] : '', - }) - }); - } + + // Odoo custom exception: UserError, AccessError, ... + if (_.has(this.odooExceptionTitleMap, error.data.name)) { + error = _.extend({}, error, { + data: _.extend({}, error.data, { + message: error.data.arguments[0], + title: this.odooExceptionTitleMap[error.data.name], + }), + }); this.show_warning(error); - } else { - this.show_error(error); + return; } + + // Any other Python exception + this.show_error(error); }, show_warning: function (error, options) { if (!active) { diff --git a/addons/web/static/tests/chrome/action_manager_tests.js b/addons/web/static/tests/chrome/action_manager_tests.js index 5af731abf81..96835f8eab5 100644 --- a/addons/web/static/tests/chrome/action_manager_tests.js +++ b/addons/web/static/tests/chrome/action_manager_tests.js @@ -2277,7 +2277,6 @@ QUnit.module('ActionManager', { params.error({ data: { name: 'error', - exception_type: 'warning', arguments: ['could not download file'], } }); diff --git a/addons/web_editor/static/src/js/editor/rte.js b/addons/web_editor/static/src/js/editor/rte.js index cbce901c48d..feb28100854 100644 --- a/addons/web_editor/static/src/js/editor/rte.js +++ b/addons/web_editor/static/src/js/editor/rte.js @@ -510,7 +510,7 @@ var RTEWidget = Widget.extend({ // new rejection with all relevant info var id = _.uniqueId('carlos_danger_'); $el.addClass('o_dirty oe_carlos_danger ' + id); - var html = (response.message.data.exception_type === 'except_osv'); + var html = Boolean(response.data.name); if (html) { var msg = $('<div/>', {text: response.message.data.message}).html(); var data = msg.substring(3, msg.length -2).split(/', u'/); diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index ae68a21e174..2e6aa532f88 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -299,7 +299,7 @@ class Http(models.AbstractModel): try: # exception.name might be int, string exception_template = int(exception.name) - except: + except ValueError: exception_template = exception.name view = View._view_obj(exception_template) if exception.html and exception.html in view.arch: diff --git a/addons/website_slides/controllers/main.py b/addons/website_slides/controllers/main.py index 8691313dc88..25d53bce316 100644 --- a/addons/website_slides/controllers/main.py +++ b/addons/website_slides/controllers/main.py @@ -602,9 +602,9 @@ class WebsiteSlides(WebsiteProfile): channel = request.env['slide.channel'].browse(int(channel_id)) can_upload = channel.can_upload can_publish = channel.can_publish - except (UserError, AccessError) as e: + except UserError as e: _logger.error(e) - return {'error': e.name} + return {'error': e.args[0]} else: if not can_upload or not can_publish: return {'error': _('You cannot add tags to this course.')} @@ -1013,9 +1013,9 @@ class WebsiteSlides(WebsiteProfile): channel = request.env['slide.channel'].browse(values['channel_id']) can_upload = channel.can_upload can_publish = channel.can_publish - except (UserError, AccessError) as e: + except UserError as e: _logger.error(e) - return {'error': e.name} + return {'error': e.args[0]} else: if not can_upload: return {'error': _('You cannot upload on this channel.')} @@ -1042,9 +1042,9 @@ class WebsiteSlides(WebsiteProfile): values['user_id'] = request.env.uid values['is_published'] = values.get('is_published', False) and can_publish slide = request.env['slide.slide'].sudo().create(values) - except (UserError, AccessError) as e: + except UserError as e: _logger.error(e) - return {'error': e.name} + return {'error': e.args[0]} except Exception as e: _logger.error(e) return {'error': _('Internal server error, please try again later or contact administrator.\nHere is the error message: %s') % e} diff --git a/odoo/addons/base/models/ir_actions.py b/odoo/addons/base/models/ir_actions.py index a3c7c4857ca..5d207e80591 100644 --- a/odoo/addons/base/models/ir_actions.py +++ b/odoo/addons/base/models/ir_actions.py @@ -508,6 +508,7 @@ class IrActionsServer(models.Model): 'model': model, # Exceptions 'Warning': odoo.exceptions.Warning, + 'UserError': odoo.exceptions.UserError, # record 'record': record, 'records': records, diff --git a/odoo/addons/base/models/ir_mail_server.py b/odoo/addons/base/models/ir_mail_server.py index 8c5399236c7..8d0bf2a21f1 100644 --- a/odoo/addons/base/models/ir_mail_server.py +++ b/odoo/addons/base/models/ir_mail_server.py @@ -15,7 +15,7 @@ import threading import html2text from odoo import api, fields, models, tools, _ -from odoo.exceptions import except_orm, UserError +from odoo.exceptions import UserError from odoo.tools import ustr, pycompat _logger = logging.getLogger(__name__) @@ -24,10 +24,9 @@ _test_logger = logging.getLogger('odoo.tests') SMTP_TIMEOUT = 60 -class MailDeliveryException(except_orm): +class MailDeliveryException(Exception): """Specific exception subclass for mail delivery errors""" - def __init__(self, name, value): - super(MailDeliveryException, self).__init__(name, value) + # Python 3: patch SMTP's internal printer/debugger def _print_debug(self, *args): diff --git a/odoo/addons/base/tests/test_ir_filters.py b/odoo/addons/base/tests/test_ir_filters.py index cb4b39ff8e2..59c4a39c608 100644 --- a/odoo/addons/base/tests/test_ir_filters.py +++ b/odoo/addons/base/tests/test_ir_filters.py @@ -222,7 +222,7 @@ class TestGlobalDefaults(FiltersCase): ) Filters = self.env['ir.filters'].with_user(self.USER_ID) - with self.assertRaises(exceptions.Warning): + with self.assertRaises(exceptions.UserError): Filters.create_or_replace({ 'name': 'c', 'model_id': 'ir.filters', @@ -242,7 +242,7 @@ class TestGlobalDefaults(FiltersCase): ) Filters = self.env['ir.filters'].with_user(self.USER_ID) - with self.assertRaises(exceptions.Warning): + with self.assertRaises(exceptions.UserError): Filters.create_or_replace({ 'name': 'a', 'model_id': 'ir.filters', diff --git a/odoo/addons/base/tests/test_res_config.py b/odoo/addons/base/tests/test_res_config.py index 70d1aed99b8..552d8759715 100644 --- a/odoo/addons/base/tests/test_res_config.py +++ b/odoo/addons/base/tests/test_res_config.py @@ -78,7 +78,7 @@ class TestResConfig(TransactionCase): res = self.ResConfig.get_config_warning(self.error_msg_wo_menu) # Check type - self.assertIsInstance(res, exceptions.Warning) + self.assertIsInstance(res, exceptions.UserError) # Check returned value self.assertEqual(res.args[0], self.expected_final_error_msg_wo_menu) diff --git a/odoo/addons/test_exceptions/models.py b/odoo/addons/test_exceptions/models.py index 7028ddda128..cb218aaac32 100644 --- a/odoo/addons/test_exceptions/models.py +++ b/odoo/addons/test_exceptions/models.py @@ -2,7 +2,6 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import odoo.exceptions -import odoo.osv.osv from odoo import models, api from odoo.tools.safe_eval import safe_eval @@ -14,17 +13,6 @@ class m(models.Model): _name = 'test.exceptions.model' _description = 'Test Exception Model' - def generate_except_osv(self): - # title is ignored in the new (6.1) exceptions - raise odoo.osv.osv.except_osv('title', 'description') - - def generate_except_orm(self): - # title is ignored in the new (6.1) exceptions - raise odoo.exceptions.except_orm('title', 'description') - - def generate_warning(self): - raise odoo.exceptions.Warning('description') - def generate_redirect_warning(self): action = self.env.ref('test_exceptions.action_test_exceptions') raise odoo.exceptions.RedirectWarning('description', action.id, 'Go to the redirection') @@ -50,15 +38,6 @@ class m(models.Model): def generate_validation_error(self): raise odoo.exceptions.ValidationError('description') - def generate_except_osv_safe_eval(self): - self.generate_safe_eval(self.generate_except_osv) - - def generate_except_orm_safe_eval(self): - self.generate_safe_eval(self.generate_except_orm) - - def generate_warning_safe_eval(self): - self.generate_safe_eval(self.generate_warning) - def generate_redirect_warning_safe_eval(self): self.generate_safe_eval(self.generate_redirect_warning) diff --git a/odoo/addons/test_exceptions/view.xml b/odoo/addons/test_exceptions/view.xml index 0e8f96a7c91..30f19e19b58 100644 --- a/odoo/addons/test_exceptions/view.xml +++ b/odoo/addons/test_exceptions/view.xml @@ -11,18 +11,6 @@ <separator/> <group> <div> - <button name="generate_except_osv" string="except_osv" type="object" icon="fa-refresh text-primary" /> - <label for="generate_except_osv" string="Warning-description"/> - </div> - <div> - <button name="generate_except_orm" string="except_orm" type="object" icon="fa-refresh text-primary"/> - <label for="generate_except_orm" string="Warning-description"/> - </div> - <div> - <button name="generate_warning" string="Warning" type="object" icon="fa-refresh text-primary"/> - <label for="generate_warning" string="Warning-description"/> - </div> - <div> <button name="generate_redirect_warning" string="RedirectWarning" type="object" icon="fa-refresh text-primary"/> <label for="generate_redirect_warning" string="Warning-description-redirection button"/> </div> @@ -56,18 +44,6 @@ </div> <div> - <button name="generate_except_osv_safe_eval" string="except_osv*" type="object" icon="fa-refresh text-primary"/> - <label for="generate_except_osv_safe_eval" string="Warning-description"/> - </div> - <div> - <button name="generate_except_orm_safe_eval" string="except_orm*" type="object" icon="fa-refresh text-primary"/> - <label for="generate_except_orm_safe_eval" string="Warning-description"/> - </div> - <div> - <button name="generate_warning_safe_eval" string="Warning*" type="object" icon="fa-refresh text-primary"/> - <label for="generate_warning_safe_eval" string="Warning-description"/> - </div> - <div> <button name="generate_redirect_warning_safe_eval" string="RedirectWarning*" type="object" icon="fa-refresh text-primary"/> <label for="generate_redirect_warning_safe_eval" string="Warning-description-redirection button" class="oe_inline"/> </div> diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py index 97d1cb2dacc..9f895ba5732 100644 --- a/odoo/addons/test_new_api/tests/test_new_fields.py +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -1539,7 +1539,7 @@ class TestFields(TransactionCaseWithUserDemo): 'name': 'Test without attachment', 'image_wo_attachment': SVG.decode("utf-8"), }) - self.assertEqual(e.exception.name, 'Only admins can upload SVG files.') + self.assertEqual(e.exception.args[0], 'Only admins can upload SVG files.') def test_90_binary_svg(self): from odoo.addons.base.tests.test_mimetypes import SVG diff --git a/odoo/exceptions.py b/odoo/exceptions.py index 93a0a84e20f..8400194e75a 100644 --- a/odoo/exceptions.py +++ b/odoo/exceptions.py @@ -13,58 +13,64 @@ treated as a 'Server error'. """ import logging -from inspect import currentframe -from .tools.func import frame_codeinfo +import warnings _logger = logging.getLogger(__name__) -# kept for backward compatibility -class except_orm(Exception): - def __init__(self, name, value=None): - if type(self) == except_orm: - caller = frame_codeinfo(currentframe(), 1) - _logger.warning('except_orm is deprecated. Please use specific exceptions like UserError or AccessError. Caller: %s:%s', *caller) - self.name = name - self.value = value - self.args = (name, value) - - def __str__(self): - if not self.value: - return str(self.name) - else: - return super().__str__() - - -class UserError(except_orm): +class UserError(Exception): """Generic error managed by the client. Typically when the user tries to do something that has no sense given the current - state of a record. + state of a record. Semantically comparable to the generic 400 HTTP status codes. """ - def __init__(self, msg): - super(UserError, self).__init__(msg, value='') + + def __init__(self, message): + """ + :param message: exception message and frontend modal content + """ + super().__init__(message) + + @property + def name(self): + warnings.warn( + "UserError attribute 'name' is a deprecated alias to args[0]", + DeprecationWarning) + return self.args[0] # deprecated due to collision with builtins, kept for compatibility -Warning = UserError +class Warning(UserError): + def __init__(self, *args, **kwargs): + warnings.warn( + "Warning is a deprecated alias to UserError and is going to be " + "removed in a future version.", + DeprecationWarning) + super().__init__(*args, **kwargs) class RedirectWarning(Exception): """ Warning with a possibility to redirect the user instead of simply displaying the warning message. + :param str message: exception message and frontend modal content :param int action_id: id of the action where to perform the redirection :param str button_text: text to put on the button that will trigger the redirection. """ - # using this RedirectWarning won't crash if used as an except_orm + def __init__(self, message, action, button_text): + super().__init__(message, action, button_text) + + # using this RedirectWarning won't crash if used as an UserError @property def name(self): + warnings.warn( + "RedirectWarning attribute 'name' is a deprecated alias to args[0]", + DeprecationWarning) return self.args[0] -class AccessDenied(Exception): +class AccessDenied(UserError): """Login/password error. .. note:: @@ -76,14 +82,14 @@ class AccessDenied(Exception): When you try to log with a wrong password. """ - def __init__(self, message='Access denied'): - super(AccessDenied, self).__init__(message) + def __init__(self, message="Access Denied"): + super().__init__(message) self.with_traceback(None) self.__cause__ = None self.traceback = ('', '', '') -class AccessError(except_orm): +class AccessError(UserError): """Access rights error. .. admonition:: Example @@ -91,11 +97,8 @@ class AccessError(except_orm): When you try to read a record that you are not allowed to. """ - def __init__(self, msg): - super(AccessError, self).__init__(msg) - -class CacheMiss(except_orm, KeyError): +class CacheMiss(KeyError): """Missing value(s) in cache. .. admonition:: Example @@ -104,10 +107,10 @@ class CacheMiss(except_orm, KeyError): """ def __init__(self, record, field): - super(CacheMiss, self).__init__("%r.%s" % (record, field.name)) + super().__init__("%r.%s" % (record, field.name)) -class MissingError(except_orm): +class MissingError(UserError): """Missing record(s). .. admonition:: Example @@ -115,36 +118,11 @@ class MissingError(except_orm): When you try to write on a deleted record. """ - def __init__(self, msg): - super(MissingError, self).__init__(msg) - -class ValidationError(except_orm): +class ValidationError(UserError): """Violation of python constraints. .. admonition:: Example When you try to create a new user with a login which already exist in the db. """ - - def __init__(self, msg): - super(ValidationError, self).__init__(msg) - - -class DeferredException(Exception): - """ Exception object holding a traceback for asynchronous reporting. - - Some RPC calls (database creation and report generation) happen with - an initial request followed by multiple, polling requests. This class - is used to store the possible exception occuring in the thread serving - the first request, and is then sent to a polling request. - - ('Traceback' is misleading, this is really a exc_info() triple.) - """ - def __init__(self, msg, tb): - self.message = msg - self.traceback = tb - - -class QWebException(Exception): - pass diff --git a/odoo/http.py b/odoo/http.py index 085eaa29240..2b3472ddc9d 100644 --- a/odoo/http.py +++ b/odoo/http.py @@ -106,9 +106,8 @@ def replace_request_password(args): # don't trigger debugger for those exceptions, they carry user-facing warnings # and indications, they're not necessarily indicative of anything being # *broken* -NO_POSTMORTEM = (odoo.exceptions.except_orm, - odoo.exceptions.AccessDenied, - odoo.exceptions.Warning, +NO_POSTMORTEM = (odoo.exceptions.AccessDenied, + odoo.exceptions.UserError, odoo.exceptions.RedirectWarning) @@ -639,8 +638,8 @@ class JsonRequest(WebRequest): if not isinstance(exception, SessionExpiredException): if exception.args and exception.args[0] == "bus.Bus not available in test mode": _logger.info(exception) - elif isinstance(exception, (odoo.exceptions.Warning, odoo.exceptions.except_orm, - werkzeug.exceptions.NotFound)): + elif isinstance(exception, (odoo.exceptions.UserError, + werkzeug.exceptions.NotFound)): _logger.warning(exception) else: _logger.exception("Exception during JSON request handling.") @@ -696,31 +695,13 @@ class JsonRequest(WebRequest): def serialize_exception(e): - tmp = { + return { "name": type(e).__module__ + "." + type(e).__name__ if type(e).__module__ else type(e).__name__, "debug": traceback.format_exc(), "message": ustr(e), "arguments": e.args, - "exception_type": "internal_error", "context": getattr(e, 'context', {}), } - if isinstance(e, odoo.exceptions.UserError): - tmp["exception_type"] = "user_error" - elif isinstance(e, odoo.exceptions.Warning): - tmp["exception_type"] = "warning" - elif isinstance(e, odoo.exceptions.RedirectWarning): - tmp["exception_type"] = "warning" - elif isinstance(e, odoo.exceptions.AccessError): - tmp["exception_type"] = "access_error" - elif isinstance(e, odoo.exceptions.MissingError): - tmp["exception_type"] = "missing_error" - elif isinstance(e, odoo.exceptions.AccessDenied): - tmp["exception_type"] = "access_denied" - elif isinstance(e, odoo.exceptions.ValidationError): - tmp["exception_type"] = "validation_error" - elif isinstance(e, odoo.exceptions.except_orm): - tmp["exception_type"] = "except_orm" - return tmp class HttpRequest(WebRequest): diff --git a/odoo/models.py b/odoo/models.py index 67aacc33417..927263f2025 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -342,7 +342,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): # default values for _transient_vacuum() _transient_check_count = 0 _transient_max_count = lazy_classproperty(lambda _: config.get('osv_memory_count_limit')) - _transient_max_hours = lazy_classproperty(lambda _: config.get('osv_memory_age_limit')) + _transient_max_hours = lazy_classproperty(lambda _: config.get('transient_age_limit')) CONCURRENCY_CHECK_FIELD = '__last_update' diff --git a/odoo/osv/__init__.py b/odoo/osv/__init__.py index 9399e4410fb..2903e8d9121 100644 --- a/odoo/osv/__init__.py +++ b/odoo/osv/__init__.py @@ -1,4 +1,3 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import osv diff --git a/odoo/osv/osv.py b/odoo/osv/osv.py deleted file mode 100644 index dc23494450b..00000000000 --- a/odoo/osv/osv.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from ..exceptions import except_orm -from ..models import Model, TransientModel, AbstractModel - -# Deprecated, kept for backward compatibility. -# openerp.exceptions.Warning should be used instead. -except_osv = except_orm - -# Deprecated, kept for backward compatibility. -osv = Model -osv_memory = TransientModel -osv_abstract = AbstractModel # ;-) diff --git a/odoo/service/model.py b/odoo/service/model.py index 82a32c2cfc1..731007abd23 100644 --- a/odoo/service/model.py +++ b/odoo/service/model.py @@ -9,7 +9,7 @@ import threading import time import odoo -from odoo.exceptions import UserError, ValidationError, QWebException +from odoo.exceptions import UserError, ValidationError from odoo.models import check_method_name from odoo.tools.translate import translate, translate_sql_constraint from odoo.tools.translate import _ @@ -91,13 +91,7 @@ def check(f): if odoo.registry(dbname)._init and not odoo.tools.config['test_enable']: raise odoo.exceptions.Warning('Currently, this database is not fully loaded and can not be used.') return f(dbname, *args, **kwargs) - except (OperationalError, QWebException) as e: - if isinstance(e, QWebException): - cause = e.qweb.get('cause') - if isinstance(cause, OperationalError): - e = cause - else: - raise + except OperationalError as e: # Automatically retry the typical transaction serialization errors if e.pgcode not in PG_CONCURRENCY_ERRORS_TO_RETRY: raise diff --git a/odoo/service/wsgi_server.py b/odoo/service/wsgi_server.py index 65ca8806df9..c2712203804 100644 --- a/odoo/service/wsgi_server.py +++ b/odoo/service/wsgi_server.py @@ -35,22 +35,14 @@ RPC_FAULT_CODE_ACCESS_DENIED = 3 RPC_FAULT_CODE_ACCESS_ERROR = 4 def xmlrpc_handle_exception_int(e): - if isinstance(e, odoo.exceptions.UserError): - fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, odoo.tools.ustr(e.name)) - elif isinstance(e, odoo.exceptions.RedirectWarning): + if isinstance(e, odoo.exceptions.RedirectWarning): fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, str(e)) - elif isinstance(e, odoo.exceptions.MissingError): - fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, str(e)) - elif isinstance (e, odoo.exceptions.AccessError): + elif isinstance(e, odoo.exceptions.AccessError): fault = xmlrpclib.Fault(RPC_FAULT_CODE_ACCESS_ERROR, str(e)) elif isinstance(e, odoo.exceptions.AccessDenied): fault = xmlrpclib.Fault(RPC_FAULT_CODE_ACCESS_DENIED, str(e)) - elif isinstance(e, odoo.exceptions.DeferredException): - info = e.traceback - # Which one is the best ? - formatted_info = "".join(traceback.format_exception(*info)) - #formatted_info = odoo.tools.exception_to_unicode(e) + '\n' + info - fault = xmlrpclib.Fault(RPC_FAULT_CODE_APPLICATION_ERROR, formatted_info) + elif isinstance(e, odoo.exceptions.UserError): + fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, str(e)) else: info = sys.exc_info() # Which one is the best ? @@ -61,9 +53,7 @@ def xmlrpc_handle_exception_int(e): return xmlrpclib.dumps(fault, allow_none=None) def xmlrpc_handle_exception_string(e): - if isinstance(e, odoo.exceptions.UserError): - fault = xmlrpclib.Fault('warning -- %s\n\n%s' % (e.name, e.value), '') - elif isinstance(e, odoo.exceptions.RedirectWarning): + if isinstance(e, odoo.exceptions.RedirectWarning): fault = xmlrpclib.Fault('warning -- Warning\n\n' + str(e), '') elif isinstance(e, odoo.exceptions.MissingError): fault = xmlrpclib.Fault('warning -- MissingError\n\n' + str(e), '') @@ -71,10 +61,8 @@ def xmlrpc_handle_exception_string(e): fault = xmlrpclib.Fault('warning -- AccessError\n\n' + str(e), '') elif isinstance(e, odoo.exceptions.AccessDenied): fault = xmlrpclib.Fault('AccessDenied', str(e)) - elif isinstance(e, odoo.exceptions.DeferredException): - info = e.traceback - formatted_info = "".join(traceback.format_exception(*info)) - fault = xmlrpclib.Fault(odoo.tools.ustr(e), formatted_info) + elif isinstance(e, odoo.exceptions.UserError): + fault = xmlrpclib.Fault('warning -- UserError\n\n' + str(e), '') #InternalError else: info = sys.exc_info() diff --git a/odoo/tools/config.py b/odoo/tools/config.py index c9f6e2556c3..a50e3a8460c 100644 --- a/odoo/tools/config.py +++ b/odoo/tools/config.py @@ -9,6 +9,7 @@ import glob import os import sys import tempfile +import warnings import odoo from os.path import expandvars, expanduser, abspath, realpath from .. import release, conf, loglevels @@ -280,10 +281,12 @@ class configmanager(object): help="Force a limit on the maximum number of records kept in the virtual " "osv_memory tables. The default is False, which means no count-based limit.", type="int") - group.add_option("--osv-memory-age-limit", dest="osv_memory_age_limit", my_default=1.0, - help="Force a limit on the maximum age of records kept in the virtual " - "osv_memory tables. This is a decimal value expressed in hours, " - "and the default is 1 hour.", + group.add_option("--transient-age-limit", dest="transient_age_limit", my_default=1.0, + help="Time limit (decimal value in hours) records created with a " + "TransientModel (mosly wizard) are kept in the database. Default to 1 hour.", + type="float") + group.add_option("--osv-memory-age-limit", dest="osv_memory_age_limit", my_default=False, + help="Deprecated alias to the transient-age-limit option", type="float") group.add_option("--max-cron-threads", dest="max_cron_threads", my_default=2, help="Maximum number of threads processing concurrently cron jobs (default 2).", @@ -350,6 +353,7 @@ class configmanager(object): """ opt = self._parse_config(args) odoo.netsvc.init_logger() + self._warn_deprecated_options() odoo.modules.module.initialize_sys_path() return opt @@ -382,6 +386,10 @@ class configmanager(object): "The config file '%s' selected with -c/--config doesn't exist or is not readable, "\ "use -s/--save if you want to generate it"% opt.config) + die(bool(opt.osv_memory_age_limit) and bool(opt.transient_memory_age_limit), + "the osv-memory-count-limit option cannot be used with the " + "transient-age-limit option, please only use the latter.") + # place/search the config file on Win32 near the server installation # (../etc from the server) # if the server is run by an unprivileged user, he has to specify location of a config file where he has the rights to write, @@ -448,7 +456,7 @@ class configmanager(object): 'stop_after_init', 'without_demo', 'http_enable', 'syslog', 'list_db', 'proxy_mode', 'test_file', 'test_tags', - 'osv_memory_count_limit', 'osv_memory_age_limit', 'max_cron_threads', 'unaccent', + 'osv_memory_count_limit', 'osv_memory_age_limit', 'transient_age_limit', 'max_cron_threads', 'unaccent', 'data_dir', 'server_wide_modules', ] @@ -525,9 +533,16 @@ class configmanager(object): conf.server_wide_modules = [ m.strip() for m in self.options['server_wide_modules'].split(',') if m.strip() ] - return opt + def _warn_deprecated_options(self): + if self.options['osv_memory_age_limit']: + warnings.warn( + "The osv-memory-age-limit is a deprecated alias to " + "the transient-age-limit option, please use the latter.", + DeprecationWarning) + self.options['transient_age_limit'] = self.options.pop('osv_memory_age_limit') + def _is_addons_path(self, path): from odoo.modules.module import MANIFEST_NAMES for f in os.listdir(path): diff --git a/odoo/tools/safe_eval.py b/odoo/tools/safe_eval.py index 460eccccae8..262928874e4 100644 --- a/odoo/tools/safe_eval.py +++ b/odoo/tools/safe_eval.py @@ -322,16 +322,10 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal c = test_expr(expr, _SAFE_OPCODES, mode=mode) try: return unsafe_eval(c, globals_dict, locals_dict) - except odoo.exceptions.except_orm: - raise - except odoo.exceptions.Warning: + except odoo.exceptions.UserError: raise except odoo.exceptions.RedirectWarning: raise - except odoo.exceptions.AccessDenied: - raise - except odoo.exceptions.AccessError: - raise except werkzeug.exceptions.HTTPException: raise except odoo.http.AuthenticationError: @@ -340,8 +334,6 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal # Do not hide PostgreSQL low-level exceptions, to let the auto-replay # of serialized transactions work its magic raise - except odoo.exceptions.MissingError: - raise except ZeroDivisionError: raise except Exception as e: diff --git a/odoo/tools/test-config-values-00.conf b/odoo/tools/test-config-values-00.conf index 1d0bd7419fe..bcf90301708 100644 --- a/odoo/tools/test-config-values-00.conf +++ b/odoo/tools/test-config-values-00.conf @@ -1,3 +1,3 @@ [options] osv_memory_count_limit = 5 -osv_memory_age_limit = 3.4 +transient_age_limit = 3.4 diff --git a/odoo/tools/test_config.py b/odoo/tools/test_config.py index 045bcaaa9c7..2e05d288f4f 100644 --- a/odoo/tools/test_config.py +++ b/odoo/tools/test_config.py @@ -18,7 +18,7 @@ config_file_00 = os.path.join(os.path.dirname(__file__),'test-config-values-00.c conf = config.configmanager() conf.parse_config() -assert conf['osv_memory_age_limit'] == 1.0 +assert conf['transient_age_limit'] == 1.0 assert os.path.join(conf['root_path'], 'addons') == conf['addons_path'] # 2. No config file, some command-line arguments @@ -26,9 +26,9 @@ assert os.path.join(conf['root_path'], 'addons') == conf['addons_path'] conf = config.configmanager() # mess with the optparse.Option definition to allow an invalid path conf.casts['addons_path'].action = 'store' -conf.parse_config(['--addons-path=/xyz/dont-exist', '--osv-memory-age-limit=2.3']) +conf.parse_config(['--addons-path=/xyz/dont-exist', '--transient-age-limit=2.3']) -assert conf['osv_memory_age_limit'] == 2.3 +assert conf['transient_age_limit'] == 2.3 assert conf['addons_path'] == '/xyz/dont-exist' # 3. Config file, no command-line arguments @@ -36,11 +36,11 @@ assert conf['addons_path'] == '/xyz/dont-exist' conf = config.configmanager() conf.parse_config(['-c', config_file_00]) -assert conf['osv_memory_age_limit'] == 3.4 +assert conf['transient_age_limit'] == 3.4 # 4. Config file, and command-line arguments conf = config.configmanager() -conf.parse_config(['-c', config_file_00, '--osv-memory-age-limit=2.3']) +conf.parse_config(['-c', config_file_00, '--transient-age-limit=2.3']) -assert conf['osv_memory_age_limit'] == 2.3 +assert conf['transient_age_limit'] == 2.3 |
