[MERGE] Forward-port saas-4 up to 5ceded9
[odoo/odoo.git] / openerp / addons / base / ir / ir_rule.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21 import time
22
23 from openerp import SUPERUSER_ID
24 from openerp import tools
25 from openerp.osv import fields, osv, expression
26 from openerp.tools.safe_eval import safe_eval as eval
27 from openerp.tools.misc import unquote as unquote
28
29 class ir_rule(osv.osv):
30     _name = 'ir.rule'
31     _order = 'name'
32     _MODES = ['read', 'write', 'create', 'unlink']
33
34     def _eval_context_for_combinations(self):
35         """Returns a dictionary to use as evaluation context for
36            ir.rule domains, when the goal is to obtain python lists
37            that are easier to parse and combine, but not to
38            actually execute them."""
39         return {'user': unquote('user'),
40                 'time': unquote('time')}
41
42     def _eval_context(self, cr, uid):
43         """Returns a dictionary to use as evaluation context for
44            ir.rule domains."""
45         return {'user': self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid),
46                 'time':time}
47
48     def _domain_force_get(self, cr, uid, ids, field_name, arg, context=None):
49         res = {}
50         eval_context = self._eval_context(cr, uid)
51         for rule in self.browse(cr, uid, ids, context):
52             if rule.domain_force:
53                 res[rule.id] = expression.normalize_domain(eval(rule.domain_force, eval_context))
54             else:
55                 res[rule.id] = []
56         return res
57
58     def _get_value(self, cr, uid, ids, field_name, arg, context=None):
59         res = {}
60         for rule in self.browse(cr, uid, ids, context):
61             if not rule.groups:
62                 res[rule.id] = True
63             else:
64                 res[rule.id] = False
65         return res
66
67     def _check_model_obj(self, cr, uid, ids, context=None):
68         return not any(self.pool[rule.model_id.model].is_transient() for rule in self.browse(cr, uid, ids, context))
69
70     def _check_model_name(self, cr, uid, ids, context=None):
71         # Don't allow rules on rules records (this model).
72         return not any(rule.model_id.model == self._name for rule in self.browse(cr, uid, ids, context))
73
74     _columns = {
75         'name': fields.char('Name', size=128, select=1),
76         '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."),
77         'model_id': fields.many2one('ir.model', 'Object',select=1, required=True, ondelete="cascade"),
78         '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"),
79         'groups': fields.many2many('res.groups', 'rule_group_rel', 'rule_group_id', 'group_id', 'Groups'),
80         'domain_force': fields.text('Domain'),
81         'domain': fields.function(_domain_force_get, string='Domain', type='text'),
82         'perm_read': fields.boolean('Apply for Read'),
83         'perm_write': fields.boolean('Apply for Write'),
84         'perm_create': fields.boolean('Apply for Create'),
85         'perm_unlink': fields.boolean('Apply for Delete')
86     }
87
88     _order = 'model_id DESC'
89
90     _defaults = {
91         'active': True,
92         'perm_read': True,
93         'perm_write': True,
94         'perm_create': True,
95         'perm_unlink': True,
96         'global': True,
97     }
98     _sql_constraints = [
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 !'),
100     ]
101     _constraints = [
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']),
104     ]
105
106     @tools.ormcache()
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,))
110
111         if uid == SUPERUSER_ID:
112             return None
113         cr.execute("""SELECT r.id
114                 FROM ir_rule r
115                 JOIN ir_model m ON (r.model_id = m.id)
116                 WHERE m.model = %s
117                 AND r.active is True
118                 AND r.perm_""" + mode + """
119                 AND (r.id IN (SELECT rule_group_id FROM rule_group_rel g_rel
120                             JOIN res_groups_users_rel u_rel ON (g_rel.group_id = u_rel.gid)
121                             WHERE u_rel.uid = %s) OR r.global)""", (model_name, uid))
122         rule_ids = [x[0] for x in cr.fetchall()]
123         if rule_ids:
124             # browse user as super-admin root to avoid access errors!
125             user = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid)
126             global_domains = []                 # list of domains
127             group_domains = {}                  # map: group -> list of domains
128             for rule in self.browse(cr, SUPERUSER_ID, rule_ids):
129                 # read 'domain' as UID to have the correct eval context for the rule.
130                 rule_domain = self.read(cr, uid, rule.id, ['domain'])['domain']
131                 dom = expression.normalize_domain(rule_domain)
132                 for group in rule.groups:
133                     if group in user.groups_id:
134                         group_domains.setdefault(group, []).append(dom)
135                 if not rule.groups:
136                     global_domains.append(dom)
137             # combine global domains and group domains
138             if group_domains:
139                 group_domain = expression.OR(map(expression.OR, group_domains.values()))
140             else:
141                 group_domain = []
142             domain = expression.AND(global_domains + [group_domain])
143             return domain
144         return []
145
146     def clear_cache(self, cr, uid):
147         self._compute_domain.clear_cache(self)
148
149     def domain_get(self, cr, uid, model_name, mode='read', context=None):
150         dom = self._compute_domain(cr, uid, model_name, mode)
151         if dom:
152             # _where_calc is called as superuser. This means that rules can
153             # involve objects on which the real uid has no acces rights.
154             # This means also there is no implicit restriction (e.g. an object
155             # references another object the user can't see).
156             query = self.pool[model_name]._where_calc(cr, SUPERUSER_ID, dom, active_test=False)
157             return query.where_clause, query.where_clause_params, query.tables
158         return [], [], ['"' + self.pool[model_name]._table + '"']
159
160     def unlink(self, cr, uid, ids, context=None):
161         res = super(ir_rule, self).unlink(cr, uid, ids, context=context)
162         self.clear_cache(cr, uid)
163         return res
164
165     def create(self, cr, uid, vals, context=None):
166         res = super(ir_rule, self).create(cr, uid, vals, context=context)
167         self.clear_cache(cr, uid)
168         return res
169
170     def write(self, cr, uid, ids, vals, context=None):
171         res = super(ir_rule, self).write(cr, uid, ids, vals, context=context)
172         self.clear_cache(cr,uid)
173         return res
174
175 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: