Analysis of odoo12 user rights management interface

Cause:

Due to the need to understand odoo's permission management, I went to see how odoo grants permissions to users. I found a lot that I couldn't understand. Therefore, we intend to start from the user's xml. See what it means inside

The first step is to check the user's xml. Find user source code

odoo / odoo / addons / base / views / res_users_views.xml

  <record id="view_users_form" model="ir.ui.view">
            <field name="name">res.users.form</field>
            <field name="model">res.users</field>
            <field name="arch" type="xml">
                <form string="Users">
                    <header>
                    </header>
                    <sheet>
                        由于只看权限,这里全部省略....
                        <notebook colspan="4">
                            <page name="access_rights" string="Access Rights">
                                <group string="Multi Companies" attrs="{'invisible': [('companies_count', '&lt;=', 1)]}">
                                    <field string="Allowed Companies" name="company_ids" widget="many2many_tags" options="{'no_create': True}"/>
                                    <field string="Current Company" name="company_id" context="{'user_preference': 0}"/>
                                    <field string="Companies count" name="companies_count" invisible="1"/>
                                </group>
                                <field name="groups_id"/>
                            </page>
                            <page string="Preferences">
                                省略......
                            </page>
                        </notebook>
                    </sheet>
                </form>
            </field>
        </record>

Find. There is no permission part of the code. . Then I looked for the inherited view_users_form, and found nothing. It's right to think about it. Since the permissions are dynamically generated, they must be done in the background, instead of being fixed in the foreground.

The interface needs to be updated due to additions, deletions and changes. So check the user's code behind. turn up

 @api.model
    def _update_user_groups_view(self):
        """ Modify the view with xmlid ``base.user_groups_view``, which inherits
            the user form view, and introduces the reified group fields.
        """

        # remove the language to avoid translations, it will be handled at the view level
        self = self.with_context(lang=None)

        # We have to try-catch this, because at first init the view does not
        # exist but we are already creating some basic groups.
        # 获取 base.user_groups_view 组view 的xml
        view = self.env.ref('base.user_groups_view', raise_if_not_found=False)
        if view and view.exists() and view._name == 'ir.ui.view':
            group_no_one = view.env.ref('base.group_no_one')
            group_employee = view.env.ref('base.group_user')
            xml1, xml2, xml3 = [], [], []
            # 这两个固定
            xml1.append(E.separator(string='User Type', colspan="2", groups='base.group_no_one'))
            xml2.append(E.separator(string='Application Accesses', colspan="2"))

            user_type_field_name = ''
            for app, kind, gs in self.get_groups_by_application():
                attrs = {}
                # hide groups in categories 'Hidden' and 'Extra' (except for group_no_one)
                if app.xml_id in ('base.module_category_hidden', 'base.module_category_extra', 'base.module_category_usability'):
                    attrs['groups'] = 'base.group_no_one'

                # User type (employee, portal or public) is a separated group. This is the only 'selection'
                # group of res.groups without implied groups (with each other).
                if app.xml_id == 'base.module_category_user_type':
                    # application name with a selection field
                    field_name = name_selection_groups(gs.ids)
                    user_type_field_name = field_name
                    attrs['widget'] = 'radio'
                    attrs['groups'] = 'base.group_no_one'
                    xml1.append(E.field(name=field_name, **attrs))
                    xml1.append(E.newline())

                elif kind == 'selection':
                    # 只要属于selection 的就放在Application Accesses 下
                    # application name with a selection field
                    field_name = name_selection_groups(gs.ids)
                    xml2.append(E.field(name=field_name, **attrs))
                    xml2.append(E.newline())
                else:
                    # application separator with boolean fields
                    # 不属于那两个的则为 app_name
                    app_name = app.name or 'Other'
                    xml3.append(E.separator(string=app_name, colspan="4", **attrs))
                    for g in gs:
                        field_name = name_boolean_group(g.id)
                        if g == group_no_one:
                            # make the group_no_one invisible in the form view
                            xml3.append(E.field(name=field_name, invisible="1", **attrs))
                        else:
                            xml3.append(E.field(name=field_name, **attrs))

            xml3.append({'class': "o_label_nowrap"})
            if user_type_field_name:
                user_type_attrs = {'invisible': [(user_type_field_name, '!=', group_employee.id)]}
            else:
                user_type_attrs = {}

            xml = E.field(
                E.group(*(xml1), col="2"),
                E.group(*(xml2), col="2", attrs=str(user_type_attrs)),
                E.group(*(xml3), col="4", attrs=str(user_type_attrs)), name="groups_id", position="replace")
            xml.addprevious(etree.Comment("GENERATED AUTOMATICALLY BY GROUPS"))
            xml_content = etree.tostring(xml, pretty_print=True, encoding="unicode")

            new_context = dict(view._context)
            new_context.pop('install_mode_data', None)  # don't set arch_fs for this computed view
            new_context['lang'] = None
            # 写入base.user_groups_view
            view.with_context(new_context).write({'arch': xml_content})

can be seen. Generated completely in the background. . All have comments. Just talk about the principle

<record id="user_groups_view" model="ir.ui.view">
            <field name="name">res.users.groups</field>
            <field name="model">res.users</field>
            <field name="inherit_id" ref="view_users_form"/>
            <field name="arch" type="xml">
                <!-- dummy, will be modified by groups -->
                <field name="groups_id" position="after"/>
            </field>
        </record>

1. Get the view of user_groups_view first. . And this view inherits users_form, and after

2. Generate content according to logic. Then write to the arch of the database and it is ok

 

There is still a question, how to determine the selection

    def get_groups_by_application(self):
        """ Return all groups classified by application (module category), as a list::

                [(app, kind, groups), ...],

            where ``app`` and ``groups`` are recordsets, and ``kind`` is either
            ``'boolean'`` or ``'selection'``. Applications are given in sequence
            order.  If ``kind`` is ``'selection'``, ``groups`` are given in
            reverse implication order.
        """
        #  app 分类  gs 组
        def linearize(app, gs):
            # 'User Type' is an exception 特例  app.xml_id 是计算字段
            if app.xml_id == 'base.module_category_user_type':
                return (app, 'selection', gs.sorted('id'))
            # determine sequence order: a group appears after its implied groups  自己继承的组
            order = {g: len(g.trans_implied_ids & gs) for g in gs}
            # check whether order is total, i.e., sequence orders are distinct
            #判断当前group 是否有平级关系,没有平级关系的话,res_groups_implied_rel 肯定无数据,set{g:0,g:0}
            # /set{g:1,g:2,g:1} 肯定不等于gs(组对象)
            if len(set(order.values())) == len(gs):
                return (app, 'selection', gs.sorted(key=order.get))
            else:
                return (app, 'boolean', gs)

        # classify all groups by application
        by_app, others = defaultdict(self.browse), self.browse()
        # 遍历所有group
        for g in self.get_application_groups([]):
            if g.category_id:
                #如果有 category
                by_app[g.category_id] += g
            else:
                others += g
        # build the result
        res = []
        for app, gs in sorted(by_app.items(), key=lambda it: it[0].sequence or 0):
            #app 分类  gs 组
            res.append(linearize(app, gs))
        if others:
            res.append((self.env['ir.module.category'], 'boolean', others))
        return res

The key point is order = {g: len(g.trans_implied_ids & gs) for g in gs}

trans_implied_ids = fields.Many2many('res.groups', string='Transitively inherits',
    compute='_compute_trans_implied')

@api.depends('implied_ids.trans_implied_ids')
def _compute_trans_implied(self):
    # Compute the transitive closure recursively. Note that the performance
    # is good, because the record cache behaves as a memo (the field is
    # never computed twice on a given group.)
    for g in self:
        g.trans_implied_ids = g.implied_ids | g.mapped('implied_ids.trans_implied_ids')

As can be seen. trans_implied_ids is actually res_groups_implied_rel that only thinks about itself.

Only when the group of the current category and the id of trans_implied_ids are equal to each other, the selection will be displayed

a -----b

---------c

Not equal

 

 

Guess you like

Origin blog.csdn.net/sr50611/article/details/100016732