Introduction
When developing an Odoo application, you may encounter a situation where you need to change the data type of a model field that already contains data. A common example is changing a field from a character string (Char) to a number (Integer or Float). Simply changing the field type in your Python code can lead to errors or data loss. By leveraging the openupgradelib library (Upgrade Utils), you can safely manage these migrations.
This blog post will guide you through the process of safely migrating a field type in Odoo without losing data, using an example with openupgradelib. We will create two modules: one to set up the initial state (a field with a string type) and another to perform the migration.
Scenario
Our goal is to change a field named my_age on the Partner (res.partner) model from a Char to an Integer. We will start by creating a module that adds the my_age field as a Char and allows us to add some data. Then, we will create a second module to handle the migration.
Module 1: The Test Module (partner_my_age_test)
This module is responsible for creating the initial state. It adds the my_age field as a Char to the res.partner model and adds the field to the Partner form view.
Module Structure
partner_my_age_test/
+-- __init__.py
+-- __manifest__.py
+-- models/
¦ +-- __init__.py
¦ +-- res_partner.py
+-- views/
+-- res_partner_view.xml
Code
__manifest__.py
{
'name': 'Partner My Age Test',
'version': '17.0.1.0.0',
'summary': 'Adds a my_age string field to Partner for testing migration',
'author': 'Your Name',
'website': 'https://www.yourcompany.com',
'category': 'Technical',
'depends': ['base'],
'data': ['views/res_partner_view.xml'],
'installable': True,
'application': False,
'auto_install': False,
}models/res_partner.py
# -*- coding: utf-8 -*-
from odoo import models, fields
class ResPartner(models.Model):
_inherit = 'res.partner'
my_age = fields.Char(string='My Age')
views/res_partner_view.xml
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_partner_form_my_age" model="ir.ui.view">
<field name="name">res.partner.form.my_age</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<field name="vat" position="after">
<field name="my_age"/>
</field>
</field>
</record>
</data>
</odoo>
Module 2: The Migration Module (partner_my_age_migration)
This module performs the actual migration. It uses Odoo's openupgradelib library to handle the database operations in a safe and predictable way.
Module Structure
partner_my_age_migration/
+-- __init__.py
+-- __manifest__.py
+-- migrations/
¦ +-- 17.0.1.0.0/
¦ ¦ +-- __init__.py
¦ ¦ +-- post-migrate.py
¦ ¦ +-- pre-migrate.py
¦ +-- __init__.py
+-- models/
+-- __init__.py
+-- res_partner.py
Code
__manifest__.py
{
'name': 'Partner My Age Migration',
'version': '17.0.1.0.0',
'summary': 'Migrates the my_age field from Char to Integer',
'author': 'Your Name',
'website': 'https://www.yourcompany.com',
'category': 'Technical',
'depends': ['base'],
'data': [],
'installable': True,
'application': False,
'auto_install': False,
}migrations/17.0.1.0.0/pre-migrate.py
# -*- coding: utf-8 -*-
from openupgradelib import openupgrade
@openupgrade.migrate()
def migrate(env, version):
if openupgrade.column_exists(env.cr, 'res_partner', 'my_age'):
openupgrade.rename_column(env.cr, 'res_partner', 'my_age', 'my_age_old')
models/res_partner.py
# -*- coding: utf-8 -*-
from odoo import models, fields
class ResPartner(models.Model):
_inherit = 'res.partner'
my_age = fields.Integer(string='My Age')
migrations/17.0.1.0.0/post-migrate.py
# -*- coding: utf-8 -*-
from odoo.tools import safe_eval
from openupgradelib import openupgrade
@openupgrade.migrate()
def migrate(env, version):
if openupgrade.column_exists(env.cr, 'res_partner', 'my_age_old'):
openupgrade.add_fields(env, [('my_age', 'res.partner', 'res_partner')])
env.cr.execute("SELECT id, my_age_old FROM res_partner WHERE my_age_old IS NOT NULL")
for record_id, old_value in env.cr.fetchall():
try:
new_value = int(old_value)
env.cr.execute("UPDATE res_partner SET my_age = %s WHERE id = %s", (new_value, record_id))
except (ValueError, TypeError):
# Handle cases where the old value is not a valid integer
# You can log this or set a default value
pass
openupgrade.drop_column(env.cr, 'res_partner', 'my_age_old')
How to Test
- Add both partner_my_age_test and partner_my_age_migration to your Odoo addons path.
- Restart your Odoo server.
- Install the partner_my_age_test module.
- Go to any partner record, and you will see the "My Age" field. Enter some numeric values (as text) in this field for a few partner records.
- Now, install the partner_my_age_migration module.
- After the installation is complete, upgrade the partner_my_age_migration module.
- Once the upgrade is finished, the "My Age" field will be an integer field, and the values you entered should be preserved as numbers.
Conclusion
By using Odoo's migration scripts and the openupgradelib library (Upgrade Utils), you can perform complex data migrations with confidence. The pre-migrate.py and post-migrate.py scripts, powered by openupgradelib, provide a powerful mechanism to manage database schema changes without losing valuable data. This approach ensures that your Odoo modules are robust and maintainable in the long run.
To read more about Overview of Database Migration in Odoo, refer to our blog Overview of Database Migration in Odoo.