summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--openerp/addons/base/ir/ir_model.py4
-rw-r--r--openerp/fields.py103
-rw-r--r--openerp/models.py160
-rw-r--r--openerp/modules/loading.py26
-rw-r--r--openerp/modules/registry.py28
5 files changed, 166 insertions, 155 deletions
diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py
index ae1ffce2fc4..579178c36cb 100644
--- a/openerp/addons/base/ir/ir_model.py
+++ b/openerp/addons/base/ir/ir_model.py
@@ -388,9 +388,7 @@ class ir_model_fields(osv.osv):
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)
diff --git a/openerp/fields.py b/openerp/fields.py
index 8510dc8dc2d..b393348821a 100644
--- a/openerp/fields.py
+++ b/openerp/fields.py
@@ -336,8 +336,6 @@ class Field(object):
# 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.
@@ -403,33 +401,40 @@ class Field(object):
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
@@ -445,7 +450,6 @@ class Field(object):
recs = env[self.model_name]
fields = []
for name in self.related:
- recs._setup_fields()
field = recs._fields[name]
field.setup(env)
recs = recs[name]
@@ -524,31 +528,14 @@ class Field(object):
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
@@ -558,7 +545,6 @@ class Field(object):
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])
@@ -571,8 +557,6 @@ class Field(object):
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'])))
@@ -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)
@@ -1253,8 +1237,8 @@ class Selection(Field):
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):
@@ -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)
@@ -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))
@@ -1664,7 +1648,6 @@ class One2many(_RelationalMulti):
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
diff --git a/openerp/models.py b/openerp/models.py
index 78b6aaaaf32..9561d3dde40 100644
--- a/openerp/models.py
+++ b/openerp/models.py
@@ -481,11 +481,7 @@ class BaseModel(object):
# basic setup of field
field.set_class_name(cls, name)
- if field.store or field.column:
- cls._columns[name] = field.to_column()
- else:
- # remove potential column that may be overridden by field
- cls._columns.pop(name, None)
+ # cls._columns will be updated once fields are set up
@classmethod
def _pop_field(cls, name):
@@ -605,13 +601,6 @@ class BaseModel(object):
# inferred metadata; use its ancestor instead
parent_class = type(parent_model).__base__
- # don't inherit custom fields
- columns = dict((key, val)
- for key, val in parent_class._columns.iteritems()
- if not val.manual
- )
- columns.update(cls._columns)
-
inherits = dict(parent_class._inherits)
inherits.update(cls._inherits)
@@ -635,7 +624,6 @@ class BaseModel(object):
attrs = {
'_name': name,
'_register': False,
- '_columns': columns,
'_inherits': inherits,
'_depends': depends,
'_constraints': constraints,
@@ -648,7 +636,7 @@ class BaseModel(object):
attrs = {
'_name': name,
'_register': False,
- '_columns': dict(cls._columns),
+ '_columns': {}, # filled by _setup_fields()
'_defaults': {}, # filled by Field._determine_default()
'_inherits': dict(cls._inherits),
'_depends': dict(cls._depends),
@@ -705,16 +693,11 @@ class BaseModel(object):
pool._store_function[model].sort(key=lambda x: x[4])
@classmethod
- def _init_manual_fields(cls, cr, partial=False):
- # Check whether the query is already done
- if cls.pool.fields_by_model is not None:
- manual_fields = cls.pool.fields_by_model.get(cls._name, [])
- else:
- cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (cls._name, 'manual'))
- manual_fields = cr.dictfetchall()
+ def _init_manual_fields(cls, cr, partial):
+ manual_fields = cls.pool.get_manual_fields(cr, cls._name)
- for field in manual_fields:
- if field['name'] in cls._fields:
+ for name, field in manual_fields.iteritems():
+ if name in cls._fields:
continue
attrs = {
'manual': True,
@@ -735,7 +718,11 @@ class BaseModel(object):
attrs['ondelete'] = field['on_delete']
attrs['domain'] = eval(field['domain']) if field['domain'] else None
elif field['ttype'] == 'one2many':
- if partial and field['relation'] not in cls.pool:
+ if partial and not (
+ field['relation'] in cls.pool and (
+ field['relation_field'] in cls.pool[field['relation']]._fields or
+ field['relation_field'] in cls.pool.get_manual_fields(cr, field['relation'])
+ )):
continue
attrs['comodel_name'] = field['relation']
attrs['inverse_name'] = field['relation_field']
@@ -746,11 +733,11 @@ class BaseModel(object):
attrs['comodel_name'] = field['relation']
_rel1 = field['relation'].replace('.', '_')
_rel2 = field['model'].replace('.', '_')
- attrs['relation'] = 'x_%s_%s_%s_rel' % (_rel1, _rel2, field['name'])
+ attrs['relation'] = 'x_%s_%s_%s_rel' % (_rel1, _rel2, name)
attrs['column1'] = 'id1'
attrs['column2'] = 'id2'
attrs['domain'] = eval(field['domain']) if field['domain'] else None
- cls._add_field(field['name'], Field.by_type[field['ttype']](**attrs))
+ cls._add_field(name, Field.by_type[field['ttype']](**attrs))
@classmethod
def _init_constraints_onchanges(cls):
@@ -823,9 +810,6 @@ class BaseModel(object):
# introduce magic fields
cls._add_magic_fields()
- # register stuff about low-level function fields and custom fields
- cls._init_function_fields(pool, cr)
-
# register constraints and onchange methods
cls._init_constraints_onchanges()
@@ -2874,18 +2858,12 @@ class BaseModel(object):
#
@classmethod
- def _inherits_reload(cls):
- """ Recompute the _inherit_fields mapping, and inherited fields. """
- struct = {}
+ def _init_inherited_fields(cls):
+ """ Determine inherited fields. """
+ # determine candidate inherited fields
fields = {}
for parent_model, parent_field in cls._inherits.iteritems():
parent = cls.pool[parent_model]
- # old-api struct for _inherit_fields
- for name, column in parent._columns.iteritems():
- struct[name] = (parent_model, parent_field, column, parent_model)
- for name, source in parent._inherit_fields.iteritems():
- struct[name] = (parent_model, parent_field, source[2], source[3])
- # new-api fields for _fields
for name, field in parent._fields.iteritems():
fields[name] = field.new(
inherited=True,
@@ -2893,16 +2871,26 @@ class BaseModel(object):
related_sudo=False,
)
- # old-api stuff
- cls._inherit_fields = struct
- cls._all_columns = cls._get_column_infos()
-
# add inherited fields that are not redefined locally
for name, field in fields.iteritems():
if name not in cls._fields:
cls._add_field(name, field)
@classmethod
+ def _inherits_reload(cls):
+ """ Recompute the _inherit_fields and _all_columns mappings. """
+ cls._inherit_fields = struct = {}
+ for parent_model, parent_field in cls._inherits.iteritems():
+ parent = cls.pool[parent_model]
+ for name, column in parent._columns.iteritems():
+ struct[name] = (parent_model, parent_field, column, parent_model)
+ for name, source in parent._inherit_fields.iteritems():
+ struct[name] = (parent_model, parent_field, source[2], source[3])
+
+ # old-api stuff
+ cls._all_columns = cls._get_column_infos()
+
+ @classmethod
def _get_column_infos(cls):
"""Returns a dict mapping all fields names (direct fields and
inherited field via _inherits) to a ``column_info`` struct
@@ -2918,14 +2906,16 @@ class BaseModel(object):
@classmethod
def _inherits_check(cls):
for table, field_name in cls._inherits.items():
- if field_name not in cls._columns:
+ field = cls._fields.get(field_name)
+ if not field:
_logger.info('Missing many2one field definition for _inherits reference "%s" in "%s", using default one.', field_name, cls._name)
- cls._columns[field_name] = fields.many2one(table, string="Automatically created field to link to parent %s" % table,
- required=True, ondelete="cascade")
- elif not cls._columns[field_name].required or cls._columns[field_name].ondelete.lower() not in ("cascade", "restrict"):
+ from .fields import Many2one
+ field = Many2one(table, string="Automatically created field to link to parent %s" % table, required=True, ondelete="cascade")
+ cls._add_field(field_name, field)
+ elif not field.required or field.ondelete.lower() not in ("cascade", "restrict"):
_logger.warning('Field definition for _inherits reference "%s" in "%s" must be marked as "required" with ondelete="cascade" or "restrict", forcing it to required + cascade.', field_name, cls._name)
- cls._columns[field_name].required = True
- cls._columns[field_name].ondelete = "cascade"
+ field.required = True
+ field.ondelete = "cascade"
# reflect fields with delegate=True in dictionary cls._inherits
for field in cls._fields.itervalues():
@@ -2938,44 +2928,48 @@ class BaseModel(object):
cls._inherits[field.comodel_name] = field.name
@api.model
- def _prepare_setup_fields(self):
- """ Prepare the setup of fields once the models have been loaded. """
+ def _prepare_setup(self):
+ """ Prepare the setup of the model. """
type(self)._setup_done = False
- for name, field in self._fields.items():
- if field.inherited:
- del self._fields[name]
- else:
- field.reset()
@api.model
- def _setup_fields(self, partial=False):
- """ Setup the fields (dependency triggers, etc).
-
- :param partial: ``True`` if all models have not been loaded yet.
- """
+ def _setup_base(self, partial):
+ """ Determine the inherited and custom fields of the model. """
cls = type(self)
if cls._setup_done:
return
- cls._setup_done = True
- # first make sure that parent models are all set up
- for parent in self._inherits:
- self.env[parent]._setup_fields()
+ # first make sure that parent models determine all their fields
+ 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=partial)
+ cls._init_manual_fields(self._cr, partial)
# retrieve inherited fields
- cls._inherits_check()
- cls._inherits_reload()
+ cls._init_inherited_fields()
- # set up fields
+ # prepare the setup of fields
for field in cls._fields.itervalues():
- field.setup(self.env)
+ field.reset()
- # update columns (fields may have changed)
+ cls._setup_done = True
+
+ @api.model
+ def _setup_fields(self):
+ """ Setup the fields, except for recomputation triggers. """
+ cls = type(self)
+
+ # set up fields, and update their corresponding columns
for name, field in cls._fields.iteritems():
- if field.column:
+ field.setup(self.env)
+ if field.store or field.column:
cls._columns[name] = field.to_column()
# group fields by compute to determine field.computed_fields
@@ -2987,6 +2981,30 @@ class BaseModel(object):
else:
field.computed_fields = []
+ @api.model
+ def _setup_complete(self):
+ """ Setup recomputation triggers, and complete the model setup. """
+ cls = type(self)
+
+ # set up field triggers
+ for field in cls._fields.itervalues():
+ field.setup_triggers(self.env)
+
+ # add invalidation triggers on model dependencies
+ if cls._depends:
+ triggers = [(field, None) for field in cls._fields.itervalues()]
+ for model_name, field_names in cls._depends.iteritems():
+ model = self.env[model_name]
+ for field_name in field_names:
+ field = model._fields[field_name]
+ field._triggers.update(triggers)
+
+ # determine old-api cls._inherit_fields and cls._all_columns
+ cls._inherits_reload()
+
+ # 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):
diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py
index f80332d8dd5..9f22c2571a5 100644
--- a/openerp/modules/loading.py
+++ b/openerp/modules/loading.py
@@ -126,13 +126,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
migrations = openerp.modules.migration.MigrationManager(cr, graph)
_logger.info('loading %d modules...', len(graph))
- # Query manual fields for all models at once and save them on the registry
- # so the initialization code for each model does not have to do it
- # one model at a time.
- registry.fields_by_model = {}
- cr.execute('SELECT * FROM ir_model_fields WHERE state=%s', ('manual',))
- for field in cr.dictfetchall():
- registry.fields_by_model.setdefault(field['model'], []).append(field)
+ registry.clear_manual_fields()
# register, instantiate and initialize models for each modules
t0 = time.time()
@@ -162,13 +156,6 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
registry.setup_models(cr, partial=True)
init_module_models(cr, package.name, models)
- # Can't put this line out of the loop: ir.module.module will be
- # registered by init_module_models() above.
- modobj = registry['ir.module.module']
-
- if perform_checks:
- modobj.check(cr, SUPERUSER_ID, [module_id])
-
idref = {}
mode = 'update'
@@ -176,6 +163,13 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
mode = 'init'
if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
+ # Can't put this line out of the loop: ir.module.module will be
+ # registered by init_module_models() above.
+ modobj = registry['ir.module.module']
+
+ if perform_checks:
+ modobj.check(cr, SUPERUSER_ID, [module_id])
+
if package.state=='to upgrade':
# upgrading the module information
modobj.write(cr, SUPERUSER_ID, [module_id], modobj.get_values_from_terp(package.data))
@@ -227,9 +221,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
_logger.log(25, "%s modules loaded in %.2fs, %s queries", len(graph), time.time() - t0, openerp.sql_db.sql_counter - t0_sql)
- # The query won't be valid for models created later (i.e. custom model
- # created after the registry has been loaded), so empty its result.
- registry.fields_by_model = None
+ registry.clear_manual_fields()
cr.commit()
diff --git a/openerp/modules/registry.py b/openerp/modules/registry.py
index e1a14d52210..c6e3e2ed1d2 100644
--- a/openerp/modules/registry.py
+++ b/openerp/modules/registry.py
@@ -22,7 +22,7 @@
""" Models registries.
"""
-from collections import Mapping
+from collections import Mapping, defaultdict
import logging
import os
import threading
@@ -51,7 +51,7 @@ class Registry(Mapping):
self._init = True
self._init_parent = {}
self._assertion_report = assertion_report.assertion_report()
- self.fields_by_model = None
+ self._fields_by_model = None
# modules fully loaded (maintained during init phase by `loading` module)
self._init_modules = set()
@@ -114,6 +114,20 @@ class Registry(Mapping):
fields.append(model_fields[fname])
return fields
+ def clear_manual_fields(self):
+ """ Invalidate the cache for manual fields. """
+ self._fields_by_model = None
+
+ def get_manual_fields(self, cr, model_name):
+ """ Return the manual fields (as a dict) for the given model. """
+ if self._fields_by_model is None:
+ # Query manual fields for all models at once
+ self._fields_by_model = dic = defaultdict(dict)
+ cr.execute('SELECT * FROM ir_model_fields WHERE state=%s', ('manual',))
+ for field in cr.dictfetchall():
+ dic[field['model']][field['name']] = field
+ return self._fields_by_model[model_name]
+
def do_parent_store(self, cr):
for o in self._init_parent:
self.get(o)._parent_store_compute(cr)
@@ -166,12 +180,18 @@ class Registry(Mapping):
# prepare the setup on all models
for model in self.models.itervalues():
- model._prepare_setup_fields(cr, SUPERUSER_ID)
+ model._prepare_setup(cr, SUPERUSER_ID)
# do the actual setup from a clean state
self._m2m = {}
for model in self.models.itervalues():
- model._setup_fields(cr, SUPERUSER_ID, partial=partial)
+ model._setup_base(cr, SUPERUSER_ID, partial)
+
+ for model in self.models.itervalues():
+ model._setup_fields(cr, SUPERUSER_ID)
+
+ for model in self.models.itervalues():
+ model._setup_complete(cr, SUPERUSER_ID)
def clear_caches(self):
""" Clear the caches