[MERGE] forward merge 7.0 until revision 4919.
authorVo Minh Thu <vmt@openerp.com>
Thu, 4 Apr 2013 13:07:04 +0000 (15:07 +0200)
committerVo Minh Thu <vmt@openerp.com>
Thu, 4 Apr 2013 13:07:04 +0000 (15:07 +0200)
bzr revid: vmt@openerp.com-20130404130704-24vsmczw34cssytd

27 files changed:
1  2 
debian/control
openerp/__init__.py
openerp/addons/base/ir/ir_actions.py
openerp/addons/base/ir/ir_cron.py
openerp/addons/base/ir/ir_model.py
openerp/addons/base/module/module.py
openerp/addons/base/res/res_partner.py
openerp/addons/base/res/res_partner_view.xml
openerp/addons/base/res/res_users.py
openerp/cli/server.py
openerp/modules/loading.py
openerp/modules/registry.py
openerp/osv/expression.py
openerp/osv/orm.py
openerp/service/cron.py
openerp/service/db.py
openerp/service/model.py
openerp/service/workers.py
openerp/service/wsgi_server.py
openerp/sql_db.py
openerp/tools/image.py
openerp/tools/mail.py
openerp/tools/misc.py
openerp/tools/translate.py
openerpcommand/cron.py
openerpcommand/web.py
setup.py

diff --cc debian/control
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -25,8 -25,9 +25,9 @@@ import tim
  import types
  
  import openerp
+ import openerp.modules.registry
  from openerp import SUPERUSER_ID
 -from openerp import netsvc, pooler, tools
 +from openerp import tools
  from openerp.osv import fields,osv
  from openerp.osv.orm import Model
  from openerp.tools.safe_eval import safe_eval as eval
@@@ -168,7 -169,9 +169,9 @@@ class ir_model(osv.osv)
          if not context.get(MODULE_UNINSTALL_FLAG):
              # only reload pool for normal unlink. For module uninstall the
              # reload is done independently in openerp.modules.loading
+             cr.commit() # must be committed before reloading registry in new cursor
 -            pooler.restart_pool(cr.dbname)
 +            openerp.modules.registry.RegistryManager.new(cr.dbname)
+             openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
  
          return res
  
                  field_name=vals['name'],
                  field_state='manual',
                  select=vals.get('select_level', '0'))
 -            self.pool.get(vals['model'])._auto_init(cr, ctx)
 +            self.pool[vals['model']]._auto_init(cr, ctx)
+             openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
          return res
  
      def instanciate(self, cr, user, model, context=None):
@@@ -348,7 -354,8 +354,8 @@@ class ir_model_fields(osv.osv)
                      field_state='manual',
                      select=vals.get('select_level', '0'),
                      update_custom_fields=True)
 -                self.pool.get(vals['model'])._auto_init(cr, ctx)
 +                self.pool[vals['model']]._auto_init(cr, ctx)
+                 openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
  
          return res
  
Simple merge
@@@ -423,10 -427,12 +427,12 @@@ class res_users(osv.osv)
              # Successfully logged in as admin!
              # Attempt to guess the web base url...
              if user_agent_env and user_agent_env.get('base_location'):
 -                cr = pooler.get_db(db).cursor()
 +                cr = self.pool.db.cursor()
                  try:
                      base = user_agent_env['base_location']
-                     self.pool['ir.config_parameter'].set_param(cr, uid, 'web.base.url', base)
 -                    ICP = self.pool.get('ir.config_parameter')
++                    ICP = self.pool['ir.config_parameter']
+                     if not ICP.get_param(cr, uid, 'web.base.url.freeze'):
+                         ICP.set_param(cr, uid, 'web.base.url', base)
                      cr.commit()
                  except Exception:
                      _logger.exception("Failed to update web.base.url configuration parameter")
@@@ -246,9 -228,7 +238,7 @@@ def main(args)
  
      config = openerp.tools.config
  
-     configure_babel_localedata_path()
 -    setup_signal_handlers()
 +    setup_signal_handlers(signal_handler)
  
      if config["test_file"]:
          run_test_file(config['db_name'], config['test_file'])
@@@ -34,7 -34,9 +34,8 @@@ import opener
  import openerp.modules.db
  import openerp.modules.graph
  import openerp.modules.migration
+ import openerp.modules.registry
  import openerp.osv as osv
 -import openerp.pooler as pooler
  import openerp.tools as tools
  from openerp import SUPERUSER_ID
  
@@@ -129,9 -137,9 +130,9 @@@ def load_module_graph(cr, graph, status
  
      processed_modules = []
      loaded_modules = []
 -    pool = pooler.get_pool(cr.dbname)
 +    registry = openerp.registry(cr.dbname)
      migrations = openerp.modules.migration.MigrationManager(cr, graph)
-     _logger.debug('loading %d packages...', len(graph))
+     _logger.info('loading %d modules...', len(graph))
  
      # Query manual fields for all models at once and save them on the registry
      # so the initialization code for each model does not have to do it
Simple merge
Simple merge
@@@ -4504,7 -4484,8 +4503,8 @@@ class BaseModel(object)
                  self.name_get(cr, user, [id_new], context=context)[0][1] + \
                  "' " + _("created.")
              self.log(cr, user, id_new, message, True, context=context)
+         self.check_access_rule(cr, user, [id_new], 'create', context=context)
 -        self._workflow_trigger(cr, user, [id_new], 'trg_create', context=context)
 +        self.create_workflow(cr, user, [id_new], context=context)
          return id_new
  
      def browse(self, cr, uid, select, context=None, list_class=None, fields_process=None):
Simple merge
index 07d9085,0000000..a480b65
mode 100644,000000..100644
--- /dev/null
@@@ -1,357 -1,0 +1,365 @@@
 +# -*- coding: utf-8 -*-
 +
 +import base64
 +import contextlib
 +import logging
 +import os
 +import threading
 +import traceback
 +
 +import openerp
 +from openerp import SUPERUSER_ID
 +import openerp.release
 +import openerp.sql_db
 +import openerp.tools
 +
 +import security
 +
 +_logger = logging.getLogger(__name__)
 +
 +self_actions = {}
 +self_id = 0
 +self_id_protect = threading.Semaphore()
 +
 +# This should be moved to openerp.modules.db, along side initialize().
 +def _initialize_db(id, db_name, demo, lang, user_password):
 +    try:
 +        cr = None
 +        try:
 +            self_actions[id]['progress'] = 0
 +            cr = openerp.sql_db.db_connect(db_name).cursor()
 +            openerp.modules.db.initialize(cr) # TODO this should be removed as it is done by RegistryManager.new().
 +            openerp.tools.config['lang'] = lang
 +            cr.commit()
 +        finally:
 +            if cr:
 +                cr.close()
 +                cr = None
 +
 +        registry = openerp.modules.registry.RegistryManager.new(
 +            db_name, demo, self_actions[id], update_module=True)
 +
 +        try:
 +            cr = openerp.sql_db.db_connect(db_name).cursor()
 +
 +            if lang:
 +                modobj = registry['ir.module.module']
 +                mids = modobj.search(cr, SUPERUSER_ID, [('state', '=', 'installed')])
 +                modobj.update_translations(cr, SUPERUSER_ID, mids, lang)
 +
 +            # update admin's password and lang
 +            values = {'password': user_password, 'lang': lang}
 +            registry['res.users'].write(cr, SUPERUSER_ID, [SUPERUSER_ID], values)
 +
 +            cr.execute('SELECT login, password FROM res_users ORDER BY login')
 +            self_actions[id].update(users=cr.dictfetchall(), clean=True)
 +            cr.commit()
 +        finally:
 +            if cr:
 +                cr.close()
 +    except Exception, e:
 +        self_actions[id].update(clean=False, exception=e)
 +        _logger.exception('CREATE DATABASE failed:')
 +        self_actions[id]['traceback'] = traceback.format_exc()
 +
 +def dispatch(method, params):
 +    if method in [ 'create', 'get_progress', 'drop', 'dump',
 +        'restore', 'rename',
 +        'change_admin_password', 'migrate_databases',
 +        'create_database', 'duplicate_database' ]:
 +        passwd = params[0]
 +        params = params[1:]
 +        security.check_super(passwd)
 +    elif method in [ 'db_exist', 'list', 'list_lang', 'server_version' ]:
 +        # params = params
 +        # No security check for these methods
 +        pass
 +    else:
 +        raise KeyError("Method not found: %s" % method)
 +    fn = globals()['exp_' + method]
 +    return fn(*params)
 +
 +def _create_empty_database(name):
 +    db = openerp.sql_db.db_connect('postgres')
 +    cr = db.cursor()
 +    chosen_template = openerp.tools.config['db_template']
 +    cr.execute("""SELECT datname 
 +                          FROM pg_database
 +                          WHERE datname = %s """,
 +                       (name,))
 +    if cr.fetchall():
 +        raise openerp.exceptions.Warning(" %s database already exists!" % name )
 +    try:
 +        cr.autocommit(True) # avoid transaction block
 +        cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (name, chosen_template))
 +    finally:
 +        cr.close()
 +
 +def exp_create(db_name, demo, lang, user_password='admin'):
 +    self_id_protect.acquire()
 +    global self_id
 +    self_id += 1
 +    id = self_id
 +    self_id_protect.release()
 +
 +    self_actions[id] = {'clean': False}
 +
 +    _create_empty_database(db_name)
 +
 +    _logger.info('CREATE DATABASE %s', db_name.lower())
 +    create_thread = threading.Thread(target=_initialize_db,
 +            args=(id, db_name, demo, lang, user_password))
 +    create_thread.start()
 +    self_actions[id]['thread'] = create_thread
 +    return id
 +
 +def exp_create_database(db_name, demo, lang, user_password='admin'):
 +    """ Similar to exp_create but blocking."""
 +    self_id_protect.acquire()
 +    global self_id
 +    self_id += 1
 +    id = self_id
 +    self_id_protect.release()
 +
 +    self_actions[id] = {'clean': False}
 +
 +    _logger.info('Create database `%s`.', db_name)
 +    _create_empty_database(db_name)
 +    _initialize_db(id, db_name, demo, lang, user_password)
 +    return True
 +
 +def exp_duplicate_database(db_original_name, db_name):
 +    _logger.info('Duplicate database `%s` to `%s`.', db_original_name, db_name)
 +    openerp.sql_db.close_db(db_original_name)
 +    db = openerp.sql_db.db_connect('postgres')
 +    cr = db.cursor()
 +    try:
 +        cr.autocommit(True) # avoid transaction block
 +        cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (db_name, db_original_name))
 +    finally:
 +        cr.close()
 +    return True
 +
 +def exp_get_progress(id):
 +    if self_actions[id]['thread'].isAlive():
 +#       return openerp.modules.init_progress[db_name]
 +        return min(self_actions[id].get('progress', 0),0.95), []
 +    else:
 +        clean = self_actions[id]['clean']
 +        if clean:
 +            users = self_actions[id]['users']
 +            for user in users:
 +                # Remove the None passwords as they can't be marshalled by XML-RPC.
 +                if user['password'] is None:
 +                    user['password'] = ''
 +            self_actions.pop(id)
 +            return 1.0, users
 +        else:
 +            e = self_actions[id]['exception'] # TODO this seems wrong: actions[id]['traceback'] is set, but not 'exception'.
 +            self_actions.pop(id)
 +            raise Exception, e
 +
 +def exp_drop(db_name):
 +    if not exp_db_exist(db_name):
 +        return False
 +    openerp.modules.registry.RegistryManager.delete(db_name)
 +    openerp.sql_db.close_db(db_name)
 +
 +    db = openerp.sql_db.db_connect('postgres')
 +    cr = db.cursor()
 +    cr.autocommit(True) # avoid transaction block
 +    try:
 +        # Try to terminate all other connections that might prevent
 +        # dropping the database
 +        try:
 +
 +            # PostgreSQL 9.2 renamed pg_stat_activity.procpid to pid:
 +            # http://www.postgresql.org/docs/9.2/static/release-9-2.html#AEN110389
 +            pid_col = 'pid' if cr._cnx.server_version >= 90200 else 'procpid'
 +
 +            cr.execute("""SELECT pg_terminate_backend(%(pid_col)s)
 +                          FROM pg_stat_activity
 +                          WHERE datname = %%s AND 
 +                                %(pid_col)s != pg_backend_pid()""" % {'pid_col': pid_col},
 +                       (db_name,))
 +        except Exception:
 +            pass
 +
 +        try:
 +            cr.execute('DROP DATABASE "%s"' % db_name)
 +        except Exception, e:
 +            _logger.error('DROP DB: %s failed:\n%s', db_name, e)
 +            raise Exception("Couldn't drop database %s: %s" % (db_name, e))
 +        else:
 +            _logger.info('DROP DB: %s', db_name)
 +    finally:
 +        cr.close()
 +    return True
 +
 +@contextlib.contextmanager
- def _set_pg_password_in_environment():
-     """ On Win32, pg_dump (and pg_restore) require that
-     :envvar:`PGPASSWORD` be set
++def _set_pg_password_in_environment(self):
++    """ On systems where pg_restore/pg_dump require an explicit
++    password (i.e. when not connecting via unix sockets, and most
++    importantly on Windows), it is necessary to pass the PG user
++    password in the environment or in a special .pgpass file.
 +
 +    This context management method handles setting
-     :envvar:`PGPASSWORD` iif win32 and the envvar is not already
++    :envvar:`PGPASSWORD` if it is not already
 +    set, and removing it afterwards.
++
++    See also http://www.postgresql.org/docs/8.4/static/libpq-envars.html
++    
++    .. note:: This is not thread-safe, and should never be enabled for
++         SaaS (giving SaaS users the super-admin password is not a good idea
++         anyway)
 +    """
-     if os.name != 'nt' or os.environ.get('PGPASSWORD'):
++    if os.environ.get('PGPASSWORD') or not tools.config['db_password']:
 +        yield
 +    else:
-         os.environ['PGPASSWORD'] = openerp.tools.config['db_password']
++        os.environ['PGPASSWORD'] = tools.config['db_password']
 +        try:
 +            yield
 +        finally:
 +            del os.environ['PGPASSWORD']
 +
 +
 +def exp_dump(db_name):
 +    with _set_pg_password_in_environment():
 +        cmd = ['pg_dump', '--format=c', '--no-owner']
 +        if openerp.tools.config['db_user']:
 +            cmd.append('--username=' + openerp.tools.config['db_user'])
 +        if openerp.tools.config['db_host']:
 +            cmd.append('--host=' + openerp.tools.config['db_host'])
 +        if openerp.tools.config['db_port']:
 +            cmd.append('--port=' + str(openerp.tools.config['db_port']))
 +        cmd.append(db_name)
 +
 +        stdin, stdout = openerp.tools.exec_pg_command_pipe(*tuple(cmd))
 +        stdin.close()
 +        data = stdout.read()
 +        res = stdout.close()
 +
 +        if not data or res:
 +            _logger.error(
 +                    'DUMP DB: %s failed! Please verify the configuration of the database password on the server. '
-                     'It should be provided as a -w <PASSWD> command-line option, or as `db_password` in the '
++                    'You may need to create a .pgpass file for authentication, or specify `db_password` in the '
 +                    'server configuration file.\n %s', db_name, data)
 +            raise Exception, "Couldn't dump database"
 +        _logger.info('DUMP DB successful: %s', db_name)
 +
 +        return base64.encodestring(data)
 +
 +def exp_restore(db_name, data):
 +    with _set_pg_password_in_environment():
 +        if exp_db_exist(db_name):
 +            _logger.warning('RESTORE DB: %s already exists', db_name)
 +            raise Exception, "Database already exists"
 +
 +        _create_empty_database(db_name)
 +
 +        cmd = ['pg_restore', '--no-owner']
 +        if openerp.tools.config['db_user']:
 +            cmd.append('--username=' + openerp.tools.config['db_user'])
 +        if openerp.tools.config['db_host']:
 +            cmd.append('--host=' + openerp.tools.config['db_host'])
 +        if openerp.tools.config['db_port']:
 +            cmd.append('--port=' + str(openerp.tools.config['db_port']))
 +        cmd.append('--dbname=' + db_name)
 +        args2 = tuple(cmd)
 +
 +        buf=base64.decodestring(data)
 +        if os.name == "nt":
 +            tmpfile = (os.environ['TMP'] or 'C:\\') + os.tmpnam()
 +            file(tmpfile, 'wb').write(buf)
 +            args2=list(args2)
 +            args2.append(tmpfile)
 +            args2=tuple(args2)
 +        stdin, stdout = openerp.tools.exec_pg_command_pipe(*args2)
 +        if not os.name == "nt":
 +            stdin.write(base64.decodestring(data))
 +        stdin.close()
 +        res = stdout.close()
 +        if res:
 +            raise Exception, "Couldn't restore database"
 +        _logger.info('RESTORE DB: %s', db_name)
 +
 +        return True
 +
 +def exp_rename(old_name, new_name):
 +    openerp.modules.registry.RegistryManager.delete(old_name)
 +    openerp.sql_db.close_db(old_name)
 +
 +    db = openerp.sql_db.db_connect('postgres')
 +    cr = db.cursor()
 +    cr.autocommit(True) # avoid transaction block
 +    try:
 +        try:
 +            cr.execute('ALTER DATABASE "%s" RENAME TO "%s"' % (old_name, new_name))
 +        except Exception, e:
 +            _logger.error('RENAME DB: %s -> %s failed:\n%s', old_name, new_name, e)
 +            raise Exception("Couldn't rename database %s to %s: %s" % (old_name, new_name, e))
 +        else:
 +            fs = os.path.join(openerp.tools.config['root_path'], 'filestore')
 +            if os.path.exists(os.path.join(fs, old_name)):
 +                os.rename(os.path.join(fs, old_name), os.path.join(fs, new_name))
 +
 +            _logger.info('RENAME DB: %s -> %s', old_name, new_name)
 +    finally:
 +        cr.close()
 +    return True
 +
 +def exp_db_exist(db_name):
 +    ## Not True: in fact, check if connection to database is possible. The database may exists
 +    return bool(openerp.sql_db.db_connect(db_name))
 +
 +def exp_list(document=False):
 +    if not openerp.tools.config['list_db'] and not document:
 +        raise openerp.exceptions.AccessDenied()
 +    chosen_template = openerp.tools.config['db_template']
 +    templates_list = tuple(set(['template0', 'template1', 'postgres', chosen_template]))
 +    db = openerp.sql_db.db_connect('postgres')
 +    cr = db.cursor()
 +    try:
 +        try:
 +            db_user = openerp.tools.config["db_user"]
 +            if not db_user and os.name == 'posix':
 +                import pwd
 +                db_user = pwd.getpwuid(os.getuid())[0]
 +            if not db_user:
 +                cr.execute("select usename from pg_user where usesysid=(select datdba from pg_database where datname=%s)", (openerp.tools.config["db_name"],))
 +                res = cr.fetchone()
 +                db_user = res and str(res[0])
 +            if db_user:
 +                cr.execute("select datname from pg_database where datdba=(select usesysid from pg_user where usename=%s) and datname not in %s order by datname", (db_user, templates_list))
 +            else:
 +                cr.execute("select datname from pg_database where datname not in %s order by datname", (templates_list,))
 +            res = [str(name) for (name,) in cr.fetchall()]
 +        except Exception:
 +            res = []
 +    finally:
 +        cr.close()
 +    res.sort()
 +    return res
 +
 +def exp_change_admin_password(new_password):
 +    openerp.tools.config['admin_passwd'] = new_password
 +    openerp.tools.config.save()
 +    return True
 +
 +def exp_list_lang():
 +    return openerp.tools.scan_languages()
 +
 +def exp_server_version():
 +    """ Return the version of the server
 +        Used by the client to verify the compatibility with its own version
 +    """
 +    return openerp.release.version
 +
 +def exp_migrate_databases(databases):
 +    for db in databases:
 +        _logger.info('migrate database %s', db)
 +        openerp.tools.config['update']['base'] = True
 +        openerp.modules.registry.RegistryManager.new(db, force_demo=False, update_module=True)
 +    return True
 +
 +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index 79f0ace,0000000..adaac53
mode 100644,000000..100644
--- /dev/null
@@@ -1,180 -1,0 +1,202 @@@
 +# -*- coding: utf-8 -*-
 +
 +from functools import wraps
 +import logging
 +from psycopg2 import IntegrityError, errorcodes
++import random
 +import threading
++import time
 +
 +import openerp
 +from openerp.tools.translate import translate
 +from openerp.osv.orm import except_orm
 +
 +import security
 +
 +_logger = logging.getLogger(__name__)
 +
++PG_CONCURRENCY_ERRORS_TO_RETRY = (errorcodes.LOCK_NOT_AVAILABLE, errorcodes.SERIALIZATION_FAILURE, errorcodes.DEADLOCK_DETECTED)
++MAX_TRIES_ON_CONCURRENCY_FAILURE = 5
++
 +def dispatch(method, params):
 +    (db, uid, passwd ) = params[0:3]
++
++    # set uid tracker - cleaned up at the WSGI
++    # dispatching phase in openerp.service.wsgi_server.application
 +    threading.current_thread().uid = uid
++
 +    params = params[3:]
 +    if method == 'obj_list':
 +        raise NameError("obj_list has been discontinued via RPC as of 6.0, please query ir.model directly!")
 +    if method not in ['execute', 'execute_kw', 'exec_workflow']:
 +        raise NameError("Method not available %s" % method)
 +    security.check(db,uid,passwd)
 +    openerp.modules.registry.RegistryManager.check_registry_signaling(db)
 +    fn = globals()[method]
 +    res = fn(db, uid, *params)
 +    openerp.modules.registry.RegistryManager.signal_caches_change(db)
 +    return res
 +
 +def check(f):
 +    @wraps(f)
 +    def wrapper(dbname, *args, **kwargs):
 +        """ Wraps around OSV functions and normalises a few exceptions
 +        """
 +
 +        def tr(src, ttype):
 +            # We try to do the same as the _(), but without the frame
 +            # inspection, since we aready are wrapping an osv function
 +            # trans_obj = self.get('ir.translation') cannot work yet :(
 +            ctx = {}
 +            if not kwargs:
 +                if args and isinstance(args[-1], dict):
 +                    ctx = args[-1]
 +            elif isinstance(kwargs, dict):
 +                ctx = kwargs.get('context', {})
 +
 +            uid = 1
 +            if args and isinstance(args[0], (long, int)):
 +                uid = args[0]
 +
 +            lang = ctx and ctx.get('lang')
 +            if not (lang or hasattr(src, '__call__')):
 +                return src
 +
 +            # We open a *new* cursor here, one reason is that failed SQL
 +            # queries (as in IntegrityError) will invalidate the current one.
 +            cr = False
 +
 +            if hasattr(src, '__call__'):
 +                # callable. We need to find the right parameters to call
 +                # the  orm._sql_message(self, cr, uid, ids, context) function,
 +                # or we skip..
 +                # our signature is f(registry, dbname [,uid, obj, method, args])
 +                try:
 +                    if args and len(args) > 1:
 +                        # TODO self doesn't exist, but was already wrong before (it was not a registry but just the object_service.
 +                        obj = self.get(args[1])
 +                        if len(args) > 3 and isinstance(args[3], (long, int, list)):
 +                            ids = args[3]
 +                        else:
 +                            ids = []
 +                    cr = openerp.sql_db.db_connect(dbname).cursor()
 +                    return src(obj, cr, uid, ids, context=(ctx or {}))
 +                except Exception:
 +                    pass
 +                finally:
 +                    if cr: cr.close()
 +
 +                return False # so that the original SQL error will
 +                             # be returned, it is the best we have.
 +
 +            try:
 +                cr = openerp.sql_db.db_connect(dbname).cursor()
 +                res = translate(cr, name=False, source_type=ttype,
 +                                lang=lang, source=src)
 +                if res:
 +                    return res
 +                else:
 +                    return src
 +            finally:
 +                if cr: cr.close()
 +
 +        def _(src):
 +            return tr(src, 'code')
 +
-         try:
-             if openerp.registry(dbname)._init:
-                 raise openerp.exceptions.Warning('Currently, this database is not fully loaded and can not be used.')
-             return f(dbname, *args, **kwargs)
-         except IntegrityError, inst:
-             registry = openerp.registry(dbname)
-             for key in registry._sql_error.keys():
-                 if key in inst[0]:
-                     raise openerp.osv.orm.except_orm(_('Constraint Error'), tr(registry._sql_error[key], 'sql_constraint') or inst[0])
-             if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION):
-                 msg = _('The operation cannot be completed, probably due to the following:\n- deletion: you may be trying to delete a record while other records still reference it\n- creation/update: a mandatory field is not correctly set')
-                 _logger.debug("IntegrityError", exc_info=True)
-                 try:
-                     errortxt = inst.pgerror.replace('«','"').replace('»','"')
-                     if '"public".' in errortxt:
-                         context = errortxt.split('"public".')[1]
-                         model_name = table = context.split('"')[1]
-                     else:
-                         last_quote_end = errortxt.rfind('"')
-                         last_quote_begin = errortxt.rfind('"', 0, last_quote_end)
-                         model_name = table = errortxt[last_quote_begin+1:last_quote_end].strip()
-                     model = table.replace("_",".")
-                     model_obj = registry.get(model)
-                     if model_obj:
-                         model_name = model_obj._description or model_obj._name
-                     msg += _('\n\n[object with reference: %s - %s]') % (model_name, model)
-                 except Exception:
-                     pass
-                 raise openerp.osv.orm.except_orm(_('Integrity Error'), msg)
-             else:
-                 raise openerp.osv.orm.except_orm(_('Integrity Error'), inst[0])
++        tries = 0
++        while True:
++            try:
++                if openerp.registry(dbname)._init:
++                    raise openerp.exceptions.Warning('Currently, this database is not fully loaded and can not be used.')
++                return f(dbname, *args, **kwargs)
++            except OperationalError, e:
++                # Automatically retry the typical transaction serialization errors
++                if e.pgcode not in PG_CONCURRENCY_ERRORS_TO_RETRY:
++                    raise
++                if tries >= MAX_TRIES_ON_CONCURRENCY_FAILURE:
++                    _logger.warning("%s, maximum number of tries reached" % errorcodes.lookup(e.pgcode))
++                    raise
++                wait_time = random.uniform(0.0, 2 ** tries)
++                tries += 1
++                _logger.info("%s, retry %d/%d in %.04f sec..." % (errorcodes.lookup(e.pgcode), tries, MAX_TRIES_ON_CONCURRENCY_FAILURE, wait_time))
++                time.sleep(wait_time)
++            except IntegrityError, inst:
++                registry = openerp.registry(dbname)
++                for key in registry._sql_error.keys():
++                    if key in inst[0]:
++                        raise openerp.osv.orm.except_orm(_('Constraint Error'), tr(registry._sql_error[key], 'sql_constraint') or inst[0])
++                if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION):
++                    msg = _('The operation cannot be completed, probably due to the following:\n- deletion: you may be trying to delete a record while other records still reference it\n- creation/update: a mandatory field is not correctly set')
++                    _logger.debug("IntegrityError", exc_info=True)
++                    try:
++                        errortxt = inst.pgerror.replace('«','"').replace('»','"')
++                        if '"public".' in errortxt:
++                            context = errortxt.split('"public".')[1]
++                            model_name = table = context.split('"')[1]
++                        else:
++                            last_quote_end = errortxt.rfind('"')
++                            last_quote_begin = errortxt.rfind('"', 0, last_quote_end)
++                            model_name = table = errortxt[last_quote_begin+1:last_quote_end].strip()
++                        model = table.replace("_",".")
++                        model_obj = registry.get(model)
++                        if model_obj:
++                            model_name = model_obj._description or model_obj._name
++                        msg += _('\n\n[object with reference: %s - %s]') % (model_name, model)
++                    except Exception:
++                        pass
++                    raise openerp.osv.orm.except_orm(_('Integrity Error'), msg)
++                else:
++                    raise openerp.osv.orm.except_orm(_('Integrity Error'), inst[0])
 +
 +    return wrapper
 +
 +def execute_cr(cr, uid, obj, method, *args, **kw):
 +    object = openerp.registry(cr.dbname).get(obj)
 +    if not object:
 +        raise except_orm('Object Error', 'Object %s doesn\'t exist' % str(obj))
 +    return getattr(object, method)(cr, uid, *args, **kw)
 +
 +def execute_kw(db, uid, obj, method, args, kw=None):
 +    return execute(db, uid, obj, method, *args, **kw or {})
 +
 +@check
 +def execute(db, uid, obj, method, *args, **kw):
 +    threading.currentThread().dbname = db
 +    cr = openerp.registry(db).db.cursor()
 +    try:
 +        try:
 +            if method.startswith('_'):
 +                raise except_orm('Access Denied', 'Private methods (such as %s) cannot be called remotely.' % (method,))
 +            res = execute_cr(cr, uid, obj, method, *args, **kw)
 +            if res is None:
 +                _logger.warning('The method %s of the object %s can not return `None` !', method, obj)
 +            cr.commit()
 +        except Exception:
 +            cr.rollback()
 +            raise
 +    finally:
 +        cr.close()
 +    return res
 +
 +def exec_workflow_cr(cr, uid, obj, signal, *args):
 +    object = openerp.registry(cr.dbname).get(obj)
 +    if not object:
 +        raise except_orm('Object Error', 'Object %s doesn\'t exist' % str(obj))
 +    res_id = args[0]
 +    return object.signal_workflow(cr, uid, [res_id], signal)[res_id]
 +
 +@check
 +def exec_workflow(db, uid, obj, signal, *args):
 +    cr = openerp.registry(db).db.cursor()
 +    try:
 +        try:
 +            res = exec_workflow_cr(cr, uid, obj, signal, *args)
 +            cr.commit()
 +        except Exception:
 +            cr.rollback()
 +            raise
 +    finally:
 +        cr.close()
 +    return res
 +
 +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@@ -399,8 -377,10 +409,10 @@@ class WorkerCron(Worker)
          if config['db_name']:
              db_names = config['db_name'].split(',')
          else:
 -            db_names = openerp.netsvc.ExportService._services['db'].exp_list(True)
 +            db_names = openerp.service.db.exp_list(True)
-         for db_name in db_names:
+         if len(db_names):
+             self.db_index = (self.db_index + 1) % len(db_names)
+             db_name = db_names[self.db_index]
              if rpc_request_flag:
                  start_time = time.time()
                  start_rss, start_vms = psutil.Process(os.getpid()).get_memory_info()
@@@ -383,13 -382,18 +383,23 @@@ def register_wsgi_handler(handler)
      """
      module_handlers.append(handler)
  
 +def register_rpc_endpoint(endpoint, handler):
 +    """ Register a handler for a given RPC enpoint.
 +    """
 +    rpc_handlers[endpoint] = handler
 +
  def application_unproxied(environ, start_response):
      """ WSGI entry point."""
+     # cleanup db/uid trackers - they're set at HTTP dispatch in
+     # web.session.OpenERPSession.send() and at RPC dispatch in
+     # openerp.service.web_services.objects_proxy.dispatch().
+     # /!\ The cleanup cannot be done at the end of this `application`
+     # method because werkzeug still produces relevant logging afterwards 
+     if hasattr(threading.current_thread(), 'uid'):
+         del threading.current_thread().uid
+     if hasattr(threading.current_thread(), 'dbname'):
+         del threading.current_thread().dbname
      openerp.service.start_internal()
  
      # Try all handlers until one returns some result (i.e. not None).
Simple merge
@@@ -80,9 -78,11 +80,11 @@@ def image_resize_image(base64_source, s
      if avoid_if_small and image.size[0] <= size[0] and image.size[1] <= size[1]:
          return base64_source
  
 -    if image.size <> size:
 +    if image.size != size:
          # If you need faster thumbnails you may use use Image.NEAREST
          image = ImageOps.fit(image, size, Image.ANTIALIAS)
+     if image.mode not in ["1", "L", "P", "RGB", "RGBA"]:
+         image = image.convert("RGB")
  
      background_stream = StringIO.StringIO()
      image.save(background_stream, filetype)
Simple merge
Simple merge
Simple merge
index 16b983d,0000000..abfe127
mode 100644,000000..100644
--- /dev/null
@@@ -1,47 -1,0 +1,46 @@@
 +"""
 +Run an OpenERP cron process.
 +"""
 +
 +import os
 +
 +import common
 +
 +def run(args):
 +    import openerp
 +    import openerp.cli.server
 +    import openerp.tools.config
 +    import openerp.service.cron
 +    config = openerp.tools.config
 +
 +    os.environ["TZ"] = "UTC"
 +    common.set_addons(args)
 +    args.database = args.database or []
 +
 +    config['log_handler'] = [':WARNING', 'openerp.addons.base.ir.ir_cron:DEBUG']
 +
 +    openerp.multi_process = True
 +    common.setproctitle('openerp-cron [%s]' % ', '.join(args.database))
 +
 +    openerp.cli.server.check_root_user()
 +    openerp.netsvc.init_logger()
 +    #openerp.cli.server.report_configuration()
-     openerp.cli.server.configure_babel_localedata_path()
 +    openerp.cli.server.setup_signal_handlers(openerp.cli.server.signal_handler)
 +    import openerp.addons.base
 +    if args.database:
 +        for db in args.database:
 +            openerp.cli.server.preload_registry(db)
 +        openerp.service.cron.start_service()
 +        openerp.cli.server.quit_on_signals()
 +    else:
 +        print "No database given."
 +
 +
 +def add_parser(subparsers):
 +    parser = subparsers.add_parser('cron',
 +        description='Run an OpenERP cron process.')
 +    common.add_addons_argument(parser)
 +    parser.add_argument('--database', action='append',
 +        help='Database for which cron jobs are processed (can be repeated)')
 +
 +    parser.set_defaults(run=run)
index 728318d,0000000..5875978
mode 100644,000000..100644
--- /dev/null
@@@ -1,94 -1,0 +1,93 @@@
 +"""
 +Run a normal OpenERP HTTP process.
 +"""
 +
 +import logging
 +import os
 +import signal
 +
 +import common
 +
 +_logger = logging.getLogger(__name__)
 +
 +def mk_signal_handler(server):
 +    def signal_handler(sig, frame):
 +        """
 +        Specialized signal handler for the evented process.
 +        """
 +        print "\n\n\nStopping gevent HTTP server...\n\n\n"
 +        server.stop()
 +    return signal_handler
 +
 +def setup_signal_handlers(signal_handler):
 +    SIGNALS = (signal.SIGINT, signal.SIGTERM)
 +    map(lambda sig: signal.signal(sig, signal_handler), SIGNALS)
 +
 +def run(args):
 +    # Note that gevent monkey patching must be done before importing the
 +    # `threading` module, see http://stackoverflow.com/questions/8774958/.
 +    if args.gevent:
 +        import gevent
 +        import gevent.monkey
 +        import gevent.wsgi
 +        import gevent_psycopg2
 +        gevent.monkey.patch_all()
 +        gevent_psycopg2.monkey_patch()
 +    import threading
 +    import openerp
 +    import openerp.cli.server
 +    import openerp.service.wsgi_server
 +    import openerp.tools.config
 +    config = openerp.tools.config
 +
 +    os.environ["TZ"] = "UTC"
 +    common.set_addons(args)
 +
 +    openerp.multi_process = True
 +    common.setproctitle('openerp-web')
 +
 +    openerp.cli.server.check_root_user()
 +    openerp.netsvc.init_logger()
 +    #openerp.cli.server.report_configuration()
-     openerp.cli.server.configure_babel_localedata_path()
 +
 +    target = openerp.service.wsgi_server.serve
 +    if not args.gevent:
 +        openerp.evented = False
 +        openerp.cli.server.setup_signal_handlers(openerp.cli.server.signal_handler)
 +        # TODO openerp.multi_process with a multi-threaded process probably
 +        # doesn't work very well (e.g. waiting for all threads to complete
 +        # before killing the process is not implemented).
 +        arg = (args.interface, int(args.port), args.threaded)
 +        threading.Thread(target=target, args=arg).start()
 +        openerp.cli.server.quit_on_signals()
 +    else:
 +        openerp.evented = True
 +
 +        app = openerp.service.wsgi_server.application
 +        server = gevent.wsgi.WSGIServer((args.interface, int(args.port)), app)
 +        setup_signal_handlers(mk_signal_handler(server))
 +        try:
 +            server.serve_forever()
 +        except KeyboardInterrupt:
 +            try:
 +                server.stop()
 +                gevent.shutdown()
 +            except KeyboardInterrupt:
 +                sys.stderr.write("Forced shutdown.\n")
 +                gevent.shutdown()
 +
 +def add_parser(subparsers):
 +    parser = subparsers.add_parser('web',
 +        description='Run a normal OpenERP HTTP process. By default a '
 +        'singly-threaded Werkzeug server is used.')
 +    common.add_addons_argument(parser)
 +    parser.add_argument('--interface', default='0.0.0.0',
 +        help='HTTP interface to listen on (default is %(default)s)')
 +    parser.add_argument('--port', metavar='INT', default=8069,
 +        help='HTTP port to listen on (default is %(default)s)')
 +    parser.add_argument('--threaded', action='store_true',
 +        help='Use a multithreaded Werkzeug server (incompatible with --gevent)')
 +    parser.add_argument('--gevent', action='store_true',
 +        help="Use gevent's WSGI server (incompatible with --threaded)")
 +
 +    parser.set_defaults(run=run)
diff --cc setup.py
+++ b/setup.py
@@@ -118,9 -126,8 +126,9 @@@ setuptools.setup
            'mock',
            'PIL', # windows binary http://www.lfd.uci.edu/~gohlke/pythonlibs/
            'psutil', # windows binary code.google.com/p/psutil/downloads/list
-           'psycopg2',
+           'psycopg2 >= 2.2',
            'pydot',
 +          'pyparsing < 2',
            'python-dateutil < 2',
            'python-ldap', # optional
            'python-openid',