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 BaseModel, Model, MAGIC_COLUMNS, except_orm
33 from openerp.tools import config
34 from openerp.tools.safe_eval import safe_eval as eval
35 from openerp.tools.translate import _
37 _logger = logging.getLogger(__name__)
39 MODULE_UNINSTALL_FLAG = '_force_unlink'
41 def _get_fields_type(self, cr, uid, context=None):
42 # Avoid too many nested `if`s below, as RedHat's Python 2.6
43 # break on it. See bug 939653.
44 return sorted([(k,k) for k,v in fields.__dict__.iteritems()
45 if type(v) == types.TypeType and \
46 issubclass(v, fields._column) and \
47 v != fields._column and \
48 not v._deprecated and \
49 not issubclass(v, fields.function)])
51 def _in_modules(self, cr, uid, ids, field_name, arg, context=None):
52 #pseudo-method used by fields.function in ir.model/ir.model.fields
53 module_pool = self.pool["ir.module.module"]
54 installed_module_ids = module_pool.search(cr, uid, [('state','=','installed')])
55 installed_module_names = module_pool.read(cr, uid, installed_module_ids, ['name'], context=context)
56 installed_modules = set(x['name'] for x in installed_module_names)
59 xml_ids = osv.osv._get_xml_ids(self, cr, uid, ids)
60 for k,v in xml_ids.iteritems():
61 result[k] = ', '.join(sorted(installed_modules & set(xml_id.split('.')[0] for xml_id in v)))
64 class ir_model(osv.osv):
66 _description = "Models"
69 def _is_osv_memory(self, cr, uid, ids, field_name, arg, context=None):
70 models = self.browse(cr, uid, ids, context=context)
71 res = dict.fromkeys(ids)
73 if model.model in self.pool:
74 res[model.id] = self.pool[model.model].is_transient()
76 _logger.error('Missing model %s' % (model.model, ))
79 def _search_osv_memory(self, cr, uid, model, name, domain, context=None):
82 __, operator, value = domain[0]
83 if operator not in ['=', '!=']:
84 raise osv.except_osv(_("Invalid Search Criteria"), _('The osv_memory field can only be compared with = and != operator.'))
85 value = bool(value) if operator == '=' else not bool(value)
86 all_model_ids = self.search(cr, uid, [], context=context)
87 is_osv_mem = self._is_osv_memory(cr, uid, all_model_ids, 'osv_memory', arg=None, context=context)
88 return [('id', 'in', [id for id in is_osv_mem if bool(is_osv_mem[id]) == value])]
90 def _view_ids(self, cr, uid, ids, field_name, arg, context=None):
91 models = self.browse(cr, uid, ids)
94 res[model.id] = self.pool["ir.ui.view"].search(cr, uid, [('model', '=', model.model)])
98 'name': fields.char('Model Description', translate=True, required=True),
99 'model': fields.char('Model', required=True, select=1),
100 'info': fields.text('Information'),
101 'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields', required=True, copy=True),
102 'state': fields.selection([('manual','Custom Object'),('base','Base Object')],'Type', readonly=True),
103 'access_ids': fields.one2many('ir.model.access', 'model_id', 'Access'),
104 'osv_memory': fields.function(_is_osv_memory, string='Transient Model', type='boolean',
105 fnct_search=_search_osv_memory,
106 help="This field specifies whether the model is transient or not (i.e. if records are automatically deleted from the database or not)"),
107 'modules': fields.function(_in_modules, type='char', string='In Modules', help='List of modules in which the object is defined or inherited'),
108 'view_ids': fields.function(_view_ids, type='one2many', obj='ir.ui.view', string='Views'),
113 'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
116 def _check_model_name(self, cr, uid, ids, context=None):
117 for model in self.browse(cr, uid, ids, context=context):
118 if model.state=='manual':
119 if not model.model.startswith('x_'):
121 if not re.match('^[a-z_A-Z0-9.]+$',model.model):
125 def _model_name_msg(self, cr, uid, ids, context=None):
126 return _('The Object name must start with x_ and not contain any special character !')
129 (_check_model_name, _model_name_msg, ['model']),
132 ('obj_name_uniq', 'unique (model)', 'Each model must be unique!'),
135 def _search_display_name(self, operator, value):
136 # overridden to allow searching both on model name (model field) and
137 # model description (name field)
138 return ['|', ('model', operator, value), ('name', operator, value)]
140 def _drop_table(self, cr, uid, ids, context=None):
141 for model in self.browse(cr, uid, ids, context):
142 model_pool = self.pool[model.model]
143 cr.execute('select relkind from pg_class where relname=%s', (model_pool._table,))
144 result = cr.fetchone()
145 if result and result[0] == 'v':
146 cr.execute('DROP view %s' % (model_pool._table,))
147 elif result and result[0] == 'r':
148 cr.execute('DROP TABLE %s CASCADE' % (model_pool._table,))
151 def unlink(self, cr, user, ids, context=None):
152 # Prevent manual deletion of module tables
153 if context is None: context = {}
154 if isinstance(ids, (int, long)):
156 if not context.get(MODULE_UNINSTALL_FLAG):
157 for model in self.browse(cr, user, ids, context):
158 if model.state != 'manual':
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 cr.commit() # must be committed before reloading registry in new cursor
167 openerp.modules.registry.RegistryManager.new(cr.dbname)
168 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
172 def write(self, cr, user, ids, vals, context=None):
174 context = dict(context)
175 context.pop('__last_update', None)
176 # Filter out operations 4 link from field id, because openerp-web
177 # always write (4,id,False) even for non dirty items
178 if 'field_id' in vals:
179 vals['field_id'] = [op for op in vals['field_id'] if op[0] != 4]
180 return super(ir_model,self).write(cr, user, ids, vals, context)
182 def create(self, cr, user, vals, context=None):
185 if context and context.get('manual'):
186 vals['state']='manual'
187 res = super(ir_model,self).create(cr, user, vals, context)
188 if vals.get('state','base')=='manual':
189 self.instanciate(cr, user, vals['model'], context)
190 model = self.pool[vals['model']]
191 model._prepare_setup_fields(cr, SUPERUSER_ID)
192 model._setup_fields(cr, SUPERUSER_ID)
194 field_name=vals['name'],
195 field_state='manual',
196 select=vals.get('select_level', '0'),
197 update_custom_fields=True)
198 model._auto_init(cr, ctx)
199 model._auto_end(cr, ctx) # actually create FKs!
200 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
203 def instanciate(self, cr, user, model, context=None):
204 class x_custom_model(osv.osv):
206 if isinstance(model, unicode):
207 model = model.encode('utf-8')
208 x_custom_model._name = model
209 x_custom_model._module = False
210 a = x_custom_model._build_model(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)
316 # remove m2m relation table for custom fields
317 # we consider the m2m relation is only one way as it's not possible
318 # to specify the relation table in the interface for custom fields
319 # TODO master: maybe use ir.model.relations for custom fields
320 if field.state == 'manual' and field.ttype == 'many2many':
321 rel_name = self.pool[field.model]._all_columns[field.name].column._rel
322 cr.execute('DROP table "%s"' % (rel_name))
325 def unlink(self, cr, user, ids, context=None):
326 # Prevent manual deletion of module columns
327 if context is None: context = {}
328 if isinstance(ids, (int, long)):
330 if not context.get(MODULE_UNINSTALL_FLAG) and \
331 any(field.state != 'manual' for field in self.browse(cr, user, ids, context)):
332 raise except_orm(_('Error'), _("This column contains module data and cannot be removed!"))
334 self._drop_column(cr, user, ids, context)
335 res = super(ir_model_fields, self).unlink(cr, user, ids, context)
336 if not context.get(MODULE_UNINSTALL_FLAG):
338 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
341 def create(self, cr, user, vals, context=None):
342 if 'model_id' in vals:
343 model_data = self.pool['ir.model'].browse(cr, user, vals['model_id'])
344 vals['model'] = model_data.model
347 if context and context.get('manual',False):
348 vals['state'] = 'manual'
349 if vals.get('ttype', False) == 'selection':
350 if not vals.get('selection',False):
351 raise except_orm(_('Error'), _('For selection fields, the Selection Options must be given!'))
352 self._check_selection(cr, user, vals['selection'], context=context)
353 res = super(ir_model_fields,self).create(cr, user, vals, context)
354 if vals.get('state','base') == 'manual':
355 if not vals['name'].startswith('x_'):
356 raise except_orm(_('Error'), _("Custom fields must have a name that starts with 'x_' !"))
358 if vals.get('relation',False) and not self.pool['ir.model'].search(cr, user, [('model','=',vals['relation'])]):
359 raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
361 if vals['model'] in self.pool:
362 model = self.pool[vals['model']]
363 if vals['model'].startswith('x_') and vals['name'] == 'x_name':
364 model._rec_name = 'x_name'
365 model.__init__(self.pool, cr)
366 model._prepare_setup_fields(cr, SUPERUSER_ID)
367 model._setup_fields(cr, SUPERUSER_ID)
369 #Added context to _auto_init for special treatment to custom field for select_level
371 field_name=vals['name'],
372 field_state='manual',
373 select=vals.get('select_level', '0'),
374 update_custom_fields=True)
375 model._auto_init(cr, ctx)
376 model._auto_end(cr, ctx) # actually create FKs!
377 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
381 def write(self, cr, user, ids, vals, context=None):
384 if context and context.get('manual',False):
385 vals['state'] = 'manual'
387 #For the moment renaming a sparse field or changing the storing system is not allowed. This may be done later
388 if 'serialization_field_id' in vals or 'name' in vals:
389 for field in self.browse(cr, user, ids, context=context):
390 if 'serialization_field_id' in vals and field.serialization_field_id.id != vals['serialization_field_id']:
391 raise except_orm(_('Error!'), _('Changing the storing system for field "%s" is not allowed.')%field.name)
392 if field.serialization_field_id and (field.name != vals['name']):
393 raise except_orm(_('Error!'), _('Renaming sparse field "%s" is not allowed')%field.name)
395 column_rename = None # if set, *one* column can be renamed here
396 models_patch = {} # structs of (obj, [(field, prop, change_to),..])
397 # data to be updated on the orm model
399 # static table of properties
400 model_props = [ # (our-name, fields.prop, set_fn)
401 ('field_description', 'string', tools.ustr),
402 ('required', 'required', bool),
403 ('readonly', 'readonly', bool),
404 ('domain', '_domain', eval),
405 ('size', 'size', int),
406 ('on_delete', 'ondelete', str),
407 ('translate', 'translate', bool),
408 ('selectable', 'selectable', bool),
409 ('select_level', 'select', int),
410 ('selection', 'selection', eval),
414 checked_selection = False # need only check it once, so defer
416 for item in self.browse(cr, user, ids, context=context):
417 obj = self.pool.get(item.model)
419 if item.state != 'manual':
420 raise except_orm(_('Error!'),
421 _('Properties of base fields cannot be altered in this manner! '
422 'Please modify them through Python code, '
423 'preferably through a custom addon!'))
425 if item.ttype == 'selection' and 'selection' in vals \
426 and not checked_selection:
427 self._check_selection(cr, user, vals['selection'], context=context)
428 checked_selection = True
430 final_name = item.name
431 if 'name' in vals and vals['name'] != item.name:
432 # We need to rename the column
434 raise except_orm(_('Error!'), _('Can only rename one column at a time!'))
435 if vals['name'] in obj._columns:
436 raise except_orm(_('Error!'), _('Cannot rename column to %s, because that column already exists!') % vals['name'])
437 if vals.get('state', 'base') == 'manual' and not vals['name'].startswith('x_'):
438 raise except_orm(_('Error!'), _('New column name must still start with x_ , because it is a custom field!'))
439 if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']:
440 raise ValueError('Invalid character in column name')
441 column_rename = (obj, (obj._table, item.name, vals['name']))
442 final_name = vals['name']
444 if 'model_id' in vals and vals['model_id'] != item.model_id:
445 raise except_orm(_("Error!"), _("Changing the model of a field is forbidden!"))
447 if 'ttype' in vals and vals['ttype'] != item.ttype:
448 raise except_orm(_("Error!"), _("Changing the type of a column is not yet supported. "
449 "Please drop it and create it again!"))
451 # We don't check the 'state', because it might come from the context
452 # (thus be set for multiple fields) and will be ignored anyway.
454 models_patch.setdefault(obj._name, (obj,[]))
455 # find out which properties (per model) we need to update
456 for field_name, field_property, set_fn in model_props:
457 if field_name in vals:
458 property_value = set_fn(vals[field_name])
459 if getattr(obj._columns[item.name], field_property) != property_value:
460 models_patch[obj._name][1].append((final_name, field_property, property_value))
461 # our dict is ready here, but no properties are changed so far
463 # These shall never be written (modified)
464 for column_name in ('model_id', 'model', 'state'):
465 if column_name in vals:
466 del vals[column_name]
468 res = super(ir_model_fields,self).write(cr, user, ids, vals, context=context)
471 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % column_rename[1])
472 # This is VERY risky, but let us have this feature:
473 # we want to change the key of column in obj._columns dict
474 col = column_rename[0]._columns.pop(column_rename[1][1]) # take object out, w/o copy
475 column_rename[0]._columns[column_rename[1][2]] = col
478 # We have to update _columns of the model(s) and then call their
479 # _auto_init to sync the db with the model. Hopefully, since write()
480 # was called earlier, they will be in-sync before the _auto_init.
481 # Anything we don't update in _columns now will be reset from
482 # the model into ir.model.fields (db).
483 ctx = dict(context, select=vals.get('select_level', '0'),
484 update_custom_fields=True)
486 for __, patch_struct in models_patch.items():
487 obj = patch_struct[0]
488 for col_name, col_prop, val in patch_struct[1]:
489 setattr(obj._columns[col_name], col_prop, val)
490 obj._auto_init(cr, ctx)
491 obj._auto_end(cr, ctx) # actually create FKs!
492 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
495 class ir_model_constraint(Model):
497 This model tracks PostgreSQL foreign keys and constraints used by OpenERP
500 _name = 'ir.model.constraint'
502 'name': fields.char('Constraint', required=True, select=1,
503 help="PostgreSQL constraint or foreign key name."),
504 'model': fields.many2one('ir.model', string='Model',
505 required=True, select=1),
506 'module': fields.many2one('ir.module.module', string='Module',
507 required=True, select=1),
508 'type': fields.char('Constraint Type', required=True, size=1, select=1,
509 help="Type of the constraint: `f` for a foreign key, "
510 "`u` for other constraints."),
511 'date_update': fields.datetime('Update Date'),
512 'date_init': fields.datetime('Initialization Date')
516 ('module_name_uniq', 'unique(name, module)',
517 'Constraints with the same name are unique per module.'),
520 def _module_data_uninstall(self, cr, uid, ids, context=None):
522 Delete PostgreSQL foreign keys and constraints tracked by this model.
525 if uid != SUPERUSER_ID and not self.pool['ir.model.access'].check_groups(cr, uid, "base.group_system"):
526 raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
528 context = dict(context or {})
533 for data in self.browse(cr, uid, ids, context):
534 model = data.model.model
535 model_obj = self.pool[model]
536 name = openerp.tools.ustr(data.name)
539 # double-check we are really going to delete all the owners of this schema element
540 cr.execute("""SELECT id from ir_model_constraint where name=%s""", (data.name,))
541 external_ids = [x[0] for x in cr.fetchall()]
542 if set(external_ids)-ids_set:
543 # as installed modules have defined this element we must not delete it!
547 # test if FK exists on this table (it could be on a related m2m table, in which case we ignore it)
548 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
549 WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_obj._table))
551 cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
552 _logger.info('Dropped FK CONSTRAINT %s@%s', name, model)
555 # test if constraint exists
556 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
557 WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_obj._table))
559 cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
560 _logger.info('Dropped CONSTRAINT %s@%s', name, model)
562 self.unlink(cr, uid, ids, context)
564 class ir_model_relation(Model):
566 This model tracks PostgreSQL tables used to implement OpenERP many2many
569 _name = 'ir.model.relation'
571 'name': fields.char('Relation Name', required=True, select=1,
572 help="PostgreSQL table name implementing a many2many relation."),
573 'model': fields.many2one('ir.model', string='Model',
574 required=True, select=1),
575 'module': fields.many2one('ir.module.module', string='Module',
576 required=True, select=1),
577 'date_update': fields.datetime('Update Date'),
578 'date_init': fields.datetime('Initialization Date')
581 def _module_data_uninstall(self, cr, uid, ids, context=None):
583 Delete PostgreSQL many2many relations tracked by this model.
586 if uid != SUPERUSER_ID and not self.pool['ir.model.access'].check_groups(cr, uid, "base.group_system"):
587 raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
593 for data in self.browse(cr, uid, ids, context):
595 name = openerp.tools.ustr(data.name)
597 # double-check we are really going to delete all the owners of this schema element
598 cr.execute("""SELECT id from ir_model_relation where name = %s""", (data.name,))
599 external_ids = [x[0] for x in cr.fetchall()]
600 if set(external_ids)-ids_set:
601 # as installed modules have defined this element we must not delete it!
604 cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name=%s", (name,))
605 if cr.fetchone() and not name in to_drop_table:
606 to_drop_table.append(name)
608 self.unlink(cr, uid, ids, context)
610 # drop m2m relation tables
611 for table in to_drop_table:
612 cr.execute('DROP TABLE %s CASCADE'% table,)
613 _logger.info('Dropped table %s', table)
617 class ir_model_access(osv.osv):
618 _name = 'ir.model.access'
620 'name': fields.char('Name', required=True, select=True),
621 '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.'),
622 'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)], select=True, ondelete='cascade'),
623 'group_id': fields.many2one('res.groups', 'Group', ondelete='cascade', select=True),
624 'perm_read': fields.boolean('Read Access'),
625 'perm_write': fields.boolean('Write Access'),
626 'perm_create': fields.boolean('Create Access'),
627 'perm_unlink': fields.boolean('Delete Access'),
633 def check_groups(self, cr, uid, group):
634 grouparr = group.split('.')
637 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],))
638 return bool(cr.fetchone())
640 def check_group(self, cr, uid, model, mode, group_ids):
641 """ Check if a specific group has the access mode to the specified model"""
642 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
644 if isinstance(model, BaseModel):
645 assert model._name == 'ir.model', 'Invalid model object'
646 model_name = model.name
650 if isinstance(group_ids, (int, long)):
651 group_ids = [group_ids]
652 for group_id in group_ids:
653 cr.execute("SELECT perm_" + mode + " "
654 " FROM ir_model_access a "
655 " JOIN ir_model m ON (m.id = a.model_id) "
656 " WHERE m.model = %s AND a.active IS True "
657 " AND a.group_id = %s", (model_name, group_id)
661 cr.execute("SELECT perm_" + mode + " "
662 " FROM ir_model_access a "
663 " JOIN ir_model m ON (m.id = a.model_id) "
664 " WHERE m.model = %s AND a.active IS True "
665 " AND a.group_id IS NULL", (model_name, )
669 access = bool(r and r[0])
672 # pass no groups -> no access
675 def group_names_with_access(self, cr, model_name, access_mode):
676 """Returns the names of visible groups which have been granted ``access_mode`` on
677 the model ``model_name``.
680 assert access_mode in ['read','write','create','unlink'], 'Invalid access mode: %s' % access_mode
685 JOIN ir_model m ON (a.model_id=m.id)
686 JOIN res_groups g ON (a.group_id=g.id)
687 LEFT JOIN ir_module_category c ON (c.id=g.category_id)
691 a.perm_''' + access_mode, (model_name,))
692 return [('%s/%s' % x) if x[0] else x[1] for x in cr.fetchall()]
695 def check(self, cr, uid, model, mode='read', raise_exception=True, context=None):
697 # User root have all accesses
698 # TODO: exclude xml-rpc requests
701 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
703 if isinstance(model, BaseModel):
704 assert model._name == 'ir.model', 'Invalid model object'
705 model_name = model.model
709 # TransientModel records have no access rights, only an implicit access rule
710 if model_name not in self.pool:
711 _logger.error('Missing model %s' % (model_name, ))
712 elif self.pool[model_name].is_transient():
715 # We check if a specific rule exists
716 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
717 ' FROM ir_model_access a '
718 ' JOIN ir_model m ON (m.id = a.model_id) '
719 ' JOIN res_groups_users_rel gu ON (gu.gid = a.group_id) '
720 ' WHERE m.model = %s '
722 ' AND a.active IS True '
728 # there is no specific rule. We check the generic rule
729 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
730 ' FROM ir_model_access a '
731 ' JOIN ir_model m ON (m.id = a.model_id) '
732 ' WHERE a.group_id IS NULL '
734 ' AND a.active IS True '
739 if not r and raise_exception:
740 groups = '\n\t'.join('- %s' % g for g in self.group_names_with_access(cr, model_name, mode))
742 # Messages are declared in extenso so they are properly exported in translation terms
743 'read': _("Sorry, you are not allowed to access this document."),
744 'write': _("Sorry, you are not allowed to modify this document."),
745 'create': _("Sorry, you are not allowed to create this kind of document."),
746 'unlink': _("Sorry, you are not allowed to delete this document."),
749 msg_tail = _("Only users with the following access level are currently allowed to do that") + ":\n%s\n\n(" + _("Document model") + ": %s)"
750 msg_params = (groups, model_name)
752 msg_tail = _("Please contact your system administrator if you think this is an error.") + "\n\n(" + _("Document model") + ": %s)"
753 msg_params = (model_name,)
754 _logger.warning('Access Denied by ACLs for operation: %s, uid: %s, model: %s', mode, uid, model_name)
755 msg = '%s %s' % (msg_heads[mode], msg_tail)
756 raise openerp.exceptions.AccessError(msg % msg_params)
759 __cache_clearing_methods = []
761 def register_cache_clearing_method(self, model, method):
762 self.__cache_clearing_methods.append((model, method))
764 def unregister_cache_clearing_method(self, model, method):
766 i = self.__cache_clearing_methods.index((model, method))
767 del self.__cache_clearing_methods[i]
771 def call_cache_clearing_methods(self, cr):
772 self.invalidate_cache(cr, SUPERUSER_ID)
773 self.check.clear_cache(self) # clear the cache of check function
774 for model, method in self.__cache_clearing_methods:
775 if model in self.pool:
776 getattr(self.pool[model], method)()
779 # Check rights on actions
781 def write(self, cr, uid, ids, values, context=None):
782 self.call_cache_clearing_methods(cr)
783 res = super(ir_model_access, self).write(cr, uid, ids, values, context=context)
786 def create(self, cr, uid, values, context=None):
787 self.call_cache_clearing_methods(cr)
788 res = super(ir_model_access, self).create(cr, uid, values, context=context)
791 def unlink(self, cr, uid, ids, context=None):
792 self.call_cache_clearing_methods(cr)
793 res = super(ir_model_access, self).unlink(cr, uid, ids, context=context)
796 class ir_model_data(osv.osv):
797 """Holds external identifier keys for records in the database.
798 This has two main uses:
800 * allows easy data integration with third-party systems,
801 making import/export/sync of data possible, as records
802 can be uniquely identified across multiple systems
803 * allows tracking the origin of data installed by OpenERP
804 modules themselves, thus making it possible to later
805 update them seamlessly.
807 _name = 'ir.model.data'
808 _order = 'module,model,name'
809 def _display_name_get(self, cr, uid, ids, prop, unknow_none, context=None):
812 for res in self.browse(cr, uid, ids, context=context):
814 result.setdefault(res.model, {})
815 result[res.model][res.res_id] = res.id
816 result2[res.id] = False
820 r = dict(self.pool[model].name_get(cr, uid, result[model].keys(), context=context))
821 for key,val in result[model].items():
822 result2[val] = r.get(key, False)
824 # some object have no valid name_get implemented, we accept this
828 def _complete_name_get(self, cr, uid, ids, prop, unknow_none, context=None):
830 for res in self.browse(cr, uid, ids, context=context):
831 result[res.id] = (res.module and (res.module + '.') or '')+res.name
835 'name': fields.char('External Identifier', required=True, select=1,
836 help="External Key/Identifier that can be used for "
837 "data integration with third-party systems"),
838 'complete_name': fields.function(_complete_name_get, type='char', string='Complete ID'),
839 'display_name': fields.function(_display_name_get, type='char', string='Record Name'),
840 'model': fields.char('Model Name', required=True, select=1),
841 'module': fields.char('Module', required=True, select=1),
842 'res_id': fields.integer('Record ID', select=1,
843 help="ID of the target record in the database"),
844 'noupdate': fields.boolean('Non Updatable'),
845 'date_update': fields.datetime('Update Date'),
846 'date_init': fields.datetime('Init Date')
849 'date_init': fields.datetime.now,
850 'date_update': fields.datetime.now,
855 ('module_name_uniq', 'unique(name, module)', 'You cannot have multiple records with the same external ID in the same module!'),
858 def __init__(self, pool, cr):
859 osv.osv.__init__(self, pool, cr)
860 # also stored in pool to avoid being discarded along with this osv instance
861 if getattr(pool, 'model_data_reference_ids', None) is None:
862 self.pool.model_data_reference_ids = {}
863 # put loads on the class, in order to share it among all instances
864 type(self).loads = self.pool.model_data_reference_ids
866 def _auto_init(self, cr, context=None):
867 super(ir_model_data, self)._auto_init(cr, context)
868 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_model_data_module_name_index\'')
869 if not cr.fetchone():
870 cr.execute('CREATE INDEX ir_model_data_module_name_index ON ir_model_data (module, name)')
873 @tools.ormcache(skiparg=3)
874 def xmlid_lookup(self, cr, uid, xmlid):
875 """Low level xmlid lookup
876 Return (id, res_model, res_id) or raise ValueError if not found
878 module, name = xmlid.split('.', 1)
879 ids = self.search(cr, uid, [('module','=',module), ('name','=', name)])
881 raise ValueError('External ID not found in the system: %s' % (xmlid))
882 # the sql constraints ensure us we have only one result
883 res = self.read(cr, uid, ids[0], ['model', 'res_id'])
884 if not res['res_id']:
885 raise ValueError('External ID not found in the system: %s' % (xmlid))
886 return ids[0], res['model'], res['res_id']
888 def xmlid_to_res_model_res_id(self, cr, uid, xmlid, raise_if_not_found=False):
889 """ Return (res_model, res_id)"""
891 return self.xmlid_lookup(cr, uid, xmlid)[1:3]
893 if raise_if_not_found:
895 return (False, False)
897 def xmlid_to_res_id(self, cr, uid, xmlid, raise_if_not_found=False):
898 """ Returns res_id """
899 return self.xmlid_to_res_model_res_id(cr, uid, xmlid, raise_if_not_found)[1]
901 def xmlid_to_object(self, cr, uid, xmlid, raise_if_not_found=False, context=None):
902 """ Return a browse_record
903 if not found and raise_if_not_found is True return None
905 t = self.xmlid_to_res_model_res_id(cr, uid, xmlid, raise_if_not_found)
906 res_model, res_id = t
908 if res_model and res_id:
909 record = self.pool[res_model].browse(cr, uid, res_id, context=context)
912 if raise_if_not_found:
913 raise ValueError('No record found for unique ID %s. It may have been deleted.' % (xml_id))
917 def _get_id(self, cr, uid, module, xml_id):
918 """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"""
919 return self.xmlid_lookup(cr, uid, "%s.%s" % (module, xml_id))[0]
921 def get_object_reference(self, cr, uid, module, xml_id):
922 """Returns (model, res_id) corresponding to a given module and xml_id (cached) or raise ValueError if not found"""
923 return self.xmlid_lookup(cr, uid, "%s.%s" % (module, xml_id))[1:3]
925 def check_object_reference(self, cr, uid, module, xml_id, raise_on_access_error=False):
926 """Returns (model, res_id) corresponding to a given module and xml_id (cached), if and only if the user has the necessary access rights
927 to see that object, otherwise raise a ValueError if raise_on_access_error is True or returns a tuple (model found, False)"""
928 model, res_id = self.get_object_reference(cr, uid, module, xml_id)
929 #search on id found in result to check if current user has read access right
930 check_right = self.pool.get(model).search(cr, uid, [('id', '=', res_id)])
933 if raise_on_access_error:
934 raise ValueError('Not enough access rights on the external ID: %s.%s' % (module, xml_id))
937 def get_object(self, cr, uid, module, xml_id, context=None):
938 """ Returns a browsable record for the given module name and xml_id.
939 If not found, raise a ValueError or return None, depending
940 on the value of `raise_exception`.
942 return self.xmlid_to_object(cr, uid, "%s.%s" % (module, xml_id), raise_if_not_found=True, context=context)
944 def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
948 id = self.read(cr, uid, [self._get_id(cr, uid, module, xml_id)], ['res_id'])[0]['res_id']
949 self.loads[(module,xml_id)] = (model,id)
954 def clear_caches(self):
955 """ Clears all orm caches on the object's methods
959 self.xmlid_lookup.clear_cache(self)
962 def unlink(self, cr, uid, ids, context=None):
963 """ Regular unlink method, but make sure to clear the caches. """
965 return super(ir_model_data,self).unlink(cr, uid, ids, context=context)
967 def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None):
968 model_obj = self.pool[model]
971 # records created during module install should not display the messages of OpenChatter
972 context = dict(context, install_mode=True)
973 if xml_id and ('.' in xml_id):
974 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
975 module, xml_id = xml_id.split('.')
978 cr.execute('''SELECT imd.id, imd.res_id, md.id, imd.model, imd.noupdate
979 FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
980 WHERE imd.module=%%s AND imd.name=%%s''' % model_obj._table,
982 results = cr.fetchall()
983 for imd_id2,res_id2,real_id2,real_model,noupdate_imd in results:
984 # In update mode, do not update a record if it's ir.model.data is flagged as noupdate
985 if mode == 'update' and noupdate_imd:
989 cr.execute('delete from ir_model_data where id=%s', (imd_id2,))
992 assert model == real_model, "External ID conflict, %s already refers to a `%s` record,"\
993 " you can't define a `%s` record with this ID." % (xml_id, real_model, model)
994 res_id,action_id = res_id2,imd_id2
996 if action_id and res_id:
997 model_obj.write(cr, uid, [res_id], values, context=context)
998 self.write(cr, uid, [action_id], {
999 'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
1002 model_obj.write(cr, uid, [res_id], values, context=context)
1004 if model_obj._inherits:
1005 for table in model_obj._inherits:
1006 inherit_id = model_obj.browse(cr, uid,
1007 res_id,context=context)[model_obj._inherits[table]]
1008 self.create(cr, uid, {
1009 'name': xml_id + '_' + table.replace('.', '_'),
1012 'res_id': inherit_id.id,
1013 'noupdate': noupdate,
1015 self.create(cr, uid, {
1020 'noupdate': noupdate,
1023 if mode=='init' or (mode=='update' and xml_id):
1024 res_id = model_obj.create(cr, uid, values, context=context)
1026 if model_obj._inherits:
1027 for table in model_obj._inherits:
1028 inherit_id = model_obj.browse(cr, uid,
1029 res_id,context=context)[model_obj._inherits[table]]
1030 self.create(cr, uid, {
1031 'name': xml_id + '_' + table.replace('.', '_'),
1034 'res_id': inherit_id.id,
1035 'noupdate': noupdate,
1037 self.create(cr, uid, {
1042 'noupdate': noupdate
1044 if xml_id and res_id:
1045 self.loads[(module, xml_id)] = (model, res_id)
1046 for table, inherit_field in model_obj._inherits.iteritems():
1047 inherit_id = model_obj.read(cr, uid, [res_id],
1048 [inherit_field])[0][inherit_field]
1049 self.loads[(module, xml_id + '_' + table.replace('.', '_'))] = (table, inherit_id)
1052 def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None, xml_id=False):
1053 if isinstance(models[0], (list, tuple)):
1054 model,res_id = models[0]
1060 where = ' and res_id=%s' % (res_id,)
1062 where = ' and (res_id is null)'
1065 where += ' and key2=\'%s\'' % (key2,)
1067 where += ' and (key2 is null)'
1069 cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
1071 ir_values_obj = openerp.registry(cr.dbname)['ir.values']
1073 ir_values_obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
1075 cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
1076 ir_values_obj.invalidate_cache(cr, uid, ['value'])
1079 def _module_data_uninstall(self, cr, uid, modules_to_remove, context=None):
1080 """Deletes all the records referenced by the ir.model.data entries
1081 ``ids`` along with their corresponding database backed (including
1082 dropping tables, columns, FKs, etc, as long as there is no other
1083 ir.model.data entry holding a reference to them (which indicates that
1084 they are still owned by another module).
1085 Attempts to perform the deletion in an appropriate order to maximize
1086 the chance of gracefully deleting all records.
1087 This step is performed as part of the full uninstallation of a module.
1090 ids = self.search(cr, uid, [('module', 'in', modules_to_remove)])
1092 if uid != 1 and not self.pool['ir.model.access'].check_groups(cr, uid, "base.group_system"):
1093 raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
1095 context = dict(context or {})
1096 context[MODULE_UNINSTALL_FLAG] = True # enable model/field deletion
1103 for data in self.browse(cr, uid, ids, context):
1105 res_id = data.res_id
1107 pair_to_unlink = (model, res_id)
1108 if pair_to_unlink not in to_unlink:
1109 to_unlink.append(pair_to_unlink)
1111 if model == 'workflow.activity':
1112 # Special treatment for workflow activities: temporarily revert their
1113 # incoming transition and trigger an update to force all workflow items
1114 # to move out before deleting them
1115 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,))
1116 wkf_todo.extend(cr.fetchall())
1117 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))
1118 self.invalidate_cache(cr, uid, context=context)
1120 for model,res_id in wkf_todo:
1122 openerp.workflow.trg_write(uid, model, res_id, cr)
1124 _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)
1126 def unlink_if_refcount(to_unlink):
1127 for model, res_id in to_unlink:
1128 external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
1129 if set(external_ids)-ids_set:
1130 # if other modules have defined this record, we must not delete it
1132 if model == 'ir.model.fields':
1133 # Don't remove the LOG_ACCESS_COLUMNS unless _log_access
1134 # has been turned off on the model.
1135 field = self.pool[model].browse(cr, uid, [res_id], context=context)[0]
1136 if not field.exists():
1137 _logger.info('Deleting orphan external_ids %s', external_ids)
1138 self.unlink(cr, uid, external_ids)
1140 if field.name in openerp.models.LOG_ACCESS_COLUMNS and self.pool[field.model]._log_access:
1142 if field.name == 'id':
1144 _logger.info('Deleting %s@%s', res_id, model)
1146 cr.execute('SAVEPOINT record_unlink_save')
1147 self.pool[model].unlink(cr, uid, [res_id], context=context)
1149 _logger.info('Unable to delete %s@%s', res_id, model, exc_info=True)
1150 cr.execute('ROLLBACK TO SAVEPOINT record_unlink_save')
1152 cr.execute('RELEASE SAVEPOINT record_unlink_save')
1154 # Remove non-model records first, then model fields, and finish with models
1155 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1156 if model not in ('ir.model','ir.model.fields','ir.model.constraint'))
1157 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1158 if model == 'ir.model.constraint')
1160 ir_module_module = self.pool['ir.module.module']
1161 ir_model_constraint = self.pool['ir.model.constraint']
1162 modules_to_remove_ids = ir_module_module.search(cr, uid, [('name', 'in', modules_to_remove)], context=context)
1163 constraint_ids = ir_model_constraint.search(cr, uid, [('module', 'in', modules_to_remove_ids)], context=context)
1164 ir_model_constraint._module_data_uninstall(cr, uid, constraint_ids, context)
1166 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1167 if model == 'ir.model.fields')
1169 ir_model_relation = self.pool['ir.model.relation']
1170 relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove_ids)])
1171 ir_model_relation._module_data_uninstall(cr, uid, relation_ids, context)
1173 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1174 if model == 'ir.model')
1178 self.unlink(cr, uid, ids, context)
1180 def _process_end(self, cr, uid, modules):
1181 """ Clear records removed from updated module data.
1182 This method is called at the end of the module loading process.
1183 It is meant to removed records that are no longer present in the
1184 updated data. Such records are recognised as the one with an xml id
1185 and a module in ir_model_data and noupdate set to false, but not
1186 present in self.loads.
1191 cr.execute("""SELECT id,name,model,res_id,module FROM ir_model_data
1192 WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s ORDER BY id DESC""",
1193 (tuple(modules), False))
1194 for (id, name, model, res_id, module) in cr.fetchall():
1195 if (module,name) not in self.loads:
1196 to_unlink.append((model,res_id))
1197 if not config.get('import_partial'):
1198 for (model, res_id) in to_unlink:
1199 if model in self.pool:
1200 _logger.info('Deleting %s@%s', res_id, model)
1201 self.pool[model].unlink(cr, uid, [res_id])
1203 class wizard_model_menu(osv.osv_memory):
1204 _name = 'wizard.ir.model.menu.create'
1206 'menu_id': fields.many2one('ir.ui.menu', 'Parent Menu', required=True),
1207 'name': fields.char('Menu Name', required=True),
1210 def menu_create(self, cr, uid, ids, context=None):
1213 model_pool = self.pool.get('ir.model')
1214 for menu in self.browse(cr, uid, ids, context):
1215 model = model_pool.browse(cr, uid, context.get('model_id'), context=context)
1218 'res_model': model.model,
1219 'view_type': 'form',
1220 'view_mode': 'tree,form'
1222 action_id = self.pool.get('ir.actions.act_window').create(cr, uid, val)
1223 self.pool.get('ir.ui.menu').create(cr, uid, {
1225 'parent_id': menu.menu_id.id,
1226 'action': 'ir.actions.act_window,%d' % (action_id,),
1227 'icon': 'STOCK_INDENT'
1229 return {'type':'ir.actions.act_window_close'}
1231 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: