odoo笔记
一、入门基础
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-16fields.Datetime(),定义时间日期类型,如2025-11-16 10:00:00fields.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_orderdescription
字段基本属性
字段属性可以在模型定义字段时配置,也可以在 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)
二、模型进阶
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)
**(0, 0, values)**** —— 创建新记录并关联**
record.tag_ids = [(0, 0, {'name': 'VIP'})]
创建新的记录,自动加入many2many关系表
(1, id, values)—— 修改已存在的记录
record.tag_ids = [(1, 5, {'name': 'Updated'})]
(2, id, 0)—— 从关系中移除,并删除该记录**(慎用)**
record.tag_ids = [(2, 4, 0)]
把tag id=4移出Many2many,并且该处tag id=4的记录基本不用
**(3, id, 0)**** —— 从关系中移除(不删除记录)**
record.tag_ids = [(3, 7, 0)]
只移除,不删除
(4, id, 0)—— 添加已有记录到关系
record.tag_ids = [(4, 8, 0)]
中间表添加一条记录
**(5, 0, 0)**** —— 清空所有关系**
record.tag_ids = [(5, 0, 0)]
self.tax_ids = [Command.clear()] # 推荐
删除中间表中所有与当前记录相关的条目
(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='我的条码')
两种继承核心区别
- 纯扩展(只写 _inherit)
- 不定义
_name - 直接修改原表结构,原有数据保留
- 多用于二次开发给系统原有单据加字段
- 新建子类(_name + _inherit)
- 会创建一张全新数据表
- 完整复制父模型的字段与方法
- 适合创建独立新业务单据
- 多继承(_inherit = 列表)
- 同时复用多个模型的字段与逻辑
- 最常用来引入公共抽象类的公共代码
其他
修改用户密码
python odoo-bin shell -c conf/sale.conf
user = env['res.users'].sudo().browse(2) # id为2
user.password = '123'
env.cr.commit()
quit()