Addon updates and data migration

The data model you choose when writing an addon might turn out to have some weaknesses, so you may need to adjust it during the life cycle of your addon. In order to allow that without a lot of hacks, Odoo supports versioning in addons and running migrations if necessary.

How to do it...

We assume that in an earlier version of our module, the date_release field was a character field, where people wrote whatever they saw fit as the date. Now we realize we need this field for comparisons and aggregations, which is why we want to change its type to Date. Odoo does a great job at type conversions, but, in this case, we're on our own, which is why we need to provide instructions on how to transform a database with the previous version of our module installed to a database where the current version can run:

  1. Bump the version in your __openerp__.py manifest:
        'version': '9.0.1.0.1',
  2. Provide pre-migration code in migrations/9.0.1.0.1/pre-migrate.py:
    def migrate(cr, version):
        cr.execute('ALTER TABLE library_book RENAME COLUMN
            date_release TO date_release_char')
  3. Provide post migration code in migrations/9.0.1.0.1/post-migrate.py:
    from openerp import fields
    from datetime import date
    
    def migrate(cr, version):
        cr.execute('SELECT id, date_release_char FROM library_book')
        for record_id, old_date in cr.fetchall():
            # check if the field happens to be set in Odoo's internal
            # format
            new_date = None
            try:
                new_date = fields.Date.from_string(old_date)
            except:
                if len(old_date) == 4 and old_date.isdigit():
                    # probably a year
                    new_date = date(int(old_date), 1, 1)
                else:
                    # try some separators, play with day/month/year 
                    # order
                    # ...
            if new_date:
                cr.execute('UPDATE library_book SET date_release=%s', (new_date,))

Without this code, Odoo would have renamed the old date_release column to date_release_moved and created a new one, because there's no automatic conversion from character fields to date fields. From the point of view of the user, the data in date_release would simply be gone.

How it works...

The first crucial point is that you increase the version number of your addon, as migrations run only between different versions. During every update, Odoo writes the version number from the manifest at the time of the update into the table ir_module_module. The version number is prefixed with Odoo's major and minor version, if the version number has three or fewer components. In the example above, we explicitly also named Odoo's major and minor version, which is good practice, but a value of 1.0.1 would have had the same effect, because, internally, Odoo prefixes short version numbers for addons with its own major and minor version number. Generally, using the long notation is a good thing because you can see with just a glance at the manifest file for which version of Odoo an addon is meant.

The two migration files are just code files that don't need to be registered anywhere. When updating an addon, Odoo compares the addon's version as noted in ir_module_module with the version in the addon's manifest. If the manifest's version is higher (after possibly adding Odoo's major and minor version), this addon's migrations folder will be searched if it contains folders with the version(s) in between, up to, and including the version that is currently updated.

Then, within the folders found, it searches for Python files whose names start with pre-, loads them, and expects them to define a function called migrate, which has two parameters. This function is called with a database cursor as the first argument and the currently installed version as the second argument. This happens before Odoo even looks at the rest of the code the addon defines, so you can assume nothing changed in your database layout compared to the previous version.

After all pre-migrate functions run successfully, Odoo loads the models and data declared in the addon, which can cause changes in the database layout. Given we renamed date_release in pre-migrate.py, Odoo will just create a new column with that name, but with the correct data type.

After that, with the same search algorithm, post-migrate files will be searched and executed if found. In our case, we need to look at every value and see if we can make something usable out of it, otherwise, we keep NULL as data. Don't write scripts iterating over a whole table yourself if not absolutely necessary; in this case, we should have written a very big unreadable SQL switch that does what we want.

Note

If you simply want to rename a column, you don't need a migration script. In this case, you can set the oldname parameter of the field in question to the field's old column name, Odoo then takes care of the renaming itself.

There's more...

In both the pre- and post-migration steps, you only have access to a cursor, which is not very convenient if you're used to Odoo environments. It can lead to unexpected results to use models at this stage, because in the pre- step, the addon's models are not yet loaded and also, in the post- step, models defined by addons depending on the current addon are not yet loaded too. But if this is not a problem for you, either because you want to use a model your addon doesn't touch or a model for which you know the aforementioned is not a problem, you can create the environment you're used to by writing the following:

from openerp import SUPERUSER_ID
from openerp.api import Environment

def migrate(cr, version):
    env = Environment(cr, SUPERUSER_ID, {})
    # env holds all currently loaded models

See also

When writing migration scripts, you'll often be confronted with repetitive tasks like checking if a column or table exists, renaming things, or mapping some old values to new values. It's frustrating and error-prone to reinvent the wheel here; consider using https://github.com/OCA/openupgradelib if you can afford the extra dependency.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset