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 = netsvc.Logger()
57 class MigrationManager(object):
59 This class manage the migration of modules
60 Migrations files must be python files containing a "migrate(cr, installed_version)" function.
61 Theses files must respect a directory tree structure: A 'migrations' folder which containt a
62 folder by version. Version can be 'module' version or 'server.module' version (in this case,
63 the files will only be processed by this version of the server). Python file names must start
64 by 'pre' or 'post' and will be executed, respectively, before and after the module initialisation
70 | |-- pre-update_table_x.py
71 | |-- pre-update_table_y.py
72 | |-- post-clean-data.py
73 | `-- README.txt # not processed
74 |-- 5.0.1.1 # files in this folder will be executed only on a 5.0 server
75 | |-- pre-delete_table_z.py
76 | `-- post-clean-data.py
77 `-- foo.py # not processed
79 This similar structure is generated by the maintenance module with the migrations files get by
80 the maintenance contract
83 def __init__(self, cr, graph):
92 import addons.base.maintenance.utils as maintenance_utils
93 maintenance_utils.update_migrations_files(self.cr)
96 for pkg in self.graph:
97 self.migrations[pkg.name] = {}
98 if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
101 get_module_filetree = openerp.modules.module.get_module_filetree
102 self.migrations[pkg.name]['module'] = get_module_filetree(pkg.name, 'migrations') or {}
103 self.migrations[pkg.name]['maintenance'] = get_module_filetree('base', 'maintenance/migrations/' + pkg.name) or {}
105 def migrate_module(self, pkg, stage):
106 assert stage in ('pre', 'post')
107 stageformat = {'pre': '[>%s]',
111 if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
114 def convert_version(version):
115 if version.startswith(release.major_version) and version != release.major_version:
116 return version # the version number already containt the server version
117 return "%s.%s" % (release.major_version, version)
119 def _get_migration_versions(pkg):
121 return [d for d in tree if tree[d] is not None]
124 __get_dir(self.migrations[pkg.name]['module']) +
125 __get_dir(self.migrations[pkg.name]['maintenance'])
127 versions.sort(key=lambda k: parse_version(convert_version(k)))
130 def _get_migration_files(pkg, version, stage):
131 """ return a list of tuple (module, file)
133 m = self.migrations[pkg.name]
136 mapping = {'module': opj(pkg.name, 'migrations'),
137 'maintenance': opj('base', 'maintenance', 'migrations', pkg.name),
140 for x in mapping.keys():
142 for f in m[x][version]:
143 if m[x][version][f] is not None:
145 if not f.startswith(stage + '-'):
147 lst.append(opj(mapping[x], version, f))
156 from openerp.tools.parse_version import parse_version
158 parsed_installed_version = parse_version(pkg.installed_version or '')
159 current_version = parse_version(convert_version(pkg.data['version']))
161 versions = _get_migration_versions(pkg)
163 for version in versions:
164 if parsed_installed_version < parse_version(convert_version(version)) <= current_version:
166 strfmt = {'addon': pkg.name,
168 'version': stageformat[stage] % version,
171 for pyfile in _get_migration_files(pkg, version, stage):
172 name, ext = os.path.splitext(os.path.basename(pyfile))
173 if ext.lower() != '.py':
175 mod = fp = fp2 = None
177 fp = tools.file_open(pyfile)
179 # imp.load_source need a real file object, so we create
180 # one from the file-like object we get from file_open
185 mod = imp.load_source(name, pyfile, fp2)
186 logger.notifyChannel('migration', netsvc.LOG_INFO, 'module %(addon)s: Running migration %(version)s %(name)s' % mergedict({'name': mod.__name__}, strfmt))
187 mod.migrate(self.cr, pkg.installed_version)
189 logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Unable to load %(stage)s-migration file %(file)s' % mergedict({'file': pyfile}, strfmt))
191 except AttributeError:
192 logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Each %(stage)s-migration file must have a "migrate(cr, installed_version)" function' % strfmt)
204 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: