odoo16 Model的操作符重载
今天给某模块新建了一个角色,然后给用户配置了角色,角色关联了菜单,菜单却加载不出来,于是跟踪了一下odoo加载菜单的逻辑。
odoo/addons/base/models/ir_ui_menu.py
入口点是load_menus函数,然后调用了私有的_visible_menu_ids函数, 这个函数把我搞的有点懵
def _visible_menu_ids(self, debug=False):
""" Return the ids of the menu items visible to the user. """
# retrieve all menus, and determine which ones are visible
context = {'ir.ui.menu.full_list': True}
menus = self.with_context(context).search([]).sudo()
groups = self.env.user.groups_id
if not debug:
groups = groups - self.env.ref('base.group_no_one')
# first discard all menus with groups the user does not have
menus = menus.filtered(
lambda menu: not menu.groups_id or menu.groups_id & groups)
menus 获取到了所有的菜单项
groups获取到了用户的所有角色
然后根据角色对菜单做过滤,关键的是这句
menus = menus.filtered(
lambda menu: not menu.groups_id or menu.groups_id & groups)
filtered里面的这个匿名函数:
not menu.groups_id 这个好理解,就是没有给菜单设置组
menu.groups_id & groups 这句把我眼睛看瞎了也没看明白,python里的逻辑操作符是and or not 这个“&” 是个位操作符, 这里怎么会出现位操作符呢?
后经高手指点, 这是操作符重载, & 两边都是Model类,那要重载也一定是在Model的代码里,于是找到models.py文件搜索 def __
然后就看到了一堆的操作符重载
def __bool__(self):
""" Test whether ``self`` is nonempty. """
return True if self._ids else False # fast version of bool(self._ids)
__nonzero__ = __bool__
def __len__(self):
""" Return the size of ``self``. """
return len(self._ids)
def __iter__(self):
""" Return an iterator over ``self``. """
if len(self._ids) > PREFETCH_MAX and self._prefetch_ids is self._ids:
for ids in self.env.cr.split_for_in_conditions(self._ids):
for id_ in ids:
yield self.__class__(self.env, (id_,), ids)
else:
for id_ in self._ids:
yield self.__class__(self.env, (id_,), self._prefetch_ids)
def __reversed__(self):
""" Return an reversed iterator over ``self``. """
if len(self._ids) > PREFETCH_MAX and self._prefetch_ids is self._ids:
for ids in self.env.cr.split_for_in_conditions(reversed(self._ids)):
for id_ in ids:
yield self.__class__(self.env, (id_,), ids)
elif self._ids:
prefetch_ids = ReversedIterable(self._prefetch_ids)
for id_ in reversed(self._ids):
yield self.__class__(self.env, (id_,), prefetch_ids)
def __contains__(self, item):
""" Test whether ``item`` (record or field name) is an element of ``self``.
In the first case, the test is fully equivalent to::
any(item == record for record in self)
"""
try:
if self._name == item._name:
return len(item) == 1 and item.id in self._ids
raise TypeError(f"inconsistent models in: {item} in {self}")
except AttributeError:
if isinstance(item, str):
return item in self._fields
raise TypeError(f"unsupported operand types in: {item!r} in {self}")
def __add__(self, other):
""" Return the concatenation of two recordsets. """
return self.concat(other)
def concat(self, *args):
""" Return the concatenation of ``self`` with all the arguments (in
linear time complexity).
"""
ids = list(self._ids)
for arg in args:
try:
if arg._name != self._name:
raise TypeError(f"inconsistent models in: {self} + {arg}")
ids.extend(arg._ids)
except AttributeError:
raise TypeError(f"unsupported operand types in: {self} + {arg!r}")
return self.browse(ids)
def __sub__(self, other):
""" Return the recordset of all the records in ``self`` that are not in
``other``. Note that recordset order is preserved.
"""
try:
if self._name != other._name:
raise TypeError(f"inconsistent models in: {self} - {other}")
other_ids = set(other._ids)
return self.browse([id for id in self._ids if id not in other_ids])
except AttributeError:
raise TypeError(f"unsupported operand types in: {self} - {other!r}")
def __and__(self, other):
""" Return the intersection of two recordsets.
Note that first occurrence order is preserved.
"""
try:
if self._name != other._name:
raise TypeError(f"inconsistent models in: {self} & {other}")
other_ids = set(other._ids)
return self.browse(OrderedSet(id for id in self._ids if id in other_ids))
except AttributeError:
raise TypeError(f"unsupported operand types in: {self} & {other!r}")
def __or__(self, other):
""" Return the union of two recordsets.
Note that first occurrence order is preserved.
"""
return self.union(other)
def union(self, *args):
""" Return the union of ``self`` with all the arguments (in linear time
complexity, with first occurrence order preserved).
"""
ids = list(self._ids)
for arg in args:
try:
if arg._name != self._name:
raise TypeError(f"inconsistent models in: {self} | {arg}")
ids.extend(arg._ids)
except AttributeError:
raise TypeError(f"unsupported operand types in: {self} | {arg!r}")
return self.browse(OrderedSet(ids))
def __eq__(self, other):
""" Test whether two recordsets are equivalent (up to reordering). """
try:
return self._name == other._name and set(self._ids) == set(other._ids)
except AttributeError:
if other:
warnings.warn(f"unsupported operand type(s) for \"==\": '{self._name}()' == '{other!r}'", stacklevel=2)
return NotImplemented
def __lt__(self, other):
try:
if self._name == other._name:
return set(self._ids) < set(other._ids)
except AttributeError:
pass
return NotImplemented
def __le__(self, other):
try:
if self._name == other._name:
# these are much cheaper checks than a proper subset check, so
# optimise for checking if a null or singleton are subsets of a
# recordset
if not self or self in other:
return True
return set(self._ids) <= set(other._ids)
except AttributeError:
pass
return NotImplemented
def __gt__(self, other):
try:
if self._name == other._name:
return set(self._ids) > set(other._ids)
except AttributeError:
pass
return NotImplemented
def __ge__(self, other):
try:
if self._name == other._name:
if not other or other in self:
return True
return set(self._ids) >= set(other._ids)
except AttributeError:
pass
return NotImplemented
def __int__(self):
return self.id or 0
def __repr__(self):
return f"{self._name}{self._ids!r}"
def __hash__(self):
return hash((self._name, frozenset(self._ids)))
def __getitem__(self, key):
""" If ``key`` is an integer or a slice, return the corresponding record
selection as an instance (attached to ``self.env``).
Otherwise read the field ``key`` of the first record in ``self``.
Examples::
inst = model.search(dom) # inst is a recordset
r4 = inst[3] # fourth record in inst
rs = inst[10:20] # subset of inst
nm = rs['name'] # name of first record in inst
"""
if isinstance(key, str):
# important: one must call the field's getter
return self._fields[key].__get__(self, type(self))
elif isinstance(key, slice):
return self.browse(self._ids[key])
else:
return self.browse((self._ids[key],))
def __setitem__(self, key, value):
""" Assign the field ``key`` to ``value`` in record ``self``. """
# important: one must call the field's setter
return self._fields[key].__set__(self, value)
#
我们要关注的是这个
def __and__(self, other):
""" Return the intersection of two recordsets.
Note that first occurrence order is preserved.
"""
try:
if self._name != other._name:
raise TypeError(f"inconsistent models in: {self} & {other}")
other_ids = set(other._ids)
return self.browse(OrderedSet(id for id in self._ids if id in other_ids))
except AttributeError:
raise TypeError(f"unsupported operand types in: {self} & {other!r}")
& 返回两个集合的交集。
| 是返回两个集合的并集。
还有其他的一些操作符,等有机会了再去研究。