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 ##############################################################################
25 from osv import fields,osv
27 from osv.orm import except_orm, browse_record
29 from tools.safe_eval import safe_eval as eval
30 from tools import config
31 from tools.translate import _
34 def _get_fields_type(self, cr, uid, context=None):
35 cr.execute('select distinct ttype,ttype from ir_model_fields')
36 field_types = cr.fetchall()
37 field_types_copy = field_types
38 for types in field_types_copy:
39 if not hasattr(fields,types[0]):
40 field_types.remove(types)
43 def _in_modules(self, cr, uid, ids, field_name, arg, context=None):
44 #pseudo-method used by fields.function in ir.model/ir.model.fields
45 module_pool = self.pool.get("ir.module.module")
46 installed_module_ids = module_pool.search(cr, uid, [('state','=','installed')])
47 installed_module_names = module_pool.read(cr, uid, installed_module_ids, ['name'], context=context)
48 installed_modules = set(x['name'] for x in installed_module_names)
51 xml_ids = osv.osv._get_xml_ids(self, cr, uid, ids)
52 for k,v in xml_ids.iteritems():
53 result[k] = ', '.join(sorted(installed_modules & set(xml_id.split('.')[0] for xml_id in v)))
57 class ir_model(osv.osv):
59 _description = "Objects"
62 def _is_osv_memory(self, cr, uid, ids, field_name, arg, context=None):
63 models = self.browse(cr, uid, ids, context=context)
64 res = dict.fromkeys(ids)
66 res[model.id] = isinstance(self.pool.get(model.model), osv.osv_memory)
69 def _search_osv_memory(self, cr, uid, model, name, domain, context=None):
72 field, operator, value = domain[0]
73 if operator not in ['=', '!=']:
74 raise osv.except_osv(_('Invalid search criterions'), _('The osv_memory field can only be compared with = and != operator.'))
75 value = bool(value) if operator == '=' else not bool(value)
76 all_model_ids = self.search(cr, uid, [], context=context)
77 is_osv_mem = self._is_osv_memory(cr, uid, all_model_ids, 'osv_memory', arg=None, context=context)
78 return [('id', 'in', [id for id in is_osv_mem if bool(is_osv_mem[id]) == value])]
80 def _view_ids(self, cr, uid, ids, field_name, arg, context=None):
81 models = self.browse(cr, uid, ids)
84 res[model.id] = self.pool.get("ir.ui.view").search(cr, uid, [('model', '=', model.model)])
88 'name': fields.char('Object Name', size=64, translate=True, required=True),
89 'model': fields.char('Object', size=64, required=True, select=1),
90 'info': fields.text('Information'),
91 'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields', required=True),
92 'state': fields.selection([('manual','Custom Object'),('base','Base Object')],'Type',readonly=True),
93 'access_ids': fields.one2many('ir.model.access', 'model_id', 'Access'),
94 'osv_memory': fields.function(_is_osv_memory, method=True, string='In-memory model', type='boolean',
95 fnct_search=_search_osv_memory,
96 help="Indicates whether this object model lives in memory only, i.e. is not persisted (osv.osv_memory)"),
97 'modules': fields.function(_in_modules, method=True, type='char', size=128, string='In modules', help='List of modules in which the object is defined or inherited'),
98 'view_ids': fields.function(_view_ids, method=True, type='one2many', obj='ir.ui.view', string='Views'),
102 'model': lambda *a: 'x_',
103 'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
106 def _check_model_name(self, cr, uid, ids, context=None):
107 for model in self.browse(cr, uid, ids, context=context):
108 if model.state=='manual':
109 if not model.model.startswith('x_'):
111 if not re.match('^[a-z_A-Z0-9.]+$',model.model):
115 def _model_name_msg(self, cr, uid, ids, context=None):
116 return _('The Object name must start with x_ and not contain any special character !')
118 (_check_model_name, _model_name_msg, ['model']),
121 # overridden to allow searching both on model name (model field)
122 # and model description (name field)
123 def name_search(self, cr, uid, name='', args=None, operator='ilike', context=None, limit=None):
126 domain = args + ['|', ('model', operator, name), ('name', operator, name)]
127 return super(ir_model, self).name_search(cr, uid, None, domain,
128 operator=operator, limit=limit, context=context)
131 def unlink(self, cr, user, ids, context=None):
132 for model in self.browse(cr, user, ids, context):
133 if model.state != 'manual':
134 raise except_orm(_('Error'), _("You can not remove the model '%s' !") %(model.name,))
135 res = super(ir_model, self).unlink(cr, user, ids, context)
136 pooler.restart_pool(cr.dbname)
139 def write(self, cr, user, ids, vals, context=None):
141 context.pop('__last_update', None)
142 return super(ir_model,self).write(cr, user, ids, vals, context)
144 def create(self, cr, user, vals, context=None):
147 if context and context.get('manual',False):
148 vals['state']='manual'
149 res = super(ir_model,self).create(cr, user, vals, context)
150 if vals.get('state','base')=='manual':
151 self.instanciate(cr, user, vals['model'], context)
152 self.pool.get(vals['model']).__init__(self.pool, cr)
154 ctx.update({'field_name':vals['name'],'field_state':'manual','select':vals.get('select_level','0')})
155 self.pool.get(vals['model'])._auto_init(cr, ctx)
156 #pooler.restart_pool(cr.dbname)
159 def instanciate(self, cr, user, model, context={}):
160 class x_custom_model(osv.osv):
162 x_custom_model._name = model
163 x_custom_model._module = False
164 a = x_custom_model.createInstance(self.pool, cr)
165 if (not a._columns) or ('x_name' in a._columns.keys()):
168 x_name = a._columns.keys()[0]
169 x_custom_model._rec_name = x_name
172 class ir_model_fields(osv.osv):
173 _name = 'ir.model.fields'
174 _description = "Fields"
177 'name': fields.char('Name', required=True, size=64, select=1),
178 'model': fields.char('Object Name', size=64, required=True, select=1,
179 help="The technical name of the model this field belongs to"),
180 'relation': fields.char('Object Relation', size=64,
181 help="For relationship fields, the technical name of the target model"),
182 'relation_field': fields.char('Relation Field', size=64,
183 help="For one2many fields, the field on the target model that implement the opposite many2one relationship"),
184 'model_id': fields.many2one('ir.model', 'Model', required=True, select=True, ondelete='cascade',
185 help="The model this field belongs to"),
186 'field_description': fields.char('Field Label', required=True, size=256),
187 'ttype': fields.selection(_get_fields_type, 'Field Type',size=64, required=True),
188 'selection': fields.char('Selection Options',size=128, help="List of options for a selection field, "
189 "specified as a Python expression defining a list of (key, label) pairs. "
190 "For example: [('blue','Blue'),('yellow','Yellow')]"),
191 'required': fields.boolean('Required'),
192 'readonly': fields.boolean('Readonly'),
193 'select_level': fields.selection([('0','Not Searchable'),('1','Always Searchable'),('2','Advanced Search (deprecated)')],'Searchable', required=True),
194 'translate': fields.boolean('Translate', help="Whether values for this field can be translated (enables the translation mechanism for that field)"),
195 'size': fields.integer('Size'),
196 'state': fields.selection([('manual','Custom Field'),('base','Base Field')],'Type', required=True, readonly=True, select=1),
197 'on_delete': fields.selection([('cascade','Cascade'),('set null','Set NULL')], 'On delete', help='On delete property for many2one fields'),
198 'domain': fields.char('Domain', size=256, help="The optional domain to restrict possible values for relationship fields, "
199 "specified as a Python expression defining a list of triplets. "
200 "For example: [('color','=','red')]"),
201 'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'),
202 'view_load': fields.boolean('View Auto-Load'),
203 'selectable': fields.boolean('Selectable'),
204 'modules': fields.function(_in_modules, method=True, type='char', size=128, string='In modules', help='List of modules in which the field is defined'),
206 _rec_name='field_description'
212 'state': lambda self,cr,uid,ctx={}: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
213 'on_delete': 'set null',
216 'field_description': '',
221 def _check_selection(self, cr, uid, selection, context=None):
223 selection_list = eval(selection)
225 logging.getLogger('ir.model').warning('Invalid selection list definition for fields.selection', exc_info=True)
226 raise except_orm(_('Error'),
227 _("The Selection Options expression is not a valid Pythonic expression." \
228 "Please provide an expression in the [('key','Label'), ...] format."))
231 if not (isinstance(selection_list, list) and selection_list):
234 for item in selection_list:
235 if not (isinstance(item, (tuple,list)) and len(item) == 2):
240 raise except_orm(_('Error'),
241 _("The Selection Options expression is must be in the [('key','Label'), ...] format!"))
244 def _size_gt_zero_msg(self, cr, user, ids, context=None):
245 return _('Size of the field can never be less than 1 !')
248 ('size_gt_zero', 'CHECK (size>0)',_size_gt_zero_msg ),
251 def unlink(self, cr, user, ids, context=None):
252 for field in self.browse(cr, user, ids, context):
253 if field.state <> 'manual':
254 raise except_orm(_('Error'), _("You cannot remove the field '%s' !") %(field.name,))
256 # MAY BE ADD A ALTER TABLE DROP ?
258 #Removing _columns entry for that table
259 self.pool.get(field.model)._columns.pop(field.name,None)
260 return super(ir_model_fields, self).unlink(cr, user, ids, context)
262 def create(self, cr, user, vals, context=None):
263 if 'model_id' in vals:
264 model_data = self.pool.get('ir.model').browse(cr, user, vals['model_id'])
265 vals['model'] = model_data.model
268 if context and context.get('manual',False):
269 vals['state'] = 'manual'
270 if vals.get('ttype', False) == 'selection':
271 if not vals.get('selection',False):
272 raise except_orm(_('Error'), _('For selection fields, the Selection Options must be given!'))
273 self._check_selection(cr, user, vals['selection'], context=context)
274 res = super(ir_model_fields,self).create(cr, user, vals, context)
275 if vals.get('state','base') == 'manual':
276 if not vals['name'].startswith('x_'):
277 raise except_orm(_('Error'), _("Custom fields must have a name that starts with 'x_' !"))
279 if vals.get('relation',False) and not self.pool.get('ir.model').search(cr, user, [('model','=',vals['relation'])]):
280 raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
282 if self.pool.get(vals['model']):
283 self.pool.get(vals['model']).__init__(self.pool, cr)
284 #Added context to _auto_init for special treatment to custom field for select_level
286 ctx.update({'field_name':vals['name'],'field_state':'manual','select':vals.get('select_level','0'),'update_custom_fields':True})
287 self.pool.get(vals['model'])._auto_init(cr, ctx)
291 def write(self, cr, user, ids, vals, context=None):
294 if context and context.get('manual',False):
295 vals['state'] = 'manual'
297 column_rename = None # if set, *one* column can be renamed here
299 models_patch = {} # structs of (obj, [(field, prop, change_to),..])
300 # data to be updated on the orm model
302 # static table of properties
303 model_props = [ # (our-name, fields.prop, set_fn)
304 ('field_description', 'string', str),
305 ('required', 'required', bool),
306 ('readonly', 'readonly', bool),
307 ('domain', '_domain', eval),
308 ('size', 'size', int),
309 ('on_delete', 'ondelete', str),
310 ('translate', 'translate', bool),
311 ('view_load', 'view_load', bool),
312 ('selectable', 'selectable', bool),
313 ('select_level', 'select', int),
314 ('selection', 'selection', eval),
318 checked_selection = False # need only check it once, so defer
320 for item in self.browse(cr, user, ids, context=context):
321 if not (obj and obj._name == item.model):
322 obj = self.pool.get(item.model)
324 if item.state != 'manual':
325 raise except_orm(_('Error!'),
326 _('Properties of base fields cannot be altered in this manner! '
327 'Please modify them through Python code, '
328 'preferably through a custom addon!'))
330 if item.ttype == 'selection' and 'selection' in vals \
331 and not checked_selection:
332 self._check_selection(cr, user, vals['selection'], context=context)
333 checked_selection = True
335 final_name = item.name
336 if 'name' in vals and vals['name'] != item.name:
337 # We need to rename the column
339 raise except_orm(_('Error!'), _('Can only rename one column at a time!'))
340 if vals['name'] in obj._columns:
341 raise except_orm(_('Error!'), _('Cannot rename column to %s, because that column already exists!') % vals['name'])
342 if vals.get('state', 'base') == 'manual' and not vals['name'].startswith('x_'):
343 raise except_orm(_('Error!'), _('New column name must still start with x_ , because it is a custom field!'))
344 if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']:
345 raise ValueError('Invalid character in column name')
346 column_rename = (obj, (obj._table, item.name, vals['name']))
347 final_name = vals['name']
349 if 'model_id' in vals and vals['model_id'] != item.model_id:
350 raise except_orm(_("Error!"), _("Changing the model of a field is forbidden!"))
352 if 'ttype' in vals and vals['ttype'] != item.ttype:
353 raise except_orm(_("Error!"), _("Changing the type of a column is not yet supported. "
354 "Please drop it and create it again!"))
356 # We don't check the 'state', because it might come from the context
357 # (thus be set for multiple fields) and will be ignored anyway.
359 models_patch.setdefault(obj._name, (obj,[]))
360 # find out which properties (per model) we need to update
361 for field_name, field_property, set_fn in model_props:
362 if field_name in vals:
363 property_value = set_fn(vals[field_name])
364 if getattr(obj._columns[item.name], field_property) != property_value:
365 models_patch[obj._name][1].append((final_name, field_property, property_value))
366 # our dict is ready here, but no properties are changed so far
368 # These shall never be written (modified)
369 for column_name in ('model_id', 'model', 'state'):
370 if column_name in vals:
371 del vals[column_name]
373 res = super(ir_model_fields,self).write(cr, user, ids, vals, context=context)
376 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % column_rename[1])
377 # This is VERY risky, but let us have this feature:
378 # we want to change the key of column in obj._columns dict
379 col = column_rename[0]._columns.pop(column_rename[1][1]) # take object out, w/o copy
380 column_rename[0]._columns[column_rename[1][2]] = col
383 # We have to update _columns of the model(s) and then call their
384 # _auto_init to sync the db with the model. Hopefully, since write()
385 # was called earlier, they will be in-sync before the _auto_init.
386 # Anything we don't update in _columns now will be reset from
387 # the model into ir.model.fields (db).
389 ctx.update({'select': vals.get('select_level','0'),'update_custom_fields':True})
391 for model_key, patch_struct in models_patch.items():
392 obj = patch_struct[0]
393 for col_name, col_prop, val in patch_struct[1]:
394 setattr(obj._columns[col_name], col_prop, val)
395 obj._auto_init(cr, ctx)
400 class ir_model_access(osv.osv):
401 _name = 'ir.model.access'
403 'name': fields.char('Name', size=64, required=True, select=True),
404 'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)], select=True, ondelete='cascade'),
405 'group_id': fields.many2one('res.groups', 'Group', ondelete='cascade', select=True),
406 'perm_read': fields.boolean('Read Access'),
407 'perm_write': fields.boolean('Write Access'),
408 'perm_create': fields.boolean('Create Access'),
409 'perm_unlink': fields.boolean('Delete Access'),
412 def check_groups(self, cr, uid, group):
413 grouparr = group.split('.')
416 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],))
417 return bool(cr.fetchone())
419 def check_group(self, cr, uid, model, mode, group_ids):
420 """ Check if a specific group has the access mode to the specified model"""
421 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
423 if isinstance(model, browse_record):
424 assert model._table_name == 'ir.model', 'Invalid model object'
425 model_name = model.name
429 if isinstance(group_ids, (int, long)):
430 group_ids = [group_ids]
431 for group_id in group_ids:
432 cr.execute("SELECT perm_" + mode + " "
433 " FROM ir_model_access a "
434 " JOIN ir_model m ON (m.id = a.model_id) "
435 " WHERE m.model = %s AND a.group_id = %s", (model_name, group_id)
439 cr.execute("SELECT perm_" + mode + " "
440 " FROM ir_model_access a "
441 " JOIN ir_model m ON (m.id = a.model_id) "
442 " WHERE m.model = %s AND a.group_id IS NULL", (model_name, )
446 access = bool(r and r[0])
449 # pass no groups -> no access
452 def group_names_with_access(self, cr, model_name, access_mode):
453 """Returns the names of visible groups which have been granted ``access_mode`` on
454 the model ``model_name``.
457 assert access_mode in ['read','write','create','unlink'], 'Invalid access mode: %s' % access_mode
462 JOIN ir_model m ON (a.model_id=m.id)
463 JOIN res_groups g ON (a.group_id=g.id)
466 a.perm_''' + access_mode, (model_name,))
467 return [x[0] for x in cr.fetchall()]
470 def check(self, cr, uid, model, mode='read', raise_exception=True, context=None):
472 # User root have all accesses
473 # TODO: exclude xml-rpc requests
476 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
478 if isinstance(model, browse_record):
479 assert model._table_name == 'ir.model', 'Invalid model object'
480 model_name = model.name
484 # osv_memory objects can be read by everyone, as they only return
485 # results that belong to the current user (except for superuser)
486 model_obj = self.pool.get(model_name)
487 if isinstance(model_obj, osv.osv_memory):
490 # We check if a specific rule exists
491 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
492 ' FROM ir_model_access a '
493 ' JOIN ir_model m ON (m.id = a.model_id) '
494 ' JOIN res_groups_users_rel gu ON (gu.gid = a.group_id) '
495 ' WHERE m.model = %s '
502 # there is no specific rule. We check the generic rule
503 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
504 ' FROM ir_model_access a '
505 ' JOIN ir_model m ON (m.id = a.model_id) '
506 ' WHERE a.group_id IS NULL '
512 if not r and raise_exception:
513 groups = ', '.join(self.group_names_with_access(cr, model_name, mode)) or '/'
515 'read': _("You can not read this document (%s) ! Be sure your user belongs to one of these groups: %s."),
516 'write': _("You can not write in this document (%s) ! Be sure your user belongs to one of these groups: %s."),
517 'create': _("You can not create this document (%s) ! Be sure your user belongs to one of these groups: %s."),
518 'unlink': _("You can not delete this document (%s) ! Be sure your user belongs to one of these groups: %s."),
521 raise except_orm(_('AccessError'), msgs[mode] % (model_name, groups) )
524 __cache_clearing_methods = []
526 def register_cache_clearing_method(self, model, method):
527 self.__cache_clearing_methods.append((model, method))
529 def unregister_cache_clearing_method(self, model, method):
531 i = self.__cache_clearing_methods.index((model, method))
532 del self.__cache_clearing_methods[i]
536 def call_cache_clearing_methods(self, cr):
537 self.check.clear_cache(self) # clear the cache of check function
538 for model, method in self.__cache_clearing_methods:
539 object_ = self.pool.get(model)
541 getattr(object_, method)()
544 # Check rights on actions
546 def write(self, cr, uid, *args, **argv):
547 self.call_cache_clearing_methods(cr)
548 res = super(ir_model_access, self).write(cr, uid, *args, **argv)
551 def create(self, cr, uid, *args, **argv):
552 self.call_cache_clearing_methods(cr)
553 res = super(ir_model_access, self).create(cr, uid, *args, **argv)
556 def unlink(self, cr, uid, *args, **argv):
557 self.call_cache_clearing_methods(cr)
558 res = super(ir_model_access, self).unlink(cr, uid, *args, **argv)
563 class ir_model_data(osv.osv):
564 """Holds external identifier keys for records in the database.
565 This has two main uses:
567 * allows easy data integration with third-party systems,
568 making import/export/sync of data possible, as records
569 can be uniquely identified across multiple systems
570 * allows tracking the origin of data installed by OpenERP
571 modules themselves, thus making it possible to later
572 update them seamlessly.
574 _name = 'ir.model.data'
575 __logger = logging.getLogger('addons.base.'+_name)
576 _order = 'module,model,name'
578 'name': fields.char('External Identifier', required=True, size=128, select=1,
579 help="External Key/Identifier that can be used for "
580 "data integration with third-party systems"),
581 'model': fields.char('Model Name', required=True, size=64, select=1),
582 'module': fields.char('Module', required=True, size=64, select=1),
583 'res_id': fields.integer('Record ID', select=1,
584 help="ID of the target record in the database"),
585 'noupdate': fields.boolean('Non Updatable'),
586 'date_update': fields.datetime('Update Date'),
587 'date_init': fields.datetime('Init Date')
590 'date_init': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
591 'date_update': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
596 ('module_name_uniq', 'unique(name, module)', 'You cannot have multiple records with the same external ID in the same module!'),
599 def __init__(self, pool, cr):
600 osv.osv.__init__(self, pool, cr)
603 # also stored in pool to avoid being discarded along with this osv instance
604 if getattr(pool, 'model_data_reference_ids', None) is None:
605 self.pool.model_data_reference_ids = {}
606 self.loads = self.pool.model_data_reference_ids
608 def _auto_init(self, cr, context=None):
609 super(ir_model_data, self)._auto_init(cr, context)
610 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_model_data_module_name_index\'')
611 if not cr.fetchone():
612 cr.execute('CREATE INDEX ir_model_data_module_name_index ON ir_model_data (module, name)')
615 def _get_id(self, cr, uid, module, xml_id, context=None):
616 """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"""
617 ids = self.search(cr, uid, [('module','=',module), ('name','=', xml_id)])
619 raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
620 # the sql constraints ensure us we have only one result
624 def get_object_reference(self, cr, uid, module, xml_id, context=None):
625 """Returns (model, res_id) corresponding to a given module and xml_id (cached) or raise ValueError if not found"""
626 data_id = self._get_id(cr, uid, module, xml_id)
627 res = self.read(cr, uid, data_id, ['model', 'res_id'])
628 if not res['res_id']:
629 raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
630 return (res['model'], res['res_id'])
632 def get_object(self, cr, uid, module, xml_id, context=None):
633 """Returns a browsable record for the given module name and xml_id or raise ValueError if not found"""
634 res_model, res_id = self.get_object_reference(cr, uid, module, xml_id)
635 result = self.pool.get(res_model).browse(cr, uid, res_id, context=context)
636 if not result.exists():
637 raise ValueError('No record found for unique ID %s.%s. It may have been deleted.' % (module, xml_id))
640 def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
644 id = self.read(cr, uid, [self._get_id(cr, uid, module, xml_id)], ['res_id'])[0]['res_id']
645 self.loads[(module,xml_id)] = (model,id)
650 def unlink(self, cr, uid, ids, context=None):
651 """ Regular unlink method, but make sure to clear the caches. """
652 self._get_id.clear_cache(self)
653 self.get_object_reference.clear_cache(self)
654 return super(ir_model_data,self).unlink(cr, uid, ids, context=context)
656 def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None):
657 model_obj = self.pool.get(model)
661 # records created during module install should result in res.log entries that are already read!
662 context = dict(context, res_log_read=True)
664 if xml_id and ('.' in xml_id):
665 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)
666 module, xml_id = xml_id.split('.')
667 if (not xml_id) and (not self.doinit):
672 cr.execute('''SELECT imd.id, imd.res_id, md.id
673 FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
674 WHERE imd.module=%%s AND imd.name=%%s''' % model_obj._table,
676 results = cr.fetchall()
677 for imd_id2,res_id2,real_id2 in results:
679 self._get_id.clear_cache(self, uid, module, xml_id)
680 self.get_object_reference.clear_cache(self, uid, module, xml_id)
681 cr.execute('delete from ir_model_data where id=%s', (imd_id2,))
684 res_id,action_id = res_id2,imd_id2
686 if action_id and res_id:
687 model_obj.write(cr, uid, [res_id], values, context=context)
688 self.write(cr, uid, [action_id], {
689 'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
692 model_obj.write(cr, uid, [res_id], values, context=context)
694 self.create(cr, uid, {
699 'noupdate': noupdate,
701 if model_obj._inherits:
702 for table in model_obj._inherits:
703 inherit_id = model_obj.browse(cr, uid,
704 res_id,context=context)[model_obj._inherits[table]]
705 self.create(cr, uid, {
706 'name': xml_id + '_' + table.replace('.', '_'),
709 'res_id': inherit_id.id,
710 'noupdate': noupdate,
713 if mode=='init' or (mode=='update' and xml_id):
714 res_id = model_obj.create(cr, uid, values, context=context)
716 self.create(cr, uid, {
723 if model_obj._inherits:
724 for table in model_obj._inherits:
725 inherit_id = model_obj.browse(cr, uid,
726 res_id,context=context)[model_obj._inherits[table]]
727 self.create(cr, uid, {
728 'name': xml_id + '_' + table.replace('.', '_'),
731 'res_id': inherit_id.id,
732 'noupdate': noupdate,
736 self.loads[(module, xml_id)] = (model, res_id)
737 if model_obj._inherits:
738 for table in model_obj._inherits:
739 inherit_field = model_obj._inherits[table]
740 inherit_id = model_obj.read(cr, uid, res_id,
741 [inherit_field])[inherit_field]
742 self.loads[(module, xml_id + '_' + \
743 table.replace('.', '_'))] = (table, inherit_id)
746 def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None, xml_id=False):
747 if type(models[0])==type([]) or type(models[0])==type(()):
748 model,res_id = models[0]
754 where = ' and res_id=%s' % (res_id,)
756 where = ' and (res_id is null)'
759 where += ' and key2=\'%s\'' % (key2,)
761 where += ' and (key2 is null)'
763 cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
766 ir_values_obj = pooler.get_pool(cr.dbname).get('ir.values')
767 res = ir_values_obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
769 cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
772 def _process_end(self, cr, uid, modules):
773 """ Clear records removed from updated module data.
775 This method is called at the end of the module loading process.
776 It is meant to removed records that are no longer present in the
777 updated data. Such records are recognised as the one with an xml id
778 and a module in ir_model_data and noupdate set to false, but not
779 present in self.loads.
784 modules = list(modules)
785 module_in = ",".join(["%s"] * len(modules))
786 cr.execute('select id,name,model,res_id,module from ir_model_data where module IN (' + module_in + ') and noupdate=%s', modules + [False])
789 for (id, name, model, res_id,module) in cr.fetchall():
790 if (module,name) not in self.loads:
791 to_unlink.append((model,res_id))
792 if model=='workflow.activity':
793 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,))
794 wkf_todo.extend(cr.fetchall())
795 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))
796 cr.execute("delete from wkf_transition where act_to=%s", (res_id,))
798 for model,id in wkf_todo:
799 wf_service = netsvc.LocalService("workflow")
800 wf_service.trg_write(uid, model, id, cr)
803 if not config.get('import_partial'):
804 for (model, res_id) in to_unlink:
805 if self.pool.get(model):
806 self.__logger.info('Deleting %s@%s', res_id, model)
808 self.pool.get(model).unlink(cr, uid, [res_id])
813 'Could not delete obsolete record with id: %d of model %s\n'
814 'There should be some relation that points to this resource\n'
815 'You should manually fix this and restart with --update=module',
820 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: