summaryrefslogtreecommitdiff
path: root/odoo/addons/base
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2018-09-10 17:06:18 +0200
committerChristophe Simonis <chs@odoo.com>2018-09-10 17:06:18 +0200
commit86ff929ad6718d2bfb691dd272aa3f353406ff4f (patch)
treea62b50cc471452ff2dc3c034a38d6cade79c03ed /odoo/addons/base
parent4ad896bd3d6bbc851a59adead1a653abc31fc882 (diff)
parent1199451606cab7342e34fc9c41a2085b8beb1243 (diff)
[MERGE] forward port branch saas-11.4 up to 1199451606c
Diffstat (limited to 'odoo/addons/base')
-rw-r--r--odoo/addons/base/data/ir_module_category_data.xml32
-rw-r--r--odoo/addons/base/data/res_users_demo.xml3
-rw-r--r--odoo/addons/base/models/res_users.py476
-rw-r--r--odoo/addons/base/tests/test_non_regression.py12
-rw-r--r--odoo/addons/base/views/res_users_views.xml62
5 files changed, 389 insertions, 196 deletions
diff --git a/odoo/addons/base/data/ir_module_category_data.xml b/odoo/addons/base/data/ir_module_category_data.xml
index 7d8b3803208..3346121dbbc 100644
--- a/odoo/addons/base/data/ir_module_category_data.xml
+++ b/odoo/addons/base/data/ir_module_category_data.xml
@@ -1,6 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
+ <record model="ir.module.category" id="module_category_hidden">
+ <field name="name">Technical Settings</field>
+ <field name="sequence">0</field>
+ <field name="visible" eval="0" />
+ </record>
+
<record model="ir.module.category" id="module_category_localization">
<field name="name">Localization</field>
<field name="visible" eval="0" />
@@ -185,6 +191,16 @@
<field name="sequence">100</field>
</record>
+ <record model="ir.module.category" id="module_category_usability">
+ <field name="name">Extra Rights</field>
+ <field name="sequence">101</field>
+ </record>
+
+ <record model="ir.module.category" id="module_category_extra">
+ <field name="name">Other Extra Rights</field>
+ <field name="sequence">102</field>
+ </record>
+
<!-- add applications to base groups -->
<record model="res.groups" id="group_erp_manager">
<field name="category_id" ref="module_category_administration"/>
@@ -197,6 +213,18 @@
<field name="category_id" ref="module_category_user_type"/>
</record>
+ <record model="res.groups" id="group_multi_company">
+ <field name="category_id" ref="module_category_usability"/>
+ </record>
+
+ <record model="res.groups" id="group_multi_currency">
+ <field name="category_id" ref="module_category_usability"/>
+ </record>
+
+ <record model="res.groups" id="group_no_one">
+ <field name="category_id" ref="module_category_usability"/>
+ </record>
+
<record id="group_portal" model="res.groups">
<field name="category_id" ref="module_category_user_type"/>
</record>
@@ -205,5 +233,9 @@
<field name="category_id" ref="module_category_user_type"/>
</record>
+ <record id="group_partner_manager" model="res.groups">
+ <field name="category_id" ref="module_category_usability"/>
+ </record>
+
</data>
</odoo>
diff --git a/odoo/addons/base/data/res_users_demo.xml b/odoo/addons/base/data/res_users_demo.xml
index 38d6323ee28..7e0bfa7dfec 100644
--- a/odoo/addons/base/data/res_users_demo.xml
+++ b/odoo/addons/base/data/res_users_demo.xml
@@ -82,9 +82,6 @@
<field name="groups_id" eval="[(5,)]"/><!-- Avoid auto-including this user in any default group -->
</record>
- <record id="base.group_user" model="res.groups"><!-- Remove the demo user for the internal_user group -->
- <field name="users" eval="[(3,ref('demo_user0'))]"/>
- </record>
<record id="base.group_portal" model="res.groups"><!-- Add the demo user to the portal (and therefore to the portal member group) -->
<field name="users" eval="[(4,ref('demo_user0'))]"/>
</record>
diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py
index e966b2172b4..1f88db8d519 100644
--- a/odoo/addons/base/models/res_users.py
+++ b/odoo/addons/base/models/res_users.py
@@ -42,23 +42,48 @@ DEFAULT_CRYPT_CONTEXT = passlib.context.CryptContext(
concat = chain.from_iterable
#
-# Functions for manipulating boolean and selection fields
+# Functions for manipulating boolean and selection pseudo-fields
#
+def name_boolean_group(id):
+ return 'in_group_' + str(id)
+def name_selection_groups(ids):
+ return 'sel_groups_' + '_'.join(str(it) for it in ids)
def is_boolean_group(name):
- return name.startswith('has_group_')
-
+ return name.startswith('in_group_')
def is_selection_groups(name):
- return name.startswith('group_')
-
+ return name.startswith('sel_groups_')
+
+def is_reified_group(name):
+ return is_boolean_group(name) or is_selection_groups(name)
+
+def get_boolean_group(name):
+ return int(name[9:])
+
+def get_selection_groups(name):
+ return [int(v) for v in name[11:].split('_')]
+
+def parse_m2m(commands):
+ "return a list of ids corresponding to a many2many value"
+ ids = []
+ for command in commands:
+ if isinstance(command, (tuple, list)):
+ if command[0] in (1, 4):
+ ids.append(command[1])
+ elif command[0] == 5:
+ ids = []
+ elif command[0] == 6:
+ ids = list(command[2])
+ else:
+ ids.append(command)
+ return ids
#----------------------------------------------------------
# Basic res.groups and res.users
#----------------------------------------------------------
-
class Groups(models.Model):
_name = "res.groups"
_description = "Access Groups"
@@ -225,36 +250,6 @@ class Users(models.Model):
name = fields.Char(related='partner_id.name', inherited=True)
email = fields.Char(related='partner_id.email', inherited=True)
- group_base_user = fields.Selection(
- selection=lambda self: self._get_group_selection('base.module_category_user_type'),
- string="User type", compute='_compute_groups_id', inverse='_inverse_groups_id',
- category_xml_id='base.module_category_user_type')
-
- group_administration_user = fields.Selection(
- selection=lambda self: self._get_group_selection('base.module_category_administration'),
- string="Administration", compute='_compute_groups_id', inverse='_inverse_groups_id',
- category_xml_id='base.module_category_administration')
-
- has_group_multi_company = fields.Boolean(
- 'Multi Companies', compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='base.group_multi_company')
-
- has_group_multi_currency = fields.Boolean(
- 'Multi Currencies', compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='base.group_multi_currency')
-
- has_group_no_one = fields.Boolean(
- 'Technical Features', compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='base.group_no_one')
-
- has_group_partner_manager = fields.Boolean(
- 'Contact Creation', compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='base.group_partner_manager')
-
- has_group_private_addresses = fields.Boolean(
- 'Access to Private Addresses', compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='base.group_private_addresses')
-
_sql_constraints = [
('login_key', 'UNIQUE (login)', 'You can not have two users with the same login !')
]
@@ -738,111 +733,6 @@ class Users(models.Model):
"""
return DEFAULT_CRYPT_CONTEXT
- def _add_missing_default_values(self, vals):
- # Remove the default values of 'group_' and 'has_group' fields at the user creation if
- # 'groups_id' is provided in vals and if the fields value is not explicitely given.
- res = super(Users, self)._add_missing_default_values(vals)
- return {
- key: value for (key, value) in res.items()
- if 'groups_id' not in vals or key in vals or (not is_boolean_group(key) or not is_selection_groups(key))
- }
-
- def _get_group_selection(self, category_xml_id):
- """Returns the ordered selection for the given ir.module.category xmlid"""
- category = self.env.ref(category_xml_id, raise_if_not_found=False)
- selections = []
- if category:
- groups = self.env['res.groups'].search([('category_id', '=', category.id)])
- order = {group: len(group.trans_implied_ids & groups) for group in groups}
- groups = groups.sorted(key=order.get)
- selections = [(group.id, group.name) for group in groups]
- return selections
-
- @api.depends('groups_id')
- def _compute_groups_id(self):
- """Set the value for the group field (Selection of Boolean)
- according to the user's groups, defined by the field 'groups_id'.
-
- These methods should be used on fields defined like one the
- following cases:
- 1. A boolean field with the compute/inverse method and an attribute
- group_xml_id.
- Example:
- has_group_discount_per_so_line = fields.Boolean(
- "Discount on lines",
- compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='sale.group_discount_per_so_line')
- 2. A selection field with the compute/inverse method, the selection
- got from '_get_group_selection' and an attribute
- 'category_xml_id'
- Example:
- group_sales_team_user = fields.Selection(
- selection=lambda self: self._get_group_selection('base.module_category_sales_management'),
- string="Sales", compute='_compute_groups_id', inverse='_inverse_groups_id',
- category_xml_id='base.module_category_sales_management')
- The fields will be set according to the groups defined in the 'groups_id'
- field, and the 'groups_id' field will be modified according to the
- modifications made on theses fields when saving the users's form.
- """
- computed_group_fields = [field for field in self._field_computed.keys() if field.compute == '_compute_groups_id']
- all_groups = self.env['res.groups'].search([])
- category_data = {}
- # Retrieve groups order for selection fields
- for field in computed_group_fields:
- if getattr(field, 'category_xml_id', None):
- category = self.env.ref(getattr(field, 'category_xml_id'))
- category_groups = all_groups.filtered(lambda group: group.category_id == category)
- order = {group: len(group.trans_implied_ids & category_groups) for group in category_groups}
- category_data[field] = category_groups.sorted(key=order.get, reverse=True)
- # Set group fields values according to groups_id
- for user in self:
- for field in computed_group_fields:
- # Checkbox group
- if getattr(field, 'group_xml_id', None):
- group = self.env.ref(getattr(field, 'group_xml_id'))
- user[field.name] = group in user.groups_id
- # Selection group
- elif getattr(field, 'category_xml_id', None):
- # Take the highest level group the user belongs to
- user_groups = [group for group in category_data[field] if group in user.groups_id]
- user[field.name] = user_groups[0].id if user_groups else False
- else:
- _logger.warning(_("No 'group_xml_id' or 'category_xml_id' is set on the computed field %s linked to the method _compute_groups_id") % (field.name))
-
- def _inverse_groups_id(self):
- """Update 'groups_id' according the group fields values in cache."""
- computed_group_fields = [field for field in self._field_computed.keys() if field.compute == '_compute_groups_id']
- all_groups = self.env['res.groups'].search([])
- for user in self:
- # we need to read all values in cache before any prefetch
- field_values = {field: user[field.name] for field in computed_group_fields if field.name in user._cache}
- groups_id_vals = []
- for field, value in field_values.items():
- # Checkbox group
- if getattr(field, 'group_xml_id', None):
- selected_group = self.env.ref(getattr(field, 'group_xml_id'))
- if value:
- groups_id_vals.append((4, selected_group.id))
- else:
- groups_id_vals.append((3, selected_group.id))
- for group in selected_group.trans_implied_ids:
- groups_id_vals.append((4, group.id))
- # Selection group
- elif getattr(field, 'category_xml_id', None):
- category = self.env.ref(getattr(field, 'category_xml_id'))
- category_groups = [group for group in all_groups if group.category_id == category]
- selected_group = [group for group in all_groups if group.id == value]
- selected_group = selected_group and selected_group[0] or self.env['res.groups']
- for group in category_groups:
- if group in (selected_group.trans_implied_ids | selected_group):
- groups_id_vals.append((4, group.id))
- else:
- groups_id_vals.append((3, group.id))
- else:
- _logger.warning(_("No 'group_xml_id' or 'category_xml_id' is set on the computed field %s linked to the method _compute_groups_id") % (field.name))
- if groups_id_vals:
- user.write({'groups_id': groups_id_vals})
-
@contextlib.contextmanager
def _assert_can_auth(self):
""" Checks that the current environment even allows the current auth
@@ -939,7 +829,6 @@ class Users(models.Model):
def _register_hook(self):
if hasattr(self, 'check_credentials'):
_logger.warn("The check_credentials method of res.users has been renamed _check_credentials. One of your installed modules defines one, but it will not be called anymore.")
-
#
# Implied groups
#
@@ -977,7 +866,6 @@ class GroupsImplied(models.Model):
@api.multi
def write(self, values):
res = super(GroupsImplied, self).write(values)
-
if values.get('users') or values.get('implied_ids'):
# add all implied groups (to all users of each group)
for group in self:
@@ -1004,11 +892,309 @@ class UsersImplied(models.Model):
if values.get('groups_id'):
# add implied groups for all users
for user in self.with_context({}):
- gs = set(concat(g.trans_implied_ids for g in user.groups_id))
- vals = {'groups_id': [(4, g.id) for g in gs]}
+ if not user.has_group('base.group_user'):
+ vals = {'groups_id': [(5, 0, 0)] + values['groups_id']}
+ else:
+ gs = set(concat(g.trans_implied_ids for g in user.groups_id))
+ vals = {'groups_id': [(4, g.id) for g in gs]}
super(UsersImplied, user).write(vals)
return res
+#
+# Virtual checkbox and selection for res.user form view
+#
+# Extension of res.groups and res.users for the special groups view in the users
+# form. This extension presents groups with selection and boolean widgets:
+# - Groups are shown by application, with boolean and/or selection fields.
+# Selection fields typically defines a role "Name" for the given application.
+# - Uncategorized groups are presented as boolean fields and grouped in a
+# section "Others".
+#
+# The user form view is modified by an inherited view (base.user_groups_view);
+# the inherited view replaces the field 'groups_id' by a set of reified group
+# fields (boolean or selection fields). The arch of that view is regenerated
+# each time groups are changed.
+#
+# Naming conventions for reified groups fields:
+# - boolean field 'in_group_ID' is True iff
+# ID 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}
+#
+
+class GroupsView(models.Model):
+ _inherit = 'res.groups'
+
+ @api.model
+ def create(self, values):
+ user = super(GroupsView, self).create(values)
+ self._update_user_groups_view()
+ # actions.get_bindings() depends on action records
+ self.env['ir.actions.actions'].clear_caches()
+ return user
+
+ @api.multi
+ def write(self, values):
+ res = super(GroupsView, self).write(values)
+ self._update_user_groups_view()
+ # actions.get_bindings() depends on action records
+ self.env['ir.actions.actions'].clear_caches()
+ return res
+
+ @api.multi
+ def unlink(self):
+ res = super(GroupsView, self).unlink()
+ self._update_user_groups_view()
+ # actions.get_bindings() depends on action records
+ self.env['ir.actions.actions'].clear_caches()
+ return res
+
+ @api.model
+ def _update_user_groups_view(self):
+ """ Modify the view with xmlid ``base.user_groups_view``, which inherits
+ the user form view, and introduces the reified group fields.
+ """
+ if self._context.get('install_mode'):
+ # use installation/admin language for translatable names in the view
+ user_context = self.env['res.users'].context_get()
+ self = self.with_context(**user_context)
+
+ # We have to try-catch this, because at first init the view does not
+ # exist but we are already creating some basic groups.
+ view = self.env.ref('base.user_groups_view', raise_if_not_found=False)
+ if view and view.exists() and view._name == 'ir.ui.view':
+ group_no_one = view.env.ref('base.group_no_one')
+ group_employee = view.env.ref('base.group_user')
+ xml1, xml2, xml3 = [], [], []
+ xml1.append(E.separator(string=_('User Type'), colspan="2", groups='base.group_no_one'))
+ xml2.append(E.separator(string=_('Application Accesses'), colspan="2"))
+
+ user_type_field_name = ''
+ for app, kind, gs in self.get_groups_by_application():
+ attrs = {}
+ # hide groups in categories 'Hidden' and 'Extra' (except for group_no_one)
+ if app.xml_id in ('base.module_category_hidden', 'base.module_category_extra', 'base.module_category_usability'):
+ attrs['groups'] = 'base.group_no_one'
+
+ # User type (employee, portal or public) is a separated group. This is the only 'selection'
+ # group of res.groups without implied groups (with each other).
+ if app.xml_id == 'base.module_category_user_type':
+ # application name with a selection field
+ field_name = name_selection_groups(gs.ids)
+ user_type_field_name = field_name
+ attrs['widget'] = 'radio'
+ attrs['groups'] = 'base.group_no_one'
+ xml1.append(E.field(name=field_name, **attrs))
+ xml1.append(E.newline())
+
+ elif kind == 'selection':
+ # application name with a selection field
+ field_name = name_selection_groups(gs.ids)
+ xml2.append(E.field(name=field_name, **attrs))
+ xml2.append(E.newline())
+ else:
+ # application separator with boolean fields
+ app_name = app.name or _('Other')
+ xml3.append(E.separator(string=app_name, colspan="4", **attrs))
+ for g in gs:
+ field_name = name_boolean_group(g.id)
+ if g == group_no_one:
+ # make the group_no_one invisible in the form view
+ xml3.append(E.field(name=field_name, invisible="1", **attrs))
+ else:
+ xml3.append(E.field(name=field_name, **attrs))
+
+ xml3.append({'class': "o_label_nowrap"})
+ user_type_attrs = {'invisible': [(user_type_field_name, '!=', group_employee.id)]}
+
+ xml = E.field(
+ E.group(*(xml1), col="2"),
+ E.group(*(xml2), col="2", attrs=str(user_type_attrs)),
+ E.group(*(xml3), col="4", attrs=str(user_type_attrs)), name="groups_id", position="replace")
+ xml.addprevious(etree.Comment("GENERATED AUTOMATICALLY BY GROUPS"))
+ xml_content = etree.tostring(xml, pretty_print=True, encoding="unicode")
+
+ new_context = dict(view._context)
+ new_context.pop('install_mode_data', None) # don't set arch_fs for this computed view
+ new_context['lang'] = None
+ view.with_context(new_context).write({'arch': xml_content})
+
+ def get_application_groups(self, domain):
+ """ Return the non-share groups that satisfy ``domain``. """
+ return self.search(domain + [('share', '=', False)])
+
+ @api.model
+ def get_groups_by_application(self):
+ """ Return all groups classified by application (module category), as a list::
+
+ [(app, kind, groups), ...],
+
+ where ``app`` and ``groups`` are recordsets, and ``kind`` is either
+ ``'boolean'`` or ``'selection'``. Applications are given in sequence
+ order. If ``kind`` is ``'selection'``, ``groups`` are given in
+ reverse implication order.
+ """
+ def linearize(app, gs):
+ # 'User Type' is an exception
+ if app.xml_id == 'base.module_category_user_type':
+ return (app, 'selection', gs)
+ # determine sequence order: a group appears after its implied groups
+ order = {g: len(g.trans_implied_ids & gs) for g in gs}
+ # check whether order is total, i.e., sequence orders are distinct
+ if len(set(order.values())) == len(gs):
+ return (app, 'selection', gs.sorted(key=order.get))
+ else:
+ return (app, 'boolean', gs)
+
+ # classify all groups by application
+ by_app, others = defaultdict(self.browse), self.browse()
+ for g in self.get_application_groups([]):
+ if g.category_id:
+ by_app[g.category_id] += g
+ else:
+ others += g
+ # build the result
+ res = []
+ for app, gs in sorted(by_app.items(), key=lambda it: it[0].sequence or 0):
+ res.append(linearize(app, gs))
+ if others:
+ res.append((self.env['ir.module.category'], 'boolean', others))
+ return res
+
+
+class UsersView(models.Model):
+ _inherit = 'res.users'
+
+ @api.model
+ def create(self, values):
+ values = self._remove_reified_groups(values)
+ user = super(UsersView, self).create(values)
+ group_multi_company = self.env.ref('base.group_multi_company', False)
+ if group_multi_company and 'company_ids' in values:
+ if len(user.company_ids) <= 1 and user.id in group_multi_company.users.ids:
+ group_multi_company.write({'users': [(3, user.id)]})
+ elif len(user.company_ids) > 1 and user.id not in group_multi_company.users.ids:
+ group_multi_company.write({'users': [(4, user.id)]})
+ return user
+
+ @api.multi
+ def write(self, values):
+ values = self._remove_reified_groups(values)
+ res = super(UsersView, self).write(values)
+ group_multi_company = self.env.ref('base.group_multi_company', False)
+ if group_multi_company and 'company_ids' in values:
+ for user in self:
+ if len(user.company_ids) <= 1 and user.id in group_multi_company.users.ids:
+ group_multi_company.write({'users': [(3, user.id)]})
+ elif len(user.company_ids) > 1 and user.id not in group_multi_company.users.ids:
+ group_multi_company.write({'users': [(4, user.id)]})
+ return res
+
+ def _remove_reified_groups(self, values):
+ """ return `values` without reified group fields """
+ add, rem = [], []
+ values1 = {}
+
+ for key, val in values.items():
+ 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
+
+ if 'groups_id' not in values and (add or rem):
+ # remove group ids in `rem` and add group ids in `add`
+ values1['groups_id'] = list(itertools.chain(
+ pycompat.izip(repeat(3), rem),
+ pycompat.izip(repeat(4), add)
+ ))
+
+ return values1
+
+ @api.model
+ def default_get(self, fields):
+ group_fields, fields = partition(is_reified_group, fields)
+ fields1 = (fields + ['groups_id']) if group_fields else fields
+ values = super(UsersView, self).default_get(fields1)
+ self._add_reified_groups(group_fields, values)
+ return values
+
+ @api.multi
+ def read(self, fields=None, load='_classic_read'):
+ # determine whether reified groups fields are required, and which ones
+ fields1 = fields or list(self.fields_get())
+ 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
+
+ res = super(UsersView, self).read(other_fields, load=load)
+
+ # post-process result to add reified group fields
+ if group_fields:
+ for values in res:
+ self._add_reified_groups(group_fields, values)
+ if drop_groups_id:
+ values.pop('groups_id', None)
+ return res
+
+ 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_selection_groups(f):
+ selected = [gid for gid in get_selection_groups(f) if gid in gids]
+ values[f] = selected and selected[-1] or False
+
+ @api.model
+ def fields_get(self, allfields=None, attributes=None):
+ res = super(UsersView, self).fields_get(allfields, attributes=attributes)
+ # add reified groups fields
+ for app, kind, gs in self.env['res.groups'].sudo().get_groups_by_application():
+ if kind == 'selection':
+ # 'User Type' should not be 'False'. A user is either 'employee', 'portal' or 'public' (required).
+ selection_vals = [(False, '')]
+ if app.xml_id == 'base.module_category_user_type':
+ selection_vals = []
+ field_name = name_selection_groups(gs.ids)
+ if allfields and field_name not in allfields:
+ continue
+ # selection group field
+ tips = ['%s: %s' % (g.name, g.comment) for g in gs if g.comment]
+ res[field_name] = {
+ 'type': 'selection',
+ 'string': app.name or _('Other'),
+ 'selection': selection_vals + [(g.id, g.name) for g in gs],
+ 'help': '\n'.join(tips),
+ 'exportable': False,
+ 'selectable': False,
+ }
+ else:
+ # boolean group fields
+ for g in gs:
+ field_name = name_boolean_group(g.id)
+ if allfields and field_name not in allfields:
+ continue
+ res[field_name] = {
+ 'type': 'boolean',
+ 'string': g.name,
+ 'help': g.comment,
+ 'exportable': False,
+ 'selectable': False,
+ }
+ return res
+
#----------------------------------------------------------
# change password wizard
#----------------------------------------------------------
diff --git a/odoo/addons/base/tests/test_non_regression.py b/odoo/addons/base/tests/test_non_regression.py
index 1f75578079a..12faee0e1f0 100644
--- a/odoo/addons/base/tests/test_non_regression.py
+++ b/odoo/addons/base/tests/test_non_regression.py
@@ -8,8 +8,11 @@ from openerp.tests.common import TransactionCase
class TestNR(TransactionCase):
def test_issue26036(self):
+ # Coming from https://github.com/odoo/odoo/pull/26036
U = self.env["res.users"]
G = self.env["res.groups"]
+ group_user = self.env.ref('base.group_user')
+ group_no_one = self.env.ref('base.group_no_one')
group_A = G.create({"name": "A"})
group_AA = G.create({"name": "AA", "implied_ids": [(6, 0, [group_A.id])]})
@@ -17,10 +20,13 @@ class TestNR(TransactionCase):
group_BB = G.create({"name": "BB", "implied_ids": [(6, 0, [group_B.id])]})
group_C = G.create({"name": "C"})
- user_a = U.create({"name": "a", "login": "a", "groups_id": [(6, 0, [group_AA.id])]})
+ user_a = U.create({"name": "a", "login": "a", "groups_id": [(6, 0, [group_AA.id, group_user.id])]})
user_b = U.create({"name": "b", "login": "b", "groups_id": [(6, 0, [group_BB.id])]})
+ self.assertEqual(user_a.groups_id, (group_AA + group_A + group_user + group_no_one))
+
(user_a + user_b).write({"groups_id": [(4, group_C.id)]})
- self.assertEqual(user_a.groups_id, (group_AA + group_A + group_C))
- self.assertEqual(user_b.groups_id, (group_BB + group_B + group_C))
+ self.assertEqual(user_a.groups_id, (group_AA + group_A + group_C + group_user + group_no_one))
+ # As user_b is not an internal user, all its groups are removed
+ self.assertEqual(user_b.groups_id, group_C)
diff --git a/odoo/addons/base/views/res_users_views.xml b/odoo/addons/base/views/res_users_views.xml
index 9844be381e1..88aca904336 100644
--- a/odoo/addons/base/views/res_users_views.xml
+++ b/odoo/addons/base/views/res_users_views.xml
@@ -141,6 +141,11 @@
<field name="company_id" context="{'user_preference': 0}" groups="base.group_multi_company"/>
</div>
<group>
+ <label for="groups_id" string="Access Rights"
+ attrs="{'invisible': [('id', '>', 0)]}" groups="base.group_no_one"/>
+ <div attrs="{'invisible': [('id', '>', 0)]}" groups="base.group_no_one">
+ <field name="groups_id" readonly="1" widget="many2many_tags" options="{'color_field': 'color'}" style="display: inline;"/> You will be able to define additional access rights by editing the newly created user under the Settings / Users menu.
+ </div>
<field name="phone" widget="phone"/>
<field name="mobile" widget="phone"/>
</group>
@@ -185,40 +190,12 @@
</div>
<notebook colspan="4">
<page name="access_rights" string="Access Rights">
- <group string="User Type" name="user_type" groups="base.group_no_one">
- <field name="group_base_user" groups="base.group_no_one"/>
- </group>
<group string="Multi Companies" attrs="{'invisible': [('companies_count', '&lt;=', 1)]}">
<field string="Allowed Companies" name="company_ids" widget="many2many_tags" options="{'no_create': True}"/>
<field string="Current Company" name="company_id" context="{'user_preference': 0}"/>
<field string="Companies count" name="companies_count" invisible="1"/>
</group>
- <group string="Application Accesses" name="applications"/>
- <group string="Technical Settings" name="technical" groups="base.group_no_one" col="8">
- <!-- TODO: Hide the no_one group -->
- <label for="has_group_no_one" colspan="3"/>
- <div>
- <field name='has_group_no_one' colspan="1" class="oe_inline"/>
- </div>
- </group>
- <group string="Extra Rights" name="extra" groups="base.group_no_one" col="8">
- <label for="has_group_multi_company" colspan="3"/>
- <div>
- <field name='has_group_multi_company' colspan="1" class="oe_inline"/>
- </div>
- <label for="has_group_multi_currency" colspan="3"/>
- <div>
- <field name='has_group_multi_currency' colspan="1" class="oe_inline"/>
- </div>
- <label for="has_group_partner_manager" colspan="3"/>
- <div>
- <field name='has_group_partner_manager' colspan="1" class="oe_inline"/>
- </div>
- <label for="has_group_private_addresses" colspan="3"/>
- <div>
- <field name="has_group_private_addresses" colspan="1" class="oe_inline"/>
- </div>
- </group>
+ <field name="groups_id"/>
</page>
<page string="Preferences">
<group>
@@ -235,27 +212,12 @@
<field name="signature"/>
</group>
</page>
- <page string="Groups" groups="base.group_no_one">
- <group>
- <field name="groups_id"/>
- </group>
- </page>
</notebook>
</sheet>
</form>
</field>
</record>
- <record id="res_users_view_form_administation" model="ir.ui.view">
- <field name="name">res.users.view.form.administration</field>
- <field name="model">res.users</field>
- <field name="inherit_id" ref="view_users_form"/>
- <field name="priority">1000</field>
- <field name="arch" type="xml">
- <group name="applications" position="inside">
- <field name="group_administration_user"/>
- </group>
- </field>
- </record>
+
<record id="view_users_tree" model="ir.ui.view">
<field name="name">res.users.tree</field>
<field name="model">res.users</field>
@@ -315,6 +277,16 @@
</search>
</field>
</record>
+ <record id="user_groups_view" model="ir.ui.view">
+ <field name="name">res.users.groups</field>
+ <field name="model">res.users</field>
+ <field name="inherit_id" ref="view_users_form"/>
+ <field name="arch" type="xml">
+ <!-- dummy, will be modified by groups -->
+ <field name="groups_id" position="after"/>
+ </field>
+ </record>
+ <!-- dummy update on group, to force the view above to be update -->
<record model="res.groups" id="group_no_one"/>
<record id="action_res_users" model="ir.actions.act_window">
<field name="name">Users</field>