06. 模型和字段
在上章节中,我们已经学习并创建了一个新的 Odoo 模块。但它仍是一个空壳,没有菜单也没有任何操作,也不能存储任何数据。接下来将学习如何在 estate 模块中,实现将房产相关的信息(名称、描述、价格、居住面积等)存储在数据库中。
在继续本小节实践之前,请确保 estate 模块已安装,即在“应用程序”列表中, estate 模块的状态应显示为“已安装”。
注:请勿使用可变的全局变量。
一个 Odoo 实例可以在同一个 Python 进程中并行运行多个数据库。由于每个数据库可能安装了不同的模块,因此不能依赖那些会根据已安装模块而发生变化的全局变量。
对象关系映射
首先,创建名称为 estate_property 的数据库表:
$ psql -d odoodb
rd-demo=# SELECT COUNT(*) FROM estate_property;
count
-------
0
(1 row)Odoo 的一个关键组件是 ORM 层。ORM 层避免了手动编写大部分 SQL 的需求,并提供了可扩展性和安全服务。业务对象标准的做法是继承自 odoo.models.Model 类,从而将业务对象集成到自动持久化系统中。
可以通过在模型定义中设置属性来配置模型。一个非常重要的属性是 _name,该属性为必填项,用于定义模型在 Odoo 系统中的名称。以下是一个模型的最简定义:
from odoo import models
class Property(models.Model):
_name = "estate_property"
_description = "The Real Estate Advertisement module."以上定义让 ORM 自动生成一个名称为 estate_property 的数据库表。按照惯例,所有模型都位于模块目录下的 models 目录中,且每个模型都在各自的 Python 文件中定义。
定义房地产属性模型
下面是 estate_property 表相应的文件和文件夹。
from odoo import models
class Property(models.Model):
_name = "estate.property"
_description = "房地产模型"以上定义让 ORM 自动生成一个名称为 estate_property 的数据库表,可以看到数据库表名与模型中 _name 属性的值相关联,Odoo 系统把 estate.property 替换成为 estate_property 来命名数据库中表的名称。按照惯例,所有模型都位于模块目录下的 models 目录中,且每个模型都在各自的 Python 文件中定义。
定义房地产属性模型
实践一: 创建 estate.property 模型相应的文件和文件夹。具体如下所示:
创建模型文件:estate/models/property.py,详细内容如下:
from odoo import models
class Property(models.Model):
_name = "estate.property"创建 estate/__init.py 文件,并导入文件模型包:
from . import models模型包文件:estate/models/__init.py
from . import property文件创建完成后,使用以下脚本重启服务器:
.\venv\Scripts\python.exe odoo-bin.py `
--db_host 127.0.0.1 --db_port=5432 -r userodoo `
-w 123456 -d odoodb --addons-path=addons -u estate `
--limit-time-real=120000或者( bat 脚本):
.\venv\Scripts\python.exe odoo-bin.py ^
--db_host 127.0.0.1 --db_port=5432 -r userodoo ^
-w 123456 -d odoodb --addons-path=addons -u estate ^
--limit-time-real=120000-u estate 表示要升级 estate 模块,即 ORM 将应用数据库模式变更,它会创建模型中对应的新表。-d odoodb 表示在 odoodb 这个数据库上执行升级。-u 必须始终与 -d 配合使用。
注:对 Python 文件进行任何修改都需要重启 Odoo 服务器,才能生效。
启动过程中,可以会看到类似以下警告内容:
2026-08-03 08:03:15,217 8956 INFO doodb odoo.modules.loading: Loading module estate (2/15)
2026-08-03 08:03:15,278 8956 WARNING doodb odoo.registry: The model estate.property has no _description
2026-08-03 08:03:15,323 8956 INFO doodb odoo.registry: module estate: creating or updating database tables
2026-08-03 08:03:15,777 8956 INFO doodb odoo.addons.base.models.ir_module: module estate: no translation for language zh_CN
2026-08-03 08:03:15,811 8956 WARNING doodb odoo.modules.loading: The models ['estate.property'] have no access rules in module estate, consider adding some, like:
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,0,0,0
2026-08-03 08:03:15,857 8956 INFO doodb odoo.modules.loading: Module estate loaded in 0.64s, 37 queries (+37 other)
2026-08-03 08:03:15,864 8956 WARNING doodb odoo.registry: The model estate.property has no _description
2026-08-03 08:03:15,937 8956 WARNING doodb odoo.registry: The model estate.property has no _description
2026-08-03 08:03:16,242 8956 WARNING doodb odoo.registry: The model estate.property has no _description
2026-08-03 08:03:16,256 8956 INFO doodb odoo.modules.loading: 15 modules loaded in 1.04s, 37 queries (+37 extra)从上面输出结果中可以看到,已经正常启动!为了保险起见可使用 psql 查看创建的表信息再次确认。数据库表中缺少了一个字段,需要在模型中添加一个 _description 字段,即可消除其中一条警告。
到了这个阶段,数据库中已经创建了 estate_property,表的结构如下:
CREATE TABLE "public"."estate_property" (
"id" int4 NOT NULL DEFAULT nextval('estate_property_id_seq'::regclass),
"create_uid" int4,
"write_uid" int4,
"create_date" timestamp(6),
"write_date" timestamp(6),
CONSTRAINT "estate_property_pkey" PRIMARY KEY ("id"),
CONSTRAINT "estate_property_create_uid_fkey" FOREIGN KEY ("create_uid") REFERENCES "public"."res_users" ("id") ON DELETE SET NULL ON UPDATE NO ACTION,
CONSTRAINT "estate_property_write_uid_fkey" FOREIGN KEY ("write_uid") REFERENCES "public"."res_users" ("id") ON DELETE SET NULL ON UPDATE NO ACTION
)
;
ALTER TABLE "public"."estate_property"
OWNER TO "userdoo";
COMMENT ON COLUMN "public"."estate_property"."create_uid" IS 'Created by';
COMMENT ON COLUMN "public"."estate_property"."write_uid" IS 'Last Updated by';
COMMENT ON COLUMN "public"."estate_property"."create_date" IS 'Created on';
COMMENT ON COLUMN "public"."estate_property"."write_date" IS 'Last Updated on';
COMMENT ON TABLE "public"."estate_property" IS 'estate.property';模型属性
属性(也叫字段,一般 Python 类中叫属性,数据表中叫字段)用于定义模型可以存储哪些数据以及这些数据存储的位置。在模型类中可以定义各种属性。如下一个简单的示例:
from odoo import fields, models
class Property(models.Model):
_name = "estate_property"
_description = "房地产模型"
name = fields.Char()name 字段是 Property 类的一个字符型属性,系统表示为 Python 的 Unicode 字符串,在数据库中存储为 SQL 的 VARCHAR 类型。
实践二: 在 estate.property 模型中添加一些属性,这些属性最终会被创建并存储在数据库表中。属性大致可分为两类:
- “简单”属性,即直接存储在模型表中的原子值;
- “关系”属性,用于链接(同一模型或不同模型的)记录。
在 estate.property 模型中添加基本属性。在对应的 estate_property 表中添加以下基本字段:
| 字段 | 类型 | 备注 |
|---|---|---|
| name | Char | 名称 |
| description | Text | 描述 |
| postcode | Char | 邮编 |
| date_availability | Date | 有效期 |
| expected_price | Float | 期望价 |
| selling_price | Float | 实际售价 |
| bedrooms | Integer | 房间数 |
| living_area | Integer | 居住面积 |
| facades | Boolean | 立面/外表 |
| has_garage | Boolean | 是否有车库 |
| has_garden | Boolean | 是否有庭院 |
| garden_area | Integer | 庭院面积 |
| garden_orientation | Selection | 朝向 |
garden_orientation 字段包含 4 个可能的取值:“朝北”、“朝南”、“朝东”和“朝西”。下拉列表定义为元组列表,示例请参见 - 此处 。
继续打开文件:models/property.py,在 Property 类中添加以下属性:
from odoo import models, fields, api, exceptions
from odoo.tools.float_utils import float_compare, float_is_zero
from odoo import models
from datetime import datetime
class Property(models.Model):
_name = "estate.property"
_description = "房地产模型"
name = fields.Char("标题/名称", required=True, translate=True)
description = fields.Text("描述", )
postcode = fields.Char("邮编")
date_availability = fields.Date(string="有效期", required=True, default=datetime.now(), copy=False)
expected_price = fields.Float("期望价格", required=True)
selling_price = fields.Float("实际售价", readonly=True, copy=False)
bedrooms = fields.Integer("房间数量", default=2)
living_area = fields.Integer("室内面积(M²)")
has_garage = fields.Boolean("是否有车库")
has_garden = fields.Boolean("是否有花园")
garden_area = fields.Integer("花园面积(M²)", copy=False)
garden_orientation = fields.Selection(
string='朝向',
selection=[('north', '朝北'), ('south', '朝南'), ('east', '朝东'), ('west', '朝西')],
help="“朝向”可用于区分潜在客户和商机")Odoo 会根据上面模型的定义自动创建相应的数据库表:estate_property。数据库表字段的数据类型有:布尔型、浮点型、字符型、文本型、日期型和选择型。
将字段添加到模型后,然后使用 -u estate 参数重启服务器。
$ ./odoo-bin --addons-path=addons,dev-addons/ -d odoodb -u estate连接到 psql 并查看 estate_property 表的结构,就会发现新增了几个字段。
$ psql -d ododb
ododb=# \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 | | |
has_garage | boolean | | |
has_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字段大致可分为两类:“简单”字段,即直接存储在模型表中的原子值;以及“关系”字段,用于链接(同一模型或不同模型的)记录。
简单字段的示例包括:布尔型、浮点型、字符型、文本型、日期型和选择型。
在 estate_property 表中添加基本字段。在该表中添加以下基本字段:
| 字段 | 类型 | 备注 |
|---|---|---|
| name | Char | |
| description | Text | |
| postcode | Char | |
| date_availability | Date | |
| expected_price | Float | |
| selling_price | Float | |
| bedrooms | Integer | |
| living_area | Integer | |
| facades | Boolean | |
| garage | Boolean | |
| garden_area | Integer | |
| garden_orientation | Selection |
garden_orientation字段必须包含 4 个可能的取值:“North”、“South”、“East”和“West”。下拉列表定义为元组列表,示例请参见此处。
将字段添加到模型后,然后使用 -u estate 参数重启服务器。
$ ./odoo-bin --addons-path=addons,../dev-addons/ -d dev-demo -u estate连接到 psql 并查看 estate_property 表的结构,就会发现该表还新增了几个字段。我们稍后会再讨论这些字段。
常见属性
目标在本节结束时,表 estate_property 中的 name 和 expected_price 列应设置为不可为空:
dev-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 在该列上创建数据库索引。
为现有字段设置属性。添加以下属性(required=True):
| 字段 | 属性 | 备注 |
|---|---|---|
| name | required | |
| expected_price | required |
重启服务器后,这两个字段都应设置为不可为空。
自动字段
参考:有关此主题的文档请参见“自动字段”。
可能已经注意到,模型中存在一些您从未定义过的字段。Odoo会在所有模型中创建几个字段。这些字段由系统管理,无法进行写入操作,但在有用或必要时可以进行读取:
id (Id)- 该模型记录的唯一标识符。create_date (Datetime)- 记录的创建日期时间。create_uid (Many2one)- 创建该记录的用户。write_date (Datetime)- 记录的最后修改日期时间。write_uid (Many2one)- 最后修改该记录的用户。
虽然可以编写原始 SQL 查询,但需谨慎操作,因为这会绕过 Odoo 的所有身份验证和安全机制。