[FIX]: uninstall module.
authorAtul Patel (OpenERP) <atp@tinyerp.com>
Fri, 9 Mar 2012 12:47:53 +0000 (18:17 +0530)
committerAtul Patel (OpenERP) <atp@tinyerp.com>
Fri, 9 Mar 2012 12:47:53 +0000 (18:17 +0530)
Remove foreign key references.
Remove sql constraint .
Remove workflow activity and transition based on deleted cascade.
Drop ir model fields columns and drop table.

bzr revid: atp@tinyerp.com-20120309124753-c4yzeoij5p2fmhgg

openerp/addons/base/ir/ir_model.py
openerp/addons/base/ir/workflow/workflow.py
openerp/addons/base/module/module.py
openerp/addons/base/module/wizard/base_module_upgrade.py
openerp/addons/base/res/res_users.py
openerp/modules/loading.py
openerp/osv/orm.py

index a85c83e..f838a6a 100644 (file)
@@ -138,15 +138,21 @@ class ir_model(osv.osv):
     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)
+            # this test should be removed, but check if drop view instead of drop table
+            # just check if table or view exists
+            cr.execute("select relkind from pg_class where relname=%s", (model_pool._table,))
+            result = cr.fetchone()
+            if result and result[0] == 'v':
+                cr.execute("DROP view %s" % (model_pool._table,))
+            elif result and result[0] == 'r':
+                cr.execute("DROP TABLE %s" % (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,))
-       # self._drop_table(cr, user, ids, context)
+        self._drop_table(cr, user, ids, context)
         res = super(ir_model, self).unlink(cr, user, ids, context)
         pooler.restart_pool(cr.dbname)
         return res
@@ -273,11 +279,15 @@ class ir_model_fields(osv.osv):
     ]
     
     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)
+        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()[0]
+        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 == '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):
@@ -630,7 +640,6 @@ class ir_model_data(osv.osv):
     def __init__(self, pool, cr):
         osv.osv.__init__(self, pool, cr)
         self.doinit = True
-
         # 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 = {}
@@ -682,7 +691,6 @@ class ir_model_data(osv.osv):
 
     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)
@@ -691,10 +699,8 @@ class ir_model_data(osv.osv):
         model_obj = self.pool.get(model)
         if not context:
             context = {}
-
         # records created during module install should result in res.log entries that are already read!
         context = dict(context, res_log_read=True)
-
         if xml_id and ('.' in xml_id):
             assert len(xml_id.split('.'))==2, _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id)
             module, xml_id = xml_id.split('.')
@@ -807,41 +813,90 @@ class ir_model_data(osv.osv):
     def _pre_process_unlink(self, cr, uid, ids, context=None):
         wkf_todo = []
         to_unlink = []
+        to_drop_table = []
+        ids.sort()
+        ids.reverse()
         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)
+            name = data.name
+            if str(name).startswith('foreign_key_'):
+                name = name[12:]
+                # test if constraint 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)
                 continue
-            to_unlink.append((model,res_id))
+            
+            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),)
+                if cr.fetchall():
+                    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,))
                 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)
+            try:
+               wf_service.trg_write(uid, model, res_id, cr)
+            except:
+                _logger.info('Unable to process workflow %s@%s', res_id, model)
+
+        # drop relation .table
+        for model in to_drop_table:
+            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
+                continue
+            _logger.info('Deleting %s@%s', res_id, model)
+            try:
+                self.pool.get(model).unlink(cr, uid, res_id)
+            except:
+                _logger.info('Unable to delete %s@%s', res_id, model)
+            cr.commit()
+
+        for (model, res_id) in to_unlink:
+            if model not in ('ir.model.fields',):
+                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
+                continue
+            _logger.info('Deleting %s@%s', res_id, model)
+            self.pool.get(model).unlink(cr, uid, res_id)
 
-        #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)
+        for (model, res_id) in to_unlink:
+            if model not in ('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
+                continue
+            _logger.info('Deleting %s@%s', res_id, model)
+            self.pool.get(model).unlink(cr, uid, [res_id])
+        cr.commit()
 
     def _process_end(self, cr, uid, modules):
         """ Clear records removed from updated module data.
@@ -851,8 +906,6 @@ class ir_model_data(osv.osv):
         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)
@@ -871,9 +924,6 @@ class ir_model_data(osv.osv):
                     _logger.info('Deleting %s@%s', res_id, model)
                     self.pool.get(model).unlink(cr, uid, [res_id])
                     
-                  #  cr.commit()          
-
-    
 ir_model_data()
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index edd070a..8ec8432 100644 (file)
@@ -194,7 +194,7 @@ class wkf_workitem(osv.osv):
     _log_access = False
     _rec_name = 'state'
     _columns = {
-        'act_id': fields.many2one('workflow.activity', 'Activity', required=True, ondelete="restrict", select=True),
+        'act_id': fields.many2one('workflow.activity', 'Activity', required=True, ondelete="cascade", select=True),
         'wkf_id': fields.related('act_id','wkf_id', type='many2one', relation='workflow', string='Workflow'),
         'subflow_id': fields.many2one('workflow.instance', 'Subflow', ondelete="cascade", select=True),
         'inst_id': fields.many2one('workflow.instance', 'Instance', required=True, ondelete="cascade", select=True),
index 328afef..d673268 100644 (file)
@@ -343,7 +343,6 @@ class module(osv.osv):
         # Mark them to be installed.
         if to_install_ids:
             self.button_install(cr, uid, to_install_ids, context=context)
-
         return dict(ACTION_DICT, name=_('Install'))
 
     def button_immediate_install(self, cr, uid, ids, context=None):
@@ -377,11 +376,20 @@ class module(osv.osv):
         return True
     
     def module_uninstall(self, cr, uid, ids, context=None):
+
+        # you have to uninstall in the right order, not all modules at the same time
+
         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._pre_process_unlink(cr, uid, data_ids, context)
         model_data.unlink(cr, uid, data_ids, context)
+        
         self.write(cr, uid, ids, {'state': 'uninstalled'})
+        
+        # should we call process_end istead of loading, or both ?
         return True
     
     def button_uninstall(self, cr, uid, ids, context=None):
index 5e7b82d..f19decf 100644 (file)
@@ -97,12 +97,10 @@ 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)
         
         id2 = data_obj._get_id(cr, uid, 'base', 'view_base_module_upgrade_install')
index b196cb8..e7264b1 100644 (file)
@@ -107,21 +107,6 @@ class groups(osv.osv):
                 aid.write({'groups_id': [(4, gid)]})
         return gid
 
-    def unlink(self, cr, uid, ids, context=None):
-        group_users = []
-        for record in self.read(cr, uid, ids, ['users'], context=context):
-            if record['users']:
-                group_users.extend(record['users'])
-        if group_users:
-            user_names = [user.name for user in self.pool.get('res.users').browse(cr, uid, group_users, context=context)]
-            user_names = list(set(user_names))
-            if len(user_names) >= 5:
-                user_names = user_names[:5] + ['...']
-            raise osv.except_osv(_('Warning !'),
-                        _('Group(s) cannot be deleted, because some user(s) still belong to them: %s !') % \
-                            ', '.join(user_names))
-        return super(groups, self).unlink(cr, uid, ids, context=context)
-
     def get_extended_interface_group(self, cr, uid, context=None):
         data_obj = self.pool.get('ir.model.data')
         extended_group_data_id = data_obj._get_id(cr, uid, 'base', 'group_extended')
index 66ed210..1ec39ab 100644 (file)
@@ -374,13 +374,12 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
             tools.config[kind] = {}
 
         cr.commit()
-        if update_module:
+#        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',))
             #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,))
@@ -398,20 +397,20 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
             # (child) menu item, ir_values, or ir_model_data.
             # This code could be a method of ir_ui_menu.
             # TODO: remove menu without actions of children
-            while True:
-                cr.execute('''delete from
-                        ir_ui_menu
-                    where
-                        (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
-                    and
-                        (id not IN (select res_id from ir_values where model='ir.ui.menu'))
-                    and
-                        (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
-                cr.commit()
-                if not cr.rowcount:
-                    break
-                else:
-                    _logger.info('removed %d unused menus', cr.rowcount)
+#            while True:
+#                cr.execute('''delete from
+#                        ir_ui_menu
+#                    where
+#                        (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
+#                    and
+#                        (id not IN (select res_id from ir_values where model='ir.ui.menu'))
+#                    and
+#                        (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
+#                cr.commit()
+#                if not cr.rowcount:
+#                    break
+#                else:
+#                    _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',))
index fae53bd..40a4f41 100644 (file)
@@ -904,6 +904,7 @@ class BaseModel(object):
                                     # If new class defines a constraint with
                                     # same function name, we let it override
                                     # the old one.
+                                    
                                     new[c2] = c
                                     exist = True
                                     break
@@ -2760,7 +2761,6 @@ class BaseModel(object):
         update_custom_fields = context.get('update_custom_fields', False)
         self._field_create(cr, context=context)
         create = not self._table_exist(cr)
-
         if getattr(self, '_auto', True):
 
             if create:
@@ -3029,11 +3029,16 @@ class BaseModel(object):
 
         return todo_end
 
-
     def _auto_end(self, cr, context=None):
         """ Create the foreign keys recorded by _auto_init. """
         for t, k, r, d in self._foreign_keys:
             cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (t, k, r, d))
+            name_id = "foreign_key_"+t+"_"+k+"_fkey"
+            cr.execute('select * from ir_model_data where name=%s and module=%s', (name_id, self._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, self._module, t)
+                )
         cr.commit()
         del self._foreign_keys
 
@@ -3112,6 +3117,7 @@ class BaseModel(object):
 
     def _o2m_raise_on_missing_reference(self, cr, f):
         # TODO this check should be a method on fields.one2many.
+        
         other = self.pool.get(f._obj)
         if other:
             # TODO the condition could use fields_get_keys().
@@ -3119,7 +3125,6 @@ class BaseModel(object):
                 if f._fields_id not in other._inherit_fields.keys():
                     raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id, f._obj,))
 
-
     def _m2m_raise_or_create_relation(self, cr, f):
         m2m_tbl, col1, col2 = f._sql_names(self)
         cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (m2m_tbl,))
@@ -3129,7 +3134,14 @@ class BaseModel(object):
             dest_model = self.pool.get(f._obj)
             ref = dest_model._table
             cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL, "%s" INTEGER NOT NULL, UNIQUE("%s","%s")) WITH OIDS' % (m2m_tbl, col1, col2, col1, col2))
-
+            #create many2many references
+            name_id = 'table_'+m2m_tbl
+            cr.execute('select * from ir_model_data where name=%s and module=%s', (name_id, self._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, self._module, self._name)
+                )
+         #   self.pool.get('ir.model.data')._update(cr, 1, self._name,  self._module, {}, 'table_'+m2m_tbl, store=True, noupdate=False, mode='init', res_id=False, context=None)
             # create foreign key references with ondelete=cascade, unless the targets are SQL views
             cr.execute("SELECT relkind FROM pg_class WHERE relkind IN ('v') AND relname=%s", (ref,))
             if not cr.fetchall():
@@ -3157,7 +3169,6 @@ class BaseModel(object):
 
             cr.execute("SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_constraint where conname=%s", (conname,))
             existing_constraints = cr.dictfetchall()
-
             sql_actions = {
                 'drop': {
                     'execute': False,
@@ -3198,11 +3209,10 @@ class BaseModel(object):
                     _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:
+                    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()