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 _name = 'ir.model.data'
565 __logger = logging.getLogger('addons.base.'+_name)
566 _order = 'module,model,name'
568 'name': fields.char('XML Identifier', required=True, size=128, select=1),
569 'model': fields.char('Object', required=True, size=64, select=1),
570 'module': fields.char('Module', required=True, size=64, select=1),
571 'res_id': fields.integer('Resource ID', select=1),
572 'noupdate': fields.boolean('Non Updatable'),
573 'date_update': fields.datetime('Update Date'),
574 'date_init': fields.datetime('Init Date')
577 'date_init': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
578 'date_update': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
583 ('module_name_uniq', 'unique(name, module)', 'You cannot have multiple records with the same id for the same module !'),
586 def __init__(self, pool, cr):
587 osv.osv.__init__(self, pool, cr)
590 # also stored in pool to avoid being discarded along with this osv instance
591 if getattr(pool, 'model_data_reference_ids', None) is None:
592 self.pool.model_data_reference_ids = {}
593 self.loads = self.pool.model_data_reference_ids
595 def _auto_init(self, cr, context=None):
596 super(ir_model_data, self)._auto_init(cr, context)
597 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_model_data_module_name_index\'')
598 if not cr.fetchone():
599 cr.execute('CREATE INDEX ir_model_data_module_name_index ON ir_model_data (module, name)')
602 def _get_id(self, cr, uid, module, xml_id):
603 """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"""
604 ids = self.search(cr, uid, [('module','=',module), ('name','=', xml_id)])
606 raise ValueError('No references to %s.%s' % (module, xml_id))
607 # the sql constraints ensure us we have only one result
611 def get_object_reference(self, cr, uid, module, xml_id):
612 """Returns (model, res_id) corresponding to a given module and xml_id (cached) or raise ValueError if not found"""
613 data_id = self._get_id(cr, uid, module, xml_id)
614 res = self.read(cr, uid, data_id, ['model', 'res_id'])
615 if not res['res_id']:
616 raise ValueError('No references to %s.%s' % (module, xml_id))
617 return (res['model'], res['res_id'])
619 def get_object(self, cr, uid, module, xml_id, context=None):
620 """Returns a browsable record for the given module name and xml_id or raise ValueError if not found"""
621 res_model, res_id = self.get_object_reference(cr, uid, module, xml_id)
622 result = self.pool.get(res_model).browse(cr, uid, res_id, context=context)
623 if not result.exists():
624 raise ValueError('No record found for unique ID %s.%s. It may have been deleted.' % (module, xml_id))
627 def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
631 id = self.read(cr, uid, [self._get_id(cr, uid, module, xml_id)], ['res_id'])[0]['res_id']
632 self.loads[(module,xml_id)] = (model,id)
637 def unlink(self, cr, uid, ids, context=None):
638 """ Regular unlink method, but make sure to clear the caches. """
639 ref_ids = self.browse(cr, uid, ids, context=context)
640 for ref_id in ref_ids:
641 self._get_id.clear_cache(self, uid, ref_id.module, ref_id.name)
642 self.get_object_reference.clear_cache(self, uid, ref_id.module, ref_id.name)
643 return super(ir_model_data,self).unlink(cr, uid, ids, context=context)
645 def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None):
646 model_obj = self.pool.get(model)
650 # records created during module install should result in res.log entries that are already read!
651 context = dict(context, res_log_read=True)
653 if xml_id and ('.' in xml_id):
654 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)
655 module, xml_id = xml_id.split('.')
656 if (not xml_id) and (not self.doinit):
661 cr.execute('''SELECT imd.id, imd.res_id, md.id
662 FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
663 WHERE imd.module=%%s AND imd.name=%%s''' % model_obj._table,
665 results = cr.fetchall()
666 for imd_id2,res_id2,real_id2 in results:
668 self._get_id.clear_cache(self, uid, module, xml_id)
669 self.get_object_reference.clear_cache(self, uid, module, xml_id)
670 cr.execute('delete from ir_model_data where id=%s', (imd_id2,))
673 res_id,action_id = res_id2,imd_id2
675 if action_id and res_id:
676 model_obj.write(cr, uid, [res_id], values, context=context)
677 self.write(cr, uid, [action_id], {
678 'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
681 model_obj.write(cr, uid, [res_id], values, context=context)
683 self.create(cr, uid, {
688 'noupdate': noupdate,
690 if model_obj._inherits:
691 for table in model_obj._inherits:
692 inherit_id = model_obj.browse(cr, uid,
693 res_id,context=context)[model_obj._inherits[table]]
694 self.create(cr, uid, {
695 'name': xml_id + '_' + table.replace('.', '_'),
698 'res_id': inherit_id.id,
699 'noupdate': noupdate,
702 if mode=='init' or (mode=='update' and xml_id):
703 res_id = model_obj.create(cr, uid, values, context=context)
705 self.create(cr, uid, {
712 if model_obj._inherits:
713 for table in model_obj._inherits:
714 inherit_id = model_obj.browse(cr, uid,
715 res_id,context=context)[model_obj._inherits[table]]
716 self.create(cr, uid, {
717 'name': xml_id + '_' + table.replace('.', '_'),
720 'res_id': inherit_id.id,
721 'noupdate': noupdate,
725 self.loads[(module, xml_id)] = (model, res_id)
726 if model_obj._inherits:
727 for table in model_obj._inherits:
728 inherit_field = model_obj._inherits[table]
729 inherit_id = model_obj.read(cr, uid, res_id,
730 [inherit_field])[inherit_field]
731 self.loads[(module, xml_id + '_' + \
732 table.replace('.', '_'))] = (table, inherit_id)
735 def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None, xml_id=False):
736 if type(models[0])==type([]) or type(models[0])==type(()):
737 model,res_id = models[0]
743 where = ' and res_id=%s' % (res_id,)
745 where = ' and (res_id is null)'
748 where += ' and key2=\'%s\'' % (key2,)
750 where += ' and (key2 is null)'
752 cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
755 ir_values_obj = pooler.get_pool(cr.dbname).get('ir.values')
756 res = ir_values_obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
758 cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
761 def _process_end(self, cr, uid, modules):
762 """ Clear records removed from updated module data.
764 This method is called at the end of the module loading process.
765 It is meant to removed records that are no longer present in the
766 updated data. Such records are recognised as the one with an xml id
767 and a module in ir_model_data and noupdate set to false, but not
768 present in self.loads.
773 modules = list(modules)
774 module_in = ",".join(["%s"] * len(modules))
775 cr.execute('select id,name,model,res_id,module from ir_model_data where module IN (' + module_in + ') and noupdate=%s', modules + [False])
778 for (id, name, model, res_id,module) in cr.fetchall():
779 if (module,name) not in self.loads:
780 to_unlink.append((model,res_id))
781 if model=='workflow.activity':
782 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,))
783 wkf_todo.extend(cr.fetchall())
784 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))
785 cr.execute("delete from wkf_transition where act_to=%s", (res_id,))
787 for model,id in wkf_todo:
788 wf_service = netsvc.LocalService("workflow")
789 wf_service.trg_write(uid, model, id, cr)
792 if not config.get('import_partial'):
793 for (model, res_id) in to_unlink:
794 if self.pool.get(model):
795 self.__logger.info('Deleting %s@%s', res_id, model)
797 self.pool.get(model).unlink(cr, uid, [res_id])
802 'Could not delete obsolete record with id: %d of model %s\n'
803 'There should be some relation that points to this resource\n'
804 'You should manually fix this and restart with --update=module',
809 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: