Alan Hou的个人博客

Odoo插件应用的模型(Models)深入探讨

这是Odoo系列文章的第五篇,完整目录请见最好用的免费ERP系统Odoo 11开发指南

以下开发均假设读者已完成第四篇的代码,并且所有代码更新后均需自行更新方会在客户端看到变化。如未阅读该篇,请参考代码:Github存储代码

模型显示及排序

记录表示(record representation)还有一个Odoo 8.0的新增的魔术字段display_name,其值通过name_get()方法生成。默认情况下name_get()使用_rec_name属性来查找该数据,如需更复杂逻辑仅需重写该方法,返回一个包含记录id和记录表示的元组即可。要显示为如平凡的世界 (2017-06-01),在类中定义如下方法

模型中的数据类型讲解

从上例中我们也可以看出字段中可以包含属性:

扩展知识

Selection字段除了可像前例中包含选项外,还可进行方法引用 ,动态获取选项列表,本文稍后会进行演示。

Date和Datetime字段有一些工具方法让操作更加方便,Date拥有的方法:

Datetime拥有的方法:

除了基础字段外,还有关系型字段:Many2one, One2many以及Many2many,后面会进行更深入的探讨。此外还有自动计算值的字段 compute,也在后面介绍。

细心的读者会发现Odoo模型还会默认创建一些字段,所以在定义模型时请不要使用这些名称。这些字段名包含自动生成的标识符id,还有一些日志审计字段:

这些自动创建的日志字段,可通过加入_log_access=False属性来进行关闭。另一个可添加到模型中的特别字段名是active,它是一个标识记录是否可用的布尔字段:

默认情况下只有active设为True的才可见,而要获取不可见记录,可通过域控制器[(‘active’, ‘=’, False)],另一种方法是将‘active_test’: False加入到环境上下文中,否则ORM无法获取不可用记录。

小知识:有时无法通过修改上下文来同时获取可见和不可见记录,这时使用[‘|’, (‘active’, ‘=’, True), (‘active’, ‘=’, False)]域。注意[(‘acitve’, ‘in’ (True, False))]并不会像你所预期的起到相似作用。

浮点数字段的精度配置

在使用浮点数字段时,可能需要终端用户来配置精度,下面我们用Decimal Precision Configuration插件来实现,然后在Library Book模型中加上一个成本字段供用户指定精度。

1.首先在后台中点击Apps搜索到Decimal Precision Configuration点击Install进行安装

2.开启开发者模式点击Settings > Technical > Database Structure > Decimal Accuracy,添加一个Book Price并设置精度为小数点后两位

3.添加依赖

4.添加cost_price并使用前述精度设置

 

通过以上方法可以让终端用户在后台中自行控制精度。

在模型中添加货币(Monetary)字段

Monetary字段需要一个额外的currency字段来存储金额:

更新后可在Settings > Technical > Database Structure > Models中看到相应的变化,currency字段通常使用currency_id,当然也可使用其它名称,此时就需要通过可选的currency_field属性来进行指定。

小技巧:这点非常有用,我们可以通过设置多个fields.Many2one(res.currency)来维护不同货币,比如一个销售订单货币,一个公司结算货币。

为模型添加关系型(relational)字段

Odoo的模型间关系通过关系型字段表示,有三种不同关系:many-to-one(缩写为m2o)、one-to-many(缩写为o2m)、many-to-many(缩写为m2m)。以我们的图书案例而言,每本书有一个出版社,所以书和出版社间是many-to-one的关系;而以出版性的视角来看,其与书的关系为one-to-many;每本书可以有多个作者,而每个作者也可以有多本书,所以作者与书的关系为many-to-many。

Odoo中使用Partner模型res.partner来表示人、机构和地址,所以此处作者和出版社则使用res.partner:

更新后可在Settings > Technical > Database Structure > Models中看到相应的变化

扩展知识

Many2one字段有一个auto_join属性可允许ORM在此字段上使用SQL合并,这会不受用户权限控制以及记录权限的限制。在特定情况下这会提高性能,但一般不建议这么使用。

前述定义关联字段的方式较为简洁,出于介绍的完整性进行如下补充:

One2many字段属性:

Many2many字段属性:

对于Many2many关联,大多数情况下ORM都会很好地处理这些属性的默认值,甚至发现反向Many2many关联以及已存relation表,并妥善翻转column1和column2的值。但在两种情况下需人为干预,一种情况是在两个模型间需要一个以上的Many2many关联,这时我们需指定relation属性以避免冲突;另一种情况是自动生成的关联表名称长度超过PostgreSQL数据库对象名称的63个字符上限。

自动生成的关联表名称为<model1>_<model2>_rel,但同时还会为关联表主键创建一个索引,标识符为<model1>_<model2>_rel_<model1>_id_<model2>_id_key,它同样受63个字符上限的限制。

为模型添加等级(hierarchy)

等级表现为与自身的模型关联,每条记录在同一模型中有一个父级记录以及一些子记录,这可以通过与模型自身的many-to-one关联实现。Odoo还为这类字段添加了Nested set model的支持,激活后在域过滤中使用child_of操作符的查询会有明显的提速。

下面我们来创建一个等级分类树来对书本分类,新增一个library_book_categ.py文件:

1. 在__init__.py添加代码以载入该文件

2.添加library_book_categ.py文件内容

扩展知识:以上方法用于“静态”等级中,也就是说常用操作为读取和查询 ,而很少会进行更新。显然图书的分类比较固定,读者也常常会按分类进行搜索,因而非常适用。这么说的原因在于嵌套集模型(Nested Set Model)的使用要求在新类的插入、删除或修改时parent_left和parent_right及其它相关数据库索引均需更新,在并行事务同时运行时这会带来很大的系统开销。

小技巧:在处理动态等级结构时,标准的parent_id和child_ids关联会通过避免表级锁来提高性能。

为模型添加约束验证

模型可通过验证来避免出现非预期情况,这一约束有两种:一种是数据库级的检查,另一种是服务器级的检查。前者仅限于PostgreSQL支持的约束,最常用的就是UNIQUE约束,此外还有CHECK和EXCLUDE约束,如果这些都无满足需求,则可通过书写Python代码在应用Odoo服务器级别的约束。

以下我们会添加一个数据库约束来防止重复的书名,Python模型约束在避免发行日期晚于当前日期:

1.创建一个数据库约束

2.通过python代码在类中添加一个服务器级约束

注意:_constraints模型属性依然存在,在8.0版本后弃用,推荐使用@api.constrains装饰器

为模型添加可计算(computed)字段

有时我们需从本记录或关联记录中计算获取字段值,常见的有根据单价和数量计算总价,在Odoo中通过可计算字段来实现。我们将在本例中根据发行日期计算天数来学习这一知识。可计算字段同样也可以被编辑和搜索。

 

暴露其它模型中存储的关联字段

Odoo客户端在从服务器中读取数据时,仅能获取查询模型中的字段值,客户端无法像服务器端那样使用点标记来获取数据。但是通过添加为关联字段则可进行获取,下面我们对出版商的城市进行这一操作。

关联字段和普通字段相似,但多了一个related属性,采取另一套遍历链。本例中通过publisher_id来获取出版社相关记录,然后读取city字段,也以使用更长的链式结构,如publisher_id.country_id.country_code。上面我们还使用了readonly属性,否则的话用户可以修改值,这就会更改掉关联出版商的城市。

扩展知识

关联字段实际上是可计算字段,仅仅通过快捷方式读取关联模型中的值。既然是可计算字段,store字段也可以用在这里,它也可以引用关联字段中的name, translatable和required等属性。此外还有类似compute_sudo的related_sudo,设为True,字段链式遍历不会进行用户权限检查。

小技巧:在关联字段中使用create()会损害性能,因为计算会等到创建完成后进行。因而如果有One2many关联,比如sale.order和sale.order.line模型,并在line模型中引用order模型中的字段,需在记录创建时显式读取order模型中的字段,而不使用关联字段快捷方式,在有多条line记录尤其应当如此。

使用引用(Reference)字段添加动态关联

使用关联字段,我们需事先决定关联的目标模型(comodel),但这有时需让用户来决定,我们只要设置好想要用到的模型和指向的记录即可,这Odoo中用相用字段来实现

引用字段与many-to-one字段相似,区别在于它允许用户选择指向的模型。目标模型可从selection字段指定的列表中选择,这一列表中需包含一组带有两个参数的元组,第一个参数为模型内部标识符,第二个参数为文字描述。例如

[(‘res.users’, ‘User’), (‘res.partner’, ‘Partner’)]

一般不使用固定列表,我们可以让终端用户来配置模型列表,这也是内置Settings > Technical > Database Structure下的Referencable Models的目的所在,该模型的内置标识符为res.request.link。以上我们使用了一个方法浏览所有可被引用以动态创建给selection属性列表的模型记录,虽然selection后可直接引用方法,但我们这里添加了引号,这种方式更为灵活,它允许方法在声明字段后进行定义。使用了@api.model装饰器是因为在模型级别上操作而非记录集。

注意:这种方式虽然很好用,但负载比较大,比如以列表视图显示大量记录引用字段时会因每个值进行一次单独查询而带来数据库的压力,同时也不能像普通关联字段那样用到数据库的引用完整性。

使用继承为模型添加特征(features)

Odoo的一个重要特性是能够在模块插件中继承其它模块插件的特征,而无需对原特征进行编辑。特征可以为添加字段或方法、修改现存字段或继承已有方法来添加逻辑。这是最常用的继承方法,官方文档中称之为传统/经典继承(traditional/classical inheritance)。我们将一起继承内置的Partner模型,将有作者书本数量添加到可计算字段中。

扩展知识:

使用_inherit传统继承,也可以将父级模型的特征拷贝到新模型中,通过在类中给_name属性加一个不同的标识符即可,如

新模型会拥有独立于res.partner父模型以外的自己的数据表,因其继承自Partner模型,后续更改也会在新模型中体现。官方文档中称之为原型继承(prototype inheritance),实际应用中很少使用,因为委托继承(delegation inheritance)以更高效的方式满足了这一需求,它无需复制一套数据结构,在后面就会介绍到。

使用抽象模型来复用模型特征

有时会存在想要在多个模型中使用的特殊特征,重复编码不是一个好的代码习惯,那么最好自然是一次编写多次复用。抽象模型可以用于编写通用模型,将其中的特征供其它普通模型继承使用。下面我们编写一个存档特征,它将active字段添加到模型中并开放存档方法供切换active标识。这个起作用是因为active是一个魔术字段,如果在模型中默认存在,active=False的记录会查询中被过滤掉。

Archive特性可以单独成为一个插件,但为了结构简洁,依然使用我们一直以来的library_book.py文件:

扩展知识

值得一提的是有一个内置抽象模型mail.thread,由mail插件模型提供,它可以在模型中激活讨论特征(用于很多表单底部的信息墙)。除了AbstractModel外,还有一个models.TransientModel。表现类似models.Model,但所创建记录为临时性的,会被服务端定时任务按时清理,其它的都和普通模型相同。对于更为复杂的用户交互(称作向导wizards)它会比较有用,比如要求用户输入内容来运行进程或报告。

使用委托(delegation)继承向另一个模型拷贝特征

传统继承通过_inherit来继承模型特征进行修改,但还存在是修改已存在模型的情况,仅仅是想要使用一些现有特性,这在Odoo中以_inherits用委托继承来实现。传统继承不同于面向对向编程的概念,而委托代理则更为相似,它可以创建包含父级模型特征的新模型,并且支持多态继承,即从两个或以上的模型中继承。

接下来我们来添加图书会员,需要Partner模型中所存在的身份和地址数据,以及开始日期,终止日期和卡号等会员所用到的信息。

 

创建会员时,数据库中会在res_partner表中创建一条新记录,在library_member中创建一条新记录,library_member的partner_id字段设置为在res_partner中创建记录的id。会员记录和新的Partner记录自动关联。那么在删除会员时会发生什么呢?这通过ondelete的值来控制,这里用的cascade表示删除Partner时也会删除Member,更保守的设置是restrict,这时删除Partner并不会删除Member。

要注意委托继承仅能用于字段,无法用于方法。

扩展知识

委托继承的用户模型res.users,继承自res.partner,也就意味着User中存储的一些字段实际存储在Partner模型中(如name字段),创建用户时,也会自动创建一个Partner。

本章代码:Chapter 5

 

退出移动版