第二章 模型

与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中,瞬态模型最常被使用的一种场景是作为向导

向导是odoo中常见的一种操作引导方式,该方式通常由一个弹出式的窗体和若干字段、按钮组成。用户通过向导可以选择指定特定的字段值,然后进行下一步的操作。向导背后的技术即用到了瞬态模型,作为一种临时性的数据存储方案,向导的数据不会长期留存在数据库中,会由系统定期进行清理。

可以在配置文件中指定瞬态模型的保留的记录大小和时间长短,详细内容参见第十七章。

13.0及更早的版本对于瞬态模型是不需要设置访问权限的, 从14.0开始瞬态模型也需要在模块中预设权限.

数据模型

与TransientModel相对的就是数据的持久化存储方案,即Model对象,odoo中的绝大多数对象都是由Model继承而来的。

Model是继承基础模型(BaseModel)而来, 基础模型是odoo所有类的基类。实例化类对象的方式即继承自Model、TransientModel或AbstractModel中的一种。每个类的实例都是一个有序的记录集合(RecordSet)。如果希望创建一个不被实例化的类,可以把_register属性设置为False。

默认情况下,Model和TransientModel的子类在初始化的过程中都会自动创建数据库表,如果不希望自动创建数据库表,可以将类的_auto属性设置为False。但创建没有数据库表的模型的更推荐的方式是使用抽象类(AbstractModel)。

results matching ""

    No results matching ""