本文目录导读:

在Odoo中,对象(Model) 与 字段(Field) 的映射关系是框架的核心机制,它通过 ORM(对象关系映射) 将Python类结构自动转换为数据库表结构,以下是其核心映射规则和实现方式:
核心映射规则
模型 → 数据库表
class SaleOrder(models.Model):
_name = 'sale.order' # → 数据库表名: sale_order
_description = 'Sales Order'
# 默认字段自动添加:
# id (主键)
# create_uid (创建用户)
# create_date (创建时间)
# write_uid (最后修改用户)
# write_date (最后修改时间)
字段 → 数据库列
| 字段类型 | Python表示 | 数据库列类型 |
|---|---|---|
| Char | fields.Char() |
VARCHAR |
| Integer | fields.Integer() |
INTEGER |
| Float | fields.Float() |
NUMERIC/DOUBLE |
| Boolean | fields.Boolean() |
BOOLEAN |
| Date | fields.Date() |
DATE |
| Datetime | fields.Datetime() |
TIMESTAMP |
| Text | fields.Text() |
TEXT |
| Binary | fields.Binary() |
BYTEA |
| Selection | fields.Selection() |
VARCHAR |
| Many2one | fields.Many2one() |
INTEGER(外键) |
| One2many | fields.One2many() |
无独立列(关联表) |
| Many2many | fields.Many2many() |
关联表 |
字段映射详解
基础字段映射
class ProductTemplate(models.Model):
_name = 'product.template'
name = fields.Char(string='Name', required=True)
# → 列: name VARCHAR NOT NULL
price = fields.Float(string='Price', digits=(10,2))
# → 列: price NUMERIC(10,2)
active = fields.Boolean(string='Active', default=True)
# → 列: active BOOLEAN DEFAULT TRUE
description = fields.Text(string='Description')
# → 列: description TEXT
关系字段映射
Many2one(多对一)
class SaleOrderLine(models.Model):
_name = 'sale.order.line'
order_id = fields.Many2one('sale.order', string='Order', required=True)
# → 列: order_id INTEGER REFERENCES sale_order(id)
product_id = fields.Many2one('product.product', string='Product')
# → 列: product_id INTEGER REFERENCES product_product(id)
One2many(一对多)
class SaleOrder(models.Model):
_name = 'sale.order'
order_line_ids = fields.One2many('sale.order.line', 'order_id', string='Order Lines')
# → 无独立数据库列,通过关联字段'order_id'建立关系
# 等效SQL: SELECT * FROM sale_order_line WHERE order_id = :current_id
Many2many(多对多)
class ProductTemplate(models.Model):
_name = 'product.template'
tag_ids = fields.Many2many('product.tag', string='Tags')
# → 自动创建关联表: product_template_product_tag_rel
# 列: product_template_id, product_tag_id
# 等效SQL:
# SELECT pt.* FROM product_tag pt
# JOIN product_template_product_tag_rel rel ON rel.product_tag_id = pt.id
# WHERE rel.product_template_id = :current_id
字段属性映射
数据库约束
class Partner(models.Model):
_name = 'res.partner'
email = fields.Char(string='Email', unique=True)
# → 列: email VARCHAR UNIQUE
ref = fields.Char(string='Reference', index=True)
# → 列: ref VARCHAR, 创建索引
phone = fields.Char(string='Phone', required=True)
# → 列: phone VARCHAR NOT NULL
默认值和计算字段
class Invoice(models.Model):
_name = 'account.move'
# 带默认值
state = fields.Selection([
('draft', 'Draft'),
('posted', 'Posted'),
], default='draft')
# → 列: state VARCHAR DEFAULT 'draft'
# 计算字段(不存储)
total_amount = fields.Float(compute='_compute_total', string='Total')
# → 无数据库列
# 存储的计算字段
total_amount_stored = fields.Float(compute='_compute_total', store=True)
# → 列: total_amount_stored NUMERIC
字段继承与重写
class ExtendedPartner(models.Model):
_inherit = 'res.partner'
# 重写已有字段
name = fields.Char(string='Full Name', required=True)
# → 修改字段属性,保留原有数据库列
# 扩展字段
business_type = fields.Selection(...)
# → 新增列: business_type VARCHAR
高级映射特性
映射选项
class Model(models.Model):
_name = 'example.model'
field1 = fields.Char(
string='Field1',
size=100, # VARCHAR(100)
translate=True, # 创建翻译表
company_dependent=True, # 公司级字段
readonly=True, # 只读
help='帮助文本', # 字段描述
groups='base.group_user', # 可见性组
)
子类型字段
# Monetary字段(货币相关) amount = fields.Monetary(string='Amount', currency_field='currency_id') # → 列: amount NUMERIC, 配合货币字段使用 # HTML字段 content = fields.Html(string='Content') # → 列: content TEXT # Image字段 image = fields.Image(string='Image', max_width=1920, max_height=1080) # → 列: image BYTEA # Reference字段(动态多态) reference = fields.Reference(selection='_referencable_models', string='Reference') # → 列: reference VARCHAR(存储 "model,id")
性能相关的映射
class Model(models.Model):
_name = 'example.model'
# 索引和约束
name = fields.Char(index=True) # 创建普通索引
code = fields.Char(index='btree') # 指定索引类型
ref = fields.Char(index=True, unique=True) # 唯一索引
# 分区字段(PostgreSQL特有)
date = fields.Date(index=True) # 日期字段索引
# JSONB存储
metadata = fields.Json(string='Metadata')
# → 列: metadata JSONB
完整示例
from odoo import models, fields, api
class CustomOrder(models.Model):
_name = 'custom.order'
_description = 'Custom Order'
_rec_name = 'display_name'
_order = 'create_date desc'
# 基础信息
name = fields.Char(string='Order Reference', required=True, index=True, copy=False)
customer_id = fields.Many2one('res.partner', string='Customer', required=True)
order_date = fields.Date(string='Order Date', default=fields.Date.today)
# 金额字段
amount_total = fields.Monetary(string='Total', currency_field='currency_id', compute='_compute_total')
currency_id = fields.Many2one('res.currency', string='Currency', default=lambda self: self.env.company.currency_id)
# 状态字段
state = fields.Selection([
('draft', 'Draft'),
('confirmed', 'Confirmed'),
('done', 'Done'),
('cancelled', 'Cancelled'),
], string='Status', default='draft', tracking=True)
# 行明细
line_ids = fields.One2many('custom.order.line', 'order_id', string='Order Lines')
# 技术字段
active = fields.Boolean(default=True)
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
@api.depends('line_ids.subtotal')
def _compute_total(self):
for order in self:
order.amount_total = sum(line.subtotal for line in order.line_ids)
class CustomOrderLine(models.Model):
_name = 'custom.order.line'
_description = 'Order Line'
order_id = fields.Many2one('custom.order', string='Order', required=True, ondelete='cascade')
product_id = fields.Many2one('product.product', string='Product', required=True)
quantity = fields.Float(string='Quantity', default=1.0)
price_unit = fields.Float(string='Unit Price', digits='Product Price')
subtotal = fields.Float(string='Subtotal', compute='_compute_subtotal', store=True)
@api.depends('quantity', 'price_unit')
def _compute_subtotal(self):
for line in self:
line.subtotal = line.quantity * line.price_unit
数据库验证
-- 查看生成的表结构
\d custom_order
\d custom_order_line
-- 查看外键约束
SELECT
tc.constraint_name,
tc.table_name,
kcu.column_name,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name
FROM
information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage AS ccu
ON ccu.constraint_name = tc.constraint_name
WHERE tc.table_name = 'custom_order_line' AND tc.constraint_type = 'FOREIGN KEY';
迁移注意事项
- 字段添加:Odoo自动处理ALTER TABLE添加列
- 字段删除:建议使用
deprecated=True而非直接删除 - 类型更改:需要手动编写迁移脚本
- 约束更改:通过ORM的_sql_constraints处理
Odoo的ORM映射机制极大地简化了数据库操作,开发者只需关注Python类定义,框架会自动处理:
- 表创建与字段映射
- 外键关系维护
- 索引与约束管理
- 数据验证与类型转换
理解这些映射规则对于开发高效、健壮的Odoo模块至关重要。