From a08b2bac0b2e8e3e560668302e58caea65ea7d28 Mon Sep 17 00:00:00 2001 From: bve-odoo <bve@odoo.com> Date: Thu, 23 Feb 2023 14:07:47 +0000 Subject: [PATCH] [FIX] *: prevent warnings on runbot and sentry noisy tracebacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As we have a team dedicated to work on analysing tracebacks with sentry, and to takedown all tracebacks occuring on the saas, we do an effort to avoid unecessary warnings and tracebacks for both sentry and the runbot. closes odoo/odoo#112101 Signed-off-by: Rémy Voet <ryv@odoo.com> --- addons/web_editor/controllers/main.py | 2 +- odoo/addons/base/models/res_users.py | 65 ++++++++++++++------------- odoo/fields.py | 58 +++++++++++++----------- odoo/models.py | 14 +++--- 4 files changed, 73 insertions(+), 66 deletions(-) diff --git a/addons/web_editor/controllers/main.py b/addons/web_editor/controllers/main.py index 92d79774bf0c..2a811702d65f 100644 --- a/addons/web_editor/controllers/main.py +++ b/addons/web_editor/controllers/main.py @@ -98,7 +98,7 @@ class Web_Editor(http.Controller): @http.route('/web_editor/checklist', type='json', auth='user') def update_checklist(self, res_model, res_id, filename, checklistId, checked, **kwargs): record = request.env[res_model].browse(res_id) - value = getattr(record, filename, False) + value = filename in record._fields and record[filename] htmlelem = etree.fromstring("<div>%s</div>" % value, etree.HTMLParser()) checked = bool(checked) diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py index c33aba3d9dd0..3577e11eaee9 100644 --- a/odoo/addons/base/models/res_users.py +++ b/odoo/addons/base/models/res_users.py @@ -13,10 +13,10 @@ import logging import os import time from collections import defaultdict +from functools import wraps from hashlib import sha256 from itertools import chain, repeat -import decorator import passlib.context import pytz from lxml import etree @@ -94,8 +94,7 @@ def _jsonable(o): except TypeError: return False else: return True -@decorator.decorator -def check_identity(fn, self): +def check_identity(fn): """ Wrapped method should be an *action method* (called from a button type=object), and requires extra security to be executed. This decorator checks if the identity (password) has been checked in the last 10mn, and @@ -103,32 +102,36 @@ def check_identity(fn, self): Prevents access outside of interactive contexts (aka with a request) """ - if not request: - raise UserError(_("This method can only be accessed over HTTP")) - - if request.session.get('identity-check-last', 0) > time.time() - 10 * 60: - # update identity-check-last like github? - return fn(self) - - w = self.sudo().env['res.users.identitycheck'].create({ - 'request': json.dumps([ - { # strip non-jsonable keys (e.g. mapped to recordsets like binary_field_real_user) - k: v for k, v in self.env.context.items() - if _jsonable(v) - }, - self._name, - self.ids, - fn.__name__ - ]) - }) - return { - 'type': 'ir.actions.act_window', - 'res_model': 'res.users.identitycheck', - 'res_id': w.id, - 'name': _("Security Control"), - 'target': 'new', - 'views': [(False, 'form')], - } + @wraps(fn) + def wrapped(self): + if not request: + raise UserError(_("This method can only be accessed over HTTP")) + + if request.session.get('identity-check-last', 0) > time.time() - 10 * 60: + # update identity-check-last like github? + return fn(self) + + w = self.sudo().env['res.users.identitycheck'].create({ + 'request': json.dumps([ + { # strip non-jsonable keys (e.g. mapped to recordsets like binary_field_real_user) + k: v for k, v in self.env.context.items() + if _jsonable(v) + }, + self._name, + self.ids, + fn.__name__ + ]) + }) + return { + 'type': 'ir.actions.act_window', + 'res_model': 'res.users.identitycheck', + 'res_id': w.id, + 'name': _("Security Control"), + 'target': 'new', + 'views': [(False, 'form')], + } + wrapped.__has_check_identity = True + return wrapped #---------------------------------------------------------- # Basic res.groups and res.users @@ -1521,7 +1524,9 @@ class CheckIdentity(models.TransientModel): request.session['identity-check-last'] = time.time() ctx, model, ids, method = json.loads(self.sudo().request) - return getattr(self.env(context=ctx)[model].browse(ids), method)() + method = getattr(self.env(context=ctx)[model].browse(ids), method) + assert getattr(method, '__has_check_identity', False) + return method() #---------------------------------------------------------- # change password wizard diff --git a/odoo/fields.py b/odoo/fields.py index fb5b3956fd77..55ce2c9158f5 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -64,6 +64,29 @@ def resolve_mro(model, name, predicate): return result +def determine(needle, records, *args): + """ Simple helper for calling a method given as a string or a function. + + :param needle: callable or name of method to call on ``records`` + :param BaseModel records: recordset to call ``needle`` on or with + :params args: additional arguments to pass to the determinant + :returns: the determined value if the determinant is a method name or callable + :raise TypeError: if ``records`` is not a recordset, or ``needle`` is not + a callable or valid method name + """ + if not isinstance(records, BaseModel): + raise TypeError("Determination requires a subject recordset") + if isinstance(needle, str): + needle = getattr(records, needle) + if needle.__name__.find('__'): + return needle(*args) + elif callable(needle): + if needle.__name__.find('__'): + return needle(records, *args) + + raise TypeError("Determination requires a callable or method name") + + class MetaField(type): """ Metaclass for field classes. """ by_type = {} @@ -253,10 +276,6 @@ class Field(MetaField('DummyField', (object,), {})): self._sequence = kwargs['_sequence'] = next(_global_seq) self.args = {key: val for key, val in kwargs.items() if val is not Default} - def new(self, **kwargs): - """ Return a field of the same type as ``self``, with its own parameters. """ - return type(self)(**kwargs) - def __str__(self): return "%s.%s" % (self.model_name, self.name) @@ -482,7 +501,7 @@ class Field(MetaField('DummyField', (object,), {})): for attr, prop in self.related_attrs: # check whether 'attr' is explicitly set on self (from its field # definition), and ignore its class-level value (only a default) - if attr not in self.__dict__: + if attr not in self.__dict__ and prop.startswith('_related_'): setattr(self, attr, getattr(field, prop)) for attr, value in field.__dict__.items(): @@ -662,6 +681,8 @@ class Field(MetaField('DummyField', (object,), {})): """ Return a dictionary that describes the field ``self``. """ desc = {'type': self.type} for attr, prop in self.description_attrs: + if not prop.startswith('_description_'): + continue value = getattr(self, prop) if callable(value): value = value(env) @@ -1183,22 +1204,11 @@ class Field(MetaField('DummyField', (object,), {})): def determine_inverse(self, records): """ Given the value of ``self`` on ``records``, inverse the computation. """ - if isinstance(self.inverse, str): - getattr(records, self.inverse)() - else: - self.inverse(records) + determine(self.inverse, records) def determine_domain(self, records, operator, value): """ Return a domain representing a condition on ``self``. """ - if isinstance(self.search, str): - return getattr(records, self.search)(operator, value) - else: - return self.search(records, operator, value) - - ############################################################################ - # - # Notification when fields are modified - # + return determine(self.search, records, operator, value) class Boolean(Field): @@ -2383,10 +2393,8 @@ class Selection(Field): translated according to context language """ selection = self.selection - if isinstance(selection, str): - return getattr(env[self.model_name], selection)() - if callable(selection): - return selection(env[self.model_name]) + if isinstance(selection, str) or callable(selection): + return determine(selection, env[self.model_name]) # translate selection labels if env.lang: @@ -2397,10 +2405,8 @@ class Selection(Field): def get_values(self, env): """Return a list of the possible values.""" selection = self.selection - if isinstance(selection, str): - selection = getattr(env[self.model_name], selection)() - elif callable(selection): - selection = selection(env[self.model_name]) + if isinstance(selection, str) or callable(selection): + selection = determine(selection, env[self.model_name]) return [value for value, _ in selection] def convert_to_column(self, value, record, values=None, validate=True): diff --git a/odoo/models.py b/odoo/models.py index 89aa01d23152..f3ef92e8db13 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -2692,7 +2692,8 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): # following specific properties: # - reading inherited fields should not bypass access rights # - copy inherited fields iff their original field is copied - self._add_field(name, field.new( + Field = type(field) + self._add_field(name, Field( inherited=True, inherited_field=field, related=(parent_fname, name), @@ -2778,7 +2779,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): if not field.related: self._add_field(name, field) else: - self._add_field(name, field.new(**field.args)) + self._add_field(name, type(field)(**field.args)) cls._model_fields = list(cls._fields) else: @@ -2790,7 +2791,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): for name, field in sorted(getmembers(cls, Field.__instancecheck__), key=lambda f: f[1]._sequence): # do not retrieve magic, custom and inherited fields if not any(field.args.get(k) for k in ('automatic', 'manual', 'inherited')): - self._add_field(name, field.new()) + self._add_field(name, type(field)()) self._add_magic_fields() cls._model_fields = list(cls._fields) @@ -4072,12 +4073,7 @@ Fields: return records def _compute_field_value(self, field): - # This is for base automation, to have something to override to catch - # the changes of values for stored compute fields. - if isinstance(field.compute, str): - getattr(self, field.compute)() - else: - field.compute(self) + odoo.fields.determine(field.compute, self) if field.store and any(self._ids): # check constraints of the fields that have been computed -- GitLab