1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
26 from osv import fields,osv
28 from osv.orm import except_orm, browse_record
30 from tools.safe_eval import safe_eval as eval
31 from tools import config
32 from tools.translate import _
35 _logger = logging.getLogger(__name__)
37 def _get_fields_type(self, cr, uid, context=None):
38 return sorted([(k,k) for k,v in fields.__dict__.iteritems()
39 if type(v) == types.TypeType
40 if issubclass(v, fields._column)
41 if v != fields._column
43 if not issubclass(v, fields.function)])
45 def _in_modules(self, cr, uid, ids, field_name, arg, context=None):
46 #pseudo-method used by fields.function in ir.model/ir.model.fields
47 module_pool = self.pool.get("ir.module.module")
48 installed_module_ids = module_pool.search(cr, uid, [('state','=','installed')])
49 installed_module_names = module_pool.read(cr, uid, installed_module_ids, ['name'], context=context)
50 installed_modules = set(x['name'] for x in installed_module_names)
53 xml_ids = osv.osv._get_xml_ids(self, cr, uid, ids)
54 for k,v in xml_ids.iteritems():
55 result[k] = ', '.join(sorted(installed_modules & set(xml_id.split('.')[0] for xml_id in v)))
59 class ir_model(osv.osv):
61 _description = "Models"
64 def _is_osv_memory(self, cr, uid, ids, field_name, arg, context=None):
65 models = self.browse(cr, uid, ids, context=context)
66 res = dict.fromkeys(ids)
68 res[model.id] = self.pool.get(model.model).is_transient()
71 def _search_osv_memory(self, cr, uid, model, name, domain, context=None):
74 field, operator, value = domain[0]
75 if operator not in ['=', '!=']:
76 raise osv.except_osv(_('Invalid search criterions'), _('The osv_memory field can only be compared with = and != operator.'))
77 value = bool(value) if operator == '=' else not bool(value)
78 all_model_ids = self.search(cr, uid, [], context=context)
79 is_osv_mem = self._is_osv_memory(cr, uid, all_model_ids, 'osv_memory', arg=None, context=context)
80 return [('id', 'in', [id for id in is_osv_mem if bool(is_osv_mem[id]) == value])]
82 def _view_ids(self, cr, uid, ids, field_name, arg, context=None):
83 models = self.browse(cr, uid, ids)
86 res[model.id] = self.pool.get("ir.ui.view").search(cr, uid, [('model', '=', model.model)])
90 'name': fields.char('Model Description', size=64, translate=True, required=True),
91 'model': fields.char('Model', size=64, required=True, select=1),
92 'info': fields.text('Information'),
93 'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields', required=True),
94 'state': fields.selection([('manual','Custom Object'),('base','Base Object')],'Type',readonly=True),
95 'access_ids': fields.one2many('ir.model.access', 'model_id', 'Access'),
96 'osv_memory': fields.function(_is_osv_memory, string='In-memory model', type='boolean',
97 fnct_search=_search_osv_memory,
98 help="Indicates whether this object model lives in memory only, i.e. is not persisted (osv.osv_memory)"),
99 'modules': fields.function(_in_modules, type='char', size=128, string='In modules', help='List of modules in which the object is defined or inherited'),
100 'view_ids': fields.function(_view_ids, type='one2many', obj='ir.ui.view', string='Views'),
104 'model': lambda *a: 'x_',
105 'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
108 def _check_model_name(self, cr, uid, ids, context=None):
109 for model in self.browse(cr, uid, ids, context=context):
110 if model.state=='manual':
111 if not model.model.startswith('x_'):
113 if not re.match('^[a-z_A-Z0-9.]+$',model.model):
117 def _model_name_msg(self, cr, uid, ids, context=None):
118 return _('The Object name must start with x_ and not contain any special character !')
121 (_check_model_name, _model_name_msg, ['model']),
124 ('obj_name_uniq', 'unique (model)', 'Each model must be unique!'),
127 # overridden to allow searching both on model name (model field)
128 # and model description (name field)
129 def _name_search(self, cr, uid, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
132 domain = args + ['|', ('model', operator, name), ('name', operator, name)]
133 return self.name_get(cr, name_get_uid or uid,
134 super(ir_model, self).search(cr, uid, domain, limit=limit, context=context),
138 def unlink(self, cr, user, ids, context=None):
139 for model in self.browse(cr, user, ids, context):
140 if model.state != 'manual':
141 raise except_orm(_('Error'), _("You can not remove the model '%s' !") %(model.name,))
142 res = super(ir_model, self).unlink(cr, user, ids, context)
143 pooler.restart_pool(cr.dbname)
146 def write(self, cr, user, ids, vals, context=None):
148 context.pop('__last_update', None)
149 # Filter out operations 4 link from field id, because openerp-web
150 # always write (4,id,False) even for non dirty items
151 if 'field_id' in vals:
152 vals['field_id'] = [op for op in vals['field_id'] if op[0] != 4]
153 return super(ir_model,self).write(cr, user, ids, vals, context)
155 def create(self, cr, user, vals, context=None):
158 if context and context.get('manual',False):
159 vals['state']='manual'
160 res = super(ir_model,self).create(cr, user, vals, context)
161 if vals.get('state','base')=='manual':
162 self.instanciate(cr, user, vals['model'], context)
163 self.pool.get(vals['model']).__init__(self.pool, cr)
165 ctx.update({'field_name':vals['name'],'field_state':'manual','select':vals.get('select_level','0')})
166 self.pool.get(vals['model'])._auto_init(cr, ctx)
167 #pooler.restart_pool(cr.dbname)
170 def instanciate(self, cr, user, model, context=None):
171 class x_custom_model(osv.osv):
173 x_custom_model._name = model
174 x_custom_model._module = False
175 a = x_custom_model.create_instance(self.pool, cr)
176 if (not a._columns) or ('x_name' in a._columns.keys()):
179 x_name = a._columns.keys()[0]
180 x_custom_model._rec_name = x_name
183 class ir_model_fields(osv.osv):
184 _name = 'ir.model.fields'
185 _description = "Fields"
188 'name': fields.char('Name', required=True, size=64, select=1),
189 'model': fields.char('Object Name', size=64, required=True, select=1,
190 help="The technical name of the model this field belongs to"),
191 'relation': fields.char('Object Relation', size=64,
192 help="For relationship fields, the technical name of the target model"),
193 'relation_field': fields.char('Relation Field', size=64,
194 help="For one2many fields, the field on the target model that implement the opposite many2one relationship"),
195 'model_id': fields.many2one('ir.model', 'Model', required=True, select=True, ondelete='cascade',
196 help="The model this field belongs to"),
197 'field_description': fields.char('Field Label', required=True, size=256),
198 'ttype': fields.selection(_get_fields_type, 'Field Type',size=64, required=True),
199 'selection': fields.char('Selection Options',size=128, help="List of options for a selection field, "
200 "specified as a Python expression defining a list of (key, label) pairs. "
201 "For example: [('blue','Blue'),('yellow','Yellow')]"),
202 'required': fields.boolean('Required'),
203 'readonly': fields.boolean('Readonly'),
204 'select_level': fields.selection([('0','Not Searchable'),('1','Always Searchable'),('2','Advanced Search (deprecated)')],'Searchable', required=True),
205 'translate': fields.boolean('Translate', help="Whether values for this field can be translated (enables the translation mechanism for that field)"),
206 'size': fields.integer('Size'),
207 'state': fields.selection([('manual','Custom Field'),('base','Base Field')],'Type', required=True, readonly=True, select=1),
208 'on_delete': fields.selection([('cascade','Cascade'),('set null','Set NULL')], 'On delete', help='On delete property for many2one fields'),
209 'domain': fields.char('Domain', size=256, help="The optional domain to restrict possible values for relationship fields, "
210 "specified as a Python expression defining a list of triplets. "
211 "For example: [('color','=','red')]"),
212 'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'),
213 'view_load': fields.boolean('View Auto-Load'),
214 'selectable': fields.boolean('Selectable'),
215 'modules': fields.function(_in_modules, type='char', size=128, string='In modules', help='List of modules in which the field is defined'),
216 'serialization_field_id': fields.many2one('ir.model.fields', 'Serialization Field', domain = "[('ttype','=','serialized')]",
217 ondelete='cascade', help="If set, this field will be stored in the sparse "
218 "structure of the serialization field, instead "
219 "of having its own database column. This cannot be "
220 "changed after creation."),
222 _rec_name='field_description'
228 'state': lambda self,cr,uid,ctx={}: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
229 'on_delete': 'set null',
232 'field_description': '',
237 def _check_selection(self, cr, uid, selection, context=None):
239 selection_list = eval(selection)
241 _logger.warning('Invalid selection list definition for fields.selection', exc_info=True)
242 raise except_orm(_('Error'),
243 _("The Selection Options expression is not a valid Pythonic expression." \
244 "Please provide an expression in the [('key','Label'), ...] format."))
247 if not (isinstance(selection_list, list) and selection_list):
250 for item in selection_list:
251 if not (isinstance(item, (tuple,list)) and len(item) == 2):
256 raise except_orm(_('Error'),
257 _("The Selection Options expression is must be in the [('key','Label'), ...] format!"))
260 def _size_gt_zero_msg(self, cr, user, ids, context=None):
261 return _('Size of the field can never be less than 1 !')
264 ('size_gt_zero', 'CHECK (size>0)',_size_gt_zero_msg ),
267 def unlink(self, cr, user, ids, context=None):
268 for field in self.browse(cr, user, ids, context):
269 if field.state <> 'manual':
270 raise except_orm(_('Error'), _("You cannot remove the field '%s' !") %(field.name,))
272 # MAY BE ADD A ALTER TABLE DROP ?
274 #Removing _columns entry for that table
275 self.pool.get(field.model)._columns.pop(field.name,None)
276 return super(ir_model_fields, self).unlink(cr, user, ids, context)
278 def create(self, cr, user, vals, context=None):
279 if 'model_id' in vals:
280 model_data = self.pool.get('ir.model').browse(cr, user, vals['model_id'])
281 vals['model'] = model_data.model
284 if context and context.get('manual',False):
285 vals['state'] = 'manual'
286 if vals.get('ttype', False) == 'selection':
287 if not vals.get('selection',False):
288 raise except_orm(_('Error'), _('For selection fields, the Selection Options must be given!'))
289 self._check_selection(cr, user, vals['selection'], context=context)
290 res = super(ir_model_fields,self).create(cr, user, vals, context)
291 if vals.get('state','base') == 'manual':
292 if not vals['name'].startswith('x_'):
293 raise except_orm(_('Error'), _("Custom fields must have a name that starts with 'x_' !"))
295 if vals.get('relation',False) and not self.pool.get('ir.model').search(cr, user, [('model','=',vals['relation'])]):
296 raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
298 if self.pool.get(vals['model']):
299 self.pool.get(vals['model']).__init__(self.pool, cr)
300 #Added context to _auto_init for special treatment to custom field for select_level
302 ctx.update({'field_name':vals['name'],'field_state':'manual','select':vals.get('select_level','0'),'update_custom_fields':True})
303 self.pool.get(vals['model'])._auto_init(cr, ctx)
307 def write(self, cr, user, ids, vals, context=None):
310 if context and context.get('manual',False):
311 vals['state'] = 'manual'
313 #For the moment renaming a sparse field or changing the storing system is not allowed. This may be done later
314 if 'serialization_field_id' in vals or 'name' in vals:
315 for field in self.browse(cr, user, ids, context=context):
316 if 'serialization_field_id' in vals and field.serialization_field_id.id != vals['serialization_field_id']:
317 raise except_orm(_('Error!'), _('Changing the storing system for field "%s" is not allowed.')%field.name)
318 if field.serialization_field_id and (field.name != vals['name']):
319 raise except_orm(_('Error!'), _('Renaming sparse field "%s" is not allowed')%field.name)
321 column_rename = None # if set, *one* column can be renamed here
323 models_patch = {} # structs of (obj, [(field, prop, change_to),..])
324 # data to be updated on the orm model
326 # static table of properties
327 model_props = [ # (our-name, fields.prop, set_fn)
328 ('field_description', 'string', str),
329 ('required', 'required', bool),
330 ('readonly', 'readonly', bool),
331 ('domain', '_domain', eval),
332 ('size', 'size', int),
333 ('on_delete', 'ondelete', str),
334 ('translate', 'translate', bool),
335 ('view_load', 'view_load', bool),
336 ('selectable', 'selectable', bool),
337 ('select_level', 'select', int),
338 ('selection', 'selection', eval),
342 checked_selection = False # need only check it once, so defer
344 for item in self.browse(cr, user, ids, context=context):
345 if not (obj and obj._name == item.model):
346 obj = self.pool.get(item.model)
348 if item.state != 'manual':
349 raise except_orm(_('Error!'),
350 _('Properties of base fields cannot be altered in this manner! '
351 'Please modify them through Python code, '
352 'preferably through a custom addon!'))
354 if item.ttype == 'selection' and 'selection' in vals \
355 and not checked_selection:
356 self._check_selection(cr, user, vals['selection'], context=context)
357 checked_selection = True
359 final_name = item.name
360 if 'name' in vals and vals['name'] != item.name:
361 # We need to rename the column
363 raise except_orm(_('Error!'), _('Can only rename one column at a time!'))
364 if vals['name'] in obj._columns:
365 raise except_orm(_('Error!'), _('Cannot rename column to %s, because that column already exists!') % vals['name'])
366 if vals.get('state', 'base') == 'manual' and not vals['name'].startswith('x_'):
367 raise except_orm(_('Error!'), _('New column name must still start with x_ , because it is a custom field!'))
368 if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']:
369 raise ValueError('Invalid character in column name')
370 column_rename = (obj, (obj._table, item.name, vals['name']))
371 final_name = vals['name']
373 if 'model_id' in vals and vals['model_id'] != item.model_id:
374 raise except_orm(_("Error!"), _("Changing the model of a field is forbidden!"))
376 if 'ttype' in vals and vals['ttype'] != item.ttype:
377 raise except_orm(_("Error!"), _("Changing the type of a column is not yet supported. "
378 "Please drop it and create it again!"))
380 # We don't check the 'state', because it might come from the context
381 # (thus be set for multiple fields) and will be ignored anyway.
383 models_patch.setdefault(obj._name, (obj,[]))
384 # find out which properties (per model) we need to update
385 for field_name, field_property, set_fn in model_props:
386 if field_name in vals:
387 property_value = set_fn(vals[field_name])
388 if getattr(obj._columns[item.name], field_property) != property_value:
389 models_patch[obj._name][1].append((final_name, field_property, property_value))
390 # our dict is ready here, but no properties are changed so far
392 # These shall never be written (modified)
393 for column_name in ('model_id', 'model', 'state'):
394 if column_name in vals:
395 del vals[column_name]
397 res = super(ir_model_fields,self).write(cr, user, ids, vals, context=context)
400 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % column_rename[1])
401 # This is VERY risky, but let us have this feature:
402 # we want to change the key of column in obj._columns dict
403 col = column_rename[0]._columns.pop(column_rename[1][1]) # take object out, w/o copy
404 column_rename[0]._columns[column_rename[1][2]] = col
407 # We have to update _columns of the model(s) and then call their
408 # _auto_init to sync the db with the model. Hopefully, since write()
409 # was called earlier, they will be in-sync before the _auto_init.
410 # Anything we don't update in _columns now will be reset from
411 # the model into ir.model.fields (db).
413 ctx.update({'select': vals.get('select_level','0'),'update_custom_fields':True})
415 for model_key, patch_struct in models_patch.items():
416 obj = patch_struct[0]
417 for col_name, col_prop, val in patch_struct[1]:
418 setattr(obj._columns[col_name], col_prop, val)
419 obj._auto_init(cr, ctx)
424 class ir_model_access(osv.osv):
425 _name = 'ir.model.access'
427 'name': fields.char('Name', size=64, required=True, select=True),
428 'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)], select=True, ondelete='cascade'),
429 'group_id': fields.many2one('res.groups', 'Group', ondelete='cascade', select=True),
430 'perm_read': fields.boolean('Read Access'),
431 'perm_write': fields.boolean('Write Access'),
432 'perm_create': fields.boolean('Create Access'),
433 'perm_unlink': fields.boolean('Delete Access'),
436 def check_groups(self, cr, uid, group):
437 grouparr = group.split('.')
440 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],))
441 return bool(cr.fetchone())
443 def check_group(self, cr, uid, model, mode, group_ids):
444 """ Check if a specific group has the access mode to the specified model"""
445 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
447 if isinstance(model, browse_record):
448 assert model._table_name == 'ir.model', 'Invalid model object'
449 model_name = model.name
453 if isinstance(group_ids, (int, long)):
454 group_ids = [group_ids]
455 for group_id in group_ids:
456 cr.execute("SELECT perm_" + mode + " "
457 " FROM ir_model_access a "
458 " JOIN ir_model m ON (m.id = a.model_id) "
459 " WHERE m.model = %s AND a.group_id = %s", (model_name, group_id)
463 cr.execute("SELECT perm_" + mode + " "
464 " FROM ir_model_access a "
465 " JOIN ir_model m ON (m.id = a.model_id) "
466 " WHERE m.model = %s AND a.group_id IS NULL", (model_name, )
470 access = bool(r and r[0])
473 # pass no groups -> no access
476 def group_names_with_access(self, cr, model_name, access_mode):
477 """Returns the names of visible groups which have been granted ``access_mode`` on
478 the model ``model_name``.
481 assert access_mode in ['read','write','create','unlink'], 'Invalid access mode: %s' % access_mode
486 JOIN ir_model m ON (a.model_id=m.id)
487 JOIN res_groups g ON (a.group_id=g.id)
490 a.perm_''' + access_mode, (model_name,))
491 return [x[0] for x in cr.fetchall()]
494 def check(self, cr, uid, model, mode='read', raise_exception=True, context=None):
496 # User root have all accesses
497 # TODO: exclude xml-rpc requests
500 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
502 if isinstance(model, browse_record):
503 assert model._table_name == 'ir.model', 'Invalid model object'
504 model_name = model.model
508 # TransientModel records have no access rights, only an implicit access rule
509 if self.pool.get(model_name).is_transient():
512 # We check if a specific rule exists
513 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
514 ' FROM ir_model_access a '
515 ' JOIN ir_model m ON (m.id = a.model_id) '
516 ' JOIN res_groups_users_rel gu ON (gu.gid = a.group_id) '
517 ' WHERE m.model = %s '
524 # there is no specific rule. We check the generic rule
525 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
526 ' FROM ir_model_access a '
527 ' JOIN ir_model m ON (m.id = a.model_id) '
528 ' WHERE a.group_id IS NULL '
534 if not r and raise_exception:
535 groups = ', '.join(self.group_names_with_access(cr, model_name, mode)) or '/'
537 'read': _("You can not read this document (%s) ! Be sure your user belongs to one of these groups: %s."),
538 'write': _("You can not write in this document (%s) ! Be sure your user belongs to one of these groups: %s."),
539 'create': _("You can not create this document (%s) ! Be sure your user belongs to one of these groups: %s."),
540 'unlink': _("You can not delete this document (%s) ! Be sure your user belongs to one of these groups: %s."),
543 raise except_orm(_('AccessError'), msgs[mode] % (model_name, groups) )
546 __cache_clearing_methods = []
548 def register_cache_clearing_method(self, model, method):
549 self.__cache_clearing_methods.append((model, method))
551 def unregister_cache_clearing_method(self, model, method):
553 i = self.__cache_clearing_methods.index((model, method))
554 del self.__cache_clearing_methods[i]
558 def call_cache_clearing_methods(self, cr):
559 self.check.clear_cache(self) # clear the cache of check function
560 for model, method in self.__cache_clearing_methods:
561 object_ = self.pool.get(model)
563 getattr(object_, method)()
566 # Check rights on actions
568 def write(self, cr, uid, *args, **argv):
569 self.call_cache_clearing_methods(cr)
570 res = super(ir_model_access, self).write(cr, uid, *args, **argv)
573 def create(self, cr, uid, *args, **argv):
574 self.call_cache_clearing_methods(cr)
575 res = super(ir_model_access, self).create(cr, uid, *args, **argv)
578 def unlink(self, cr, uid, *args, **argv):
579 self.call_cache_clearing_methods(cr)
580 res = super(ir_model_access, self).unlink(cr, uid, *args, **argv)
585 class ir_model_data(osv.osv):
586 """Holds external identifier keys for records in the database.
587 This has two main uses:
589 * allows easy data integration with third-party systems,
590 making import/export/sync of data possible, as records
591 can be uniquely identified across multiple systems
592 * allows tracking the origin of data installed by OpenERP
593 modules themselves, thus making it possible to later
594 update them seamlessly.
596 _name = 'ir.model.data'
597 _order = 'module,model,name'
599 'name': fields.char('External Identifier', required=True, size=128, select=1,
600 help="External Key/Identifier that can be used for "
601 "data integration with third-party systems"),
602 'model': fields.char('Model Name', required=True, size=64, select=1),
603 'module': fields.char('Module', required=True, size=64, select=1),
604 'res_id': fields.integer('Record ID', select=1,
605 help="ID of the target record in the database"),
606 'noupdate': fields.boolean('Non Updatable'),
607 'date_update': fields.datetime('Update Date'),
608 'date_init': fields.datetime('Init Date')
611 'date_init': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
612 'date_update': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
617 ('module_name_uniq', 'unique(name, module)', 'You cannot have multiple records with the same external ID in the same module!'),
620 def __init__(self, pool, cr):
621 osv.osv.__init__(self, pool, cr)
624 # also stored in pool to avoid being discarded along with this osv instance
625 if getattr(pool, 'model_data_reference_ids', None) is None:
626 self.pool.model_data_reference_ids = {}
627 self.loads = self.pool.model_data_reference_ids
629 def _auto_init(self, cr, context=None):
630 super(ir_model_data, self)._auto_init(cr, context)
631 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_model_data_module_name_index\'')
632 if not cr.fetchone():
633 cr.execute('CREATE INDEX ir_model_data_module_name_index ON ir_model_data (module, name)')
636 def _get_id(self, cr, uid, module, xml_id):
637 """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"""
638 ids = self.search(cr, uid, [('module','=',module), ('name','=', xml_id)])
640 raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
641 # the sql constraints ensure us we have only one result
645 def get_object_reference(self, cr, uid, module, xml_id):
646 """Returns (model, res_id) corresponding to a given module and xml_id (cached) or raise ValueError if not found"""
647 data_id = self._get_id(cr, uid, module, xml_id)
648 res = self.read(cr, uid, data_id, ['model', 'res_id'])
649 if not res['res_id']:
650 raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
651 return (res['model'], res['res_id'])
653 def get_object(self, cr, uid, module, xml_id, context=None):
654 """Returns a browsable record for the given module name and xml_id or raise ValueError if not found"""
655 res_model, res_id = self.get_object_reference(cr, uid, module, xml_id)
656 result = self.pool.get(res_model).browse(cr, uid, res_id, context=context)
657 if not result.exists():
658 raise ValueError('No record found for unique ID %s.%s. It may have been deleted.' % (module, xml_id))
661 def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
665 id = self.read(cr, uid, [self._get_id(cr, uid, module, xml_id)], ['res_id'])[0]['res_id']
666 self.loads[(module,xml_id)] = (model,id)
671 def unlink(self, cr, uid, ids, context=None):
672 """ Regular unlink method, but make sure to clear the caches. """
673 self._get_id.clear_cache(self)
674 self.get_object_reference.clear_cache(self)
675 return super(ir_model_data,self).unlink(cr, uid, ids, context=context)
677 def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None):
678 model_obj = self.pool.get(model)
682 # records created during module install should result in res.log entries that are already read!
683 context = dict(context, res_log_read=True)
685 if xml_id and ('.' in xml_id):
686 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)
687 module, xml_id = xml_id.split('.')
688 if (not xml_id) and (not self.doinit):
693 cr.execute('''SELECT imd.id, imd.res_id, md.id, imd.model
694 FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
695 WHERE imd.module=%%s AND imd.name=%%s''' % model_obj._table,
697 results = cr.fetchall()
698 for imd_id2,res_id2,real_id2,real_model in results:
700 self._get_id.clear_cache(self, uid, module, xml_id)
701 self.get_object_reference.clear_cache(self, uid, module, xml_id)
702 cr.execute('delete from ir_model_data where id=%s', (imd_id2,))
705 assert model == real_model, "External ID conflict, %s already refers to a `%s` record,"\
706 " you can't define a `%s` record with this ID." % (xml_id, real_model, model)
707 res_id,action_id = res_id2,imd_id2
709 if action_id and res_id:
710 model_obj.write(cr, uid, [res_id], values, context=context)
711 self.write(cr, uid, [action_id], {
712 'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
715 model_obj.write(cr, uid, [res_id], values, context=context)
717 self.create(cr, uid, {
722 'noupdate': noupdate,
724 if model_obj._inherits:
725 for table in model_obj._inherits:
726 inherit_id = model_obj.browse(cr, uid,
727 res_id,context=context)[model_obj._inherits[table]]
728 self.create(cr, uid, {
729 'name': xml_id + '_' + table.replace('.', '_'),
732 'res_id': inherit_id.id,
733 'noupdate': noupdate,
736 if mode=='init' or (mode=='update' and xml_id):
737 res_id = model_obj.create(cr, uid, values, context=context)
739 self.create(cr, uid, {
746 if model_obj._inherits:
747 for table in model_obj._inherits:
748 inherit_id = model_obj.browse(cr, uid,
749 res_id,context=context)[model_obj._inherits[table]]
750 self.create(cr, uid, {
751 'name': xml_id + '_' + table.replace('.', '_'),
754 'res_id': inherit_id.id,
755 'noupdate': noupdate,
759 self.loads[(module, xml_id)] = (model, res_id)
760 if model_obj._inherits:
761 for table in model_obj._inherits:
762 inherit_field = model_obj._inherits[table]
763 inherit_id = model_obj.read(cr, uid, res_id,
764 [inherit_field])[inherit_field]
765 self.loads[(module, xml_id + '_' + \
766 table.replace('.', '_'))] = (table, inherit_id)
769 def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None, xml_id=False):
770 if type(models[0])==type([]) or type(models[0])==type(()):
771 model,res_id = models[0]
777 where = ' and res_id=%s' % (res_id,)
779 where = ' and (res_id is null)'
782 where += ' and key2=\'%s\'' % (key2,)
784 where += ' and (key2 is null)'
786 cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
789 ir_values_obj = pooler.get_pool(cr.dbname).get('ir.values')
790 res = ir_values_obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
792 cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
795 def _process_end(self, cr, uid, modules):
796 """ Clear records removed from updated module data.
798 This method is called at the end of the module loading process.
799 It is meant to removed records that are no longer present in the
800 updated data. Such records are recognised as the one with an xml id
801 and a module in ir_model_data and noupdate set to false, but not
802 present in self.loads.
807 modules = list(modules)
808 module_in = ",".join(["%s"] * len(modules))
809 cr.execute('select id,name,model,res_id,module from ir_model_data where module IN (' + module_in + ') and noupdate=%s', modules + [False])
812 for (id, name, model, res_id,module) in cr.fetchall():
813 if (module,name) not in self.loads:
814 to_unlink.append((model,res_id))
815 if model=='workflow.activity':
816 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,))
817 wkf_todo.extend(cr.fetchall())
818 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))
819 cr.execute("delete from wkf_transition where act_to=%s", (res_id,))
821 for model,id in wkf_todo:
822 wf_service = netsvc.LocalService("workflow")
823 wf_service.trg_write(uid, model, id, cr)
826 if not config.get('import_partial'):
827 for (model, res_id) in to_unlink:
828 if self.pool.get(model):
829 _logger.info('Deleting %s@%s', res_id, model)
831 self.pool.get(model).unlink(cr, uid, [res_id])
836 'Could not delete obsolete record with id: %d of model %s\n'
837 'There should be some relation that points to this resource\n'
838 'You should manually fix this and restart with --update=module',
843 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: