-
-
-
-
diff --git a/addons/purchase/__manifest__.py b/addons/purchase/__manifest__.py
index d82795f39e2..78d6e761690 100644
--- a/addons/purchase/__manifest__.py
+++ b/addons/purchase/__manifest__.py
@@ -25,7 +25,6 @@
'report/purchase_report_views.xml',
'data/mail_template_data.xml',
'views/portal_templates.xml',
- 'views/res_users_views.xml',
'report/purchase_order_templates.xml',
'report/purchase_quotation_templates.xml',
],
diff --git a/addons/purchase/models/__init__.py b/addons/purchase/models/__init__.py
index 5aac1667e5d..a641ce05a71 100644
--- a/addons/purchase/models/__init__.py
+++ b/addons/purchase/models/__init__.py
@@ -7,4 +7,3 @@ from . import product
from . import res_company
from . import res_config_settings
from . import res_partner
-from . import res_users
diff --git a/addons/purchase/models/res_users.py b/addons/purchase/models/res_users.py
deleted file mode 100644
index 6366922e6fa..00000000000
--- a/addons/purchase/models/res_users.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from odoo import fields, models
-
-
-class ResUsers(models.Model):
- _inherit = 'res.users'
-
- has_group_manage_vendor_price = fields.Boolean(
- 'Manage Vendor Price', compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='purchase.group_manage_vendor_price')
-
- has_group_warning_purchase = fields.Boolean(
- 'A warning can be set on a product or a customer (Purchase)',
- compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='purchase.group_warning_purchase')
-
- group_purchase_user = fields.Selection(
- selection=lambda self: self._get_group_selection('base.module_category_purchase_management'),
- string='Purchase', compute='_compute_groups_id', inverse='_inverse_groups_id',
- category_xml_id='base.module_category_purchase_management')
diff --git a/addons/purchase/security/purchase_security.xml b/addons/purchase/security/purchase_security.xml
index 26fbbf5d8c9..85860353f21 100644
--- a/addons/purchase/security/purchase_security.xml
+++ b/addons/purchase/security/purchase_security.xml
@@ -17,10 +17,12 @@
Manage Vendor Price
+ A warning can be set on a product or a customer (Purchase)
+
diff --git a/addons/purchase/views/res_users_views.xml b/addons/purchase/views/res_users_views.xml
deleted file mode 100644
index 54e0eb080f5..00000000000
--- a/addons/purchase/views/res_users_views.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
- res.users.form.purchase.inherit
- res.users
-
- 45
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/purchase_requisition/tests/test_purchase_requisition.py b/addons/purchase_requisition/tests/test_purchase_requisition.py
index 165eff03d8a..b88a4af5cc9 100644
--- a/addons/purchase_requisition/tests/test_purchase_requisition.py
+++ b/addons/purchase_requisition/tests/test_purchase_requisition.py
@@ -23,11 +23,11 @@ class TestPurchaseRequisition(common.TransactionCase):
# Create a user as 'Purchase Requisition Manager'
self.res_users_purchase_requisition_manager = self.ResUser.create({'company_id': self.res_company_id, 'name': 'Purchase requisition Manager', 'login': 'prm', 'email': 'requisition_manager@yourcompany.com'})
# Added groups for Purchase Requisition Manager.
- self.res_users_purchase_requisition_manager.groups_id = [(4, self.ref('purchase.group_purchase_manager'), False)]
+ self.res_users_purchase_requisition_manager.group_id = self.ref('purchase.group_purchase_manager')
# Create a user as 'Purchase Requisition User'
self.res_users_purchase_requisition_user = self.ResUser.create({'company_id': self.res_company_id, 'name': 'Purchase requisition User', 'login': 'pru', 'email': 'requisition_user@yourcompany.com'})
# Added groups for Purchase Requisition User.
- self.res_users_purchase_requisition_user.groups_id = [(4, self.ref('purchase.group_purchase_user'), False)]
+ self.res_users_purchase_requisition_user.group_id = self.ref('purchase.group_purchase_user')
# In order to test process of the purchase requisition ,create requisition
self.requisition1 = self.env['purchase.requisition'].create({'line_ids': [(0, 0, {'product_id': self.product_09_id, 'product_qty': 10.0, 'product_uom_id': self.product_09_uom_id})]})
diff --git a/addons/sale/__manifest__.py b/addons/sale/__manifest__.py
index 684f72b31c1..03ea9b45f96 100644
--- a/addons/sale/__manifest__.py
+++ b/addons/sale/__manifest__.py
@@ -30,7 +30,6 @@ This module contains all the common features of Sales Management and eCommerce.
'views/sale_portal_templates.xml',
'views/sale_onboarding_views.xml',
'views/res_config_settings_views.xml',
- 'views/res_users_views.xml',
'views/payment_views.xml',
],
'demo': [
diff --git a/addons/sale/models/__init__.py b/addons/sale/models/__init__.py
index 293a9217d67..1f6f027cba3 100644
--- a/addons/sale/models/__init__.py
+++ b/addons/sale/models/__init__.py
@@ -11,5 +11,4 @@ from . import res_partner
from . import sale
from . import res_config_settings
from . import sales_team
-from . import res_users
from . import payment
diff --git a/addons/sale/models/res_users.py b/addons/sale/models/res_users.py
deleted file mode 100644
index 0e5c5364472..00000000000
--- a/addons/sale/models/res_users.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from odoo import fields, models
-
-
-class ResUsers(models.Model):
- _inherit = 'res.users'
-
- has_group_warning_sale = fields.Boolean(
- "A warning can be set on a product or a customer (Sale)",
- compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='sale.group_warning_sale')
-
- has_group_delivery_invoice_address = fields.Boolean(
- "Addresses in Sales Orders",
- compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='sale.group_delivery_invoice_address')
-
- 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')
-
- has_group_proforma_sales = fields.Boolean(
- "Pro-forma Invoices",
- compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='sale.group_proforma_sales')
-
- has_group_sale_order_dates = fields.Boolean(
- "Sale Order Dates",
- compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='sale.group_sale_order_dates')
diff --git a/addons/sale/security/sale_security.xml b/addons/sale/security/sale_security.xml
index 6c5134c0b9f..454a93ffb37 100644
--- a/addons/sale/security/sale_security.xml
+++ b/addons/sale/security/sale_security.xml
@@ -3,22 +3,27 @@
Addresses in Sales Orders
+ Discount on lines
+ A warning can be set on a product or a customer (Sale)
+ Pro-forma Invoices
+ Manage delivery dates from sales orders.
+ This option introduces extra fields in the sales order to easily schedule product deliveries on your own: expected date, commitment date, effective date.
diff --git a/addons/sale/tests/test_sale_common.py b/addons/sale/tests/test_sale_common.py
index 17ad437a10e..4cac0a30702 100644
--- a/addons/sale/tests/test_sale_common.py
+++ b/addons/sale/tests/test_sale_common.py
@@ -17,7 +17,7 @@ class TestSale(AccountingTestCase):
'email': 'a.m@example.com',
'signature': '--\nAndreww',
'notification_type': 'email',
- 'groups_id': [(6, 0, [group_manager.id])]
+ 'groups_id': [(6, 0, [group_manager.id, self.env.ref('base.group_user').id])]
})
self.user = self.env['res.users'].create({
'name': 'Mark User',
diff --git a/addons/sale/tests/test_sale_order.py b/addons/sale/tests/test_sale_order.py
index 5e59b6b7a83..78a1afec795 100644
--- a/addons/sale/tests/test_sale_order.py
+++ b/addons/sale/tests/test_sale_order.py
@@ -21,8 +21,9 @@ class TestSaleOrder(TestCommonSaleNoChart):
cls.setUpUsers()
group_salemanager = cls.env.ref('sales_team.group_sale_manager')
group_salesman = cls.env.ref('sales_team.group_sale_salesman')
- cls.user_manager.write({'groups_id': [(6, 0, [group_salemanager.id])]})
- cls.user_employee.write({'groups_id': [(6, 0, [group_salesman.id])]})
+ group_employee = cls.env.ref('base.group_user')
+ cls.user_manager.write({'groups_id': [(6, 0, [group_salemanager.id, group_employee.id])]})
+ cls.user_employee.write({'groups_id': [(6, 0, [group_salesman.id, group_employee.id])]})
# set up accounts and products and journals
cls.setUpAdditionalAccounts()
diff --git a/addons/sale/views/res_users_views.xml b/addons/sale/views/res_users_views.xml
deleted file mode 100644
index ea8a9831de5..00000000000
--- a/addons/sale/views/res_users_views.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
- res.users.form.sale.inherit
- res.users
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/sale_management/models/__init__.py b/addons/sale_management/models/__init__.py
index 658b0890cf3..d4ea9bb0f8f 100644
--- a/addons/sale_management/models/__init__.py
+++ b/addons/sale_management/models/__init__.py
@@ -2,6 +2,5 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import digest
from . import res_config_settings
-from . import res_users
from . import sale_order
from . import sale_order_template
diff --git a/addons/sale_management/models/res_users.py b/addons/sale_management/models/res_users.py
deleted file mode 100644
index 1484e3c0cfa..00000000000
--- a/addons/sale_management/models/res_users.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from odoo import fields, models
-
-
-class ResUsers(models.Model):
- _inherit = 'res.users'
-
- has_group_sale_order_template = fields.Boolean(
- "Quotation Templates",
- compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='sale_management.group_sale_order_template')
diff --git a/addons/sale_stock/__manifest__.py b/addons/sale_stock/__manifest__.py
index 101882305bd..73b46286776 100644
--- a/addons/sale_stock/__manifest__.py
+++ b/addons/sale_stock/__manifest__.py
@@ -28,7 +28,6 @@ Preferences
'views/res_config_settings_views.xml',
'views/account_invoice_views.xml',
'views/sale_stock_portal_template.xml',
- 'views/res_users_views.xml',
'views/stock_production_lot_views.xml',
'report/sale_order_report_templates.xml',
'report/stock_report_deliveryslip.xml',
diff --git a/addons/sale_stock/models/__init__.py b/addons/sale_stock/models/__init__.py
index 9aa9277bfe8..1f11274e77a 100644
--- a/addons/sale_stock/models/__init__.py
+++ b/addons/sale_stock/models/__init__.py
@@ -7,4 +7,3 @@ from . import sale_order
from . import res_config_settings
from . import stock
from . import product_template
-from . import res_users
diff --git a/addons/sale_stock/models/res_users.py b/addons/sale_stock/models/res_users.py
deleted file mode 100644
index 5a1cbd2e1e5..00000000000
--- a/addons/sale_stock/models/res_users.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from odoo import fields, models
-
-
-class ResUsers(models.Model):
- _inherit = 'res.users'
-
- has_group_route_so_lines = fields.Boolean(
- 'Enable Route on Sales Order Line',
- compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='sale_stock.group_route_so_lines')
-
- has_group_display_incoterm = fields.Boolean(
- 'Display incoterms on Sales Order and related invoices',
- compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='sale_stock.group_display_incoterm')
diff --git a/addons/sale_stock/security/sale_stock_security.xml b/addons/sale_stock/security/sale_stock_security.xml
index 72c18b01594..630a6512b2d 100644
--- a/addons/sale_stock/security/sale_stock_security.xml
+++ b/addons/sale_stock/security/sale_stock_security.xml
@@ -3,10 +3,12 @@
Enable Route on Sales Order Line
-
+
+ Display incoterms on Sales Order and related invoices
-
+
+
diff --git a/addons/sale_stock/views/res_users_views.xml b/addons/sale_stock/views/res_users_views.xml
deleted file mode 100644
index 99d64a9f5a8..00000000000
--- a/addons/sale_stock/views/res_users_views.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
- res.users.form.sale_stock.inherit
- res.users
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/sales_team/__manifest__.py b/addons/sales_team/__manifest__.py
index 3ecf2d97481..471fc179c52 100644
--- a/addons/sales_team/__manifest__.py
+++ b/addons/sales_team/__manifest__.py
@@ -16,7 +16,6 @@ Using this application you can manage Sales Teams with CRM and/or Sales
'data/sales_team_data.xml',
'views/crm_team_views.xml',
'views/sales_team_dashboard.xml',
- 'views/res_users_views.xml',
],
'demo': ['data/sales_team_demo.xml'],
'installable': True,
diff --git a/addons/sales_team/models/res_users.py b/addons/sales_team/models/res_users.py
index 6ce0c27f983..7a31376f887 100644
--- a/addons/sales_team/models/res_users.py
+++ b/addons/sales_team/models/res_users.py
@@ -11,12 +11,6 @@ class ResUsers(models.Model):
'crm.team', "User's Sales Team",
help='Sales Team the user is member of. Used to compute the members of a Sales Team through the inverse one2many')
- 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',
- help='User: Own Documents Only: the user will have access to his own data in the sales application.\nUser: All Documents: the user will have access to all records of everyone in the sales application.\nManager: the user will have an access to the sales configuration as well as statistic reports.')
-
@api.model
def create(self, vals):
# Assign the new user in the sales team if there's only one sales team of type `Sales`
diff --git a/addons/sales_team/views/res_users_views.xml b/addons/sales_team/views/res_users_views.xml
deleted file mode 100644
index 2bdb98fe0a7..00000000000
--- a/addons/sales_team/views/res_users_views.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
- res.users.form.sales_team.inherit
- res.users
-
- 1
-
-
-
-
-
-
-
diff --git a/addons/stock/__manifest__.py b/addons/stock/__manifest__.py
index 4b5bbb8f677..a9010b63ac2 100644
--- a/addons/stock/__manifest__.py
+++ b/addons/stock/__manifest__.py
@@ -64,7 +64,6 @@
'views/report_stock_traceability.xml',
'views/stock_template.xml',
'views/stock_rule_views.xml',
- 'views/res_users_views.xml',
'views/stock_package_level_views.xml',
'data/default_barcode_patterns.xml',
diff --git a/addons/stock/models/__init__.py b/addons/stock/models/__init__.py
index c34d37f6d19..6a103f06041 100644
--- a/addons/stock/models/__init__.py
+++ b/addons/stock/models/__init__.py
@@ -18,5 +18,4 @@ from . import stock_traceability
from . import stock_warehouse
from . import stock_scrap
from . import product
-from . import res_users
from . import stock_package_level
diff --git a/addons/stock/models/res_users.py b/addons/stock/models/res_users.py
deleted file mode 100644
index 2a8cf3e743d..00000000000
--- a/addons/stock/models/res_users.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from odoo import fields, models
-
-
-class ResUsers(models.Model):
- _inherit = 'res.users'
-
- has_group_stock_multi_locations = fields.Boolean(
- 'Manage Multiple Stock Locations',
- compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='stock.group_stock_multi_locations')
-
- has_group_stock_multi_warehouses = fields.Boolean(
- 'Manage Multiple Warehouses',
- compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='stock.group_stock_multi_warehouses')
-
- has_group_production_lot = fields.Boolean(
- 'Manage Lots / Serial Numbers',
- compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='stock.group_production_lot')
-
- has_group_lot_on_delivery_slip = fields.Boolean(
- 'Display Lots & Serial Numbers on Delivery Slip',
- compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='stock.group_lot_on_delivery_slip')
-
- has_group_tracking_lot = fields.Boolean(
- 'Manage Packages',
- compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='stock.group_tracking_lot')
-
- has_group_adv_location = fields.Boolean(
- 'Manage Push and Pull inventory flows',
- compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='stock.group_adv_location')
-
- has_group_tracking_owner = fields.Boolean(
- 'Manage Different Stock Owners',
- compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='stock.group_tracking_owner')
-
- has_group_warning_stock = fields.Boolean(
- 'A warning can be set on a partner (Stock)',
- compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='stock.group_warning_stock')
-
- group_stock_user = fields.Selection(
- selection=lambda self: self._get_group_selection('base.module_category_warehouse_management'),
- string='Inventory', compute='_compute_groups_id', inverse='_inverse_groups_id',
- category_xml_id='base.module_category_warehouse_management')
diff --git a/addons/stock/security/stock_security.xml b/addons/stock/security/stock_security.xml
index f1ea0759215..c48b0627077 100644
--- a/addons/stock/security/stock_security.xml
+++ b/addons/stock/security/stock_security.xml
@@ -4,10 +4,12 @@
Manage Multiple Stock Locations
+ Manage Multiple Warehouses
+
@@ -24,6 +26,7 @@
Manage Lots / Serial Numbers
+
@@ -33,18 +36,22 @@
Manage Packages
+ Manage Push and Pull inventory flows
+ Manage Different Stock Owners
+ A warning can be set on a partner (Stock)
+
diff --git a/addons/stock/views/res_users_views.xml b/addons/stock/views/res_users_views.xml
deleted file mode 100644
index beb0aaf5951..00000000000
--- a/addons/stock/views/res_users_views.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
- res.users.form.stock.inherit
- res.users
-
- 15
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/survey/__manifest__.py b/addons/survey/__manifest__.py
index 697b784c7b4..6304e6957d7 100644
--- a/addons/survey/__manifest__.py
+++ b/addons/survey/__manifest__.py
@@ -1,4 +1,3 @@
-
# -*- encoding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
@@ -24,7 +23,6 @@ sent mails with personal token for the invitation of the survey.
'views/survey_views.xml',
'views/survey_templates.xml',
'views/survey_result.xml',
- 'views/res_users_views.xml',
'data/mail_template_data.xml',
'wizard/survey_email_compose_message.xml',
'data/survey_stages.xml',
diff --git a/addons/survey/models/__init__.py b/addons/survey/models/__init__.py
index 1acf09ab8d1..26edfafe8ad 100644
--- a/addons/survey/models/__init__.py
+++ b/addons/survey/models/__init__.py
@@ -3,4 +3,3 @@
from . import survey
from . import ir_autovacuum
-from . import res_users
diff --git a/addons/survey/models/res_users.py b/addons/survey/models/res_users.py
deleted file mode 100644
index 939db447151..00000000000
--- a/addons/survey/models/res_users.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from odoo import fields, models
-
-
-class ResUsers(models.Model):
- _inherit = 'res.users'
-
- group_survey_user = fields.Selection(
- selection=lambda self: self._get_group_selection('base.module_category_survey'),
- string='Survey', compute='_compute_groups_id', inverse='_inverse_groups_id',
- category_xml_id='base.module_category_survey')
diff --git a/addons/survey/views/res_users_views.xml b/addons/survey/views/res_users_views.xml
deleted file mode 100644
index dd60ccd8fae..00000000000
--- a/addons/survey/views/res_users_views.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
- res.users.form.survey.inherit
- res.users
-
- 115
-
-
-
-
-
-
-
diff --git a/addons/uom/__manifest__.py b/addons/uom/__manifest__.py
index 0ecdf757599..390682370b9 100644
--- a/addons/uom/__manifest__.py
+++ b/addons/uom/__manifest__.py
@@ -15,7 +15,6 @@ This is the base module for managing Units of measure.
'security/uom_security.xml',
'security/ir.model.access.csv',
'views/uom_uom_views.xml',
- 'views/res_users_views.xml',
],
'installable': True,
'auto_install': False,
diff --git a/addons/uom/models/__init__.py b/addons/uom/models/__init__.py
index 4b4c458ea60..357b0410c00 100644
--- a/addons/uom/models/__init__.py
+++ b/addons/uom/models/__init__.py
@@ -2,4 +2,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import uom_uom
-from . import res_users
diff --git a/addons/uom/models/res_users.py b/addons/uom/models/res_users.py
deleted file mode 100644
index 8281b78ef14..00000000000
--- a/addons/uom/models/res_users.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from odoo import fields, models
-
-
-class ResUsers(models.Model):
- _inherit = 'res.users'
-
- has_group_uom = fields.Boolean(
- 'Manage Multiple Units of Measure', compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='uom.group_uom')
diff --git a/addons/uom/security/uom_security.xml b/addons/uom/security/uom_security.xml
index f7e0c0dd4d4..ebfe3e1178d 100644
--- a/addons/uom/security/uom_security.xml
+++ b/addons/uom/security/uom_security.xml
@@ -3,6 +3,7 @@
Manage Multiple Units of Measure
+
diff --git a/addons/uom/views/res_users_views.xml b/addons/uom/views/res_users_views.xml
deleted file mode 100644
index 55240233a6a..00000000000
--- a/addons/uom/views/res_users_views.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
- res.users.form.uom.inherit
- res.users
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/website/__manifest__.py b/addons/website/__manifest__.py
index 463ec49af7c..fe7c479d23e 100644
--- a/addons/website/__manifest__.py
+++ b/addons/website/__manifest__.py
@@ -28,7 +28,6 @@
'views/res_config_settings_views.xml',
'views/ir_actions_views.xml',
'views/ir_attachment_views.xml',
- 'views/res_users_views.xml',
'views/res_partner_views.xml',
'wizard/base_language_install_views.xml',
],
diff --git a/addons/website/models/__init__.py b/addons/website/models/__init__.py
index 0ed659a8f3a..efeda5560b7 100644
--- a/addons/website/models/__init__.py
+++ b/addons/website/models/__init__.py
@@ -13,4 +13,3 @@ from . import res_company
from . import res_partner
from . import res_users
from . import res_config_settings
-from . import res_users
diff --git a/addons/website/models/res_users.py b/addons/website/models/res_users.py
index 3f9697eb797..6d701f55241 100644
--- a/addons/website/models/res_users.py
+++ b/addons/website/models/res_users.py
@@ -12,15 +12,6 @@ _logger = logging.getLogger(__name__)
class ResUsers(models.Model):
_inherit = 'res.users'
- group_website_user = fields.Selection(
- selection=lambda self: self._get_group_selection('base.module_category_website'),
- string='Website Editor', compute='_compute_groups_id', inverse='_inverse_groups_id',
- category_xml_id='base.module_category_website')
- has_group_multi_website = fields.Boolean(
- 'Multi-Websites',
- compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='website.group_multi_website')
-
_sql_constraints = [
# this is done in Python because a SQL constraint like UNIQUE
# (login, website_id) allows ('abc', NULL) and
diff --git a/addons/website/views/res_users_views.xml b/addons/website/views/res_users_views.xml
deleted file mode 100644
index c17e79697c7..00000000000
--- a/addons/website/views/res_users_views.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
- res.users.form.website.inherit
- res.users
-
- 135
-
-
-
-
-
-
-
diff --git a/addons/website_mass_mailing/__manifest__.py b/addons/website_mass_mailing/__manifest__.py
index ac128e27a7f..3691d7a5da7 100644
--- a/addons/website_mass_mailing/__manifest__.py
+++ b/addons/website_mass_mailing/__manifest__.py
@@ -17,7 +17,6 @@ On a simple click, your visitors can subscribe to mailing lists managed in the E
'views/snippets_templates.xml',
'views/mass_mailing_view.xml',
'views/res_config_settings_views.xml',
- 'views/res_users_views.xml',
],
'auto_install': True,
}
diff --git a/addons/website_mass_mailing/models/__init__.py b/addons/website_mass_mailing/models/__init__.py
index d70758fc817..c9e39e466ca 100644
--- a/addons/website_mass_mailing/models/__init__.py
+++ b/addons/website_mass_mailing/models/__init__.py
@@ -3,4 +3,3 @@
from . import res_config_settings
from . import mass_mailing_list
-from . import res_users
diff --git a/addons/website_mass_mailing/models/res_users.py b/addons/website_mass_mailing/models/res_users.py
deleted file mode 100644
index 6024234dfb9..00000000000
--- a/addons/website_mass_mailing/models/res_users.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from odoo import fields, models
-
-
-class ResUsers(models.Model):
- _inherit = 'res.users'
-
- has_group_website_popup_on_exit = fields.Boolean(
- 'Use subscription pop up on the website',
- compute='_compute_groups_id', inverse='_inverse_groups_id',
- group_xml_id='website_mass_mailing.group_website_popup_on_exit')
diff --git a/addons/website_mass_mailing/security/mass_mailing_security.xml b/addons/website_mass_mailing/security/mass_mailing_security.xml
index 95c9d4d56bb..1f1438fcab3 100644
--- a/addons/website_mass_mailing/security/mass_mailing_security.xml
+++ b/addons/website_mass_mailing/security/mass_mailing_security.xml
@@ -2,5 +2,6 @@
Use subscription pop up on the website
+
diff --git a/addons/website_mass_mailing/views/res_users_views.xml b/addons/website_mass_mailing/views/res_users_views.xml
deleted file mode 100644
index dc0b20a84f0..00000000000
--- a/addons/website_mass_mailing/views/res_users_views.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
- res.users.form.website_mass_mailing.inherit
- res.users
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/website_theme_install/views/views.xml b/addons/website_theme_install/views/views.xml
index 95f9990be35..7cbe6d7102e 100644
--- a/addons/website_theme_install/views/views.xml
+++ b/addons/website_theme_install/views/views.xml
@@ -79,7 +79,7 @@
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 @@
+
+ Technical Settings
+ 0
+
+
+
Localization
@@ -185,6 +191,16 @@
100
+
+ Extra Rights
+ 101
+
+
+
+ Other Extra Rights
+ 102
+
+
@@ -197,6 +213,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -205,5 +233,9 @@
+
+
+
+
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 @@
-
-
-
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 @@
+
+
+ You will be able to define additional access rights by editing the newly created user under the Settings / Users menu.
+