1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2011 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 osv, fields
23 from tools.translate import _
26 class portal(osv.osv):
28 _description = 'Portal'
30 'name': fields.char(string='Name', size=64, required=True),
31 'menu_id': fields.many2one('ir.actions.actions', required=True,
33 help=_("What replaces the standard menu for the portal's users")),
34 'user_ids': fields.one2many('res.users', 'portal_id',
36 help=_('The set of users associated to this portal')),
37 'group_ids': fields.many2many('res.groups', 'res_portals_groups_rel', 'pid', 'gid',
39 help=_('Users of this portal automatically belong to those groups')),
42 ('unique_name', 'UNIQUE(name)', _('Portals must have different names.'))
45 def copy(self, cr, uid, id, defaults, context=None):
46 """ override copy() to not copy the portal users """
47 # find an unused name of the form "old_name [N]" for some random N
48 old_name = self.browse(cr, uid, id, context).name
49 new_name = copy_random(old_name)
50 while self.search(cr, uid, [('name', '=', new_name)], limit=1, context=context):
51 new_name = copy_random(old_name)
53 defaults['name'] = new_name
54 defaults['user_ids'] = []
55 return super(portal, self).copy(cr, uid, id, defaults, context)
57 def create(self, cr, uid, values, context=None):
58 """ extend create() to assign the portal menu and groups to users """
59 # as 'user_ids' is a many2one relation, values['user_ids'] must be a
60 # list of tuples of the form (0, 0, {values})
61 for op, _, user_values in values['user_ids']:
63 user_values['menu_id'] = values['menu_id']
64 user_values['groups_id'] = values['group_ids']
66 return super(portal, self).create(cr, uid, values, context)
68 def write(self, cr, uid, ids, values, context=None):
69 """ extend write() to reflect menu and groups changes on users """
71 # analyse groups changes, and determine how to change users
73 for change in values.get('group_ids', []):
74 if change[0] in [0, 5, 6]: # change creates or sets groups,
75 groups_diff = None # must compute per-portal diff
77 if change[0] in [3, 4]: # change add or remove group,
78 groups_diff.append(change) # add or remove group on users
80 if groups_diff is None:
81 return self._write_compute_diff(cr, uid, ids, values, context)
83 return self._write_diff(cr, uid, ids, values, groups_diff, context)
85 def _write_diff(self, cr, uid, ids, values, groups_diff, context=None):
86 """ perform write() and apply groups_diff on users """
87 # first apply portal changes
88 super(portal, self).write(cr, uid, ids, values, context)
90 # then apply menu and group changes on their users
92 if 'menu_id' in values:
93 user_values['menu_id'] = values['menu_id']
95 user_values['groups_id'] = groups_diff
99 for p in self.browse(cr, uid, ids, context):
100 user_ids += get_browse_ids(p.user_ids)
101 self.pool.get('res.users').write(cr, uid, user_ids, user_values, context)
105 def _write_compute_diff(self, cr, uid, ids, values, context=None):
106 """ perform write(), then compute and apply groups_diff on each portal """
107 # read group_ids before write() to compute groups_diff
109 for p in self.browse(cr, uid, ids, context):
110 old_group_ids[p.id] = get_browse_ids(p.group_ids)
112 # apply portal changes
113 super(portal, self).write(cr, uid, ids, values, context)
115 # the changes to apply on users
116 user_object = self.pool.get('res.users')
118 if 'menu_id' in values:
119 user_values['menu_id'] = values['menu_id']
121 # compute groups_diff on each portal, and apply them on users
122 for p in self.browse(cr, uid, ids, context):
123 old_groups = set(old_group_ids[p.id])
124 new_groups = set(get_browse_ids(p.group_ids))
125 # groups_diff: [(3, UNLINKED_ID), ..., (4, LINKED_ID), ...]
126 user_values['groups_id'] = \
127 [(3, g) for g in (old_groups - new_groups)] + \
128 [(4, g) for g in (new_groups - old_groups)]
129 user_ids = get_browse_ids(p.user_ids)
130 user_object.write(cr, uid, user_ids, user_values, context)
134 def action_create_menu(self, cr, uid, ids, context=None):
135 """ create a menu for this portal """
137 raise ValueError("portal.action_create_menu() applies to one portal only")
138 portal_name = self.browse(cr, uid, ids[0], context).name
140 # create a menuitem under 'portal.portal_menu_tree'
143 'parent_id': self._get_res_id(cr, uid, 'portal', 'portal_menu_tree'),
145 item_id = self.pool.get('ir.ui.menu').create(cr, uid, item_data, context)
147 # create an action to open the menuitems under item_id
150 'type': 'ir.actions.act_window',
152 'res_model': 'ir.ui.menu',
154 'view_id': self._get_res_id(cr, uid, 'base', 'view_menu'),
155 'domain': [('parent_id', '=', item_id)],
157 action_id = self.pool.get('ir.actions.act_window').create(cr, uid, action_data, context)
159 # set the portal menu_id to action_id
160 return self.write(cr, uid, ids, {'menu_id': action_id}, context)
162 def _get_res_id(self, cr, uid, module, xml_id):
163 """ return the resource id associated to the given xml_id """
164 ir_model_data_obj = self.pool.get('ir.model.data')
165 record_id = ir_model_data_obj._get_id(cr, uid, module, xml_id)
166 record_data = ir_model_data_obj.read(cr, uid, [record_id], ['res_id'])
167 assert (len(record_data) == 1) and ('res_id' in record_data[0])
168 return record_data[0]['res_id']
172 class users(osv.osv):
174 _inherit = 'res.users'
176 'portal_id': fields.many2one('res.portal', string='Portal',
177 help=_('If given, the portal defines customized menu and access rules')),
180 def default_get(self, cr, uid, fields, context=None):
181 """ override default values of menu_id and groups_id for portal users """
183 # How it works: the values of 'menu_id' and 'groups_id' are passed in
184 # context by the portal form view
185 if ('menu_id' in context) and ('menu_id' in fields):
186 fields.remove('menu_id')
187 others['menu_id'] = context['menu_id']
188 if ('groups_id' in context) and ('groups_id' in fields):
189 fields.remove('groups_id')
190 others['groups_id'] = get_many2many(context['groups_id'])
191 # the remaining fields use inherited defaults
192 defs = super(users, self).default_get(cr, uid, fields, context)
199 def get_browse_id(obj):
200 """ return the id of a browse() object, or None """
201 return (obj and obj.id or None)
203 def get_browse_ids(objs):
204 """ return the ids of a list of browse() objects """
205 return map(get_browse_id, objs)
207 def get_many2many(arg):
208 """ get the list of ids from a many2many 'values' field """
209 assert len(arg) == 1 and arg[0][0] == 6 # arg = [(6, _, IDs)]
212 def copy_random(name):
213 """ return "name [N]" for some random integer N """
214 return "%s [%s]" % (name, random.choice(xrange(1000000)))