11# Copyright 2023 Hunki Enterprises BV
22# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl-3.0)
33
4-
54from lxml import etree
65
7- from odoo import api , models
6+ from odoo import api , fields , models
87
98from odoo .addons .base .models .res_users import name_boolean_group , name_selection_groups
109
1110
1211class 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 )
0 commit comments