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 if vals['model'].startswith('x_') and vals['name'] == 'x_name':
355 self.pool[vals['model']]._rec_name = 'x_name'
356 self.pool[vals['model']].__init__(self.pool, cr)
357 #Added context to _auto_init for special treatment to custom field for select_level
359 field_name=vals['name'],
360 field_state='manual',
361 select=vals.get('select_level', '0'),
362 update_custom_fields=True)
363 self.pool[vals['model']]._auto_init(cr, ctx)
364 self.pool[vals['model']]._auto_end(cr, ctx) # actually create FKs!
365 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
369 def write(self, cr, user, ids, vals, context=None):
372 if context and context.get('manual',False):
373 vals['state'] = 'manual'
375 #For the moment renaming a sparse field or changing the storing system is not allowed. This may be done later
376 if 'serialization_field_id' in vals or 'name' in vals:
377 for field in self.browse(cr, user, ids, context=context):
378 if 'serialization_field_id' in vals and field.serialization_field_id.id != vals['serialization_field_id']:
379 raise except_orm(_('Error!'), _('Changing the storing system for field "%s" is not allowed.')%field.name)
380 if field.serialization_field_id and (field.name != vals['name']):
381 raise except_orm(_('Error!'), _('Renaming sparse field "%s" is not allowed')%field.name)
383 column_rename = None # if set, *one* column can be renamed here
384 models_patch = {} # structs of (obj, [(field, prop, change_to),..])
385 # data to be updated on the orm model
387 # static table of properties
388 model_props = [ # (our-name, fields.prop, set_fn)
389 ('field_description', 'string', tools.ustr),
390 ('required', 'required', bool),
391 ('readonly', 'readonly', bool),
392 ('domain', '_domain', eval),
393 ('size', 'size', int),
394 ('on_delete', 'ondelete', str),
395 ('translate', 'translate', bool),
396 ('selectable', 'selectable', bool),
397 ('select_level', 'select', int),
398 ('selection', 'selection', eval),
402 checked_selection = False # need only check it once, so defer
404 for item in self.browse(cr, user, ids, context=context):
405 obj = self.pool.get(item.model)
407 if item.state != 'manual':
408 raise except_orm(_('Error!'),
409 _('Properties of base fields cannot be altered in this manner! '
410 'Please modify them through Python code, '
411 'preferably through a custom addon!'))
413 if item.ttype == 'selection' and 'selection' in vals \
414 and not checked_selection:
415 self._check_selection(cr, user, vals['selection'], context=context)
416 checked_selection = True
418 final_name = item.name
419 if 'name' in vals and vals['name'] != item.name:
420 # We need to rename the column
422 raise except_orm(_('Error!'), _('Can only rename one column at a time!'))
423 if vals['name'] in obj._columns:
424 raise except_orm(_('Error!'), _('Cannot rename column to %s, because that column already exists!') % vals['name'])
425 if vals.get('state', 'base') == 'manual' and not vals['name'].startswith('x_'):
426 raise except_orm(_('Error!'), _('New column name must still start with x_ , because it is a custom field!'))
427 if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']:
428 raise ValueError('Invalid character in column name')
429 column_rename = (obj, (obj._table, item.name, vals['name']))
430 final_name = vals['name']
432 if 'model_id' in vals and vals['model_id'] != item.model_id:
433 raise except_orm(_("Error!"), _("Changing the model of a field is forbidden!"))
435 if 'ttype' in vals and vals['ttype'] != item.ttype:
436 raise except_orm(_("Error!"), _("Changing the type of a column is not yet supported. "
437 "Please drop it and create it again!"))
439 # We don't check the 'state', because it might come from the context
440 # (thus be set for multiple fields) and will be ignored anyway.
442 models_patch.setdefault(obj._name, (obj,[]))
443 # find out which properties (per model) we need to update
444 for field_name, field_property, set_fn in model_props:
445 if field_name in vals:
446 property_value = set_fn(vals[field_name])
447 if getattr(obj._columns[item.name], field_property) != property_value:
448 models_patch[obj._name][1].append((final_name, field_property, property_value))
449 # our dict is ready here, but no properties are changed so far
451 # These shall never be written (modified)
452 for column_name in ('model_id', 'model', 'state'):
453 if column_name in vals:
454 del vals[column_name]
456 res = super(ir_model_fields,self).write(cr, user, ids, vals, context=context)
459 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % column_rename[1])
460 # This is VERY risky, but let us have this feature:
461 # we want to change the key of column in obj._columns dict
462 col = column_rename[0]._columns.pop(column_rename[1][1]) # take object out, w/o copy
463 column_rename[0]._columns[column_rename[1][2]] = col
466 # We have to update _columns of the model(s) and then call their
467 # _auto_init to sync the db with the model. Hopefully, since write()
468 # was called earlier, they will be in-sync before the _auto_init.
469 # Anything we don't update in _columns now will be reset from
470 # the model into ir.model.fields (db).
471 ctx = dict(context, select=vals.get('select_level', '0'),
472 update_custom_fields=True)
474 for __, patch_struct in models_patch.items():
475 obj = patch_struct[0]
476 for col_name, col_prop, val in patch_struct[1]:
477 setattr(obj._columns[col_name], col_prop, val)
478 obj._auto_init(cr, ctx)
479 obj._auto_end(cr, ctx) # actually create FKs!
480 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
483 class ir_model_constraint(Model):
485 This model tracks PostgreSQL foreign keys and constraints used by OpenERP
488 _name = 'ir.model.constraint'
490 'name': fields.char('Constraint', required=True, select=1,
491 help="PostgreSQL constraint or foreign key name."),
492 'model': fields.many2one('ir.model', string='Model',
493 required=True, select=1),
494 'module': fields.many2one('ir.module.module', string='Module',
495 required=True, select=1),
496 'type': fields.char('Constraint Type', required=True, size=1, select=1,
497 help="Type of the constraint: `f` for a foreign key, "
498 "`u` for other constraints."),
499 'date_update': fields.datetime('Update Date'),
500 'date_init': fields.datetime('Initialization Date')
504 ('module_name_uniq', 'unique(name, module)',
505 'Constraints with the same name are unique per module.'),
508 def _module_data_uninstall(self, cr, uid, ids, context=None):
510 Delete PostgreSQL foreign keys and constraints tracked by this model.
513 if uid != SUPERUSER_ID and not self.pool['ir.model.access'].check_groups(cr, uid, "base.group_system"):
514 raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
516 context = dict(context or {})
521 for data in self.browse(cr, uid, ids, context):
522 model = data.model.model
523 model_obj = self.pool[model]
524 name = openerp.tools.ustr(data.name)
527 # double-check we are really going to delete all the owners of this schema element
528 cr.execute("""SELECT id from ir_model_constraint where name=%s""", (data.name,))
529 external_ids = [x[0] for x in cr.fetchall()]
530 if set(external_ids)-ids_set:
531 # as installed modules have defined this element we must not delete it!
535 # test if FK exists on this table (it could be on a related m2m table, in which case we ignore it)
536 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
537 WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_obj._table))
539 cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
540 _logger.info('Dropped FK CONSTRAINT %s@%s', name, model)
543 # test if constraint exists
544 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
545 WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_obj._table))
547 cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
548 _logger.info('Dropped CONSTRAINT %s@%s', name, model)
550 self.unlink(cr, uid, ids, context)
552 class ir_model_relation(Model):
554 This model tracks PostgreSQL tables used to implement OpenERP many2many
557 _name = 'ir.model.relation'
559 'name': fields.char('Relation Name', required=True, select=1,
560 help="PostgreSQL table name implementing a many2many relation."),
561 'model': fields.many2one('ir.model', string='Model',
562 required=True, select=1),
563 'module': fields.many2one('ir.module.module', string='Module',
564 required=True, select=1),
565 'date_update': fields.datetime('Update Date'),
566 'date_init': fields.datetime('Initialization Date')
569 def _module_data_uninstall(self, cr, uid, ids, context=None):
571 Delete PostgreSQL many2many relations tracked by this model.
574 if uid != SUPERUSER_ID and not self.pool['ir.model.access'].check_groups(cr, uid, "base.group_system"):
575 raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
581 for data in self.browse(cr, uid, ids, context):
583 name = openerp.tools.ustr(data.name)
585 # double-check we are really going to delete all the owners of this schema element
586 cr.execute("""SELECT id from ir_model_relation where name = %s""", (data.name,))
587 external_ids = [x[0] for x in cr.fetchall()]
588 if set(external_ids)-ids_set:
589 # as installed modules have defined this element we must not delete it!
592 cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name=%s", (name,))
593 if cr.fetchone() and not name in to_drop_table:
594 to_drop_table.append(name)
596 self.unlink(cr, uid, ids, context)
598 # drop m2m relation tables
599 for table in to_drop_table:
600 cr.execute('DROP TABLE %s CASCADE'% table,)
601 _logger.info('Dropped table %s', table)
605 class ir_model_access(osv.osv):
606 _name = 'ir.model.access'
608 'name': fields.char('Name', required=True, select=True),
609 '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.'),
610 'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)], select=True, ondelete='cascade'),
611 'group_id': fields.many2one('res.groups', 'Group', ondelete='cascade', select=True),
612 'perm_read': fields.boolean('Read Access'),
613 'perm_write': fields.boolean('Write Access'),
614 'perm_create': fields.boolean('Create Access'),
615 'perm_unlink': fields.boolean('Delete Access'),
621 def check_groups(self, cr, uid, group):
622 grouparr = group.split('.')
625 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],))
626 return bool(cr.fetchone())
628 def check_group(self, cr, uid, model, mode, group_ids):
629 """ Check if a specific group has the access mode to the specified model"""
630 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
632 if isinstance(model, browse_record):
633 assert model._table_name == 'ir.model', 'Invalid model object'
634 model_name = model.name
638 if isinstance(group_ids, (int, long)):
639 group_ids = [group_ids]
640 for group_id in group_ids:
641 cr.execute("SELECT perm_" + mode + " "
642 " FROM ir_model_access a "
643 " JOIN ir_model m ON (m.id = a.model_id) "
644 " WHERE m.model = %s AND a.active IS True "
645 " AND a.group_id = %s", (model_name, group_id)
649 cr.execute("SELECT perm_" + mode + " "
650 " FROM ir_model_access a "
651 " JOIN ir_model m ON (m.id = a.model_id) "
652 " WHERE m.model = %s AND a.active IS True "
653 " AND a.group_id IS NULL", (model_name, )
657 access = bool(r and r[0])
660 # pass no groups -> no access
663 def group_names_with_access(self, cr, model_name, access_mode):
664 """Returns the names of visible groups which have been granted ``access_mode`` on
665 the model ``model_name``.
668 assert access_mode in ['read','write','create','unlink'], 'Invalid access mode: %s' % access_mode
673 JOIN ir_model m ON (a.model_id=m.id)
674 JOIN res_groups g ON (a.group_id=g.id)
675 LEFT JOIN ir_module_category c ON (c.id=g.category_id)
679 a.perm_''' + access_mode, (model_name,))
680 return [('%s/%s' % x) if x[0] else x[1] for x in cr.fetchall()]
683 def check(self, cr, uid, model, mode='read', raise_exception=True, context=None):
685 # User root have all accesses
686 # TODO: exclude xml-rpc requests
689 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
691 if isinstance(model, browse_record):
692 assert model._table_name == 'ir.model', 'Invalid model object'
693 model_name = model.model
697 # TransientModel records have no access rights, only an implicit access rule
698 if model_name not in self.pool:
699 _logger.error('Missing model %s' % (model_name, ))
700 elif self.pool[model_name].is_transient():
703 # We check if a specific rule exists
704 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
705 ' FROM ir_model_access a '
706 ' JOIN ir_model m ON (m.id = a.model_id) '
707 ' JOIN res_groups_users_rel gu ON (gu.gid = a.group_id) '
708 ' WHERE m.model = %s '
710 ' AND a.active IS True '
716 # there is no specific rule. We check the generic rule
717 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
718 ' FROM ir_model_access a '
719 ' JOIN ir_model m ON (m.id = a.model_id) '
720 ' WHERE a.group_id IS NULL '
722 ' AND a.active IS True '
727 if not r and raise_exception:
728 groups = '\n\t'.join('- %s' % g for g in self.group_names_with_access(cr, model_name, mode))
730 # Messages are declared in extenso so they are properly exported in translation terms
731 'read': _("Sorry, you are not allowed to access this document."),
732 'write': _("Sorry, you are not allowed to modify this document."),
733 'create': _("Sorry, you are not allowed to create this kind of document."),
734 'unlink': _("Sorry, you are not allowed to delete this document."),
737 msg_tail = _("Only users with the following access level are currently allowed to do that") + ":\n%s\n\n(" + _("Document model") + ": %s)"
738 msg_params = (groups, model_name)
740 msg_tail = _("Please contact your system administrator if you think this is an error.") + "\n\n(" + _("Document model") + ": %s)"
741 msg_params = (model_name,)
742 _logger.warning('Access Denied by ACLs for operation: %s, uid: %s, model: %s', mode, uid, model_name)
743 msg = '%s %s' % (msg_heads[mode], msg_tail)
744 raise openerp.exceptions.AccessError(msg % msg_params)
747 __cache_clearing_methods = []
749 def register_cache_clearing_method(self, model, method):
750 self.__cache_clearing_methods.append((model, method))
752 def unregister_cache_clearing_method(self, model, method):
754 i = self.__cache_clearing_methods.index((model, method))
755 del self.__cache_clearing_methods[i]
759 def call_cache_clearing_methods(self, cr):
760 self.check.clear_cache(self) # clear the cache of check function
761 for model, method in self.__cache_clearing_methods:
762 if model in self.pool:
763 getattr(self.pool[model], method)()
766 # Check rights on actions
768 def write(self, cr, uid, *args, **argv):
769 self.call_cache_clearing_methods(cr)
770 res = super(ir_model_access, self).write(cr, uid, *args, **argv)
773 def create(self, cr, uid, *args, **argv):
774 self.call_cache_clearing_methods(cr)
775 res = super(ir_model_access, self).create(cr, uid, *args, **argv)
778 def unlink(self, cr, uid, *args, **argv):
779 self.call_cache_clearing_methods(cr)
780 res = super(ir_model_access, self).unlink(cr, uid, *args, **argv)
783 class ir_model_data(osv.osv):
784 """Holds external identifier keys for records in the database.
785 This has two main uses:
787 * allows easy data integration with third-party systems,
788 making import/export/sync of data possible, as records
789 can be uniquely identified across multiple systems
790 * allows tracking the origin of data installed by OpenERP
791 modules themselves, thus making it possible to later
792 update them seamlessly.
794 _name = 'ir.model.data'
795 _order = 'module,model,name'
796 def _display_name_get(self, cr, uid, ids, prop, unknow_none, context=None):
799 for res in self.browse(cr, uid, ids, context=context):
801 result.setdefault(res.model, {})
802 result[res.model][res.res_id] = res.id
803 result2[res.id] = False
807 r = dict(self.pool[model].name_get(cr, uid, result[model].keys(), context=context))
808 for key,val in result[model].items():
809 result2[val] = r.get(key, False)
811 # some object have no valid name_get implemented, we accept this
815 def _complete_name_get(self, cr, uid, ids, prop, unknow_none, context=None):
817 for res in self.browse(cr, uid, ids, context=context):
818 result[res.id] = (res.module and (res.module + '.') or '')+res.name
822 'name': fields.char('External Identifier', required=True, select=1,
823 help="External Key/Identifier that can be used for "
824 "data integration with third-party systems"),
825 'complete_name': fields.function(_complete_name_get, type='char', string='Complete ID'),
826 'display_name': fields.function(_display_name_get, type='char', string='Record Name'),
827 'model': fields.char('Model Name', required=True, select=1),
828 'module': fields.char('Module', required=True, select=1),
829 'res_id': fields.integer('Record ID', select=1,
830 help="ID of the target record in the database"),
831 'noupdate': fields.boolean('Non Updatable'),
832 'date_update': fields.datetime('Update Date'),
833 'date_init': fields.datetime('Init Date')
836 'date_init': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
837 'date_update': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
842 ('module_name_uniq', 'unique(name, module)', 'You cannot have multiple records with the same external ID in the same module!'),
845 def __init__(self, pool, cr):
846 osv.osv.__init__(self, pool, cr)
848 # also stored in pool to avoid being discarded along with this osv instance
849 if getattr(pool, 'model_data_reference_ids', None) is None:
850 self.pool.model_data_reference_ids = {}
852 self.loads = self.pool.model_data_reference_ids
854 def _auto_init(self, cr, context=None):
855 super(ir_model_data, self)._auto_init(cr, context)
856 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_model_data_module_name_index\'')
857 if not cr.fetchone():
858 cr.execute('CREATE INDEX ir_model_data_module_name_index ON ir_model_data (module, name)')
861 @tools.ormcache(skiparg=3)
862 def xmlid_lookup(self, cr, uid, xmlid):
863 """Low level xmlid lookup
864 Return (id, res_model, res_id) or raise ValueError if not found
866 module, name = xmlid.split('.', 1)
867 ids = self.search(cr, uid, [('module','=',module), ('name','=', name)])
869 raise ValueError('External ID not found in the system: %s' % (xmlid))
870 # the sql constraints ensure us we have only one result
871 res = self.read(cr, uid, ids[0], ['model', 'res_id'])
872 if not res['res_id']:
873 raise ValueError('External ID not found in the system: %s' % (xmlid))
874 return ids[0], res['model'], res['res_id']
876 def xmlid_to_res_model_res_id(self, cr, uid, xmlid, raise_if_not_found=False):
877 """ Return (res_model, res_id)"""
879 return self.xmlid_lookup(cr, uid, xmlid)[1:3]
881 if raise_if_not_found:
883 return (False, False)
885 def xmlid_to_res_id(self, cr, uid, xmlid, raise_if_not_found=False):
886 """ Returns res_id """
887 return self.xmlid_to_res_model_res_id(cr, uid, xmlid, raise_if_not_found)[1]
889 def xmlid_to_object(self, cr, uid, xmlid, raise_if_not_found=False, context=None):
890 """ Return a browse_record
891 if not found and raise_if_not_found is True return the browse_null
893 t = self.xmlid_to_res_model_res_id(cr, uid, xmlid, raise_if_not_found)
894 res_model, res_id = t
896 if res_model and res_id:
897 record = self.pool[res_model].browse(cr, uid, res_id, context=context)
900 if raise_if_not_found:
901 raise ValueError('No record found for unique ID %s. It may have been deleted.' % (xml_id))
905 def _get_id(self, cr, uid, module, xml_id):
906 """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"""
907 return self.xmlid_lookup(cr, uid, "%s.%s" % (module, xml_id))[0]
909 def get_object_reference(self, cr, uid, module, xml_id):
910 """Returns (model, res_id) corresponding to a given module and xml_id (cached) or raise ValueError if not found"""
911 return self.xmlid_lookup(cr, uid, "%s.%s" % (module, xml_id))[1:3]
913 def check_object_reference(self, cr, uid, module, xml_id, raise_on_access_error=False):
914 """Returns (model, res_id) corresponding to a given module and xml_id (cached), if and only if the user has the necessary access rights
915 to see that object, otherwise raise a ValueError if raise_on_access_error is True or returns a tuple (model found, False)"""
916 model, res_id = self.get_object_reference(cr, uid, module, xml_id)
917 #search on id found in result to check if current user has read access right
918 check_right = self.pool.get(model).search(cr, uid, [('id', '=', res_id)])
921 if raise_on_access_error:
922 raise ValueError('Not enough access rights on the external ID: %s.%s' % (module, xml_id))
925 def get_object(self, cr, uid, module, xml_id, context=None):
926 """ Returns a browsable record for the given module name and xml_id.
927 If not found, raise a ValueError or return a browse_null, depending
928 on the value of `raise_exception`.
930 return self.xmlid_to_object(cr, uid, "%s.%s" % (module, xml_id), raise_if_not_found=True, context=context)
932 def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
936 id = self.read(cr, uid, [self._get_id(cr, uid, module, xml_id)], ['res_id'])[0]['res_id']
937 self.loads[(module,xml_id)] = (model,id)
942 def clear_caches(self):
943 """ Clears all orm caches on the object's methods
947 self.xmlid_lookup.clear_cache(self)
950 def unlink(self, cr, uid, ids, context=None):
951 """ Regular unlink method, but make sure to clear the caches. """
953 return super(ir_model_data,self).unlink(cr, uid, ids, context=context)
955 def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None):
956 model_obj = self.pool[model]
959 # records created during module install should not display the messages of OpenChatter
960 context = dict(context, install_mode=True)
961 if xml_id and ('.' in xml_id):
962 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
963 module, xml_id = xml_id.split('.')
964 if (not xml_id) and (not self.doinit):
968 cr.execute('''SELECT imd.id, imd.res_id, md.id, imd.model, imd.noupdate
969 FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
970 WHERE imd.module=%%s AND imd.name=%%s''' % model_obj._table,
972 results = cr.fetchall()
973 for imd_id2,res_id2,real_id2,real_model,noupdate_imd in results:
974 # In update mode, do not update a record if it's ir.model.data is flagged as noupdate
975 if mode == 'update' and noupdate_imd:
979 cr.execute('delete from ir_model_data where id=%s', (imd_id2,))
982 assert model == real_model, "External ID conflict, %s already refers to a `%s` record,"\
983 " you can't define a `%s` record with this ID." % (xml_id, real_model, model)
984 res_id,action_id = res_id2,imd_id2
986 if action_id and res_id:
987 model_obj.write(cr, uid, [res_id], values, context=context)
988 self.write(cr, uid, [action_id], {
989 'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
992 model_obj.write(cr, uid, [res_id], values, context=context)
994 if model_obj._inherits:
995 for table in model_obj._inherits:
996 inherit_id = model_obj.browse(cr, uid,
997 res_id,context=context)[model_obj._inherits[table]]
998 self.create(cr, uid, {
999 'name': xml_id + '_' + table.replace('.', '_'),
1002 'res_id': inherit_id.id,
1003 'noupdate': noupdate,
1005 self.create(cr, uid, {
1010 'noupdate': noupdate,
1013 if mode=='init' or (mode=='update' and xml_id):
1014 res_id = model_obj.create(cr, uid, values, context=context)
1016 if model_obj._inherits:
1017 for table in model_obj._inherits:
1018 inherit_id = model_obj.browse(cr, uid,
1019 res_id,context=context)[model_obj._inherits[table]]
1020 self.create(cr, uid, {
1021 'name': xml_id + '_' + table.replace('.', '_'),
1024 'res_id': inherit_id.id,
1025 'noupdate': noupdate,
1027 self.create(cr, uid, {
1032 'noupdate': noupdate
1034 if xml_id and res_id:
1035 self.loads[(module, xml_id)] = (model, res_id)
1036 for table, inherit_field in model_obj._inherits.iteritems():
1037 inherit_id = model_obj.read(cr, uid, res_id,
1038 [inherit_field])[inherit_field]
1039 self.loads[(module, xml_id + '_' + table.replace('.', '_'))] = (table, inherit_id)
1042 def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None, xml_id=False):
1043 if isinstance(models[0], (list, tuple)):
1044 model,res_id = models[0]
1050 where = ' and res_id=%s' % (res_id,)
1052 where = ' and (res_id is null)'
1055 where += ' and key2=\'%s\'' % (key2,)
1057 where += ' and (key2 is null)'
1059 cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
1062 ir_values_obj = openerp.registry(cr.dbname)['ir.values']
1063 ir_values_obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
1065 cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
1068 def _module_data_uninstall(self, cr, uid, modules_to_remove, context=None):
1069 """Deletes all the records referenced by the ir.model.data entries
1070 ``ids`` along with their corresponding database backed (including
1071 dropping tables, columns, FKs, etc, as long as there is no other
1072 ir.model.data entry holding a reference to them (which indicates that
1073 they are still owned by another module).
1074 Attempts to perform the deletion in an appropriate order to maximize
1075 the chance of gracefully deleting all records.
1076 This step is performed as part of the full uninstallation of a module.
1079 ids = self.search(cr, uid, [('module', 'in', modules_to_remove)])
1081 if uid != 1 and not self.pool['ir.model.access'].check_groups(cr, uid, "base.group_system"):
1082 raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
1084 context = dict(context or {})
1085 context[MODULE_UNINSTALL_FLAG] = True # enable model/field deletion
1092 for data in self.browse(cr, uid, ids, context):
1094 res_id = data.res_id
1096 pair_to_unlink = (model, res_id)
1097 if pair_to_unlink not in to_unlink:
1098 to_unlink.append(pair_to_unlink)
1100 if model == 'workflow.activity':
1101 # Special treatment for workflow activities: temporarily revert their
1102 # incoming transition and trigger an update to force all workflow items
1103 # to move out before deleting them
1104 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,))
1105 wkf_todo.extend(cr.fetchall())
1106 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))
1108 for model,res_id in wkf_todo:
1110 openerp.workflow.trg_write(uid, model, res_id, cr)
1112 _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)
1114 def unlink_if_refcount(to_unlink):
1115 for model, res_id in to_unlink:
1116 external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
1117 if set(external_ids)-ids_set:
1118 # if other modules have defined this record, we must not delete it
1120 if model == 'ir.model.fields':
1121 # Don't remove the LOG_ACCESS_COLUMNS unless _log_access
1122 # has been turned off on the model.
1123 field = self.pool[model].browse(cr, uid, [res_id], context=context)[0]
1124 if not field.exists():
1125 _logger.info('Deleting orphan external_ids %s', external_ids)
1126 self.unlink(cr, uid, external_ids)
1128 if field.name in openerp.osv.orm.LOG_ACCESS_COLUMNS and self.pool[field.model]._log_access:
1130 if field.name == 'id':
1132 _logger.info('Deleting %s@%s', res_id, model)
1134 cr.execute('SAVEPOINT record_unlink_save')
1135 self.pool[model].unlink(cr, uid, [res_id], context=context)
1137 _logger.info('Unable to delete %s@%s', res_id, model, exc_info=True)
1138 cr.execute('ROLLBACK TO SAVEPOINT record_unlink_save')
1140 cr.execute('RELEASE SAVEPOINT record_unlink_save')
1142 # Remove non-model records first, then model fields, and finish with models
1143 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1144 if model not in ('ir.model','ir.model.fields','ir.model.constraint'))
1145 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1146 if model == 'ir.model.constraint')
1148 ir_module_module = self.pool['ir.module.module']
1149 ir_model_constraint = self.pool['ir.model.constraint']
1150 modules_to_remove_ids = ir_module_module.search(cr, uid, [('name', 'in', modules_to_remove)], context=context)
1151 constraint_ids = ir_model_constraint.search(cr, uid, [('module', 'in', modules_to_remove_ids)], context=context)
1152 ir_model_constraint._module_data_uninstall(cr, uid, constraint_ids, context)
1154 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1155 if model == 'ir.model.fields')
1157 ir_model_relation = self.pool['ir.model.relation']
1158 relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove_ids)])
1159 ir_model_relation._module_data_uninstall(cr, uid, relation_ids, context)
1161 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1162 if model == 'ir.model')
1166 self.unlink(cr, uid, ids, context)
1168 def _process_end(self, cr, uid, modules):
1169 """ Clear records removed from updated module data.
1170 This method is called at the end of the module loading process.
1171 It is meant to removed records that are no longer present in the
1172 updated data. Such records are recognised as the one with an xml id
1173 and a module in ir_model_data and noupdate set to false, but not
1174 present in self.loads.
1179 cr.execute("""SELECT id,name,model,res_id,module FROM ir_model_data
1180 WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s ORDER BY id DESC""",
1181 (tuple(modules), False))
1182 for (id, name, model, res_id, module) in cr.fetchall():
1183 if (module,name) not in self.loads:
1184 to_unlink.append((model,res_id))
1185 if not config.get('import_partial'):
1186 for (model, res_id) in to_unlink:
1187 if model in self.pool:
1188 _logger.info('Deleting %s@%s', res_id, model)
1189 self.pool[model].unlink(cr, uid, [res_id])
1191 class wizard_model_menu(osv.osv_memory):
1192 _name = 'wizard.ir.model.menu.create'
1194 'menu_id': fields.many2one('ir.ui.menu', 'Parent Menu', required=True),
1195 'name': fields.char('Menu Name', size=64, required=True),
1198 def menu_create(self, cr, uid, ids, context=None):
1201 model_pool = self.pool.get('ir.model')
1202 for menu in self.browse(cr, uid, ids, context):
1203 model = model_pool.browse(cr, uid, context.get('model_id'), context=context)
1206 'res_model': model.model,
1207 'view_type': 'form',
1208 'view_mode': 'tree,form'
1210 action_id = self.pool.get('ir.actions.act_window').create(cr, uid, val)
1211 self.pool.get('ir.ui.menu').create(cr, uid, {
1213 'parent_id': menu.menu_id.id,
1214 'action': 'ir.actions.act_window,%d' % (action_id,),
1215 'icon': 'STOCK_INDENT'
1217 return {'type':'ir.actions.act_window_close'}
1219 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: