第三章 基本字段

上一章,我们介绍了如何从无到有地创建一个模块,本篇将介绍Odoo中常见得几种字段类型。

字段类型

由于Odoo使用了自己编写的ORM框架,因此它有着自己的一套与数据库映射的关系逻辑,简单地说就是由简单字段关系和复杂字段关系组成的。简单的字段关系比如Char,Integer,Float等,复杂字段关系如Many2one,Many2many等。下面就常见的几种字段类型进行简要的介绍。

通用字段属性

有些属性是字段共同拥有的,如下表:

属性名 解释
name 字段名
model_name 模型名称
store 是否存储在数据库中(默认True)
index 字段在数据库中是否索引(默认False)
manual 是否为自定义字段(默认False)
default 字段的默认值
string 字段的标签值
help 帮助提示的值
readonly 是否只读(默认False)
required 是否必填(默认False)
states 根据state设置reaonly和required属性
groups 可以访问该字段的组的xmlid
related_sudo 字段是否应该作为admin来访问

Char

字符类型,对应数据库中varchar类型,除了通用类型外接收另外两个参数:

  • size: 字符长度,超出的长度将被截断
  • trim: 默认True,是否字段值应该被去空白。

Text

文本类型,对应数据库中的text类型,不限长度,没有额外的参数。

Integer

整数类型, 对应数据库中的int4类型

Float

浮点数类型, 兼容数据库中int4, numberic, float8类型.

Float类型可以使用digits属性来指定小数的精度, 例如:

price = fields.Float("价格", digits=(16,3))

digits这里是一个元组,16代表整个数据的长度, 3代表小数的位数. 另外, digits也可以接收一个用来描述系统精度的文本字符串.例如,系统中预置的精度变量名称Discount, 那么,上述定义也可以写成这个样子:

price = fields.Float("价格", digits="Discount")

系统设置参考第十章小数精度一节

Html

Html类型,用于存储一段HTML代码,对应数据库中的text类型,接收下面几个参数:

  • sanitize:是否过滤安全字符(默认True)
  • sanitize_tags: 是否过滤的html标签(只接收白名单列表标签,默认True)
  • sanitize_attributes: 是否过滤的html属性(只接收白名单列表属性,默认True)
  • sanitize_style: 是否过滤style属性(默认False)
  • strip_style: 是否去除style属性中的空白(默认False)
  • strip_classes:是否去除class属性中的空白(默认False)

Date

日期类型,对应于数据库中的date类型,该字段包括如下几个特有方法:

  • today: 当前日期
  • context_today: 返回客户端时区的当前时间
  • to_date: 将时间值转换为date类型的时间
  • to_string: 将时间值转为文本

例如我们想要给某个日期字段设置默认的属性为当天,那么可以像下面这样写:

date_start = fields.Date("开始日期",default=fields.Date.today())

Datetime

时间类型,对应于数据库中的timestamp类型,该字段包括如下几个特有方法:

  • now: 当前时间
  • today: 当前日期
  • context_timestamp: 客户端时区的当前时间戳
  • to_datetime:将时间值转换成datetime类型
  • to_string: 将时间值转成文本

假如我们想要给某个时间字段设置默认当前时间,那么可以像下面这样写:

date_start = fields.Datetime("开始时间",default=fields.Datetime.now())

Binary

二进制文件类型,接收三个参数:

  • prefetch: 默认False
  • context_dependent:默认True
  • attachment:默认True,是否作为附件存储

二进制文件作为附件存储时存在服务器指定的文件夹路径中,否则存在数据库中,对应的数据库类型为bytea.

图片类型的字段在odoo中亦是使用Binary字段存储的,只不过视图渲染时使用了image部件。

二进制字段通常以base64编码的形式存储在数据库中,因此在展示时,需要将base64进行解码输出。另外,二进制文件在form表单中只读状态下表现为下载按钮,但不包含原始的文件名。如果想要显示文件名,需要使用另外一字段将文件名存储起来。可参考如下代码:

<field name="xls" filename="xls_name"/>
<field name="xls_name" invisible="1"/>

关于二进制字段更多的内容,参考第二部分的二进制与附件一章。

Selection

下拉选择类型,多选一类型,不同于Many2one类型,Selection类型在数据库中对应的类型是int4或varchar类型。

除了通用的属性,另外接收2个参数:

  • selection: 可选的范围值,值和名称组成的元组列表。
  • validate: 默认True,是否写入时校验。

Selection包含如下的属性或方法:

  • get_values: 返回可选的值列表。

从14.0版本开始Selection开始支持ondelete属性,并且如果Selection是通过继承方式拓展了选项,那么ondelete属性是必须要指定的。

例如,由笔者开发的百度地图视图模块在升级到14.0版本的过程中,就碰到了这个问题,因为百度地图视图模块新增了一个视图类型,因此,要指定ondelete方法来指明模块在卸载时,应该如何处理这个新增的类型数据。

type = fields.Selection(
        selection_add=[("bmap", "百度地图")], ondelete={'bmap': "set default"}, default="form")

可选的处理选项有:

  • set null:默认选项,将字段设置为False
  • cascade:设置了这个选项的记录将被一同删除
  • set default:将所有设置的了这个值的记录还原为默认值
  • :定制化处理方式,第一个参数为使用了此值的所有记录。

大多数情况下,我们在开发过程中只需要关注Selection字段的key部分, 极少数的情况下可能会有这样的需求, 想要获取到Selection字段的描述部分的值. 这种时候,我们就可以使用下面的方法获取:

selection_field = fields.Selection([('a','A'),('b','B')])
...

def get_selection_value(self): 
  description = dict(self._fields[selection_field].selection).get(self.selection_field)
  return description

Monetary

odoo中对于货币类型的数值单独做了一个字段用来处理,这个字段就是Monetary。Monetary的基本使用方法同Float类型一致,不同的是Monetary的值会更精确,同时Monetary的值还依赖于当前计算环境的币种。

默认情况下要使用Monetary字段,需要在当前模型中同时新增一个currency_id的Many2one的字段,该字段表明本模型使用的货币类型(res.currency)。

例如:

currency_id = fields.Monetary("res.currency","货币")
amount = fields.Monetary("小计")

在视图中,如果需要带上货币符号,我们可以使用monetary小部件:

<field name="amount_total" widget="monetary" options="{'currency_field': 'currency_id'}"/>

如果不想使用默认的currency_id字段,可以在options中传入指定的货币类型字段名。

Reference

引用类型(继承自Selection),对应数据库类型varchar,Reference字段不同于Many2*类型的地方在于Many2*类型的字段的comodel是固定的,而Reference可以提供一种动态的选择。

举个例子, 下图是某客户的项目管理中的截图, 其中有个商机或订单字段,需要关联销售订单(sale.order)或线索(crm.lead), 此时就可以使用Reference字段来完成这个任务:

Reference字段的定义示例:

lead = fields.Reference(
        selection=[('sale.order', '销售订单'), ('crm.lead', '商机/线索')],  string="商机或订单")

当前Reference字段所面临的两个问题, 一个是Reference所关联的对象并不支持domain的过滤, 另外一个问题是Reference在分组时并不能显示关联对象的名称, 而是显示一个诸如'sale.order,1'式的值. 关于这两个问题的解决方案, 将在后面的部分给予解答.

Many2one

多对一类型,对应于数据库中的类型是int4,相当于数据库主表中的外键。可选的参数如下:

  • comodel_name: 被关联的对象
  • domain: 过滤条件
  • context: 上下文
  • ondelete: 删除时的选项,可选set null(本字段设置null),restrict(严格控制,只有先删除本字段才允许删除)和cascade(级联删除本字段关联的所有记录)。
  • auto_join: 当字段被搜索时是否自动聚合,默认为False
  • delegate: 当设置为True时,关联对象的所有字段将在本字段变得可用。

One2many

一对多类型,返回值是一个关联对象的集合。接收的参数列表如下:

  • comodel_name: 关联对象名称
  • inverse_name: 在Many2one对象中的字段名 默认为None
  • domain: 过滤条件
  • context: 上下文
  • auto_join: 当字段被搜索时是否自动聚合,默认为False
  • limit: 读取的条数限制
  • copy: 是否允许拷贝(默认False)

对于One2many和Many2many对象,有几种特殊的操作命令, 具体如下列表:

命令符 解释 举例 命令结构*
CREATE 根据values里的值创建一条新记录 Command.create({'name':'张三'}) (0,0,{values})
UPDATE 根据values的值更新id对应的记录 Command.update(1,{'name':'王心怡'}) (1,ID,{values})
DELETE 删除id=ID的这条记录 Command.delete(1) (2,ID)
UNLINK 切断主从关系,但不删除该数据 Command.unlink(1) (3,ID)
LINK 为id=ID的数据添加关联关系,3的反向操作 Command.link(1) (4,ID)
CLEAR 删除所有的主从关系,等价于循环调用记录集中的UNLINK方法 Command.clear() (5,)
SET 用IDS中的记录替换原来的记录, 等价于先使用CLEAR命令再循环调用LINK命令 Command.set([1,2,3]) (6,0,[IDS])

