diff --git a/bin/addons/base/base_update.xml b/bin/addons/base/base_update.xml
index 4bb2296493f..364f9fb0ee3 100644
--- a/bin/addons/base/base_update.xml
+++ b/bin/addons/base/base_update.xml
@@ -59,6 +59,8 @@
@@ -116,6 +118,7 @@
+
diff --git a/bin/addons/base/ir/__init__.py b/bin/addons/base/ir/__init__.py
index 46331d821fd..affb98af7f3 100644
--- a/bin/addons/base/ir/__init__.py
+++ b/bin/addons/base/ir/__init__.py
@@ -39,3 +39,4 @@ import ir_values
import ir_translation
import ir_exports
import workflow
+import ir_rule
diff --git a/bin/addons/base/ir/ir.xml b/bin/addons/base/ir/ir.xml
index a8f051b8a1c..dc4941e3fcd 100644
--- a/bin/addons/base/ir/ir.xml
+++ b/bin/addons/base/ir/ir.xml
@@ -792,6 +792,48 @@
+ ==========================================================
+ Rules
+ ==========================================================
+
+
+ Rule
+ ir.rule
+ form
+
+
+
+
+
+
+ Rule
+ ir.rule
+ tree
+
+
+
+
+
+
+
+
+
+
+ Rule
+ ir.rule
+ form
+
+
+
+
diff --git a/bin/addons/base/ir/ir_rule.py b/bin/addons/base/ir/ir_rule.py
new file mode 100644
index 00000000000..92cb09d8d84
--- /dev/null
+++ b/bin/addons/base/ir/ir_rule.py
@@ -0,0 +1,121 @@
+##############################################################################
+#
+# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from osv import fields,osv
+import time
+import tools
+
+class ir_rule(osv.osv):
+ _name = 'ir.rule'
+
+ def _operand(self,cr,uid,context):
+ def get(object, level=3, ending=[], ending_excl=[], recur=[], root_tech='', root=''):
+ res= []
+ fields = self.pool.get(object).fields_get(cr,uid)
+ key = fields.keys()
+ key.sort()
+ for k in key:
+ if (not ending or fields[k]['type'] in ending) and ((not ending_excl) or not (fields[k]['type'] in ending_excl)):
+ res.append((root_tech+'.'+k,root+'/'+fields[k]['string']))
+
+ if fields[k]['type'] in recur:
+ res.append((root_tech+'.'+k+'.id',root+'/'+fields[k]['string']))
+ if (fields[k]['type'] in recur) and (level>0):
+ res.extend(get(fields[k]['relation'], level-1, ending,
+ ending_excl, recur, root_tech+'.'+k, root+'/'+fields[k]['string']))
+ return res
+ res = [("False", "False"),("user.id","User")]+get('res.users', level=1,ending_excl=['one2many','many2one','many2many','reference'],
+ recur=['many2one'],root_tech='user',root='User')
+ return res
+
+ _columns = {
+ 'name': fields.char('Name',size=128, required=True, select=True),
+ 'type': fields.selection( (('add','Additive'),('sub','Subtractive')),'Type',required=True, select=True),
+ 'model_id': fields.many2one('ir.model', 'Model',select=True, required=True),
+ 'field_id': fields.many2one('ir.model.fields', 'Field',domain= "[('model_id','=',model_id)]",select=True),
+ 'operator':fields.selection( (('=','='),('<>','<>'),('<=','<='),('>=','>=')),'Operator'),
+ 'operand':fields.selection(_operand,'Operand', size=64),
+ 'domain': fields.char('Domain', size=256, required=True)
+ }
+
+ _defaults={
+ 'type': lambda *a : 'add'
+ }
+
+
+ def domain_get(self, cr, uid, model_name):
+ # root user above constraint
+ if uid == 1:
+ return '', []
+
+ cr.execute("select r.id from ir_rule r join ir_model m on (r.model_id = m.id ) where m.model = %s and r.id in ( select rule_id from user_rule_rel where users_id = %d union select rule_id from group_rule_rel g join res_groups_users_rel u on (g.group_id = u.gid) where u.uid = %d )", (model_name,uid,uid))
+ ids = map(lambda x:x[0], cr.fetchall())
+ obj = self.pool.get(model_name)
+ add = []
+ add_str = []
+ sub = []
+ sub_str = []
+ for rule in self.browse(cr, uid, ids):
+ dom = eval(rule.domain, {'user': self.pool.get('res.users').browse(cr, uid, uid), 'time':time})
+ d1,d2 = obj._where_calc(dom)
+ if rule.type=='add':
+ add_str += d1
+ add +=d2
+ else:
+ sub_str += d1
+ sub += d2
+ add_str = ' or '.join(add_str)
+ sub_str = ' and '.join(sub_str)
+
+ if not (add or sub):
+ return '', []
+ if add and sub:
+ return '((%s) and (%s))' % (add_str, sub_str), add+sub
+ if add:
+ return '%s' % (add_str,), add
+ if sub:
+ return '%s' % (sub_str,),sub
+ domain_get = tools.cache()(domain_get)
+
+ def onchange_rule(self, cr, uid, context, model_id, field_id, operator, operand):
+
+ if not ( field_id and operator and operand): return {}
+
+ field_names= self.pool.get('ir.model.fields').read(cr,uid,[field_id], ["name"])
+ if not field_names : return {}
+
+ return {'value':{'domain': "[('%s', '%s', %s)]"%(field_names[0]['name'], operator, operand)}}
+
+ def write(self, cr, uid, *args, **argv):
+ res = super(ir_rule, self).write(cr, uid, *args, **argv)
+ # Restart the cache on the company_get method
+ self.domain_get()
+ return res
+
+
+ir_rule()
diff --git a/bin/addons/base/res/res_user.py b/bin/addons/base/res/res_user.py
index 194d7e049c2..45252070252 100644
--- a/bin/addons/base/res/res_user.py
+++ b/bin/addons/base/res/res_user.py
@@ -33,7 +33,13 @@ class groups(osv.osv):
_name = "res.groups"
_columns = {
'name': fields.char('Group Name', size=64, required=True),
+ 'rule_ids': fields.many2many('ir.rule', 'group_rule_rel', 'group_id', 'rule_id', 'Acces Rules'),
}
+ def write(self, cr, uid, *args, **argv):
+ res = super(groups, self).write(cr, uid, *args, **argv)
+ # Restart the cache on the company_get method
+ self.pool.get('ir.rule').domain_get()
+ return res
groups()
@@ -86,6 +92,7 @@ class users(osv.osv):
'groups_id': fields.many2many('res.groups', 'res_groups_users_rel', 'uid', 'gid', 'groups'),
'roles_id': fields.many2many('res.roles', 'res_roles_users_rel', 'uid', 'rid', 'Roles'),
'company_id': fields.many2one('res.company', 'Company'),
+ 'rule_ids': fields.many2many('ir.rule', 'user_rule_rel', 'users_id', 'rule_id', 'Acces Rules'),
}
_sql_constraints = [
('login_key', 'UNIQUE (login)', 'You can not have two users with the same login !')
@@ -101,8 +108,8 @@ class users(osv.osv):
def write(self, cr, uid, *args, **argv):
res = super(users, self).write(cr, uid, *args, **argv)
- # Restart the cache on the company_get method
self.company_get()
+ self.pool.get('ir.rule').domain_get()
return res
def unlink(self, cr, uid, ids):
@@ -120,5 +127,7 @@ class users(osv.osv):
login = self.read(cr, uid, [id], ['login'])[0]['login']
default.update({'login': login+' (copy)'})
return super(users, self).copy(cr, uid, id, default, context)
+
+
users()
diff --git a/bin/osv/fields.py b/bin/osv/fields.py
index 0e65ea60872..bd37694895b 100644
--- a/bin/osv/fields.py
+++ b/bin/osv/fields.py
@@ -285,8 +285,11 @@ class many2one(_column):
for id in filter(None, res.values()):
try:
names[id] = dict(obj.name_get(cr, user, [id], context))[id]
- except except_orm:
- names[id] = "== Access denied =="
+ except except_orm, e:
+ if e.name == 'AccessError':
+ names[id] = "== Access denied =="
+ else :
+ raise
for r in res.keys():
if res[r] and res[r] in names:
res[r] = (res[r], names[res[r]])
@@ -397,11 +400,12 @@ class many2many(_column):
ids_s = ','.join(map(str,ids))
limit_str = self._limit is not None and ' limit %d' % self._limit or ''
obj = obj.pool.get(self._obj)
- if 'company_id' in obj._columns:
- compids = tools.get_user_companies(cr, user)
- cr.execute('SELECT r.'+self._id2+', r.'+self._id1+' FROM '+self._rel+' AS r, '+obj._table+' AS o WHERE r.'+self._id1+' in ('+ids_s+') AND r.'+self._id2+' = o.id AND (o.company_id IN ('+','.join(map(str,compids))+') OR o.company_id IS NULL)'+limit_str+' OFFSET %d', (offset,))
- else:
- cr.execute('select '+self._id2+','+self._id1+' from '+self._rel+' where '+self._id1+' in ('+ids_s+')'+limit_str+' offset %d', (offset,))
+
+ d1, d2 = obj.pool.get('ir.rule').domain_get(cr, user, obj._name)
+ if d1:
+ d1 = ' and '+d1
+
+ cr.execute('SELECT '+self._rel+'.'+self._id2+','+self._rel+'.'+self._id1+' FROM '+self._rel+' , '+obj._table+' where '+self._rel+'.'+self._id1+' in ('+ids_s+') AND '+self._rel+'.'+self._id2+' = '+obj._table+'.id ' +d1+limit_str+' offset %d', d2+[offset])
for r in cr.fetchall():
res[r[1]].append(r[0])
return res
@@ -425,12 +429,13 @@ class many2many(_column):
elif act[0]==5:
cr.execute('update '+self._rel+' set '+self._id2+'=null where '+self._id2+'=%d', (id,))
elif act[0]==6:
- if 'company_id' in obj._columns and not user == 1:
- compids = tools.get_user_companies(cr, user)
- cr.execute('delete from '+self._rel+' where '+self._id1+'=%d AND '+self._id2+' IN (SELECT r.'+self._id2+' FROM '+self._rel+' AS r, '+obj._table+' AS o WHERE r.'+self._id1+'=%d AND r.'+self._id2+' = o.id AND (o.company_id IN ('+','.join(map(str,compids))+') OR o.company_id IS NULL))', (id, id, ))
- else:
- cr.execute('delete from '+self._rel+' where '+self._id1+'=%d', (id, ))
- for act_nbr in act[2]:
+
+ d1, d2 = obj.pool.get('ir.rule').domain_get(cr, user, obj._name)
+ if d1:
+ d1 = ' and '+d1
+ cr.execute('delete from '+self._rel+' where '+self._id1+'=%d AND '+self._id2+' IN (SELECT '+self._rel+'.'+self._id2+' FROM '+self._rel+', '+obj._table+' WHERE '+self._rel+'.'+self._id1+'=%d AND '+self._rel+'.'+self._id2+' = '+obj._table+'.id '+ d1 +')', [id, id]+d2 )
+
+ for act_nbr in act[2]: # XXX add clause ? [bch 20070518]
cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%d, %d)', (id, act_nbr))
#
diff --git a/bin/osv/orm.py b/bin/osv/orm.py
index c449350d5e9..7ccc39aa44c 100644
--- a/bin/osv/orm.py
+++ b/bin/osv/orm.py
@@ -174,12 +174,15 @@ class browse_record(object):
if data[n]:
obj = self._table.pool.get(f._obj)
compids=False
- if 'company_id' in obj._columns and not self._uid == 1:
- compids = tools.get_user_companies(self._cr, self._uid)
- if compids:
- self._cr.execute('SELECT id FROM '+obj._table+' where id = %d AND (company_id in ('+','.join(map(str,compids))+') or company_id is null)', (data[n],))
- if not self._cr.fetchall():
- raise except_orm('BrowseError', 'Object %s (id:%d) is linked to the object %s (id:%d) which is not in your company' %(self._table._description, self._id, obj._description, data[n]))
+ #
+ # Removed for performance sake. (Bug may arise) [20070516]
+ #
+# if 'company_id' in obj._columns :
+# compids = tools.get_user_companies(self._cr, self._uid)
+# if compids:
+# self._cr.execute('SELECT id FROM '+obj._table+' where id = %d AND (company_id in ('+','.join(map(str,compids))+') or company_id is null)', (data[n],))
+# if not self._cr.fetchall():
+# raise except_orm('BrowseError', 'Object %s (id:%d) is linked to the object %s (id:%d) which is not in your company' %(self._table._description, self._id, obj._description, data[n]))
data[n] = browse_record(self._cr, self._uid, data[n], obj, self._cache, context=self._context, list_class=self._list_class)
else:
data[n] = browse_null()
@@ -479,7 +482,19 @@ class orm(object):
self._sequence = self._table+'_id_seq'
for k in self._defaults:
assert (k in self._columns) or (k in self._inherit_fields), 'Default function defined in %s but field %s does not exist !' % (self._name, k,)
+ if self._log_access:
+ self._columns.update({
+ 'create_uid': fields.many2one('res.users','Creation user',required=True, readonly=True),
+ 'create_date': fields.datetime('Creation date',required=True, readonly=True),
+ 'write_uid': fields.many2one('res.users','Last modification by', readonly=True),
+ 'write_date': fields.datetime('Last modification date', readonly=True),
+ })
+ #FIXME : does not work :
+# self._defaults.update({
+# 'create_uid': lambda self,cr,uid,context : uid,
+# 'create_date': lambda *a : time.strftime("%Y-%m-%d %H:%M:%S")
+# })
#
# Update objects that uses this one to update their _inherits fields
#
@@ -710,25 +725,20 @@ class orm(object):
if fields==None:
fields = self._columns.keys()
- # if the object has a field named 'company_id', filter out all
- # records which do not concern the current company (the company
- # of the current user) or its "childs"
- company_clause='true'
- compids=False
- if 'company_id' in self._columns and not user == 1:
- compids = tools.get_user_companies(cr, user)
- if compids:
- company_clause = '(company_id in ('+','.join(map(str,compids))+') or company_id is null)'
+ # construct a clause for the rules :
+ d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
# all inherited fields + all non inherited fields for which the attribute whose name is in load is True
fields_pre = filter(lambda x: x in self._columns and getattr(self._columns[x],'_classic_write'), fields) + self._inherits.values()
- if len(fields_pre) or compids:
- cr.execute('select %s from %s where id in (%s) and %s order by %s' % (','.join(fields_pre + ['id']), self._table, ','.join([str(x) for x in ids]), company_clause, self._order))
- uniq_id = []
- [uniq_id.append(i) for i in ids if not uniq_id.count(i)]
- if not cr.rowcount == len(uniq_id) and compids:
- raise except_orm('ReadError', 'You try to read objects (%s) that is not in your company' % self._description)
+ if len(fields_pre) :
+ if d1:
+ cr.execute('select %s from %s where id in (%s) and %s order by %s' % (','.join(fields_pre + ['id']), self._table, ','.join([str(x) for x in ids]), d1, self._order),d2)
+ if not cr.rowcount == len({}.fromkeys(ids)):
+ raise except_orm('AccessError', 'You try to bypass an access rule (Document type : %s).' % self._description)
+ else:
+ cr.execute('select %s from %s where id in (%s) order by %s' % (','.join(fields_pre + ['id']), self._table, ','.join([str(x) for x in ids]), self._order))
+
res = cr.dictfetchall()
else:
res = map(lambda x: {'id':x}, ids)
@@ -835,19 +845,26 @@ class orm(object):
wf_service.trg_delete(uid, self._name, id, cr)
str_d = string.join(('%d',)*len(ids),',')
- cr.execute('select * from '+self._table+' where id in ('+str_d+')', ids)
- res = cr.dictfetchall()
+ #cr.execute('select * from '+self._table+' where id in ('+str_d+')', ids)
+ #res = cr.dictfetchall()
#for key in self._inherits:
# ids2 = [x[self._inherits[key]] for x in res]
# self.pool.get(key).unlink(cr, uid, ids2)
- cr.execute('delete from inherit where (obj_type=%s and obj_id in ('+str_d+')) or (inst_type=%s and inst_id in ('+str_d+'))', (self._name,)+tuple(ids)+(self._name,)+tuple(ids))
- cr.execute('delete from '+self._table+' where id in ('+str_d+')', ids)
+
+ d1, d2 = self.pool.get('ir.rule').domain_get(cr, uid, self._name)
+ if d1:
+ d1 = ' and '+d1
+
+ cr.execute('delete from inherit where (obj_type=%s and obj_id in ('+str_d+'))'+d1+' or (inst_type=%s and inst_id in ('+str_d+')'+d1+')', [self._name]+ids+d2+[self._name]+ids+d2)
+ cr.execute('delete from '+self._table+' where id in ('+str_d+')'+d1, ids+d2)
return True
#
# TODO: Validate
#
def write(self, cr, user, ids, vals, context={}):
+ if not ids:
+ return True
delta= context.get('read_delta',False)
if delta and self._log_access:
cr.execute("select (now() - min(write_date)) <= '%s'::interval from %s where id in (%s)"% (delta,self._table,",".join(map(str, ids))) )
@@ -856,10 +873,9 @@ class orm(object):
raise except_orm('ConcurrencyException', 'This record was modified in the meanwhile')
self.pool.get('ir.model.access').check(cr, user, self._name, 'write')
+
#for v in self._inherits.values():
# assert v not in vals, (v, vals)
- if not ids:
- return
ids_str = string.join(map(str, ids),',')
upd0=[]
upd1=[]
@@ -884,7 +900,11 @@ class orm(object):
upd1.append(user)
if len(upd0):
- cr.execute('update '+self._table+' set '+string.join(upd0,',')+' where id in ('+ids_str+')', upd1)
+
+ d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
+ if d1:
+ d1 = ' and '+d1
+ cr.execute('update '+self._table+' set '+string.join(upd0,',')+' where id in ('+ids_str+')'+d1, upd1+ d2)
if totranslate:
for f in direct:
@@ -1391,11 +1411,11 @@ class orm(object):
# if the object has a field named 'company_id', filter out all
# records which do not concern the current company (the company
# of the current user) or its "childs"
- if 'company_id' in self._columns and not user == 1:
- compids = tools.get_user_companies(cr, user)
- if compids:
- compids.append(False)
- args.append(('company_id','in',compids))
+# if 'company_id' in self._columns and not user == 1:
+# compids = tools.get_user_companies(cr, user)
+# if compids:
+# compids.append(False)
+# args.append(('company_id','in',compids))
i = 0
tables=[self._table]
@@ -1497,6 +1517,7 @@ class orm(object):
# compute the where, order by, limit and offset clauses
(qu1,qu2) = self._where_calc(args)
+
if len(qu1):
qu1 = ' where '+string.join(qu1,' and ')
else:
@@ -1505,7 +1526,13 @@ class orm(object):
limit_str = limit and ' limit %d' % limit or ''
offset_str = offset and ' offset %d' % offset or ''
-
+
+
+ # construct a clause for the rules :
+ d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
+ if d1:
+ qu1 = qu1 and qu1+' and '+d1 or ' where '+d1
+ qu2 += d2
# execute the "main" query to fetch the ids we were searching for
cr.execute('select %s.id from ' % self._table + ','.join(tables) +qu1+' order by '+order_by+limit_str+offset_str, qu2)
res = cr.fetchall()