odoo-upgrade by ahmed-lakosha/odoo-upgrade-skill
npx skills add https://github.com/ahmed-lakosha/odoo-upgrade-skill --skill odoo-upgrade一个用于在 Odoo 版本之间升级模块的综合技能,具有广泛的模式识别、自动化修复功能,并深度集成了 Odoo 架构以解决常见的迁移问题。
| Odoo 版本 | Python | PostgreSQL | Bootstrap | Owl | 状态 |
|---|---|---|---|---|---|
| 14.0 | 3.7-3.10 | 13+ | 4.5.0 | experimental | 旧版 |
| 15.0 | 3.8-3.11 | 13+ | 5.0.2 | experimental | 旧版 |
| 16.0 | 3.9-3.12 | 13+ | 5.1.3 | v1 | 稳定 |
| 17.0 | 3.10-3.13 | 13+ | 5.1.3 |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| v1 |
| 活跃 |
| 18.0 | 3.10-3.13 | 13+ | 5.1.3 | v2 | 活跃 |
| 19.0 | 3.10-3.13 | 13+ | 5.1.3 | v2 | 最新 |
在以下情况下激活此技能:
# Analyze source module structure
- Check __manifest__.py version
- Identify module dependencies
- List all file types (XML, Python, JS, SCSS)
- Create backup before changes
{
'name': 'Module Display Name',
'version': '19.0.1.0.0', # Format: ODOO_VERSION.MAJOR.MINOR.PATCH
'summary': 'Brief description',
'category': 'Category Name',
'author': 'TaqaTechno', # REQUIRED for Odoo 19
'website': 'https://www.taqatechno.com/', # REQUIRED
'support': 'support@example.com', # REQUIRED
'license': 'LGPL-3', # REQUIRED
'contributors': [ # Names only, NO emails
'Developer Name',
],
'depends': ['base', 'other_module'],
'data': [
'security/group_*.xml', # User groups first
'security/ir.model.access.csv', # Model access
'security/rules_*.xml', # Record rules
'views/*.xml', # UI views
'data/*.xml', # Default data
],
'assets': {
'web.assets_frontend': [
'module_name/static/src/js/*.js',
'module_name/static/src/scss/*.scss',
],
'web._assets_primary_variables': [
'module_name/static/src/scss/primary_variables.scss',
],
'web._assets_frontend_helpers': [
'module_name/static/src/scss/bootstrap_overridden.scss',
],
},
'installable': True,
'auto_install': False,
'application': False,
}
# Odoo 14: '14.0.1.0.0'
# Odoo 15: '15.0.1.0.0'
# Odoo 16: '16.0.1.0.0'
# Odoo 17: '17.0.1.0.0'
# Odoo 18: '18.0.1.0.0'
# Odoo 19: '19.0.1.0.0'
<!-- BEFORE (Invalid in Odoo 19) -->
<search>
<group expand="0" string="Group By">
<filter name="type" string="Type"/>
</group>
</search>
<!-- AFTER (Valid in Odoo 19) -->
<search>
<filter name="type" string="Type"/>
</search>
<!-- 1. View Template Tags -->
<!-- BEFORE -->
<tree string="Title" edit="1" editable="top">
<field name="name"/>
</tree>
<!-- AFTER -->
<list string="Title" editable="top">
<field name="name"/>
</list>
<!-- 2. Action Window view_mode -->
<!-- BEFORE -->
<record id="action_my_model" model="ir.actions.act_window">
<field name="view_mode">tree,form,kanban</field>
</record>
<!-- AFTER -->
<record id="action_my_model" model="ir.actions.act_window">
<field name="view_mode">list,form,kanban</field>
</record>
<!-- 3. XPath Expressions (CRITICAL for Odoo 18/19) -->
<!-- BEFORE -->
<xpath expr="//tree" position="inside">
<field name="new_field"/>
</xpath>
<xpath expr="//tree/field[@name='name']" position="after">
<field name="other_field"/>
</xpath>
<!-- AFTER -->
<xpath expr="//list" position="inside">
<field name="new_field"/>
</xpath>
<xpath expr="//list/field[@name='name']" position="after">
<field name="other_field"/>
</xpath>
<!-- 4. Search View Group expand Attribute (Deprecated) -->
<!-- BEFORE -->
<group expand="0" string="Group By">
<filter name="group_status" context="{'group_by': 'state'}"/>
</group>
<!-- AFTER -->
<group string="Group By">
<filter name="group_status" context="{'group_by': 'state'}"/>
</group>
# BEFORE
def action_view_records(self):
return {
'type': 'ir.actions.act_window',
'res_model': 'my.model',
'view_mode': 'tree,form',
'view_type': 'tree', # Deprecated parameter
}
# AFTER
def action_view_records(self):
return {
'type': 'ir.actions.act_window',
'res_model': 'my.model',
'view_mode': 'list,form',
# view_type parameter removed
}
<!-- BEFORE -->
<t t-name="kanban-box">
<!-- AFTER -->
<t t-name="card">
<!-- BEFORE -->
context="{'search_default_type_id': active_id}"
<!-- AFTER -->
context="{'search_default_type_id': id}"
移除 numbercall 字段 - 不再支持:
<!-- Remove this line -->
<field name="numbercall">-1</field>
# Add compatibility wrapper
from odoo.http import request
def slug(value):
"""Compatibility wrapper for slug function"""
return request.env['ir.http']._slug(value)
def unslug(value):
"""Compatibility wrapper for unslug function"""
return request.env['ir.http']._unslug(value)
# BEFORE
from odoo.addons.http_routing.models.ir_http import url_for
url = url_for('/path')
# AFTER
url = self.env['ir.http']._url_for('/path')
RPC 服务在 Odoo 19 的前端/公共组件中不可用。
/** @odoo-module **/
// BEFORE (Odoo 17)
import {useService} from "@web/core/utils/hooks";
export class MyComponent extends Component {
setup() {
this.rpc = useService("rpc");
}
async fetchData() {
const data = await this.rpc("/api/endpoint", params);
}
}
// AFTER (Odoo 19)
export class MyComponent extends Component {
setup() {
// RPC service removed - using fetch instead
}
async _jsonRpc(endpoint, params = {}) {
try {
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Csrf-Token': document.querySelector('meta[name="csrf-token"]')?.content || '',
},
body: JSON.stringify({
jsonrpc: "2.0",
method: "call",
params: params,
id: Math.floor(Math.random() * 1000000)
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.error) {
throw new Error(data.error.message || 'RPC call failed');
}
return data.result;
} catch (error) {
console.error('JSON-RPC call failed:', error);
throw error;
}
}
async fetchData() {
const data = await this._jsonRpc("/api/endpoint", params);
}
}
OWL 2.0(在 Odoo 18+ 中使用)重命名了所有生命周期钩子并更改了构造函数签名。这是一个破坏性变更——旧的方法名将不会被调用。
生命周期钩子重命名表:
| OWL 1.x (Odoo 14-17) | OWL 2.0 (Odoo 18-19) | 备注 |
|---|---|---|
constructor(parent, props) | setup() | 不需要 super() |
willStart() | onWillStart(callback) | 现在使用 onWillStart 钩子函数 |
mounted() | onMounted(callback) | 现在使用 onMounted 钩子函数 |
willUpdateProps(nextProps) | onWillUpdateProps(callback) | 回调接收 nextProps |
patched() | onPatched(callback) | 现在使用 onPatched 钩子函数 |
willUnmount() | onWillUnmount(callback) | 现在使用 onWillUnmount 钩子函数 |
willPatch() | onWillPatch(callback) | 现在使用 onWillPatch 钩子函数 |
destroyed() | onDestroyed(callback) | 现在使用 onDestroyed 钩子函数 |
之前(OWL 1.x — Odoo 14-17):
/** @odoo-module **/
import { Component } from "@odoo/owl";
export class MyWidget extends Component {
constructor(parent, props) {
super(...arguments);
this.state = { count: 0 };
}
async willStart() {
this.data = await this._loadData();
}
mounted() {
this._setupListeners();
}
patched() {
this._updateDOM();
}
willUnmount() {
this._cleanupListeners();
}
}
之后(OWL 2.0 — Odoo 18-19):
/** @odoo-module **/
import { Component, onMounted, onPatched, onWillStart, onWillUnmount, useState } from "@odoo/owl";
export class MyWidget extends Component {
setup() {
// useState replaces this.state = {}
this.state = useState({ count: 0 });
onWillStart(async () => {
this.data = await this._loadData();
});
onMounted(() => {
this._setupListeners();
});
onPatched(() => {
this._updateDOM();
});
onWillUnmount(() => {
this._cleanupListeners();
});
}
}
关键 OWL 2.0 导入变更:
// OLD — OWL 1.x
import { Component } from "@odoo/owl";
// NEW — OWL 2.0 (import hooks explicitly)
import {
Component,
useState,
useRef,
useEnv,
onMounted,
onPatched,
onWillStart,
onWillUpdateProps,
onWillPatch,
onWillUnmount,
onDestroyed,
} from "@odoo/owl";
自动修复命令:
python scripts/auto_fix_library.py --fix owl_lifecycle <project_path>
检测命令:
python scripts/odoo19_precheck.py <project_path>
# Look for: [HIGH] OWL 1.x lifecycle methods found
// ===================================================================
// Theme Name - Primary Variables
// ===================================================================
// Typography Hierarchy
$o-theme-h1-font-size-multiplier: (64 / 16);
$o-theme-headings-font-weight: 700; // NOT $headings-font-weight
// Website Values Palette
$o-website-values-palettes: (
(
'color-palettes-name': 'my_theme',
'font': 'Inter',
'headings-font': 'Inter',
'btn-padding-y': 1rem, // Use rem not px
'btn-padding-x': 2rem,
),
);
// Color Palette with menu/footer assignments
$o-color-palettes: map-merge($o-color-palettes, (
'my_theme': (
'o-color-1': #124F81,
'o-color-2': #B1025D,
'o-color-3': #f8fafc,
'o-color-4': #ffffff,
'o-color-5': #1e293b,
'menu': 1, // IMPORTANT: Specify which color for menu
'footer': 4, // IMPORTANT: Specify which color for footer
'copyright': 5, // IMPORTANT: Specify which color for copyright
),
));
// Font Configuration (use map-merge!)
$o-theme-font-configs: map-merge($o-theme-font-configs, (
'Inter': (
'family': ('Inter', sans-serif),
'url': 'Inter:300,400,500,600,700&display=swap',
'properties': ( // IMPORTANT: Add properties section
'base': (
'font-size-base': 1rem,
'line-height-base': 1.6,
),
)
),
));
移除不兼容的 website.snippet_options 继承:
<!-- REMOVE this template - not compatible with Odoo 19 -->
<template id="custom_footer_op" inherit_id="website.snippet_options">
<!-- Snippet options content -->
</template>
useService("rpc")_jsonRpc 辅助方法替换<field name="numbercall"><group> 标签(Odoo 19)<group> 标签,将过滤器保持在根级别t-name="kanban-box" 改为 t-name="card"active_id(Odoo 19)active_id 替换为 id升级后,测试:
# Install upgraded module
python -m odoo -d [DB] -i [MODULE] --addons-path=odoo/addons,projects/[PROJECT] --stop-after-init
# Update module after changes
python -m odoo -d [DB] -u [MODULE] --stop-after-init
# Run with development mode for debugging
python -m odoo -d [DB] --dev=xml,css,js
# Install Python dependencies
pip install geopy spacy hachoir
生成全面的报告,记录:
升级具有多个相互依赖模块的项目时:
主题的特殊注意事项:
升级后:
t-use-call 已移除,使用 t-callkanban-box → card<group> 标签<tree> 视图 → <list> 视图//tree → //listenv 参数numbercall 字段已移除id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_my_model_user,my.model.user,model_my_model,base.group_user,1,1,1,0
access_my_model_admin,my.model.admin,model_my_model,base.group_erp_manager,1,1,1,1
<record id="group_my_manager" model="res.groups">
<field name="name">My Manager</field>
<field name="category_id" ref="base.module_category_services"/>
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
</record>
<record id="my_model_rule" model="ir.rule">
<field name="name">My Model: User own records</field>
<field name="model_id" ref="model_my_model"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="False"/>
</record>
销售门户模板在 Odoo 19 中已完全重构:
<!-- BEFORE (Odoo 17/18) - Positional selectors -->
<xpath expr="//table[@id='sales_order_table']/thead/tr/th[3]" position="attributes">
<!-- AFTER (Odoo 19) - Named element selectors -->
<xpath expr="//table[@id='sales_order_table']/thead/tr/th[@id='product_unit_price_header']" position="attributes">
可用的表头 ID:
th[@id='product_qty_header'] - 数量th[@id='product_unit_price_header'] - 单价th[@id='product_discount_header'] - 折扣 %th[@id='taxes_header'] - 税费th[@id='subtotal_header'] - 金额/小计<!-- BEFORE (Odoo 17/18) -->
<xpath expr="//table[@id='sales_order_table']/tbody//t[@t-foreach='lines_to_report']//tr/t[@t-if='not line.display_type']/td[3]" position="attributes">
<!-- AFTER (Odoo 19) -->
<xpath expr="//table[@id='sales_order_table']/tbody//tr[@name='tr_product']/td[@name='td_product_priceunit']" position="attributes">
可用的表体元素名称:
tr[@name='tr_product'] - 产品行tr[@name='tr_section'] - 章节行tr[@name='tr_note'] - 备注行td[@name='td_product_name'] - 产品名称td[@name='td_product_quantity'] - 数量td[@name='td_product_priceunit'] - 单价td[@name='td_product_discount'] - 折扣 %td[@name='td_product_taxes'] - 税费td[@name='td_product_subtotal'] - 小计<!-- BEFORE (Odoo 17/18) - FAILS in Odoo 19 -->
<t t-out="format_datetime(env, object.date_start, dt_format='long')"/>
<t t-out="format_date(env, object.date_start)"/>
<t t-out="format_amount(env, object.amount, object.currency_id)"/>
<!-- AFTER (Odoo 19) - Remove env parameter -->
<t t-out="format_datetime(object.date_start, dt_format='long')"/>
<t t-out="format_date(object.date_start)"/>
<t t-out="format_amount(object.amount, object.currency_id)"/>
<!-- BEFORE - HTML entities (FAILS in XML) -->
<p>© 2025 Company Name</p>
<p>Company Name</p>
<!-- AFTER - Numeric character references -->
<p>© 2025 Company Name</p>
<p>Company Name</p>
常用字符代码:
© → ©(版权) →  (不间断空格)— → —(破折号)™ → ™(商标)€ → €(欧元)| Bootstrap 4 | Bootstrap 5 | 描述 |
|---|---|---|
ml-* | ms-* | 左边距 → 起始边距 |
mr-* | me-* | 右边距 → 结束边距 |
pl-* | ps-* | 左内边距 → 起始内边距 |
pr-* | pe-* | 右内边距 → 结束内边距 |
text-left | text-start | 文本左对齐 |
text-right | text-end | 文本右对齐 |
float-left | float-start | 左浮动 |
float-right | float-end | 右浮动 |
form-group | mb-3 | 表单组间距 |
custom-select | form-select | 自定义选择 |
close | btn-close | 关闭按钮 |
badge-primary | bg-primary | 主要徽章 |
font-weight-bold | fw-bold | 粗体文本 |
sr-only | visually-hidden | 仅屏幕阅读器 |
no-gutters | g-0 | 网格中无间距 |
# Pre-check for compatibility issues
python scripts/odoo19_precheck.py <project_path>
# Quick targeted fixes
python scripts/quick_fix_odoo19.py <project_path>
# Full comprehensive upgrade
python scripts/upgrade_to_odoo19.py <project_path>
# Update manifest versions
python scripts/upgrade_manifest.py <project_path> 19.0
# Fix RPC service migrations
python scripts/fix_rpc_service.py <project_path>
# Install upgraded module
python -m odoo -d [DB] -i [MODULE] --addons-path=odoo/addons,projects/[PROJECT] --stop-after-init
# Update module after changes
python -m odoo -d [DB] -u [MODULE] --stop-after-init
# Run with development mode for debugging
python -m odoo -d [DB] --dev=all
生成全面的报告,记录:
每周安装数
115
代码仓库
GitHub 星标数
18
首次出现
2026年1月22日
安全审计
安装于
opencode96
gemini-cli95
codex89
github-copilot83
kimi-cli74
amp74
A comprehensive skill for upgrading Odoo modules between versions, with extensive pattern recognition, automated fixes, and deep integration with Odoo's architecture for common migration issues.
| Odoo Version | Python | PostgreSQL | Bootstrap | Owl | Status |
|---|---|---|---|---|---|
| 14.0 | 3.7-3.10 | 13+ | 4.5.0 | experimental | Legacy |
| 15.0 | 3.8-3.11 | 13+ | 5.0.2 | experimental | Legacy |
| 16.0 | 3.9-3.12 | 13+ | 5.1.3 | v1 | Stable |
| 17.0 | 3.10-3.13 | 13+ | 5.1.3 | v1 | Active |
| 18.0 | 3.10-3.13 | 13+ | 5.1.3 | v2 | Active |
| 19.0 | 3.10-3.13 | 13+ | 5.1.3 | v2 | Latest |
Activate this skill when:
# Analyze source module structure
- Check __manifest__.py version
- Identify module dependencies
- List all file types (XML, Python, JS, SCSS)
- Create backup before changes
{
'name': 'Module Display Name',
'version': '19.0.1.0.0', # Format: ODOO_VERSION.MAJOR.MINOR.PATCH
'summary': 'Brief description',
'category': 'Category Name',
'author': 'TaqaTechno', # REQUIRED for Odoo 19
'website': 'https://www.taqatechno.com/', # REQUIRED
'support': 'support@example.com', # REQUIRED
'license': 'LGPL-3', # REQUIRED
'contributors': [ # Names only, NO emails
'Developer Name',
],
'depends': ['base', 'other_module'],
'data': [
'security/group_*.xml', # User groups first
'security/ir.model.access.csv', # Model access
'security/rules_*.xml', # Record rules
'views/*.xml', # UI views
'data/*.xml', # Default data
],
'assets': {
'web.assets_frontend': [
'module_name/static/src/js/*.js',
'module_name/static/src/scss/*.scss',
],
'web._assets_primary_variables': [
'module_name/static/src/scss/primary_variables.scss',
],
'web._assets_frontend_helpers': [
'module_name/static/src/scss/bootstrap_overridden.scss',
],
},
'installable': True,
'auto_install': False,
'application': False,
}
# Odoo 14: '14.0.1.0.0'
# Odoo 15: '15.0.1.0.0'
# Odoo 16: '16.0.1.0.0'
# Odoo 17: '17.0.1.0.0'
# Odoo 18: '18.0.1.0.0'
# Odoo 19: '19.0.1.0.0'
<!-- BEFORE (Invalid in Odoo 19) -->
<search>
<group expand="0" string="Group By">
<filter name="type" string="Type"/>
</group>
</search>
<!-- AFTER (Valid in Odoo 19) -->
<search>
<filter name="type" string="Type"/>
</search>
<!-- 1. View Template Tags -->
<!-- BEFORE -->
<tree string="Title" edit="1" editable="top">
<field name="name"/>
</tree>
<!-- AFTER -->
<list string="Title" editable="top">
<field name="name"/>
</list>
<!-- 2. Action Window view_mode -->
<!-- BEFORE -->
<record id="action_my_model" model="ir.actions.act_window">
<field name="view_mode">tree,form,kanban</field>
</record>
<!-- AFTER -->
<record id="action_my_model" model="ir.actions.act_window">
<field name="view_mode">list,form,kanban</field>
</record>
<!-- 3. XPath Expressions (CRITICAL for Odoo 18/19) -->
<!-- BEFORE -->
<xpath expr="//tree" position="inside">
<field name="new_field"/>
</xpath>
<xpath expr="//tree/field[@name='name']" position="after">
<field name="other_field"/>
</xpath>
<!-- AFTER -->
<xpath expr="//list" position="inside">
<field name="new_field"/>
</xpath>
<xpath expr="//list/field[@name='name']" position="after">
<field name="other_field"/>
</xpath>
<!-- 4. Search View Group expand Attribute (Deprecated) -->
<!-- BEFORE -->
<group expand="0" string="Group By">
<filter name="group_status" context="{'group_by': 'state'}"/>
</group>
<!-- AFTER -->
<group string="Group By">
<filter name="group_status" context="{'group_by': 'state'}"/>
</group>
# BEFORE
def action_view_records(self):
return {
'type': 'ir.actions.act_window',
'res_model': 'my.model',
'view_mode': 'tree,form',
'view_type': 'tree', # Deprecated parameter
}
# AFTER
def action_view_records(self):
return {
'type': 'ir.actions.act_window',
'res_model': 'my.model',
'view_mode': 'list,form',
# view_type parameter removed
}
<!-- BEFORE -->
<t t-name="kanban-box">
<!-- AFTER -->
<t t-name="card">
<!-- BEFORE -->
context="{'search_default_type_id': active_id}"
<!-- AFTER -->
context="{'search_default_type_id': id}"
Remove numbercall field - no longer supported:
<!-- Remove this line -->
<field name="numbercall">-1</field>
# Add compatibility wrapper
from odoo.http import request
def slug(value):
"""Compatibility wrapper for slug function"""
return request.env['ir.http']._slug(value)
def unslug(value):
"""Compatibility wrapper for unslug function"""
return request.env['ir.http']._unslug(value)
# BEFORE
from odoo.addons.http_routing.models.ir_http import url_for
url = url_for('/path')
# AFTER
url = self.env['ir.http']._url_for('/path')
The RPC service is NOT available in Odoo 19 frontend/public components.
/** @odoo-module **/
// BEFORE (Odoo 17)
import {useService} from "@web/core/utils/hooks";
export class MyComponent extends Component {
setup() {
this.rpc = useService("rpc");
}
async fetchData() {
const data = await this.rpc("/api/endpoint", params);
}
}
// AFTER (Odoo 19)
export class MyComponent extends Component {
setup() {
// RPC service removed - using fetch instead
}
async _jsonRpc(endpoint, params = {}) {
try {
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Csrf-Token': document.querySelector('meta[name="csrf-token"]')?.content || '',
},
body: JSON.stringify({
jsonrpc: "2.0",
method: "call",
params: params,
id: Math.floor(Math.random() * 1000000)
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.error) {
throw new Error(data.error.message || 'RPC call failed');
}
return data.result;
} catch (error) {
console.error('JSON-RPC call failed:', error);
throw error;
}
}
async fetchData() {
const data = await this._jsonRpc("/api/endpoint", params);
}
}
OWL 2.0 (used in Odoo 18+) renames all lifecycle hooks and changes the constructor signature. This is a breaking change — old method names simply won't be called.
Lifecycle Hook Rename Table:
| OWL 1.x (Odoo 14-17) | OWL 2.0 (Odoo 18-19) | Notes |
|---|---|---|
constructor(parent, props) | setup() | No super() needed |
willStart() | onWillStart(callback) | Now uses onWillStart hook function |
mounted() | onMounted(callback) |
Before (OWL 1.x — Odoo 14-17):
/** @odoo-module **/
import { Component } from "@odoo/owl";
export class MyWidget extends Component {
constructor(parent, props) {
super(...arguments);
this.state = { count: 0 };
}
async willStart() {
this.data = await this._loadData();
}
mounted() {
this._setupListeners();
}
patched() {
this._updateDOM();
}
willUnmount() {
this._cleanupListeners();
}
}
After (OWL 2.0 — Odoo 18-19):
/** @odoo-module **/
import { Component, onMounted, onPatched, onWillStart, onWillUnmount, useState } from "@odoo/owl";
export class MyWidget extends Component {
setup() {
// useState replaces this.state = {}
this.state = useState({ count: 0 });
onWillStart(async () => {
this.data = await this._loadData();
});
onMounted(() => {
this._setupListeners();
});
onPatched(() => {
this._updateDOM();
});
onWillUnmount(() => {
this._cleanupListeners();
});
}
}
Key OWL 2.0 Import Changes:
// OLD — OWL 1.x
import { Component } from "@odoo/owl";
// NEW — OWL 2.0 (import hooks explicitly)
import {
Component,
useState,
useRef,
useEnv,
onMounted,
onPatched,
onWillStart,
onWillUpdateProps,
onWillPatch,
onWillUnmount,
onDestroyed,
} from "@odoo/owl";
Auto-fix command:
python scripts/auto_fix_library.py --fix owl_lifecycle <project_path>
Detection command:
python scripts/odoo19_precheck.py <project_path>
# Look for: [HIGH] OWL 1.x lifecycle methods found
// ===================================================================
// Theme Name - Primary Variables
// ===================================================================
// Typography Hierarchy
$o-theme-h1-font-size-multiplier: (64 / 16);
$o-theme-headings-font-weight: 700; // NOT $headings-font-weight
// Website Values Palette
$o-website-values-palettes: (
(
'color-palettes-name': 'my_theme',
'font': 'Inter',
'headings-font': 'Inter',
'btn-padding-y': 1rem, // Use rem not px
'btn-padding-x': 2rem,
),
);
// Color Palette with menu/footer assignments
$o-color-palettes: map-merge($o-color-palettes, (
'my_theme': (
'o-color-1': #124F81,
'o-color-2': #B1025D,
'o-color-3': #f8fafc,
'o-color-4': #ffffff,
'o-color-5': #1e293b,
'menu': 1, // IMPORTANT: Specify which color for menu
'footer': 4, // IMPORTANT: Specify which color for footer
'copyright': 5, // IMPORTANT: Specify which color for copyright
),
));
// Font Configuration (use map-merge!)
$o-theme-font-configs: map-merge($o-theme-font-configs, (
'Inter': (
'family': ('Inter', sans-serif),
'url': 'Inter:300,400,500,600,700&display=swap',
'properties': ( // IMPORTANT: Add properties section
'base': (
'font-size-base': 1rem,
'line-height-base': 1.6,
),
)
),
));
Remove incompatible website.snippet_options inheritance:
<!-- REMOVE this template - not compatible with Odoo 19 -->
<template id="custom_footer_op" inherit_id="website.snippet_options">
<!-- Snippet options content -->
</template>
useService("rpc") in frontend components_jsonRpc helper method using fetch API<field name="numbercall"> from cron definitions<group> tags not allowed in search views (Odoo 19)<group> tags, keep filters at root levelt-name="kanban-box" to t-name="card"active_id not available in form view contexts (Odoo 19)active_id with idAfter upgrade, test:
# Install upgraded module
python -m odoo -d [DB] -i [MODULE] --addons-path=odoo/addons,projects/[PROJECT] --stop-after-init
# Update module after changes
python -m odoo -d [DB] -u [MODULE] --stop-after-init
# Run with development mode for debugging
python -m odoo -d [DB] --dev=xml,css,js
# Install Python dependencies
pip install geopy spacy hachoir
Generate comprehensive reports documenting:
When upgrading projects with multiple interdependent modules:
Special considerations for themes:
After upgrade:
t-use-call removed, use t-callkanban-box → card<group> tags NOT allowed<tree> views → <list> views//tree → //listenv parameternumbercall field removedid,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_my_model_user,my.model.user,model_my_model,base.group_user,1,1,1,0
access_my_model_admin,my.model.admin,model_my_model,base.group_erp_manager,1,1,1,1
<record id="group_my_manager" model="res.groups">
<field name="name">My Manager</field>
<field name="category_id" ref="base.module_category_services"/>
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
</record>
<record id="my_model_rule" model="ir.rule">
<field name="name">My Model: User own records</field>
<field name="model_id" ref="model_my_model"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="False"/>
</record>
The sale portal templates have been completely restructured in Odoo 19:
<!-- BEFORE (Odoo 17/18) - Positional selectors -->
<xpath expr="//table[@id='sales_order_table']/thead/tr/th[3]" position="attributes">
<!-- AFTER (Odoo 19) - Named element selectors -->
<xpath expr="//table[@id='sales_order_table']/thead/tr/th[@id='product_unit_price_header']" position="attributes">
Available Header IDs:
th[@id='product_qty_header'] - Quantityth[@id='product_unit_price_header'] - Unit Priceth[@id='product_discount_header'] - Discount %th[@id='taxes_header'] - Taxesth[@id='subtotal_header'] - Amount/Subtotal<!-- BEFORE (Odoo 17/18) -->
<xpath expr="//table[@id='sales_order_table']/tbody//t[@t-foreach='lines_to_report']//tr/t[@t-if='not line.display_type']/td[3]" position="attributes">
<!-- AFTER (Odoo 19) -->
<xpath expr="//table[@id='sales_order_table']/tbody//tr[@name='tr_product']/td[@name='td_product_priceunit']" position="attributes">
Available Body Element Names:
tr[@name='tr_product'] - Product rowtr[@name='tr_section'] - Section rowtr[@name='tr_note'] - Note rowtd[@name='td_product_name'] - Product nametd[@name='td_product_quantity'] - Quantitytd[@name='td_product_priceunit'] - Unit pricetd[@name='td_product_discount'] - Discount %td[@name='td_product_taxes'] - Taxestd[@name='td_product_subtotal'] - Subtotal<!-- BEFORE (Odoo 17/18) - FAILS in Odoo 19 -->
<t t-out="format_datetime(env, object.date_start, dt_format='long')"/>
<t t-out="format_date(env, object.date_start)"/>
<t t-out="format_amount(env, object.amount, object.currency_id)"/>
<!-- AFTER (Odoo 19) - Remove env parameter -->
<t t-out="format_datetime(object.date_start, dt_format='long')"/>
<t t-out="format_date(object.date_start)"/>
<t t-out="format_amount(object.amount, object.currency_id)"/>
<!-- BEFORE - HTML entities (FAILS in XML) -->
<p>© 2025 Company Name</p>
<p>Company Name</p>
<!-- AFTER - Numeric character references -->
<p>© 2025 Company Name</p>
<p>Company Name</p>
Common Character Codes:
© → © (copyright) →   (non-breaking space)— → — (em dash)™ → ™ (trademark)€ → € (euro)| Bootstrap 4 | Bootstrap 5 | Description |
|---|---|---|
ml-* | ms-* | Margin Left → Start |
mr-* | me-* | Margin Right → End |
pl-* | ps-* | Padding Left → Start |
pr-* |
# Pre-check for compatibility issues
python scripts/odoo19_precheck.py <project_path>
# Quick targeted fixes
python scripts/quick_fix_odoo19.py <project_path>
# Full comprehensive upgrade
python scripts/upgrade_to_odoo19.py <project_path>
# Update manifest versions
python scripts/upgrade_manifest.py <project_path> 19.0
# Fix RPC service migrations
python scripts/fix_rpc_service.py <project_path>
# Install upgraded module
python -m odoo -d [DB] -i [MODULE] --addons-path=odoo/addons,projects/[PROJECT] --stop-after-init
# Update module after changes
python -m odoo -d [DB] -u [MODULE] --stop-after-init
# Run with development mode for debugging
python -m odoo -d [DB] --dev=all
Generate comprehensive reports documenting:
Weekly Installs
115
Repository
GitHub Stars
18
First Seen
Jan 22, 2026
Security Audits
Gen Agent Trust HubPassSocketWarnSnykPass
Installed on
opencode96
gemini-cli95
codex89
github-copilot83
kimi-cli74
amp74
agent-browser 浏览器自动化工具 - Vercel Labs 命令行网页操作与测试
152,900 周安装
Now uses onMounted hook function |
willUpdateProps(nextProps) | onWillUpdateProps(callback) | Callback receives nextProps |
patched() | onPatched(callback) | Now uses onPatched hook function |
willUnmount() | onWillUnmount(callback) | Now uses onWillUnmount hook function |
willPatch() | onWillPatch(callback) | Now uses onWillPatch hook function |
destroyed() | onDestroyed(callback) | Now uses onDestroyed hook function |
pe-* |
| Padding Right → End |
text-left | text-start | Text align left |
text-right | text-end | Text align right |
float-left | float-start | Float left |
float-right | float-end | Float right |
form-group | mb-3 | Form group spacing |
custom-select | form-select | Custom select |
close | btn-close | Close button |
badge-primary | bg-primary | Primary badge |
font-weight-bold | fw-bold | Bold text |
sr-only | visually-hidden | Screen reader only |
no-gutters | g-0 | No gutters in grid |