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 ##############################################################################
22 from collections import defaultdict
29 from openerp import SUPERUSER_ID
30 from openerp import models, tools
31 from openerp.modules.registry import RegistryManager
32 from openerp.osv import fields, osv
33 from openerp.osv.orm import BaseModel, Model, MAGIC_COLUMNS, except_orm
34 from openerp.tools import config
35 from openerp.tools.safe_eval import safe_eval as eval
36 from openerp.tools.translate import _
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)])
98 def _inherited_models(self, cr, uid, ids, field_name, arg, context=None):
100 for model in self.browse(cr, uid, ids, context=context):
102 inherited_models = [model_name for model_name in self.pool[model.model]._inherits]
104 res[model.id] = self.search(cr, uid, [('model', 'in', inherited_models)], context=context)
108 'name': fields.char('Model Description', translate=True, required=True),
109 'model': fields.char('Model', required=True, select=1),
110 'info': fields.text('Information'),
111 'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields', required=True, copy=True),
112 'inherited_model_ids': fields.function(_inherited_models, type="many2many", obj="ir.model", string="Inherited models",
113 help="The list of models that extends the current model."),
114 'state': fields.selection([('manual','Custom Object'),('base','Base Object')],'Type', readonly=True),
115 'access_ids': fields.one2many('ir.model.access', 'model_id', 'Access'),
116 'osv_memory': fields.function(_is_osv_memory, string='Transient Model', type='boolean',
117 fnct_search=_search_osv_memory,
118 help="This field specifies whether the model is transient or not (i.e. if records are automatically deleted from the database or not)"),
119 'modules': fields.function(_in_modules, type='char', string='In Modules', help='List of modules in which the object is defined or inherited'),
120 'view_ids': fields.function(_view_ids, type='one2many', obj='ir.ui.view', string='Views'),
125 'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
128 def _check_model_name(self, cr, uid, ids, context=None):
129 for model in self.browse(cr, uid, ids, context=context):
130 if model.state=='manual':
131 if not model.model.startswith('x_'):
133 if not re.match('^[a-z_A-Z0-9.]+$',model.model):
137 def _model_name_msg(self, cr, uid, ids, context=None):
138 return _('The Object name must start with x_ and not contain any special character !')
141 (_check_model_name, _model_name_msg, ['model']),
144 ('obj_name_uniq', 'unique (model)', 'Each model must be unique!'),
147 # overridden to allow searching both on model name (model field)
148 # and model description (name field)
149 def _name_search(self, cr, uid, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
152 domain = args + ['|', ('model', operator, name), ('name', operator, name)]
153 return self.name_get(cr, name_get_uid or uid,
154 super(ir_model, self).search(cr, uid, domain, limit=limit, context=context),
157 def _drop_table(self, cr, uid, ids, context=None):
158 for model in self.browse(cr, uid, ids, context):
159 model_pool = self.pool[model.model]
160 cr.execute('select relkind from pg_class where relname=%s', (model_pool._table,))
161 result = cr.fetchone()
162 if result and result[0] == 'v':
163 cr.execute('DROP view %s' % (model_pool._table,))
164 elif result and result[0] == 'r':
165 cr.execute('DROP TABLE %s CASCADE' % (model_pool._table,))
168 def unlink(self, cr, user, ids, context=None):
169 # Prevent manual deletion of module tables
170 if context is None: context = {}
171 if isinstance(ids, (int, long)):
173 if not context.get(MODULE_UNINSTALL_FLAG):
174 for model in self.browse(cr, user, ids, context):
175 if model.state != 'manual':
176 raise except_orm(_('Error'), _("Model '%s' contains module data and cannot be removed!") % (model.name,))
178 self._drop_table(cr, user, ids, context)
179 res = super(ir_model, self).unlink(cr, user, ids, context)
180 if not context.get(MODULE_UNINSTALL_FLAG):
181 # only reload pool for normal unlink. For module uninstall the
182 # reload is done independently in openerp.modules.loading
183 cr.commit() # must be committed before reloading registry in new cursor
184 RegistryManager.new(cr.dbname)
185 RegistryManager.signal_registry_change(cr.dbname)
189 def write(self, cr, user, ids, vals, context=None):
191 context = dict(context)
192 context.pop('__last_update', None)
193 # Filter out operations 4 link from field id, because openerp-web
194 # always write (4,id,False) even for non dirty items
195 if 'field_id' in vals:
196 vals['field_id'] = [op for op in vals['field_id'] if op[0] != 4]
197 return super(ir_model,self).write(cr, user, ids, vals, context)
199 def create(self, cr, user, vals, context=None):
202 if context and context.get('manual'):
203 vals['state']='manual'
204 res = super(ir_model,self).create(cr, user, vals, context)
205 if vals.get('state','base')=='manual':
206 self.instanciate(cr, user, vals['model'], context)
207 model = self.pool[vals['model']]
209 field_name=vals['name'],
210 field_state='manual',
211 select=vals.get('select_level', '0'),
212 update_custom_fields=True)
213 model._auto_init(cr, ctx)
214 model._auto_end(cr, ctx) # actually create FKs!
215 self.pool.setup_models(cr, partial=(not self.pool.ready))
216 RegistryManager.signal_registry_change(cr.dbname)
219 def instanciate(self, cr, user, model, context=None):
220 if isinstance(model, unicode):
221 model = model.encode('utf-8')
223 class CustomModel(models.Model):
228 obj = CustomModel._build_model(self.pool, cr)
229 obj._rec_name = CustomModel._rec_name = (
230 'x_name' if 'x_name' in obj._columns else
231 list(obj._columns)[0] if obj._columns else
235 class ir_model_fields(osv.osv):
236 _name = 'ir.model.fields'
237 _description = "Fields"
238 _rec_name = 'field_description'
241 'name': fields.char('Name', required=True, select=1),
242 'complete_name': fields.char('Complete Name', select=1),
243 'model': fields.char('Object Name', required=True, select=1,
244 help="The technical name of the model this field belongs to"),
245 'relation': fields.char('Object Relation',
246 help="For relationship fields, the technical name of the target model"),
247 'relation_field': fields.char('Relation Field',
248 help="For one2many fields, the field on the target model that implement the opposite many2one relationship"),
249 'model_id': fields.many2one('ir.model', 'Model', required=True, select=True, ondelete='cascade',
250 help="The model this field belongs to"),
251 'field_description': fields.char('Field Label', required=True),
252 'ttype': fields.selection(_get_fields_type, 'Field Type', required=True),
253 'selection': fields.char('Selection Options', help="List of options for a selection field, "
254 "specified as a Python expression defining a list of (key, label) pairs. "
255 "For example: [('blue','Blue'),('yellow','Yellow')]"),
256 'required': fields.boolean('Required'),
257 'readonly': fields.boolean('Readonly'),
258 'select_level': fields.selection([('0','Not Searchable'),('1','Always Searchable'),('2','Advanced Search (deprecated)')],'Searchable', required=True),
259 'translate': fields.boolean('Translatable', help="Whether values for this field can be translated (enables the translation mechanism for that field)"),
260 'size': fields.integer('Size'),
261 'state': fields.selection([('manual','Custom Field'),('base','Base Field')],'Type', required=True, readonly=True, select=1),
262 'on_delete': fields.selection([('cascade', 'Cascade'), ('set null', 'Set NULL'), ('restrict', 'Restrict')],
263 'On Delete', help='On delete property for many2one fields'),
264 'domain': fields.char('Domain', help="The optional domain to restrict possible values for relationship fields, "
265 "specified as a Python expression defining a list of triplets. "
266 "For example: [('color','=','red')]"),
267 'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'),
268 'selectable': fields.boolean('Selectable'),
269 'modules': fields.function(_in_modules, type='char', string='In Modules', help='List of modules in which the field is defined'),
270 'serialization_field_id': fields.many2one('ir.model.fields', 'Serialization Field', domain = "[('ttype','=','serialized')]",
271 ondelete='cascade', help="If set, this field will be stored in the sparse "
272 "structure of the serialization field, instead "
273 "of having its own database column. This cannot be "
274 "changed after creation."),
276 _rec_name='field_description'
281 'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
282 'on_delete': 'set null',
284 'field_description': '',
289 def _check_selection(self, cr, uid, selection, context=None):
291 selection_list = eval(selection)
293 _logger.warning('Invalid selection list definition for fields.selection', exc_info=True)
294 raise except_orm(_('Error'),
295 _("The Selection Options expression is not a valid Pythonic expression."
296 "Please provide an expression in the [('key','Label'), ...] format."))
299 if not (isinstance(selection_list, list) and selection_list):
302 for item in selection_list:
303 if not (isinstance(item, (tuple,list)) and len(item) == 2):
308 raise except_orm(_('Error'),
309 _("The Selection Options expression is must be in the [('key','Label'), ...] format!"))
312 def _size_gt_zero_msg(self, cr, user, ids, context=None):
313 return _('Size of the field can never be less than 0 !')
316 ('size_gt_zero', 'CHECK (size>=0)',_size_gt_zero_msg ),
319 def _drop_column(self, cr, uid, ids, context=None):
320 for field in self.browse(cr, uid, ids, context):
321 if field.name in MAGIC_COLUMNS:
323 model = self.pool[field.model]
324 cr.execute('select relkind from pg_class where relname=%s', (model._table,))
325 result = cr.fetchone()
326 cr.execute("SELECT column_name FROM information_schema.columns WHERE table_name ='%s' and column_name='%s'" %(model._table, field.name))
327 column_name = cr.fetchone()
328 if column_name and (result and result[0] == 'r'):
329 cr.execute('ALTER table "%s" DROP column "%s" cascade' % (model._table, field.name))
330 # remove m2m relation table for custom fields
331 # we consider the m2m relation is only one way as it's not possible
332 # to specify the relation table in the interface for custom fields
333 # TODO master: maybe use ir.model.relations for custom fields
334 if field.state == 'manual' and field.ttype == 'many2many':
335 rel_name = model._fields[field.name].relation
336 cr.execute('DROP table "%s"' % (rel_name))
337 model._pop_field(field.name)
341 def unlink(self, cr, user, ids, context=None):
342 # Prevent manual deletion of module columns
343 if context is None: context = {}
344 if isinstance(ids, (int, long)):
346 if not context.get(MODULE_UNINSTALL_FLAG) and \
347 any(field.state != 'manual' for field in self.browse(cr, user, ids, context)):
348 raise except_orm(_('Error'), _("This column contains module data and cannot be removed!"))
350 self._drop_column(cr, user, ids, context)
351 res = super(ir_model_fields, self).unlink(cr, user, ids, context)
352 if not context.get(MODULE_UNINSTALL_FLAG):
354 self.pool.setup_models(cr, partial=(not self.pool.ready))
355 RegistryManager.signal_registry_change(cr.dbname)
358 def create(self, cr, user, vals, context=None):
359 if 'model_id' in vals:
360 model_data = self.pool['ir.model'].browse(cr, user, vals['model_id'])
361 vals['model'] = model_data.model
364 if context and context.get('manual',False):
365 vals['state'] = 'manual'
366 if vals.get('ttype', False) == 'selection':
367 if not vals.get('selection',False):
368 raise except_orm(_('Error'), _('For selection fields, the Selection Options must be given!'))
369 self._check_selection(cr, user, vals['selection'], context=context)
370 res = super(ir_model_fields,self).create(cr, user, vals, context)
371 if vals.get('state','base') == 'manual':
372 if not vals['name'].startswith('x_'):
373 raise except_orm(_('Error'), _("Custom fields must have a name that starts with 'x_' !"))
375 if vals.get('relation',False) and not self.pool['ir.model'].search(cr, user, [('model','=',vals['relation'])]):
376 raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
378 if vals['model'] in self.pool:
379 model = self.pool[vals['model']]
380 if vals['model'].startswith('x_') and vals['name'] == 'x_name':
381 model._rec_name = 'x_name'
383 if self.pool.fields_by_model is not None:
384 cr.execute('SELECT * FROM ir_model_fields WHERE id=%s', (res,))
385 self.pool.fields_by_model.setdefault(vals['model'], []).append(cr.dictfetchone())
387 model.__init__(self.pool, cr)
388 #Added context to _auto_init for special treatment to custom field for select_level
390 field_name=vals['name'],
391 field_state='manual',
392 select=vals.get('select_level', '0'),
393 update_custom_fields=True)
394 model._auto_init(cr, ctx)
395 model._auto_end(cr, ctx) # actually create FKs!
396 self.pool.setup_models(cr, partial=(not self.pool.ready))
397 RegistryManager.signal_registry_change(cr.dbname)
401 def write(self, cr, user, ids, vals, context=None):
404 if context and context.get('manual',False):
405 vals['state'] = 'manual'
407 #For the moment renaming a sparse field or changing the storing system is not allowed. This may be done later
408 if 'serialization_field_id' in vals or 'name' in vals:
409 for field in self.browse(cr, user, ids, context=context):
410 if 'serialization_field_id' in vals and field.serialization_field_id.id != vals['serialization_field_id']:
411 raise except_orm(_('Error!'), _('Changing the storing system for field "%s" is not allowed.')%field.name)
412 if field.serialization_field_id and (field.name != vals['name']):
413 raise except_orm(_('Error!'), _('Renaming sparse field "%s" is not allowed')%field.name)
415 column_rename = None # if set, *one* column can be renamed here
416 models_patch = {} # structs of (obj, [(field, prop, change_to),..])
417 # data to be updated on the orm model
419 # static table of properties
420 model_props = [ # (our-name, fields.prop, set_fn)
421 ('field_description', 'string', tools.ustr),
422 ('required', 'required', bool),
423 ('readonly', 'readonly', bool),
424 ('domain', '_domain', eval),
425 ('size', 'size', int),
426 ('on_delete', 'ondelete', str),
427 ('translate', 'translate', bool),
428 ('selectable', 'selectable', bool),
429 ('select_level', 'select', int),
430 ('selection', 'selection', eval),
434 checked_selection = False # need only check it once, so defer
436 for item in self.browse(cr, user, ids, context=context):
437 obj = self.pool.get(item.model)
439 if item.state != 'manual':
440 raise except_orm(_('Error!'),
441 _('Properties of base fields cannot be altered in this manner! '
442 'Please modify them through Python code, '
443 'preferably through a custom addon!'))
445 if item.ttype == 'selection' and 'selection' in vals \
446 and not checked_selection:
447 self._check_selection(cr, user, vals['selection'], context=context)
448 checked_selection = True
450 final_name = item.name
451 if 'name' in vals and vals['name'] != item.name:
452 # We need to rename the column
454 raise except_orm(_('Error!'), _('Can only rename one column at a time!'))
455 if vals['name'] in obj._columns:
456 raise except_orm(_('Error!'), _('Cannot rename column to %s, because that column already exists!') % vals['name'])
457 if vals.get('state', 'base') == 'manual' and not vals['name'].startswith('x_'):
458 raise except_orm(_('Error!'), _('New column name must still start with x_ , because it is a custom field!'))
459 if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']:
460 raise ValueError('Invalid character in column name')
461 column_rename = (obj, (obj._table, item.name, vals['name']))
462 final_name = vals['name']
464 if 'model_id' in vals and vals['model_id'] != item.model_id.id:
465 raise except_orm(_("Error!"), _("Changing the model of a field is forbidden!"))
467 if 'ttype' in vals and vals['ttype'] != item.ttype:
468 raise except_orm(_("Error!"), _("Changing the type of a column is not yet supported. "
469 "Please drop it and create it again!"))
471 # We don't check the 'state', because it might come from the context
472 # (thus be set for multiple fields) and will be ignored anyway.
474 models_patch.setdefault(obj._name, (obj,[]))
475 # find out which properties (per model) we need to update
476 for field_name, field_property, set_fn in model_props:
477 if field_name in vals:
478 property_value = set_fn(vals[field_name])
479 if getattr(obj._columns[item.name], field_property) != property_value:
480 models_patch[obj._name][1].append((final_name, field_property, property_value))
481 # our dict is ready here, but no properties are changed so far
483 # These shall never be written (modified)
484 for column_name in ('model_id', 'model', 'state'):
485 if column_name in vals:
486 del vals[column_name]
488 res = super(ir_model_fields,self).write(cr, user, ids, vals, context=context)
491 obj, rename = column_rename
492 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % rename)
493 # This is VERY risky, but let us have this feature:
494 # we want to change the key of field in obj._fields and obj._columns
495 field = obj._pop_field(rename[1])
496 obj._add_field(rename[2], field)
499 # We have to update _columns of the model(s) and then call their
500 # _auto_init to sync the db with the model. Hopefully, since write()
501 # was called earlier, they will be in-sync before the _auto_init.
502 # Anything we don't update in _columns now will be reset from
503 # the model into ir.model.fields (db).
504 ctx = dict(context, select=vals.get('select_level', '0'),
505 update_custom_fields=True)
507 for __, patch_struct in models_patch.items():
508 obj = patch_struct[0]
509 # TODO: update new-style fields accordingly
510 for col_name, col_prop, val in patch_struct[1]:
511 setattr(obj._columns[col_name], col_prop, val)
512 obj._auto_init(cr, ctx)
513 obj._auto_end(cr, ctx) # actually create FKs!
515 if column_rename or models_patch:
516 self.pool.setup_models(cr, partial=(not self.pool.ready))
517 RegistryManager.signal_registry_change(cr.dbname)
521 class ir_model_constraint(Model):
523 This model tracks PostgreSQL foreign keys and constraints used by OpenERP
526 _name = 'ir.model.constraint'
528 'name': fields.char('Constraint', required=True, select=1,
529 help="PostgreSQL constraint or foreign key name."),
530 'definition': fields.char('Definition', help="PostgreSQL constraint definition"),
531 'model': fields.many2one('ir.model', string='Model',
532 required=True, select=1),
533 'module': fields.many2one('ir.module.module', string='Module',
534 required=True, select=1),
535 'type': fields.char('Constraint Type', required=True, size=1, select=1,
536 help="Type of the constraint: `f` for a foreign key, "
537 "`u` for other constraints."),
538 'date_update': fields.datetime('Update Date'),
539 'date_init': fields.datetime('Initialization Date')
543 ('module_name_uniq', 'unique(name, module)',
544 'Constraints with the same name are unique per module.'),
547 def _module_data_uninstall(self, cr, uid, ids, context=None):
549 Delete PostgreSQL foreign keys and constraints tracked by this model.
552 if uid != SUPERUSER_ID and not self.pool['ir.model.access'].check_groups(cr, uid, "base.group_system"):
553 raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
555 context = dict(context or {})
560 for data in self.browse(cr, uid, ids, context):
561 model = data.model.model
562 model_obj = self.pool[model]
563 name = openerp.tools.ustr(data.name)
566 # double-check we are really going to delete all the owners of this schema element
567 cr.execute("""SELECT id from ir_model_constraint where name=%s""", (data.name,))
568 external_ids = [x[0] for x in cr.fetchall()]
569 if set(external_ids)-ids_set:
570 # as installed modules have defined this element we must not delete it!
574 # test if FK exists on this table (it could be on a related m2m table, in which case we ignore it)
575 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
576 WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_obj._table))
578 cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
579 _logger.info('Dropped FK CONSTRAINT %s@%s', name, model)
582 # test if constraint exists
583 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
584 WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_obj._table))
586 cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
587 _logger.info('Dropped CONSTRAINT %s@%s', name, model)
589 self.unlink(cr, uid, ids, context)
591 class ir_model_relation(Model):
593 This model tracks PostgreSQL tables used to implement OpenERP many2many
596 _name = 'ir.model.relation'
598 'name': fields.char('Relation Name', required=True, select=1,
599 help="PostgreSQL table name implementing a many2many relation."),
600 'model': fields.many2one('ir.model', string='Model',
601 required=True, select=1),
602 'module': fields.many2one('ir.module.module', string='Module',
603 required=True, select=1),
604 'date_update': fields.datetime('Update Date'),
605 'date_init': fields.datetime('Initialization Date')
608 def _module_data_uninstall(self, cr, uid, ids, context=None):
610 Delete PostgreSQL many2many relations tracked by this model.
613 if uid != SUPERUSER_ID and not self.pool['ir.model.access'].check_groups(cr, uid, "base.group_system"):
614 raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
620 for data in self.browse(cr, uid, ids, context):
622 name = openerp.tools.ustr(data.name)
624 # double-check we are really going to delete all the owners of this schema element
625 cr.execute("""SELECT id from ir_model_relation where name = %s""", (data.name,))
626 external_ids = [x[0] for x in cr.fetchall()]
627 if set(external_ids)-ids_set:
628 # as installed modules have defined this element we must not delete it!
631 cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name=%s", (name,))
632 if cr.fetchone() and not name in to_drop_table:
633 to_drop_table.append(name)
635 self.unlink(cr, uid, ids, context)
637 # drop m2m relation tables
638 for table in to_drop_table:
639 cr.execute('DROP TABLE %s CASCADE'% table,)
640 _logger.info('Dropped table %s', table)
644 class ir_model_access(osv.osv):
645 _name = 'ir.model.access'
647 'name': fields.char('Name', required=True, select=True),
648 '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.'),
649 'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)], select=True, ondelete='cascade'),
650 'group_id': fields.many2one('res.groups', 'Group', ondelete='cascade', select=True),
651 'perm_read': fields.boolean('Read Access'),
652 'perm_write': fields.boolean('Write Access'),
653 'perm_create': fields.boolean('Create Access'),
654 'perm_unlink': fields.boolean('Delete Access'),
660 def check_groups(self, cr, uid, group):
661 grouparr = group.split('.')
664 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],))
665 return bool(cr.fetchone())
667 def check_group(self, cr, uid, model, mode, group_ids):
668 """ Check if a specific group has the access mode to the specified model"""
669 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
671 if isinstance(model, BaseModel):
672 assert model._name == 'ir.model', 'Invalid model object'
673 model_name = model.name
677 if isinstance(group_ids, (int, long)):
678 group_ids = [group_ids]
679 for group_id in group_ids:
680 cr.execute("SELECT perm_" + mode + " "
681 " FROM ir_model_access a "
682 " JOIN ir_model m ON (m.id = a.model_id) "
683 " WHERE m.model = %s AND a.active IS True "
684 " AND a.group_id = %s", (model_name, group_id)
688 cr.execute("SELECT perm_" + mode + " "
689 " FROM ir_model_access a "
690 " JOIN ir_model m ON (m.id = a.model_id) "
691 " WHERE m.model = %s AND a.active IS True "
692 " AND a.group_id IS NULL", (model_name, )
696 access = bool(r and r[0])
699 # pass no groups -> no access
702 def group_names_with_access(self, cr, model_name, access_mode):
703 """Returns the names of visible groups which have been granted ``access_mode`` on
704 the model ``model_name``.
707 assert access_mode in ['read','write','create','unlink'], 'Invalid access mode: %s' % access_mode
712 JOIN ir_model m ON (a.model_id=m.id)
713 JOIN res_groups g ON (a.group_id=g.id)
714 LEFT JOIN ir_module_category c ON (c.id=g.category_id)
718 a.perm_''' + access_mode, (model_name,))
719 return [('%s/%s' % x) if x[0] else x[1] for x in cr.fetchall()]
722 def check(self, cr, uid, model, mode='read', raise_exception=True, context=None):
724 # User root have all accesses
725 # TODO: exclude xml-rpc requests
728 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
730 if isinstance(model, BaseModel):
731 assert model._name == 'ir.model', 'Invalid model object'
732 model_name = model.model
736 # TransientModel records have no access rights, only an implicit access rule
737 if model_name not in self.pool:
738 _logger.error('Missing model %s' % (model_name, ))
739 elif self.pool[model_name].is_transient():
742 # We check if a specific rule exists
743 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
744 ' FROM ir_model_access a '
745 ' JOIN ir_model m ON (m.id = a.model_id) '
746 ' JOIN res_groups_users_rel gu ON (gu.gid = a.group_id) '
747 ' WHERE m.model = %s '
749 ' AND a.active IS True '
755 # there is no specific rule. We check the generic rule
756 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
757 ' FROM ir_model_access a '
758 ' JOIN ir_model m ON (m.id = a.model_id) '
759 ' WHERE a.group_id IS NULL '
761 ' AND a.active IS True '
766 if not r and raise_exception:
767 groups = '\n\t'.join('- %s' % g for g in self.group_names_with_access(cr, model_name, mode))
769 # Messages are declared in extenso so they are properly exported in translation terms
770 'read': _("Sorry, you are not allowed to access this document."),
771 'write': _("Sorry, you are not allowed to modify this document."),
772 'create': _("Sorry, you are not allowed to create this kind of document."),
773 'unlink': _("Sorry, you are not allowed to delete this document."),
776 msg_tail = _("Only users with the following access level are currently allowed to do that") + ":\n%s\n\n(" + _("Document model") + ": %s)"
777 msg_params = (groups, model_name)
779 msg_tail = _("Please contact your system administrator if you think this is an error.") + "\n\n(" + _("Document model") + ": %s)"
780 msg_params = (model_name,)
781 _logger.warning('Access Denied by ACLs for operation: %s, uid: %s, model: %s', mode, uid, model_name)
782 msg = '%s %s' % (msg_heads[mode], msg_tail)
783 raise openerp.exceptions.AccessError(msg % msg_params)
786 __cache_clearing_methods = []
788 def register_cache_clearing_method(self, model, method):
789 self.__cache_clearing_methods.append((model, method))
791 def unregister_cache_clearing_method(self, model, method):
793 i = self.__cache_clearing_methods.index((model, method))
794 del self.__cache_clearing_methods[i]
798 def call_cache_clearing_methods(self, cr):
799 self.invalidate_cache(cr, SUPERUSER_ID)
800 self.check.clear_cache(self) # clear the cache of check function
801 for model, method in self.__cache_clearing_methods:
802 if model in self.pool:
803 getattr(self.pool[model], method)()
806 # Check rights on actions
808 def write(self, cr, uid, ids, values, context=None):
809 self.call_cache_clearing_methods(cr)
810 res = super(ir_model_access, self).write(cr, uid, ids, values, context=context)
813 def create(self, cr, uid, values, context=None):
814 self.call_cache_clearing_methods(cr)
815 res = super(ir_model_access, self).create(cr, uid, values, context=context)
818 def unlink(self, cr, uid, ids, context=None):
819 self.call_cache_clearing_methods(cr)
820 res = super(ir_model_access, self).unlink(cr, uid, ids, context=context)
823 class ir_model_data(osv.osv):
824 """Holds external identifier keys for records in the database.
825 This has two main uses:
827 * allows easy data integration with third-party systems,
828 making import/export/sync of data possible, as records
829 can be uniquely identified across multiple systems
830 * allows tracking the origin of data installed by OpenERP
831 modules themselves, thus making it possible to later
832 update them seamlessly.
834 _name = 'ir.model.data'
835 _order = 'module,model,name'
837 def name_get(self, cr, uid, ids, context=None):
838 bymodel = defaultdict(dict)
841 for res in self.browse(cr, uid, ids, context=context):
842 bymodel[res.model][res.res_id] = res
843 names[res.id] = res.complete_name
844 #result[res.model][res.res_id] = res.id
846 for model, id_map in bymodel.iteritems():
848 ng = dict(self.pool[model].name_get(cr, uid, id_map.keys(), context=context))
852 for r in id_map.itervalues():
853 names[r.id] = ng.get(r.res_id, r.complete_name)
855 return [(i, names[i]) for i in ids]
857 def _complete_name_get(self, cr, uid, ids, prop, unknow_none, context=None):
859 for res in self.browse(cr, uid, ids, context=context):
860 result[res.id] = (res.module and (res.module + '.') or '')+res.name
864 'name': fields.char('External Identifier', required=True, select=1,
865 help="External Key/Identifier that can be used for "
866 "data integration with third-party systems"),
867 'complete_name': fields.function(_complete_name_get, type='char', string='Complete ID'),
868 'model': fields.char('Model Name', required=True, select=1),
869 'module': fields.char('Module', required=True, select=1),
870 'res_id': fields.integer('Record ID', select=1,
871 help="ID of the target record in the database"),
872 'noupdate': fields.boolean('Non Updatable'),
873 'date_update': fields.datetime('Update Date'),
874 'date_init': fields.datetime('Init Date')
877 'date_init': fields.datetime.now,
878 'date_update': fields.datetime.now,
883 ('module_name_uniq', 'unique(name, module)', 'You cannot have multiple records with the same external ID in the same module!'),
886 def __init__(self, pool, cr):
887 osv.osv.__init__(self, pool, cr)
888 # also stored in pool to avoid being discarded along with this osv instance
889 if getattr(pool, 'model_data_reference_ids', None) is None:
890 self.pool.model_data_reference_ids = {}
891 # put loads on the class, in order to share it among all instances
892 type(self).loads = self.pool.model_data_reference_ids
894 def _auto_init(self, cr, context=None):
895 super(ir_model_data, self)._auto_init(cr, context)
896 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_model_data_module_name_index\'')
897 if not cr.fetchone():
898 cr.execute('CREATE INDEX ir_model_data_module_name_index ON ir_model_data (module, name)')
901 @tools.ormcache(skiparg=3)
902 def xmlid_lookup(self, cr, uid, xmlid):
903 """Low level xmlid lookup
904 Return (id, res_model, res_id) or raise ValueError if not found
906 module, name = xmlid.split('.', 1)
907 ids = self.search(cr, uid, [('module','=',module), ('name','=', name)])
909 raise ValueError('External ID not found in the system: %s' % (xmlid))
910 # the sql constraints ensure us we have only one result
911 res = self.read(cr, uid, ids[0], ['model', 'res_id'])
912 if not res['res_id']:
913 raise ValueError('External ID not found in the system: %s' % (xmlid))
914 return ids[0], res['model'], res['res_id']
916 def xmlid_to_res_model_res_id(self, cr, uid, xmlid, raise_if_not_found=False):
917 """ Return (res_model, res_id)"""
919 return self.xmlid_lookup(cr, uid, xmlid)[1:3]
921 if raise_if_not_found:
923 return (False, False)
925 def xmlid_to_res_id(self, cr, uid, xmlid, raise_if_not_found=False):
926 """ Returns res_id """
927 return self.xmlid_to_res_model_res_id(cr, uid, xmlid, raise_if_not_found)[1]
929 def xmlid_to_object(self, cr, uid, xmlid, raise_if_not_found=False, context=None):
930 """ Return a browse_record
931 if not found and raise_if_not_found is True return None
933 t = self.xmlid_to_res_model_res_id(cr, uid, xmlid, raise_if_not_found)
934 res_model, res_id = t
936 if res_model and res_id:
937 record = self.pool[res_model].browse(cr, uid, res_id, context=context)
940 if raise_if_not_found:
941 raise ValueError('No record found for unique ID %s. It may have been deleted.' % (xmlid))
945 def _get_id(self, cr, uid, module, xml_id):
946 """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"""
947 return self.xmlid_lookup(cr, uid, "%s.%s" % (module, xml_id))[0]
949 def get_object_reference(self, cr, uid, module, xml_id):
950 """Returns (model, res_id) corresponding to a given module and xml_id (cached) or raise ValueError if not found"""
951 return self.xmlid_lookup(cr, uid, "%s.%s" % (module, xml_id))[1:3]
953 def check_object_reference(self, cr, uid, module, xml_id, raise_on_access_error=False):
954 """Returns (model, res_id) corresponding to a given module and xml_id (cached), if and only if the user has the necessary access rights
955 to see that object, otherwise raise a ValueError if raise_on_access_error is True or returns a tuple (model found, False)"""
956 model, res_id = self.get_object_reference(cr, uid, module, xml_id)
957 #search on id found in result to check if current user has read access right
958 check_right = self.pool.get(model).search(cr, uid, [('id', '=', res_id)])
961 if raise_on_access_error:
962 raise ValueError('Not enough access rights on the external ID: %s.%s' % (module, xml_id))
965 def get_object(self, cr, uid, module, xml_id, context=None):
966 """ Returns a browsable record for the given module name and xml_id.
967 If not found, raise a ValueError or return None, depending
968 on the value of `raise_exception`.
970 return self.xmlid_to_object(cr, uid, "%s.%s" % (module, xml_id), raise_if_not_found=True, context=context)
972 def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
976 id = self.read(cr, uid, [self._get_id(cr, uid, module, xml_id)], ['res_id'])[0]['res_id']
977 self.loads[(module,xml_id)] = (model,id)
982 def clear_caches(self):
983 """ Clears all orm caches on the object's methods
987 self.xmlid_lookup.clear_cache(self)
990 def unlink(self, cr, uid, ids, context=None):
991 """ Regular unlink method, but make sure to clear the caches. """
993 return super(ir_model_data,self).unlink(cr, uid, ids, context=context)
995 def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None):
996 model_obj = self.pool[model]
999 # records created during module install should not display the messages of OpenChatter
1000 context = dict(context, install_mode=True)
1001 if xml_id and ('.' in xml_id):
1002 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
1003 module, xml_id = xml_id.split('.')
1006 cr.execute('''SELECT imd.id, imd.res_id, md.id, imd.model, imd.noupdate
1007 FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
1008 WHERE imd.module=%%s AND imd.name=%%s''' % model_obj._table,
1010 results = cr.fetchall()
1011 for imd_id2,res_id2,real_id2,real_model,noupdate_imd in results:
1012 # In update mode, do not update a record if it's ir.model.data is flagged as noupdate
1013 if mode == 'update' and noupdate_imd:
1017 cr.execute('delete from ir_model_data where id=%s', (imd_id2,))
1020 assert model == real_model, "External ID conflict, %s already refers to a `%s` record,"\
1021 " you can't define a `%s` record with this ID." % (xml_id, real_model, model)
1022 res_id,action_id = res_id2,imd_id2
1024 if action_id and res_id:
1025 model_obj.write(cr, uid, [res_id], values, context=context)
1026 self.write(cr, uid, [action_id], {
1027 'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
1030 model_obj.write(cr, uid, [res_id], values, context=context)
1032 if model_obj._inherits:
1033 for table in model_obj._inherits:
1034 inherit_id = model_obj.browse(cr, uid,
1035 res_id,context=context)[model_obj._inherits[table]]
1036 self.create(cr, uid, {
1037 'name': xml_id + '_' + table.replace('.', '_'),
1040 'res_id': inherit_id.id,
1041 'noupdate': noupdate,
1043 self.create(cr, uid, {
1048 'noupdate': noupdate,
1051 if mode=='init' or (mode=='update' and xml_id):
1052 res_id = model_obj.create(cr, uid, values, context=context)
1054 if model_obj._inherits:
1055 for table in model_obj._inherits:
1056 inherit_id = model_obj.browse(cr, uid,
1057 res_id,context=context)[model_obj._inherits[table]]
1058 self.create(cr, uid, {
1059 'name': xml_id + '_' + table.replace('.', '_'),
1062 'res_id': inherit_id.id,
1063 'noupdate': noupdate,
1065 self.create(cr, uid, {
1070 'noupdate': noupdate
1072 if xml_id and res_id:
1073 self.loads[(module, xml_id)] = (model, res_id)
1074 for table, inherit_field in model_obj._inherits.iteritems():
1075 inherit_id = model_obj.read(cr, uid, [res_id],
1076 [inherit_field])[0][inherit_field]
1077 self.loads[(module, xml_id + '_' + table.replace('.', '_'))] = (table, inherit_id)
1080 def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None, xml_id=False):
1081 if isinstance(models[0], (list, tuple)):
1082 model,res_id = models[0]
1088 where = ' and res_id=%s' % (res_id,)
1090 where = ' and (res_id is null)'
1093 where += ' and key2=\'%s\'' % (key2,)
1095 where += ' and (key2 is null)'
1097 cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
1099 ir_values_obj = openerp.registry(cr.dbname)['ir.values']
1101 ir_values_obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
1103 cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
1104 ir_values_obj.invalidate_cache(cr, uid, ['value'])
1107 def _module_data_uninstall(self, cr, uid, modules_to_remove, context=None):
1108 """Deletes all the records referenced by the ir.model.data entries
1109 ``ids`` along with their corresponding database backed (including
1110 dropping tables, columns, FKs, etc, as long as there is no other
1111 ir.model.data entry holding a reference to them (which indicates that
1112 they are still owned by another module).
1113 Attempts to perform the deletion in an appropriate order to maximize
1114 the chance of gracefully deleting all records.
1115 This step is performed as part of the full uninstallation of a module.
1118 ids = self.search(cr, uid, [('module', 'in', modules_to_remove)])
1120 if uid != 1 and not self.pool['ir.model.access'].check_groups(cr, uid, "base.group_system"):
1121 raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
1123 context = dict(context or {})
1124 context[MODULE_UNINSTALL_FLAG] = True # enable model/field deletion
1131 for data in self.browse(cr, uid, ids, context):
1133 res_id = data.res_id
1135 pair_to_unlink = (model, res_id)
1136 if pair_to_unlink not in to_unlink:
1137 to_unlink.append(pair_to_unlink)
1139 if model == 'workflow.activity':
1140 # Special treatment for workflow activities: temporarily revert their
1141 # incoming transition and trigger an update to force all workflow items
1142 # to move out before deleting them
1143 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,))
1144 wkf_todo.extend(cr.fetchall())
1145 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))
1146 self.invalidate_cache(cr, uid, context=context)
1148 for model,res_id in wkf_todo:
1150 openerp.workflow.trg_write(uid, model, res_id, cr)
1152 _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)
1154 def unlink_if_refcount(to_unlink):
1155 for model, res_id in to_unlink:
1156 external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
1157 if set(external_ids)-ids_set:
1158 # if other modules have defined this record, we must not delete it
1160 if model == 'ir.model.fields':
1161 # Don't remove the LOG_ACCESS_COLUMNS unless _log_access
1162 # has been turned off on the model.
1163 field = self.pool[model].browse(cr, uid, [res_id], context=context)[0]
1164 if not field.exists():
1165 _logger.info('Deleting orphan external_ids %s', external_ids)
1166 self.unlink(cr, uid, external_ids)
1168 if field.name in openerp.models.LOG_ACCESS_COLUMNS and self.pool[field.model]._log_access:
1170 if field.name == 'id':
1172 _logger.info('Deleting %s@%s', res_id, model)
1174 cr.execute('SAVEPOINT record_unlink_save')
1175 self.pool[model].unlink(cr, uid, [res_id], context=context)
1177 _logger.info('Unable to delete %s@%s', res_id, model, exc_info=True)
1178 cr.execute('ROLLBACK TO SAVEPOINT record_unlink_save')
1180 cr.execute('RELEASE SAVEPOINT record_unlink_save')
1182 # Remove non-model records first, then model fields, and finish with models
1183 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1184 if model not in ('ir.model','ir.model.fields','ir.model.constraint'))
1185 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1186 if model == 'ir.model.constraint')
1188 ir_module_module = self.pool['ir.module.module']
1189 ir_model_constraint = self.pool['ir.model.constraint']
1190 modules_to_remove_ids = ir_module_module.search(cr, uid, [('name', 'in', modules_to_remove)], context=context)
1191 constraint_ids = ir_model_constraint.search(cr, uid, [('module', 'in', modules_to_remove_ids)], context=context)
1192 ir_model_constraint._module_data_uninstall(cr, uid, constraint_ids, context)
1194 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1195 if model == 'ir.model.fields')
1197 ir_model_relation = self.pool['ir.model.relation']
1198 relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove_ids)])
1199 ir_model_relation._module_data_uninstall(cr, uid, relation_ids, context)
1201 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1202 if model == 'ir.model')
1206 self.unlink(cr, uid, ids, context)
1208 def _process_end(self, cr, uid, modules):
1209 """ Clear records removed from updated module data.
1210 This method is called at the end of the module loading process.
1211 It is meant to removed records that are no longer present in the
1212 updated data. Such records are recognised as the one with an xml id
1213 and a module in ir_model_data and noupdate set to false, but not
1214 present in self.loads.
1219 cr.execute("""SELECT id,name,model,res_id,module FROM ir_model_data
1220 WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s ORDER BY id DESC""",
1221 (tuple(modules), False))
1222 for (id, name, model, res_id, module) in cr.fetchall():
1223 if (module,name) not in self.loads:
1224 to_unlink.append((model,res_id))
1225 if not config.get('import_partial'):
1226 for (model, res_id) in to_unlink:
1227 if model in self.pool:
1228 _logger.info('Deleting %s@%s', res_id, model)
1229 self.pool[model].unlink(cr, uid, [res_id])
1231 class wizard_model_menu(osv.osv_memory):
1232 _name = 'wizard.ir.model.menu.create'
1234 'menu_id': fields.many2one('ir.ui.menu', 'Parent Menu', required=True),
1235 'name': fields.char('Menu Name', required=True),
1238 def menu_create(self, cr, uid, ids, context=None):
1241 model_pool = self.pool.get('ir.model')
1242 for menu in self.browse(cr, uid, ids, context):
1243 model = model_pool.browse(cr, uid, context.get('model_id'), context=context)
1246 'res_model': model.model,
1247 'view_type': 'form',
1248 'view_mode': 'tree,form'
1250 action_id = self.pool.get('ir.actions.act_window').create(cr, uid, val)
1251 self.pool.get('ir.ui.menu').create(cr, uid, {
1253 'parent_id': menu.menu_id.id,
1254 'action': 'ir.actions.act_window,%d' % (action_id,),
1255 'icon': 'STOCK_INDENT'
1257 return {'type':'ir.actions.act_window_close'}
1259 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: