simplify method to get res_id from xml_id
[odoo/odoo.git] / addons / portal / portal.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2011 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 osv, fields
23 from tools.translate import _
24 import random
25
26 class portal(osv.osv):
27     _name = 'res.portal'
28     _description = 'Portal'
29     _columns = {
30         'name': fields.char(string='Name', size=64, required=True),
31         'menu_id': fields.many2one('ir.actions.actions', required=True,
32             string='Menu Action',
33             help=_("What replaces the standard menu for the portal's users")),
34         'user_ids': fields.one2many('res.users', 'portal_id',
35             string='Users',
36             help=_('The set of users associated to this portal')),
37         'group_ids': fields.many2many('res.groups', 'res_portals_groups_rel', 'pid', 'gid',
38             string='Groups',
39             help=_('Users of this portal automatically belong to those groups')),
40     }
41     _sql_constraints = [
42         ('unique_name', 'UNIQUE(name)', _('Portals must have different names.'))
43     ]
44     
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)
52         
53         defaults['name'] = new_name
54         defaults['user_ids'] = []
55         return super(portal, self).copy(cr, uid, id, defaults, context)
56     
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']:
62             assert op == 0
63             user_values['menu_id'] = values['menu_id']
64             user_values['groups_id'] = values['group_ids']
65         
66         return super(portal, self).create(cr, uid, values, context)
67     
68     def write(self, cr, uid, ids, values, context=None):
69         """ extend write() to reflect menu and groups changes on users """
70         
71         # analyse groups changes, and determine how to change users
72         groups_diff = []
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
76                 break
77             if change[0] in [3, 4]:             # change add or remove group,
78                 groups_diff.append(change)      # add or remove group on users
79         
80         if groups_diff is None:
81             return self._write_compute_diff(cr, uid, ids, values, context)
82         else:
83             return self._write_diff(cr, uid, ids, values, groups_diff, context)
84     
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)
89         
90         # then apply menu and group changes on their users
91         user_values = {}
92         if 'menu_id' in values:
93             user_values['menu_id'] = values['menu_id']
94         if groups_diff:
95             user_values['groups_id'] = groups_diff
96         
97         if user_values:
98             user_ids = []
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)
102         
103         return True
104     
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
108         old_group_ids = {}
109         for p in self.browse(cr, uid, ids, context):
110             old_group_ids[p.id] = get_browse_ids(p.group_ids)
111         
112         # apply portal changes
113         super(portal, self).write(cr, uid, ids, values, context)
114         
115         # the changes to apply on users
116         user_object = self.pool.get('res.users')
117         user_values = {}
118         if 'menu_id' in values:
119             user_values['menu_id'] = values['menu_id']
120         
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)
131         
132         return True
133     
134     def action_create_menu(self, cr, uid, ids, context=None):
135         """ create a menu for this portal """
136         if len(ids) != 1:
137             raise ValueError("portal.action_create_menu() applies to one portal only")
138         portal_name = self.browse(cr, uid, ids[0], context).name
139         
140         # create a menuitem under 'portal.portal_menu_tree'
141         item_data = {
142             'name': portal_name,
143             'parent_id': self._res_xml_id(cr, uid, 'portal', 'portal_menu_tree'),
144         }
145         item_id = self.pool.get('ir.ui.menu').create(cr, uid, item_data, context)
146         
147         # create an action to open the menuitems under item_id
148         action_data = {
149             'name': portal_name,
150             'type': 'ir.actions.act_window',
151             'usage': 'menu',
152             'res_model': 'ir.ui.menu',
153             'view_type': 'tree',
154             'view_id': self._res_xml_id(cr, uid, 'base', 'view_menu'),
155             'domain': [('parent_id', '=', item_id)],
156         }
157         action_id = self.pool.get('ir.actions.act_window').create(cr, uid, action_data, context)
158         
159         # set the portal menu_id to action_id
160         return self.write(cr, uid, ids, {'menu_id': action_id}, context)
161     
162     def _res_xml_id(self, cr, uid, module, xml_id):
163         """ return the resource id associated to the given xml_id """
164         data_obj = self.pool.get('ir.model.data')
165         data_id = data_obj._get_id(cr, uid, module, xml_id)
166         return data_obj.browse(cr, uid, data_id).res_id
167
168 portal()
169
170 class users(osv.osv):
171     _name = 'res.users'
172     _inherit = 'res.users'
173     _columns = {
174         'portal_id': fields.many2one('res.portal', readonly=True,
175             string='Portal',
176             help=_('If given, the portal defines customized menu and access rules')),
177     }
178     
179     def default_get(self, cr, uid, fields, context=None):
180         """ override default values of menu_id and groups_id for portal users """
181         others = {}
182         # How it works: the values of 'menu_id' and 'groups_id' are passed in
183         # context by the portal form view
184         if ('menu_id' in context) and ('menu_id' in fields):
185             fields.remove('menu_id')
186             others['menu_id'] = context['menu_id']
187         if ('groups_id' in context) and ('groups_id' in fields):
188             fields.remove('groups_id')
189             others['groups_id'] = get_many2many(context['groups_id'])
190         # the remaining fields use inherited defaults
191         defs = super(users, self).default_get(cr, uid, fields, context)
192         defs.update(others)
193         return defs
194
195 users()
196
197 # utils
198 def get_browse_id(obj):
199     """ return the id of a browse() object, or None """
200     return (obj and obj.id or None)
201
202 def get_browse_ids(objs):
203     """ return the ids of a list of browse() objects """
204     return map(get_browse_id, objs)
205
206 def get_many2many(arg):
207     """ get the list of ids from a many2many 'values' field """
208     assert len(arg) == 1 and arg[0][0] == 6             # arg = [(6, _, IDs)]
209     return arg[0][2]
210
211 def copy_random(name):
212     """ return "name [N]" for some random integer N """
213     return "%s [%s]" % (name, random.choice(xrange(1000000)))
214