第 3 章:模型与基本字段

在 上一章 末尾,我们成功创建了一个 Odoo 模块。然而,此时它仍然是一个无法存储任何数据的空壳。在我们的房地产模块中,我们希望把与房产相关的信息(名称、描述、价格、居住面积……)存储在数据库中。Odoo 框架提供了便于进行数据库交互的工具。

在继续练习之前,请确保 estate 模块已安装,即它必须在应用列表中显示为“已安装”。

警告

不要使用可变的全局变量。

单个 Odoo 实例可以在同一个 python 进程中并行运行多个数据库。每个数据库上可能安装不同的模块,因此我们不能依赖那些会随已安装模块而变化、需要被更新的全局变量。

对象关系映射

参考:本主题相关的文档可在 型号 API 中找到。

注解

目标:本节结束时,应已创建表 estate_property:

$ psql -d rd-demo
rd-demo=# SELECT COUNT(*) FROM estate_property;
count
-------
    0
(1 row)

Odoo 的一个关键组件是 ORM 层。这一层免去了手动编写大部分 SQL 的工作,并提供可扩展性与安全服务2。

业务对象被声明为继承 Model 的 Python 类,从而被集成到自动持久化系统中。

模型可以通过在其定义中设置属性来进行配置。最重要的属性是 _name,它是必需的,用于定义模型在 Odoo 系统中的名称。以下是一个模型的最小定义:

from odoo import models

class TestModel(models.Model):
    _name = "test_model"

这个定义足以让 ORM 生成一个名为 test_model 的数据库表。按照约定,所有模型都位于 models 目录中,且每个模型都定义在各自的 Python 文件里。

请看看 crm_recurring_plan 表是如何定义的,以及对应的 Python 文件是如何被导入的:

  1. 该模型定义在文件 crm/models/crm_recurring_plan.py 中(参见 此处)

  2. 文件 crm_recurring_plan.py 在 crm/models/__init__.py 中被导入(参见 此处)

  3. 文件夹 models 在 crm/__init__.py 中被导入(参见 此处)

练习

定义房地产的房产模型。

参照 CRM 模块中的示例,为 estate_property 表创建相应的文件和文件夹。

创建好这些文件后,为 estate.property 模型添加一个最小定义。

对 Python 文件的任何修改都需要重启 Odoo 服务器。重启服务器时,我们会加上参数 -d 和 -u:

$ ./odoo-bin --addons-path=addons,../enterprise/,../tutorials/ -d rd-demo -u estate

-u estate 表示我们希望升级 estate 模块,即 ORM 将应用数据库架构变更。在本例中,它会创建一个新表。-d rd-demo 表示升级应在 rd-demo 数据库上执行。-u 应始终与 -d 一起使用。

启动过程中,你应该会看到以下警告:

...
WARNING rd-demo odoo.models: The model estate.property has no _description
...
WARNING rd-demo odoo.modules.loading: The model estate.property has no access rules, consider adding one...
...

如果确实如此,那就没问题了!为确保无误,请按 目标 中的演示用 psql 再核对一次。

练习

添加描述。

为你的模型添加 _description,以消除其中一条警告。

模型字段

参考:本主题相关的文档可在 字段 API 中找到。

字段用于定义模型能存储什么数据以及这些数据存放在何处。字段在模型类中以属性的形式定义:

from odoo import fields, models

class TestModel(models.Model):
    _name = "test_model"
    _description = "Test Model"

    name = fields.Char()

name 字段是一个 Char,它会表示为 Python 的 unicode str 和 SQL 的 VARCHAR。

类型

注解

目标:本节结束时,应已向表 estate_property 添加若干基本字段:

$ psql -d rd-demo

rd-demo=# \d estate_property;
                                            Table "public.estate_property"
    Column       |            Type             | Collation | Nullable |                   Default
--------------------+-----------------------------+-----------+----------+---------------------------------------------
id                 | integer                     |           | not null | nextval('estate_property_id_seq'::regclass)
create_uid         | integer                     |           |          |
create_date        | timestamp without time zone |           |          |
write_uid          | integer                     |           |          |
write_date         | timestamp without time zone |           |          |
name               | character varying           |           |          |
description        | text                        |           |          |
postcode           | character varying           |           |          |
date_availability  | date                        |           |          |
expected_price     | double precision            |           |          |
selling_price      | double precision            |           |          |
bedrooms           | integer                     |           |          |
living_area        | integer                     |           |          |
facades            | integer                     |           |          |
garage             | boolean                     |           |          |
garden             | boolean                     |           |          |
garden_area        | integer                     |           |          |
garden_orientation | character varying           |           |          |
Indexes:
    "estate_property_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
    "estate_property_create_uid_fkey" FOREIGN KEY (create_uid) REFERENCES res_users(id) ON DELETE SET NULL
    "estate_property_write_uid_fkey" FOREIGN KEY (write_uid) REFERENCES res_users(id) ON DELETE SET NULL

字段大体分为两类:“简单”字段,即直接存储在模型表中的原子值;以及“关联”字段,用于链接记录(同一模型或不同模型的记录)。

简单字段的例子包括 Boolean、Float、Char、Text、Date 和 Selection。

练习

向房地产房产表添加基本字段。

向表中添加以下基本字段:

字段

类型

name

字符型

description

文本

postcode

字符型

date_availability

日期

expected_price

浮动

selling_price

浮动

bedrooms

整数

living_area

整数

facades

整数

garage

布尔值

garden

布尔值

garden_area

整数

garden_orientation

选择

garden_orientation 字段必须有 4 个可选值:’North’、’South’、’East’ 和 ‘West’。选择列表定义为一个元组列表,示例参见 此处。

将字段添加到模型后,使用 -u estate 重启服务器。

$ ./odoo-bin --addons-path=addons,../enterprise/,../tutorials/ -d rd-demo -u estate

连接到 psql 并检查表 estate_property 的结构。你会注意到表中还额外添加了几个字段。我们稍后会再讨论它们。

常用属性

注解

目标:本节结束时,表 estate_property 中的列 name 和 expected_price 应为非空:

rd-demo=# \d estate_property;
                                            Table "public.estate_property"
    Column       |            Type             | Collation | Nullable |                   Default
--------------------+-----------------------------+-----------+----------+---------------------------------------------
...
name               | character varying           |           | not null |
...
expected_price     | double precision            |           | not null |
...

与模型本身类似,字段可以通过将配置属性作为参数传入来进行配置:

name = fields.Char(required=True)

某些属性适用于所有字段,以下是最常用的几个:

string (str,默认值:字段名)

字段在界面中显示的标签(用户可见)。

required (bool,默认值:False)

如果为 True,则该字段不能为空。它必须具有默认值,或者在创建记录时始终被赋予一个值。

help (str,默认值:'')

在 UI 中为用户提供详细说明的提示信息。

index (bool,默认值:False)

请求 Odoo 在该列上创建 database index。

练习

为现有字段设置属性。

添加以下属性:

字段

属性

name

required

expected_price

required

重启服务器后,这两个字段都应为非空。

自动字段

参考:本主题相关的文档可在 自动字段 中找到。

你可能已经注意到,你的模型中有几个从未定义过的字段。Odoo 会在所有模型中创建一些字段1。这些字段由系统管理,不能被写入,但如有需要,可以读取它们:

id (Id)

模型中记录的唯一标识符。

create_date (Datetime)

记录的创建日期。

create_uid (Many2one)

创建该记录的用户。

write_date (Datetime)

记录的最后修改日期。

write_uid (Many2one)

最后修改该记录的用户。

既然我们已经创建了第一个模型,那就来 添加一些安全性 吧!

1

可以 禁用某些字段的自动创建

2

编写原生 SQL 查询是可行的,但需要谨慎,因为这会绕过 Odoo 的所有身份验证和安全机制。