1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 # Copyright (C) 2010-2013 OpenERP s.a. (<http://openerp.com>).
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Affero General Public License as
10 # published by the Free Software Foundation, either version 3 of the
11 # License, or (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Affero General Public License for more details.
18 # You should have received a copy of the GNU Affero General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
23 """ Modules migration handling. """
28 from os.path import join as opj
31 import openerp.release as release
32 import openerp.tools as tools
33 from openerp.tools.parse_version import parse_version
36 _logger = logging.getLogger(__name__)
38 class MigrationManager(object):
40 This class manage the migration of modules
41 Migrations files must be python files containing a "migrate(cr, installed_version)" function.
42 Theses files must respect a directory tree structure: A 'migrations' folder which containt a
43 folder by version. Version can be 'module' version or 'server.module' version (in this case,
44 the files will only be processed by this version of the server). Python file names must start
45 by 'pre' or 'post' and will be executed, respectively, before and after the module initialisation
51 | |-- pre-update_table_x.py
52 | |-- pre-update_table_y.py
53 | |-- post-clean-data.py
54 | `-- README.txt # not processed
55 |-- 5.0.1.1 # files in this folder will be executed only on a 5.0 server
56 | |-- pre-delete_table_z.py
57 | `-- post-clean-data.py
58 `-- foo.py # not processed
60 This similar structure is generated by the maintenance module with the migrations files get by
61 the maintenance contract
64 def __init__(self, cr, graph):
73 import addons.base.maintenance.utils as maintenance_utils
74 maintenance_utils.update_migrations_files(self.cr)
77 for pkg in self.graph:
78 self.migrations[pkg.name] = {}
79 if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
82 get_module_filetree = openerp.modules.module.get_module_filetree
83 self.migrations[pkg.name]['module'] = get_module_filetree(pkg.name, 'migrations') or {}
84 self.migrations[pkg.name]['maintenance'] = get_module_filetree('base', 'maintenance/migrations/' + pkg.name) or {}
86 def migrate_module(self, pkg, stage):
87 assert stage in ('pre', 'post')
93 if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade') or pkg.installed_version is None:
96 def convert_version(version):
97 if version.count('.') >= 2:
98 return version # the version number already containt the server version
99 return "%s.%s" % (release.major_version, version)
101 def _get_migration_versions(pkg):
103 return [d for d in tree if tree[d] is not None]
106 __get_dir(self.migrations[pkg.name]['module']) +
107 __get_dir(self.migrations[pkg.name]['maintenance'])
109 versions.sort(key=lambda k: parse_version(convert_version(k)))
112 def _get_migration_files(pkg, version, stage):
113 """ return a list of tuple (module, file)
115 m = self.migrations[pkg.name]
119 'module': opj(pkg.name, 'migrations'),
120 'maintenance': opj('base', 'maintenance', 'migrations', pkg.name),
123 for x in mapping.keys():
125 for f in m[x][version]:
126 if m[x][version][f] is not None:
128 if not f.startswith(stage + '-'):
130 lst.append(opj(mapping[x], version, f))
139 parsed_installed_version = parse_version(pkg.installed_version or '')
140 current_version = parse_version(convert_version(pkg.data['version']))
142 versions = _get_migration_versions(pkg)
144 for version in versions:
145 if parsed_installed_version < parse_version(convert_version(version)) <= current_version:
147 strfmt = {'addon': pkg.name,
149 'version': stageformat[stage] % version,
152 for pyfile in _get_migration_files(pkg, version, stage):
153 name, ext = os.path.splitext(os.path.basename(pyfile))
154 if ext.lower() != '.py':
156 mod = fp = fp2 = None
158 fp = tools.file_open(pyfile)
160 # imp.load_source need a real file object, so we create
161 # one from the file-like object we get from file_open
166 mod = imp.load_source(name, pyfile, fp2)
167 _logger.info('module %(addon)s: Running migration %(version)s %(name)s' % mergedict({'name': mod.__name__}, strfmt))
168 migrate = mod.migrate
170 _logger.exception('module %(addon)s: Unable to load %(stage)s-migration file %(file)s' % mergedict({'file': pyfile}, strfmt))
172 except AttributeError:
173 _logger.error('module %(addon)s: Each %(stage)s-migration file must have a "migrate(cr, installed_version)" function' % strfmt)
175 migrate(self.cr, pkg.installed_version)
185 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: