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 import openerp.modules.registry
30 from openerp import SUPERUSER_ID
31 from openerp import tools
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)])
99 'name': fields.char('Model Description', translate=True, required=True),
100 'model': fields.char('Model', required=True, select=1),
101 'info': fields.text('Information'),
102 'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields', required=True, copy=True),
103 'state': fields.selection([('manual','Custom Object'),('base','Base Object')],'Type', readonly=True),
104 'access_ids': fields.one2many('ir.model.access', 'model_id', 'Access'),
105 'osv_memory': fields.function(_is_osv_memory, string='Transient Model', type='boolean',
106 fnct_search=_search_osv_memory,
107 help="This field specifies whether the model is transient or not (i.e. if records are automatically deleted from the database or not)"),
108 'modules': fields.function(_in_modules, type='char', string='In Modules', help='List of modules in which the object is defined or inherited'),
109 'view_ids': fields.function(_view_ids, type='one2many', obj='ir.ui.view', string='Views'),
114 'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
117 def _check_model_name(self, cr, uid, ids, context=None):
118 for model in self.browse(cr, uid, ids, context=context):
119 if model.state=='manual':
120 if not model.model.startswith('x_'):
122 if not re.match('^[a-z_A-Z0-9.]+$',model.model):
126 def _model_name_msg(self, cr, uid, ids, context=None):
127 return _('The Object name must start with x_ and not contain any special character !')
130 (_check_model_name, _model_name_msg, ['model']),
133 ('obj_name_uniq', 'unique (model)', 'Each model must be unique!'),
136 # overridden to allow searching both on model name (model field)
137 # and model description (name field)
138 def _name_search(self, cr, uid, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
141 domain = args + ['|', ('model', operator, name), ('name', operator, name)]
142 return self.name_get(cr, name_get_uid or uid,
143 super(ir_model, self).search(cr, uid, domain, limit=limit, context=context),
146 def _drop_table(self, cr, uid, ids, context=None):
147 for model in self.browse(cr, uid, ids, context):
148 model_pool = self.pool[model.model]
149 cr.execute('select relkind from pg_class where relname=%s', (model_pool._table,))
150 result = cr.fetchone()
151 if result and result[0] == 'v':
152 cr.execute('DROP view %s' % (model_pool._table,))
153 elif result and result[0] == 'r':
154 cr.execute('DROP TABLE %s 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 openerp.modules.registry.RegistryManager.new(cr.dbname)
174 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
178 def write(self, cr, user, ids, vals, context=None):
180 context = dict(context)
181 context.pop('__last_update', None)
182 # Filter out operations 4 link from field id, because openerp-web
183 # always write (4,id,False) even for non dirty items
184 if 'field_id' in vals:
185 vals['field_id'] = [op for op in vals['field_id'] if op[0] != 4]
186 return super(ir_model,self).write(cr, user, ids, vals, context)
188 def create(self, cr, user, vals, context=None):
191 if context and context.get('manual'):
192 vals['state']='manual'
193 res = super(ir_model,self).create(cr, user, vals, context)
194 if vals.get('state','base')=='manual':
195 self.instanciate(cr, user, vals['model'], context)
196 model = self.pool[vals['model']]
197 model._prepare_setup_fields(cr, SUPERUSER_ID)
198 model._setup_fields(cr, SUPERUSER_ID)
200 field_name=vals['name'],
201 field_state='manual',
202 select=vals.get('select_level', '0'),
203 update_custom_fields=True)
204 model._auto_init(cr, ctx)
205 model._auto_end(cr, ctx) # actually create FKs!
206 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
209 def instanciate(self, cr, user, model, context=None):
210 class x_custom_model(osv.osv):
212 if isinstance(model, unicode):
213 model = model.encode('utf-8')
214 x_custom_model._name = model
215 x_custom_model._module = False
216 a = x_custom_model._build_model(self.pool, cr)
219 elif 'x_name' in a._columns.keys():
222 x_name = a._columns.keys()[0]
223 x_custom_model._rec_name = x_name
226 class ir_model_fields(osv.osv):
227 _name = 'ir.model.fields'
228 _description = "Fields"
229 _rec_name = 'field_description'
232 'name': fields.char('Name', required=True, select=1),
233 'complete_name': fields.char('Complete Name', select=1),
234 'model': fields.char('Object Name', required=True, select=1,
235 help="The technical name of the model this field belongs to"),
236 'relation': fields.char('Object Relation',
237 help="For relationship fields, the technical name of the target model"),
238 'relation_field': fields.char('Relation Field',
239 help="For one2many fields, the field on the target model that implement the opposite many2one relationship"),
240 'model_id': fields.many2one('ir.model', 'Model', required=True, select=True, ondelete='cascade',
241 help="The model this field belongs to"),
242 'field_description': fields.char('Field Label', required=True),
243 'ttype': fields.selection(_get_fields_type, 'Field Type', required=True),
244 'selection': fields.char('Selection Options', help="List of options for a selection field, "
245 "specified as a Python expression defining a list of (key, label) pairs. "
246 "For example: [('blue','Blue'),('yellow','Yellow')]"),
247 'required': fields.boolean('Required'),
248 'readonly': fields.boolean('Readonly'),
249 'select_level': fields.selection([('0','Not Searchable'),('1','Always Searchable'),('2','Advanced Search (deprecated)')],'Searchable', required=True),
250 'translate': fields.boolean('Translatable', help="Whether values for this field can be translated (enables the translation mechanism for that field)"),
251 'size': fields.integer('Size'),
252 'state': fields.selection([('manual','Custom Field'),('base','Base Field')],'Type', required=True, readonly=True, select=1),
253 'on_delete': fields.selection([('cascade','Cascade'),('set null','Set NULL')], 'On Delete', help='On delete property for many2one fields'),
254 'domain': fields.char('Domain', help="The optional domain to restrict possible values for relationship fields, "
255 "specified as a Python expression defining a list of triplets. "
256 "For example: [('color','=','red')]"),
257 'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'),
258 'selectable': fields.boolean('Selectable'),
259 'modules': fields.function(_in_modules, type='char', string='In Modules', help='List of modules in which the field is defined'),
260 'serialization_field_id': fields.many2one('ir.model.fields', 'Serialization Field', domain = "[('ttype','=','serialized')]",
261 ondelete='cascade', help="If set, this field will be stored in the sparse "
262 "structure of the serialization field, instead "
263 "of having its own database column. This cannot be "
264 "changed after creation."),
266 _rec_name='field_description'
271 'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
272 'on_delete': 'set null',
274 'field_description': '',
279 def _check_selection(self, cr, uid, selection, context=None):
281 selection_list = eval(selection)
283 _logger.warning('Invalid selection list definition for fields.selection', exc_info=True)
284 raise except_orm(_('Error'),
285 _("The Selection Options expression is not a valid Pythonic expression."
286 "Please provide an expression in the [('key','Label'), ...] format."))
289 if not (isinstance(selection_list, list) and selection_list):
292 for item in selection_list:
293 if not (isinstance(item, (tuple,list)) and len(item) == 2):
298 raise except_orm(_('Error'),
299 _("The Selection Options expression is must be in the [('key','Label'), ...] format!"))
302 def _size_gt_zero_msg(self, cr, user, ids, context=None):
303 return _('Size of the field can never be less than 0 !')
306 ('size_gt_zero', 'CHECK (size>=0)',_size_gt_zero_msg ),
309 def _drop_column(self, cr, uid, ids, context=None):
310 for field in self.browse(cr, uid, ids, context):
311 if field.name in MAGIC_COLUMNS:
313 model = self.pool[field.model]
314 cr.execute('select relkind from pg_class where relname=%s', (model._table,))
315 result = cr.fetchone()
316 cr.execute("SELECT column_name FROM information_schema.columns WHERE table_name ='%s' and column_name='%s'" %(model._table, field.name))
317 column_name = cr.fetchone()
318 if column_name and (result and result[0] == 'r'):
319 cr.execute('ALTER table "%s" DROP column "%s" cascade' % (model._table, field.name))
320 model._columns.pop(field.name, None)
322 # remove m2m relation table for custom fields
323 # we consider the m2m relation is only one way as it's not possible
324 # to specify the relation table in the interface for custom fields
325 # TODO master: maybe use ir.model.relations for custom fields
326 if field.state == 'manual' and field.ttype == 'many2many':
327 rel_name = self.pool[field.model]._all_columns[field.name].column._rel
328 cr.execute('DROP table "%s"' % (rel_name))
331 def unlink(self, cr, user, ids, context=None):
332 # Prevent manual deletion of module columns
333 if context is None: context = {}
334 if isinstance(ids, (int, long)):
336 if not context.get(MODULE_UNINSTALL_FLAG) and \
337 any(field.state != 'manual' for field in self.browse(cr, user, ids, context)):
338 raise except_orm(_('Error'), _("This column contains module data and cannot be removed!"))
340 self._drop_column(cr, user, ids, context)
341 res = super(ir_model_fields, self).unlink(cr, user, ids, context)
342 if not context.get(MODULE_UNINSTALL_FLAG):
344 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
347 def create(self, cr, user, vals, context=None):
348 if 'model_id' in vals:
349 model_data = self.pool['ir.model'].browse(cr, user, vals['model_id'])
350 vals['model'] = model_data.model
353 if context and context.get('manual',False):
354 vals['state'] = 'manual'
355 if vals.get('ttype', False) == 'selection':
356 if not vals.get('selection',False):
357 raise except_orm(_('Error'), _('For selection fields, the Selection Options must be given!'))
358 self._check_selection(cr, user, vals['selection'], context=context)
359 res = super(ir_model_fields,self).create(cr, user, vals, context)
360 if vals.get('state','base') == 'manual':
361 if not vals['name'].startswith('x_'):
362 raise except_orm(_('Error'), _("Custom fields must have a name that starts with 'x_' !"))
364 if vals.get('relation',False) and not self.pool['ir.model'].search(cr, user, [('model','=',vals['relation'])]):
365 raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
367 if vals['model'] in self.pool:
368 model = self.pool[vals['model']]
369 if vals['model'].startswith('x_') and vals['name'] == 'x_name':
370 model._rec_name = 'x_name'
372 if self.pool.fields_by_model is not None:
373 cr.execute('SELECT * FROM ir_model_fields WHERE id=%s', (res,))
374 self.pool.fields_by_model.setdefault(vals['model'], []).append(cr.dictfetchone())
375 model.__init__(self.pool, cr)
376 model._prepare_setup_fields(cr, SUPERUSER_ID)
377 model._setup_fields(cr, SUPERUSER_ID)
379 #Added context to _auto_init for special treatment to custom field for select_level
381 field_name=vals['name'],
382 field_state='manual',
383 select=vals.get('select_level', '0'),
384 update_custom_fields=True)
385 model._auto_init(cr, ctx)
386 model._auto_end(cr, ctx) # actually create FKs!
387 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
391 def write(self, cr, user, ids, vals, context=None):
394 if context and context.get('manual',False):
395 vals['state'] = 'manual'
397 #For the moment renaming a sparse field or changing the storing system is not allowed. This may be done later
398 if 'serialization_field_id' in vals or 'name' in vals:
399 for field in self.browse(cr, user, ids, context=context):
400 if 'serialization_field_id' in vals and field.serialization_field_id.id != vals['serialization_field_id']:
401 raise except_orm(_('Error!'), _('Changing the storing system for field "%s" is not allowed.')%field.name)
402 if field.serialization_field_id and (field.name != vals['name']):
403 raise except_orm(_('Error!'), _('Renaming sparse field "%s" is not allowed')%field.name)
405 column_rename = None # if set, *one* column can be renamed here
406 models_patch = {} # structs of (obj, [(field, prop, change_to),..])
407 # data to be updated on the orm model
409 # static table of properties
410 model_props = [ # (our-name, fields.prop, set_fn)
411 ('field_description', 'string', tools.ustr),
412 ('required', 'required', bool),
413 ('readonly', 'readonly', bool),
414 ('domain', '_domain', eval),
415 ('size', 'size', int),
416 ('on_delete', 'ondelete', str),
417 ('translate', 'translate', bool),
418 ('selectable', 'selectable', bool),
419 ('select_level', 'select', int),
420 ('selection', 'selection', eval),
424 checked_selection = False # need only check it once, so defer
426 for item in self.browse(cr, user, ids, context=context):
427 obj = self.pool.get(item.model)
429 if item.state != 'manual':
430 raise except_orm(_('Error!'),
431 _('Properties of base fields cannot be altered in this manner! '
432 'Please modify them through Python code, '
433 'preferably through a custom addon!'))
435 if item.ttype == 'selection' and 'selection' in vals \
436 and not checked_selection:
437 self._check_selection(cr, user, vals['selection'], context=context)
438 checked_selection = True
440 final_name = item.name
441 if 'name' in vals and vals['name'] != item.name:
442 # We need to rename the column
444 raise except_orm(_('Error!'), _('Can only rename one column at a time!'))
445 if vals['name'] in obj._columns:
446 raise except_orm(_('Error!'), _('Cannot rename column to %s, because that column already exists!') % vals['name'])
447 if vals.get('state', 'base') == 'manual' and not vals['name'].startswith('x_'):
448 raise except_orm(_('Error!'), _('New column name must still start with x_ , because it is a custom field!'))
449 if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']:
450 raise ValueError('Invalid character in column name')
451 column_rename = (obj, (obj._table, item.name, vals['name']))
452 final_name = vals['name']
454 if 'model_id' in vals and vals['model_id'] != item.model_id.id:
455 raise except_orm(_("Error!"), _("Changing the model of a field is forbidden!"))
457 if 'ttype' in vals and vals['ttype'] != item.ttype:
458 raise except_orm(_("Error!"), _("Changing the type of a column is not yet supported. "
459 "Please drop it and create it again!"))
461 # We don't check the 'state', because it might come from the context
462 # (thus be set for multiple fields) and will be ignored anyway.
464 models_patch.setdefault(obj._name, (obj,[]))
465 # find out which properties (per model) we need to update
466 for field_name, field_property, set_fn in model_props:
467 if field_name in vals:
468 property_value = set_fn(vals[field_name])
469 if getattr(obj._columns[item.name], field_property) != property_value:
470 models_patch[obj._name][1].append((final_name, field_property, property_value))
471 # our dict is ready here, but no properties are changed so far
473 # These shall never be written (modified)
474 for column_name in ('model_id', 'model', 'state'):
475 if column_name in vals:
476 del vals[column_name]
478 res = super(ir_model_fields,self).write(cr, user, ids, vals, context=context)
481 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % column_rename[1])
482 # This is VERY risky, but let us have this feature:
483 # we want to change the key of column in obj._columns dict
484 col = column_rename[0]._columns.pop(column_rename[1][1]) # take object out, w/o copy
485 column_rename[0]._columns[column_rename[1][2]] = col
488 # We have to update _columns of the model(s) and then call their
489 # _auto_init to sync the db with the model. Hopefully, since write()
490 # was called earlier, they will be in-sync before the _auto_init.
491 # Anything we don't update in _columns now will be reset from
492 # the model into ir.model.fields (db).
493 ctx = dict(context, select=vals.get('select_level', '0'),
494 update_custom_fields=True)
496 for __, patch_struct in models_patch.items():
497 obj = patch_struct[0]
498 for col_name, col_prop, val in patch_struct[1]:
499 setattr(obj._columns[col_name], col_prop, val)
500 obj._auto_init(cr, ctx)
501 obj._auto_end(cr, ctx) # actually create FKs!
502 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
505 class ir_model_constraint(Model):
507 This model tracks PostgreSQL foreign keys and constraints used by OpenERP
510 _name = 'ir.model.constraint'
512 'name': fields.char('Constraint', required=True, select=1,
513 help="PostgreSQL constraint or foreign key name."),
514 'model': fields.many2one('ir.model', string='Model',
515 required=True, select=1),
516 'module': fields.many2one('ir.module.module', string='Module',
517 required=True, select=1),
518 'type': fields.char('Constraint Type', required=True, size=1, select=1,
519 help="Type of the constraint: `f` for a foreign key, "
520 "`u` for other constraints."),
521 'date_update': fields.datetime('Update Date'),
522 'date_init': fields.datetime('Initialization Date')
526 ('module_name_uniq', 'unique(name, module)',
527 'Constraints with the same name are unique per module.'),
530 def _module_data_uninstall(self, cr, uid, ids, context=None):
532 Delete PostgreSQL foreign keys and constraints tracked by this model.
535 if uid != SUPERUSER_ID and not self.pool['ir.model.access'].check_groups(cr, uid, "base.group_system"):
536 raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
538 context = dict(context or {})
543 for data in self.browse(cr, uid, ids, context):
544 model = data.model.model
545 model_obj = self.pool[model]
546 name = openerp.tools.ustr(data.name)
549 # double-check we are really going to delete all the owners of this schema element
550 cr.execute("""SELECT id from ir_model_constraint where name=%s""", (data.name,))
551 external_ids = [x[0] for x in cr.fetchall()]
552 if set(external_ids)-ids_set:
553 # as installed modules have defined this element we must not delete it!
557 # test if FK exists on this table (it could be on a related m2m table, in which case we ignore it)
558 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
559 WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_obj._table))
561 cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
562 _logger.info('Dropped FK CONSTRAINT %s@%s', name, model)
565 # test if constraint exists
566 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
567 WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_obj._table))
569 cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
570 _logger.info('Dropped CONSTRAINT %s@%s', name, model)
572 self.unlink(cr, uid, ids, context)
574 class ir_model_relation(Model):
576 This model tracks PostgreSQL tables used to implement OpenERP many2many
579 _name = 'ir.model.relation'
581 'name': fields.char('Relation Name', required=True, select=1,
582 help="PostgreSQL table name implementing a many2many relation."),
583 'model': fields.many2one('ir.model', string='Model',
584 required=True, select=1),
585 'module': fields.many2one('ir.module.module', string='Module',
586 required=True, select=1),
587 'date_update': fields.datetime('Update Date'),
588 'date_init': fields.datetime('Initialization Date')
591 def _module_data_uninstall(self, cr, uid, ids, context=None):
593 Delete PostgreSQL many2many relations tracked by this model.
596 if uid != SUPERUSER_ID and not self.pool['ir.model.access'].check_groups(cr, uid, "base.group_system"):
597 raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
603 for data in self.browse(cr, uid, ids, context):
605 name = openerp.tools.ustr(data.name)
607 # double-check we are really going to delete all the owners of this schema element
608 cr.execute("""SELECT id from ir_model_relation where name = %s""", (data.name,))
609 external_ids = [x[0] for x in cr.fetchall()]
610 if set(external_ids)-ids_set:
611 # as installed modules have defined this element we must not delete it!
614 cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name=%s", (name,))
615 if cr.fetchone() and not name in to_drop_table:
616 to_drop_table.append(name)
618 self.unlink(cr, uid, ids, context)
620 # drop m2m relation tables
621 for table in to_drop_table:
622 cr.execute('DROP TABLE %s CASCADE'% table,)
623 _logger.info('Dropped table %s', table)
627 class ir_model_access(osv.osv):
628 _name = 'ir.model.access'
630 'name': fields.char('Name', required=True, select=True),
631 '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.'),
632 'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)], select=True, ondelete='cascade'),
633 'group_id': fields.many2one('res.groups', 'Group', ondelete='cascade', select=True),
634 'perm_read': fields.boolean('Read Access'),
635 'perm_write': fields.boolean('Write Access'),
636 'perm_create': fields.boolean('Create Access'),
637 'perm_unlink': fields.boolean('Delete Access'),
643 def check_groups(self, cr, uid, group):
644 grouparr = group.split('.')
647 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],))
648 return bool(cr.fetchone())
650 def check_group(self, cr, uid, model, mode, group_ids):
651 """ Check if a specific group has the access mode to the specified model"""
652 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
654 if isinstance(model, BaseModel):
655 assert model._name == 'ir.model', 'Invalid model object'
656 model_name = model.name
660 if isinstance(group_ids, (int, long)):
661 group_ids = [group_ids]
662 for group_id in group_ids:
663 cr.execute("SELECT perm_" + mode + " "
664 " FROM ir_model_access a "
665 " JOIN ir_model m ON (m.id = a.model_id) "
666 " WHERE m.model = %s AND a.active IS True "
667 " AND a.group_id = %s", (model_name, group_id)
671 cr.execute("SELECT perm_" + mode + " "
672 " FROM ir_model_access a "
673 " JOIN ir_model m ON (m.id = a.model_id) "
674 " WHERE m.model = %s AND a.active IS True "
675 " AND a.group_id IS NULL", (model_name, )
679 access = bool(r and r[0])
682 # pass no groups -> no access
685 def group_names_with_access(self, cr, model_name, access_mode):
686 """Returns the names of visible groups which have been granted ``access_mode`` on
687 the model ``model_name``.
690 assert access_mode in ['read','write','create','unlink'], 'Invalid access mode: %s' % access_mode
695 JOIN ir_model m ON (a.model_id=m.id)
696 JOIN res_groups g ON (a.group_id=g.id)
697 LEFT JOIN ir_module_category c ON (c.id=g.category_id)
701 a.perm_''' + access_mode, (model_name,))
702 return [('%s/%s' % x) if x[0] else x[1] for x in cr.fetchall()]
705 def check(self, cr, uid, model, mode='read', raise_exception=True, context=None):
707 # User root have all accesses
708 # TODO: exclude xml-rpc requests
711 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
713 if isinstance(model, BaseModel):
714 assert model._name == 'ir.model', 'Invalid model object'
715 model_name = model.model
719 # TransientModel records have no access rights, only an implicit access rule
720 if model_name not in self.pool:
721 _logger.error('Missing model %s' % (model_name, ))
722 elif self.pool[model_name].is_transient():
725 # We check if a specific rule exists
726 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
727 ' FROM ir_model_access a '
728 ' JOIN ir_model m ON (m.id = a.model_id) '
729 ' JOIN res_groups_users_rel gu ON (gu.gid = a.group_id) '
730 ' WHERE m.model = %s '
732 ' AND a.active IS True '
738 # there is no specific rule. We check the generic rule
739 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
740 ' FROM ir_model_access a '
741 ' JOIN ir_model m ON (m.id = a.model_id) '
742 ' WHERE a.group_id IS NULL '
744 ' AND a.active IS True '
749 if not r and raise_exception:
750 groups = '\n\t'.join('- %s' % g for g in self.group_names_with_access(cr, model_name, mode))
752 # Messages are declared in extenso so they are properly exported in translation terms
753 'read': _("Sorry, you are not allowed to access this document."),
754 'write': _("Sorry, you are not allowed to modify this document."),
755 'create': _("Sorry, you are not allowed to create this kind of document."),
756 'unlink': _("Sorry, you are not allowed to delete this document."),
759 msg_tail = _("Only users with the following access level are currently allowed to do that") + ":\n%s\n\n(" + _("Document model") + ": %s)"
760 msg_params = (groups, model_name)
762 msg_tail = _("Please contact your system administrator if you think this is an error.") + "\n\n(" + _("Document model") + ": %s)"
763 msg_params = (model_name,)
764 _logger.warning('Access Denied by ACLs for operation: %s, uid: %s, model: %s', mode, uid, model_name)
765 msg = '%s %s' % (msg_heads[mode], msg_tail)
766 raise openerp.exceptions.AccessError(msg % msg_params)
769 __cache_clearing_methods = []
771 def register_cache_clearing_method(self, model, method):
772 self.__cache_clearing_methods.append((model, method))
774 def unregister_cache_clearing_method(self, model, method):
776 i = self.__cache_clearing_methods.index((model, method))
777 del self.__cache_clearing_methods[i]
781 def call_cache_clearing_methods(self, cr):
782 self.invalidate_cache(cr, SUPERUSER_ID)
783 self.check.clear_cache(self) # clear the cache of check function
784 for model, method in self.__cache_clearing_methods:
785 if model in self.pool:
786 getattr(self.pool[model], method)()
789 # Check rights on actions
791 def write(self, cr, uid, ids, values, context=None):
792 self.call_cache_clearing_methods(cr)
793 res = super(ir_model_access, self).write(cr, uid, ids, values, context=context)
796 def create(self, cr, uid, values, context=None):
797 self.call_cache_clearing_methods(cr)
798 res = super(ir_model_access, self).create(cr, uid, values, context=context)
801 def unlink(self, cr, uid, ids, context=None):
802 self.call_cache_clearing_methods(cr)
803 res = super(ir_model_access, self).unlink(cr, uid, ids, context=context)
806 class ir_model_data(osv.osv):
807 """Holds external identifier keys for records in the database.
808 This has two main uses:
810 * allows easy data integration with third-party systems,
811 making import/export/sync of data possible, as records
812 can be uniquely identified across multiple systems
813 * allows tracking the origin of data installed by OpenERP
814 modules themselves, thus making it possible to later
815 update them seamlessly.
817 _name = 'ir.model.data'
818 _order = 'module,model,name'
820 def name_get(self, cr, uid, ids, context=None):
821 bymodel = defaultdict(dict)
824 for res in self.browse(cr, uid, ids, context=context):
825 bymodel[res.model][res.res_id] = res
826 names[res.id] = res.complete_name
827 #result[res.model][res.res_id] = res.id
829 for model, id_map in bymodel.iteritems():
831 ng = dict(self.pool[model].name_get(cr, uid, id_map.keys(), context=context))
835 for r in id_map.itervalues():
836 names[r.id] = ng.get(r.res_id, r.complete_name)
838 return [(i, names[i]) for i in ids]
840 def _complete_name_get(self, cr, uid, ids, prop, unknow_none, context=None):
842 for res in self.browse(cr, uid, ids, context=context):
843 result[res.id] = (res.module and (res.module + '.') or '')+res.name
847 'name': fields.char('External Identifier', required=True, select=1,
848 help="External Key/Identifier that can be used for "
849 "data integration with third-party systems"),
850 'complete_name': fields.function(_complete_name_get, type='char', string='Complete ID'),
851 'model': fields.char('Model Name', required=True, select=1),
852 'module': fields.char('Module', required=True, select=1),
853 'res_id': fields.integer('Record ID', select=1,
854 help="ID of the target record in the database"),
855 'noupdate': fields.boolean('Non Updatable'),
856 'date_update': fields.datetime('Update Date'),
857 'date_init': fields.datetime('Init Date')
860 'date_init': fields.datetime.now,
861 'date_update': fields.datetime.now,
866 ('module_name_uniq', 'unique(name, module)', 'You cannot have multiple records with the same external ID in the same module!'),
869 def __init__(self, pool, cr):
870 osv.osv.__init__(self, pool, cr)
871 # also stored in pool to avoid being discarded along with this osv instance
872 if getattr(pool, 'model_data_reference_ids', None) is None:
873 self.pool.model_data_reference_ids = {}
874 # put loads on the class, in order to share it among all instances
875 type(self).loads = self.pool.model_data_reference_ids
877 def _auto_init(self, cr, context=None):
878 super(ir_model_data, self)._auto_init(cr, context)
879 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_model_data_module_name_index\'')
880 if not cr.fetchone():
881 cr.execute('CREATE INDEX ir_model_data_module_name_index ON ir_model_data (module, name)')
884 @tools.ormcache(skiparg=3)
885 def xmlid_lookup(self, cr, uid, xmlid):
886 """Low level xmlid lookup
887 Return (id, res_model, res_id) or raise ValueError if not found
889 module, name = xmlid.split('.', 1)
890 ids = self.search(cr, uid, [('module','=',module), ('name','=', name)])
892 raise ValueError('External ID not found in the system: %s' % (xmlid))
893 # the sql constraints ensure us we have only one result
894 res = self.read(cr, uid, ids[0], ['model', 'res_id'])
895 if not res['res_id']:
896 raise ValueError('External ID not found in the system: %s' % (xmlid))
897 return ids[0], res['model'], res['res_id']
899 def xmlid_to_res_model_res_id(self, cr, uid, xmlid, raise_if_not_found=False):
900 """ Return (res_model, res_id)"""
902 return self.xmlid_lookup(cr, uid, xmlid)[1:3]
904 if raise_if_not_found:
906 return (False, False)
908 def xmlid_to_res_id(self, cr, uid, xmlid, raise_if_not_found=False):
909 """ Returns res_id """
910 return self.xmlid_to_res_model_res_id(cr, uid, xmlid, raise_if_not_found)[1]
912 def xmlid_to_object(self, cr, uid, xmlid, raise_if_not_found=False, context=None):
913 """ Return a browse_record
914 if not found and raise_if_not_found is True return None
916 t = self.xmlid_to_res_model_res_id(cr, uid, xmlid, raise_if_not_found)
917 res_model, res_id = t
919 if res_model and res_id:
920 record = self.pool[res_model].browse(cr, uid, res_id, context=context)
923 if raise_if_not_found:
924 raise ValueError('No record found for unique ID %s. It may have been deleted.' % (xmlid))
928 def _get_id(self, cr, uid, module, xml_id):
929 """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"""
930 return self.xmlid_lookup(cr, uid, "%s.%s" % (module, xml_id))[0]
932 def get_object_reference(self, cr, uid, module, xml_id):
933 """Returns (model, res_id) corresponding to a given module and xml_id (cached) or raise ValueError if not found"""
934 return self.xmlid_lookup(cr, uid, "%s.%s" % (module, xml_id))[1:3]
936 def check_object_reference(self, cr, uid, module, xml_id, raise_on_access_error=False):
937 """Returns (model, res_id) corresponding to a given module and xml_id (cached), if and only if the user has the necessary access rights
938 to see that object, otherwise raise a ValueError if raise_on_access_error is True or returns a tuple (model found, False)"""
939 model, res_id = self.get_object_reference(cr, uid, module, xml_id)
940 #search on id found in result to check if current user has read access right
941 check_right = self.pool.get(model).search(cr, uid, [('id', '=', res_id)])
944 if raise_on_access_error:
945 raise ValueError('Not enough access rights on the external ID: %s.%s' % (module, xml_id))
948 def get_object(self, cr, uid, module, xml_id, context=None):
949 """ Returns a browsable record for the given module name and xml_id.
950 If not found, raise a ValueError or return None, depending
951 on the value of `raise_exception`.
953 return self.xmlid_to_object(cr, uid, "%s.%s" % (module, xml_id), raise_if_not_found=True, context=context)
955 def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
959 id = self.read(cr, uid, [self._get_id(cr, uid, module, xml_id)], ['res_id'])[0]['res_id']
960 self.loads[(module,xml_id)] = (model,id)
965 def clear_caches(self):
966 """ Clears all orm caches on the object's methods
970 self.xmlid_lookup.clear_cache(self)
973 def unlink(self, cr, uid, ids, context=None):
974 """ Regular unlink method, but make sure to clear the caches. """
976 return super(ir_model_data,self).unlink(cr, uid, ids, context=context)
978 def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None):
979 model_obj = self.pool[model]
982 # records created during module install should not display the messages of OpenChatter
983 context = dict(context, install_mode=True)
984 if xml_id and ('.' in xml_id):
985 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
986 module, xml_id = xml_id.split('.')
989 cr.execute('''SELECT imd.id, imd.res_id, md.id, imd.model, imd.noupdate
990 FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
991 WHERE imd.module=%%s AND imd.name=%%s''' % model_obj._table,
993 results = cr.fetchall()
994 for imd_id2,res_id2,real_id2,real_model,noupdate_imd in results:
995 # In update mode, do not update a record if it's ir.model.data is flagged as noupdate
996 if mode == 'update' and noupdate_imd:
1000 cr.execute('delete from ir_model_data where id=%s', (imd_id2,))
1003 assert model == real_model, "External ID conflict, %s already refers to a `%s` record,"\
1004 " you can't define a `%s` record with this ID." % (xml_id, real_model, model)
1005 res_id,action_id = res_id2,imd_id2
1007 if action_id and res_id:
1008 model_obj.write(cr, uid, [res_id], values, context=context)
1009 self.write(cr, uid, [action_id], {
1010 'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
1013 model_obj.write(cr, uid, [res_id], values, context=context)
1015 if model_obj._inherits:
1016 for table in model_obj._inherits:
1017 inherit_id = model_obj.browse(cr, uid,
1018 res_id,context=context)[model_obj._inherits[table]]
1019 self.create(cr, uid, {
1020 'name': xml_id + '_' + table.replace('.', '_'),
1023 'res_id': inherit_id.id,
1024 'noupdate': noupdate,
1026 self.create(cr, uid, {
1031 'noupdate': noupdate,
1034 if mode=='init' or (mode=='update' and xml_id):
1035 res_id = model_obj.create(cr, uid, values, context=context)
1037 if model_obj._inherits:
1038 for table in model_obj._inherits:
1039 inherit_id = model_obj.browse(cr, uid,
1040 res_id,context=context)[model_obj._inherits[table]]
1041 self.create(cr, uid, {
1042 'name': xml_id + '_' + table.replace('.', '_'),
1045 'res_id': inherit_id.id,
1046 'noupdate': noupdate,
1048 self.create(cr, uid, {
1053 'noupdate': noupdate
1055 if xml_id and res_id:
1056 self.loads[(module, xml_id)] = (model, res_id)
1057 for table, inherit_field in model_obj._inherits.iteritems():
1058 inherit_id = model_obj.read(cr, uid, [res_id],
1059 [inherit_field])[0][inherit_field]
1060 self.loads[(module, xml_id + '_' + table.replace('.', '_'))] = (table, inherit_id)
1063 def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None, xml_id=False):
1064 if isinstance(models[0], (list, tuple)):
1065 model,res_id = models[0]
1071 where = ' and res_id=%s' % (res_id,)
1073 where = ' and (res_id is null)'
1076 where += ' and key2=\'%s\'' % (key2,)
1078 where += ' and (key2 is null)'
1080 cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
1082 ir_values_obj = openerp.registry(cr.dbname)['ir.values']
1084 ir_values_obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
1086 cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
1087 ir_values_obj.invalidate_cache(cr, uid, ['value'])
1090 def _module_data_uninstall(self, cr, uid, modules_to_remove, context=None):
1091 """Deletes all the records referenced by the ir.model.data entries
1092 ``ids`` along with their corresponding database backed (including
1093 dropping tables, columns, FKs, etc, as long as there is no other
1094 ir.model.data entry holding a reference to them (which indicates that
1095 they are still owned by another module).
1096 Attempts to perform the deletion in an appropriate order to maximize
1097 the chance of gracefully deleting all records.
1098 This step is performed as part of the full uninstallation of a module.
1101 ids = self.search(cr, uid, [('module', 'in', modules_to_remove)])
1103 if uid != 1 and not self.pool['ir.model.access'].check_groups(cr, uid, "base.group_system"):
1104 raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
1106 context = dict(context or {})
1107 context[MODULE_UNINSTALL_FLAG] = True # enable model/field deletion
1114 for data in self.browse(cr, uid, ids, context):
1116 res_id = data.res_id
1118 pair_to_unlink = (model, res_id)
1119 if pair_to_unlink not in to_unlink:
1120 to_unlink.append(pair_to_unlink)
1122 if model == 'workflow.activity':
1123 # Special treatment for workflow activities: temporarily revert their
1124 # incoming transition and trigger an update to force all workflow items
1125 # to move out before deleting them
1126 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,))
1127 wkf_todo.extend(cr.fetchall())
1128 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))
1129 self.invalidate_cache(cr, uid, context=context)
1131 for model,res_id in wkf_todo:
1133 openerp.workflow.trg_write(uid, model, res_id, cr)
1135 _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)
1137 def unlink_if_refcount(to_unlink):
1138 for model, res_id in to_unlink:
1139 external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
1140 if set(external_ids)-ids_set:
1141 # if other modules have defined this record, we must not delete it
1143 if model == 'ir.model.fields':
1144 # Don't remove the LOG_ACCESS_COLUMNS unless _log_access
1145 # has been turned off on the model.
1146 field = self.pool[model].browse(cr, uid, [res_id], context=context)[0]
1147 if not field.exists():
1148 _logger.info('Deleting orphan external_ids %s', external_ids)
1149 self.unlink(cr, uid, external_ids)
1151 if field.name in openerp.models.LOG_ACCESS_COLUMNS and self.pool[field.model]._log_access:
1153 if field.name == 'id':
1155 _logger.info('Deleting %s@%s', res_id, model)
1157 cr.execute('SAVEPOINT record_unlink_save')
1158 self.pool[model].unlink(cr, uid, [res_id], context=context)
1160 _logger.info('Unable to delete %s@%s', res_id, model, exc_info=True)
1161 cr.execute('ROLLBACK TO SAVEPOINT record_unlink_save')
1163 cr.execute('RELEASE SAVEPOINT record_unlink_save')
1165 # Remove non-model records first, then model fields, and finish with models
1166 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1167 if model not in ('ir.model','ir.model.fields','ir.model.constraint'))
1168 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1169 if model == 'ir.model.constraint')
1171 ir_module_module = self.pool['ir.module.module']
1172 ir_model_constraint = self.pool['ir.model.constraint']
1173 modules_to_remove_ids = ir_module_module.search(cr, uid, [('name', 'in', modules_to_remove)], context=context)
1174 constraint_ids = ir_model_constraint.search(cr, uid, [('module', 'in', modules_to_remove_ids)], context=context)
1175 ir_model_constraint._module_data_uninstall(cr, uid, constraint_ids, context)
1177 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1178 if model == 'ir.model.fields')
1180 ir_model_relation = self.pool['ir.model.relation']
1181 relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove_ids)])
1182 ir_model_relation._module_data_uninstall(cr, uid, relation_ids, context)
1184 unlink_if_refcount((model, res_id) for model, res_id in to_unlink
1185 if model == 'ir.model')
1189 self.unlink(cr, uid, ids, context)
1191 def _process_end(self, cr, uid, modules):
1192 """ Clear records removed from updated module data.
1193 This method is called at the end of the module loading process.
1194 It is meant to removed records that are no longer present in the
1195 updated data. Such records are recognised as the one with an xml id
1196 and a module in ir_model_data and noupdate set to false, but not
1197 present in self.loads.
1202 cr.execute("""SELECT id,name,model,res_id,module FROM ir_model_data
1203 WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s ORDER BY id DESC""",
1204 (tuple(modules), False))
1205 for (id, name, model, res_id, module) in cr.fetchall():
1206 if (module,name) not in self.loads:
1207 to_unlink.append((model,res_id))
1208 if not config.get('import_partial'):
1209 for (model, res_id) in to_unlink:
1210 if model in self.pool:
1211 _logger.info('Deleting %s@%s', res_id, model)
1212 self.pool[model].unlink(cr, uid, [res_id])
1214 class wizard_model_menu(osv.osv_memory):
1215 _name = 'wizard.ir.model.menu.create'
1217 'menu_id': fields.many2one('ir.ui.menu', 'Parent Menu', required=True),
1218 'name': fields.char('Menu Name', required=True),
1221 def menu_create(self, cr, uid, ids, context=None):
1224 model_pool = self.pool.get('ir.model')
1225 for menu in self.browse(cr, uid, ids, context):
1226 model = model_pool.browse(cr, uid, context.get('model_id'), context=context)
1229 'res_model': model.model,
1230 'view_type': 'form',
1231 'view_mode': 'tree,form'
1233 action_id = self.pool.get('ir.actions.act_window').create(cr, uid, val)
1234 self.pool.get('ir.ui.menu').create(cr, uid, {
1236 'parent_id': menu.menu_id.id,
1237 'action': 'ir.actions.act_window,%d' % (action_id,),
1238 'icon': 'STOCK_INDENT'
1240 return {'type':'ir.actions.act_window_close'}
1242 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: