[IMP] ir.model: avoid name clash between dummy vars and translation function _()
[odoo/odoo.git] / openerp / addons / base / ir / ir_model.py
1 # -*- coding: utf-8 -*-
2
3 ##############################################################################
4 #
5 #    OpenERP, Open Source Business Applications
6 #    Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
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 openerp.osv import fields,osv
28 from openerp import netsvc, pooler, tools
29 from openerp.tools.safe_eval import safe_eval as eval
30 from openerp.tools import config
31 from openerp.tools.translate import _
32 from openerp.osv.orm import except_orm, browse_record, EXT_ID_PREFIX_FK, \
33                             EXT_ID_PREFIX_M2M_TABLE, EXT_ID_PREFIX_CONSTRAINT
34
35 _logger = logging.getLogger(__name__)
36
37 MODULE_UNINSTALL_FLAG = '_force_unlink'
38
39 def _get_fields_type(self, cr, uid, context=None):
40     # Avoid too many nested `if`s below, as RedHat's Python 2.6
41     # break on it. See bug 939653.
42     return sorted([(k,k) for k,v in fields.__dict__.iteritems()
43                       if type(v) == types.TypeType and \
44                          issubclass(v, fields._column) and \
45                          v != fields._column and \
46                          not v._deprecated and \
47                          not issubclass(v, fields.function)])
48
49 def _in_modules(self, cr, uid, ids, field_name, arg, context=None):
50     #pseudo-method used by fields.function in ir.model/ir.model.fields
51     module_pool = self.pool.get("ir.module.module")
52     installed_module_ids = module_pool.search(cr, uid, [('state','=','installed')])
53     installed_module_names = module_pool.read(cr, uid, installed_module_ids, ['name'], context=context)
54     installed_modules = set(x['name'] for x in installed_module_names)
55
56     result = {}
57     xml_ids = osv.osv._get_xml_ids(self, cr, uid, ids)
58     for k,v in xml_ids.iteritems():
59         result[k] = ', '.join(sorted(installed_modules & set(xml_id.split('.')[0] for xml_id in v)))
60     return result
61
62
63 class ir_model(osv.osv):
64     _name = 'ir.model'
65     _description = "Models"
66     _order = 'model'
67
68     def _is_osv_memory(self, cr, uid, ids, field_name, arg, context=None):
69         models = self.browse(cr, uid, ids, context=context)
70         res = dict.fromkeys(ids)
71         for model in models:
72             res[model.id] = self.pool.get(model.model).is_transient()
73         return res
74
75     def _search_osv_memory(self, cr, uid, model, name, domain, context=None):
76         if not domain:
77             return []
78         __, operator, value = domain[0]
79         if operator not in ['=', '!=']:
80             raise osv.except_osv(_('Invalid search criterions'), _('The osv_memory field can only be compared with = and != operator.'))
81         value = bool(value) if operator == '=' else not bool(value)
82         all_model_ids = self.search(cr, uid, [], context=context)
83         is_osv_mem = self._is_osv_memory(cr, uid, all_model_ids, 'osv_memory', arg=None, context=context)
84         return [('id', 'in', [id for id in is_osv_mem if bool(is_osv_mem[id]) == value])]
85
86     def _view_ids(self, cr, uid, ids, field_name, arg, context=None):
87         models = self.browse(cr, uid, ids)
88         res = {}
89         for model in models:
90             res[model.id] = self.pool.get("ir.ui.view").search(cr, uid, [('model', '=', model.model)])
91         return res
92
93     _columns = {
94         'name': fields.char('Model Description', size=64, translate=True, required=True),
95         'model': fields.char('Model', size=64, required=True, select=1),
96         'info': fields.text('Information'),
97         'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields', required=True),
98         'state': fields.selection([('manual','Custom Object'),('base','Base Object')],'Type',readonly=True),
99         'access_ids': fields.one2many('ir.model.access', 'model_id', 'Access'),
100         'osv_memory': fields.function(_is_osv_memory, string='In-memory model', type='boolean',
101             fnct_search=_search_osv_memory,
102             help="Indicates whether this object model lives in memory only, i.e. is not persisted (osv.osv_memory)"),
103         'modules': fields.function(_in_modules, type='char', size=128, string='In modules', help='List of modules in which the object is defined or inherited'),
104         'view_ids': fields.function(_view_ids, type='one2many', obj='ir.ui.view', string='Views'),
105     }
106
107     _defaults = {
108         'model': lambda *a: 'x_',
109         'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
110     }
111
112     def _check_model_name(self, cr, uid, ids, context=None):
113         for model in self.browse(cr, uid, ids, context=context):
114             if model.state=='manual':
115                 if not model.model.startswith('x_'):
116                     return False
117             if not re.match('^[a-z_A-Z0-9.]+$',model.model):
118                 return False
119         return True
120
121     def _model_name_msg(self, cr, uid, ids, context=None):
122         return _('The Object name must start with x_ and not contain any special character !')
123
124     _constraints = [
125         (_check_model_name, _model_name_msg, ['model']),
126     ]
127     _sql_constraints = [
128         ('obj_name_uniq', 'unique (model)', 'Each model must be unique!'),
129     ]
130
131     # overridden to allow searching both on model name (model field)
132     # and model description (name field)
133     def _name_search(self, cr, uid, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
134         if args is None:
135             args = []
136         domain = args + ['|', ('model', operator, name), ('name', operator, name)]
137         return self.name_get(cr, name_get_uid or uid,
138                              super(ir_model, self).search(cr, uid, domain, limit=limit, context=context),
139                              context=context)
140
141     def _drop_table(self, cr, uid, ids, context=None):
142         for model in self.browse(cr, uid, ids, context):
143             model_pool = self.pool.get(model.model)
144             # this test should be removed, but check if drop view instead of drop table
145             # just check if table or view exists
146             cr.execute('select relkind from pg_class where relname=%s', (model_pool._table,))
147             result = cr.fetchone()
148             if result and result[0] == 'v':
149                 cr.execute('DROP view %s' % (model_pool._table,))
150             elif result and result[0] == 'r':
151                 cr.execute('DROP TABLE %s' % (model_pool._table,))
152         return True
153
154     def unlink(self, cr, user, ids, context=None):
155         # Prevent manual deletion of module tables
156         if context is None: context = {}
157         if isinstance(ids, (int, long)):
158             ids = [ids]
159         if not context.get(MODULE_UNINSTALL_FLAG) and \
160                 any(model.state != 'manual' for model in self.browse(cr, user, ids, context)):
161             raise except_orm(_('Error'), _("Model '%s' contains module data and cannot be removed!") % (model.name,))
162
163         self._drop_table(cr, user, ids, context)
164         res = super(ir_model, self).unlink(cr, user, ids, context)
165         if not context.get(MODULE_UNINSTALL_FLAG):
166             # only reload pool for normal unlink. For module uninstall the
167             # reload is done independently in openerp.modules.loading
168             pooler.restart_pool(cr.dbname)
169
170         return res
171
172     def write(self, cr, user, ids, vals, context=None):
173         if context:
174             context.pop('__last_update', None)
175         # Filter out operations 4 link from field id, because openerp-web
176         # always write (4,id,False) even for non dirty items
177         if 'field_id' in vals:
178             vals['field_id'] = [op for op in vals['field_id'] if op[0] != 4]
179         return super(ir_model,self).write(cr, user, ids, vals, context)
180
181     def create(self, cr, user, vals, context=None):
182         if  context is None:
183             context = {}
184         if context and context.get('manual',False):
185             vals['state']='manual'
186         res = super(ir_model,self).create(cr, user, vals, context)
187         if vals.get('state','base')=='manual':
188             self.instanciate(cr, user, vals['model'], context)
189             self.pool.get(vals['model']).__init__(self.pool, cr)
190             ctx = context.copy()
191             ctx.update({'field_name':vals['name'],'field_state':'manual','select':vals.get('select_level','0')})
192             self.pool.get(vals['model'])._auto_init(cr, ctx)
193             #pooler.restart_pool(cr.dbname)
194         return res
195
196     def instanciate(self, cr, user, model, context=None):
197         class x_custom_model(osv.osv):
198             pass
199         x_custom_model._name = model
200         x_custom_model._module = False
201         a = x_custom_model.create_instance(self.pool, cr)
202         if (not a._columns) or ('x_name' in a._columns.keys()):
203             x_name = 'x_name'
204         else:
205             x_name = a._columns.keys()[0]
206         x_custom_model._rec_name = x_name
207 ir_model()
208
209 class ir_model_fields(osv.osv):
210     _name = 'ir.model.fields'
211     _description = "Fields"
212
213     _columns = {
214         'name': fields.char('Name', required=True, size=64, select=1),
215         'model': fields.char('Object Name', size=64, required=True, select=1,
216             help="The technical name of the model this field belongs to"),
217         'relation': fields.char('Object Relation', size=64,
218             help="For relationship fields, the technical name of the target model"),
219         'relation_field': fields.char('Relation Field', size=64,
220             help="For one2many fields, the field on the target model that implement the opposite many2one relationship"),
221         'model_id': fields.many2one('ir.model', 'Model', required=True, select=True, ondelete='cascade',
222             help="The model this field belongs to"),
223         'field_description': fields.char('Field Label', required=True, size=256),
224         'ttype': fields.selection(_get_fields_type, 'Field Type',size=64, required=True),
225         'selection': fields.char('Selection Options',size=128, help="List of options for a selection field, "
226             "specified as a Python expression defining a list of (key, label) pairs. "
227             "For example: [('blue','Blue'),('yellow','Yellow')]"),
228         'required': fields.boolean('Required'),
229         'readonly': fields.boolean('Readonly'),
230         'select_level': fields.selection([('0','Not Searchable'),('1','Always Searchable'),('2','Advanced Search (deprecated)')],'Searchable', required=True),
231         'translate': fields.boolean('Translate', help="Whether values for this field can be translated (enables the translation mechanism for that field)"),
232         'size': fields.integer('Size'),
233         'state': fields.selection([('manual','Custom Field'),('base','Base Field')],'Type', required=True, readonly=True, select=1),
234         'on_delete': fields.selection([('cascade','Cascade'),('set null','Set NULL')], 'On delete', help='On delete property for many2one fields'),
235         'domain': fields.char('Domain', size=256, help="The optional domain to restrict possible values for relationship fields, "
236             "specified as a Python expression defining a list of triplets. "
237             "For example: [('color','=','red')]"),
238         'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'),
239         'view_load': fields.boolean('View Auto-Load'),
240         'selectable': fields.boolean('Selectable'),
241         'modules': fields.function(_in_modules, type='char', size=128, string='In modules', help='List of modules in which the field is defined'),
242         'serialization_field_id': fields.many2one('ir.model.fields', 'Serialization Field', domain = "[('ttype','=','serialized')]",
243                                                   ondelete='cascade', help="If set, this field will be stored in the sparse "
244                                                                            "structure of the serialization field, instead "
245                                                                            "of having its own database column. This cannot be "
246                                                                            "changed after creation."),
247     }
248     _rec_name='field_description'
249     _defaults = {
250         'view_load': 0,
251         'selection': "",
252         'domain': "[]",
253         'name': 'x_',
254         'state': lambda self,cr,uid,ctx={}: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
255         'on_delete': 'set null',
256         'select_level': '0',
257         'size': 64,
258         'field_description': '',
259         'selectable': 1,
260     }
261     _order = "name"
262
263     def _check_selection(self, cr, uid, selection, context=None):
264         try:
265             selection_list = eval(selection)
266         except Exception:
267             _logger.warning('Invalid selection list definition for fields.selection', exc_info=True)
268             raise except_orm(_('Error'),
269                     _("The Selection Options expression is not a valid Pythonic expression." \
270                       "Please provide an expression in the [('key','Label'), ...] format."))
271
272         check = True
273         if not (isinstance(selection_list, list) and selection_list):
274             check = False
275         else:
276             for item in selection_list:
277                 if not (isinstance(item, (tuple,list)) and len(item) == 2):
278                     check = False
279                     break
280
281         if not check:
282                 raise except_orm(_('Error'),
283                     _("The Selection Options expression is must be in the [('key','Label'), ...] format!"))
284         return True
285
286     def _size_gt_zero_msg(self, cr, user, ids, context=None):
287         return _('Size of the field can never be less than 1 !')
288
289     _sql_constraints = [
290         ('size_gt_zero', 'CHECK (size>0)',_size_gt_zero_msg ),
291     ]
292
293     def _drop_column(self, cr, uid, ids, context=None):
294         for field in self.browse(cr, uid, ids, context):
295             model = self.pool.get(field.model)
296             cr.execute('select relkind from pg_class where relname=%s', (model._table,))
297             result = cr.fetchone()
298             cr.execute("SELECT column_name FROM information_schema.columns WHERE table_name ='%s' and column_name='%s'" %(model._table, field.name))
299             column_name = cr.fetchone()
300             if column_name and (result and result[0] == 'r'):
301                 cr.execute('ALTER table "%s" DROP column "%s" cascade' % (model._table, field.name))
302             model._columns.pop(field.name, None)
303         return True
304
305     def unlink(self, cr, user, ids, context=None):
306         # Prevent manual deletion of module columns
307         if context is None: context = {}
308         if isinstance(ids, (int, long)):
309             ids = [ids]
310         if not context.get(MODULE_UNINSTALL_FLAG) and \
311                 any(field.state != 'manual' for field in self.browse(cr, user, ids, context)):
312             raise except_orm(_('Error'), _("This column contains module data and cannot be removed!"))
313
314         self._drop_column(cr, user, ids, context)
315         res = super(ir_model_fields, self).unlink(cr, user, ids, context)
316         return res
317
318     def create(self, cr, user, vals, context=None):
319         if 'model_id' in vals:
320             model_data = self.pool.get('ir.model').browse(cr, user, vals['model_id'])
321             vals['model'] = model_data.model
322         if context is None:
323             context = {}
324         if context and context.get('manual',False):
325             vals['state'] = 'manual'
326         if vals.get('ttype', False) == 'selection':
327             if not vals.get('selection',False):
328                 raise except_orm(_('Error'), _('For selection fields, the Selection Options must be given!'))
329             self._check_selection(cr, user, vals['selection'], context=context)
330         res = super(ir_model_fields,self).create(cr, user, vals, context)
331         if vals.get('state','base') == 'manual':
332             if not vals['name'].startswith('x_'):
333                 raise except_orm(_('Error'), _("Custom fields must have a name that starts with 'x_' !"))
334
335             if vals.get('relation',False) and not self.pool.get('ir.model').search(cr, user, [('model','=',vals['relation'])]):
336                 raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
337
338             if self.pool.get(vals['model']):
339                 self.pool.get(vals['model']).__init__(self.pool, cr)
340                 #Added context to _auto_init for special treatment to custom field for select_level
341                 ctx = context.copy()
342                 ctx.update({'field_name':vals['name'],'field_state':'manual','select':vals.get('select_level','0'),'update_custom_fields':True})
343                 self.pool.get(vals['model'])._auto_init(cr, ctx)
344
345         return res
346
347     def write(self, cr, user, ids, vals, context=None):
348         if context is None:
349             context = {}
350         if context and context.get('manual',False):
351             vals['state'] = 'manual'
352
353         #For the moment renaming a sparse field or changing the storing system is not allowed. This may be done later
354         if 'serialization_field_id' in vals or 'name' in vals:
355             for field in self.browse(cr, user, ids, context=context):
356                 if 'serialization_field_id' in vals and field.serialization_field_id.id != vals['serialization_field_id']:
357                     raise except_orm(_('Error!'),  _('Changing the storing system for field "%s" is not allowed.')%field.name)
358                 if field.serialization_field_id and (field.name != vals['name']):
359                     raise except_orm(_('Error!'),  _('Renaming sparse field "%s" is not allowed')%field.name)
360
361         column_rename = None # if set, *one* column can be renamed here
362         obj = None
363         models_patch = {}    # structs of (obj, [(field, prop, change_to),..])
364                              # data to be updated on the orm model
365
366         # static table of properties
367         model_props = [ # (our-name, fields.prop, set_fn)
368             ('field_description', 'string', str),
369             ('required', 'required', bool),
370             ('readonly', 'readonly', bool),
371             ('domain', '_domain', eval),
372             ('size', 'size', int),
373             ('on_delete', 'ondelete', str),
374             ('translate', 'translate', bool),
375             ('view_load', 'view_load', bool),
376             ('selectable', 'selectable', bool),
377             ('select_level', 'select', int),
378             ('selection', 'selection', eval),
379             ]
380
381         if vals and ids:
382             checked_selection = False # need only check it once, so defer
383
384             for item in self.browse(cr, user, ids, context=context):
385                 if not (obj and obj._name == item.model):
386                     obj = self.pool.get(item.model)
387
388                 if item.state != 'manual':
389                     raise except_orm(_('Error!'),
390                         _('Properties of base fields cannot be altered in this manner! '
391                           'Please modify them through Python code, '
392                           'preferably through a custom addon!'))
393
394                 if item.ttype == 'selection' and 'selection' in vals \
395                         and not checked_selection:
396                     self._check_selection(cr, user, vals['selection'], context=context)
397                     checked_selection = True
398
399                 final_name = item.name
400                 if 'name' in vals and vals['name'] != item.name:
401                     # We need to rename the column
402                     if column_rename:
403                         raise except_orm(_('Error!'), _('Can only rename one column at a time!'))
404                     if vals['name'] in obj._columns:
405                         raise except_orm(_('Error!'), _('Cannot rename column to %s, because that column already exists!') % vals['name'])
406                     if vals.get('state', 'base') == 'manual' and not vals['name'].startswith('x_'):
407                         raise except_orm(_('Error!'), _('New column name must still start with x_ , because it is a custom field!'))
408                     if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']:
409                         raise ValueError('Invalid character in column name')
410                     column_rename = (obj, (obj._table, item.name, vals['name']))
411                     final_name = vals['name']
412
413                 if 'model_id' in vals and vals['model_id'] != item.model_id:
414                     raise except_orm(_("Error!"), _("Changing the model of a field is forbidden!"))
415
416                 if 'ttype' in vals and vals['ttype'] != item.ttype:
417                     raise except_orm(_("Error!"), _("Changing the type of a column is not yet supported. "
418                                 "Please drop it and create it again!"))
419
420                 # We don't check the 'state', because it might come from the context
421                 # (thus be set for multiple fields) and will be ignored anyway.
422                 if obj:
423                     models_patch.setdefault(obj._name, (obj,[]))
424                     # find out which properties (per model) we need to update
425                     for field_name, field_property, set_fn in model_props:
426                         if field_name in vals:
427                             property_value = set_fn(vals[field_name])
428                             if getattr(obj._columns[item.name], field_property) != property_value:
429                                 models_patch[obj._name][1].append((final_name, field_property, property_value))
430                         # our dict is ready here, but no properties are changed so far
431
432         # These shall never be written (modified)
433         for column_name in ('model_id', 'model', 'state'):
434             if column_name in vals:
435                 del vals[column_name]
436
437         res = super(ir_model_fields,self).write(cr, user, ids, vals, context=context)
438
439         if column_rename:
440             cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % column_rename[1])
441             # This is VERY risky, but let us have this feature:
442             # we want to change the key of column in obj._columns dict
443             col = column_rename[0]._columns.pop(column_rename[1][1]) # take object out, w/o copy
444             column_rename[0]._columns[column_rename[1][2]] = col
445
446         if models_patch:
447             # We have to update _columns of the model(s) and then call their
448             # _auto_init to sync the db with the model. Hopefully, since write()
449             # was called earlier, they will be in-sync before the _auto_init.
450             # Anything we don't update in _columns now will be reset from
451             # the model into ir.model.fields (db).
452             ctx = context.copy()
453             ctx.update({'select': vals.get('select_level','0'),'update_custom_fields':True})
454
455             for __, patch_struct in models_patch.items():
456                 obj = patch_struct[0]
457                 for col_name, col_prop, val in patch_struct[1]:
458                     setattr(obj._columns[col_name], col_prop, val)
459                 obj._auto_init(cr, ctx)
460         return res
461
462 ir_model_fields()
463
464 class ir_model_access(osv.osv):
465     _name = 'ir.model.access'
466     _columns = {
467         'name': fields.char('Name', size=64, required=True, select=True),
468         'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)], select=True, ondelete='cascade'),
469         'group_id': fields.many2one('res.groups', 'Group', ondelete='cascade', select=True),
470         'perm_read': fields.boolean('Read Access'),
471         'perm_write': fields.boolean('Write Access'),
472         'perm_create': fields.boolean('Create Access'),
473         'perm_unlink': fields.boolean('Delete Access'),
474     }
475
476     def check_groups(self, cr, uid, group):
477         grouparr  = group.split('.')
478         if not grouparr:
479             return False
480         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],))
481         return bool(cr.fetchone())
482
483     def check_group(self, cr, uid, model, mode, group_ids):
484         """ Check if a specific group has the access mode to the specified model"""
485         assert mode in ['read','write','create','unlink'], 'Invalid access mode'
486
487         if isinstance(model, browse_record):
488             assert model._table_name == 'ir.model', 'Invalid model object'
489             model_name = model.name
490         else:
491             model_name = model
492
493         if isinstance(group_ids, (int, long)):
494             group_ids = [group_ids]
495         for group_id in group_ids:
496             cr.execute("SELECT perm_" + mode + " "
497                    "  FROM ir_model_access a "
498                    "  JOIN ir_model m ON (m.id = a.model_id) "
499                    " WHERE m.model = %s AND a.group_id = %s", (model_name, group_id)
500                    )
501             r = cr.fetchone()
502             if r is None:
503                 cr.execute("SELECT perm_" + mode + " "
504                        "  FROM ir_model_access a "
505                        "  JOIN ir_model m ON (m.id = a.model_id) "
506                        " WHERE m.model = %s AND a.group_id IS NULL", (model_name, )
507                        )
508                 r = cr.fetchone()
509
510             access = bool(r and r[0])
511             if access:
512                 return True
513         # pass no groups -> no access
514         return False
515
516     def group_names_with_access(self, cr, model_name, access_mode):
517         """Returns the names of visible groups which have been granted ``access_mode`` on
518            the model ``model_name``.
519            :rtype: list
520         """
521         assert access_mode in ['read','write','create','unlink'], 'Invalid access mode: %s' % access_mode
522         cr.execute('''SELECT
523                         g.name
524                       FROM
525                         ir_model_access a
526                         JOIN ir_model m ON (a.model_id=m.id)
527                         JOIN res_groups g ON (a.group_id=g.id)
528                       WHERE
529                         m.model=%s AND
530                         a.perm_''' + access_mode, (model_name,))
531         return [x[0] for x in cr.fetchall()]
532
533     @tools.ormcache()
534     def check(self, cr, uid, model, mode='read', raise_exception=True, context=None):
535         if uid==1:
536             # User root have all accesses
537             # TODO: exclude xml-rpc requests
538             return True
539
540         assert mode in ['read','write','create','unlink'], 'Invalid access mode'
541
542         if isinstance(model, browse_record):
543             assert model._table_name == 'ir.model', 'Invalid model object'
544             model_name = model.model
545         else:
546             model_name = model
547
548         # TransientModel records have no access rights, only an implicit access rule
549         if self.pool.get(model_name).is_transient():
550             return True
551
552         # We check if a specific rule exists
553         cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
554                    '  FROM ir_model_access a '
555                    '  JOIN ir_model m ON (m.id = a.model_id) '
556                    '  JOIN res_groups_users_rel gu ON (gu.gid = a.group_id) '
557                    ' WHERE m.model = %s '
558                    '   AND gu.uid = %s '
559                    , (model_name, uid,)
560                    )
561         r = cr.fetchone()[0]
562
563         if r is None:
564             # there is no specific rule. We check the generic rule
565             cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
566                        '  FROM ir_model_access a '
567                        '  JOIN ir_model m ON (m.id = a.model_id) '
568                        ' WHERE a.group_id IS NULL '
569                        '   AND m.model = %s '
570                        , (model_name,)
571                        )
572             r = cr.fetchone()[0]
573
574         if not r and raise_exception:
575             groups = ', '.join(self.group_names_with_access(cr, model_name, mode)) or '/'
576             msgs = {
577                 'read':   _("You can not read this document (%s) ! Be sure your user belongs to one of these groups: %s."),
578                 'write':  _("You can not write in this document (%s) ! Be sure your user belongs to one of these groups: %s."),
579                 'create': _("You can not create this document (%s) ! Be sure your user belongs to one of these groups: %s."),
580                 'unlink': _("You can not delete this document (%s) ! Be sure your user belongs to one of these groups: %s."),
581             }
582
583             raise except_orm(_('AccessError'), msgs[mode] % (model_name, groups) )
584         return r or False
585
586     __cache_clearing_methods = []
587
588     def register_cache_clearing_method(self, model, method):
589         self.__cache_clearing_methods.append((model, method))
590
591     def unregister_cache_clearing_method(self, model, method):
592         try:
593             i = self.__cache_clearing_methods.index((model, method))
594             del self.__cache_clearing_methods[i]
595         except ValueError:
596             pass
597
598     def call_cache_clearing_methods(self, cr):
599         self.check.clear_cache(self)    # clear the cache of check function
600         for model, method in self.__cache_clearing_methods:
601             object_ = self.pool.get(model)
602             if object_:
603                 getattr(object_, method)()
604
605     #
606     # Check rights on actions
607     #
608     def write(self, cr, uid, *args, **argv):
609         self.call_cache_clearing_methods(cr)
610         res = super(ir_model_access, self).write(cr, uid, *args, **argv)
611         return res
612
613     def create(self, cr, uid, *args, **argv):
614         self.call_cache_clearing_methods(cr)
615         res = super(ir_model_access, self).create(cr, uid, *args, **argv)
616         return res
617
618     def unlink(self, cr, uid, *args, **argv):
619         self.call_cache_clearing_methods(cr)
620         res = super(ir_model_access, self).unlink(cr, uid, *args, **argv)
621         return res
622
623 ir_model_access()
624
625 class ir_model_data(osv.osv):
626     """Holds external identifier keys for records in the database.
627        This has two main uses:
628
629            * allows easy data integration with third-party systems,
630              making import/export/sync of data possible, as records
631              can be uniquely identified across multiple systems
632            * allows tracking the origin of data installed by OpenERP
633              modules themselves, thus making it possible to later
634              update them seamlessly.
635     """
636     _name = 'ir.model.data'
637     _order = 'module,model,name'
638     _columns = {
639         'name': fields.char('External Identifier', required=True, size=128, select=1,
640                             help="External Key/Identifier that can be used for "
641                                  "data integration with third-party systems"),
642         'model': fields.char('Model Name', required=True, size=64, select=1),
643         'module': fields.char('Module', required=True, size=64, select=1),
644         'res_id': fields.integer('Record ID', select=1,
645                                  help="ID of the target record in the database"),
646         'noupdate': fields.boolean('Non Updatable'),
647         'date_update': fields.datetime('Update Date'),
648         'date_init': fields.datetime('Init Date')
649     }
650     _defaults = {
651         'date_init': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
652         'date_update': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
653         'noupdate': False,
654         'module': ''
655     }
656     _sql_constraints = [
657         ('module_name_uniq', 'unique(name, module)', 'You cannot have multiple records with the same external ID in the same module!'),
658     ]
659
660     def __init__(self, pool, cr):
661         osv.osv.__init__(self, pool, cr)
662         self.doinit = True
663         # also stored in pool to avoid being discarded along with this osv instance
664         if getattr(pool, 'model_data_reference_ids', None) is None:
665             self.pool.model_data_reference_ids = {}
666
667         self.loads = self.pool.model_data_reference_ids
668
669     def _auto_init(self, cr, context=None):
670         super(ir_model_data, self)._auto_init(cr, context)
671         cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_model_data_module_name_index\'')
672         if not cr.fetchone():
673             cr.execute('CREATE INDEX ir_model_data_module_name_index ON ir_model_data (module, name)')
674
675     @tools.ormcache()
676     def _get_id(self, cr, uid, module, xml_id):
677         """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"""
678         ids = self.search(cr, uid, [('module','=',module), ('name','=', xml_id)])
679         if not ids:
680             raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
681         # the sql constraints ensure us we have only one result
682         return ids[0]
683
684     @tools.ormcache()
685     def get_object_reference(self, cr, uid, module, xml_id):
686         """Returns (model, res_id) corresponding to a given module and xml_id (cached) or raise ValueError if not found"""
687         data_id = self._get_id(cr, uid, module, xml_id)
688         res = self.read(cr, uid, data_id, ['model', 'res_id'])
689         if not res['res_id']:
690             raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
691         return (res['model'], res['res_id'])
692
693     def get_object(self, cr, uid, module, xml_id, context=None):
694         """Returns a browsable record for the given module name and xml_id or raise ValueError if not found"""
695         res_model, res_id = self.get_object_reference(cr, uid, module, xml_id)
696         result = self.pool.get(res_model).browse(cr, uid, res_id, context=context)
697         if not result.exists():
698             raise ValueError('No record found for unique ID %s.%s. It may have been deleted.' % (module, xml_id))
699         return result
700
701     def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
702         if not xml_id:
703             return False
704         try:
705             id = self.read(cr, uid, [self._get_id(cr, uid, module, xml_id)], ['res_id'])[0]['res_id']
706             self.loads[(module,xml_id)] = (model,id)
707         except:
708             id = False
709         return id
710
711
712     def unlink(self, cr, uid, ids, context=None):
713         """ Regular unlink method, but make sure to clear the caches. """
714         self._get_id.clear_cache(self)
715         self.get_object_reference.clear_cache(self)
716         return super(ir_model_data,self).unlink(cr, uid, ids, context=context)
717
718     def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None):
719         model_obj = self.pool.get(model)
720         if not context:
721             context = {}
722         # records created during module install should result in res.log entries that are already read!
723         context = dict(context, res_log_read=True)
724         if xml_id and ('.' in xml_id):
725             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)
726             module, xml_id = xml_id.split('.')
727         if (not xml_id) and (not self.doinit):
728             return False
729         action_id = False
730         if xml_id:
731             cr.execute('''SELECT imd.id, imd.res_id, md.id, imd.model
732                           FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
733                           WHERE imd.module=%%s AND imd.name=%%s''' % model_obj._table,
734                           (module, xml_id))
735             results = cr.fetchall()
736             for imd_id2,res_id2,real_id2,real_model in results:
737                 if not real_id2:
738                     self._get_id.clear_cache(self, uid, module, xml_id)
739                     self.get_object_reference.clear_cache(self, uid, module, xml_id)
740                     cr.execute('delete from ir_model_data where id=%s', (imd_id2,))
741                     res_id = False
742                 else:
743                     assert model == real_model, "External ID conflict, %s already refers to a `%s` record,"\
744                         " you can't define a `%s` record with this ID." % (xml_id, real_model, model)
745                     res_id,action_id = res_id2,imd_id2
746
747         if action_id and res_id:
748             model_obj.write(cr, uid, [res_id], values, context=context)
749             self.write(cr, uid, [action_id], {
750                 'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
751                 },context=context)
752         elif res_id:
753             model_obj.write(cr, uid, [res_id], values, context=context)
754             if xml_id:
755                 self.create(cr, uid, {
756                     'name': xml_id,
757                     'model': model,
758                     'module':module,
759                     'res_id':res_id,
760                     'noupdate': noupdate,
761                     },context=context)
762                 if model_obj._inherits:
763                     for table in model_obj._inherits:
764                         inherit_id = model_obj.browse(cr, uid,
765                                 res_id,context=context)[model_obj._inherits[table]]
766                         self.create(cr, uid, {
767                             'name': xml_id + '_' + table.replace('.', '_'),
768                             'model': table,
769                             'module': module,
770                             'res_id': inherit_id.id,
771                             'noupdate': noupdate,
772                             },context=context)
773         else:
774             if mode=='init' or (mode=='update' and xml_id):
775                 res_id = model_obj.create(cr, uid, values, context=context)
776                 if xml_id:
777                     self.create(cr, uid, {
778                         'name': xml_id,
779                         'model': model,
780                         'module': module,
781                         'res_id': res_id,
782                         'noupdate': noupdate
783                         },context=context)
784                     if model_obj._inherits:
785                         for table in model_obj._inherits:
786                             inherit_id = model_obj.browse(cr, uid,
787                                     res_id,context=context)[model_obj._inherits[table]]
788                             self.create(cr, uid, {
789                                 'name': xml_id + '_' + table.replace('.', '_'),
790                                 'model': table,
791                                 'module': module,
792                                 'res_id': inherit_id.id,
793                                 'noupdate': noupdate,
794                                 },context=context)
795         if xml_id:
796             if res_id:
797                 self.loads[(module, xml_id)] = (model, res_id)
798                 if model_obj._inherits:
799                     for table in model_obj._inherits:
800                         inherit_field = model_obj._inherits[table]
801                         inherit_id = model_obj.read(cr, uid, res_id,
802                                 [inherit_field])[inherit_field]
803                         self.loads[(module, xml_id + '_' + \
804                                 table.replace('.', '_'))] = (table, inherit_id)
805         return res_id
806
807     def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None, xml_id=False):
808         if type(models[0])==type([]) or type(models[0])==type(()):
809             model,res_id = models[0]
810         else:
811             res_id=None
812             model = models[0]
813
814         if res_id:
815             where = ' and res_id=%s' % (res_id,)
816         else:
817             where = ' and (res_id is null)'
818
819         if key2:
820             where += ' and key2=\'%s\'' % (key2,)
821         else:
822             where += ' and (key2 is null)'
823
824         cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
825         res = cr.fetchone()
826         if not res:
827             ir_values_obj = pooler.get_pool(cr.dbname).get('ir.values')
828             res = ir_values_obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
829         elif xml_id:
830             cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
831         return True
832
833     def _module_data_uninstall(self, cr, uid, ids, context=None):
834         """Deletes all the records referenced by the ir.model.data entries
835         ``ids`` along with their corresponding database backed (including
836         dropping tables, columns, FKs, etc, as long as there is no other
837         ir.model.data entry holding a reference to them (which indicates that
838         they are still owned by another module). 
839         Attempts to perform the deletion in an appropriate order to maximize
840         the chance of gracefully deleting all records.
841         This step is performed as part of the full uninstallation of a module.
842         """ 
843
844         if uid != 1 and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"):
845             raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
846
847         context = dict(context or {})
848         context[MODULE_UNINSTALL_FLAG] = True # enable model/field deletion
849
850         ids_set = set(ids)
851         wkf_todo = []
852         to_unlink = []
853         to_drop_table = []
854         ids.sort()
855         ids.reverse()
856         for data in self.browse(cr, uid, ids, context):
857             model = data.model
858             res_id = data.res_id
859             model_obj = self.pool.get(model)
860             name = tools.ustr(data.name)
861
862             if name.startswith(EXT_ID_PREFIX_FK) or name.startswith(EXT_ID_PREFIX_M2M_TABLE)\
863                  or name.startswith(EXT_ID_PREFIX_CONSTRAINT):
864                 # double-check we are really going to delete all the owners of this schema element
865                 cr.execute("""SELECT id from ir_model_data where name = %s and res_id IS NULL""", (data.name,))
866                 external_ids = [x[0] for x in cr.fetchall()]
867                 if (set(external_ids)-ids_set):
868                     # as installed modules have defined this element we must not delete it!
869                     continue
870
871             if name.startswith(EXT_ID_PREFIX_FK):
872                 name = name[len(EXT_ID_PREFIX_FK):]
873                 # test if FK exists on this table (it could be on a related m2m table, in which case we ignore it)
874                 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
875                               WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_obj._table))
876                 if cr.fetchone():
877                     cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
878                     _logger.info('Dropped FK CONSTRAINT %s@%s', name, model)
879                 continue
880
881             if name.startswith(EXT_ID_PREFIX_M2M_TABLE):
882                 name = name[len(EXT_ID_PREFIX_M2M_TABLE):]
883                 cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name=%s", (name,))
884                 if cr.fetchone() and not name in to_drop_table:
885                     to_drop_table.append(name)
886                 continue
887
888             if name.startswith(EXT_ID_PREFIX_CONSTRAINT):
889                 name = name[len(EXT_ID_PREFIX_CONSTRAINT):]
890                 # test if constraint exists
891                 cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
892                               WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_obj._table))
893                 if cr.fetchone():
894                     cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
895                     _logger.info('Dropped CONSTRAINT %s@%s', name, model)
896                 continue
897
898             pair_to_unlink = (model, res_id)
899             if pair_to_unlink not in to_unlink:
900                 to_unlink.append(pair_to_unlink)
901
902             if model == 'workflow.activity':
903                 # Special treatment for workflow activities: temporarily revert their
904                 # incoming transition and trigger an update to force all workflow items
905                 # to move out before deleting them
906                 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,))
907                 wkf_todo.extend(cr.fetchall())
908                 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))
909
910         wf_service = netsvc.LocalService("workflow")
911         for model,res_id in wkf_todo:
912             try:
913                 wf_service.trg_write(uid, model, res_id, cr)
914             except:
915                 _logger.info('Unable to force processing of workflow for item %s@%s in order to leave activity to be deleted', res_id, model)
916
917         # drop m2m relation tables
918         for table in to_drop_table:
919             cr.execute('DROP TABLE %s CASCADE'% (table),)
920             _logger.info('Dropped table %s', table)
921
922         for (model, res_id) in to_unlink:
923             if model in ('ir.model','ir.model.fields', 'ir.model.data'):
924                 continue
925             external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
926             if (set(external_ids)-ids_set):
927                 # if other modules have defined this record, we do not delete it
928                 continue
929             _logger.info('Deleting %s@%s', res_id, model)
930             try:
931                 self.pool.get(model).unlink(cr, uid, [res_id], context=context)
932             except:
933                 _logger.info('Unable to delete %s@%s', res_id, model, exc_info=True)
934             cr.commit()
935
936         for (model, res_id) in to_unlink:
937             if model != 'ir.model.fields':
938                 continue
939             external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
940             if (set(external_ids)-ids_set):
941                 # if other modules have defined this record, we do not delete it
942                 continue
943             _logger.info('Deleting %s@%s', res_id, model)
944             self.pool.get(model).unlink(cr, uid, [res_id], context=context)
945
946         for (model, res_id) in to_unlink:
947             if model != 'ir.model':
948                 continue
949             external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
950             if (set(external_ids)-ids_set):
951                 # if other modules have defined this record, we do not delete it
952                 continue
953             _logger.info('Deleting %s@%s', res_id, model)
954             self.pool.get(model).unlink(cr, uid, [res_id], context=context)
955         cr.commit()
956
957     def _process_end(self, cr, uid, modules):
958         """ Clear records removed from updated module data.
959         This method is called at the end of the module loading process.
960         It is meant to removed records that are no longer present in the
961         updated data. Such records are recognised as the one with an xml id
962         and a module in ir_model_data and noupdate set to false, but not
963         present in self.loads.
964         """
965         if not modules:
966             return True
967         to_unlink = []
968         cr.execute("""SELECT id,name,model,res_id,module FROM ir_model_data
969                       WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s""",
970                       (tuple(modules), False))
971         for (id, name, model, res_id, module) in cr.fetchall():
972             if (module,name) not in self.loads:
973                 to_unlink.append((model,res_id))
974         if not config.get('import_partial'):
975             for (model, res_id) in to_unlink:
976                 if self.pool.get(model):
977                     _logger.info('Deleting %s@%s', res_id, model)
978                     self.pool.get(model).unlink(cr, uid, [res_id])
979
980
981 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: