[MERGE] forward port of branch 8.0 up to e883193
[odoo/odoo.git] / openerp / addons / base / res / res_users.py
index f1ddf4f..faf5840 100644 (file)
@@ -22,6 +22,7 @@
 import itertools
 import logging
 from functools import partial
+from itertools import repeat
 
 from lxml import etree
 from lxml.builder import E
@@ -162,10 +163,10 @@ class res_users(osv.osv):
 
     _columns = {
         'id': fields.integer('ID'),
-        'login_date': fields.date('Latest connection', select=1),
+        'login_date': fields.datetime('Latest connection', select=1, copy=False),
         'partner_id': fields.many2one('res.partner', required=True,
             string='Related Partner', ondelete='restrict',
-            help='Partner-related data of the user'),
+            help='Partner-related data of the user', auto_join=True),
         'login': fields.char('Login', size=64, required=True,
             help="Used to log into the system"),
         'password': fields.char('Password', size=64, invisible=True, copy=False,
@@ -187,6 +188,11 @@ class res_users(osv.osv):
         'company_ids':fields.many2many('res.company','res_company_users_rel','user_id','cid','Companies'),
     }
 
+    # overridden inherited fields to bypass access rights, in case you have
+    # access to the user but not its corresponding partner
+    name = openerp.fields.Char(related='partner_id.name', inherited=True)
+    email = openerp.fields.Char(related='partner_id.email', inherited=True)
+
     def on_change_login(self, cr, uid, ids, login, context=None):
         if login and tools.single_email_re.match(login):
             return {'value': {'email': login}}
@@ -429,7 +435,9 @@ class res_users(osv.osv):
                 # prevent/delay login in that case. It will also have been logged
                 # as a SQL error, if anyone cares.
                 try:
-                    cr.execute("SELECT id FROM res_users WHERE id=%s FOR UPDATE NOWAIT", (user_id,), log_exceptions=False)
+                    # NO KEY introduced in PostgreSQL 9.3 http://www.postgresql.org/docs/9.3/static/release-9-3.html#AEN115299
+                    update_clause = 'NO KEY UPDATE' if cr._cnx.server_version >= 90300 else 'UPDATE'
+                    cr.execute("SELECT id FROM res_users WHERE id=%%s FOR %s NOWAIT" % update_clause, (user_id,), log_exceptions=False)
                     cr.execute("UPDATE res_users SET login_date = now() AT TIME ZONE 'UTC' WHERE id=%s", (user_id,))
                     self.invalidate_cache(cr, user_id, ['login_date'], [user_id])
                 except Exception:
@@ -650,25 +658,30 @@ class users_implied(osv.osv):
 # Naming conventions for reified groups fields:
 # - boolean field 'in_group_ID' is True iff
 #       ID is in 'groups_id'
-# - boolean field 'in_groups_ID1_..._IDk' is True iff
-#       any of ID1, ..., IDk is in 'groups_id'
 # - selection field 'sel_groups_ID1_..._IDk' is ID iff
 #       ID is in 'groups_id' and ID is maximal in the set {ID1, ..., IDk}
 #----------------------------------------------------------
 
-def name_boolean_group(id): return 'in_group_' + str(id)
-def name_boolean_groups(ids): return 'in_groups_' + '_'.join(map(str, ids))
-def name_selection_groups(ids): return 'sel_groups_' + '_'.join(map(str, ids))
+def name_boolean_group(id):
+    return 'in_group_' + str(id)
+
+def name_selection_groups(ids):
+    return 'sel_groups_' + '_'.join(map(str, ids))
+
+def is_boolean_group(name):
+    return name.startswith('in_group_')
+
+def is_selection_groups(name):
+    return name.startswith('sel_groups_')
 
-def is_boolean_group(name): return name.startswith('in_group_')
-def is_boolean_groups(name): return name.startswith('in_groups_')
-def is_selection_groups(name): return name.startswith('sel_groups_')
 def is_reified_group(name):
