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 # this test should be removed, but check if drop view instead of drop table
142 # just check if table or view exists
143 cr.execute("select relkind from pg_class where relname=%s", (model_pool._table,))
144 result = cr.fetchone()
145 if result and result[0] == 'v':
146 cr.execute("DROP view %s" % (model_pool._table,))
147 elif result and result[0] == 'r':
148 cr.execute("DROP TABLE %s" % (model_pool._table,))
151 def unlink(self, cr, user, ids, context=None):
152 # for model in self.browse(cr, user, ids, context):
153 # if model.state != 'manual':
154 # raise except_orm(_('Error'), _("You can not remove the model '%s' !") %(model.name,))
155 self._drop_table(cr, user, ids, context)
156 res = super(ir_model, self).unlink(cr, user, ids, context)
157 pooler.restart_pool(cr.dbname)
160 def write(self, cr, user, ids, vals, context=None):
162 context.pop('__last_update', None)
163 # Filter out operations 4 link from field id, because openerp-web
164 # always write (4,id,False) even for non dirty items
165 if 'field_id' in vals:
166 vals['field_id'] = [op for op in vals['field_id'] if op[0] != 4]
167 return super(ir_model,self).write(cr, user, ids, vals, context)
169 def create(self, cr, user, vals, context=None):
172 if context and context.get('manual',False):
173 vals['state']='manual'
174 res = super(ir_model,self).create(cr, user, vals, context)
175 if vals.get('state','base')=='manual':
176 self.instanciate(cr, user, vals['model'], context)
177 self.pool.get(vals['model']).__init__(self.pool, cr)
179 ctx.update({'field_name':vals['name'],'field_state':'manual','select':vals.get('select_level','0')})
180 self.pool.get(vals['model'])._auto_init(cr, ctx)
181 #pooler.restart_pool(cr.dbname)
184 def instanciate(self, cr, user, model, context=None):
185 class x_custom_model(osv.osv):
187 x_custom_model._name = model
188 x_custom_model._module = False
189 a = x_custom_model.create_instance(self.pool, cr)
190 if (not a._columns) or ('x_name' in a._columns.keys()):
193 x_name = a._columns.keys()[0]
194 x_custom_model._rec_name = x_name
197 class ir_model_fields(osv.osv):
198 _name = 'ir.model.fields'
199 _description = "Fields"
202 'name': fields.char('Name', required=True, size=64, select=1),
203 'model': fields.char('Object Name', size=64, required=True, select=1,
204 help="The technical name of the model this field belongs to"),
205 'relation': fields.char('Object Relation', size=64,
206 help="For relationship fields, the technical name of the target model"),
207 'relation_field': fields.char('Relation Field', size=64,
208 help="For one2many fields, the field on the target model that implement the opposite many2one relationship"),
209 'model_id': fields.many2one('ir.model', 'Model', required=True, select=True, ondelete='cascade',
210 help="The model this field belongs to"),
211 'field_description': fields.char('Field Label', required=True, size=256),
212 'ttype': fields.selection(_get_fields_type, 'Field Type',size=64, required=True),
213 'selection': fields.char('Selection Options',size=128, help="List of options for a selection field, "
214 "specified as a Python expression defining a list of (key, label) pairs. "
215 "For example: [('blue','Blue'),('yellow','Yellow')]"),
216 'required': fields.boolean('Required'),
217 'readonly': fields.boolean('Readonly'),
218 'select_level': fields.selection([('0','Not Searchable'),('1','Always Searchable'),('2','Advanced Search (deprecated)')],'Searchable', required=True),
219 'translate': fields.boolean('Translate', help="Whether values for this field can be translated (enables the translation mechanism for that field)"),
220 'size': fields.integer('Size'),
221 'state': fields.selection([('manual','Custom Field'),('base','Base Field')],'Type', required=True, readonly=True, select=1),
222 'on_delete': fields.selection([('cascade','Cascade'),('set null','Set NULL')], 'On delete', help='On delete property for many2one fields'),
223 'domain': fields.char('Domain', size=256, help="The optional domain to restrict possible values for relationship fields, "
224 "specified as a Python expression defining a list of triplets. "
225 "For example: [('color','=','red')]"),
226 'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'),
227 'view_load': fields.boolean('View Auto-Load'),
228 'selectable': fields.boolean('Selectable'),
229 'modules': fields.function(_in_modules, type='char', size=128, string='In modules', help='List of modules in which the field is defined'),
230 'serialization_field_id': fields.many2one('ir.model.fields', 'Serialization Field', domain = "[('ttype','=','serialized')]",
231 ondelete='cascade', help="If set, this field will be stored in the sparse "
232 "structure of the serialization field, instead "
233 "of having its own database column. This cannot be "
234 "changed after creation."),
236 _rec_name='field_description'
242 'state': lambda self,cr,uid,ctx={}: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
243 'on_delete': 'set null',
246 'field_description': '',
251 def _check_selection(self, cr, uid, selection, context=None):
253 selection_list = eval(selection)
255 _logger.warning('Invalid selection list definition for fields.selection', exc_info=True)
256 raise except_orm(_('Error'),
257 _("The Selection Options expression is not a valid Pythonic expression." \
258 "Please provide an expression in the [('key','Label'), ...] format."))
261 if not (isinstance(selection_list, list) and selection_list):
264 for item in selection_list:
265 if not (isinstance(item, (tuple,list)) and len(item) == 2):
270 raise except_orm(_('Error'),
271 _("The Selection Options expression is must be in the [('key','Label'), ...] format!"))
274 def _size_gt_zero_msg(self, cr, user, ids, context=None):
275 return _('Size of the field can never be less than 1 !')
278 ('size_gt_zero', 'CHECK (size>0)',_size_gt_zero_msg ),
281 def _drop_column(self, cr, uid, ids, context=None):
282 field = self.browse(cr, uid, ids, context)
283 model = self.pool.get(field.model)
284 cr.execute("select relkind from pg_class where relname=%s", (model._table,))
285 result = cr.fetchone()[0]
286 cr.execute("SELECT column_name FROM information_schema.columns WHERE table_name ='%s'and column_name='%s'"%(model._table, field.name))
287 column_name = cr.fetchone()
288 if column_name and result == 'r':
289 cr.execute("ALTER table %s DROP column %s cascade" % (model._table, field.name))
290 model._columns.pop(field.name, None)
293 def unlink(self, cr, user, ids, context=None):
294 self._drop_column(cr, user, ids, context)
295 res = super(ir_model_fields, self).unlink(cr, user, ids, context)
298 def create(self, cr, user, vals, context=None):
299 if 'model_id' in vals:
300 model_data = self.pool.get('ir.model').browse(cr, user, vals['model_id'])
301 vals['model'] = model_data.model
304 if context and context.get('manual',False):
305 vals['state'] = 'manual'
306 if vals.get('ttype', False) == 'selection':
307 if not vals.get('selection',False):
308 raise except_orm(_('Error'), _('For selection fields, the Selection Options must be given!'))
309 self._check_selection(cr, user, vals['selection'], context=context)
310 res = super(ir_model_fields,self).create(cr, user, vals, context)
311 if vals.get('state','base') == 'manual':
312 if not vals['name'].startswith('x_'):
313 raise except_orm(_('Error'), _("Custom fields must have a name that starts with 'x_' !"))
315 if vals.get('relation',False) and not self.pool.get('ir.model').search(cr, user, [('model','=',vals['relation'])]):
316 raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
318 if self.pool.get(vals['model']):
319 self.pool.get(vals['model']).__init__(self.pool, cr)
320 #Added context to _auto_init for special treatment to custom field for select_level
322 ctx.update({'field_name':vals['name'],'field_state':'manual','select':vals.get('select_level','0'),'update_custom_fields':True})
323 self.pool.get(vals['model'])._auto_init(cr, ctx)
327 def write(self, cr, user, ids, vals, context=None):
330 if context and context.get('manual',False):
331 vals['state'] = 'manual'
333 #For the moment renaming a sparse field or changing the storing system is not allowed. This may be done later
334 if 'serialization_field_id' in vals or 'name' in vals:
335 for field in self.browse(cr, user, ids, context=context):
336 if 'serialization_field_id' in vals and field.serialization_field_id.id != vals['serialization_field_id']:
337 raise except_orm(_('Error!'), _('Changing the storing system for field "%s" is not allowed.')%field.name)
338 if field.serialization_field_id and (field.name != vals['name']):
339 raise except_orm(_('Error!'), _('Renaming sparse field "%s" is not allowed')%field.name)
341 column_rename = None # if set, *one* column can be renamed here
343 models_patch = {} # structs of (obj, [(field, prop, change_to),..])
344 # data to be updated on the orm model
346 # static table of properties
347 model_props = [ # (our-name, fields.prop, set_fn)
348 ('field_description', 'string', str),
349 ('required', 'required', bool),
350 ('readonly', 'readonly', bool),
351 ('domain', '_domain', eval),
352 ('size', 'size', int),
353 ('on_delete', 'ondelete', str),
354 ('translate', 'translate', bool),
355 ('view_load', 'view_load', bool),
356 ('selectable', 'selectable', bool),
357 ('select_level', 'select', int),
358 ('selection', 'selection', eval),
362 checked_selection = False # need only check it once, so defer
364 for item in self.browse(cr, user, ids, context=context):
365 if not (obj and obj._name == item.model):
366 obj = self.pool.get(item.model)
368 if item.state != 'manual':
369 raise except_orm(_('Error!'),
370 _('Properties of base fields cannot be altered in this manner! '
371 'Please modify them through Python code, '
372 'preferably through a custom addon!'))
374 if item.ttype == 'selection' and 'selection' in vals \
375 and not checked_selection:
376 self._check_selection(cr, user, vals['selection'], context=context)
377 checked_selection = True
379 final_name = item.name
380 if 'name' in vals and vals['name'] != item.name:
381 # We need to rename the column
383 raise except_orm(_('Error!'), _('Can only rename one column at a time!'))
384 if vals['name'] in obj._columns:
385 raise except_orm(_('Error!'), _('Cannot rename column to %s, because that column already exists!') % vals['name'])
386 if vals.get('state', 'base') == 'manual' and not vals['name'].startswith('x_'):
387 raise except_orm(_('Error!'), _('New column name must still start with x_ , because it is a custom field!'))
388 if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']:
389 raise ValueError('Invalid character in column name')
390 column_rename = (obj, (obj._table, item.name, vals['name']))
391 final_name = vals['name']
393 if 'model_id' in vals and vals['model_id'] != item.model_id:
394 raise except_orm(_("Error!"), _("Changing the model of a field is forbidden!"))
396 if 'ttype' in vals and vals['ttype'] != item.ttype:
397 raise except_orm(_("Error!"), _("Changing the type of a column is not yet supported. "
398 "Please drop it and create it again!"))
400 # We don't check the 'state', because it might come from the context
401 # (thus be set for multiple fields) and will be ignored anyway.
403 models_patch.setdefault(obj._name, (obj,[]))
404 # find out which properties (per model) we need to update
405 for field_name, field_property, set_fn in model_props:
406 if field_name in vals:
407 property_value = set_fn(vals[field_name])
408 if getattr(obj._columns[item.name], field_property) != property_value:
409 models_patch[obj._name][1].append((final_name, field_property, property_value))
410 # our dict is ready here, but no properties are changed so far
412 # These shall never be written (modified)
413 for column_name in ('model_id', 'model', 'state'):
414 if column_name in vals:
415 del vals[column_name]
417 res = super(ir_model_fields,self).write(cr, user, ids, vals, context=context)
420 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % column_rename[1])
421 # This is VERY risky, but let us have this feature:
422 # we want to change the key of column in obj._columns dict
423 col = column_rename[0]._columns.pop(column_rename[1][1]) # take object out, w/o copy
424 column_rename[0]._columns[column_rename[1][2]] = col
427 # We have to update _columns of the model(s) and then call their
428 # _auto_init to sync the db with the model. Hopefully, since write()
429 # was called earlier, they will be in-sync before the _auto_init.
430 # Anything we don't update in _columns now will be reset from
431 # the model into ir.model.fields (db).
433 ctx.update({'select': vals.get('select_level','0'),'update_custom_fields':True})
435 for model_key, patch_struct in models_patch.items():
436 obj = patch_struct[0]
437 for col_name, col_prop, val in patch_struct[1]:
438 setattr(obj._columns[col_name], col_prop, val)
439 obj._auto_init(cr, ctx)
444 class ir_model_access(osv.osv):
445 _name = 'ir.model.access'
447 'name': fields.char('Name', size=64, required=True, select=True),
448 'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)], select=True, ondelete='cascade'),
449 'group_id': fields.many2one('res.groups', 'Group', ondelete='cascade', select=True),
450 'perm_read': fields.boolean('Read Access'),
451 'perm_write': fields.boolean('Write Access'),
452 'perm_create': fields.boolean('Create Access'),
453 'perm_unlink': fields.boolean('Delete Access'),
456 def check_groups(self, cr, uid, group):
457 grouparr = group.split('.')
460 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],))
461 return bool(cr.fetchone())
463 def check_group(self, cr, uid, model, mode, group_ids):
464 """ Check if a specific group has the access mode to the specified model"""
465 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
467 if isinstance(model, browse_record):
468 assert model._table_name == 'ir.model', 'Invalid model object'
469 model_name = model.name
473 if isinstance(group_ids, (int, long)):
474 group_ids = [group_ids]
475 for group_id in group_ids:
476 cr.execute("SELECT perm_" + mode + " "
477 " FROM ir_model_access a "
478 " JOIN ir_model m ON (m.id = a.model_id) "
479 " WHERE m.model = %s AND a.group_id = %s", (model_name, group_id)
483 cr.execute("SELECT perm_" + mode + " "
484 " FROM ir_model_access a "
485 " JOIN ir_model m ON (m.id = a.model_id) "
486 " WHERE m.model = %s AND a.group_id IS NULL", (model_name, )
490 access = bool(r and r[0])
493 # pass no groups -> no access
496 def group_names_with_access(self, cr, model_name, access_mode):
497 """Returns the names of visible groups which have been granted ``access_mode`` on
498 the model ``model_name``.
501 assert access_mode in ['read','write','create','unlink'], 'Invalid access mode: %s' % access_mode
506 JOIN ir_model m ON (a.model_id=m.id)
507 JOIN res_groups g ON (a.group_id=g.id)
510 a.perm_''' + access_mode, (model_name,))
511 return [x[0] for x in cr.fetchall()]
514 def check(self, cr, uid, model, mode='read', raise_exception=True, context=None):
516 # User root have all accesses
517 # TODO: exclude xml-rpc requests
520 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
522 if isinstance(model, browse_record):
523 assert model._table_name == 'ir.model', 'Invalid model object'
524 model_name = model.model
528 # TransientModel records have no access rights, only an implicit access rule
529 if self.pool.get(model_name).is_transient():
532 # We check if a specific rule exists
533 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
534 ' FROM ir_model_access a '
535 ' JOIN ir_model m ON (m.id = a.model_id) '
536 ' JOIN res_groups_users_rel gu ON (gu.gid = a.group_id) '
537 ' WHERE m.model = %s '
544 # there is no specific rule. We check the generic rule
545 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
546 ' FROM ir_model_access a '
547 ' JOIN ir_model m ON (m.id = a.model_id) '
548 ' WHERE a.group_id IS NULL '
554 if not r and raise_exception:
555 groups = ', '.join(self.group_names_with_access(cr, model_name, mode)) or '/'
557 'read': _("You can not read this document (%s) ! Be sure your user belongs to one of these groups: %s."),
558 'write': _("You can not write in this document (%s) ! Be sure your user belongs to one of these groups: %s."),
559 'create': _("You can not create this document (%s) ! Be sure your user belongs to one of these groups: %s."),
560 'unlink': _("You can not delete this document (%s) ! Be sure your user belongs to one of these groups: %s."),
563 raise except_orm(_('AccessError'), msgs[mode] % (model_name, groups) )
566 __cache_clearing_methods = []
568 def register_cache_clearing_method(self, model, method):
569 self.__cache_clearing_methods.append((model, method))
571 def unregister_cache_clearing_method(self, model, method):
573 i = self.__cache_clearing_methods.index((model, method))
574 del self.__cache_clearing_methods[i]
578 def call_cache_clearing_methods(self, cr):
579 self.check.clear_cache(self) # clear the cache of check function
580 for model, method in self.__cache_clearing_methods:
581 object_ = self.pool.get(model)
583 getattr(object_, method)()
586 # Check rights on actions
588 def write(self, cr, uid, *args, **argv):
589 self.call_cache_clearing_methods(cr)
590 res = super(ir_model_access, self).write(cr, uid, *args, **argv)
593 def create(self, cr, uid, *args, **argv):
594 self.call_cache_clearing_methods(cr)
595 res = super(ir_model_access, self).create(cr, uid, *args, **argv)
598 def unlink(self, cr, uid, *args, **argv):
599 self.call_cache_clearing_methods(cr)
600 res = super(ir_model_access, self).unlink(cr, uid, *args, **argv)
605 class ir_model_data(osv.osv):
606 """Holds external identifier keys for records in the database.
607 This has two main uses:
609 * allows easy data integration with third-party systems,
610 making import/export/sync of data possible, as records
611 can be uniquely identified across multiple systems
612 * allows tracking the origin of data installed by OpenERP
613 modules themselves, thus making it possible to later
614 update them seamlessly.
616 _name = 'ir.model.data'
617 _order = 'module,model,name'
619 'name': fields.char('External Identifier', required=True, size=128, select=1,
620 help="External Key/Identifier that can be used for "
621 "data integration with third-party systems"),
622 'model': fields.char('Model Name', required=True, size=64, select=1),
623 'module': fields.char('Module', required=True, size=64, select=1),
624 'res_id': fields.integer('Record ID', select=1,
625 help="ID of the target record in the database"),
626 'noupdate': fields.boolean('Non Updatable'),
627 'date_update': fields.datetime('Update Date'),
628 'date_init': fields.datetime('Init Date')
631 'date_init': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
632 'date_update': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
637 ('module_name_uniq', 'unique(name, module)', 'You cannot have multiple records with the same external ID in the same module!'),
640 def __init__(self, pool, cr):
641 osv.osv.__init__(self, pool, cr)
643 # also stored in pool to avoid being discarded along with this osv instance
644 if getattr(pool, 'model_data_reference_ids', None) is None:
645 self.pool.model_data_reference_ids = {}
647 self.loads = self.pool.model_data_reference_ids
649 def _auto_init(self, cr, context=None):
650 super(ir_model_data, self)._auto_init(cr, context)
651 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_model_data_module_name_index\'')
652 if not cr.fetchone():
653 cr.execute('CREATE INDEX ir_model_data_module_name_index ON ir_model_data (module, name)')
656 def _get_id(self, cr, uid, module, xml_id):
657 """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"""
658 ids = self.search(cr, uid, [('module','=',module), ('name','=', xml_id)])
660 raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
661 # the sql constraints ensure us we have only one result
665 def get_object_reference(self, cr, uid, module, xml_id):
666 """Returns (model, res_id) corresponding to a given module and xml_id (cached) or raise ValueError if not found"""
667 data_id = self._get_id(cr, uid, module, xml_id)
668 res = self.read(cr, uid, data_id, ['model', 'res_id'])
669 if not res['res_id']:
670 raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
671 return (res['model'], res['res_id'])
673 def get_object(self, cr, uid, module, xml_id, context=None):
674 """Returns a browsable record for the given module name and xml_id or raise ValueError if not found"""
675 res_model, res_id = self.get_object_reference(cr, uid, module, xml_id)
676 result = self.pool.get(res_model).browse(cr, uid, res_id, context=context)
677 if not result.exists():
678 raise ValueError('No record found for unique ID %s.%s. It may have been deleted.' % (module, xml_id))
681 def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
685 id = self.read(cr, uid, [self._get_id(cr, uid, module, xml_id)], ['res_id'])[0]['res_id']
686 self.loads[(module,xml_id)] = (model,id)
692 def unlink(self, cr, uid, ids, context=None):
693 """ Regular unlink method, but make sure to clear the caches. """
694 self._get_id.clear_cache(self)
695 self.get_object_reference.clear_cache(self)
696 return super(ir_model_data,self).unlink(cr, uid, ids, context=context)
698 def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None):
699 model_obj = self.pool.get(model)
702 # records created during module install should result in res.log entries that are already read!
703 context = dict(context, res_log_read=True)
704 if xml_id and ('.' in xml_id):
705 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)
706 module, xml_id = xml_id.split('.')
707 if (not xml_id) and (not self.doinit):
711 cr.execute('''SELECT imd.id, imd.res_id, md.id, imd.model
712 FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
713 WHERE imd.module=%%s AND imd.name=%%s''' % model_obj._table,
715 results = cr.fetchall()
716 for imd_id2,res_id2,real_id2,real_model in results:
718 self._get_id.clear_cache(self, uid, module, xml_id)
719 self.get_object_reference.clear_cache(self, uid, module, xml_id)
720 cr.execute('delete from ir_model_data where id=%s', (imd_id2,))
723 assert model == real_model, "External ID conflict, %s already refers to a `%s` record,"\
724 " you can't define a `%s` record with this ID." % (xml_id, real_model, model)
725 res_id,action_id = res_id2,imd_id2
727 if action_id and res_id:
728 model_obj.write(cr, uid, [res_id], values, context=context)
729 self.write(cr, uid, [action_id], {
730 'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
733 model_obj.write(cr, uid, [res_id], values, context=context)
735 self.create(cr, uid, {
740 'noupdate': noupdate,
742 if model_obj._inherits:
743 for table in model_obj._inherits:
744 inherit_id = model_obj.browse(cr, uid,
745 res_id,context=context)[model_obj._inherits[table]]
746 self.create(cr, uid, {
747 'name': xml_id + '_' + table.replace('.', '_'),
750 'res_id': inherit_id.id,
751 'noupdate': noupdate,
754 if mode=='init' or (mode=='update' and xml_id):
755 res_id = model_obj.create(cr, uid, values, context=context)
757 self.create(cr, uid, {
764 if model_obj._inherits:
765 for table in model_obj._inherits:
766 inherit_id = model_obj.browse(cr, uid,
767 res_id,context=context)[model_obj._inherits[table]]
768 self.create(cr, uid, {
769 'name': xml_id + '_' + table.replace('.', '_'),
772 'res_id': inherit_id.id,
773 'noupdate': noupdate,
777 self.loads[(module, xml_id)] = (model, res_id)
778 if model_obj._inherits:
779 for table in model_obj._inherits:
780 inherit_field = model_obj._inherits[table]
781 inherit_id = model_obj.read(cr, uid, res_id,
782 [inherit_field])[inherit_field]
783 self.loads[(module, xml_id + '_' + \
784 table.replace('.', '_'))] = (table, inherit_id)
787 def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None, xml_id=False):
788 if type(models[0])==type([]) or type(models[0])==type(()):
789 model,res_id = models[0]
795 where = ' and res_id=%s' % (res_id,)
797 where = ' and (res_id is null)'
800 where += ' and key2=\'%s\'' % (key2,)
802 where += ' and (key2 is null)'
804 cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
807 ir_values_obj = pooler.get_pool(cr.dbname).get('ir.values')
808 res = ir_values_obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
810 cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
813 def _pre_process_unlink(self, cr, uid, ids, context=None):
819 for data in self.browse(cr, uid, ids, context):
822 model_obj = self.pool.get(model)
824 if str(name).startswith('foreign_key_'):
826 # test if constraint exists
827 cr.execute('select conname from pg_constraint where contype=%s and conname=%s',('f', name),)
829 cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model,name),)
830 _logger.info('Drop CONSTRAINT %s@%s', name, model)
833 if str(name).startswith('table_'):
834 cr.execute("SELECT table_name FROM information_schema.tables WHERE table_name='%s'"%(name[6:]))
835 column_name = cr.fetchone()
837 to_drop_table.append(name[6:])
840 if str(name).startswith('constraint_'):
841 # test if constraint exists
842 cr.execute('select conname from pg_constraint where contype=%s and conname=%s',('u', name),)
844 cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table,name[11:]),)
845 _logger.info('Drop CONSTRAINT %s@%s', name[11:], model)
848 to_unlink.append((model, res_id))
849 if model=='workflow.activity':
850 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,))
851 wkf_todo.extend(cr.fetchall())
852 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))
854 for model,res_id in wkf_todo:
855 wf_service = netsvc.LocalService("workflow")
857 wf_service.trg_write(uid, model, res_id, cr)
859 _logger.info('Unable to process workflow %s@%s', res_id, model)
861 # drop relation .table
862 for model in to_drop_table:
863 cr.execute('DROP TABLE %s cascade'% (model),)
864 _logger.info('Dropping table %s', model)
866 for (model, res_id) in to_unlink:
867 if model in ('ir.model','ir.model.fields', 'ir.model.data'):
869 model_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
870 if len(model_ids) > 1:
871 # if others module have defined this record, we do not delete it
873 _logger.info('Deleting %s@%s', res_id, model)
875 self.pool.get(model).unlink(cr, uid, res_id)
877 _logger.info('Unable to delete %s@%s', res_id, model)
880 for (model, res_id) in to_unlink:
881 if model not in ('ir.model.fields',):
883 model_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
884 if len(model_ids) > 1:
885 # if others module have defined this record, we do not delete it
887 _logger.info('Deleting %s@%s', res_id, model)
888 self.pool.get(model).unlink(cr, uid, res_id)
890 for (model, res_id) in to_unlink:
891 if model not in ('ir.model',):
893 model_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
894 if len(model_ids) > 1:
895 # if others module have defined this record, we do not delete it
897 _logger.info('Deleting %s@%s', res_id, model)
898 self.pool.get(model).unlink(cr, uid, [res_id])
901 def _process_end(self, cr, uid, modules):
902 """ Clear records removed from updated module data.
903 This method is called at the end of the module loading process.
904 It is meant to removed records that are no longer present in the
905 updated data. Such records are recognised as the one with an xml id
906 and a module in ir_model_data and noupdate set to false, but not
907 present in self.loads.
911 modules = list(modules)
912 data_ids = self.search(cr, uid, [('module','in',modules)])
913 module_in = ",".join(["%s"] * len(modules))
914 process_query = 'select id,name,model,res_id,module from ir_model_data where module IN (' + module_in + ')'
915 process_query+= ' and noupdate=%s'
917 cr.execute(process_query, modules + [False])
918 for (id, name, model, res_id,module) in cr.fetchall():
919 if (module,name) not in self.loads:
920 to_unlink.append((model,res_id))
921 if not config.get('import_partial'):
922 for (model, res_id) in to_unlink:
923 if self.pool.get(model):
924 _logger.info('Deleting %s@%s', res_id, model)
925 self.pool.get(model).unlink(cr, uid, [res_id])
929 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: