浅析 odoo用户视图中权限字段的实现

浅析 odoo用户视图中权限字段的实现

设置-用户中编辑用户信息是我们常用到的功能,但是当我们在开发者权限下查看用户form视图字段时,里面访问权限字段设置却显示如下:


        
        
        
        
        
        
        

这确实很让人困惑.

相关知识

在Ruter的这篇权限组的设置中介绍了权限组的实现.
从这篇文章的实现中我们可以很直观了解到在xml中设置model="ir.module.category"的record,用户视图中权限字段的显示会多出一个新分类,在分类下可以选择他们的子项(也就是model="res.groups"的record),当选择完时,可以发现用户被加入到了相关权限组中.
看到这里,也许你已经大概猜到了我接下来要说什么了.

odoo用户视图中权限字段的实现

addons/base/res/res_users_view.xml中我们可以发现id="view_users_form"的record,是用户表单视图的基本实现,通过对比源码以及odoo开发者模式下的字段视图获取,他们之间的主要差别是源码中的
被替换成了开头所说的让人费解的字段.

在继续从源码中查找,我们还可以发现另外一段在同文件下继承了id="view_users_form"的一个id="user_groups_view"的record.在这段record中,主要定义了


    
    
 

也就是说groups_id的字段的显示部分已经id="user_groups_view"被替换了.
从开发者模式下进入设置-页面-视图 中搜索这个id,可以发现这个视图的结构完全与被替换掉的用户视图一致.

但是,在XML中,id = user_groups_view的record中并没有定义出具体字段,那么odoo是如何精确的把相关分组设置加入到用户的视图呢?

addons/base/res/res_users.py中发现了一个继承了res.groups的类GroupsView里面存在一个方法
_update_user_groups_view,在这个方法的介绍里,作者介绍了作用:

Modify the view with xmlid base.user_groups_view, which inherits
the user form view, and introduces the reified group fields.

下面给出这个方法的代码:

    @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.
        """
        if self._context.get('install_mode'):
            # use installation/admin language for translatable names in the view
            user_context = self.env['res.users'].context_get()
            self = self.with_context(**user_context)

        # We have to try-catch this, because at first init the view does not
        # exist but we are already creating some basic groups.
        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')
            xml1, xml2 = [], []
            xml1.append(E.separator(string=_('Application'), colspan="2"))
            for app, kind, gs in self.get_groups_by_application():
                # hide groups in categories 'Hidden' and 'Extra' (except for group_no_one)
                attrs = {}
                if app.xml_id in ('base.module_category_hidden', 'base.module_category_extra', 'base.module_category_usability'):
                    attrs['groups'] = 'base.group_no_one'

                if kind == 'selection':
                    # application name with a selection field
                    field_name = name_selection_groups(gs.ids)
                    xml1.append(E.field(name=field_name, **attrs))
                    xml1.append(E.newline())
                else:
                    # application separator with boolean fields
                    app_name = app.name or _('Other')
                    xml2.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
                            xml2.append(E.field(name=field_name, invisible="1", **attrs))
                        else:
                            xml2.append(E.field(name=field_name, **attrs))

            xml2.append({'class': "o_label_nowrap"})
            xml = E.field(E.group(*(xml1), col="2"), E.group(*(xml2), col="4"), name="groups_id", position="replace")
            xml.addprevious(etree.Comment("GENERATED AUTOMATICALLY BY GROUPS"))
            xml_content = etree.tostring(xml, pretty_print=True, xml_declaration=True, encoding="utf-8")
            view.with_context(lang=None).write({'arch': xml_content})

这段代码即便没仔细的阅读,我们也可以很轻松的了解到大体功能:得到所有application(也就是categroy),
构造出field,其中field会根据该选项是否是selection类型以及其他处理(意味着选项是boolean)
最后,把构造好的XML重新写入数据库中id = user_groups_view的记录.

再仔细阅读,可以发现方法中循环的主题是通过for app, kind, gs in self.get_groups_by_application():这段代码生产的.这段代码作者也给出了相关介绍:

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.

至于看起来很奇怪的字段名,在源码中有一段name_selection_groups的方法:

def name_selection_groups(ids):
    return 'sel_groups_' + '_'.join(map(str, ids))

可以发现_update_user_groups_view中在对字段处理的时候调用了该方法,所以field字段显示的逻辑是各种不同权限中包含的的groups_id的组合.(对应implied_ids字段,分组包含其他分组的权限)

_update_user_groups_view方法会在页面升级的时候调用一次生成视图,此外,每当res.groups模型中执行了创建,删除,修改操作也会同时执行该段函数更新视图

应用

知道了原理,操作这部分就变得十分轻而易举了.首先继承res.groups模块,然后重写_update_user_groups_view方法

    @api.model
    def _update_user_groups_view(self):
        super(SpeGroupsView, self)._update_user_groups_view()

        # 修改base.user_groups_view视图,把Admin设置只分配给超级管理员
        view = self.env.ref('base.user_groups_view', raise_if_not_found=False)
        etree = ET.fromstring(view.arch)
        change_field = etree.find(".//field[@name='sel_groups_1_2']")
        if change_field is not None:
            change_field.set('groups', 'base.group_system')
        xml_content = ET.tostring(etree, encoding='utf-8', method='xml')
        view.with_context(lang=None).write({'arch': xml_content})

这段代码中等odoo的原本的处理完毕之后,我们再取出来,根据自己的需要操作XML(就跟平时编写xml一样),
在这里,我对 系统管理字段设置了groups,只有group_system权限组才可以看的到该视图.
然后重新与原方法一样,重新写回数据库中.

你可能感兴趣的:(浅析 odoo用户视图中权限字段的实现)