diff options
| author | Christophe Simonis <chs@odoo.com> | 2018-09-10 17:06:18 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2018-09-10 17:06:18 +0200 |
| commit | 86ff929ad6718d2bfb691dd272aa3f353406ff4f (patch) | |
| tree | a62b50cc471452ff2dc3c034a38d6cade79c03ed /odoo/addons | |
| parent | 4ad896bd3d6bbc851a59adead1a653abc31fc882 (diff) | |
| parent | 1199451606cab7342e34fc9c41a2085b8beb1243 (diff) | |
[MERGE] forward port branch saas-11.4 up to 1199451606c
Diffstat (limited to 'odoo/addons')
| -rw-r--r-- | odoo/addons/base/data/ir_module_category_data.xml | 32 | ||||
| -rw-r--r-- | odoo/addons/base/data/res_users_demo.xml | 3 | ||||
| -rw-r--r-- | odoo/addons/base/models/res_users.py | 476 | ||||
| -rw-r--r-- | odoo/addons/base/tests/test_non_regression.py | 12 | ||||
| -rw-r--r-- | odoo/addons/base/views/res_users_views.xml | 62 |
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', '<=', 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> |
