[FIX] ir: small fix to handle missing models when upgrading
[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
22 from osv import fields, osv, expression
23 import time
24 from operator import itemgetter
25 from functools import partial
26 import tools
27 from tools.safe_eval import safe_eval as eval
28 from tools.misc import unquote as unquote
29 from openerp import SUPERUSER_ID
30
31 class ir_rule(osv.osv):
32     _name = 'ir.rule'
33     _order = 'name'
34     _MODES = ['read', 'write', 'create', 'unlink']
35
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')}
43
44     def _eval_context(self, cr, uid):
45         """Returns a dictionary to use as evaluation context for
46            ir.rule domains."""
47         return {'user': self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid),
48                 'time':time}
49
50     def _domain_force_get(self, cr, uid, ids, field_name, arg, context=None):
51         res = {}
52         eval_context = self._eval_context(cr, uid)
53         for rule in self.browse(cr, uid, ids, context):
54             if rule.domain_force:
55                 res[rule.id] = expression.normalize(eval(rule.domain_force, eval_context))
56             else:
57                 res[rule.id] = []
58         return res
59
60     def _get_value(self, cr, uid, ids, field_name, arg, context=None):
61         res = {}
62         for rule in self.browse(cr, uid, ids, context):
63             if not rule.groups:
64                 res[rule.id] = True
65             else:
66                 res[rule.id] = False
67         return res
68
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))
71
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))
75
76     _columns = {
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')
88     }
89
90     _order = 'model_id DESC'
91
92     _defaults = {
93         'active': True,
94         'perm_read': True,
95         'perm_write': True,
96         'perm_create': True,
97         'perm_unlink': True,
98         'global': True,
99     }
100     _sql_constraints = [
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 !'),
102     ]
103     _constraints = [
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']),
106     ]
107
108     @tools.ormcache()
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,))
112
113         if uid == SUPERUSER_ID:
114             return None
115         cr.execute("""SELECT r.id
116                 FROM ir_rule r
117                 JOIN ir_model m ON (r.model_id = m.id)
118                 WHERE m.model = %s
119                 AND r.active is True
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()]
125         if rule_ids:
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)
137                 if not rule.groups:
138                     global_domains.append(dom)
139             # combine global domains and group domains
140             if group_domains:
141                 group_domain = expression.OR(map(expression.OR, group_domains.values()))
142             else:
143                 group_domain = []
144             domain = expression.AND(global_domains + [group_domain])
145             return domain
146         return []
147
148     def clear_cache(self, cr, uid):
149         self._compute_domain.clear_cache(self)
150
151     def domain_get(self, cr, uid, model_name, mode='read', context=None):
152         dom = self._compute_domain(cr, uid, model_name, mode)
153         if dom:
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+'"']
161
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)
165         return res
166
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)
170         return res
171
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)
175         return res
176
177 ir_rule()
178
179 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
180