-    return is_boolean_group(name) or is_boolean_groups(name) or is_selection_groups(name)
+    return is_boolean_group(name) or is_selection_groups(name)
+
+def get_boolean_group(name):
+    return int(name[9:])
 
-def get_boolean_group(name): return int(name[9:])
-def get_boolean_groups(name): return map(int, name[10:].split('_'))
-def get_selection_groups(name): return map(int, name[11:].split('_'))
+def get_selection_groups(name):
+    return map(int, name[11:].split('_'))
 
 def partition(f, xs):
     "return a pair equivalent to (filter(f, xs), filter(lambda x: not f(x), xs))"
@@ -789,45 +802,39 @@ class users_view(osv.osv):
     _inherit = 'res.users'
 
     def create(self, cr, uid, values, context=None):
-        self._set_reified_groups(values)
+        values = self._remove_reified_groups(values)
         return super(users_view, self).create(cr, uid, values, context)
 
     def write(self, cr, uid, ids, values, context=None):
-        self._set_reified_groups(values)
+        values = self._remove_reified_groups(values)
         return super(users_view, self).write(cr, uid, ids, values, context)
 
-    def _set_reified_groups(self, values):
-        """ reflect reified group fields in values['groups_id'] """
-        if 'groups_id' in values:
-            # groups are already given, ignore group fields
-            for f in filter(is_reified_group, values.iterkeys()):
-                del values[f]
-            return
+    def _remove_reified_groups(self, values):
+        """ return `values` without reified group fields """
+        add, rem = [], []
+        values1 = {}
+
+        for key, val in values.iteritems():
+            if is_boolean_group(key):
+                (add if val else rem).append(get_boolean_group(key))
+            elif is_selection_groups(key):
+                rem += get_selection_groups(key)
+                if val:
+                    add.append(val)
+            else:
+                values1[key] = val
 
-        add, remove = [], []
-        for f in values.keys():
-            if is_boolean_group(f):
-                target = add if values.pop(f) else remove
-                target.append(get_boolean_group(f))
-            elif is_boolean_groups(f):
-                if not values.pop(f):
-                    remove.extend(get_boolean_groups(f))
-            elif is_selection_groups(f):
-                remove.extend(get_selection_groups(f))
-                selected = values.pop(f)
-                if selected:
-                    add.append(selected)
-        # update values *only* if groups are being modified, otherwise
-        # we introduce spurious changes that might break the super.write() call.
-        if add or remove:
-            # remove groups in 'remove' and add groups in 'add'
-            values['groups_id'] = [(3, id) for id in remove] + [(4, id) for id in add]
+        if 'groups_id' not in values and (add or rem):
+            # remove group ids in `rem` and add group ids in `add`
+            values1['groups_id'] = zip(repeat(3), rem) + zip(repeat(4), add)
+
+        return values1
 
     def default_get(self, cr, uid, fields, context=None):
         group_fields, fields = partition(is_reified_group, fields)
         fields1 = (fields + ['groups_id']) if group_fields else fields
         values = super(users_view, self).default_get(cr, uid, fields1, context)
-        self._get_reified_groups(group_fields, values)
+        self._add_reified_groups(group_fields, values)
 
         # add "default_groups_ref" inside the context to set default value for group_id with xml values
         if 'groups_id' in fields and isinstance(context.get("default_groups_ref"), list):
@@ -846,29 +853,35 @@ class users_view(osv.osv):
         return values
 
     def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
-        fields_get = fields if fields is not None else self.fields_get(cr, uid, context=context).keys()
-        group_fields, _ = partition(is_reified_group, fields_get)
+        # determine whether reified groups fields are required, and which ones
+        fields1 = fields or self.fields_get(cr, uid, context=context).keys()
+        group_fields, other_fields = partition(is_reified_group, fields1)
+
+        # read regular fields (other_fields); add 'groups_id' if necessary
+        drop_groups_id = False
+        if group_fields and fields:
+            if 'groups_id' not in other_fields:
+                other_fields.append('groups_id')
+                drop_groups_id = True
+        else:
+            other_fields = fields
 
-        inject_groups_id = group_fields and fields and 'groups_id' not in fields
-        if inject_groups_id:
-            fields.append('groups_id')
-        res = super(users_view, self).read(cr, uid, ids, fields, context=context, load=load)
+        res = super(users_view, self).read(cr, uid, ids, other_fields, context=context, load=load)
 
-        if res and group_fields:
+        # post-process result to add reified group fields
+        if group_fields:
             for values in (res if isinstance(res, list) else [res]):
-                self._get_reified_groups(group_fields, values)
-                if inject_groups_id:
+                self._add_reified_groups(group_fields, values)
+                if drop_groups_id:
                     values.pop('groups_id', None)
         return res
 
-    def _get_reified_groups(self, fields, values):
-        """ compute the given reified group fields from values['groups_id'] """
+    def _add_reified_groups(self, fields, values):
+        """ add the given reified group fields into `values` """
         gids = set(parse_m2m(values.get('groups_id') or []))
         for f in fields:
             if is_boolean_group(f):
                 values[f] = get_boolean_group(f) in gids
-            elif is_boolean_groups(f):
-                values[f] = not gids.isdisjoint(get_boolean_groups(f))
             elif is_selection_groups(f):
                 selected = [gid for gid in get_selection_groups(f) if gid in gids]
                 values[f] = selected and selected[-1] or False
@@ -915,29 +928,26 @@ class change_password_wizard(osv.TransientModel):
         'user_ids': fields.one2many('change.password.user', 'wizard_id', string='Users'),
     }
 
-    def default_get(self, cr, uid, fields, context=None):
-        if context == None:
+    def _default_user_ids(self, cr, uid, context=None):
+        if context is None:
             context = {}
-        user_ids = context.get('active_ids', [])
-        wiz_id = context.get('active_id', None)
-        res = []
-        users = self.pool.get('res.users').browse(cr, uid, user_ids, context=context)
-        for user in users:
-            res.append((0, 0, {
-                'wizard_id': wiz_id,
-                'user_id': user.id,
-                'user_login': user.login,
-            }))
-        return {'user_ids': res}
-
-    def change_password_button(self, cr, uid, id, context=None):
-        wizard = self.browse(cr, uid, id, context=context)[0]
+        user_model = self.pool['res.users']
+        user_ids = context.get('active_model') == 'res.users' and context.get('active_ids') or []
+        return [
+            (0, 0, {'user_id': user.id, 'user_login': user.login})
+            for user in user_model.browse(cr, uid, user_ids, context=context)
+        ]
+
+    _defaults = {
+        'user_ids': _default_user_ids,
+    }
+
+    def change_password_button(self, cr, uid, ids, context=None):
+        wizard = self.browse(cr, uid, ids, context=context)[0]
         need_reload = any(uid == user.user_id.id for user in wizard.user_ids)
-        line_ids = [user.id for user in wizard.user_ids]
 
+        line_ids = [user.id for user in wizard.user_ids]
         self.pool.get('change.password.user').change_password_button(cr, uid, line_ids, context=context)
-        # don't keep temporary password copies in the database longer than necessary
-        self.pool.get('change.password.user').write(cr, uid, line_ids, {'new_passwd': False}, context=context)
 
         if need_reload:
             return {
@@ -965,8 +975,10 @@ class change_password_user(osv.TransientModel):
     }
 
     def change_password_button(self, cr, uid, ids, context=None):
-        for user in self.browse(cr, uid, ids, context=context):
-            self.pool.get('res.users').write(cr, uid, user.user_id.id, {'password': user.new_passwd})
+        for line in self.browse(cr, uid, ids, context=context):
+            line.user_id.write({'password': line.new_passwd})
+        # don't keep temporary passwords in the database longer than necessary
+        self.write(cr, uid, ids, {'new_passwd': False}, context=context)
 
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: