3e566e0be56b699f66d9113f7a76b96c49fd1dff
[odoo/odoo.git] / bin / addons / base / ir / ir_ui_menu.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution   
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 from osv import fields, osv
24 from osv.orm import browse_null, browse_record
25 import re
26 import tools
27
28 def one_in(setA, setB):
29     """Check the presence of an element of setA in setB
30     """
31     for x in setA:
32         if x in setB:
33             return True
34     return False
35
36 class many2many_unique(fields.many2many):
37     def set(self, cr, obj, id, name, values, user=None, context=None):
38         if not values:
39             return
40         val = values[:]
41         for act in values:
42             if act[0]==4:
43                 cr.execute('SELECT * FROM '+self._rel+' \
44                         WHERE '+self._id1+'=%s AND '+self._id2+'=%s', (id, act[1]))
45                 if cr.fetchall():
46                     val.remove(act)
47         return super(many2many_unique, self).set(cr, obj, id, name, val, user=user,
48                 context=context)
49
50
51 class ir_ui_menu(osv.osv):
52     _name = 'ir.ui.menu'
53
54     def __init__(self, *args, **kwargs):
55         self._cache = {}
56         r = super(ir_ui_menu, self).__init__(*args, **kwargs)
57         self.pool.get('ir.model.access').register_cache_clearing_method(self._name, 'clear_cache')
58         return r
59
60     def __del__(self):
61         self.pool.get('ir.model.access').unregister_cache_clearing_method(self._name, 'clear_cache')
62         return super(ir_ui_menu, self).__del__()
63
64     def clear_cache(self):
65         # radical but this doesn't frequently happen
66         self._cache = {}
67
68     def search(self, cr, uid, args, offset=0, limit=2000, order=None,
69             context=None, count=False):
70         if context is None:
71             context = {}
72         ids = osv.orm.orm.search(self, cr, uid, args, offset, limit, order, context=context, count=(count and uid==1))
73         if uid==1:
74             return ids
75
76         if not ids:
77             if count:
78                 return 0
79             return []
80
81         modelaccess = self.pool.get('ir.model.access')
82         user_groups = set(self.pool.get('res.users').read(cr, 1, uid, ['groups_id'])['groups_id'])
83         result = []
84         for menu in self.browse(cr, uid, ids):
85             # this key works because user access rights are all based on user's groups (cfr ir_model_access.check)
86             key = (cr.dbname, menu.id, tuple(user_groups))
87             if key in self._cache:
88                 if self._cache[key]:
89                     result.append(menu.id)
90                 continue
91
92             self._cache[key] = False
93             if menu.groups_id:
94                 restrict_to_groups = [g.id for g in menu.groups_id]
95                 if not user_groups.intersection(restrict_to_groups):
96                     continue
97                 result.append(menu.id)
98                 self._cache[key] = True
99                 continue
100
101             if menu.action:
102                 # we check if the user has access to the action of the menu
103                 m, oid = menu.action.split(',', 1)
104                 data = self.pool.get(m).browse(cr, 1, int(oid))
105
106                 if data:
107                     model_field = { 'ir.actions.act_window':    'res_model',
108                                     'ir.actions.report.custom': 'model',
109                                     'ir.actions.report.xml':    'model', 
110                                     'ir.actions.wizard':        'model',
111                                     'ir.actions.server':        'model_id',
112                                   }
113
114                     field = model_field.get(m)
115                     if field and data[field]:
116                         if not modelaccess.check(cr, uid, data[field], raise_exception=False):
117                             continue
118             else:
119                 # if there is no action, it's a 'folder' menu
120                 if not menu.child_id:
121                     # not displayed if there is no children 
122                     continue
123
124             result.append(menu.id)
125             self._cache[key] = True
126
127         if count:
128             return len(result)
129         return result
130
131     def _get_full_name(self, cr, uid, ids, name, args, context):
132         res = {}
133         for m in self.browse(cr, uid, ids, context=context):
134             res[m.id] = self._get_one_full_name(m)
135         return res
136
137     def _get_one_full_name(self, menu, level=6):
138         if level<=0:
139             return '...'
140         if menu.parent_id:
141             parent_path = self._get_one_full_name(menu.parent_id, level-1) + "/"
142         else:
143             parent_path = ''
144         return parent_path + menu.name
145
146     def write(self, *args, **kwargs):
147         self.clear_cache()
148         return super(ir_ui_menu, self).write(*args, **kwargs)
149
150     def unlink(self, *args, **kwargs):
151         self.clear_cache()
152         return super(ir_ui_menu, self).unlink(*args, **kwargs)
153
154     def copy(self, cr, uid, id, default=None, context=None):
155         ir_values_obj = self.pool.get('ir.values')
156         res = super(ir_ui_menu, self).copy(cr, uid, id, context=context)
157         datas=self.read(cr,uid,[res],['name'])[0]
158         rex=re.compile('\([0-9]+\)')
159         concat=rex.findall(datas['name'])
160         if concat:
161             next_num=eval(concat[0])+1
162             datas['name']=rex.sub(('(%d)'%next_num),datas['name'])
163         else:
164             datas['name']=datas['name']+'(1)'
165         self.write(cr,uid,[res],{'name':datas['name']})
166         ids = ir_values_obj.search(cr, uid, [
167             ('model', '=', 'ir.ui.menu'),
168             ('res_id', '=', id),
169             ])
170         for iv in ir_values_obj.browse(cr, uid, ids):
171             new_id = ir_values_obj.copy(cr, uid, iv.id,
172                     default={'res_id': res}, context=context)
173         return res
174
175     def _action(self, cursor, user, ids, name, arg, context=None):
176         res = {}
177         values_obj = self.pool.get('ir.values')
178         value_ids = values_obj.search(cursor, user, [
179             ('model', '=', self._name), ('key', '=', 'action'),
180             ('key2', '=', 'tree_but_open'), ('res_id', 'in', ids)],
181             context=context)
182         values_action = {}
183         for value in values_obj.browse(cursor, user, value_ids, context=context):
184             values_action[value.res_id] = value.value
185         for menu_id in ids:
186             res[menu_id] = values_action.get(menu_id, False)
187         return res
188
189     def _action_inv(self, cursor, user, menu_id, name, value, arg, context=None):
190         if context is None:
191             context = {}
192         ctx = context.copy()
193         if self.CONCURRENCY_CHECK_FIELD in ctx:
194             del ctx[self.CONCURRENCY_CHECK_FIELD]
195         values_obj = self.pool.get('ir.values')
196         values_ids = values_obj.search(cursor, user, [
197             ('model', '=', self._name), ('key', '=', 'action'),
198             ('key2', '=', 'tree_but_open'), ('res_id', '=', menu_id)],
199             context=context)
200         if values_ids:
201             values_obj.write(cursor, user, values_ids[0], {'value': value},
202                     context=ctx)
203         else:
204             values_obj.create(cursor, user, {
205                 'name': 'Menuitem',
206                 'model': self._name,
207                 'value': value,
208                 'object': True,
209                 'key': 'action',
210                 'key2': 'tree_but_open',
211                 'res_id': menu_id,
212                 }, context=ctx)
213
214     def _get_icon_pict(self, cr, uid, ids, name, args, context):
215         res = {}
216         for m in self.browse(cr, uid, ids, context=context):
217             res[m.id] = ('stock', (m.icon,'ICON_SIZE_MENU'))
218         return res
219
220     def onchange_icon(self, cr, uid, ids, icon):
221         if not icon:
222             return {}
223         return {'type': {'icon_pict': 'picture'}, 'value': {'icon_pict': ('stock', (icon,'ICON_SIZE_MENU'))}}
224
225     _columns = {
226         'name': fields.char('Menu', size=64, required=True, translate=True),
227         'sequence': fields.integer('Sequence'),
228         'child_id' : fields.one2many('ir.ui.menu', 'parent_id','Child ids'),
229         'parent_id': fields.many2one('ir.ui.menu', 'Parent Menu', select=True),
230         'groups_id': many2many_unique('res.groups', 'ir_ui_menu_group_rel',
231             'menu_id', 'gid', 'Groups', help="If you put groups, the visibility of this menu will be based on these groups. "\
232                 "If this field is empty, Open ERP will compute visibility based on the related object's read access."),
233         'complete_name': fields.function(_get_full_name, method=True,
234             string='Complete Name', type='char', size=128),
235         'icon': fields.selection(tools.icons, 'Icon', size=64),
236         'icon_pict': fields.function(_get_icon_pict, method=True, type='picture'),
237         'action': fields.function(_action, fnct_inv=_action_inv,
238             method=True, type='reference', string='Action',
239             selection=[
240                 ('ir.actions.report.custom', 'ir.actions.report.custom'),
241                 ('ir.actions.report.xml', 'ir.actions.report.xml'),
242                 ('ir.actions.act_window', 'ir.actions.act_window'),
243                 ('ir.actions.wizard', 'ir.actions.wizard'),
244                 ('ir.actions.url', 'ir.actions.url'),
245             ]),
246     }
247     _defaults = {
248         'icon' : lambda *a: 'STOCK_OPEN',
249         'icon_pict': lambda *a: ('stock', ('STOCK_OPEN','ICON_SIZE_MENU')),
250         'sequence' : lambda *a: 10
251     }
252     _order = "sequence,id"
253 ir_ui_menu()
254
255
256
257 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
258