2 # -*- coding: utf-8 -*-
3 ##############################################################################
5 # OpenERP, Open Source Management Solution
6 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
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 osv import fields,osv
29 from osv.orm import except_orm, browse_record
31 from tools.safe_eval import safe_eval as eval
32 from tools import config
33 from tools.translate import _
36 _logger = logging.getLogger(__name__)
38 def _get_fields_type(self, cr, uid, context=None):
39 return sorted([(k,k) for k,v in fields.__dict__.iteritems()
40 if type(v) == types.TypeType
41 if issubclass(v, fields._column)
42 if v != fields._column
44 if not issubclass(v, fields.function)])
46 def _in_modules(self, cr, uid, ids, field_name, arg, context=None):
47 #pseudo-method used by fields.function in ir.model/ir.model.fields
48 module_pool = self.pool.get("ir.module.module")
49 installed_module_ids = module_pool.search(cr, uid, [('state','=','installed')])
50 installed_module_names = module_pool.read(cr, uid, installed_module_ids, ['name'], context=context)
51 installed_modules = set(x['name'] for x in installed_module_names)
54 xml_ids = osv.osv._get_xml_ids(self, cr, uid, ids)
55 for k,v in xml_ids.iteritems():
56 result[k] = ', '.join(sorted(installed_modules & set(xml_id.split('.')[0] for xml_id in v)))
60 class ir_model(osv.osv):
62 _description = "Models"
65 def _is_osv_memory(self, cr, uid, ids, field_name, arg, context=None):
66 models = self.browse(cr, uid, ids, context=context)
67 res = dict.fromkeys(ids)
69 res[model.id] = self.pool.get(model.model).is_transient()
72 def _search_osv_memory(self, cr, uid, model, name, domain, context=None):
75 field, operator, value = domain[0]
76 if operator not in ['=', '!=']:
77 raise osv.except_osv(_('Invalid search criterions'), _('The osv_memory field can only be compared with = and != operator.'))
78 value = bool(value) if operator == '=' else not bool(value)
79 all_model_ids = self.search(cr, uid, [], context=context)
80 is_osv_mem = self._is_osv_memory(cr, uid, all_model_ids, 'osv_memory', arg=None, context=context)
81 return [('id', 'in', [id for id in is_osv_mem if bool(is_osv_mem[id]) == value])]
83 def _view_ids(self, cr, uid, ids, field_name, arg, context=None):
84 models = self.browse(cr, uid, ids)
87 res[model.id] = self.pool.get("ir.ui.view").search(cr, uid, [('model', '=', model.model)])
91 'name': fields.char('Model Description', size=64, translate=True, required=True),
92 'model': fields.char('Model', size=64, required=True, select=1),
93 'info': fields.text('Information'),
94 'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields', required=True),
95 'state': fields.selection([('manual','Custom Object'),('base','Base Object')],'Type',readonly=True),
96 'access_ids': fields.one2many('ir.model.access', 'model_id', 'Access'),
97 'osv_memory': fields.function(_is_osv_memory, string='In-memory model', type='boolean',
98 fnct_search=_search_osv_memory,
99 help="Indicates whether this object model lives in memory only, i.e. is not persisted (osv.osv_memory)"),
100 'modules': fields.function(_in_modules, type='char', size=128, string='In modules', help='List of modules in which the object is defined or inherited'),
101 'view_ids': fields.function(_view_ids, type='one2many', obj='ir.ui.view', string='Views'),
105 'model': lambda *a: 'x_',
106 'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
109 def _check_model_name(self, cr, uid, ids, context=None):
110 for model in self.browse(cr, uid, ids, context=context):
111 if model.state=='manual':
112 if not model.model.startswith('x_'):
114 if not re.match('^[a-z_A-Z0-9.]+$',model.model):
118 def _model_name_msg(self, cr, uid, ids, context=None):
119 return _('The Object name must start with x_ and not contain any special character !')
122 (_check_model_name, _model_name_msg, ['model']),
125 ('obj_name_uniq', 'unique (model)', 'Each model must be unique!'),
128 # overridden to allow searching both on model name (model field)
129 # and model description (name field)
130 def _name_search(self, cr, uid, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
133 domain = args + ['|', ('model', operator, name), ('name', operator, name)]
134 return self.name_get(cr, name_get_uid or uid,
135 super(ir_model, self).search(cr, uid, domain, limit=limit, context=context),
138 def _drop_table(self, cr, uid, ids, context=None):
139 for model in self.browse(cr, uid, ids, context):
140 model_pool = self.pool.get(model.model)
141 if getattr(model_pool, '_auto', True) and not model.osv_memory:
142 cr.execute("DROP table %s cascade" % model_pool._table)
145 def unlink(self, cr, user, ids, context=None):
146 # for model in self.browse(cr, user, ids, context):
147 # if model.state != 'manual':
148 # raise except_orm(_('Error'), _("You can not remove the model '%s' !") %(model.name,))
149 # self._drop_table(cr, user, ids, context)
150 res = super(ir_model, self).unlink(cr, user, ids, context)
151 pooler.restart_pool(cr.dbname)
154 def write(self, cr, user, ids, vals, context=None):
156 context.pop('__last_update', None)
157 # Filter out operations 4 link from field id, because openerp-web
158 # always write (4,id,False) even for non dirty items
159 if 'field_id' in vals:
160 vals['field_id'] = [op for op in vals['field_id'] if op[0] != 4]
161 return super(ir_model,self).write(cr, user, ids, vals, context)
163 def create(self, cr, user, vals, context=None):
166 if context and context.get('manual',False):
167 vals['state']='manual'
168 res = super(ir_model,self).create(cr, user, vals, context)
169 if vals.get('state','base')=='manual':
170 self.instanciate(cr, user, vals['model'], context)
171 self.pool.get(vals['model']).__init__(self.pool, cr)
173 ctx.update({'field_name':vals['name'],'field_state':'manual','select':vals.get('select_level','0')})
174 self.pool.get(vals['model'])._auto_init(cr, ctx)
175 #pooler.restart_pool(cr.dbname)
178 def instanciate(self, cr, user, model, context=None):
179 class x_custom_model(osv.osv):
181 x_custom_model._name = model
182 x_custom_model._module = False
183 a = x_custom_model.create_instance(self.pool, cr)
184 if (not a._columns) or ('x_name' in a._columns.keys()):
187 x_name = a._columns.keys()[0]
188 x_custom_model._rec_name = x_name
191 class ir_model_fields(osv.osv):
192 _name = 'ir.model.fields'
193 _description = "Fields"
196 'name': fields.char('Name', required=True, size=64, select=1),
197 'model': fields.char('Object Name', size=64, required=True, select=1,
198 help="The technical name of the model this field belongs to"),
199 'relation': fields.char('Object Relation', size=64,
200 help="For relationship fields, the technical name of the target model"),
201 'relation_field': fields.char('Relation Field', size=64,
202 help="For one2many fields, the field on the target model that implement the opposite many2one relationship"),
203 'model_id': fields.many2one('ir.model', 'Model', required=True, select=True, ondelete='cascade',
204 help="The model this field belongs to"),
205 'field_description': fields.char('Field Label', required=True, size=256),
206 'ttype': fields.selection(_get_fields_type, 'Field Type',size=64, required=True),
207 'selection': fields.char('Selection Options',size=128, help="List of options for a selection field, "
208 "specified as a Python expression defining a list of (key, label) pairs. "
209 "For example: [('blue','Blue'),('yellow','Yellow')]"),
210 'required': fields.boolean('Required'),
211 'readonly': fields.boolean('Readonly'),
212 'select_level': fields.selection([('0','Not Searchable'),('1','Always Searchable'),('2','Advanced Search (deprecated)')],'Searchable', required=True),
213 'translate': fields.boolean('Translate', help="Whether values for this field can be translated (enables the translation mechanism for that field)"),
214 'size': fields.integer('Size'),
215 'state': fields.selection([('manual','Custom Field'),('base','Base Field')],'Type', required=True, readonly=True, select=1),
216 'on_delete': fields.selection([('cascade','Cascade'),('set null','Set NULL')], 'On delete', help='On delete property for many2one fields'),
217 'domain': fields.char('Domain', size=256, help="The optional domain to restrict possible values for relationship fields, "
218 "specified as a Python expression defining a list of triplets. "
219 "For example: [('color','=','red')]"),
220 'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'),
221 'view_load': fields.boolean('View Auto-Load'),
222 'selectable': fields.boolean('Selectable'),
223 'modules': fields.function(_in_modules, type='char', size=128, string='In modules', help='List of modules in which the field is defined'),
224 'serialization_field_id': fields.many2one('ir.model.fields', 'Serialization Field', domain = "[('ttype','=','serialized')]",
225 ondelete='cascade', help="If set, this field will be stored in the sparse "
226 "structure of the serialization field, instead "
227 "of having its own database column. This cannot be "
228 "changed after creation."),
230 _rec_name='field_description'
236 'state': lambda self,cr,uid,ctx={}: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
237 'on_delete': 'set null',
240 'field_description': '',
245 def _check_selection(self, cr, uid, selection, context=None):
247 selection_list = eval(selection)
249 _logger.warning('Invalid selection list definition for fields.selection', exc_info=True)
250 raise except_orm(_('Error'),
251 _("The Selection Options expression is not a valid Pythonic expression." \
252 "Please provide an expression in the [('key','Label'), ...] format."))
255 if not (isinstance(selection_list, list) and selection_list):
258 for item in selection_list:
259 if not (isinstance(item, (tuple,list)) and len(item) == 2):
264 raise except_orm(_('Error'),
265 _("The Selection Options expression is must be in the [('key','Label'), ...] format!"))
268 def _size_gt_zero_msg(self, cr, user, ids, context=None):
269 return _('Size of the field can never be less than 1 !')
272 ('size_gt_zero', 'CHECK (size>0)',_size_gt_zero_msg ),
275 def _drop_column(self, cr, uid, ids, context=None):
276 for field in self.browse(cr, uid, ids, context):
277 model = self.pool.get(field.model)
278 if not field.model.osv_memory and getattr(model, '_auto', True):
279 cr.execute("ALTER table %s DROP column %s" % (model._table, field.name))
280 model._columns.pop(field.name, None)
283 def unlink(self, cr, user, ids, context=None):
284 self._drop_column(cr, user, ids, context)
285 res = super(ir_model_fields, self).unlink(cr, user, ids, context)
288 def create(self, cr, user, vals, context=None):
289 if 'model_id' in vals:
290 model_data = self.pool.get('ir.model').browse(cr, user, vals['model_id'])
291 vals['model'] = model_data.model
294 if context and context.get('manual',False):
295 vals['state'] = 'manual'
296 if vals.get('ttype', False) == 'selection':
297 if not vals.get('selection',False):
298 raise except_orm(_('Error'), _('For selection fields, the Selection Options must be given!'))
299 self._check_selection(cr, user, vals['selection'], context=context)
300 res = super(ir_model_fields,self).create(cr, user, vals, context)
301 if vals.get('state','base') == 'manual':
302 if not vals['name'].startswith('x_'):
303 raise except_orm(_('Error'), _("Custom fields must have a name that starts with 'x_' !"))
305 if vals.get('relation',False) and not self.pool.get('ir.model').search(cr, user, [('model','=',vals['relation'])]):
306 raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
308 if self.pool.get(vals['model']):
309 self.pool.get(vals['model']).__init__(self.pool, cr)
310 #Added context to _auto_init for special treatment to custom field for select_level
312 ctx.update({'field_name':vals['name'],'field_state':'manual','select':vals.get('select_level','0'),'update_custom_fields':True})
313 self.pool.get(vals['model'])._auto_init(cr, ctx)
317 def write(self, cr, user, ids, vals, context=None):
320 if context and context.get('manual',False):
321 vals['state'] = 'manual'
323 #For the moment renaming a sparse field or changing the storing system is not allowed. This may be done later
324 if 'serialization_field_id' in vals or 'name' in vals:
325 for field in self.browse(cr, user, ids, context=context):
326 if 'serialization_field_id' in vals and field.serialization_field_id.id != vals['serialization_field_id']:
327 raise except_orm(_('Error!'), _('Changing the storing system for field "%s" is not allowed.')%field.name)
328 if field.serialization_field_id and (field.name != vals['name']):
329 raise except_orm(_('Error!'), _('Renaming sparse field "%s" is not allowed')%field.name)
331 column_rename = None # if set, *one* column can be renamed here
333 models_patch = {} # structs of (obj, [(field, prop, change_to),..])
334 # data to be updated on the orm model
336 # static table of properties
337 model_props = [ # (our-name, fields.prop, set_fn)
338 ('field_description', 'string', str),
339 ('required', 'required', bool),
340 ('readonly', 'readonly', bool),
341 ('domain', '_domain', eval),
342 ('size', 'size', int),
343 ('on_delete', 'ondelete', str),
344 ('translate', 'translate', bool),
345 ('view_load', 'view_load', bool),
346 ('selectable', 'selectable', bool),
347 ('select_level', 'select', int),
348 ('selection', 'selection', eval),
352 checked_selection = False # need only check it once, so defer
354 for item in self.browse(cr, user, ids, context=context):
355 if not (obj and obj._name == item.model):
356 obj = self.pool.get(item.model)
358 if item.state != 'manual':
359 raise except_orm(_('Error!'),
360 _('Properties of base fields cannot be altered in this manner! '
361 'Please modify them through Python code, '
362 'preferably through a custom addon!'))
364 if item.ttype == 'selection' and 'selection' in vals \
365 and not checked_selection:
366 self._check_selection(cr, user, vals['selection'], context=context)
367 checked_selection = True
369 final_name = item.name
370 if 'name' in vals and vals['name'] != item.name:
371 # We need to rename the column
373 raise except_orm(_('Error!'), _('Can only rename one column at a time!'))
374 if vals['name'] in obj._columns:
375 raise except_orm(_('Error!'), _('Cannot rename column to %s, because that column already exists!') % vals['name'])
376 if vals.get('state', 'base') == 'manual' and not vals['name'].startswith('x_'):
377 raise except_orm(_('Error!'), _('New column name must still start with x_ , because it is a custom field!'))
378 if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']:
379 raise ValueError('Invalid character in column name')
380 column_rename = (obj, (obj._table, item.name, vals['name']))
381 final_name = vals['name']
383 if 'model_id' in vals and vals['model_id'] != item.model_id:
384 raise except_orm(_("Error!"), _("Changing the model of a field is forbidden!"))
386 if 'ttype' in vals and vals['ttype'] != item.ttype:
387 raise except_orm(_("Error!"), _("Changing the type of a column is not yet supported. "
388 "Please drop it and create it again!"))
390 # We don't check the 'state', because it might come from the context
391 # (thus be set for multiple fields) and will be ignored anyway.
393 models_patch.setdefault(obj._name, (obj,[]))
394 # find out which properties (per model) we need to update
395 for field_name, field_property, set_fn in model_props:
396 if field_name in vals:
397 property_value = set_fn(vals[field_name])
398 if getattr(obj._columns[item.name], field_property) != property_value:
399 models_patch[obj._name][1].append((final_name, field_property, property_value))
400 # our dict is ready here, but no properties are changed so far
402 # These shall never be written (modified)
403 for column_name in ('model_id', 'model', 'state'):
404 if column_name in vals:
405 del vals[column_name]
407 res = super(ir_model_fields,self).write(cr, user, ids, vals, context=context)
410 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % column_rename[1])
411 # This is VERY risky, but let us have this feature:
412 # we want to change the key of column in obj._columns dict
413 col = column_rename[0]._columns.pop(column_rename[1][1]) # take object out, w/o copy
414 column_rename[0]._columns[column_rename[1][2]] = col
417 # We have to update _columns of the model(s) and then call their
418 # _auto_init to sync the db with the model. Hopefully, since write()
419 # was called earlier, they will be in-sync before the _auto_init.
420 # Anything we don't update in _columns now will be reset from
421 # the model into ir.model.fields (db).
423 ctx.update({'select': vals.get('select_level','0'),'update_custom_fields':True})
425 for model_key, patch_struct in models_patch.items():
426 obj = patch_struct[0]
427 for col_name, col_prop, val in patch_struct[1]:
428 setattr(obj._columns[col_name], col_prop, val)
429 obj._auto_init(cr, ctx)
434 class ir_model_access(osv.osv):
435 _name = 'ir.model.access'
437 'name': fields.char('Name', size=64, required=True, select=True),
438 'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)], select=True, ondelete='cascade'),
439 'group_id': fields.many2one('res.groups', 'Group', ondelete='cascade', select=True),
440 'perm_read': fields.boolean('Read Access'),
441 'perm_write': fields.boolean('Write Access'),
442 'perm_create': fields.boolean('Create Access'),
443 'perm_unlink': fields.boolean('Delete Access'),
446 def check_groups(self, cr, uid, group):
447 grouparr = group.split('.')
450 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],))
451 return bool(cr.fetchone())
453 def check_group(self, cr, uid, model, mode, group_ids):
454 """ Check if a specific group has the access mode to the specified model"""
455 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
457 if isinstance(model, browse_record):
458 assert model._table_name == 'ir.model', 'Invalid model object'
459 model_name = model.name
463 if isinstance(group_ids, (int, long)):
464 group_ids = [group_ids]
465 for group_id in group_ids:
466 cr.execute("SELECT perm_" + mode + " "
467 " FROM ir_model_access a "
468 " JOIN ir_model m ON (m.id = a.model_id) "
469 " WHERE m.model = %s AND a.group_id = %s", (model_name, group_id)
473 cr.execute("SELECT perm_" + mode + " "
474 " FROM ir_model_access a "
475 " JOIN ir_model m ON (m.id = a.model_id) "
476 " WHERE m.model = %s AND a.group_id IS NULL", (model_name, )
480 access = bool(r and r[0])
483 # pass no groups -> no access
486 def group_names_with_access(self, cr, model_name, access_mode):
487 """Returns the names of visible groups which have been granted ``access_mode`` on
488 the model ``model_name``.
491 assert access_mode in ['read','write','create','unlink'], 'Invalid access mode: %s' % access_mode
496 JOIN ir_model m ON (a.model_id=m.id)
497 JOIN res_groups g ON (a.group_id=g.id)
500 a.perm_''' + access_mode, (model_name,))
501 return [x[0] for x in cr.fetchall()]
504 def check(self, cr, uid, model, mode='read', raise_exception=True, context=None):
506 # User root have all accesses
507 # TODO: exclude xml-rpc requests
510 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
512 if isinstance(model, browse_record):
513 assert model._table_name == 'ir.model', 'Invalid model object'
514 model_name = model.model
518 # TransientModel records have no access rights, only an implicit access rule
519 if self.pool.get(model_name).is_transient():
522 # We check if a specific rule exists
523 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
524 ' FROM ir_model_access a '
525 ' JOIN ir_model m ON (m.id = a.model_id) '
526 ' JOIN res_groups_users_rel gu ON (gu.gid = a.group_id) '
527 ' WHERE m.model = %s '
534 # there is no specific rule. We check the generic rule
535 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
536 ' FROM ir_model_access a '
537 ' JOIN ir_model m ON (m.id = a.model_id) '
538 ' WHERE a.group_id IS NULL '
544 if not r and raise_exception:
545 groups = ', '.join(self.group_names_with_access(cr, model_name, mode)) or '/'
547 'read': _("You can not read this document (%s) ! Be sure your user belongs to one of these groups: %s."),
548 'write': _("You can not write in this document (%s) ! Be sure your user belongs to one of these groups: %s."),
549 'create': _("You can not create this document (%s) ! Be sure your user belongs to one of these groups: %s."),
550 'unlink': _("You can not delete this document (%s) ! Be sure your user belongs to one of these groups: %s."),
553 raise except_orm(_('AccessError'), msgs[mode] % (model_name, groups) )
556 __cache_clearing_methods = []
558 def register_cache_clearing_method(self, model, method):
559 self.__cache_clearing_methods.append((model, method))
561 def unregister_cache_clearing_method(self, model, method):
563 i = self.__cache_clearing_methods.index((model, method))
564 del self.__cache_clearing_methods[i]
568 def call_cache_clearing_methods(self, cr):
569 self.check.clear_cache(self) # clear the cache of check function
570 for model, method in self.__cache_clearing_methods:
571 object_ = self.pool.get(model)
573 getattr(object_, method)()
576 # Check rights on actions
578 def write(self, cr, uid, *args, **argv):
579 self.call_cache_clearing_methods(cr)
580 res = super(ir_model_access, self).write(cr, uid, *args, **argv)
583 def create(self, cr, uid, *args, **argv):
584 self.call_cache_clearing_methods(cr)
585 res = super(ir_model_access, self).create(cr, uid, *args, **argv)
588 def unlink(self, cr, uid, *args, **argv):
589 self.call_cache_clearing_methods(cr)
590 res = super(ir_model_access, self).unlink(cr, uid, *args, **argv)
595 class ir_model_data(osv.osv):
596 """Holds external identifier keys for records in the database.
597 This has two main uses:
599 * allows easy data integration with third-party systems,
600 making import/export/sync of data possible, as records
601 can be uniquely identified across multiple systems
602 * allows tracking the origin of data installed by OpenERP
603 modules themselves, thus making it possible to later
604 update them seamlessly.
606 _name = 'ir.model.data'
607 _order = 'module,model,name'
609 'name': fields.char('External Identifier', required=True, size=128, select=1,
610 help="External Key/Identifier that can be used for "
611 "data integration with third-party systems"),
612 'model': fields.char('Model Name', required=True, size=64, select=1),
613 'module': fields.char('Module', required=True, size=64, select=1),
614 'res_id': fields.integer('Record ID', select=1,
615 help="ID of the target record in the database"),
616 'noupdate': fields.boolean('Non Updatable'),
617 'date_update': fields.datetime('Update Date'),
618 'date_init': fields.datetime('Init Date')
621 'date_init': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
622 'date_update': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
627 ('module_name_uniq', 'unique(name, module)', 'You cannot have multiple records with the same external ID in the same module!'),
630 def __init__(self, pool, cr):
631 osv.osv.__init__(self, pool, cr)
634 # also stored in pool to avoid being discarded along with this osv instance
635 if getattr(pool, 'model_data_reference_ids', None) is None:
636 self.pool.model_data_reference_ids = {}
638 self.loads = self.pool.model_data_reference_ids
640 def _auto_init(self, cr, context=None):
641 super(ir_model_data, self)._auto_init(cr, context)
642 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_model_data_module_name_index\'')
643 if not cr.fetchone():
644 cr.execute('CREATE INDEX ir_model_data_module_name_index ON ir_model_data (module, name)')
647 def _get_id(self, cr, uid, module, xml_id):
648 """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"""
649 ids = self.search(cr, uid, [('module','=',module), ('name','=', xml_id)])
651 raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
652 # the sql constraints ensure us we have only one result
656 def get_object_reference(self, cr, uid, module, xml_id):
657 """Returns (model, res_id) corresponding to a given module and xml_id (cached) or raise ValueError if not found"""
658 data_id = self._get_id(cr, uid, module, xml_id)
659 res = self.read(cr, uid, data_id, ['model', 'res_id'])
660 if not res['res_id']:
661 raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
662 return (res['model'], res['res_id'])
664 def get_object(self, cr, uid, module, xml_id, context=None):
665 """Returns a browsable record for the given module name and xml_id or raise ValueError if not found"""
666 res_model, res_id = self.get_object_reference(cr, uid, module, xml_id)
667 result = self.pool.get(res_model).browse(cr, uid, res_id, context=context)
668 if not result.exists():
669 raise ValueError('No record found for unique ID %s.%s. It may have been deleted.' % (module, xml_id))
672 def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
676 id = self.read(cr, uid, [self._get_id(cr, uid, module, xml_id)], ['res_id'])[0]['res_id']
677 self.loads[(module,xml_id)] = (model,id)
683 def unlink(self, cr, uid, ids, context=None):
684 """ Regular unlink method, but make sure to clear the caches. """
685 self._pre_process_unlink(cr, uid, ids, context)
686 self._get_id.clear_cache(self)
687 self.get_object_reference.clear_cache(self)
688 return super(ir_model_data,self).unlink(cr, uid, ids, context=context)
690 def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None):
691 model_obj = self.pool.get(model)
695 # records created during module install should result in res.log entries that are already read!
696 context = dict(context, res_log_read=True)
698 if xml_id and ('.' in xml_id):
699 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)
700 module, xml_id = xml_id.split('.')
701 if (not xml_id) and (not self.doinit):
705 cr.execute('''SELECT imd.id, imd.res_id, md.id, imd.model
706 FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
707 WHERE imd.module=%%s AND imd.name=%%s''' % model_obj._table,
709 results = cr.fetchall()
710 for imd_id2,res_id2,real_id2,real_model in results:
712 self._get_id.clear_cache(self, uid, module, xml_id)
713 self.get_object_reference.clear_cache(self, uid, module, xml_id)
714 cr.execute('delete from ir_model_data where id=%s', (imd_id2,))
717 assert model == real_model, "External ID conflict, %s already refers to a `%s` record,"\
718 " you can't define a `%s` record with this ID." % (xml_id, real_model, model)
719 res_id,action_id = res_id2,imd_id2
721 if action_id and res_id:
722 model_obj.write(cr, uid, [res_id], values, context=context)
723 self.write(cr, uid, [action_id], {
724 'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
727 model_obj.write(cr, uid, [res_id], values, context=context)
729 self.create(cr, uid, {
734 'noupdate': noupdate,
736 if model_obj._inherits:
737 for table in model_obj._inherits:
738 inherit_id = model_obj.browse(cr, uid,
739 res_id,context=context)[model_obj._inherits[table]]
740 self.create(cr, uid, {
741 'name': xml_id + '_' + table.replace('.', '_'),
744 'res_id': inherit_id.id,
745 'noupdate': noupdate,
748 if mode=='init' or (mode=='update' and xml_id):
749 res_id = model_obj.create(cr, uid, values, context=context)
751 self.create(cr, uid, {
758 if model_obj._inherits:
759 for table in model_obj._inherits:
760 inherit_id = model_obj.browse(cr, uid,
761 res_id,context=context)[model_obj._inherits[table]]
762 self.create(cr, uid, {
763 'name': xml_id + '_' + table.replace('.', '_'),
766 'res_id': inherit_id.id,
767 'noupdate': noupdate,
771 self.loads[(module, xml_id)] = (model, res_id)
772 if model_obj._inherits:
773 for table in model_obj._inherits:
774 inherit_field = model_obj._inherits[table]
775 inherit_id = model_obj.read(cr, uid, res_id,
776 [inherit_field])[inherit_field]
777 self.loads[(module, xml_id + '_' + \
778 table.replace('.', '_'))] = (table, inherit_id)
781 def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None, xml_id=False):
782 if type(models[0])==type([]) or type(models[0])==type(()):
783 model,res_id = models[0]
789 where = ' and res_id=%s' % (res_id,)
791 where = ' and (res_id is null)'
794 where += ' and key2=\'%s\'' % (key2,)
796 where += ' and (key2 is null)'
798 cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
801 ir_values_obj = pooler.get_pool(cr.dbname).get('ir.values')
802 res = ir_values_obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
804 cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
807 def _pre_process_unlink(self, cr, uid, ids, context=None):
810 for data in self.browse(cr, uid, ids, context):
813 model_obj = self.pool.get(model)
814 if str(data.name).startswith('constraint_'):
815 cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table,data.name[11:]),)
816 _logger.info('Drop CONSTRAINT %s@%s', data.name[11:], model)
818 to_unlink.append((model,res_id))
819 if model=='workflow.activity':
820 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,))
821 wkf_todo.extend(cr.fetchall())
822 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))
823 cr.execute("delete from wkf_transition where act_to=%s", (res_id,))
825 for model,res_id in wkf_todo:
826 wf_service = netsvc.LocalService("workflow")
827 wf_service.trg_write(uid, model, res_id, cr)
830 if not config.get('import_partial'):
831 for (model, res_id) in to_unlink:
832 if self.pool.get(model):
833 _logger.info('Deleting %s@%s', res_id, model)
834 res_ids = self.pool.get(model).search(cr, uid, [('id', '=', res_id)])
836 self.pool.get(model).unlink(cr, uid, [res_id])
841 # 'Could not delete obsolete record with id: %d of model %s\n'
842 ## 'There should be some relation that points to this resource\n'
843 # 'You should manually fix this and restart with --update=module',
846 def _process_end(self, cr, uid, modules):
847 """ Clear records removed from updated module data.
848 This method is called at the end of the module loading process.
849 It is meant to removed records that are no longer present in the
850 updated data. Such records are recognised as the one with an xml id
851 and a module in ir_model_data and noupdate set to false, but not
852 present in self.loads.
858 modules = list(modules)
859 data_ids = self.search(cr, uid, [('module','in',modules)])
860 module_in = ",".join(["%s"] * len(modules))
861 process_query = 'select id,name,model,res_id,module from ir_model_data where module IN (' + module_in + ')'
862 process_query+= ' and noupdate=%s'
864 cr.execute(process_query, modules + [False])
865 for (id, name, model, res_id,module) in cr.fetchall():
866 if (module,name) not in self.loads:
867 to_unlink.append((model,res_id))
868 if not config.get('import_partial'):
869 for (model, res_id) in to_unlink:
870 if self.pool.get(model):
871 _logger.info('Deleting %s@%s', res_id, model)
872 self.pool.get(model).unlink(cr, uid, [res_id])
879 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: