一、入门基础

1,环境安装以及准备

首先检查python版本、poostgresql版本

# 00 Odoo 源码下线
https://www.odoo.com/zh_CN/page/download

# 01 python3 安装,推荐 python3.12
pip3 install virtualenv
cd home/odoo/odoo18
virtualenv myvenv
source myvenv/bin/activate
deactivate
pip3 install -r requirements.txt

# 02 数据库 postgresql
https://www.postgresql.org/download/linux/ubuntu/
sudo apt install postgresql-16

# 03 IDE,推荐使用 pycharm
https://www.jetbrains.com/zh-cn/pycharm/download/other.html

windows安装完成pg数据库之后是不能使用初始化的超级用户和密码,postgres postgres

需要新建一个用户密码 odoo odoo

2.Odoo架构

业务对象

通过ORM自动映射到数据库,业务对象被声明为python类

对象试图

UI视图的定义,主要使用OWL(JS),XML、Qweb

数据文件

数据文件使用XML或csv

控制器

处理来自浏览器的web请求

3、认识ODOO模块结构

  • controller 存放路由的文件、所有来自浏览器启动请求、以及定义路由都在这里(api接口)
  • data 静态资源文件,定义静态模型、权限等
  • demo 演示文件夹,演示数据存放
  • doc 存放文件夹
  • i8n 翻译文件夹
  • models 所有的数据模型都在这里
  • report 报表相关文件
  • security 模型权限定义,本质是一个csv文件

id,name ,model,权限组1,1,1,1

  • static 静态资源、前端文件、js、xml、img、scss
  • tools 自定义工具包
  • views 定义odoo前端视图文件
  • wizard 向导相关

4.manifest.py文件

_manifest_是odoo模块的入口文件所有的资源加载都在这里配置

只有配置类清单文件,odoo才能识别出来这是一个新的模块

清单文件内部都是一个字典,每一个key都有其含义不能随便定义

  • name模块名称
  • version 模块迭代的版本,但是不要和odoo的版本有冲突
  • summary 模块概要
  • sequence 模块排序,在应用看板中出现的位置
  • description 详细描述
  • catgory 模块所属类别
  • website 网站
  • depends 依赖模块
  • data 是一个列表,引用通过xml定义的模型数据
    • security 的权限加载
    • data 初始化模型数据
    • views模型视图
    • wzard向导视图
  • demo演示数据
  • installable 运行安装
  • application 是否独立应用
  • assets 前端字段加载,js/css,xml等
  • auther 开发者
  • license 开源协议
  • auto_ install

5 配置文件

odoo配置文件统一放在odoo根目录,以***.conf结尾
配置文件需要手动创建,并且在启动时,通过 -c 指定配置文件
一般习惯将odoo-bin 和 odoo.conf放在同一个目录下

1 配置文件明细

必须要以[options]开头,自定义模块没有引入,系统默认是不会加载的

[options]
# 引入自定义模块
addons_path = odoo/addons, my_addons/demo_addons
db_host = 127.0.0.1
db_port = 5432
db_user = odoo
db_password = odoo
db_name = test_db

# 定义端口号
xmlrpc_port = 8069
# 静态资源存放路径,data文件中包含了session,静态资源(图片,导入的文件)
data_dir = data
# 数据库管理密码
admin_passwd = admin123

2 odoo-bin的使用

新下载的odoo源码包一般没有叫做odoo-bin的文件
其启动文件默认在setup/odoo,可以将其复制到根目录,并且命名为odoo-bin

启动命令:

./odoo-bin -c odoo.conf

3pycharm配置odoo启动

右上角>编辑配置文件

01添加python配置文件

02选择python的运行环境

03名称、指定odoo-bin -c <指定配置文件(相对路径)>

6、创建一个应用

创建应用

1.创建一个可以存放模块的文件夹

2.创建一个自己的模块

2.1 有一个自己的清单文件,用来说明我是一个odoo模块,并指定必要依赖

2.2model是用来存放模型

2.3 views用来存放xml文件

1.业务模型(对象)声明

新增一个模型的时候,建议使用一个固定的前缀,防止和原生模型命名冲突。

from odoo import api,models,fields

api:odoo模型的一些常用的装饰器,(监听,校验等等)

models:定义模型的一个基类

fields:定义模型字段

模型的创建

class LSaleOrder(models.Model)

_name = ‘l.sale.order’

模型类的每次通常使用驼峰命名(首字母大写)

_name使用‘.’分隔,模块被安装时,会在pg数据库自动生成一个’l_sale_order‘的表,

name = fields.Char(string=‘单号’,help=‘这是我的单号’)

fields.Char 定义一个字符类型的字段,string属性是视图层显示的字段名称,help是一个提示

odoo的表有4个默认字段,分别是:

id,create_uid,write_uid,create_date,write_date

2.视图声明

**小提示:**在pychram中可以安装odoo插件可以给予开发者提示

List 列表视图

<record id="l_sale_order_list_view" model="ir.ui.view">
    <field name="name">l.sale.order.list.view</field>
    <field name="model">l.sale.order</field>
    <field name="arch" type="xml">
        <list>
            <field name="name"/>
            <field name="create_date"/>
            <field name="create_uid"/>
        </list>
    </field>
</record>

注意:18版本前列表是tree,后面是list

Search 搜索视图

<record id="l_sale_order_search_view" model="ir.ui.view">
    <field name="name">l.sale.order.search.view</field>
    <field name="model">l.sale.order</field>
    <field name="arch" type="xml">
        <search>
            <field name="name"/>
            <field name="create_	uid"/>
            <separator/>
            <filter string="日期" name="create_date" date="create_date"/>
            <filter string="创建人" name="create_uid" date="create_uid"/>
        </search>
    </field>
</record>


Window Actions 窗口动作

    <record id="nano_sale_order_act_window" model="ir.actions.act_window">
        <field name="name">我的测试单据</field>
        <field name="res_model">nano.sale.order</field>
        <field name="view_mode">list,form</field> <!--这里的先后顺序代表访问后的先后顺序-->
<!--        <field name="domain"></field>-->
<!--        <field name="context"></field>-->
<!--        <field name="view_ids"></field>-->
    </record>
  • 动作存在模型 ir.actions.act_window
  • res_model 指定需要绑定到具体模型
  • view_mode 当前模型激活哪些视图,并且此字段的值,有先后顺序

Menuitem菜单

<menuitem id="nano_sale_order_menu"
              name="测试二级菜单"
              sequence="1"
              parent="test_application.nano_sale_order_menu_root"
              action="test_application.nano_sale_order_act_window"
    />
  • id是标签的唯一值
  • name是菜单名称
  • sequence是排序
  • parent是父级菜单
  • action是指定执行的动作
  • parent和action指定的如果在同一个模块下则需要前缀,如果在不同模块需要加上模块名称前缀(。。。总之都要加)

注意:菜单是有先后顺序的一般在后面或者单独弄一个文件,在清单文件的data里也要注意

   'data': [
        'views/nano_sale_order_views.xml',
        'views/menu_views.xml'
    ],

菜单和模型数据xml文件是纯真加载顺序的必须将menu_views放在定义模型以及窗口动作文件之后加载

常用标签

<record></record>
<field></field>
<search></search>
<form></form>
<list></list>
<filter></filter>
<sheet></sheet>
<group></group>
  • <font style="color:rgba(0, 0, 0, 0.9);background-color:rgba(0, 0, 0, 0.03);"><record></record></font> 定义记录
    • 作用:创建或更新数据库中的记录
    • <font style="color:rgba(0, 0, 0, 0.9);background-color:rgba(0, 0, 0, 0.03);">id</font>:唯一标识
    • <font style="color:rgba(0, 0, 0, 0.9);background-color:rgba(0, 0, 0, 0.03);">model</font>:模型名称
  • <font style="color:rgba(0, 0, 0, 0.9);background-color:rgba(0, 0, 0, 0.03);"><field></field></font> 字段赋值
    • 作用:为记录的字段设置值
    • <font style="color:rgba(0, 0, 0, 0.9);background-color:rgba(0, 0, 0, 0.03);">name</font>:字段名称
    • <font style="color:rgba(0, 0, 0, 0.9);background-color:rgba(0, 0, 0, 0.03);">string</font>:字段显示名称
  • <font style="color:rgba(0, 0, 0, 0.9);background-color:rgba(0, 0, 0, 0.03);"><search></search></font> 搜索视图
    • 作用:定义列表页面的搜索条件和过滤器
  • <font style="color:rgba(0, 0, 0, 0.9);background-color:rgba(0, 0, 0, 0.03);">form</font> 表单视图
    • 作用:定义数据编辑/查看的表单界面
  • <font style="color:rgba(0, 0, 0, 0.9);background-color:rgba(0, 0, 0, 0.03);">list</font> 列表视图
    • 作用:定义数据列表展示界面,以表格形式显示多条记录
  • <font style="color:rgba(0, 0, 0, 0.9);background-color:rgba(0, 0, 0, 0.03);">filter</font> 过滤器
    • 作用:定义搜索视图中的预设过滤条件
  • <font style="color:rgba(0, 0, 0, 0.9);background-color:rgba(0, 0, 0, 0.03);">sheet</font> 表单页签
    • 作用:在表单视图中定义一个内容区域,通常用于包裹字段布局
  • <font style="color:rgba(0, 0, 0, 0.9);background-color:rgba(0, 0, 0, 0.03);">group</font> 字段分组
    • 作用:将相关字段组织在一起,提供更好的布局(通常两列布局)

视图存放在ir.ui.view

动作存放在ir.actions.act_window

权限定义

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_l_sale_order,l.sale.order,model_l_sale_order,base.group_user,1,1,1,1
  • id 定义权限唯一值
  • 名称
  • 模型名称 model_开头 后面跟模型对于的数据库表名
  • 权限组名称
  • 读,写,创建,删除(0不可以,1可以)

7 模型的基本字段

基本字段是模型一个模型的重要组成部分

除了字段的定义方式外,自有本身也有属性和参数

Odoo 模型需要引入 fields 来定义字段

from odoo import fields
  • fields.Char(),定义字符类型的字段
  • fields.Text(),定义长文本类型字段
  • fields.Integer(),定义整数类型
  • fields.Float(),定义浮点类型,
  • fields.Selection(),定义枚举类型
  • fields.Boolean,定义布尔类型
  • fields.Date(),定义日期类型,如 2025-11-16
  • fields.Datetime(),定义时间日期类型,如 2025-11-16 10:00:00
  • fields.Image(),定义图片
  • fields.Binary(),定义二进制存储
  • fields.Many2one(),定义关系类型字段,多对一
  • fields.One2many(),定义关系类型字段,一对多
  • fields.Many2many(),定义关系类型字段,多对多
name = fields.Char(string='单号',help='这是我的单号')
note = fields.Text(string='我是text')
sequence = fields.Integer(string='我是整数')
qty = fields.Float(string='我是浮点数')
is_bool = fields.Boolean(string='我是布尔')
state = fields.Selection([('draft','草稿'),
                          ("confirm",'已确认'),
                          ('paid','已支付')],string="我是枚举")
date_01 = fields.Date(string='我是date')
date_02 = fields.Datetime(string='我是DateTime')
image = fields.Image(string='我是图片')
binary = fields.Binary(string='我是binary')

image 和binary字段的区别在于image只能上传图片odoo会自动校验,二进制文件(binary)可以上传任何类型的文件

selection()是一个列表,列表每一个元素是一个元组,元组第一项是存储在数据库中的值,第二项是视图显示的内容

常用模型属性

  • _name
  • _order
  • description

字段基本属性

字段属性可以在模型定义字段时配置,也可以在 xml 中配置,但是 xml 中配置更为灵活

xml 配置的属性仅在视图层校验

模型中字段配置的属性,会在逻辑层和视图层都进行校验

  • string 定义字段名名称 xml和模型视图都可以定义
  • readonly 字段只读 xml和模型视图都可以定义
  • required 必填属性 xml和模型视图都可以定义
  • default 默认值,可以是一个字符串也可以是一个函数返回值,需要定义在模型字段上
  • compute 计算值,绑定一个函数 需要定义在模型字段上
  • store 是否存储数据库,默认为 True,如果配置了 compute 属性,则该字段默认为 False 需要定义在模型字段上
  • copy 是否允许复制
  • related 关联的字段,一般配合 many2one 字段使用 需要定义在模型字段上
  • help 帮助提示文本内容 需要定义在模型字段上
  • **digits**是浮点类型字段的精度,通常有以下3种设置方式:
price_unit = fields.Float(string='单价',digits='Product Price')
price_unit = fields.Float(string='单价',digits=2)
price_unit = fields.Float(string='单价',digits=(16,2)) 

模型字段上定义属性

name = fields.Char(string='单号', help='这是我的单号', readonly=1)
note = fields.Text(string='我是Text', required=1)
sequence = fields.Integer(string='我是整数')
qty = fields.Float(string='我是浮点')
is_bool = fields.Boolean(string='我是布尔')
state = fields.Selection([('draft', '草稿'),
                        ('confirm', '已确认'),
                        ('paid', '已支付')], string='我是枚举', default='draft')

date_01 = fields.Date(string='我是Date', default=fields.Date.today())
date_02 = fields.Datetime(string='我是Datetime', default=fields.Datetime.now())
image = fields.Image(string='我是Image')
binary = fields.Binary(string='我是Binary')

xml视图增加字段的属性值

<field name="name" readonly="1" force_save="1"/>
<field name="note" required="1"/>

说明:xml中定义的readonly必须要强制搭配force_save,模型中则不需要

如果xml没有搭配force_saves使用,这字段值无法保存

compute属性基本用法

Compute 属性值是一个方法名称的字符串,compute必须使用for循环对self记录集进行遍历更新,

默认store属性是=False,此时name字段值是不会存储到数据中的

字段的是无法用sql查询,只能用.name的方式,并且每调取一次name的值,就会重新计执行一次 _compute_name 方法

def _compute_name(self):
    """compute 的self是一个记录集,包含多条记录"""
    for record in self:
        print(record.state, record.id)
        if record.state and record.id:
            record.name = record.state + '/' + str(record.id)
        else:
            record.name = 'New'

name = fields.Char(string='单号', help='这是我的单号', compute='_compute_name')

store = True时,必须搭配@api.depends('note'),用来触发执行_compute_name的时机,并且此时name值是会存储到数据库中

@api.depends('note')
def _compute_name(self):
    for record in self:
        print(record)
        if record.state and record.id:
            record.name = f'{record.state}/{str(record.id)}'
        else:
            record.name = 'New'

name = fields.Char(string='单号', help='这是我的单号', compute='_compute_name', store=True)

my_addons.zip

二、模型进阶

1 模型关系的深度解析

  • Many2one/One2many 实战配置(inverse_name 关联字段必选说明)
  • 级联操作控制(ondelete 属性:cascade/set null/restrict)
  • Many2many 三种定义方式(基础型、关联表扩展型、双向关联)
  • 关系字段视图展示优化(tree_view_inline 嵌入列表)

Many2one和One2many

一对多和多对一是一对儿字段,通常搭配使用
定义了One2many则对应关联模型中必须定义Many2one字段
定义Many2one字段,对应关联模型非必须定义One2many字段,根据实际场景定义

┌─────────────────────────┐
│        Model A          │
├─────────────────────────┤
│  name = fields.Char()   │
│                         │
│  line_ids = fields.One2many(  ◄── One2many(多个B指向同一个A)
│      'model.b', 'a_id') │
└─────────────────────────┘
│
│ Many2one(B 指向 A)
▼
┌─────────────────────────┐
│        Model B          │
├─────────────────────────┤
│  product = fields.Char()│
│                         │
│  a_id = fields.Many2one('model.a') ─────┐
└─────────────────────────┘               │
│ 										  │
(数据库真实外键存储位置) ◄──────────────────┘

Many2one 才是真正存在于数据库的字段

  • 在 B 表(model.b)里会有一列 a_id(外键)
  • 用来指向 A 表(model.a)的主键 id
  • Many2one 用于"多的那一方"指向"单的那一方"

One2many 只是 Many2one 的反向关系

  • 在 model.a 中定义的 line_ids = fields.One2many('model.b', 'a_id')
  • 不会生成数据库字段!!
  • 它只是告诉 Odoo:

“去 model.b 表里找所有 a_id == 当前记录 的数据”

总结:

  • One2many = 数据不存储,只是通过 Many2one 查出来。
  • Many2one = 真实字段,数据库外键。

Many2many & One2many

  • ✅ Many2many 不会在 A 表或 B 表中存储关系
  • ✅ 关系实际保存在中间表(relation table)中
  • ✅ 中间表只有两列外键(A 和 B 的 id)
┌──────────────┐          ┌──────────────┐
│   Table A    │          │   Table B    │
│  (如 product) │          │  (如 tag)    │
└──────────────┘          └──────────────┘
       │                          │
       │    多对多关系(Many2many)   │
       ▼                          ▼
┌──────────────────────────────────────────┐
│         中间表(Relation Table)           │
│      如:product_tag_rel                 │
├──────────────────────────────────────────┤
│  id  |  product_id (A id)  | tag_id (B id) │
└──────────────────────────────────────────┘

many2many字段定义时接收5个参数:

comodel_name: str	关系表的模型名称
relation: str			中间表名称
column1: str			当前字段所在的表的外键         
column2: str			关联表的外键
string: str				字段名称
  • 关系表的模型名称
tax_id = fields.Many2many('account.tax', 'nano_sale_order_account_tax_rel','nano_sale_order_id','account_tax_id',string='税项')

CURD操作(不止是many2many)

  1. **(0, 0, values)**** —— 创建新记录并关联**
record.tag_ids = [(0, 0, {'name': 'VIP'})]

创建新的记录,自动加入many2many关系表

  1. (1, id, values) —— 修改已存在的记录
record.tag_ids = [(1, 5, {'name': 'Updated'})]
  1. (2, id, 0) —— 从关系中移除,并删除该记录**(慎用)**
record.tag_ids = [(2, 4, 0)]

把tag id=4移出Many2many,并且该处tag id=4的记录基本不用

  1. **(3, id, 0)**** —— 从关系中移除(不删除记录)**
record.tag_ids = [(3, 7, 0)]

只移除,不删除

  1. (4, id, 0) —— 添加已有记录到关系
record.tag_ids = [(4, 8, 0)]

中间表添加一条记录

  1. **(5, 0, 0)**** —— 清空所有关系**
record.tag_ids = [(5, 0, 0)]
self.tax_ids = [Command.clear()] # 推荐

删除中间表中所有与当前记录相关的条目

  1. (6, 0, [id1, id2]) —— 替换所有关系(常用)
record.tag_ids = [(6, 0, [1, 2, 3])]

相当于先执行5(清空,这里5不是上面的序号是参数)再执行4(同)

2. 级联操作控制 ondelete

<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">ondelete="cascade"</font>(级联删除)

order_id = fields.Many2one('sale.order', ondelete='cascade')
  • 效果:删除主记录,自动删除所有关联的子记录
  • 场景:子记录完全依赖父记录,例如日志、明细行、附件关系
  • ⚠️风险:可能误删大量数据,谨慎使用

<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">ondelete="set null"</font>(置空,默认值)

order_id = fields.Many2one('sale.order', ondelete='set null')
  • 效果:父记录被删除,子记录保留,外键字段变为 NULL
  • 场景:父记录删除后子记录仍需保留,产品类别、业务员字段常用

<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">ondelete="restrict"</font>(禁止删除)

order_id = fields.Many2one('sale.order', ondelete='restrict')
  • 效果:子记录还在引用父记录时,父记录不允许被删除
  • 场景:父记录有业务用途不能删除,例如:仓库有库存则不能删仓库;税率已被开票引用则不能删税率

总结表格

类型 描述 适用场景
cascade 删除父 → 子也删 明细表、必须跟随的依赖
set null 删除父 → 字段置空 可选字段,子记录独立存在
restrict 有子数据则禁止删父 业务主数据,防止误删

4 模型约束与数据校验

_sql_constraints 数据库约束(唯一值、外键关联)

用于数据库执行的硬性约束(unique、check、foreign key)。

_sql_constraints = [
    ('unique_barcode',  # 约束名(数据库级别必须唯一)
     'unique(barcode)',  # SQL 约束表达式
     '订单条码不能重复')  # 错误提示(用户界面显示)
]

特点

  • 数据库层面强制执行
  • 性能高
  • 无法捕获业务上下文(仅适合简单逻辑)
  • 违反约束后会显示你写的 第三个参数 的提示
  • ⭐ odoo19 开始不在推荐使用 _sql_constraints

constrains 装饰器业务约束(自定义校验逻辑)— 推荐

用于需要复杂逻辑、跨字段校验、检查上下文的情况。

@api.constrains('barcode')
def constrains_barcode(self):
    for record in self:
        domain = [('barcode', '=', record.barcode), ('id', '!=', record.id)]
        if self.search(domain, limit=1):
            raise ValidationError('订单条码不能重复')

特点

  • Python 层执行
  • 可跨字段校验
  • 可自定义任何逻辑
  • 不会干扰数据库事务(但会阻止提交)
  • 错误提示通过 raise ValidationError("xxx") 设置

5 模型业务逻辑增强

Onchange 装饰器(字段联动更新、数据校验)

@api.onchange('product_id')
def _onchange_product_id(self):
    if self.product_id:
        self.price_unit = self.product_id.list_price

onchange 中可以配置多个监听字段
前端的监听方法,不会触发数据库事务
不建议做复杂的计算逻辑


Compute 装饰器(计算字段实战:如金额合计、库存余量)

amount_total = fields.Float(
    compute="_compute_amount_total"
)

@api.depends('price_unit', 'quantity')
def _compute_amount_total(self):
    for rec in self:
        rec.amount_total = rec.price_unit * rec.quantity

默认实时计算存储数据库,字段只读

**存储数据库需要配置属性 **store=True

amount_total = fields.Float(
    compute="_compute_amount_total",
    store=True
)

适合频繁查询、排序、筛选


可编辑的计算字段 inverse 属性

price_unit = fields.Float()
price_tax_included = fields.Float(
    compute="_compute_tax",
    inverse="_inverse_tax",
)

def _compute_tax(self):
    for rec in self:
        rec.price_tax_included = rec.price_unit * 1.21

def _inverse_tax(self):
    for rec in self:
        rec.price_unit = rec.price_tax_included / 1.21

6.模型的继承

1. models.TransientModel(临时/向导模型)

class MyWizard(models.TransientModel):
    _name = 'my.wizard'
    # 自动存储数据,但有生命周期限制

用作向导模型,临时处理数据,数据不会持久化存储。

向导模型需要定义权限

弹窗用向导模型

2. models.AbstractModel(抽象模型)

class MyAbstract(models.AbstractModel):
    _name = 'my.abstract'
    _abstract = True
# 不存储数据,只作为模板

抽象类提供可复用的字段、业务逻辑。

抽象类不需要定义权限

特性对比表

特性 TransientModel AbstractModel
数据库表 ✅ 创建临时表 ❌ 不创建表
数据持久性 临时存储(定期清理) 不存储数据
继承方式 可被继承(_inherit 只能被继承(_abstract=True
直接实例化 ✅ 可以 ❌ 不可以
使用场景 向导、临时操作 代码复用、公共逻辑
数据生命周期 定时清理(vacuum) 不适用

经典继承(类继承)

只写 _inherit,不写 _name,用来扩展原有模型。
作用:给原有模型追加字段、重写方法,不会生成新数据表。

from odoo import api, fields, models

class ProductTemplate(models.Model):
    _inherit = 'product.template'

    # 新增字段
    l_barcode = fields.Char(string='我的条码')

完整写法(同时写 _name + _inherit,生成一张新表,完整复制父模型结构):

class InheritedModel(models.Model):
    _name = 'new.model'
    _inherit = 'parent.model'

    # 添加新字段
    new_field = fields.Char(string='New Field')

    # 重写原有方法
    def existing_method(self):
        # 调用父类原有逻辑
        result = super().existing_method()
        # 在此扩展业务逻辑
        return result

扩展继承(混合多继承)

_inherit 传入一个列表,可以同时继承多个模型(常搭配抽象模型 AbstractModel)。

class ExtendedModel(models.Model):
    _name = 'new.model'
    # 同时继承多个模型
    _inherit = ['model1', 'model2', 'model3']

    new_field = fields.Char(string='Combined Model')

搭配抽象模型的实战写法:

from odoo import api, fields, models

class ProductTemplate(models.Model):
    _name = 'product.template'
    _inherit = ['product.template', 'l.test.abstract']

    l_barcode = fields.Char(string='我的条码')
class ProductTemplate(models.Model):
    _name = 'test.product.template'
    _inherit = ['product.template', 'l.test.abstract']

    l_barcode = fields.Char(string='我的条码')

两种继承核心区别

  1. 纯扩展(只写 _inherit)
  • 不定义 _name
  • 直接修改原表结构,原有数据保留
  • 多用于二次开发给系统原有单据加字段
  1. 新建子类(_name + _inherit)
  • 会创建一张全新数据表
  • 完整复制父模型的字段与方法
  • 适合创建独立新业务单据
  1. 多继承(_inherit = 列表)
  • 同时复用多个模型的字段与逻辑
  • 最常用来引入公共抽象类的公共代码

my_addons.zip

my_addons.7z


其他

修改用户密码

python odoo-bin shell -c conf/sale.conf

user = env['res.users'].sudo().browse(2) # id为2
user.password = '123'
env.cr.commit()
quit()