From 9f77d2e2f4b580e2fb07bc044c875dfd30008f44 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Mon, 17 Dec 2012 22:46:45 +0100 Subject: [PATCH] [FIX] orm,registry: properly check m2o FKs during model update + fix some models `auto_init`ed multiple times The case where no constraint existed at all was not working, and after fixing it, the ORM started to re-create the same constraints multiple times for some modules. This was for models that are split within a module (such as res.users in base, which is made of several small classes): all the partial models were going through _auto_init instead of only the final one - doing useless extra work. Unfortunately establishing the FK happens before the XML data files are loaded, so a number of FK and NOT NULL constraints failed to apply due to missing tables/records. base.sql had to be extended a bit to cover these cases in a minimalist fashion bzr revid: odo@openerp.com-20121217214645-av9n003yzterhujw --- openerp/addons/base/base.sql | 41 ++++++++++++++++++++++------ openerp/addons/base/res/res_users.py | 2 +- openerp/modules/registry.py | 13 +++++---- openerp/osv/orm.py | 24 +++++++++------- 4 files changed, 54 insertions(+), 26 deletions(-) diff --git a/openerp/addons/base/base.sql b/openerp/addons/base/base.sql index 3fa847ce23c..b1ddb2eec70 100644 --- a/openerp/addons/base/base.sql +++ b/openerp/addons/base/base.sql @@ -149,7 +149,6 @@ CREATE TABLE res_users ( active boolean default True, login varchar(64) NOT NULL UNIQUE, password varchar(64) default null, - lang varchar(64) default '', -- No FK references below, will be added later by ORM -- (when the destination rows exist) company_id int, @@ -316,13 +315,29 @@ CREATE TABLE ir_module_module_dependency ( primary key(id) ); -CREATE TABLE res_company ( +CREATE TABLE res_partner ( id serial NOT NULL, - name character varying(64) not null, - parent_id integer references res_company on delete set null, + name character varying(128), + lang varchar(64), + company_id int, primary key(id) ); + +CREATE TABLE res_currency ( + id serial PRIMARY KEY, + name VARCHAR(32) NOT NULL +); + +CREATE TABLE res_company ( + id serial PRIMARY KEY, + name character varying(128) not null, + parent_id integer references res_company on delete set null, + partner_id integer not null references res_partner, + currency_id integer not null references res_currency + +); + CREATE TABLE res_lang ( id serial PRIMARY KEY, name VARCHAR(64) NOT NULL UNIQUE, @@ -375,16 +390,24 @@ CREATE TABLE ir_model_relation ( module integer NOT NULL references ir_module_module on delete restrict, model integer NOT NULL references ir_model on delete restrict, name character varying(128) NOT NULL -); +); --------------------------------- -- Users --------------------------------- +insert into res_users (id,login,password,active,company_id,partner_id) VALUES (1,'admin','admin',true,1,1); +insert into ir_model_data (name,module,model,noupdate,res_id) VALUES ('user_root','base','res.users',true,1); -insert into res_users (id,login,password,active,company_id,partner_id,lang) values (1,'admin','admin',True,1,1,'en_US'); -insert into ir_model_data (name,module,model,noupdate,res_id) values ('user_root','base','res.users',True,1); +insert into res_partner (id, name, lang, company_id) VALUES (1, 'Your Company', 'en_US', 1); +insert into ir_model_data (name,module,model,noupdate,res_id) VALUES ('main_partner','base','res.partner',true,1); --- Compatibility purpose, to remove V6.0 -insert into ir_model_data (name,module,model,noupdate,res_id) values ('user_admin','base','res.users',True,1); +insert into res_currency (id, name) VALUES (1, 'EUR'); +insert into ir_model_data (name,module,model,noupdate,res_id) VALUES ('EUR','base','res.currency',true,1); +insert into res_company (id, name, partner_id, currency_id) VALUES (1, 'Your Company', 1, 1); +insert into ir_model_data (name,module,model,noupdate,res_id) VALUES ('main_company','base','res.company',true,1); + +select setval('res_company_id_seq', 2); select setval('res_users_id_seq', 2); +select setval('res_partner_id_seq', 2); +select setval('res_currency_id_seq', 2); \ No newline at end of file diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index 41288c3c3ee..520f0f14538 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -142,7 +142,7 @@ class res_users(osv.osv): 'id': fields.integer('ID'), 'login_date': fields.date('Latest connection', select=1), 'partner_id': fields.many2one('res.partner', required=True, - string='Related Partner', ondelete='cascade', + string='Related Partner', ondelete='restrict', help='Partner-related data of the user'), 'login': fields.char('Login', size=64, required=True, help="Used to log into the system"), diff --git a/openerp/modules/registry.py b/openerp/modules/registry.py index afa8351bce9..23dbc280803 100644 --- a/openerp/modules/registry.py +++ b/openerp/modules/registry.py @@ -110,15 +110,16 @@ class Registry(object): and registers them in the registry. """ - - res = [] - + models_to_load = [] # need to preserve loading order # Instantiate registered classes (via the MetaModel automatic discovery # or via explicit constructor call), and add them to the pool. for cls in openerp.osv.orm.MetaModel.module_to_models.get(module.name, []): - res.append(cls.create_instance(self, cr)) - - return res + # models register themselves in self.models + model = cls.create_instance(self, cr) + if model._name not in models_to_load: + # avoid double-loading models whose declaration is split + models_to_load.append(model._name) + return [self.models[m] for m in models_to_load] def schedule_cron_jobs(self): """ Make the cron thread care about this registry/database jobs. diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index bfe579512b6..92eb8dad776 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -2890,15 +2890,15 @@ class BaseModel(object): # usually because they could block deletion due to the FKs. # So unless stated otherwise we default them to ondelete=cascade. ondelete = ondelete or 'cascade' - self._foreign_keys.append((self._table, source_field, dest_model._table, ondelete or 'set null')) - _schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s", - self._table, source_field, dest_model._table, ondelete) + fk_def = (self._table, source_field, dest_model._table, ondelete or 'set null') + self._foreign_keys.add(fk_def) + _schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s", *fk_def) # unchecked version: for custom cases, such as m2m relationships def _m2o_add_foreign_key_unchecked(self, source_table, source_field, dest_model, ondelete): - self._foreign_keys.append((source_table, source_field, dest_model._table, ondelete or 'set null')) - _schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s", - source_table, source_field, dest_model._table, ondelete) + fk_def = (source_table, source_field, dest_model._table, ondelete or 'set null') + self._foreign_keys.add(fk_def) + _schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s", *fk_def) def _drop_constraint(self, cr, source_table, constraint_name): cr.execute("ALTER TABLE %s DROP CONSTRAINT %s" % (source_table,constraint_name)) @@ -2929,18 +2929,22 @@ class BaseModel(object): cons, = constraints if cons['ondelete_rule'] != POSTGRES_CONFDELTYPES.get((ondelete or 'set null').upper(), 'a')\ or cons['foreign_table'] != dest_model._table: + # Wrong FK: drop it and recreate _schema.debug("Table '%s': dropping obsolete FK constraint: '%s'", source_table, cons['constraint_name']) self._drop_constraint(cr, source_table, cons['constraint_name']) - self._m2o_add_foreign_key_checked(source_field, dest_model, ondelete) - # else it's all good, nothing to do! + else: + # it's all good, nothing to do! + return else: # Multiple FKs found for the same field, drop them all, and re-create for cons in constraints: _schema.debug("Table '%s': dropping duplicate FK constraints: '%s'", source_table, cons['constraint_name']) self._drop_constraint(cr, source_table, cons['constraint_name']) - self._m2o_add_foreign_key_checked(source_field, dest_model, ondelete) + + # (re-)create the FK + self._m2o_add_foreign_key_checked(source_field, dest_model, ondelete) @@ -2962,7 +2966,7 @@ class BaseModel(object): _auto_end). """ - self._foreign_keys = [] + self._foreign_keys = set() raise_on_invalid_object_name(self._name) if context is None: context = {}