[REF] base: Remove deprecated exceptions and osv

TL;DR: remember `osv` and `except_orm` ? You can forget about them.

* Deprecated `except_orm` dropped.
* `UserError` elevated as super type of all user-related
  errors.
* Unused `DeferredException` dropped.
* Unused `QWebException` dropped (real one is in `qweb.py`).
* `MailDeliveryException` made a python exception.
* `name` legacy exception attribute made an alias of the python standard
  `args[0]` attribute and deprecated.
* `value` legacy exception attribute dropped.
* `exception_type` RPC error response key dropped.
* Deprecated `osv` module dropped.
* `--osv-memory-age-limit` cli option made an alias of
  `--transient-age-limit` and deprecated.

The `odoo.exceptions.Warning` have long been a deprecated alias to
`UserError`. It is going to be removed in a future version but first we
explicitly deprecate it with a warning.

The `odoo.exceptions.DeferredException` was a very old internal
exception, it has been removed without deprecation notice as it is never
raised.

The `odoo.exceptions.except_orm` has been a deprecated exception type
with deprecation warning for 5 years, it has been removed in favor of
UserError which becomes the super class of all user-related errors.

The `odoo.base.models.ir_mail_server.MailDeliveryException` was
inheriting `except_orm`. As it is not related to a user error but is
more of a problem an admin much take care of, the exception has been
made a Python error.

The `exception_type` JSON key in RPC error responses was holding an
hardcoded value derived from the exception type. Its usage has been
dropped in favor of the `name` JSON key that holds the precise exception
name. Again as it was hardly used in the source code (beside the crash
manager) it has been dropped without deprecation warning.

Since we are here trying to clean odoo custom exceptions, we are also
deprecating the `name` exception attribute in favor of the more standard
`args[0]` attribute.

The `name` (along with `value`) were two attributes used to raise
`except_orm` exceptions before the introduction of `UserError`,
`AccessError` and related exceptions. The `name` attribute, at the time,
was holding the exception type/title. Nowadays it contains the error
message. The `value` attribute, at the time, was holding the error
message. Nowadays it is no more used.

The `osv` module contains very old deprecated aliases. There is no
simple way to log a deprecation warning for osv, osv_memory and
osv_abstract but as they have not been in use for ages, they have been
removed too. To be consistent, the `--osv-memory-age-limit` cli option
has been made a deprecated alias to the `--transient-age-limit`.

closes odoo/odoo#45723

Task: 2187728
Related: odoo/enterprise#9162
Signed-off-by: Raphael Collet (rco) <rco@openerp.com>
This commit is contained in:
Julien Castiaux
2020-04-08 08:41:17 +00:00
committed by Raphael Collet
parent 640e63e371
commit ab4000fb3c
35 changed files with 148 additions and 312 deletions
@@ -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
+2 -2
View File
@@ -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')
+1 -1
View File
@@ -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(
+2 -3
View File
@@ -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
+1 -3
View File
@@ -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:
+1 -1
View File
@@ -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():
+2 -2
View File
@@ -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.
+1 -1
View File
@@ -57,7 +57,7 @@ class AuthorizeController(http.Controller):
}
except UserError as e:
return {
'error': e.name,
'error': e.args[0],
}
if not token:
@@ -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);
});
+3 -7
View File
@@ -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
+7 -11
View File
@@ -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
+2 -2
View File
@@ -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")
@@ -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) {
@@ -2277,7 +2277,6 @@ QUnit.module('ActionManager', {
params.error({
data: {
name: 'error',
exception_type: 'warning',
arguments: ['could not download file'],
}
});
@@ -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'/);
+1 -1
View File
@@ -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:
+6 -6
View File
@@ -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}
+1
View File
@@ -508,6 +508,7 @@ class IrActionsServer(models.Model):
'model': model,
# Exceptions
'Warning': odoo.exceptions.Warning,
'UserError': odoo.exceptions.UserError,
# record
'record': record,
'records': records,
+3 -4
View File
@@ -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):
+2 -2
View File
@@ -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',
+1 -1
View File
@@ -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)
-21
View File
@@ -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)
-24
View File
@@ -10,18 +10,6 @@
<p>Each button generates a specific exception on the server. The text on the right is the expected representation of the exception when displayed on the client. Button marked with a '*' use safe_eval().</p>
<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"/>
@@ -55,18 +43,6 @@
<label for="generate_validation_error" string="Validation Error-description"/>
</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"/>
@@ -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
+39 -61
View File
@@ -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
+5 -24
View File
@@ -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):
+1 -1
View File
@@ -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'
-1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import osv
-14
View File
@@ -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 # ;-)
+2 -8
View File
@@ -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
+7 -19
View File
@@ -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()
+21 -6
View File
@@ -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):
+1 -9
View File
@@ -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:
+1 -1
View File
@@ -1,3 +1,3 @@
[options]
osv_memory_count_limit = 5
osv_memory_age_limit = 3.4
transient_age_limit = 3.4
+6 -6
View File
@@ -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