1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
22 from osv import fields, osv, expression
24 from operator import itemgetter
25 from functools import partial
27 from tools.safe_eval import safe_eval as eval
28 from tools.misc import unquote as unquote
29 from openerp import SUPERUSER_ID
31 class ir_rule(osv.osv):
34 _MODES = ['read', 'write', 'create', 'unlink']
36 def _eval_context_for_combinations(self):
37 """Returns a dictionary to use as evaluation context for
38 ir.rule domains, when the goal is to obtain python lists
39 that are easier to parse and combine, but not to
40 actually execute them."""
41 return {'user': unquote('user'),
42 'time': unquote('time')}
44 def _eval_context(self, cr, uid):
45 """Returns a dictionary to use as evaluation context for
47 return {'user': self.pool.get('res.users').browse(cr, 1, uid),
50 def _domain_force_get(self, cr, uid, ids, field_name, arg, context=None):
52 eval_context = self._eval_context(cr, uid)
53 for rule in self.browse(cr, uid, ids, context):
55 res[rule.id] = expression.normalize(eval(rule.domain_force, eval_context))
60 def _get_value(self, cr, uid, ids, field_name, arg, context=None):
62 for rule in self.browse(cr, uid, ids, context):
69 def _check_model_obj(self, cr, uid, ids, context=None):
70 return not any(self.pool.get(rule.model_id.model).is_transient() for rule in self.browse(cr, uid, ids, context))
72 def _check_model_name(self, cr, uid, ids, context=None):
73 # Don't allow rules on rules records (this model).
74 return not any(rule.model_id.model == self._name for rule in self.browse(cr, uid, ids, context))
77 'name': fields.char('Name', size=128, select=1),
78 'model_id': fields.many2one('ir.model', 'Object',select=1, required=True, ondelete="cascade"),
79 'global': fields.function(_get_value, string='Global', type='boolean', store=True, help="If no group is specified the rule is global and applied to everyone"),
80 'groups': fields.many2many('res.groups', 'rule_group_rel', 'rule_group_id', 'group_id', 'Groups'),
81 'domain_force': fields.text('Domain'),
82 'domain': fields.function(_domain_force_get, string='Domain', type='text'),
83 'perm_read': fields.boolean('Apply for Read'),
84 'perm_write': fields.boolean('Apply for Write'),
85 'perm_create': fields.boolean('Apply for Create'),
86 'perm_unlink': fields.boolean('Apply for Delete')
89 _order = 'model_id DESC'
99 ('no_access_rights', 'CHECK (perm_read!=False or perm_write!=False or perm_create!=False or perm_unlink!=False)', 'Rule must have at least one checked access right !'),
102 (_check_model_obj, 'Rules can not be applied on Transient models.', ['model_id']),
103 (_check_model_name, 'Rules can not be applied on the Record Rules model.', ['model_id']),
107 def _compute_domain(self, cr, uid, model_name, mode="read"):
108 if mode not in self._MODES:
109 raise ValueError('Invalid mode: %r' % (mode,))
111 if uid == SUPERUSER_ID:
113 cr.execute("""SELECT r.id
115 JOIN ir_model m ON (r.model_id = m.id)
117 AND r.perm_""" + mode + """
118 AND (r.id IN (SELECT rule_group_id FROM rule_group_rel g_rel
119 JOIN res_groups_users_rel u_rel ON (g_rel.group_id = u_rel.gid)
120 WHERE u_rel.uid = %s) OR r.global)""", (model_name, uid))
121 rule_ids = [x[0] for x in cr.fetchall()]
123 # browse user as super-admin root to avoid access errors!
124 user = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid)
125 global_domains = [] # list of domains
126 group_domains = {} # map: group -> list of domains
127 for rule in self.browse(cr, SUPERUSER_ID, rule_ids):
128 # read 'domain' as UID to have the correct eval context for the rule.
129 rule_domain = self.read(cr, uid, rule.id, ['domain'])['domain']
130 dom = expression.normalize(rule_domain)
131 for group in rule.groups:
132 if group in user.groups_id:
133 group_domains.setdefault(group, []).append(dom)
135 global_domains.append(dom)
136 # combine global domains and group domains
138 group_domain = expression.OR(map(expression.OR, group_domains.values()))
141 domain = expression.AND(global_domains + [group_domain])
145 def clear_cache(self, cr, uid):
146 self._compute_domain.clear_cache(self)
148 def domain_get(self, cr, uid, model_name, mode='read', context=None):
149 dom = self._compute_domain(cr, uid, model_name, mode)
151 # _where_calc is called as superuser. This means that rules can
152 # involve objects on which the real uid has no acces rights.
153 # This means also there is no implicit restriction (e.g. an object
154 # references another object the user can't see).
155 query = self.pool.get(model_name)._where_calc(cr, 1, dom, active_test=False)
156 return query.where_clause, query.where_clause_params, query.tables
157 return [], [], ['"'+self.pool.get(model_name)._table+'"']
159 def unlink(self, cr, uid, ids, context=None):
160 res = super(ir_rule, self).unlink(cr, uid, ids, context=context)
161 self.clear_cache(cr, uid)
164 def create(self, cr, uid, vals, context=None):
165 res = super(ir_rule, self).create(cr, uid, vals, context=context)
166 self.clear_cache(cr, uid)
169 def write(self, cr, uid, ids, vals, context=None):
170 res = super(ir_rule, self).write(cr, uid, ids, vals, context=context)
171 self.clear_cache(cr,uid)
176 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: