Merge PR #104 into 14.0

Signed-off-by dreispt
This commit is contained in:
OCA-git-bot
2021-12-04 13:50:05 +00:00
26 changed files with 1097 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
=============
User profiles
=============
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |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/licence-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/12.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-12-0/server-backend-12-0-base_user_role_profile
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/253/12.0
:alt: Try me on Runbot
|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
Note that this module assumes a multicompany environment
**Table of contents**
.. contents::
:local:
Configuration
=============
Go to Configuration / Users / Profiles and create a profile. Go to Configuration / Users / Roles and define some role lines with profiles.
Be careful when defining role lines that company ids and profiles are correctly configured.
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 <https://github.com/OCA/server-backend/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/server-backend/issues/new?body=module:%20base_user_role_profile%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* Akretion
Contributors
~~~~~~~~~~~~
* Kevin Khao <kevin.khao@akretion.com>
* Sébastien Beau <sebastien.beau@akretion.com>
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 <https://github.com/OCA/server-backend/tree/12.0/base_user_role_profile>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1,2 @@
from . import models
from .hooks import post_init_hook

View File

@@ -0,0 +1,22 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "User profiles",
"version": "14.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/data.xml",
"security/ir.model.access.csv",
"views/user.xml",
"views/role.xml",
"views/profile.xml",
"views/assets.xml",
],
"qweb": ["static/src/xml/templates.xml"],
"installable": True,
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="default_profile" model="res.users.profile">
<field name="name">No profile</field>
</record>
</odoo>

View File

@@ -0,0 +1,12 @@
# Copyright 2020 Akretion (https://www.akretion.com).
# @author Pierrick Brun <pierrick.brun@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import SUPERUSER_ID, api
def post_init_hook(cr, registry):
env = api.Environment(cr, SUPERUSER_ID, {})
env["res.users"].search([("profile_id", "=", False)]).profile_id = env.ref(
"base_user_role_profile.default_profile"
)

View File

@@ -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 12.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
#. openerp-web
#: code:addons/base_user_role_profile/static/src/xml/templates.xml:6
#, python-format
msgid "Dropdown menu"
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,field_description:base_user_role_profile.field_res_users_profile____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
#. openerp-web
#: code:addons/base_user_role_profile/static/src/js/switch_profile_menu.js:31
#, python-format
msgid "Tap on the list to change profile"
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 ""

View File

@@ -0,0 +1,4 @@
from . import profile
from . import user
from . import role
from . import ir_http

View File

@@ -0,0 +1,22 @@
# 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 = [(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

View File

@@ -0,0 +1,19 @@
# 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("Name")
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)]
)

View File

@@ -0,0 +1,17 @@
# 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",
)
class ResUsersRoleLine(models.Model):
_inherit = "res.users.role.line"
profile_id = fields.Many2one(related="role_id.profile_id")

View File

@@ -0,0 +1,75 @@
# 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",
)
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):
self.write(vals)
return self._get_action_root_menu()
@api.model
def create(self, vals):
new_record = super().create(vals)
if vals.get("role_line_ids"):
new_record.sudo()._compute_profile_ids()
return new_record
def write(self, vals):
# inspired by base/models/res_users.py l. 491
if self == self.env.user and vals.get("profile_id"):
self.sudo().write({"profile_id": vals["profile_id"]})
del vals["profile_id"]
res = super().write(vals)
if vals.get("profile_id") or vals.get("role_line_ids"):
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):
for rec in self:
role_lines = rec.role_line_ids
profiles = role_lines.mapped("profile_id")
rec.profile_ids = profiles
# set defaults in case applicable profile changes
rec._update_profile_id()

View File

@@ -0,0 +1 @@
Go to Configuration / Users / Profiles and create a profile. Go to Configuration / Users / Roles and define some role lines with profiles.

View File

@@ -0,0 +1,2 @@
* Kevin Khao <kevin.khao@akretion.com>
* Sébastien Beau <sebastien.beau@akretion.com>

View File

@@ -0,0 +1,10 @@
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

View File

@@ -0,0 +1 @@
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).

View File

@@ -0,0 +1,3 @@
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_role_line_users,access_res_users_role_line,model_res_users_role_line,base.group_user,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_res_users_profile access_res_users_profile model_res_users_profile base.group_user 1 0 0 0
3 access_res_users_role_line_users access_res_users_role_line model_res_users_role_line base.group_user 1 0 0 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,447 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.15.1: http://docutils.sourceforge.net/" />
<title>User profiles</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="user-profiles">
<h1 class="title">User profiles</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/server-backend/tree/12.0/base_user_role_profile"><img alt="OCA/server-backend" src="https://img.shields.io/badge/github-OCA%2Fserver--backend-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/server-backend-12-0/server-backend-12-0-base_user_role_profile"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/253/12.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>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. Through the new widget, much in the same way that a user can switch companies when they are part of the multi company group, users have the possibility to change profiles when they are part of the multi profiles group.</p>
<dl class="docutils">
<dt>This allows users to switch their permission groups dynamically. This can be useful for example to:</dt>
<dd><ul class="first last simple">
<li>finer grain control on menu and model permissions (with record rules this becomes very flexible)</li>
<li>break down complicated menus into simpler ones</li>
<li>easily restrict users accidentally editing or creating records in O2M fields and in general misusing the interface, instead of excessively explaining things to them</li>
</ul>
</dd>
<dt>When you define a role, you have the possibility to link it to a profile. Roles are applied to users in the following way:</dt>
<dd><ul class="first last simple">
<li>Apply users roles without profiles in any case</li>
<li>Apply users roles that are linked to the currently selected profile</li>
</ul>
</dd>
</dl>
<p>Note that this module assumes a multicompany environment</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="id1">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="id2">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id6">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id1">Configuration</a></h1>
<p>Go to Configuration / Users / Profiles and create a profile. Go to Configuration / Users / Roles and define some role lines with profiles.
Be careful when defining role lines that company ids and profiles are correctly configured.</p>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id2">Usage</a></h1>
<p>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).</p>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id3">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/server-backend/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/server-backend/issues/new?body=module:%20base_user_role_profile%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id4">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id5">Authors</a></h2>
<ul class="simple">
<li>Akretion</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id6">Contributors</a></h2>
<ul class="simple">
<li>Kevin Khao &lt;<a class="reference external" href="mailto:kevin.khao&#64;akretion.com">kevin.khao&#64;akretion.com</a>&gt;</li>
<li>Sébastien Beau &lt;<a class="reference external" href="mailto:sebastien.beau&#64;akretion.com">sebastien.beau&#64;akretion.com</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id7">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>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.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/server-backend/tree/12.0/base_user_role_profile">OCA/server-backend</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1 @@
from . import test_user_role

View File

@@ -0,0 +1,112 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.tests.common import TransactionCase
class TestUserProfile(TransactionCase):
def _helper_unpack_groups_role(self, role):
role_group_ids = role.trans_implied_ids.ids
role_group_ids.append(role.group_id.id)
return sorted(set(role_group_ids))
def _helper_unpack_groups_group(self, group):
group_ids = group.trans_implied_ids.ids
group_ids.append(group.id)
return sorted(set(group_ids))
def setUp(self):
super().setUp()
self.user_model = self.env["res.users"]
self.role_model = self.env["res.users.role"]
self.default_user = self.env.ref("base.default_user")
user_vals = {
"name": "USER TEST (ROLES)",
"login": "user_test_roles",
}
self.user_id = self.user_model.create(user_vals)
self.profile1_id = self.env["res.users.profile"].create({"name": "profile1"})
self.profile2_id = self.env["res.users.profile"].create({"name": "profile2"})
# role 1
self.group_user_id = self.env.ref("base.group_user")
self.group_no_one_id = self.env.ref("base.group_no_one")
# role 2
self.group_system_id = self.env.ref("base.group_system")
self.group_multi_company_id = self.env.ref("base.group_multi_company")
# role 3
self.group_erp_manager_id = self.env.ref("base.group_erp_manager")
self.group_partner_manager_id = self.env.ref("base.group_partner_manager")
# roles 1 and 2 have a profile, role 3 no profile
vals = {
"name": "ROLE_1",
"implied_ids": [(6, 0, [self.group_user_id.id, self.group_no_one_id.id])],
"profile_id": self.profile1_id.id,
}
self.role1_id = self.role_model.create(vals)
self.role1_group_ids = self._helper_unpack_groups_role(self.role1_id)
vals = {
"name": "ROLE_2",
"implied_ids": [
(
6,
0,
[self.group_system_id.id, self.group_multi_company_id.id],
)
],
"profile_id": self.profile2_id.id,
}
self.role2_id = self.role_model.create(vals)
self.role2_group_ids = self._helper_unpack_groups_role(self.role2_id)
vals = {
"name": "ROLE_3",
"implied_ids": [
(
6,
0,
[
self.group_erp_manager_id.id,
self.group_partner_manager_id.id,
],
)
],
}
self.role3_id = self.role_model.create(vals)
self.role3_group_ids = self._helper_unpack_groups_role(self.role3_id)
def test_filter_by_profile(self):
line1_vals = {"role_id": self.role1_id.id, "user_id": self.user_id.id}
self.user_id.write({"role_line_ids": [(0, 0, line1_vals)]})
line2_vals = {"role_id": self.role2_id.id, "user_id": self.user_id.id}
self.user_id.write({"role_line_ids": [(0, 0, line2_vals)]})
self.assertEqual(self.user_id.profile_ids, self.profile1_id + self.profile2_id)
self.assertEqual(self.user_id.profile_id, self.profile1_id)
self.user_id.action_profile_change({"profile_id": self.profile1_id.id})
user_group_ids = sorted({group.id for group in self.user_id.groups_id})
expected_group_ids = sorted(set(self.role1_group_ids))
self.assertEqual(user_group_ids, expected_group_ids)
self.user_id.action_profile_change({"profile_id": self.profile2_id.id})
user_group_ids = sorted({group.id for group in self.user_id.groups_id})
expected_group_ids = sorted(set(self.role2_group_ids))
self.assertEqual(user_group_ids, expected_group_ids)
def test_allow_by_noprofile(self):
line1_vals = {"role_id": self.role1_id.id, "user_id": self.user_id.id}
self.user_id.write({"role_line_ids": [(0, 0, line1_vals)]})
line2_vals = {"role_id": self.role3_id.id, "user_id": self.user_id.id}
self.user_id.write({"role_line_ids": [(0, 0, line2_vals)]})
self.assertEqual(self.user_id.profile_ids, self.profile1_id)
user_group_ids = []
for group in self.user_id.groups_id:
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)

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<template
id="assets_backend"
name="User Role Profile assets"
inherit_id="web.assets_backend"
>
<xpath expr="." position="inside">
<script
type="text/javascript"
src="/base_user_role_profile/static/src/js/switch_profile_menu.js"
/>
</xpath>
</template>
</odoo>

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).-->
<odoo>
<record id="view_res_users_profile_form" model="ir.ui.view">
<field name="name">res.users.profile.form</field>
<field name="model">res.users.profile</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="name" />
<field name="user_ids" />
</group>
</sheet>
</form>
</field>
</record>
<record id="view_res_users_profile_tree" model="ir.ui.view">
<field name="name">res.users.profile.tree</field>
<field name="model">res.users.profile</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
</tree>
</field>
</record>
<record model="ir.actions.act_window" id="action_res_users_profile_tree">
<field name="name">User Profiles</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.users.profile</field>
<field name="view_id" ref="view_res_users_profile_tree" />
</record>
<menuitem
id="menu_action_res_users_profile_tree"
parent="base.menu_users"
action="action_res_users_profile_tree"
/>
</odoo>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).-->
<odoo>
<record id="view_res_users_form_inherit" model="ir.ui.view">
<field name="name">res.users.role.form.inherit</field>
<field name="model">res.users.role</field>
<field name="inherit_id" ref="base_user_role.view_res_users_role_form" />
<field name="arch" type="xml">
<xpath
expr="//field[@name='line_ids']//field[@name='is_enabled']"
position="after"
>
<field name="profile_id" />
</xpath>
<xpath expr="//field[@name='group_id']" position="after">
<field name="profile_id" />
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).-->
<odoo>
<record id="view_res_users_form_inherit_profile" model="ir.ui.view">
<field name="name">res.users.form.inherit</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form" />
<field name="arch" type="xml">
<xpath expr="//field[@name='role_line_ids']" position="before">
<group>
<field
name="profile_ids"
widget="many2many_tags"
attrs="{'readonly': True}"
/>
<field
name="profile_id"
domain="[('id', 'in', profile_ids)]"
options="{'no_create_edit': True, 'no_open': True}"
/>
</group>
</xpath>
<xpath
expr="//field[@name='role_line_ids']//field[@name='is_enabled']"
position="after"
>
<field name="profile_id" />
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1 @@
../../../../base_user_role_profile

View File

@@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)