1 # -*- coding: utf-8 -*-
3 ##############################################################################
5 # OpenERP, Open Source Business Applications
6 # Copyright (C) 2004-2014 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 ##############################################################################
28 import openerp.modules.registry
29 from openerp import SUPERUSER_ID
30 from openerp import tools
31 from openerp.osv import fields,osv
32 from openerp.osv.orm import Model, browse_null
33 from openerp.tools.safe_eval import safe_eval as eval
34 from openerp.tools import config
35 from openerp.tools.translate import _
36 from openerp.osv.orm import except_orm, browse_record, MAGIC_COLUMNS
38 _logger = logging.getLogger(__name__)
40 MODULE_UNINSTALL_FLAG = '_force_unlink'
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)])
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)
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)))
65 class ir_model(osv.osv):
67 _description = "Models"
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)
74 if model.model in self.pool:
75 res[model.id] = self.pool[model.model].is_transient()
77 _logger.error('Missing model %s' % (model.model, ))
80 def _search_osv_memory(self, cr, uid, model, name, domain, context=None):
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])]
91 def _view_ids(self, cr, uid, ids, field_name, arg, context=None):
92 models = self.browse(cr, uid, ids)
95 res[model.id] = self.pool["ir.ui.view"].search(cr, uid, [('model', '=', model.model)])
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),
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'),
114 'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
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_'):
122 if not re.match('^[a-z_A-Z0-9.]+$',model.model):
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 !')
130 (_check_model_name, _model_name_msg, ['model']),
133 ('obj_name_uniq', 'unique (model)', 'Each model must be unique!'),
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):
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),
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' % (model_pool._table,))
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)):
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,))
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)
178 def write(self, cr, user, ids, vals, context=None):
180 context.pop('__last_update', None)
181 # Filter out operations 4 link from field id, because openerp-web
182 # always write (4,id,False) even for non dirty items
183 if 'field_id' in vals:
184 vals['field_id'] = [op for op in vals['field_id'] if op[0] != 4]
185 return super(ir_model,self).write(cr, user, ids, vals, context)
187 def create(self, cr, user, vals, context=None):
190 if context and context.get('manual'):
191 vals['state']='manual'
192 res = super(ir_model,self).create(cr, user, vals, context)
193 if vals.get('state','base')=='manual':
194 self.instanciate(cr, user, vals['model'], context)
196 field_name=vals['name'],
197 field_state='manual',
198 select=vals.get('select_level', '0'),
199 update_custom_fields=True)
200 self.pool[vals['model']]._auto_init(cr, ctx)
201 self.pool[vals['model']]._auto_end(cr, ctx) # actually create FKs!
202 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
205 def instanciate(self, cr, user, model, context=None):
206 class x_custom_model(osv.osv):
208 x_custom_model._name = model
209 x_custom_model._module = False
210 a = x_custom_model.create_instance(self.pool, cr)
213 elif 'x_name' in a._columns.keys():
216 x_name = a._columns.keys()[0]
217 x_custom_model._rec_name = x_name
220 class ir_model_fields(osv.osv):
221 _name = 'ir.model.fields'
222 _description = "Fields"
223 _rec_name = 'field_description'
226 'name': fields.char('Name', required=True, select=1),
227 'complete_name': fields.char('Complete Name', select=1),
228 'model': fields.char('Object Name', required=True, select=1,
229 help="The technical name of the model this field belongs to"),
230 'relation': fields.char('Object Relation',
231 help="For relationship fields, the technical name of the target model"),
232 'relation_field': fields.char('Relation Field',
233 help="For one2many fields, the field on the target model that implement the opposite many2one relationship"),
234 'model_id': fields.many2one('ir.model', 'Model', required=True, select=True, ondelete='cascade',
235 help="The model this field belongs to"),
236 'field_description': fields.char('Field Label', required=True),
237 'ttype': fields.selection(_get_fields_type, 'Field Type', required=True),
238 'selection': fields.char('Selection Options', help="List of options for a selection field, "
239 "specified as a Python expression defining a list of (key, label) pairs. "
240 "For example: [('blue','Blue'),('yellow','Yellow')]"),
241 'required': fields.boolean('Required'),
242 'readonly': fields.boolean('Readonly'),
243 'select_level': fields.selection([('0','Not Searchable'),('1','Always Searchable'),('2','Advanced Search (deprecated)')],'Searchable', required=True),
244 'translate': fields.boolean('Translatable', help="Whether values for this field can be translated (enables the translation mechanism for that field)"),
245 'size': fields.integer('Size'),
246 'state': fields.selection([('manual','Custom Field'),('base','Base Field')],'Type', required=True, readonly=True, select=1),
247 'on_delete': fields.selection([('cascade','Cascade'),('set null','Set NULL')], 'On Delete', help='On delete property for many2one fields'),
248 'domain': fields.char('Domain', help="The optional domain to restrict possible values for relationship fields, "
249 "specified as a Python expression defining a list of triplets. "
250 "For example: [('color','=','red')]"),
251 'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'),
252 'selectable': fields.boolean('Selectable'),
253 'modules': fields.function(_in_modules, type='char', string='In Modules', help='List of modules in which the field is defined'),
254 'serialization_field_id': fields.many2one('ir.model.fields', 'Serialization Field', domain = "[('ttype','=','serialized')]",
255 ondelete='cascade', help="If set, this field will be stored in the sparse "
256 "structure of the serialization field, instead "
257 "of having its own database column. This cannot be "
258 "changed after creation."),
260 _rec_name='field_description'
265 'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
266 'on_delete': 'set null',
268 'field_description': '',
273 def _check_selection(self, cr, uid, selection, context=None):
275 selection_list = eval(selection)
277 _logger.warning('Invalid selection list definition for fields.selection', exc_info=True)
278 raise except_orm(_('Error'),
279 _("The Selection Options expression is not a valid Pythonic expression."
280 "Please provide an expression in the [('key','Label'), ...] format."))
283 if not (isinstance(selection_list, list) and selection_list):
286 for item in selection_list:
287 if not (isinstance(item, (tuple,list)) and len(item) == 2):
292 raise except_orm(_('Error'),
293 _("The Selection Options expression is must be in the [('key','Label'), ...] format!"))
296 def _size_gt_zero_msg(self, cr, user, ids, context=None):
297 return _('Size of the field can never be less than 0 !')
300 ('size_gt_zero', 'CHECK (size>=0)',_size_gt_zero_msg ),
303 def _drop_column(self, cr, uid, ids, context=None):
304 for field in self.browse(cr, uid, ids, context):
305 if field.name in MAGIC_COLUMNS:
307 model = self.pool[field.model]
308 cr.execute('select relkind from pg_class where relname=%s', (model._table,))
309 result = cr.fetchone()
310 cr.execute("SELECT column_name FROM information_schema.columns WHERE table_name ='%s' and column_name='%s'" %(model._table, field.name))
311 column_name = cr.fetchone()
312 if column_name and (result and result[0] == 'r'):
313 cr.execute('ALTER table "%s" DROP column "%s" cascade' % (model._table, field.name))
314 model._columns.pop(field.name, None)
317 def unlink(self, cr, user, ids, context=None):
318 # Prevent manual deletion of module columns
319 if context is None: context = {}
320 if isinstance(ids, (int, long)):
322 if not context.get(MODULE_UNINSTALL_FLAG) and \
323 any(field.state != 'manual' for field in self.browse(cr, user, ids, context)):
324 raise except_orm(_('Error'), _("This column contains module data and cannot be removed!"))
326 self._drop_column(cr, user, ids, context)
327 res = super(ir_model_fields, self).unlink(cr, user, ids, context)
328 if not context.get(MODULE_UNINSTALL_FLAG):
330 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
333 def create(self, cr, user, vals, context=None):
334 if 'model_id' in vals:
335 model_data = self.pool['ir.model'].browse(cr, user, vals['model_id'])
336 vals['model'] = model_data.model
339 if context and context.get('manual',False):
340 vals['state'] = 'manual'
341 if vals.get('ttype', False) == 'selection':
342 if not vals.get('selection',False):
343 raise except_orm(_('Error'), _('For selection fields, the Selection Options must be given!'))
344 self._check_selection(cr, user, vals['selection'], context=context)
345 res = super(ir_model_fields,self).create(cr, user, vals, context)
346 if vals.get('state','base') == 'manual':
347 if not vals['name'].startswith('x_'):
348 raise except_orm(_('Error'), _("Custom fields must have a name that starts with 'x_' !"))
350 if vals.get('relation',False) and not self.pool['ir.model'].search(cr, user, [('model','=',vals['relation'])]):
351 raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
353 if vals['model'] in self.pool:
354 self.pool[vals['model']].__init__(self.pool, cr)
355 #Added context to _auto_init for special treatment to custom field for select_level
357 field_name=vals['name'],
358 field_state='manual',
359 select=vals.get('select_level', '0'),
360 update_custom_fields=True)
361 self.pool[vals['model']]._auto_init(cr, ctx)
362 self.pool[vals['model']]._auto_end(cr, ctx) # actually create FKs!
363 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
367 def write(self, cr, user, ids, vals, context=None):
370 if context and context.get('manual',False):
371 vals['state'] = 'manual'
373 #For the moment renaming a sparse field or changing the storing system is not allowed. This may be done later
374 if 'serialization_field_id' in vals or 'name' in vals:
375 for field in self.browse(cr, user, ids, context=context):
376 if 'serialization_field_id' in vals and field.serialization_field_id.id != vals['serialization_field_id']:
377 raise except_orm(_('Error!'), _('Changing the storing system for field "%s" is not allowed.')%field.name)
378 if field.serialization_field_id and (field.name != vals['name']):
379 raise except_orm(_('Error!'), _('Renaming sparse field "%s" is not allowed')%field.name)
381 column_rename = None # if set, *one* column can be renamed here
382 models_patch = {} # structs of (obj, [(field, prop, change_to),..])
383 # data to be updated on the orm model
385 # static table of properties
386 model_props = [ # (our-name, fields.prop, set_fn)
387 ('field_description', 'string', tools.ustr),
388 ('required', 'required', bool),
389 ('readonly', 'readonly', bool),
390 ('domain', '_domain', eval),
391 ('size', 'size', int),
392 ('on_delete', 'ondelete', str),
393 ('translate', 'translate', bool),
394 ('selectable', 'selectable', bool),
395 ('select_level', 'select', int),
396 ('selection', 'selection', eval),
400 checked_selection = False # need only check it once, so defer
402 for item in self.browse(cr, user, ids, context=context):
403 obj = self.pool.get(item.model)
405 if item.state != 'manual':
406 raise except_orm(_('Error!'),
407 _('Properties of base fields cannot be altered in this manner! '
408 'Please modify them through Python code, '
409 'preferably through a custom addon!'))
411 if item.ttype == 'selection' and 'selection' in vals \
412 and not checked_selection:
413 self._check_selection(cr, user, vals['selection'], context=context)
414 checked_selection = True
416 final_name = item.name
417 if 'name' in vals and vals['name'] != item.name:
418 # We need to rename the column
420 raise except_orm(_('Error!'), _('Can only rename one column at a time!'))
421 if vals['name'] in obj._columns:
422 raise except_orm(_('Error!'), _('Cannot rename column to %s, because that column already exists!') % vals['name'])
423 if vals.get('state', 'base') == 'manual' and not vals['name'].startswith('x_'):
424 raise except_orm(_('Error!'), _('New column name must still start with x_ , because it is a custom field!'))
425 if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']:
426 raise ValueError('Invalid character in column name')
427 column_rename = (obj, (obj._table, item.name, vals['name']))
428 final_name = vals['name']
430 if 'model_id' in vals and vals['model_id'] != item.model_id:
431 raise except_orm(_("Error!"), _("Changing the model of a field is forbidden!"))
433 if 'ttype' in vals and vals['ttype'] != item.ttype:
434 raise except_orm(_("Error!"), _("Changing the type of a column is not yet supported. "
435 "Please drop it and create it again!"))
437 # We don't check the 'state', because it might come from the context
438 # (thus be set for multiple fields) and will be ignored anyway.
440 models_patch.setdefault(obj._name, (obj,[]))
441 # find out which properties (per model) we need to update
442 for field_name, field_property, set_fn in model_props:
443 if field_name in vals:
444 property_value = set_fn(vals[field_name])
445 if getattr(obj._columns[item.name], field_property) != property_value:
446 models_patch[obj._name][1].append((final_name, field_property, property_value))
447 # our dict is ready here, but no properties are changed so far
449 # These shall never be written (modified)
450 for column_name in ('model_id', 'model', 'state'):
451 if column_name in vals:
452 del vals[column_name]
454 res = super(ir_model_fields,self).write(cr, user, ids, vals, context=context)
457 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % column_rename[1])
458 # This is VERY risky, but let us have this feature:
459 # we want to change the key of column in obj._columns dict
460 col = column_rename[0]._columns.pop(column_rename[1][1]) # take object out, w/o copy
461 column_rename[0]._columns[column_rename[1][2]] = col
464 # We have to update _columns of the model(s) and then call their
465 # _auto_init to sync the db with the model. Hopefully, since write()
466 # was called earlier, they will be in-sync before the _auto_init.
467 # Anything we don't update in _columns now will be reset from
468 # the model into ir.model.fields (db).
469 ctx = dict(context, select=vals.get('select_level', '0'),
470 update_custom_fields=True)
472 for __, patch_struct in models_patch.items():
473 obj = patch_struct[0]
474 for col_name, col_prop, val in patch_struct[1]:
475 setattr(obj._columns[col_name], col_prop, val)
476 obj._auto_init(cr, ctx)
477 obj._auto_end(cr, ctx) # actually create FKs!
478 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
481 class ir_model_constraint(Model):
483 This model tracks PostgreSQL foreign keys and constraints used by OpenERP
486 _name = 'ir.model.constraint'
488 'name': fields.char('Constraint', required=True, select=1,
489 help="PostgreSQL constraint or foreign key name."),
490 'model': fields.many2one('ir.model', string='Model',
491 required=True, select=1),
492 'module': fields.many2one('ir.module.module', string='Module',
493 required=True, select=1),
494 'type': fields.char('Constraint Type', required=True, size=1, select=1,
495 help="Type of the constraint: `f` for a foreign key, "
496 "`u` for other constraints."),
497 'date_update': fields.datetime('Update Date'),
498 'date_init': fields.datetime('Initialization Date')
502 ('module_name_uniq', 'unique(name, module)',
503 'Constraints with the same name are unique per module.'),
506 def _module_data_uninstall(self, cr, uid, ids, context=None):
508 Delete PostgreSQL foreign keys and constraints tracked by this model.
511 if uid != SUPERUSER_ID and not self.pool['ir.model.access'].check_groups(cr, uid, "base.group_system"):
512 raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
514 context = dict(context or {})
519 for data in self.browse(cr, uid, ids, context):
520 model = data.model.model
521 model_obj = self.pool[model]
522 name = openerp.tools.ustr(data.name)
525 # double-check we are really going to delete all the owners of this schema element
526 cr.execute("""SELECT id from ir_model_constraint where name=%s""", (data.name,))
527 external_ids = [x[0] for x in cr.fetchall()]
528 if set(external_ids)-ids_set:
529 # as installed modules have defined this element we must not delete it!
533 # test if FK exists on this table (it could be on a related m2m table, in which case we ignore it)
534 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
535 WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_obj._table))
537 cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
538 _logger.info('Dropped FK CONSTRAINT %s@%s', name, model)
541 # test if constraint exists
542 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
543 WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_obj._table))
545 cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
546 _logger.info('Dropped CONSTRAINT %s@%s', name, model)
548 self.unlink(cr, uid, ids, context)
550 class ir_model_relation(Model):
552 This model tracks PostgreSQL tables used to implement OpenERP many2many
555 _name = 'ir.model.relation'
557 'name': fields.char('Relation Name', required=True, select=1,
558 help="PostgreSQL table name implementing a many2many relation."),
559 'model': fields.many2one('ir.model', string='Model',
560 required=True, select=1),
561 'module': fields.many2one('ir.module.module', string='Module',
562 required=True, select=1),
563 'date_update': fields.datetime('Update Date'),
564 'date_init': fields.datetime('Initialization Date')
567 def _module_data_uninstall(self, cr, uid, ids, context=None):
569 Delete PostgreSQL many2many relations tracked by this model.
572 if uid != SUPERUSER_ID and not self.pool['ir.model.access'].check_groups(cr, uid, "base.group_system"):
573 raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
579 for data in self.browse(cr, uid, ids, context):
581 name = openerp.tools.ustr(data.name)
583 # double-check we are really going to delete all the owners of this schema element
584 cr.execute("""SELECT id from ir_model_relation where name = %s""", (data.name,))
585 external_ids = [x[0] for x in cr.fetchall()]
586 if set(external_ids)-ids_set:
587 # as installed modules have defined this element we must not delete it!
590 cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name=%s", (name,))
591 if cr.fetchone() and not name in to_drop_table:
592 to_drop_table.append(name)
594 self.unlink(cr, uid, ids, context)
596 # drop m2m relation tables
597 for table in to_drop_table:
598 cr.execute('DROP TABLE %s CASCADE'% table,)
599 _logger.info('Dropped table %s', table)
603 class ir_model_access(osv.osv):
604 _name = 'ir.model.access'
606 'name': fields.char('Name', required=True, select=True),
607 '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.'),
608 'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)], select=True, ondelete='cascade'),
609 'group_id': fields.many2one('res.groups', 'Group', ondelete='cascade', select=True),
610 'perm_read': fields.boolean('Read Access'),
611 'perm_write': fields.boolean('Write Access'),
612 'perm_create': fields.boolean('Create Access'),
613 'perm_unlink': fields.boolean('Delete Access'),
619 def check_groups(self, cr, uid, group):
620 grouparr = group.split('.')
623 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],))
624 return bool(cr.fetchone())
626 def check_group(self, cr, uid, model, mode, group_ids):
627 """ Check if a specific group has the access mode to the specified model"""
628 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
630 if isinstance(model, browse_record):
631 assert model._table_name == 'ir.model', 'Invalid model object'
632 model_name = model.name
636 if isinstance(group_ids, (int, long)):
637 group_ids = [group_ids]
638 for group_id in group_ids:
639 cr.execute("SELECT perm_" + mode + " "
640 " FROM ir_model_access a "
641 " JOIN ir_model m ON (m.id = a.model_id) "
642 " WHERE m.model = %s AND a.active IS True "
643 " AND a.group_id = %s", (model_name, group_id)
647 cr.execute("SELECT perm_" + mode + " "
648 " FROM ir_model_access a "
649 " JOIN ir_model m ON (m.id = a.model_id) "
650 " WHERE m.model = %s AND a.active IS True "
651 " AND a.group_id IS NULL", (model_name, )
655 access = bool(r and r[0])
658 # pass no groups -> no access
661 def group_names_with_access(self, cr, model_name, access_mode):
662 """Returns the names of visible groups which have been granted ``access_mode`` on
663 the model ``model_name``.
666 assert access_mode in ['read','write','create','unlink'], 'Invalid access mode: %s' % access_mode
671 JOIN ir_model m ON (a.model_id=m.id)
672 JOIN res_groups g ON (a.group_id=g.id)
673 LEFT JOIN ir_module_category c ON (c.id=g.category_id)
677 a.perm_''' + access_mode, (model_name,))
678 return [('%s/%s' % x) if x[0] else x[1] for x in cr.fetchall()]
681 def check(self, cr, uid, model, mode='read', raise_exception=True, context=None):
683 # User root have all accesses
684 # TODO: exclude xml-rpc requests
687 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
689 if isinstance(model, browse_record):
690 assert model._table_name == 'ir.model', 'Invalid model object'
691 model_name = model.model
695 # TransientModel records have no access rights, only an implicit access rule
696 if model_name not in self.pool:
697 _logger.error('Missing model %s' % (model_name, ))
698 elif self.pool[model_name].is_transient():
701 # We check if a specific rule exists
702 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
703 ' FROM ir_model_access a '
704 ' JOIN ir_model m ON (m.id = a.model_id) '
705 ' JOIN res_groups_users_rel gu ON (gu.gid = a.group_id) '
706 ' WHERE m.model = %s '
708 ' AND a.active IS True '
714 # there is no specific rule. We check the generic rule
715 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
716 ' FROM ir_model_access a '
717 ' JOIN ir_model m ON (m.id = a.model_id) '
718 ' WHERE a.group_id IS NULL '
720 ' AND a.active IS True '
725 if not r and raise_exception:
726 groups = '\n\t'.join('- %s' % g for g in self.group_names_with_access(cr, model_name, mode))
728 # Messages are declared in extenso so they are properly exported in translation terms
729 'read': _("Sorry, you are not allowed to access this document."),
730 'write': _("Sorry, you are not allowed to modify this document."),
731 'create': _("Sorry, you are not allowed to create this kind of document."),
732 'unlink': _("Sorry, you are not allowed to delete this document."),
735 msg_tail = _("Only users with the following access level are currently allowed to do that") + ":\n%s\n\n(" + _("Document model") + ": %s)"
736 msg_params = (groups, model_name)
738 msg_tail = _("Please contact your system administrator if you think this is an error.") + "\n\n(" + _("Document model") + ": %s)"
739 msg_params = (model_name,)
740 _logger.warning('Access Denied by ACLs for operation: %s, uid: %s, model: %s', mode, uid, model_name)
741 msg = '%s %s' % (msg_heads[mode], msg_tail)
742 raise openerp.exceptions.AccessError(msg % msg_params)
745 __cache_clearing_methods = []
747 def register_cache_clearing_method(self, model, method):
748 self.__cache_clearing_methods.append((model, method))
750 def unregister_cache_clearing_method(self, model, method):
752 i = self.__cache_clearing_methods.index((model, method))
753 del self.__cache_clearing_methods[i]
757 def call_cache_clearing_methods(self, cr):
758 self.check.clear_cache(self) # clear the cache of check function
759 for model, method in self.__cache_clearing_methods:
760 if model in self.pool:
761 getattr(self.pool[model], method)()
764 # Check rights on actions
766 def write(self, cr, uid, *args, **argv):
767 self.call_cache_clearing_methods(cr)
768 res = super(ir_model_access, self).write(cr, uid, *args, **argv)
771 def create(self, cr, uid, *args, **argv):
772 self.call_cache_clearing_methods(cr)
773 res = super(ir_model_access, self).create(cr, uid, *args, **argv)
776 def unlink(self, cr, uid, *args, **argv):
777 self.call_cache_clearing_methods(cr)
778 res = super(ir_model_access, self).unlink(cr, uid, *args, **argv)
781 class ir_model_data(osv.osv):
782 """Holds external identifier keys for records in the database.
783 This has two main uses:
785 * allows easy data integration with third-party systems,
786 making import/export/sync of data possible, as records
787 can be uniquely identified across multiple systems
788 * allows tracking the origin of data installed by OpenERP
789 modules themselves, thus making it possible to later
790 update them seamlessly.
792 _name = 'ir.model.data'
793 _order = 'module,model,name'
794 def _display_name_get(self, cr, uid, ids, prop, unknow_none, context=None):
797 for res in self.browse(cr, uid, ids, context=context):
799 result.setdefault(res.model, {})
800 result[res.model][res.res_id] = res.id
801 result2[res.id] = False
805 r = dict(self.pool[model].name_get(cr, uid, result[model].keys(), context=context))
806 for key,val in result[model].items():
807 result2[val] = r.get(key, False)
809 # some object have no valid name_get implemented, we accept this
813 def _complete_name_get(self, cr, uid, ids, prop, unknow_none, context=None):
815 for res in self.browse(cr, uid, ids, context=context):
816 result[res.id] = (res.module and (res.module + '.') or '')+res.name
820 'name': fields.char('External Identifier', required=True, select=1,
821 help="External Key/Identifier that can be used for "
822 "data integration with third-party systems"),
823 'complete_name': fields.function(_complete_name_get, type='char', string='Complete ID'),
824 'display_name': fields.function(_display_name_get, type='char', string='Record Name'),
825 'model': fields.char('Model Name', required=True, select=1),
826 'module': fields.char('Module', required=True, select=1),
827 'res_id': fields.integer('Record ID', select=1,
828 help="ID of the target record in the database"),
829 'noupdate': fields.boolean('Non Updatable'),
830 'date_update': fields.datetime('Update Date'),
831 'date_init': fields.datetime('Init Date')
834 'date_init': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
835 'date_update': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
840 ('module_name_uniq', 'unique(name, module)', 'You cannot have multiple records with the same external ID in the same module!'),
843 def __init__(self, pool, cr):
844 osv.osv.__init__(self, pool, cr)
846 # also stored in pool to avoid being discarded along with this osv instance
847 if getattr(pool, 'model_data_reference_ids', None) is None:
848 self.pool.model_data_reference_ids = {}
850 self.loads = self.pool.model_data_reference_ids
852 def _auto_init(self, cr, context=None):
853 super(ir_model_data, self)._auto_init(cr, context)
854 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_model_data_module_name_index\'')
855 if not cr.fetchone():
856 cr.execute('CREATE INDEX ir_model_data_module_name_index ON ir_model_data (module, name)')
859 @tools.ormcache(skiparg=3)
860 def xmlid_lookup(self, cr, uid, xmlid):
861 """Low level xmlid lookup
862 Return (id, res_model, res_id) or raise ValueError if not found
864 module, name = xmlid.split('.', 1)
865 ids = self.search(cr, uid, [('module','=',module), ('name','=', name)])
867 raise ValueError('External ID not found in the system: %s' % (xmlid))
868 # the sql constraints ensure us we have only one result
869 res = self.read(cr, uid, ids[0], ['model', 'res_id'])
870 if not res['res_id']:
871 raise ValueError('External ID not found in the system: %s' % (xmlid))
872 return ids[0], res['model'], res['res_id']
874 def xmlid_to_res_model_res_id(self, cr, uid, xmlid, raise_if_not_found=False):
875 """ Return (res_model, res_id)"""
877 return self.xmlid_lookup(cr, uid, xmlid)[1:3]
879 if raise_if_not_found:
881 return (False, False)
883 def xmlid_to_res_id(self, cr, uid, xmlid, raise_if_not_found=False):
884 """ Returns res_id """
885 return self.xmlid_to_res_model_res_id(cr, uid, xmlid, raise_if_not_found)[1]
887 def xmlid_to_object(self, cr, uid, xmlid, raise_if_not_found=False, context=None):
888 """ Return a browse_record
889 if not found and raise_if_not_found is True return the browse_null
891 t = self.xmlid_to_res_model_res_id(cr, uid, xmlid, raise_if_not_found)
892 res_model, res_id = t
894 if res_model and res_id:
895 record = self.pool[res_model].browse(cr, uid, res_id, context=context)
898 if raise_if_not_found:
899 raise ValueError('No record found for unique ID %s. It may have been deleted.' % (xml_id))
903 def _get_id(self, cr, uid, module, xml_id):
904 """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"""
905 return self.xmlid_lookup(cr, uid, "%s.%s" % (module, xml_id))[0]
907 def get_object_reference(self, cr, uid, module, xml_id):
908 """Returns (model, res_id) corresponding to a given module and xml_id (cached) or raise ValueError if not found"""
909 return self.xmlid_lookup(cr, uid, "%s.%s" % (module, xml_id))[1:3]
911 def check_object_reference(self, cr, uid, module, xml_id, raise_on_access_error=False):
912 """Returns (model, res_id) corresponding to a given module and xml_id (cached), if and only if the user has the necessary access rights
913 to see that object, otherwise raise a ValueError if raise_on_access_error is True or returns a tuple (model found, False)"""
914 model, res_id = self.get_object_reference(cr, uid, module, xml_id)
915 #search on id found in result to check if current user has read access right
916 check_right = self.pool.get(model).search(cr, uid, [('id', '=', res_id)])
919 if raise_on_access_error:
920 raise ValueError('Not enough access rights on the external ID: %s.%s' % (module, xml_id))
923 def get_object(self, cr, uid, module, xml_id, context=None):
924 """ Returns a browsable record for the given module name and xml_id.
925 If not found, raise a ValueError or return a browse_null, depending
926 on the value of `raise_exception`.
928 return self.xmlid_to_object(cr, uid, "%s.%s" % (module, xml_id), raise_if_not_found=True, context=context)
930 def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
934 id = self.read(cr, uid, [self._get_id(cr, uid, module, xml_id)], ['res_id'])[0]['res_id']
935 self.loads[(module,xml_id)] = (model,id)
940 def clear_caches(self):
941 """ Clears all orm caches on the object's methods
945 self.xmlid_lookup.clear_cache(self)
948 def unlink(self, cr, uid, ids, context=None):
949 """ Regular unlink method, but make sure to clear the caches. """
951 return super(ir_model_data,self).unlink(cr, uid, ids, context=context)
953 def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None):
954 model_obj = self.pool[model]
957 # records created during module install should not display the messages of OpenChatter
958 context = dict(context, install_mode=True)
959 if xml_id and ('.' in xml_id):
960 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
961 module, xml_id = xml_id.split('.')
962 if (not xml_id) and (not self.doinit):
966 cr.execute('''SELECT imd.id, imd.res_id, md.id, imd.model, imd.noupdate
967 FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
968 WHERE imd.module=%%s AND imd.name=%%s''' % model_obj._table,
970 results = cr.fetchall()
971 for imd_id2,res_id2,real_id2,real_model,noupdate_imd in results:
972 # In update mode, do not update a record if it's ir.model.data is flagged as noupdate
973 if mode == 'update' and noupdate_imd:
977 cr.execute('delete from ir_model_data where id=%s', (imd_id2,))
980 assert model == real_model, "External ID conflict, %s already refers to a `%s` record,"\
981 " you can't define a `%s` record with this ID." % (xml_id, real_model, model)
982 res_id,action_id = res_id2,imd_id2
984 if action_id and res_id:
985 model_obj.write(cr, uid, [res_id], values, context=context)
986 self.write(cr, uid, [action_id], {
987 'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
990 model_obj.write(cr, uid, [res_id], values, context=context)
992 if model_obj._inherits:
993 for table in model_obj._inherits:
994 inherit_id = model_obj.browse(cr, uid,
995 res_id,context=context)[model_obj._inherits[table]]
996 self.create(cr, uid, {
997 'name': xml_id + '_' + table.replace('.', '_'),
1000 'res_id': inherit_id.id,
1001 'noupdate': noupdate,
1003 self.create(cr, uid, {
1008 'noupdate': noupdate,
1011 if mode=='init' or (mode=='update' and xml_id):
1012 res_id = model_obj.create(cr, uid, values, context=context)
1014 if model_obj._inherits:
1015 for table in model_obj._inherits:
1016 inherit_id = model_obj.browse(cr, uid,
1017 res_id,context=context)[model_obj._inherits[table]]
1018 self.create(cr, uid, {
1019 'name': xml_id + '_' + table.replace('.', '_'),
1022 'res_id': inherit_id.id,
1023 'noupdate': noupdate,
1025 self.create(cr, uid, {
1030 'noupdate': noupdate
1032 if xml_id and res_id:
1033 self.loads[(module, xml_id)] = (model, res_id)
1034 for table, inherit_field in model_obj._inherits.iteritems():
1035 inherit_id = model_obj.read(cr, uid, res_id,
1036 [inherit_field])[inherit_field]
1037 self.loads[(module, xml_id + '_' + table.replace('.', '_'))] = (table, inherit_id)
1040 def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None, xml_id=False):
1041 if isinstance(models[0], (list, tuple)):
1042 model,res_id = models[0]
1048 where = ' and res_id=%s' % (res_id,)
1050 where = ' and (res_id is null)'
1053 where += ' and key2=\'%s\'' % (key2,)
1055 where += ' and (key2 is null)'
1057 cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
1060 ir_values_obj = openerp.registry(cr.dbname)['ir.values']
1061 ir_values_obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
1063 cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
1066 def _module_data_uninstall(self, cr, uid, modules_to_remove, context=None):
1067 """Deletes all the records referenced by the ir.model.data entries
1068 ``ids`` along with their corresponding database backed (including
1069 dropping tables, columns, FKs, etc, as long as there is no other
1070 ir.model.data entry holding a reference to them (which indicates that
1071 they are still owned by another module).
1072 Attempts to perform the deletion in an appropriate order to maximize
1073 the chance of gracefully deleting all records.
1074 This step is performed as part of the full uninstallation of a module.
1077 ids = self.search(cr, uid, [('module', 'in', modules_to_remove)])
1079 if uid != 1 and not self.pool['ir.model.access'].check_groups(cr, uid, "base.group_system"):
1080 raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
1082 context = dict(context or {})
1083 context[MODULE_UNINSTALL_FLAG] = True # enable model/field deletion
1090 for data in self.browse(cr, uid, ids, context):
1092 res_id = data.res_id
1094 pair_to_unlink = (model, res_id)
1095 if pair_to_unlink not in to_unlink:
1096 to_unlink.append(pair_to_unlink)
1098 if model == 'workflow.activity':
1099 # Special treatment for workflow activities: temporarily revert their
1100 # incoming transition and trigger an update to force all workflow items
1101 # to move out before deleting them
1102 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,))
1103 wkf_todo.extend(cr.fetchall())
1104 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))
1106 for model,res_id in wkf_todo:
1108 openerp.workflow.trg_write(uid, model, res_id, cr)
1110 _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)
1112 def unlink_if_refcount(to_unlink):
1113 for model, res_id in to_unlink:
1114 external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
1115 if set(external_ids)-ids_set:
1116 # if other modules have defined this record, we must not delete it
1118 if model == 'ir.model.fields':
1119 # Don't remove the LOG_ACCESS_COLUMNS unless _log_access
1120 # has been turned off on the model.
1121 field = self.pool[model].browse(cr, uid, [res_id], context=context)[0]
1122 if not field.exists():
1123 _logger.info('Deleting orphan external_ids %s', external_ids)
1124 self.unlink(cr, uid, external_ids)
1126 if field.name in openerp.osv.orm.LOG_ACCESS_COLUMNS and self.pool[field.model]._log_access:
1128 if field.name == 'id':
1130 _logger.info('Deleting %s@%s', res_id, model)
1132 cr.execute('SAVEPOINT record_unlink_save')
1133 self.pool[model].unlink(cr, uid, [res_id], context=context)
1135 _logger.info('Unable to delete %s@%s', res_id, model, exc_info=True)
1136 cr.execute('ROLLBACK TO SAVEPOINT record_unlink_save')
1138 cr.execute('RELEASE SAVEPOINT record_unlink_save')
1140 # Remove non-model records first, then model fields, and finish with models
1141 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1142 if model not in ('ir.model','ir.model.fields'))
1143 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1144 if model == 'ir.model.fields')
1146 ir_model_relation = self.pool['ir.model.relation']
1147 ir_module_module = self.pool['ir.module.module']
1148 modules_to_remove_ids = ir_module_module.search(cr, uid, [('name', 'in', modules_to_remove)])
1149 relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove_ids)])
1150 ir_model_relation._module_data_uninstall(cr, uid, relation_ids, context)
1152 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1153 if model == 'ir.model')
1157 self.unlink(cr, uid, ids, context)
1159 def _process_end(self, cr, uid, modules):
1160 """ Clear records removed from updated module data.
1161 This method is called at the end of the module loading process.
1162 It is meant to removed records that are no longer present in the
1163 updated data. Such records are recognised as the one with an xml id
1164 and a module in ir_model_data and noupdate set to false, but not
1165 present in self.loads.
1170 cr.execute("""SELECT id,name,model,res_id,module FROM ir_model_data
1171 WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s ORDER BY id DESC""",
1172 (tuple(modules), False))
1173 for (id, name, model, res_id, module) in cr.fetchall():
1174 if (module,name) not in self.loads:
1175 to_unlink.append((model,res_id))
1176 if not config.get('import_partial'):
1177 for (model, res_id) in to_unlink:
1178 if model in self.pool:
1179 _logger.info('Deleting %s@%s', res_id, model)
1180 self.pool[model].unlink(cr, uid, [res_id])
1182 class wizard_model_menu(osv.osv_memory):
1183 _name = 'wizard.ir.model.menu.create'
1185 'menu_id': fields.many2one('ir.ui.menu', 'Parent Menu', required=True),
1186 'name': fields.char('Menu Name', size=64, required=True),
1189 def menu_create(self, cr, uid, ids, context=None):
1192 model_pool = self.pool.get('ir.model')
1193 for menu in self.browse(cr, uid, ids, context):
1194 model = model_pool.browse(cr, uid, context.get('model_id'), context=context)
1197 'res_model': model.model,
1198 'view_type': 'form',
1199 'view_mode': 'tree,form'
1201 action_id = self.pool.get('ir.actions.act_window').create(cr, uid, val)
1202 self.pool.get('ir.ui.menu').create(cr, uid, {
1204 'parent_id': menu.menu_id.id,
1205 'action': 'ir.actions.act_window,%d' % (action_id,),
1206 'icon': 'STOCK_INDENT'
1208 return {'type':'ir.actions.act_window_close'}
1210 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: