[FIX] ir.model clear_cache use self
[odoo/odoo.git] / openerp / addons / base / ir / ir_model.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21 import logging
22 import re
23 import time
24
25 from osv import fields,osv
26 import netsvc
27 from osv.orm import except_orm, browse_record
28 import tools
29 from tools.safe_eval import safe_eval as eval
30 from tools import config
31 from tools.translate import _
32 import pooler
33
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)
41     return field_types
42
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)
49
50     result = {}
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)))
54     return result
55
56
57 class ir_model(osv.osv):
58     _name = 'ir.model'
59     _description = "Objects"
60     _order = 'model'
61
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)
65         for model in models:
66             res[model.id] = isinstance(self.pool.get(model.model), osv.osv_memory)
67         return res
68
69     def _search_osv_memory(self, cr, uid, model, name, domain, context=None):
70         if not domain:
71             return []
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])]
79
80     def _view_ids(self, cr, uid, ids, field_name, arg, context=None):
81         models = self.browse(cr, uid, ids)
82         res = {}
83         for model in models:
84             res[model.id] = self.pool.get("ir.ui.view").search(cr, uid, [('model', '=', model.model)])
85         return res
86
87     _columns = {
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'),
99     }
100     
101     _defaults = {
102         'model': lambda *a: 'x_',
103         'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
104     }
105     
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_'):
110                     return False
111             if not re.match('^[a-z_A-Z0-9.]+$',model.model):
112                 return False
113         return True
114
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 !')
117     _constraints = [
118         (_check_model_name, _model_name_msg, ['model']),
119     ]
120
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):
124         if args is None:
125             args = []
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)
129
130
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)
137         return res
138
139     def write(self, cr, user, ids, vals, context=None):
140         if context:
141             context.pop('__last_update', None)
142         return super(ir_model,self).write(cr, user, ids, vals, context)
143
144     def create(self, cr, user, vals, context=None):
145         if  context is None:
146             context = {}
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)
153             ctx = context.copy()
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)
157         return res
158
159     def instanciate(self, cr, user, model, context={}):
160         class x_custom_model(osv.osv):
161             pass
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()):
166             x_name = 'x_name'
167         else:
168             x_name = a._columns.keys()[0]
169         x_custom_model._rec_name = x_name
170 ir_model()
171
172 class ir_model_fields(osv.osv):
173     _name = 'ir.model.fields'
174     _description = "Fields"
175
176     _columns = {
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'),
205     }
206     _rec_name='field_description'
207     _defaults = {
208         'view_load': 0,
209         'selection': "",
210         'domain': "[]",
211         'name': 'x_',
212         'state': lambda self,cr,uid,ctx={}: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
213         'on_delete': 'set null',
214         'select_level': '0',
215         'size': 64,
216         'field_description': '',
217         'selectable': 1,
218     }
219     _order = "name"
220
221     def _check_selection(self, cr, uid, selection, context=None):
222         try:
223             selection_list = eval(selection)
224         except Exception:
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."))
229
230         check = True
231         if not (isinstance(selection_list, list) and selection_list):
232             check = False
233         else:
234             for item in selection_list:
235                 if not (isinstance(item, (tuple,list)) and len(item) == 2):
236                     check = False
237                     break
238
239         if not check:
240                 raise except_orm(_('Error'),
241                     _("The Selection Options expression is must be in the [('key','Label'), ...] format!"))
242         return True
243
244     def _size_gt_zero_msg(self, cr, user, ids, context=None):
245         return _('Size of the field can never be less than 1 !')
246
247     _sql_constraints = [
248         ('size_gt_zero', 'CHECK (size>0)',_size_gt_zero_msg ),
249     ]
250
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,))
255         #
256         # MAY BE ADD A ALTER TABLE DROP ?
257         #
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)
261
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
266         if context is None:
267             context = {}
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_' !"))
278
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'])
281
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
285                 ctx = context.copy()
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)
288
289         return res
290
291     def write(self, cr, user, ids, vals, context=None):
292         if context is None:
293             context = {}
294         if context and context.get('manual',False):
295             vals['state'] = 'manual'
296
297         column_rename = None # if set, *one* column can be renamed here
298         obj = None
299         models_patch = {}    # structs of (obj, [(field, prop, change_to),..])
300                              # data to be updated on the orm model
301
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),
315             ]
316
317         if vals and ids:
318             checked_selection = False # need only check it once, so defer
319
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)
323
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!'))
329
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
334
335                 final_name = item.name
336                 if 'name' in vals and vals['name'] != item.name:
337                     # We need to rename the column
338                     if column_rename:
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']
348
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!"))
351
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!"))
355
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.
358                 if obj:
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
367
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]
372
373         res = super(ir_model_fields,self).write(cr, user, ids, vals, context=context)
374
375         if column_rename:
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
381
382         if models_patch:
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).
388             ctx = context.copy()
389             ctx.update({'select': vals.get('select_level','0'),'update_custom_fields':True})
390
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)
396         return res
397
398 ir_model_fields()
399
400 class ir_model_access(osv.osv):
401     _name = 'ir.model.access'
402     _columns = {
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'),
410     }
411
412     def check_groups(self, cr, uid, group):
413         grouparr  = group.split('.')
414         if not grouparr:
415             return False
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())
418
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'
422
423         if isinstance(model, browse_record):
424             assert model._table_name == 'ir.model', 'Invalid model object'
425             model_name = model.name
426         else:
427             model_name = model
428
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)
436                    )
437             r = cr.fetchone()
438             if r is None:
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, )
443                        )
444                 r = cr.fetchone()
445
446             access = bool(r and r[0])
447             if access:
448                 return True
449         # pass no groups -> no access
450         return False
451
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``.
455            :rtype: list
456         """
457         assert access_mode in ['read','write','create','unlink'], 'Invalid access mode: %s' % access_mode
458         cr.execute('''SELECT
459                         g.name
460                       FROM
461                         ir_model_access a
462                         JOIN ir_model m ON (a.model_id=m.id)
463                         JOIN res_groups g ON (a.group_id=g.id)
464                       WHERE
465                         m.model=%s AND
466                         a.perm_''' + access_mode, (model_name,))
467         return [x[0] for x in cr.fetchall()]
468
469     @tools.ormcache()
470     def check(self, cr, uid, model, mode='read', raise_exception=True, context=None):
471         if uid==1:
472             # User root have all accesses
473             # TODO: exclude xml-rpc requests
474             return True
475
476         assert mode in ['read','write','create','unlink'], 'Invalid access mode'
477
478         if isinstance(model, browse_record):
479             assert model._table_name == 'ir.model', 'Invalid model object'
480             model_name = model.name
481         else:
482             model_name = model
483
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):
488             return True
489
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 '
496                    '   AND gu.uid = %s '
497                    , (model_name, uid,)
498                    )
499         r = cr.fetchone()[0]
500
501         if r is None:
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 '
507                        '   AND m.model = %s '
508                        , (model_name,)
509                        )
510             r = cr.fetchone()[0]
511
512         if not r and raise_exception:
513             groups = ', '.join(self.group_names_with_access(cr, model_name, mode)) or '/'
514             msgs = {
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."),
519             }
520
521             raise except_orm(_('AccessError'), msgs[mode] % (model_name, groups) )
522         return r
523
524     __cache_clearing_methods = []
525
526     def register_cache_clearing_method(self, model, method):
527         self.__cache_clearing_methods.append((model, method))
528
529     def unregister_cache_clearing_method(self, model, method):
530         try:
531             i = self.__cache_clearing_methods.index((model, method))
532             del self.__cache_clearing_methods[i]
533         except ValueError:
534             pass
535
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)
540             if object_:
541                 getattr(object_, method)()
542
543     #
544     # Check rights on actions
545     #
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)
549         return res
550
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)
554         return res
555
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)
559         return res
560
561 ir_model_access()
562
563 class ir_model_data(osv.osv):
564     _name = 'ir.model.data'
565     __logger = logging.getLogger('addons.base.'+_name)
566     _order = 'module,model,name'
567     _columns = {
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')
575     }
576     _defaults = {
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'),
579         'noupdate': False,
580         'module': ''
581     }
582     _sql_constraints = [
583         ('module_name_uniq', 'unique(name, module)', 'You cannot have multiple records with the same id for the same module !'),
584     ]
585
586     def __init__(self, pool, cr):
587         osv.osv.__init__(self, pool, cr)
588         self.doinit = True
589
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
594
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)')
600
601     @tools.ormcache()
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)])
605         if not ids:
606             raise ValueError('No references to %s.%s' % (module, xml_id))
607         # the sql constraints ensure us we have only one result
608         return ids[0]
609
610     @tools.ormcache()
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'])
618
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))
625         return result
626
627     def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
628         if not xml_id:
629             return False
630         try:
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)
633         except:
634             id = False
635         return id
636
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)
644
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)
647         if not context:
648             context = {}
649
650         # records created during module install should result in res.log entries that are already read!
651         context = dict(context, res_log_read=True)
652
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):
657             return False
658         action_id = False
659
660         if xml_id:
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,
664                           (module, xml_id))
665             results = cr.fetchall()
666             for imd_id2,res_id2,real_id2 in results:
667                 if not real_id2:
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,))
671                     res_id = False
672                 else:
673                     res_id,action_id = res_id2,imd_id2
674
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'),
679                 },context=context)
680         elif res_id:
681             model_obj.write(cr, uid, [res_id], values, context=context)
682             if xml_id:
683                 self.create(cr, uid, {
684                     'name': xml_id,
685                     'model': model,
686                     'module':module,
687                     'res_id':res_id,
688                     'noupdate': noupdate,
689                     },context=context)
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('.', '_'),
696                             'model': table,
697                             'module': module,
698                             'res_id': inherit_id.id,
699                             'noupdate': noupdate,
700                             },context=context)
701         else:
702             if mode=='init' or (mode=='update' and xml_id):
703                 res_id = model_obj.create(cr, uid, values, context=context)
704                 if xml_id:
705                     self.create(cr, uid, {
706                         'name': xml_id,
707                         'model': model,
708                         'module': module,
709                         'res_id': res_id,
710                         'noupdate': noupdate
711                         },context=context)
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('.', '_'),
718                                 'model': table,
719                                 'module': module,
720                                 'res_id': inherit_id.id,
721                                 'noupdate': noupdate,
722                                 },context=context)
723         if xml_id:
724             if res_id:
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)
733         return res_id
734
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]
738         else:
739             res_id=None
740             model = models[0]
741
742         if res_id:
743             where = ' and res_id=%s' % (res_id,)
744         else:
745             where = ' and (res_id is null)'
746
747         if key2:
748             where += ' and key2=\'%s\'' % (key2,)
749         else:
750             where += ' and (key2 is null)'
751
752         cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
753         res = cr.fetchone()
754         if not res:
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)
757         elif xml_id:
758             cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
759         return True
760
761     def _process_end(self, cr, uid, modules):
762         """ Clear records removed from updated module data.
763
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.
769
770         """
771         if not modules:
772             return True
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])
776         wkf_todo = []
777         to_unlink = []
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,))
786
787         for model,id in wkf_todo:
788             wf_service = netsvc.LocalService("workflow")
789             wf_service.trg_write(uid, model, id, cr)
790
791         cr.commit()
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)
796                     try:
797                         self.pool.get(model).unlink(cr, uid, [res_id])
798                         cr.commit()
799                     except Exception:
800                         cr.rollback()
801                         self.__logger.warn(
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',
805                             res_id, model)
806         return True
807 ir_model_data()
808
809 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: