← Back to team overview

credativ team mailing list archive

[Merge] lp:~therp-nl/openupgrade-server/5.0-use_orm into lp:openupgrade-server/5.0

 

Stefan Rijnhart (Therp) has proposed merging lp:~therp-nl/openupgrade-server/5.0-use_orm into lp:openupgrade-server/5.0.

Requested reviews:
  OpenUpgrade Committers (openupgrade-committers)

For more details, see:
https://code.launchpad.net/~therp-nl/openupgrade-server/5.0-use_orm/+merge/105195

This merge constitutes the refactoring of the database layout analysis and the inclusion of the analysis files for 5.0, as described here:

https://lists.launchpad.net/openupgrade-drivers/msg00001.html

-- 
https://code.launchpad.net/~therp-nl/openupgrade-server/5.0-use_orm/+merge/105195
Your team OpenUpgrade Committers is requested to review the proposed merge of lp:~therp-nl/openupgrade-server/5.0-use_orm into lp:openupgrade-server/5.0.
=== modified file 'bin/addons/__init__.py'
--- bin/addons/__init__.py	2011-11-30 22:57:11 +0000
+++ bin/addons/__init__.py	2012-05-09 12:36:18 +0000
@@ -41,6 +41,15 @@
 
 logger = netsvc.Logger()
 
+### OpenUpgrade
+def table_exists(cr, table):
+    """ Check whether a certain table or view exists """
+    cr.execute(
+        'SELECT count(relname) FROM pg_class WHERE relname = %s',
+        (table,))
+    return cr.fetchone()[0] == 1
+### End of OpenUpgrade
+
 _ad = os.path.abspath(opj(tools.config['root_path'], 'addons'))     # default addons path (base)
 ad = os.path.abspath(tools.config['addons_path'])           # alternate addons path
 
@@ -571,12 +580,133 @@
     modobj = None
 
     import string
+                
+    local_registry = {}
     def get_repr(properties, type='val'):
+        """ 
+        OpenUpgrade: Return the string representation of the model or field
+        for logging purposes 
+        """
         if type == 'key':
             props = ['model', 'field']
         elif type == 'val':
-            props = ['type', 'isfunction', 'relation', 'required', 'selection_keys', 'req_default', 'inherits']
-        return ','.join(["\"" + string.replace(properties[prop], '\"', '\'') + "\"" for prop in props])
+            props = [
+                'type', 'isfunction', 'relation', 'required', 'selection_keys',
+                'req_default', 'inherits'
+                ]
+        return ','.join([
+                '\"' + string.replace(
+                    string.replace(
+                        properties[prop], '\"', '\''), '\n','')
+                + '\"' for prop in props
+                ])
+
+    def log_model(model):
+        """                                                                                          
+        OpenUpgrade: Store the characteristics of the BaseModel and its fields
+        in the local registry, so that we can compare changes with the
+        main registry
+        """
+
+        # persistent models only
+        if isinstance(model, osv.osv.osv_memory):
+            return
+
+        model_registry = local_registry.setdefault(
+                model._name, {})
+        if model._inherits:
+            model_registry['_inherits'] = {'_inherits': unicode(model._inherits)}
+        for k, v in model._columns.items():
+            properties = { 
+                'type': v._type,
+                'isfunction': (
+                    isinstance(v, osv.fields.function) and 'function' or ''),
+                'relation': (
+                    v._type in ('many2many', 'many2one','one2many')
+                    and v._obj or ''
+                    ),
+                'required': v.required and 'required' or '',
+                'selection_keys': '',
+                'req_default': '',
+                'inherits': '',
+                }
+            if v._type == 'selection':
+                if hasattr(v.selection, "__iter__"):
+                    properties['selection_keys'] = unicode(
+                        sorted([x[0] for x in v.selection]))
+                else:
+                    properties['selection_keys'] = 'function'
+            if v.required and k in model._defaults:
+                if isinstance(model._defaults[k], types.FunctionType):
+                    # todo: in OpenERP 5 (and in 6 as well),
+                    # literals are wrapped in a lambda function.
+                    properties['req_default'] = 'function'
+                else:
+                    properties['req_default'] = unicode(model._defaults[k])
+            for key, value in properties.items():
+                if value:
+                    model_registry.setdefault(k, {})[key] = value
+
+    def get_record_id(cr, module, model, field, mode):
+        """
+        OpenUpgrade: get or create the id from the record table matching
+        the key parameter values
+        """
+        cr.execute(
+            "SELECT id FROM openupgrade_record "
+            "WHERE module = %s AND model = %s AND "
+            "field = %s AND mode = %s AND type = %s",
+            (module, model, field, mode, 'field')
+            )
+        record = cr.fetchone()
+        if record:
+            return record[0]
+        cr.execute(
+            "INSERT INTO openupgrade_record "
+            "(module, model, field, mode, type) "
+            "VALUES (%s, %s, %s, %s, %s)",
+            (module, model, field, mode, 'field')
+            )
+        cr.execute(
+            "SELECT id FROM openupgrade_record "
+            "WHERE module = %s AND model = %s AND "
+            "field = %s AND mode = %s AND type = %s",
+            (module, model, field, mode, 'field')
+            )
+        return cr.fetchone()[0]
+        
+    def compare_registries(cr, module):
+        """
+        OpenUpgrade: Compare the local registry with the global registry,
+        log any differences and merge the local registry with
+        the global one.
+        """
+        if not table_exists(cr, 'openupgrade_record'):
+            return
+        for model, fields in local_registry.items():
+            registry.setdefault(model, {})
+            for field, attributes in fields.items():
+                old_field = registry[model].setdefault(field, {})
+                mode = old_field and 'modify' or 'create'
+                record_id = False
+                for key, value in attributes.items():
+                    if key not in old_field or old_field[key] != value:
+                        if not record_id:
+                            record_id = get_record_id(
+                                cr, module, model, field, mode)
+                        cr.execute(
+                            "SELECT id FROM openupgrade_attribute "
+                            "WHERE name = %s AND value = %s AND "
+                            "record_id = %s",
+                            (key, value, record_id)
+                            )
+                        if not cr.fetchone():
+                            cr.execute(
+                                "INSERT INTO openupgrade_attribute "
+                                "(name, value, record_id) VALUES (%s, %s, %s)",
+                                (key, value, record_id)
+                                )
+                        old_field[key] = value
 
     for package in graph:
         logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading objects' % package.name)
@@ -584,53 +714,10 @@
         register_class(package.name)
         modules = pool.instanciate(package.name, cr)
 
-        logger.notifyChannel('OpenUpgrade_FIELD', netsvc.LOG_INFO, 'module %s' % (package.name))
         local_registry = {}
-        for orm_object in osv.orm.orm:
-            if orm_object._inherits:
-                properties = { 
-                    'model': orm_object._name,
-                    'field': '_inherits',
-                    'type': '',
-                    'isfunction': '',
-                    'relation': '',
-                    'required': '',
-                    'selection_keys': '',
-                    'req_default': '',
-                    'inherits': unicode(orm_object._inherits),
-                    }
-                local_registry[get_repr(properties, 'key')] = get_repr(properties)
-            for k,v  in orm_object._columns.items():
-                properties = { 
-                    'model': orm_object._name,
-                    'field': k,
-                    'type': v._type,
-                    'isfunction': isinstance(v, osv.fields.function) and 'function' or '',
-                    'relation': v._type in ('many2many', 'many2one','one2many') and v._obj or '',
-                    'required': v.required and 'required' or '',
-                    'selection_keys': '',
-                    'req_default': '',
-                    'inherits': '',
-                    }
-                if v._type == 'selection':
-                    if hasattr(v.selection, "__iter__"):
-                        properties['selection_keys'] = unicode(sorted([x[0] for x in v.selection]))
-                    else:
-                        properties['selection_keys'] = 'function'
-                if v.required and k in orm_object._defaults:
-                    if isinstance(orm_object._defaults[k], types.FunctionType):
-                        properties['req_default'] = 'function'
-                    else:
-                        properties['req_default'] = unicode(orm_object._defaults[k])
-                local_registry[get_repr(properties, 'key')] = get_repr(properties)
-        for key in sorted(local_registry.keys()):
-            if key in registry:
-                if registry[key] != local_registry[key]:
-                    logger.notifyChannel('OpenUpgrade_FIELD', netsvc.LOG_INFO, '"%s","modify",%s,%s' % (package.name, key, local_registry[key]))
-            else:
-                logger.notifyChannel('OpenUpgrade_FIELD', netsvc.LOG_INFO, '"%s","create",%s,%s' % (package.name, key, local_registry[key]))
-            registry[key] = local_registry[key]
-
+        for model in modules:
+            log_model(model)
+        compare_registries(cr, package.name)
         if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
             init_module_objects(cr, package.name, modules)
         cr.commit()
@@ -712,10 +799,6 @@
                     delattr(package, kind)
 
         statusi += 1
-        cr.execute('select model, name from ir_model_data where module=%s order by model, name', (package.name,))
-        for res in cr.fetchall():
-            xmlid_repr = ','.join(["\"" + string.replace(property, '\"', '\'') + "\"" for property in (res[0], res[1], package.name)])
-            logger.notifyChannel('OpenUpgrade_XMLID', netsvc.LOG_INFO, xmlid_repr)
 
     cr.execute('select model from ir_model where state=%s', ('manual',))
     for model in cr.dictfetchall():

=== modified file 'bin/addons/base/ir/ir_model.py'
--- bin/addons/base/ir/ir_model.py	2010-05-18 09:30:44 +0000
+++ bin/addons/base/ir/ir_model.py	2012-05-09 12:36:18 +0000
@@ -31,6 +31,8 @@
 from tools.translate import _
 import pooler
 
+from openupgrade import openupgrade_log
+
 def _get_fields_type(self, cr, uid, context=None):
     cr.execute('select distinct ttype,ttype from ir_model_fields')
     return cr.fetchall()
@@ -459,6 +461,10 @@
         return id
 
     def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None):
+        #OpenUpgrade: log entry (used in csv import)
+        if xml_id:
+            openupgrade_log.log_xml_id(cr, module, xml_id)
+
         warning = True
         model_obj = self.pool.get(model)
         if not context:

=== added directory 'bin/addons/openupgrade_records'
=== added file 'bin/addons/openupgrade_records/__init__.py'
--- bin/addons/openupgrade_records/__init__.py	1970-01-01 00:00:00 +0000
+++ bin/addons/openupgrade_records/__init__.py	2012-05-09 12:36:18 +0000
@@ -0,0 +1,2 @@
+import model
+import lib

=== added file 'bin/addons/openupgrade_records/__openerp__.py'
--- bin/addons/openupgrade_records/__openerp__.py	1970-01-01 00:00:00 +0000
+++ bin/addons/openupgrade_records/__openerp__.py	2012-05-09 12:36:18 +0000
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module Copyright (C) 2012 OpenUpgrade community
+#    https://launchpad.net/~openupgrade-committers
+#
+#    Contributors:
+#    Therp BV <http://therp.nl>
+#
+#    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/>.
+#
+##############################################################################
+
+
+{
+    'name': 'OpenUpgrade Records',
+    'version': '0.2',
+    'category': 'Normal',
+    'description': """Allow OpenUpgrade records to be 
+stored in the database and compare with other servers.
+
+This module depends on OpenERP client lib:
+
+    easy_install openerp-client-lib
+
+""",
+    'author': 'OpenUpgrade Community',
+    'maintainer': 'OpenUpgrade Community',
+    'contributors': ['Therp BV'],
+    'website': 'https://launchpad.net/~openupgrade-committers',
+    'depends': [],
+    'init_xml': [],
+    'update_xml': [
+        'view/openupgrade_record.xml',
+        'view/comparison_config.xml',
+        'view/analysis_wizard.xml',
+        'view/generate_records_wizard.xml',
+        'view/install_all_wizard.xml',
+        'security/ir.model.access.csv',
+        ],
+    'demo_xml': [
+    ],
+    'test': [
+    ],
+    'installable': True,
+    'auto_install': False,
+    'external_dependencies': {
+        'python' : ['openerplib'],
+        },
+}
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

=== added file 'bin/addons/openupgrade_records/__terp__.py'
--- bin/addons/openupgrade_records/__terp__.py	1970-01-01 00:00:00 +0000
+++ bin/addons/openupgrade_records/__terp__.py	2012-05-09 12:36:18 +0000
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module Copyright (C) 2012 OpenUpgrade community
+#    https://launchpad.net/~openupgrade-committers
+#
+#    Contributors:
+#    Therp BV <http://therp.nl>
+#
+#    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/>.
+#
+##############################################################################
+
+
+{
+    'name': 'OpenUpgrade Records',
+    'version': '0.2',
+    'category': 'Normal',
+    'description': """Allow OpenUpgrade records to be 
+stored in the database and compare with other servers.
+
+This module depends on OpenERP client lib:
+
+    easy_install openerp-client-lib
+
+""",
+    'author': 'OpenUpgrade Community',
+    'maintainer': 'OpenUpgrade Community',
+    'contributors': ['Therp BV'],
+    'website': 'https://launchpad.net/~openupgrade-committers',
+    'depends': [],
+    'init_xml': [],
+    'update_xml': [
+        'view/openupgrade_record.xml',
+        'view/comparison_config.xml',
+        'view/analysis_wizard.xml',
+        'view/generate_records_wizard.xml',
+        'view/install_all_wizard.xml',
+        'security/ir.model.access.csv',
+        ],
+    'demo_xml': [
+    ],
+    'test': [
+    ],
+    'installable': True,
+    'auto_install': False,
+    'external_dependencies': {
+        'python' : ['openerplib'],
+        },
+}
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

=== added directory 'bin/addons/openupgrade_records/model'
=== added file 'bin/addons/openupgrade_records/model/__init__.py'
--- bin/addons/openupgrade_records/model/__init__.py	1970-01-01 00:00:00 +0000
+++ bin/addons/openupgrade_records/model/__init__.py	2012-05-09 12:36:18 +0000
@@ -0,0 +1,6 @@
+import openupgrade_record
+import comparison_config
+import analysis_wizard
+import generate_records_wizard
+import install_all_wizard
+

=== added file 'bin/addons/openupgrade_records/model/analysis_wizard.py'
--- bin/addons/openupgrade_records/model/analysis_wizard.py	1970-01-01 00:00:00 +0000
+++ bin/addons/openupgrade_records/model/analysis_wizard.py	2012-05-09 12:36:18 +0000
@@ -0,0 +1,178 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module Copyright (C) 2012 OpenUpgrade community
+#    https://launchpad.net/~openupgrade-committers
+#
+#    Contributors:
+#    Therp BV <http://therp.nl>
+#
+#    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/>.
+#
+##############################################################################
+
+import os
+from osv import osv, fields
+
+try:
+    from openerp.addons.openupgrade_records.lib import compare
+    from openerp.openupgrade_records.lib import apriori
+    from openerp.addons import get_module_path
+except ImportError:
+    from openupgrade_records.lib import compare
+    from openupgrade_records.lib import apriori
+    from addons import get_module_path
+
+class openupgrade_analysis_wizard(osv.osv_memory):
+    _name = 'openupgrade.analysis.wizard'
+    _description = 'OpenUpgrade Analysis Wizard'
+    _columns = {
+        'server_config': fields.many2one(
+            'openupgrade.comparison.config',
+            'Configuration', required=True),
+        'state': fields.selection(
+            [('init', 'Init'), ('ready', 'Ready')], 'State',
+            readonly=True),
+        'log': fields.text('Log'),
+        'write': fields.boolean(
+            'Write files',
+            help='Write analysis files to the module directories'
+            ),
+        }
+    _defaults = {
+        'state': lambda *a: 'init',
+        'write': lambda *a: True,
+        }
+
+    def get_communication(self, cr, uid, ids, context=None):
+        """ 
+        Retrieve both sets of database representations,
+        perform the comparison and register the resulting
+        change set
+        """
+        def write_file(
+            module, version, contents, filename='openupgrade_analysis.txt'):
+            module_path = get_module_path(module)
+            if not module_path:
+                return "ERROR: could not find module path:\n"
+            full_path = os.path.join(
+                module_path, 'migrations', version)
+            if not os.path.exists(full_path):
+                try:
+                    os.makedirs(full_path)
+                except os.error:
+                    return "ERROR: could not create migrations directory:\n"
+            logfile = os.path.join(full_path, filename)
+            try:
+                f = open(logfile, 'w')
+            except Exception:
+                return "ERROR: could not open file %s for writing:\n" % logfile
+            f.write(contents)
+            f.close()
+            return None
+
+        wizard = self.browse(cr, uid, ids[0], context=context)
+        # Retrieve connection and access methods
+        conf_obj = self.pool.get('openupgrade.comparison.config')
+        connection = conf_obj.get_connection(
+            cr, uid, [wizard.server_config.id], context=context)
+        remote_record_obj = connection.get_model('openupgrade.record')
+        local_record_obj = self.pool.get('openupgrade.record')
+        
+        # Retrieve field representations and compare
+        remote_records = remote_record_obj.field_dump(context)
+        local_records = local_record_obj.field_dump(cr, uid, context)
+        res = compare.compare_sets(remote_records, local_records)
+
+        # Retrieve xml id representations and compare
+        fields = ['module', 'model', 'name']
+        local_xml_record_ids = local_record_obj.search(
+            cr, uid, [('type', '=', 'xmlid')])
+        remote_xml_record_ids = remote_record_obj.search(
+            [('type', '=', 'xmlid')])
+        local_xml_records = [
+            dict([(field, x[field]) for field in fields])
+            for x in local_record_obj.read(
+                cr, uid, local_xml_record_ids, fields)
+            ]
+        remote_xml_records = [
+            dict([(field, x[field]) for field in fields])
+            for x in remote_record_obj.read(
+                remote_xml_record_ids, fields)
+            ]
+        res_xml = compare.compare_xml_sets(
+            remote_xml_records, local_xml_records)
+
+        # reorder and output the result
+        keys = list(set(res.keys() + res_xml.keys()))
+        keys.remove('general')
+        keys = ['general'] + keys
+        module_obj = self.pool.get('ir.module.module')
+        module_ids = module_obj.search(
+            cr, uid, [('state', '=', 'installed')])
+        modules = dict([(x['name'], x) for x in module_obj.read(cr, uid, module_ids)])
+        general = ''
+        for key in keys:
+            contents = "---%s---\n" % key
+            if key in res:
+                contents += '\n'.join([unicode(line) for line in sorted(res[key])])
+                if res[key]:
+                    contents += '\n'
+            if key in res_xml:
+                contents += '\n'.join([unicode(line) for line in sorted(res_xml[key])])
+                if res_xml[key]:
+                    contents += '\n'
+            if key == 'general':
+                general += contents
+                continue
+            if key not in modules:
+                general += (
+                    "ERROR: module not in list of installed modules:\n"
+                    + contents)
+                continue
+            if wizard.write:
+                error = write_file(
+                    key, modules[key]['installed_version'], contents)
+                if error:
+                    general += error
+                    general += contents
+            else:
+                general += contents
+        
+        # Store the general log in as many places as possible ;-)
+        if wizard.write and 'base' in modules:
+            write_file(
+                'base', modules['base']['installed_version'], general,
+                'openupgrade_general_log.txt')
+        self.pool.get('openupgrade.comparison.config').write(
+            cr, uid, wizard.server_config.id,
+            {'last_log': general})
+        self.write(cr, uid, ids, {'state': 'ready', 'log': general})
+
+        result = {
+            'name': self._description,
+            'view_type': 'form',
+            'view_mode': 'form',
+            'res_model': 'openupgrade.analysis.wizard',
+            'domain': [],
+            'context': context,
+            'type': 'ir.actions.act_window',
+            #'target': 'new',
+            'res_id': ids[0],
+            }
+        return result
+
+openupgrade_analysis_wizard()
+

=== added file 'bin/addons/openupgrade_records/model/comparison_config.py'
--- bin/addons/openupgrade_records/model/comparison_config.py	1970-01-01 00:00:00 +0000
+++ bin/addons/openupgrade_records/model/comparison_config.py	2012-05-09 12:36:18 +0000
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module Copyright (C) 2012 OpenUpgrade community
+#    https://launchpad.net/~openupgrade-committers
+#
+#    Contributors:
+#    Therp BV <http://therp.nl>
+#
+#    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/>.
+#
+##############################################################################
+
+from osv import osv, fields
+import openerplib
+from tools.translate import _
+
+class openupgrade_comparison_config(osv.osv):
+    _name = 'openupgrade.comparison.config'
+    _columns = {
+        'name': fields.char('Name', size=64),
+        'server': fields.char('Server', size=64, required=True),
+        'port': fields.integer('Port', required=True),
+        'protocol': fields.selection(
+            [('http://', 'XML-RPC')],
+            # ('https://', 'XML-RPC Secure')], not supported by libopenerp
+            'Protocol', required=True),
+        'database': fields.char('Database', size=64, required=True),
+        'username': fields.char('Username', size=24, required=True),
+        'password': fields.char('Password', size=24, required=True, password=True),
+        'last_log': fields.text('Last log'),
+        }
+    _defaults = {
+        'port': lambda *a: 8069,
+        'protocol': lambda *a: 'http://',
+        }
+
+    def get_connection(self, cr, uid, ids, context=None):
+        if not ids:
+            raise osv.except_osv(
+                _("Cannot connect"), _("Invalid id passed."))
+        conf = self.read(cr, uid, ids[0], context=None)
+        return openerplib.get_connection(
+           hostname=conf['server'],
+           database=conf['database'],
+           login=conf['username'],
+           password=conf['password'],
+           port=conf['port'],
+           )
+
+    def test_connection(self, cr, uid, ids, context=None):
+        try:
+            connection = self.get_connection(cr, uid, [ids[0]], context)
+            user_model = connection.get_model("res.users")
+            ids = user_model.search([("login", "=", "admin")])
+            user_info = user_model.read(ids[0], ["name"])
+        except Exception, e:
+            raise osv.except_osv(
+                _("Connection failed."), unicode(e))
+        raise osv.except_osv(
+            _("Connection succesful."),
+            _("%s is connected.") % user_info["name"]
+            )
+    
+    def analyze(self, cr, uid, ids, context=None):
+        """
+        Run the analysis wizard
+        """
+        wizard_obj = self.pool.get('openupgrade.analysis.wizard')
+        wizard_id = wizard_obj.create(
+            cr, uid, {'server_config': ids[0]}, context)
+        result = {
+            'name': wizard_obj._description,
+            'view_type': 'form',
+            'view_mode': 'form',
+            'res_model': 'openupgrade.analysis.wizard',
+            'domain': [],
+            'context': context,
+            'type': 'ir.actions.act_window',
+            'target': 'new',
+            'res_id': wizard_id,
+            'nodestroy': True,
+            }
+        return result
+
+openupgrade_comparison_config()

=== added file 'bin/addons/openupgrade_records/model/generate_records_wizard.py'
--- bin/addons/openupgrade_records/model/generate_records_wizard.py	1970-01-01 00:00:00 +0000
+++ bin/addons/openupgrade_records/model/generate_records_wizard.py	2012-05-09 12:36:18 +0000
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module Copyright (C) 2012 OpenUpgrade community
+#    https://launchpad.net/~openupgrade-committers
+#
+#    Contributors:
+#    Therp BV <http://therp.nl>
+#
+#    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/>.
+#
+##############################################################################
+
+import os
+from osv import osv, fields
+import pooler
+try:
+    from openerp.openupgrade import openupgrade_tools
+except ImportError:
+    from openupgrade import openupgrade_tools
+
+class generate_records_wizard(osv.osv_memory):
+    _name = 'openupgrade.generate.records.wizard'
+    _description = 'OpenUpgrade Generate Records Wizard'
+    _columns = {
+        'state': fields.selection([('init', 'init'), ('ready', 'ready')], 'State'),
+        }
+    _defaults = {
+        'state': lambda *a: 'init',
+        }
+
+    def generate(self, cr, uid, ids, context=None):
+        """
+        Main wizard step. Make sure that all modules are up-to-date,
+        then reinitialize all installed modules.
+        Equivalent of running the server with '-d <database> --init all'
+
+        The goal of this is to fill the records table.
+
+        TODO: update module list and versions, then update all modules?
+        """
+        # Truncate the records table
+        if (openupgrade_tools.table_exists(cr, 'openupgrade_attribute') and
+            openupgrade_tools.table_exists(cr, 'openupgrade_record')):
+            cr.execute(
+                'TRUNCATE openupgrade_attribute, openupgrade_record;'
+                )
+
+        # Need to get all modules in state 'installed'
+        module_obj = self.pool.get('ir.module.module')
+        module_ids = module_obj.search(
+            cr, uid, [('state', 'in', ['to install', 'to upgrade'])])
+        if module_ids:
+            cr.commit()
+            _db, pool = pooler.restart_pool(cr.dbname, update_module=True)
+        # Did we succeed above?
+        module_ids = module_obj.search(
+            cr, uid, [('state', 'in', ['to install', 'to upgrade'])])
+        if module_ids:
+            modules = module_obj.read(
+                cr, uid, module_ids, ['name'], context=context)
+            raise except_osv(
+                "Cannot reliably generate records", 
+                ("Cannot seem to install or upgrade modules " +
+                 ', '.join([x['name'] for x in modules])))
+        # Now reinitialize all installed modules
+        module_ids = module_obj.search(
+            cr, uid, [('state', '=', 'installed')])
+        module_obj.write(
+            cr, uid, module_ids, {'state': 'to install'})
+        cr.commit()
+        _db, pool = pooler.restart_pool(cr.dbname, update_module=True)
+        self.write(cr, uid, ids, {'state': 'ready'})
+        # and we are done
+        return True
+
+generate_records_wizard()
+

=== added file 'bin/addons/openupgrade_records/model/install_all_wizard.py'
--- bin/addons/openupgrade_records/model/install_all_wizard.py	1970-01-01 00:00:00 +0000
+++ bin/addons/openupgrade_records/model/install_all_wizard.py	2012-05-09 12:36:18 +0000
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module Copyright (C) 2012 OpenUpgrade community
+#    https://launchpad.net/~openupgrade-committers
+#
+#    Contributors:
+#    Therp BV <http://therp.nl>
+#
+#    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/>.
+#
+##############################################################################
+
+import time
+import os
+from osv import osv, fields
+import pooler
+
+class install_all_wizard(osv.osv_memory):
+    _name = 'openupgrade.install.all.wizard'
+    _description = 'OpenUpgrade Install All Wizard'
+    _columns = {
+        'state': fields.selection([('init', 'init'), ('ready', 'ready')], 'State', readonly=True),
+        'to_install': fields.integer('Number of modules to install', readonly=True),
+        }
+    _defaults = {
+        'state': lambda *a: 'init',
+        }
+
+
+    def default_get(self, cr, uid, fields, context=None):
+        """
+        Update module list and retrieve the number
+        of installable modules
+        """
+        res = super(install_all_wizard, self).default_get(
+            cr, uid, fields, context=None)
+        module_obj = self.pool.get('ir.module.module')
+        update, add = module_obj.update_list(cr, uid,)
+        print "%s modules added" % add
+        module_ids = module_obj.search(
+            cr, uid, [('state', 'not in', ['installed', 'uninstallable', 'unknown'])])
+        res.update(
+            {'to_install': module_ids and len(module_ids) or False}
+            )
+        return res
+
+    def quirk_fiscalyear(self, cr, uid, ids, context=None):
+        """ 
+        Install account module first and create a fiscal year,
+        in order to prevent "No fiscal year defined" exception
+        during an upgrade or reinstallation of the account module.
+        
+        Refer to account_fiscalyear.find(), which is called as
+        a default function by the orm upon module upgrade.
+        """
+        module_obj = self.pool.get('ir.module.module')
+        pool = self.pool
+        # Retrieve status of the account module
+        account_module_id = module_obj.search(
+            cr, uid, [('name', '=', 'account')], context=context)[0]
+        state = module_obj.read(
+            cr, uid, account_module_id, ['state'], context=context)['state']
+        if state != 'installed':
+            # Cancel installation of other modules
+            module_ids = module_obj.search(
+                cr, uid, [('state', '=', 'to install')])
+            module_obj.write(cr, uid, module_ids, {'state': 'uninstalled'})
+            # Mark the module and its dependencies
+            module_obj.button_install(cr, uid, [account_module_id])
+            # Install account module
+            cr.commit()
+            _db, pool = pooler.restart_pool(cr.dbname, update_module=True)
+        # get or create today's fiscal year
+        fy_obj = pool.get('account.fiscalyear')
+        if not fy_obj.find(cr, uid, False, exception=False, context=context):
+            fy_obj.create(cr, uid, {
+                    'name': time.strftime('%Y'),
+                    'code': time.strftime('%Y'),
+                    'date_start': "%s-01-01" % time.strftime('%Y'),
+                    'date_stop': "%s-12-31" % time.strftime('%Y'),
+                    })
+        
+    def install_all(self, cr, uid, ids, context=None):
+        """
+        Main wizard step. Set all installable modules to install
+        and actually install them.
+        """
+        module_obj = self.pool.get('ir.module.module')
+        module_ids = module_obj.search(
+            cr, uid, [('state', 'not in', ['installed', 'uninstallable', 'unknown'])])
+        if module_ids:
+            module_obj.write(
+                cr, uid, module_ids, {'state': 'to install'})
+            cr.commit()
+            _db, pool = pooler.restart_pool(cr.dbname, update_module=True)
+            self.write(cr, uid, ids, {'state': 'ready'})
+        return True
+
+install_all_wizard()
+

=== added file 'bin/addons/openupgrade_records/model/openupgrade_record.py'
--- bin/addons/openupgrade_records/model/openupgrade_record.py	1970-01-01 00:00:00 +0000
+++ bin/addons/openupgrade_records/model/openupgrade_record.py	2012-05-09 12:36:18 +0000
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module Copyright (C) 2012 OpenUpgrade community
+#    https://launchpad.net/~openupgrade-committers
+#
+#    Contributors:
+#    Therp BV <http://therp.nl>
+#
+#    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/>.
+#
+##############################################################################
+
+from osv import osv, fields
+
+# Cannot use forward references in 6.0
+class openupgrade_record(osv.osv):
+    _name = 'openupgrade.record'
+openupgrade_record()
+
+class openupgrade_attribute(osv.osv):
+    _name = 'openupgrade.attribute'
+    _rec_name = 'attribute_id'
+    _columns = {
+        'name': fields.char(
+            'Name', size=24,
+            readonly=True,
+            ),
+        'value': fields.char(
+            'Value',
+            size=4096,
+            readonly=True,
+            ),
+        'record_id': fields.many2one(
+            'openupgrade.record', ondelete='CASCADE',
+            readonly=True,
+            ),
+        }
+openupgrade_attribute()
+
+class openupgrade_record(osv.osv):
+    _inherit = 'openupgrade.record'
+
+    _columns = {
+        'name': fields.char('Name', size=256, readonly=True),
+        'module': fields.char('Module', size=128, readonly=True),
+        'model': fields.char('Model', size=128, readonly=True),
+        'field': fields.char('Field', size=128, readonly=True),
+        'mode': fields.selection(
+            [('create', 'Create'), ('modify', 'Modify')],
+            'Mode',
+            help='Set to Create if a field is newly created '
+            'in this module. If this module modifies an attribute of an '
+            'exting field, set to Modify.',
+            readonly=True,
+             ),
+        'type': fields.selection(
+            [('field', 'Field'), ('xmlid', 'XML ID')],
+            'Type',
+            readonly=True,
+            ),
+        'attribute_ids': fields.one2many(
+            'openupgrade.attribute', 'record_id', 'Attributes',
+            readonly=True,
+            ),
+        }
+    def field_dump(self, cr, uid, context=None):
+        keys = [
+            'module',
+            'mode',
+            'model',
+            'field',
+            'type',
+            'isfunction',
+            'relation',
+            'required',
+            'selection_keys',
+            'req_default',
+            'inherits',
+            ]
+
+        template = dict([(x, False) for x in keys])
+        ids = self.search(cr, uid, [('type', '=', 'field')], context=context)
+        records = self.browse(cr, uid, ids, context=context)
+        data = []
+        for record in records:
+            repr = template.copy()
+            repr.update({
+                    'module': record.module,
+                    'model': record.model,
+                    'field': record.field,
+                    'mode': record.mode,
+                    })
+            repr.update(
+                dict([(x.name, x.value) for x in record.attribute_ids]))
+            data.append(repr)
+        return data
+
+openupgrade_record()

=== added directory 'bin/addons/openupgrade_records/security'
=== added file 'bin/addons/openupgrade_records/security/ir.model.access.csv'
--- bin/addons/openupgrade_records/security/ir.model.access.csv	1970-01-01 00:00:00 +0000
+++ bin/addons/openupgrade_records/security/ir.model.access.csv	2012-05-09 12:36:18 +0000
@@ -0,0 +1,3 @@
+"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
+"access_openupgrade_record","openupgrade.record all","model_openupgrade_record",,1,0,0,0
+"access_openupgrade_attribute","openupgrade.attribute all","model_openupgrade_attribute",,1,0,0,0

=== added directory 'bin/addons/openupgrade_records/view'
=== added file 'bin/addons/openupgrade_records/view/analysis_wizard.xml'
--- bin/addons/openupgrade_records/view/analysis_wizard.xml	1970-01-01 00:00:00 +0000
+++ bin/addons/openupgrade_records/view/analysis_wizard.xml	2012-05-09 12:36:18 +0000
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <record id="view_openupgrade_analysis_wizard_form" model="ir.ui.view">
+            <field name="name">view.openupgrade.analysis_wizard.form</field>
+            <field name="model">openupgrade.analysis.wizard</field>
+            <field name="type">form</field>
+            <field name="arch" type="xml">
+                <form string="OpenUpgrade Analysis Wizard">
+                    <field name="server_config" readonly="1"/>
+                    <field name="state"/>
+                    <field name="log" colspan="4"
+                           attrs="{'invisible': [('state', '!=', 'ready')]}"/>
+                    <field name="write"                           
+                           attrs="{'readonly': [('state', '!=', 'init')]}"/>
+                    <button icon="gtk-close"
+                            special="cancel"
+                            string="Close"
+                            />
+                    <button icon="gtk-ok"
+                            string="Create" 
+                            name="get_communication"
+                            type="object"
+                            states="init"
+                            />
+                </form>
+            </field>
+        </record>
+    </data>
+</openerp>

=== added file 'bin/addons/openupgrade_records/view/comparison_config.xml'
--- bin/addons/openupgrade_records/view/comparison_config.xml	1970-01-01 00:00:00 +0000
+++ bin/addons/openupgrade_records/view/comparison_config.xml	2012-05-09 12:36:18 +0000
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <record id="view_openupgrade_comparison_config_tree" model="ir.ui.view">
+            <field name="name">view.openupgrade.comparison_config.tree</field>
+            <field name="model">openupgrade.comparison.config</field>
+            <field name="type">tree</field>
+            <field name="arch" type="xml">
+                <tree string="OpenUpgrade Comparison Config">
+                    <field name="name" select="1"/>
+                    <field name="protocol"/>
+                    <field name="server" select="1"/>
+                    <field name="port" select="1"/>
+                    <field name="database" select="1"/>
+                </tree>
+            </field>
+        </record>
+        <record id="view_openupgrade_comparison_config_form" model="ir.ui.view">
+            <field name="name">view.openupgrade.comparison_config.form</field>
+            <field name="model">openupgrade.comparison.config</field>
+            <field name="type">form</field>
+            <field name="arch" type="xml">
+                <form string="OpenUpgrade Comparison Config">
+                    <field name="name"/>
+                    <field name="protocol"/>
+                    <field name="server"/>
+                    <field name="port"/>
+                    <field name="database"/>
+                    <field name="username"/>
+                    <field name="password" password="1"/>
+                    <button
+                        name="test_connection" 
+                        string="Test Connection"
+                        type="object" icon="gtk-network"
+                        colspan="2"
+                        />
+                    <newline/>
+                    <button 
+                        name="analyze"
+                        string="Perform Analysis"
+                        type="object" icon="gtk-execute"
+                        colspan="2"
+                        />
+                    <separator string="Last log" colspan="4"/>
+                    <field name="last_log" nolabel="1" colspan="4"/>
+                </form>
+            </field>
+        </record>
+        <record id="action_openupgrade_comparison_config_tree" model="ir.actions.act_window">
+            <field name="name">OpenUpgrade Comparison Configs</field>
+            <field name="type">ir.actions.act_window</field>
+            <field name="res_model">openupgrade.comparison.config</field>
+            <field name="view_type">form</field>
+        </record>
+        <menuitem
+            action="action_openupgrade_comparison_config_tree"
+            id="menu_openupgrade_comparison_config"
+            name="Comparison Configurations"
+            parent="menu_openupgrade"
+            />
+    </data>
+</openerp>

=== added file 'bin/addons/openupgrade_records/view/generate_records_wizard.xml'
--- bin/addons/openupgrade_records/view/generate_records_wizard.xml	1970-01-01 00:00:00 +0000
+++ bin/addons/openupgrade_records/view/generate_records_wizard.xml	2012-05-09 12:36:18 +0000
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <record id="view_openupgrade_generate_records_wizard_form" model="ir.ui.view">
+            <field name="name">view.openupgrade.generate_records_wizard.form</field>
+            <field name="model">openupgrade.generate.records.wizard</field>
+            <field name="type">form</field>
+            <field name="arch" type="xml">
+                <form string="OpenUpgrade Generate Records Wizard">
+                    <group states="init" colspan="4">
+                        <label string="This will reinitialize all the modules installed on this database. Do not continue if you use this database in production."
+                               />
+                        <button icon="gtk-close"
+                                special="cancel"
+                                string="Cancel"
+                                />
+                        <button icon="gtk-ok"
+                                string="Continue" 
+                                name="generate"
+                                type="object"
+                                />
+                    </group>
+                    <group states="ready" colspan="4">
+                        <label string="Modules initialized and records created"
+                               />
+                        <field name="state" invisible="1"/>
+                        <button icon="gtk-close"
+                                special="cancel"
+                                string="Close"
+                            />
+                    </group>
+                </form>
+            </field>
+        </record>
+
+        <record id="action_generate_records" model="ir.actions.act_window">
+            <field name="name">Generate Records</field>
+	    <field name="type">ir.actions.act_window</field>
+            <field name="res_model">openupgrade.generate.records.wizard</field>
+            <field name="view_type">form</field>
+            <field name="view_mode">form,tree</field>
+            <field name="target">new</field>
+        </record>
+
+        <menuitem name="Generate Records"
+            id="menu_openupgrade_generate_records"
+            parent="menu_openupgrade"
+            action="action_generate_records"
+            sequence="15"/>
+
+    </data>
+</openerp>

=== added file 'bin/addons/openupgrade_records/view/install_all_wizard.xml'
--- bin/addons/openupgrade_records/view/install_all_wizard.xml	1970-01-01 00:00:00 +0000
+++ bin/addons/openupgrade_records/view/install_all_wizard.xml	2012-05-09 12:36:18 +0000
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <record id="view_openupgrade_install_all_wizard_form" model="ir.ui.view">
+            <field name="name">view.openupgrade.install_all_wizard.form</field>
+            <field name="model">openupgrade.install.all.wizard</field>
+            <field name="type">form</field>
+            <field name="arch" type="xml">
+                <form string="OpenUpgrade Install All Modules Wizard">
+                    <group states="init" colspan="4">
+                        <label string="This will install all modules on the database. Do not continue if you use this database in production." colspan="4"
+                               />
+                        <field name="to_install"/>
+                        <newline/>
+                        <button icon="gtk-close"
+                                special="cancel"
+                                string="Cancel"
+                                />
+                        <button icon="gtk-ok"
+                                string="Continue" 
+                                name="install_all"
+                                type="object"
+                                />
+                    </group>
+                    <group states="ready" colspan="4">
+                        <label string="Modules installed"
+                               />
+                        <field name="state" invisible="1"/>
+                        <button icon="gtk-close"
+                                special="cancel"
+                                string="Close"
+                            />
+                    </group>
+                </form>
+            </field>
+        </record>
+
+        <record id="action_install_all" model="ir.actions.act_window">
+            <field name="name">Install All Modules</field>
+	    <field name="type">ir.actions.act_window</field>
+            <field name="res_model">openupgrade.install.all.wizard</field>
+            <field name="view_type">form</field>
+            <field name="view_mode">form,tree</field>
+            <field name="target">new</field>
+        </record>
+
+        <menuitem name="Install All Modules"
+            id="menu_openupgrade_install_all"
+            parent="menu_openupgrade"
+            action="action_install_all"
+            sequence="14"/>
+
+    </data>
+</openerp>

=== added file 'bin/addons/openupgrade_records/view/openupgrade_record.xml'
--- bin/addons/openupgrade_records/view/openupgrade_record.xml	1970-01-01 00:00:00 +0000
+++ bin/addons/openupgrade_records/view/openupgrade_record.xml	2012-05-09 12:36:18 +0000
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <!-- Top level menu under 'Database structure' -->
+        <menuitem
+            id="menu_openupgrade"
+            name="OpenUpgrade Development"
+            parent="base.menu_administration"
+            sequence="99"
+            />
+        <record id="view_openupgrade_record_tree" model="ir.ui.view">
+            <field name="name">view.openupgrade.record.tree</field>
+            <field name="model">openupgrade.record</field>
+            <field name="type">tree</field>
+            <field name="arch" type="xml">
+                <tree string="OpenUpgrade Records">
+                    <field name="module" select="1"/>
+                    <field name="model" select="1"/>
+                    <field name="field" select="1"/>
+                    <field name="name" select="1"/>
+                    <field name="type" select="1"/>
+                    <field name="mode" select="1"/>
+                </tree>
+            </field>
+        </record>
+        <record id="view_openupgrade_record_form" model="ir.ui.view">
+            <field name="name">view.openupgrade.record.form</field>
+            <field name="model">openupgrade.record</field>
+            <field name="type">form</field>
+            <field name="arch" type="xml">
+                <form string="OpenUpgrade Record">
+                    <field name="module" select="1"/>
+                    <field name="model" select="1"/>
+                    <field name="field" select="1"/>
+                    <field name="name" select="1"/>
+                    <field name="type" select="1"/>
+                    <field name="mode" select="1"/>
+                    <separator string="Attributes" colspan="4"/>
+                    <field name="attribute_ids" mode="tree,form" nolabel="1" colspan="4">
+                        <tree string="Attributes">
+                            <field name="name"/>
+                            <field name="value"/>
+                        </tree>
+                        <form string="Attribute">
+                            <field name="name"/>
+                            <field name="value"/>
+                        </form>
+                    </field>
+                </form>
+            </field>
+        </record>
+        <record id="action_openupgrade_record_tree" model="ir.actions.act_window">
+            <field name="name">OpenUpgrade Records</field>
+            <field name="type">ir.actions.act_window</field>
+            <field name="res_model">openupgrade.record</field>
+            <field name="view_type">form</field>
+        </record>
+        <menuitem
+            action="action_openupgrade_record_tree"
+            id="menu_openupgrade_records"
+            name="Records"
+            parent="menu_openupgrade"
+            />
+    </data>
+</openerp>

=== added directory 'bin/openupgrade'
=== added file 'bin/openupgrade/__init__.py'
=== added directory 'bin/openupgrade/doc'
=== added file 'bin/openupgrade/doc/readme.txt'
--- bin/openupgrade/doc/readme.txt	1970-01-01 00:00:00 +0000
+++ bin/openupgrade/doc/readme.txt	2012-05-09 12:36:18 +0000
@@ -0,0 +1,2 @@
+The documentation of this project is currently maintained in the
+6.1 branch. You can also consult it at http://readthedocs.org/docs/openupgrade-server

=== added file 'bin/openupgrade/openupgrade.py'
--- bin/openupgrade/openupgrade.py	1970-01-01 00:00:00 +0000
+++ bin/openupgrade/openupgrade.py	2012-05-09 12:36:18 +0000
@@ -0,0 +1,259 @@
+# -*- coding: utf-8 -*-
+import os
+from osv import osv
+import pooler
+import logging
+import tools
+import openupgrade_tools
+
+logger = logging.getLogger('OpenUpgrade')
+
+__all__ = [
+    'load_data',
+    'rename_columns',
+    'rename_tables',
+    'drop_columns',
+    'table_exists',
+    'column_exists',
+    'delete_model_workflow',
+    'set_defaults',
+    'update_module_names',
+    'add_ir_model_fields',
+]    
+
+def load_data(cr, module_name, filename, idref=None, mode='init'):
+    """
+    Load an xml or csv data file from your post script. The usual case for this is the
+    occurrence of newly added essential or useful data in the module that is
+    marked with "noupdate='1'" and without "forcecreate='1'" so that it will
+    not be loaded by the usual upgrade mechanism. Leaving the 'mode' argument to
+    its default 'init' will load the data from your migration script.
+    
+    Theoretically, you could simply load a stock file from the module, but be 
+    careful not to reinitialize any data that could have been customized.
+    Preferably, select only the newly added items. Copy these to a file
+    in your migrations directory and load that file.
+    Leave it to the user to actually delete existing resources that are
+    marked with 'noupdate' (other named items will be deleted
+    automatically).
+
+
+    :param module_name: the name of the module
+    :param filename: the path to the filename, relative to the module \
+    directory.
+    :param idref: optional hash with ?id mapping cache?
+    :param mode: one of 'init', 'update', 'demo'. Always use 'init' for adding new items \
+    from files that are marked with 'noupdate'. Defaults to 'init'.
+
+    """
+
+    if idref is None:
+        idref = {}
+    logger.info('%s: loading %s' % (module_name, filename))
+    _, ext = os.path.splitext(filename)
+    pathname = os.path.join(module_name, filename)
+    fp = tools.file_open(pathname)
+    try:
+        if ext == '.csv':
+            noupdate = True
+            tools.convert_csv_import(cr, module_name, pathname, fp.read(), idref, mode, noupdate)
+        else:
+            tools.convert_xml_import(cr, module_name, fp, idref, mode=mode)
+    finally:
+        fp.close()
+
+# for backwards compatibility
+load_xml = load_data
+table_exists = openupgrade_tools.table_exists
+
+def rename_columns(cr, column_spec):
+    """
+    Rename table columns. Typically called in the pre script.
+
+    :param column_spec: a hash with table keys, with lists of tuples as values. \
+    Tuples consist of (old_name, new_name).
+
+    """
+    for table in column_spec.keys():
+        for (old, new) in column_spec[table]:
+            logger.info("table %s, column %s: renaming to %s",
+                     table, old, new)
+            cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % (table, old, new,))
+
+def rename_tables(cr, table_spec):
+    """
+    Rename tables. Typically called in the pre script.
+    :param column_spec: a list of tuples (old table name, new table name).
+
+    """
+    for (old, new) in table_spec:
+        logger.info("table %s: renaming to %s",
+                    old, new)
+        cr.execute('ALTER TABLE "%s" RENAME TO "%s"' % (old, new,))
+
+def rename_models(cr, model_spec):
+    """
+    Rename models. Typically called in the pre script.
+    :param column_spec: a list of tuples (old table name, new table name).
+    
+    Use case: if a model changes name, but still implements equivalent
+    functionality you will want to update references in for instance
+    relation fields.
+
+    """
+    for (old, new) in model_spec:
+        logger.info("model %s: renaming to %s",
+                    old, new)
+        cr.execute('UPDATE ir_model_fields SET relation = %s '
+                   'WHERE relation = %s', (new, old,))
+
+def drop_columns(cr, column_spec):
+    """
+    Drop columns but perform an additional check if a column exists.
+    This covers the case of function fields that may or may not be stored.
+    Consider that this may not be obvious: an additional module can govern
+    a function fields' store properties.
+
+    :param column_spec: a list of (table, column) tuples
+    """
+    for (table, column) in column_spec:
+        logger.info("table %s: drop column %s",
+                    table, column)
+        if column_exists(cr, table, column):
+            cr.execute('ALTER TABLE "%s" DROP COLUMN "%s"' % 
+                       (table, column))
+        else:
+            logger.warn("table %s: column %s did not exist",
+                    table, column)
+
+def delete_model_workflow(cr, model):
+    """ 
+    Forcefully remove active workflows for obsolete models,
+    to prevent foreign key issues when the orm deletes the model.
+    """
+    logged_query(
+        cr,
+        "DELETE FROM wkf_workitem WHERE act_id in "
+        "( SELECT wkf_activity.id "
+        "  FROM wkf_activity, wkf "
+        "  WHERE wkf_id = wkf.id AND "
+        "  wkf.osv = %s"
+        ")", (model,))
+    logged_query(
+        cr,
+        "DELETE FROM wkf WHERE osv = %s", (model,))
+
+def set_defaults(cr, pool, default_spec, force=False):
+    """
+    Set default value. Useful for fields that are newly required. Uses orm, so
+    call from the post script.
+    
+    :param default_spec: a hash with model names as keys. Values are lists of \
+    tuples (field, value). None as a value has a special meaning: it assigns \
+    the default value. If this value is provided by a function, the function is \
+    called as the user that created the resource.
+    :param force: overwrite existing values. To be used for assigning a non- \
+    default value (presumably in the case of a new column). The ORM assigns \
+    the default value as declared in the model in an earlier stage of the \
+    process. Beware of issues with resources loaded from new data that \
+    actually do require the model's default, in combination with the post \
+    script possible being run multiple times.
+    """
+
+    def write_value(ids, field, value):
+        logger.info("model %s, field %s: setting default value of %d resources to %s",
+                 model, field, len(ids), unicode(value))
+        obj.write(cr, 1, ids, {field: value})
+
+    for model in default_spec.keys():
+        obj = pool.get(model)
+        if not obj:
+            raise osv.except_osv("Migration: error setting default, no such model: %s" % model, "")
+
+    for field, value in default_spec[model]:
+        domain = not force and [(field, '=', False)] or []
+        ids = obj.search(cr, 1, domain)
+        if not ids:
+            continue
+        if value is None:
+            # Set the value by calling the _defaults of the object.
+            # Typically used for company_id on various models, and in that
+            # case the result depends on the user associated with the object.
+            # We retrieve create_uid for this purpose and need to call the _defaults
+            # function per resource. Otherwise, write all resources at once.
+            if field in obj._defaults:
+                if not callable(obj._defaults[field]):
+                    write_value(ids, field, obj._defaults[field])
+                else:
+                    # existence users is covered by foreign keys, so this is not needed
+                    # cr.execute("SELECT %s.id, res_users.id FROM %s LEFT OUTER JOIN res_users ON (%s.create_uid = res_users.id) WHERE %s.id IN %s" %
+                    #                     (obj._table, obj._table, obj._table, obj._table, tuple(ids),))
+                    cr.execute("SELECT id, COALESCE(create_uid, 1) FROM %s " % obj._table + "WHERE id in %s", (tuple(ids),))
+                    fetchdict = dict(cr.fetchall())
+                    for id in ids:
+                        write_value([id], field, obj._defaults[field](obj, cr, fetchdict.get(id, 1), None))
+                        if id not in fetchdict:
+                            logger.info("model %s, field %s, id %d: no create_uid defined or user does not exist anymore",
+                                     model, field, id)
+            else:
+                error = ("OpenUpgrade: error setting default, field %s with "
+                         "None default value not in %s' _defaults" % (
+                        field, model))
+                logger.error(error)
+                # this exeption seems to get lost in a higher up try block
+                osv.except_osv("OpenUpgrade", error)
+        else:
+            write_value(ids, field, value)
+    
+def logged_query(cr, query, args=None):
+    if args is None:
+        args = []
+    res = cr.execute(query, args)
+    logger.debug('Running %s', query)
+    if not res:
+        query = query % args
+        logger.warn('No rows affected for query "%s"', query)
+    return res
+
+def column_exists(cr, table, column):
+    """ Check whether a certain column exists """
+    cr.execute(
+        'SELECT count(attname) FROM pg_attribute '
+        'WHERE attrelid = '
+        '( SELECT oid FROM pg_class WHERE relname = %s ) '
+        'AND attname = %s',
+        (table, column));
+    return cr.fetchone()[0] == 1
+
+def update_module_names(cr, namespec):
+    """
+    Deal with changed module names of certified modules
+    in order to prevent  'certificate not unique' error,
+    as well as updating the module reference in the
+    XML id.
+    
+    :param namespec: tuple of (old name, new name)
+    """
+    for (old_name, new_name) in namespec:
+        query = ("UPDATE ir_module_module SET name = %s "
+                 "WHERE name = %s")
+        logged_query(cr, query, (new_name, old_name))
+        query = ("UPDATE ir_model_data SET module = %s "
+                 "WHERE module = %s ")
+        logged_query(cr, query, (new_name, old_name))
+
+def add_ir_model_fields(cr, columnspec):
+    """
+    Typically, new columns on ir_model_fields need to be added in a very
+    early stage in the upgrade process of the base module, in raw sql
+    as they need to be in place before any model gets initialized.
+    Do not use for fields with additional SQL constraints, such as a
+    reference to another table or the cascade constraint, but craft your
+    own statement taking them into account.
+    
+    :param columnspec: tuple of (column name, column type)
+    """
+    for column in columnspec:
+        query = 'ALTER TABLE ir_model_fields ADD COLUMN %s %s' % (
+            column)
+        logged_query(cr, query, [])

=== added file 'bin/openupgrade/openupgrade_log.py'
--- bin/openupgrade/openupgrade_log.py	1970-01-01 00:00:00 +0000
+++ bin/openupgrade/openupgrade_log.py	2012-05-09 12:36:18 +0000
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+from openupgrade_tools import table_exists
+
+def log_xml_id(cr, module, xml_id):
+    """
+    Log xml_ids at load time in the records table.
+    Called from openerp/tools/convert.py:xml_import._test_xml_id()
+
+    # Catcha's
+    - The module needs to be loaded with 'init', or the calling method
+    won't be called. This can be brought about by installing the
+    module or updating the 'state' field of the module to 'to install'
+    or call the server with '--init <module>' and the database argument.
+    
+    - Do you get the right results immediately when installing the module?
+    No, sorry. This method retrieves the model from the ir_model_table, but when
+    the xml id is encountered for the first time, this method is called
+    before the item is present in this table. Therefore, you will not
+    get any meaningful results until the *second* time that you 'init'
+    the module.
+
+    - The good news is that the openupgrade_records module that comes
+    with this distribution allows you to deal with all of this with
+    one click on the menu item Settings -> Customizations ->
+    Database Structure -> OpenUpgrade -> Generate Records
+
+    - You cannot reinitialize the modules in your production database
+    and expect to keep working on it happily ever after. Do not perform
+    this routine on your production database.
+
+    :param module: The module that contains the xml_id
+    :param xml_id: the xml_id, with or without 'module.' prefix
+    """
+    if not table_exists(cr, 'openupgrade_record'):
+        return
+    if not '.' in xml_id:
+        xml_id = '%s.%s' % (module, xml_id)
+    cr.execute(
+        "SELECT model FROM ir_model_data "
+        "WHERE module = %s AND name = %s",
+        xml_id.split('.'))
+    record = cr.fetchone()
+    if not record:
+        #print "Cannot find xml_id %s" % xml_id
+        return
+    else:
+        cr.execute(
+            "SELECT id FROM openupgrade_record "
+            "WHERE module=%s AND model=%s AND name=%s AND type=%s",
+            (module, record[0], xml_id, 'xmlid'))
+        if not cr.fetchone():
+            cr.execute(
+                "INSERT INTO openupgrade_record "
+                "(module, model, name, type) values(%s, %s, %s, %s)",
+                (module, record[0], xml_id, 'xmlid'))
+

=== added file 'bin/openupgrade/openupgrade_tools.py'
--- bin/openupgrade/openupgrade_tools.py	1970-01-01 00:00:00 +0000
+++ bin/openupgrade/openupgrade_tools.py	2012-05-09 12:36:18 +0000
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+def table_exists(cr, table):
+    """ Check whether a certain table or view exists """
+    cr.execute(
+        'SELECT count(relname) FROM pg_class WHERE relname = %s',
+        (table,))
+    return cr.fetchone()[0] == 1
+

=== modified file 'bin/tools/convert.py'
--- bin/tools/convert.py	2010-12-17 12:06:16 +0000
+++ bin/tools/convert.py	2012-05-09 12:36:18 +0000
@@ -42,6 +42,8 @@
 
 from tools.safe_eval import safe_eval as eval 
 
+from openupgrade import openupgrade_log
+
 class ConvertError(Exception):
     def __init__(self, doc, orig_excpt):
         self.d = doc
@@ -244,6 +246,7 @@
 
         if len(id) > 64:
             self.logger.notifyChannel('init', netsvc.LOG_ERROR, 'id: %s is to long (max: 64)'% (id,))
+        openupgrade_log.log_xml_id(self.cr, self.module, xml_id)
 
     def _tag_delete(self, cr, rec, data_node=None):
         d_model = rec.get("model",'')

=== modified file 'bin/tools/sql.py'
--- bin/tools/sql.py	2009-01-04 22:13:29 +0000
+++ bin/tools/sql.py	2012-05-09 12:36:18 +0000
@@ -23,7 +23,8 @@
 def drop_view_if_exists(cr, viewname):
     cr.execute("select count(1) from pg_class where relkind=%s and relname=%s", ('v', viewname,))
     if cr.fetchone()[0]:
-        cr.execute("DROP view %s" % (viewname,))
+        # OpenUpgrade: add CASCADE
+        cr.execute("DROP view %s CASCADE" % (viewname,))
         cr.commit()
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:


Follow ups