From 8f38a7806a73a3e6df210a7b740d1dd0d8a59717 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Tue, 16 Dec 2014 17:20:20 +0100 Subject: [PATCH] [IMP] enable loading custom models/fields and views from module data files Loading views for custom models from module data files was not possible because custom models and fields were introduced into the registry after all modules were loaded. As a consequence, the view architecture did not pass the checks. This patch takes a different approach: custom models and fields are loaded early on in the registry, so that views can be validated. The trick is to take special care of relational custom fields: we skip them if their comodel does not appear in the registry. This allows to install and upgrade modules that create/modify custom models, fields and views for them. --- openerp/addons/base/ir/ir_model.py | 5 +++-- openerp/models.py | 18 +++++++++++++----- openerp/modules/loading.py | 4 ---- openerp/modules/registry.py | 9 +++++++-- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py index 96819f918dc..ae1ffce2fc4 100644 --- a/openerp/addons/base/ir/ir_model.py +++ b/openerp/addons/base/ir/ir_model.py @@ -446,6 +446,7 @@ class ir_model_fields(osv.osv): for item in self.browse(cr, user, ids, context=context): obj = self.pool.get(item.model) + field = getattr(obj, '_fields', {}).get(item.name) if item.state != 'manual': raise except_orm(_('Error!'), @@ -481,12 +482,12 @@ class ir_model_fields(osv.osv): # We don't check the 'state', because it might come from the context # (thus be set for multiple fields) and will be ignored anyway. - if obj is not None: + if obj is not None and field is not None: # find out which properties (per model) we need to update for field_name, prop_name, func in model_props: if field_name in vals: prop_value = func(vals[field_name]) - if getattr(obj._fields[item.name], prop_name) != prop_value: + if getattr(field, prop_name) != prop_value: patches[obj][final_name][prop_name] = prop_value # These shall never be written (modified) diff --git a/openerp/models.py b/openerp/models.py index e995e87b4e9..028cffbc392 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -705,7 +705,7 @@ class BaseModel(object): pool._store_function[model].sort(key=lambda x: x[4]) @classmethod - def _init_manual_fields(cls, cr): + 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, []) @@ -729,14 +729,20 @@ class BaseModel(object): elif field['ttype'] in ('selection', 'reference'): attrs['selection'] = eval(field['selection']) elif field['ttype'] == 'many2one': + if partial and field['relation'] not in cls.pool: + continue attrs['comodel_name'] = field['relation'] 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: + continue attrs['comodel_name'] = field['relation'] attrs['inverse_name'] = field['relation_field'] attrs['domain'] = eval(field['domain']) if field['domain'] else None elif field['ttype'] == 'many2many': + if partial and field['relation'] not in cls.pool: + continue attrs['comodel_name'] = field['relation'] _rel1 = field['relation'].replace('.', '_') _rel2 = field['model'].replace('.', '_') @@ -2939,8 +2945,11 @@ class BaseModel(object): field.reset() @api.model - def _setup_fields(self): - """ Setup the fields (dependency triggers, etc). """ + def _setup_fields(self, partial=False): + """ Setup the fields (dependency triggers, etc). + + :param partial: ``True`` if all models have not been loaded yet. + """ cls = type(self) if cls._setup_done: return @@ -2951,8 +2960,7 @@ class BaseModel(object): self.env[parent]._setup_fields() # retrieve custom fields - if not self._context.get('_setup_fields_partial'): - cls._init_manual_fields(self._cr) + cls._init_manual_fields(self._cr, partial=partial) # retrieve inherited fields cls._inherits_check() diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py index 006a077a92d..c6123f82560 100644 --- a/openerp/modules/loading.py +++ b/openerp/modules/loading.py @@ -360,10 +360,6 @@ def load_modules(db, force_demo=False, status=None, update_module=False): ['to install'], force, status, report, loaded_modules, update_module) - # load custom models - cr.execute('select model from ir_model where state=%s', ('manual',)) - for model in cr.dictfetchall(): - registry['ir.model'].instanciate(cr, SUPERUSER_ID, model['model'], {}) registry.setup_models(cr) # STEP 4: Finish and cleanup installations diff --git a/openerp/modules/registry.py b/openerp/modules/registry.py index afdb48fe304..e1a14d52210 100644 --- a/openerp/modules/registry.py +++ b/openerp/modules/registry.py @@ -158,15 +158,20 @@ class Registry(Mapping): :param partial: ``True`` if all models have not been loaded yet. """ + # load custom models + ir_model = self['ir.model'] + cr.execute('select model from ir_model where state=%s', ('manual',)) + for (model_name,) in cr.fetchall(): + ir_model.instanciate(cr, SUPERUSER_ID, model_name, {}) + # prepare the setup on all models for model in self.models.itervalues(): model._prepare_setup_fields(cr, SUPERUSER_ID) # do the actual setup from a clean state self._m2m = {} - context = {'_setup_fields_partial': partial} for model in self.models.itervalues(): - model._setup_fields(cr, SUPERUSER_ID, context=context) + model._setup_fields(cr, SUPERUSER_ID, partial=partial) def clear_caches(self): """ Clear the caches