+
+
+
+
diff --git a/addons/purchase/__manifest__.py b/addons/purchase/__manifest__.py
index 7f1cb5e5474..8e4a43cc50e 100644
--- a/addons/purchase/__manifest__.py
+++ b/addons/purchase/__manifest__.py
@@ -24,6 +24,7 @@
'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 d0ec983e287..d402046cde0 100644
--- a/addons/purchase/models/__init__.py
+++ b/addons/purchase/models/__init__.py
@@ -8,3 +8,4 @@ from . import res_company
from . import res_config_settings
from . import res_partner
from . import stock
+from . import res_users
diff --git a/addons/purchase/models/res_users.py b/addons/purchase/models/res_users.py
new file mode 100644
index 00000000000..5264a6fd89e
--- /dev/null
+++ b/addons/purchase/models/res_users.py
@@ -0,0 +1,22 @@
+# -*- 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='Purchases', 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 75fbca272f6..b2d8e241ba7 100644
--- a/addons/purchase/security/purchase_security.xml
+++ b/addons/purchase/security/purchase_security.xml
@@ -17,12 +17,10 @@
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
new file mode 100644
index 00000000000..54e0eb080f5
--- /dev/null
+++ b/addons/purchase/views/res_users_views.xml
@@ -0,0 +1,24 @@
+
+
+
+ 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 1b4ff7c1da0..e80f04ecb95 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.group_id = self.ref('purchase.group_purchase_manager')
+ self.res_users_purchase_requisition_manager.groups_id = [(4, self.ref('purchase.group_purchase_manager'), False)]
# 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.group_id = self.ref('purchase.group_purchase_user')
+ self.res_users_purchase_requisition_user.groups_id = [(4, self.ref('purchase.group_purchase_user'), False)]
# 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 17983579339..1e78334c26d 100644
--- a/addons/sale/__manifest__.py
+++ b/addons/sale/__manifest__.py
@@ -31,6 +31,7 @@ This module contains all the common features of Sales Management and eCommerce.
'views/sale_portal_templates.xml',
'views/sale_layout_category_view.xml',
'views/res_config_settings_views.xml',
+ 'views/res_users_views.xml',
],
'demo': [
'data/sale_demo.xml',
diff --git a/addons/sale/models/__init__.py b/addons/sale/models/__init__.py
index 7a157566790..dcd51bccca5 100644
--- a/addons/sale/models/__init__.py
+++ b/addons/sale/models/__init__.py
@@ -13,3 +13,4 @@ from . import res_config_settings
from . import sale_layout
from . import sales_team
from . import ir_model_fields
+from . import res_users
\ No newline at end of file
diff --git a/addons/sale/models/res_users.py b/addons/sale/models/res_users.py
new file mode 100644
index 00000000000..5ff6b06d878
--- /dev/null
+++ b/addons/sale/models/res_users.py
@@ -0,0 +1,43 @@
+# -*- 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_sale_layout = fields.Boolean(
+ "Personalize sales order and invoice report",
+ compute='_compute_groups_id', inverse='_inverse_groups_id',
+ group_xml_id='sale.group_sale_layout')
+
+ 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_show_price_subtotal = fields.Boolean(
+ "Show line subtotals without taxes (B2B)",
+ compute='_compute_groups_id', inverse='_inverse_groups_id',
+ group_xml_id='sale.group_show_price_subtotal')
+
+ has_group_show_price_total = fields.Boolean(
+ "Show line subtotals with taxes included (B2C)",
+ compute='_compute_groups_id', inverse='_inverse_groups_id',
+ group_xml_id='sale.group_show_price_total')
+
+ 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')
diff --git a/addons/sale/security/sale_security.xml b/addons/sale/security/sale_security.xml
index f753c3bed13..881b462b14e 100644
--- a/addons/sale/security/sale_security.xml
+++ b/addons/sale/security/sale_security.xml
@@ -3,23 +3,19 @@
Personalize sales order and invoice report
- Addresses in Sales Orders
- Tax display B2B
- Show line subtotals without taxes (B2B)Tax display B2C
- Show line subtotals with taxes included (B2C)
@@ -37,23 +33,14 @@
Discount on lines
- A warning can be set on a product or a customer (Sale)
- Pro-forma Invoices
-
-
-
-
- Analytic Accounting for Sales
-
-
diff --git a/addons/sale/views/res_users_views.xml b/addons/sale/views/res_users_views.xml
new file mode 100644
index 00000000000..a4eae27cc6c
--- /dev/null
+++ b/addons/sale/views/res_users_views.xml
@@ -0,0 +1,44 @@
+
+
+
+ res.users.form.sale.inherit
+ res.users
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/sale_stock/__manifest__.py b/addons/sale_stock/__manifest__.py
index c4b41c4ca3b..77a181a7195 100644
--- a/addons/sale_stock/__manifest__.py
+++ b/addons/sale_stock/__manifest__.py
@@ -35,6 +35,7 @@ You can choose flexible invoicing methods:
'views/account_invoice_views.xml',
'views/sale_stock_portal_template.xml',
'views/product_template_views.xml',
+ 'views/res_users_views.xml',
'report/sale_order_report_templates.xml',
'report/stock_report_deliveryslip.xml',
'data/sale_stock_data.xml',
diff --git a/addons/sale_stock/models/__init__.py b/addons/sale_stock/models/__init__.py
index 1f11274e77a..9aa9277bfe8 100644
--- a/addons/sale_stock/models/__init__.py
+++ b/addons/sale_stock/models/__init__.py
@@ -7,3 +7,4 @@ 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
new file mode 100644
index 00000000000..5a1cbd2e1e5
--- /dev/null
+++ b/addons/sale_stock/models/res_users.py
@@ -0,0 +1,18 @@
+# -*- 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 630a6512b2d..72c18b01594 100644
--- a/addons/sale_stock/security/sale_stock_security.xml
+++ b/addons/sale_stock/security/sale_stock_security.xml
@@ -3,12 +3,10 @@
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
new file mode 100644
index 00000000000..99d64a9f5a8
--- /dev/null
+++ b/addons/sale_stock/views/res_users_views.xml
@@ -0,0 +1,20 @@
+
+
+
+ res.users.form.sale_stock.inherit
+ res.users
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/sales_team/__manifest__.py b/addons/sales_team/__manifest__.py
index 199b34db0e8..fd8ce74a30c 100644
--- a/addons/sales_team/__manifest__.py
+++ b/addons/sales_team/__manifest__.py
@@ -16,6 +16,7 @@ Using this application you can manage Sales Channels 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 d5e657f2ce5..50b7e5cd28c 100644
--- a/addons/sales_team/models/res_users.py
+++ b/addons/sales_team/models/res_users.py
@@ -11,6 +11,12 @@ class ResUsers(models.Model):
'crm.team', 'Sales Team',
help='Sales Channel the user is member of. Used to compute the members of a sales channel 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
new file mode 100644
index 00000000000..2bdb98fe0a7
--- /dev/null
+++ b/addons/sales_team/views/res_users_views.xml
@@ -0,0 +1,14 @@
+
+
+
+ res.users.form.sales_team.inherit
+ res.users
+
+ 1
+
+
+
+
+
+
+
diff --git a/addons/stock/__manifest__.py b/addons/stock/__manifest__.py
index 59a2c23e6d0..ed7e5f2d9ef 100644
--- a/addons/stock/__manifest__.py
+++ b/addons/stock/__manifest__.py
@@ -63,6 +63,7 @@
'views/report_stock_traceability.xml',
'views/stock_template.xml',
'views/procurement_views.xml',
+ 'views/res_users_views.xml',
'data/default_barcode_patterns.xml',
'data/stock_data.xml',
diff --git a/addons/stock/models/__init__.py b/addons/stock/models/__init__.py
index af1550378ab..9741628e950 100644
--- a/addons/stock/models/__init__.py
+++ b/addons/stock/models/__init__.py
@@ -19,3 +19,4 @@ from . import stock_traceability
from . import stock_warehouse
from . import stock_scrap
from . import product
+from . import res_users
diff --git a/addons/stock/models/res_users.py b/addons/stock/models/res_users.py
new file mode 100644
index 00000000000..e6d1ccd5262
--- /dev/null
+++ b/addons/stock/models/res_users.py
@@ -0,0 +1,48 @@
+# -*- 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_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 3301a5fbb8c..153b39dd9e4 100644
--- a/addons/stock/security/stock_security.xml
+++ b/addons/stock/security/stock_security.xml
@@ -4,12 +4,10 @@
Manage Multiple Stock Locations
- Manage Multiple Warehouses
-
@@ -26,27 +24,22 @@
Manage Lots / Serial Numbers
- 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
new file mode 100644
index 00000000000..7d736825fc0
--- /dev/null
+++ b/addons/stock/views/res_users_views.xml
@@ -0,0 +1,44 @@
+
+
+
+ res.users.form.stock.inherit
+ res.users
+
+ 15
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/survey/__manifest__.py b/addons/survey/__manifest__.py
index 38fa932d670..78290313cbf 100644
--- a/addons/survey/__manifest__.py
+++ b/addons/survey/__manifest__.py
@@ -1,3 +1,4 @@
+
# -*- encoding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
@@ -23,6 +24,7 @@ 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',
'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 26edfafe8ad..1acf09ab8d1 100644
--- a/addons/survey/models/__init__.py
+++ b/addons/survey/models/__init__.py
@@ -3,3 +3,4 @@
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
new file mode 100644
index 00000000000..939db447151
--- /dev/null
+++ b/addons/survey/models/res_users.py
@@ -0,0 +1,13 @@
+# -*- 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
new file mode 100644
index 00000000000..dd60ccd8fae
--- /dev/null
+++ b/addons/survey/views/res_users_views.xml
@@ -0,0 +1,14 @@
+
+
+
+ res.users.form.survey.inherit
+ res.users
+
+ 115
+
+
+
+
+
+
+
diff --git a/addons/uom/__manifest__.py b/addons/uom/__manifest__.py
index 390682370b9..0ecdf757599 100644
--- a/addons/uom/__manifest__.py
+++ b/addons/uom/__manifest__.py
@@ -15,6 +15,7 @@ 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 357b0410c00..4b4c458ea60 100644
--- a/addons/uom/models/__init__.py
+++ b/addons/uom/models/__init__.py
@@ -2,3 +2,4 @@
# 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
new file mode 100644
index 00000000000..8281b78ef14
--- /dev/null
+++ b/addons/uom/models/res_users.py
@@ -0,0 +1,12 @@
+# -*- 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 ebfe3e1178d..f7e0c0dd4d4 100644
--- a/addons/uom/security/uom_security.xml
+++ b/addons/uom/security/uom_security.xml
@@ -3,7 +3,6 @@
Manage Multiple Units of Measure
-
diff --git a/addons/uom/views/res_users_views.xml b/addons/uom/views/res_users_views.xml
new file mode 100644
index 00000000000..55240233a6a
--- /dev/null
+++ b/addons/uom/views/res_users_views.xml
@@ -0,0 +1,16 @@
+
+
+
+ res.users.form.uom.inherit
+ res.users
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/website/__manifest__.py b/addons/website/__manifest__.py
index fbedd44425b..119732b0257 100644
--- a/addons/website/__manifest__.py
+++ b/addons/website/__manifest__.py
@@ -27,6 +27,7 @@
'views/website_views.xml',
'views/res_config_settings_views.xml',
'views/ir_actions_views.xml',
+ 'views/res_users_views.xml',
'wizard/base_language_install_views.xml',
],
'demo': [
diff --git a/addons/website/models/__init__.py b/addons/website/models/__init__.py
index 750744ce7d1..ae4613baff0 100644
--- a/addons/website/models/__init__.py
+++ b/addons/website/models/__init__.py
@@ -12,3 +12,4 @@ from . import res_partner
from . import res_config_settings
from . import ir_model_fields
from . import ir_model
+from . import res_users
diff --git a/addons/website/models/res_users.py b/addons/website/models/res_users.py
new file mode 100644
index 00000000000..bab898d24c8
--- /dev/null
+++ b/addons/website/models/res_users.py
@@ -0,0 +1,13 @@
+# -*- 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_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')
diff --git a/addons/website/views/res_users_views.xml b/addons/website/views/res_users_views.xml
new file mode 100644
index 00000000000..80dfc371994
--- /dev/null
+++ b/addons/website/views/res_users_views.xml
@@ -0,0 +1,14 @@
+
+
+
+ res.users.form.website.inherit
+ res.users
+
+ 135
+
+
+
+
+
+
+
diff --git a/addons/website_forum/__manifest__.py b/addons/website_forum/__manifest__.py
index a34afed27f9..d27dd595bce 100644
--- a/addons/website_forum/__manifest__.py
+++ b/addons/website_forum/__manifest__.py
@@ -20,7 +20,7 @@ Ask questions, get answers, no distractions
'data': [
'data/forum_data.xml',
'views/forum.xml',
- 'views/res_users.xml',
+ 'views/res_users_views.xml',
'views/website_forum.xml',
'views/ir_qweb.xml',
'security/ir.model.access.csv',
diff --git a/addons/website_forum/views/res_users.xml b/addons/website_forum/views/res_users_views.xml
similarity index 100%
rename from addons/website_forum/views/res_users.xml
rename to addons/website_forum/views/res_users_views.xml
diff --git a/addons/website_mass_mailing/__manifest__.py b/addons/website_mass_mailing/__manifest__.py
index ac3d34c0e1c..2b8f63808ff 100644
--- a/addons/website_mass_mailing/__manifest__.py
+++ b/addons/website_mass_mailing/__manifest__.py
@@ -16,6 +16,7 @@ Add a snippet in the website builder to subscribe a mass_mailing list
'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 c9e39e466ca..d70758fc817 100644
--- a/addons/website_mass_mailing/models/__init__.py
+++ b/addons/website_mass_mailing/models/__init__.py
@@ -3,3 +3,4 @@
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
new file mode 100644
index 00000000000..6024234dfb9
--- /dev/null
+++ b/addons/website_mass_mailing/models/res_users.py
@@ -0,0 +1,13 @@
+# -*- 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 1f1438fcab3..95c9d4d56bb 100644
--- a/addons/website_mass_mailing/security/mass_mailing_security.xml
+++ b/addons/website_mass_mailing/security/mass_mailing_security.xml
@@ -2,6 +2,5 @@
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
new file mode 100644
index 00000000000..dc0b20a84f0
--- /dev/null
+++ b/addons/website_mass_mailing/views/res_users_views.xml
@@ -0,0 +1,16 @@
+
+
+
+ res.users.form.website_mass_mailing.inherit
+ res.users
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/website_theme_install/models/ir_module_module.py b/addons/website_theme_install/models/ir_module_module.py
index 0ceea66f159..5350abea437 100644
--- a/addons/website_theme_install/models/ir_module_module.py
+++ b/addons/website_theme_install/models/ir_module_module.py
@@ -43,11 +43,10 @@ class IrModuleModule(models.Model):
@api.multi
def button_choose_theme(self):
theme_category = self.env.ref('base.module_category_theme', False)
- hidden_category = self.env.ref('base.module_category_hidden', False)
theme_hidden_category = self.env.ref('base.module_category_theme_hidden', False)
theme_category_id = theme_category.id if theme_category else 0
- hidden_categories_ids = [hidden_category.id if hidden_category else 0, theme_hidden_category.id if theme_hidden_category else 0]
+ hidden_categories_ids = [theme_hidden_category.id if theme_hidden_category else 0]
self.search([ # Uninstall the theme(s) which is (are) installed
('state', '=', 'installed'),
diff --git a/addons/website_theme_install/views/views.xml b/addons/website_theme_install/views/views.xml
index ed9b393ddf6..f080ee96005 100644
--- a/addons/website_theme_install/views/views.xml
+++ b/addons/website_theme_install/views/views.xml
@@ -67,7 +67,7 @@
diff --git a/odoo/addons/base/data/ir_module_category_data.xml b/odoo/addons/base/data/ir_module_category_data.xml
index 9eb3c218280..aaf28c0bb86 100644
--- a/odoo/addons/base/data/ir_module_category_data.xml
+++ b/odoo/addons/base/data/ir_module_category_data.xml
@@ -1,12 +1,6 @@
-
- Technical Settings
- 0
-
-
-
Localization
@@ -66,9 +60,9 @@
8
-
- Employees
- Helps you manage your employees.
+
+ User types
+ Helps you manage users.9
@@ -166,16 +160,6 @@
100
-
- Extra Rights
- 101
-
-
-
- Other Extra Rights
- 102
-
-
@@ -185,31 +169,15 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
+
diff --git a/odoo/addons/base/data/res_users_demo.xml b/odoo/addons/base/data/res_users_demo.xml
index aa632b5bac8..d5977f63173 100644
--- a/odoo/addons/base/data/res_users_demo.xml
+++ b/odoo/addons/base/data/res_users_demo.xml
@@ -72,6 +72,9 @@
+
+
+
diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py
index efcf203edf0..8664e70c556 100644
--- a/odoo/addons/base/models/res_users.py
+++ b/odoo/addons/base/models/res_users.py
@@ -26,48 +26,23 @@ USER_PRIVATE_FIELDS = ['password']
concat = chain.from_iterable
#
-# Functions for manipulating boolean and selection pseudo-fields
+# Functions for manipulating boolean and selection 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('in_group_')
+ return name.startswith('has_group_')
+
def is_selection_groups(name):
- return name.startswith('sel_groups_')
+ return name.startswith('group_')
-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"
@@ -230,6 +205,32 @@ 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')
+
_sql_constraints = [
('login_key', 'UNIQUE (login)', 'You can not have two users with the same login !')
]
@@ -330,6 +331,7 @@ class Users(models.Model):
@api.model
def create(self, vals):
+ # import pudb; pudb.set_trace()
user = super(Users, self).create(vals)
user.partner_id.active = user.active
if user.partner_id.company_id:
@@ -624,6 +626,112 @@ class Users(models.Model):
def get_company_currency_id(self):
return self.env.user.company_id.currency_id.id
+ 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})
+
+
#
# Implied groups
#
@@ -660,6 +768,7 @@ 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:
@@ -691,267 +800,6 @@ class UsersImplied(models.Model):
super(UsersImplied, self).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')
- xml1, xml2 = [], []
- xml1.append(E.separator(string=_('Application Accesses'), colspan="2"))
- for app, kind, gs in self.get_groups_by_application():
- # hide groups in categories 'Hidden' and 'Extra' (except for group_no_one)
- attrs = {}
- if app.xml_id in ('base.module_category_hidden', 'base.module_category_extra', 'base.module_category_usability'):
- attrs['groups'] = 'base.group_no_one'
-
- if kind == 'selection':
- # application name with a selection field
- field_name = name_selection_groups(gs.ids)
- xml1.append(E.field(name=field_name, **attrs))
- xml1.append(E.newline())
- else:
- # application separator with boolean fields
- app_name = app.name or _('Other')
- xml2.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
- xml2.append(E.field(name=field_name, invisible="1", **attrs))
- else:
- xml2.append(E.field(name=field_name, **attrs))
-
- xml2.append({'class': "o_label_nowrap"})
- xml = E.field(E.group(*(xml1), col="2"), E.group(*(xml2), col="4"), 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):
- # 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':
- # selection group field
- tips = ['%s: %s' % (g.name, g.comment) for g in gs if g.comment]
- res[name_selection_groups(gs.ids)] = {
- 'type': 'selection',
- 'string': app.name or _('Other'),
- 'selection': [(False, '')] + [(g.id, g.name) for g in gs],
- 'help': '\n'.join(tips),
- 'exportable': False,
- 'selectable': False,
- }
- else:
- # boolean group fields
- for g in gs:
- res[name_boolean_group(g.id)] = {
- 'type': 'boolean',
- 'string': g.name,
- 'help': g.comment,
- 'exportable': False,
- 'selectable': False,
- }
- return res
#----------------------------------------------------------
# change password wizard
diff --git a/odoo/addons/base/security/base_groups.xml b/odoo/addons/base/security/base_groups.xml
index c2e13857358..863046f9c4e 100644
--- a/odoo/addons/base/security/base_groups.xml
+++ b/odoo/addons/base/security/base_groups.xml
@@ -18,7 +18,7 @@
- Employee
+ Internal User
diff --git a/odoo/addons/base/views/res_users_views.xml b/odoo/addons/base/views/res_users_views.xml
index 7cd872c966d..db9181ac2bf 100644
--- a/odoo/addons/base/views/res_users_views.xml
+++ b/odoo/addons/base/views/res_users_views.xml
@@ -141,11 +141,6 @@
-
-
- You will be able to define additional access rights by editing the newly created user under the Settings / Users menu.
-