diff --git a/odoo/addons/base/tests/test_ir_actions.py b/odoo/addons/base/tests/test_ir_actions.py index 29d2f890038..7228c0d8d3f 100644 --- a/odoo/addons/base/tests/test_ir_actions.py +++ b/odoo/addons/base/tests/test_ir_actions.py @@ -510,8 +510,8 @@ class TestCustomFields(common.TransactionCase): }) query_count = self.cr.sql_log_count - query_count - # create the related field, and assert it only takes 1 extra query - with self.assertQueryCount(query_count + 1): + # create the related field, and assert it only takes 3 extra queries + with self.assertQueryCount(query_count + 3): self.env['ir.model.fields'].create({ 'model_id': self.env['ir.model']._get_id('res.partner'), 'name': 'x_oh_boy', diff --git a/odoo/addons/test_new_api/models/test_new_api.py b/odoo/addons/test_new_api/models/test_new_api.py index 7b66dcf2f46..9849eb876b2 100644 --- a/odoo/addons/test_new_api/models/test_new_api.py +++ b/odoo/addons/test_new_api/models/test_new_api.py @@ -228,6 +228,7 @@ class Multi(models.Model): partner = fields.Many2one('res.partner') lines = fields.One2many('test_new_api.multi.line', 'multi') partners = fields.One2many(related='partner.child_ids') + tags = fields.Many2many('test_new_api.multi.tag', domain=[('name', 'ilike', 'a')]) @api.onchange('name') def _onchange_name(self): 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 b461d4ac201..313dcd7d94b 100644 --- a/odoo/addons/test_new_api/tests/test_new_fields.py +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -1824,28 +1824,23 @@ class TestFields(TransactionCaseWithUserDemo): def test_60_many2many_domain(self): """ test the cache consistency of a many2many field with a domain """ - discussion = self.env.ref('test_new_api.discussion_0') - category = self.env['test_new_api.category'].create({'name': "Foo"}) - discussion.categories = category - discussion.flush() - discussion.invalidate_cache() + tag = self.env['test_new_api.multi.tag'].create({'name': 'bar'}) + record = self.env['test_new_api.multi'].create({'tags': tag.ids}) + record.flush() + record.invalidate_cache() - # patch the many2many field to give it a domain (this simply avoids - # adding yet another test model) - field = discussion._fields['categories'] - self.patch(field, 'domain', [('color', '!=', 42)]) - self.registry.setup_models(self.cr) + self.assertEqual(type(record).tags.domain, [('name', 'ilike', 'a')]) - # the category is in the many2many - self.assertIn(category, discussion.categories) + # the tag is in the many2many + self.assertIn(tag, record.tags) - # modify the category; it should not longer be in the many2many - category.color = 42 - self.assertNotIn(category, discussion.categories) + # modify the tag; it should not longer be in the many2many + tag.name = 'foo' + self.assertNotIn(tag, record.tags) - # modify again the category; it should be back in the many2many - category.color = 69 - self.assertIn(category, discussion.categories) + # modify again the tag; it should be back in the many2many + tag.name = 'baz' + self.assertIn(tag, record.tags) def test_70_x2many_write(self): discussion = self.env.ref('test_new_api.discussion_0') @@ -2676,7 +2671,6 @@ class TestMagicFields(common.TransactionCase): # check setup of models in alphanumeric order self.patch(registry, 'models', OrderedDict(sorted(models.items()))) - registry.model_cache.clear() registry.setup_models(self.cr) field = registry['test_new_api.display'].display_name self.assertFalse(field.automatic) @@ -2684,7 +2678,6 @@ class TestMagicFields(common.TransactionCase): # check setup of models in reverse alphanumeric order self.patch(registry, 'models', OrderedDict(sorted(models.items(), reverse=True))) - registry.model_cache.clear() registry.setup_models(self.cr) field = registry['test_new_api.display'].display_name self.assertFalse(field.automatic) @@ -3094,7 +3087,6 @@ class TestSelectionOndeleteAdvanced(common.TransactionCase): for model_name in (self.MODEL_BASE, self.MODEL_REQUIRED): Model = self.registry[model_name] self.addCleanup(setattr, Model, '__bases__', Model.__bases__) - self.addCleanup(self.registry.model_cache.clear) def test_ondelete_unexisting_policy(self): class Foo(models.Model): @@ -3155,7 +3147,6 @@ class TestSelectionOndeleteAdvanced(common.TransactionCase): class TestFieldParametersValidation(common.TransactionCase): def test_invalid_parameter(self): - self.addCleanup(self.registry.model_cache.clear) class Foo(models.Model): _module = None diff --git a/odoo/fields.py b/odoo/fields.py index 06a1d4af3bf..1d9c1292bf5 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -54,17 +54,18 @@ def first(records): def resolve_mro(model, name, predicate): """ Return the list of successively overridden values of attribute ``name`` - in mro order on ``model`` that satisfy ``predicate``. Model classes - (the ones that appear in the registry) are ignored. + in mro order on ``model`` that satisfy ``predicate``. Model registry + classes are ignored. """ result = [] - for cls in model._model_classes: - value = cls.__dict__.get(name, Default) - if value is Default: - continue - if not predicate(value): - break - result.append(value) + for cls in type(model).mro(): + if not is_registry_class(cls): + value = cls.__dict__.get(name, Default) + if value is Default: + continue + if not predicate(value): + break + result.append(value) return result @@ -4035,5 +4036,6 @@ def apply_required(model, field_name): # pylint: disable=wrong-import-position from .exceptions import AccessError, MissingError, UserError from .models import ( - check_pg_name, expand_ids, is_definition_class, BaseModel, IdType, NewId, PREFETCH_MAX, + check_pg_name, expand_ids, is_definition_class, is_registry_class, + BaseModel, IdType, NewId, PREFETCH_MAX, ) diff --git a/odoo/models.py b/odoo/models.py index b5f0e13eb50..70729fb097e 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -2850,9 +2850,6 @@ class BaseModel(metaclass=MetaModel): cls = type(self) cls._setup_done = False - # the classes that define this model's base fields and methods - cls._model_classes = tuple(c for c in cls.mro() if not is_registry_class(c)) - # reset those attributes on the model's class for _setup_fields() below for attr in ('_rec_name', '_active_name'): try: @@ -2869,51 +2866,23 @@ class BaseModel(metaclass=MetaModel): # 1. determine the proper fields of the model: the fields defined on the # class and magic fields, not the inherited or custom ones - cls0 = cls.pool.model_cache.get(cls._model_classes) - if cls0 and cls0._model_classes == cls._model_classes: - # cls0 is either a model class from another registry, or cls itself. - # The point is that it has the same base classes. We retrieve stuff - # from cls0 to optimize the setup of cls. cls0 is guaranteed to be - # properly set up: registries are loaded under a global lock, - # therefore two registries are never set up at the same time. + # retrieve fields from parent classes, and duplicate them on cls to + # avoid clashes with inheritance between different models + for name in cls._fields: + delattr(cls, name) + cls._fields.clear() - # remove fields that are not proper to cls - for name in set(cls._fields).difference(cls0._model_fields): - delattr(cls, name) - del cls._fields[name] + # collect the definitions of each field (base definition + overrides) + definitions = defaultdict(list) + for klass in reversed(cls.mro()): + if is_definition_class(klass): + for field in klass._field_definitions: + definitions[field.name].append(field) + for name, fields_ in definitions.items(): + self._add_field(name, fields_[-1].new(_base_fields=fields_)) - if cls0 is not cls: - # collect proper fields on cls0, and add them on cls - for name in cls0._model_fields: - field = cls0._fields[name] - # regular fields are shared, while related fields are setup from scratch - if not field.related: - self._add_field(name, field) - else: - self._add_field(name, field.new(**field.args)) - cls._model_fields = list(cls._fields) - - else: - # retrieve fields from parent classes, and duplicate them on cls to - # avoid clashes with inheritance between different models - for name in cls._fields: - delattr(cls, name) - cls._fields.clear() - - # collect the definitions of each field (base definition + overrides) - definitions = defaultdict(list) - for klass in reversed(cls.mro()): - if is_definition_class(klass): - for field in klass._field_definitions: - definitions[field.name].append(field) - for name, fields_ in definitions.items(): - self._add_field(name, fields_[-1].new(_base_fields=fields_)) - - self._add_magic_fields() - cls._model_fields = list(cls._fields) - - cls.pool.model_cache[cls._model_classes] = cls + self._add_magic_fields() # 2. add manual fields if self.pool._init_modules: diff --git a/odoo/modules/registry.py b/odoo/modules/registry.py index 756c7b409dc..294596dd2d3 100644 --- a/odoo/modules/registry.py +++ b/odoo/modules/registry.py @@ -9,7 +9,6 @@ from collections.abc import Mapping from contextlib import closing, contextmanager from functools import partial from operator import attrgetter -from weakref import WeakValueDictionary import logging import os import threading @@ -38,9 +37,6 @@ class Registry(Mapping): _lock = threading.RLock() _saved_lock = None - # a cache for model classes, indexed by their base classes - model_cache = WeakValueDictionary() - @lazy_classproperty def registries(cls): """ A mapping from database names to registries. """