1 # -*- coding: utf-8 -*-
3 ##############################################################################
5 # OpenERP, Open Source Business Applications
6 # Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Affero General Public License as
10 # published by the Free Software Foundation, either version 3 of the
11 # License, or (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Affero General Public License for more details.
18 # You should have received a copy of the GNU Affero General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
27 from openerp.osv import fields,osv
28 from openerp import netsvc, pooler, tools
29 from openerp.tools.safe_eval import safe_eval as eval
30 from openerp.tools import config
31 from openerp.tools.translate import _
32 from openerp.osv.orm import except_orm, browse_record, EXT_ID_PREFIX_FK, \
33 EXT_ID_PREFIX_M2M_TABLE, EXT_ID_PREFIX_CONSTRAINT
35 _logger = logging.getLogger(__name__)
37 MODULE_UNINSTALL_FLAG = '_force_unlink'
39 def _get_fields_type(self, cr, uid, context=None):
40 # Avoid too many nested `if`s below, as RedHat's Python 2.6
41 # break on it. See bug 939653.
42 return sorted([(k,k) for k,v in fields.__dict__.iteritems()
43 if type(v) == types.TypeType and \
44 issubclass(v, fields._column) and \
45 v != fields._column and \
46 not v._deprecated and \
47 not issubclass(v, fields.function)])
49 def _in_modules(self, cr, uid, ids, field_name, arg, context=None):
50 #pseudo-method used by fields.function in ir.model/ir.model.fields
51 module_pool = self.pool.get("ir.module.module")
52 installed_module_ids = module_pool.search(cr, uid, [('state','=','installed')])
53 installed_module_names = module_pool.read(cr, uid, installed_module_ids, ['name'], context=context)
54 installed_modules = set(x['name'] for x in installed_module_names)
57 xml_ids = osv.osv._get_xml_ids(self, cr, uid, ids)
58 for k,v in xml_ids.iteritems():
59 result[k] = ', '.join(sorted(installed_modules & set(xml_id.split('.')[0] for xml_id in v)))
63 class ir_model(osv.osv):
65 _description = "Models"
68 def _is_osv_memory(self, cr, uid, ids, field_name, arg, context=None):
69 models = self.browse(cr, uid, ids, context=context)
70 res = dict.fromkeys(ids)
72 res[model.id] = self.pool.get(model.model).is_transient()
75 def _search_osv_memory(self, cr, uid, model, name, domain, context=None):
78 __, operator, value = domain[0]
79 if operator not in ['=', '!=']:
80 raise osv.except_osv(_('Invalid search criterions'), _('The osv_memory field can only be compared with = and != operator.'))
81 value = bool(value) if operator == '=' else not bool(value)
82 all_model_ids = self.search(cr, uid, [], context=context)
83 is_osv_mem = self._is_osv_memory(cr, uid, all_model_ids, 'osv_memory', arg=None, context=context)
84 return [('id', 'in', [id for id in is_osv_mem if bool(is_osv_mem[id]) == value])]
86 def _view_ids(self, cr, uid, ids, field_name, arg, context=None):
87 models = self.browse(cr, uid, ids)
90 res[model.id] = self.pool.get("ir.ui.view").search(cr, uid, [('model', '=', model.model)])
94 'name': fields.char('Model Description', size=64, translate=True, required=True),
95 'model': fields.char('Model', size=64, required=True, select=1),
96 'info': fields.text('Information'),
97 'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields', required=True),
98 'state': fields.selection([('manual','Custom Object'),('base','Base Object')],'Type',readonly=True),
99 'access_ids': fields.one2many('ir.model.access', 'model_id', 'Access'),
100 'osv_memory': fields.function(_is_osv_memory, string='In-memory model', type='boolean',
101 fnct_search=_search_osv_memory,
102 help="Indicates whether this object model lives in memory only, i.e. is not persisted (osv.osv_memory)"),
103 'modules': fields.function(_in_modules, type='char', size=128, string='In modules', help='List of modules in which the object is defined or inherited'),
104 'view_ids': fields.function(_view_ids, type='one2many', obj='ir.ui.view', string='Views'),
108 'model': lambda *a: 'x_',
109 'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
112 def _check_model_name(self, cr, uid, ids, context=None):
113 for model in self.browse(cr, uid, ids, context=context):
114 if model.state=='manual':
115 if not model.model.startswith('x_'):
117 if not re.match('^[a-z_A-Z0-9.]+$',model.model):
121 def _model_name_msg(self, cr, uid, ids, context=None):
122 return _('The Object name must start with x_ and not contain any special character !')
125 (_check_model_name, _model_name_msg, ['model']),
128 ('obj_name_uniq', 'unique (model)', 'Each model must be unique!'),
131 # overridden to allow searching both on model name (model field)
132 # and model description (name field)
133 def _name_search(self, cr, uid, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
136 domain = args + ['|', ('model', operator, name), ('name', operator, name)]
137 return self.name_get(cr, name_get_uid or uid,
138 super(ir_model, self).search(cr, uid, domain, limit=limit, context=context),
141 def _drop_table(self, cr, uid, ids, context=None):
142 for model in self.browse(cr, uid, ids, context):
143 model_pool = self.pool.get(model.model)
144 # this test should be removed, but check if drop view instead of drop table
145 # just check if table or view exists
146 cr.execute('select relkind from pg_class where relname=%s', (model_pool._table,))
147 result = cr.fetchone()
148 if result and result[0] == 'v':
149 cr.execute('DROP view %s' % (model_pool._table,))
150 elif result and result[0] == 'r':
151 cr.execute('DROP TABLE %s' % (model_pool._table,))
154 def unlink(self, cr, user, ids, context=None):
155 # Prevent manual deletion of module tables
156 if context is None: context = {}
157 if isinstance(ids, (int, long)):
159 if not context.get(MODULE_UNINSTALL_FLAG) and \
160 any(model.state != 'manual' for model in self.browse(cr, user, ids, context)):
161 raise except_orm(_('Error'), _("Model '%s' contains module data and cannot be removed!") % (model.name,))
163 self._drop_table(cr, user, ids, context)
164 res = super(ir_model, self).unlink(cr, user, ids, context)
165 if not context.get(MODULE_UNINSTALL_FLAG):
166 # only reload pool for normal unlink. For module uninstall the
167 # reload is done independently in openerp.modules.loading
168 pooler.restart_pool(cr.dbname)
172 def write(self, cr, user, ids, vals, context=None):
174 context.pop('__last_update', None)
175 # Filter out operations 4 link from field id, because openerp-web
176 # always write (4,id,False) even for non dirty items
177 if 'field_id' in vals:
178 vals['field_id'] = [op for op in vals['field_id'] if op[0] != 4]
179 return super(ir_model,self).write(cr, user, ids, vals, context)
181 def create(self, cr, user, vals, context=None):
184 if context and context.get('manual',False):
185 vals['state']='manual'
186 res = super(ir_model,self).create(cr, user, vals, context)
187 if vals.get('state','base')=='manual':
188 self.instanciate(cr, user, vals['model'], context)
189 self.pool.get(vals['model']).__init__(self.pool, cr)
191 ctx.update({'field_name':vals['name'],'field_state':'manual','select':vals.get('select_level','0')})
192 self.pool.get(vals['model'])._auto_init(cr, ctx)
193 #pooler.restart_pool(cr.dbname)
196 def instanciate(self, cr, user, model, context=None):
197 class x_custom_model(osv.osv):
199 x_custom_model._name = model
200 x_custom_model._module = False
201 a = x_custom_model.create_instance(self.pool, cr)
202 if (not a._columns) or ('x_name' in a._columns.keys()):
205 x_name = a._columns.keys()[0]
206 x_custom_model._rec_name = x_name
209 class ir_model_fields(osv.osv):
210 _name = 'ir.model.fields'
211 _description = "Fields"
214 'name': fields.char('Name', required=True, size=64, select=1),
215 'model': fields.char('Object Name', size=64, required=True, select=1,
216 help="The technical name of the model this field belongs to"),
217 'relation': fields.char('Object Relation', size=64,
218 help="For relationship fields, the technical name of the target model"),
219 'relation_field': fields.char('Relation Field', size=64,
220 help="For one2many fields, the field on the target model that implement the opposite many2one relationship"),
221 'model_id': fields.many2one('ir.model', 'Model', required=True, select=True, ondelete='cascade',
222 help="The model this field belongs to"),
223 'field_description': fields.char('Field Label', required=True, size=256),
224 'ttype': fields.selection(_get_fields_type, 'Field Type',size=64, required=True),
225 'selection': fields.char('Selection Options',size=128, help="List of options for a selection field, "
226 "specified as a Python expression defining a list of (key, label) pairs. "
227 "For example: [('blue','Blue'),('yellow','Yellow')]"),
228 'required': fields.boolean('Required'),
229 'readonly': fields.boolean('Readonly'),
230 'select_level': fields.selection([('0','Not Searchable'),('1','Always Searchable'),('2','Advanced Search (deprecated)')],'Searchable', required=True),
231 'translate': fields.boolean('Translate', help="Whether values for this field can be translated (enables the translation mechanism for that field)"),
232 'size': fields.integer('Size'),
233 'state': fields.selection([('manual','Custom Field'),('base','Base Field')],'Type', required=True, readonly=True, select=1),
234 'on_delete': fields.selection([('cascade','Cascade'),('set null','Set NULL')], 'On delete', help='On delete property for many2one fields'),
235 'domain': fields.char('Domain', size=256, help="The optional domain to restrict possible values for relationship fields, "
236 "specified as a Python expression defining a list of triplets. "
237 "For example: [('color','=','red')]"),
238 'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'),
239 'view_load': fields.boolean('View Auto-Load'),
240 'selectable': fields.boolean('Selectable'),
241 'modules': fields.function(_in_modules, type='char', size=128, string='In modules', help='List of modules in which the field is defined'),
242 'serialization_field_id': fields.many2one('ir.model.fields', 'Serialization Field', domain = "[('ttype','=','serialized')]",
243 ondelete='cascade', help="If set, this field will be stored in the sparse "
244 "structure of the serialization field, instead "
245 "of having its own database column. This cannot be "
246 "changed after creation."),
248 _rec_name='field_description'
254 'state': lambda self,cr,uid,ctx={}: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
255 'on_delete': 'set null',
258 'field_description': '',
263 def _check_selection(self, cr, uid, selection, context=None):
265 selection_list = eval(selection)
267 _logger.warning('Invalid selection list definition for fields.selection', exc_info=True)
268 raise except_orm(_('Error'),
269 _("The Selection Options expression is not a valid Pythonic expression." \
270 "Please provide an expression in the [('key','Label'), ...] format."))
273 if not (isinstance(selection_list, list) and selection_list):
276 for item in selection_list:
277 if not (isinstance(item, (tuple,list)) and len(item) == 2):
282 raise except_orm(_('Error'),
283 _("The Selection Options expression is must be in the [('key','Label'), ...] format!"))
286 def _size_gt_zero_msg(self, cr, user, ids, context=None):
287 return _('Size of the field can never be less than 1 !')
290 ('size_gt_zero', 'CHECK (size>0)',_size_gt_zero_msg ),
293 def _drop_column(self, cr, uid, ids, context=None):
294 for field in self.browse(cr, uid, ids, context):
295 model = self.pool.get(field.model)
296 cr.execute('select relkind from pg_class where relname=%s', (model._table,))
297 result = cr.fetchone()
298 cr.execute("SELECT column_name FROM information_schema.columns WHERE table_name ='%s' and column_name='%s'" %(model._table, field.name))
299 column_name = cr.fetchone()
300 if column_name and (result and result[0] == 'r'):
301 cr.execute('ALTER table "%s" DROP column "%s" cascade' % (model._table, field.name))
302 model._columns.pop(field.name, None)
305 def unlink(self, cr, user, ids, context=None):
306 # Prevent manual deletion of module columns
307 if context is None: context = {}
308 if isinstance(ids, (int, long)):
310 if not context.get(MODULE_UNINSTALL_FLAG) and \
311 any(field.state != 'manual' for field in self.browse(cr, user, ids, context)):
312 raise except_orm(_('Error'), _("This column contains module data and cannot be removed!"))
314 self._drop_column(cr, user, ids, context)
315 res = super(ir_model_fields, self).unlink(cr, user, ids, context)
318 def create(self, cr, user, vals, context=None):
319 if 'model_id' in vals:
320 model_data = self.pool.get('ir.model').browse(cr, user, vals['model_id'])
321 vals['model'] = model_data.model
324 if context and context.get('manual',False):
325 vals['state'] = 'manual'
326 if vals.get('ttype', False) == 'selection':
327 if not vals.get('selection',False):
328 raise except_orm(_('Error'), _('For selection fields, the Selection Options must be given!'))
329 self._check_selection(cr, user, vals['selection'], context=context)
330 res = super(ir_model_fields,self).create(cr, user, vals, context)
331 if vals.get('state','base') == 'manual':
332 if not vals['name'].startswith('x_'):
333 raise except_orm(_('Error'), _("Custom fields must have a name that starts with 'x_' !"))
335 if vals.get('relation',False) and not self.pool.get('ir.model').search(cr, user, [('model','=',vals['relation'])]):
336 raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
338 if self.pool.get(vals['model']):
339 self.pool.get(vals['model']).__init__(self.pool, cr)
340 #Added context to _auto_init for special treatment to custom field for select_level
342 ctx.update({'field_name':vals['name'],'field_state':'manual','select':vals.get('select_level','0'),'update_custom_fields':True})
343 self.pool.get(vals['model'])._auto_init(cr, ctx)
347 def write(self, cr, user, ids, vals, context=None):
350 if context and context.get('manual',False):
351 vals['state'] = 'manual'
353 #For the moment renaming a sparse field or changing the storing system is not allowed. This may be done later
354 if 'serialization_field_id' in vals or 'name' in vals:
355 for field in self.browse(cr, user, ids, context=context):
356 if 'serialization_field_id' in vals and field.serialization_field_id.id != vals['serialization_field_id']:
357 raise except_orm(_('Error!'), _('Changing the storing system for field "%s" is not allowed.')%field.name)
358 if field.serialization_field_id and (field.name != vals['name']):
359 raise except_orm(_('Error!'), _('Renaming sparse field "%s" is not allowed')%field.name)
361 column_rename = None # if set, *one* column can be renamed here
363 models_patch = {} # structs of (obj, [(field, prop, change_to),..])
364 # data to be updated on the orm model
366 # static table of properties
367 model_props = [ # (our-name, fields.prop, set_fn)
368 ('field_description', 'string', str),
369 ('required', 'required', bool),
370 ('readonly', 'readonly', bool),
371 ('domain', '_domain', eval),
372 ('size', 'size', int),
373 ('on_delete', 'ondelete', str),
374 ('translate', 'translate', bool),
375 ('view_load', 'view_load', bool),
376 ('selectable', 'selectable', bool),
377 ('select_level', 'select', int),
378 ('selection', 'selection', eval),
382 checked_selection = False # need only check it once, so defer
384 for item in self.browse(cr, user, ids, context=context):
385 if not (obj and obj._name == item.model):
386 obj = self.pool.get(item.model)
388 if item.state != 'manual':
389 raise except_orm(_('Error!'),
390 _('Properties of base fields cannot be altered in this manner! '
391 'Please modify them through Python code, '
392 'preferably through a custom addon!'))
394 if item.ttype == 'selection' and 'selection' in vals \
395 and not checked_selection:
396 self._check_selection(cr, user, vals['selection'], context=context)
397 checked_selection = True
399 final_name = item.name
400 if 'name' in vals and vals['name'] != item.name:
401 # We need to rename the column
403 raise except_orm(_('Error!'), _('Can only rename one column at a time!'))
404 if vals['name'] in obj._columns:
405 raise except_orm(_('Error!'), _('Cannot rename column to %s, because that column already exists!') % vals['name'])
406 if vals.get('state', 'base') == 'manual' and not vals['name'].startswith('x_'):
407 raise except_orm(_('Error!'), _('New column name must still start with x_ , because it is a custom field!'))
408 if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']:
409 raise ValueError('Invalid character in column name')
410 column_rename = (obj, (obj._table, item.name, vals['name']))
411 final_name = vals['name']
413 if 'model_id' in vals and vals['model_id'] != item.model_id:
414 raise except_orm(_("Error!"), _("Changing the model of a field is forbidden!"))
416 if 'ttype' in vals and vals['ttype'] != item.ttype:
417 raise except_orm(_("Error!"), _("Changing the type of a column is not yet supported. "
418 "Please drop it and create it again!"))
420 # We don't check the 'state', because it might come from the context
421 # (thus be set for multiple fields) and will be ignored anyway.
423 models_patch.setdefault(obj._name, (obj,[]))
424 # find out which properties (per model) we need to update
425 for field_name, field_property, set_fn in model_props:
426 if field_name in vals:
427 property_value = set_fn(vals[field_name])
428 if getattr(obj._columns[item.name], field_property) != property_value:
429 models_patch[obj._name][1].append((final_name, field_property, property_value))
430 # our dict is ready here, but no properties are changed so far
432 # These shall never be written (modified)
433 for column_name in ('model_id', 'model', 'state'):
434 if column_name in vals:
435 del vals[column_name]
437 res = super(ir_model_fields,self).write(cr, user, ids, vals, context=context)
440 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % column_rename[1])
441 # This is VERY risky, but let us have this feature:
442 # we want to change the key of column in obj._columns dict
443 col = column_rename[0]._columns.pop(column_rename[1][1]) # take object out, w/o copy
444 column_rename[0]._columns[column_rename[1][2]] = col
447 # We have to update _columns of the model(s) and then call their
448 # _auto_init to sync the db with the model. Hopefully, since write()
449 # was called earlier, they will be in-sync before the _auto_init.
450 # Anything we don't update in _columns now will be reset from
451 # the model into ir.model.fields (db).
453 ctx.update({'select': vals.get('select_level','0'),'update_custom_fields':True})
455 for __, patch_struct in models_patch.items():
456 obj = patch_struct[0]
457 for col_name, col_prop, val in patch_struct[1]:
458 setattr(obj._columns[col_name], col_prop, val)
459 obj._auto_init(cr, ctx)
464 class ir_model_access(osv.osv):
465 _name = 'ir.model.access'
467 'name': fields.char('Name', size=64, required=True, select=True),
468 'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)], select=True, ondelete='cascade'),
469 'group_id': fields.many2one('res.groups', 'Group', ondelete='cascade', select=True),
470 'perm_read': fields.boolean('Read Access'),
471 'perm_write': fields.boolean('Write Access'),
472 'perm_create': fields.boolean('Create Access'),
473 'perm_unlink': fields.boolean('Delete Access'),
476 def check_groups(self, cr, uid, group):
477 grouparr = group.split('.')
480 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],))
481 return bool(cr.fetchone())
483 def check_group(self, cr, uid, model, mode, group_ids):
484 """ Check if a specific group has the access mode to the specified model"""
485 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
487 if isinstance(model, browse_record):
488 assert model._table_name == 'ir.model', 'Invalid model object'
489 model_name = model.name
493 if isinstance(group_ids, (int, long)):
494 group_ids = [group_ids]
495 for group_id in group_ids:
496 cr.execute("SELECT perm_" + mode + " "
497 " FROM ir_model_access a "
498 " JOIN ir_model m ON (m.id = a.model_id) "
499 " WHERE m.model = %s AND a.group_id = %s", (model_name, group_id)
503 cr.execute("SELECT perm_" + mode + " "
504 " FROM ir_model_access a "
505 " JOIN ir_model m ON (m.id = a.model_id) "
506 " WHERE m.model = %s AND a.group_id IS NULL", (model_name, )
510 access = bool(r and r[0])
513 # pass no groups -> no access
516 def group_names_with_access(self, cr, model_name, access_mode):
517 """Returns the names of visible groups which have been granted ``access_mode`` on
518 the model ``model_name``.
521 assert access_mode in ['read','write','create','unlink'], 'Invalid access mode: %s' % access_mode
526 JOIN ir_model m ON (a.model_id=m.id)
527 JOIN res_groups g ON (a.group_id=g.id)
530 a.perm_''' + access_mode, (model_name,))
531 return [x[0] for x in cr.fetchall()]
534 def check(self, cr, uid, model, mode='read', raise_exception=True, context=None):
536 # User root have all accesses
537 # TODO: exclude xml-rpc requests
540 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
542 if isinstance(model, browse_record):
543 assert model._table_name == 'ir.model', 'Invalid model object'
544 model_name = model.model
548 # TransientModel records have no access rights, only an implicit access rule
549 if self.pool.get(model_name).is_transient():
552 # We check if a specific rule exists
553 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
554 ' FROM ir_model_access a '
555 ' JOIN ir_model m ON (m.id = a.model_id) '
556 ' JOIN res_groups_users_rel gu ON (gu.gid = a.group_id) '
557 ' WHERE m.model = %s '
564 # there is no specific rule. We check the generic rule
565 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
566 ' FROM ir_model_access a '
567 ' JOIN ir_model m ON (m.id = a.model_id) '
568 ' WHERE a.group_id IS NULL '
574 if not r and raise_exception:
575 groups = ', '.join(self.group_names_with_access(cr, model_name, mode)) or '/'
577 'read': _("You can not read this document (%s) ! Be sure your user belongs to one of these groups: %s."),
578 'write': _("You can not write in this document (%s) ! Be sure your user belongs to one of these groups: %s."),
579 'create': _("You can not create this document (%s) ! Be sure your user belongs to one of these groups: %s."),
580 'unlink': _("You can not delete this document (%s) ! Be sure your user belongs to one of these groups: %s."),
583 raise except_orm(_('AccessError'), msgs[mode] % (model_name, groups) )
586 __cache_clearing_methods = []
588 def register_cache_clearing_method(self, model, method):
589 self.__cache_clearing_methods.append((model, method))
591 def unregister_cache_clearing_method(self, model, method):
593 i = self.__cache_clearing_methods.index((model, method))
594 del self.__cache_clearing_methods[i]
598 def call_cache_clearing_methods(self, cr):
599 self.check.clear_cache(self) # clear the cache of check function
600 for model, method in self.__cache_clearing_methods:
601 object_ = self.pool.get(model)
603 getattr(object_, method)()
606 # Check rights on actions
608 def write(self, cr, uid, *args, **argv):
609 self.call_cache_clearing_methods(cr)
610 res = super(ir_model_access, self).write(cr, uid, *args, **argv)
613 def create(self, cr, uid, *args, **argv):
614 self.call_cache_clearing_methods(cr)
615 res = super(ir_model_access, self).create(cr, uid, *args, **argv)
618 def unlink(self, cr, uid, *args, **argv):
619 self.call_cache_clearing_methods(cr)
620 res = super(ir_model_access, self).unlink(cr, uid, *args, **argv)
625 class ir_model_data(osv.osv):
626 """Holds external identifier keys for records in the database.
627 This has two main uses:
629 * allows easy data integration with third-party systems,
630 making import/export/sync of data possible, as records
631 can be uniquely identified across multiple systems
632 * allows tracking the origin of data installed by OpenERP
633 modules themselves, thus making it possible to later
634 update them seamlessly.
636 _name = 'ir.model.data'
637 _order = 'module,model,name'
639 'name': fields.char('External Identifier', required=True, size=128, select=1,
640 help="External Key/Identifier that can be used for "
641 "data integration with third-party systems"),
642 'model': fields.char('Model Name', required=True, size=64, select=1),
643 'module': fields.char('Module', required=True, size=64, select=1),
644 'res_id': fields.integer('Record ID', select=1,
645 help="ID of the target record in the database"),
646 'noupdate': fields.boolean('Non Updatable'),
647 'date_update': fields.datetime('Update Date'),
648 'date_init': fields.datetime('Init Date')
651 'date_init': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
652 'date_update': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
657 ('module_name_uniq', 'unique(name, module)', 'You cannot have multiple records with the same external ID in the same module!'),
660 def __init__(self, pool, cr):
661 osv.osv.__init__(self, pool, cr)
663 # also stored in pool to avoid being discarded along with this osv instance
664 if getattr(pool, 'model_data_reference_ids', None) is None:
665 self.pool.model_data_reference_ids = {}
667 self.loads = self.pool.model_data_reference_ids
669 def _auto_init(self, cr, context=None):
670 super(ir_model_data, self)._auto_init(cr, context)
671 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_model_data_module_name_index\'')
672 if not cr.fetchone():
673 cr.execute('CREATE INDEX ir_model_data_module_name_index ON ir_model_data (module, name)')
676 def _get_id(self, cr, uid, module, xml_id):
677 """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"""
678 ids = self.search(cr, uid, [('module','=',module), ('name','=', xml_id)])
680 raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
681 # the sql constraints ensure us we have only one result
685 def get_object_reference(self, cr, uid, module, xml_id):
686 """Returns (model, res_id) corresponding to a given module and xml_id (cached) or raise ValueError if not found"""
687 data_id = self._get_id(cr, uid, module, xml_id)
688 res = self.read(cr, uid, data_id, ['model', 'res_id'])
689 if not res['res_id']:
690 raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
691 return (res['model'], res['res_id'])
693 def get_object(self, cr, uid, module, xml_id, context=None):
694 """Returns a browsable record for the given module name and xml_id or raise ValueError if not found"""
695 res_model, res_id = self.get_object_reference(cr, uid, module, xml_id)
696 result = self.pool.get(res_model).browse(cr, uid, res_id, context=context)
697 if not result.exists():
698 raise ValueError('No record found for unique ID %s.%s. It may have been deleted.' % (module, xml_id))
701 def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
705 id = self.read(cr, uid, [self._get_id(cr, uid, module, xml_id)], ['res_id'])[0]['res_id']
706 self.loads[(module,xml_id)] = (model,id)
712 def unlink(self, cr, uid, ids, context=None):
713 """ Regular unlink method, but make sure to clear the caches. """
714 self._get_id.clear_cache(self)
715 self.get_object_reference.clear_cache(self)
716 return super(ir_model_data,self).unlink(cr, uid, ids, context=context)
718 def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None):
719 model_obj = self.pool.get(model)
722 # records created during module install should result in res.log entries that are already read!
723 context = dict(context, res_log_read=True)
724 if xml_id and ('.' in xml_id):
725 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)
726 module, xml_id = xml_id.split('.')
727 if (not xml_id) and (not self.doinit):
731 cr.execute('''SELECT imd.id, imd.res_id, md.id, imd.model
732 FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
733 WHERE imd.module=%%s AND imd.name=%%s''' % model_obj._table,
735 results = cr.fetchall()
736 for imd_id2,res_id2,real_id2,real_model in results:
738 self._get_id.clear_cache(self, uid, module, xml_id)
739 self.get_object_reference.clear_cache(self, uid, module, xml_id)
740 cr.execute('delete from ir_model_data where id=%s', (imd_id2,))
743 assert model == real_model, "External ID conflict, %s already refers to a `%s` record,"\
744 " you can't define a `%s` record with this ID." % (xml_id, real_model, model)
745 res_id,action_id = res_id2,imd_id2
747 if action_id and res_id:
748 model_obj.write(cr, uid, [res_id], values, context=context)
749 self.write(cr, uid, [action_id], {
750 'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
753 model_obj.write(cr, uid, [res_id], values, context=context)
755 self.create(cr, uid, {
760 'noupdate': noupdate,
762 if model_obj._inherits:
763 for table in model_obj._inherits:
764 inherit_id = model_obj.browse(cr, uid,
765 res_id,context=context)[model_obj._inherits[table]]
766 self.create(cr, uid, {
767 'name': xml_id + '_' + table.replace('.', '_'),
770 'res_id': inherit_id.id,
771 'noupdate': noupdate,
774 if mode=='init' or (mode=='update' and xml_id):
775 res_id = model_obj.create(cr, uid, values, context=context)
777 self.create(cr, uid, {
784 if model_obj._inherits:
785 for table in model_obj._inherits:
786 inherit_id = model_obj.browse(cr, uid,
787 res_id,context=context)[model_obj._inherits[table]]
788 self.create(cr, uid, {
789 'name': xml_id + '_' + table.replace('.', '_'),
792 'res_id': inherit_id.id,
793 'noupdate': noupdate,
797 self.loads[(module, xml_id)] = (model, res_id)
798 if model_obj._inherits:
799 for table in model_obj._inherits:
800 inherit_field = model_obj._inherits[table]
801 inherit_id = model_obj.read(cr, uid, res_id,
802 [inherit_field])[inherit_field]
803 self.loads[(module, xml_id + '_' + \
804 table.replace('.', '_'))] = (table, inherit_id)
807 def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None, xml_id=False):
808 if type(models[0])==type([]) or type(models[0])==type(()):
809 model,res_id = models[0]
815 where = ' and res_id=%s' % (res_id,)
817 where = ' and (res_id is null)'
820 where += ' and key2=\'%s\'' % (key2,)
822 where += ' and (key2 is null)'
824 cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
827 ir_values_obj = pooler.get_pool(cr.dbname).get('ir.values')
828 res = ir_values_obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
830 cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
833 def _module_data_uninstall(self, cr, uid, ids, context=None):
834 """Deletes all the records referenced by the ir.model.data entries
835 ``ids`` along with their corresponding database backed (including
836 dropping tables, columns, FKs, etc, as long as there is no other
837 ir.model.data entry holding a reference to them (which indicates that
838 they are still owned by another module).
839 Attempts to perform the deletion in an appropriate order to maximize
840 the chance of gracefully deleting all records.
841 This step is performed as part of the full uninstallation of a module.
844 if uid != 1 and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"):
845 raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
847 context = dict(context or {})
848 context[MODULE_UNINSTALL_FLAG] = True # enable model/field deletion
856 for data in self.browse(cr, uid, ids, context):
859 model_obj = self.pool.get(model)
860 name = tools.ustr(data.name)
862 if name.startswith(EXT_ID_PREFIX_FK) or name.startswith(EXT_ID_PREFIX_M2M_TABLE)\
863 or name.startswith(EXT_ID_PREFIX_CONSTRAINT):
864 # double-check we are really going to delete all the owners of this schema element
865 cr.execute("""SELECT id from ir_model_data where name = %s and res_id IS NULL""", (data.name,))
866 external_ids = [x[0] for x in cr.fetchall()]
867 if (set(external_ids)-ids_set):
868 # as installed modules have defined this element we must not delete it!
871 if name.startswith(EXT_ID_PREFIX_FK):
872 name = name[len(EXT_ID_PREFIX_FK):]
873 # test if FK exists on this table (it could be on a related m2m table, in which case we ignore it)
874 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
875 WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_obj._table))
877 cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
878 _logger.info('Dropped FK CONSTRAINT %s@%s', name, model)
881 if name.startswith(EXT_ID_PREFIX_M2M_TABLE):
882 name = name[len(EXT_ID_PREFIX_M2M_TABLE):]
883 cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name=%s", (name,))
884 if cr.fetchone() and not name in to_drop_table:
885 to_drop_table.append(name)
888 if name.startswith(EXT_ID_PREFIX_CONSTRAINT):
889 name = name[len(EXT_ID_PREFIX_CONSTRAINT):]
890 # test if constraint exists
891 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
892 WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_obj._table))
894 cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
895 _logger.info('Dropped CONSTRAINT %s@%s', name, model)
898 pair_to_unlink = (model, res_id)
899 if pair_to_unlink not in to_unlink:
900 to_unlink.append(pair_to_unlink)
902 if model == 'workflow.activity':
903 # Special treatment for workflow activities: temporarily revert their
904 # incoming transition and trigger an update to force all workflow items
905 # to move out before deleting them
906 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,))
907 wkf_todo.extend(cr.fetchall())
908 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))
910 wf_service = netsvc.LocalService("workflow")
911 for model,res_id in wkf_todo:
913 wf_service.trg_write(uid, model, res_id, cr)
915 _logger.info('Unable to force processing of workflow for item %s@%s in order to leave activity to be deleted', res_id, model)
917 # drop m2m relation tables
918 for table in to_drop_table:
919 cr.execute('DROP TABLE %s CASCADE'% (table),)
920 _logger.info('Dropped table %s', table)
922 for (model, res_id) in to_unlink:
923 if model in ('ir.model','ir.model.fields', 'ir.model.data'):
925 external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
926 if (set(external_ids)-ids_set):
927 # if other modules have defined this record, we do not delete it
929 _logger.info('Deleting %s@%s', res_id, model)
931 self.pool.get(model).unlink(cr, uid, [res_id], context=context)
933 _logger.info('Unable to delete %s@%s', res_id, model, exc_info=True)
936 for (model, res_id) in to_unlink:
937 if model != 'ir.model.fields':
939 external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
940 if (set(external_ids)-ids_set):
941 # if other modules have defined this record, we do not delete it
943 _logger.info('Deleting %s@%s', res_id, model)
944 self.pool.get(model).unlink(cr, uid, [res_id], context=context)
946 for (model, res_id) in to_unlink:
947 if model != 'ir.model':
949 external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
950 if (set(external_ids)-ids_set):
951 # if other modules have defined this record, we do not delete it
953 _logger.info('Deleting %s@%s', res_id, model)
954 self.pool.get(model).unlink(cr, uid, [res_id], context=context)
957 def _process_end(self, cr, uid, modules):
958 """ Clear records removed from updated module data.
959 This method is called at the end of the module loading process.
960 It is meant to removed records that are no longer present in the
961 updated data. Such records are recognised as the one with an xml id
962 and a module in ir_model_data and noupdate set to false, but not
963 present in self.loads.
968 cr.execute("""SELECT id,name,model,res_id,module FROM ir_model_data
969 WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s""",
970 (tuple(modules), False))
971 for (id, name, model, res_id, module) in cr.fetchall():
972 if (module,name) not in self.loads:
973 to_unlink.append((model,res_id))
974 if not config.get('import_partial'):
975 for (model, res_id) in to_unlink:
976 if self.pool.get(model):
977 _logger.info('Deleting %s@%s', res_id, model)
978 self.pool.get(model).unlink(cr, uid, [res_id])
981 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: