1 # -*- coding: utf-8 -*-
3 ##############################################################################
5 # OpenERP, Open Source Business Applications
6 # Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
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.
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.
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/>.
21 ##############################################################################
27 from openerp.osv import fields,osv
28 from openerp import netsvc, pooler, tools
29 from openerp.tools.safe_eval import safe_eval as eval
30 from openerp.tools import config
31 from openerp.tools.translate import _
32 from openerp.osv.orm import except_orm, browse_record, EXT_ID_PREFIX_FK, \
33 EXT_ID_PREFIX_M2M_TABLE, EXT_ID_PREFIX_CONSTRAINT
35 _logger = logging.getLogger(__name__)
37 MODULE_UNINSTALL_FLAG = '_force_unlink'
39 def _get_fields_type(self, cr, uid, context=None):
40 # Avoid too many nested `if`s below, as RedHat's Python 2.6
41 # break on it. See bug 939653.
42 return sorted([(k,k) for k,v in fields.__dict__.iteritems()
43 if type(v) == types.TypeType and \
44 issubclass(v, fields._column) and \
45 v != fields._column and \
46 not v._deprecated and \
47 not issubclass(v, fields.function)])
49 def _in_modules(self, cr, uid, ids, field_name, arg, context=None):
50 #pseudo-method used by fields.function in ir.model/ir.model.fields
51 module_pool = self.pool.get("ir.module.module")
52 installed_module_ids = module_pool.search(cr, uid, [('state','=','installed')])
53 installed_module_names = module_pool.read(cr, uid, installed_module_ids, ['name'], context=context)
54 installed_modules = set(x['name'] for x in installed_module_names)
57 xml_ids = osv.osv._get_xml_ids(self, cr, uid, ids)
58 for k,v in xml_ids.iteritems():
59 result[k] = ', '.join(sorted(installed_modules & set(xml_id.split('.')[0] for xml_id in v)))
63 class ir_model(osv.osv):
65 _description = "Models"
68 def _is_osv_memory(self, cr, uid, ids, field_name, arg, context=None):
69 models = self.browse(cr, uid, ids, context=context)
70 res = dict.fromkeys(ids)
72 res[model.id] = self.pool.get(model.model).is_transient()
75 def _search_osv_memory(self, cr, uid, model, name, domain, context=None):
78 __, operator, value = domain[0]
79 if operator not in ['=', '!=']:
80 raise osv.except_osv(_('Invalid search criterions'), _('The osv_memory field can only be compared with = and != operator.'))
81 value = bool(value) if operator == '=' else not bool(value)
82 all_model_ids = self.search(cr, uid, [], context=context)
83 is_osv_mem = self._is_osv_memory(cr, uid, all_model_ids, 'osv_memory', arg=None, context=context)
84 return [('id', 'in', [id for id in is_osv_mem if bool(is_osv_mem[id]) == value])]
86 def _view_ids(self, cr, uid, ids, field_name, arg, context=None):
87 models = self.browse(cr, uid, ids)
90 res[model.id] = self.pool.get("ir.ui.view").search(cr, uid, [('model', '=', model.model)])
94 'name': fields.char('Model Description', size=64, translate=True, required=True),
95 'model': fields.char('Model', size=64, required=True, select=1),
96 'info': fields.text('Information'),
97 'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields', required=True),
98 'state': fields.selection([('manual','Custom Object'),('base','Base Object')],'Type',readonly=True),
99 'access_ids': fields.one2many('ir.model.access', 'model_id', 'Access'),
100 'osv_memory': fields.function(_is_osv_memory, string='In-Memory Model', type='boolean',
101 fnct_search=_search_osv_memory,
102 help="Indicates whether this object model lives in memory only, i.e. is not persisted (osv.osv_memory)"),
103 'modules': fields.function(_in_modules, type='char', size=128, string='In Modules', help='List of modules in which the object is defined or inherited'),
104 'view_ids': fields.function(_view_ids, type='one2many', obj='ir.ui.view', string='Views'),
108 'model': lambda *a: 'x_',
109 'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
112 def _check_model_name(self, cr, uid, ids, context=None):
113 for model in self.browse(cr, uid, ids, context=context):
114 if model.state=='manual':
115 if not model.model.startswith('x_'):
117 if not re.match('^[a-z_A-Z0-9.]+$',model.model):
121 def _model_name_msg(self, cr, uid, ids, context=None):
122 return _('The Object name must start with x_ and not contain any special character !')
125 (_check_model_name, _model_name_msg, ['model']),
128 ('obj_name_uniq', 'unique (model)', 'Each model must be unique!'),
131 # overridden to allow searching both on model name (model field)
132 # and model description (name field)
133 def _name_search(self, cr, uid, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
136 domain = args + ['|', ('model', operator, name), ('name', operator, name)]
137 return self.name_get(cr, name_get_uid or uid,
138 super(ir_model, self).search(cr, uid, domain, limit=limit, context=context),
141 def _drop_table(self, cr, uid, ids, context=None):
142 for model in self.browse(cr, uid, ids, context):
143 model_pool = self.pool.get(model.model)
144 cr.execute('select relkind from pg_class where relname=%s', (model_pool._table,))
145 result = cr.fetchone()
146 if result and result[0] == 'v':
147 cr.execute('DROP view %s' % (model_pool._table,))
148 elif result and result[0] == 'r':
149 cr.execute('DROP TABLE %s' % (model_pool._table,))
152 def unlink(self, cr, user, ids, context=None):
153 # Prevent manual deletion of module tables
154 if context is None: context = {}
155 if isinstance(ids, (int, long)):
157 if not context.get(MODULE_UNINSTALL_FLAG) and \
158 any(model.state != 'manual' for model in self.browse(cr, user, ids, context)):
159 raise except_orm(_('Error'), _("Model '%s' contains module data and cannot be removed!") % (model.name,))
161 self._drop_table(cr, user, ids, context)
162 res = super(ir_model, self).unlink(cr, user, ids, context)
163 if not context.get(MODULE_UNINSTALL_FLAG):
164 # only reload pool for normal unlink. For module uninstall the
165 # reload is done independently in openerp.modules.loading
166 pooler.restart_pool(cr.dbname)
170 def write(self, cr, user, ids, vals, context=None):
172 context.pop('__last_update', None)
173 # Filter out operations 4 link from field id, because openerp-web
174 # always write (4,id,False) even for non dirty items
175 if 'field_id' in vals:
176 vals['field_id'] = [op for op in vals['field_id'] if op[0] != 4]
177 return super(ir_model,self).write(cr, user, ids, vals, context)
179 def create(self, cr, user, vals, context=None):
182 if context and context.get('manual',False):
183 vals['state']='manual'
184 res = super(ir_model,self).create(cr, user, vals, context)
185 if vals.get('state','base')=='manual':
186 self.instanciate(cr, user, vals['model'], context)
187 self.pool.get(vals['model']).__init__(self.pool, cr)
189 ctx.update({'field_name':vals['name'],'field_state':'manual','select':vals.get('select_level','0')})
190 self.pool.get(vals['model'])._auto_init(cr, ctx)
191 #pooler.restart_pool(cr.dbname)
194 def instanciate(self, cr, user, model, context=None):
195 class x_custom_model(osv.osv):
197 x_custom_model._name = model
198 x_custom_model._module = False
199 a = x_custom_model.create_instance(self.pool, cr)
200 if (not a._columns) or ('x_name' in a._columns.keys()):
203 x_name = a._columns.keys()[0]
204 x_custom_model._rec_name = x_name
207 class ir_model_fields(osv.osv):
208 _name = 'ir.model.fields'
209 _description = "Fields"
212 'name': fields.char('Name', required=True, size=64, select=1),
213 'model': fields.char('Object Name', size=64, required=True, select=1,
214 help="The technical name of the model this field belongs to"),
215 'relation': fields.char('Object Relation', size=64,
216 help="For relationship fields, the technical name of the target model"),
217 'relation_field': fields.char('Relation Field', size=64,
218 help="For one2many fields, the field on the target model that implement the opposite many2one relationship"),
219 'model_id': fields.many2one('ir.model', 'Model', required=True, select=True, ondelete='cascade',
220 help="The model this field belongs to"),
221 'field_description': fields.char('Field Label', required=True, size=256),
222 'ttype': fields.selection(_get_fields_type, 'Field Type',size=64, required=True),
223 'selection': fields.char('Selection Options',size=128, help="List of options for a selection field, "
224 "specified as a Python expression defining a list of (key, label) pairs. "
225 "For example: [('blue','Blue'),('yellow','Yellow')]"),
226 'required': fields.boolean('Required'),
227 'readonly': fields.boolean('Readonly'),
228 'select_level': fields.selection([('0','Not Searchable'),('1','Always Searchable'),('2','Advanced Search (deprecated)')],'Searchable', required=True),
229 'translate': fields.boolean('Translate', help="Whether values for this field can be translated (enables the translation mechanism for that field)"),
230 'size': fields.integer('Size'),
231 'state': fields.selection([('manual','Custom Field'),('base','Base Field')],'Type', required=True, readonly=True, select=1),
232 'on_delete': fields.selection([('cascade','Cascade'),('set null','Set NULL')], 'On Delete', help='On delete property for many2one fields'),
233 'domain': fields.char('Domain', size=256, help="The optional domain to restrict possible values for relationship fields, "
234 "specified as a Python expression defining a list of triplets. "
235 "For example: [('color','=','red')]"),
236 'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'),
237 'view_load': fields.boolean('View Auto-Load'),
238 'selectable': fields.boolean('Selectable'),
239 'modules': fields.function(_in_modules, type='char', size=128, string='In Modules', help='List of modules in which the field is defined'),
240 'serialization_field_id': fields.many2one('ir.model.fields', 'Serialization Field', domain = "[('ttype','=','serialized')]",
241 ondelete='cascade', help="If set, this field will be stored in the sparse "
242 "structure of the serialization field, instead "
243 "of having its own database column. This cannot be "
244 "changed after creation."),
246 _rec_name='field_description'
252 'state': lambda self,cr,uid,ctx={}: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
253 'on_delete': 'set null',
256 'field_description': '',
261 def _check_selection(self, cr, uid, selection, context=None):
263 selection_list = eval(selection)
265 _logger.warning('Invalid selection list definition for fields.selection', exc_info=True)
266 raise except_orm(_('Error'),
267 _("The Selection Options expression is not a valid Pythonic expression." \
268 "Please provide an expression in the [('key','Label'), ...] format."))
271 if not (isinstance(selection_list, list) and selection_list):
274 for item in selection_list:
275 if not (isinstance(item, (tuple,list)) and len(item) == 2):
280 raise except_orm(_('Error'),
281 _("The Selection Options expression is must be in the [('key','Label'), ...] format!"))
284 def _size_gt_zero_msg(self, cr, user, ids, context=None):
285 return _('Size of the field can never be less than 1 !')
288 ('size_gt_zero', 'CHECK (size>0)',_size_gt_zero_msg ),
291 def _drop_column(self, cr, uid, ids, context=None):
292 for field in self.browse(cr, uid, ids, context):
293 model = self.pool.get(field.model)
294 cr.execute('select relkind from pg_class where relname=%s', (model._table,))
295 result = cr.fetchone()
296 cr.execute("SELECT column_name FROM information_schema.columns WHERE table_name ='%s' and column_name='%s'" %(model._table, field.name))
297 column_name = cr.fetchone()
298 if column_name and (result and result[0] == 'r'):
299 cr.execute('ALTER table "%s" DROP column "%s" cascade' % (model._table, field.name))
300 model._columns.pop(field.name, None)
303 def unlink(self, cr, user, ids, context=None):
304 # Prevent manual deletion of module columns
305 if context is None: context = {}
306 if isinstance(ids, (int, long)):
308 if not context.get(MODULE_UNINSTALL_FLAG) and \
309 any(field.state != 'manual' for field in self.browse(cr, user, ids, context)):
310 raise except_orm(_('Error'), _("This column contains module data and cannot be removed!"))
312 self._drop_column(cr, user, ids, context)
313 res = super(ir_model_fields, self).unlink(cr, user, ids, context)
316 def create(self, cr, user, vals, context=None):
317 if 'model_id' in vals:
318 model_data = self.pool.get('ir.model').browse(cr, user, vals['model_id'])
319 vals['model'] = model_data.model
322 if context and context.get('manual',False):
323 vals['state'] = 'manual'
324 if vals.get('ttype', False) == 'selection':
325 if not vals.get('selection',False):
326 raise except_orm(_('Error'), _('For selection fields, the Selection Options must be given!'))
327 self._check_selection(cr, user, vals['selection'], context=context)
328 res = super(ir_model_fields,self).create(cr, user, vals, context)
329 if vals.get('state','base') == 'manual':
330 if not vals['name'].startswith('x_'):
331 raise except_orm(_('Error'), _("Custom fields must have a name that starts with 'x_' !"))
333 if vals.get('relation',False) and not self.pool.get('ir.model').search(cr, user, [('model','=',vals['relation'])]):
334 raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
336 if self.pool.get(vals['model']):
337 self.pool.get(vals['model']).__init__(self.pool, cr)
338 #Added context to _auto_init for special treatment to custom field for select_level
340 ctx.update({'field_name':vals['name'],'field_state':'manual','select':vals.get('select_level','0'),'update_custom_fields':True})
341 self.pool.get(vals['model'])._auto_init(cr, ctx)
345 def write(self, cr, user, ids, vals, context=None):
348 if context and context.get('manual',False):
349 vals['state'] = 'manual'
351 #For the moment renaming a sparse field or changing the storing system is not allowed. This may be done later
352 if 'serialization_field_id' in vals or 'name' in vals:
353 for field in self.browse(cr, user, ids, context=context):
354 if 'serialization_field_id' in vals and field.serialization_field_id.id != vals['serialization_field_id']:
355 raise except_orm(_('Error!'), _('Changing the storing system for field "%s" is not allowed.')%field.name)
356 if field.serialization_field_id and (field.name != vals['name']):
357 raise except_orm(_('Error!'), _('Renaming sparse field "%s" is not allowed')%field.name)
359 column_rename = None # if set, *one* column can be renamed here
361 models_patch = {} # structs of (obj, [(field, prop, change_to),..])
362 # data to be updated on the orm model
364 # static table of properties
365 model_props = [ # (our-name, fields.prop, set_fn)
366 ('field_description', 'string', str),
367 ('required', 'required', bool),
368 ('readonly', 'readonly', bool),
369 ('domain', '_domain', eval),
370 ('size', 'size', int),
371 ('on_delete', 'ondelete', str),
372 ('translate', 'translate', bool),
373 ('view_load', 'view_load', bool),
374 ('selectable', 'selectable', bool),
375 ('select_level', 'select', int),
376 ('selection', 'selection', eval),
380 checked_selection = False # need only check it once, so defer
382 for item in self.browse(cr, user, ids, context=context):
383 if not (obj and obj._name == item.model):
384 obj = self.pool.get(item.model)
386 if item.state != 'manual':
387 raise except_orm(_('Error!'),
388 _('Properties of base fields cannot be altered in this manner! '
389 'Please modify them through Python code, '
390 'preferably through a custom addon!'))
392 if item.ttype == 'selection' and 'selection' in vals \
393 and not checked_selection:
394 self._check_selection(cr, user, vals['selection'], context=context)
395 checked_selection = True
397 final_name = item.name
398 if 'name' in vals and vals['name'] != item.name:
399 # We need to rename the column
401 raise except_orm(_('Error!'), _('Can only rename one column at a time!'))
402 if vals['name'] in obj._columns:
403 raise except_orm(_('Error!'), _('Cannot rename column to %s, because that column already exists!') % vals['name'])
404 if vals.get('state', 'base') == 'manual' and not vals['name'].startswith('x_'):
405 raise except_orm(_('Error!'), _('New column name must still start with x_ , because it is a custom field!'))
406 if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']:
407 raise ValueError('Invalid character in column name')
408 column_rename = (obj, (obj._table, item.name, vals['name']))
409 final_name = vals['name']
411 if 'model_id' in vals and vals['model_id'] != item.model_id:
412 raise except_orm(_("Error!"), _("Changing the model of a field is forbidden!"))
414 if 'ttype' in vals and vals['ttype'] != item.ttype:
415 raise except_orm(_("Error!"), _("Changing the type of a column is not yet supported. "
416 "Please drop it and create it again!"))
418 # We don't check the 'state', because it might come from the context
419 # (thus be set for multiple fields) and will be ignored anyway.
421 models_patch.setdefault(obj._name, (obj,[]))
422 # find out which properties (per model) we need to update
423 for field_name, field_property, set_fn in model_props:
424 if field_name in vals:
425 property_value = set_fn(vals[field_name])
426 if getattr(obj._columns[item.name], field_property) != property_value:
427 models_patch[obj._name][1].append((final_name, field_property, property_value))
428 # our dict is ready here, but no properties are changed so far
430 # These shall never be written (modified)
431 for column_name in ('model_id', 'model', 'state'):
432 if column_name in vals:
433 del vals[column_name]
435 res = super(ir_model_fields,self).write(cr, user, ids, vals, context=context)
438 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % column_rename[1])
439 # This is VERY risky, but let us have this feature:
440 # we want to change the key of column in obj._columns dict
441 col = column_rename[0]._columns.pop(column_rename[1][1]) # take object out, w/o copy
442 column_rename[0]._columns[column_rename[1][2]] = col
445 # We have to update _columns of the model(s) and then call their
446 # _auto_init to sync the db with the model. Hopefully, since write()
447 # was called earlier, they will be in-sync before the _auto_init.
448 # Anything we don't update in _columns now will be reset from
449 # the model into ir.model.fields (db).
451 ctx.update({'select': vals.get('select_level','0'),'update_custom_fields':True})
453 for __, patch_struct in models_patch.items():
454 obj = patch_struct[0]
455 for col_name, col_prop, val in patch_struct[1]:
456 setattr(obj._columns[col_name], col_prop, val)
457 obj._auto_init(cr, ctx)
462 class ir_model_access(osv.osv):
463 _name = 'ir.model.access'
465 'name': fields.char('Name', size=64, required=True, select=True),
466 'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)], select=True, ondelete='cascade'),
467 'group_id': fields.many2one('res.groups', 'Group', ondelete='cascade', select=True),
468 'perm_read': fields.boolean('Read Access'),
469 'perm_write': fields.boolean('Write Access'),
470 'perm_create': fields.boolean('Create Access'),
471 'perm_unlink': fields.boolean('Delete Access'),
474 def check_groups(self, cr, uid, group):
475 grouparr = group.split('.')
478 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],))
479 return bool(cr.fetchone())
481 def check_group(self, cr, uid, model, mode, group_ids):
482 """ Check if a specific group has the access mode to the specified model"""
483 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
485 if isinstance(model, browse_record):
486 assert model._table_name == 'ir.model', 'Invalid model object'
487 model_name = model.name
491 if isinstance(group_ids, (int, long)):
492 group_ids = [group_ids]
493 for group_id in group_ids:
494 cr.execute("SELECT perm_" + mode + " "
495 " FROM ir_model_access a "
496 " JOIN ir_model m ON (m.id = a.model_id) "
497 " WHERE m.model = %s AND a.group_id = %s", (model_name, group_id)
501 cr.execute("SELECT perm_" + mode + " "
502 " FROM ir_model_access a "
503 " JOIN ir_model m ON (m.id = a.model_id) "
504 " WHERE m.model = %s AND a.group_id IS NULL", (model_name, )
508 access = bool(r and r[0])
511 # pass no groups -> no access
514 def group_names_with_access(self, cr, model_name, access_mode):
515 """Returns the names of visible groups which have been granted ``access_mode`` on
516 the model ``model_name``.
519 assert access_mode in ['read','write','create','unlink'], 'Invalid access mode: %s' % access_mode
524 JOIN ir_model m ON (a.model_id=m.id)
525 JOIN res_groups g ON (a.group_id=g.id)
526 LEFT JOIN ir_module_category c ON (c.id=g.category_id)
529 a.perm_''' + access_mode, (model_name,))
530 return [('%s/%s' % x) if x[0] else x[1] for x in cr.fetchall()]
533 def check(self, cr, uid, model, mode='read', raise_exception=True, context=None):
535 # User root have all accesses
536 # TODO: exclude xml-rpc requests
539 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
541 if isinstance(model, browse_record):
542 assert model._table_name == 'ir.model', 'Invalid model object'
543 model_name = model.model
547 # TransientModel records have no access rights, only an implicit access rule
548 if self.pool.get(model_name).is_transient():
551 # We check if a specific rule exists
552 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
553 ' FROM ir_model_access a '
554 ' JOIN ir_model m ON (m.id = a.model_id) '
555 ' JOIN res_groups_users_rel gu ON (gu.gid = a.group_id) '
556 ' WHERE m.model = %s '
563 # there is no specific rule. We check the generic rule
564 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
565 ' FROM ir_model_access a '
566 ' JOIN ir_model m ON (m.id = a.model_id) '
567 ' WHERE a.group_id IS NULL '
573 if not r and raise_exception:
574 groups = '\n\t'.join('- %s' % g for g in self.group_names_with_access(cr, model_name, mode))
576 # Messages are declared in extenso so they are properly exported in translation terms
577 'read': _("Sorry, you are not allowed to access this document."),
578 'write': _("Sorry, you are not allowed to modify this document."),
579 'create': _("Sorry, you are not allowed to create this kind of document."),
580 'unlink': _("Sorry, you are not allowed to delete this document."),
583 msg_tail = _("Only users with the following access level are currently allowed to do that") + ":\n%s\n\n(" + _("Document model") + ": %s)"
584 msg_params = (groups, model_name)
586 msg_tail = _("Please contact your system administrator if you think this is an error.") + "\n\n(" + _("Document model") + ": %s)"
587 msg_params = (model_name,)
588 _logger.warning('Access Denied by ACLs for operation: %s, uid: %s, model: %s', mode, uid, model_name)
589 msg = '%s %s' % (msg_heads[mode], msg_tail)
590 raise except_orm(_('Access Denied'), msg % msg_params)
593 __cache_clearing_methods = []
595 def register_cache_clearing_method(self, model, method):
596 self.__cache_clearing_methods.append((model, method))
598 def unregister_cache_clearing_method(self, model, method):
600 i = self.__cache_clearing_methods.index((model, method))
601 del self.__cache_clearing_methods[i]
605 def call_cache_clearing_methods(self, cr):
606 self.check.clear_cache(self) # clear the cache of check function
607 for model, method in self.__cache_clearing_methods:
608 object_ = self.pool.get(model)
610 getattr(object_, method)()
613 # Check rights on actions
615 def write(self, cr, uid, *args, **argv):
616 self.call_cache_clearing_methods(cr)
617 res = super(ir_model_access, self).write(cr, uid, *args, **argv)
620 def create(self, cr, uid, *args, **argv):
621 self.call_cache_clearing_methods(cr)
622 res = super(ir_model_access, self).create(cr, uid, *args, **argv)
625 def unlink(self, cr, uid, *args, **argv):
626 self.call_cache_clearing_methods(cr)
627 res = super(ir_model_access, self).unlink(cr, uid, *args, **argv)
632 class ir_model_data(osv.osv):
633 """Holds external identifier keys for records in the database.
634 This has two main uses:
636 * allows easy data integration with third-party systems,
637 making import/export/sync of data possible, as records
638 can be uniquely identified across multiple systems
639 * allows tracking the origin of data installed by OpenERP
640 modules themselves, thus making it possible to later
641 update them seamlessly.
643 _name = 'ir.model.data'
644 _order = 'module,model,name'
646 'name': fields.char('External Identifier', required=True, size=128, select=1,
647 help="External Key/Identifier that can be used for "
648 "data integration with third-party systems"),
649 'model': fields.char('Model Name', required=True, size=64, select=1),
650 'module': fields.char('Module', required=True, size=64, select=1),
651 'res_id': fields.integer('Record ID', select=1,
652 help="ID of the target record in the database"),
653 'noupdate': fields.boolean('Non Updatable'),
654 'date_update': fields.datetime('Update Date'),
655 'date_init': fields.datetime('Init Date')
658 'date_init': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
659 'date_update': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
664 ('module_name_uniq', 'unique(name, module)', 'You cannot have multiple records with the same external ID in the same module!'),
667 def __init__(self, pool, cr):
668 osv.osv.__init__(self, pool, cr)
670 # also stored in pool to avoid being discarded along with this osv instance
671 if getattr(pool, 'model_data_reference_ids', None) is None:
672 self.pool.model_data_reference_ids = {}
674 self.loads = self.pool.model_data_reference_ids
676 def _auto_init(self, cr, context=None):
677 super(ir_model_data, self)._auto_init(cr, context)
678 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_model_data_module_name_index\'')
679 if not cr.fetchone():
680 cr.execute('CREATE INDEX ir_model_data_module_name_index ON ir_model_data (module, name)')
683 def _get_id(self, cr, uid, module, xml_id):
684 """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"""
685 ids = self.search(cr, uid, [('module','=',module), ('name','=', xml_id)])
687 raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
688 # the sql constraints ensure us we have only one result
692 def get_object_reference(self, cr, uid, module, xml_id):
693 """Returns (model, res_id) corresponding to a given module and xml_id (cached) or raise ValueError if not found"""
694 data_id = self._get_id(cr, uid, module, xml_id)
695 res = self.read(cr, uid, data_id, ['model', 'res_id'])
696 if not res['res_id']:
697 raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
698 return (res['model'], res['res_id'])
700 def get_object(self, cr, uid, module, xml_id, context=None):
701 """Returns a browsable record for the given module name and xml_id or raise ValueError if not found"""
702 res_model, res_id = self.get_object_reference(cr, uid, module, xml_id)
703 result = self.pool.get(res_model).browse(cr, uid, res_id, context=context)
704 if not result.exists():
705 raise ValueError('No record found for unique ID %s.%s. It may have been deleted.' % (module, xml_id))
708 def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
712 id = self.read(cr, uid, [self._get_id(cr, uid, module, xml_id)], ['res_id'])[0]['res_id']
713 self.loads[(module,xml_id)] = (model,id)
719 def unlink(self, cr, uid, ids, context=None):
720 """ Regular unlink method, but make sure to clear the caches. """
721 self._get_id.clear_cache(self)
722 self.get_object_reference.clear_cache(self)
723 return super(ir_model_data,self).unlink(cr, uid, ids, context=context)
725 def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None):
726 model_obj = self.pool.get(model)
729 # records created during module install should not display the messages of OpenChatter
730 context = dict(context, install_mode=True)
731 if xml_id and ('.' in xml_id):
732 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)
733 module, xml_id = xml_id.split('.')
734 if (not xml_id) and (not self.doinit):
738 cr.execute('''SELECT imd.id, imd.res_id, md.id, imd.model
739 FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
740 WHERE imd.module=%%s AND imd.name=%%s''' % model_obj._table,
742 results = cr.fetchall()
743 for imd_id2,res_id2,real_id2,real_model in results:
745 self._get_id.clear_cache(self, uid, module, xml_id)
746 self.get_object_reference.clear_cache(self, uid, module, xml_id)
747 cr.execute('delete from ir_model_data where id=%s', (imd_id2,))
750 assert model == real_model, "External ID conflict, %s already refers to a `%s` record,"\
751 " you can't define a `%s` record with this ID." % (xml_id, real_model, model)
752 res_id,action_id = res_id2,imd_id2
754 if action_id and res_id:
755 model_obj.write(cr, uid, [res_id], values, context=context)
756 self.write(cr, uid, [action_id], {
757 'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
760 model_obj.write(cr, uid, [res_id], values, context=context)
762 self.create(cr, uid, {
767 'noupdate': noupdate,
769 if model_obj._inherits:
770 for table in model_obj._inherits:
771 inherit_id = model_obj.browse(cr, uid,
772 res_id,context=context)[model_obj._inherits[table]]
773 self.create(cr, uid, {
774 'name': xml_id + '_' + table.replace('.', '_'),
777 'res_id': inherit_id.id,
778 'noupdate': noupdate,
781 if mode=='init' or (mode=='update' and xml_id):
782 res_id = model_obj.create(cr, uid, values, context=context)
784 self.create(cr, uid, {
791 if model_obj._inherits:
792 for table in model_obj._inherits:
793 inherit_id = model_obj.browse(cr, uid,
794 res_id,context=context)[model_obj._inherits[table]]
795 self.create(cr, uid, {
796 'name': xml_id + '_' + table.replace('.', '_'),
799 'res_id': inherit_id.id,
800 'noupdate': noupdate,
804 self.loads[(module, xml_id)] = (model, res_id)
805 if model_obj._inherits:
806 for table in model_obj._inherits:
807 inherit_field = model_obj._inherits[table]
808 inherit_id = model_obj.read(cr, uid, res_id,
809 [inherit_field])[inherit_field]
810 self.loads[(module, xml_id + '_' + \
811 table.replace('.', '_'))] = (table, inherit_id)
814 def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None, xml_id=False):
815 if type(models[0])==type([]) or type(models[0])==type(()):
816 model,res_id = models[0]
822 where = ' and res_id=%s' % (res_id,)
824 where = ' and (res_id is null)'
827 where += ' and key2=\'%s\'' % (key2,)
829 where += ' and (key2 is null)'
831 cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
834 ir_values_obj = pooler.get_pool(cr.dbname).get('ir.values')
835 res = ir_values_obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
837 cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
840 def _module_data_uninstall(self, cr, uid, ids, context=None):
841 """Deletes all the records referenced by the ir.model.data entries
842 ``ids`` along with their corresponding database backed (including
843 dropping tables, columns, FKs, etc, as long as there is no other
844 ir.model.data entry holding a reference to them (which indicates that
845 they are still owned by another module).
846 Attempts to perform the deletion in an appropriate order to maximize
847 the chance of gracefully deleting all records.
848 This step is performed as part of the full uninstallation of a module.
851 if uid != 1 and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"):
852 raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
854 context = dict(context or {})
855 context[MODULE_UNINSTALL_FLAG] = True # enable model/field deletion
863 for data in self.browse(cr, uid, ids, context):
866 model_obj = self.pool.get(model)
867 name = tools.ustr(data.name)
869 if name.startswith(EXT_ID_PREFIX_FK) or name.startswith(EXT_ID_PREFIX_M2M_TABLE)\
870 or name.startswith(EXT_ID_PREFIX_CONSTRAINT):
871 # double-check we are really going to delete all the owners of this schema element
872 cr.execute("""SELECT id from ir_model_data where name = %s and res_id IS NULL""", (data.name,))
873 external_ids = [x[0] for x in cr.fetchall()]
874 if (set(external_ids)-ids_set):
875 # as installed modules have defined this element we must not delete it!
878 if name.startswith(EXT_ID_PREFIX_FK):
879 name = name[len(EXT_ID_PREFIX_FK):]
880 # test if FK exists on this table (it could be on a related m2m table, in which case we ignore it)
881 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
882 WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_obj._table))
884 cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
885 _logger.info('Dropped FK CONSTRAINT %s@%s', name, model)
888 if name.startswith(EXT_ID_PREFIX_M2M_TABLE):
889 name = name[len(EXT_ID_PREFIX_M2M_TABLE):]
890 cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name=%s", (name,))
891 if cr.fetchone() and not name in to_drop_table:
892 to_drop_table.append(name)
895 if name.startswith(EXT_ID_PREFIX_CONSTRAINT):
896 name = name[len(EXT_ID_PREFIX_CONSTRAINT):]
897 # test if constraint exists
898 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
899 WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_obj._table))
901 cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
902 _logger.info('Dropped CONSTRAINT %s@%s', name, model)
905 pair_to_unlink = (model, res_id)
906 if pair_to_unlink not in to_unlink:
907 to_unlink.append(pair_to_unlink)
909 if model == 'workflow.activity':
910 # Special treatment for workflow activities: temporarily revert their
911 # incoming transition and trigger an update to force all workflow items
912 # to move out before deleting them
913 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,))
914 wkf_todo.extend(cr.fetchall())
915 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))
917 wf_service = netsvc.LocalService("workflow")
918 for model,res_id in wkf_todo:
920 wf_service.trg_write(uid, model, res_id, cr)
922 _logger.info('Unable to force processing of workflow for item %s@%s in order to leave activity to be deleted', res_id, model)
924 # drop m2m relation tables
925 for table in to_drop_table:
926 cr.execute('DROP TABLE %s CASCADE'% (table),)
927 _logger.info('Dropped table %s', table)
929 def unlink_if_refcount(to_unlink):
930 for model, res_id in to_unlink:
931 external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
932 if (set(external_ids)-ids_set):
933 # if other modules have defined this record, we must not delete it
935 _logger.info('Deleting %s@%s', res_id, model)
937 self.pool.get(model).unlink(cr, uid, [res_id], context=context)
939 _logger.info('Unable to delete %s@%s', res_id, model, exc_info=True)
941 # Remove non-model records first, then model fields, and finish with models
942 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
943 if model not in ('ir.model','ir.model.fields'))
944 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
945 if model == 'ir.model.fields')
946 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
947 if model == 'ir.model')
951 def _process_end(self, cr, uid, modules):
952 """ Clear records removed from updated module data.
953 This method is called at the end of the module loading process.
954 It is meant to removed records that are no longer present in the
955 updated data. Such records are recognised as the one with an xml id
956 and a module in ir_model_data and noupdate set to false, but not
957 present in self.loads.
962 cr.execute("""SELECT id,name,model,res_id,module FROM ir_model_data
963 WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s""",
964 (tuple(modules), False))
965 for (id, name, model, res_id, module) in cr.fetchall():
966 if (module,name) not in self.loads:
967 to_unlink.append((model,res_id))
968 if not config.get('import_partial'):
969 for (model, res_id) in to_unlink:
970 if self.pool.get(model):
971 _logger.info('Deleting %s@%s', res_id, model)
972 self.pool.get(model).unlink(cr, uid, [res_id])
975 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: