18.0 vanilla

This commit is contained in:
Ernad Husremovic 2025-10-03 18:06:50 +02:00
parent d72e748793
commit 0a7ae8db93
337 changed files with 399651 additions and 232598 deletions

View file

@ -6,20 +6,22 @@ import time
from collections.abc import Mapping, Sequence
from functools import partial
from psycopg2 import IntegrityError, OperationalError, errorcodes
from psycopg2 import IntegrityError, OperationalError, errorcodes, errors
import odoo
from odoo.exceptions import UserError, ValidationError, AccessError
from odoo.models import BaseModel
from odoo.http import request
from odoo.tools import DotDict
from odoo.tools.translate import _, translate_sql_constraint
from odoo.modules.registry import Registry
from odoo.tools import DotDict, lazy
from odoo.tools.translate import translate_sql_constraint
from . import security
from ..tools import lazy
_logger = logging.getLogger(__name__)
PG_CONCURRENCY_ERRORS_TO_RETRY = (errorcodes.LOCK_NOT_AVAILABLE, errorcodes.SERIALIZATION_FAILURE, errorcodes.DEADLOCK_DETECTED)
PG_CONCURRENCY_EXCEPTIONS_TO_RETRY = (errors.LockNotAvailable, errors.SerializationFailure, errors.DeadlockDetected)
MAX_TRIES_ON_CONCURRENCY_FAILURE = 5
@ -48,7 +50,7 @@ def dispatch(method, params):
threading.current_thread().dbname = db
threading.current_thread().uid = uid
registry = odoo.registry(db).check_signaling()
registry = Registry(db).check_signaling()
with registry.manage_changes():
if method == 'execute':
res = execute(db, uid, *params[3:])
@ -65,7 +67,7 @@ def execute_cr(cr, uid, obj, method, *args, **kw):
env = odoo.api.Environment(cr, uid, {})
recs = env.get(obj)
if recs is None:
raise UserError(_("Object %s doesn't exist", obj))
raise UserError(env._("Object %s doesn't exist", obj))
get_public_method(recs, method) # Don't use the result, call_kw will redo the getattr
result = retrying(partial(odoo.api.call_kw, recs, method, args, kw), env)
# force evaluation of lazy values before the cursor is closed, as it would
@ -80,7 +82,8 @@ def execute_kw(db, uid, obj, method, args, kw=None):
def execute(db, uid, obj, method, *args, **kw):
with odoo.registry(db).cursor() as cr:
# TODO could be conditionnaly readonly as in _call_kw_readonly
with Registry(db).cursor() as cr:
res = execute_cr(cr, uid, obj, method, *args, **kw)
if res is None:
_logger.info('The method %s of the object %s can not return `None`!', method, obj)
@ -90,47 +93,48 @@ def execute(db, uid, obj, method, *args, **kw):
def _as_validation_error(env, exc):
""" Return the IntegrityError encapsuled in a nice ValidationError """
unknown = _('Unknown')
model = DotDict({'_name': unknown.lower(), '_description': unknown})
field = DotDict({'name': unknown.lower(), 'string': unknown})
unknown = env._('Unknown')
model = DotDict({'_name': 'unknown', '_description': unknown})
field = DotDict({'name': 'unknown', 'string': unknown})
for _name, rclass in env.registry.items():
if exc.diag.table_name == rclass._table:
model = rclass
field = model._fields.get(exc.diag.column_name) or field
break
if exc.pgcode == errorcodes.NOT_NULL_VIOLATION:
return ValidationError(_(
"The operation cannot be completed:\n"
"- Create/update: a mandatory field is not set.\n"
"- Delete: another model requires the record being deleted."
" If possible, archive it instead.\n\n"
"Model: %(model_name)s (%(model_tech_name)s)\n"
"Field: %(field_name)s (%(field_tech_name)s)\n",
model_name=model._description,
model_tech_name=model._name,
field_name=field.string,
field_tech_name=field.name,
))
match exc:
case errors.NotNullViolation():
return ValidationError(env._(
"The operation cannot be completed:\n"
"- Create/update: a mandatory field is not set.\n"
"- Delete: another model requires the record being deleted."
" If possible, archive it instead.\n\n"
"Model: %(model_name)s (%(model_tech_name)s)\n"
"Field: %(field_name)s (%(field_tech_name)s)\n",
model_name=model._description,
model_tech_name=model._name,
field_name=field.string,
field_tech_name=field.name,
))
if exc.pgcode == errorcodes.FOREIGN_KEY_VIOLATION:
return ValidationError(_(
"The operation cannot be completed: another model requires "
"the record being deleted. If possible, archive it instead.\n\n"
"Model: %(model_name)s (%(model_tech_name)s)\n"
"Constraint: %(constraint)s\n",
model_name=model._description,
model_tech_name=model._name,
constraint=exc.diag.constraint_name,
))
case errors.ForeignKeyViolation():
return ValidationError(env._(
"The operation cannot be completed: another model requires "
"the record being deleted. If possible, archive it instead.\n\n"
"Model: %(model_name)s (%(model_tech_name)s)\n"
"Constraint: %(constraint)s\n",
model_name=model._description,
model_tech_name=model._name,
constraint=exc.diag.constraint_name,
))
if exc.diag.constraint_name in env.registry._sql_constraints:
return ValidationError(_(
return ValidationError(env._(
"The operation cannot be completed: %s",
translate_sql_constraint(env.cr, exc.diag.constraint_name, env.context.get('lang', 'en_US'))
))
return ValidationError(_("The operation cannot be completed: %s", exc.args[0]))
return ValidationError(env._("The operation cannot be completed: %s", exc.args[0]))
def retrying(func, env):
@ -169,7 +173,7 @@ def retrying(func, env):
raise RuntimeError(f"Cannot retry request on input file {filename!r} after serialization failure") from exc
if isinstance(exc, IntegrityError):
raise _as_validation_error(env, exc) from exc
if exc.pgcode not in PG_CONCURRENCY_ERRORS_TO_RETRY:
if not isinstance(exc, PG_CONCURRENCY_EXCEPTIONS_TO_RETRY):
raise
if not tryleft:
_logger.info("%s, maximum number of tries reached!", errorcodes.lookup(exc.pgcode))