Skip to content

Commit

Permalink
Merge pull request odoo#4676 from odoo-dev/8.0-speedup_loading-rco
Browse files Browse the repository at this point in the history
[IMP] models: speedup registry loading (25% less time)

The field setup on models is improved: only new-api fields are determined when building the model's class; the final _columns dict is derived from the fields once they are set up. This avoids setting up fields and columns at the same time, and finally overriding columns with the result of field setup.

This branch also fixes the model setup in general: with the former code, a model could be introspected while it was only partially set up, and not all its fields were known.

It incidentally fixes the "loss" of parameter partial that is necessary when loading custom fields. And during a partial setup, one2many custom fields are not introduced if their counterparts (model and many2one field) are not ready.
  • Loading branch information
rco-odoo committed Jan 21, 2015
2 parents 37e85a1 + 431f8de commit 592f1cd
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 155 deletions.
4 changes: 1 addition & 3 deletions openerp/addons/base/ir/ir_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,9 +388,7 @@ def create(self, cr, user, vals, context=None):
if vals['model'].startswith('x_') and vals['name'] == 'x_name':
model._rec_name = 'x_name'

if self.pool.fields_by_model is not None:
cr.execute('SELECT * FROM ir_model_fields WHERE id=%s', (res,))
self.pool.fields_by_model.setdefault(vals['model'], []).append(cr.dictfetchone())
self.pool.clear_manual_fields()

# re-initialize model in registry
model.__init__(self.pool, cr)
Expand Down
103 changes: 43 additions & 60 deletions openerp/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,8 +336,6 @@ def set_class_name(self, cls, name):
# determine self.default and cls._defaults in a consistent way
self._determine_default(cls, name)

self.reset()

def _determine_default(self, cls, name):
""" Retrieve the default value for `self` in the hierarchy of `cls`, and
determine `self.default` and `cls._defaults` accordingly.
Expand Down Expand Up @@ -403,33 +401,40 @@ def reset(self):
self.inverse_fields = []

def setup(self, env):
""" Complete the setup of `self` (dependencies, recomputation triggers,
and other properties). This method is idempotent: it has no effect
if `self` has already been set up.
"""
""" Make sure that `self` is set up, except for recomputation triggers. """
if not self.setup_done:
self._setup(env)
if self.related:
self._setup_related(env)
else:
self._setup_regular(env)
self.setup_done = True

def _setup(self, env):
""" Do the actual setup of `self`. """
if self.related:
self._setup_related(env)
else:
self._setup_regular(env)
#
# Setup of non-related fields
#

# put invalidation/recomputation triggers on field dependencies
model = env[self.model_name]
for path in self.depends:
self._setup_dependency([], model, path.split('.'))
def _setup_regular(self, env):
""" Setup the attributes of a non-related field. """
recs = env[self.model_name]

def make_depends(deps):
return tuple(deps(recs) if callable(deps) else deps)

# put invalidation triggers on model dependencies
for dep_model_name, field_names in model._depends.iteritems():
dep_model = env[dep_model_name]
dep_model._setup_fields()
for field_name in field_names:
field = dep_model._fields[field_name]
field._triggers.add((self, None))
# convert compute into a callable and determine depends
if isinstance(self.compute, basestring):
# if the compute method has been overridden, concatenate all their _depends
self.depends = ()
for method in resolve_all_mro(type(recs), self.compute, reverse=True):
self.depends += make_depends(getattr(method, '_depends', ()))
self.compute = getattr(type(recs), self.compute)
else:
self.depends = make_depends(getattr(self.compute, '_depends', ()))

# convert inverse and search into callables
if isinstance(self.inverse, basestring):
self.inverse = getattr(type(recs), self.inverse)
if isinstance(self.search, basestring):
self.search = getattr(type(recs), self.search)

#
# Setup of related fields
Expand All @@ -445,7 +450,6 @@ def _setup_related(self, env):
recs = env[self.model_name]
fields = []
for name in self.related:
recs._setup_fields()
field = recs._fields[name]
field.setup(env)
recs = recs[name]
Expand Down Expand Up @@ -524,31 +528,14 @@ def base_field(self):
return self.related_field if self.inherited else self

#
# Setup of non-related fields
# Setup of field triggers
#

def _setup_regular(self, env):
""" Setup the attributes of a non-related field. """
recs = env[self.model_name]

def make_depends(deps):
return tuple(deps(recs) if callable(deps) else deps)

# convert compute into a callable and determine depends
if isinstance(self.compute, basestring):
# if the compute method has been overridden, concatenate all their _depends
self.depends = ()
for method in resolve_all_mro(type(recs), self.compute, reverse=True):
self.depends += make_depends(getattr(method, '_depends', ()))
self.compute = getattr(type(recs), self.compute)
else:
self.depends = make_depends(getattr(self.compute, '_depends', ()))

# convert inverse and search into callables
if isinstance(self.inverse, basestring):
self.inverse = getattr(type(recs), self.inverse)
if isinstance(self.search, basestring):
self.search = getattr(type(recs), self.search)
def setup_triggers(self, env):
""" Add the necessary triggers to invalidate/recompute `self`. """
model = env[self.model_name]
for path in self.depends:
self._setup_dependency([], model, path.split('.'))

def _setup_dependency(self, path0, model, path1):
""" Make `self` depend on `model`; `path0 + path1` is a dependency of
Expand All @@ -558,7 +545,6 @@ def _setup_dependency(self, path0, model, path1):
env = model.env
head, tail = path1[0], path1[1:]

model._setup_fields()
if head == '*':
# special case: add triggers on all fields of model (except self)
fields = set(model._fields.itervalues()) - set([self])
Expand All @@ -571,8 +557,6 @@ def _setup_dependency(self, path0, model, path1):
self.recursive = True
continue

field.setup(env)

#_logger.debug("Add trigger on %s to recompute %s", field, self)
field._triggers.add((self, '.'.join(path0 or ['id'])))

Expand Down Expand Up @@ -1050,8 +1034,8 @@ class Char(_String):
type = 'char'
size = None

def _setup(self, env):
super(Char, self)._setup(env)
def _setup_regular(self, env):
super(Char, self)._setup_regular(env)
assert isinstance(self.size, (NoneType, int)), \
"Char field %s with non-integer size %r" % (self, self.size)

Expand Down Expand Up @@ -1253,8 +1237,8 @@ def __init__(self, selection=None, string=None, **kwargs):
selection = api.expected(api.model, selection)
super(Selection, self).__init__(selection=selection, string=string, **kwargs)

def _setup(self, env):
super(Selection, self)._setup(env)
def _setup_regular(self, env):
super(Selection, self)._setup_regular(env)
assert self.selection is not None, "Field %s without selection" % self

def _setup_related(self, env):
Expand Down Expand Up @@ -1341,8 +1325,8 @@ class Reference(Selection):
def __init__(self, selection=None, string=None, **kwargs):
super(Reference, self).__init__(selection=selection, string=string, **kwargs)

def _setup(self, env):
super(Reference, self)._setup(env)
def _setup_regular(self, env):
super(Reference, self)._setup_regular(env)
assert isinstance(self.size, (NoneType, int)), \
"Reference field %s with non-integer size %r" % (self, self.size)

Expand Down Expand Up @@ -1378,8 +1362,8 @@ class _Relational(Field):
domain = None # domain for searching values
context = None # context for searching values

def _setup(self, env):
super(_Relational, self)._setup(env)
def _setup_regular(self, env):
super(_Relational, self)._setup_regular(env)
if self.comodel_name not in env.registry:
_logger.warning("Field %s with unknown comodel_name %r"
% (self, self.comodel_name))
Expand Down Expand Up @@ -1664,7 +1648,6 @@ def _setup_regular(self, env):
if self.inverse_name:
# link self to its inverse field and vice-versa
comodel = env[self.comodel_name]
comodel._setup_fields()
invf = comodel._fields[self.inverse_name]
# In some rare cases, a `One2many` field can link to `Int` field
# (res_model/res_id pattern). Only inverse the field if this is
Expand Down
Loading

0 comments on commit 592f1cd

Please sign in to comment.