diff --git a/base_user_role_profile/README.rst b/base_user_role_profile/README.rst new file mode 100644 index 000000000..b5802b794 --- /dev/null +++ b/base_user_role_profile/README.rst @@ -0,0 +1,138 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +============= +User profiles +============= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:1d979633c755172c0348a066f1d5484a57f3015750cba27e3784b645a9e9a59d + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--backend-lightgray.png?logo=github + :target: https://github.com/OCA/server-backend/tree/19.0/base_user_role_profile + :alt: OCA/server-backend +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-backend-19-0/server-backend-19-0-base_user_role_profile + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/server-backend&target_branch=19.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Extending the base_user_role module, this one adds the notion of +profiles. Effectively profiles act as an additional filter to how the +roles are used. + +This allows users to switch their permission groups dynamically. This +can be useful for example to: + +- finer grain control on menu and model permissions (with record rules + this becomes very flexible) +- break down complicated menus into simpler ones +- easily restrict users accidentally editing or creating records in O2M + fields and in general misusing the interface, instead of excessively + explaining things to them + +When you define a role, you have the possibility to link it to a +profile. Roles are applied to users in the following way: + +- Apply user's roles without profiles in any case +- Apply user's roles that are linked to the currently selected profile + +In addition you can: + +- Add a 'no profile' profile to the user's choice of profile, to allow + him to select a specific profile which enables only the roles without + a profile. +- Restrict the user to change its profile, which can be useful in a + security emergency. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To configure this module, you need to go to *Configuration / Users / +Profiles*, and create a new profile. + +Then go to *Configuration / Users / Roles* and set some roles with a +profile. + +Then go to *Configuration / Users / Users* and add some roles if not +already done. The allowed profiles will computes automatically. + +In addition you can: + +- Check the "Include Default Profile" box to have the default profile + as an allowed profile. +- Change the current profile if needed. +- Check the "Restrict Profile Switching" to restrict the user to change + it's profile, in that case you can still change it from the user's + settings form. + +Usage +===== + +Once you have set up at least one profile for a user, use the widget in +the top bar to switch user profiles. Note that it is possible to use no +profile; in this case, the user will only get the roles that always +apply (i.e the ones with no profile_id). + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Akretion + +Contributors +------------ + +- Kevin Khao +- Sébastien Beau +- Olivier Nibart +- Florian Mounier + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/server-backend `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/base_user_role_profile/__init__.py b/base_user_role_profile/__init__.py new file mode 100644 index 000000000..cc6b6354a --- /dev/null +++ b/base_user_role_profile/__init__.py @@ -0,0 +1,2 @@ +from . import models +from .hooks import post_init_hook diff --git a/base_user_role_profile/__manifest__.py b/base_user_role_profile/__manifest__.py new file mode 100644 index 000000000..449aba4ef --- /dev/null +++ b/base_user_role_profile/__manifest__.py @@ -0,0 +1,25 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "User profiles", + "version": "19.0.1.0.0", + "category": "Tools", + "author": "Akretion, Odoo Community Association (OCA)", + "license": "AGPL-3", + "website": "https://github.com/OCA/server-backend", + "depends": ["base_user_role", "web"], + "post_init_hook": "post_init_hook", + "data": [ + "data/res_users_profile_data.xml", + "security/ir.model.access.csv", + "views/res_users_views.xml", + "views/res_users_profile_views.xml", + "views/res_users_role_views.xml", + ], + "assets": { + "web.assets_backend": [ + "base_user_role_profile/static/src/**/*", + ], + }, + "installable": True, +} diff --git a/base_user_role_profile/data/res_users_profile_data.xml b/base_user_role_profile/data/res_users_profile_data.xml new file mode 100644 index 000000000..ea7778aaf --- /dev/null +++ b/base_user_role_profile/data/res_users_profile_data.xml @@ -0,0 +1,6 @@ + + + + No profile + + diff --git a/base_user_role_profile/hooks.py b/base_user_role_profile/hooks.py new file mode 100644 index 000000000..284dcfed5 --- /dev/null +++ b/base_user_role_profile/hooks.py @@ -0,0 +1,9 @@ +# Copyright 2020 Akretion (https://www.akretion.com). +# @author Pierrick Brun +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + + +def post_init_hook(env): + env["res.users"].search([("profile_id", "=", False)]).profile_id = env.ref( + "base_user_role_profile.default_profile" + ) diff --git a/base_user_role_profile/i18n/base_user_role_profile.pot b/base_user_role_profile/i18n/base_user_role_profile.pot new file mode 100644 index 000000000..93b595b33 --- /dev/null +++ b/base_user_role_profile/i18n/base_user_role_profile.pot @@ -0,0 +1,137 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * base_user_role_profile +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__user_ids +msgid "Allowed users" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__create_uid +msgid "Created by" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__create_date +msgid "Created on" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users__profile_id +msgid "Current profile" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users__profile_ids +msgid "Currently allowed profiles" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__display_name +msgid "Display Name" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model,name:base_user_role_profile.model_ir_http +msgid "HTTP Routing" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__id +msgid "ID" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,help:base_user_role_profile.field_res_users__include_default_profile +msgid "" +"If enabled, the default profile ('no profile') will be added to the user's " +"allowed profiles. This allows the user to select a profile that activates " +"only the roles not associated with any specific profile." +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,help:base_user_role_profile.field_res_users__restrict_profile_switching +msgid "" +"If enabled, the user will be prevented from changing their profile. This " +"acts as a security measure to lock users into their current profile." +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users__include_default_profile +msgid "Include Default Profile" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__write_date +msgid "Last Updated on" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__name +msgid "Name" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role__profile_id +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role_line__profile_id +msgid "Profile" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users__restrict_profile_switching +msgid "Restrict Profile Switching" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model,name:base_user_role_profile.model_res_users_profile +msgid "Role profile" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__role_ids +msgid "Roles" +msgstr "" + +#. module: base_user_role_profile +#. odoo-javascript +#: code:addons/base_user_role_profile/static/src/components/switch_profile_menu/switch_profile_menu.esm.js:0 +msgid "Switch Profile" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model,name:base_user_role_profile.model_res_users +msgid "User" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.actions.act_window,name:base_user_role_profile.action_res_users_profile_tree +#: model:ir.ui.menu,name:base_user_role_profile.menu_action_res_users_profile_tree +msgid "User Profiles" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model,name:base_user_role_profile.model_res_users_role +msgid "User Role" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model,name:base_user_role_profile.model_res_users_role_line +msgid "Users associated to a role" +msgstr "" diff --git a/base_user_role_profile/i18n/es.po b/base_user_role_profile/i18n/es.po new file mode 100644 index 000000000..98c9317da --- /dev/null +++ b/base_user_role_profile/i18n/es.po @@ -0,0 +1,126 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * base_user_role_profile +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-04-18 19:34+0000\n" +"Last-Translator: Ivorra78 \n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__user_ids +msgid "Allowed users" +msgstr "Usuarios permitidos" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users__profile_id +msgid "Current profile" +msgstr "Perfil actual" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users__profile_ids +msgid "Currently allowed profiles" +msgstr "Perfiles permitidos actualmente" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_ir_http__display_name +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users__display_name +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__display_name +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role__display_name +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role_line__display_name +msgid "Display Name" +msgstr "Mostrar Nombre" + +#. module: base_user_role_profile +#: model:ir.model,name:base_user_role_profile.model_ir_http +msgid "HTTP Routing" +msgstr "Ruta HTTP" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_ir_http__id +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users__id +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__id +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role__id +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role_line__id +msgid "ID" +msgstr "ID" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_ir_http____last_update +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users____last_update +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile____last_update +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role____last_update +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role_line____last_update +msgid "Last Modified on" +msgstr "Última Modificación el" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__write_uid +msgid "Last Updated by" +msgstr "Última Actualización por" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__write_date +msgid "Last Updated on" +msgstr "Última Actualización el" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__name +msgid "Name" +msgstr "Nombre" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role__profile_id +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role_line__profile_id +msgid "Profile" +msgstr "Perfil" + +#. module: base_user_role_profile +#: model:ir.model,name:base_user_role_profile.model_res_users_profile +msgid "Role profile" +msgstr "Perfil de la Función" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__role_ids +msgid "Roles" +msgstr "Funciones" + +#. module: base_user_role_profile +#: model:ir.actions.act_window,name:base_user_role_profile.action_res_users_profile_tree +#: model:ir.ui.menu,name:base_user_role_profile.menu_action_res_users_profile_tree +msgid "User Profiles" +msgstr "Perfiles de Usuario" + +#. module: base_user_role_profile +#: model:ir.model,name:base_user_role_profile.model_res_users_role +msgid "User role" +msgstr "Rol de usuario" + +#. module: base_user_role_profile +#: model:ir.model,name:base_user_role_profile.model_res_users +msgid "Users" +msgstr "Usuarios" + +#. module: base_user_role_profile +#: model:ir.model,name:base_user_role_profile.model_res_users_role_line +msgid "Users associated to a role" +msgstr "Usuarios asociados a un papel" diff --git a/base_user_role_profile/i18n/fr.po b/base_user_role_profile/i18n/fr.po new file mode 100644 index 000000000..c517bcc6d --- /dev/null +++ b/base_user_role_profile/i18n/fr.po @@ -0,0 +1,126 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * base_user_role_profile +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-07-17 11:08+0000\n" +"Last-Translator: Yann Papouin \n" +"Language-Team: none\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__user_ids +msgid "Allowed users" +msgstr "Utilisateurs autorisés" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__create_uid +msgid "Created by" +msgstr "Créer par" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__create_date +msgid "Created on" +msgstr "Créer le" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users__profile_id +msgid "Current profile" +msgstr "Profil actuel" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users__profile_ids +msgid "Currently allowed profiles" +msgstr "Profils actuellement autorisés" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_ir_http__display_name +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users__display_name +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__display_name +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role__display_name +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role_line__display_name +msgid "Display Name" +msgstr "Nom affiché" + +#. module: base_user_role_profile +#: model:ir.model,name:base_user_role_profile.model_ir_http +msgid "HTTP Routing" +msgstr "Routage HTTP" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_ir_http__id +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users__id +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__id +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role__id +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role_line__id +msgid "ID" +msgstr "ID" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_ir_http____last_update +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users____last_update +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile____last_update +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role____last_update +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role_line____last_update +msgid "Last Modified on" +msgstr "Dernière modification le" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__write_uid +msgid "Last Updated by" +msgstr "Mis à jour par" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__write_date +msgid "Last Updated on" +msgstr "Mis à jour le" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__name +msgid "Name" +msgstr "Nom" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role__profile_id +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role_line__profile_id +msgid "Profile" +msgstr "Profil" + +#. module: base_user_role_profile +#: model:ir.model,name:base_user_role_profile.model_res_users_profile +msgid "Role profile" +msgstr "Profil de rôle" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__role_ids +msgid "Roles" +msgstr "Rôles" + +#. module: base_user_role_profile +#: model:ir.actions.act_window,name:base_user_role_profile.action_res_users_profile_tree +#: model:ir.ui.menu,name:base_user_role_profile.menu_action_res_users_profile_tree +msgid "User Profiles" +msgstr "Profils utilisateur" + +#. module: base_user_role_profile +#: model:ir.model,name:base_user_role_profile.model_res_users_role +msgid "User role" +msgstr "Rôle utilisateur" + +#. module: base_user_role_profile +#: model:ir.model,name:base_user_role_profile.model_res_users +msgid "Users" +msgstr "Utilisateurs" + +#. module: base_user_role_profile +#: model:ir.model,name:base_user_role_profile.model_res_users_role_line +msgid "Users associated to a role" +msgstr "Utilisateurs associés à un rôle" diff --git a/base_user_role_profile/i18n/it.po b/base_user_role_profile/i18n/it.po new file mode 100644 index 000000000..259725238 --- /dev/null +++ b/base_user_role_profile/i18n/it.po @@ -0,0 +1,124 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * base_user_role_profile +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__user_ids +msgid "Allowed users" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__create_uid +msgid "Created by" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__create_date +msgid "Created on" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users__profile_id +msgid "Current profile" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users__profile_ids +msgid "Currently allowed profiles" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_ir_http__display_name +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users__display_name +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__display_name +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role__display_name +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role_line__display_name +msgid "Display Name" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model,name:base_user_role_profile.model_ir_http +msgid "HTTP Routing" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_ir_http__id +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users__id +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__id +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role__id +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role_line__id +msgid "ID" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_ir_http____last_update +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users____last_update +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile____last_update +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role____last_update +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role_line____last_update +msgid "Last Modified on" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__write_date +msgid "Last Updated on" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__name +msgid "Name" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role__profile_id +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_role_line__profile_id +msgid "Profile" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model,name:base_user_role_profile.model_res_users_profile +msgid "Role profile" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model.fields,field_description:base_user_role_profile.field_res_users_profile__role_ids +msgid "Roles" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.actions.act_window,name:base_user_role_profile.action_res_users_profile_tree +#: model:ir.ui.menu,name:base_user_role_profile.menu_action_res_users_profile_tree +msgid "User Profiles" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model,name:base_user_role_profile.model_res_users_role +msgid "User role" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model,name:base_user_role_profile.model_res_users +msgid "Users" +msgstr "" + +#. module: base_user_role_profile +#: model:ir.model,name:base_user_role_profile.model_res_users_role_line +msgid "Users associated to a role" +msgstr "" diff --git a/base_user_role_profile/models/__init__.py b/base_user_role_profile/models/__init__.py new file mode 100644 index 000000000..10c42ea9c --- /dev/null +++ b/base_user_role_profile/models/__init__.py @@ -0,0 +1,5 @@ +from . import ir_http +from . import res_users +from . import res_users_profile +from . import res_users_role +from . import res_users_role_line diff --git a/base_user_role_profile/models/ir_http.py b/base_user_role_profile/models/ir_http.py new file mode 100644 index 000000000..2bd2f5882 --- /dev/null +++ b/base_user_role_profile/models/ir_http.py @@ -0,0 +1,26 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import models +from odoo.http import request + + +class Http(models.AbstractModel): + _inherit = "ir.http" + + def session_info(self): # pragma: no cover + result = super().session_info() + user = request.env.user + allowed_profiles = ( + [] + if user.restrict_profile_switching + else [(profile.id, profile.name) for profile in user.profile_ids] + ) + if len(allowed_profiles) > 1: + current_profile = (user.profile_id.id, user.profile_id.name) + result["user_profiles"] = { + "current_profile": current_profile, + "allowed_profiles": allowed_profiles, + } + else: + result["user_profiles"] = False + result["profile_id"] = user.profile_id.id if request.session.uid else None + return result diff --git a/base_user_role_profile/models/res_users.py b/base_user_role_profile/models/res_users.py new file mode 100644 index 000000000..70fd6e73a --- /dev/null +++ b/base_user_role_profile/models/res_users.py @@ -0,0 +1,96 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import api, fields, models + + +class ResUsers(models.Model): + _inherit = "res.users" + + def _get_default_profile(self): + return self.env.ref( + "base_user_role_profile.default_profile", raise_if_not_found=False + ) + + profile_id = fields.Many2one( + "res.users.profile", + "Current profile", + default=lambda self: self._get_default_profile(), + ) + + profile_ids = fields.Many2many( + "res.users.profile", + string="Currently allowed profiles", + ) + + restrict_profile_switching = fields.Boolean( + help="If enabled, the user will be prevented from changing their profile. " + "This acts as a security measure to " + "lock users into their current profile.", + ) + + include_default_profile = fields.Boolean( + help="If enabled, the default profile ('no profile') will be added to " + "the user's allowed profiles. " + "This allows the user to select a profile that activates only the roles " + "not associated with any specific profile.", + ) + + def _get_action_root_menu(self): + # used JS-side. Reload the client; open the first available root menu + menu = self.env["ir.ui.menu"].search([("parent_id", "=", False)])[:1] + return { + "type": "ir.actions.client", + "tag": "reload", + "params": {"menu_id": menu.id}, + } + + def action_profile_change(self, vals): + if not self.restrict_profile_switching: + self.write(vals) + return self._get_action_root_menu() + + @api.model_create_multi + def create(self, vals_list): + new_records = super().create(vals_list) + for new_record, vals in zip(new_records, vals_list, strict=False): + if vals.get("role_line_ids"): + new_record.sudo()._compute_profile_ids() + return new_records + + def write(self, vals): + if not self.env.su and vals.get("profile_id"): + self.sudo().write({"profile_id": vals["profile_id"]}) + del vals["profile_id"] + res = super().write(vals) + if ( + "profile_id" in vals + or "role_line_ids" in vals + or "include_default_profile" in vals + ): + self.sudo()._compute_profile_ids() + return res + + def _get_enabled_roles(self): + res = super()._get_enabled_roles() + res = res.filtered( + lambda r: not r.profile_id or (r.profile_id.id == r.user_id.profile_id.id) + ) + return res + + def _update_profile_id(self): + default_profile = self._get_default_profile() + if not self.profile_ids: + if self.profile_id != default_profile: + self.profile_id = default_profile + elif self.profile_id not in self.profile_ids: + self.write({"profile_id": self.profile_ids[0].id}) + + def _compute_profile_ids(self): + default_profile = self._get_default_profile() + for rec in self: + role_lines = rec.role_line_ids + profiles = role_lines.mapped("profile_id") + if rec.include_default_profile: + profiles = default_profile + profiles + rec.profile_ids = profiles + # set defaults in case applicable profile changes + rec._update_profile_id() diff --git a/base_user_role_profile/models/res_users_profile.py b/base_user_role_profile/models/res_users_profile.py new file mode 100644 index 000000000..4d2438031 --- /dev/null +++ b/base_user_role_profile/models/res_users_profile.py @@ -0,0 +1,26 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResUsersProfile(models.Model): + _name = "res.users.profile" + _description = "Role profile" + + name = fields.Char() + user_ids = fields.Many2many( + "res.users", + string="Allowed users", + compute="_compute_user_ids", + ) + role_ids = fields.One2many( + "res.users.role", + "profile_id", + string="Roles", + ) + + def _compute_user_ids(self): + for rec in self: + rec.user_ids = self.env["res.users"].search( + [("profile_ids", "in", rec.ids)] + ) diff --git a/base_user_role_profile/models/res_users_role.py b/base_user_role_profile/models/res_users_role.py new file mode 100644 index 000000000..27bbb718e --- /dev/null +++ b/base_user_role_profile/models/res_users_role.py @@ -0,0 +1,15 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class ResUsersRole(models.Model): + _inherit = "res.users.role" + + profile_id = fields.Many2one("res.users.profile", "Profile") + + def write(self, vals): + res = super().write(vals) + if "profile_id" in vals: + # update the user's allowed profiles + self.user_ids._compute_profile_ids() + return res diff --git a/base_user_role_profile/models/res_users_role_line.py b/base_user_role_profile/models/res_users_role_line.py new file mode 100644 index 000000000..7468f4432 --- /dev/null +++ b/base_user_role_profile/models/res_users_role_line.py @@ -0,0 +1,8 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class ResUsersRoleLine(models.Model): + _inherit = "res.users.role.line" + + profile_id = fields.Many2one(related="role_id.profile_id") diff --git a/base_user_role_profile/pyproject.toml b/base_user_role_profile/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/base_user_role_profile/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/base_user_role_profile/readme/CONFIGURE.md b/base_user_role_profile/readme/CONFIGURE.md new file mode 100644 index 000000000..290b8286d --- /dev/null +++ b/base_user_role_profile/readme/CONFIGURE.md @@ -0,0 +1,11 @@ +To configure this module, you need to go to *Configuration / Users / Profiles*, +and create a new profile. + +Then go to *Configuration / Users / Roles* and set some roles with a profile. + +Then go to *Configuration / Users / Users* and add some roles if not already done. The allowed profiles will computes automatically. + +In addition you can: +- Check the "Include Default Profile" box to have the default profile as an allowed profile. +- Change the current profile if needed. +- Check the "Restrict Profile Switching" to restrict the user to change it's profile, in that case you can still change it from the user's settings form. diff --git a/base_user_role_profile/readme/CONTRIBUTORS.md b/base_user_role_profile/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..b95321c82 --- /dev/null +++ b/base_user_role_profile/readme/CONTRIBUTORS.md @@ -0,0 +1,4 @@ +- Kevin Khao \ +- Sébastien Beau \ +- Olivier Nibart \ +- Florian Mounier \ diff --git a/base_user_role_profile/readme/DESCRIPTION.md b/base_user_role_profile/readme/DESCRIPTION.md new file mode 100644 index 000000000..955b43b3a --- /dev/null +++ b/base_user_role_profile/readme/DESCRIPTION.md @@ -0,0 +1,19 @@ +Extending the base_user_role module, this one adds the notion of +profiles. Effectively profiles act as an additional filter to how the +roles are used. + +This allows users to switch their permission groups dynamically. This can be useful for example to: +- finer grain control on menu and model permissions (with record rules + this becomes very flexible) +- break down complicated menus into simpler ones +- easily restrict users accidentally editing or creating records in O2M + fields and in general misusing the interface, instead of excessively + explaining things to them + +When you define a role, you have the possibility to link it to a profile. Roles are applied to users in the following way: +- Apply user's roles without profiles in any case +- Apply user's roles that are linked to the currently selected profile + +In addition you can: +- Add a 'no profile' profile to the user's choice of profile, to allow him to select a specific profile which enables only the roles without a profile. +- Restrict the user to change its profile, which can be useful in a security emergency. diff --git a/base_user_role_profile/readme/USAGE.md b/base_user_role_profile/readme/USAGE.md new file mode 100644 index 000000000..accb09a42 --- /dev/null +++ b/base_user_role_profile/readme/USAGE.md @@ -0,0 +1,4 @@ +Once you have set up at least one profile for a user, use the widget in +the top bar to switch user profiles. Note that it is possible to use no +profile; in this case, the user will only get the roles that always +apply (i.e the ones with no profile_id). diff --git a/base_user_role_profile/security/ir.model.access.csv b/base_user_role_profile/security/ir.model.access.csv new file mode 100644 index 000000000..2ce7b8a08 --- /dev/null +++ b/base_user_role_profile/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_res_users_profile,access_res_users_profile,model_res_users_profile,base.group_user,1,0,0,0 +access_res_users_profile_managers,access_res_managers_role,model_res_users_profile,base.group_erp_manager,1,1,1,1 +access_res_users_role_line_users,access_res_users_role_line,model_res_users_role_line,base.group_user,1,0,0,0 diff --git a/base_user_role_profile/static/description/icon.png b/base_user_role_profile/static/description/icon.png new file mode 100644 index 000000000..4a8a6d74e Binary files /dev/null and b/base_user_role_profile/static/description/icon.png differ diff --git a/base_user_role_profile/static/description/index.html b/base_user_role_profile/static/description/index.html new file mode 100644 index 000000000..2bbe93c2d --- /dev/null +++ b/base_user_role_profile/static/description/index.html @@ -0,0 +1,485 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

User profiles

+ +

Beta License: AGPL-3 OCA/server-backend Translate me on Weblate Try me on Runboat

+

Extending the base_user_role module, this one adds the notion of +profiles. Effectively profiles act as an additional filter to how the +roles are used.

+

This allows users to switch their permission groups dynamically. This +can be useful for example to:

+
    +
  • finer grain control on menu and model permissions (with record rules +this becomes very flexible)
  • +
  • break down complicated menus into simpler ones
  • +
  • easily restrict users accidentally editing or creating records in O2M +fields and in general misusing the interface, instead of excessively +explaining things to them
  • +
+

When you define a role, you have the possibility to link it to a +profile. Roles are applied to users in the following way:

+
    +
  • Apply user’s roles without profiles in any case
  • +
  • Apply user’s roles that are linked to the currently selected profile
  • +
+

In addition you can:

+
    +
  • Add a ‘no profile’ profile to the user’s choice of profile, to allow +him to select a specific profile which enables only the roles without +a profile.
  • +
  • Restrict the user to change its profile, which can be useful in a +security emergency.
  • +
+

Table of contents

+ +
+

Configuration

+

To configure this module, you need to go to Configuration / Users / +Profiles, and create a new profile.

+

Then go to Configuration / Users / Roles and set some roles with a +profile.

+

Then go to Configuration / Users / Users and add some roles if not +already done. The allowed profiles will computes automatically.

+

In addition you can:

+
    +
  • Check the “Include Default Profile” box to have the default profile +as an allowed profile.
  • +
  • Change the current profile if needed.
  • +
  • Check the “Restrict Profile Switching” to restrict the user to change +it’s profile, in that case you can still change it from the user’s +settings form.
  • +
+
+
+

Usage

+

Once you have set up at least one profile for a user, use the widget in +the top bar to switch user profiles. Note that it is possible to use no +profile; in this case, the user will only get the roles that always +apply (i.e the ones with no profile_id).

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Akretion
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/server-backend project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+
+ + diff --git a/base_user_role_profile/static/src/components/switch_profile_menu/switch_profile_menu.esm.js b/base_user_role_profile/static/src/components/switch_profile_menu/switch_profile_menu.esm.js new file mode 100644 index 000000000..256f33150 --- /dev/null +++ b/base_user_role_profile/static/src/components/switch_profile_menu/switch_profile_menu.esm.js @@ -0,0 +1,96 @@ +// Copyright 2025 Akretion (http://www.akretion.com). +// @author Florian Mounier +// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import {useChildRef, useService} from "@web/core/utils/hooks"; + +import {Component} from "@odoo/owl"; +import {Dropdown} from "@web/core/dropdown/dropdown"; +import {DropdownGroup} from "@web/core/dropdown/dropdown_group"; +import {DropdownItem} from "@web/core/dropdown/dropdown_item"; +import {_t} from "@web/core/l10n/translation"; +import {registry} from "@web/core/registry"; +import {session} from "@web/session"; +import {useCommand} from "@web/core/commands/command_hook"; +import {useDropdownState} from "@web/core/dropdown/dropdown_hooks"; +import {user} from "@web/core/user"; + +export class SwitchProfileMenu extends Component { + static components = {Dropdown, DropdownItem, DropdownGroup}; + static props = []; + static template = "base_user_role_profile.switchProfileMenu"; + + setup() { + this.orm = useService("orm"); + this.action = useService("action"); + this.dropdown = useDropdownState(); + + useCommand(_t("Switch Profile"), () => this.dropdown.open(), { + hotkey: "alt+shift+p", + }); + + this.containerRef = useChildRef(); + this.navigationOptions = { + hotkeys: { + space: (index, items) => { + if (!items[index]) { + return; + } + if (items[index].el.classList.contains("o_switch_profile_item")) { + const profileId = parseInt( + items[index].el.dataset.profileId, + 10 + ); + this.switchToProfile(profileId); + this.dropdown.close(); + } + }, + enter: (index, items) => { + if (!items[index]) { + return; + } + if (items[index].el.classList.contains("o_switch_profile_item")) { + const profileId = parseInt( + items[index].el.dataset.profileId, + 10 + ); + this.switchToProfile(profileId); + this.dropdown.close(); + } + }, + }, + }; + } + + get currentProfile() { + return session.user_profiles?.current_profile; + } + + get profiles() { + return session.user_profiles?.allowed_profiles || []; + } + + get isEnabled() { + return this.profiles.length > 1; + } + + async switchToProfile(profileId) { + if (profileId === this.currentProfile) { + return; + } + const action = await this.orm.call("res.users", "action_profile_change", [ + user.userId, + {profile_id: profileId}, + ]); + if (action) { + this.action.doAction(action); + } + } +} + +export const systrayItem = { + Component: SwitchProfileMenu, +}; + +registry + .category("systray") + .add("base_user_role_profile.switchProfileMenu", systrayItem, {sequence: 5}); diff --git a/base_user_role_profile/static/src/components/switch_profile_menu/switch_profile_menu.scss b/base_user_role_profile/static/src/components/switch_profile_menu/switch_profile_menu.scss new file mode 100644 index 000000000..5d0c00c5d --- /dev/null +++ b/base_user_role_profile/static/src/components/switch_profile_menu/switch_profile_menu.scss @@ -0,0 +1,33 @@ +// Copyright 2025 Akretion (http://www.akretion.com). +// @author Florian Mounier +// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +.o_switch_profile_menu_dropdown { + min-width: 20rem; +} + +.o_switch_profile_menu_items { + max-height: 20rem; + overflow-x: auto; + + .o_switch_profile_item { + height: 33px; + + [role="button"] { + min-width: 0; + } + + &.focus, + &.focus > * { + background-color: var(--gray-200); + } + + .disabled { + cursor: default; + } + } +} + +.o_switch_profile_menu .oe_topbar_name { + max-width: 15rem; +} diff --git a/base_user_role_profile/static/src/components/switch_profile_menu/switch_profile_menu.xml b/base_user_role_profile/static/src/components/switch_profile_menu/switch_profile_menu.xml new file mode 100644 index 000000000..79e597829 --- /dev/null +++ b/base_user_role_profile/static/src/components/switch_profile_menu/switch_profile_menu.xml @@ -0,0 +1,70 @@ + + + + + +
+ + + + + + + + + +
+
+ + +
+ + + +
+ + + + + +
+
+
+
+
+ +
diff --git a/base_user_role_profile/tests/__init__.py b/base_user_role_profile/tests/__init__.py new file mode 100644 index 000000000..12f8746a0 --- /dev/null +++ b/base_user_role_profile/tests/__init__.py @@ -0,0 +1 @@ +from . import test_user_role_profile diff --git a/base_user_role_profile/tests/test_user_role_profile.py b/base_user_role_profile/tests/test_user_role_profile.py new file mode 100644 index 000000000..e0b6e4c72 --- /dev/null +++ b/base_user_role_profile/tests/test_user_role_profile.py @@ -0,0 +1,216 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields +from odoo.tests.common import TransactionCase, new_test_user + + +class TestUserProfile(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.role_model = cls.env["res.users.role"] + + cls.user_id = new_test_user( + cls.env, login="user_test_roles", name="USER TEST (ROLES)" + ) + + cls.profile1_id = cls.env["res.users.profile"].create({"name": "profile1"}) + cls.profile2_id = cls.env["res.users.profile"].create({"name": "profile2"}) + + # role 1 + cls.group_user_id = cls.env.ref("base.group_user") + cls.group_no_one_id = cls.env.ref("base.group_no_one") + + # role 2 + cls.group_system_id = cls.env.ref("base.group_system") + cls.group_multi_company_id = cls.env.ref("base.group_multi_company") + + # role 3 + cls.group_erp_manager_id = cls.env.ref("base.group_erp_manager") + cls.group_partner_manager_id = cls.env.ref("base.group_partner_manager") + + # roles 1 and 2 have a profile, role 3 no profile + vals = { + "name": "ROLE_1", + "implied_ids": [ + fields.Command.set([cls.group_user_id.id, cls.group_no_one_id.id]) + ], + "profile_id": cls.profile1_id.id, + } + cls.role1_id = cls.role_model.create(vals) + cls.role1_group_ids = cls._helper_unpack_groups_role(cls.role1_id) + + vals = { + "name": "ROLE_2", + "implied_ids": [ + fields.Command.set( + [cls.group_system_id.id, cls.group_multi_company_id.id] + ) + ], + "profile_id": cls.profile2_id.id, + } + cls.role2_id = cls.role_model.create(vals) + cls.role2_group_ids = cls._helper_unpack_groups_role(cls.role2_id) + + vals = { + "name": "ROLE_3", + "implied_ids": [ + fields.Command.set( + [cls.group_erp_manager_id.id, cls.group_partner_manager_id.id] + ) + ], + } + cls.role3_id = cls.role_model.create(vals) + cls.role3_group_ids = cls._helper_unpack_groups_role(cls.role3_id) + + @staticmethod + def _helper_unpack_groups_role(role): + role_group_ids = role.all_implied_ids.ids + return sorted(set(role_group_ids)) + + @staticmethod + def _helper_unpack_groups_group(group): + group_ids = group.all_implied_ids.ids + group_ids.append(group.id) + return sorted(set(group_ids)) + + def test_filter_by_profile(self): + self.user_id.write( + { + "role_line_ids": [ + fields.Command.create( + {"role_id": self.role1_id.id, "user_id": self.user_id.id} + ) + ] + } + ) + self.user_id.write( + { + "role_line_ids": [ + fields.Command.create( + {"role_id": self.role2_id.id, "user_id": self.user_id.id} + ) + ] + } + ) + self.assertEqual(self.user_id.profile_ids, self.profile1_id + self.profile2_id) + self.assertEqual(self.user_id.profile_id, self.profile1_id) + self.assertFalse(self.user_id.restrict_profile_switching) + self.user_id.action_profile_change({"profile_id": self.profile1_id.id}) + + user_group_ids = sorted({group.id for group in self.user_id.group_ids}) + expected_group_ids = sorted(set(self.role1_group_ids)) + self.assertEqual(user_group_ids, expected_group_ids) + + self.assertFalse(self.user_id.restrict_profile_switching) + self.user_id.action_profile_change({"profile_id": self.profile2_id.id}) + + user_group_ids = sorted({group.id for group in self.user_id.group_ids}) + expected_group_ids = sorted(set(self.role2_group_ids)) + self.assertEqual(user_group_ids, expected_group_ids) + + def test_restrict_profile_switching(self): + self.user_id.write( + { + "role_line_ids": [ + fields.Command.create( + {"role_id": self.role1_id.id, "user_id": self.user_id.id} + ) + ] + } + ) + self.user_id.write( + { + "role_line_ids": [ + fields.Command.create( + {"role_id": self.role2_id.id, "user_id": self.user_id.id} + ) + ] + } + ) + self.assertEqual(self.user_id.profile_id, self.profile1_id) + self.user_id.restrict_profile_switching = True + self.assertTrue(self.user_id.restrict_profile_switching) + self.user_id.action_profile_change({"profile_id": self.profile2_id.id}) + self.assertEqual(self.user_id.profile_id, self.profile1_id) + + def test_allow_by_noprofile(self): + self.user_id.write( + { + "role_line_ids": [ + fields.Command.create( + {"role_id": self.role1_id.id, "user_id": self.user_id.id} + ) + ] + } + ) + self.user_id.write( + { + "role_line_ids": [ + fields.Command.create( + {"role_id": self.role3_id.id, "user_id": self.user_id.id} + ) + ] + } + ) + self.assertFalse(self.user_id.include_default_profile) + self.assertEqual(self.user_id.profile_ids, self.profile1_id) + user_group_ids = [] + for group in self.user_id.group_ids: + user_group_ids += self._helper_unpack_groups_group(group) + user_group_ids = set(user_group_ids) + expected_groups = set(self.role1_group_ids + self.role3_group_ids) + self.assertEqual(user_group_ids, expected_groups) + + def test_include_default_profile(self): + self.user_id.write( + { + "role_line_ids": [ + fields.Command.create( + {"role_id": self.role1_id.id, "user_id": self.user_id.id} + ) + ] + } + ) + self.user_id.write( + { + "role_line_ids": [ + fields.Command.create( + {"role_id": self.role3_id.id, "user_id": self.user_id.id} + ) + ] + } + ) + self.user_id.write({"include_default_profile": True}) + self.assertTrue(self.user_id.include_default_profile) + self.assertEqual( + self.user_id.profile_ids, + self.user_id._get_default_profile() + self.profile1_id, + ) + + def test_update_profile_id(self): + self.user_id.write( + { + "role_line_ids": [ + fields.Command.create( + {"role_id": self.role1_id.id, "user_id": self.user_id.id} + ) + ] + } + ) + self.user_id.write( + { + "role_line_ids": [ + fields.Command.create( + {"role_id": self.role3_id.id, "user_id": self.user_id.id} + ) + ] + } + ) + self.assertFalse(self.user_id.include_default_profile) + self.user_id.profile_ids = False + self.user_id._update_profile_id() + self.assertEqual(self.user_id.profile_id, self.profile1_id) + self.user_id.write({"include_default_profile": True}) + self.user_id.profile_id = False + self.user_id._update_profile_id() + self.assertEqual(self.user_id.profile_id, self.user_id._get_default_profile()) diff --git a/base_user_role_profile/views/res_users_profile_views.xml b/base_user_role_profile/views/res_users_profile_views.xml new file mode 100644 index 000000000..e15eea2ec --- /dev/null +++ b/base_user_role_profile/views/res_users_profile_views.xml @@ -0,0 +1,41 @@ + + + + + res.users.profile.form + res.users.profile + +
+ + + + + + +
+
+
+ + + res.users.profile.list + res.users.profile + + + + + + + + + User Profiles + ir.actions.act_window + res.users.profile + + + + +
diff --git a/base_user_role_profile/views/res_users_role_views.xml b/base_user_role_profile/views/res_users_role_views.xml new file mode 100644 index 000000000..77bcd711b --- /dev/null +++ b/base_user_role_profile/views/res_users_role_views.xml @@ -0,0 +1,40 @@ + + + + + res.users.role.form.inherit + res.users.role + + + + + + + + + + + + res.users.role.list + res.users.role + + + + + + + + + res.users.role.search + res.users.role + + + + + + + + diff --git a/base_user_role_profile/views/res_users_views.xml b/base_user_role_profile/views/res_users_views.xml new file mode 100644 index 000000000..cbda1bc8e --- /dev/null +++ b/base_user_role_profile/views/res_users_views.xml @@ -0,0 +1,29 @@ + + + + + res.users.form.inherit + res.users + + + + + + + + + + + + + + + +