From 2f06adde9c46be73d2fbf13a9cfd6b333117e395 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Tue, 13 Jan 2015 17:36:50 +0100 Subject: [PATCH 1/7] [IMP] models: speedup registry loading (35% less time) The field setup on models is improved: only fields are determined when building the model's class; the final _columns is computed from the fields once they are set up. --- openerp/models.py | 74 ++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/openerp/models.py b/openerp/models.py index 78b6aaaaf32..b71b4383412 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), @@ -2874,18 +2862,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,15 +2875,25 @@ 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 @@ -2918,14 +2910,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(): @@ -2967,17 +2961,17 @@ class BaseModel(object): # retrieve inherited fields cls._inherits_check() - cls._inherits_reload() + cls._init_inherited_fields() - # set up fields - for field in cls._fields.itervalues(): - field.setup(self.env) - - # update columns (fields may have changed) + # 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() + # determine old-api cls._inherit_fields and cls._all_columns + cls._inherits_reload() + # group fields by compute to determine field.computed_fields fields_by_compute = defaultdict(list) for field in cls._fields.itervalues(): From 5fee95ca6366954839e6db0cec5cf1b9b06c023d Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Wed, 14 Jan 2015 17:17:47 +0100 Subject: [PATCH 2/7] [FIX] models, fields: reorganize model setup to retrieve all inherited fields The model setup sometimes misses entries in _inherit_fields and _all_columns. This is because those dictionaries are computed from parent models which are not guaranteed to be completely set up: sometimes a parent field is only partially set up, and columns are missing (they are generated from fields after their setup). To avoid this bug, the setup has been split in three phases: (1) determine all inherited and custom fields on models; (2) setup fields, except for recomputation triggers, and generate columns; (3) add recomputation triggers and complete the setup of the model. Making these three phases explicit brings good invariants: - when setting up a field, all models know all their fields; - when adding recomputation triggers, you know that fields have been set up. --- openerp/fields.py | 101 +++++++++++++++--------------------- openerp/models.py | 55 ++++++++++++++------ openerp/modules/registry.py | 10 +++- 3 files changed, 89 insertions(+), 77 deletions(-) diff --git a/openerp/fields.py b/openerp/fields.py index 8510dc8dc2d..10c378bfcaa 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -403,33 +403,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) + # + # Setup of non-related fields + # + + 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._setup_regular(env) + self.depends = make_depends(getattr(self.compute, '_depends', ())) - # put invalidation/recomputation triggers on field dependencies - model = env[self.model_name] - for path in self.depends: - self._setup_dependency([], model, path.split('.')) - - # 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 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 +452,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 +530,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 +547,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 +559,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 +1036,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 +1239,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 +1327,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 +1364,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 +1650,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 b71b4383412..639ba996a90 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -693,7 +693,7 @@ class BaseModel(object): pool._store_function[model].sort(key=lambda x: x[4]) @classmethod - def _init_manual_fields(cls, cr, partial=False): + def _init_manual_fields(cls, cr, partial): # 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, []) @@ -2932,8 +2932,8 @@ 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 and its fields. """ type(self)._setup_done = False for name, field in self._fields.items(): if field.inherited: @@ -2942,36 +2942,36 @@ class BaseModel(object): 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) # 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._init_inherited_fields() + 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(): field.setup(self.env) if field.store or field.column: cls._columns[name] = field.to_column() - # determine old-api cls._inherit_fields and cls._all_columns - cls._inherits_reload() - # group fields by compute to determine field.computed_fields fields_by_compute = defaultdict(list) for field in cls._fields.itervalues(): @@ -2981,6 +2981,27 @@ 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() + # 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/registry.py b/openerp/modules/registry.py index e1a14d52210..7a377d3eafd 100644 --- a/openerp/modules/registry.py +++ b/openerp/modules/registry.py @@ -166,12 +166,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 From 6c29af3fa544e18e0b8f08ce70424751a76974b1 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 15 Jan 2015 11:22:29 +0100 Subject: [PATCH 3/7] [FIX] models: init function fields once columns are known and not before --- openerp/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openerp/models.py b/openerp/models.py index 639ba996a90..65180419c9b 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -811,9 +811,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() @@ -3002,6 +2999,9 @@ class BaseModel(object): # 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): From 5d7bba4eb7706aebff2090d20e630ed656c27b18 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 15 Jan 2015 11:43:23 +0100 Subject: [PATCH 4/7] [FIX] loading: move code that should not be executed on simply installed modules --- openerp/modules/loading.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py index f80332d8dd5..1694fdbccf7 100644 --- a/openerp/modules/loading.py +++ b/openerp/modules/loading.py @@ -162,13 +162,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 +169,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)) From cf26f7ed802b82a488de1dab0dbec27b8e87009b Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 15 Jan 2015 15:25:19 +0100 Subject: [PATCH 5/7] [IMP] models, registry: let the registry retrieve manual fields from database --- openerp/addons/base/ir/ir_model.py | 4 +--- openerp/models.py | 15 +++++---------- openerp/modules/loading.py | 12 ++---------- openerp/modules/registry.py | 18 ++++++++++++++++-- 4 files changed, 24 insertions(+), 25 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/models.py b/openerp/models.py index 65180419c9b..1c808ad74ba 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -694,15 +694,10 @@ class BaseModel(object): @classmethod def _init_manual_fields(cls, cr, partial): - # 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() + 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, @@ -734,11 +729,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): diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py index 1694fdbccf7..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() @@ -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 7a377d3eafd..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) From 54c655cb71df19a2e8fb8aacbdf43007a1cdb3c2 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Tue, 13 Jan 2015 16:25:30 +0100 Subject: [PATCH 6/7] [FIX] models: do not introduce a one2many manual field if its inverse is not on its comodel --- openerp/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openerp/models.py b/openerp/models.py index 1c808ad74ba..2e36a7527e9 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -718,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'] From 431f8de815bd87a20da867c88a13baf7104005e7 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Tue, 20 Jan 2015 12:16:34 +0100 Subject: [PATCH 7/7] [IMP] models: prepare the setup of fields at one place only --- openerp/fields.py | 2 -- openerp/models.py | 16 ++++++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/openerp/fields.py b/openerp/fields.py index 10c378bfcaa..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. diff --git a/openerp/models.py b/openerp/models.py index 2e36a7527e9..9561d3dde40 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -2929,13 +2929,8 @@ class BaseModel(object): @api.model def _prepare_setup(self): - """ Prepare the setup of the model and its fields. """ + """ 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_base(self, partial): @@ -2949,12 +2944,21 @@ class BaseModel(object): 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