1 # -*- coding: utf-8 -*-
3 ##############################################################################
5 # OpenERP, Open Source Business Applications
6 # Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Affero General Public License as
10 # published by the Free Software Foundation, either version 3 of the
11 # License, or (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Affero General Public License for more details.
18 # You should have received a copy of the GNU Affero General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
28 import openerp.modules.registry
29 from openerp import SUPERUSER_ID
30 from openerp import netsvc, pooler, tools
31 from openerp.osv import fields,osv
32 from openerp.osv.orm import Model
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.get("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 self.pool.get(model.model):
75 res[model.id] = self.pool.get(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.get("ir.ui.view").search(cr, uid, [('model', '=', model.model)])
99 'name': fields.char('Model Description', size=64, translate=True, required=True),
100 'model': fields.char('Model', size=64, 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', size=128, 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.get(model.model)
149 cr.execute('select relkind from pg_class where relname=%s', (model_pool._table,))
150 result = cr.fetchone()
151 if result and result[0] == 'v':
152 cr.execute('DROP view %s' % (model_pool._table,))
153 elif result and result[0] == 'r':
154 cr.execute('DROP TABLE %s CASCADE' % (model_pool._table,))
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 pooler.restart_pool(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.get(vals['model'])._auto_init(cr, ctx)
201 self.pool.get(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"
225 'name': fields.char('Name', required=True, size=64, select=1),
226 'model': fields.char('Object Name', size=64, required=True, select=1,
227 help="The technical name of the model this field belongs to"),
228 'relation': fields.char('Object Relation', size=64,
229 help="For relationship fields, the technical name of the target model"),
230 'relation_field': fields.char('Relation Field', size=64,
231 help="For one2many fields, the field on the target model that implement the opposite many2one relationship"),
232 'model_id': fields.many2one('ir.model', 'Model', required=True, select=True, ondelete='cascade',
233 help="The model this field belongs to"),
234 'field_description': fields.char('Field Label', required=True, size=256),
235 'ttype': fields.selection(_get_fields_type, 'Field Type',size=64, required=True),
236 'selection': fields.char('Selection Options',size=128, help="List of options for a selection field, "
237 "specified as a Python expression defining a list of (key, label) pairs. "
238 "For example: [('blue','Blue'),('yellow','Yellow')]"),
239 'required': fields.boolean('Required'),
240 'readonly': fields.boolean('Readonly'),
241 'select_level': fields.selection([('0','Not Searchable'),('1','Always Searchable'),('2','Advanced Search (deprecated)')],'Searchable', required=True),
242 'translate': fields.boolean('Translatable', help="Whether values for this field can be translated (enables the translation mechanism for that field)"),
243 'size': fields.integer('Size'),
244 'state': fields.selection([('manual','Custom Field'),('base','Base Field')],'Type', required=True, readonly=True, select=1),
245 'on_delete': fields.selection([('cascade','Cascade'),('set null','Set NULL')], 'On Delete', help='On delete property for many2one fields'),
246 'domain': fields.char('Domain', size=256, help="The optional domain to restrict possible values for relationship fields, "
247 "specified as a Python expression defining a list of triplets. "
248 "For example: [('color','=','red')]"),
249 'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'),
250 'view_load': fields.boolean('View Auto-Load'),
251 'selectable': fields.boolean('Selectable'),
252 'modules': fields.function(_in_modules, type='char', size=128, string='In Modules', help='List of modules in which the field is defined'),
253 'serialization_field_id': fields.many2one('ir.model.fields', 'Serialization Field', domain = "[('ttype','=','serialized')]",
254 ondelete='cascade', help="If set, this field will be stored in the sparse "
255 "structure of the serialization field, instead "
256 "of having its own database column. This cannot be "
257 "changed after creation."),
259 _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.get(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.get('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.get('ir.model').search(cr, user, [('model','=',vals['relation'])]):
359 raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
361 if self.pool.get(vals['model']):
362 if vals['model'].startswith('x_') and vals['name'] == 'x_name':
363 self.pool[vals['model']]._rec_name = 'x_name'
364 self.pool.get(vals['model']).__init__(self.pool, cr)
365 #Added context to _auto_init for special treatment to custom field for select_level
367 field_name=vals['name'],
368 field_state='manual',
369 select=vals.get('select_level', '0'),
370 update_custom_fields=True)
371 self.pool.get(vals['model'])._auto_init(cr, ctx)
372 self.pool.get(vals['model'])._auto_end(cr, ctx) # actually create FKs!
373 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
377 def write(self, cr, user, ids, vals, context=None):
380 if context and context.get('manual',False):
381 vals['state'] = 'manual'
383 #For the moment renaming a sparse field or changing the storing system is not allowed. This may be done later
384 if 'serialization_field_id' in vals or 'name' in vals:
385 for field in self.browse(cr, user, ids, context=context):
386 if 'serialization_field_id' in vals and field.serialization_field_id.id != vals['serialization_field_id']:
387 raise except_orm(_('Error!'), _('Changing the storing system for field "%s" is not allowed.')%field.name)
388 if field.serialization_field_id and (field.name != vals['name']):
389 raise except_orm(_('Error!'), _('Renaming sparse field "%s" is not allowed')%field.name)
391 column_rename = None # if set, *one* column can be renamed here
393 models_patch = {} # structs of (obj, [(field, prop, change_to),..])
394 # data to be updated on the orm model
396 # static table of properties
397 model_props = [ # (our-name, fields.prop, set_fn)
398 ('field_description', 'string', tools.ustr),
399 ('required', 'required', bool),
400 ('readonly', 'readonly', bool),
401 ('domain', '_domain', eval),
402 ('size', 'size', int),
403 ('on_delete', 'ondelete', str),
404 ('translate', 'translate', bool),
405 ('view_load', 'view_load', bool),
406 ('selectable', 'selectable', bool),
407 ('select_level', 'select', int),
408 ('selection', 'selection', eval),
412 checked_selection = False # need only check it once, so defer
414 for item in self.browse(cr, user, ids, context=context):
415 if not (obj and obj._name == item.model):
416 obj = self.pool.get(item.model)
418 if item.state != 'manual':
419 raise except_orm(_('Error!'),
420 _('Properties of base fields cannot be altered in this manner! '
421 'Please modify them through Python code, '
422 'preferably through a custom addon!'))
424 if item.ttype == 'selection' and 'selection' in vals \
425 and not checked_selection:
426 self._check_selection(cr, user, vals['selection'], context=context)
427 checked_selection = True
429 final_name = item.name
430 if 'name' in vals and vals['name'] != item.name:
431 # We need to rename the column
433 raise except_orm(_('Error!'), _('Can only rename one column at a time!'))
434 if vals['name'] in obj._columns:
435 raise except_orm(_('Error!'), _('Cannot rename column to %s, because that column already exists!') % vals['name'])
436 if vals.get('state', 'base') == 'manual' and not vals['name'].startswith('x_'):
437 raise except_orm(_('Error!'), _('New column name must still start with x_ , because it is a custom field!'))
438 if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']:
439 raise ValueError('Invalid character in column name')
440 column_rename = (obj, (obj._table, item.name, vals['name']))
441 final_name = vals['name']
443 if 'model_id' in vals and vals['model_id'] != item.model_id:
444 raise except_orm(_("Error!"), _("Changing the model of a field is forbidden!"))
446 if 'ttype' in vals and vals['ttype'] != item.ttype:
447 raise except_orm(_("Error!"), _("Changing the type of a column is not yet supported. "
448 "Please drop it and create it again!"))
450 # We don't check the 'state', because it might come from the context
451 # (thus be set for multiple fields) and will be ignored anyway.
453 models_patch.setdefault(obj._name, (obj,[]))
454 # find out which properties (per model) we need to update
455 for field_name, field_property, set_fn in model_props:
456 if field_name in vals:
457 property_value = set_fn(vals[field_name])
458 if getattr(obj._columns[item.name], field_property) != property_value:
459 models_patch[obj._name][1].append((final_name, field_property, property_value))
460 # our dict is ready here, but no properties are changed so far
462 # These shall never be written (modified)
463 for column_name in ('model_id', 'model', 'state'):
464 if column_name in vals:
465 del vals[column_name]
467 res = super(ir_model_fields,self).write(cr, user, ids, vals, context=context)
470 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % column_rename[1])
471 # This is VERY risky, but let us have this feature:
472 # we want to change the key of column in obj._columns dict
473 col = column_rename[0]._columns.pop(column_rename[1][1]) # take object out, w/o copy
474 column_rename[0]._columns[column_rename[1][2]] = col
477 # We have to update _columns of the model(s) and then call their
478 # _auto_init to sync the db with the model. Hopefully, since write()
479 # was called earlier, they will be in-sync before the _auto_init.
480 # Anything we don't update in _columns now will be reset from
481 # the model into ir.model.fields (db).
482 ctx = dict(context, select=vals.get('select_level', '0'),
483 update_custom_fields=True)
485 for __, patch_struct in models_patch.items():
486 obj = patch_struct[0]
487 for col_name, col_prop, val in patch_struct[1]:
488 setattr(obj._columns[col_name], col_prop, val)
489 obj._auto_init(cr, ctx)
490 obj._auto_end(cr, ctx) # actually create FKs!
491 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
494 class ir_model_constraint(Model):
496 This model tracks PostgreSQL foreign keys and constraints used by OpenERP
499 _name = 'ir.model.constraint'
501 'name': fields.char('Constraint', required=True, size=128, select=1,
502 help="PostgreSQL constraint or foreign key name."),
503 'model': fields.many2one('ir.model', string='Model',
504 required=True, select=1),
505 'module': fields.many2one('ir.module.module', string='Module',
506 required=True, select=1),
507 'type': fields.char('Constraint Type', required=True, size=1, select=1,
508 help="Type of the constraint: `f` for a foreign key, "
509 "`u` for other constraints."),
510 'date_update': fields.datetime('Update Date'),
511 'date_init': fields.datetime('Initialization Date')
515 ('module_name_uniq', 'unique(name, module)',
516 'Constraints with the same name are unique per module.'),
519 def _module_data_uninstall(self, cr, uid, ids, context=None):
521 Delete PostgreSQL foreign keys and constraints tracked by this model.
524 if uid != SUPERUSER_ID and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"):
525 raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
527 context = dict(context or {})
532 for data in self.browse(cr, uid, ids, context):
533 model = data.model.model
534 model_obj = self.pool.get(model)
535 name = openerp.tools.ustr(data.name)
538 # double-check we are really going to delete all the owners of this schema element
539 cr.execute("""SELECT id from ir_model_constraint where name=%s""", (data.name,))
540 external_ids = [x[0] for x in cr.fetchall()]
541 if set(external_ids)-ids_set:
542 # as installed modules have defined this element we must not delete it!
546 # test if FK exists on this table (it could be on a related m2m table, in which case we ignore it)
547 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
548 WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_obj._table))
550 cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
551 _logger.info('Dropped FK CONSTRAINT %s@%s', name, model)
554 # test if constraint exists
555 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
556 WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_obj._table))
558 cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
559 _logger.info('Dropped CONSTRAINT %s@%s', name, model)
561 self.unlink(cr, uid, ids, context)
563 class ir_model_relation(Model):
565 This model tracks PostgreSQL tables used to implement OpenERP many2many
568 _name = 'ir.model.relation'
570 'name': fields.char('Relation Name', required=True, size=128, select=1,
571 help="PostgreSQL table name implementing a many2many relation."),
572 'model': fields.many2one('ir.model', string='Model',
573 required=True, select=1),
574 'module': fields.many2one('ir.module.module', string='Module',
575 required=True, select=1),
576 'date_update': fields.datetime('Update Date'),
577 'date_init': fields.datetime('Initialization Date')
580 def _module_data_uninstall(self, cr, uid, ids, context=None):
582 Delete PostgreSQL many2many relations tracked by this model.
585 if uid != SUPERUSER_ID and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"):
586 raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
592 for data in self.browse(cr, uid, ids, context):
594 name = openerp.tools.ustr(data.name)
596 # double-check we are really going to delete all the owners of this schema element
597 cr.execute("""SELECT id from ir_model_relation where name = %s""", (data.name,))
598 external_ids = [x[0] for x in cr.fetchall()]
599 if set(external_ids)-ids_set:
600 # as installed modules have defined this element we must not delete it!
603 cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name=%s", (name,))
604 if cr.fetchone() and not name in to_drop_table:
605 to_drop_table.append(name)
607 self.unlink(cr, uid, ids, context)
609 # drop m2m relation tables
610 for table in to_drop_table:
611 cr.execute('DROP TABLE %s CASCADE'% table,)
612 _logger.info('Dropped table %s', table)
616 class ir_model_access(osv.osv):
617 _name = 'ir.model.access'
619 'name': fields.char('Name', size=64, required=True, select=True),
620 '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.'),
621 'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)], select=True, ondelete='cascade'),
622 'group_id': fields.many2one('res.groups', 'Group', ondelete='cascade', select=True),
623 'perm_read': fields.boolean('Read Access'),
624 'perm_write': fields.boolean('Write Access'),
625 'perm_create': fields.boolean('Create Access'),
626 'perm_unlink': fields.boolean('Delete Access'),
632 def check_groups(self, cr, uid, group):
633 grouparr = group.split('.')
636 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],))
637 return bool(cr.fetchone())
639 def check_group(self, cr, uid, model, mode, group_ids):
640 """ Check if a specific group has the access mode to the specified model"""
641 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
643 if isinstance(model, browse_record):
644 assert model._table_name == 'ir.model', 'Invalid model object'
645 model_name = model.name
649 if isinstance(group_ids, (int, long)):
650 group_ids = [group_ids]
651 for group_id in group_ids:
652 cr.execute("SELECT perm_" + mode + " "
653 " FROM ir_model_access a "
654 " JOIN ir_model m ON (m.id = a.model_id) "
655 " WHERE m.model = %s AND a.active IS True "
656 " AND a.group_id = %s", (model_name, group_id)
660 cr.execute("SELECT perm_" + mode + " "
661 " FROM ir_model_access a "
662 " JOIN ir_model m ON (m.id = a.model_id) "
663 " WHERE m.model = %s AND a.active IS True "
664 " AND a.group_id IS NULL", (model_name, )
668 access = bool(r and r[0])
671 # pass no groups -> no access
674 def group_names_with_access(self, cr, model_name, access_mode):
675 """Returns the names of visible groups which have been granted ``access_mode`` on
676 the model ``model_name``.
679 assert access_mode in ['read','write','create','unlink'], 'Invalid access mode: %s' % access_mode
684 JOIN ir_model m ON (a.model_id=m.id)
685 JOIN res_groups g ON (a.group_id=g.id)
686 LEFT JOIN ir_module_category c ON (c.id=g.category_id)
690 a.perm_''' + access_mode, (model_name,))
691 return [('%s/%s' % x) if x[0] else x[1] for x in cr.fetchall()]
694 def check(self, cr, uid, model, mode='read', raise_exception=True, context=None):
696 # User root have all accesses
697 # TODO: exclude xml-rpc requests
700 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
702 if isinstance(model, browse_record):
703 assert model._table_name == 'ir.model', 'Invalid model object'
704 model_name = model.model
708 # TransientModel records have no access rights, only an implicit access rule
709 if not self.pool.get(model_name):
710 _logger.error('Missing model %s' % (model_name, ))
711 elif self.pool.get(model_name).is_transient():
714 # We check if a specific rule exists
715 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
716 ' FROM ir_model_access a '
717 ' JOIN ir_model m ON (m.id = a.model_id) '
718 ' JOIN res_groups_users_rel gu ON (gu.gid = a.group_id) '
719 ' WHERE m.model = %s '
721 ' AND a.active IS True '
727 # there is no specific rule. We check the generic rule
728 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
729 ' FROM ir_model_access a '
730 ' JOIN ir_model m ON (m.id = a.model_id) '
731 ' WHERE a.group_id IS NULL '
733 ' AND a.active IS True '
738 if not r and raise_exception:
739 groups = '\n\t'.join('- %s' % g for g in self.group_names_with_access(cr, model_name, mode))
741 # Messages are declared in extenso so they are properly exported in translation terms
742 'read': _("Sorry, you are not allowed to access this document."),
743 'write': _("Sorry, you are not allowed to modify this document."),
744 'create': _("Sorry, you are not allowed to create this kind of document."),
745 'unlink': _("Sorry, you are not allowed to delete this document."),
748 msg_tail = _("Only users with the following access level are currently allowed to do that") + ":\n%s\n\n(" + _("Document model") + ": %s)"
749 msg_params = (groups, model_name)
751 msg_tail = _("Please contact your system administrator if you think this is an error.") + "\n\n(" + _("Document model") + ": %s)"
752 msg_params = (model_name,)
753 _logger.warning('Access Denied by ACLs for operation: %s, uid: %s, model: %s', mode, uid, model_name)
754 msg = '%s %s' % (msg_heads[mode], msg_tail)
755 raise except_orm(_('Access Denied'), msg % msg_params)
758 __cache_clearing_methods = []
760 def register_cache_clearing_method(self, model, method):
761 self.__cache_clearing_methods.append((model, method))
763 def unregister_cache_clearing_method(self, model, method):
765 i = self.__cache_clearing_methods.index((model, method))
766 del self.__cache_clearing_methods[i]
770 def call_cache_clearing_methods(self, cr):
771 self.check.clear_cache(self) # clear the cache of check function
772 for model, method in self.__cache_clearing_methods:
773 object_ = self.pool.get(model)
775 getattr(object_, method)()
778 # Check rights on actions
780 def write(self, cr, uid, *args, **argv):
781 self.call_cache_clearing_methods(cr)
782 res = super(ir_model_access, self).write(cr, uid, *args, **argv)
785 def create(self, cr, uid, *args, **argv):
786 self.call_cache_clearing_methods(cr)
787 res = super(ir_model_access, self).create(cr, uid, *args, **argv)
790 def unlink(self, cr, uid, *args, **argv):
791 self.call_cache_clearing_methods(cr)
792 res = super(ir_model_access, self).unlink(cr, uid, *args, **argv)
795 class ir_model_data(osv.osv):
796 """Holds external identifier keys for records in the database.
797 This has two main uses:
799 * allows easy data integration with third-party systems,
800 making import/export/sync of data possible, as records
801 can be uniquely identified across multiple systems
802 * allows tracking the origin of data installed by OpenERP
803 modules themselves, thus making it possible to later
804 update them seamlessly.
806 _name = 'ir.model.data'
807 _order = 'module,model,name'
808 def _display_name_get(self, cr, uid, ids, prop, unknow_none, context=None):
811 for res in self.browse(cr, uid, ids, context=context):
813 result.setdefault(res.model, {})
814 result[res.model][res.res_id] = res.id
815 result2[res.id] = False
819 r = dict(self.pool.get(model).name_get(cr, uid, result[model].keys(), context=context))
820 for key,val in result[model].items():
821 result2[val] = r.get(key, False)
823 # some object have no valid name_get implemented, we accept this
827 def _complete_name_get(self, cr, uid, ids, prop, unknow_none, context=None):
829 for res in self.browse(cr, uid, ids, context=context):
830 result[res.id] = (res.module and (res.module + '.') or '')+res.name
834 'name': fields.char('External Identifier', required=True, size=128, select=1,
835 help="External Key/Identifier that can be used for "
836 "data integration with third-party systems"),
837 'complete_name': fields.function(_complete_name_get, type='char', string='Complete ID'),
838 'display_name': fields.function(_display_name_get, type='char', string='Record Name'),
839 'model': fields.char('Model Name', required=True, size=64, select=1),
840 'module': fields.char('Module', required=True, size=64, select=1),
841 'res_id': fields.integer('Record ID', select=1,
842 help="ID of the target record in the database"),
843 'noupdate': fields.boolean('Non Updatable'),
844 'date_update': fields.datetime('Update Date'),
845 'date_init': fields.datetime('Init Date')
848 'date_init': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
849 'date_update': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
854 ('module_name_uniq', 'unique(name, module)', 'You cannot have multiple records with the same external ID in the same module!'),
857 def __init__(self, pool, cr):
858 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 = {}
864 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 def _get_id(self, cr, uid, module, xml_id):
874 """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"""
875 ids = self.search(cr, uid, [('module','=',module), ('name','=', xml_id)])
877 raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
878 # the sql constraints ensure us we have only one result
882 def get_object_reference(self, cr, uid, module, xml_id):
883 """Returns (model, res_id) corresponding to a given module and xml_id (cached) or raise ValueError if not found"""
884 data_id = self._get_id(cr, uid, module, xml_id)
885 res = self.read(cr, uid, data_id, ['model', 'res_id'])
886 if not res['res_id']:
887 raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
888 return res['model'], res['res_id']
890 def get_object(self, cr, uid, module, xml_id, context=None):
891 """Returns a browsable record for the given module name and xml_id or raise ValueError if not found"""
892 res_model, res_id = self.get_object_reference(cr, uid, module, xml_id)
893 result = self.pool.get(res_model).browse(cr, uid, res_id, context=context)
894 if not result.exists():
895 raise ValueError('No record found for unique ID %s.%s. It may have been deleted.' % (module, xml_id))
898 def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
902 id = self.read(cr, uid, [self._get_id(cr, uid, module, xml_id)], ['res_id'])[0]['res_id']
903 self.loads[(module,xml_id)] = (model,id)
908 def clear_caches(self):
909 """ Clears all orm caches on the object's methods
913 self._get_id.clear_cache(self)
914 self.get_object_reference.clear_cache(self)
917 def unlink(self, cr, uid, ids, context=None):
918 """ Regular unlink method, but make sure to clear the caches. """
920 return super(ir_model_data,self).unlink(cr, uid, ids, context=context)
922 def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None):
923 model_obj = self.pool.get(model)
926 # records created during module install should not display the messages of OpenChatter
927 context = dict(context, install_mode=True)
928 if xml_id and ('.' in xml_id):
929 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
930 module, xml_id = xml_id.split('.')
931 if (not xml_id) and (not self.doinit):
935 cr.execute('''SELECT imd.id, imd.res_id, md.id, imd.model
936 FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
937 WHERE imd.module=%%s AND imd.name=%%s''' % model_obj._table,
939 results = cr.fetchall()
940 for imd_id2,res_id2,real_id2,real_model in results:
942 self._get_id.clear_cache(self, uid, module, xml_id)
943 self.get_object_reference.clear_cache(self, uid, module, xml_id)
944 cr.execute('delete from ir_model_data where id=%s', (imd_id2,))
947 assert model == real_model, "External ID conflict, %s already refers to a `%s` record,"\
948 " you can't define a `%s` record with this ID." % (xml_id, real_model, model)
949 res_id,action_id = res_id2,imd_id2
951 if action_id and res_id:
952 model_obj.write(cr, uid, [res_id], values, context=context)
953 self.write(cr, uid, [action_id], {
954 'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
957 model_obj.write(cr, uid, [res_id], values, context=context)
959 self.create(cr, uid, {
964 'noupdate': noupdate,
966 if model_obj._inherits:
967 for table in model_obj._inherits:
968 inherit_id = model_obj.browse(cr, uid,
969 res_id,context=context)[model_obj._inherits[table]]
970 self.create(cr, uid, {
971 'name': xml_id + '_' + table.replace('.', '_'),
974 'res_id': inherit_id.id,
975 'noupdate': noupdate,
978 if mode=='init' or (mode=='update' and xml_id):
979 res_id = model_obj.create(cr, uid, values, context=context)
981 self.create(cr, uid, {
988 if model_obj._inherits:
989 for table in model_obj._inherits:
990 inherit_id = model_obj.browse(cr, uid,
991 res_id,context=context)[model_obj._inherits[table]]
992 self.create(cr, uid, {
993 'name': xml_id + '_' + table.replace('.', '_'),
996 'res_id': inherit_id.id,
997 'noupdate': noupdate,
999 if xml_id and res_id:
1000 self.loads[(module, xml_id)] = (model, res_id)
1001 for table, inherit_field in model_obj._inherits.iteritems():
1002 inherit_id = model_obj.read(cr, uid, res_id,
1003 [inherit_field])[inherit_field]
1004 self.loads[(module, xml_id + '_' + table.replace('.', '_'))] = (table, inherit_id)
1007 def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None, xml_id=False):
1008 if isinstance(models[0], (list, tuple)):
1009 model,res_id = models[0]
1015 where = ' and res_id=%s' % (res_id,)
1017 where = ' and (res_id is null)'
1020 where += ' and key2=\'%s\'' % (key2,)
1022 where += ' and (key2 is null)'
1024 cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
1027 ir_values_obj = pooler.get_pool(cr.dbname).get('ir.values')
1028 ir_values_obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
1030 cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
1033 def _module_data_uninstall(self, cr, uid, modules_to_remove, context=None):
1034 """Deletes all the records referenced by the ir.model.data entries
1035 ``ids`` along with their corresponding database backed (including
1036 dropping tables, columns, FKs, etc, as long as there is no other
1037 ir.model.data entry holding a reference to them (which indicates that
1038 they are still owned by another module).
1039 Attempts to perform the deletion in an appropriate order to maximize
1040 the chance of gracefully deleting all records.
1041 This step is performed as part of the full uninstallation of a module.
1044 ids = self.search(cr, uid, [('module', 'in', modules_to_remove)])
1046 if uid != 1 and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"):
1047 raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
1049 context = dict(context or {})
1050 context[MODULE_UNINSTALL_FLAG] = True # enable model/field deletion
1057 for data in self.browse(cr, uid, ids, context):
1059 res_id = data.res_id
1061 pair_to_unlink = (model, res_id)
1062 if pair_to_unlink not in to_unlink:
1063 to_unlink.append(pair_to_unlink)
1065 if model == 'workflow.activity':
1066 # Special treatment for workflow activities: temporarily revert their
1067 # incoming transition and trigger an update to force all workflow items
1068 # to move out before deleting them
1069 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,))
1070 wkf_todo.extend(cr.fetchall())
1071 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))
1073 wf_service = netsvc.LocalService("workflow")
1074 for model,res_id in wkf_todo:
1076 wf_service.trg_write(uid, model, res_id, cr)
1078 _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)
1080 def unlink_if_refcount(to_unlink):
1081 for model, res_id in to_unlink:
1082 external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
1083 if set(external_ids)-ids_set:
1084 # if other modules have defined this record, we must not delete it
1086 _logger.info('Deleting %s@%s', res_id, model)
1088 cr.execute('SAVEPOINT record_unlink_save')
1089 self.pool.get(model).unlink(cr, uid, [res_id], context=context)
1091 _logger.info('Unable to delete %s@%s', res_id, model, exc_info=True)
1092 cr.execute('ROLLBACK TO SAVEPOINT record_unlink_save')
1094 cr.execute('RELEASE SAVEPOINT record_unlink_save')
1096 # Remove non-model records first, then model fields, and finish with models
1097 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1098 if model not in ('ir.model','ir.model.fields','ir.model.constraint'))
1099 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1100 if model == 'ir.model.constraint')
1102 ir_module_module = self.pool['ir.module.module']
1103 ir_model_constraint = self.pool['ir.model.constraint']
1104 modules_to_remove_ids = ir_module_module.search(cr, uid, [('name', 'in', modules_to_remove)], context=context)
1105 constraint_ids = ir_model_constraint.search(cr, uid, [('module', 'in', modules_to_remove_ids)], context=context)
1106 ir_model_constraint._module_data_uninstall(cr, uid, constraint_ids, context)
1108 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1109 if model == 'ir.model.fields')
1111 ir_model_relation = self.pool.get('ir.model.relation')
1112 relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove_ids)])
1113 ir_model_relation._module_data_uninstall(cr, uid, relation_ids, context)
1115 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1116 if model == 'ir.model')
1120 self.unlink(cr, uid, ids, context)
1122 def _process_end(self, cr, uid, modules):
1123 """ Clear records removed from updated module data.
1124 This method is called at the end of the module loading process.
1125 It is meant to removed records that are no longer present in the
1126 updated data. Such records are recognised as the one with an xml id
1127 and a module in ir_model_data and noupdate set to false, but not
1128 present in self.loads.
1133 cr.execute("""SELECT id,name,model,res_id,module FROM ir_model_data
1134 WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s
1135 ORDER BY id DESC""",
1136 (tuple(modules), False))
1137 for (id, name, model, res_id, module) in cr.fetchall():
1138 if (module,name) not in self.loads:
1139 to_unlink.append((model,res_id))
1140 if not config.get('import_partial'):
1141 for (model, res_id) in to_unlink:
1142 if self.pool.get(model):
1143 _logger.info('Deleting %s@%s', res_id, model)
1144 self.pool.get(model).unlink(cr, uid, [res_id])
1146 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: