← Back to team overview

credativ team mailing list archive

[Merge] lp:~therp-nl/openupgrade-server/6.1-api_ports into lp:openupgrade-server

 

Holger Brunn (Therp) has proposed merging lp:~therp-nl/openupgrade-server/6.1-api_ports into lp:openupgrade-server.

Requested reviews:
  Stefan Rijnhart (Therp) (stefan-therp)

For more details, see:
https://code.launchpad.net/~therp-nl/openupgrade-server/6.1-api_ports/+merge/109645

1&3: ACK

2: Non-namepsaced access to openerp modules is a deprecated feature that is enabled in loading.py:433. As we want to access the openupgrade module in there we have to import it after that if it makes use of the deprecated API.

lp:~therp-nl/openupgrade-server/6.1-api_ports/revision/3984 contains the necessary changes for that, but I don't particularly like it because you have to pay attention to when you import the openupgrade module. And chances are there will be a situation where you want to use it before the openerp namespace is expanded. Further https://bugs.launchpad.net/openobject-addons/+bug/1004539 suggests that it won't take too much time until this expansion is dropped altogether.
-- 
https://code.launchpad.net/~therp-nl/openupgrade-server/6.1-api_ports/+merge/109645
Your team OpenUpgrade Committers is subscribed to branch lp:openupgrade-server.
=== modified file 'openerp/addons/base/migrations/6.1.1.3/post-migration.py'
--- openerp/addons/base/migrations/6.1.1.3/post-migration.py	2012-03-06 22:36:49 +0000
+++ openerp/addons/base/migrations/6.1.1.3/post-migration.py	2012-06-11 14:20:26 +0000
@@ -24,5 +24,12 @@
         openupgrade.load_data(cr, 'base', 'migrations/6.1.1.3/data/base_data.xml')
         openupgrade.load_data(cr, 'base', 'migrations/6.1.1.3/data/base_security.xml')
         openupgrade.load_data(cr, 'base', 'migrations/6.1.1.3/data/ir.model.access.csv')
+        #force recreating module categories for all categories without xmlid
+        #this fixes addons getting wrong category_ids assigned in case of
+        #multiple categories with the same name
+        cr.execute("""
+            delete from ir_module_category where id not in 
+            (select res_id from ir_model_data where model='ir.module.category')
+        """)
     except Exception, e:
         raise osv.except_osv("OpenUpgrade", '%s: %s' % (me, e))

=== modified file 'openerp/modules/loading.py'
--- openerp/modules/loading.py	2012-05-09 10:48:46 +0000
+++ openerp/modules/loading.py	2012-06-11 14:20:26 +0000
@@ -66,15 +66,6 @@
 
 _logger = logging.getLogger(__name__)
 
-### 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
-
 def open_openerp_namespace():
     # See comment for open_openerp_namespace.
     if openerp.conf.deprecation.open_openerp_namespace:
@@ -252,12 +243,13 @@
         return cr.fetchone()[0]
         
     def compare_registries(cr, module):
+        from openerp.openupgrade import openupgrade
         """
         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'):
+        if not openupgrade.table_exists(cr, 'openupgrade_record'):
             return
         for model, fields in local_registry.items():
             registry.setdefault(model, {})
@@ -287,6 +279,9 @@
     if status is None:
         status = {}
 
+    if skip_modules is None:
+        skip_modules={'pre': [], 'post': []}
+
     processed_modules = []
     loaded_modules = []
     pool = pooler.get_pool(cr.dbname)
@@ -299,14 +294,16 @@
 
     # register, instantiate and initialize models for each modules
     for index, package in enumerate(graph):
+
+        if package.name in skip_modules['pre'] and package.name in skip_modules['post']:
+            continue
+
         module_name = package.name
         module_id = package.id
 
-        if skip_modules and module_name in skip_modules:
-            continue
-
         _logger.info('module %s: loading objects', package.name)
-        migrations.migrate_module(package, 'pre')
+        if package.name not in skip_modules['pre']:
+            migrations.migrate_module(package, 'pre')
         load_openerp_module(package.name)
 
         models = pool.load(cr, package)
@@ -366,11 +363,12 @@
 
             # OpenUpgrade: add 'try' block for logging exceptions
             # as errors in post scripts seem to be dropped
-            try:
-                migrations.migrate_module(package, 'post')
-            except Exception, e:
-                _logger.error('Error executing post migration script for module %s: %s', package, e)
-                raise
+            if package.name not in skip_modules['post']:
+                try:
+                    migrations.migrate_module(package, 'post')
+                except Exception, e:
+                    _logger.error('Error executing post migration script for module %s: %s', package, e)
+                    raise
 
             ver = release.major_version + '.' + package.data['version']
             # Set new modules and dependencies
@@ -384,6 +382,8 @@
                     delattr(package, kind)
 
         cr.commit()
+        skip_modules['pre'].append(package.name)
+        skip_modules['post'].append(package.name)
 
     # mark new res_log records as read
     cr.execute("update res_log set read=True where create_date >= %s", (dt_before_load,))
@@ -409,13 +409,15 @@
 def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_modules, registry):
     """Loads modules marked with ``states``, adding them to ``graph`` and
        ``loaded_modules`` and returns a list of installed/upgraded modules."""
+    from openerp.openupgrade import openupgrade
     processed_modules = []
     while True:
         cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),))
         module_list = [name for (name,) in cr.fetchall() if name not in graph]
-        new_modules_in_graph = graph.add_modules(cr, module_list, force)
+        module_list = openupgrade.add_module_dependencies(cr, module_list)
+        graph.add_modules(cr, module_list, force)
         _logger.debug('Updating graph with %d more modules', len(module_list))
-        loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules, registry=registry)
+        loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules={'pre': loaded_modules, 'post': loaded_modules}, registry=registry)
         processed_modules.extend(processed)
         loaded_modules.extend(loaded)
         if not processed: break
@@ -511,6 +513,7 @@
                 states_to_load = ['to install']
                 processed_install = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules, registry)
                 processed_modules.extend(processed_install)
+                loaded_modules.extend(processed_install)
             else:
                 processed_install = False
 

=== modified file 'openerp/openupgrade/openupgrade.py'
--- openerp/openupgrade/openupgrade.py	2012-05-06 18:58:42 +0000
+++ openerp/openupgrade/openupgrade.py	2012-06-11 14:20:26 +0000
@@ -1,24 +1,51 @@
 # -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module copyright (C) 2011-2012 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
+import inspect
+import logging
+import release
+import osv
 import pooler
-import logging
 import tools
 import openupgrade_tools
 
 logger = logging.getLogger('OpenUpgrade')
 
 __all__ = [
+    'migrate',
     'load_data',
     'rename_columns',
     'rename_tables',
     'drop_columns',
     'table_exists',
     'column_exists',
+    'logged_query',
     'delete_model_workflow',
     'set_defaults',
     'update_module_names',
     'add_ir_model_fields',
+    'rename_models',
+    'rename_xmlids',
+    'get_legacy_name',
 ]    
 
 def load_data(cr, module_name, filename, idref=None, mode='init'):
@@ -83,9 +110,18 @@
 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).
+    This function also renames the id sequence if it exists and if it is
+    not modified in the same run.
+
+    :param table_spec: a list of tuples (old table name, new table name).
 
     """
+    # Append id sequences
+    to_rename = [x[0] for x in table_spec]
+    for old, new in list(table_spec):
+        if (table_exists(cr, old + '_id_seq') and
+            old + '_id_seq' not in to_rename): 
+            table_spec.append((old + '_id_seq', new + '_id_seq'))
     for (old, new) in table_spec:
         logger.info("table %s: renaming to %s",
                     old, new)
@@ -94,7 +130,7 @@
 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).
+    :param model_spec: a list of tuples (old model name, new model name).
     
     Use case: if a model changes name, but still implements equivalent
     functionality you will want to update references in for instance
@@ -106,6 +142,24 @@
                     old, new)
         cr.execute('UPDATE ir_model_fields SET relation = %s '
                    'WHERE relation = %s', (new, old,))
+    # TODO: signal where the model occurs in references to ir_model
+
+def rename_xmlids(cr, xmlids_spec):
+    """
+    Rename XML IDs. Typically called in the pre script.
+    One usage example is when an ID changes module. In OpenERP 6 for example,
+    a number of res_groups IDs moved to module base from other modules (
+    although they were still being defined in their respective module).
+    """
+    for (old, new) in xmlids_spec:
+        if not old.split('.') or not new.split('.'):
+            logger.error(
+            'Cannot rename XMLID %s to %s: need the module '
+            'reference to be specified in the IDs' % (old, new))
+        else:
+            query = ("UPDATE ir_model_data SET module = %s, name = %s "
+                     "WHERE module = %s and name = %s")
+            logged_query(cr, query, tuple(new.split('.') + old.split('.')))
 
 def drop_columns(cr, column_spec):
     """
@@ -161,8 +215,8 @@
     """
 
     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))
+        logger.debug("model %s, field %s: setting default value of resources %s to %s",
+                 model, field, ids, unicode(value))
         obj.write(cr, 1, ids, {field: value})
 
     for model in default_spec.keys():
@@ -170,40 +224,42 @@
         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])
+        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),))
+                        # Execute the function once per user_id
+                        user_id_map = {}
+                        for row in cr.fetchall():
+                            user_id_map.setdefault(row[1], []).append(row[0])
+                        for user_id in user_id_map:
+                            write_value(
+                                user_id_map[user_id], field,
+                                obj._defaults[field](obj, cr, user_id, None))
                 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)
+                    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:
-                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)
+                write_value(ids, field, value)
     
 def logged_query(cr, query, args=None):
     if args is None:
@@ -257,3 +313,74 @@
         query = 'ALTER TABLE ir_model_fields ADD COLUMN %s %s' % (
             column)
         logged_query(cr, query, [])
+
+def get_legacy_name(original_name):
+    """
+    Returns a versioned name for legacy tables/columns/etc
+    Use this function instead of some custom name to avoid
+    collisions with future or past legacy tables/columns/etc
+
+    :param original_name: the original name of the column
+    :param version: current version as passed to migrate()
+    """
+    return 'openupgrade_legacy_'+('_').join(map(str, release.version_info))+'_'+original_name
+        
+def add_module_dependencies(cr, module_list):
+    """
+    Select (new) dependencies from the modules in the list
+    so that we can inject them into the graph at upgrade
+    time. Used in the modified OpenUpgrade Server,
+    not to be used in migration scripts
+    """
+    if not module_list:
+        return module_list
+    cr.execute("""
+        SELECT ir_module_module_dependency.name
+        FROM
+            ir_module_module,
+            ir_module_module_dependency
+        WHERE
+            module_id = ir_module_module.id
+            AND ir_module_module.name in %s
+        """, (tuple(module_list),))
+    dependencies = [x[0] for x in cr.fetchall()]
+    return list(set(module_list + dependencies))
+
+def migrate():
+    """
+    This is the decorator for the migrate() function
+    in migration scripts.
+    Return when the 'version' argument is not defined,
+    and log execeptions.
+    Retrieve debug context data from the frame above for
+    logging purposes.
+    """
+    def wrap(func):
+        def wrapped_function(cr, version):
+            stage =  'unknown'
+            module = 'unknown'
+            filename = 'unknown'
+            try:
+                frame = inspect.getargvalues(inspect.stack()[1][0])
+                stage = frame.locals['stage']
+                module = frame.locals['pkg'].name
+                filename = frame.locals['fp'].name
+            except Exception, e:
+                logger.error(
+                    "'migrate' decorator: failed to inspect "
+                    "the frame above: %s" % e)
+                pass
+            if not version:
+                return
+            logger.info(
+                "%s: %s-migration script called with version %s" %
+                (module, stage, version))
+            try:
+                # The actual function is called here
+                func(cr, version)
+            except Exception, e:
+                logger.error(
+                    "%s: error in migration script %s: %s" % 
+                    (module, filename, e))
+        return wrapped_function
+    return wrap


Follow ups