Breaking Changes
Contents
Breaking Changes#
This guide is for developers fixing Odoo 19 compatibility issues when migrating to OpenSPP V2.
Overview#
OpenSPP V2 upgrades from Odoo 17 to Odoo 19. This introduces critical breaking changes that require code updates.
Critical: View Type Changes#
<tree> → <list> (BLOCKS MODULE INSTALLATION)#
Impact: HIGH - Affects virtually every module Effort: Medium - Automated replacement possible
The Problem:
ParseError: Invalid view type: 'tree'.
Allowed types are: list, form, graph, pivot, calendar, kanban, search, qweb, activity
Fix Required:
<!-- Before (Odoo 17) -->
<record id="view_program_tree" model="ir.ui.view">
<field name="model">spp.program</field>
<field name="arch" type="xml">
<tree string="Programs">
<field name="name"/>
<field name="state"/>
</tree>
</field>
</record>
<!-- After (Odoo 19) -->
<record id="view_program_list" model="ir.ui.view">
<field name="model">spp.program</field>
<field name="arch" type="xml">
<list string="Programs">
<field name="name"/>
<field name="state"/>
</list>
</field>
</record>
Automated Fix:
# Replace all <tree> with <list> in XML files
find . -name "*.xml" -exec sed -i 's/<tree/<list/g' {} \;
find . -name "*.xml" -exec sed -i 's/<\/tree>/<\/list>/g' {} \;
# Also update in action view_mode
find . -name "*.xml" -exec sed -i 's/view_mode>tree,/view_mode>list,/g' {} \;
find . -name "*.xml" -exec sed -i 's/view_mode>tree</view_mode>list</g' {} \;
Also update in Python:
# Before (Odoo 17)
return {
'type': 'ir.actions.act_window',
'res_model': 'spp.program',
'view_mode': 'tree,form',
}
# After (Odoo 19)
return {
'type': 'ir.actions.act_window',
'res_model': 'spp.program',
'view_mode': 'list,form', # tree → list
}
Critical: Security Group Changes#
category_id Removed from res.groups#
Impact: HIGH - Blocks module installation Effort: Low - Simple deletion
The Problem:
ValueError: Invalid field 'category_id' in 'res.groups'
Fix Required:
<!-- Before (Odoo 17) -->
<record id="group_spp_admin" model="res.groups">
<field name="name">Administrator</field>
<field name="category_id" ref="module_category_openspp"/> <!-- REMOVE THIS -->
</record>
<!-- After (Odoo 19) -->
<record id="group_spp_admin" model="res.groups">
<field name="name">Administrator</field>
<!-- category_id removed - not needed in Odoo 19 -->
</record>
Automated Fix:
# Remove all category_id lines from security XML
find . -name "*.xml" -exec sed -i '/<field name="category_id"/d' {} \;
users Field Removed from res.groups#
Impact: MEDIUM - Blocks some modules Effort: Low - Remove field assignments
The Problem:
ValueError: Invalid field 'users' in 'res.groups'
Fix Required:
<!-- Before (Odoo 17) -->
<record id="group_spp_admin" model="res.groups">
<field name="name">Administrator</field>
<field name="users" eval="[(4, ref('base.user_admin'))]"/> <!-- REMOVE -->
</record>
<!-- After (Odoo 19) -->
<record id="group_spp_admin" model="res.groups">
<field name="name">Administrator</field>
<!-- Assign users via user records instead -->
</record>
Assign users differently:
<!-- Odoo 19 way: Update user records -->
<record id="base.user_admin" model="res.users">
<field name="groups_id" eval="[(4, ref('group_spp_admin'))]"/>
</record>
Python Import Changes#
odoo.osv Deprecated#
Impact: MEDIUM - Produces warnings Effort: Low - Simple import change
The Problem:
DeprecationWarning: Since 19.0, odoo.osv is deprecated use odoo.fields.Domain
Fix Required:
# Before (Odoo 17)
from odoo.osv.expression import OR, AND
domain = OR([domain1, domain2])
# After (Odoo 19)
from odoo.osv import expression # Keep for expression functions
domain = expression.OR([domain1, domain2])
# OR use tools
from odoo.tools import domain as domain_tools
domain = domain_tools.OR([domain1, domain2])
Command API Changes#
Many2many Command Format#
Impact: LOW - Only if using old format Effort: Low - Update to Command class
Updated approach (Odoo 19):
# Before (Odoo 17) - Numeric tuples still work but deprecated
vals = {
'group_ids': [(4, group_id), (4, another_id)]
}
# After (Odoo 19) - Use Command class (preferred)
from odoo import Command
vals = {
'group_ids': [
Command.link(group_id),
Command.link(another_id)
]
}
# Other Command operations
Command.create(vals_dict) # (0, 0, vals)
Command.update(id, vals) # (1, id, vals)
Command.delete(id) # (2, id)
Command.unlink(id) # (3, id)
Command.link(id) # (4, id)
Command.clear() # (5,)
Command.set(ids_list) # (6, 0, ids)
Constraint Changes#
SQL Constraints Must Have Unique Names#
Impact: LOW - Only affects modules with duplicate constraint names Effort: Low - Rename constraints
The Problem:
psycopg2.errors.DuplicateObject: constraint "unique_name" already exists
Fix Required:
# Before (Odoo 17) - Same name in multiple models
class Model1(models.Model):
_name = "model.one"
_sql_constraints = [
('unique_name', 'unique(name)', 'Name must be unique')
]
class Model2(models.Model):
_name = "model.two"
_sql_constraints = [
('unique_name', 'unique(name)', 'Name must be unique') # Duplicate!
]
# After (Odoo 19) - Unique names per model
class Model1(models.Model):
_name = "model.one"
_sql_constraints = [
('model_one_unique_name', 'unique(name)', 'Name must be unique')
]
class Model2(models.Model):
_name = "model.two"
_sql_constraints = [
('model_two_unique_name', 'unique(name)', 'Name must be unique')
]
XML Data Changes#
eval Attribute Changes#
Impact: LOW - Only specific eval patterns Effort: Low - Update eval expressions
Some eval patterns changed:
<!-- Before (Odoo 17) -->
<field name="date" eval="datetime.now()"/>
<!-- After (Odoo 19) - Use time module -->
<field name="date" eval="time.strftime('%Y-%m-%d')"/>
Testing Changes#
Test Decorators#
No major changes, but best practices updated:
# Odoo 19 best practices
from odoo.tests import TransactionCase, tagged
@tagged('post_install', '-at_install')
class TestProgram(TransactionCase):
def test_program_creation(self):
program = self.env['spp.program'].create({
'name': 'Test Program'
})
self.assertTrue(program.exists())
Migration Checklist#
After updating code, verify:
1. XML Views#
# Check for remaining <tree> tags
grep -r "<tree" --include="*.xml" .
# Should return no results in view definitions
2. Security Files#
# Check for category_id
grep -r "category_id" --include="*.xml" .
# Check for users in groups
grep -r '<field name="users"' --include="*.xml" .
# Check for groups in menus
grep -r 'menuitem.*groups=' --include="*.xml" .
3. Python Imports#
# Check for old osv imports
grep -r "from odoo.osv.expression import" --include="*.py" .
Automated Migration Script#
Complete script to fix all breaking changes:
#!/usr/bin/env python3
"""
Fix Odoo 19 breaking changes
Usage: python fix_odoo19.py /path/to/module
"""
import re
from pathlib import Path
def fix_tree_to_list(content):
"""Replace <tree> with <list>"""
content = re.sub(r'<tree(\s|>)', r'<list\1', content)
content = re.sub(r'</tree>', r'</list>', content)
content = re.sub(r"view_mode>tree,", r"view_mode>list,", content)
content = re.sub(r"view_mode>tree<", r"view_mode>list<", content)
return content
def fix_security_groups(content):
"""Remove category_id and users from groups"""
# Remove category_id lines
content = re.sub(r'\s*<field name="category_id"[^>]*/>.*\n', '', content)
# Remove users field lines
content = re.sub(r'\s*<field name="users"[^>]*/>.*\n', '', content)
return content
def fix_menu_groups(content):
"""Remove groups from menuitem"""
content = re.sub(r'\s+groups="[^"]*"', '', content, flags=re.MULTILINE)
return content
def fix_osv_imports(content):
"""Update osv imports"""
# Replace direct OR/AND imports
content = re.sub(
r'from odoo\.osv\.expression import (OR|AND)',
r'from odoo.osv import expression',
content
)
# Update usage if simple OR/AND
content = re.sub(r'\bOR\(', r'expression.OR(', content)
content = re.sub(r'\bAND\(', r'expression.AND(', content)
return content
def process_xml_file(file_path):
"""Process XML file"""
content = file_path.read_text()
original = content
content = fix_tree_to_list(content)
content = fix_security_groups(content)
content = fix_menu_groups(content)
if content != original:
file_path.write_text(content)
return True
return False
def process_py_file(file_path):
"""Process Python file"""
content = file_path.read_text()
original = content
content = fix_osv_imports(content)
if content != original:
file_path.write_text(content)
return True
return False
def main():
import sys
if len(sys.argv) != 2:
print("Usage: python fix_odoo19.py /path/to/module")
sys.exit(1)
module_path = Path(sys.argv[1])
# Process XML files
xml_changed = 0
for xml_file in module_path.rglob("*.xml"):
if process_xml_file(xml_file):
print(f"✓ XML: {xml_file.relative_to(module_path)}")
xml_changed += 1
# Process Python files
py_changed = 0
for py_file in module_path.rglob("*.py"):
if process_py_file(py_file):
print(f"✓ PY: {py_file.relative_to(module_path)}")
py_changed += 1
print(f"\nFixed {xml_changed} XML files and {py_changed} Python files")
if __name__ == "__main__":
main()
Common Error Messages#
Error: Invalid view type 'tree'#
ParseError: Invalid view type: 'tree'
Solution: Replace <tree> with <list> in view definitions
Error: Invalid field 'category_id'#
ValueError: Invalid field 'category_id' in 'res.groups'
Solution: Remove <field name="category_id"/> from group records
Error: Invalid field 'groups'#
ValueError: Invalid field 'groups' in 'ir.ui.menu'
Solution: Remove groups= attribute from menuitem tags
Warning: odoo.osv deprecated#
DeprecationWarning: Since 19.0, odoo.osv is deprecated
Solution: Update imports to use odoo.osv.expression or odoo.tools.domain
Testing After Fixes#
# tests/test_odoo19_compat.py
from odoo.tests import TransactionCase
class TestOdoo19Compatibility(TransactionCase):
def test_module_installs(self):
"""Verify module installs without errors"""
# Your module should install cleanly
pass
def test_views_load(self):
"""Verify all views load correctly"""
views = self.env['ir.ui.view'].search([
('model', 'like', 'spp.%')
])
for view in views:
# Should not raise ParseError
view._check_xml()
def test_no_tree_views(self):
"""Verify no tree views remain"""
tree_views = self.env['ir.ui.view'].search([
('type', '=', 'tree')
])
self.assertEqual(len(tree_views), 0, "All tree views should be list views")
See Also#
Namespace Changes (g2p.* → spp.*) - g2p to spp migration
Model and Field Renames - Model reference updates
openspp.org