[FIX] base: ir.model.data.name_get() respect expected behavior.
[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-2014 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 from collections import defaultdict
23 import logging
24 import re
25 import time
26 import types
27
28 import openerp
29 import openerp.modules.registry
30 from openerp import SUPERUSER_ID
31 from openerp import tools
32 from openerp.osv import fields, osv
33 from openerp.osv.orm import BaseModel, Model, MAGIC_COLUMNS, except_orm
34 from openerp.tools import config
35 from openerp.tools.safe_eval import safe_eval as eval
36 from openerp.tools.translate import _
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["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 model.model in self.pool:
75                 res[model.id] = self.pool[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["ir.ui.view"].search(cr, uid, [('model', '=', model.model)])
96         return res
97
98     _columns = {
99         'name': fields.char('Model Description', translate=True, required=True),
100         'model': fields.char('Model', required=True, select=1),
101         'info': fields.text('Information'),
102         'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields', required=True, copy=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', 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[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 CASCADE' % (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             openerp.modules.registry.RegistryManager.new(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 = dict(context)
181             context.pop('__last_update', None)
182         # Filter out operations 4 link from field id, because openerp-web
183         # always write (4,id,False) even for non dirty items
184         if 'field_id' in vals:
185             vals['field_id'] = [op for op in vals['field_id'] if op[0] != 4]
186         return super(ir_model,self).write(cr, user, ids, vals, context)
187
188     def create(self, cr, user, vals, context=None):
189         if  context is None:
190             context = {}
191         if context and context.get('manual'):
192             vals['state']='manual'
193         res = super(ir_model,self).create(cr, user, vals, context)
194         if vals.get('state','base')=='manual':
195             self.instanciate(cr, user, vals['model'], context)
196             model = self.pool[vals['model']]
197             model._prepare_setup_fields(cr, SUPERUSER_ID)
198             model._setup_fields(cr, SUPERUSER_ID)
199             ctx = dict(context,
200                 field_name=vals['name'],
201                 field_state='manual',
202                 select=vals.get('select_level', '0'),
203                 update_custom_fields=True)
204             model._auto_init(cr, ctx)
205             model._auto_end(cr, ctx) # actually create FKs!
206             openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
207         return res
208
209     def instanciate(self, cr, user, model, context=None):
210         class x_custom_model(osv.osv):
211             _custom = True
212         if isinstance(model, unicode):
213             model = model.encode('utf-8')
214         x_custom_model._name = model
215         x_custom_model._module = False
216         a = x_custom_model._build_model(self.pool, cr)
217         if not a._columns:
218             x_name = 'id'
219         elif 'x_name' in a._columns.keys():
220             x_name = 'x_name'
221         else:
222             x_name = a._columns.keys()[0]
223         x_custom_model._rec_name = x_name
224         a._rec_name = x_name
225
226 class ir_model_fields(osv.osv):
227     _name = 'ir.model.fields'
228     _description = "Fields"
229     _rec_name = 'field_description'
230
231     _columns = {
232         'name': fields.char('Name', required=True, select=1),
233         'complete_name': fields.char('Complete Name', select=1),
234         'model': fields.char('Object Name', required=True, select=1,
235             help="The technical name of the model this field belongs to"),
236         'relation': fields.char('Object Relation',
237             help="For relationship fields, the technical name of the target model"),
238         'relation_field': fields.char('Relation Field',
239             help="For one2many fields, the field on the target model that implement the opposite many2one relationship"),
240         'model_id': fields.many2one('ir.model', 'Model', required=True, select=True, ondelete='cascade',
241             help="The model this field belongs to"),
242         'field_description': fields.char('Field Label', required=True),
243         'ttype': fields.selection(_get_fields_type, 'Field Type', required=True),
244         'selection': fields.char('Selection Options', help="List of options for a selection field, "
245             "specified as a Python expression defining a list of (key, label) pairs. "
246             "For example: [('blue','Blue'),('yellow','Yellow')]"),
247         'required': fields.boolean('Required'),
248         'readonly': fields.boolean('Readonly'),
249         'select_level': fields.selection([('0','Not Searchable'),('1','Always Searchable'),('2','Advanced Search (deprecated)')],'Searchable', required=True),
250         'translate': fields.boolean('Translatable', help="Whether values for this field can be translated (enables the translation mechanism for that field)"),
251         'size': fields.integer('Size'),
252         'state': fields.selection([('manual','Custom Field'),('base','Base Field')],'Type', required=True, readonly=True, select=1),
253         'on_delete': fields.selection([('cascade','Cascade'),('set null','Set NULL')], 'On Delete', help='On delete property for many2one fields'),
254         'domain': fields.char('Domain', help="The optional domain to restrict possible values for relationship fields, "
255             "specified as a Python expression defining a list of triplets. "
256             "For example: [('color','=','red')]"),
257         'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'),
258         'selectable': fields.boolean('Selectable'),
259         'modules': fields.function(_in_modules, type='char', string='In Modules', help='List of modules in which the field is defined'),
260         'serialization_field_id': fields.many2one('ir.model.fields', 'Serialization Field', domain = "[('ttype','=','serialized')]",
261                                                   ondelete='cascade', help="If set, this field will be stored in the sparse "
262                                                                            "structure of the serialization field, instead "
263                                                                            "of having its own database column. This cannot be "
264                                                                            "changed after creation."),
265     }
266     _rec_name='field_description'
267     _defaults = {
268         'selection': "",
269         'domain': "[]",
270         'name': 'x_',
271         'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
272         'on_delete': 'set null',
273         'select_level': '0',
274         'field_description': '',
275         'selectable': 1,
276     }
277     _order = "name"
278
279     def _check_selection(self, cr, uid, selection, context=None):
280         try:
281             selection_list = eval(selection)
282         except Exception:
283             _logger.warning('Invalid selection list definition for fields.selection', exc_info=True)
284             raise except_orm(_('Error'),
285                     _("The Selection Options expression is not a valid Pythonic expression."
286                       "Please provide an expression in the [('key','Label'), ...] format."))
287
288         check = True
289         if not (isinstance(selection_list, list) and selection_list):
290             check = False
291         else:
292             for item in selection_list:
293                 if not (isinstance(item, (tuple,list)) and len(item) == 2):
294                     check = False
295                     break
296
297         if not check:
298                 raise except_orm(_('Error'),
299                     _("The Selection Options expression is must be in the [('key','Label'), ...] format!"))
300         return True
301
302     def _size_gt_zero_msg(self, cr, user, ids, context=None):
303         return _('Size of the field can never be less than 0 !')
304
305     _sql_constraints = [
306         ('size_gt_zero', 'CHECK (size>=0)',_size_gt_zero_msg ),
307     ]
308
309     def _drop_column(self, cr, uid, ids, context=None):
310         for field in self.browse(cr, uid, ids, context):
311             if field.name in MAGIC_COLUMNS:
312                 continue
313             model = self.pool[field.model]
314             cr.execute('select relkind from pg_class where relname=%s', (model._table,))
315             result = cr.fetchone()
316             cr.execute("SELECT column_name FROM information_schema.columns WHERE table_name ='%s' and column_name='%s'" %(model._table, field.name))
317             column_name = cr.fetchone()
318             if column_name and (result and result[0] == 'r'):
319                 cr.execute('ALTER table "%s" DROP column "%s" cascade' % (model._table, field.name))
320             model._columns.pop(field.name, None)
321
322             # remove m2m relation table for custom fields
323             # we consider the m2m relation is only one way as it's not possible
324             # to specify the relation table in the interface for custom fields
325             # TODO master: maybe use ir.model.relations for custom fields
326             if field.state == 'manual' and field.ttype == 'many2many':
327                 rel_name = self.pool[field.model]._all_columns[field.name].column._rel
328                 cr.execute('DROP table "%s"' % (rel_name))
329         return True
330
331     def unlink(self, cr, user, ids, context=None):
332         # Prevent manual deletion of module columns
333         if context is None: context = {}
334         if isinstance(ids, (int, long)):
335             ids = [ids]
336         if not context.get(MODULE_UNINSTALL_FLAG) and \
337                 any(field.state != 'manual' for field in self.browse(cr, user, ids, context)):
338             raise except_orm(_('Error'), _("This column contains module data and cannot be removed!"))
339
340         self._drop_column(cr, user, ids, context)
341         res = super(ir_model_fields, self).unlink(cr, user, ids, context)
342         if not context.get(MODULE_UNINSTALL_FLAG):
343             cr.commit()
344             openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
345         return res
346
347     def create(self, cr, user, vals, context=None):
348         if 'model_id' in vals:
349             model_data = self.pool['ir.model'].browse(cr, user, vals['model_id'])
350             vals['model'] = model_data.model
351         if context is None:
352             context = {}
353         if context and context.get('manual',False):
354             vals['state'] = 'manual'
355         if vals.get('ttype', False) == 'selection':
356             if not vals.get('selection',False):
357                 raise except_orm(_('Error'), _('For selection fields, the Selection Options must be given!'))
358             self._check_selection(cr, user, vals['selection'], context=context)
359         res = super(ir_model_fields,self).create(cr, user, vals, context)
360         if vals.get('state','base') == 'manual':
361             if not vals['name'].startswith('x_'):
362                 raise except_orm(_('Error'), _("Custom fields must have a name that starts with 'x_' !"))
363
364             if vals.get('relation',False) and not self.pool['ir.model'].search(cr, user, [('model','=',vals['relation'])]):
365                 raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
366
367             if vals['model'] in self.pool:
368                 model = self.pool[vals['model']]
369                 if vals['model'].startswith('x_') and vals['name'] == 'x_name':
370                     model._rec_name = 'x_name'
371
372                 if self.pool.fields_by_model is not None:
373                     cr.execute('SELECT * FROM ir_model_fields WHERE id=%s', (res,))
374                     self.pool.fields_by_model.setdefault(vals['model'], []).append(cr.dictfetchone())
375                 model.__init__(self.pool, cr)
376                 model._prepare_setup_fields(cr, SUPERUSER_ID)
377                 model._setup_fields(cr, SUPERUSER_ID)
378
379                 #Added context to _auto_init for special treatment to custom field for select_level
380                 ctx = dict(context,
381                     field_name=vals['name'],
382                     field_state='manual',
383                     select=vals.get('select_level', '0'),
384                     update_custom_fields=True)
385                 model._auto_init(cr, ctx)
386                 model._auto_end(cr, ctx) # actually create FKs!
387                 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
388
389         return res
390
391     def write(self, cr, user, ids, vals, context=None):
392         if context is None:
393             context = {}
394         if context and context.get('manual',False):
395             vals['state'] = 'manual'
396
397         #For the moment renaming a sparse field or changing the storing system is not allowed. This may be done later
398         if 'serialization_field_id' in vals or 'name' in vals:
399             for field in self.browse(cr, user, ids, context=context):
400                 if 'serialization_field_id' in vals and field.serialization_field_id.id != vals['serialization_field_id']:
401                     raise except_orm(_('Error!'),  _('Changing the storing system for field "%s" is not allowed.')%field.name)
402                 if field.serialization_field_id and (field.name != vals['name']):
403                     raise except_orm(_('Error!'),  _('Renaming sparse field "%s" is not allowed')%field.name)
404
405         column_rename = None # if set, *one* column can be renamed here
406         models_patch = {}    # structs of (obj, [(field, prop, change_to),..])
407                              # data to be updated on the orm model
408
409         # static table of properties
410         model_props = [ # (our-name, fields.prop, set_fn)
411             ('field_description', 'string', tools.ustr),
412             ('required', 'required', bool),
413             ('readonly', 'readonly', bool),
414             ('domain', '_domain', eval),
415             ('size', 'size', int),
416             ('on_delete', 'ondelete', str),
417             ('translate', 'translate', bool),
418             ('selectable', 'selectable', bool),
419             ('select_level', 'select', int),
420             ('selection', 'selection', eval),
421             ]
422
423         if vals and ids:
424             checked_selection = False # need only check it once, so defer
425
426             for item in self.browse(cr, user, ids, context=context):
427                 obj = self.pool.get(item.model)
428
429                 if item.state != 'manual':
430                     raise except_orm(_('Error!'),
431                         _('Properties of base fields cannot be altered in this manner! '
432                           'Please modify them through Python code, '
433                           'preferably through a custom addon!'))
434
435                 if item.ttype == 'selection' and 'selection' in vals \
436                         and not checked_selection:
437                     self._check_selection(cr, user, vals['selection'], context=context)
438                     checked_selection = True
439
440                 final_name = item.name
441                 if 'name' in vals and vals['name'] != item.name:
442                     # We need to rename the column
443                     if column_rename:
444                         raise except_orm(_('Error!'), _('Can only rename one column at a time!'))
445                     if vals['name'] in obj._columns:
446                         raise except_orm(_('Error!'), _('Cannot rename column to %s, because that column already exists!') % vals['name'])
447                     if vals.get('state', 'base') == 'manual' and not vals['name'].startswith('x_'):
448                         raise except_orm(_('Error!'), _('New column name must still start with x_ , because it is a custom field!'))
449                     if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']:
450                         raise ValueError('Invalid character in column name')
451                     column_rename = (obj, (obj._table, item.name, vals['name']))
452                     final_name = vals['name']
453
454                 if 'model_id' in vals and vals['model_id'] != item.model_id.id:
455                     raise except_orm(_("Error!"), _("Changing the model of a field is forbidden!"))
456
457                 if 'ttype' in vals and vals['ttype'] != item.ttype:
458                     raise except_orm(_("Error!"), _("Changing the type of a column is not yet supported. "
459                                 "Please drop it and create it again!"))
460
461                 # We don't check the 'state', because it might come from the context
462                 # (thus be set for multiple fields) and will be ignored anyway.
463                 if obj is not None:
464                     models_patch.setdefault(obj._name, (obj,[]))
465                     # find out which properties (per model) we need to update
466                     for field_name, field_property, set_fn in model_props:
467                         if field_name in vals:
468                             property_value = set_fn(vals[field_name])
469                             if getattr(obj._columns[item.name], field_property) != property_value:
470                                 models_patch[obj._name][1].append((final_name, field_property, property_value))
471                         # our dict is ready here, but no properties are changed so far
472
473         # These shall never be written (modified)
474         for column_name in ('model_id', 'model', 'state'):
475             if column_name in vals:
476                 del vals[column_name]
477
478         res = super(ir_model_fields,self).write(cr, user, ids, vals, context=context)
479
480         if column_rename:
481             cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % column_rename[1])
482             # This is VERY risky, but let us have this feature:
483             # we want to change the key of column in obj._columns dict
484             col = column_rename[0]._columns.pop(column_rename[1][1]) # take object out, w/o copy
485             column_rename[0]._columns[column_rename[1][2]] = col
486
487         if models_patch:
488             # We have to update _columns of the model(s) and then call their
489             # _auto_init to sync the db with the model. Hopefully, since write()
490             # was called earlier, they will be in-sync before the _auto_init.
491             # Anything we don't update in _columns now will be reset from
492             # the model into ir.model.fields (db).
493             ctx = dict(context, select=vals.get('select_level', '0'),
494                        update_custom_fields=True)
495
496             for __, patch_struct in models_patch.items():
497                 obj = patch_struct[0]
498                 for col_name, col_prop, val in patch_struct[1]:
499                     setattr(obj._columns[col_name], col_prop, val)
500                 obj._auto_init(cr, ctx)
501                 obj._auto_end(cr, ctx) # actually create FKs!
502             openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
503         return res
504
505 class ir_model_constraint(Model):
506     """
507     This model tracks PostgreSQL foreign keys and constraints used by OpenERP
508     models.
509     """
510     _name = 'ir.model.constraint'
511     _columns = {
512         'name': fields.char('Constraint', required=True, select=1,
513             help="PostgreSQL constraint or foreign key name."),
514         'model': fields.many2one('ir.model', string='Model',
515             required=True, select=1),
516         'module': fields.many2one('ir.module.module', string='Module',
517             required=True, select=1),
518         'type': fields.char('Constraint Type', required=True, size=1, select=1,
519             help="Type of the constraint: `f` for a foreign key, "
520                 "`u` for other constraints."),
521         'date_update': fields.datetime('Update Date'),
522         'date_init': fields.datetime('Initialization Date')
523     }
524
525     _sql_constraints = [
526         ('module_name_uniq', 'unique(name, module)',
527             'Constraints with the same name are unique per module.'),
528     ]
529
530     def _module_data_uninstall(self, cr, uid, ids, context=None):
531         """
532         Delete PostgreSQL foreign keys and constraints tracked by this model.
533         """ 
534
535         if uid != SUPERUSER_ID and not self.pool['ir.model.access'].check_groups(cr, uid, "base.group_system"):
536             raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
537
538         context = dict(context or {})
539
540         ids_set = set(ids)
541         ids.sort()
542         ids.reverse()
543         for data in self.browse(cr, uid, ids, context):
544             model = data.model.model
545             model_obj = self.pool[model]
546             name = openerp.tools.ustr(data.name)
547             typ = data.type
548
549             # double-check we are really going to delete all the owners of this schema element
550             cr.execute("""SELECT id from ir_model_constraint where name=%s""", (data.name,))
551             external_ids = [x[0] for x in cr.fetchall()]
552             if set(external_ids)-ids_set:
553                 # as installed modules have defined this element we must not delete it!
554                 continue
555
556             if typ == 'f':
557                 # test if FK exists on this table (it could be on a related m2m table, in which case we ignore it)
558                 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
559                               WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_obj._table))
560                 if cr.fetchone():
561                     cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
562                     _logger.info('Dropped FK CONSTRAINT %s@%s', name, model)
563
564             if typ == 'u':
565                 # test if constraint exists
566                 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
567                               WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_obj._table))
568                 if cr.fetchone():
569                     cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
570                     _logger.info('Dropped CONSTRAINT %s@%s', name, model)
571
572         self.unlink(cr, uid, ids, context)
573
574 class ir_model_relation(Model):
575     """
576     This model tracks PostgreSQL tables used to implement OpenERP many2many
577     relations.
578     """
579     _name = 'ir.model.relation'
580     _columns = {
581         'name': fields.char('Relation Name', required=True, select=1,
582             help="PostgreSQL table name implementing a many2many relation."),
583         'model': fields.many2one('ir.model', string='Model',
584             required=True, select=1),
585         'module': fields.many2one('ir.module.module', string='Module',
586             required=True, select=1),
587         'date_update': fields.datetime('Update Date'),
588         'date_init': fields.datetime('Initialization Date')
589     }
590
591     def _module_data_uninstall(self, cr, uid, ids, context=None):
592         """
593         Delete PostgreSQL many2many relations tracked by this model.
594         """ 
595
596         if uid != SUPERUSER_ID and not self.pool['ir.model.access'].check_groups(cr, uid, "base.group_system"):
597             raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
598
599         ids_set = set(ids)
600         to_drop_table = []
601         ids.sort()
602         ids.reverse()
603         for data in self.browse(cr, uid, ids, context):
604             model = data.model
605             name = openerp.tools.ustr(data.name)
606
607             # double-check we are really going to delete all the owners of this schema element
608             cr.execute("""SELECT id from ir_model_relation where name = %s""", (data.name,))
609             external_ids = [x[0] for x in cr.fetchall()]
610             if set(external_ids)-ids_set:
611                 # as installed modules have defined this element we must not delete it!
612                 continue
613
614             cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name=%s", (name,))
615             if cr.fetchone() and not name in to_drop_table:
616                 to_drop_table.append(name)
617
618         self.unlink(cr, uid, ids, context)
619
620         # drop m2m relation tables
621         for table in to_drop_table:
622             cr.execute('DROP TABLE %s CASCADE'% table,)
623             _logger.info('Dropped table %s', table)
624
625         cr.commit()
626
627 class ir_model_access(osv.osv):
628     _name = 'ir.model.access'
629     _columns = {
630         'name': fields.char('Name', required=True, select=True),
631         '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.'),
632         'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)], select=True, ondelete='cascade'),
633         'group_id': fields.many2one('res.groups', 'Group', ondelete='cascade', select=True),
634         'perm_read': fields.boolean('Read Access'),
635         'perm_write': fields.boolean('Write Access'),
636         'perm_create': fields.boolean('Create Access'),
637         'perm_unlink': fields.boolean('Delete Access'),
638     }
639     _defaults = {
640         'active': True,
641     }
642
643     def check_groups(self, cr, uid, group):
644         grouparr  = group.split('.')
645         if not grouparr:
646             return False
647         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],))
648         return bool(cr.fetchone())
649
650     def check_group(self, cr, uid, model, mode, group_ids):
651         """ Check if a specific group has the access mode to the specified model"""
652         assert mode in ['read','write','create','unlink'], 'Invalid access mode'
653
654         if isinstance(model, BaseModel):
655             assert model._name == 'ir.model', 'Invalid model object'
656             model_name = model.name
657         else:
658             model_name = model
659
660         if isinstance(group_ids, (int, long)):
661             group_ids = [group_ids]
662         for group_id in group_ids:
663             cr.execute("SELECT perm_" + mode + " "
664                    "  FROM ir_model_access a "
665                    "  JOIN ir_model m ON (m.id = a.model_id) "
666                    " WHERE m.model = %s AND a.active IS True "
667                    " AND a.group_id = %s", (model_name, group_id)
668                    )
669             r = cr.fetchone()
670             if r is None:
671                 cr.execute("SELECT perm_" + mode + " "
672                        "  FROM ir_model_access a "
673                        "  JOIN ir_model m ON (m.id = a.model_id) "
674                        " WHERE m.model = %s AND a.active IS True "
675                        " AND a.group_id IS NULL", (model_name, )
676                        )
677                 r = cr.fetchone()
678
679             access = bool(r and r[0])
680             if access:
681                 return True
682         # pass no groups -> no access
683         return False
684
685     def group_names_with_access(self, cr, model_name, access_mode):
686         """Returns the names of visible groups which have been granted ``access_mode`` on
687            the model ``model_name``.
688            :rtype: list
689         """
690         assert access_mode in ['read','write','create','unlink'], 'Invalid access mode: %s' % access_mode
691         cr.execute('''SELECT
692                         c.name, g.name
693                       FROM
694                         ir_model_access a
695                         JOIN ir_model m ON (a.model_id=m.id)
696                         JOIN res_groups g ON (a.group_id=g.id)
697                         LEFT JOIN ir_module_category c ON (c.id=g.category_id)
698                       WHERE
699                         m.model=%s AND
700                         a.active IS True AND
701                         a.perm_''' + access_mode, (model_name,))
702         return [('%s/%s' % x) if x[0] else x[1] for x in cr.fetchall()]
703
704     @tools.ormcache()
705     def check(self, cr, uid, model, mode='read', raise_exception=True, context=None):
706         if uid==1:
707             # User root have all accesses
708             # TODO: exclude xml-rpc requests
709             return True
710
711         assert mode in ['read','write','create','unlink'], 'Invalid access mode'
712
713         if isinstance(model, BaseModel):
714             assert model._name == 'ir.model', 'Invalid model object'
715             model_name = model.model
716         else:
717             model_name = model
718
719         # TransientModel records have no access rights, only an implicit access rule
720         if model_name not in self.pool:
721             _logger.error('Missing model %s' % (model_name, ))
722         elif self.pool[model_name].is_transient():
723             return True
724
725         # We check if a specific rule exists
726         cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
727                    '  FROM ir_model_access a '
728                    '  JOIN ir_model m ON (m.id = a.model_id) '
729                    '  JOIN res_groups_users_rel gu ON (gu.gid = a.group_id) '
730                    ' WHERE m.model = %s '
731                    '   AND gu.uid = %s '
732                    '   AND a.active IS True '
733                    , (model_name, uid,)
734                    )
735         r = cr.fetchone()[0]
736
737         if r is None:
738             # there is no specific rule. We check the generic rule
739             cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
740                        '  FROM ir_model_access a '
741                        '  JOIN ir_model m ON (m.id = a.model_id) '
742                        ' WHERE a.group_id IS NULL '
743                        '   AND m.model = %s '
744                        '   AND a.active IS True '
745                        , (model_name,)
746                        )
747             r = cr.fetchone()[0]
748
749         if not r and raise_exception:
750             groups = '\n\t'.join('- %s' % g for g in self.group_names_with_access(cr, model_name, mode))
751             msg_heads = {
752                 # Messages are declared in extenso so they are properly exported in translation terms
753                 'read': _("Sorry, you are not allowed to access this document."),
754                 'write':  _("Sorry, you are not allowed to modify this document."),
755                 'create': _("Sorry, you are not allowed to create this kind of document."),
756                 'unlink': _("Sorry, you are not allowed to delete this document."),
757             }
758             if groups:
759                 msg_tail = _("Only users with the following access level are currently allowed to do that") + ":\n%s\n\n(" + _("Document model") + ": %s)"
760                 msg_params = (groups, model_name)
761             else:
762                 msg_tail = _("Please contact your system administrator if you think this is an error.") + "\n\n(" + _("Document model") + ": %s)"
763                 msg_params = (model_name,)
764             _logger.warning('Access Denied by ACLs for operation: %s, uid: %s, model: %s', mode, uid, model_name)
765             msg = '%s %s' % (msg_heads[mode], msg_tail)
766             raise openerp.exceptions.AccessError(msg % msg_params)
767         return r or False
768
769     __cache_clearing_methods = []
770
771     def register_cache_clearing_method(self, model, method):
772         self.__cache_clearing_methods.append((model, method))
773
774     def unregister_cache_clearing_method(self, model, method):
775         try:
776             i = self.__cache_clearing_methods.index((model, method))
777             del self.__cache_clearing_methods[i]
778         except ValueError:
779             pass
780
781     def call_cache_clearing_methods(self, cr):
782         self.invalidate_cache(cr, SUPERUSER_ID)
783         self.check.clear_cache(self)    # clear the cache of check function
784         for model, method in self.__cache_clearing_methods:
785             if model in self.pool:
786                 getattr(self.pool[model], method)()
787
788     #
789     # Check rights on actions
790     #
791     def write(self, cr, uid, ids, values, context=None):
792         self.call_cache_clearing_methods(cr)
793         res = super(ir_model_access, self).write(cr, uid, ids, values, context=context)
794         return res
795
796     def create(self, cr, uid, values, context=None):
797         self.call_cache_clearing_methods(cr)
798         res = super(ir_model_access, self).create(cr, uid, values, context=context)
799         return res
800
801     def unlink(self, cr, uid, ids, context=None):
802         self.call_cache_clearing_methods(cr)
803         res = super(ir_model_access, self).unlink(cr, uid, ids, context=context)
804         return res
805
806 class ir_model_data(osv.osv):
807     """Holds external identifier keys for records in the database.
808        This has two main uses:
809
810            * allows easy data integration with third-party systems,
811              making import/export/sync of data possible, as records
812              can be uniquely identified across multiple systems
813            * allows tracking the origin of data installed by OpenERP
814              modules themselves, thus making it possible to later
815              update them seamlessly.
816     """
817     _name = 'ir.model.data'
818     _order = 'module,model,name'
819
820     def name_get(self, cr, uid, ids, context=None):
821         bymodel = defaultdict(dict)
822         names = {}
823
824         for res in self.browse(cr, uid, ids, context=context):
825             bymodel[res.model][res.res_id] = res
826             names[res.id] = res.complete_name
827             #result[res.model][res.res_id] = res.id
828
829         for model, id_map in bymodel.iteritems():
830             try:
831                 ng = dict(self.pool[model].name_get(cr, uid, id_map.keys(), context=context))
832             except Exception:
833                 pass
834             else:
835                 for r in id_map.itervalues():
836                     names[r.id] = ng.get(r.res_id, r.complete_name)
837
838         return [(i, names[i]) for i in ids]
839
840     def _complete_name_get(self, cr, uid, ids, prop, unknow_none, context=None):
841         result = {}
842         for res in self.browse(cr, uid, ids, context=context):
843             result[res.id] = (res.module and (res.module + '.') or '')+res.name
844         return result
845
846     _columns = {
847         'name': fields.char('External Identifier', required=True, select=1,
848                             help="External Key/Identifier that can be used for "
849                                  "data integration with third-party systems"),
850         'complete_name': fields.function(_complete_name_get, type='char', string='Complete ID'),
851         'model': fields.char('Model Name', required=True, select=1),
852         'module': fields.char('Module', required=True, select=1),
853         'res_id': fields.integer('Record ID', select=1,
854                                  help="ID of the target record in the database"),
855         'noupdate': fields.boolean('Non Updatable'),
856         'date_update': fields.datetime('Update Date'),
857         'date_init': fields.datetime('Init Date')
858     }
859     _defaults = {
860         'date_init': fields.datetime.now,
861         'date_update': fields.datetime.now,
862         'noupdate': False,
863         'module': ''
864     }
865     _sql_constraints = [
866         ('module_name_uniq', 'unique(name, module)', 'You cannot have multiple records with the same external ID in the same module!'),
867     ]
868
869     def __init__(self, pool, cr):
870         osv.osv.__init__(self, pool, cr)
871         # also stored in pool to avoid being discarded along with this osv instance
872         if getattr(pool, 'model_data_reference_ids', None) is None:
873             self.pool.model_data_reference_ids = {}
874         # put loads on the class, in order to share it among all instances
875         type(self).loads = self.pool.model_data_reference_ids
876
877     def _auto_init(self, cr, context=None):
878         super(ir_model_data, self)._auto_init(cr, context)
879         cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_model_data_module_name_index\'')
880         if not cr.fetchone():
881             cr.execute('CREATE INDEX ir_model_data_module_name_index ON ir_model_data (module, name)')
882
883     # NEW V8 API
884     @tools.ormcache(skiparg=3)
885     def xmlid_lookup(self, cr, uid, xmlid):
886         """Low level xmlid lookup
887         Return (id, res_model, res_id) or raise ValueError if not found
888         """
889         module, name = xmlid.split('.', 1)
890         ids = self.search(cr, uid, [('module','=',module), ('name','=', name)])
891         if not ids:
892             raise ValueError('External ID not found in the system: %s' % (xmlid))
893         # the sql constraints ensure us we have only one result
894         res = self.read(cr, uid, ids[0], ['model', 'res_id'])
895         if not res['res_id']:
896             raise ValueError('External ID not found in the system: %s' % (xmlid))
897         return ids[0], res['model'], res['res_id']
898     
899     def xmlid_to_res_model_res_id(self, cr, uid, xmlid, raise_if_not_found=False):
900         """ Return (res_model, res_id)"""
901         try:
902             return self.xmlid_lookup(cr, uid, xmlid)[1:3]
903         except ValueError:
904             if raise_if_not_found:
905                 raise
906             return (False, False)
907
908     def xmlid_to_res_id(self, cr, uid, xmlid, raise_if_not_found=False):
909         """ Returns res_id """
910         return self.xmlid_to_res_model_res_id(cr, uid, xmlid, raise_if_not_found)[1]
911
912     def xmlid_to_object(self, cr, uid, xmlid, raise_if_not_found=False, context=None):
913         """ Return a browse_record
914         if not found and raise_if_not_found is True return None
915         """ 
916         t = self.xmlid_to_res_model_res_id(cr, uid, xmlid, raise_if_not_found)
917         res_model, res_id = t
918
919         if res_model and res_id:
920             record = self.pool[res_model].browse(cr, uid, res_id, context=context)
921             if record.exists():
922                 return record
923             if raise_if_not_found:
924                 raise ValueError('No record found for unique ID %s. It may have been deleted.' % (xmlid))
925         return None
926
927     # OLD API
928     def _get_id(self, cr, uid, module, xml_id):
929         """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"""
930         return self.xmlid_lookup(cr, uid, "%s.%s" % (module, xml_id))[0]
931
932     def get_object_reference(self, cr, uid, module, xml_id):
933         """Returns (model, res_id) corresponding to a given module and xml_id (cached) or raise ValueError if not found"""
934         return self.xmlid_lookup(cr, uid, "%s.%s" % (module, xml_id))[1:3]
935
936     def check_object_reference(self, cr, uid, module, xml_id, raise_on_access_error=False):
937         """Returns (model, res_id) corresponding to a given module and xml_id (cached), if and only if the user has the necessary access rights
938         to see that object, otherwise raise a ValueError if raise_on_access_error is True or returns a tuple (model found, False)"""
939         model, res_id = self.get_object_reference(cr, uid, module, xml_id)
940         #search on id found in result to check if current user has read access right
941         check_right = self.pool.get(model).search(cr, uid, [('id', '=', res_id)])
942         if check_right:
943             return model, res_id
944         if raise_on_access_error:
945             raise ValueError('Not enough access rights on the external ID: %s.%s' % (module, xml_id))
946         return model, False
947
948     def get_object(self, cr, uid, module, xml_id, context=None):
949         """ Returns a browsable record for the given module name and xml_id.
950             If not found, raise a ValueError or return None, depending
951             on the value of `raise_exception`.
952         """
953         return self.xmlid_to_object(cr, uid, "%s.%s" % (module, xml_id), raise_if_not_found=True, context=context)
954
955     def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
956         if not xml_id:
957             return False
958         try:
959             id = self.read(cr, uid, [self._get_id(cr, uid, module, xml_id)], ['res_id'])[0]['res_id']
960             self.loads[(module,xml_id)] = (model,id)
961         except:
962             id = False
963         return id
964
965     def clear_caches(self):
966         """ Clears all orm caches on the object's methods
967
968         :returns: itself
969         """
970         self.xmlid_lookup.clear_cache(self)
971         return self
972
973     def unlink(self, cr, uid, ids, context=None):
974         """ Regular unlink method, but make sure to clear the caches. """
975         self.clear_caches()
976         return super(ir_model_data,self).unlink(cr, uid, ids, context=context)
977
978     def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None):
979         model_obj = self.pool[model]
980         if not context:
981             context = {}
982         # records created during module install should not display the messages of OpenChatter
983         context = dict(context, install_mode=True)
984         if xml_id and ('.' in xml_id):
985             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
986             module, xml_id = xml_id.split('.')
987         action_id = False
988         if xml_id:
989             cr.execute('''SELECT imd.id, imd.res_id, md.id, imd.model, imd.noupdate
990                           FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
991                           WHERE imd.module=%%s AND imd.name=%%s''' % model_obj._table,
992                           (module, xml_id))
993             results = cr.fetchall()
994             for imd_id2,res_id2,real_id2,real_model,noupdate_imd in results:
995                 # In update mode, do not update a record if it's ir.model.data is flagged as noupdate
996                 if mode == 'update' and noupdate_imd:
997                     return res_id2
998                 if not real_id2:
999                     self.clear_caches()
1000                     cr.execute('delete from ir_model_data where id=%s', (imd_id2,))
1001                     res_id = False
1002                 else:
1003                     assert model == real_model, "External ID conflict, %s already refers to a `%s` record,"\
1004                         " you can't define a `%s` record with this ID." % (xml_id, real_model, model)
1005                     res_id,action_id = res_id2,imd_id2
1006
1007         if action_id and res_id:
1008             model_obj.write(cr, uid, [res_id], values, context=context)
1009             self.write(cr, uid, [action_id], {
1010                 'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
1011                 },context=context)
1012         elif res_id:
1013             model_obj.write(cr, uid, [res_id], values, context=context)
1014             if xml_id:
1015                 if model_obj._inherits:
1016                     for table in model_obj._inherits:
1017                         inherit_id = model_obj.browse(cr, uid,
1018                                 res_id,context=context)[model_obj._inherits[table]]
1019                         self.create(cr, uid, {
1020                             'name': xml_id + '_' + table.replace('.', '_'),
1021                             'model': table,
1022                             'module': module,
1023                             'res_id': inherit_id.id,
1024                             'noupdate': noupdate,
1025                             },context=context)
1026                 self.create(cr, uid, {
1027                     'name': xml_id,
1028                     'model': model,
1029                     'module':module,
1030                     'res_id':res_id,
1031                     'noupdate': noupdate,
1032                     },context=context)
1033         else:
1034             if mode=='init' or (mode=='update' and xml_id):
1035                 res_id = model_obj.create(cr, uid, values, context=context)
1036                 if xml_id:
1037                     if model_obj._inherits:
1038                         for table in model_obj._inherits:
1039                             inherit_id = model_obj.browse(cr, uid,
1040                                     res_id,context=context)[model_obj._inherits[table]]
1041                             self.create(cr, uid, {
1042                                 'name': xml_id + '_' + table.replace('.', '_'),
1043                                 'model': table,
1044                                 'module': module,
1045                                 'res_id': inherit_id.id,
1046                                 'noupdate': noupdate,
1047                                 },context=context)
1048                     self.create(cr, uid, {
1049                         'name': xml_id,
1050                         'model': model,
1051                         'module': module,
1052                         'res_id': res_id,
1053                         'noupdate': noupdate
1054                         },context=context)
1055         if xml_id and res_id:
1056             self.loads[(module, xml_id)] = (model, res_id)
1057             for table, inherit_field in model_obj._inherits.iteritems():
1058                 inherit_id = model_obj.read(cr, uid, [res_id],
1059                         [inherit_field])[0][inherit_field]
1060                 self.loads[(module, xml_id + '_' + table.replace('.', '_'))] = (table, inherit_id)
1061         return res_id
1062
1063     def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None, xml_id=False):
1064         if isinstance(models[0], (list, tuple)):
1065             model,res_id = models[0]
1066         else:
1067             res_id=None
1068             model = models[0]
1069
1070         if res_id:
1071             where = ' and res_id=%s' % (res_id,)
1072         else:
1073             where = ' and (res_id is null)'
1074
1075         if key2:
1076             where += ' and key2=\'%s\'' % (key2,)
1077         else:
1078             where += ' and (key2 is null)'
1079
1080         cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
1081         res = cr.fetchone()
1082         ir_values_obj = openerp.registry(cr.dbname)['ir.values']
1083         if not res:
1084             ir_values_obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
1085         elif xml_id:
1086             cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
1087             ir_values_obj.invalidate_cache(cr, uid, ['value'])
1088         return True
1089
1090     def _module_data_uninstall(self, cr, uid, modules_to_remove, context=None):
1091         """Deletes all the records referenced by the ir.model.data entries
1092         ``ids`` along with their corresponding database backed (including
1093         dropping tables, columns, FKs, etc, as long as there is no other
1094         ir.model.data entry holding a reference to them (which indicates that
1095         they are still owned by another module). 
1096         Attempts to perform the deletion in an appropriate order to maximize
1097         the chance of gracefully deleting all records.
1098         This step is performed as part of the full uninstallation of a module.
1099         """ 
1100
1101         ids = self.search(cr, uid, [('module', 'in', modules_to_remove)])
1102
1103         if uid != 1 and not self.pool['ir.model.access'].check_groups(cr, uid, "base.group_system"):
1104             raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
1105
1106         context = dict(context or {})
1107         context[MODULE_UNINSTALL_FLAG] = True # enable model/field deletion
1108
1109         ids_set = set(ids)
1110         wkf_todo = []
1111         to_unlink = []
1112         ids.sort()
1113         ids.reverse()
1114         for data in self.browse(cr, uid, ids, context):
1115             model = data.model
1116             res_id = data.res_id
1117
1118             pair_to_unlink = (model, res_id)
1119             if pair_to_unlink not in to_unlink:
1120                 to_unlink.append(pair_to_unlink)
1121
1122             if model == 'workflow.activity':
1123                 # Special treatment for workflow activities: temporarily revert their
1124                 # incoming transition and trigger an update to force all workflow items
1125                 # to move out before deleting them
1126                 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,))
1127                 wkf_todo.extend(cr.fetchall())
1128                 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))
1129                 self.invalidate_cache(cr, uid, context=context)
1130
1131         for model,res_id in wkf_todo:
1132             try:
1133                 openerp.workflow.trg_write(uid, model, res_id, cr)
1134             except Exception:
1135                 _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)
1136
1137         def unlink_if_refcount(to_unlink):
1138             for model, res_id in to_unlink:
1139                 external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
1140                 if set(external_ids)-ids_set:
1141                     # if other modules have defined this record, we must not delete it
1142                     continue
1143                 if model == 'ir.model.fields':
1144                     # Don't remove the LOG_ACCESS_COLUMNS unless _log_access
1145                     # has been turned off on the model.
1146                     field = self.pool[model].browse(cr, uid, [res_id], context=context)[0]
1147                     if not field.exists():
1148                         _logger.info('Deleting orphan external_ids %s', external_ids)
1149                         self.unlink(cr, uid, external_ids)
1150                         continue
1151                     if field.name in openerp.models.LOG_ACCESS_COLUMNS and self.pool[field.model]._log_access:
1152                         continue
1153                     if field.name == 'id':
1154                         continue
1155                 _logger.info('Deleting %s@%s', res_id, model)
1156                 try:
1157                     cr.execute('SAVEPOINT record_unlink_save')
1158                     self.pool[model].unlink(cr, uid, [res_id], context=context)
1159                 except Exception:
1160                     _logger.info('Unable to delete %s@%s', res_id, model, exc_info=True)
1161                     cr.execute('ROLLBACK TO SAVEPOINT record_unlink_save')
1162                 else:
1163                     cr.execute('RELEASE SAVEPOINT record_unlink_save')
1164
1165         # Remove non-model records first, then model fields, and finish with models
1166         unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1167                                 if model not in ('ir.model','ir.model.fields','ir.model.constraint'))
1168         unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1169                                 if model == 'ir.model.constraint')
1170
1171         ir_module_module = self.pool['ir.module.module']
1172         ir_model_constraint = self.pool['ir.model.constraint']
1173         modules_to_remove_ids = ir_module_module.search(cr, uid, [('name', 'in', modules_to_remove)], context=context)
1174         constraint_ids = ir_model_constraint.search(cr, uid, [('module', 'in', modules_to_remove_ids)], context=context)
1175         ir_model_constraint._module_data_uninstall(cr, uid, constraint_ids, context)
1176
1177         unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1178                                 if model == 'ir.model.fields')
1179
1180         ir_model_relation = self.pool['ir.model.relation']
1181         relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove_ids)])
1182         ir_model_relation._module_data_uninstall(cr, uid, relation_ids, context)
1183
1184         unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1185                                 if model == 'ir.model')
1186
1187         cr.commit()
1188
1189         self.unlink(cr, uid, ids, context)
1190
1191     def _process_end(self, cr, uid, modules):
1192         """ Clear records removed from updated module data.
1193         This method is called at the end of the module loading process.
1194         It is meant to removed records that are no longer present in the
1195         updated data. Such records are recognised as the one with an xml id
1196         and a module in ir_model_data and noupdate set to false, but not
1197         present in self.loads.
1198         """
1199         if not modules:
1200             return True
1201         to_unlink = []
1202         cr.execute("""SELECT id,name,model,res_id,module FROM ir_model_data
1203                       WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s ORDER BY id DESC""",
1204                       (tuple(modules), False))
1205         for (id, name, model, res_id, module) in cr.fetchall():
1206             if (module,name) not in self.loads:
1207                 to_unlink.append((model,res_id))
1208         if not config.get('import_partial'):
1209             for (model, res_id) in to_unlink:
1210                 if model in self.pool:
1211                     _logger.info('Deleting %s@%s', res_id, model)
1212                     self.pool[model].unlink(cr, uid, [res_id])
1213
1214 class wizard_model_menu(osv.osv_memory):
1215     _name = 'wizard.ir.model.menu.create'
1216     _columns = {
1217         'menu_id': fields.many2one('ir.ui.menu', 'Parent Menu', required=True),
1218         'name': fields.char('Menu Name', required=True),
1219     }
1220
1221     def menu_create(self, cr, uid, ids, context=None):
1222         if not context:
1223             context = {}
1224         model_pool = self.pool.get('ir.model')
1225         for menu in self.browse(cr, uid, ids, context):
1226             model = model_pool.browse(cr, uid, context.get('model_id'), context=context)
1227             val = {
1228                 'name': menu.name,
1229                 'res_model': model.model,
1230                 'view_type': 'form',
1231                 'view_mode': 'tree,form'
1232             }
1233             action_id = self.pool.get('ir.actions.act_window').create(cr, uid, val)
1234             self.pool.get('ir.ui.menu').create(cr, uid, {
1235                 'name': menu.name,
1236                 'parent_id': menu.menu_id.id,
1237                 'action': 'ir.actions.act_window,%d' % (action_id,),
1238                 'icon': 'STOCK_INDENT'
1239             }, context)
1240         return {'type':'ir.actions.act_window_close'}
1241
1242 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: