Merge pull request #5321 from odoo-dev/8.0-speedup_loading-rco

[IMP] models: speedup loading of registry (-40%)
This commit is contained in:
Raphael Collet 2015-02-18 16:48:53 +01:00
commit d8eb9dd347
7 changed files with 75 additions and 88 deletions

View File

@ -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')

View File

@ -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']

View File

@ -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

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_category test_new_api_category test_new_api.model_test_new_api_category 1 1 1 1
3 access_discussion test_new_api_discussion test_new_api.model_test_new_api_discussion 1 1 1 1
4 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
5 access_mixed test_new_api_mixed test_new_api.model_test_new_api_mixed 1 1 1 1

View File

@ -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'

View File

@ -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)

View File

@ -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:

View File

@ -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: