1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
23 from osv import fields,osv
26 from osv.orm import except_orm, browse_record
30 from tools import config
33 def _get_fields_type(self, cr, uid, context=None):
34 cr.execute('select distinct ttype,ttype from ir_model_fields')
37 class ir_model(osv.osv):
39 _description = "Objects"
42 'name': fields.char('Object Name', size=64, translate=True, required=True),
43 'model': fields.char('Object Name', size=64, required=True, select=1),
44 'info': fields.text('Information'),
45 'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields', required=True),
46 'state': fields.selection([('manual','Custom Object'),('base','Base Object')],'Manually Created',readonly=True),
47 'access_ids': fields.one2many('ir.model.access', 'model_id', 'Access'),
50 'model': lambda *a: 'x_',
51 'state': lambda self,cr,uid,ctx={}: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
53 def _check_model_name(self, cr, uid, ids):
54 for model in self.browse(cr, uid, ids):
55 if model.state=='manual':
56 if not model.model.startswith('x_'):
58 if not re.match('^[a-z_A-Z0-9.]+$',model.model):
63 (_check_model_name, 'The Object name must start with x_ and not contain any special character !', ['model']),
65 def unlink(self, cr, user, ids, context=None):
66 for model in self.browse(cr, user, ids, context):
67 if model.state <> 'manual':
68 raise except_orm(_('Error'), _("You can not remove the model '%s' !") %(model.name,))
69 res = super(ir_model, self).unlink(cr, user, ids, context)
70 pooler.restart_pool(cr.dbname)
73 def create(self, cr, user, vals, context=None):
74 if context and context.get('manual',False):
75 vals['state']='manual'
76 res = super(ir_model,self).create(cr, user, vals, context)
77 if vals.get('state','base')=='manual':
78 self.instanciate(cr, user, vals['model'], context)
79 self.pool.get(vals['model']).__init__(self.pool, cr)
80 self.pool.get(vals['model'])._auto_init(cr,{})
81 #pooler.restart_pool(cr.dbname)
84 def instanciate(self, cr, user, model, context={}):
85 class x_custom_model(osv.osv):
87 x_custom_model._name = model
88 x_custom_model._module = False
89 x_custom_model.createInstance(self.pool, '', cr)
90 x_custom_model._rec_name = 'x_name'
94 class ir_model_grid(osv.osv):
95 _name = 'ir.model.grid'
98 _description = "Objects Security Grid"
101 # 'name': fields.char('Object', size=64),
102 # 'model': fields.char('Object Name', size=64),
105 def create(self, cr, uid, vals, context=None):
106 raise osv.except_osv('Error !', 'You cannot add an entry to this view !')
108 def unlink(self, *args, **argv):
109 raise osv.except_osv('Error !', 'You cannot delete an entry of this view !')
111 def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
112 result = super(osv.osv, self).read(cr, uid, ids, fields, context, load)
113 allgr = self.pool.get('res.groups').search(cr, uid, [], context=context)
114 acc_obj = self.pool.get('ir.model.access')
116 if not isinstance(result,list):
120 rules = acc_obj.search(cr, uid, [('model_id', '=', res['id'])])
121 rules_br = acc_obj.browse(cr, uid, rules, context=context)
123 res['group_'+str(g)] = ''
124 for rule in rules_br:
127 perm_list.append('r')
129 perm_list.append('w')
131 perm_list.append('c')
133 perm_list.append('u')
134 perms = ",".join(perm_list)
136 res['group_%d'%rule.group_id.id] = perms
138 res['group_0'] = perms
142 # This function do not write fields from ir.model because
143 # access rights may be different for managing models and
146 def write(self, cr, uid, ids, vals, context=None):
147 vals_new = vals.copy()
148 acc_obj = self.pool.get('ir.model.access')
149 for grid in self.browse(cr, uid, ids, context=context):
151 perms_rel = ['read','write','create','unlink']
153 if not val[:6]=='group_':
155 group_id = int(val[6:]) or False
156 rules = acc_obj.search(cr, uid, [('model_id', '=', model_id),('group_id', '=', group_id)])
158 rules = [acc_obj.create(cr, uid, {
163 vals2 = dict(map(lambda x: ('perm_'+x, x[0] in (vals[val] or '')), perms_rel))
164 acc_obj.write(cr, uid, rules, vals2, context=context)
167 def fields_get(self, cr, uid, fields=None, context=None, read_access=True):
168 result = super(ir_model_grid, self).fields_get(cr, uid, fields, context)
169 groups = self.pool.get('res.groups').search(cr, uid, [])
170 groups_br = self.pool.get('res.groups').browse(cr, uid, groups)
171 result['group_0'] = {'string': 'All Users','type': 'char','size': 7}
172 for group in groups_br:
173 result['group_%d'%group.id] = {'string': '%s'%group.name,'type': 'char','size': 7}
176 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context={}, toolbar=False):
177 result = super(ir_model_grid, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar)
178 groups = self.pool.get('res.groups').search(cr, uid, [])
179 groups_br = self.pool.get('res.groups').browse(cr, uid, groups)
180 cols = ['model', 'name']
181 xml = '''<?xml version="1.0"?>
182 <%s editable="bottom">
183 <field name="name" select="1" readonly="1" required="1"/>
184 <field name="model" select="1" readonly="1" required="1"/>
185 <field name="group_0"/>
187 for group in groups_br:
188 xml += '''<field name="group_%d"/>''' % (group.id, )
189 xml += '''</%s>''' % (view_type,)
191 result['fields'] = self.fields_get(cr, uid, cols, context)
195 class ir_model_fields(osv.osv):
196 _name = 'ir.model.fields'
197 _description = "Fields"
199 'name': fields.char('Name', required=True, size=64, select=1),
200 'model': fields.char('Object Name', size=64, required=True),
201 'relation': fields.char('Object Relation', size=64),
202 'relation_field': fields.char('Relation Field', size=64),
203 'model_id': fields.many2one('ir.model', 'Object ID', required=True, select=True, ondelete='cascade'),
204 'field_description': fields.char('Field Label', required=True, size=256),
205 'ttype': fields.selection(_get_fields_type, 'Field Type',size=64, required=True),
206 'selection': fields.char('Field Selection',size=128),
207 'required': fields.boolean('Required'),
208 'readonly': fields.boolean('Readonly'),
209 'select_level': fields.selection([('0','Not Searchable'),('1','Always Searchable'),('2','Advanced Search')],'Searchable', required=True),
210 'translate': fields.boolean('Translate'),
211 'size': fields.integer('Size'),
212 'state': fields.selection([('manual','Custom Field'),('base','Base Field')],'Manually Created', required=True, readonly=True),
213 'on_delete': fields.selection([('cascade','Cascade'),('set null','Set NULL')], 'On delete', help='On delete property for many2one fields'),
214 'domain': fields.char('Domain', size=256),
215 'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'),
216 'view_load': fields.boolean('View Auto-Load'),
218 _rec_name='field_description'
220 'view_load': lambda *a: 0,
221 'selection': lambda *a: "[]",
222 'domain': lambda *a: "[]",
223 'name': lambda *a: 'x_',
224 'state': lambda self,cr,uid,ctx={}: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
225 'on_delete': lambda *a: 'set null',
226 'select_level': lambda *a: '0',
227 'size': lambda *a: 64,
228 'field_description': lambda *a: '',
231 def unlink(self, cr, user, ids, context=None):
232 for field in self.browse(cr, user, ids, context):
233 if field.state <> 'manual':
234 raise except_orm(_('Error'), _("You can not remove the field '%s' !") %(field.name,))
236 # MAY BE ADD A ALTER TABLE DROP ?
238 return super(ir_model_fields, self).unlink(cr, user, ids, context)
240 def create(self, cr, user, vals, context=None):
241 if 'model_id' in vals:
242 model_data = self.pool.get('ir.model').browse(cr, user, vals['model_id'])
243 vals['model'] = model_data.model
244 if context and context.get('manual',False):
245 vals['state'] = 'manual'
246 res = super(ir_model_fields,self).create(cr, user, vals, context)
247 if vals.get('state','base') == 'manual':
248 if not vals['name'].startswith('x_'):
249 raise except_orm(_('Error'), _("Custom fields must have a name that starts with 'x_' !"))
250 if self.pool.get(vals['model']):
251 self.pool.get(vals['model']).__init__(self.pool, cr)
252 self.pool.get(vals['model'])._auto_init(cr, {})
256 class ir_model_access(osv.osv):
257 _name = 'ir.model.access'
259 'name': fields.char('Name', size=64, required=True),
260 'model_id': fields.many2one('ir.model', 'Object', required=True),
261 'group_id': fields.many2one('res.groups', 'Group'),
262 'perm_read': fields.boolean('Read Access'),
263 'perm_write': fields.boolean('Write Access'),
264 'perm_create': fields.boolean('Create Access'),
265 'perm_unlink': fields.boolean('Delete Permission'),
268 def check_groups(self, cr, uid, group):
270 grouparr = group.split('.')
274 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],))
275 return bool(cr.fetchone())
277 def check_group(self, cr, uid, model, mode, group_ids):
278 """ Check if a specific group has the access mode to the specified model"""
279 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
281 if isinstance(model, browse_record):
282 assert model._table_name == 'ir.model', 'Invalid model object'
283 model_name = model.name
287 if isinstance(group_ids, (int, long)):
288 group_ids = [group_ids]
289 for group_id in group_ids:
290 cr.execute("SELECT perm_" + mode + " "
291 " FROM ir_model_access a "
292 " JOIN ir_model m ON (m.id = a.model_id) "
293 " WHERE m.model = %s AND a.group_id = %s", (model_name, group_id)
297 cr.execute("SELECT perm_" + mode + " "
298 " FROM ir_model_access a "
299 " JOIN ir_model m ON (m.id = a.model_id) "
300 " WHERE m.model = %s AND a.group_id IS NULL", (model_name, )
304 access = bool(r and r[0])
307 # pass no groups -> no access
310 def check(self, cr, uid, model, mode='read', raise_exception=True):
312 # User root have all accesses
313 # TODO: exclude xml-rpc requests
316 assert mode in ['read','write','create','unlink'], 'Invalid access mode'
318 if isinstance(model, browse_record):
319 assert model._table_name == 'ir.model', 'Invalid model object'
320 model_name = model.name
324 # We check if a specific rule exists
325 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
326 ' FROM ir_model_access a '
327 ' JOIN ir_model m ON (m.id = a.model_id) '
328 ' JOIN res_groups_users_rel gu ON (gu.gid = a.group_id) '
329 ' WHERE m.model = %s '
336 # there is no specific rule. We check the generic rule
337 cr.execute('SELECT MAX(CASE WHEN perm_' + mode + ' THEN 1 ELSE 0 END) '
338 ' FROM ir_model_access a '
339 ' JOIN ir_model m ON (m.id = a.model_id) '
340 ' WHERE a.group_id IS NULL '
346 if not r and raise_exception:
348 'read': _('You can not read this document! (%s)'),
349 'write': _('You can not write in this document! (%s)'),
350 'create': _('You can not create this kind of document! (%s)'),
351 'unlink': _('You can not delete this document! (%s)'),
353 raise except_orm(_('AccessError'), msgs[mode] % model_name )
356 check = tools.cache()(check)
358 __cache_clearing_methods = []
360 def register_cache_clearing_method(self, model, method):
361 self.__cache_clearing_methods.append((model, method))
363 def unregister_cache_clearing_method(self, model, method):
365 i = self.__cache_clearing_methods.index((model, method))
366 del self.__cache_clearing_methods[i]
370 def call_cache_clearing_methods(self):
371 for model, method in self.__cache_clearing_methods:
372 getattr(self.pool.get(model), method)()
375 # Check rights on actions
377 def write(self, cr, uid, *args, **argv):
378 self.call_cache_clearing_methods()
379 res = super(ir_model_access, self).write(cr, uid, *args, **argv)
380 self.check.clear_cache(cr.dbname) # clear the cache of check function
383 def create(self, cr, uid, *args, **argv):
384 self.call_cache_clearing_methods()
385 res = super(ir_model_access, self).create(cr, uid, *args, **argv)
386 self.check.clear_cache(cr.dbname) # clear the cache of check function
389 def unlink(self, cr, uid, *args, **argv):
390 self.call_cache_clearing_methods()
391 res = super(ir_model_access, self).unlink(cr, uid, *args, **argv)
392 self.check.clear_cache(cr.dbname) # clear the cache of check function
397 class ir_model_data(osv.osv):
398 _name = 'ir.model.data'
400 'name': fields.char('XML Identifier', required=True, size=64),
401 'model': fields.char('Object', required=True, size=64),
402 'module': fields.char('Module', required=True, size=64),
403 'res_id': fields.integer('Resource ID'),
404 'noupdate': fields.boolean('Non Updatable'),
405 'date_update': fields.datetime('Update Date'),
406 'date_init': fields.datetime('Init Date')
409 'date_init': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
410 'date_update': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
411 'noupdate': lambda *a: False
414 def __init__(self, pool, cr):
415 osv.osv.__init__(self, pool, cr)
418 self.unlink_mark = {}
420 def _get_id(self,cr, uid, module, xml_id):
421 ids = self.search(cr, uid, [('module','=',module),('name','=', xml_id)])
422 assert len(ids)==1, '%d reference(s) to %s.%s. You should have one and only one !' % (len(ids), module, xml_id)
424 _get_id = tools.cache(skiparg=2)(_get_id)
426 def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
430 id = self.read(cr, uid, [self._get_id(cr, uid, module, xml_id)], ['res_id'])[0]['res_id']
431 self.loads[(module,xml_id)] = (model,id)
436 def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False):
438 model_obj = self.pool.get(model)
440 if xml_id and ('.' in xml_id):
441 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)
443 module, xml_id = xml_id.split('.')
444 if (not xml_id) and (not self.doinit):
448 cr.execute('select id,res_id from ir_model_data where module=%s and name=%s', (module,xml_id))
449 results = cr.fetchall()
450 for action_id2,res_id2 in results:
451 cr.execute('select id from '+self.pool.get(model)._table+' where id=%s', (res_id2,))
452 result3 = cr.fetchone()
454 cr.execute('delete from ir_model_data where id=%s', (action_id2,))
456 res_id,action_id = res_id2,action_id2
458 if action_id and res_id:
459 model_obj.write(cr, uid, [res_id], values)
460 self.write(cr, uid, [action_id], {
461 'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
464 model_obj.write(cr, uid, [res_id], values)
466 self.create(cr, uid, {
471 'noupdate': noupdate,
473 if model_obj._inherits:
474 for table in model_obj._inherits:
475 inherit_id = model_obj.browse(cr, uid,
476 res_id)[model_obj._inherits[table]]
477 self.create(cr, uid, {
478 'name': xml_id + '_' + table.replace('.', '_'),
481 'res_id': inherit_id,
482 'noupdate': noupdate,
485 if mode=='init' or (mode=='update' and xml_id):
486 res_id = model_obj.create(cr, uid, values)
488 self.create(cr, uid, {
495 if model_obj._inherits:
496 for table in model_obj._inherits:
497 inherit_id = model_obj.browse(cr, uid,
498 res_id)[model_obj._inherits[table]]
499 self.create(cr, uid, {
500 'name': xml_id + '_' + table.replace('.', '_'),
503 'res_id': inherit_id,
504 'noupdate': noupdate,
508 self.loads[(module, xml_id)] = (model, res_id)
509 if model_obj._inherits:
510 for table in model_obj._inherits:
511 inherit_field = model_obj._inherits[table]
512 inherit_id = model_obj.read(cr, uid, res_id,
513 [inherit_field])[inherit_field]
514 self.loads[(module, xml_id + '_' + \
515 table.replace('.', '_'))] = (table, inherit_id)
518 def _unlink(self, cr, uid, model, ids, direct=False):
519 #self.pool.get(model).unlink(cr, uid, ids)
521 self.unlink_mark[(model, id)]=False
522 cr.execute('delete from ir_model_data where res_id=%s and model=\'%s\'', (id,model))
525 def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None, xml_id=False):
526 obj = self.pool.get('ir.values')
527 if type(models[0])==type([]) or type(models[0])==type(()):
528 model,res_id = models[0]
534 where = ' and res_id=%s' % (res_id,)
536 where = ' and (res_id is null)'
539 where += ' and key2=\'%s\'' % (key2,)
541 where += ' and (key2 is null)'
543 cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
546 res = ir.ir_set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
548 cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
551 def _process_end(self, cr, uid, modules):
554 modules = list(modules)
555 module_in = ",".join(["%s"] * len(modules))
556 cr.execute('select id,name,model,res_id,module from ir_model_data where module in (' + module_in + ') and noupdate=%s', modules + [False])
558 for (id, name, model, res_id,module) in cr.fetchall():
559 if (module,name) not in self.loads:
560 self.unlink_mark[(model,res_id)] = id
561 if model=='workflow.activity':
562 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,))
563 wkf_todo.extend(cr.fetchall())
564 cr.execute("update wkf_transition set condition='True', role_id=NULL, signal=NULL,act_to=act_from,act_from=%s where act_to=%s", (res_id,res_id))
565 cr.execute("delete from wkf_transition where act_to=%s", (res_id,))
567 for model,id in wkf_todo:
568 wf_service = netsvc.LocalService("workflow")
569 wf_service.trg_write(uid, model, id, cr)
572 if not config.get('import_partial', False):
573 for (model,id) in self.unlink_mark.keys():
574 if self.pool.get(model):
575 logger = netsvc.Logger()
576 logger.notifyChannel('init', netsvc.LOG_INFO, 'Deleting %s@%s' % (id, model))
578 self.pool.get(model).unlink(cr, uid, [id])
579 if self.unlink_mark[(model,id)]:
580 self.unlink(cr, uid, [self.unlink_mark[(model,id)]])
581 cr.execute('DELETE FROM ir_values WHERE value=%s', (model+','+str(id),))
584 logger.notifyChannel('init', netsvc.LOG_ERROR, 'Could not delete id: %d of model %s\tThere should be some relation that points to this resource\tYou should manually fix this and restart --update=module' % (id, model))
588 class ir_model_config(osv.osv):
589 _name = 'ir.model.config'
591 'password': fields.char('Password', size=64),
592 'password_check': fields.char('Confirmation', size=64),
595 def action_cancel(self, cr, uid, ids, context={}):
599 'res_model': 'ir.actions.configuration.wizard',
600 'type': 'ir.actions.act_window',
604 def action_update_pw(self, cr, uid, ids, context={}):
605 res = self.read(cr,uid,ids)[0]
606 root = self.pool.get('res.users').browse(cr, uid, [1])[0]
607 self.unlink(cr, uid, [res['id']])
608 if res['password']!=res['password_check']:
609 raise except_orm(_('Error'), _("Password mismatch !"))
610 elif not res['password']:
611 raise except_orm(_('Error'), _("Password empty !"))
612 self.pool.get('res.users').write(cr, uid, [root.id], {'password':res['password']})
616 'res_model': 'ir.actions.configuration.wizard',
617 'type': 'ir.actions.act_window',
622 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: