Merge lp:~openerp-dev/openobject-server/trunk-fix-unlink-irmodelfields-chs into lp:openobject-server

Proposed by Christophe Simonis (OpenERP)
Status: Needs review
Proposed branch: lp:~openerp-dev/openobject-server/trunk-fix-unlink-irmodelfields-chs
Merge into: lp:openobject-server
Diff against target: 389 lines (+83/-58)
7 files modified
openerp/addons/base/base.sql (+1/-0)
openerp/addons/base/ir/ir_model.py (+32/-26)
openerp/addons/base/module/module.py (+2/-9)
openerp/modules/db.py (+10/-3)
openerp/modules/loading.py (+4/-2)
openerp/osv/orm.py (+32/-12)
openerp/service/db.py (+2/-6)
To merge this branch: bzr merge lp:~openerp-dev/openobject-server/trunk-fix-unlink-irmodelfields-chs
Reviewer Review Type Date Requested Status
OpenERP Core Team Pending
Review via email: mp+207622@code.launchpad.net
To post a comment you must log in.
4966. By Christophe Simonis (OpenERP)

[FIX] base: pre-create table_name column on ir_model table

4967. By Christophe Simonis (OpenERP)

merge upstream

Unmerged revisions

4967. By Christophe Simonis (OpenERP)

merge upstream

4966. By Christophe Simonis (OpenERP)

[FIX] base: pre-create table_name column on ir_model table

4965. By Christophe Simonis (OpenERP)

[MERGE] go trunk

4964. By Christophe Simonis (OpenERP)

[IMP] ir.model: new field table_name, allow deletion of table and views that are not in the registry

4963. By Christophe Simonis (OpenERP)

[IMP] create_categories(): avoid a useless query as we already know the xmlids

4962. By Christophe Simonis (OpenERP)

[FIX] ir_module_module._update_category(): do not try to be clever and always call create_categories() to be sure xmlids are tracked

4961. By Christophe Simonis (OpenERP)

[IMP] ir_model_data._process_end(): also log xmlid of deleted records

4960. By Christophe Simonis (OpenERP)

[FIX] ir_model_data._process_end(): correct context passed to unlink()

4959. By Christophe Simonis (OpenERP)

[FIX] do not set the category of modules at initialization. Wait the registry to be available to allow tracking of generated xmlids

4958. By Christophe Simonis (OpenERP)

[FIX] orm: during update of ir.model and ir.model.fields, mark xmlid's to force unlink() of deleted models and columns.
[FIX] ir.model.data: during cleanup of deleted xmlid's, also delete ones with noupdate set to NULL. Force unlink() of ir.model and ir.model.fields records

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openerp/addons/base/base.sql'
2--- openerp/addons/base/base.sql 2014-04-25 16:28:28 +0000
3+++ openerp/addons/base/base.sql 2014-06-05 10:03:57 +0000
4@@ -17,6 +17,7 @@
5 id serial,
6 model varchar NOT NULL,
7 name varchar,
8+ table_name varchar NOT NULL,
9 state varchar,
10 info text,
11 primary key(id)
12
13=== modified file 'openerp/addons/base/ir/ir_model.py'
14--- openerp/addons/base/ir/ir_model.py 2014-05-06 12:16:27 +0000
15+++ openerp/addons/base/ir/ir_model.py 2014-06-05 10:03:57 +0000
16@@ -98,6 +98,7 @@
17 _columns = {
18 'name': fields.char('Model Description', translate=True, required=True),
19 'model': fields.char('Model', required=True, select=1),
20+ 'table_name': fields.char('Table', required=True, readonly=True),
21 'info': fields.text('Information'),
22 'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields', required=True),
23 'state': fields.selection([('manual','Custom Object'),('base','Base Object')],'Type',readonly=True),
24@@ -145,13 +146,12 @@
25
26 def _drop_table(self, cr, uid, ids, context=None):
27 for model in self.browse(cr, uid, ids, context):
28- model_pool = self.pool[model.model]
29- cr.execute('select relkind from pg_class where relname=%s', (model_pool._table,))
30+ cr.execute('select relkind from pg_class where relname=%s', (model.table_name,))
31 result = cr.fetchone()
32 if result and result[0] == 'v':
33- cr.execute('DROP view %s' % (model_pool._table,))
34+ cr.execute('DROP view %s' % (model.table_name,))
35 elif result and result[0] == 'r':
36- cr.execute('DROP TABLE %s' % (model_pool._table,))
37+ cr.execute('DROP TABLE %s' % (model.table_name,))
38 return True
39
40 def unlink(self, cr, user, ids, context=None):
41@@ -185,10 +185,13 @@
42 return super(ir_model,self).write(cr, user, ids, vals, context)
43
44 def create(self, cr, user, vals, context=None):
45- if context is None:
46+ if context is None:
47 context = {}
48 if context and context.get('manual'):
49- vals['state']='manual'
50+ vals['state'] = 'manual'
51+ table = vals.get('model', '').replace('.', '_')
52+ vals['table_name'] = table
53+
54 res = super(ir_model,self).create(cr, user, vals, context)
55 if vals.get('state','base')=='manual':
56 self.instanciate(cr, user, vals['model'], context)
57@@ -304,14 +307,16 @@
58 for field in self.browse(cr, uid, ids, context):
59 if field.name in MAGIC_COLUMNS:
60 continue
61- model = self.pool[field.model]
62- cr.execute('select relkind from pg_class where relname=%s', (model._table,))
63+ table_name = field.model_id.table_name
64+ cr.execute('select relkind from pg_class where relname=%s', (table_name,))
65 result = cr.fetchone()
66- cr.execute("SELECT column_name FROM information_schema.columns WHERE table_name ='%s' and column_name='%s'" %(model._table, field.name))
67+ cr.execute("SELECT column_name FROM information_schema.columns WHERE table_name=%s and column_name=%s", (table_name, field.name))
68 column_name = cr.fetchone()
69 if column_name and (result and result[0] == 'r'):
70- cr.execute('ALTER table "%s" DROP column "%s" cascade' % (model._table, field.name))
71- model._columns.pop(field.name, None)
72+ cr.execute('ALTER table "%s" DROP column "%s" cascade' % (table_name, field.name))
73+ model = self.pool.get(field.model)
74+ if model:
75+ model._columns.pop(field.name, None)
76 return True
77
78 def unlink(self, cr, user, ids, context=None):
79@@ -488,7 +493,7 @@
80 'name': fields.char('Constraint', required=True, select=1,
81 help="PostgreSQL constraint or foreign key name."),
82 'model': fields.many2one('ir.model', string='Model',
83- required=True, select=1),
84+ required=True, select=1, ondelete='cascade'),
85 'module': fields.many2one('ir.module.module', string='Module',
86 required=True, select=1),
87 'type': fields.char('Constraint Type', required=True, size=1, select=1,
88@@ -518,7 +523,7 @@
89 ids.reverse()
90 for data in self.browse(cr, uid, ids, context):
91 model = data.model.model
92- model_obj = self.pool[model]
93+ model_table = data.model.table_name
94 name = openerp.tools.ustr(data.name)
95 typ = data.type
96
97@@ -532,17 +537,17 @@
98 if typ == 'f':
99 # test if FK exists on this table (it could be on a related m2m table, in which case we ignore it)
100 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
101- WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_obj._table))
102+ WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_table))
103 if cr.fetchone():
104- cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
105+ cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_table, name),)
106 _logger.info('Dropped FK CONSTRAINT %s@%s', name, model)
107
108 if typ == 'u':
109 # test if constraint exists
110 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
111- WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_obj._table))
112+ WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_table))
113 if cr.fetchone():
114- cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
115+ cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_table, name),)
116 _logger.info('Dropped CONSTRAINT %s@%s', name, model)
117
118 self.unlink(cr, uid, ids, context)
119@@ -557,7 +562,7 @@
120 'name': fields.char('Relation Name', required=True, select=1,
121 help="PostgreSQL table name implementing a many2many relation."),
122 'model': fields.many2one('ir.model', string='Model',
123- required=True, select=1),
124+ required=True, select=1, ondelete='cascade'),
125 'module': fields.many2one('ir.module.module', string='Module',
126 required=True, select=1),
127 'date_update': fields.datetime('Update Date'),
128@@ -577,7 +582,6 @@
129 ids.sort()
130 ids.reverse()
131 for data in self.browse(cr, uid, ids, context):
132- model = data.model
133 name = openerp.tools.ustr(data.name)
134
135 # double-check we are really going to delete all the owners of this schema element
136@@ -687,7 +691,7 @@
137 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
138
139 if isinstance(model, browse_record):
140- assert model._table_name == 'ir.model', 'Invalid model object'
141+ assert model_table_name == 'ir.model', 'Invalid model object'
142 model_name = model.model
143 else:
144 model_name = model
145@@ -1166,18 +1170,20 @@
146 """
147 if not modules:
148 return True
149+ context = {MODULE_UNINSTALL_FLAG: True}
150 to_unlink = []
151 cr.execute("""SELECT id,name,model,res_id,module FROM ir_model_data
152- WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s ORDER BY id DESC""",
153+ WHERE module IN %s AND res_id IS NOT NULL AND coalesce(noupdate, false)=%s ORDER BY id DESC""",
154 (tuple(modules), False))
155 for (id, name, model, res_id, module) in cr.fetchall():
156- if (module,name) not in self.loads:
157- to_unlink.append((model,res_id))
158+ if (module, name) not in self.loads:
159+ to_unlink.append((model, res_id, module, name))
160 if not config.get('import_partial'):
161- for (model, res_id) in to_unlink:
162+ for (model, res_id, module, name) in to_unlink:
163 if model in self.pool:
164- _logger.info('Deleting %s@%s', res_id, model)
165- self.pool[model].unlink(cr, uid, [res_id])
166+ _logger.info('Deleting %s@%s (%s.%s)', res_id, model, module, name)
167+ if self.pool[model].exists(cr, uid, [res_id], context=context):
168+ self.pool[model].unlink(cr, uid, [res_id], context=context)
169
170 class wizard_model_menu(osv.osv_memory):
171 _name = 'wizard.ir.model.menu.create'
172
173=== modified file 'openerp/addons/base/module/module.py'
174--- openerp/addons/base/module/module.py 2014-04-15 05:32:50 +0000
175+++ openerp/addons/base/module/module.py 2014-06-05 10:03:57 +0000
176@@ -731,16 +731,9 @@
177 cr.execute('DELETE FROM ir_module_module_dependency WHERE module_id = %s and name = %s', (mod_browse.id, dep))
178
179 def _update_category(self, cr, uid, mod_browse, category='Uncategorized'):
180- current_category = mod_browse.category_id
181- current_category_path = []
182- while current_category:
183- current_category_path.insert(0, current_category.name)
184- current_category = current_category.parent_id
185-
186 categs = category.split('/')
187- if categs != current_category_path:
188- cat_id = create_categories(cr, categs)
189- mod_browse.write({'category_id': cat_id})
190+ cat_id = create_categories(cr, categs)
191+ mod_browse.write({'category_id': cat_id})
192
193 def update_translations(self, cr, uid, ids, filter_lang=None, context=None):
194 if not filter_lang:
195
196=== modified file 'openerp/modules/db.py'
197--- openerp/modules/db.py 2012-11-28 18:37:01 +0000
198+++ openerp/modules/db.py 2014-06-05 10:03:57 +0000
199@@ -3,7 +3,7 @@
200 #
201 # OpenERP, Open Source Management Solution
202 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
203-# Copyright (C) 2010-2012 OpenERP s.a. (<http://openerp.com>).
204+# Copyright (C) 2010-2013 OpenERP s.a. (<http://openerp.com>).
205 #
206 # This program is free software: you can redistribute it and/or modify
207 # it under the terms of the GNU Affero General Public License as
208@@ -64,14 +64,16 @@
209
210 if not info:
211 continue
212- categories = info['category'].split('/')
213- category_id = create_categories(cr, categories)
214+
215+ # NOTE: category will be set later when registry will be available
216+ category_id = None
217
218 if info['installable']:
219 state = 'uninstalled'
220 else:
221 state = 'uninstallable'
222
223+ # TODO insert minimal module (only name, state, auto_install)
224 cr.execute('INSERT INTO ir_module_module \
225 (author, website, name, shortdesc, description, \
226 category_id, auto_install, state, web, license, application, icon, sequence, summary) \
227@@ -115,6 +117,7 @@
228 Return the database id of the (last) category.
229
230 """
231+ registry = openerp.modules.registry.RegistryManager.registries[cr.dbname]
232 p_id = None
233 category = []
234 while categories:
235@@ -134,8 +137,12 @@
236 VALUES (%s, %s, %s, %s)', ('base', xml_id, c_id, 'ir.module.category'))
237 else:
238 c_id = c_id[0]
239+
240+ registry.model_data_reference_ids[('base', xml_id)] = ('ir.module.category', c_id)
241+
242 p_id = c_id
243 categories = categories[1:]
244+
245 return p_id
246
247 def has_unaccent(cr):
248
249=== modified file 'openerp/modules/loading.py'
250--- openerp/modules/loading.py 2014-04-18 14:15:50 +0000
251+++ openerp/modules/loading.py 2014-06-05 10:03:57 +0000
252@@ -264,6 +264,7 @@
253 try:
254 if not openerp.modules.db.is_initialized(cr):
255 _logger.info("init db")
256+ update_module = True
257 openerp.modules.db.initialize(cr)
258 tools.config["init"]["all"] = 1
259 tools.config['update']['all'] = 1
260@@ -277,7 +278,7 @@
261 if 'base' in tools.config['update'] or 'all' in tools.config['update']:
262 cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed'))
263
264- # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps)
265+ # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps)
266 graph = openerp.modules.graph.Graph()
267 graph.add_module(cr, 'base', force)
268 if not graph:
269@@ -296,7 +297,8 @@
270 # STEP 2: Mark other modules to be loaded/updated
271 if update_module:
272 modobj = registry['ir.module.module']
273- if ('base' in tools.config['init']) or ('base' in tools.config['update']):
274+ iu = tools.config['init'].keys() + tools.config['update'].keys()
275+ if ('base' in iu) or ('all' in iu):
276 _logger.info('updating modules list')
277 modobj.update_list(cr, SUPERUSER_ID)
278
279
280=== modified file 'openerp/osv/orm.py'
281--- openerp/osv/orm.py 2014-04-16 14:34:31 +0000
282+++ openerp/osv/orm.py 2014-06-05 10:03:57 +0000
283@@ -760,20 +760,32 @@
284 """
285 if context is None:
286 context = {}
287+ module = context.get('module')
288+
289+ # NOTE: this variable is also initialised by __init__ of ir.model.data
290+ # but it may not be already initialized. For the same reason, we
291+ # fill the variable directly instead of calling _update_dummy()
292+ if getattr(self.pool, 'model_data_reference_ids', None) is None:
293+ self.pool.model_data_reference_ids = {}
294+
295 cr.execute("SELECT id FROM ir_model WHERE model=%s", (self._name,))
296 if not cr.rowcount:
297 cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',))
298 model_id = cr.fetchone()[0]
299- cr.execute("INSERT INTO ir_model (id,model, name, info,state) VALUES (%s, %s, %s, %s, %s)", (model_id, self._name, self._description, self.__doc__, 'base'))
300+ cr.execute("INSERT INTO ir_model (id, model, table_name, name, info, state) VALUES (%s, %s, %s, %s, %s, %s)", (model_id, self._name, self._table, self._description, self.__doc__, 'base'))
301 else:
302 model_id = cr.fetchone()[0]
303- if 'module' in context:
304+ cr.execute("UPDATE ir_model SET table_name=%s, name=%s, info=%s, state=%s WHERE id=%s", (self._table, self._description, self.__doc__, 'base', model_id))
305+
306+ if module:
307 name_id = 'model_'+self._name.replace('.', '_')
308- cr.execute('select * from ir_model_data where name=%s and module=%s', (name_id, context['module']))
309+ cr.execute('select * from ir_model_data where name=%s and module=%s', (name_id, module))
310 if not cr.rowcount:
311 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, (now() at time zone 'UTC'), (now() at time zone 'UTC'), %s, %s, %s)", \
312- (name_id, context['module'], 'ir.model', model_id)
313+ (name_id, module, 'ir.model', model_id)
314 )
315+
316+ self.pool.model_data_reference_ids[(module, name_id)] = ('ir.model', model_id)
317
318 cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,))
319 cols = {}
320@@ -817,27 +829,28 @@
321
322 if k not in cols:
323 cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',))
324- id = cr.fetchone()[0]
325- vals['id'] = id
326+ field_id = cr.fetchone()[0]
327+ vals['id'] = field_id
328 cr.execute("""INSERT INTO ir_model_fields (
329 id, model_id, model, name, field_description, ttype,
330 relation,state,select_level,relation_field, translate, serialization_field_id
331 ) VALUES (
332 %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
333 )""", (
334- id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
335+ field_id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
336 vals['relation'], 'base',
337 vals['select_level'], vals['relation_field'], bool(vals['translate']), vals['serialization_field_id']
338 ))
339- if 'module' in context:
340- name1 = 'field_' + self._table + '_' + k
341- cr.execute("select name from ir_model_data where name=%s", (name1,))
342+ if module:
343+ field_xmlid = 'field_' + self._table + '_' + k
344+ cr.execute("select name from ir_model_data where module=%s AND name=%s", (module, field_xmlid,))
345 if cr.fetchone():
346- name1 = name1 + "_" + str(id)
347+ field_xmlid = field_xmlid + "_" + str(field_id)
348 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, (now() at time zone 'UTC'), (now() at time zone 'UTC'), %s, %s, %s)", \
349- (name1, context['module'], 'ir.model.fields', id)
350+ (field_xmlid, module, 'ir.model.fields', field_id)
351 )
352 else:
353+ field_id = cols[k]['id']
354 for key, val in vals.items():
355 if cols[k][key] != vals[key]:
356 cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name']))
357@@ -852,6 +865,13 @@
358 ))
359 break
360
361+ if module:
362+ cr.execute("SELECT name FROM ir_model_data WHERE module=%s AND model=%s AND res_id=%s", (module, 'ir.model.fields', field_id))
363+ for xmlid, in cr.fetchall():
364+ self.pool.model_data_reference_ids[(module, xmlid)] = ('ir.model.fields', field_id)
365+
366+
367+
368 #
369 # Goal: try to apply inheritance at the instanciation level and
370 # put objects in the pool var
371
372=== modified file 'openerp/service/db.py'
373--- openerp/service/db.py 2014-03-21 15:56:59 +0000
374+++ openerp/service/db.py 2014-06-05 10:03:57 +0000
375@@ -29,12 +29,8 @@
376 def _initialize_db(id, db_name, demo, lang, user_password):
377 try:
378 self_actions[id]['progress'] = 0
379- db = openerp.sql_db.db_connect(db_name)
380- with closing(db.cursor()) as cr:
381- # TODO this should be removed as it is done by RegistryManager.new().
382- openerp.modules.db.initialize(cr)
383- openerp.tools.config['lang'] = lang
384- cr.commit()
385+ openerp.tools.config['lang'] = lang
386+ openerp.tools.config['without_demo'] = not demo
387
388 registry = openerp.modules.registry.RegistryManager.new(
389 db_name, demo, self_actions[id], update_module=True)