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
class daughter(models.Model):
_name = 'test.inherit.daughter'
_inherits = {'test.inherit.mother': 'template_id'}
template_id = fields.Many2one('test.inherit.mother', 'Template',
required=True, ondelete='cascade')
delegate=True, required=True, ondelete='cascade')
field_in_daughter = fields.Char('Field1')

View File

@ -3,7 +3,13 @@ from openerp.tests import common
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) """
# This test checks if the new added column of a parent model
# 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', daughter._fields)
def test_field_extension(self):
def test_20_field_extension(self):
""" check the extension of a field in an inherited model """
mother = self.env['test.inherit.mother']
daughter = self.env['test.inherit.daughter']
@ -41,7 +47,7 @@ class test_inherits(common.TransactionCase):
self.assertEqual(field.string, "Template")
self.assertTrue(field.required)
def test_depends_extension(self):
def test_30_depends_extension(self):
""" check that @depends on overridden compute methods extends dependencies """
mother = self.env['test.inherit.mother']
field = mother._fields['surname']
@ -49,7 +55,7 @@ class test_inherits(common.TransactionCase):
# the field dependencies are added
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. """
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_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_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

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
class Talk(models.Model):
_name = 'test_new_api.talk'
parent = fields.Many2one('test_new_api.discussion', delegate=True, required=True)
class MixedModel(models.Model):
_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'})
self.assertEqual(record.create_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._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):
""" Return a field of the same type as `self`, with its own parameters. """
return type(self)(**kwargs)
@ -396,16 +403,6 @@ class Field(object):
# 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):
""" Make sure that `self` is set up, except for recomputation triggers. """
if not self.setup_done:

View File

@ -636,8 +636,8 @@ class BaseModel(object):
attrs = {
'_name': name,
'_register': False,
'_columns': {}, # filled by _setup_fields()
'_defaults': {}, # filled by Field._determine_default()
'_columns': None, # recomputed in _setup_fields()
'_defaults': None, # recomputed in _setup_base()
'_inherits': dict(cls._inherits),
'_depends': dict(cls._depends),
'_constraints': list(cls._constraints),
@ -745,15 +745,40 @@ class BaseModel(object):
for (key, _, msg) in cls._sql_constraints:
cls.pool._sql_error[cls._table + '_' + key] = msg
# collect constraint and onchange methods
cls._constraint_methods = []
cls._onchange_methods = defaultdict(list)
for attr, func in getmembers(cls, callable):
if hasattr(func, '_constrains'):
cls._constraint_methods.append(func)
if hasattr(func, '_onchange'):
for name in func._onchange:
cls._onchange_methods[name].append(func)
@property
def _constraint_methods(self):
""" Return a list of methods implementing Python constraints. """
def is_constraint(func):
return callable(func) and hasattr(func, '_constrains')
cls = type(self)
methods = []
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):
# 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, " \
"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
cls._ormcache = {}
@ -2941,26 +2953,28 @@ class BaseModel(object):
if cls._setup_done:
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()
for parent in cls._inherits:
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()
# prepare the setup of fields
for field in cls._fields.itervalues():
field.reset()
cls._setup_done = True
@api.model
@ -2968,7 +2982,8 @@ class BaseModel(object):
""" Setup the fields, except for recomputation triggers. """
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():
field.setup(self.env)
if field.store or field.column:
@ -3007,14 +3022,8 @@ class BaseModel(object):
# register stuff about low-level function fields
cls._init_function_fields(cls.pool, self._cr)
# check constraints
for func in cls._constraint_methods:
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)
# register constraints and onchange methods
cls._init_constraints_onchanges()
# check defaults
for name in cls._defaults: