[FIX]: uninstall module.
[odoo/odoo.git] / openerp / addons / base / ir / ir_model.py
1
2  # -*- coding: utf-8 -*-
3 ##############################################################################
4 #
5 #    OpenERP, Open Source Management Solution
6 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 #
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.
12 #
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.
17 #
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/>.
20 #
21 ##############################################################################
22 import logging
23 import re
24 import time
25 import types
26
27 from osv import fields,osv
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 _logger = logging.getLogger(__name__)
37
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
43                       if not v._deprecated
44                       if not issubclass(v, fields.function)])
45
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)
52
53     result = {}
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)))
57     return result
58
59
60 class ir_model(osv.osv):
61     _name = 'ir.model'
62     _description = "Models"
63     _order = 'model'
64
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)
68         for model in models:
69             res[model.id] = self.pool.get(model.model).is_transient()
70         return res
71
72     def _search_osv_memory(self, cr, uid, model, name, domain, context=None):
73         if not domain:
74             return []
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])]
82
83     def _view_ids(self, cr, uid, ids, field_name, arg, context=None):
84         models = self.browse(cr, uid, ids)
85         res = {}
86         for model in models:
87             res[model.id] = self.pool.get("ir.ui.view").search(cr, uid, [('model', '=', model.model)])
88         return res
89
90     _columns = {
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'),
102     }
103
104     _defaults = {
105         'model': lambda *a: 'x_',
106         'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
107     }
108
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_'):
113                     return False
114             if not re.match('^[a-z_A-Z0-9.]+$',model.model):
115                 return False
116         return True
117
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 !')
120
121     _constraints = [
122         (_check_model_name, _model_name_msg, ['model']),
123     ]
124     _sql_constraints = [
125         ('obj_name_uniq', 'unique (model)', 'Each model must be unique!'),
126     ]
127
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):
131         if args is None:
132             args = []
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),
136                              context=context)
137
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,))
149         return True
150
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)
158         return res
159
160     def write(self, cr, user, ids, vals, context=None):
161         if context:
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)
168
169     def create(self, cr, user, vals, context=None):
170         if  context is None:
171             context = {}
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)
178             ctx = context.copy()
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)
182         return res
183
184     def instanciate(self, cr, user, model, context=None):
185         class x_custom_model(osv.osv):
186             pass
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()):
191             x_name = 'x_name'
192         else:
193             x_name = a._columns.keys()[0]
194         x_custom_model._rec_name = x_name
195 ir_model()
196
197 class ir_model_fields(osv.osv):
198     _name = 'ir.model.fields'
199     _description = "Fields"
200
201     _columns = {
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."),
235     }
236     _rec_name='field_description'
237     _defaults = {
238         'view_load': 0,
239         'selection': "",
240         'domain': "[]",
241         'name': 'x_',
242         'state': lambda self,cr,uid,ctx={}: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
243         'on_delete': 'set null',
244         'select_level': '0',
245         'size': 64,
246         'field_description': '',
247         'selectable': 1,
248     }
249     _order = "name"
250
251     def _check_selection(self, cr, uid, selection, context=None):
252         try:
253             selection_list = eval(selection)
254         except Exception:
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."))
259
260         check = True
261         if not (isinstance(selection_list, list) and selection_list):
262             check = False
263         else:
264             for item in selection_list:
265                 if not (isinstance(item, (tuple,list)) and len(item) == 2):
266                     check = False
267                     break
268
269         if not check:
270                 raise except_orm(_('Error'),
271                     _("The Selection Options expression is must be in the [('key','Label'), ...] format!"))
272         return True
273
274     def _size_gt_zero_msg(self, cr, user, ids, context=None):
275         return _('Size of the field can never be less than 1 !')
276
277     _sql_constraints = [
278         ('size_gt_zero', 'CHECK (size>0)',_size_gt_zero_msg ),
279     ]
280     
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)
291         return True
292
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)
296         return res
297
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
302         if context is None:
303             context = {}
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_' !"))
314
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'])
317
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
321                 ctx = context.copy()
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)
324
325         return res
326
327     def write(self, cr, user, ids, vals, context=None):
328         if context is None:
329             context = {}
330         if context and context.get('manual',False):
331             vals['state'] = 'manual'
332
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)
340
341         column_rename = None # if set, *one* column can be renamed here
342         obj = None
343         models_patch = {}    # structs of (obj, [(field, prop, change_to),..])
344                              # data to be updated on the orm model
345
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),
359             ]
360
361         if vals and ids:
362             checked_selection = False # need only check it once, so defer
363
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)
367
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!'))
373
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
378
379                 final_name = item.name
380                 if 'name' in vals and vals['name'] != item.name:
381                     # We need to rename the column
382                     if column_rename:
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']
392
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!"))
395
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!"))
399
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.
402                 if obj:
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
411
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]
416
417         res = super(ir_model_fields,self).write(cr, user, ids, vals, context=context)
418
419         if column_rename:
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
425
426         if models_patch:
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).
432             ctx = context.copy()
433             ctx.update({'select': vals.get('select_level','0'),'update_custom_fields':True})
434
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)
440         return res
441
442 ir_model_fields()
443
444 class ir_model_access(osv.osv):
445     _name = 'ir.model.access'
446     _columns = {
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'),
454     }
455
456     def check_groups(self, cr, uid, group):
457         grouparr  = group.split('.')
458         if not grouparr:
459             return False
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())
462
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'
466
467         if isinstance(model, browse_record):
468             assert model._table_name == 'ir.model', 'Invalid model object'
469             model_name = model.name
470         else:
471             model_name = model
472
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)
480                    )
481             r = cr.fetchone()
482             if r is None:
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, )
487                        )
488                 r = cr.fetchone()
489
490             access = bool(r and r[0])
491             if access:
492                 return True
493         # pass no groups -> no access
494         return False
495
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``.
499            :rtype: list
500         """
501         assert access_mode in ['read','write','create','unlink'], 'Invalid access mode: %s' % access_mode
502         cr.execute('''SELECT
503                         g.name
504                       FROM
505                         ir_model_access a
506                         JOIN ir_model m ON (a.model_id=m.id)
507                         JOIN res_groups g ON (a.group_id=g.id)
508                       WHERE
509                         m.model=%s AND
510                         a.perm_''' + access_mode, (model_name,))
511         return [x[0] for x in cr.fetchall()]
512
513     @tools.ormcache()
514     def check(self, cr, uid, model, mode='read', raise_exception=True, context=None):
515         if uid==1:
516             # User root have all accesses
517             # TODO: exclude xml-rpc requests
518             return True
519
520         assert mode in ['read','write','create','unlink'], 'Invalid access mode'
521
522         if isinstance(model, browse_record):
523             assert model._table_name == 'ir.model', 'Invalid model object'
524             model_name = model.model
525         else:
526             model_name = model
527
528         # TransientModel records have no access rights, only an implicit access rule
529         if self.pool.get(model_name).is_transient():
530             return True
531
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 '
538                    '   AND gu.uid = %s '
539                    , (model_name, uid,)
540                    )
541         r = cr.fetchone()[0]
542
543         if r is None:
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 '
549                        '   AND m.model = %s '
550                        , (model_name,)
551                        )
552             r = cr.fetchone()[0]
553
554         if not r and raise_exception:
555             groups = ', '.join(self.group_names_with_access(cr, model_name, mode)) or '/'
556             msgs = {
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."),
561             }
562
563             raise except_orm(_('AccessError'), msgs[mode] % (model_name, groups) )
564         return r or False
565
566     __cache_clearing_methods = []
567
568     def register_cache_clearing_method(self, model, method):
569         self.__cache_clearing_methods.append((model, method))
570
571     def unregister_cache_clearing_method(self, model, method):
572         try:
573             i = self.__cache_clearing_methods.index((model, method))
574             del self.__cache_clearing_methods[i]
575         except ValueError:
576             pass
577
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)
582             if object_:
583                 getattr(object_, method)()
584
585     #
586     # Check rights on actions
587     #
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)
591         return res
592
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)
596         return res
597
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)
601         return res
602
603 ir_model_access()
604
605 class ir_model_data(osv.osv):
606     """Holds external identifier keys for records in the database.
607        This has two main uses:
608
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.
615     """
616     _name = 'ir.model.data'
617     _order = 'module,model,name'
618     _columns = {
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')
629     }
630     _defaults = {
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'),
633         'noupdate': False,
634         'module': ''
635     }
636     _sql_constraints = [
637         ('module_name_uniq', 'unique(name, module)', 'You cannot have multiple records with the same external ID in the same module!'),
638     ]
639
640     def __init__(self, pool, cr):
641         osv.osv.__init__(self, pool, cr)
642         self.doinit = True
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 = {}
646             
647         self.loads = self.pool.model_data_reference_ids
648
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)')
654
655     @tools.ormcache()
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)])
659         if not ids:
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
662         return ids[0]
663
664     @tools.ormcache()
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'])
672
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))
679         return result
680
681     def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
682         if not xml_id:
683             return False
684         try:
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)
687         except:
688             id = False
689         return id
690     
691
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)
697
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)
700         if not context:
701             context = {}
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):
708             return False
709         action_id = False
710         if xml_id:
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,
714                           (module, xml_id))
715             results = cr.fetchall()
716             for imd_id2,res_id2,real_id2,real_model in results:
717                 if not real_id2:
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,))
721                     res_id = False
722                 else:
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
726
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'),
731                 },context=context)
732         elif res_id:
733             model_obj.write(cr, uid, [res_id], values, context=context)
734             if xml_id:
735                 self.create(cr, uid, {
736                     'name': xml_id,
737                     'model': model,
738                     'module':module,
739                     'res_id':res_id,
740                     'noupdate': noupdate,
741                     },context=context)
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('.', '_'),
748                             'model': table,
749                             'module': module,
750                             'res_id': inherit_id.id,
751                             'noupdate': noupdate,
752                             },context=context)
753         else:
754             if mode=='init' or (mode=='update' and xml_id):
755                 res_id = model_obj.create(cr, uid, values, context=context)
756                 if xml_id:
757                     self.create(cr, uid, {
758                         'name': xml_id,
759                         'model': model,
760                         'module': module,
761                         'res_id': res_id,
762                         'noupdate': noupdate
763                         },context=context)
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('.', '_'),
770                                 'model': table,
771                                 'module': module,
772                                 'res_id': inherit_id.id,
773                                 'noupdate': noupdate,
774                                 },context=context)
775         if xml_id:
776             if res_id:
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)
785         return res_id
786
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]
790         else:
791             res_id=None
792             model = models[0]
793
794         if res_id:
795             where = ' and res_id=%s' % (res_id,)
796         else:
797             where = ' and (res_id is null)'
798
799         if key2:
800             where += ' and key2=\'%s\'' % (key2,)
801         else:
802             where += ' and (key2 is null)'
803
804         cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
805         res = cr.fetchone()
806         if not res:
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)
809         elif xml_id:
810             cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
811         return True
812     
813     def _pre_process_unlink(self, cr, uid, ids, context=None):
814         wkf_todo = []
815         to_unlink = []
816         to_drop_table = []
817         ids.sort()
818         ids.reverse()
819         for data in self.browse(cr, uid, ids, context):
820             model = data.model
821             res_id = data.res_id
822             model_obj = self.pool.get(model)
823             name = data.name
824             if str(name).startswith('foreign_key_'):
825                 name = name[12:]
826                 # test if constraint exists
827                 cr.execute('select conname from pg_constraint where contype=%s and conname=%s',('f', name),)
828                 if cr.fetchall():
829                       cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model,name),)
830                       _logger.info('Drop CONSTRAINT %s@%s', name, model)
831                 continue
832             
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()
836                 if column_name:
837                     to_drop_table.append(name[6:])
838                 continue
839             
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),)
843                 if cr.fetchall():
844                     cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table,name[11:]),)
845                     _logger.info('Drop CONSTRAINT %s@%s', name[11:], model)
846                 continue
847             
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))
853
854         for model,res_id in wkf_todo:
855             wf_service = netsvc.LocalService("workflow")
856             try:
857                wf_service.trg_write(uid, model, res_id, cr)
858             except:
859                 _logger.info('Unable to process workflow %s@%s', res_id, model)
860
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)                    
865                     
866         for (model, res_id) in to_unlink:
867             if model in ('ir.model','ir.model.fields', 'ir.model.data'):
868                 continue
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
872                 continue
873             _logger.info('Deleting %s@%s', res_id, model)
874             try:
875                 self.pool.get(model).unlink(cr, uid, res_id)
876             except:
877                 _logger.info('Unable to delete %s@%s', res_id, model)
878             cr.commit()
879
880         for (model, res_id) in to_unlink:
881             if model not in ('ir.model.fields',):
882                 continue
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
886                 continue
887             _logger.info('Deleting %s@%s', res_id, model)
888             self.pool.get(model).unlink(cr, uid, res_id)
889
890         for (model, res_id) in to_unlink:
891             if model not in ('ir.model',):
892                 continue
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
896                 continue
897             _logger.info('Deleting %s@%s', res_id, model)
898             self.pool.get(model).unlink(cr, uid, [res_id])
899         cr.commit()
900
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.
908         """
909         if not modules:
910             return True
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'
916         to_unlink = []
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])
926                     
927 ir_model_data()
928
929 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: