Merge pull request #5321 from odoo-dev/8.0-speedup_loading-rco
[IMP] models: speedup loading of registry (-40%)
This commit is contained in:
commit
d8eb9dd347
|
@ -25,10 +25,9 @@ class mother(models.Model):
|
||||||
# in the child object
|
# in the child object
|
||||||
class daughter(models.Model):
|
class daughter(models.Model):
|
||||||
_name = 'test.inherit.daughter'
|
_name = 'test.inherit.daughter'
|
||||||
_inherits = {'test.inherit.mother': 'template_id'}
|
|
||||||
|
|
||||||
template_id = fields.Many2one('test.inherit.mother', 'Template',
|
template_id = fields.Many2one('test.inherit.mother', 'Template',
|
||||||
required=True, ondelete='cascade')
|
delegate=True, required=True, ondelete='cascade')
|
||||||
field_in_daughter = fields.Char('Field1')
|
field_in_daughter = fields.Char('Field1')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,13 @@ from openerp.tests import common
|
||||||
|
|
||||||
class test_inherits(common.TransactionCase):
|
class test_inherits(common.TransactionCase):
|
||||||
|
|
||||||
def test_access_from_child_to_parent_model(self):
|
def test_00_inherits(self):
|
||||||
|
""" Check that a many2one field with delegate=True adds an entry in _inherits """
|
||||||
|
daughter = self.env['test.inherit.daughter']
|
||||||
|
|
||||||
|
self.assertEqual(daughter._inherits, {'test.inherit.mother': 'template_id'})
|
||||||
|
|
||||||
|
def test_10_access_from_child_to_parent_model(self):
|
||||||
""" check whether added field in model is accessible from children models (_inherits) """
|
""" check whether added field in model is accessible from children models (_inherits) """
|
||||||
# This test checks if the new added column of a parent model
|
# This test checks if the new added column of a parent model
|
||||||
# is accessible from the child model. This test has been written
|
# is accessible from the child model. This test has been written
|
||||||
|
@ -15,7 +21,7 @@ class test_inherits(common.TransactionCase):
|
||||||
self.assertIn('field_in_mother', mother._fields)
|
self.assertIn('field_in_mother', mother._fields)
|
||||||
self.assertIn('field_in_mother', daughter._fields)
|
self.assertIn('field_in_mother', daughter._fields)
|
||||||
|
|
||||||
def test_field_extension(self):
|
def test_20_field_extension(self):
|
||||||
""" check the extension of a field in an inherited model """
|
""" check the extension of a field in an inherited model """
|
||||||
mother = self.env['test.inherit.mother']
|
mother = self.env['test.inherit.mother']
|
||||||
daughter = self.env['test.inherit.daughter']
|
daughter = self.env['test.inherit.daughter']
|
||||||
|
@ -41,7 +47,7 @@ class test_inherits(common.TransactionCase):
|
||||||
self.assertEqual(field.string, "Template")
|
self.assertEqual(field.string, "Template")
|
||||||
self.assertTrue(field.required)
|
self.assertTrue(field.required)
|
||||||
|
|
||||||
def test_depends_extension(self):
|
def test_30_depends_extension(self):
|
||||||
""" check that @depends on overridden compute methods extends dependencies """
|
""" check that @depends on overridden compute methods extends dependencies """
|
||||||
mother = self.env['test.inherit.mother']
|
mother = self.env['test.inherit.mother']
|
||||||
field = mother._fields['surname']
|
field = mother._fields['surname']
|
||||||
|
@ -49,7 +55,7 @@ class test_inherits(common.TransactionCase):
|
||||||
# the field dependencies are added
|
# the field dependencies are added
|
||||||
self.assertItemsEqual(field.depends, ['name', 'field_in_mother'])
|
self.assertItemsEqual(field.depends, ['name', 'field_in_mother'])
|
||||||
|
|
||||||
def test_selection_extension(self):
|
def test_40_selection_extension(self):
|
||||||
""" check that attribute selection_add=... extends selection on fields. """
|
""" check that attribute selection_add=... extends selection on fields. """
|
||||||
mother = self.env['test.inherit.mother']
|
mother = self.env['test.inherit.mother']
|
||||||
|
|
||||||
|
|
|
@ -2,5 +2,4 @@
|
||||||
access_category,test_new_api_category,test_new_api.model_test_new_api_category,,1,1,1,1
|
access_category,test_new_api_category,test_new_api.model_test_new_api_category,,1,1,1,1
|
||||||
access_discussion,test_new_api_discussion,test_new_api.model_test_new_api_discussion,,1,1,1,1
|
access_discussion,test_new_api_discussion,test_new_api.model_test_new_api_discussion,,1,1,1,1
|
||||||
access_message,test_new_api_message,test_new_api.model_test_new_api_message,,1,1,1,1
|
access_message,test_new_api_message,test_new_api.model_test_new_api_message,,1,1,1,1
|
||||||
access_talk,test_new_api_talk,test_new_api.model_test_new_api_talk,,1,1,1,1
|
|
||||||
access_mixed,test_new_api_mixed,test_new_api.model_test_new_api_mixed,,1,1,1,1
|
access_mixed,test_new_api_mixed,test_new_api.model_test_new_api_mixed,,1,1,1,1
|
||||||
|
|
|
|
@ -153,12 +153,6 @@ class Message(models.Model):
|
||||||
self.double_size = self.double_size + size
|
self.double_size = self.double_size + size
|
||||||
|
|
||||||
|
|
||||||
class Talk(models.Model):
|
|
||||||
_name = 'test_new_api.talk'
|
|
||||||
|
|
||||||
parent = fields.Many2one('test_new_api.discussion', delegate=True, required=True)
|
|
||||||
|
|
||||||
|
|
||||||
class MixedModel(models.Model):
|
class MixedModel(models.Model):
|
||||||
_name = 'test_new_api.mixed'
|
_name = 'test_new_api.mixed'
|
||||||
|
|
||||||
|
|
|
@ -375,20 +375,3 @@ class TestMagicFields(common.TransactionCase):
|
||||||
record = self.env['test_new_api.discussion'].create({'name': 'Booba'})
|
record = self.env['test_new_api.discussion'].create({'name': 'Booba'})
|
||||||
self.assertEqual(record.create_uid, self.env.user)
|
self.assertEqual(record.create_uid, self.env.user)
|
||||||
self.assertEqual(record.write_uid, self.env.user)
|
self.assertEqual(record.write_uid, self.env.user)
|
||||||
|
|
||||||
|
|
||||||
class TestInherits(common.TransactionCase):
|
|
||||||
|
|
||||||
def test_inherits(self):
|
|
||||||
""" Check that a many2one field with delegate=True adds an entry in _inherits """
|
|
||||||
Talk = self.env['test_new_api.talk']
|
|
||||||
self.assertEqual(Talk._inherits, {'test_new_api.discussion': 'parent'})
|
|
||||||
self.assertIn('name', Talk._fields)
|
|
||||||
self.assertEqual(Talk._fields['name'].related, ('parent', 'name'))
|
|
||||||
|
|
||||||
talk = Talk.create({'name': 'Foo'})
|
|
||||||
discussion = talk.parent
|
|
||||||
self.assertTrue(discussion)
|
|
||||||
self.assertEqual(talk._name, 'test_new_api.talk')
|
|
||||||
self.assertEqual(discussion._name, 'test_new_api.discussion')
|
|
||||||
self.assertEqual(talk.name, discussion.name)
|
|
||||||
|
|
|
@ -297,6 +297,13 @@ class Field(object):
|
||||||
self._attrs = {key: val for key, val in kwargs.iteritems() if val is not None}
|
self._attrs = {key: val for key, val in kwargs.iteritems() if val is not None}
|
||||||
self._free_attrs = []
|
self._free_attrs = []
|
||||||
|
|
||||||
|
# self._triggers is a set of pairs (field, path) that represents the
|
||||||
|
# computed fields that depend on `self`. When `self` is modified, it
|
||||||
|
# invalidates the cache of each `field`, and registers the records to
|
||||||
|
# recompute based on `path`. See method `modified` below for details.
|
||||||
|
self._triggers = set()
|
||||||
|
self.inverse_fields = []
|
||||||
|
|
||||||
def new(self, **kwargs):
|
def new(self, **kwargs):
|
||||||
""" Return a field of the same type as `self`, with its own parameters. """
|
""" Return a field of the same type as `self`, with its own parameters. """
|
||||||
return type(self)(**kwargs)
|
return type(self)(**kwargs)
|
||||||
|
@ -396,16 +403,6 @@ class Field(object):
|
||||||
# Field setup
|
# Field setup
|
||||||
#
|
#
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
""" Prepare `self` for a new setup. """
|
|
||||||
self.setup_done = False
|
|
||||||
# self._triggers is a set of pairs (field, path) that represents the
|
|
||||||
# computed fields that depend on `self`. When `self` is modified, it
|
|
||||||
# invalidates the cache of each `field`, and registers the records to
|
|
||||||
# recompute based on `path`. See method `modified` below for details.
|
|
||||||
self._triggers = set()
|
|
||||||
self.inverse_fields = []
|
|
||||||
|
|
||||||
def setup(self, env):
|
def setup(self, env):
|
||||||
""" Make sure that `self` is set up, except for recomputation triggers. """
|
""" Make sure that `self` is set up, except for recomputation triggers. """
|
||||||
if not self.setup_done:
|
if not self.setup_done:
|
||||||
|
|
|
@ -636,8 +636,8 @@ class BaseModel(object):
|
||||||
attrs = {
|
attrs = {
|
||||||
'_name': name,
|
'_name': name,
|
||||||
'_register': False,
|
'_register': False,
|
||||||
'_columns': {}, # filled by _setup_fields()
|
'_columns': None, # recomputed in _setup_fields()
|
||||||
'_defaults': {}, # filled by Field._determine_default()
|
'_defaults': None, # recomputed in _setup_base()
|
||||||
'_inherits': dict(cls._inherits),
|
'_inherits': dict(cls._inherits),
|
||||||
'_depends': dict(cls._depends),
|
'_depends': dict(cls._depends),
|
||||||
'_constraints': list(cls._constraints),
|
'_constraints': list(cls._constraints),
|
||||||
|
@ -745,15 +745,40 @@ class BaseModel(object):
|
||||||
for (key, _, msg) in cls._sql_constraints:
|
for (key, _, msg) in cls._sql_constraints:
|
||||||
cls.pool._sql_error[cls._table + '_' + key] = msg
|
cls.pool._sql_error[cls._table + '_' + key] = msg
|
||||||
|
|
||||||
# collect constraint and onchange methods
|
@property
|
||||||
cls._constraint_methods = []
|
def _constraint_methods(self):
|
||||||
cls._onchange_methods = defaultdict(list)
|
""" Return a list of methods implementing Python constraints. """
|
||||||
for attr, func in getmembers(cls, callable):
|
def is_constraint(func):
|
||||||
if hasattr(func, '_constrains'):
|
return callable(func) and hasattr(func, '_constrains')
|
||||||
cls._constraint_methods.append(func)
|
|
||||||
if hasattr(func, '_onchange'):
|
cls = type(self)
|
||||||
for name in func._onchange:
|
methods = []
|
||||||
cls._onchange_methods[name].append(func)
|
for attr, func in getmembers(cls, is_constraint):
|
||||||
|
if not all(name in cls._fields for name in func._constrains):
|
||||||
|
_logger.warning("@constrains%r parameters must be field names", func._constrains)
|
||||||
|
methods.append(func)
|
||||||
|
|
||||||
|
# optimization: memoize result on cls, it will not be recomputed
|
||||||
|
cls._constraint_methods = methods
|
||||||
|
return methods
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _onchange_methods(self):
|
||||||
|
""" Return a dictionary mapping field names to onchange methods. """
|
||||||
|
def is_onchange(func):
|
||||||
|
return callable(func) and hasattr(func, '_onchange')
|
||||||
|
|
||||||
|
cls = type(self)
|
||||||
|
methods = defaultdict(list)
|
||||||
|
for attr, func in getmembers(cls, is_onchange):
|
||||||
|
for name in func._onchange:
|
||||||
|
if name not in cls._fields:
|
||||||
|
_logger.warning("@onchange%r parameters must be field names", func._onchange)
|
||||||
|
methods[name].append(func)
|
||||||
|
|
||||||
|
# optimization: memoize result on cls, it will not be recomputed
|
||||||
|
cls._onchange_methods = methods
|
||||||
|
return methods
|
||||||
|
|
||||||
def __new__(cls):
|
def __new__(cls):
|
||||||
# In the past, this method was registering the model class in the server.
|
# In the past, this method was registering the model class in the server.
|
||||||
|
@ -800,19 +825,6 @@ class BaseModel(object):
|
||||||
"TransientModels must have log_access turned on, " \
|
"TransientModels must have log_access turned on, " \
|
||||||
"in order to implement their access rights policy"
|
"in order to implement their access rights policy"
|
||||||
|
|
||||||
# retrieve new-style fields (from above registry class) and duplicate
|
|
||||||
# them (to avoid clashes with inheritance between different models)
|
|
||||||
cls._fields = {}
|
|
||||||
above = cls.__bases__[0]
|
|
||||||
for attr, field in getmembers(above, Field.__instancecheck__):
|
|
||||||
cls._add_field(attr, field.new())
|
|
||||||
|
|
||||||
# introduce magic fields
|
|
||||||
cls._add_magic_fields()
|
|
||||||
|
|
||||||
# register constraints and onchange methods
|
|
||||||
cls._init_constraints_onchanges()
|
|
||||||
|
|
||||||
# prepare ormcache, which must be shared by all instances of the model
|
# prepare ormcache, which must be shared by all instances of the model
|
||||||
cls._ormcache = {}
|
cls._ormcache = {}
|
||||||
|
|
||||||
|
@ -2941,26 +2953,28 @@ class BaseModel(object):
|
||||||
if cls._setup_done:
|
if cls._setup_done:
|
||||||
return
|
return
|
||||||
|
|
||||||
# first make sure that parent models determine all their fields
|
# 1. determine the proper fields of the model; duplicate them on cls to
|
||||||
|
# avoid clashes with inheritance between different models
|
||||||
|
for name in getattr(cls, '_fields', {}):
|
||||||
|
delattr(cls, name)
|
||||||
|
|
||||||
|
# retrieve fields from parent classes
|
||||||
|
cls._fields = {}
|
||||||
|
cls._defaults = {}
|
||||||
|
for attr, field in getmembers(cls, Field.__instancecheck__):
|
||||||
|
cls._add_field(attr, field.new())
|
||||||
|
|
||||||
|
# add magic and custom fields
|
||||||
|
cls._add_magic_fields()
|
||||||
|
cls._init_manual_fields(self._cr, partial)
|
||||||
|
|
||||||
|
# 2. make sure that parent models determine their own fields, then add
|
||||||
|
# inherited fields to cls
|
||||||
cls._inherits_check()
|
cls._inherits_check()
|
||||||
for parent in cls._inherits:
|
for parent in cls._inherits:
|
||||||
self.env[parent]._setup_base(partial)
|
self.env[parent]._setup_base(partial)
|
||||||
|
|
||||||
# remove inherited fields from cls._fields
|
|
||||||
for name, field in cls._fields.items():
|
|
||||||
if field.inherited:
|
|
||||||
del cls._fields[name]
|
|
||||||
|
|
||||||
# retrieve custom fields
|
|
||||||
cls._init_manual_fields(self._cr, partial)
|
|
||||||
|
|
||||||
# retrieve inherited fields
|
|
||||||
cls._init_inherited_fields()
|
cls._init_inherited_fields()
|
||||||
|
|
||||||
# prepare the setup of fields
|
|
||||||
for field in cls._fields.itervalues():
|
|
||||||
field.reset()
|
|
||||||
|
|
||||||
cls._setup_done = True
|
cls._setup_done = True
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
|
@ -2968,7 +2982,8 @@ class BaseModel(object):
|
||||||
""" Setup the fields, except for recomputation triggers. """
|
""" Setup the fields, except for recomputation triggers. """
|
||||||
cls = type(self)
|
cls = type(self)
|
||||||
|
|
||||||
# set up fields, and update their corresponding columns
|
# set up fields, and determine their corresponding column
|
||||||
|
cls._columns = {}
|
||||||
for name, field in cls._fields.iteritems():
|
for name, field in cls._fields.iteritems():
|
||||||
field.setup(self.env)
|
field.setup(self.env)
|
||||||
if field.store or field.column:
|
if field.store or field.column:
|
||||||
|
@ -3007,14 +3022,8 @@ class BaseModel(object):
|
||||||
# register stuff about low-level function fields
|
# register stuff about low-level function fields
|
||||||
cls._init_function_fields(cls.pool, self._cr)
|
cls._init_function_fields(cls.pool, self._cr)
|
||||||
|
|
||||||
# check constraints
|
# register constraints and onchange methods
|
||||||
for func in cls._constraint_methods:
|
cls._init_constraints_onchanges()
|
||||||
if not all(name in cls._fields for name in func._constrains):
|
|
||||||
_logger.warning("@constrains%r parameters must be field names", func._constrains)
|
|
||||||
for name in cls._onchange_methods:
|
|
||||||
if name not in cls._fields:
|
|
||||||
func = cls._onchange_methods[name]
|
|
||||||
_logger.warning("@onchange%r parameters must be field names", func._onchange)
|
|
||||||
|
|
||||||
# check defaults
|
# check defaults
|
||||||
for name in cls._defaults:
|
for name in cls._defaults:
|
||||||
|
|
Loading…
Reference in New Issue