淺析 odoo用戶視圖中權(quán)限字段的實現(xiàn)
設(shè)置-用戶中編輯用戶信息是我們常用到的功能,但是當我們在開發(fā)者權(quán)限下查看用戶form視圖字段時,里面訪問權(quán)限字段設(shè)置卻顯示如下:
<group col="2">
<separator colspan="2" string="應用程序"/>
<field name="sel_groups_20_21_28" modifiers="{}"/>
<newline/>
<field name="sel_groups_3_10_11" modifiers="{}"/>
<newline/>
<field name="sel_groups_1_2" modifiers="{}"/>
<newline/>
</group>
這確實很讓人困惑.
相關(guān)知識
在Ruter的這篇權(quán)限組的設(shè)置中介紹了權(quán)限組的實現(xiàn).
從這篇文章的實現(xiàn)中我們可以很直觀了解到在xml中設(shè)置model="ir.module.category"
的record,用戶視圖中權(quán)限字段的顯示會多出一個新分類,在分類下可以選擇他們的子項(也就是model="res.groups"
的record),當選擇完時,可以發(fā)現(xiàn)用戶被加入到了相關(guān)權(quán)限組中.
看到這里,也許你已經(jīng)大概猜到了我接下來要說什么了.
odoo用戶視圖中權(quán)限字段的實現(xiàn)
在addons/base/res/res_users_view.xml
中我們可以發(fā)現(xiàn)id="view_users_form"
的record,是用戶表單視圖的基本實現(xiàn),通過對比源碼以及odoo開發(fā)者模式下的字段視圖獲取,他們之間的主要差別是源碼中的
<field name="groups_id"/>
被替換成了開頭所說的讓人費解的字段.
在繼續(xù)從源碼中查找,我們還可以發(fā)現(xiàn)另外一段在同文件下繼承了id="view_users_form"
的一個id="user_groups_view"
的record.在這段record中,主要定義了
<field name="arch" type="xml">
<!-- dummy, will be modified by groups -->
<field name="groups_id" position="after"/>
</field>
也就是說groups_id
的字段的顯示部分已經(jīng)id="user_groups_view"
被替換了.
從開發(fā)者模式下進入設(shè)置-頁面-視圖 中搜索這個id,可以發(fā)現(xiàn)這個視圖的結(jié)構(gòu)完全與被替換掉<field name="groups_id"/>
的用戶視圖一致.
但是,在XML中,id = user_groups_view
的record中并沒有定義出具體字段,那么odoo是如何精確的把相關(guān)分組設(shè)置加入到用戶的視圖呢?
在addons/base/res/res_users.py
中發(fā)現(xiàn)了一個繼承了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),
構(gòu)造出field,其中field會根據(jù)該選項是否是selection
類型以及其他處理(意味著選項是boolean
)
最后,把構(gòu)造好的XML重新寫入數(shù)據(jù)庫中id = user_groups_view
的記錄.
再仔細閱讀,可以發(fā)現(xiàn)方法中循環(huán)的主題是通過for app, kind, gs in self.get_groups_by_application():
這段代碼生產(chǎn)的.這段代碼作者也給出了相關(guān)介紹:
Return all groups classified by application (module category), as a list::
[(app, kind, groups), ...],
whereapp
andgroups
are recordsets, andkind
is either
'boolean'
or'selection'
. Applications are given in sequence
order. Ifkind
is'selection'
,groups
are given in
reverse implication order.
至于看起來很奇怪的字段名,在源碼中有一段name_selection_groups
的方法:
def name_selection_groups(ids):
return 'sel_groups_' + '_'.join(map(str, ids))
可以發(fā)現(xiàn)_update_user_groups_view
中在對字段處理的時候調(diào)用了該方法,所以field字段顯示的邏輯是各種不同權(quán)限中包含的的groups_id的組合.(對應implied_ids
字段,分組包含其他分組的權(quán)限)
_update_user_groups_view
方法會在頁面升級的時候調(diào)用一次生成視圖,此外,每當res.groups
模型中執(zhí)行了創(chuàng)建,刪除,修改操作也會同時執(zhí)行該段函數(shù)更新視圖
應用
知道了原理,操作這部分就變得十分輕而易舉了.首先繼承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設(shè)置只分配給超級管理員
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的原本的處理完畢之后,我們再取出來,根據(jù)自己的需要操作XML(就跟平時編寫xml一樣),
在這里,我對 系統(tǒng)管理
字段設(shè)置了groups,只有group_system
權(quán)限組才可以看的到該視圖.
然后重新與原方法一樣,重新寫回數(shù)據(jù)庫中.