[MERGE] Forward-port of latest 7.0 bugfixes, up to rev. 5294 revid:odo@openerp.com...
[odoo/odoo.git] / openerp / addons / base / ir / ir_model.py
index d716227..aca8ac4 100644 (file)
@@ -3,7 +3,7 @@
 ##############################################################################
 #
 #    OpenERP, Open Source Business Applications
-#    Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
+#    Copyright (C) 2004-2014 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
@@ -27,9 +27,9 @@ import types
 import openerp
 import openerp.modules.registry
 from openerp import SUPERUSER_ID
-from openerp import netsvc, pooler, tools
+from openerp import tools
 from openerp.osv import fields,osv
-from openerp.osv.orm import Model
+from openerp.osv.orm import Model, browse_null
 from openerp.tools.safe_eval import safe_eval as eval
 from openerp.tools import config
 from openerp.tools.translate import _
@@ -51,7 +51,7 @@ def _get_fields_type(self, cr, uid, context=None):
 
 def _in_modules(self, cr, uid, ids, field_name, arg, context=None):
     #pseudo-method used by fields.function in ir.model/ir.model.fields
-    module_pool = self.pool.get("ir.module.module")
+    module_pool = self.pool["ir.module.module"]
     installed_module_ids = module_pool.search(cr, uid, [('state','=','installed')])
     installed_module_names = module_pool.read(cr, uid, installed_module_ids, ['name'], context=context)
     installed_modules = set(x['name'] for x in installed_module_names)
@@ -71,8 +71,8 @@ class ir_model(osv.osv):
         models = self.browse(cr, uid, ids, context=context)
         res = dict.fromkeys(ids)
         for model in models:
-            if self.pool.get(model.model):
-                res[model.id] = self.pool.get(model.model).is_transient()
+            if model.model in self.pool:
+                res[model.id] = self.pool[model.model].is_transient()
             else:
                 _logger.error('Missing model %s' % (model.model, ))
         return res
@@ -92,12 +92,12 @@ class ir_model(osv.osv):
         models = self.browse(cr, uid, ids)
         res = {}
         for model in models:
-            res[model.id] = self.pool.get("ir.ui.view").search(cr, uid, [('model', '=', model.model)])
+            res[model.id] = self.pool["ir.ui.view"].search(cr, uid, [('model', '=', model.model)])
         return res
 
     _columns = {
-        'name': fields.char('Model Description', size=64, translate=True, required=True),
-        'model': fields.char('Model', size=64, required=True, select=1),
+        'name': fields.char('Model Description', translate=True, required=True),
+        'model': fields.char('Model', required=True, select=1),
         'info': fields.text('Information'),
         'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields', required=True),
         'state': fields.selection([('manual','Custom Object'),('base','Base Object')],'Type',readonly=True),
@@ -105,7 +105,7 @@ class ir_model(osv.osv):
         'osv_memory': fields.function(_is_osv_memory, string='Transient Model', type='boolean',
             fnct_search=_search_osv_memory,
             help="This field specifies whether the model is transient or not (i.e. if records are automatically deleted from the database or not)"),
-        'modules': fields.function(_in_modules, type='char', size=128, string='In Modules', help='List of modules in which the object is defined or inherited'),
+        'modules': fields.function(_in_modules, type='char', string='In Modules', help='List of modules in which the object is defined or inherited'),
         'view_ids': fields.function(_view_ids, type='one2many', obj='ir.ui.view', string='Views'),
     }
 
@@ -145,7 +145,7 @@ 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)
+            model_pool = self.pool[model.model]
             cr.execute('select relkind from pg_class where relname=%s', (model_pool._table,))
             result = cr.fetchone()
             if result and result[0] == 'v':
@@ -170,7 +170,7 @@ class ir_model(osv.osv):
             # only reload pool for normal unlink. For module uninstall the
             # reload is done independently in openerp.modules.loading
             cr.commit() # must be committed before reloading registry in new cursor
-            pooler.restart_pool(cr.dbname)
+            openerp.modules.registry.RegistryManager.new(cr.dbname)
             openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
 
         return res
@@ -197,8 +197,8 @@ class ir_model(osv.osv):
                 field_state='manual',
                 select=vals.get('select_level', '0'),
                 update_custom_fields=True)
-            self.pool.get(vals['model'])._auto_init(cr, ctx)
-            self.pool.get(vals['model'])._auto_end(cr, ctx) # actually create FKs!
+            self.pool[vals['model']]._auto_init(cr, ctx)
+            self.pool[vals['model']]._auto_end(cr, ctx) # actually create FKs!
             openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
         return res
 
@@ -220,20 +220,22 @@ class ir_model(osv.osv):
 class ir_model_fields(osv.osv):
     _name = 'ir.model.fields'
     _description = "Fields"
+    _rec_name = 'field_description'
 
     _columns = {
-        'name': fields.char('Name', required=True, size=64, select=1),
-        'model': fields.char('Object Name', size=64, required=True, select=1,
+        'name': fields.char('Name', required=True, select=1),
+        'complete_name': fields.char('Complete Name', select=1),
+        'model': fields.char('Object Name', required=True, select=1,
             help="The technical name of the model this field belongs to"),
-        'relation': fields.char('Object Relation', size=64,
+        'relation': fields.char('Object Relation',
             help="For relationship fields, the technical name of the target model"),
-        'relation_field': fields.char('Relation Field', size=64,
+        'relation_field': fields.char('Relation Field',
             help="For one2many fields, the field on the target model that implement the opposite many2one relationship"),
         'model_id': fields.many2one('ir.model', 'Model', required=True, select=True, ondelete='cascade',
             help="The model this field belongs to"),
-        'field_description': fields.char('Field Label', required=True, size=256),
-        'ttype': fields.selection(_get_fields_type, 'Field Type',size=64, required=True),
-        'selection': fields.char('Selection Options',size=128, help="List of options for a selection field, "
+        'field_description': fields.char('Field Label', required=True),
+        'ttype': fields.selection(_get_fields_type, 'Field Type', required=True),
+        'selection': fields.char('Selection Options', help="List of options for a selection field, "
             "specified as a Python expression defining a list of (key, label) pairs. "
             "For example: [('blue','Blue'),('yellow','Yellow')]"),
         'required': fields.boolean('Required'),
@@ -243,13 +245,12 @@ class ir_model_fields(osv.osv):
         'size': fields.integer('Size'),
         'state': fields.selection([('manual','Custom Field'),('base','Base Field')],'Type', required=True, readonly=True, select=1),
         'on_delete': fields.selection([('cascade','Cascade'),('set null','Set NULL')], 'On Delete', help='On delete property for many2one fields'),
-        'domain': fields.char('Domain', size=256, help="The optional domain to restrict possible values for relationship fields, "
+        'domain': fields.char('Domain', help="The optional domain to restrict possible values for relationship fields, "
             "specified as a Python expression defining a list of triplets. "
             "For example: [('color','=','red')]"),
         'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'),
-        'view_load': fields.boolean('View Auto-Load'),
         'selectable': fields.boolean('Selectable'),
-        'modules': fields.function(_in_modules, type='char', size=128, string='In Modules', help='List of modules in which the field is defined'),
+        'modules': fields.function(_in_modules, type='char', string='In Modules', help='List of modules in which the field is defined'),
         'serialization_field_id': fields.many2one('ir.model.fields', 'Serialization Field', domain = "[('ttype','=','serialized')]",
                                                   ondelete='cascade', help="If set, this field will be stored in the sparse "
                                                                            "structure of the serialization field, instead "
@@ -258,7 +259,6 @@ class ir_model_fields(osv.osv):
     }
     _rec_name='field_description'
     _defaults = {
-        'view_load': 0,
         'selection': "",
         'domain': "[]",
         'name': 'x_',
@@ -304,7 +304,7 @@ class ir_model_fields(osv.osv):
         for field in self.browse(cr, uid, ids, context):
             if field.name in MAGIC_COLUMNS:
                 continue
-            model = self.pool.get(field.model)
+            model = self.pool[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))
@@ -332,7 +332,7 @@ class ir_model_fields(osv.osv):
 
     def create(self, cr, user, vals, context=None):
         if 'model_id' in vals:
-            model_data = self.pool.get('ir.model').browse(cr, user, vals['model_id'])
+            model_data = self.pool['ir.model'].browse(cr, user, vals['model_id'])
             vals['model'] = model_data.model
         if context is None:
             context = {}
@@ -347,19 +347,19 @@ class ir_model_fields(osv.osv):
             if not vals['name'].startswith('x_'):
                 raise except_orm(_('Error'), _("Custom fields must have a name that starts with 'x_' !"))
 
-            if vals.get('relation',False) and not self.pool.get('ir.model').search(cr, user, [('model','=',vals['relation'])]):
+            if vals.get('relation',False) and not self.pool['ir.model'].search(cr, user, [('model','=',vals['relation'])]):
                 raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
 
-            if self.pool.get(vals['model']):
-                self.pool.get(vals['model']).__init__(self.pool, cr)
+            if vals['model'] in self.pool:
+                self.pool[vals['model']].__init__(self.pool, cr)
                 #Added context to _auto_init for special treatment to custom field for select_level
                 ctx = dict(context,
                     field_name=vals['name'],
                     field_state='manual',
                     select=vals.get('select_level', '0'),
                     update_custom_fields=True)
-                self.pool.get(vals['model'])._auto_init(cr, ctx)
-                self.pool.get(vals['model'])._auto_end(cr, ctx) # actually create FKs!
+                self.pool[vals['model']]._auto_init(cr, ctx)
+                self.pool[vals['model']]._auto_end(cr, ctx) # actually create FKs!
                 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
 
         return res
@@ -379,7 +379,6 @@ class ir_model_fields(osv.osv):
                     raise except_orm(_('Error!'),  _('Renaming sparse field "%s" is not allowed')%field.name)
 
         column_rename = None # if set, *one* column can be renamed here
-        obj = None
         models_patch = {}    # structs of (obj, [(field, prop, change_to),..])
                              # data to be updated on the orm model
 
@@ -392,7 +391,6 @@ class ir_model_fields(osv.osv):
             ('size', 'size', int),
             ('on_delete', 'ondelete', str),
             ('translate', 'translate', bool),
-            ('view_load', 'view_load', bool),
             ('selectable', 'selectable', bool),
             ('select_level', 'select', int),
             ('selection', 'selection', eval),
@@ -402,8 +400,7 @@ class ir_model_fields(osv.osv):
             checked_selection = False # need only check it once, so defer
 
             for item in self.browse(cr, user, ids, context=context):
-                if not (obj and obj._name == item.model):
-                    obj = self.pool.get(item.model)
+                obj = self.pool.get(item.model)
 
                 if item.state != 'manual':
                     raise except_orm(_('Error!'),
@@ -439,7 +436,7 @@ class ir_model_fields(osv.osv):
 
                 # We don't check the 'state', because it might come from the context
                 # (thus be set for multiple fields) and will be ignored anyway.
-                if obj:
+                if obj is not None:
                     models_patch.setdefault(obj._name, (obj,[]))
                     # find out which properties (per model) we need to update
                     for field_name, field_property, set_fn in model_props:
@@ -488,7 +485,7 @@ class ir_model_constraint(Model):
     """
     _name = 'ir.model.constraint'
     _columns = {
-        'name': fields.char('Constraint', required=True, size=128, select=1,
+        'name': fields.char('Constraint', required=True, select=1,
             help="PostgreSQL constraint or foreign key name."),
         'model': fields.many2one('ir.model', string='Model',
             required=True, select=1),
@@ -511,7 +508,7 @@ class ir_model_constraint(Model):
         Delete PostgreSQL foreign keys and constraints tracked by this model.
         """ 
 
-        if uid != SUPERUSER_ID and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"):
+        if uid != SUPERUSER_ID and not self.pool['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 {})
@@ -521,7 +518,7 @@ class ir_model_constraint(Model):
         ids.reverse()
         for data in self.browse(cr, uid, ids, context):
             model = data.model.model
-            model_obj = self.pool.get(model)
+            model_obj = self.pool[model]
             name = openerp.tools.ustr(data.name)
             typ = data.type
 
@@ -557,7 +554,7 @@ class ir_model_relation(Model):
     """
     _name = 'ir.model.relation'
     _columns = {
-        'name': fields.char('Relation Name', required=True, size=128, select=1,
+        'name': fields.char('Relation Name', required=True, select=1,
             help="PostgreSQL table name implementing a many2many relation."),
         'model': fields.many2one('ir.model', string='Model',
             required=True, select=1),
@@ -572,7 +569,7 @@ class ir_model_relation(Model):
         Delete PostgreSQL many2many relations tracked by this model.
         """ 
 
-        if uid != SUPERUSER_ID and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"):
+        if uid != SUPERUSER_ID and not self.pool['ir.model.access'].check_groups(cr, uid, "base.group_system"):
             raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
 
         ids_set = set(ids)
@@ -606,7 +603,7 @@ class ir_model_relation(Model):
 class ir_model_access(osv.osv):
     _name = 'ir.model.access'
     _columns = {
-        'name': fields.char('Name', size=64, required=True, select=True),
+        'name': fields.char('Name', required=True, select=True),
         'active': fields.boolean('Active', help='If you uncheck the active field, it will disable the ACL without deleting it (if you delete a native ACL, it will be re-created when you reload the module.'),
         'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)], select=True, ondelete='cascade'),
         'group_id': fields.many2one('res.groups', 'Group', ondelete='cascade', select=True),
@@ -696,9 +693,9 @@ class ir_model_access(osv.osv):
             model_name = model
 
         # TransientModel records have no access rights, only an implicit access rule
-        if not self.pool.get(model_name):
+        if model_name not in self.pool:
             _logger.error('Missing model %s' % (model_name, ))
-        elif self.pool.get(model_name).is_transient():
+        elif self.pool[model_name].is_transient():
             return True
 
         # We check if a specific rule exists
@@ -742,7 +739,7 @@ class ir_model_access(osv.osv):
                 msg_params = (model_name,)
             _logger.warning('Access Denied by ACLs for operation: %s, uid: %s, model: %s', mode, uid, model_name)
             msg = '%s %s' % (msg_heads[mode], msg_tail)
-            raise except_orm(_('Access Denied'), msg % msg_params)
+            raise openerp.exceptions.AccessError(msg % msg_params)
         return r or False
 
     __cache_clearing_methods = []
@@ -760,9 +757,8 @@ class ir_model_access(osv.osv):
     def call_cache_clearing_methods(self, cr):
         self.check.clear_cache(self)    # clear the cache of check function
         for model, method in self.__cache_clearing_methods:
-            object_ = self.pool.get(model)
-            if object_:
-                getattr(object_, method)()
+            if model in self.pool:
+                getattr(self.pool[model], method)()
 
     #
     # Check rights on actions
@@ -806,7 +802,7 @@ class ir_model_data(osv.osv):
 
         for model in result:
             try:
-                r = dict(self.pool.get(model).name_get(cr, uid, result[model].keys(), context=context))
+                r = dict(self.pool[model].name_get(cr, uid, result[model].keys(), context=context))
                 for key,val in result[model].items():
                     result2[val] = r.get(key, False)
             except:
@@ -821,13 +817,13 @@ class ir_model_data(osv.osv):
         return result
 
     _columns = {
-        'name': fields.char('External Identifier', required=True, size=128, select=1,
+        'name': fields.char('External Identifier', required=True, select=1,
                             help="External Key/Identifier that can be used for "
                                  "data integration with third-party systems"),
         'complete_name': fields.function(_complete_name_get, type='char', string='Complete ID'),
         'display_name': fields.function(_display_name_get, type='char', string='Record Name'),
-        'model': fields.char('Model Name', required=True, size=64, select=1),
-        'module': fields.char('Module', required=True, size=64, select=1),
+        'model': fields.char('Model Name', required=True, select=1),
+        'module': fields.char('Module', required=True, select=1),
         'res_id': fields.integer('Record ID', select=1,
                                  help="ID of the target record in the database"),
         'noupdate': fields.boolean('Non Updatable'),
@@ -859,31 +855,77 @@ class ir_model_data(osv.osv):
         if not cr.fetchone():
             cr.execute('CREATE INDEX ir_model_data_module_name_index ON ir_model_data (module, name)')
 
-    @tools.ormcache()
-    def _get_id(self, cr, uid, module, xml_id):
-        """Returns the id of the ir.model.data record corresponding to a given module and xml_id (cached) or raise a ValueError if not found"""
-        ids = self.search(cr, uid, [('module','=',module), ('name','=', xml_id)])
+    # NEW V8 API
+    @tools.ormcache(skiparg=3)
+    def xmlid_lookup(self, cr, uid, xmlid):
+        """Low level xmlid lookup
+        Return (id, res_model, res_id) or raise ValueError if not found
+        """
+        module, name = xmlid.split('.', 1)
+        ids = self.search(cr, uid, [('module','=',module), ('name','=', name)])
         if not ids:
-            raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
+            raise ValueError('External ID not found in the system: %s' % (xmlid))
         # the sql constraints ensure us we have only one result
-        return ids[0]
+        res = self.read(cr, uid, ids[0], ['model', 'res_id'])
+        if not res['res_id']:
+            raise ValueError('External ID not found in the system: %s' % (xmlid))
+        return ids[0], res['model'], res['res_id']
+    
+    def xmlid_to_res_model_res_id(self, cr, uid, xmlid, raise_if_not_found=False):
+        """ Return (res_model, res_id)"""
+        try:
+            return self.xmlid_lookup(cr, uid, xmlid)[1:3]
+        except ValueError:
+            if raise_if_not_found:
+                raise
+            return (False, False)
+
+    def xmlid_to_res_id(self, cr, uid, xmlid, raise_if_not_found=False):
+        """ Returns res_id """
+        return self.xmlid_to_res_model_res_id(cr, uid, xmlid, raise_if_not_found)[1]
+
+    def xmlid_to_object(self, cr, uid, xmlid, raise_if_not_found=False, context=None):
+        """ Return a browse_record
+        if not found and raise_if_not_found is True return the browse_null
+        """ 
+        t = self.xmlid_to_res_model_res_id(cr, uid, xmlid, raise_if_not_found)
+        res_model, res_id = t
+
+        if res_model and res_id:
+            record = self.pool[res_model].browse(cr, uid, res_id, context=context)
+            if record.exists():
+                return record
+            if raise_if_not_found:
+                raise ValueError('No record found for unique ID %s. It may have been deleted.' % (xml_id))
+        return browse_null()
+
+    # OLD API
+    def _get_id(self, cr, uid, module, xml_id):
+        """Returns the id of the ir.model.data record corresponding to a given module and xml_id (cached) or raise a ValueError if not found"""
+        return self.xmlid_lookup(cr, uid, "%s.%s" % (module, xml_id))[0]
 
-    @tools.ormcache()
     def get_object_reference(self, cr, uid, module, xml_id):
         """Returns (model, res_id) corresponding to a given module and xml_id (cached) or raise ValueError if not found"""
-        data_id = self._get_id(cr, uid, module, xml_id)
-        res = self.read(cr, uid, data_id, ['model', 'res_id'])
-        if not res['res_id']:
-            raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
-        return res['model'], res['res_id']
+        return self.xmlid_lookup(cr, uid, "%s.%s" % (module, xml_id))[1:3]
+
+    def check_object_reference(self, cr, uid, module, xml_id, raise_on_access_error=False):
+        """Returns (model, res_id) corresponding to a given module and xml_id (cached), if and only if the user has the necessary access rights
+        to see that object, otherwise raise a ValueError if raise_on_access_error is True or returns a tuple (model found, False)"""
+        model, res_id = self.get_object_reference(cr, uid, module, xml_id)
+        #search on id found in result to check if current user has read access right
+        check_right = self.pool.get(model).search(cr, uid, [('id', '=', res_id)])
+        if check_right:
+            return model, res_id
+        if raise_on_access_error:
+            raise ValueError('Not enough access rights on the external ID: %s.%s' % (module, xml_id))
+        return model, False
 
     def get_object(self, cr, uid, module, xml_id, context=None):
-        """Returns a browsable record for the given module name and xml_id or raise ValueError if not found"""
-        res_model, res_id = self.get_object_reference(cr, uid, module, xml_id)
-        result = self.pool.get(res_model).browse(cr, uid, res_id, context=context)
-        if not result.exists():
-            raise ValueError('No record found for unique ID %s.%s. It may have been deleted.' % (module, xml_id))
-        return result
+        """ Returns a browsable record for the given module name and xml_id.
+            If not found, raise a ValueError or return a browse_null, depending
+            on the value of `raise_exception`.
+        """
+        return self.xmlid_to_object(cr, uid, "%s.%s" % (module, xml_id), raise_if_not_found=True, context=context)
 
     def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
         if not xml_id:
@@ -900,8 +942,7 @@ class ir_model_data(osv.osv):
 
         :returns: itself
         """
-        self._get_id.clear_cache(self)
-        self.get_object_reference.clear_cache(self)
+        self.xmlid_lookup.clear_cache(self)
         return self
 
     def unlink(self, cr, uid, ids, context=None):
@@ -910,7 +951,7 @@ class ir_model_data(osv.osv):
         return super(ir_model_data,self).unlink(cr, uid, ids, context=context)
 
     def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None):
-        model_obj = self.pool.get(model)
+        model_obj = self.pool[model]
         if not context:
             context = {}
         # records created during module install should not display the messages of OpenChatter
@@ -922,15 +963,17 @@ class ir_model_data(osv.osv):
             return False
         action_id = False
         if xml_id:
-            cr.execute('''SELECT imd.id, imd.res_id, md.id, imd.model
+            cr.execute('''SELECT imd.id, imd.res_id, md.id, imd.model, imd.noupdate
                           FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
                           WHERE imd.module=%%s AND imd.name=%%s''' % model_obj._table,
                           (module, xml_id))
             results = cr.fetchall()
-            for imd_id2,res_id2,real_id2,real_model in results:
+            for imd_id2,res_id2,real_id2,real_model,noupdate_imd in results:
+                # In update mode, do not update a record if it's ir.model.data is flagged as noupdate
+                if mode == 'update' and noupdate_imd:
+                    return res_id2
                 if not real_id2:
-                    self._get_id.clear_cache(self, uid, module, xml_id)
-                    self.get_object_reference.clear_cache(self, uid, module, xml_id)
+                    self.clear_caches()
                     cr.execute('delete from ir_model_data where id=%s', (imd_id2,))
                     res_id = False
                 else:
@@ -946,13 +989,6 @@ class ir_model_data(osv.osv):
         elif res_id:
             model_obj.write(cr, uid, [res_id], values, context=context)
             if xml_id:
-                self.create(cr, uid, {
-                    'name': xml_id,
-                    'model': model,
-                    'module':module,
-                    'res_id':res_id,
-                    'noupdate': noupdate,
-                    },context=context)
                 if model_obj._inherits:
                     for table in model_obj._inherits:
                         inherit_id = model_obj.browse(cr, uid,
@@ -964,17 +1000,17 @@ class ir_model_data(osv.osv):
                             'res_id': inherit_id.id,
                             'noupdate': noupdate,
                             },context=context)
+                self.create(cr, uid, {
+                    'name': xml_id,
+                    'model': model,
+                    'module':module,
+                    'res_id':res_id,
+                    'noupdate': noupdate,
+                    },context=context)
         else:
             if mode=='init' or (mode=='update' and xml_id):
                 res_id = model_obj.create(cr, uid, values, context=context)
                 if xml_id:
-                    self.create(cr, uid, {
-                        'name': xml_id,
-                        'model': model,
-                        'module': module,
-                        'res_id': res_id,
-                        'noupdate': noupdate
-                        },context=context)
                     if model_obj._inherits:
                         for table in model_obj._inherits:
                             inherit_id = model_obj.browse(cr, uid,
@@ -986,6 +1022,13 @@ class ir_model_data(osv.osv):
                                 'res_id': inherit_id.id,
                                 'noupdate': noupdate,
                                 },context=context)
+                    self.create(cr, uid, {
+                        'name': xml_id,
+                        'model': model,
+                        'module': module,
+                        'res_id': res_id,
+                        'noupdate': noupdate
+                        },context=context)
         if xml_id and res_id:
             self.loads[(module, xml_id)] = (model, res_id)
             for table, inherit_field in model_obj._inherits.iteritems():
@@ -1014,7 +1057,7 @@ class ir_model_data(osv.osv):
         cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
         res = cr.fetchone()
         if not res:
-            ir_values_obj = pooler.get_pool(cr.dbname).get('ir.values')
+            ir_values_obj = openerp.registry(cr.dbname)['ir.values']
             ir_values_obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
         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))
@@ -1033,7 +1076,7 @@ class ir_model_data(osv.osv):
 
         ids = self.search(cr, uid, [('module', 'in', modules_to_remove)])
 
-        if uid != 1 and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"):
+        if uid != 1 and not self.pool['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 {})
@@ -1060,10 +1103,9 @@ class ir_model_data(osv.osv):
                 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))
 
-        wf_service = netsvc.LocalService("workflow")
         for model,res_id in wkf_todo:
             try:
-                wf_service.trg_write(uid, model, res_id, cr)
+                openerp.workflow.trg_write(uid, model, res_id, cr)
             except Exception:
                 _logger.info('Unable to force processing of workflow for item %s@%s in order to leave activity to be deleted', res_id, model, exc_info=True)
 
@@ -1073,10 +1115,22 @@ class ir_model_data(osv.osv):
                 if set(external_ids)-ids_set:
                     # if other modules have defined this record, we must not delete it
                     continue
+                if model == 'ir.model.fields':
+                    # Don't remove the LOG_ACCESS_COLUMNS unless _log_access
+                    # has been turned off on the model.
+                    field = self.pool[model].browse(cr, uid, [res_id], context=context)[0]
+                    if not field.exists():
+                        _logger.info('Deleting orphan external_ids %s', external_ids)
+                        self.unlink(cr, uid, external_ids)
+                        continue
+                    if field.name in openerp.osv.orm.LOG_ACCESS_COLUMNS and self.pool[field.model]._log_access:
+                        continue
+                    if field.name == 'id':
+                        continue
                 _logger.info('Deleting %s@%s', res_id, model)
                 try:
                     cr.execute('SAVEPOINT record_unlink_save')
-                    self.pool.get(model).unlink(cr, uid, [res_id], context=context)
+                    self.pool[model].unlink(cr, uid, [res_id], context=context)
                 except Exception:
                     _logger.info('Unable to delete %s@%s', res_id, model, exc_info=True)
                     cr.execute('ROLLBACK TO SAVEPOINT record_unlink_save')
@@ -1089,8 +1143,8 @@ class ir_model_data(osv.osv):
         unlink_if_refcount((model, res_id) for model, res_id in to_unlink
                                 if model == 'ir.model.fields')
 
-        ir_model_relation = self.pool.get('ir.model.relation')
-        ir_module_module = self.pool.get('ir.module.module')
+        ir_model_relation = self.pool['ir.model.relation']
+        ir_module_module = self.pool['ir.module.module']
         modules_to_remove_ids = ir_module_module.search(cr, uid, [('name', 'in', modules_to_remove)])
         relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove_ids)])
         ir_model_relation._module_data_uninstall(cr, uid, relation_ids, context)
@@ -1114,16 +1168,43 @@ class ir_model_data(osv.osv):
             return True
         to_unlink = []
         cr.execute("""SELECT id,name,model,res_id,module FROM ir_model_data
-                      WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s
-                      ORDER BY id DESC""",
+                      WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s ORDER BY id DESC""",
                       (tuple(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 not config.get('import_partial'):
             for (model, res_id) in to_unlink:
-                if self.pool.get(model):
+                if model in self.pool:
                     _logger.info('Deleting %s@%s', res_id, model)
-                    self.pool.get(model).unlink(cr, uid, [res_id])
+                    self.pool[model].unlink(cr, uid, [res_id])
+
+class wizard_model_menu(osv.osv_memory):
+    _name = 'wizard.ir.model.menu.create'
+    _columns = {
+        'menu_id': fields.many2one('ir.ui.menu', 'Parent Menu', required=True),
+        'name': fields.char('Menu Name', size=64, required=True),
+    }
+
+    def menu_create(self, cr, uid, ids, context=None):
+        if not context:
+            context = {}
+        model_pool = self.pool.get('ir.model')
+        for menu in self.browse(cr, uid, ids, context):
+            model = model_pool.browse(cr, uid, context.get('model_id'), context=context)
+            val = {
+                'name': menu.name,
+                'res_model': model.model,
+                'view_type': 'form',
+                'view_mode': 'tree,form'
+            }
+            action_id = self.pool.get('ir.actions.act_window').create(cr, uid, val)
+            self.pool.get('ir.ui.menu').create(cr, uid, {
+                'name': menu.name,
+                'parent_id': menu.menu_id.id,
+                'action': 'ir.actions.act_window,%d' % (action_id,),
+                'icon': 'STOCK_INDENT'
+            }, context)
+        return {'type':'ir.actions.act_window_close'}
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: