1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 # Copyright (C) 2010-2011 OpenERP SA (<http://openerp.com>).
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Affero General Public License as
10 # published by the Free Software Foundation, either version 3 of the
11 # License, or (at your option) any later version.
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 Affero General Public License for more details.
18 # You should have received a copy of the GNU Affero General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
26 from tools.safe_eval import safe_eval as eval
28 import openerp.modules
29 from osv import fields, osv
30 from tools.translate import _
31 from openerp import SUPERUSER_ID
33 def one_in(setA, setB):
34 """Check the presence of an element of setA in setB
41 class ir_ui_menu(osv.osv):
44 def __init__(self, *args, **kwargs):
45 self.cache_lock = threading.RLock()
47 r = super(ir_ui_menu, self).__init__(*args, **kwargs)
48 self.pool.get('ir.model.access').register_cache_clearing_method(self._name, 'clear_cache')
51 def clear_cache(self):
53 # radical but this doesn't frequently happen
56 def _filter_visible_menus(self, cr, uid, ids, context=None):
57 """Filters the give menu ids to only keep the menu items that should be
58 visible in the menu hierarchy of the current user.
59 Uses a cache for speeding up the computation.
62 modelaccess = self.pool.get('ir.model.access')
63 user_groups = set(self.pool.get('res.users').read(cr, SUPERUSER_ID, uid, ['groups_id'])['groups_id'])
65 for menu in self.browse(cr, uid, ids, context=context):
66 # this key works because user access rights are all based on user's groups (cfr ir_model_access.check)
67 key = (cr.dbname, menu.id, tuple(user_groups))
68 if key in self._cache:
70 result.append(menu.id)
71 #elif not menu.groups_id and not menu.action:
72 # result.append(menu.id)
75 self._cache[key] = False
77 restrict_to_groups = [g.id for g in menu.groups_id]
78 if not user_groups.intersection(restrict_to_groups):
80 #result.append(menu.id)
81 #self._cache[key] = True
85 # we check if the user has access to the action of the menu
88 model_field = { 'ir.actions.act_window': 'res_model',
89 'ir.actions.report.xml': 'model',
90 'ir.actions.wizard': 'model',
91 'ir.actions.server': 'model_id',
94 field = model_field.get(menu.action._name)
95 if field and data[field]:
96 if not modelaccess.check(cr, uid, data[field], 'read', False):
99 # if there is no action, it's a 'folder' menu
100 if not menu.child_id:
101 # not displayed if there is no children
104 result.append(menu.id)
105 self._cache[key] = True
108 def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
112 ids = super(ir_ui_menu, self).search(cr, uid, args, offset=0,
113 limit=None, order=order, context=context, count=False)
120 # menu filtering is done only on main menu tree, not other menu lists
121 if context.get('ir.ui.menu.full_list'):
124 result = self._filter_visible_menus(cr, uid, ids, context=context)
127 result = result[long(offset):]
129 result = result[:long(limit)]
135 def _get_full_name(self, cr, uid, ids, name, args, context):
137 for m in self.browse(cr, uid, ids, context=context):
138 res[m.id] = self._get_one_full_name(m)
141 def _get_one_full_name(self, menu, level=6):
145 parent_path = self._get_one_full_name(menu.parent_id, level-1) + "/"
148 return parent_path + menu.name
150 def create(self, *args, **kwargs):
152 return super(ir_ui_menu, self).create(*args, **kwargs)
154 def write(self, *args, **kwargs):
156 return super(ir_ui_menu, self).write(*args, **kwargs)
158 def unlink(self, *args, **kwargs):
160 return super(ir_ui_menu, self).unlink(*args, **kwargs)
162 def copy(self, cr, uid, id, default=None, context=None):
163 ir_values_obj = self.pool.get('ir.values')
164 res = super(ir_ui_menu, self).copy(cr, uid, id, context=context)
165 datas=self.read(cr,uid,[res],['name'])[0]
166 rex=re.compile('\([0-9]+\)')
167 concat=rex.findall(datas['name'])
169 next_num=int(concat[0])+1
170 datas['name']=rex.sub(('(%d)'%next_num),datas['name'])
172 datas['name']=datas['name']+'(1)'
173 self.write(cr,uid,[res],{'name':datas['name']})
174 ids = ir_values_obj.search(cr, uid, [
175 ('model', '=', 'ir.ui.menu'),
178 for iv in ir_values_obj.browse(cr, uid, ids):
179 ir_values_obj.copy(cr, uid, iv.id, default={'res_id': res},
183 def _action(self, cursor, user, ids, name, arg, context=None):
185 ir_values_obj = self.pool.get('ir.values')
186 value_ids = ir_values_obj.search(cursor, user, [
187 ('model', '=', self._name), ('key', '=', 'action'),
188 ('key2', '=', 'tree_but_open'), ('res_id', 'in', ids)],
191 for value in ir_values_obj.browse(cursor, user, value_ids, context=context):
192 values_action[value.res_id] = value.value
194 res[menu_id] = values_action.get(menu_id, False)
197 def _action_inv(self, cursor, user, menu_id, name, value, arg, context=None):
201 if self.CONCURRENCY_CHECK_FIELD in ctx:
202 del ctx[self.CONCURRENCY_CHECK_FIELD]
203 ir_values_obj = self.pool.get('ir.values')
204 values_ids = ir_values_obj.search(cursor, user, [
205 ('model', '=', self._name), ('key', '=', 'action'),
206 ('key2', '=', 'tree_but_open'), ('res_id', '=', menu_id)],
208 if value and values_ids:
209 ir_values_obj.write(cursor, user, values_ids, {'value': value}, context=ctx)
211 # no values_ids, create binding
212 ir_values_obj.create(cursor, user, {
217 'key2': 'tree_but_open',
221 # value is False, remove existing binding
222 ir_values_obj.unlink(cursor, user, values_ids, context=ctx)
224 def _get_icon_pict(self, cr, uid, ids, name, args, context):
226 for m in self.browse(cr, uid, ids, context=context):
227 res[m.id] = ('stock', (m.icon,'ICON_SIZE_MENU'))
230 def onchange_icon(self, cr, uid, ids, icon):
233 return {'type': {'icon_pict': 'picture'}, 'value': {'icon_pict': ('stock', (icon,'ICON_SIZE_MENU'))}}
235 def read_image(self, path):
238 path_info = path.split(',')
239 icon_path = openerp.modules.get_module_resource(path_info[0],path_info[1])
243 icon_file = tools.file_open(icon_path,'rb')
244 icon_image = base64.encodestring(icon_file.read())
249 def _get_image_icon(self, cr, uid, ids, names, args, context=None):
251 for menu in self.browse(cr, uid, ids, context=context):
252 res[menu.id] = r = {}
254 fn_src = fn[:-5] # remove _data
255 r[fn] = self.read_image(menu[fn_src])
259 def _get_needaction(self, cr, uid, ids, field_names, args, context=None):
261 for menu in self.browse(cr, uid, ids, context=context):
263 'needaction_enabled': False,
264 'needaction_counter': False,
266 if menu.action and menu.action.type in ('ir.actions.act_window','ir.actions.client') and menu.action.res_model:
267 obj = self.pool.get(menu.action.res_model)
268 if obj and obj._needaction:
269 if menu.action.type=='ir.actions.act_window':
270 dom = menu.action.domain and eval(menu.action.domain, {'uid': uid}) or []
272 dom = eval(menu.action.params_store or '{}', {'uid': uid}).get('domain')
273 res[menu.id]['needaction_enabled'] = obj._needaction
274 res[menu.id]['needaction_counter'] = obj._needaction_count(cr, uid, dom, context=context)
278 'name': fields.char('Menu', size=64, required=True, translate=True),
279 'sequence': fields.integer('Sequence'),
280 'child_id' : fields.one2many('ir.ui.menu', 'parent_id','Child IDs'),
281 'parent_id': fields.many2one('ir.ui.menu', 'Parent Menu', select=True),
282 'groups_id': fields.many2many('res.groups', 'ir_ui_menu_group_rel',
283 'menu_id', 'gid', 'Groups', help="If you have groups, the visibility of this menu will be based on these groups. "\
284 "If this field is empty, OpenERP will compute visibility based on the related object's read access."),
285 'complete_name': fields.function(_get_full_name,
286 string='Full Path', type='char', size=128),
287 'icon': fields.selection(tools.icons, 'Icon', size=64),
288 'icon_pict': fields.function(_get_icon_pict, type='char', size=32),
289 'web_icon': fields.char('Web Icon File', size=128),
290 'web_icon_hover':fields.char('Web Icon File (hover)', size=128),
291 'web_icon_data': fields.function(_get_image_icon, string='Web Icon Image', type='binary', readonly=True, store=True, multi='icon'),
292 'web_icon_hover_data':fields.function(_get_image_icon, string='Web Icon Image (hover)', type='binary', readonly=True, store=True, multi='icon'),
293 'needaction_enabled': fields.function(_get_needaction, string='Target model uses the need action mechanism', type='boolean', help='If the menu entry action is an act_window action, and if this action is related to a model that uses the need_action mechanism, this field is set to true. Otherwise, it is false.', multi='_get_needaction'),
294 'needaction_counter': fields.function(_get_needaction, string='Number of actions the user has to perform', type='integer', help='If the target model uses the need action mechanism, this field gives the number of actions the current user has to perform.', multi='_get_needaction'),
295 'action': fields.function(_action, fnct_inv=_action_inv,
296 type='reference', string='Action',
298 ('ir.actions.report.xml', 'ir.actions.report.xml'),
299 ('ir.actions.act_window', 'ir.actions.act_window'),
300 ('ir.actions.wizard', 'ir.actions.wizard'),
301 ('ir.actions.act_url', 'ir.actions.act_url'),
302 ('ir.actions.server', 'ir.actions.server'),
303 ('ir.actions.client', 'ir.actions.client'),
307 def _rec_message(self, cr, uid, ids, context=None):
308 return _('Error ! You can not create recursive Menu.')
311 (osv.osv._check_recursion, _rec_message , ['parent_id'])
314 'icon' : 'STOCK_OPEN',
315 'icon_pict': ('stock', ('STOCK_OPEN','ICON_SIZE_MENU')),
318 _order = "sequence,id"
323 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: