2011-05-11 16:41:44 +00:00
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2010-2011 OpenERP s.a. (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
""" Modules migration handling. """
import os , sys , imp
from os . path import join as opj
import itertools
import zipimport
import openerp
import openerp . osv as osv
import openerp . tools as tools
import openerp . tools . osutil as osutil
from openerp . tools . safe_eval import safe_eval as eval
import openerp . pooler as pooler
from openerp . tools . translate import _
import openerp . netsvc as netsvc
import zipfile
import openerp . release as release
import re
import base64
from zipfile import PyZipFile , ZIP_DEFLATED
from cStringIO import StringIO
import logging
import openerp . modules . db
import openerp . modules . graph
logger = netsvc . Logger ( )
class MigrationManager ( object ) :
"""
This class manage the migration of modules
Migrations files must be python files containing a " migrate(cr, installed_version) " function .
Theses files must respect a directory tree structure : A ' migrations ' folder which containt a
folder by version . Version can be ' module ' version or ' server.module ' version ( in this case ,
the files will only be processed by this version of the server ) . Python file names must start
by ' pre ' or ' post ' and will be executed , respectively , before and after the module initialisation
Example :
< moduledir >
` - - migrations
| - - 1.0
| | - - pre - update_table_x . py
| | - - pre - update_table_y . py
| | - - post - clean - data . py
| ` - - README . txt # not processed
| - - 5.0 .1 .1 # files in this folder will be executed only on a 5.0 server
| | - - pre - delete_table_z . py
| ` - - post - clean - data . py
` - - foo . py # not processed
This similar structure is generated by the maintenance module with the migrations files get by
the maintenance contract
"""
def __init__ ( self , cr , graph ) :
self . cr = cr
self . graph = graph
self . migrations = { }
self . _get_files ( )
def _get_files ( self ) :
"""
import addons . base . maintenance . utils as maintenance_utils
maintenance_utils . update_migrations_files ( self . cr )
#"""
for pkg in self . graph :
self . migrations [ pkg . name ] = { }
if not ( hasattr ( pkg , ' update ' ) or pkg . state == ' to upgrade ' ) :
continue
2011-05-11 17:24:48 +00:00
get_module_filetree = openerp . modules . module . get_module_filetree
2011-05-11 16:41:44 +00:00
self . migrations [ pkg . name ] [ ' module ' ] = get_module_filetree ( pkg . name , ' migrations ' ) or { }
self . migrations [ pkg . name ] [ ' maintenance ' ] = get_module_filetree ( ' base ' , ' maintenance/migrations/ ' + pkg . name ) or { }
def migrate_module ( self , pkg , stage ) :
assert stage in ( ' pre ' , ' post ' )
stageformat = { ' pre ' : ' [> %s ] ' ,
' post ' : ' [ %s >] ' ,
}
if not ( hasattr ( pkg , ' update ' ) or pkg . state == ' to upgrade ' ) :
return
def convert_version ( version ) :
if version . startswith ( release . major_version ) and version != release . major_version :
return version # the version number already containt the server version
return " %s . %s " % ( release . major_version , version )
def _get_migration_versions ( pkg ) :
def __get_dir ( tree ) :
return [ d for d in tree if tree [ d ] is not None ]
versions = list ( set (
__get_dir ( self . migrations [ pkg . name ] [ ' module ' ] ) +
__get_dir ( self . migrations [ pkg . name ] [ ' maintenance ' ] )
) )
versions . sort ( key = lambda k : parse_version ( convert_version ( k ) ) )
return versions
def _get_migration_files ( pkg , version , stage ) :
""" return a list of tuple (module, file)
"""
m = self . migrations [ pkg . name ]
lst = [ ]
mapping = { ' module ' : opj ( pkg . name , ' migrations ' ) ,
' maintenance ' : opj ( ' base ' , ' maintenance ' , ' migrations ' , pkg . name ) ,
}
for x in mapping . keys ( ) :
if version in m [ x ] :
for f in m [ x ] [ version ] :
if m [ x ] [ version ] [ f ] is not None :
continue
if not f . startswith ( stage + ' - ' ) :
continue
lst . append ( opj ( mapping [ x ] , version , f ) )
lst . sort ( )
return lst
def mergedict ( a , b ) :
a = a . copy ( )
a . update ( b )
return a
from openerp . tools . parse_version import parse_version
parsed_installed_version = parse_version ( pkg . installed_version or ' ' )
current_version = parse_version ( convert_version ( pkg . data [ ' version ' ] ) )
versions = _get_migration_versions ( pkg )
for version in versions :
if parsed_installed_version < parse_version ( convert_version ( version ) ) < = current_version :
strfmt = { ' addon ' : pkg . name ,
' stage ' : stage ,
' version ' : stageformat [ stage ] % version ,
}
for pyfile in _get_migration_files ( pkg , version , stage ) :
name , ext = os . path . splitext ( os . path . basename ( pyfile ) )
if ext . lower ( ) != ' .py ' :
continue
mod = fp = fp2 = None
try :
fp = tools . file_open ( pyfile )
# imp.load_source need a real file object, so we create
# one from the file-like object we get from file_open
fp2 = os . tmpfile ( )
fp2 . write ( fp . read ( ) )
fp2 . seek ( 0 )
try :
mod = imp . load_source ( name , pyfile , fp2 )
logger . notifyChannel ( ' migration ' , netsvc . LOG_INFO , ' module %(addon)s : Running migration %(version)s %(name)s ' % mergedict ( { ' name ' : mod . __name__ } , strfmt ) )
mod . migrate ( self . cr , pkg . installed_version )
except ImportError :
logger . notifyChannel ( ' migration ' , netsvc . LOG_ERROR , ' module %(addon)s : Unable to load %(stage)s -migration file %(file)s ' % mergedict ( { ' file ' : pyfile } , strfmt ) )
raise
except AttributeError :
logger . notifyChannel ( ' migration ' , netsvc . LOG_ERROR , ' module %(addon)s : Each %(stage)s -migration file must have a " migrate(cr, installed_version) " function ' % strfmt )
except :
raise
finally :
if fp :
fp . close ( )
if fp2 :
fp2 . close ( )
if mod :
del mod
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: