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-2011 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. """
26 from os.path import join as opj
32 import openerp.osv as osv
33 import openerp.tools as tools
34 import openerp.tools.osutil as osutil
35 from openerp.tools.safe_eval import safe_eval as eval
36 import openerp.pooler as pooler
37 from openerp.tools.translate import _
39 import openerp.netsvc as netsvc
42 import openerp.release as release
46 from zipfile import PyZipFile, ZIP_DEFLATED
47 from cStringIO import StringIO
51 import openerp.modules.db
52 import openerp.modules.graph
54 _logger = logging.getLogger(__name__)
56 class MigrationManager(object):
58 This class manage the migration of modules
59 Migrations files must be python files containing a "migrate(cr, installed_version)" function.
60 Theses files must respect a directory tree structure: A 'migrations' folder which containt a
61 folder by version. Version can be 'module' version or 'server.module' version (in this case,
62 the files will only be processed by this version of the server). Python file names must start
63 by 'pre' or 'post' and will be executed, respectively, before and after the module initialisation
69 | |-- pre-update_table_x.py
70 | |-- pre-update_table_y.py
71 | |-- post-clean-data.py
72 | `-- README.txt # not processed
73 |-- 5.0.1.1 # files in this folder will be executed only on a 5.0 server
74 | |-- pre-delete_table_z.py
75 | `-- post-clean-data.py
76 `-- foo.py # not processed
78 This similar structure is generated by the maintenance module with the migrations files get by
79 the maintenance contract
82 def __init__(self, cr, graph):
91 import addons.base.maintenance.utils as maintenance_utils
92 maintenance_utils.update_migrations_files(self.cr)
95 for pkg in self.graph:
96 self.migrations[pkg.name] = {}
97 if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
100 get_module_filetree = openerp.modules.module.get_module_filetree
101 self.migrations[pkg.name]['module'] = get_module_filetree(pkg.name, 'migrations') or {}
102 self.migrations[pkg.name]['maintenance'] = get_module_filetree('base', 'maintenance/migrations/' + pkg.name) or {}
104 def migrate_module(self, pkg, stage):
105 assert stage in ('pre', 'post')
106 stageformat = {'pre': '[>%s]',
110 if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
113 def convert_version(version):
114 if version.startswith(release.major_version) and version != release.major_version:
115 return version # the version number already containt the server version
116 return "%s.%s" % (release.major_version, version)
118 def _get_migration_versions(pkg):
120 return [d for d in tree if tree[d] is not None]
123 __get_dir(self.migrations[pkg.name]['module']) +
124 __get_dir(self.migrations[pkg.name]['maintenance'])
126 versions.sort(key=lambda k: parse_version(convert_version(k)))
129 def _get_migration_files(pkg, version, stage):
130 """ return a list of tuple (module, file)
132 m = self.migrations[pkg.name]
135 mapping = {'module': opj(pkg.name, 'migrations'),
136 'maintenance': opj('base', 'maintenance', 'migrations', pkg.name),
139 for x in mapping.keys():
141 for f in m[x][version]:
142 if m[x][version][f] is not None:
144 if not f.startswith(stage + '-'):
146 lst.append(opj(mapping[x], version, f))
155 from openerp.tools.parse_version import parse_version
157 parsed_installed_version = parse_version(pkg.installed_version or '')
158 current_version = parse_version(convert_version(pkg.data['version']))
160 versions = _get_migration_versions(pkg)
162 for version in versions:
163 if parsed_installed_version < parse_version(convert_version(version)) <= current_version:
165 strfmt = {'addon': pkg.name,
167 'version': stageformat[stage] % version,
170 for pyfile in _get_migration_files(pkg, version, stage):
171 name, ext = os.path.splitext(os.path.basename(pyfile))
172 if ext.lower() != '.py':
174 mod = fp = fp2 = None
176 fp = tools.file_open(pyfile)
178 # imp.load_source need a real file object, so we create
179 # one from the file-like object we get from file_open
184 mod = imp.load_source(name, pyfile, fp2)
185 _logger.info('module %(addon)s: Running migration %(version)s %(name)s' % mergedict({'name': mod.__name__}, strfmt))
186 mod.migrate(self.cr, pkg.installed_version)
188 _logger.error('module %(addon)s: Unable to load %(stage)s-migration file %(file)s' % mergedict({'file': pyfile}, strfmt))
190 except AttributeError:
191 _logger.error('module %(addon)s: Each %(stage)s-migration file must have a "migrate(cr, installed_version)" function' % strfmt)
203 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: