[FIX] base: ir.model.fields, write, use tools.ustr instead of str so it handle if...
[odoo/odoo.git] / openerp / addons / base / ir / ir_model.py
1 # -*- coding: utf-8 -*-
2
3 ##############################################################################
4 #
5 #    OpenERP, Open Source Business Applications
6 #    Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU Affero General Public License as
10 #    published by the Free Software Foundation, either version 3 of the
11 #    License, or (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU Affero General Public License for more details.
17 #
18 #    You should have received a copy of the GNU Affero General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22 import logging
23 import re
24 import time
25 import types
26
27 import openerp
28 import openerp.modules.registry
29 from openerp import SUPERUSER_ID
30 from openerp import netsvc, pooler, tools
31 from openerp.osv import fields,osv
32 from openerp.osv.orm import Model
33 from openerp.tools.safe_eval import safe_eval as eval
34 from openerp.tools import config
35 from openerp.tools.translate import _
36 from openerp.osv.orm import except_orm, browse_record, MAGIC_COLUMNS
37
38 _logger = logging.getLogger(__name__)
39
40 MODULE_UNINSTALL_FLAG = '_force_unlink'
41
42 def _get_fields_type(self, cr, uid, context=None):
43     # Avoid too many nested `if`s below, as RedHat's Python 2.6
44     # break on it. See bug 939653.
45     return sorted([(k,k) for k,v in fields.__dict__.iteritems()
46                       if type(v) == types.TypeType and \
47                          issubclass(v, fields._column) and \
48                          v != fields._column and \
49                          not v._deprecated and \
50                          not issubclass(v, fields.function)])
51
52 def _in_modules(self, cr, uid, ids, field_name, arg, context=None):
53     #pseudo-method used by fields.function in ir.model/ir.model.fields
54     module_pool = self.pool.get("ir.module.module")
55     installed_module_ids = module_pool.search(cr, uid, [('state','=','installed')])
56     installed_module_names = module_pool.read(cr, uid, installed_module_ids, ['name'], context=context)
57     installed_modules = set(x['name'] for x in installed_module_names)
58
59     result = {}
60     xml_ids = osv.osv._get_xml_ids(self, cr, uid, ids)
61     for k,v in xml_ids.iteritems():
62         result[k] = ', '.join(sorted(installed_modules & set(xml_id.split('.')[0] for xml_id in v)))
63     return result
64
65 class ir_model(osv.osv):
66     _name = 'ir.model'
67     _description = "Models"
68     _order = 'model'
69
70     def _is_osv_memory(self, cr, uid, ids, field_name, arg, context=None):
71         models = self.browse(cr, uid, ids, context=context)
72         res = dict.fromkeys(ids)
73         for model in models:
74             if self.pool.get(model.model):
75                 res[model.id] = self.pool.get(model.model).is_transient()
76             else:
77                 _logger.error('Missing model %s' % (model.model, ))
78         return res
79
80     def _search_osv_memory(self, cr, uid, model, name, domain, context=None):
81         if not domain:
82             return []
83         __, operator, value = domain[0]
84         if operator not in ['=', '!=']:
85             raise osv.except_osv(_("Invalid Search Criteria"), _('The osv_memory field can only be compared with = and != operator.'))
86         value = bool(value) if operator == '=' else not bool(value)
87         all_model_ids = self.search(cr, uid, [], context=context)
88         is_osv_mem = self._is_osv_memory(cr, uid, all_model_ids, 'osv_memory', arg=None, context=context)
89         return [('id', 'in', [id for id in is_osv_mem if bool(is_osv_mem[id]) == value])]
90
91     def _view_ids(self, cr, uid, ids, field_name, arg, context=None):
92         models = self.browse(cr, uid, ids)
93         res = {}
94         for model in models:
95             res[model.id] = self.pool.get("ir.ui.view").search(cr, uid, [('model', '=', model.model)])
96         return res
97
98     _columns = {
99         'name': fields.char('Model Description', size=64, translate=True, required=True),
100         'model': fields.char('Model', size=64, required=True, select=1),
101         'info': fields.text('Information'),
102         'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields', required=True),
103         'state': fields.selection([('manual','Custom Object'),('base','Base Object')],'Type',readonly=True),
104         'access_ids': fields.one2many('ir.model.access', 'model_id', 'Access'),
105         'osv_memory': fields.function(_is_osv_memory, string='Transient Model', type='boolean',
106             fnct_search=_search_osv_memory,
107             help="This field specifies whether the model is transient or not (i.e. if records are automatically deleted from the database or not)"),
108         'modules': fields.function(_in_modules, type='char', size=128, string='In Modules', help='List of modules in which the object is defined or inherited'),
109         'view_ids': fields.function(_view_ids, type='one2many', obj='ir.ui.view', string='Views'),
110     }
111
112     _defaults = {
113         'model': 'x_',
114         'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
115     }
116
117     def _check_model_name(self, cr, uid, ids, context=None):
118         for model in self.browse(cr, uid, ids, context=context):
119             if model.state=='manual':
120                 if not model.model.startswith('x_'):
121                     return False
122             if not re.match('^[a-z_A-Z0-9.]+$',model.model):
123                 return False
124         return True
125
126     def _model_name_msg(self, cr, uid, ids, context=None):
127         return _('The Object name must start with x_ and not contain any special character !')
128
129     _constraints = [
130         (_check_model_name, _model_name_msg, ['model']),
131     ]
132     _sql_constraints = [
133         ('obj_name_uniq', 'unique (model)', 'Each model must be unique!'),
134     ]
135
136     # overridden to allow searching both on model name (model field)
137     # and model description (name field)
138     def _name_search(self, cr, uid, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
139         if args is None:
140             args = []
141         domain = args + ['|', ('model', operator, name), ('name', operator, name)]
142         return self.name_get(cr, name_get_uid or uid,
143                              super(ir_model, self).search(cr, uid, domain, limit=limit, context=context),
144                              context=context)
145
146     def _drop_table(self, cr, uid, ids, context=None):
147         for model in self.browse(cr, uid, ids, context):
148             model_pool = self.pool.get(model.model)
149             cr.execute('select relkind from pg_class where relname=%s', (model_pool._table,))
150             result = cr.fetchone()
151             if result and result[0] == 'v':
152                 cr.execute('DROP view %s' % (model_pool._table,))
153             elif result and result[0] == 'r':
154                 cr.execute('DROP TABLE %s' % (model_pool._table,))
155         return True
156
157     def unlink(self, cr, user, ids, context=None):
158         # Prevent manual deletion of module tables
159         if context is None: context = {}
160         if isinstance(ids, (int, long)):
161             ids = [ids]
162         if not context.get(MODULE_UNINSTALL_FLAG):
163             for model in self.browse(cr, user, ids, context):
164                 if model.state != 'manual':
165                     raise except_orm(_('Error'), _("Model '%s' contains module data and cannot be removed!") % (model.name,))
166
167         self._drop_table(cr, user, ids, context)
168         res = super(ir_model, self).unlink(cr, user, ids, context)
169         if not context.get(MODULE_UNINSTALL_FLAG):
170             # only reload pool for normal unlink. For module uninstall the
171             # reload is done independently in openerp.modules.loading
172             cr.commit() # must be committed before reloading registry in new cursor
173             pooler.restart_pool(cr.dbname)
174             openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
175
176         return res
177
178     def write(self, cr, user, ids, vals, context=None):
179         if context:
180             context.pop('__last_update', None)
181         # Filter out operations 4 link from field id, because openerp-web
182         # always write (4,id,False) even for non dirty items
183         if 'field_id' in vals:
184             vals['field_id'] = [op for op in vals['field_id'] if op[0] != 4]
185         return super(ir_model,self).write(cr, user, ids, vals, context)
186
187     def create(self, cr, user, vals, context=None):
188         if  context is None:
189             context = {}
190         if context and context.get('manual'):
191             vals['state']='manual'
192         res = super(ir_model,self).create(cr, user, vals, context)
193         if vals.get('state','base')=='manual':
194             self.instanciate(cr, user, vals['model'], context)
195             ctx = dict(context,
196                 field_name=vals['name'],
197                 field_state='manual',
198                 select=vals.get('select_level', '0'),
199                 update_custom_fields=True)
200             self.pool.get(vals['model'])._auto_init(cr, ctx)
201             self.pool.get(vals['model'])._auto_end(cr, ctx) # actually create FKs!
202             openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
203         return res
204
205     def instanciate(self, cr, user, model, context=None):
206         class x_custom_model(osv.osv):
207             _custom = True
208         x_custom_model._name = model
209         x_custom_model._module = False
210         a = x_custom_model.create_instance(self.pool, cr)
211         if not a._columns:
212             x_name = 'id'
213         elif 'x_name' in a._columns.keys():
214             x_name = 'x_name'
215         else:
216             x_name = a._columns.keys()[0]
217         x_custom_model._rec_name = x_name
218         a._rec_name = x_name
219
220 class ir_model_fields(osv.osv):
221     _name = 'ir.model.fields'
222     _description = "Fields"
223
224     _columns = {
225         'name': fields.char('Name', required=True, size=64, select=1),
226         'model': fields.char('Object Name', size=64, required=True, select=1,
227             help="The technical name of the model this field belongs to"),
228         'relation': fields.char('Object Relation', size=64,
229             help="For relationship fields, the technical name of the target model"),
230         'relation_field': fields.char('Relation Field', size=64,
231             help="For one2many fields, the field on the target model that implement the opposite many2one relationship"),
232         'model_id': fields.many2one('ir.model', 'Model', required=True, select=True, ondelete='cascade',
233             help="The model this field belongs to"),
234         'field_description': fields.char('Field Label', required=True, size=256),
235         'ttype': fields.selection(_get_fields_type, 'Field Type',size=64, required=True),
236         'selection': fields.char('Selection Options',size=128, help="List of options for a selection field, "
237             "specified as a Python expression defining a list of (key, label) pairs. "
238             "For example: [('blue','Blue'),('yellow','Yellow')]"),
239         'required': fields.boolean('Required'),
240         'readonly': fields.boolean('Readonly'),
241         'select_level': fields.selection([('0','Not Searchable'),('1','Always Searchable'),('2','Advanced Search (deprecated)')],'Searchable', required=True),
242         'translate': fields.boolean('Translatable', help="Whether values for this field can be translated (enables the translation mechanism for that field)"),
243         'size': fields.integer('Size'),
244         'state': fields.selection([('manual','Custom Field'),('base','Base Field')],'Type', required=True, readonly=True, select=1),
245         'on_delete': fields.selection([('cascade','Cascade'),('set null','Set NULL')], 'On Delete', help='On delete property for many2one fields'),
246         'domain': fields.char('Domain', size=256, help="The optional domain to restrict possible values for relationship fields, "
247             "specified as a Python expression defining a list of triplets. "
248             "For example: [('color','=','red')]"),
249         'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'),
250         'view_load': fields.boolean('View Auto-Load'),
251         'selectable': fields.boolean('Selectable'),
252         'modules': fields.function(_in_modules, type='char', size=128, string='In Modules', help='List of modules in which the field is defined'),
253         'serialization_field_id': fields.many2one('ir.model.fields', 'Serialization Field', domain = "[('ttype','=','serialized')]",
254                                                   ondelete='cascade', help="If set, this field will be stored in the sparse "
255                                                                            "structure of the serialization field, instead "
256                                                                            "of having its own database column. This cannot be "
257                                                                            "changed after creation."),
258     }
259     _rec_name='field_description'
260     _defaults = {
261         'view_load': 0,
262         'selection': "",
263         'domain': "[]",
264         'name': 'x_',
265         'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
266         'on_delete': 'set null',
267         'select_level': '0',
268         'field_description': '',
269         'selectable': 1,
270     }
271     _order = "name"
272
273     def _check_selection(self, cr, uid, selection, context=None):
274         try:
275             selection_list = eval(selection)
276         except Exception:
277             _logger.warning('Invalid selection list definition for fields.selection', exc_info=True)
278             raise except_orm(_('Error'),
279                     _("The Selection Options expression is not a valid Pythonic expression."
280                       "Please provide an expression in the [('key','Label'), ...] format."))
281
282         check = True
283         if not (isinstance(selection_list, list) and selection_list):
284             check = False
285         else:
286             for item in selection_list:
287                 if not (isinstance(item, (tuple,list)) and len(item) == 2):
288                     check = False
289                     break
290
291         if not check:
292                 raise except_orm(_('Error'),
293                     _("The Selection Options expression is must be in the [('key','Label'), ...] format!"))
294         return True
295
296     def _size_gt_zero_msg(self, cr, user, ids, context=None):
297         return _('Size of the field can never be less than 0 !')
298
299     _sql_constraints = [
300         ('size_gt_zero', 'CHECK (size>=0)',_size_gt_zero_msg ),
301     ]
302
303     def _drop_column(self, cr, uid, ids, context=None):
304         for field in self.browse(cr, uid, ids, context):
305             if field.name in MAGIC_COLUMNS:
306                 continue
307             model = self.pool.get(field.model)
308             cr.execute('select relkind from pg_class where relname=%s', (model._table,))
309             result = cr.fetchone()
310             cr.execute("SELECT column_name FROM information_schema.columns WHERE table_name ='%s' and column_name='%s'" %(model._table, field.name))
311             column_name = cr.fetchone()
312             if column_name and (result and result[0] == 'r'):
313                 cr.execute('ALTER table "%s" DROP column "%s" cascade' % (model._table, field.name))
314             model._columns.pop(field.name, None)
315         return True
316
317     def unlink(self, cr, user, ids, context=None):
318         # Prevent manual deletion of module columns
319         if context is None: context = {}
320         if isinstance(ids, (int, long)):
321             ids = [ids]
322         if not context.get(MODULE_UNINSTALL_FLAG) and \
323                 any(field.state != 'manual' for field in self.browse(cr, user, ids, context)):
324             raise except_orm(_('Error'), _("This column contains module data and cannot be removed!"))
325
326         self._drop_column(cr, user, ids, context)
327         res = super(ir_model_fields, self).unlink(cr, user, ids, context)
328         if not context.get(MODULE_UNINSTALL_FLAG):
329             cr.commit()
330             openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
331         return res
332
333     def create(self, cr, user, vals, context=None):
334         if 'model_id' in vals:
335             model_data = self.pool.get('ir.model').browse(cr, user, vals['model_id'])
336             vals['model'] = model_data.model
337         if context is None:
338             context = {}
339         if context and context.get('manual',False):
340             vals['state'] = 'manual'
341         if vals.get('ttype', False) == 'selection':
342             if not vals.get('selection',False):
343                 raise except_orm(_('Error'), _('For selection fields, the Selection Options must be given!'))
344             self._check_selection(cr, user, vals['selection'], context=context)
345         res = super(ir_model_fields,self).create(cr, user, vals, context)
346         if vals.get('state','base') == 'manual':
347             if not vals['name'].startswith('x_'):
348                 raise except_orm(_('Error'), _("Custom fields must have a name that starts with 'x_' !"))
349
350             if vals.get('relation',False) and not self.pool.get('ir.model').search(cr, user, [('model','=',vals['relation'])]):
351                 raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
352
353             if self.pool.get(vals['model']):
354                 self.pool.get(vals['model']).__init__(self.pool, cr)
355                 #Added context to _auto_init for special treatment to custom field for select_level
356                 ctx = dict(context,
357                     field_name=vals['name'],
358                     field_state='manual',
359                     select=vals.get('select_level', '0'),
360                     update_custom_fields=True)
361                 self.pool.get(vals['model'])._auto_init(cr, ctx)
362                 self.pool.get(vals['model'])._auto_end(cr, ctx) # actually create FKs!
363                 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
364
365         return res
366
367     def write(self, cr, user, ids, vals, context=None):
368         if context is None:
369             context = {}
370         if context and context.get('manual',False):
371             vals['state'] = 'manual'
372
373         #For the moment renaming a sparse field or changing the storing system is not allowed. This may be done later
374         if 'serialization_field_id' in vals or 'name' in vals:
375             for field in self.browse(cr, user, ids, context=context):
376                 if 'serialization_field_id' in vals and field.serialization_field_id.id != vals['serialization_field_id']:
377                     raise except_orm(_('Error!'),  _('Changing the storing system for field "%s" is not allowed.')%field.name)
378                 if field.serialization_field_id and (field.name != vals['name']):
379                     raise except_orm(_('Error!'),  _('Renaming sparse field "%s" is not allowed')%field.name)
380
381         column_rename = None # if set, *one* column can be renamed here
382         obj = None
383         models_patch = {}    # structs of (obj, [(field, prop, change_to),..])
384                              # data to be updated on the orm model
385
386         # static table of properties
387         model_props = [ # (our-name, fields.prop, set_fn)
388             ('field_description', 'string', tools.ustr),
389             ('required', 'required', bool),
390             ('readonly', 'readonly', bool),
391             ('domain', '_domain', eval),
392             ('size', 'size', int),
393             ('on_delete', 'ondelete', str),
394             ('translate', 'translate', bool),
395             ('view_load', 'view_load', bool),
396             ('selectable', 'selectable', bool),
397             ('select_level', 'select', int),
398             ('selection', 'selection', eval),
399             ]
400
401         if vals and ids:
402             checked_selection = False # need only check it once, so defer
403
404             for item in self.browse(cr, user, ids, context=context):
405                 if not (obj and obj._name == item.model):
406                     obj = self.pool.get(item.model)
407
408                 if item.state != 'manual':
409                     raise except_orm(_('Error!'),
410                         _('Properties of base fields cannot be altered in this manner! '
411                           'Please modify them through Python code, '
412                           'preferably through a custom addon!'))
413
414                 if item.ttype == 'selection' and 'selection' in vals \
415                         and not checked_selection:
416                     self._check_selection(cr, user, vals['selection'], context=context)
417                     checked_selection = True
418
419                 final_name = item.name
420                 if 'name' in vals and vals['name'] != item.name:
421                     # We need to rename the column
422                     if column_rename:
423                         raise except_orm(_('Error!'), _('Can only rename one column at a time!'))
424                     if vals['name'] in obj._columns:
425                         raise except_orm(_('Error!'), _('Cannot rename column to %s, because that column already exists!') % vals['name'])
426                     if vals.get('state', 'base') == 'manual' and not vals['name'].startswith('x_'):
427                         raise except_orm(_('Error!'), _('New column name must still start with x_ , because it is a custom field!'))
428                     if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']:
429                         raise ValueError('Invalid character in column name')
430                     column_rename = (obj, (obj._table, item.name, vals['name']))
431                     final_name = vals['name']
432
433                 if 'model_id' in vals and vals['model_id'] != item.model_id:
434                     raise except_orm(_("Error!"), _("Changing the model of a field is forbidden!"))
435
436                 if 'ttype' in vals and vals['ttype'] != item.ttype:
437                     raise except_orm(_("Error!"), _("Changing the type of a column is not yet supported. "
438                                 "Please drop it and create it again!"))
439
440                 # We don't check the 'state', because it might come from the context
441                 # (thus be set for multiple fields) and will be ignored anyway.
442                 if obj:
443                     models_patch.setdefault(obj._name, (obj,[]))
444                     # find out which properties (per model) we need to update
445                     for field_name, field_property, set_fn in model_props:
446                         if field_name in vals:
447                             property_value = set_fn(vals[field_name])
448                             if getattr(obj._columns[item.name], field_property) != property_value:
449                                 models_patch[obj._name][1].append((final_name, field_property, property_value))
450                         # our dict is ready here, but no properties are changed so far
451
452         # These shall never be written (modified)
453         for column_name in ('model_id', 'model', 'state'):
454             if column_name in vals:
455                 del vals[column_name]
456
457         res = super(ir_model_fields,self).write(cr, user, ids, vals, context=context)
458
459         if column_rename:
460             cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % column_rename[1])
461             # This is VERY risky, but let us have this feature:
462             # we want to change the key of column in obj._columns dict
463             col = column_rename[0]._columns.pop(column_rename[1][1]) # take object out, w/o copy
464             column_rename[0]._columns[column_rename[1][2]] = col
465
466         if models_patch:
467             # We have to update _columns of the model(s) and then call their
468             # _auto_init to sync the db with the model. Hopefully, since write()
469             # was called earlier, they will be in-sync before the _auto_init.
470             # Anything we don't update in _columns now will be reset from
471             # the model into ir.model.fields (db).
472             ctx = dict(context, select=vals.get('select_level', '0'),
473                        update_custom_fields=True)
474
475             for __, patch_struct in models_patch.items():
476                 obj = patch_struct[0]
477                 for col_name, col_prop, val in patch_struct[1]:
478                     setattr(obj._columns[col_name], col_prop, val)
479                 obj._auto_init(cr, ctx)
480                 obj._auto_end(cr, ctx) # actually create FKs!
481             openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
482         return res
483
484 class ir_model_constraint(Model):
485     """
486     This model tracks PostgreSQL foreign keys and constraints used by OpenERP
487     models.
488     """
489     _name = 'ir.model.constraint'
490     _columns = {
491         'name': fields.char('Constraint', required=True, size=128, select=1,
492             help="PostgreSQL constraint or foreign key name."),
493         'model': fields.many2one('ir.model', string='Model',
494             required=True, select=1),
495         'module': fields.many2one('ir.module.module', string='Module',
496             required=True, select=1),
497         'type': fields.char('Constraint Type', required=True, size=1, select=1,
498             help="Type of the constraint: `f` for a foreign key, "
499                 "`u` for other constraints."),
500         'date_update': fields.datetime('Update Date'),
501         'date_init': fields.datetime('Initialization Date')
502     }
503
504     _sql_constraints = [
505         ('module_name_uniq', 'unique(name, module)',
506             'Constraints with the same name are unique per module.'),
507     ]
508
509     def _module_data_uninstall(self, cr, uid, ids, context=None):
510         """
511         Delete PostgreSQL foreign keys and constraints tracked by this model.
512         """ 
513
514         if uid != SUPERUSER_ID and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"):
515             raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
516
517         context = dict(context or {})
518
519         ids_set = set(ids)
520         ids.sort()
521         ids.reverse()
522         for data in self.browse(cr, uid, ids, context):
523             model = data.model.model
524             model_obj = self.pool.get(model)
525             name = openerp.tools.ustr(data.name)
526             typ = data.type
527
528             # double-check we are really going to delete all the owners of this schema element
529             cr.execute("""SELECT id from ir_model_constraint where name=%s""", (data.name,))
530             external_ids = [x[0] for x in cr.fetchall()]
531             if set(external_ids)-ids_set:
532                 # as installed modules have defined this element we must not delete it!
533                 continue
534
535             if typ == 'f':
536                 # test if FK exists on this table (it could be on a related m2m table, in which case we ignore it)
537                 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
538                               WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_obj._table))
539                 if cr.fetchone():
540                     cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
541                     _logger.info('Dropped FK CONSTRAINT %s@%s', name, model)
542
543             if typ == 'u':
544                 # test if constraint exists
545                 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
546                               WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_obj._table))
547                 if cr.fetchone():
548                     cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
549                     _logger.info('Dropped CONSTRAINT %s@%s', name, model)
550
551         self.unlink(cr, uid, ids, context)
552
553 class ir_model_relation(Model):
554     """
555     This model tracks PostgreSQL tables used to implement OpenERP many2many
556     relations.
557     """
558     _name = 'ir.model.relation'
559     _columns = {
560         'name': fields.char('Relation Name', required=True, size=128, select=1,
561             help="PostgreSQL table name implementing a many2many relation."),
562         'model': fields.many2one('ir.model', string='Model',
563             required=True, select=1),
564         'module': fields.many2one('ir.module.module', string='Module',
565             required=True, select=1),
566         'date_update': fields.datetime('Update Date'),
567         'date_init': fields.datetime('Initialization Date')
568     }
569
570     def _module_data_uninstall(self, cr, uid, ids, context=None):
571         """
572         Delete PostgreSQL many2many relations tracked by this model.
573         """ 
574
575         if uid != SUPERUSER_ID and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"):
576             raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
577
578         ids_set = set(ids)
579         to_drop_table = []
580         ids.sort()
581         ids.reverse()
582         for data in self.browse(cr, uid, ids, context):
583             model = data.model
584             name = openerp.tools.ustr(data.name)
585
586             # double-check we are really going to delete all the owners of this schema element
587             cr.execute("""SELECT id from ir_model_relation where name = %s""", (data.name,))
588             external_ids = [x[0] for x in cr.fetchall()]
589             if set(external_ids)-ids_set:
590                 # as installed modules have defined this element we must not delete it!
591                 continue
592
593             cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name=%s", (name,))
594             if cr.fetchone() and not name in to_drop_table:
595                 to_drop_table.append(name)
596
597         self.unlink(cr, uid, ids, context)
598
599         # drop m2m relation tables
600         for table in to_drop_table:
601             cr.execute('DROP TABLE %s CASCADE'% table,)
602             _logger.info('Dropped table %s', table)
603
604         cr.commit()
605
606 class ir_model_access(osv.osv):
607     _name = 'ir.model.access'
608     _columns = {
609         'name': fields.char('Name', size=64, required=True, select=True),
610         '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.'),
611         'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)], select=True, ondelete='cascade'),
612         'group_id': fields.many2one('res.groups', 'Group', ondelete='cascade', select=True),
613         'perm_read': fields.boolean('Read Access'),
614         'perm_write': fields.boolean('Write Access'),
615         'perm_create': fields.boolean('Create Access'),
616         'perm_unlink': fields.boolean('Delete Access'),
617     }
618     _defaults = {
619         'active': True,
620     }
621
622     def check_groups(self, cr, uid, group):
623         grouparr  = group.split('.')
624         if not grouparr:
625             return False
626         cr.execute("select 1 from res_groups_users_rel where uid=%s and gid IN (select res_id from ir_model_data where module=%s and name=%s)", (uid, grouparr[0], grouparr[1],))
627         return bool(cr.fetchone())
628
629     def check_group(self, cr, uid, model, mode, group_ids):
630         """ Check if a specific group has the access mode to the specified model"""
631         assert mode in ['read','write','create','unlink'], 'Invalid access mode'
632
633         if isinstance(model, browse_record):
634             assert model._table_name == 'ir.model', 'Invalid model object'
635             model_name = model.name
636         else:
637             model_name = model
638
639         if isinstance(group_ids, (int, long)):
640             group_ids = [group_ids]
641         for group_id in group_ids:
642             cr.execute("SELECT perm_" + mode + " "
643                    "  FROM ir_model_access a "
644                    "  JOIN ir_model m ON (m.id = a.model_id) "
645                    " WHERE m.model = %s AND a.active IS True "
646                    " AND a.group_id = %s", (model_name, group_id)
647                    )
648             r = cr.fetchone()
649             if r is None:
650                 cr.execute("SELECT perm_" + mode + " "
651                        "  FROM ir_model_access a "
652                        "  JOIN ir_model m ON (m.id = a.model_id) "
653                        " WHERE m.model = %s AND a.active IS True "
654                        " AND a.group_id IS NULL", (model_name, )
655                        )
656                 r = cr.fetchone()
657
658             access = bool(r and r[0])
659             if access:
660                 return True
661         # pass no groups -> no access
662         return False
663
664     def group_names_with_access(self, cr, model_name, access_mode):
665         """Returns the names of visible groups which have been granted ``access_mode`` on
666            the model ``model_name``.
667            :rtype: list
668         """
669         assert access_mode in ['read','write','create','unlink'], 'Invalid access mode: %s' % access_mode
670         cr.execute('''SELECT
671                         c.name, g.name
672                       FROM
673                         ir_model_access a
674                         JOIN ir_model m ON (a.model_id=m.id)
675                         JOIN res_groups g ON (a.group_id=g.id)
676                         LEFT JOIN ir_module_category c ON (c.id=g.category_id)
677                       WHERE
678                         m.model=%s AND
679                         a.active IS True AND
680                         a.perm_''' + access_mode, (model_name,))
681         return [('%s/%s' % x) if x[0] else x[1] for x in cr.fetchall()]
682
683     @tools.ormcache()
684     def check(self, cr, uid, model, mode='read', raise_exception=True, context=None):
685         if uid==1:
686             # User root have all accesses
687             # TODO: exclude xml-rpc requests
688             return True
689
690         assert mode in ['read','write','create','unlink'], 'Invalid access mode'
691
692         if isinstance(model, browse_record):
693             assert model._table_name == 'ir.model', 'Invalid model object'
694             model_name = model.model
695         else:
696             model_name = model
697
698         # TransientModel records have no access rights, only an implicit access rule
699         if not self.pool.get(model_name):
700             _logger.error('Missing model %s' % (model_name, ))
701         elif self.pool.get(model_name).is_transient():
702             return True
703
704         # We check if a specific rule exists
705         cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
706                    '  FROM ir_model_access a '
707                    '  JOIN ir_model m ON (m.id = a.model_id) '
708                    '  JOIN res_groups_users_rel gu ON (gu.gid = a.group_id) '
709                    ' WHERE m.model = %s '
710                    '   AND gu.uid = %s '
711                    '   AND a.active IS True '
712                    , (model_name, uid,)
713                    )
714         r = cr.fetchone()[0]
715
716         if r is None:
717             # there is no specific rule. We check the generic rule
718             cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
719                        '  FROM ir_model_access a '
720                        '  JOIN ir_model m ON (m.id = a.model_id) '
721                        ' WHERE a.group_id IS NULL '
722                        '   AND m.model = %s '
723                        '   AND a.active IS True '
724                        , (model_name,)
725                        )
726             r = cr.fetchone()[0]
727
728         if not r and raise_exception:
729             groups = '\n\t'.join('- %s' % g for g in self.group_names_with_access(cr, model_name, mode))
730             msg_heads = {
731                 # Messages are declared in extenso so they are properly exported in translation terms
732                 'read': _("Sorry, you are not allowed to access this document."),
733                 'write':  _("Sorry, you are not allowed to modify this document."),
734                 'create': _("Sorry, you are not allowed to create this kind of document."),
735                 'unlink': _("Sorry, you are not allowed to delete this document."),
736             }
737             if groups:
738                 msg_tail = _("Only users with the following access level are currently allowed to do that") + ":\n%s\n\n(" + _("Document model") + ": %s)"
739                 msg_params = (groups, model_name)
740             else:
741                 msg_tail = _("Please contact your system administrator if you think this is an error.") + "\n\n(" + _("Document model") + ": %s)"
742                 msg_params = (model_name,)
743             _logger.warning('Access Denied by ACLs for operation: %s, uid: %s, model: %s', mode, uid, model_name)
744             msg = '%s %s' % (msg_heads[mode], msg_tail)
745             raise except_orm(_('Access Denied'), msg % msg_params)
746         return r or False
747
748     __cache_clearing_methods = []
749
750     def register_cache_clearing_method(self, model, method):
751         self.__cache_clearing_methods.append((model, method))
752
753     def unregister_cache_clearing_method(self, model, method):
754         try:
755             i = self.__cache_clearing_methods.index((model, method))
756             del self.__cache_clearing_methods[i]
757         except ValueError:
758             pass
759
760     def call_cache_clearing_methods(self, cr):
761         self.check.clear_cache(self)    # clear the cache of check function
762         for model, method in self.__cache_clearing_methods:
763             object_ = self.pool.get(model)
764             if object_:
765                 getattr(object_, method)()
766
767     #
768     # Check rights on actions
769     #
770     def write(self, cr, uid, *args, **argv):
771         self.call_cache_clearing_methods(cr)
772         res = super(ir_model_access, self).write(cr, uid, *args, **argv)
773         return res
774
775     def create(self, cr, uid, *args, **argv):
776         self.call_cache_clearing_methods(cr)
777         res = super(ir_model_access, self).create(cr, uid, *args, **argv)
778         return res
779
780     def unlink(self, cr, uid, *args, **argv):
781         self.call_cache_clearing_methods(cr)
782         res = super(ir_model_access, self).unlink(cr, uid, *args, **argv)
783         return res
784
785 class ir_model_data(osv.osv):
786     """Holds external identifier keys for records in the database.
787        This has two main uses:
788
789            * allows easy data integration with third-party systems,
790              making import/export/sync of data possible, as records
791              can be uniquely identified across multiple systems
792            * allows tracking the origin of data installed by OpenERP
793              modules themselves, thus making it possible to later
794              update them seamlessly.
795     """
796     _name = 'ir.model.data'
797     _order = 'module,model,name'
798     def _display_name_get(self, cr, uid, ids, prop, unknow_none, context=None):
799         result = {}
800         result2 = {}
801         for res in self.browse(cr, uid, ids, context=context):
802             if res.id:
803                 result.setdefault(res.model, {})
804                 result[res.model][res.res_id] = res.id
805             result2[res.id] = False
806
807         for model in result:
808             try:
809                 r = dict(self.pool.get(model).name_get(cr, uid, result[model].keys(), context=context))
810                 for key,val in result[model].items():
811                     result2[val] = r.get(key, False)
812             except:
813                 # some object have no valid name_get implemented, we accept this
814                 pass
815         return result2
816
817     def _complete_name_get(self, cr, uid, ids, prop, unknow_none, context=None):
818         result = {}
819         for res in self.browse(cr, uid, ids, context=context):
820             result[res.id] = (res.module and (res.module + '.') or '')+res.name
821         return result
822
823     _columns = {
824         'name': fields.char('External Identifier', required=True, size=128, select=1,
825                             help="External Key/Identifier that can be used for "
826                                  "data integration with third-party systems"),
827         'complete_name': fields.function(_complete_name_get, type='char', string='Complete ID'),
828         'display_name': fields.function(_display_name_get, type='char', string='Record Name'),
829         'model': fields.char('Model Name', required=True, size=64, select=1),
830         'module': fields.char('Module', required=True, size=64, select=1),
831         'res_id': fields.integer('Record ID', select=1,
832                                  help="ID of the target record in the database"),
833         'noupdate': fields.boolean('Non Updatable'),
834         'date_update': fields.datetime('Update Date'),
835         'date_init': fields.datetime('Init Date')
836     }
837     _defaults = {
838         'date_init': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
839         'date_update': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
840         'noupdate': False,
841         'module': ''
842     }
843     _sql_constraints = [
844         ('module_name_uniq', 'unique(name, module)', 'You cannot have multiple records with the same external ID in the same module!'),
845     ]
846
847     def __init__(self, pool, cr):
848         osv.osv.__init__(self, pool, cr)
849         self.doinit = True
850         # also stored in pool to avoid being discarded along with this osv instance
851         if getattr(pool, 'model_data_reference_ids', None) is None:
852             self.pool.model_data_reference_ids = {}
853
854         self.loads = self.pool.model_data_reference_ids
855
856     def _auto_init(self, cr, context=None):
857         super(ir_model_data, self)._auto_init(cr, context)
858         cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_model_data_module_name_index\'')
859         if not cr.fetchone():
860             cr.execute('CREATE INDEX ir_model_data_module_name_index ON ir_model_data (module, name)')
861
862     @tools.ormcache()
863     def _get_id(self, cr, uid, module, xml_id):
864         """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"""
865         ids = self.search(cr, uid, [('module','=',module), ('name','=', xml_id)])
866         if not ids:
867             raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
868         # the sql constraints ensure us we have only one result
869         return ids[0]
870
871     @tools.ormcache()
872     def get_object_reference(self, cr, uid, module, xml_id):
873         """Returns (model, res_id) corresponding to a given module and xml_id (cached) or raise ValueError if not found"""
874         data_id = self._get_id(cr, uid, module, xml_id)
875         res = self.read(cr, uid, data_id, ['model', 'res_id'])
876         if not res['res_id']:
877             raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
878         return res['model'], res['res_id']
879
880     def get_object(self, cr, uid, module, xml_id, context=None):
881         """Returns a browsable record for the given module name and xml_id or raise ValueError if not found"""
882         res_model, res_id = self.get_object_reference(cr, uid, module, xml_id)
883         result = self.pool.get(res_model).browse(cr, uid, res_id, context=context)
884         if not result.exists():
885             raise ValueError('No record found for unique ID %s.%s. It may have been deleted.' % (module, xml_id))
886         return result
887
888     def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
889         if not xml_id:
890             return False
891         try:
892             id = self.read(cr, uid, [self._get_id(cr, uid, module, xml_id)], ['res_id'])[0]['res_id']
893             self.loads[(module,xml_id)] = (model,id)
894         except:
895             id = False
896         return id
897
898     def clear_caches(self):
899         """ Clears all orm caches on the object's methods
900
901         :returns: itself
902         """
903         self._get_id.clear_cache(self)
904         self.get_object_reference.clear_cache(self)
905         return self
906
907     def unlink(self, cr, uid, ids, context=None):
908         """ Regular unlink method, but make sure to clear the caches. """
909         self.clear_caches()
910         return super(ir_model_data,self).unlink(cr, uid, ids, context=context)
911
912     def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None):
913         model_obj = self.pool.get(model)
914         if not context:
915             context = {}
916         # records created during module install should not display the messages of OpenChatter
917         context = dict(context, install_mode=True)
918         if xml_id and ('.' in xml_id):
919             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
920             module, xml_id = xml_id.split('.')
921         if (not xml_id) and (not self.doinit):
922             return False
923         action_id = False
924         if xml_id:
925             cr.execute('''SELECT imd.id, imd.res_id, md.id, imd.model
926                           FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
927                           WHERE imd.module=%%s AND imd.name=%%s''' % model_obj._table,
928                           (module, xml_id))
929             results = cr.fetchall()
930             for imd_id2,res_id2,real_id2,real_model in results:
931                 if not real_id2:
932                     self._get_id.clear_cache(self, uid, module, xml_id)
933                     self.get_object_reference.clear_cache(self, uid, module, xml_id)
934                     cr.execute('delete from ir_model_data where id=%s', (imd_id2,))
935                     res_id = False
936                 else:
937                     assert model == real_model, "External ID conflict, %s already refers to a `%s` record,"\
938                         " you can't define a `%s` record with this ID." % (xml_id, real_model, model)
939                     res_id,action_id = res_id2,imd_id2
940
941         if action_id and res_id:
942             model_obj.write(cr, uid, [res_id], values, context=context)
943             self.write(cr, uid, [action_id], {
944                 'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
945                 },context=context)
946         elif res_id:
947             model_obj.write(cr, uid, [res_id], values, context=context)
948             if xml_id:
949                 self.create(cr, uid, {
950                     'name': xml_id,
951                     'model': model,
952                     'module':module,
953                     'res_id':res_id,
954                     'noupdate': noupdate,
955                     },context=context)
956                 if model_obj._inherits:
957                     for table in model_obj._inherits:
958                         inherit_id = model_obj.browse(cr, uid,
959                                 res_id,context=context)[model_obj._inherits[table]]
960                         self.create(cr, uid, {
961                             'name': xml_id + '_' + table.replace('.', '_'),
962                             'model': table,
963                             'module': module,
964                             'res_id': inherit_id.id,
965                             'noupdate': noupdate,
966                             },context=context)
967         else:
968             if mode=='init' or (mode=='update' and xml_id):
969                 res_id = model_obj.create(cr, uid, values, context=context)
970                 if xml_id:
971                     self.create(cr, uid, {
972                         'name': xml_id,
973                         'model': model,
974                         'module': module,
975                         'res_id': res_id,
976                         'noupdate': noupdate
977                         },context=context)
978                     if model_obj._inherits:
979                         for table in model_obj._inherits:
980                             inherit_id = model_obj.browse(cr, uid,
981                                     res_id,context=context)[model_obj._inherits[table]]
982                             self.create(cr, uid, {
983                                 'name': xml_id + '_' + table.replace('.', '_'),
984                                 'model': table,
985                                 'module': module,
986                                 'res_id': inherit_id.id,
987                                 'noupdate': noupdate,
988                                 },context=context)
989         if xml_id and res_id:
990             self.loads[(module, xml_id)] = (model, res_id)
991             for table, inherit_field in model_obj._inherits.iteritems():
992                 inherit_id = model_obj.read(cr, uid, res_id,
993                         [inherit_field])[inherit_field]
994                 self.loads[(module, xml_id + '_' + table.replace('.', '_'))] = (table, inherit_id)
995         return res_id
996
997     def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None, xml_id=False):
998         if isinstance(models[0], (list, tuple)):
999             model,res_id = models[0]
1000         else:
1001             res_id=None
1002             model = models[0]
1003
1004         if res_id:
1005             where = ' and res_id=%s' % (res_id,)
1006         else:
1007             where = ' and (res_id is null)'
1008
1009         if key2:
1010             where += ' and key2=\'%s\'' % (key2,)
1011         else:
1012             where += ' and (key2 is null)'
1013
1014         cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
1015         res = cr.fetchone()
1016         if not res:
1017             ir_values_obj = pooler.get_pool(cr.dbname).get('ir.values')
1018             ir_values_obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
1019         elif xml_id:
1020             cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
1021         return True
1022
1023     def _module_data_uninstall(self, cr, uid, modules_to_remove, context=None):
1024         """Deletes all the records referenced by the ir.model.data entries
1025         ``ids`` along with their corresponding database backed (including
1026         dropping tables, columns, FKs, etc, as long as there is no other
1027         ir.model.data entry holding a reference to them (which indicates that
1028         they are still owned by another module). 
1029         Attempts to perform the deletion in an appropriate order to maximize
1030         the chance of gracefully deleting all records.
1031         This step is performed as part of the full uninstallation of a module.
1032         """ 
1033
1034         ids = self.search(cr, uid, [('module', 'in', modules_to_remove)])
1035
1036         if uid != 1 and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"):
1037             raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
1038
1039         context = dict(context or {})
1040         context[MODULE_UNINSTALL_FLAG] = True # enable model/field deletion
1041
1042         ids_set = set(ids)
1043         wkf_todo = []
1044         to_unlink = []
1045         ids.sort()
1046         ids.reverse()
1047         for data in self.browse(cr, uid, ids, context):
1048             model = data.model
1049             res_id = data.res_id
1050
1051             pair_to_unlink = (model, res_id)
1052             if pair_to_unlink not in to_unlink:
1053                 to_unlink.append(pair_to_unlink)
1054
1055             if model == 'workflow.activity':
1056                 # Special treatment for workflow activities: temporarily revert their
1057                 # incoming transition and trigger an update to force all workflow items
1058                 # to move out before deleting them
1059                 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,))
1060                 wkf_todo.extend(cr.fetchall())
1061                 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))
1062
1063         wf_service = netsvc.LocalService("workflow")
1064         for model,res_id in wkf_todo:
1065             try:
1066                 wf_service.trg_write(uid, model, res_id, cr)
1067             except Exception:
1068                 _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)
1069
1070         def unlink_if_refcount(to_unlink):
1071             for model, res_id in to_unlink:
1072                 external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
1073                 if set(external_ids)-ids_set:
1074                     # if other modules have defined this record, we must not delete it
1075                     continue
1076                 _logger.info('Deleting %s@%s', res_id, model)
1077                 try:
1078                     cr.execute('SAVEPOINT record_unlink_save')
1079                     self.pool.get(model).unlink(cr, uid, [res_id], context=context)
1080                 except Exception:
1081                     _logger.info('Unable to delete %s@%s', res_id, model, exc_info=True)
1082                     cr.execute('ROLLBACK TO SAVEPOINT record_unlink_save')
1083                 else:
1084                     cr.execute('RELEASE SAVEPOINT record_unlink_save')
1085
1086         # Remove non-model records first, then model fields, and finish with models
1087         unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1088                                 if model not in ('ir.model','ir.model.fields'))
1089         unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1090                                 if model == 'ir.model.fields')
1091
1092         ir_model_relation = self.pool.get('ir.model.relation')
1093         ir_module_module = self.pool.get('ir.module.module')
1094         modules_to_remove_ids = ir_module_module.search(cr, uid, [('name', 'in', modules_to_remove)])
1095         relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove_ids)])
1096         ir_model_relation._module_data_uninstall(cr, uid, relation_ids, context)
1097
1098         unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1099                                 if model == 'ir.model')
1100
1101         cr.commit()
1102
1103         self.unlink(cr, uid, ids, context)
1104
1105     def _process_end(self, cr, uid, modules):
1106         """ Clear records removed from updated module data.
1107         This method is called at the end of the module loading process.
1108         It is meant to removed records that are no longer present in the
1109         updated data. Such records are recognised as the one with an xml id
1110         and a module in ir_model_data and noupdate set to false, but not
1111         present in self.loads.
1112         """
1113         if not modules:
1114             return True
1115         to_unlink = []
1116         cr.execute("""SELECT id,name,model,res_id,module FROM ir_model_data
1117                       WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s
1118                       ORDER BY id DESC""",
1119                       (tuple(modules), False))
1120         for (id, name, model, res_id, module) in cr.fetchall():
1121             if (module,name) not in self.loads:
1122                 to_unlink.append((model,res_id))
1123         if not config.get('import_partial'):
1124             for (model, res_id) in to_unlink:
1125                 if self.pool.get(model):
1126                     _logger.info('Deleting %s@%s', res_id, model)
1127                     self.pool.get(model).unlink(cr, uid, [res_id])
1128
1129 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: