第二章 模型
与Python的其他主流框架类似, Odoo的模型(model)也是数据持久化的主要对象。 本章就将对odoo中的模型进行详细的介绍。
odoo中的三种model类型
odoo中的模型可以分为以下三种类型:
- 抽象模型(AbstractModel)
- 数据模型(Model)
- 瞬态模型(TransientModel)
抽象模型只在编程上有意义,数据库中不存在对应的表结构。数据模型是我们最常用的模型,用来存储各种应用数据。瞬态模型在数据库对应的表类型为临时表,是一种临时性的存储对象。
抽象模型
抽象模型实际上是基础模型(BaseModel)的别称, 不创建数据库表. 抽象模型是数据模型的父类, 数据模型又是瞬态模型的父类, 因此,抽象模型实际上是所有模型的父类.
下面我们开始介绍基础模型的概念.
class BaseModel(metaclass=MetaModel):
...
基础模型是元模型(MetaModel)的子类. 元模型的主要用途就是检查模块是否已经注册,没有注册的话则注册模块.
我们知道在继承某个模型的时候,可以不用使用_name属性, 系统会自动分配_name. 而_inherit可以支持一个数组或字符串.实际上,这两个特性都是在元模型中完成判断的.
在15.0之前,模块的加载路径不需要路径校验, 而15.0开始,系统会在元模型中校验模块的前缀是否包含odoo.addons.
系统会自动初始化一个每个数据库中的已经注册的模型的实例,这些实例代表了每个数据库中可以使用的模型。每个实例都是一个有序的记录集,可以通过browse或者search等方法返回包含符合条件的记录的记录集。
如果不希望系统在初始化的时候实例化某个模型,那么可以将它的_register属性设置为False.
基础属性
基础模型包含了一些技术性的参数:
- _auto: 是否自动创建数据库中的数据表,默认False(Mdoel中为True)
- _regitster: 是否注册,不注册将在ORM中不可用
- _abstract: 是否是抽象模型
- _transient:是否是临时模型
- _table: 指定数据库中的表名
- _sequence: Id字段使用的SQL中的序号
- _sql_constraints:数据库中的限制条件
- _date_name: 日历视图中使用的字段,默认date
- _fold_name: 看板视图中使用的字段,默认fold
- _rec_name: _rec_name用于指定显示在Many2one类型的搜索中的显示字段,可以简单地理解为该模型的名称。默认情况下,_rec_name取的是name字段。
- _rec_names_search: 在name_search方法中可以搜索的字段列表。
_rec_names_search属性在16.0+版可以使用
_auto指定了该模型是否自动创建数据库表,如果设置False,那么用户需要在init方法中自行创建数据库表。
基础模型中定义了4个魔法字段,即出现在每个数据库表中的字段:
- create_uid:该条记录的创建人
- create_date: 该条记录的创建时间
- update_uid: 该条记录的更新人
- update_date: 该条记录的更新时间
- display_name: 显示名称
魔法字段在15.0中的定义已经移动到了元模型中
odoo中的每个注册完成的模型都会将自己的信息添加到系统记录中,具体来说:
- ir.model: 会将自己的反射数据插入到ir_model表中
- ir.model.fields:会将字段数据插入到ir_model_fields表中
- ir.model.fields.selection: 将字段的可选项插入到ir_model_fields_selection表中(此特性在13.0引入)
- ir.model.constraint: 将模型的约束插入到ir_modeLconstraint表中。
委托继承
我们在第一部分介绍了类继承和委托继承两种方式的区别, 现在我们来看一下委托继承是如何实现的.
首先, 基础模型在初始化的时候,初始化了_inherits属性, 这是一个frozendict类型的属性, 也就是说此属性一旦被设定将不可被更改.
然后, 系统在初始化模型属性的时候, 会遍历_inherits属性中的值, 将本模型的名称添加到父类模型的_inherits_children属性中.
在设置默认值方法中, 系统会默认值中的字段是否是从父类继承而来的, 如果是,则会跳过该字段.
系统会在_setup_base方法中, 读取本模型中的继承关系,并将委托继承中的字段添加到本模型中.
在fields_get方法中,也会将_inherits中的字段返回. 而且在创建方法中也会将委托字段的值写回到父类对象中.
常用方法
基础模型中定义了一些常用的方法, 下面详细介绍一下他们:
user_has_groups
判断用户是否在某个用户组中。
@api.model
def user_has_groups(self, groups):
....
groups是用户组的xmlid列表,用,分隔,例如:user.user_has_groups('`base.group_user,base.group_system')
with_context
with_context方法用来给当前记录集对象扩展新的上下文对象。
# 当前context对象内容{'key1':True}
r2 = records.with_context({},key2=True)
# r2._context 内容:{'key2':True}
r2 = records.with_context(key2=True)
# r2._context内容:{'key1':True,'key2':True}
如果当前上下文中对象_context中包含allowed_company_ids 那么,with_context方法在返回的结果中也会包含allowed_company_ids。
flush方法
BaseModel中还定义了一个flush方法,其作用是将所有挂起的计算更新到数据库中。
@api.model
def flush(self, fnames=None, records=None):
""" Process all the pending computations (on all models), and flush all
the pending updates to the database.
:param fnames (list<str>): list of field names to flush. If given,
limit the processing to the given fields of the current model.
:param records (Model): if given (together with ``fnames``), limit the
....
flush方法接收两个参数:
- fnames: 需要被更新的字段名称列表
- records: 需要被更新的记录集
如果没有传入任何参数,将更新所有挂起的计算。如果传入了fnames,将更新当前模型中需要被更新的字段列表。如果传入了records,将更新指定的记录集的值。
new方法
产生一个使用传入的值并附加在当前环境的新记录值。此值只存在内存中,尚未保存到数据库中。
@api.model
def new(self, values={}, origin=None, ref=None):
""" new([values], [origin], [ref]) -> record
Return a new record instance attached to the current environment and
initialized with the provided ``value``. The record is *not* created
in database, it only exists in memory.
One can pass an ``origin`` record, which is the actual record behind the
result. It is retrieved as ``record._origin``. Two new records with the
same origin record are considered equal.
One can also pass a ``ref`` value to identify the record among other new
records. The reference is encapsulated in the ``id`` of the record.
"""
if origin is not None:
origin = origin.id
record = self.browse([NewId(origin, ref)])
record._update_cache(values, validate=False)
return record
从函数定义中,我们可以看到new方法返回一个记录值,该记录值的ID是我们前面提到过的NewId对象。
recompute
重新计算所有或指定的字段值。
@api.model
def recompute(self, fnames=None, records=None):
""" Recompute all function fields (or the given ``fnames`` if present).
The fields and records to recompute have been determined by method
:meth:`modified`.
"""
...
recompute接收两个参数:
- fnames: 需要重新计算值的字段名列表
- records: 需要重新计算值的记录集
如果不传fnames,则所有需要重新计算的字段都将重新计算。
记录的归档
Odoo原生支持可以将某条数据进行逻辑删除处理,这里的功能叫做归档。其使用方法是在模型中定义一个active字段。
active = fields.Boolean("Active",default=True)
这样系统会自动在列表视图中添加归档的动作。如果想要在form表单中也添加归档动作,只需要在视图文件中添加active字段并设置不可见即可。
瞬态模型
瞬态模型是指一种临时对象,它的数据将被系统定期清理,因此这就决定了它的使用场景不可以作为数据的持久化使用,只能作为临时对象使用。在odoo中,瞬态模型最常被使用的一种场景是作为向导。
向导是odoo中常见的一种操作引导方式,该方式通常由一个弹出式的窗体和若干字段、按钮组成。用户通过向导可以选择指定特定的字段值,然后进行下一步的操作。向导背后的技术即用到了瞬态模型,作为一种临时性的数据存储方案,向导的数据不会长期留存在数据库中,会由系统定期进行清理。
可以在配置文件中指定瞬态模型的保留的记录大小和时间长短,详细内容参见第十七章。 13.0及更早的版本对于瞬态模型是不需要设置访问权限的, 从14.0开始瞬态模型也需要在模块中预设权限.
数据模型
与TransientModel相对的就是数据的持久化存储方案,即Model对象,odoo中的绝大多数对象都是由Model继承而来的。
Model是继承基础模型(BaseModel)而来, 基础模型是odoo所有类的基类。实例化类对象的方式即继承自Model、TransientModel或AbstractModel中的一种。每个类的实例都是一个有序的记录集合(RecordSet)。如果希望创建一个不被实例化的类,可以把_register属性设置为False。
默认情况下,Model和TransientModel的子类在初始化的过程中都会自动创建数据库表,如果不希望自动创建数据库表,可以将类的_auto属性设置为False。但创建没有数据库表的模型的更推荐的方式是使用抽象类(AbstractModel)。