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, SUPERUSER_ID, 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 'active': fields.boolean('Active', help="If you uncheck the active field, it will disable the record rule without deleting it (if you delete a native record rule, it may be re-created when you reload the module."),
79 'model_id': fields.many2one('ir.model', 'Object',select=1, required=True, ondelete="cascade"),
80 '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"),
81 'groups': fields.many2many('res.groups', 'rule_group_rel', 'rule_group_id', 'group_id', 'Groups'),
82 'domain_force': fields.text('Domain'),
83 'domain': fields.function(_domain_force_get, string='Domain', type='text'),
84 'perm_read': fields.boolean('Apply for Read'),
85 'perm_write': fields.boolean('Apply for Write'),
86 'perm_create': fields.boolean('Apply for Create'),
87 'perm_unlink': fields.boolean('Apply for Delete')
90 _order = 'model_id DESC'
101 ('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 !'),
104 (_check_model_obj, 'Rules can not be applied on Transient models.', ['model_id']),
105 (_check_model_name, 'Rules can not be applied on the Record Rules model.', ['model_id']),
109 def _compute_domain(self, cr, uid, model_name, mode="read"):
110 if mode not in self._MODES:
111 raise ValueError('Invalid mode: %r' % (mode,))
113 if uid == SUPERUSER_ID:
115 cr.execute("""SELECT r.id
117 JOIN ir_model m ON (r.model_id = m.id)
120 AND r.perm_""" + mode + """
121 AND (r.id IN (SELECT rule_group_id FROM rule_group_rel g_rel
122 JOIN res_groups_users_rel u_rel ON (g_rel.group_id = u_rel.gid)
123 WHERE u_rel.uid = %s) OR r.global)""", (model_name, uid))
124 rule_ids = [x[0] for x in cr.fetchall()]
126 # browse user as super-admin root to avoid access errors!
127 user = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid)
128 global_domains = [] # list of domains
129 group_domains = {} # map: group -> list of domains
130 for rule in self.browse(cr, SUPERUSER_ID, rule_ids):
131 # read 'domain' as UID to have the correct eval context for the rule.
132 rule_domain = self.read(cr, uid, rule.id, ['domain'])['domain']
133 dom = expression.normalize(rule_domain)
134 for group in rule.groups:
135 if group in user.groups_id:
136 group_domains.setdefault(group, []).append(dom)
138 global_domains.append(dom)
139 # combine global domains and group domains
141 group_domain = expression.OR(map(expression.OR, group_domains.values()))
144 domain = expression.AND(global_domains + [group_domain])
148 def clear_cache(self, cr, uid):
149 self._compute_domain.clear_cache(self)
151 def domain_get(self, cr, uid, model_name, mode='read', context=None):
152 dom = self._compute_domain(cr, uid, model_name, mode)
154 # _where_calc is called as superuser. This means that rules can
155 # involve objects on which the real uid has no acces rights.
156 # This means also there is no implicit restriction (e.g. an object
157 # references another object the user can't see).
158 query = self.pool.get(model_name)._where_calc(cr, SUPERUSER_ID, dom, active_test=False)
159 return query.where_clause, query.where_clause_params, query.tables
160 return [], [], ['"'+self.pool.get(model_name)._table+'"']
162 def unlink(self, cr, uid, ids, context=None):
163 res = super(ir_rule, self).unlink(cr, uid, ids, context=context)
164 self.clear_cache(cr, uid)
167 def create(self, cr, uid, vals, context=None):
168 res = super(ir_rule, self).create(cr, uid, vals, context=context)
169 self.clear_cache(cr, uid)
172 def write(self, cr, uid, ids, vals, context=None):
173 res = super(ir_rule, self).write(cr, uid, ids, vals, context=context)
174 self.clear_cache(cr,uid)
179 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: