Skip to content

06. 模型和字段

在上章节中,我们已经学习并创建了一个新的 Odoo 模块。但它仍是一个空壳,没有菜单也没有任何操作,也不能存储任何数据。接下来将学习如何在 estate 模块中,实现将房产相关的信息(名称、描述、价格、居住面积等)并存储到数据库中。

在继续本小节实践之前,请确保 estate 模块已安装,即在“应用程序”列表中, estate 模块的状态应显示为“已安装”(“启用”按钮已隐藏)。

03-estate-installed.png

对象关系映射

首先,创建名称为 estate_property 的数据库表:

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

Odoo 的一个关键组件是 ORM 层,ORM 层避免了手动编写大部分 SQL 的需求,并提供了可扩展性和安全服务。

业务对象标准的做法是继承 odoo.models.Model 类,从而将业务对象集成到自动持久化系统中。

可以通过在模型定义中设置属性来配置模型。一个非常重要的属性是 _name,该属性为必填项,用于定义模型在 Odoo 系统中的名称。以下是一个模型的最简定义:

py
from odoo import models

class Property(models.Model):
    _name = "estate_property"
    _description = ""

以上定义让 ORM 自动生成一个名称为 estate_property 的数据库表。按照惯例,所有模型都位于模块目录下的 models 目录中,且每个模型都在各自的 Python 文件中定义。

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

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

文件 crm_recurring_plan.pycrm/models/__init__.py 中被导入(参见此处

文件夹 modelscrm/__init__.py 中被导入(参见[此处](odoo/addons/crm/init.py at e80911aaead031e7523173789e946ac1fd27c7dc · odoo/odoo · GitHub))

定义房地产属性模型

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

python
from odoo import models

class Property(models.Model):
    _name = "estate.property"
    _description = "房地产模型"

ORM 根据上面定义自动生成一个名称为 estate_property 的数据库表,可以看到数据库表名与模型中 _name 属性的值相关联,Odoo 系统把 estate.property 替换成为 estate_property 来命名数据库中表的名称。按照惯例,所有模型都位于模块目录下的 models 目录中,且每个模型都在各自的 Python 文件中定义。

可参考 crm_recurring_plan 表是如何定义的,以及相应的 Python 文件是如何导入:

  • 模型在文件 crm/models/crm_recurring_plan.py 中定义,参考链接
  • crm/models/__init__.py 中导入文件 crm_recurring_plan.py 定义的模型,参考链接
  • crm/__init__.py 中导入文件夹 models 中定义的模型,参考链接

注:请勿使用可变的全局变量。 一个 Odoo 实例可以在同一个 Python 进程中并行运行多个数据库。由于每个数据库可能安装了不同的模块,因此我们不能依赖那些会根据已安装模块而发生变化的全局变量。

定义房地产属性模型

实践一: 参照上面 CRM 模块中的示例,创建 estate.property 模型相应的文件和文件夹。具体如下所示:

创建模型文件:estate/models/property.py

python
from odoo import models

class Property(models.Model):
    _name = "estate.property"

在文件:estate/__init.py 中导入文件模型包:

python
from . import models

在文件:estate/models/__init.py 中导入文件模型包:

python
from . import property

文件创建完成后,使用以下脚本重启服务器:

powershell
.\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 脚本):

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 服务器,才能生效。

启动过程中控制台输出的日志记录,可以会看到类似以下警告内容(WARNING):

shell
2026-05-03 08:03:15,217 8956 INFO doodb odoo.modules.loading: Loading module estate (2/15)
2026-05-03 08:03:15,278 8956 WARNING doodb odoo.registry: The model estate.property has no _description
2026-05-03 08:03:15,323 8956 INFO doodb odoo.registry: module estate: creating or updating database tables
2026-05-03 08:03:15,777 8956 INFO doodb odoo.addons.base.models.ir_module: module estate: no translation for language zh_CN
2026-05-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-05-03 08:03:15,857 8956 INFO doodb odoo.modules.loading: Module estate loaded in 0.64s, 37 queries (+37 other)
2026-05-03 08:03:15,864 8956 WARNING doodb odoo.registry: The model estate.property has no _description
2026-05-03 08:03:15,937 8956 WARNING doodb odoo.registry: The model estate.property has no _description
2026-05-03 08:03:16,242 8956 WARNING doodb odoo.registry: The model estate.property has no _description
2026-05-03 08:03:16,256 8956 INFO doodb odoo.modules.loading: 15 modules loaded in 1.04s, 37 queries (+37 extra)

从上面输出结果中可以看到,已经正常启动!为了保险起见可使用 psql 查看创建的表信息再次确认。数据库表中缺少了一个字段,需要在模型中添加一个 _description 字段,即可消除其中一条关于_description的警告信息。

到了这个阶段,数据库中已经创建了 estate_property,表的结构如下:

sql
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 "odoo_user";

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 类中叫属性,数据表中叫字段)用于定义模型可以存储哪些数据以及这些数据存储的位置。在模型类中可以定义各种属性。如下一个简单的示例:

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 表中添加以下基本字段:

字段类型备注
nameChar名称
descriptionText描述
postcodeChar邮编
date_availabilityDate有效期
expected_priceFloat期望价
selling_priceFloat实际售价
bedroomsInteger房间数
living_areaInteger居住面积
has_garageBoolean是否有车库
has_gardenBoolean是否有庭院
garden_areaInteger庭院面积
garden_orientationSelection朝向

garden_orientation 字段包含 4 个可能的取值:“朝北”、“朝南”、“朝东”和“朝西”。下拉列表定义为元组列表,示例请参见 - 此处

继续打开文件:models/property.py,在 Property 类中添加以下属性:

python
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("标题/名称")
    description = fields.Text("描述", )
    postcode = fields.Char("邮编")
    date_availability = fields.Date(string="有效期")
    expected_price = fields.Float("期望价格")
    selling_price = fields.Float("实际售价")
    bedrooms = fields.Integer("房间数量")
    living_area = fields.Integer("室内面积(M²)")
    has_garage = fields.Boolean("是否有车库")
    has_garden = fields.Boolean("是否有花园")
    garden_area = fields.Integer("花园面积(M²)")
    garden_orientation = fields.Selection(
        string='朝向',
        selection=[('north', '朝北'), ('south', '朝南'), ('east', '朝东'), ('west', '朝西')],
        help="“朝向”可用于区分潜在客户和商机")

Odoo 会根据上面模型的定义自动创建相应的数据库表:estate_property。数据库表字段的数据类型有:布尔型、浮点型、字符型、文本型、日期型和选择型。

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

shell
$ ./odoo-bin --addons-path=addons,dev-addons/ -d odoodb -u estate

连接到 pgsql 数据库并查看 estate_property 表的结构,就会发现新增了几个字段,如下所示:

sql
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),
  "name" varchar COLLATE "pg_catalog"."default",
  "bedrooms" int4,
  "living_area" int4,
  "garden_area" int4,
  "postcode" varchar COLLATE "pg_catalog"."default",
  "garden_orientation" varchar COLLATE "pg_catalog"."default",
  "date_availability" date,
  "description" text COLLATE "pg_catalog"."default",
  "has_garage" bool,
  "has_garden" bool,
  "expected_price" float8,
  "selling_price" float8,
  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 "odoo_user";

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 COLUMN "public"."estate_property"."name" IS 'Name';

COMMENT ON COLUMN "public"."estate_property"."bedrooms" IS '房间数量';

COMMENT ON COLUMN "public"."estate_property"."living_area" IS '室内面积(M²)';

COMMENT ON COLUMN "public"."estate_property"."garden_area" IS '花园面积(M²)';

COMMENT ON COLUMN "public"."estate_property"."postcode" IS '邮编';

COMMENT ON COLUMN "public"."estate_property"."garden_orientation" IS '朝向';

COMMENT ON COLUMN "public"."estate_property"."date_availability" IS '有效期';

COMMENT ON COLUMN "public"."estate_property"."description" IS '描述';

COMMENT ON COLUMN "public"."estate_property"."has_garage" IS '是否有车库';

COMMENT ON COLUMN "public"."estate_property"."has_garden" IS '是否有花园';

COMMENT ON COLUMN "public"."estate_property"."expected_price" IS '期望价格';

COMMENT ON COLUMN "public"."estate_property"."selling_price" IS '实际售价';

COMMENT ON TABLE "public"."estate_property" IS 'estate.property';

常见属性

目标在本节结束时,表 estate_property 中的 nameexpected_price 列应设置为不可为空:

shell
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 |
...

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

py
name = fields.Char(required=True)

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

  • string (str, 默认值:字段名称) - 字段在用户界面中的标签(用户可见)。
  • required (bool, 默认值:False) -若为 True,则该字段不能为空。它必须具有默认值,或者在创建记录时必须始终提供值。
  • help (str, 默认值: "") - 在 UI 中为用户提供长篇帮助提示。
  • index (bool, 默认值: False) - 要求 Odoo 在该列上创建数据库索引。

为现有字段设置属性。添加以下属性(required=True):

字段属性备注
namerequired
expected_pricerequired

重启服务器后,这两个字段都应设置为不可为空。

自动字段

可能已经注意到,模型中存在一些您从未定义过的字段。Odoo会在所有模型中创建几个字段。这些字段由系统管理,无法进行写入操作,但在有用或必要时可以进行读取:

  • id (Id) - 该模型记录的唯一标识符。
  • create_date (Datetime) - 记录的创建日期时间。
  • create_uid (Many2one) - 创建该记录的用户。
  • write_date (Datetime) - 记录的最后修改日期时间。
  • write_uid (Many2one) - 最后修改该记录的用户。

虽然可以编写原始 SQL 查询,但需谨慎操作,因为这会绕过 Odoo 的所有身份验证和安全机制。

最近更新