*15.0版本之前的命令是需要自己进行拼接的, 具体地讲,每个命令字都对应一个由元组组成的数据结构, 很显然, 开发者需要记住命令字中的1-6所代表的含义,以及他们的参数信息. 而15.0版本开始, odoo这些命令用更加容易的方式替代了.

Many2many

多对多关系类型的字段,Many2many的命令字与One2many相同,与One2many类型不同的是, One2many要对关联对象有一个回溯本身对象的inverse_name字段,而Many2many字段并无此限制。

Many2many字段的主要属性参数如下:

  • comodel_name:关联对象名
  • relation:关系表名
  • column1:本对象的关联字段名
  • column2:关联对象的关联字段名
  • domain: 过滤条件
  • context: 上下文
  • limit: 读取条数限制

与One2many类似,但不同的是参数中多了一个relation,用来存储多对多关系的表名,需要注意的是,relation,column1和column2可以不提供,odoo会根据model名称自动生成,但是需要保证column1和column2是不同的值。

举例来说,我们在第二章中创建的图书模型, 它的作者字段仅仅是一个字符字段,并没有更多的信息可以提供. 现实世界中,一本书可能有多个作者,一个作者也会写多本书,所以它们之间更合理的关系应该是多对多。现在我们将其更改为Many2many类型的字段,并关联一个新创建的对象,book_store.author。·

from odoo import models, fields, api
from odoo.fields import Command

class book_store_author(models.Model):

  _name = "book_store.author"
  _description = "图书作者"

  name = fields.Char("姓名")

这里我们只简单地定义一个姓名字段. 然后我们将book_store.book对象的作者字段变更为Many2many:

author = fields.Many2many("book.author", string="作者")

为了展示Command命令的效果, 我们这里添加4个方法:

def button_create(self):
    """创建作者方法"""
    self.authors = [Command.create({
        "name": "瑶瑶"
    })]

def button_update(self):
    """更新作者方法"""
    self.authors = [Command.update(author.id, {
        "name": f"❤{author.name}"
    }) for author in self.authors]

def button_delete(self):
    """删除一个作者"""
    if not self.authors:
        raise AccessDenied("没有更多作者了")
    author_id = self.authors[0]
    self.authors = [Command.delete(author_id.id)]

def button_clear(self):
    """"删除所有作者"""
    self.authors = [Command.clear()]

分别点击这4个按钮可以查看相应的效果。

对于One2many的字段类型,存在一个反向关联的字段,而对于Many2many类型的字段,如何建立它的反向关联字段呢?答案参考附录。

Id

integer类型,对应数据库中的int4,针对与字段Id而专门设计的类型。Id字段是存储在数据库中的只读类型,不能被更新。

当我们使用self.id获取记录的id值时,实际上是读取了record本身的_ids的值,如果_ids的值为空,那么返回False。如果_ids的长度大于1,则触发类型错误(TypeError)。

虽然我们知道获取还可以通过self.ids获取记录集的id列表,但是他们却不是用的同样的原理,关于ids的更多知识参见模型一章。

计算值

上面所提到的字段类型都可以传入一个compute参数使之成为一个计算值。


def _get_name(self):
    self.name = "韩菱纱"

name = fields.Char(compute="_get_name",string="名称")

compute接一个计算方法,在方法内部设置该字段的值。计算值默认是不存储在数据库内的,如果要让他存储在数据库中,则需要搭配store=True参数进行使用。 另外,默认情况下,非存储值是不可以被搜索的。如果想要搜索计算字段,则需要使用search属性, 具体请参考新API一章.

总结

至此,我们学习了odoo的基本的字段类型,下一章我们将开始了解基本的视图结构,了解odoo视图的基本结构。

警告,添加自定义字段时,不要使用x这种奇怪的命名方式,x开头的字段会被系统识别为由界面上添加的字段,从而禁止你再次修改改字段。请记住一点,不要随便使用开发者模式添加或删除自己不知道后果的数据,否则,你的数据库可能很快就因为你不知道的原因而崩溃。

results matching ""

    No results matching ""