[IMP] ir.model: safeguards to prevent dropping vital data

bzr revid: odo@openerp.com-20120326231829-6y8nsog1pdtbmaqt
This commit is contained in:
Olivier Dony 2012-03-27 01:18:29 +02:00
parent 63f3c4e988
commit 8bd0cf42f3
1 changed files with 64 additions and 44 deletions

View File

@ -2,8 +2,8 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# OpenERP, Open Source Business Applications
# Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -24,20 +24,20 @@ import re
import time
import types
from osv import fields,osv
import netsvc
from osv.orm import except_orm, browse_record
import tools
from tools.safe_eval import safe_eval as eval
from tools import config
from tools.translate import _
import pooler
from openerp.osv import fields,osv
from openerp import netsvc, pooler, tools
from openerp.osv.orm import except_orm, browse_record
from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools import config
from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
MODULE_UNINSTALL_FLAG = '_ir_module_uninstall'
def _get_fields_type(self, cr, uid, context=None):
# Avoid too many nested `if`s below, as RedHat's Python 2.6
# break on it. See bug 939653.
# break on it. See bug 939653.
return sorted([(k,k) for k,v in fields.__dict__.iteritems()
if type(v) == types.TypeType and \
issubclass(v, fields._column) and \
@ -151,9 +151,14 @@ class ir_model(osv.osv):
return True
def unlink(self, cr, user, ids, context=None):
# for model in self.browse(cr, user, ids, context):
# if model.state != 'manual':
# raise except_orm(_('Error'), _("You can not remove the model '%s' !") %(model.name,))
# Prevent manual deletion of module tables
if context is None: context = {}
if isinstance(ids, (int, long)):
ids = [ids]
if not context.get(MODULE_UNINSTALL_FLAG) and \
any(model.state != 'manual' for model in self.browse(cr, user, ids, context)):
raise except_orm(_('Error'), _("Model '%s' contains module data and cannot be removed!") % (model.name,))
self._drop_table(cr, user, ids, context)
res = super(ir_model, self).unlink(cr, user, ids, context)
pooler.restart_pool(cr.dbname)
@ -279,20 +284,28 @@ class ir_model_fields(osv.osv):
_sql_constraints = [
('size_gt_zero', 'CHECK (size>0)',_size_gt_zero_msg ),
]
def _drop_column(self, cr, uid, ids, context=None):
field = self.browse(cr, uid, ids, context)
model = self.pool.get(field.model)
cr.execute('select relkind from pg_class where relname=%s', (model._table,))
result = cr.fetchone()
cr.execute("SELECT column_name FROM information_schema.columns WHERE table_name ='%s' and column_name='%s'" %(model._table, field.name))
column_name = cr.fetchone()
if column_name and (result and result[0] == 'r'):
cr.execute('ALTER table "%s" DROP column "%s" cascade' % (model._table, field.name))
model._columns.pop(field.name, None)
for field in self.browse(cr, uid, ids, context):
model = self.pool.get(field.model)
cr.execute('select relkind from pg_class where relname=%s', (model._table,))
result = cr.fetchone()
cr.execute("SELECT column_name FROM information_schema.columns WHERE table_name ='%s' and column_name='%s'" %(model._table, field.name))
column_name = cr.fetchone()
if column_name and (result and result[0] == 'r'):
cr.execute('ALTER table "%s" DROP column "%s" cascade' % (model._table, field.name))
model._columns.pop(field.name, None)
return True
def unlink(self, cr, user, ids, context=None):
# Prevent manual deletion of module columns
if context is None: context = {}
if isinstance(ids, (int, long)):
ids = [ids]
if not context.get(MODULE_UNINSTALL_FLAG) and \
any(field.state != 'manual' for field in self.browse(cr, user, ids, context)):
raise except_orm(_('Error'), _("This column contains module data and cannot be removed!"))
self._drop_column(cr, user, ids, context)
res = super(ir_model_fields, self).unlink(cr, user, ids, context)
return res
@ -645,7 +658,7 @@ class ir_model_data(osv.osv):
# also stored in pool to avoid being discarded along with this osv instance
if getattr(pool, 'model_data_reference_ids', None) is None:
self.pool.model_data_reference_ids = {}
self.loads = self.pool.model_data_reference_ids
def _auto_init(self, cr, context=None):
@ -689,7 +702,7 @@ class ir_model_data(osv.osv):
except:
id = False
return id
def unlink(self, cr, uid, ids, context=None):
""" Regular unlink method, but make sure to clear the caches. """
@ -811,8 +824,14 @@ class ir_model_data(osv.osv):
elif xml_id:
cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
return True
def _pre_process_unlink(self, cr, uid, ids, context=None):
if uid != 1 and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"):
raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
context = dict(context or {})
context[MODULE_UNINSTALL_FLAG] = True # enable model/field deletion
wkf_todo = []
to_unlink = []
to_drop_table = []
@ -823,22 +842,23 @@ class ir_model_data(osv.osv):
res_id = data.res_id
model_obj = self.pool.get(model)
name = data.name
# FIXME: replace custom keys with constants
if str(name).startswith('foreign_key_'):
name = name[12:]
# test if constraint exists
# test if FK exists
cr.execute('select conname from pg_constraint where contype=%s and conname=%s',('f', name),)
if cr.fetchall():
cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model,name),)
_logger.info('Drop CONSTRAINT %s@%s', name, model)
_logger.info('Drop FK CONSTRAINT %s@%s', name, model)
continue
if str(name).startswith('table_'):
cr.execute("SELECT table_name FROM information_schema.tables WHERE table_name='%s'"%(name[6:]))
column_name = cr.fetchone()
if column_name:
to_drop_table.append(name[6:])
continue
if str(name).startswith('constraint_'):
# test if constraint exists
cr.execute('select conname from pg_constraint where contype=%s and conname=%s',('u', name),)
@ -846,7 +866,7 @@ class ir_model_data(osv.osv):
cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table,name[11:]),)
_logger.info('Drop CONSTRAINT %s@%s', name[11:], model)
continue
to_unlink.append((model, res_id))
if model=='workflow.activity':
cr.execute('select res_type,res_id from wkf_instance where id IN (select inst_id from wkf_workitem where act_id=%s)', (res_id,))
@ -862,21 +882,21 @@ class ir_model_data(osv.osv):
# drop relation .table
for model in to_drop_table:
cr.execute('DROP TABLE %s cascade'% (model),)
_logger.info('Dropping table %s', model)
cr.execute('DROP TABLE %s CASCADE'% (model),)
_logger.info('Dropping table %s', model)
for (model, res_id) in to_unlink:
if model in ('ir.model','ir.model.fields', 'ir.model.data'):
continue
model_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
if len(model_ids) > 1:
# if others module have defined this record, we do not delete it
# if other modules have defined this record, we do not delete it
continue
_logger.info('Deleting %s@%s', res_id, model)
try:
self.pool.get(model).unlink(cr, uid, res_id)
self.pool.get(model).unlink(cr, uid, res_id, context=context)
except:
_logger.info('Unable to delete %s@%s', res_id, model)
_logger.info('Unable to delete %s@%s', res_id, model, exc_info=True)
cr.commit()
for (model, res_id) in to_unlink:
@ -884,20 +904,20 @@ class ir_model_data(osv.osv):
continue
model_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
if len(model_ids) > 1:
# if others module have defined this record, we do not delete it
# if other modules have defined this record, we do not delete it
continue
_logger.info('Deleting %s@%s', res_id, model)
self.pool.get(model).unlink(cr, uid, res_id)
self.pool.get(model).unlink(cr, uid, res_id, context=context)
for (model, res_id) in to_unlink:
if model not in ('ir.model',):
if model != 'ir.model':
continue
model_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
if len(model_ids) > 1:
# if others module have defined this record, we do not delete it
# if other modules have defined this record, we do not delete it
continue
_logger.info('Deleting %s@%s', res_id, model)
self.pool.get(model).unlink(cr, uid, [res_id])
self.pool.get(model).unlink(cr, uid, [res_id], context=context)
cr.commit()
def _process_end(self, cr, uid, modules):
@ -924,6 +944,6 @@ class ir_model_data(osv.osv):
if self.pool.get(model):
_logger.info('Deleting %s@%s', res_id, model)
self.pool.get(model).unlink(cr, uid, [res_id])
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: