[FIX] merge clear_cache call within 'ir.model.data' unlink - as there is not support...
[odoo/odoo.git] / bin / 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 from operator import itemgetter
25
26 from osv import fields,osv
27 import ir
28 import netsvc
29 from osv.orm import except_orm, browse_record
30 import tools
31 from tools.safe_eval import safe_eval as eval
32 from tools import config
33 from tools.translate import _
34 import pooler
35
36 def _get_fields_type(self, cr, uid, context=None):
37     cr.execute('select distinct ttype,ttype from ir_model_fields')
38     field_types = cr.fetchall()
39     field_types_copy = field_types
40     for types in field_types_copy:
41         if not hasattr(fields,types[0]):
42             field_types.remove(types)
43     return field_types
44
45 def _in_modules(self, cr, uid, ids, field_name, arg, context=None):
46     #pseudo-method used by fields.function in ir.model/ir.model.fields
47     module_pool = self.pool.get("ir.module.module")
48     installed_module_ids = module_pool.search(cr, uid, [('state','=','installed')])
49     installed_module_names = module_pool.read(cr, uid, installed_module_ids, ['name'], context=context)
50     installed_modules = set(x['name'] for x in installed_module_names)
51
52     result = {}
53     xml_ids = osv.osv._get_xml_ids(self, cr, uid, ids)
54     for k,v in xml_ids.iteritems():
55         result[k] = ', '.join(sorted(installed_modules & set(xml_id.split('.')[0] for xml_id in v)))
56     return result
57
58
59 class ir_model(osv.osv):
60     _name = 'ir.model'
61     _description = "Objects"
62     _order = 'model'
63
64     def _is_osv_memory(self, cr, uid, ids, field_name, arg, context=None):
65         models = self.browse(cr, uid, ids, context=context)
66         res = dict.fromkeys(ids)
67         for model in models:
68             res[model.id] = isinstance(self.pool.get(model.model), osv.osv_memory)
69         return res
70
71     def _search_osv_memory(self, cr, uid, model, name, domain, context=None):
72         if not domain:
73             return []
74         field, operator, value = domain[0]
75         if operator not in ['=', '!=']:
76             raise osv.except_osv(_('Invalid search criterions'), _('The osv_memory field can only be compared with = and != operator.'))
77         value = bool(value) if operator == '=' else not bool(value)
78         all_model_ids = self.search(cr, uid, [], context=context)
79         is_osv_mem = self._is_osv_memory(cr, uid, all_model_ids, 'osv_memory', arg=None, context=context)
80         return [('id', 'in', [id for id in is_osv_mem if bool(is_osv_mem[id]) == value])]
81
82     def _view_ids(self, cr, uid, ids, field_name, arg, context=None):
83         models = self.browse(cr, uid, ids)
84         res = {}
85         for model in models:
86             res[model.id] = self.pool.get("ir.ui.view").search(cr, uid, [('model', '=', model.model)])
87         return res
88
89     _columns = {
90         'name': fields.char('Object Name', size=64, translate=True, required=True),
91         'model': fields.char('Object', size=64, required=True, select=1),
92         'info': fields.text('Information'),
93         'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields', required=True),
94         'state': fields.selection([('manual','Custom Object'),('base','Base Object')],'Type',readonly=True),
95         'access_ids': fields.one2many('ir.model.access', 'model_id', 'Access'),
96         'osv_memory': fields.function(_is_osv_memory, method=True, string='In-memory model', type='boolean',
97             fnct_search=_search_osv_memory,
98             help="Indicates whether this object model lives in memory only, i.e. is not persisted (osv.osv_memory)"),
99         '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'),
100         'view_ids': fields.function(_view_ids, method=True, type='one2many', obj='ir.ui.view', string='Views'),
101     }
102     
103     _defaults = {
104         'model': lambda *a: 'x_',
105         'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
106     }
107     
108     def _check_model_name(self, cr, uid, ids, context=None):
109         for model in self.browse(cr, uid, ids, context=context):
110             if model.state=='manual':
111                 if not model.model.startswith('x_'):
112                     return False
113             if not re.match('^[a-z_A-Z0-9.]+$',model.model):
114                 return False
115         return True
116
117     def _model_name_msg(self, cr, uid, ids, context=None):
118         return _('The Object name must start with x_ and not contain any special character !')
119     _constraints = [
120         (_check_model_name, _model_name_msg, ['model']),
121     ]
122
123     # overridden to allow searching both on model name (model field)
124     # and model description (name field)
125     def name_search(self, cr, uid, name='', args=None, operator='ilike',  context=None, limit=None):
126         if args is None:
127             args = []
128         domain = args + ['|', ('model', operator, name), ('name', operator, name)]
129         return super(ir_model, self).name_search(cr, uid, None, domain,
130                         operator=operator, limit=limit, context=context)
131
132
133     def unlink(self, cr, user, ids, context=None):
134         for model in self.browse(cr, user, ids, context):
135             if model.state != 'manual':
136                 raise except_orm(_('Error'), _("You can not remove the model '%s' !") %(model.name,))
137         res = super(ir_model, self).unlink(cr, user, ids, context)
138         pooler.restart_pool(cr.dbname)
139         return res
140
141     def write(self, cr, user, ids, vals, context=None):
142         if context:
143             context.pop('__last_update', None)
144         return super(ir_model,self).write(cr, user, ids, vals, context)
145
146     def create(self, cr, user, vals, context=None):
147         if  context is None:
148             context = {}
149         if context and context.get('manual',False):
150             vals['state']='manual'
151         res = super(ir_model,self).create(cr, user, vals, context)
152         if vals.get('state','base')=='manual':
153             self.instanciate(cr, user, vals['model'], context)
154             self.pool.get(vals['model']).__init__(self.pool, cr)
155             ctx = context.copy()
156             ctx.update({'field_name':vals['name'],'field_state':'manual','select':vals.get('select_level','0')})
157             self.pool.get(vals['model'])._auto_init(cr, ctx)
158             #pooler.restart_pool(cr.dbname)
159         return res
160
161     def instanciate(self, cr, user, model, context={}):
162         class x_custom_model(osv.osv):
163             pass
164         x_custom_model._name = model
165         x_custom_model._module = False
166         a = x_custom_model.createInstance(self.pool, '', cr)
167         if (not a._columns) or ('x_name' in a._columns.keys()):
168             x_name = 'x_name'
169         else:
170             x_name = a._columns.keys()[0]
171         x_custom_model._rec_name = x_name
172 ir_model()
173
174 class ir_model_fields(osv.osv):
175     _name = 'ir.model.fields'
176     _description = "Fields"
177
178     _columns = {
179         'name': fields.char('Name', required=True, size=64, select=1),
180         'model': fields.char('Object Name', size=64, required=True, select=1,
181             help="The technical name of the model this field belongs to"),
182         'relation': fields.char('Object Relation', size=64,
183             help="For relationship fields, the technical name of the target model"),
184         'relation_field': fields.char('Relation Field', size=64,
185             help="For one2many fields, the field on the target model that implement the opposite many2one relationship"),
186         'model_id': fields.many2one('ir.model', 'Model', required=True, select=True, ondelete='cascade',
187             help="The model this field belongs to"),
188         'field_description': fields.char('Field Label', required=True, size=256),
189         'ttype': fields.selection(_get_fields_type, 'Field Type',size=64, required=True),
190         'selection': fields.char('Selection Options',size=128, help="List of options for a selection field, "
191             "specified as a Python expression defining a list of (key, label) pairs. "
192             "For example: [('blue','Blue'),('yellow','Yellow')]"),
193         'required': fields.boolean('Required'),
194         'readonly': fields.boolean('Readonly'),
195         'select_level': fields.selection([('0','Not Searchable'),('1','Always Searchable'),('2','Advanced Search (deprecated)')],'Searchable', required=True),
196         'translate': fields.boolean('Translate', help="Whether values for this field can be translated (enables the translation mechanism for that field)"),
197         'size': fields.integer('Size'),
198         'state': fields.selection([('manual','Custom Field'),('base','Base Field')],'Type', required=True, readonly=True, select=1),
199         'on_delete': fields.selection([('cascade','Cascade'),('set null','Set NULL')], 'On delete', help='On delete property for many2one fields'),
200         'domain': fields.char('Domain', size=256, help="The optional domain to restrict possible values for relationship fields, "
201             "specified as a Python expression defining a list of triplets. "
202             "For example: [('color','=','red')]"),
203         'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'),
204         'view_load': fields.boolean('View Auto-Load'),
205         'selectable': fields.boolean('Selectable'),
206         'modules': fields.function(_in_modules, method=True, type='char', size=128, string='In modules', help='List of modules in which the field is defined'),
207     }
208     _rec_name='field_description'
209     _defaults = {
210         'view_load': 0,
211         'selection': "",
212         'domain': "[]",
213         'name': 'x_',
214         'state': lambda self,cr,uid,ctx={}: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
215         'on_delete': 'set null',
216         'select_level': '0',
217         'size': 64,
218         'field_description': '',
219         'selectable': 1,
220     }
221     _order = "name"
222
223     def _check_selection(self, cr, uid, selection, context=None):
224         try:
225             selection_list = eval(selection)
226         except Exception:
227             logging.getLogger('ir.model').warning('Invalid selection list definition for fields.selection', exc_info=True)
228             raise except_orm(_('Error'),
229                     _("The Selection Options expression is not a valid Pythonic expression." \
230                       "Please provide an expression in the [('key','Label'), ...] format."))
231
232         check = True
233         if not (isinstance(selection_list, list) and selection_list):
234             check = False
235         else:
236             for item in selection_list:
237                 if not (isinstance(item, (tuple,list)) and len(item) == 2):
238                     check = False
239                     break
240
241         if not check:
242                 raise except_orm(_('Error'),
243                     _("The Selection Options expression is must be in the [('key','Label'), ...] format!"))
244         return True
245
246     def _size_gt_zero_msg(self, cr, user, ids, context=None):
247         return _('Size of the field can never be less than 1 !')
248
249     _sql_constraints = [
250         ('size_gt_zero', 'CHECK (size>0)',_size_gt_zero_msg ),
251     ]
252
253     def unlink(self, cr, user, ids, context=None):
254         for field in self.browse(cr, user, ids, context):
255             if field.state <> 'manual':
256                 raise except_orm(_('Error'), _("You cannot remove the field '%s' !") %(field.name,))
257         #
258         # MAY BE ADD A ALTER TABLE DROP ?
259         #
260             #Removing _columns entry for that table
261             self.pool.get(field.model)._columns.pop(field.name,None)
262         return super(ir_model_fields, self).unlink(cr, user, ids, context)
263
264     def create(self, cr, user, vals, context=None):
265         if 'model_id' in vals:
266             model_data = self.pool.get('ir.model').browse(cr, user, vals['model_id'])
267             vals['model'] = model_data.model
268         if context is None:
269             context = {}
270         if context and context.get('manual',False):
271             vals['state'] = 'manual'
272         if vals.get('ttype', False) == 'selection':
273             if not vals.get('selection',False):
274                 raise except_orm(_('Error'), _('For selection fields, the Selection Options must be given!'))
275             self._check_selection(cr, user, vals['selection'], context=context)
276         res = super(ir_model_fields,self).create(cr, user, vals, context)
277         if vals.get('state','base') == 'manual':
278             if not vals['name'].startswith('x_'):
279                 raise except_orm(_('Error'), _("Custom fields must have a name that starts with 'x_' !"))
280
281             if vals.get('relation',False) and not self.pool.get('ir.model').search(cr, user, [('model','=',vals['relation'])]):
282                  raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
283
284             if self.pool.get(vals['model']):
285                 self.pool.get(vals['model']).__init__(self.pool, cr)
286                 #Added context to _auto_init for special treatment to custom field for select_level
287                 ctx = context.copy()
288                 ctx.update({'field_name':vals['name'],'field_state':'manual','select':vals.get('select_level','0'),'update_custom_fields':True})
289                 self.pool.get(vals['model'])._auto_init(cr, ctx)
290
291         return res
292
293     def write(self, cr, user, ids, vals, context=None):
294         if context is None:
295             context = {}
296         if context and context.get('manual',False):
297             vals['state'] = 'manual'
298
299         column_rename = None # if set, *one* column can be renamed here
300         obj = None
301         models_patch = {}    # structs of (obj, [(field, prop, change_to),..])
302                              # data to be updated on the orm model
303
304         # static table of properties
305         model_props = [ # (our-name, fields.prop, set_fn)
306             ('field_description', 'string', str),
307             ('required', 'required', bool),
308             ('readonly', 'readonly', bool),
309             ('domain', '_domain', eval),
310             ('size', 'size', int),
311             ('on_delete', 'ondelete', str),
312             ('translate', 'translate', bool),
313             ('view_load', 'view_load', bool),
314             ('selectable', 'selectable', bool),
315             ('select_level', 'select', int),
316             ('selection', 'selection', eval),
317             ]
318
319         if vals and ids:
320             checked_selection = False # need only check it once, so defer
321
322             for item in self.browse(cr, user, ids, context=context):
323                 if not (obj and obj._name == item.model):
324                     obj = self.pool.get(item.model)
325
326                 if item.state != 'manual':
327                     raise except_orm(_('Error!'),
328                         _('Properties of base fields cannot be altered in this manner! '
329                           'Please modify them through Python code, '
330                           'preferably through a custom addon!'))
331
332                 if item.ttype == 'selection' and 'selection' in vals \
333                         and not checked_selection:
334                     self._check_selection(cr, user, vals['selection'], context=context)
335                     checked_selection = True
336
337                 final_name = item.name
338                 if 'name' in vals and vals['name'] != item.name:
339                     # We need to rename the column
340                     if column_rename:
341                         raise except_orm(_('Error!'), _('Can only rename one column at a time!'))
342                     if vals['name'] in obj._columns:
343                         raise except_orm(_('Error!'), _('Cannot rename column to %s, because that column already exists!') % vals['name'])
344                     if vals.get('state', 'base') == 'manual' and not vals['name'].startswith('x_'):
345                         raise except_orm(_('Error!'), _('New column name must still start with x_ , because it is a custom field!'))
346                     if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']:
347                         raise ValueError('Invalid character in column name')
348                     column_rename = (obj, (obj._table, item.name, vals['name']))
349                     final_name = vals['name']
350
351                 if 'model_id' in vals and vals['model_id'] != item.model_id:
352                     raise except_orm(_("Error!"), _("Changing the model of a field is forbidden!"))
353
354                 if 'ttype' in vals and vals['ttype'] != item.ttype:
355                     raise except_orm(_("Error!"), _("Changing the type of a column is not yet supported. "
356                                 "Please drop it and create it again!"))
357
358                 # We don't check the 'state', because it might come from the context
359                 # (thus be set for multiple fields) and will be ignored anyway.
360                 if obj:
361                     models_patch.setdefault(obj._name, (obj,[]))
362                     # find out which properties (per model) we need to update
363                     for field_name, field_property, set_fn in model_props:
364                         if field_name in vals:
365                             property_value = set_fn(vals[field_name])
366                             if getattr(obj._columns[item.name], field_property) != property_value:
367                                 models_patch[obj._name][1].append((final_name, field_property, property_value))
368                         # our dict is ready here, but no properties are changed so far
369
370         # These shall never be written (modified)
371         for column_name in ('model_id', 'model', 'state'):
372             if column_name in vals:
373                 del vals[column_name]
374
375         res = super(ir_model_fields,self).write(cr, user, ids, vals, context=context)
376
377         if column_rename:
378             cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % column_rename[1])
379             # This is VERY risky, but let us have this feature:
380             # we want to change the key of column in obj._columns dict
381             col = column_rename[0]._columns.pop(column_rename[1][1]) # take object out, w/o copy
382             column_rename[0]._columns[column_rename[1][2]] = col
383
384         if models_patch:
385             # We have to update _columns of the model(s) and then call their
386             # _auto_init to sync the db with the model. Hopefully, since write()
387             # was called earlier, they will be in-sync before the _auto_init.
388             # Anything we don't update in _columns now will be reset from
389             # the model into ir.model.fields (db).
390             ctx = context.copy()
391             ctx.update({'select': vals.get('select_level','0'),'update_custom_fields':True})
392
393             for model_key, patch_struct in models_patch.items():
394                 obj = patch_struct[0]
395                 for col_name, col_prop, val in patch_struct[1]:
396                     setattr(obj._columns[col_name], col_prop, val)
397                 obj._auto_init(cr, ctx)
398         return res
399
400 ir_model_fields()
401
402 class ir_model_access(osv.osv):
403     _name = 'ir.model.access'
404     _columns = {
405         'name': fields.char('Name', size=64, required=True, select=True),
406         'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)], select=True, ondelete='cascade'),
407         'group_id': fields.many2one('res.groups', 'Group', ondelete='cascade', select=True),
408         'perm_read': fields.boolean('Read Access'),
409         'perm_write': fields.boolean('Write Access'),
410         'perm_create': fields.boolean('Create Access'),
411         'perm_unlink': fields.boolean('Delete Access'),
412     }
413
414     def check_groups(self, cr, uid, group):
415         grouparr  = group.split('.')
416         if not grouparr:
417             return False
418         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],))
419         return bool(cr.fetchone())
420
421     def check_group(self, cr, uid, model, mode, group_ids):
422         """ Check if a specific group has the access mode to the specified model"""
423         assert mode in ['read','write','create','unlink'], 'Invalid access mode'
424
425         if isinstance(model, browse_record):
426             assert model._table_name == 'ir.model', 'Invalid model object'
427             model_name = model.name
428         else:
429             model_name = model
430
431         if isinstance(group_ids, (int, long)):
432             group_ids = [group_ids]
433         for group_id in group_ids:
434             cr.execute("SELECT perm_" + mode + " "
435                    "  FROM ir_model_access a "
436                    "  JOIN ir_model m ON (m.id = a.model_id) "
437                    " WHERE m.model = %s AND a.group_id = %s", (model_name, group_id)
438                    )
439             r = cr.fetchone()
440             if r is None:
441                 cr.execute("SELECT perm_" + mode + " "
442                        "  FROM ir_model_access a "
443                        "  JOIN ir_model m ON (m.id = a.model_id) "
444                        " WHERE m.model = %s AND a.group_id IS NULL", (model_name, )
445                        )
446                 r = cr.fetchone()
447
448             access = bool(r and r[0])
449             if access:
450                 return True
451         # pass no groups -> no access
452         return False
453
454     def check(self, cr, uid, model, mode='read', raise_exception=True, context=None):
455         if uid==1:
456             # User root have all accesses
457             # TODO: exclude xml-rpc requests
458             return True
459
460         assert mode in ['read','write','create','unlink'], 'Invalid access mode'
461
462         if isinstance(model, browse_record):
463             assert model._table_name == 'ir.model', 'Invalid model object'
464             model_name = model.model
465         else:
466             model_name = model
467
468         # osv_memory objects can be read by everyone, as they only return
469         # results that belong to the current user (except for superuser)
470         model_obj = self.pool.get(model_name)
471         if isinstance(model_obj, osv.osv_memory):
472             return True
473
474         # We check if a specific rule exists
475         cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
476                    '  FROM ir_model_access a '
477                    '  JOIN ir_model m ON (m.id = a.model_id) '
478                    '  JOIN res_groups_users_rel gu ON (gu.gid = a.group_id) '
479                    ' WHERE m.model = %s '
480                    '   AND gu.uid = %s '
481                    , (model_name, uid,)
482                    )
483         r = cr.fetchone()[0]
484
485         if r is None:
486             # there is no specific rule. We check the generic rule
487             cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
488                        '  FROM ir_model_access a '
489                        '  JOIN ir_model m ON (m.id = a.model_id) '
490                        ' WHERE a.group_id IS NULL '
491                        '   AND m.model = %s '
492                        , (model_name,)
493                        )
494             r = cr.fetchone()[0]
495
496         if not r and raise_exception:
497             cr.execute('''select
498                     g.name
499                 from
500                     ir_model_access a 
501                     left join ir_model m on (a.model_id=m.id) 
502                     left join res_groups g on (a.group_id=g.id)
503                 where
504                     m.model=%s and
505                     a.group_id is not null and perm_''' + mode, (model_name, ))
506             groups = ', '.join(map(lambda x: x[0], cr.fetchall())) or '/'
507             msgs = {
508                 'read':   _("You can not read this document (%s) ! Be sure your user belongs to one of these groups: %s."),
509                 'write':  _("You can not write in this document (%s) ! Be sure your user belongs to one of these groups: %s."),
510                 'create': _("You can not create this document (%s) ! Be sure your user belongs to one of these groups: %s."),
511                 'unlink': _("You can not delete this document (%s) ! Be sure your user belongs to one of these groups: %s."),
512             }
513
514             raise except_orm(_('AccessError'), msgs[mode] % (model_name, groups) )
515         return r or False
516
517     check = tools.cache()(check)
518
519     __cache_clearing_methods = []
520
521     def register_cache_clearing_method(self, model, method):
522         self.__cache_clearing_methods.append((model, method))
523
524     def unregister_cache_clearing_method(self, model, method):
525         try:
526             i = self.__cache_clearing_methods.index((model, method))
527             del self.__cache_clearing_methods[i]
528         except ValueError:
529             pass
530
531     def call_cache_clearing_methods(self, cr):
532         self.check.clear_cache(cr.dbname)    # clear the cache of check function
533         for model, method in self.__cache_clearing_methods:
534             object_ = self.pool.get(model)
535             if object_:
536                 getattr(object_, method)()
537
538     #
539     # Check rights on actions
540     #
541     def write(self, cr, uid, *args, **argv):
542         self.call_cache_clearing_methods(cr)
543         res = super(ir_model_access, self).write(cr, uid, *args, **argv)
544         return res
545
546     def create(self, cr, uid, *args, **argv):
547         self.call_cache_clearing_methods(cr)
548         res = super(ir_model_access, self).create(cr, uid, *args, **argv)
549         return res
550
551     def unlink(self, cr, uid, *args, **argv):
552         self.call_cache_clearing_methods(cr)
553         res = super(ir_model_access, self).unlink(cr, uid, *args, **argv)
554         return res
555
556 ir_model_access()
557
558 class ir_model_data(osv.osv):
559     _name = 'ir.model.data'
560     __logger = logging.getLogger('addons.base.'+_name)
561     _order = 'module,model,name'
562     _columns = {
563         'name': fields.char('XML Identifier', required=True, size=128, select=1),
564         'model': fields.char('Object', required=True, size=64, select=1),
565         'module': fields.char('Module', required=True, size=64, select=1),
566         'res_id': fields.integer('Resource ID', select=1),
567         'noupdate': fields.boolean('Non Updatable'),
568         'date_update': fields.datetime('Update Date'),
569         'date_init': fields.datetime('Init Date')
570     }
571     _defaults = {
572         'date_init': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
573         'date_update': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
574         'noupdate': False,
575         'module': ''
576     }
577     _sql_constraints = [
578         ('module_name_uniq', 'unique(name, module)', 'You cannot have multiple records with the same id for the same module !'),
579     ]
580
581     def __init__(self, pool, cr):
582         osv.osv.__init__(self, pool, cr)
583         self.doinit = True
584         self.unlink_mark = {}
585
586         # also stored in pool to avoid being discarded along with this osv instance
587         if getattr(pool, 'model_data_reference_ids', None) is None:
588             self.pool.model_data_reference_ids = {}
589         self.loads = self.pool.model_data_reference_ids
590
591     def _auto_init(self, cr, context=None):
592         super(ir_model_data, self)._auto_init(cr, context)
593         cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_model_data_module_name_index\'')
594         if not cr.fetchone():
595             cr.execute('CREATE INDEX ir_model_data_module_name_index ON ir_model_data (module, name)')
596
597     @tools.cache()
598     def _get_id(self, cr, uid, module, xml_id):
599         """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"""
600         ids = self.search(cr, uid, [('module','=',module), ('name','=', xml_id)])
601         if not ids:
602             raise ValueError('No references to %s.%s' % (module, xml_id))
603         # the sql constraints ensure us we have only one result
604         return ids[0]
605
606     @tools.cache()
607     def get_object_reference(self, cr, uid, module, xml_id):
608         """Returns (model, res_id) corresponding to a given module and xml_id (cached) or raise ValueError if not found"""
609         data_id = self._get_id(cr, uid, module, xml_id)
610         res = self.read(cr, uid, data_id, ['model', 'res_id'])
611         if not res['res_id']:
612             raise ValueError('No references to %s.%s' % (module, xml_id))
613         return (res['model'], res['res_id'])
614
615     def get_object(self, cr, uid, module, xml_id, context=None):
616         """Returns a browsable record for the given module name and xml_id or raise ValueError if not found"""
617         res_model, res_id = self.get_object_reference(cr, uid, module, xml_id)
618         result = self.pool.get(res_model).browse(cr, uid, res_id, context=context)
619         if not result.exists():
620             raise ValueError('No record found for unique ID %s.%s. It may have been deleted.' % (module, xml_id))
621         return result
622
623     def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
624         if not xml_id:
625             return False
626         try:
627             id = self.read(cr, uid, [self._get_id(cr, uid, module, xml_id)], ['res_id'])[0]['res_id']
628             self.loads[(module,xml_id)] = (model,id)
629         except:
630             id = False
631         return id
632
633     def unlink(self, cr, uid, ids, context=None):
634         """ Regular unlink method, but make sure to clear the caches. """
635         self._get_id.clear_cache(cr.dbname)
636         self.get_object_reference.clear_cache(cr.dbname)
637         return super(ir_model_data,self).unlink(cr, uid, ids, context=context)
638
639     def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None):
640         model_obj = self.pool.get(model)
641         if not context:
642             context = {}
643
644         # records created during module install should result in res.log entries that are already read!
645         context = dict(context, res_log_read=True)
646
647         if xml_id and ('.' in xml_id):
648             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)
649             module, xml_id = xml_id.split('.')
650         if (not xml_id) and (not self.doinit):
651             return False
652         action_id = False
653
654         if xml_id:
655             cr.execute('''SELECT imd.id, imd.res_id, md.id
656                           FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
657                           WHERE imd.module=%%s AND imd.name=%%s''' % model_obj._table,
658                           (module, xml_id))
659             results = cr.fetchall()
660             for imd_id2,res_id2,real_id2 in results:
661                 if not real_id2:
662                     self._get_id.clear_cache(cr.dbname, uid, module, xml_id)
663                     self.get_object_reference.clear_cache(cr.dbname, uid, module, xml_id)
664                     cr.execute('delete from ir_model_data where id=%s', (imd_id2,))
665                     res_id = False
666                 else:
667                     res_id,action_id = res_id2,imd_id2
668
669         if action_id and res_id:
670             model_obj.write(cr, uid, [res_id], values, context=context)
671             self.write(cr, uid, [action_id], {
672                 'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
673                 },context=context)
674         elif res_id:
675             model_obj.write(cr, uid, [res_id], values, context=context)
676             if xml_id:
677                 self.create(cr, uid, {
678                     'name': xml_id,
679                     'model': model,
680                     'module':module,
681                     'res_id':res_id,
682                     'noupdate': noupdate,
683                     },context=context)
684                 if model_obj._inherits:
685                     for table in model_obj._inherits:
686                         inherit_id = model_obj.browse(cr, uid,
687                                 res_id,context=context)[model_obj._inherits[table]]
688                         self.create(cr, uid, {
689                             'name': xml_id + '_' + table.replace('.', '_'),
690                             'model': table,
691                             'module': module,
692                             'res_id': inherit_id.id,
693                             'noupdate': noupdate,
694                             },context=context)
695         else:
696             if mode=='init' or (mode=='update' and xml_id):
697                 res_id = model_obj.create(cr, uid, values, context=context)
698                 if xml_id:
699                     self.create(cr, uid, {
700                         'name': xml_id,
701                         'model': model,
702                         'module': module,
703                         'res_id': res_id,
704                         'noupdate': noupdate
705                         },context=context)
706                     if model_obj._inherits:
707                         for table in model_obj._inherits:
708                             inherit_id = model_obj.browse(cr, uid,
709                                     res_id,context=context)[model_obj._inherits[table]]
710                             self.create(cr, uid, {
711                                 'name': xml_id + '_' + table.replace('.', '_'),
712                                 'model': table,
713                                 'module': module,
714                                 'res_id': inherit_id.id,
715                                 'noupdate': noupdate,
716                                 },context=context)
717         if xml_id:
718             if res_id:
719                 self.loads[(module, xml_id)] = (model, res_id)
720                 if model_obj._inherits:
721                     for table in model_obj._inherits:
722                         inherit_field = model_obj._inherits[table]
723                         inherit_id = model_obj.read(cr, uid, res_id,
724                                 [inherit_field])[inherit_field]
725                         self.loads[(module, xml_id + '_' + \
726                                 table.replace('.', '_'))] = (table, inherit_id)
727         return res_id
728
729     def _unlink(self, cr, uid, model, res_ids):
730         for res_id in res_ids:
731             self.unlink_mark[(model, res_id)] = False
732             cr.execute('delete from ir_model_data where res_id=%s and model=%s', (res_id, model))
733         return True
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             res = ir.ir_set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
756         elif xml_id:
757             cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
758         return True
759
760     def _process_end(self, cr, uid, modules):
761         if not modules:
762             return True
763         modules = list(modules)
764         module_in = ",".join(["%s"] * len(modules))
765         cr.execute('select id,name,model,res_id,module from ir_model_data where module IN (' + module_in + ') and noupdate=%s', modules + [False])
766         wkf_todo = []
767         for (id, name, model, res_id,module) in cr.fetchall():
768             if (module,name) not in self.loads:
769                 self.unlink_mark[(model,res_id)] = id
770                 if model=='workflow.activity':
771                     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,))
772                     wkf_todo.extend(cr.fetchall())
773                     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))
774                     cr.execute("delete from wkf_transition where act_to=%s", (res_id,))
775
776         for model,id in wkf_todo:
777             wf_service = netsvc.LocalService("workflow")
778             wf_service.trg_write(uid, model, id, cr)
779
780         cr.commit()
781         if not config.get('import_partial'):
782             for (model, res_id) in self.unlink_mark.keys():
783                 if self.pool.get(model):
784                     self.__logger.info('Deleting %s@%s', res_id, model)
785                     try:
786                         self.pool.get(model).unlink(cr, uid, [res_id])
787                         if id:
788                             ids = self.search(cr, uid, [('res_id','=',res_id),
789                                                         ('model','=',model)])
790                             self.__logger.debug('=> Deleting %s: %s',
791                                                 self._name, ids)
792                             if len(ids) > 1 and \
793                                self.__logger.isEnabledFor(logging.WARNING):
794                                 self.__logger.warn(
795                                     'Got %d %s for (%s, %d): %s',
796                                     len(ids), self._name, model, res_id,
797                                     map(itemgetter('module','name'),
798                                         self.read(cr, uid, ids,
799                                                   ['name', 'module'])))
800                             self.unlink(cr, uid, ids)
801                             cr.execute(
802                                 'DELETE FROM ir_values WHERE value=%s',
803                                 ('%s,%s'%(model, res_id),))
804                         cr.commit()
805                     except Exception:
806                         cr.rollback()
807                         self.__logger.warn(
808                             'Could not delete id: %d of model %s\nThere '
809                             'should be some relation that points to this '
810                             'resource\nYou should manually fix this and '
811                             'restart with --update=module', res_id, model)
812         return True
813 ir_model_data()
814
815 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: