Skip to content

Commit 6c8bd29

Browse files
committed
make groups for portal users independent of the magic category_id provided in this module (but it is still kept for backwards compatibility):
a boolean field 'portal' can be set on a group to make it selectable for portal users. this makes it also possible to put portal-groups into actual categories
1 parent 897f587 commit 6c8bd29

File tree

10 files changed

+209
-67
lines changed

10 files changed

+209
-67
lines changed

base_portal_type/README.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ Usage
4747

4848
To use this module, you need to:
4949

50-
#. create a group of category ``base_portal_type.category_portal_type``
50+
#. create a group with either
51+
* category set to ``base_portal_type.category_portal_type``
52+
* or ``portal`` field set to True, which allows you to organize your portal-groups in other categories
5153
#. observe the group shows up on a user form for users of type `Portal`
5254
#. now you can use this group in your frontend templates and code to make some features accessible to this group
5355

@@ -78,6 +80,7 @@ Contributors
7880
~~~~~~~~~~~~
7981

8082
* Holger Brunn <[email protected]> (https://hunki-enterprises.com)
83+
* Benedikt Zimmer <[email protected]>
8184

8285
Maintainers
8386
~~~~~~~~~~~

base_portal_type/__manifest__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
],
1717
"data": [
1818
"data/ir_module_category.xml",
19+
"views/res_groups_views.xml",
1920
],
2021
"demo": [
2122
"demo/res_groups.xml",
Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
<?xml version="1.0" encoding="utf-8" ?>
22
<!-- Copyright 2023 Hunki Enterprises BV
33
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl-3.0) -->
4-
<data>
5-
6-
<record id="category_portal_type" model="ir.module.category">
7-
<field name="name">Portal type</field>
8-
</record>
9-
10-
</data>
4+
<odoo>
5+
<record id="category_portal_type" model="ir.module.category">
6+
<field name="name">Portal type</field>
7+
</record>
8+
</odoo>
Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,27 @@
11
<?xml version="1.0" encoding="utf-8" ?>
22
<!-- Copyright 2023 Hunki Enterprises BV
33
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl-3.0) -->
4-
<data>
5-
6-
<record id="group_portal_type" model="res.groups">
7-
<field name="name">A portal type</field>
8-
<field name="category_id" ref="category_portal_type" />
9-
</record>
10-
11-
</data>
4+
<odoo>
5+
<record id="group_portal_type" model="res.groups">
6+
<field name="name">A portal type</field>
7+
<!-- demonstrating backwards compatibility before introduction of this field -->
8+
<field name="portal" eval="False" />
9+
<field name="category_id" ref="category_portal_type" />
10+
</record>
11+
<record id="group_portal_example_1" model="res.groups">
12+
<field name="name">Portal Group 1</field>
13+
<field name="portal">True</field>
14+
<field name="category_id" ref="base.module_category_technical" />
15+
</record>
16+
<record id="group_portal_example_2" model="res.groups">
17+
<field name="name">Portal Group 2</field>
18+
<field name="portal">True</field>
19+
<field name="category_id" ref="base.module_category_technical" />
20+
</record>
21+
<record id="group_portal_example_3" model="res.groups">
22+
<field name="name">Portal Group 3</field>
23+
<field name="portal">True</field>
24+
<!-- just to demonstrate that portal groups don't need a category_id and will be put into "Other" section -->
25+
<field name="category_id" eval="False" />
26+
</record>
27+
</odoo>

base_portal_type/models/res_groups.py

Lines changed: 74 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
# Copyright 2023 Hunki Enterprises BV
22
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl-3.0)
33

4-
54
from lxml import etree
65

7-
from odoo import api, models
6+
from odoo import api, fields, models
87

98
from odoo.addons.base.models.res_users import name_boolean_group, name_selection_groups
109

1110

1211
class ResGroups(models.Model):
1312
_inherit = "res.groups"
1413

14+
portal = fields.Boolean("Is Portal Group")
15+
1516
@api.model
1617
def _update_user_groups_view(self):
1718
result = super()._update_user_groups_view()
@@ -24,67 +25,98 @@ def _update_user_groups_view(self):
2425
][0]
2526
portal_group = self.env.ref("base.group_portal")
2627
internal_group = self.env.ref("base.group_user")
28+
29+
user_type_field_name = name_selection_groups(user_type_groups.ids)
30+
non_public_attrs = str(
31+
{
32+
"readonly": [
33+
(
34+
user_type_field_name,
35+
"not in",
36+
[internal_group.id, portal_group.id],
37+
)
38+
],
39+
}
40+
)
41+
2742
portal_groups = (
2843
self.env["res.groups"]
29-
.with_context(lang=None)
3044
.search(
3145
[
46+
"|",
3247
(
3348
"category_id",
3449
"=",
3550
self.env.ref("base_portal_type.category_portal_type").id,
3651
),
52+
("portal", "=", True),
3753
]
3854
)
55+
.with_context(lang=None)
3956
)
40-
user_type_field = name_selection_groups(user_type_groups.ids)
41-
for node in arch.xpath("//group/field[@name='%s']" % user_type_field):
42-
for group in portal_groups:
43-
field_name = name_boolean_group(group.id)
44-
for field_node in arch.xpath("//field[@name='%s']" % field_name):
45-
field_node.attrib["attrs"] = str(
46-
{
47-
"readonly": [
48-
(
49-
user_type_field,
50-
"not in",
51-
(portal_group + internal_group).ids,
52-
)
53-
],
54-
}
55-
)
56-
node.addnext(
57-
etree.Element(
57+
58+
# during install/upgrade/uninstall, super()._update_user_groups_view() may return
59+
# a placeholder view without the field which has name=user_type_field_name added yet.
60+
# Making sure we received the real view here
61+
arch_root = arch.xpath("//field[@name='groups_id'][@position='replace']")
62+
if len(arch_root) and portal_groups:
63+
# as the original construction of the groups view makes all xml-groups
64+
# per application invisible, it is 'easier' to create another section for
65+
# portal users and append the whole section at the bottom of the view.
66+
# name attribute is given here to easily identify this group element in tests
67+
portal_group_elem = etree.SubElement(
68+
arch_root[0],
69+
"group",
70+
name="base_portal_type_extension",
71+
attrs=str(
72+
{
73+
"readonly": [(user_type_field_name, "!=", portal_group.id)],
74+
"invisible": [(user_type_field_name, "!=", portal_group.id)],
75+
}
76+
),
77+
)
78+
for app_id, app_name in portal_groups.mapped("category_id").name_get() + [
79+
(False, "Other")
80+
]:
81+
portal_groups_in_app = portal_groups.filtered(
82+
lambda r: (r.category_id.id or False) == app_id
83+
)
84+
if not portal_groups_in_app:
85+
continue
86+
app_group_elem = etree.SubElement(
87+
portal_group_elem,
88+
"group",
89+
string=app_name,
90+
)
91+
for group in portal_groups_in_app:
92+
field_name = name_boolean_group(group.id)
93+
for field_node in arch.xpath("//field[@name='%s']" % field_name):
94+
field_node.attrib["attrs"] = non_public_attrs
95+
etree.SubElement(
96+
app_group_elem,
5897
"field",
59-
{
60-
"name": field_name,
61-
"on_change": "1",
62-
"attrs": str(
63-
{
64-
"invisible": [
65-
(user_type_field, "!=", portal_group.id),
66-
],
67-
}
68-
),
69-
},
98+
name=field_name,
99+
on_change="1",
70100
)
71-
)
72-
view_context = dict(self.env.context, lang=None)
73-
view_context.pop("install_filename", None)
74-
# pylint: disable=context-overridden
75-
view.with_context(view_context).write(
76-
{"arch": etree.tostring(arch, pretty_print=True, encoding="unicode")}
77-
)
101+
102+
view_context = dict(self.env.context, lang=None)
103+
view_context.pop("install_filename", None)
104+
# pylint: disable=context-overridden
105+
view.with_context(view_context).write(
106+
{"arch": etree.tostring(arch, pretty_print=True, encoding="unicode")}
107+
)
78108
return result
79109

80110
@api.model
81111
def get_groups_by_application(self):
82112
return [
83113
(
84114
app,
85-
kind
86-
if app.xml_id != "base_portal_type.category_portal_type"
87-
else "boolean",
115+
(
116+
kind
117+
if app.xml_id != "base_portal_type.category_portal_type"
118+
else "boolean"
119+
),
88120
groups,
89121
category,
90122
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
* Holger Brunn <[email protected]> (https://hunki-enterprises.com)
2+
* Benedikt Zimmer <[email protected]>

base_portal_type/readme/USAGE.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
To use this module, you need to:
22

3-
#. create a group of category ``base_portal_type.category_portal_type``
3+
#. create a group with either
4+
* category set to ``base_portal_type.category_portal_type``
5+
* or ``portal`` field set to True, which allows you to organize your portal-groups in other categories
46
#. observe the group shows up on a user form for users of type `Portal`
57
#. now you can use this group in your frontend templates and code to make some features accessible to this group

base_portal_type/static/description/index.html

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
<?xml version="1.0" encoding="utf-8"?>
21
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
32
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
43
<head>
@@ -9,10 +8,11 @@
98

109
/*
1110
:Author: David Goodger ([email protected])
12-
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
11+
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
1312
:Copyright: This stylesheet has been placed in the public domain.
1413
1514
Default cascading style sheet for the HTML output of Docutils.
15+
Despite the name, some widely supported CSS2 features are used.
1616
1717
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
1818
customize this style sheet.
@@ -275,7 +275,7 @@
275275
margin-left: 2em ;
276276
margin-right: 2em }
277277

278-
pre.code .ln { color: grey; } /* line numbers */
278+
pre.code .ln { color: gray; } /* line numbers */
279279
pre.code, code { background-color: #eeeeee }
280280
pre.code .comment, code .comment { color: #5C6576 }
281281
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
@@ -301,7 +301,7 @@
301301
span.pre {
302302
white-space: pre }
303303

304-
span.problematic {
304+
span.problematic, pre.problematic {
305305
color: red }
306306

307307
span.section-subtitle {
@@ -396,7 +396,15 @@ <h1 class="title">Portal types</h1>
396396
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
397397
<p>To use this module, you need to:</p>
398398
<ol class="arabic simple">
399-
<li>create a group of category <tt class="docutils literal">base_portal_type.category_portal_type</tt></li>
399+
<li><dl class="first docutils">
400+
<dt>create a group with either</dt>
401+
<dd><ul class="first last">
402+
<li>category set to <tt class="docutils literal">base_portal_type.category_portal_type</tt></li>
403+
<li>or <tt class="docutils literal">portal</tt> field set to True, which allows you to organize your portal-groups in other categories</li>
404+
</ul>
405+
</dd>
406+
</dl>
407+
</li>
400408
<li>observe the group shows up on a user form for users of type <cite>Portal</cite></li>
401409
<li>now you can use this group in your frontend templates and code to make some features accessible to this group</li>
402410
</ol>
@@ -427,12 +435,15 @@ <h2><a class="toc-backref" href="#toc-entry-5">Authors</a></h2>
427435
<h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
428436
<ul class="simple">
429437
<li>Holger Brunn &lt;<a class="reference external" href="mailto:mail&#64;hunki-enterprises.com">mail&#64;hunki-enterprises.com</a>&gt; (<a class="reference external" href="https://hunki-enterprises.com">https://hunki-enterprises.com</a>)</li>
438+
<li>Benedikt Zimmer &lt;<a class="reference external" href="mailto:github&#64;kellerzimmer.net">github&#64;kellerzimmer.net</a>&gt;</li>
430439
</ul>
431440
</div>
432441
<div class="section" id="maintainers">
433442
<h2><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h2>
434443
<p>This module is maintained by the OCA.</p>
435-
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
444+
<a class="reference external image-reference" href="https://odoo-community.org">
445+
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
446+
</a>
436447
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
437448
mission is to support the collaborative development of Odoo features and
438449
promote its widespread use.</p>

base_portal_type/tests/test_base_portal_type.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,45 @@
1111

1212
class TestBasePortalGroup(TransactionCase):
1313
def test_portal_group_view(self):
14-
"""Test that our group is added to the users form twice"""
15-
group = self.env.ref("base_portal_type.group_portal_type").copy()
14+
"""Test that a group is added to the users form twice"""
15+
group = self.env["res.groups"].create(
16+
{
17+
"name": "Test Group",
18+
"category_id": self.env.ref("base_portal_type.category_portal_type").id,
19+
}
20+
)
1621
group_field_name = name_boolean_group(group.id)
1722
groups_view = etree.fromstring(self.env.ref("base.user_groups_view").arch)
1823
self.assertEqual(
1924
len(groups_view.xpath("//field[@name='%s']" % group_field_name)), 3
2025
)
26+
27+
28+
class TestNoOtherSectionOnView(TransactionCase):
29+
def test_portal_group_view_no_other_section(self):
30+
"""
31+
Test that if no portal groups exists without a category_id set,
32+
no 'Other' section is created.
33+
"""
34+
self.env["res.groups"].search(
35+
[
36+
("portal", "=", True),
37+
("category_id", "=", False),
38+
]
39+
).unlink()
40+
# adding a portal group to guarantee the view extension
41+
self.env["res.groups"].create(
42+
{
43+
"name": "Test Group",
44+
"portal": True,
45+
"category_id": self.env.ref("base_portal_type.category_portal_type").id,
46+
}
47+
)
48+
49+
groups_view = etree.fromstring(self.env.ref("base.user_groups_view").arch)
50+
group_elem = groups_view.xpath("//group[@name='base_portal_type_extension']")
51+
52+
# making sure the parent group element actually exists
53+
self.assertEqual(len(group_elem), 1)
54+
# searching for the 'Other' section, which should not exist after unlinking above
55+
self.assertEqual(len(group_elem[0].xpath("//group[@string='Other']")), 0)

0 commit comments

Comments
 (0)