diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py index 236c6af7119..db3c7fcdbae 100644 --- a/openerp/addons/base/ir/ir_actions.py +++ b/openerp/addons/base/ir/ir_actions.py @@ -510,7 +510,7 @@ class actions_server(osv.osv): 'code':fields.text('Python Code', help="Python code to be executed if condition is met.\n" "It is a Python block that can use the same values as for the condition field"), 'sequence': fields.integer('Sequence', help="Important when you deal with multiple actions, the execution order will be decided based on this, low number is higher priority."), - 'model_id': fields.many2one('ir.model', 'Object', required=True, help="Select the object on which the action will work (read, write, create)."), + 'model_id': fields.many2one('ir.model', 'Object', required=True, help="Select the object on which the action will work (read, write, create).", ondelete='cascade'), 'action_id': fields.many2one('ir.actions.actions', 'Client Action', help="Select the Action Window, Report, Wizard to be executed."), 'trigger_name': fields.selection(_select_signals, string='Trigger Signal', size=128, help="The workflow signal to trigger"), 'wkf_model_id': fields.many2one('ir.model', 'Target Object', help="The object that should receive the workflow signal (must have an associated workflow)"), diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py index e8ce3adb592..012bf2028d1 100644 --- a/openerp/addons/base/ir/ir_model.py +++ b/openerp/addons/base/ir/ir_model.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- + + # -*- coding: utf-8 -*- ############################################################################## # # OpenERP, Open Source Management Solution @@ -134,11 +135,18 @@ class ir_model(osv.osv): super(ir_model, self).search(cr, uid, domain, limit=limit, context=context), context=context) + def _drop_table(self, cr, uid, ids, context=None): + for model in self.browse(cr, uid, ids, context): + model_pool = self.pool.get(model.model) + if getattr(model_pool, '_auto', True) and not model.osv_memory: + cr.execute("DROP table %s cascade" % model_pool._table) + 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,)) +# 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,)) + # self._drop_table(cr, user, ids, context) res = super(ir_model, self).unlink(cr, user, ids, context) pooler.restart_pool(cr.dbname) return res @@ -263,17 +271,19 @@ 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): + for field in self.browse(cr, uid, ids, context): + model = self.pool.get(field.model) + if not field.model.osv_memory and getattr(model, '_auto', True): + cr.execute("ALTER table %s DROP column %s" % (model._table, field.name)) + model._columns.pop(field.name, None) + return True def unlink(self, cr, user, ids, context=None): - for field in self.browse(cr, user, ids, context): - if field.state <> 'manual': - raise except_orm(_('Error'), _("You cannot remove the field '%s' !") %(field.name,)) - # - # MAY BE ADD A ALTER TABLE DROP ? - # - #Removing _columns entry for that table - self.pool.get(field.model)._columns.pop(field.name,None) - return super(ir_model_fields, self).unlink(cr, user, ids, context) + self._drop_column(cr, user, ids, context) + res = super(ir_model_fields, self).unlink(cr, user, ids, context) + return res def create(self, cr, user, vals, context=None): if 'model_id' in vals: @@ -624,6 +634,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): @@ -667,9 +678,11 @@ 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. """ + self._pre_process_unlink(cr, uid, ids, context) self._get_id.clear_cache(self) self.get_object_reference.clear_cache(self) return super(ir_model_data,self).unlink(cr, uid, ids, context=context) @@ -688,7 +701,6 @@ class ir_model_data(osv.osv): if (not xml_id) and (not self.doinit): return False action_id = False - if xml_id: cr.execute('''SELECT imd.id, imd.res_id, md.id, imd.model FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id) @@ -791,53 +803,78 @@ 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): + wkf_todo = [] + to_unlink = [] + for data in self.browse(cr, uid, ids, context): + model = data.model + res_id = data.res_id + model_obj = self.pool.get(model) + if str(data.name).startswith('constraint_'): + cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table,data.name[11:]),) + _logger.info('Drop CONSTRAINT %s@%s', data.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,)) + wkf_todo.extend(cr.fetchall()) + cr.execute("update wkf_transition set condition='True', group_id=NULL, signal=NULL,act_to=act_from,act_from=%s where act_to=%s", (res_id,res_id)) + cr.execute("delete from wkf_transition where act_to=%s", (res_id,)) + + for model,res_id in wkf_todo: + wf_service = netsvc.LocalService("workflow") + wf_service.trg_write(uid, model, res_id, cr) + + #cr.commit() + if not config.get('import_partial'): + for (model, res_id) in to_unlink: + if self.pool.get(model): + _logger.info('Deleting %s@%s', res_id, model) + res_ids = self.pool.get(model).search(cr, uid, [('id', '=', res_id)]) + if res_ids: + self.pool.get(model).unlink(cr, uid, [res_id]) + cr.commit() +# except Exception: +# cr.rollback() +# _logger.warning( +# 'Could not delete obsolete record with id: %d of model %s\n' +## 'There should be some relation that points to this resource\n' +# 'You should manually fix this and restart with --update=module', +# res_id, model) def _process_end(self, cr, uid, modules): """ Clear records removed from updated module data. - This method is called at the end of the module loading process. It is meant to removed records that are no longer present in the updated data. Such records are recognised as the one with an xml id and a module in ir_model_data and noupdate set to false, but not present in self.loads. - """ + + if not modules: return True modules = list(modules) + data_ids = self.search(cr, uid, [('module','in',modules)]) module_in = ",".join(["%s"] * len(modules)) - cr.execute('select id,name,model,res_id,module from ir_model_data where module IN (' + module_in + ') and noupdate=%s', modules + [False]) - wkf_todo = [] + process_query = 'select id,name,model,res_id,module from ir_model_data where module IN (' + module_in + ')' + process_query+= ' and noupdate=%s' to_unlink = [] + cr.execute( process_query, modules + [False]) for (id, name, model, res_id,module) in cr.fetchall(): if (module,name) not in self.loads: 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,)) - wkf_todo.extend(cr.fetchall()) - cr.execute("update wkf_transition set condition='True', group_id=NULL, signal=NULL,act_to=act_from,act_from=%s where act_to=%s", (res_id,res_id)) - cr.execute("delete from wkf_transition where act_to=%s", (res_id,)) - - for model,id in wkf_todo: - wf_service = netsvc.LocalService("workflow") - wf_service.trg_write(uid, model, id, cr) - - cr.commit() + self.pool.get(model).unlink(cr, uid, [res_id]) if not config.get('import_partial'): for (model, res_id) in to_unlink: if self.pool.get(model): _logger.info('Deleting %s@%s', res_id, model) - try: - self.pool.get(model).unlink(cr, uid, [res_id]) - cr.commit() - except Exception: - cr.rollback() - _logger.warning( - 'Could not delete obsolete record with id: %d of model %s\n' - 'There should be some relation that points to this resource\n' - 'You should manually fix this and restart with --update=module', - res_id, model) - return True + self.pool.get(model).unlink(cr, uid, [res_id]) + + # cr.commit() + + ir_model_data() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/addons/base/ir/ir_rule.py b/openerp/addons/base/ir/ir_rule.py index 0626b2b9c1e..227f0eb32b9 100644 --- a/openerp/addons/base/ir/ir_rule.py +++ b/openerp/addons/base/ir/ir_rule.py @@ -75,7 +75,7 @@ class ir_rule(osv.osv): _columns = { 'name': fields.char('Name', size=128, select=1), - 'model_id': fields.many2one('ir.model', 'Object',select=1, required=True), + 'model_id': fields.many2one('ir.model', 'Object',select=1, required=True, ondelete='cascade'), 'global': fields.function(_get_value, string='Global', type='boolean', store=True, help="If no group is specified the rule is global and applied to everyone"), 'groups': fields.many2many('res.groups', 'rule_group_rel', 'rule_group_id', 'group_id', 'Groups'), 'domain_force': fields.text('Domain'), diff --git a/openerp/addons/base/module/module.py b/openerp/addons/base/module/module.py index beb35ee38b2..328afef76aa 100644 --- a/openerp/addons/base/module/module.py +++ b/openerp/addons/base/module/module.py @@ -375,10 +375,18 @@ class module(osv.osv): def button_install_cancel(self, cr, uid, ids, context=None): self.write(cr, uid, ids, {'state': 'uninstalled', 'demo':False}) return True - + + def module_uninstall(self, cr, uid, ids, context=None): + model_data = self.pool.get('ir.model.data') + remove_modules = map(lambda x: x.name, self.browse(cr, uid, ids, context)) + data_ids = model_data.search(cr, uid, [('module', 'in', remove_modules)]) + model_data.unlink(cr, uid, data_ids, context) + self.write(cr, uid, ids, {'state': 'uninstalled'}) + return True + def button_uninstall(self, cr, uid, ids, context=None): for module in self.browse(cr, uid, ids): - cr.execute('''select m.state,m.name + cr.execute('''select m.id from ir_module_module_dependency d join @@ -387,8 +395,11 @@ class module(osv.osv): d.name=%s and m.state not in ('uninstalled','uninstallable','to remove')''', (module.name,)) res = cr.fetchall() - if res: - raise orm.except_orm(_('Error'), _('Some installed modules depend on the module you plan to Uninstall :\n %s') % '\n'.join(map(lambda x: '\t%s: %s' % (x[0], x[1]), res))) + for i in range(0,len(res)): + ids.append(res[i][0]) +# if res: +# self.write(cr, uid, ids, {'state': 'to remove'}) +## raise orm.except_orm(_('Error'), _('Some installed modules depend on the module you plan to Uninstall :\n %s') % '\n'.join(map(lambda x: '\t%s: %s' % (x[0], x[1]), res))) self.write(cr, uid, ids, {'state': 'to remove'}) return dict(ACTION_DICT, name=_('Uninstall')) diff --git a/openerp/addons/base/module/wizard/base_module_upgrade.py b/openerp/addons/base/module/wizard/base_module_upgrade.py index 3b8671b0ebb..5e7b82d241f 100644 --- a/openerp/addons/base/module/wizard/base_module_upgrade.py +++ b/openerp/addons/base/module/wizard/base_module_upgrade.py @@ -83,7 +83,9 @@ class base_module_upgrade(osv.osv_memory): def upgrade_module(self, cr, uid, ids, context=None): mod_obj = self.pool.get('ir.module.module') - ids = mod_obj.search(cr, uid, [('state', 'in', ['to upgrade', 'to remove', 'to install'])]) + data_obj = self.pool.get('ir.model.data') + # process to install and upgrade modules + ids = mod_obj.search(cr, uid, [('state', 'in', ['to upgrade', 'to install'])]) unmet_packages = [] mod_dep_obj = self.pool.get('ir.module.module.dependency') for mod in mod_obj.browse(cr, uid, ids): @@ -95,9 +97,14 @@ class base_module_upgrade(osv.osv_memory): raise osv.except_osv(_('Unmet dependency !'), _('Following modules are not installed or unknown: %s') % ('\n\n' + '\n'.join(unmet_packages))) mod_obj.download(cr, uid, ids, context=context) cr.commit() + + # process to remove modules + remove_module_ids = mod_obj.search(cr, uid, [('state', 'in', ['to remove'])]) + mod_obj.module_uninstall(cr, uid, remove_module_ids, context) + + _db, pool = pooler.restart_pool(cr.dbname, update_module=True) - - data_obj = pool.get('ir.model.data') + id2 = data_obj._get_id(cr, uid, 'base', 'view_base_module_upgrade_install') if id2: id2 = data_obj.browse(cr, uid, id2, context=context).res_id diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index f148813ae05..b196cb862d1 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -53,7 +53,7 @@ class groups(osv.osv): _columns = { 'name': fields.char('Name', size=64, required=True, translate=True), - 'users': fields.many2many('res.users', 'res_groups_users_rel', 'gid', 'uid', 'Users'), + 'users': fields.many2many('res.users', 'res_groups_users_rel', 'gid', 'uid', 'Users', ondelete='CASCADE'), 'model_access': fields.one2many('ir.model.access', 'group_id', 'Access Controls'), 'rule_groups': fields.many2many('ir.rule', 'rule_group_rel', 'group_id', 'rule_group_id', 'Rules', domain=[('global', '=', False)]), diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py index b3f831f9709..66ed210ccc7 100644 --- a/openerp/modules/loading.py +++ b/openerp/modules/loading.py @@ -377,19 +377,22 @@ def load_modules(db, force_demo=False, status=None, update_module=False): if update_module: # Remove records referenced from ir_model_data for modules to be # removed (and removed the references from ir_model_data). - cr.execute("select id,name from ir_module_module where state=%s", ('to remove',)) - for mod_id, mod_name in cr.fetchall(): - cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,)) - for rmod, rid in cr.fetchall(): - uid = 1 - rmod_module= pool.get(rmod) - if rmod_module: - # TODO group by module so that we can delete multiple ids in a call - rmod_module.unlink(cr, uid, [rid]) - else: - _logger.error('Could not locate %s to remove res=%d' % (rmod,rid)) - cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,)) - cr.commit() + #cr.execute("select id,name from ir_module_module where state=%s", ('to remove',)) + #remove_modules = map(lambda x: x['name'], cr.dictfetchall()) + # Cleanup orphan records + #print "pooler", pool.get('mrp.bom') + #pool.get('ir.model.data')._process_end(cr, 1, remove_modules, noupdate=None) +# for mod_id, mod_name in cr.fetchall(): +# cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,)) +# for rmod, rid in cr.fetchall(): +# uid = 1 +# rmod_module= pool.get(rmod) +# if rmod_module: +# rmod_module.unlink(cr, uid, [rid]) +# else: +# _logger.error('Could not locate %s to remove res=%d' % (rmod,rid)) +# cr.execute('delete from ir_model_data where module=%s', (mod_name,)) +# cr.commit() # Remove menu items that are not referenced by any of other # (child) menu item, ir_values, or ir_model_data. @@ -411,8 +414,8 @@ def load_modules(db, force_demo=False, status=None, update_module=False): _logger.info('removed %d unused menus', cr.rowcount) # Pretend that modules to be removed are actually uninstalled. - cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',)) - cr.commit() + #cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',)) + #cr.commit() _logger.info('Modules loaded.') finally: diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 8901cf5ed87..fae53bd9abc 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -3018,7 +3018,7 @@ class BaseModel(object): cr.commit() # start a new transaction - self._add_sql_constraints(cr) + self._add_sql_constraints(cr, context["module"]) if create: self._execute_sql(cr) @@ -3145,7 +3145,7 @@ class BaseModel(object): _schema.debug("Create table '%s': m2m relation between '%s' and '%s'", m2m_tbl, self._table, ref) - def _add_sql_constraints(self, cr): + def _add_sql_constraints(self, cr, module): """ Modify this model's database table constraints so they match the one in @@ -3196,6 +3196,13 @@ class BaseModel(object): cr.execute(sql_action['query']) cr.commit() _schema.debug(sql_action['msg_ok']) + name_id = 'constraint_'+ conname + cr.execute('select * from ir_model_data where name=%s and module=%s', (name_id, module)) + if not cr.rowcount: + cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module, model) VALUES (%s, now(), now(), %s, %s)", \ + (name_id, module, self._name) + ) +# except: _schema.warning(sql_action['msg_err']) cr.rollback() @@ -3710,7 +3717,7 @@ class BaseModel(object): wf_service = netsvc.LocalService("workflow") for oid in ids: wf_service.trg_delete(uid, self._name, oid, cr) - + self.check_access_rule(cr, uid, ids, 'unlink', context=context) pool_model_data = self.pool.get('ir.model.data')