3.16 is the new stable stable
authorPierre-Yves David <pierre-yves.david@logilab.fr>
Wed, 20 Mar 2013 17:40:25 +0100
branchstable
changeset 8743 27a83746aebd
parent 8742 bd374bd906f3 (current diff)
parent 8739 30c262eb0841 (diff)
child 8744 2091d275fe5c
child 8747 c0d4244e5abc
3.16 is the new stable After discussion with David Douard I'm merging 3.16.x branches in stable and starting 3.17 feature on default.
.hgtags
__pkginfo__.py
debian/changelog
web/wdoc/ChangeLog_en
web/wdoc/ChangeLog_fr
--- a/.hgtags	Tue Mar 19 16:56:46 2013 +0100
+++ b/.hgtags	Wed Mar 20 17:40:25 2013 +0100
@@ -278,3 +278,7 @@
 29fbc632a69667840294d7b38b0ca00e5f66ec19 cubicweb-debian-version-3.15.9-1
 89bdb5444cd20213d5af03c2612ceb28340cb760 cubicweb-version-3.15.10
 feca12e4a6188fbaae0cc48c6f8cc5f4202e1662 cubicweb-debian-version-3.15.10-1
+6c7c2a02c9a0ca870accfc8ed1bb120e9c858d5d cubicweb-version-3.16.0
+853237d1daf6710af94cc2ec8ee12aa7dba16934 cubicweb-debian-version-3.16.0-1
+d95cbb7349f01b9e02e5da65d55a92582bbee6db cubicweb-version-3.16.1
+84fbcdc8021c9c198fef3c6a9ad90c298ee12566 cubicweb-debian-version-3.16.1-1
--- a/__init__.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/__init__.py	Wed Mar 20 17:40:25 2013 +0100
@@ -18,8 +18,6 @@
 """CubicWeb is a generic framework to quickly build applications which describes
 relations between entitites.
 """
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 
 # ignore the pygments UserWarnings
@@ -199,3 +197,26 @@
         CW_EVENT_MANAGER.bind(event, func, *args, **kwargs)
         return func
     return _decorator
+
+
+from yams.schema import role_name as rname
+
+def validation_error(entity, errors, substitutions=None, i18nvalues=None):
+    """easy way to retrieve a :class:`cubicweb.ValidationError` for an entity or eid.
+
+    You may also have 2-tuple as error keys, :func:`yams.role_name` will be
+    called automatically for them.
+
+    Messages in errors **should not be translated yet**, though marked for
+    internationalization. You may give an additional substition dictionary that
+    will be used for interpolation after the translation.
+    """
+    if substitutions is None:
+        # set empty dict else translation won't be done for backward
+        # compatibility reason (see ValidationError.translate method)
+        substitutions = {}
+    for key in list(errors):
+        if isinstance(key, tuple):
+            errors[rname(*key)] = errors.pop(key)
+    return ValidationError(getattr(entity, 'eid', entity), errors,
+                           substitutions, i18nvalues)
--- a/__pkginfo__.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/__pkginfo__.py	Wed Mar 20 17:40:25 2013 +0100
@@ -22,7 +22,7 @@
 
 modname = distname = "cubicweb"
 
-numversion = (3, 15, 10)
+numversion = (3, 16, 1)
 version = '.'.join(str(num) for num in numversion)
 
 description = "a repository of entities / relations for knowledge management"
@@ -40,10 +40,10 @@
 ]
 
 __depends__ = {
-    'logilab-common': '>= 0.58.0',
+    'logilab-common': '>= 0.59.0',
     'logilab-mtconverter': '>= 0.8.0',
     'rql': '>= 0.31.2',
-    'yams': '>= 0.34.0',
+    'yams': '>= 0.36.0',
     #gettext                    # for xgettext, msgcat, etc...
     # web dependancies
     'simplejson': '>= 2.0.9',
--- a/_exceptions.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/_exceptions.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -19,7 +19,7 @@
 
 __docformat__ = "restructuredtext en"
 
-from yams import ValidationError
+from yams import ValidationError as ValidationError
 
 # abstract exceptions #########################################################
 
@@ -100,7 +100,7 @@
             if self.args:
                 return ' '.join(self.args)
             return self.msg
-        except Exception, ex:
+        except Exception as ex:
             return str(ex)
 
 class Forbidden(SecurityError):
--- a/appobject.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/appobject.py	Wed Mar 20 17:40:25 2013 +0100
@@ -31,25 +31,25 @@
 """
 __docformat__ = "restructuredtext en"
 
-import types
 from logging import getLogger
-from warnings import warn
 
 from logilab.common.deprecation import deprecated, class_renamed
 from logilab.common.decorators import classproperty
 from logilab.common.logging_ext import set_log_methods
-from logilab.common.registry import yes
 
-from cubicweb.cwconfig import CubicWebConfiguration
-# XXX for bw compat
-from logilab.common.registry import objectify_predicate, traced_selection, Predicate
+# first line imports for bw compat
+from logilab.common.registry import (objectify_predicate, traced_selection, Predicate,
+                                     RegistrableObject, yes)
 
 
-objectify_selector = deprecated('[3.15] objectify_selector has been renamed to objectify_predicates in logilab.common.registry')(objectify_predicate)
-traced_selection = deprecated('[3.15] traced_selection has been moved to logilab.common.registry')(traced_selection)
-Selector = class_renamed(
-    'Selector', Predicate,
-    '[3.15] Selector has been renamed to Predicate in logilab.common.registry')
+objectify_selector = deprecated('[3.15] objectify_selector has been '
+                                'renamed to objectify_predicates in '
+                                'logilab.common.registry')(objectify_predicate)
+traced_selection = deprecated('[3.15] traced_selection has been '
+                              'moved to logilab.common.registry')(traced_selection)
+Selector = class_renamed('Selector', Predicate,
+                         '[3.15] Selector has been renamed to Predicate '
+                         'in logilab.common.registry')
 
 @deprecated('[3.15] lltrace decorator can now be removed')
 def lltrace(func):
@@ -57,27 +57,12 @@
 
 # the base class for all appobjects ############################################
 
-class AppObject(object):
+class AppObject(RegistrableObject):
     """This is the base class for CubicWeb application objects which are
-    selected according to a context (usually at least a request and a result
-    set).
+    selected in a request context.
 
     The following attributes should be set on concrete appobject classes:
 
-    :attr:`__registry__`
-      name of the registry for this object (string like 'views',
-      'templates'...)
-
-    :attr:`__regid__`
-      object's identifier in the registry (string like 'main',
-      'primary', 'folder_box')
-
-    :attr:`__select__`
-      class'selector
-
-    Moreover, the `__abstract__` attribute may be set to True to indicate that a
-    class is abstract and should not be registered.
-
     At selection time, the following attributes are set on the instance:
 
     :attr:`_cw`
@@ -107,43 +92,9 @@
       * do not inherit directly from this class but from a more specific class
         such as `AnyEntity`, `EntityView`, `AnyRsetView`, `Action`...
 
-      * to be recordable, a subclass has to define its registry (attribute
-        `__registry__`) and its identifier (attribute `__regid__`). Usually
-        you don't have to take care of the registry since it's set by the base
-        class, only the identifier `id`
-
-      * application objects are designed to be loaded by the vregistry and
-        should be accessed through it, not by direct instantiation, besides
-        to use it as base classe.
-
-
-      * When we inherit from `AppObject` (even not directly), you *always* have
-        to use **super()** to get the methods and attributes of the superclasses,
-        and not use the class identifier.
-
-        For example, instead of writting::
-
-          class Truc(PrimaryView):
-              def f(self, arg1):
-                  PrimaryView.f(self, arg1)
-
-        You must write::
-
-          class Truc(PrimaryView):
-              def f(self, arg1):
-                  super(Truc, self).f(arg1)
-
     """
-    __registry__ = None
-    __regid__ = None
     __select__ = yes()
 
-    @classproperty
-    def __registries__(cls):
-        if cls.__registry__ is None:
-            return ()
-        return (cls.__registry__,)
-
     @classmethod
     def __registered__(cls, registry):
         """called by the registry when the appobject has been registered.
--- a/cwconfig.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/cwconfig.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -353,28 +353,6 @@
           'help': 'permission umask for files created by the server',
           'group': 'main', 'level': 2,
           }),
-        # pyro options
-        ('pyro-instance-id',
-         {'type' : 'string',
-          'default': Method('default_instance_id'),
-          'help': 'identifier of the CubicWeb instance in the Pyro name server',
-          'group': 'pyro', 'level': 1,
-          }),
-        ('pyro-ns-host',
-         {'type' : 'string',
-          'default': '',
-          'help': 'Pyro name server\'s host. If not set, will be detected by a \
-broadcast query. It may contains port information using <host>:<port> notation. \
-Use "NO_PYRONS" to create a Pyro server but not register to a pyro nameserver',
-          'group': 'pyro', 'level': 1,
-          }),
-        ('pyro-ns-group',
-         {'type' : 'string',
-          'default': 'cubicweb',
-          'help': 'Pyro name server\'s group where the repository will be \
-registered.',
-          'group': 'pyro', 'level': 1,
-          }),
         # common configuration options which are potentially required as soon as
         # you're using "base" application objects (ie to really server/web
         # specific)
@@ -495,7 +473,7 @@
         try:
             parent = __import__('cubes.%s.__pkginfo__' % cube)
             return getattr(parent, cube).__pkginfo__
-        except Exception, ex:
+        except Exception as ex:
             raise ConfigurationError(
                 'unable to find packaging information for cube %s (%s: %s)'
                 % (cube, ex.__class__.__name__, ex))
@@ -602,9 +580,8 @@
                                if dep in cubes)
         try:
             return ordered_nodes(graph)
-        except UnorderableGraph, ex:
-            raise ConfigurationError('cycles in cubes dependencies: %s'
-                                     % ex.cycles)
+        except UnorderableGraph as ex:
+            raise ConfigurationError(ex)
 
     @classmethod
     def cls_adjust_sys_path(cls):
@@ -636,7 +613,7 @@
             if exists(join(CW_SOFTWARE_ROOT, ctlfile)):
                 try:
                     load_module_from_file(join(CW_SOFTWARE_ROOT, ctlfile))
-                except ImportError, err:
+                except ImportError as err:
                     cls.error('could not import the command provider %s: %s',
                               ctlfile, err)
                 cls.info('loaded cubicweb-ctl plugin %s', ctlfile)
@@ -665,60 +642,12 @@
         for cube in cls.available_cubes():
             try:
                 __import__('cubes.%s' % cube)
-            except Exception, ex:
+            except Exception as ex:
                 cls.warning("can't init cube %s: %s", cube, ex)
 
     cubicweb_appobject_path = set(['entities'])
     cube_appobject_path = set(['entities'])
 
-    @classmethod
-    def build_vregistry_path(cls, templpath, evobjpath=None, tvobjpath=None):
-        """given a list of directories, return a list of sub files and
-        directories that should be loaded by the instance objects registry.
-
-        :param evobjpath:
-          optional list of sub-directories (or files without the .py ext) of
-          the cubicweb library that should be tested and added to the output list
-          if they exists. If not give, default to `cubicweb_appobject_path` class
-          attribute.
-        :param tvobjpath:
-          optional list of sub-directories (or files without the .py ext) of
-          directories given in `templpath` that should be tested and added to
-          the output list if they exists. If not give, default to
-          `cube_appobject_path` class attribute.
-        """
-        vregpath = cls.build_vregistry_cubicweb_path(evobjpath)
-        vregpath += cls.build_vregistry_cube_path(templpath, tvobjpath)
-        return vregpath
-
-    @classmethod
-    def build_vregistry_cubicweb_path(cls, evobjpath=None):
-        vregpath = []
-        if evobjpath is None:
-            evobjpath = cls.cubicweb_appobject_path
-        # NOTE: for the order, see http://www.cubicweb.org/ticket/2330799
-        #       it is clearly a workaround
-        for subdir in sorted(evobjpath, key=lambda x:x != 'entities'):
-            path = join(CW_SOFTWARE_ROOT, subdir)
-            if exists(path):
-                vregpath.append(path)
-        return vregpath
-
-    @classmethod
-    def build_vregistry_cube_path(cls, templpath, tvobjpath=None):
-        vregpath = []
-        if tvobjpath is None:
-            tvobjpath = cls.cube_appobject_path
-        for directory in templpath:
-            # NOTE: for the order, see http://www.cubicweb.org/ticket/2330799
-            for subdir in sorted(tvobjpath, key=lambda x:x != 'entities'):
-                path = join(directory, subdir)
-                if exists(path):
-                    vregpath.append(path)
-                elif exists(path + '.py'):
-                    vregpath.append(path + '.py')
-        return vregpath
-
     def __init__(self, debugmode=False):
         if debugmode:
             # in python 2.7, DeprecationWarning are not shown anymore by default
@@ -766,12 +695,57 @@
         # configure simpleTal logger
         logging.getLogger('simpleTAL').setLevel(logging.ERROR)
 
-    def vregistry_path(self):
+    def appobjects_path(self):
         """return a list of files or directories where the registry will look
         for application objects. By default return nothing in NoApp config.
         """
         return []
 
+    def build_appobjects_path(self, templpath, evobjpath=None, tvobjpath=None):
+        """given a list of directories, return a list of sub files and
+        directories that should be loaded by the instance objects registry.
+
+        :param evobjpath:
+          optional list of sub-directories (or files without the .py ext) of
+          the cubicweb library that should be tested and added to the output list
+          if they exists. If not give, default to `cubicweb_appobject_path` class
+          attribute.
+        :param tvobjpath:
+          optional list of sub-directories (or files without the .py ext) of
+          directories given in `templpath` that should be tested and added to
+          the output list if they exists. If not give, default to
+          `cube_appobject_path` class attribute.
+        """
+        vregpath = self.build_appobjects_cubicweb_path(evobjpath)
+        vregpath += self.build_appobjects_cube_path(templpath, tvobjpath)
+        return vregpath
+
+    def build_appobjects_cubicweb_path(self, evobjpath=None):
+        vregpath = []
+        if evobjpath is None:
+            evobjpath = self.cubicweb_appobject_path
+        # NOTE: for the order, see http://www.cubicweb.org/ticket/2330799
+        #       it is clearly a workaround
+        for subdir in sorted(evobjpath, key=lambda x:x != 'entities'):
+            path = join(CW_SOFTWARE_ROOT, subdir)
+            if exists(path):
+                vregpath.append(path)
+        return vregpath
+
+    def build_appobjects_cube_path(self, templpath, tvobjpath=None):
+        vregpath = []
+        if tvobjpath is None:
+            tvobjpath = self.cube_appobject_path
+        for directory in templpath:
+            # NOTE: for the order, see http://www.cubicweb.org/ticket/2330799
+            for subdir in sorted(tvobjpath, key=lambda x:x != 'entities'):
+                path = join(directory, subdir)
+                if exists(path):
+                    vregpath.append(path)
+                elif exists(path + '.py'):
+                    vregpath.append(path + '.py')
+        return vregpath
+
     apphome = None
 
     def load_site_cubicweb(self, paths=None):
@@ -1080,7 +1054,7 @@
             self.info('creating %s directory', path)
             try:
                 os.makedirs(path)
-            except OSError, ex:
+            except OSError as ex:
                 self.warning('error while creating %s directory: %s', path, ex)
                 return
         if self['uid']:
@@ -1099,14 +1073,14 @@
             self.info('giving ownership of %s directory to %s', path, self['uid'])
             try:
                 os.chown(path, uid, os.getgid())
-            except OSError, ex:
+            except OSError as ex:
                 self.warning('error while giving ownership of %s directory to %s: %s',
                              path, self['uid'], ex)
         if not (fstat.st_mode & stat.S_IWUSR):
             self.info('forcing write permission on directory %s', path)
             try:
                 os.chmod(path, fstat.st_mode | stat.S_IWUSR)
-            except OSError, ex:
+            except OSError as ex:
                 self.warning('error while forcing write permission on directory %s: %s',
                              path, ex)
                 return
@@ -1177,14 +1151,14 @@
                     self.exception('localisation support error for language %s',
                                    language)
 
-    def vregistry_path(self):
+    def appobjects_path(self):
         """return a list of files or directories where the registry will look
         for application objects
         """
         templpath = list(reversed(self.cubes_path()))
         if self.apphome: # may be unset in tests
             templpath.append(self.apphome)
-        return self.build_vregistry_path(templpath)
+        return self.build_appobjects_path(templpath)
 
     def set_sources_mode(self, sources):
         if not 'all' in sources:
@@ -1216,7 +1190,7 @@
         try:
             try:
                 smtp = SMTP(server, port)
-            except Exception, ex:
+            except Exception as ex:
                 self.exception("can't connect to smtp server %s:%s (%s)",
                                server, port, ex)
                 return False
@@ -1224,7 +1198,7 @@
             for msg, recipients in msgs:
                 try:
                     smtp.sendmail(heloaddr, recipients, msg.as_string())
-                except Exception, ex:
+                except Exception as ex:
                     self.exception("error sending mail to %s (%s)",
                                    recipients, ex)
             smtp.close()
@@ -1339,7 +1313,7 @@
             fpath = source.binary_to_str(value)
             try:
                 return Binary(fpath)
-            except OSError, ex:
+            except OSError as ex:
                 source.critical("can't open %s: %s", fpath, ex)
                 return None
 
--- a/cwctl.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/cwctl.py	Wed Mar 20 17:40:25 2013 +0100
@@ -27,6 +27,9 @@
 import sys
 from warnings import warn
 from os import remove, listdir, system, pathsep
+from os.path import exists, join, isfile, isdir, dirname, abspath
+from urlparse import urlparse
+
 try:
     from os import kill, getpgid
 except ImportError:
@@ -36,9 +39,6 @@
         """win32 getpgid implementation"""
 
 
-from os.path import exists, join, isfile, isdir, dirname, abspath
-
-from urlparse import urlparse
 
 from logilab.common.clcommands import CommandLine
 from logilab.common.shellutils import ASK
@@ -165,11 +165,11 @@
         cmdmeth = getattr(self, '%s_instance' % self.name)
         try:
             status = cmdmeth(appid)
-        except (ExecutionError, ConfigurationError), ex:
+        except (ExecutionError, ConfigurationError) as ex:
             sys.stderr.write('instance %s not %s: %s\n' % (
                     appid, self.actionverb, ex))
             status = 4
-        except Exception, ex:
+        except Exception as ex:
             import traceback
             traceback.print_exc()
             sys.stderr.write('instance %s not %s: %s\n' % (
@@ -234,7 +234,7 @@
         try:
             cubesdir = pathsep.join(cwcfg.cubes_search_path())
             namesize = max(len(x) for x in cwcfg.available_cubes())
-        except ConfigurationError, ex:
+        except ConfigurationError as ex:
             print 'No cubes available:', ex
         except ValueError:
             print 'No cubes available in %s' % cubesdir
@@ -245,7 +245,7 @@
                     tinfo = cwcfg.cube_pkginfo(cube)
                     tversion = tinfo.version
                     cfgpb.add_cube(cube, tversion)
-                except (ConfigurationError, AttributeError), ex:
+                except (ConfigurationError, AttributeError) as ex:
                     tinfo = None
                     tversion = '[missing cube information: %s]' % ex
                 print '* %s %s' % (cube.ljust(namesize), tversion)
@@ -266,7 +266,7 @@
         print
         try:
             regdir = cwcfg.instances_dir()
-        except ConfigurationError, ex:
+        except ConfigurationError as ex:
             print 'No instance available:', ex
             print
             return
@@ -281,7 +281,7 @@
                 print '* %s (%s)' % (appid, ', '.join(modes))
                 try:
                     config = cwcfg.config_for(appid, modes[0])
-                except Exception, exc:
+                except Exception as exc:
                     print '    (BROKEN instance, %s)' % exc
                     continue
         else:
@@ -365,7 +365,7 @@
         try:
             templdirs = [cwcfg.cube_dir(cube)
                          for cube in cubes]
-        except ConfigurationError, ex:
+        except ConfigurationError as ex:
             print ex
             print '\navailable cubes:',
             print ', '.join(cwcfg.available_cubes())
@@ -466,7 +466,7 @@
         # remove instance data directory
         try:
             rm(config.appdatahome)
-        except OSError, ex:
+        except OSError as ex:
             import errno
             if ex.errno != errno.ENOENT:
                 raise
@@ -561,7 +561,7 @@
         else:
             try:
                 wait_process_end(pid)
-            except ExecutionError, ex:
+            except ExecutionError as ex:
                 sys.stderr.write('%s\ntrying SIGKILL\n' % ex)
                 try:
                     kill(pid, signal.SIGKILL)
@@ -871,64 +871,65 @@
           'help': 'URI of the CubicWeb repository to connect to. URI can be \
 pyro://[host:port] the Pyro name server host; if the pyro nameserver is not set, \
 it will be detected by using a broadcast query, a ZMQ URL or \
-inmemory:// (default) use an in-memory repository.',
+inmemory:// (default) use an in-memory repository. THIS OPTION IS DEPRECATED, \
+directly give URI as instance id instead',
           'group': 'remote'
           }),
         )
 
-    def run(self, args):
-        appid = args.pop(0)
-        if self.config.repo_uri:
-            uri = urlparse(self.config.repo_uri)
-            if uri.scheme == 'pyro':
-                cnxtype = uri.scheme
-                hostport = uri.netloc
-            elif uri.scheme == 'inmemory':
-                cnxtype = ''
-                hostport = ''
-            else:
-                cnxtype = 'zmq'
-                hostport = self.config.repo_uri
+    def _handle_inmemory(self, appid):
+        """ returns migration context handler & shutdown function """
+        config = cwcfg.config_for(appid)
+        if self.config.ext_sources:
+            assert not self.config.system_only
+            sources = self.config.ext_sources
+        elif self.config.system_only:
+            sources = ('system',)
         else:
-            cnxtype = ''
-
-        if cnxtype:
-            from cubicweb import AuthenticationError
-            from cubicweb.dbapi import connect, ConnectionProperties
-            from cubicweb.server.utils import manager_userpasswd
-            from cubicweb.server.migractions import ServerMigrationHelper
-            cnxprops = ConnectionProperties(cnxtype=cnxtype)
+            sources = ('all',)
+        config.set_sources_mode(sources)
+        config.repairing = self.config.force
+        mih = config.migration_handler()
+        return mih, lambda: mih.shutdown()
 
-            while True:
-                try:
-                    login, pwd = manager_userpasswd(msg=None)
-                    cnx = connect(appid, login=login, password=pwd,
-                                  host=hostport, mulcnx=False,
-                                  cnxprops=cnxprops)
-                except AuthenticationError, ex:
-                    print ex
-                except (KeyboardInterrupt, EOFError):
-                    print
-                    sys.exit(0)
-                else:
-                    break
-            cnx.load_appobjects()
-            repo = cnx._repo
-            mih = ServerMigrationHelper(None, repo=repo, cnx=cnx, verbosity=0,
-                                         # hack so it don't try to load fs schema
-                                        schema=1)
+    def _handle_networked(self, appuri):
+        """ returns migration context handler & shutdown function """
+        from cubicweb import AuthenticationError
+        from cubicweb.dbapi import connect
+        from cubicweb.server.utils import manager_userpasswd
+        from cubicweb.server.migractions import ServerMigrationHelper
+        while True:
+            try:
+                login, pwd = manager_userpasswd(msg=None)
+                cnx = connect(appuri, login=login, password=pwd, mulcnx=False)
+            except AuthenticationError as ex:
+                print ex
+            except (KeyboardInterrupt, EOFError):
+                print
+                sys.exit(0)
+            else:
+                break
+        cnx.load_appobjects()
+        repo = cnx._repo
+        mih = ServerMigrationHelper(None, repo=repo, cnx=cnx, verbosity=0,
+                                    # hack so it don't try to load fs schema
+                                    schema=1)
+        return mih, lambda: cnx.close()
+
+    def run(self, args):
+        appuri = args.pop(0)
+        if self.config.repo_uri:
+            warn('[3.16] --repo-uri option is deprecated, directly give the URI as instance id',
+                 DeprecationWarning)
+            if urlparse(self.config.repo_uri).scheme in ('pyro', 'inmemory'):
+                appuri = '%s/%s' % (self.config.repo_uri.rstrip('/'), appuri)
+
+        from cubicweb.utils import parse_repo_uri
+        protocol, hostport, appid = parse_repo_uri(appuri)
+        if protocol == 'inmemory':
+            mih, shutdown_callback = self._handle_inmemory(appid)
         else:
-            config = cwcfg.config_for(appid)
-            if self.config.ext_sources:
-                assert not self.config.system_only
-                sources = self.config.ext_sources
-            elif self.config.system_only:
-                sources = ('system',)
-            else:
-                sources = ('all',)
-            config.set_sources_mode(sources)
-            config.repairing = self.config.force
-            mih = config.migration_handler()
+            mih, shutdown_callback = self._handle_networked(appuri)
         try:
             if args:
                 # use cmdline parser to access left/right attributes only
@@ -940,10 +941,7 @@
             else:
                 mih.interactive_shell()
         finally:
-            if not cnxtype: # shutdown in-memory repo
-                mih.shutdown()
-            else:
-                cnx.close()
+            shutdown_callback()
 
 
 class RecompileInstanceCatalogsCommand(InstanceCommand):
@@ -1012,10 +1010,10 @@
     cwcfg.load_cwctl_plugins()
     try:
         CWCTL.run(args)
-    except ConfigurationError, err:
+    except ConfigurationError as err:
         print 'ERROR: ', err
         sys.exit(1)
-    except ExecutionError, err:
+    except ExecutionError as err:
         print err
         sys.exit(2)
 
--- a/cwvreg.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/cwvreg.py	Wed Mar 20 17:40:25 2013 +0100
@@ -91,7 +91,7 @@
    # web/views/basecomponents.py
    def registration_callback(vreg):
       # register everything in the module except SeeAlsoComponent
-      vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,))
+      vreg.register_all(globals().itervalues(), __name__, (SeeAlsoVComponent,))
       # conditionally register SeeAlsoVComponent
       if 'see_also' in vreg.schema:
           vreg.register(SeeAlsoVComponent)
@@ -197,26 +197,38 @@
 from os.path import join, dirname, realpath
 from warnings import warn
 from datetime import datetime, date, time, timedelta
+from functools import partial, reduce
 
 from logilab.common.decorators import cached, clear_cache
 from logilab.common.deprecation import deprecated, class_deprecated
 from logilab.common.modutils import cleanup_sys_modules
 from logilab.common.registry import (
-    RegistryStore, Registry, classid,
+    RegistryStore, Registry, obj_registries,
     ObjectNotFound, NoSelectableObject, RegistryNotFound)
 
 from rql import RQLHelper
 from yams.constraints import BASE_CONVERTERS
 
 from cubicweb import (CW_SOFTWARE_ROOT, ETYPE_NAME_MAP, CW_EVENT_MANAGER,
-                      Binary, UnknownProperty, UnknownEid)
-from cubicweb.rtags import RTAGS
+                      onevent, Binary, UnknownProperty, UnknownEid)
 from cubicweb.predicates import (implements, appobject_selectable,
                                  _reset_is_instance_cache)
 
-def clear_rtag_objects():
-    for rtag in RTAGS:
-        rtag.clear()
+
+@onevent('before-registry-reload')
+def cleanup_uicfg_compat():
+    """ backward compat: those modules are now refering to app objects in
+    cw.web.views.uicfg and import * from backward compat. On registry reload, we
+    should pop those modules from the cache so references are properly updated on
+    subsequent reload
+    """
+    if 'cubicweb.web' in sys.modules:
+        if getattr(sys.modules['cubicweb.web'], 'uicfg', None):
+            del sys.modules['cubicweb.web'].uicfg
+        if getattr(sys.modules['cubicweb.web'], 'uihelper', None):
+            del sys.modules['cubicweb.web'].uihelper
+    sys.modules.pop('cubicweb.web.uicfg', None)
+    sys.modules.pop('cubicweb.web.uihelper', None)
 
 def use_interfaces(obj):
     """return interfaces required by the given object by searching for
@@ -263,6 +275,15 @@
     return getattr(obj, appobjectattr, obj)
 
 
+class InstancesRegistry(CWRegistry):
+
+    def selected(self, winner, args, kwargs):
+        """overriden to avoid the default 'instanciation' behaviour, ie
+        winner(*args, **kwargs)
+        """
+        return winner
+
+
 class ETypeRegistry(CWRegistry):
 
     def clear_caches(self):
@@ -497,6 +518,7 @@
                         'views': ViewsRegistry,
                         'actions': ActionsRegistry,
                         'ctxcomponents': CtxComponentsRegistry,
+                        'uicfg': InstancesRegistry,
                         }
 
     def __init__(self, config, initlog=True):
@@ -517,11 +539,6 @@
             sys.path.remove(CW_SOFTWARE_ROOT)
         self.schema = None
         self.initialized = False
-        # XXX give force_reload (or refactor [re]loading...)
-        if self.config.mode != 'test':
-            # don't clear rtags during test, this may cause breakage with
-            # manually imported appobject modules
-            CW_EVENT_MANAGER.bind('before-registry-reload', clear_rtag_objects)
         self['boxes'] = BwCompatCWRegistry(self, 'boxes', 'ctxcomponents')
         self['contentnavigation'] = BwCompatCWRegistry(self, 'contentnavigation', 'ctxcomponents')
 
@@ -544,20 +561,6 @@
     def itervalues(self):
         return (value for key, value in self.items())
 
-    def load_module(self, module):
-        """ variation from the base implementation:
-        apply related_appobject to the automatically registered objects
-        """
-        self.info('loading %s from %s', module.__name__, module.__file__)
-        if hasattr(module, 'registration_callback'):
-            module.registration_callback(self)
-            return
-        for objname, obj in vars(module).iteritems():
-            if objname.startswith('_'):
-                continue
-            self._load_ancestors_then_object(module.__name__,
-                                             related_appobject(obj))
-
     def reset(self):
         CW_EVENT_MANAGER.emit('before-registry-reset', self)
         super(CWRegistryStore, self).reset()
@@ -588,7 +591,7 @@
         """set instance'schema and load application objects"""
         self._set_schema(schema)
         # now we can load application's web objects
-        self.reload(self.config.vregistry_path(), force_reload=False)
+        self.reload(self.config.appobjects_path(), force_reload=False)
         # map lowered entity type names to their actual name
         self.case_insensitive_etypes = {}
         for eschema in self.schema.entities():
@@ -598,7 +601,7 @@
             clear_cache(eschema, 'meta_attributes')
 
     def reload_if_needed(self):
-        path = self.config.vregistry_path()
+        path = self.config.appobjects_path()
         if self.is_reload_needed(path):
             self.reload(path)
 
@@ -614,7 +617,7 @@
             cfg = self.config
             for cube in cfg.expand_cubes(cubes, with_recommends=True):
                 if not cube in cubes:
-                    cpath = cfg.build_vregistry_cube_path([cfg.cube_dir(cube)])
+                    cpath = cfg.build_appobjects_cube_path([cfg.cube_dir(cube)])
                     cleanup_sys_modules(cpath)
         self.register_objects(path)
         CW_EVENT_MANAGER.emit('after-registry-reload')
@@ -630,7 +633,7 @@
         """
         self.schema = schema
         for registry, regcontent in self.items():
-            for objects in regcontent.values():
+            for objects in regcontent.itervalues():
                 for obj in objects:
                     obj.schema = schema
 
@@ -709,8 +712,9 @@
                                    or iface
                                    for iface in ifaces)
                 if not ('Any' in ifaces or ifaces & implemented_interfaces):
+                    reg = self[obj_registries(obj)[0]]
                     self.debug('unregister %s (no implemented '
-                               'interface among %s)', classid(obj), ifaces)
+                               'interface among %s)', reg.objid(obj), ifaces)
                     self.unregister(obj)
             # since 3.9: remove appobjects which depending on other, unexistant
             # appobjects
@@ -718,8 +722,7 @@
                 try:
                     registry = self[regname]
                 except RegistryNotFound:
-                    self.debug('unregister %s (no registry %s)', classid(obj),
-                               regname)
+                    self.debug('unregister %s (no registry %s)', obj, regname)
                     self.unregister(obj)
                     continue
                 for regid in regids:
@@ -727,12 +730,14 @@
                         break
                 else:
                     self.debug('unregister %s (no %s object in registry %s)',
-                               classid(obj), ' or '.join(regids), regname)
+                               registry.objid(obj), ' or '.join(regids), regname)
                     self.unregister(obj)
         super(CWRegistryStore, self).initialization_completed()
-        for rtag in RTAGS:
-            # don't check rtags if we don't want to cleanup_interface_sobjects
-            rtag.init(self.schema, check=self.config.cleanup_interface_sobjects)
+        if 'uicfg' in self: # 'uicfg' is not loaded in a pure repository mode
+            for rtags in self['uicfg'].itervalues():
+                for rtag in rtags:
+                    # don't check rtags if we don't want to cleanup_interface_sobjects
+                    rtag.init(self.schema, check=self.config.cleanup_interface_sobjects)
 
     # rql parsing utilities ####################################################
 
@@ -820,10 +825,10 @@
         for key, val in propvalues:
             try:
                 values[key] = self.typed_value(key, val)
-            except ValueError, ex:
+            except ValueError as ex:
                 self.warning('%s (you should probably delete that property '
                              'from the database)', ex)
-            except UnknownProperty, ex:
+            except UnknownProperty as ex:
                 self.warning('%s (you should probably delete that property '
                              'from the database)', ex)
 
--- a/dataimport.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/dataimport.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -64,17 +64,20 @@
 .. BUG file with one column are not parsable
 .. TODO rollback() invocation is not possible yet
 """
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 
+import csv
 import sys
-import csv
+import threading
 import traceback
+import cPickle
 import os.path as osp
-from StringIO import StringIO
+from collections import defaultdict
+from contextlib import contextmanager
 from copy import copy
-from datetime import datetime
+from datetime import date, datetime
+from time import asctime
+from StringIO import StringIO
 
 from logilab.common import shellutils, attrdict
 from logilab.common.date import strptime
@@ -82,9 +85,11 @@
 from logilab.common.deprecation import deprecated
 
 from cubicweb import QueryError
+from cubicweb.utils import make_uid
 from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES
+from cubicweb.server.edition import EditedEntity
+from cubicweb.server.sqlutils import SQL_PREFIX
 from cubicweb.server.utils import eschema_eid
-from cubicweb.server.edition import EditedEntity
 
 
 def count_lines(stream_or_filename):
@@ -119,15 +124,33 @@
     print ' %s rows imported' % rowcount
 
 def ucsvreader(stream, encoding='utf-8', separator=',', quote='"',
-               skipfirst=False):
+               skipfirst=False, ignore_errors=False):
     """A csv reader that accepts files with any encoding and outputs unicode
     strings
     """
     it = iter(csv.reader(stream, delimiter=separator, quotechar=quote))
-    if skipfirst:
-        it.next()
-    for row in it:
-        yield [item.decode(encoding) for item in row]
+    if not ignore_errors:
+        if skipfirst:
+            it.next()
+        for row in it:
+            yield [item.decode(encoding) for item in row]
+    else:
+        # Skip first line
+        try:
+            row = it.next()
+        except csv.Error:
+            pass
+        # Safe version, that can cope with error in CSV file
+        while True:
+            try:
+                row = it.next()
+            # End of CSV, break
+            except StopIteration:
+                break
+            # Error in CSV, ignore line and continue
+            except csv.Error:
+                continue
+            yield [item.decode(encoding) for item in row]
 
 def callfunc_every(func, number, iterable):
     """yield items of `iterable` one by one and call function `func`
@@ -193,7 +216,7 @@
                 res[dest] = func(res[dest])
                 if res[dest] is None:
                     break
-        except ValueError, err:
+        except ValueError as err:
             raise ValueError('error with %r field: %s' % (src, err)), None, sys.exc_info()[-1]
     return res
 
@@ -301,6 +324,149 @@
             if k is not None and len(v) > 1]
 
 
+# sql generator utility functions #############################################
+
+
+def _import_statements(sql_connect, statements, nb_threads=3,
+                       dump_output_dir=None,
+                       support_copy_from=True, encoding='utf-8'):
+    """
+    Import a bunch of sql statements, using different threads.
+    """
+    try:
+        chunksize = (len(statements) / nb_threads) + 1
+        threads = []
+        for i in xrange(nb_threads):
+            chunks = statements[i*chunksize:(i+1)*chunksize]
+            thread = threading.Thread(target=_execmany_thread,
+                                      args=(sql_connect, chunks,
+                                            dump_output_dir,
+                                            support_copy_from,
+                                            encoding))
+            thread.start()
+            threads.append(thread)
+        for t in threads:
+            t.join()
+    except Exception:
+        print 'Error in import statements'
+
+def _execmany_thread_not_copy_from(cu, statement, data, table=None,
+                                   columns=None, encoding='utf-8'):
+    """ Execute thread without copy from
+    """
+    cu.executemany(statement, data)
+
+def _execmany_thread_copy_from(cu, statement, data, table,
+                               columns, encoding='utf-8'):
+    """ Execute thread with copy from
+    """
+    buf = _create_copyfrom_buffer(data, columns, encoding)
+    if buf is None:
+        _execmany_thread_not_copy_from(cu, statement, data)
+    else:
+        if columns is None:
+            cu.copy_from(buf, table, null='NULL')
+        else:
+            cu.copy_from(buf, table, null='NULL', columns=columns)
+
+def _execmany_thread(sql_connect, statements, dump_output_dir=None,
+                     support_copy_from=True, encoding='utf-8'):
+    """
+    Execute sql statement. If 'INSERT INTO', try to use 'COPY FROM' command,
+    or fallback to execute_many.
+    """
+    if support_copy_from:
+        execmany_func = _execmany_thread_copy_from
+    else:
+        execmany_func = _execmany_thread_not_copy_from
+    cnx = sql_connect()
+    cu = cnx.cursor()
+    try:
+        for statement, data in statements:
+            table = None
+            columns = None
+            try:
+                if not statement.startswith('INSERT INTO'):
+                    cu.executemany(statement, data)
+                    continue
+                table = statement.split()[2]
+                if isinstance(data[0], (tuple, list)):
+                    columns = None
+                else:
+                    columns = list(data[0])
+                execmany_func(cu, statement, data, table, columns, encoding)
+            except Exception:
+                print 'unable to copy data into table %s', table
+                # Error in import statement, save data in dump_output_dir
+                if dump_output_dir is not None:
+                    pdata = {'data': data, 'statement': statement,
+                             'time': asctime(), 'columns': columns}
+                    filename = make_uid()
+                    try:
+                        with open(osp.join(dump_output_dir,
+                                           '%s.pickle' % filename), 'w') as fobj:
+                            fobj.write(cPickle.dumps(pdata))
+                    except IOError:
+                        print 'ERROR while pickling in', dump_output_dir, filename+'.pickle'
+                        pass
+                cnx.rollback()
+                raise
+    finally:
+        cnx.commit()
+        cu.close()
+
+def _create_copyfrom_buffer(data, columns, encoding='utf-8', replace_sep=None):
+    """
+    Create a StringIO buffer for 'COPY FROM' command.
+    Deals with Unicode, Int, Float, Date...
+    """
+    # Create a list rather than directly create a StringIO
+    # to correctly write lines separated by '\n' in a single step
+    rows = []
+    if isinstance(data[0], (tuple, list)):
+        columns = range(len(data[0]))
+    for row in data:
+        # Iterate over the different columns and the different values
+        # and try to convert them to a correct datatype.
+        # If an error is raised, do not continue.
+        formatted_row = []
+        for col in columns:
+            value = row[col]
+            if value is None:
+                value = 'NULL'
+            elif isinstance(value, (long, int, float)):
+                value = str(value)
+            elif isinstance(value, (str, unicode)):
+                # Remove separators used in string formatting
+                for _char in (u'\t', u'\r', u'\n'):
+                    if _char in value:
+                        # If a replace_sep is given, replace
+                        # the separator instead of returning None
+                        # (and thus avoid empty buffer)
+                        if replace_sep:
+                            value = value.replace(_char, replace_sep)
+                        else:
+                            return
+                value = value.replace('\\', r'\\')
+                if value is None:
+                    return
+                if isinstance(value, unicode):
+                    value = value.encode(encoding)
+            elif isinstance(value, (date, datetime)):
+                # Do not use strftime, as it yields issue
+                # with date < 1900
+                value = '%04d-%02d-%02d' % (value.year,
+                                            value.month,
+                                            value.day)
+            else:
+                return None
+            # We push the value to the new formatted row
+            # if the value is not None and could be converted to a string.
+            formatted_row.append(value)
+        rows.append('\t'.join(formatted_row))
+    return StringIO('\n'.join(rows))
+
+
 # object stores #################################################################
 
 class ObjectStore(object):
@@ -578,7 +744,7 @@
             txuuid = self.store.commit()
             if txuuid is not None:
                 self.tell('Transaction commited (txuuid: %s)' % txuuid)
-        except QueryError, ex:
+        except QueryError as ex:
             self.tell('Transaction aborted: %s' % ex)
         self._print_stats()
         if self.errors:
@@ -589,7 +755,7 @@
                     self.tell(pformat(sorted(error[1])))
 
     def _print_stats(self):
-        nberrors = sum(len(err) for err in self.errors.values())
+        nberrors = sum(len(err) for err in self.errors.itervalues())
         self.tell('\nImport statistics: %i entities, %i types, %i relations and %i errors'
                   % (self.store.nb_inserted_entities,
                      self.store.nb_inserted_types,
@@ -755,3 +921,261 @@
         return self.session.user.eid
     def gen_owned_by(self, entity):
         return self.session.user.eid
+
+
+###########################################################################
+## SQL object store #######################################################
+###########################################################################
+class SQLGenObjectStore(NoHookRQLObjectStore):
+    """Controller of the data import process. This version is based
+    on direct insertions throught SQL command (COPY FROM or execute many).
+
+    >>> store = SQLGenObjectStore(session)
+    >>> store.create_entity('Person', ...)
+    >>> store.flush()
+    """
+
+    def __init__(self, session, dump_output_dir=None, nb_threads_statement=3):
+        """
+        Initialize a SQLGenObjectStore.
+
+        Parameters:
+
+          - session: session on the cubicweb instance
+          - dump_output_dir: a directory to dump failed statements
+            for easier recovery. Default is None (no dump).
+          - nb_threads_statement: number of threads used
+            for SQL insertion (default is 3).
+        """
+        super(SQLGenObjectStore, self).__init__(session)
+        ### hijack default source
+        self.source = SQLGenSourceWrapper(
+            self.source, session.vreg.schema,
+            dump_output_dir=dump_output_dir,
+            nb_threads_statement=nb_threads_statement)
+        ### XXX This is done in super().__init__(), but should be
+        ### redone here to link to the correct source
+        self.add_relation = self.source.add_relation
+        self.indexes_etypes = {}
+
+    def flush(self):
+        """Flush data to the database"""
+        self.source.flush()
+
+    def relate(self, subj_eid, rtype, obj_eid, subjtype=None):
+        if subj_eid is None or obj_eid is None:
+            return
+        # XXX Could subjtype be inferred ?
+        self.source.add_relation(self.session, subj_eid, rtype, obj_eid,
+                                 self.rschema(rtype).inlined, subjtype)
+
+    def drop_indexes(self, etype):
+        """Drop indexes for a given entity type"""
+        if etype not in self.indexes_etypes:
+            cu = self.session.cnxset['system']
+            def index_to_attr(index):
+                """turn an index name to (database) attribute name"""
+                return index.replace(etype.lower(), '').replace('idx', '').strip('_')
+            indices = [(index, index_to_attr(index))
+                       for index in self.source.dbhelper.list_indices(cu, etype)
+                       # Do not consider 'cw_etype_pkey' index
+                       if not index.endswith('key')]
+            self.indexes_etypes[etype] = indices
+        for index, attr in self.indexes_etypes[etype]:
+            self.session.system_sql('DROP INDEX %s' % index)
+
+    def create_indexes(self, etype):
+        """Recreate indexes for a given entity type"""
+        for index, attr in self.indexes_etypes.get(etype, []):
+            sql = 'CREATE INDEX %s ON cw_%s(%s)' % (index, etype, attr)
+            self.session.system_sql(sql)
+
+
+###########################################################################
+## SQL Source #############################################################
+###########################################################################
+
+class SQLGenSourceWrapper(object):
+
+    def __init__(self, system_source, schema,
+                 dump_output_dir=None, nb_threads_statement=3):
+        self.system_source = system_source
+        self._sql = threading.local()
+        # Explicitely backport attributes from system source
+        self._storage_handler = self.system_source._storage_handler
+        self.preprocess_entity = self.system_source.preprocess_entity
+        self.sqlgen = self.system_source.sqlgen
+        self.copy_based_source = self.system_source.copy_based_source
+        self.uri = self.system_source.uri
+        self.eid = self.system_source.eid
+        # Directory to write temporary files
+        self.dump_output_dir = dump_output_dir
+        # Allow to execute code with SQLite backend that does
+        # not support (yet...) copy_from
+        # XXX Should be dealt with in logilab.database
+        spcfrom = system_source.dbhelper.dbapi_module.support_copy_from
+        self.support_copy_from = spcfrom
+        self.dbencoding = system_source.dbhelper.dbencoding
+        self.nb_threads_statement = nb_threads_statement
+        # initialize thread-local data for main thread
+        self.init_thread_locals()
+        self._inlined_rtypes_cache = {}
+        self._fill_inlined_rtypes_cache(schema)
+        self.schema = schema
+        self.do_fti = False
+
+    def _fill_inlined_rtypes_cache(self, schema):
+        cache = self._inlined_rtypes_cache
+        for eschema in schema.entities():
+            for rschema in eschema.ordered_relations():
+                if rschema.inlined:
+                    cache[eschema.type] = SQL_PREFIX + rschema.type
+
+    def init_thread_locals(self):
+        """initializes thread-local data"""
+        self._sql.entities = defaultdict(list)
+        self._sql.relations = {}
+        self._sql.inlined_relations = {}
+        # keep track, for each eid of the corresponding data dict
+        self._sql.eid_insertdicts = {}
+
+    def flush(self):
+        print 'starting flush'
+        _entities_sql = self._sql.entities
+        _relations_sql = self._sql.relations
+        _inlined_relations_sql = self._sql.inlined_relations
+        _insertdicts = self._sql.eid_insertdicts
+        try:
+            # try, for each inlined_relation, to find if we're also creating
+            # the host entity (i.e. the subject of the relation).
+            # In that case, simply update the insert dict and remove
+            # the need to make the
+            # UPDATE statement
+            for statement, datalist in _inlined_relations_sql.iteritems():
+                new_datalist = []
+                # for a given inlined relation,
+                # browse each couple to be inserted
+                for data in datalist:
+                    keys = list(data)
+                    # For inlined relations, it exists only two case:
+                    # (rtype, cw_eid) or (cw_eid, rtype)
+                    if keys[0] == 'cw_eid':
+                        rtype = keys[1]
+                    else:
+                        rtype = keys[0]
+                    updated_eid = data['cw_eid']
+                    if updated_eid in _insertdicts:
+                        _insertdicts[updated_eid][rtype] = data[rtype]
+                    else:
+                        # could not find corresponding insert dict, keep the
+                        # UPDATE query
+                        new_datalist.append(data)
+                _inlined_relations_sql[statement] = new_datalist
+            _import_statements(self.system_source.get_connection,
+                               _entities_sql.items()
+                               + _relations_sql.items()
+                               + _inlined_relations_sql.items(),
+                               dump_output_dir=self.dump_output_dir,
+                               nb_threads=self.nb_threads_statement,
+                               support_copy_from=self.support_copy_from,
+                               encoding=self.dbencoding)
+        except:
+            print 'failed to flush'
+        finally:
+            _entities_sql.clear()
+            _relations_sql.clear()
+            _insertdicts.clear()
+            _inlined_relations_sql.clear()
+            print 'flush done'
+
+    def add_relation(self, session, subject, rtype, object,
+                     inlined=False, subjtype=None):
+        if inlined:
+            _sql = self._sql.inlined_relations
+            data = {'cw_eid': subject, SQL_PREFIX + rtype: object}
+            if subjtype is None:
+                # Try to infer it
+                targets = [t.type for t in
+                           self.schema.rschema(rtype).targets()]
+                if len(targets) == 1:
+                    subjtype = targets[0]
+                else:
+                    raise ValueError('You should give the subject etype for '
+                                     'inlined relation %s'
+                                     ', as it cannot be inferred' % rtype)
+            statement = self.sqlgen.update(SQL_PREFIX + subjtype,
+                                           data, ['cw_eid'])
+        else:
+            _sql = self._sql.relations
+            data = {'eid_from': subject, 'eid_to': object}
+            statement = self.sqlgen.insert('%s_relation' % rtype, data)
+        if statement in _sql:
+            _sql[statement].append(data)
+        else:
+            _sql[statement] = [data]
+
+    def add_entity(self, session, entity):
+        with self._storage_handler(entity, 'added'):
+            attrs = self.preprocess_entity(entity)
+            rtypes = self._inlined_rtypes_cache.get(entity.__regid__, ())
+            if isinstance(rtypes, str):
+                rtypes = (rtypes,)
+            for rtype in rtypes:
+                if rtype not in attrs:
+                    attrs[rtype] = None
+            sql = self.sqlgen.insert(SQL_PREFIX + entity.__regid__, attrs)
+            self._sql.eid_insertdicts[entity.eid] = attrs
+            self._append_to_entities(sql, attrs)
+
+    def _append_to_entities(self, sql, attrs):
+        self._sql.entities[sql].append(attrs)
+
+    def _handle_insert_entity_sql(self, session, sql, attrs):
+        # We have to overwrite the source given in parameters
+        # as here, we directly use the system source
+        attrs['source'] = 'system'
+        attrs['asource'] = self.system_source.uri
+        self._append_to_entities(sql, attrs)
+
+    def _handle_is_relation_sql(self, session, sql, attrs):
+        self._append_to_entities(sql, attrs)
+
+    def _handle_is_instance_of_sql(self, session, sql, attrs):
+        self._append_to_entities(sql, attrs)
+
+    def _handle_source_relation_sql(self, session, sql, attrs):
+        self._append_to_entities(sql, attrs)
+
+    # XXX add_info is similar to the one in NativeSQLSource. It is rewritten
+    # here to correctly used the _handle_xxx of the SQLGenSourceWrapper. This
+    # part should be rewritten in a more clearly way.
+    def add_info(self, session, entity, source, extid, complete):
+        """add type and source info for an eid into the system table"""
+        # begin by inserting eid/type/source/extid into the entities table
+        if extid is not None:
+            assert isinstance(extid, str)
+            extid = b64encode(extid)
+        uri = 'system' if source.copy_based_source else source.uri
+        attrs = {'type': entity.__regid__, 'eid': entity.eid, 'extid': extid,
+                 'source': uri, 'asource': source.uri, 'mtime': datetime.utcnow()}
+        self._handle_insert_entity_sql(session, self.sqlgen.insert('entities', attrs), attrs)
+        # insert core relations: is, is_instance_of and cw_source
+        try:
+            self._handle_is_relation_sql(session, 'INSERT INTO is_relation(eid_from,eid_to) VALUES (%s,%s)',
+                                         (entity.eid, eschema_eid(session, entity.e_schema)))
+        except IndexError:
+            # during schema serialization, skip
+            pass
+        else:
+            for eschema in entity.e_schema.ancestors() + [entity.e_schema]:
+                self._handle_is_relation_sql(session,
+                                             'INSERT INTO is_instance_of_relation(eid_from,eid_to) VALUES (%s,%s)',
+                                             (entity.eid, eschema_eid(session, eschema)))
+        if 'CWSource' in self.schema and source.eid is not None: # else, cw < 3.10
+            self._handle_is_relation_sql(session, 'INSERT INTO cw_source_relation(eid_from,eid_to) VALUES (%s,%s)',
+                                         (entity.eid, source.eid))
+        # now we can update the full text index
+        if self.do_fti and self.need_fti_indexation(entity.__regid__):
+            if complete:
+                entity.complete(entity.e_schema.indexable_attributes())
+            self.index_entity(session, entity=entity)
--- a/dbapi.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/dbapi.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -31,6 +31,7 @@
 from warnings import warn
 from os.path import join
 from uuid import uuid4
+from urlparse import  urlparse
 
 from logilab.common.logging_ext import set_log_methods
 from logilab.common.decorators import monkeypatch
@@ -39,6 +40,7 @@
 from cubicweb import ETYPE_NAME_MAP, ConnectionError, AuthenticationError,\
      cwvreg, cwconfig
 from cubicweb.req import RequestSessionBase
+from cubicweb.utils import parse_repo_uri
 
 
 _MARKER = object()
@@ -80,58 +82,76 @@
 
 
 class ConnectionProperties(object):
-    def __init__(self, cnxtype=None, lang=None, close=True, log=False):
-        self.cnxtype = cnxtype or 'pyro'
-        self.lang = lang
+    def __init__(self, cnxtype=None, close=True, log=False):
+        if cnxtype is not None:
+            warn('[3.16] cnxtype argument is deprecated', DeprecationWarning,
+                 stacklevel=2)
+        self.cnxtype = cnxtype
         self.log_queries = log
         self.close_on_del = close
 
 
-def get_repository(method, database=None, config=None, vreg=None):
-    """get a proxy object to the CubicWeb repository, using a specific RPC method.
+def _get_inmemory_repo(config, vreg=None):
+    from cubicweb.server.repository import Repository
+    from cubicweb.server.utils import TasksManager
+    return Repository(config, TasksManager(), vreg=vreg)
 
-    Only 'in-memory' and 'pyro' are supported for now. Either vreg or config
-    argument should be given
+def get_repository(uri=None, config=None, vreg=None):
+    """get a repository for the given URI or config/vregistry (in case we're
+    loading the repository for a client, eg web server, configuration).
+
+    The returned repository may be an in-memory repository or a proxy object
+    using a specific RPC method, depending on the given URI (pyro or zmq).
     """
-    assert method in ('pyro', 'inmemory', 'zmq')
-    assert vreg or config
-    if vreg and not config:
-        config = vreg.config
-    if method == 'inmemory':
-        # get local access to the repository
-        from cubicweb.server.repository import Repository
-        from cubicweb.server.utils import TasksManager
-        return Repository(config, TasksManager(), vreg=vreg)
-    elif method == 'zmq':
+    if uri is None:
+        return _get_inmemory_repo(config, vreg)
+
+    protocol, hostport, appid = parse_repo_uri(uri)
+
+    if protocol == 'inmemory':
+        # me may have been called with a dummy 'inmemory://' uri ...
+        return _get_inmemory_repo(config, vreg)
+
+    if protocol == 'pyroloc': # direct connection to the instance
+        from logilab.common.pyro_ext import get_proxy
+        uri = uri.replace('pyroloc', 'PYRO')
+        return get_proxy(uri)
+
+    if protocol == 'pyro': # connection mediated through the pyro ns
+        from logilab.common.pyro_ext import ns_get_proxy
+        path = appid.strip('/')
+        if not path:
+            raise ConnectionError(
+                "can't find instance name in %s (expected to be the path component)"
+                % uri)
+        if '.' in path:
+            nsgroup, nsid = path.rsplit('.', 1)
+        else:
+            nsgroup = 'cubicweb'
+            nsid = path
+        return ns_get_proxy(nsid, defaultnsgroup=nsgroup, nshost=hostport)
+
+    if protocol.startswith('zmqpickle-'):
         from cubicweb.zmqclient import ZMQRepositoryClient
-        return ZMQRepositoryClient(database)
-    else: # method == 'pyro'
-        # resolve the Pyro object
-        from logilab.common.pyro_ext import ns_get_proxy, get_proxy
-        pyroid = database or config['pyro-instance-id'] or config.appid
-        try:
-            if config['pyro-ns-host'] == 'NO_PYRONS':
-                return get_proxy(pyroid)
-            else:
-                return ns_get_proxy(pyroid, defaultnsgroup=config['pyro-ns-group'],
-                                    nshost=config['pyro-ns-host'])
-        except Exception, ex:
-            raise ConnectionError(str(ex))
+        return ZMQRepositoryClient(uri)
+    else:
+        raise ConnectionError('unknown protocol: `%s`' % protocol)
 
-def repo_connect(repo, login, **kwargs):
-    """Constructor to create a new connection to the CubicWeb repository.
+
+def _repo_connect(repo, login, **kwargs):
+    """Constructor to create a new connection to the given CubicWeb repository.
 
     Returns a Connection instance.
+
+    Raises AuthenticationError if authentication failed
     """
-    if not 'cnxprops' in kwargs:
-        kwargs['cnxprops'] = ConnectionProperties('inmemory')
     cnxid = repo.connect(unicode(login), **kwargs)
-    cnx = Connection(repo, cnxid, kwargs['cnxprops'])
-    if kwargs['cnxprops'].cnxtype == 'inmemory':
+    cnx = Connection(repo, cnxid, kwargs.get('cnxprops'))
+    if cnx.is_repo_in_memory:
         cnx.vreg = repo.vreg
     return cnx
 
-def connect(database=None, login=None, host=None, group=None,
+def connect(database, login=None,
             cnxprops=None, setvreg=True, mulcnx=True, initlog=True, **kwargs):
     """Constructor for creating a connection to the CubicWeb repository.
     Returns a :class:`Connection` object.
@@ -140,24 +160,29 @@
 
       cnx = connect('myinstance', login='me', password='toto')
 
-    Arguments:
+    `database` may be:
+
+    * a simple instance id for in-memory connection
+
+    * an uri like scheme://host:port/instanceid where scheme may be one of
+      'pyro', 'inmemory' or 'zmqpickle'
 
-    :database:
-      the instance's pyro identifier.
+      * if scheme is 'pyro', <host:port> determine the name server address. If
+        not specified (e.g. 'pyro:///instanceid'), it will be detected through a
+        broadcast query. The instance id is the name of the instance in the name
+        server and may be prefixed by a group (e.g.
+        'pyro:///:cubicweb.instanceid')
+
+      * if scheme is handled by ZMQ (eg 'tcp'), you should not specify an
+        instance id
+
+    Other arguments:
 
     :login:
       the user login to use to authenticate.
 
-    :host:
-      - pyro: nameserver host. Will be detected using broadcast query if unspecified
-      - zmq: repository host socket address
-
-    :group:
-      the instance's pyro nameserver group. You don't have to specify it unless
-      tweaked in instance's configuration.
-
     :cnxprops:
-      an optional :class:`ConnectionProperties` instance, allowing to specify
+      a :class:`ConnectionProperties` instance, allowing to specify
       the connection method (eg in memory or pyro). A Pyro connection will be
       established if you don't specify that argument.
 
@@ -179,20 +204,25 @@
       there goes authentication tokens. You usually have to specify a password
       for the given user, using a named 'password' argument.
     """
-    cnxprops = cnxprops or ConnectionProperties()
-    method = cnxprops.cnxtype
-    if method == 'pyro':
+    if urlparse(database).scheme is None:
+        warn('[3.16] give an qualified URI as database instead of using '
+             'host/cnxprops to specify the connection method',
+             DeprecationWarning, stacklevel=2)
+        if cnxprops.cnxtype == 'zmq':
+            database = kwargs.pop('host')
+        elif cnxprops.cnxtype == 'inmemory':
+            database = 'inmemory://' + database
+        else:
+            database = 'pyro://%s/%s.%s' % (kwargs.pop('host', ''),
+                                            kwargs.pop('group', 'cubicweb'),
+                                            database)
+    puri = urlparse(database)
+    method = puri.scheme.lower()
+    if method == 'inmemory':
+        config = cwconfig.instance_configuration(puri.path)
+    else:
         config = cwconfig.CubicWebNoAppConfiguration()
-        if host:
-            config.global_set_option('pyro-ns-host', host)
-        if group:
-            config.global_set_option('pyro-ns-group', group)
-    elif method == 'zmq':
-        config = cwconfig.CubicWebNoAppConfiguration()
-    else:
-        assert database
-        config = cwconfig.instance_configuration(database)
-    repo = get_repository(method, database, config=config)
+    repo = get_repository(database, config=config)
     if method == 'inmemory':
         vreg = repo.vreg
     elif setvreg:
@@ -207,7 +237,7 @@
         vreg.set_schema(schema)
     else:
         vreg = None
-    cnx = repo_connect(repo, login, cnxprops=cnxprops, **kwargs)
+    cnx = _repo_connect(repo, login, cnxprops=cnxprops, **kwargs)
     cnx.vreg = vreg
     return cnx
 
@@ -219,14 +249,7 @@
     else:
         vreg = None
     # get local access to the repository
-    return get_repository('inmemory', config=config, vreg=vreg)
-
-def in_memory_cnx(repo, login, **kwargs):
-    """Establish a In memory connection to a <repo> for the user with <login>
-
-    additionel credential might be required"""
-    cnxprops = ConnectionProperties('inmemory')
-    return repo_connect(repo, login, cnxprops=cnxprops, **kwargs)
+    return get_repository('inmemory://', config=config, vreg=vreg)
 
 def in_memory_repo_cnx(config, login, **kwargs):
     """useful method for testing and scripting to get a dbapi.Connection
@@ -234,9 +257,9 @@
     """
     # connection to the CubicWeb repository
     repo = in_memory_repo(config)
-    return repo, in_memory_cnx(repo, login, **kwargs)
+    return repo, _repo_connect(repo, login, **kwargs)
 
-
+# XXX web only method, move to webconfig?
 def anonymous_session(vreg):
     """return a new anonymous session
 
@@ -246,11 +269,9 @@
     if anoninfo is None: # no anonymous user
         raise AuthenticationError('anonymous access is not authorized')
     anon_login, anon_password = anoninfo
-    cnxprops = ConnectionProperties(vreg.config.repo_method)
     # use vreg's repository cache
     repo = vreg.config.repository(vreg)
-    anon_cnx = repo_connect(repo, anon_login,
-                            cnxprops=cnxprops, password=anon_password)
+    anon_cnx = _repo_connect(repo, anon_login, password=anon_password)
     anon_cnx.vreg = vreg
     return DBAPISession(anon_cnx, anon_login)
 
@@ -282,7 +303,10 @@
     def __repr__(self):
         return '<DBAPISession %r>' % self.sessionid
 
+
 class DBAPIRequest(RequestSessionBase):
+    #: Request language identifier eg: 'en'
+    lang = None
 
     def __init__(self, vreg, session=None):
         super(DBAPIRequest, self).__init__(vreg)
@@ -292,9 +316,6 @@
             self.translations = vreg.config.translations
         except AttributeError:
             self.translations = {}
-        #: Request language identifier eg: 'en'
-        self.lang = None
-        self.set_default_language(vreg)
         #: cache entities built during the request
         self._eid_cache = {}
         if session is not None:
@@ -304,6 +325,7 @@
             # established
             self.session = None
             self.cnx = self.user = _NeedAuthAccessMock()
+        self.set_default_language(vreg)
 
     def from_controller(self):
         return 'view'
@@ -320,7 +342,7 @@
             self.cnx = session.cnx
             self.execute = session.cnx.cursor(self).execute
             if user is None:
-                user = self.cnx.user(self, {'lang': self.lang})
+                user = self.cnx.user(self)
         if user is not None:
             self.user = user
             self.set_entity_cache(user)
@@ -333,19 +355,15 @@
 
     def set_default_language(self, vreg):
         try:
-            self.lang = vreg.property_value('ui.language')
+            lang = vreg.property_value('ui.language')
         except Exception: # property may not be registered
-            self.lang = 'en'
-        # use req.__ to translate a message without registering it to the catalog
+            lang = 'en'
         try:
-            gettext, pgettext = self.translations[self.lang]
-            self._ = self.__ = gettext
-            self.pgettext = pgettext
+            self.set_language(lang)
         except KeyError:
             # this occurs usually during test execution
             self._ = self.__ = unicode
             self.pgettext = lambda x, y: unicode(y)
-        self.debug('request default language: %s', self.lang)
 
     # server-side service call #################################################
 
@@ -540,15 +558,22 @@
         self._repo = repo
         self.sessionid = cnxid
         self._close_on_del = getattr(cnxprops, 'close_on_del', True)
-        self._cnxtype = getattr(cnxprops, 'cnxtype', 'pyro')
         self._web_request = False
         if cnxprops and cnxprops.log_queries:
             self.executed_queries = []
             self.cursor_class = LogCursor
-        if self._cnxtype == 'pyro':
-            # check client/server compat
-            if self._repo.get_versions()['cubicweb'] < (3, 8, 6):
-                self._txid = lambda cursor=None: {}
+
+    @property
+    def is_repo_in_memory(self):
+        """return True if this is a local, aka in-memory, connection to the
+        repository
+        """
+        try:
+            from cubicweb.server.repository import Repository
+        except ImportError:
+            # code not available, no way
+            return False
+        return isinstance(self._repo, Repository)
 
     def __repr__(self):
         if self.anonymous_connection:
@@ -606,9 +631,9 @@
         # then init cubes
         config.init_cubes(cubes)
         # then load appobjects into the registry
-        vpath = config.build_vregistry_path(reversed(config.cubes_path()),
-                                            evobjpath=esubpath,
-                                            tvobjpath=subpath)
+        vpath = config.build_appobjects_path(reversed(config.cubes_path()),
+                                             evobjpath=esubpath,
+                                             tvobjpath=subpath)
         self.vreg.register_objects(vpath)
 
     def use_web_compatible_requests(self, baseurl, sitetitle=None):
@@ -678,11 +703,6 @@
     # session data methods #####################################################
 
     @check_not_closed
-    def set_session_props(self, **props):
-        """raise `BadConnectionId` if the connection is no more valid"""
-        self._repo.set_session_props(self.sessionid, props)
-
-    @check_not_closed
     def get_shared_data(self, key, default=None, pop=False, txdata=False):
         """return value associated to key in the session's data dictionary or
         session's transaction's data if `txdata` is true.
@@ -860,3 +880,5 @@
         """
         return self._repo.undo_transaction(self.sessionid, txuuid,
                                            **self._txid())
+
+in_memory_cnx = deprecated('[3.16] use _repo_connect instead)')(_repo_connect)
--- a/debian/changelog	Tue Mar 19 16:56:46 2013 +0100
+++ b/debian/changelog	Wed Mar 20 17:40:25 2013 +0100
@@ -1,3 +1,15 @@
+cubicweb (3.16.1-1) squeeze; urgency=low
+
+  * New upstream release
+
+ -- Aurélien Campéas <aurelien.campeas@logilab.fr>  Tue, 19 Mar 2013 16:52:00 +0100
+
+cubicweb (3.16.0-1) squeeze; urgency=low
+
+  * New upstream release
+
+ -- Aurélien Campéas <aurelien.campeas@logilab.fr>  Wed, 23 Jan 2013 17:40:00 +0100
+
 cubicweb (3.15.10-1) squeeze; urgency=low
 
   * New upstream release
@@ -7,12 +19,17 @@
 cubicweb (3.15.9-1) squeeze; urgency=low
 
   * New upstream release
+
+ -- David Douard <david.douard@logilab.fr>  Fri, 25 Jan 2013 17:49:58 +0100
+
+cubicweb (3.15.8-2) UNRELEASED; urgency=low
+
   * Don't compress txt files.  They're used by the doc's search functionality,
     and the javascript gets confused if it receives gzip instead of text.
   * Work around broken combination of jquery 1.4 and sphinx 0.6 in squeeze by
     patching up doctools.js.
 
- -- David Douard <david.douard@logilab.fr>  Fri, 25 Jan 2013 17:49:58 +0100
+ -- Julien Cristau <jcristau@debian.org>  Wed, 09 Jan 2013 19:09:16 +0100
 
 cubicweb (3.15.8-1) squeeze; urgency=low
 
--- a/debian/control	Tue Mar 19 16:56:46 2013 +0100
+++ b/debian/control	Wed Mar 20 17:40:25 2013 +0100
@@ -9,7 +9,7 @@
            Nicolas Chauvat <nicolas.chauvat@logilab.fr>
 Build-Depends:
  debhelper (>= 7),
- python (>= 2.5),
+ python (>= 2.6),
  python-central (>= 0.5),
  python-sphinx,
  python-logilab-common,
@@ -20,7 +20,7 @@
  python-lxml,
 Standards-Version: 3.9.1
 Homepage: http://www.cubicweb.org
-XS-Python-Version: >= 2.5
+XS-Python-Version: >= 2.6
 
 Package: cubicweb
 Architecture: all
@@ -107,7 +107,7 @@
 Package: cubicweb-common
 Architecture: all
 XB-Python-Version: ${python:Versions}
-Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.58.0), python-yams (>= 0.34.0), python-rql (>= 0.31.2), python-lxml
+Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.59.0), python-yams (>= 0.36.0), python-rql (>= 0.31.2), python-lxml
 Recommends: python-simpletal (>= 4.0), python-crypto
 Conflicts: cubicweb-core
 Replaces: cubicweb-core
--- a/devtools/__init__.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/devtools/__init__.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Test tools for cubicweb"""
 
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 
 import os
@@ -217,7 +215,6 @@
 
 
 class BaseApptestConfiguration(TestServerConfiguration, TwistedConfiguration):
-    repo_method = 'inmemory'
     name = 'all-in-one' # so it search for all-in-one.conf, not repository.conf
     options = cwconfig.merge_options(TestServerConfiguration.options
                                      + TwistedConfiguration.options)
@@ -355,7 +352,7 @@
     def _restore_database(self, backup_coordinates, config):
         """Actual restore of the current database.
 
-        Use the value tostored in db_cache as input """
+        Use the value stored in db_cache as input """
         raise NotImplementedError()
 
     def get_repo(self, startup=False):
@@ -385,15 +382,14 @@
         repo.turn_repo_off = partial(turn_repo_off, repo)
         return repo
 
-
     def get_cnx(self):
         """return Connection object on the current repository"""
-        from cubicweb.dbapi import in_memory_cnx
+        from cubicweb.dbapi import _repo_connect
         repo = self.get_repo()
         sources = self.config.sources()
         login  = unicode(sources['admin']['login'])
         password = sources['admin']['password'] or 'xxx'
-        cnx = in_memory_cnx(repo, login, password=password)
+        cnx = _repo_connect(repo, login, password=password)
         return cnx
 
     def get_repo_and_cnx(self, db_id=DEFAULT_EMPTY_DB_ID):
@@ -466,7 +462,6 @@
         ``pre_setup_func`` to setup the database.
 
         This function backup any database it build"""
-
         if self.has_cache(test_db_id):
             return #test_db_id, 'already in cache'
         if test_db_id is DEFAULT_EMPTY_DB_ID:
@@ -723,7 +718,7 @@
         dbfile = self.absolute_dbfile()
         self._cleanup_database(dbfile)
         shutil.copy(backup_coordinates, dbfile)
-        repo = self.get_repo()
+        self.get_repo()
 
     def init_test_database(self):
         """initialize a fresh sqlite databse used for testing purpose"""
--- a/devtools/cwwindmill.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/devtools/cwwindmill.py	Wed Mar 20 17:40:25 2013 +0100
@@ -35,7 +35,7 @@
     import windmill
     from windmill.dep import functest
     from windmill.bin.admin_lib import configure_global_settings, setup, teardown
-except ImportError, ex:
+except ImportError:
     windmill = None
 
 from cubicweb.devtools.httptest import CubicWebServerTC, CubicWebServerConfig
--- a/devtools/devctl.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/devtools/devctl.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -91,15 +91,10 @@
         if mod.__file__ is None:
             # odd/rare but real
             continue
-        for path in config.vregistry_path():
+        for path in config.appobjects_path():
             if mod.__file__.startswith(path):
                 del sys.modules[name]
                 break
-    # fresh rtags
-    from cubicweb import rtags
-    from cubicweb.web import uicfg
-    rtags.RTAGS[:] = []
-    reload(uicfg)
 
 def generate_schema_pot(w, cubedir=None):
     """generate a pot file with schema specific i18n messages
@@ -129,7 +124,6 @@
 def _generate_schema_pot(w, vreg, schema, libconfig=None):
     from copy import deepcopy
     from cubicweb.i18n import add_msg
-    from cubicweb.web import uicfg
     from cubicweb.schema import NO_I18NCONTEXT, CONSTRAINTS
     w('# schema pot file, generated on %s\n'
       % datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
@@ -138,22 +132,21 @@
     w('\n')
     vregdone = set()
     if libconfig is not None:
-        from cubicweb.cwvreg import CWRegistryStore, clear_rtag_objects
+        from cubicweb.cwvreg import CWRegistryStore
         libschema = libconfig.load_schema(remove_unused_rtypes=False)
-        afs = deepcopy(uicfg.autoform_section)
-        appearsin_addmenu = deepcopy(uicfg.actionbox_appearsin_addmenu)
-        clear_rtag_objects()
+        afs = vreg['uicfg'].select('autoform_section')
+        appearsin_addmenu = vreg['uicfg'].select('actionbox_appearsin_addmenu')
         cleanup_sys_modules(libconfig)
         libvreg = CWRegistryStore(libconfig)
         libvreg.set_schema(libschema) # trigger objects registration
-        libafs = uicfg.autoform_section
-        libappearsin_addmenu = uicfg.actionbox_appearsin_addmenu
+        libafs = libvreg['uicfg'].select('autoform_section')
+        libappearsin_addmenu = libvreg['uicfg'].select('actionbox_appearsin_addmenu')
         # prefill vregdone set
         list(_iter_vreg_objids(libvreg, vregdone))
     else:
         libschema = {}
-        afs = uicfg.autoform_section
-        appearsin_addmenu = uicfg.actionbox_appearsin_addmenu
+        afs = vreg['uicfg'].select('autoform_section')
+        appearsin_addmenu = vreg['uicfg'].select('actionbox_appearsin_addmenu')
         for cstrtype in CONSTRAINTS:
             add_msg(w, cstrtype)
     done = set()
@@ -258,7 +251,7 @@
     for reg, objdict in vreg.items():
         if reg in ('boxes', 'contentnavigation'):
             continue
-        for objects in objdict.values():
+        for objects in objdict.itervalues():
             for obj in objects:
                 objid = '%s_%s' % (reg, obj.__regid__)
                 if objid in done:
@@ -302,10 +295,11 @@
         from logilab.common.fileutils import ensure_fs_mode
         from logilab.common.shellutils import globfind, find, rm
         from logilab.common.modutils import get_module_files
-        from cubicweb.i18n import extract_from_tal, execute
+        from cubicweb.i18n import extract_from_tal, execute2
         tempdir = tempfile.mkdtemp(prefix='cw-')
         cwi18ndir = WebConfiguration.i18n_lib_dir()
-        print '-> extract schema messages.'
+        print '-> extract messages:',
+        print 'schema',
         schemapot = osp.join(tempdir, 'schema.pot')
         potfiles = [schemapot]
         potfiles.append(schemapot)
@@ -314,7 +308,7 @@
         schemapotstream = file(schemapot, 'w')
         generate_schema_pot(schemapotstream.write, cubedir=None)
         schemapotstream.close()
-        print '-> extract TAL messages.'
+        print 'TAL',
         tali18nfile = osp.join(tempdir, 'tali18n.py')
         extract_from_tal(find(osp.join(BASEDIR, 'web'), ('.py', '.pt')),
                          tali18nfile)
@@ -329,26 +323,29 @@
                                 ('tal', [tali18nfile], None),
                                 ('js', jsfiles, 'java'),
                                 ]:
-            cmd = 'xgettext --no-location --omit-header -k_ -o %s %s'
+            potfile = osp.join(tempdir, '%s.pot' % id)
+            cmd = ['xgettext', '--no-location', '--omit-header', '-k_']
             if lang is not None:
-                cmd += ' -L %s' % lang
-            potfile = osp.join(tempdir, '%s.pot' % id)
-            execute(cmd % (potfile, ' '.join('"%s"' % f for f in files)))
+                cmd.extend(['-L', lang])
+            cmd.extend(['-o', potfile])
+            cmd.extend(files)
+            execute2(cmd)
             if osp.exists(potfile):
                 potfiles.append(potfile)
             else:
                 print '-> WARNING: %s file was not generated' % potfile
         print '-> merging %i .pot files' % len(potfiles)
         cubicwebpot = osp.join(tempdir, 'cubicweb.pot')
-        execute('msgcat -o %s %s'
-                % (cubicwebpot, ' '.join('"%s"' % f for f in potfiles)))
+        cmd = ['msgcat', '-o', cubicwebpot] + potfiles
+        execute2(cmd)
         print '-> merging main pot file with existing translations.'
         chdir(cwi18ndir)
         toedit = []
         for lang in CubicWebNoAppConfiguration.cw_languages():
             target = '%s.po' % lang
-            execute('msgmerge -N --sort-output -o "%snew" "%s" "%s"'
-                    % (target, target, cubicwebpot))
+            cmd = ['msgmerge', '-N', '--sort-output', '-o',
+                   target+'new', target, cubicwebpot]
+            execute2(cmd)
             ensure_fs_mode(target)
             shutil.move('%snew' % target, target)
             toedit.append(osp.abspath(target))
@@ -382,16 +379,21 @@
 
 
 def update_cubes_catalogs(cubes):
+    from subprocess import CalledProcessError
     for cubedir in cubes:
         if not osp.isdir(cubedir):
             print '-> ignoring %s that is not a directory.' % cubedir
             continue
         try:
             toedit = update_cube_catalogs(cubedir)
+        except CalledProcessError as exc:
+            print '\n*** error while updating catalogs for cube', cubedir
+            print 'cmd:\n%s' % exc.cmd
+            print 'stdout:\n%s\nstderr:\n%s' % exc.data
         except Exception:
             import traceback
             traceback.print_exc()
-            print '-> error while updating catalogs for cube', cubedir
+            print '*** error while updating catalogs for cube', cubedir
             return False
         else:
             # instructions pour la suite
@@ -408,7 +410,7 @@
     import tempfile
     from logilab.common.fileutils import ensure_fs_mode
     from logilab.common.shellutils import find, rm
-    from cubicweb.i18n import extract_from_tal, execute
+    from cubicweb.i18n import extract_from_tal, execute2
     cube = osp.basename(osp.normpath(cubedir))
     tempdir = tempfile.mkdtemp()
     print underline_title('Updating i18n catalogs for cube %s' % cube)
@@ -421,7 +423,8 @@
         potfiles = [osp.join('i18n', 'static-messages.pot')]
     else:
         potfiles = []
-    print '-> extract schema messages'
+    print '-> extracting messages:',
+    print 'schema',
     schemapot = osp.join(tempdir, 'schema.pot')
     potfiles.append(schemapot)
     # explicit close necessary else the file may not be yet flushed when
@@ -429,50 +432,55 @@
     schemapotstream = file(schemapot, 'w')
     generate_schema_pot(schemapotstream.write, cubedir)
     schemapotstream.close()
-    print '-> extract TAL messages'
+    print 'TAL',
     tali18nfile = osp.join(tempdir, 'tali18n.py')
     ptfiles = find('.', ('.py', '.pt'), blacklist=STD_BLACKLIST+('test',))
     extract_from_tal(ptfiles, tali18nfile)
-    print '-> extract Javascript messages'
+    print 'Javascript'
     jsfiles =  [jsfile for jsfile in find('.', '.js')
                 if osp.basename(jsfile).startswith('cub')]
     if jsfiles:
         tmppotfile = osp.join(tempdir, 'js.pot')
-        execute('xgettext --no-location --omit-header -k_ -L java '
-                '--from-code=utf-8 -o %s %s' % (tmppotfile, ' '.join(jsfiles)))
+        cmd = ['xgettext', '--no-location', '--omit-header', '-k_', '-L', 'java',
+               '--from-code=utf-8', '-o', tmppotfile] + jsfiles
+        execute2(cmd)
         # no pot file created if there are no string to translate
         if osp.exists(tmppotfile):
             potfiles.append(tmppotfile)
-    print '-> create cube-specific catalog'
+    print '-> creating cube-specific catalog'
     tmppotfile = osp.join(tempdir, 'generated.pot')
     cubefiles = find('.', '.py', blacklist=STD_BLACKLIST+('test',))
     cubefiles.append(tali18nfile)
-    execute('xgettext --no-location --omit-header -k_ -o %s %s'
-            % (tmppotfile, ' '.join('"%s"' % f for f in cubefiles)))
+    cmd = ['xgettext', '--no-location', '--omit-header', '-k_', '-o', tmppotfile]
+    cmd.extend(cubefiles)
+    execute2(cmd)
     if osp.exists(tmppotfile): # doesn't exists of no translation string found
         potfiles.append(tmppotfile)
     potfile = osp.join(tempdir, 'cube.pot')
-    print '-> merging %i .pot files:' % len(potfiles)
-    execute('msgcat -o %s %s' % (potfile,
-                                 ' '.join('"%s"' % f for f in potfiles)))
+    print '-> merging %i .pot files' % len(potfiles)
+    cmd = ['msgcat', '-o', potfile]
+    cmd.extend(potfiles)
+    execute2(cmd)
     if not osp.exists(potfile):
         print 'no message catalog for cube', cube, 'nothing to translate'
         # cleanup
         rm(tempdir)
         return ()
-    print '-> merging main pot file with existing translations:'
+    print '-> merging main pot file with existing translations:',
     chdir('i18n')
     toedit = []
     for lang in CubicWebNoAppConfiguration.cw_languages():
-        print '-> language', lang
+        print lang,
         cubepo = '%s.po' % lang
         if not osp.exists(cubepo):
             shutil.copy(potfile, cubepo)
         else:
-            execute('msgmerge -N -s -o %snew %s %s' % (cubepo, cubepo, potfile))
+            cmd = ['msgmerge','-N','-s','-o', cubepo+'new', cubepo, potfile]
+            execute2(cmd)
             ensure_fs_mode(cubepo)
             shutil.move('%snew' % cubepo, cubepo)
         toedit.append(osp.abspath(cubepo))
+    print
     # cleanup
     rm(tempdir)
     return toedit
@@ -598,7 +606,7 @@
             print "-> creating cubes directory", cubesdir
             try:
                 mkdir(cubesdir)
-            except OSError, err:
+            except OSError as err:
                 self.fail("failed to create directory %r\n(%s)"
                           % (cubesdir, err))
         cubedir = osp.join(cubesdir, cubename)
@@ -686,7 +694,7 @@
         for filepath in args:
             try:
                 stream = file(filepath)
-            except OSError, ex:
+            except OSError as ex:
                 raise BadCommandUsage("can't open rql log file %s: %s"
                                       % (filepath, ex))
             for lineno, line in enumerate(stream):
@@ -703,7 +711,7 @@
                     clocktime = float(chunks[0][1:])
                     cputime = float(chunks[-3])
                     req.append( (clocktime, cputime) )
-                except Exception, exc:
+                except Exception as exc:
                     sys.stderr.write('Line %s: %s (%s)\n' % (lineno, exc, line))
         stat = []
         for rql, times in requests.iteritems():
--- a/devtools/fake.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/devtools/fake.py	Wed Mar 20 17:40:25 2013 +0100
@@ -155,6 +155,12 @@
     def set_entity_cache(self, entity):
         pass
 
+    def security_enabled(self, read=False, write=False):
+        class FakeCM(object):
+            def __enter__(self): pass
+            def __exit__(self, exctype, exc, traceback): pass
+        return FakeCM()
+
     # for use with enabled_security context manager
     read_security = write_security = True
     def init_security(self, *args):
--- a/devtools/htmlparser.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/devtools/htmlparser.py	Wed Mar 20 17:40:25 2013 +0100
@@ -37,7 +37,7 @@
         try:
             data = self.preprocess_data(data)
             return PageInfo(data, etree.fromstring(data, self.parser))
-        except etree.XMLSyntaxError, exc:
+        except etree.XMLSyntaxError as exc:
             def save_in(fname=''):
                 file(fname, 'w').write(data)
             new_exc = AssertionError(u'invalid xml %s' % exc)
--- a/devtools/httptest.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/devtools/httptest.py	Wed Mar 20 17:40:25 2013 +0100
@@ -18,8 +18,6 @@
 """this module contains base classes and utilities for integration with running
 http server
 """
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 
 import threading
@@ -52,7 +50,7 @@
         try:
             s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
             sock = s.connect(("localhost", port))
-        except socket.error, err:
+        except socket.error as err:
             if err.args[0] in (111, 106):
                 return port
         finally:
@@ -158,7 +156,7 @@
             response = self.web_get('logout')
         self._ident_cookie = None
 
-    def web_get(self, path='', headers=None):
+    def web_request(self, path='', method='GET', body=None, headers=None):
         """Return an httplib.HTTPResponse object for the specified path
 
         Use available credential if available.
@@ -168,12 +166,15 @@
         if self._ident_cookie is not None:
             assert 'Cookie' not in headers
             headers['Cookie'] = self._ident_cookie
-        self._web_test_cnx.request("GET", '/' + path, headers=headers)
+        self._web_test_cnx.request(method, '/' + path, headers=headers, body=body)
         response = self._web_test_cnx.getresponse()
         response.body = response.read() # to chain request
         response.read = lambda : response.body
         return response
 
+    def web_get(self, path='', body=None, headers=None):
+        return self.web_request(path=path, body=body, headers=headers)
+
     def setUp(self):
         super(CubicWebServerTC, self).setUp()
         self.start_server()
@@ -181,7 +182,7 @@
     def tearDown(self):
         try:
             self.stop_server()
-        except error.ReactorNotRunning, err:
+        except error.ReactorNotRunning as err:
             # Server could be launched manually
             print err
         super(CubicWebServerTC, self).tearDown()
--- a/devtools/qunit.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/devtools/qunit.py	Wed Mar 20 17:40:25 2013 +0100
@@ -82,7 +82,7 @@
             check_call(self.firefox_cmd + ['-CreateProfile',
                         '%s %s' % (self._profile_name, self._tmp_dir)],
                                   stdout=stdout, stderr=stderr)
-        except CalledProcessError, cpe:
+        except CalledProcessError as cpe:
             stdout.seek(0)
             stderr.seek(0)
             raise VerboseCalledProcessError(cpe.returncode, cpe.cmd, stdout.read(), stderr.read())
@@ -189,11 +189,13 @@
                     yield InnerTest(test_name, self.fail, msg)
             except Empty:
                 error = True
-                yield InnerTest(test_file, raise_exception, RuntimeError, "%s did not report execution end. %i test processed so far." % (test_file, test_count))
-
+                msg = '%s inactivity timeout (%is). %i test results received'
+                yield InnerTest(test_file, raise_exception, RuntimeError,
+                                 msg % (test_file, timeout, test_count))
         browser.stop()
         if test_count <= 0 and not error:
-            yield InnerTest(test_name, raise_exception, RuntimeError, 'No test yielded by qunit for %s' % test_file)
+            yield InnerTest(test_name, raise_exception, RuntimeError,
+                            'No test yielded by qunit for %s' % test_file)
 
 class QUnitResultController(Controller):
 
--- a/devtools/repotest.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/devtools/repotest.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -155,7 +155,7 @@
         if cls.backend is not None:
             try:
                 cls.dbhelper = get_db_helper(cls.backend)
-            except ImportError, ex:
+            except ImportError as ex:
                 raise SkipTest(str(ex))
 
     def setUp(self):
--- a/devtools/stresstester.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/devtools/stresstester.py	Wed Mar 20 17:40:25 2013 +0100
@@ -132,7 +132,7 @@
         opts, args = getopt.getopt(args, 'hn:t:u:p:P:o:', ['help', 'user=', 'password=',
                                                            'nb-times=', 'nb-threads=',
                                                            'profile', 'report-output=',])
-    except Exception, ex:
+    except Exception as ex:
         print ex
         usage(1)
     repeat = 100
--- a/devtools/test/unittest_httptest.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/devtools/test/unittest_httptest.py	Wed Mar 20 17:40:25 2013 +0100
@@ -28,7 +28,7 @@
     def test_response(self):
         try:
             response = self.web_get()
-        except httplib.NotConnected, ex:
+        except httplib.NotConnected as ex:
             self.fail("Can't connection to test server: %s" % ex)
 
     def test_response_anon(self):
--- a/devtools/test/unittest_testlib.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/devtools/test/unittest_testlib.py	Wed Mar 20 17:40:25 2013 +0100
@@ -16,7 +16,6 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unittests for cw.devtools.testlib module"""
-from __future__ import with_statement
 
 from cStringIO import StringIO
 
--- a/devtools/testlib.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/devtools/testlib.py	Wed Mar 20 17:40:25 2013 +0100
@@ -16,9 +16,6 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """this module contains base classes and utilities for cubicweb tests"""
-
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 
 import os
@@ -48,7 +45,7 @@
 from cubicweb.utils import json
 from cubicweb.sobjects import notification
 from cubicweb.web import Redirect, application
-from cubicweb.server.session import Session, security_enabled
+from cubicweb.server.session import Session
 from cubicweb.server.hook import SendMailOp
 from cubicweb.devtools import SYSTEM_ENTITIES, SYSTEM_RELATIONS, VIEW_VALIDATORS
 from cubicweb.devtools import BASE_URL, fake, htmlparser, DEFAULT_EMPTY_DB_ID
@@ -88,8 +85,7 @@
 
 class JsonValidator(object):
     def parse_string(self, data):
-        json.loads(data)
-        return data
+        return json.loads(data)
 
 # email handling, to test emails sent by an application ########################
 
@@ -318,7 +314,7 @@
         try:
             self._init_repo()
             self.addCleanup(self._close_cnx)
-        except Exception, ex:
+        except Exception as ex:
             self.__class__._repo_init_failed = ex
             raise
         resume_tracing()
@@ -403,7 +399,7 @@
         autoclose = kwargs.pop('autoclose', True)
         if not kwargs:
             kwargs['password'] = str(login)
-        self.set_cnx(dbapi.repo_connect(self.repo, unicode(login), **kwargs))
+        self.set_cnx(dbapi._repo_connect(self.repo, unicode(login), **kwargs))
         self.websession = dbapi.DBAPISession(self.cnx)
         if login == self.vreg.config.anonymous_user()[0]:
             self.cnx.anonymous_connection = True
@@ -451,7 +447,7 @@
         finally:
             self.session.set_cnxset() # ensure cnxset still set after commit
 
-    # # server side db api #######################################################
+    # server side db api #######################################################
 
     def sexecute(self, rql, args=None, eid_key=None):
         if eid_key is not None:
@@ -668,11 +664,11 @@
     def app_publish(self, *args, **kwargs):
         return self.app_handle_request(*args, **kwargs)
 
-    def ctrl_publish(self, req, ctrl='edit'):
+    def ctrl_publish(self, req, ctrl='edit', rset=None):
         """call the publish method of the edit controller"""
         ctrl = self.vreg['controllers'].select(ctrl, req, appli=self.app)
         try:
-            result = ctrl.publish()
+            result = ctrl.publish(rset)
             req.cnx.commit()
         except web.Redirect:
             req.cnx.commit()
@@ -684,7 +680,7 @@
 
         req.form will be setup using the url's query string
         """
-        req = self.request()
+        req = self.request(url=url)
         if isinstance(url, unicode):
             url = url.encode(req.encoding) # req.setup_params() expects encoded strings
         querystring = urlparse.urlparse(url)[-2]
@@ -692,16 +688,35 @@
         req.setup_params(params)
         return req
 
-    def url_publish(self, url):
-        """takes `url`, uses application's app_resolver to find the
-        appropriate controller, and publishes the result.
+    def url_publish(self, url, data=None):
+        """takes `url`, uses application's app_resolver to find the appropriate
+        controller and result set, then publishes the result.
+
+        To simulate post of www-form-encoded data, give a `data` dictionary
+        containing desired key/value associations.
 
         This should pretty much correspond to what occurs in a real CW server
         except the apache-rewriter component is not called.
         """
         req = self.req_from_url(url)
+        if data is not None:
+            req.form.update(data)
         ctrlid, rset = self.app.url_resolver.process(req, req.relative_path(False))
-        return self.ctrl_publish(req, ctrlid)
+        return self.ctrl_publish(req, ctrlid, rset)
+
+    def http_publish(self, url, data=None):
+        """like `url_publish`, except this returns a http response, even in case of errors"""
+        req = self.req_from_url(url)
+        if data is not None:
+            req.form.update(data)
+        # remove the monkey patched error handler
+        fake_error_handler = self.app.error_handler
+        del self.app.error_handler
+        try:
+            result = self.app_handle_request(req, req.relative_path(False))
+        finally:
+            self.app.error_handler = fake_error_handler
+        return result, req
 
     @staticmethod
     def _parse_location(req, location):
@@ -723,7 +738,7 @@
         """
         try:
             callback(req)
-        except Redirect, ex:
+        except Redirect as ex:
             return self._parse_location(req, ex.location)
         else:
             self.fail('expected a Redirect exception')
@@ -1060,7 +1075,7 @@
         """this method populates the database with `how_many` entities
         of each possible type. It also inserts random relations between them
         """
-        with security_enabled(self.session, read=False, write=False):
+        with self.session.security_enabled(read=False, write=False):
             self._auto_populate(how_many)
 
     def _auto_populate(self, how_many):
@@ -1090,7 +1105,7 @@
         for rql, args in q:
             try:
                 cu.execute(rql, args)
-            except ValidationError, ex:
+            except ValidationError as ex:
                 # failed to satisfy some constraint
                 print 'error in automatic db population', ex
                 self.session.commit_state = None # reset uncommitable flag
@@ -1209,7 +1224,7 @@
 #     # XXX broken
 #     from cubicweb.devtools.apptest import TestEnvironment
 #     env = testclass._env = TestEnvironment('data', configcls=testclass.configcls)
-#     for reg in env.vreg.values():
+#     for reg in env.vreg.itervalues():
 #         reg._selected = {}
 #         try:
 #             orig_select_best = reg.__class__.__orig_select_best
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/3.16.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -0,0 +1,97 @@
+What's new in CubicWeb 3.16?
+============================
+
+New functionalities
+--------------------
+
+* Add a new dataimport store (`SQLGenObjectStore`). This store enables a fast
+  import of data (entity creation, link creation) in CubicWeb, by directly
+  flushing information in SQL.  This may only be used with PostgreSQL, as it
+  requires the 'COPY FROM' command.
+
+
+API changes
+-----------
+
+* Orm: `set_attributes` and `set_relations` are unified (and
+  deprecated) in favor of `cw_set` that works in all cases.
+
+* db-api/configuration: all the external repository connection information is
+  now in an URL (see `#2521848 <http://www.cubicweb.org/2521848>`_),
+  allowing to drop specific options of pyro nameserver host, group, etc and fix
+  broken `ZMQ <http://www.zeromq.org/>`_ source. Configuration related changes:
+
+  * Dropped 'pyro-ns-host', 'pyro-instance-id', 'pyro-ns-group' from the client side
+    configuration, in favor of 'repository-uri'. **NO MIGRATION IS DONE**,
+    supposing there is no web-only configuration in the wild.
+
+  * Stop discovering the connection method through `repo_method` class attribute
+    of the configuration, varying according to the configuration class. This is
+    a first step on the way to a simpler configuration handling.
+
+  DB-API related changes:
+
+  * Stop indicating the connection method using `ConnectionProperties`.
+
+  * Drop `_cnxtype` attribute from `Connection` and `cnxtype` from
+    `Session`. The former is replaced by a `is_repo_in_memory` property
+    and the later is totaly useless.
+
+  * Turn `repo_connect` into `_repo_connect` to mark it as a private function.
+
+  * Deprecate `in_memory_cnx` which becomes useless, use `_repo_connect` instead
+    if necessary.
+
+* the "tcp://" uri scheme used for `ZMQ <http://www.zeromq.org/>`_
+  communications (in a way reminiscent of Pyro) is now named
+  "zmqpickle-tcp://", so as to make room for future zmq-based lightweight
+  communications (without python objects pickling).
+
+* Request.base_url gets a `secure=True` optional parameter that yields
+  an https url if possible, allowing hook-generated content to send
+  secure urls (e.g. when sending mail notifications)
+
+* Dataimport ucsvreader gets a new boolean `ignore_errors`
+  parameter.
+
+
+Unintrusive API changes
+-----------------------
+
+* Drop of `cubicweb.web.uicfg.AutoformSectionRelationTags.bw_tag_map`,
+  deprecated since 3.6.
+
+
+User interface changes
+----------------------
+
+* The RQL search bar has now some auto-completion support. It means
+  relation types or entity types can be suggested while typing. It is
+  an awesome improvement over the current behaviour !
+
+* The `action box` associated with `table` views (from `tableview.py`)
+  has been transformed into a nice-looking series of small tabs; it
+  means that the possible actions are immediately visible and need not
+  be discovered by clicking on an almost invisible icon on the upper
+  right.
+
+* The `uicfg` module has moved to web/views/ and ui configuration
+  objects are now selectable. This will reduce the amount of
+  subclassing and whole methods replacement usually needed to
+  customize the ui behaviour in many cases.
+
+* Remove changelog view, as neither cubicweb nor known
+  cubes/applications were properly feeding related files.
+
+
+Other changes
+-------------
+
+* 'pyrorql' sources will be automatically updated to use an URL to locate the source
+  rather than configuration option. 'zmqrql' sources were broken before this change,
+  so no upgrade is needed...
+
+* Debugging filters for Hooks and Operations have been added.
+
+* Some cubicweb-ctl commands used to show the output of `msgcat` and
+  `msgfmt`; they don't anymore.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/additionnal_services/index.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -0,0 +1,14 @@
+Additional services
+===================
+
+In this chapter, we introduce services crossing the *web -
+repository - administration* organisation of the first parts of the
+CubicWeb book. Those services can be either proper services (like the
+undo functionality) or mere *topical cross-sections* across CubicWeb.
+
+.. toctree::
+   :maxdepth: 2
+
+   undo
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/additionnal_services/undo.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -0,0 +1,337 @@
+Undoing changes in CubicWeb
+---------------------------
+
+Many desktop applications offer the possibility for the user to
+undo its last changes : this *undo feature* has now been
+integrated into the CubicWeb framework. This document will
+introduce you to the *undo feature* both from the end-user and the
+application developer point of view.
+
+But because a semantic web application and a common desktop
+application are not the same thing at all, especially as far as
+undoing is concerned, we will first introduce *what* is the *undo
+feature* for now.
+
+What's *undoing* in a CubicWeb application
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+What is an *undo feature* is quite intuitive in the context of a
+desktop application. But it is a bit subtler in the context of a
+Semantic Web application. This section introduces some of the main
+differences between a classical desktop and a Semantic Web
+applications to keep in mind in order to state precisely *what we
+want*.
+
+The notion transactions
+```````````````````````
+
+A CubicWeb application acts upon an *Entity-Relationship* model,
+described by a schema. This allows to ensure some data integrity
+properties. It also implies that changes are made by all-or-none
+groups called *transactions*, such that the data integrity is
+preserved whether the transaction is completely applied *or* none
+of it is applied.
+
+A transaction can thus include more actions than just those
+directly required by the main purpose of the user.  For example,
+when a user *just* writes a new blog entry, the underlying
+*transaction* holds several *actions* as illustrated below :
+
+* By admin on 2012/02/17 15:18 - Created Blog entry : Torototo
+
+  #. Created Blog entry : Torototo
+  #. Added relation : Torototo owned by admin
+  #. Added relation : Torototo blog entry of Undo Blog
+  #. Added relation : Torototo in state draft (draft)
+  #. Added relation : Torototo created by admin
+
+Because of the very nature (all-or-none) of the transactions, the
+"undoable stuff" are the transactions and not the actions !
+
+Public and private actions within a transaction
+```````````````````````````````````````````````
+
+Actually, within the *transaction* "Created Blog entry :
+Torototo", two of those *actions* are said to be *public* and
+the others are said to be *private*. *Public* here means that the
+public actions (1 and 3) were directly requested by the end user ;
+whereas *private* means that the other actions (2, 4, 5) were
+triggered "under the hood" to fulfill various requirements for the
+user operation (ensuring integrity, security, ... ).
+
+And because quite a lot of actions can be triggered by a "simple"
+end-user request, most of which the end-user is not (and does not
+need or wish to be) aware, only the so-called public actions will
+appear [1]_ in the description of the an undoable transaction.
+
+* By admin on 2012/02/17 15:18 - Created Blog entry : Torototo
+
+  #. Created Blog entry : Torototo
+  #. Added relation : Torototo blog entry of Undo Blog
+
+But note that both public and private actions will be undone
+together when the transaction is undone.
+
+(In)dependent transactions : the simple case
+````````````````````````````````````````````
+
+A CubicWeb application can be used *simultaneously* by different users
+(whereas a single user works on an given office document at a
+given time), so that there is not always a single history
+time-line in the CubicWeb case. Moreover CubicWeb provides
+security through the mechanism of *permissions* granted to each
+user. This can lead to some transactions *not* being undoable in
+some contexts.
+
+In the simple case two (unprivileged) users Alice and Bob make
+relatively independent changes : then both Alice and Bob can undo
+their changes. But in some case there is a clean dependency
+between Alice's and Bob's actions or between actions of one of
+them. For example let's suppose that :
+
+- Alice has created a blog,
+- then has published a first post inside,
+- then Bob has published a second post in the same blog,
+- and finally Alice has updated its post contents.
+
+Then it is clear that Alice can undo her contents changes and Bob
+can undo his post creation independently. But Alice can not undo
+her post creation while she has not first undone her changes.
+It is also clear that Bob should *not* have the
+permissions to undo any of Alice's transactions.
+
+
+More complex dependencies between transactions
+``````````````````````````````````````````````
+
+But more surprising things can quickly happen. Going back to the
+previous example, Alice *can* undo the creation of the blog after
+Bob has published its post in it ! But this is possible only
+because the schema does not *require* for a post to be in a
+blog. Would the *blog entry of* relation have been mandatory, then
+Alice could not have undone the blog creation because it would
+have broken integrity constraint for Bob's post.
+
+When a user attempts to undo a transaction the system will check
+whether a later transaction has explicit dependency on the
+would-be-undone transaction. In this case the system will not even
+attempt the undo operation and inform the user.
+
+If no such dependency is detected the system will attempt the undo
+operation but it can fail, typically because of integrity
+constraint violations. In such a case the undo operation is
+completely [3]_ rollbacked.
+
+
+The *undo feature* for CubicWeb end-users
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The exposition of the undo feature to the end-user through a Web
+interface is still quite basic and will be improved toward a
+greater usability. But it is already fully functional.  For now
+there are two ways to access the *undo feature* as long as the it
+has been activated in the instance configuration file with the
+option *undo-support=yes*.
+
+Immediately after having done the change to be canceled through
+the **undo** link in the message. This allows to undo an
+hastily action immediately. For example, just after having
+validated the creation of the blog entry *A second blog entry* we
+get the following message, allowing to undo the creation.
+
+.. image:: /images/undo_mesage_w600.png
+   :width: 600px
+   :alt: Screenshot of the undo link in the message
+   :align: center
+
+At any time we can access the **undo-history view** accessible from the
+start-up page.
+
+.. image:: /images/undo_startup-link_w600.png
+   :width: 600px
+   :alt: Screenshot of the startup menu with access to the history view
+   :align: center
+
+This view will provide inspection of the transaction and their (public)
+actions. Each transaction provides its own **undo** link. Only the
+transactions the user has permissions to see and undo will be shown.
+
+.. image:: /images/undo_history-view_w600.png
+   :width: 600px
+   :alt: Screenshot of the undo history main view
+   :align: center
+
+If the user attempts to undo a transaction which can't be undone or
+whose undoing fails, then a message will explain the situation and
+no partial undoing will be left behind.
+
+This is all for the end-user side of the undo mechanism : this is
+quite simple indeed ! Now, in the following section, we are going
+to introduce the developer side of the undo mechanism.
+
+The *undo feature* for CubicWeb application developers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A word of warning : this section is intended for developers,
+already having some knowledge of what's under CubicWeb's hood. If
+it is not *yet* the case, please refer to CubicWeb documentation
+http://docs.cubicweb.org/ .
+
+Overview
+````````
+
+The core of the undo mechanisms is at work in the *native source*,
+beyond the RQL. This does mean that *transactions* and *actions*
+are *no entities*. Instead they are represented at the SQL level
+and exposed through the *DB-API* supported by the repository
+*Connection* objects.
+
+Once the *undo feature* has been activated in the instance
+configuration file with the option *undo-support=yes*, each
+mutating operation (cf. [2]_) will be recorded in some special SQL
+table along with its associated transaction. Transaction are
+identified by a *txuuid* through which the functions of the
+*DB-API* handle them.
+
+On the web side the last commited transaction *txuuid* is
+remembered in the request's data to allow for imediate undoing
+whereas the *undo-history view* relies upon the *DB-API* to list
+the accessible transactions. The actual undoing is performed by
+the *UndoController* accessible at URL of the form
+`www.my.host/my/instance/undo?txuuid=...`
+
+The repository side
+```````````````````
+
+Please refer to the file `cubicweb/server/sources/native.py` and
+`cubicweb/transaction.py` for the details.
+
+The undoing information is mainly stored in three SQL tables:
+
+`transactions`
+    Stores the txuuid, the user eid and the date-and-time of
+    the transaction. This table is referenced by the two others.
+
+`tx_entity_actions`
+    Stores the undo information for actions on entities.
+
+`tx_relation_actions`
+    Stores the undo information for the actions on relations.
+
+When the undo support is activated, entries are added to those
+tables for each mutating operation on the data repository, and are
+deleted on each transaction undoing.
+
+Those table are accessible through the following methods of the
+repository `Connection` object :
+
+`undoable_transactions`
+    Returns a list of `Transaction` objects accessible to the user
+    and according to the specified filter(s) if any.
+
+`tx_info`
+    Returns a `Transaction` object from a `txuuid`
+
+`undo_transaction`
+    Returns the list of `Action` object for the given `txuuid`.
+
+    NB:  By default it only return *public* actions.
+
+The web side
+````````````
+
+The exposure of the *undo feature* to the end-user through the Web
+interface relies on the *DB-API* introduced above. This implies
+that the *transactions* and *actions* are not *entities* linked by
+*relations* on which the usual views can be applied directly.
+
+That's why the file `cubicweb/web/views/undohistory.py` defines
+some dedicated views to access the undo information :
+
+`UndoHistoryView`
+    This is a *StartupView*, the one accessible from the home
+    page of the instance which list all transactions.
+
+`UndoableTransactionView`
+    This view handles the display of a single `Transaction` object.
+
+`UndoableActionBaseView`
+    This (abstract) base class provides private methods to build
+    the display of actions whatever their nature.
+
+`Undoable[Add|Remove|Create|Delete|Update]ActionView`
+    Those views all inherit from `UndoableActionBaseView` and
+    each handles a specific kind of action.
+
+`UndoableActionPredicate`
+    This predicate is used as a *selector* to pick the appropriate
+    view for actions.
+
+Apart from this main *undo-history view* a `txuuid` is stored in
+the request's data `last_undoable_transaction` in order to allow
+immediate undoing of a hastily validated operation. This is
+handled in `cubicweb/web/application.py` in the `main_publish` and
+`add_undo_link_to_msg` methods for the storing and displaying
+respectively.
+
+Once the undo information is accessible, typically through a
+`txuuid` in an *undo* URL, the actual undo operation can be
+performed by the `UndoController` defined in
+`cubicweb/web/views/basecontrollers.py`. This controller basically
+extracts the `txuuid` and performs a call to `undo_transaction` and
+in case of an undo-specific error, lets the top level publisher
+handle it as a validation error.
+
+
+Conclusion
+~~~~~~~~~~
+
+The undo mechanism relies upon a low level recording of the
+mutating operation on the repository. Those records are accessible
+through some method added to the *DB-API* and exposed to the
+end-user either through a whole history view of through an
+immediate undoing link in the message box.
+
+The undo feature is functional but the interface and configuration
+options are still quite reduced. One major improvement would be to
+be able to filter with a finer grain which transactions or actions
+one wants to see in the *undo-history view*. Another critical
+improvement would be to enable the undo feature on a part only of
+the entity-relationship schema to avoid storing too much useless
+data and reduce the underlying overhead.
+
+But both functionality are related to the strong design choice not
+to represent transactions and actions as entities and
+relations. This has huge benefits in terms of safety and conceptual
+simplicity but prevents from using lots of convenient CubicWeb
+features such as *facets* to access undo information.
+
+Before developing further the undo feature or eventually revising
+this design choice, it appears that some return of experience is
+strongly needed. So don't hesitate to try the undo feature in your
+application and send us some feedback.
+
+
+Notes
+~~~~~
+
+.. [1] The end-user Web interface could be improved to enable
+       user to choose whether he wishes to see private actions.
+
+.. [2] There is only five kind of elementary actions (beyond
+       merely accessing data for reading):
+
+       * **C** : creating an entity
+       * **D** : deleting an entity
+       * **U** : updating an entity attributes
+       * **A** : adding a relation
+       * **R** : removing a relation
+
+.. [3] Meaning none of the actions in the transaction is
+       undone. Depending upon the application, it might make sense
+       to enable *partial* undo. That is to say undo in which some
+       actions could not be undo without preventing to undo the
+       others actions in the transaction (as long as it does not
+       break schema integrity). This is not forbidden by the
+       back-end but is deliberately not supported by the front-end
+       (for now at least).
--- a/doc/book/en/annexes/faq.rst	Tue Mar 19 16:56:46 2013 +0100
+++ b/doc/book/en/annexes/faq.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -364,7 +364,7 @@
     >>> crypted = crypt_password('joepass')
     >>> rset = rql('Any U WHERE U is CWUser, U login "joe"')
     >>> joe = rset.get_entity(0,0)
-    >>> joe.set_attributes(upassword=Binary(crypted))
+    >>> joe.cw_set(upassword=Binary(crypted))
 
 Please, refer to the script example is provided in the `misc/examples/chpasswd.py` file.
 
--- a/doc/book/en/annexes/rql/debugging.rst	Tue Mar 19 16:56:46 2013 +0100
+++ b/doc/book/en/annexes/rql/debugging.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -15,6 +15,8 @@
 .. autodata:: cubicweb.server.DBG_SQL
 .. autodata:: cubicweb.server.DBG_REPO
 .. autodata:: cubicweb.server.DBG_MS
+.. autodata:: cubicweb.server.DBG_HOOKS
+.. autodata:: cubicweb.server.DBG_OPS
 .. autodata:: cubicweb.server.DBG_MORE
 .. autodata:: cubicweb.server.DBG_ALL
 
@@ -31,6 +33,14 @@
 
 .. autofunction:: cubicweb.server.set_debug
 
+Another example showing how to debug hooks at a specific code site:
+
+.. sourcecode:: python
+
+    from cubicweb.server import debuged, DBG_HOOKS
+    with debugged(DBG_HOOKS):
+        person.cw_set(works_for=company)
+
 
 Detect largest RQL queries
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/dataimport.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -0,0 +1,58 @@
+. -*- coding: utf-8 -*-
+
+.. _dataimport:
+
+Dataimport
+==========
+
+*CubicWeb* is designed to manipulate huge of amount of data, and provides helper functions to do so.
+These functions insert data within different levels of the *CubicWeb* API,
+allowing different speed/security tradeoffs. Those keeping all the *CubicWeb* hooks
+and security will be slower but the possible errors in insertion
+(bad data types, integrity error, ...) will be raised.
+
+These dataimport function are provided in the file `dataimport.py`.
+
+All the stores have the following API::
+
+    >>> store = ObjectStore()
+    >>> user = store.create_entity('CWUser', login=u'johndoe')
+    >>> group = store.create_entity('CWUser', name=u'unknown')
+    >>> store.relate(user.eid, 'in_group', group.eid)
+
+
+ObjectStore
+-----------
+
+This store keeps objects in memory for *faster* validation. It may be useful
+in development mode. However, as it will not enforce the constraints of the schema,
+it may miss some problems.
+
+
+
+RQLObjectStore
+--------------
+
+This store works with an actual RQL repository, and it may be used in production mode.
+
+
+NoHookRQLObjectStore
+--------------------
+
+This store works similarly to the *RQLObjectStore* but bypasses some *CubicWeb* hooks to be faster.
+
+
+SQLGenObjectStore
+-----------------
+
+This store relies on *COPY FROM*/execute many sql commands to directly push data using SQL commands
+rather than using the whole *CubicWeb* API. For now, **it only works with PostgresSQL** as it requires
+the *COPY FROM* command.
+
+The API is similar to the other stores, but **it requires a flush** after some imports to copy data
+in the database (these flushes may be multiples through the processes, or be done only once at the
+end if there is no memory issue)::
+
+    >>> store = SQLGenObjectStore(session)
+    >>> store.create_entity('Person', ...)
+    >>> store.flush()
--- a/doc/book/en/devrepo/entityclasses/application-logic.rst	Tue Mar 19 16:56:46 2013 +0100
+++ b/doc/book/en/devrepo/entityclasses/application-logic.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -38,7 +38,7 @@
 object was built.
 
 Setting an attribute or relation value can be done in the context of a
-Hook/Operation, using the obj.set_relations(x=42) notation or a plain
+Hook/Operation, using the obj.cw_set(x=42) notation or a plain
 RQL SET expression.
 
 In views, it would be preferable to encapsulate the necessary logic in
--- a/doc/book/en/devrepo/entityclasses/data-as-objects.rst	Tue Mar 19 16:56:46 2013 +0100
+++ b/doc/book/en/devrepo/entityclasses/data-as-objects.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -16,50 +16,47 @@
 
 `Formatting and output generation`:
 
-* `view(__vid, __registry='views', **kwargs)`, applies the given view to the entity
+* :meth:`view(__vid, __registry='views', **kwargs)`, applies the given view to the entity
   (and returns an unicode string)
 
-* `absolute_url(*args, **kwargs)`, returns an absolute URL including the base-url
+* :meth:`absolute_url(*args, **kwargs)`, returns an absolute URL including the base-url
 
-* `rest_path()`, returns a relative REST URL to get the entity
+* :meth:`rest_path()`, returns a relative REST URL to get the entity
 
-* `printable_value(attr, value=_marker, attrtype=None, format='text/html', displaytime=True)`,
+* :meth:`printable_value(attr, value=_marker, attrtype=None, format='text/html', displaytime=True)`,
   returns a string enabling the display of an attribute value in a given format
   (the value is automatically recovered if necessary)
 
 `Data handling`:
 
-* `as_rset()`, converts the entity into an equivalent result set simulating the
+* :meth:`as_rset()`, converts the entity into an equivalent result set simulating the
   request `Any X WHERE X eid _eid_`
 
-* `complete(skip_bytes=True)`, executes a request that recovers at
+* :meth:`complete(skip_bytes=True)`, executes a request that recovers at
   once all the missing attributes of an entity
 
-* `get_value(name)`, returns the value associated to the attribute name given
+* :meth:`get_value(name)`, returns the value associated to the attribute name given
   in parameter
 
-* `related(rtype, role='subject', limit=None, entities=False)`,
+* :meth:`related(rtype, role='subject', limit=None, entities=False)`,
   returns a list of entities related to the current entity by the
   relation given in parameter
 
-* `unrelated(rtype, targettype, role='subject', limit=None)`,
+* :meth:`unrelated(rtype, targettype, role='subject', limit=None)`,
   returns a result set corresponding to the entities not (yet)
   related to the current entity by the relation given in parameter
   and satisfying its constraints
 
-* `set_attributes(**kwargs)`, updates the attributes list with the corresponding
-  values given named parameters
+* :meth:`cw_set(**kwargs)`, updates entity's attributes and/or relation with the
+  corresponding values given named parameters. To set a relation where this
+  entity is the object of the relation, use `reverse_<relation>` as argument
+  name.  Values may be an entity, a list of entities, or None (meaning that all
+  relations of the given type from or to this object should be deleted).
 
-* `set_relations(**kwargs)`, add relations to the given object. To
-  set a relation where this entity is the object of the relation,
-  use `reverse_<relation>` as argument name.  Values may be an
-  entity, a list of entities, or None (meaning that all relations of
-  the given type from or to this object should be deleted).
-
-* `copy_relations(ceid)`, copies the relations of the entities having the eid
+* :meth:`copy_relations(ceid)`, copies the relations of the entities having the eid
   given in the parameters on the current entity
 
-* `delete()` allows to delete the entity
+* :meth:`cw_delete()` allows to delete the entity
 
 
 The :class:`AnyEntity` class
@@ -81,40 +78,30 @@
 
 `Standard meta-data (Dublin Core)`:
 
-* `dc_title()`, returns a unicode string corresponding to the
+* :meth:`dc_title()`, returns a unicode string corresponding to the
   meta-data `Title` (used by default is the first non-meta attribute
   of the entity schema)
 
-* `dc_long_title()`, same as dc_title but can return a more
+* :meth:`dc_long_title()`, same as dc_title but can return a more
   detailed title
 
-* `dc_description(format='text/plain')`, returns a unicode string
+* :meth:`dc_description(format='text/plain')`, returns a unicode string
   corresponding to the meta-data `Description` (looks for a
   description attribute by default)
 
-* `dc_authors()`, returns a unicode string corresponding to the meta-data
+* :meth:`dc_authors()`, returns a unicode string corresponding to the meta-data
   `Authors` (owners by default)
 
-* `dc_creator()`, returns a unicode string corresponding to the
+* :meth:`dc_creator()`, returns a unicode string corresponding to the
   creator of the entity
 
-* `dc_date(date_format=None)`, returns a unicode string corresponding to
+* :meth:`dc_date(date_format=None)`, returns a unicode string corresponding to
   the meta-data `Date` (update date by default)
 
-* `dc_type(form='')`, returns a string to display the entity type by
+* :meth:`dc_type(form='')`, returns a string to display the entity type by
   specifying the preferred form (`plural` for a plural form)
 
-* `dc_language()`, returns the language used by the entity
-
-
-`Misc methods`:
-
-* `after_deletion_path`, return (path, parameters) which should be
-  used as redirect information when this entity is being deleted
-
-* `pre_web_edit`, callback called by the web editcontroller when an
-  entity will be created/modified, to let a chance to do some entity
-  specific stuff (does nothing by default)
+* :meth:`dc_language()`, returns the language used by the entity
 
 Inheritance
 -----------
--- a/doc/book/en/devrepo/migration.rst	Tue Mar 19 16:56:46 2013 +0100
+++ b/doc/book/en/devrepo/migration.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -139,7 +139,7 @@
 * `drop_relation_type(rtype, commit=True)`, removes a relation type and all the
   definitions of this type.
 
-* `rename_relationi_type(oldname, newname, commit=True)`, renames a relation type.
+* `rename_relation_type(oldname, newname, commit=True)`, renames a relation type.
 
 * `add_relation_definition(subjtype, rtype, objtype, commit=True)`, adds a new
   relation definition.
--- a/doc/book/en/devrepo/repo/hooks.rst	Tue Mar 19 16:56:46 2013 +0100
+++ b/doc/book/en/devrepo/repo/hooks.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -195,9 +195,9 @@
       self._cw.repo.app_instances_bus.publish(['hello', 'world'])
 
 The `zmq-address-pub` configuration variable contains the address used
-by the instance for sending messages, e.g. `tcp://*:1234`.  The
+by the instance for sending messages, e.g. `zmqpickle-tcp://*:1234`.  The
 `zmq-address-sub` variable contains a comma-separated list of addresses
-to listen on, e.g. `tcp://localhost:1234, tcp://192.168.1.1:2345`.
+to listen on, e.g. `zmqpickle-tcp://localhost:1234, zmqpickle-tcp://192.168.1.1:2345`.
 
 
 Hooks writing tips
@@ -206,10 +206,11 @@
 Reminder
 ~~~~~~~~
 
-You should never use the `entity.foo = 42` notation to update an
-entity. It will not do what you expect (updating the
-database). Instead, use the :meth:`set_attributes` and
-:meth:`set_relations` methods.
+You should never use the `entity.foo = 42` notation to update an entity. It will
+not do what you expect (updating the database). Instead, use the
+:meth:`~cubicweb.entity.Entity.cw_set` method or direct access to entity's
+:attr:`cw_edited` attribute if you're writing a hook for 'before_add_entity' or
+'before_update_entity' event.
 
 
 How to choose between a before and an after event ?
--- a/doc/book/en/devrepo/testing.rst	Tue Mar 19 16:56:46 2013 +0100
+++ b/doc/book/en/devrepo/testing.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -70,13 +70,13 @@
 
         def test_cannot_create_cycles(self):
             # direct obvious cycle
-            self.assertRaises(ValidationError, self.kw1.set_relations,
+            self.assertRaises(ValidationError, self.kw1.cw_set,
                               subkeyword_of=self.kw1)
             # testing indirect cycles
             kw3 = self.execute('INSERT Keyword SK: SK name "kwgroup2", SK included_in C, '
                                'SK subkeyword_of K WHERE C name "classif1", K eid %s'
                                % self.kw1.eid).get_entity(0,0)
-            self.kw1.set_relations(subkeyword_of=kw3)
+            self.kw1.cw_set(subkeyword_of=kw3)
             self.assertRaises(ValidationError, self.commit)
 
 The test class defines a :meth:`setup_database` method which populates the
@@ -192,10 +192,10 @@
                                 description=u'cubicweb is beautiful')
             blog_entry_1 = req.create_entity('BlogEntry', title=u'hop',
                                              content=u'cubicweb hop')
-            blog_entry_1.set_relations(entry_of=cubicweb_blog)
+            blog_entry_1.cw_set(entry_of=cubicweb_blog)
             blog_entry_2 = req.create_entity('BlogEntry', title=u'yes',
                                              content=u'cubicweb yes')
-            blog_entry_2.set_relations(entry_of=cubicweb_blog)
+            blog_entry_2.cw_set(entry_of=cubicweb_blog)
             self.assertEqual(len(MAILBOX), 0)
             self.commit()
             self.assertEqual(len(MAILBOX), 2)
--- a/doc/book/en/devweb/index.rst	Tue Mar 19 16:56:46 2013 +0100
+++ b/doc/book/en/devweb/index.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -10,6 +10,7 @@
    publisher
    controllers
    request
+   searchbar
    views/index
    rtags
    ajax
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/searchbar.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -0,0 +1,41 @@
+.. _searchbar:
+
+RQL search bar
+--------------
+
+The RQL search bar is a visual component, hidden by default, the tiny *search*
+input being enough for common use cases.
+
+An autocompletion helper is provided to help you type valid queries, both
+in terms of syntax and in terms of schema validity.
+
+.. autoclass:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder
+
+
+How search is performed
++++++++++++++++++++++++
+
+You can use the *rql search bar* to either type RQL queries, plain text queries
+or standard shortcuts such as *<EntityType>* or *<EntityType> <attrname> <value>*.
+
+Ultimately, all queries are translated to rql since it's the only
+language understood on the server (data) side. To transform the user
+query into RQL, CubicWeb uses the so-called *magicsearch component*,
+defined in :mod:`cubicweb.web.views.magicsearch`, which in turn
+delegates to a number of query preprocessor that are responsible of
+interpreting the user query and generating corresponding RQL.
+
+The code of the main processor loop is easy to understand:
+
+.. sourcecode:: python
+
+  for proc in self.processors:
+      try:
+          return proc.process_query(uquery, req)
+      except (RQLSyntaxError, BadRQLQuery):
+          pass
+
+The idea is simple: for each query processor, try to translate the
+query. If it fails, try with the next processor, if it succeeds,
+we're done and the RQL query will be executed.
+
--- a/doc/book/en/devweb/views/boxes.rst	Tue Mar 19 16:56:46 2013 +0100
+++ b/doc/book/en/devweb/views/boxes.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -15,7 +15,7 @@
 which the box is displayed). By default, the links generated in this
 box are computed from the schema properties of the displayed entity,
 but it is possible to explicitly specify them thanks to the
-`cubicweb.web.uicfg.rmode` *relation tag*:
+`cubicweb.web.views.uicfg.rmode` *relation tag*:
 
 * `link`, indicates that a relation is in general created pointing
   to an existing entity and that we should not to display a link
--- a/doc/book/en/devweb/views/primary.rst	Tue Mar 19 16:56:46 2013 +0100
+++ b/doc/book/en/devweb/views/primary.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -51,7 +51,7 @@
 
 .. sourcecode:: python
 
-   from cubicweb.web import uicfg
+   from cubicweb.web.views import uicfg
    uicfg.primaryview_section.tag_attribute(('Blog', 'title'), 'hidden')
 
 **Relations** can be either displayed in one of the three sections or hidden.
--- a/doc/book/en/devweb/views/reledit.rst	Tue Mar 19 16:56:46 2013 +0100
+++ b/doc/book/en/devweb/views/reledit.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -68,7 +68,7 @@
 
 The behaviour of reledited attributes/relations can be finely
 controlled using the reledit_ctrl rtag, defined in
-:mod:`cubicweb.web.uicfg`.
+:mod:`cubicweb.web.views.uicfg`.
 
 This rtag provides four control variables:
 
@@ -93,7 +93,7 @@
 .. sourcecode:: python
 
     from logilab.mtconverter import xml_escape
-    from cubicweb.web.uicfg import reledit_ctrl
+    from cubicweb.web.views.uicfg import reledit_ctrl
     reledit_ctrl.tag_attribute(('Company', 'name'),
                                {'reload': lambda x:x.eid,
                                 'default_value': xml_escape(u'<logilab tastes better>')})
@@ -125,7 +125,7 @@
 
 .. sourcecode:: python
 
-    import uicfg.primaryview_display_ctrl as _pvdc
+    from cubicweb.web.views.uicfg import primaryview_display_ctrl as _pvdc
     _pvdc.tag_attribute(('Company', 'name'), {'vid': 'incontext'})
 
 To deactivate it everywhere it's used automatically, you may use the code snippet
Binary file doc/book/en/images/undo_history-view_w600.png has changed
Binary file doc/book/en/images/undo_mesage_w600.png has changed
Binary file doc/book/en/images/undo_startup-link_w600.png has changed
--- a/doc/book/en/index.rst	Tue Mar 19 16:56:46 2013 +0100
+++ b/doc/book/en/index.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -65,6 +65,7 @@
    :maxdepth: 2
 
    admin/index
+   additionnal_services/index
    annexes/index
 
 See also:
--- a/doc/book/en/tutorials/advanced/part02_security.rst	Tue Mar 19 16:56:46 2013 +0100
+++ b/doc/book/en/tutorials/advanced/part02_security.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -196,7 +196,7 @@
 	    for eid in self.get_data():
 		entity = self.session.entity_from_eid(eid)
 		if entity.visibility == 'parent':
-		    entity.set_attributes(visibility=u'authenticated')
+		    entity.cw_set(visibility=u'authenticated')
 
     class SetVisibilityHook(hook.Hook):
 	__regid__ = 'sytweb.setvisibility'
@@ -215,7 +215,7 @@
 	    parent = self._cw.entity_from_eid(self.eidto)
 	    child = self._cw.entity_from_eid(self.eidfrom)
 	    if child.visibility == 'parent':
-		child.set_attributes(visibility=parent.visibility)
+		child.cw_set(visibility=parent.visibility)
 
 Notice:
 
@@ -344,7 +344,7 @@
 	    self.assertEquals(len(req.execute('Folder X')), 0) # restricted...
 	    # may_be_read_by propagation
 	    self.restore_connection()
-	    folder.set_relations(may_be_read_by=toto)
+	    folder.cw_set(may_be_read_by=toto)
 	    self.commit()
 	    photo1.clear_all_caches()
 	    self.failUnless(photo1.may_be_read_by)
--- a/doc/book/en/tutorials/advanced/part04_ui-base.rst	Tue Mar 19 16:56:46 2013 +0100
+++ b/doc/book/en/tutorials/advanced/part04_ui-base.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -294,6 +294,7 @@
 folder in which the current file (e.g. `self.entity`) is located.
 
 .. Note::
+
    The :class:`IBreadCrumbs` interface is a `breadcrumbs` method, but the default
    :class:`IBreadCrumbsAdapter` provides a default implementation for it that will look
    at the value returned by its `parent_entity` method. It also provides a
@@ -331,6 +332,7 @@
 navigate through the web site to see if everything is ok...
 
 .. Note::
+
    In the 'cubicweb-ctl i18ncube' command, `sytweb` refers to the **cube**, while
    in the two other, it refers to the **instance** (if you can't see the
    difference, reread CubicWeb's concept chapter !).
@@ -363,4 +365,4 @@
 .. _`several improvments`: http://www.cubicweb.org/blogentry/1179899
 .. _`3.8`: http://www.cubicweb.org/blogentry/917107
 .. _`first blog of this series`: http://www.cubicweb.org/blogentry/824642
-.. _`an earlier post`: http://www.cubicweb.org/867464
\ No newline at end of file
+.. _`an earlier post`: http://www.cubicweb.org/867464
--- a/doc/book/en/tutorials/advanced/part05_ui-advanced.rst	Tue Mar 19 16:56:46 2013 +0100
+++ b/doc/book/en/tutorials/advanced/part05_ui-advanced.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -215,7 +215,7 @@
 
     from logilab.common.decorators import monkeypatch
     from cubicweb import ValidationError
-    from cubicweb.web import uicfg, component
+    from cubicweb.web.views import uicfg, component
     from cubicweb.web.views import basecontrollers
 
     # hide displayed_on relation using uicfg since it will be displayed by the box below
--- a/doc/book/en/tutorials/base/customizing-the-application.rst	Tue Mar 19 16:56:46 2013 +0100
+++ b/doc/book/en/tutorials/base/customizing-the-application.rst	Wed Mar 20 17:40:25 2013 +0100
@@ -493,7 +493,7 @@
   entering the migration python shell
   just type migration commands or arbitrary python code and type ENTER to execute it
   type "exit" or Ctrl-D to quit the shell and resume operation
-  >>> add_cubes('comment', 'tag')
+  >>> add_cubes(('comment', 'tag'))
   >>>
 
 Then restart the instance. Let's look at a blog entry:
--- a/entities/authobjs.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/entities/authobjs.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -77,6 +77,19 @@
             self._properties = dict((p.pkey, p.value) for p in self.reverse_for_user)
             return self._properties
 
+    def prefered_language(self, language=None):
+        """return language used by this user, if explicitly defined (eg not
+        using http negociation)
+        """
+        language = language or self.property_value('ui.language')
+        vreg = self._cw.vreg
+        try:
+            vreg.config.translations[language]
+        except KeyError:
+            language = vreg.property_value('ui.language')
+            assert language in vreg.config.translations[language], language
+        return language
+
     def property_value(self, key):
         try:
             # properties stored on the user aren't correctly typed
@@ -101,7 +114,7 @@
                 kwargs['for_user'] = self
             self._cw.create_entity('CWProperty', **kwargs)
         else:
-            prop.set_attributes(value=value)
+            prop.cw_set(value=value)
 
     def matching_groups(self, groups):
         """return the number of the given group(s) in which the user is
--- a/entities/sources.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/entities/sources.py	Wed Mar 20 17:40:25 2013 +0100
@@ -51,7 +51,7 @@
                     continue
                 raise
         cfgstr = unicode(generate_source_config(sconfig), self._cw.encoding)
-        self.set_attributes(config=cfgstr)
+        self.cw_set(config=cfgstr)
 
 
 class CWSource(_CWSourceCfgMixIn, AnyEntity):
@@ -181,5 +181,5 @@
     def write_log(self, session, **kwargs):
         if 'status' not in kwargs:
             kwargs['status'] = getattr(self, '_status', u'success')
-        self.set_attributes(log=u'<br/>'.join(self._logs), **kwargs)
+        self.cw_set(log=u'<br/>'.join(self._logs), **kwargs)
         self._logs = []
--- a/entities/test/unittest_base.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/entities/test/unittest_base.py	Wed Mar 20 17:40:25 2013 +0100
@@ -17,9 +17,7 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for cubicweb.entities.base module
-
 """
-from __future__ import with_statement
 
 from logilab.common.testlib import unittest_main
 from logilab.common.decorators import clear_cache
@@ -70,7 +68,7 @@
         email1 = self.execute('INSERT EmailAddress X: X address "maarten.ter.huurne@philips.com"').get_entity(0, 0)
         email2 = self.execute('INSERT EmailAddress X: X address "maarten@philips.com"').get_entity(0, 0)
         email3 = self.execute('INSERT EmailAddress X: X address "toto@logilab.fr"').get_entity(0, 0)
-        email1.set_relations(prefered_form=email2)
+        email1.cw_set(prefered_form=email2)
         self.assertEqual(email1.prefered.eid, email2.eid)
         self.assertEqual(email2.prefered.eid, email2.eid)
         self.assertEqual(email3.prefered.eid, email3.eid)
@@ -104,10 +102,10 @@
         e = self.execute('CWUser U WHERE U login "member"').get_entity(0, 0)
         self.assertEqual(e.dc_title(), 'member')
         self.assertEqual(e.name(), 'member')
-        e.set_attributes(firstname=u'bouah')
+        e.cw_set(firstname=u'bouah')
         self.assertEqual(e.dc_title(), 'member')
         self.assertEqual(e.name(), u'bouah')
-        e.set_attributes(surname=u'lôt')
+        e.cw_set(surname=u'lôt')
         self.assertEqual(e.dc_title(), 'member')
         self.assertEqual(e.name(), u'bouah lôt')
 
--- a/entities/test/unittest_wfobjs.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/entities/test/unittest_wfobjs.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -16,11 +16,8 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 
-from __future__ import with_statement
-
 from cubicweb import ValidationError
 from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.server.session import security_enabled
 
 
 def add_wf(self, etype, name=None, default=False):
@@ -64,7 +61,7 @@
         # gnark gnark
         bar = wf.add_state(u'bar')
         self.commit()
-        bar.set_attributes(name=u'foo')
+        bar.cw_set(name=u'foo')
         with self.assertRaises(ValidationError) as cm:
             self.commit()
         self.assertEqual({'name-subject': 'workflow already has a state of that name'},
@@ -88,7 +85,7 @@
         # gnark gnark
         biz = wf.add_transition(u'biz', (bar,), foo)
         self.commit()
-        biz.set_attributes(name=u'baz')
+        biz.cw_set(name=u'baz')
         with self.assertRaises(ValidationError) as cm:
             self.commit()
         self.assertEqual(cm.exception.errors, {'name-subject': 'workflow already have a transition of that name'})
@@ -99,7 +96,7 @@
     def setup_database(self):
         req = self.request()
         rschema = self.schema['in_state']
-        for rdef in rschema.rdefs.values():
+        for rdef in rschema.rdefs.itervalues():
             self.assertEqual(rdef.cardinality, '1*')
         self.member = self.create_user(req, 'member')
 
@@ -128,8 +125,9 @@
         self.assertEqual(trs[0].destination(None).name, u'deactivated')
         # test a std user get no possible transition
         cnx = self.login('member')
+        req = self.request()
         # fetch the entity using the new session
-        trs = list(cnx.user().cw_adapt_to('IWorkflowable').possible_transitions())
+        trs = list(req.user.cw_adapt_to('IWorkflowable').possible_transitions())
         self.assertEqual(len(trs), 0)
         cnx.close()
 
@@ -156,7 +154,7 @@
         wf = add_wf(self, 'CWUser')
         s = wf.add_state(u'foo', initial=True)
         self.commit()
-        with security_enabled(self.session, write=False):
+        with self.session.security_enabled(write=False):
             with self.assertRaises(ValidationError) as cm:
                 self.session.execute('SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
                                      {'x': self.user().eid, 's': s.eid})
@@ -175,7 +173,7 @@
 
     def test_goback_transition(self):
         req = self.request()
-        wf = self.session.user.cw_adapt_to('IWorkflowable').current_workflow
+        wf = req.user.cw_adapt_to('IWorkflowable').current_workflow
         asleep = wf.add_state('asleep')
         wf.add_transition('rest', (wf.state_by_name('activated'),
                                    wf.state_by_name('deactivated')),
@@ -518,7 +516,7 @@
                           ['rest'])
         self.assertEqual(parse_hist(iworkflowable.workflow_history),
                           [('asleep', 'asleep', 'rest', None)])
-        user.set_attributes(surname=u'toto') # fulfill condition
+        user.cw_set(surname=u'toto') # fulfill condition
         self.commit()
         iworkflowable.fire_transition('rest')
         self.commit()
@@ -558,13 +556,12 @@
 
     def setUp(self):
         CubicWebTC.setUp(self)
-        self.wf = self.session.user.cw_adapt_to('IWorkflowable').current_workflow
-        self.session.set_cnxset()
+        req = self.request()
+        self.wf = req.user.cw_adapt_to('IWorkflowable').current_workflow
         self.s_activated = self.wf.state_by_name('activated').eid
         self.s_deactivated = self.wf.state_by_name('deactivated').eid
         self.s_dummy = self.wf.add_state(u'dummy').eid
         self.wf.add_transition(u'dummy', (self.s_deactivated,), self.s_dummy)
-        req = self.request()
         ueid = self.create_user(req, 'stduser', commit=False).eid
         # test initial state is set
         rset = self.execute('Any N WHERE S name N, X in_state S, X eid %(x)s',
@@ -625,23 +622,22 @@
         cnx.close()
 
     def test_transition_checking3(self):
-        cnx = self.login('stduser')
-        session = self.session
-        user = cnx.user(session)
-        iworkflowable = user.cw_adapt_to('IWorkflowable')
-        iworkflowable.fire_transition('deactivate')
-        cnx.commit()
-        session.set_cnxset()
-        with self.assertRaises(ValidationError) as cm:
+        with self.login('stduser') as cnx:
+            session = self.session
+            user = self.user()
+            iworkflowable = user.cw_adapt_to('IWorkflowable')
             iworkflowable.fire_transition('deactivate')
-        self.assertEqual(self._cleanup_msg(cm.exception.errors['by_transition-subject']),
-                                            u"transition isn't allowed from")
-        cnx.rollback()
-        session.set_cnxset()
-        # get back now
-        iworkflowable.fire_transition('activate')
-        cnx.commit()
-        cnx.close()
+            session.commit()
+            session.set_cnxset()
+            with self.assertRaises(ValidationError) as cm:
+                iworkflowable.fire_transition('deactivate')
+            self.assertEqual(self._cleanup_msg(cm.exception.errors['by_transition-subject']),
+                                                u"transition isn't allowed from")
+            session.rollback()
+            session.set_cnxset()
+            # get back now
+            iworkflowable.fire_transition('activate')
+            session.commit()
 
 
 if __name__ == '__main__':
--- a/entity.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/entity.py	Wed Mar 20 17:40:25 2013 +0100
@@ -20,6 +20,7 @@
 __docformat__ = "restructuredtext en"
 
 from warnings import warn
+from functools import partial
 
 from logilab.common import interface
 from logilab.common.decorators import cached
@@ -452,26 +453,13 @@
         return mainattr, needcheck
 
     @classmethod
-    def cw_instantiate(cls, execute, **kwargs):
-        """add a new entity of this given type
-
-        Example (in a shell session):
-
-        >>> companycls = vreg['etypes'].etype_class(('Company')
-        >>> personcls = vreg['etypes'].etype_class(('Person')
-        >>> c = companycls.cw_instantiate(session.execute, name=u'Logilab')
-        >>> p = personcls.cw_instantiate(session.execute, firstname=u'John', lastname=u'Doe',
-        ...                              works_for=c)
-
-        You can also set relation where the entity has 'object' role by
-        prefixing the relation by 'reverse_'.
-        """
-        rql = 'INSERT %s X' % cls.__regid__
+    def _cw_build_entity_query(cls, kwargs):
         relations = []
         restrictions = set()
-        pending_relations = []
+        pendingrels = []
         eschema = cls.e_schema
         qargs = {}
+        attrcache = {}
         for attr, value in kwargs.items():
             if attr.startswith('reverse_'):
                 attr = attr[len('reverse_'):]
@@ -487,10 +475,13 @@
                     value = iter(value).next()
                 else:
                     # prepare IN clause
-                    pending_relations.append( (attr, role, value) )
+                    pendingrels.append( (attr, role, value) )
                     continue
             if rschema.final: # attribute
                 relations.append('X %s %%(%s)s' % (attr, attr))
+                attrcache[attr] = value
+            elif value is None:
+                pendingrels.append( (attr, role, value) )
             else:
                 rvar = attr.upper()
                 if role == 'object':
@@ -503,19 +494,51 @@
                 if hasattr(value, 'eid'):
                     value = value.eid
             qargs[attr] = value
+        rql = u''
         if relations:
-            rql = '%s: %s' % (rql, ', '.join(relations))
+            rql += ', '.join(relations)
         if restrictions:
-            rql = '%s WHERE %s' % (rql, ', '.join(restrictions))
-        created = execute(rql, qargs).get_entity(0, 0)
-        for attr, role, values in pending_relations:
+            rql += ' WHERE %s' % ', '.join(restrictions)
+        return rql, qargs, pendingrels, attrcache
+
+    @classmethod
+    def _cw_handle_pending_relations(cls, eid, pendingrels, execute):
+        for attr, role, values in pendingrels:
             if role == 'object':
                 restr = 'Y %s X' % attr
             else:
                 restr = 'X %s Y' % attr
+            if values is None:
+                execute('DELETE %s WHERE X eid %%(x)s' % restr, {'x': eid})
+                continue
             execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
                 restr, ','.join(str(getattr(r, 'eid', r)) for r in values)),
-                    {'x': created.eid}, build_descr=False)
+                    {'x': eid}, build_descr=False)
+
+    @classmethod
+    def cw_instantiate(cls, execute, **kwargs):
+        """add a new entity of this given type
+
+        Example (in a shell session):
+
+        >>> companycls = vreg['etypes'].etype_class('Company')
+        >>> personcls = vreg['etypes'].etype_class('Person')
+        >>> c = companycls.cw_instantiate(session.execute, name=u'Logilab')
+        >>> p = personcls.cw_instantiate(session.execute, firstname=u'John', lastname=u'Doe',
+        ...                              works_for=c)
+
+        You can also set relations where the entity has 'object' role by
+        prefixing the relation name by 'reverse_'. Also, relation values may be
+        an entity or eid, a list of entities or eids.
+        """
+        rql, qargs, pendingrels, attrcache = cls._cw_build_entity_query(kwargs)
+        if rql:
+            rql = 'INSERT %s X: %s' % (cls.__regid__, rql)
+        else:
+            rql = 'INSERT %s X' % (cls.__regid__)
+        created = execute(rql, qargs).get_entity(0, 0)
+        created._cw_update_attr_cache(attrcache)
+        cls._cw_handle_pending_relations(created.eid, pendingrels, execute)
         return created
 
     def __init__(self, req, rset=None, row=None, col=0):
@@ -530,11 +553,50 @@
 
     def __repr__(self):
         return '<Entity %s %s %s at %s>' % (
-            self.e_schema, self.eid, self.cw_attr_cache.keys(), id(self))
+            self.e_schema, self.eid, list(self.cw_attr_cache), id(self))
 
     def __cmp__(self, other):
         raise NotImplementedError('comparison not implemented for %s' % self.__class__)
 
+    def _cw_update_attr_cache(self, attrcache):
+        # if context is a repository session, don't consider dont-cache-attrs as
+        # the instance already hold modified values and loosing them could
+        # introduce severe problems
+        get_set = partial(self._cw.get_shared_data, default=(), txdata=True,
+                          pop=True)
+        uncached_attrs = set()
+        uncached_attrs.update(get_set('%s.storage-special-process-attrs' % self.eid))
+        if self._cw.is_request:
+            uncached_attrs.update(get_set('%s.dont-cache-attrs' % self.eid))
+        for attr in uncached_attrs:
+            attrcache.pop(attr, None)
+            self.cw_attr_cache.pop(attr, None)
+        self.cw_attr_cache.update(attrcache)
+
+    def _cw_dont_cache_attribute(self, attr, repo_side=False):
+        """Repository side method called when some attribute has been
+        transformed by a hook, hence original value should not be cached by
+        the client.
+
+        If repo_side is True, this means that the attribute has been
+        transformed by a *storage*, hence the original value should
+        not be cached **by anyone**.
+
+        This only applies to a storage special case where the value
+        specified in creation or update is **not** the value that will
+        be transparently exposed later.
+
+        For example we have a special "fs_importing" mode in BFSS
+        where a file path is given as attribute value and stored as is
+        in the data base. Later access to the attribute will provide
+        the content of the file at the specified path. We do not want
+        the "filepath" value to be cached.
+        """
+        self._cw.transaction_data.setdefault('%s.dont-cache-attrs' % self.eid, set()).add(attr)
+        if repo_side:
+            trdata = self._cw.transaction_data
+            trdata.setdefault('%s.storage-special-process-attrs' % self.eid, set()).add(attr)
+
     def __json_encode__(self):
         """custom json dumps hook to dump the entity's eid
         which is not part of dict structure itself
@@ -836,7 +898,7 @@
         selected = []
         for attr in (attributes or self._cw_to_complete_attributes(skip_bytes, skip_pwd)):
             # if attribute already in entity, nothing to do
-            if self.cw_attr_cache.has_key(attr):
+            if attr in self.cw_attr_cache:
                 continue
             # case where attribute must be completed, but is not yet in entity
             var = varmaker.next()
@@ -935,30 +997,38 @@
           :exc:`Unauthorized`, else (the default), the exception is propagated
         """
         rtype = str(rtype)
-        try:
-            return self._cw_relation_cache(rtype, role, entities, limit)
-        except KeyError:
-            pass
+        if limit is None:
+            # we cannot do much wrt cache on limited queries
+            cache_key = '%s_%s' % (rtype, role)
+            if cache_key in self._cw_related_cache:
+                return self._cw_related_cache[cache_key][entities]
         if not self.has_eid():
             if entities:
                 return []
             return self._cw.empty_rset()
-        rql = self.cw_related_rql(rtype, role)
+        rql = self.cw_related_rql(rtype, role, limit=limit)
         try:
             rset = self._cw.execute(rql, {'x': self.eid})
         except Unauthorized:
             if not safe:
                 raise
             rset = self._cw.empty_rset()
-        self.cw_set_relation_cache(rtype, role, rset)
-        return self.related(rtype, role, limit, entities)
+        if entities:
+            if limit is None:
+                self.cw_set_relation_cache(rtype, role, rset)
+                return self.related(rtype, role, limit, entities)
+            return list(rset.entities())
+        else:
+            return rset
 
-    def cw_related_rql(self, rtype, role='subject', targettypes=None):
+    def cw_related_rql(self, rtype, role='subject', targettypes=None, limit=None):
         vreg = self._cw.vreg
         rschema = vreg.schema[rtype]
         select = Select()
         mainvar, evar = select.get_variable('X'), select.get_variable('E')
         select.add_selected(mainvar)
+        if limit is not None:
+            select.set_limit(limit)
         select.add_eid_restriction(evar, 'x', 'Substitute')
         if role == 'subject':
             rel = make_relation(evar, rtype, (mainvar,), VariableRef)
@@ -1013,7 +1083,7 @@
     # generic vocabulary methods ##############################################
 
     def cw_unrelated_rql(self, rtype, targettype, role, ordermethod=None,
-                         vocabconstraints=True, lt_infos={}):
+                         vocabconstraints=True, lt_infos={}, limit=None):
         """build a rql to fetch `targettype` entities unrelated to this entity
         using (rtype, role) relation.
 
@@ -1042,6 +1112,8 @@
             searchedvar = subjvar = select.get_variable('S')
             evar = objvar = select.get_variable('O')
         select.add_selected(searchedvar)
+        if limit is not None:
+            select.set_limit(limit)
         # initialize some variables according to `self` existence
         if rdef.role_cardinality(neg_role(role)) in '?1':
             # if cardinality in '1?', we want a target entity which isn't
@@ -1135,14 +1207,10 @@
         by a given relation, with self as subject or object
         """
         try:
-            rql, args = self.cw_unrelated_rql(rtype, targettype, role,
-                                              ordermethod, lt_infos=lt_infos)
+            rql, args = self.cw_unrelated_rql(rtype, targettype, role, limit=limit,
+                                              ordermethod=ordermethod, lt_infos=lt_infos)
         except Unauthorized:
             return self._cw.empty_rset()
-        # XXX should be set in unrelated rql when manipulating the AST
-        if limit is not None:
-            before, after = rql.split(' WHERE ', 1)
-            rql = '%s LIMIT %s WHERE %s' % (before, limit, after)
         return self._cw.execute(rql, args)
 
     # relations cache handling #################################################
@@ -1153,18 +1221,6 @@
         """
         return self._cw_related_cache.get('%s_%s' % (rtype, role))
 
-    def _cw_relation_cache(self, rtype, role, entities=True, limit=None):
-        """return values for the given relation if it's cached on the instance,
-        else raise `KeyError`
-        """
-        res = self._cw_related_cache['%s_%s' % (rtype, role)][entities]
-        if limit is not None and limit < len(res):
-            if entities:
-                res = res[:limit]
-            else:
-                res = res.limit(limit)
-        return res
-
     def cw_set_relation_cache(self, rtype, role, rset):
         """set cached values for the given relation"""
         if rset:
@@ -1215,54 +1271,41 @@
 
     # raw edition utilities ###################################################
 
-    def set_attributes(self, **kwargs): # XXX cw_set_attributes
+    def cw_set(self, **kwargs):
+        """update this entity using given attributes / relation, working in the
+        same fashion as :meth:`cw_instantiate`.
+
+        Example (in a shell session):
+
+        >>> c = rql('Any X WHERE X is Company').get_entity(0, 0)
+        >>> p = rql('Any X WHERE X is Person').get_entity(0, 0)
+        >>> c.set(name=u'Logilab')
+        >>> p.set(firstname=u'John', lastname=u'Doe', works_for=c)
+
+        You can also set relations where the entity has 'object' role by
+        prefixing the relation name by 'reverse_'.  Also, relation values may be
+        an entity or eid, a list of entities or eids, or None (meaning that all
+        relations of the given type from or to this object should be deleted).
+        """
         _check_cw_unsafe(kwargs)
         assert kwargs
         assert self.cw_is_saved(), "should not call set_attributes while entity "\
                "hasn't been saved yet"
-        relations = ['X %s %%(%s)s' % (key, key) for key in kwargs]
-        # and now update the database
-        kwargs['x'] = self.eid
-        self._cw.execute('SET %s WHERE X eid %%(x)s' % ','.join(relations),
-                         kwargs)
-        kwargs.pop('x')
+        rql, qargs, pendingrels, attrcache = self._cw_build_entity_query(kwargs)
+        if rql:
+            rql = 'SET ' + rql
+            qargs['x'] = self.eid
+            if ' WHERE ' in rql:
+                rql += ', X eid %(x)s'
+            else:
+                rql += ' WHERE X eid %(x)s'
+            self._cw.execute(rql, qargs)
         # update current local object _after_ the rql query to avoid
         # interferences between the query execution itself and the cw_edited /
         # skip_security machinery
-        self.cw_attr_cache.update(kwargs)
-
-    def set_relations(self, **kwargs): # XXX cw_set_relations
-        """add relations to the given object. To set a relation where this entity
-        is the object of the relation, use 'reverse_'<relation> as argument name.
-
-        Values may be an entity or eid, a list of entities or eids, or None
-        (meaning that all relations of the given type from or to this object
-        should be deleted).
-        """
-        # XXX update cache
-        _check_cw_unsafe(kwargs)
-        for attr, values in kwargs.iteritems():
-            if attr.startswith('reverse_'):
-                restr = 'Y %s X' % attr[len('reverse_'):]
-            else:
-                restr = 'X %s Y' % attr
-            if values is None:
-                self._cw.execute('DELETE %s WHERE X eid %%(x)s' % restr,
-                                 {'x': self.eid})
-                continue
-            if not isinstance(values, (tuple, list, set, frozenset)):
-                values = (values,)
-            eids = []
-            for val in values:
-                try:
-                    eids.append(str(val.eid))
-                except AttributeError:
-                    try:
-                        eids.append(str(typed_eid(val)))
-                    except (ValueError, TypeError):
-                        raise Exception('expected an Entity or eid, got %s' % val)
-            self._cw.execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
-                    restr, ','.join(eids)), {'x': self.eid})
+        self._cw_update_attr_cache(attrcache)
+        self._cw_handle_pending_relations(self.eid, pendingrels, self._cw.execute)
+        # XXX update relation cache
 
     def cw_delete(self, **kwargs):
         assert self.has_eid(), self.eid
@@ -1277,6 +1320,21 @@
 
     # deprecated stuff #########################################################
 
+    @deprecated('[3.16] use cw_set() instead')
+    def set_attributes(self, **kwargs): # XXX cw_set_attributes
+        self.cw_set(**kwargs)
+
+    @deprecated('[3.16] use cw_set() instead')
+    def set_relations(self, **kwargs): # XXX cw_set_relations
+        """add relations to the given object. To set a relation where this entity
+        is the object of the relation, use 'reverse_'<relation> as argument name.
+
+        Values may be an entity or eid, a list of entities or eids, or None
+        (meaning that all relations of the given type from or to this object
+        should be deleted).
+        """
+        self.cw_set(**kwargs)
+
     @deprecated('[3.13] use entity.cw_clear_all_caches()')
     def clear_all_caches(self):
         return self.cw_clear_all_caches()
--- a/etwist/server.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/etwist/server.py	Wed Mar 20 17:40:25 2013 +0100
@@ -16,9 +16,6 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """twisted server for CubicWeb web instances"""
-
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 
 import sys
@@ -34,7 +31,6 @@
 from datetime import date, timedelta
 from urlparse import urlsplit, urlunsplit
 from cgi import FieldStorage, parse_header
-from cStringIO import StringIO
 
 from twisted.internet import reactor, task, threads
 from twisted.internet.defer import maybeDeferred
@@ -43,6 +39,7 @@
 from twisted.web.server import NOT_DONE_YET
 
 
+from logilab.mtconverter import xml_escape
 from logilab.common.decorators import monkeypatch
 
 from cubicweb import (AuthenticationError, ConfigurationError,
@@ -85,7 +82,7 @@
         config = self.config
         # when we have an in-memory repository, clean unused sessions every XX
         # seconds and properly shutdown the server
-        if config.repo_method == 'inmemory':
+        if config['repository-uri'] == 'inmemory://':
             if config.pyro_enabled():
                 # if pyro is enabled, we have to register to the pyro name
                 # server, create a pyro daemon, and create a task to handle pyro
@@ -147,9 +144,8 @@
             request.process_multipart()
             return self._render_request(request)
         except Exception:
-            errorstream = StringIO()
-            traceback.print_exc(file=errorstream)
-            return HTTPResponse(stream='<pre>%s</pre>' % errorstream.getvalue(),
+            trace = traceback.format_exc()
+            return HTTPResponse(stream='<pre>%s</pre>' % xml_escape(trace),
                                 code=500, twisted_request=request)
 
     def _render_request(self, request):
@@ -172,7 +168,7 @@
         try:
             ### Try to generate the actual request content
             content = self.appli.handle_request(req, path)
-        except DirectResponse, ex:
+        except DirectResponse as ex:
             return ex.response
         # at last: create twisted object
         return HTTPResponse(code    = req.status_out,
--- a/etwist/service.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/etwist/service.py	Wed Mar 20 17:40:25 2013 +0100
@@ -91,7 +91,7 @@
             logger.info('instance started on %s', root_resource.base_url)
             self.ReportServiceStatus(win32service.SERVICE_RUNNING)
             reactor.run()
-        except Exception, e:
+        except Exception as e:
             logger.error('service %s stopped (cause: %s)' % (self.instance, e))
             logger.exception('what happened ...')
         self.ReportServiceStatus(win32service.SERVICE_STOPPED)
--- a/etwist/twconfig.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/etwist/twconfig.py	Wed Mar 20 17:40:25 2013 +0100
@@ -115,7 +115,6 @@
     class AllInOneConfiguration(TwistedConfiguration, ServerConfiguration):
         """repository and web instance in the same twisted process"""
         name = 'all-in-one'
-        repo_method = 'inmemory'
         options = merge_options(TwistedConfiguration.options
                                 + ServerConfiguration.options)
 
--- a/ext/html4zope.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/ext/html4zope.py	Wed Mar 20 17:40:25 2013 +0100
@@ -123,14 +123,14 @@
 
     def visit_reference(self, node):
         """syt: i want absolute urls"""
-        if node.has_key('refuri'):
+        if 'refuri' in node:
             href = node['refuri']
             if ( self.settings.cloak_email_addresses
                  and href.startswith('mailto:')):
                 href = self.cloak_mailto(href)
                 self.in_mailto = 1
         else:
-            assert node.has_key('refid'), \
+            assert 'refid' in node, \
                    'References must have "refuri" or "refid" attribute.'
             href = '%s#%s' % (self.base_url, node['refid'])
         atts = {'href': href, 'class': 'reference'}
--- a/ext/rest.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/ext/rest.py	Wed Mar 20 17:40:25 2013 +0100
@@ -123,7 +123,7 @@
             vid = 'noresult'
         view = _cw.vreg['views'].select(vid, _cw, rset=rset)
         content = view.render()
-    except Exception, exc:
+    except Exception as exc:
         content = 'an error occured while interpreting this rql directive: %r' % exc
     set_classes(options)
     return [nodes.raw('', content, format='html')], []
@@ -164,7 +164,7 @@
             source_path=path, encoding=encoding,
             error_handler=state.document.settings.input_encoding_error_handler,
             handle_io_errors=None)
-    except IOError, error:
+    except IOError as error:
         severe = state_machine.reporter.severe(
               'Problems with "%s" directive path:\n%s: %s.'
               % (name, error.__class__.__name__, error),
@@ -172,13 +172,13 @@
         return [severe]
     try:
         include_text = include_file.read()
-    except UnicodeError, error:
+    except UnicodeError as error:
         severe = state_machine.reporter.severe(
               'Problem with "%s" directive:\n%s: %s'
               % (name, error.__class__.__name__, error),
               nodes.literal_block(block_text, block_text), line=lineno)
         return [severe]
-    if options.has_key('literal'):
+    if 'literal' in options:
         literal_block = nodes.literal_block(include_text, include_text,
                                             source=path)
         literal_block.line = 1
--- a/ext/tal.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/ext/tal.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -182,7 +182,7 @@
         """ Internally used when expanding a template that is part of a context."""
         try:
             interpreter.execute(self)
-        except UnicodeError, unierror:
+        except UnicodeError as unierror:
             LOGGER.exception(str(unierror))
             raise simpleTALES.ContextContentException("found non-unicode %r string in Context!" % unierror.args[1]), None, sys.exc_info()[-1]
 
@@ -230,7 +230,7 @@
     # XXX precompile expr will avoid late syntax error
     try:
         result = eval(expr, globals, locals)
-    except Exception, ex:
+    except Exception as ex:
         ex = ex.__class__('in %r: %s' % (expr, ex))
         raise ex, None, sys.exc_info()[-1]
     if (isinstance (result, simpleTALES.ContextVariable)):
@@ -261,7 +261,7 @@
         return wrapped
 
     def _compiled_template(self, instance):
-        for fileordirectory in instance.config.vregistry_path():
+        for fileordirectory in instance.config.appobjects_path():
             filepath = join(fileordirectory, self.filename)
             if isdir(fileordirectory) and exists(filepath):
                 return compile_template_file(filepath)
--- a/hooks/__init__.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/hooks/__init__.py	Wed Mar 20 17:40:25 2013 +0100
@@ -67,7 +67,7 @@
                 session = repo.internal_session(safe=True)
                 try:
                     source.pull_data(session)
-                except Exception, exc:
+                except Exception as exc:
                     session.exception('while trying to update feed %s', source)
                 finally:
                     session.close()
--- a/hooks/bookmark.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/hooks/bookmark.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/hooks/email.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/hooks/email.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -15,9 +15,8 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""hooks to ensure use_email / primary_email relations consistency
+"""hooks to ensure use_email / primary_email relations consistency"""
 
-"""
 __docformat__ = "restructuredtext en"
 
 from cubicweb.server import hook
--- a/hooks/integrity.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/hooks/integrity.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -20,12 +20,11 @@
 """
 
 __docformat__ = "restructuredtext en"
+_ = unicode
 
 from threading import Lock
 
-from yams.schema import role_name
-
-from cubicweb import ValidationError
+from cubicweb import validation_error
 from cubicweb.schema import (META_RTYPES, WORKFLOW_RTYPES,
                              RQLConstraint, RQLUniqueConstraint)
 from cubicweb.predicates import is_instance
@@ -87,11 +86,11 @@
                 continue
             if not session.execute(self.base_rql % rtype, {'x': eid}):
                 etype = session.describe(eid)[0]
-                _ = session._
                 msg = _('at least one relation %(rtype)s is required on '
                         '%(etype)s (%(eid)s)')
-                msg %= {'rtype': _(rtype), 'etype': _(etype), 'eid': eid}
-                raise ValidationError(eid, {role_name(rtype, self.role): msg})
+                raise validation_error(eid, {(rtype, self.role): msg},
+                                       {'rtype': rtype, 'etype': etype, 'eid': eid},
+                                       ['rtype', 'etype'])
 
 
 class _CheckSRelationOp(_CheckRequiredRelationOperation):
@@ -231,9 +230,9 @@
                 rql = '%s X WHERE X %s %%(val)s' % (entity.e_schema, attr)
                 rset = self._cw.execute(rql, {'val': val})
                 if rset and rset[0][0] != entity.eid:
-                    msg = self._cw._('the value "%s" is already used, use another one')
-                    qname = role_name(attr, 'subject')
-                    raise ValidationError(entity.eid, {qname: msg % val})
+                    msg = _('the value "%s" is already used, use another one')
+                    raise validation_error(entity, {(attr, 'subject'): msg},
+                                           (val,))
 
 
 class DontRemoveOwnersGroupHook(IntegrityHook):
@@ -246,15 +245,12 @@
     def __call__(self):
         entity = self.entity
         if self.event == 'before_delete_entity' and entity.name == 'owners':
-            msg = self._cw._('can\'t be deleted')
-            raise ValidationError(entity.eid, {None: msg})
+            raise validation_error(entity, {None: _("can't be deleted")})
         elif self.event == 'before_update_entity' \
                  and 'name' in entity.cw_edited:
             oldname, newname = entity.cw_edited.oldnewvalue('name')
             if oldname == 'owners' and newname != oldname:
-                qname = role_name('name', 'subject')
-                msg = self._cw._('can\'t be changed')
-                raise ValidationError(entity.eid, {qname: msg})
+                raise validation_error(entity, {('name', 'subject'): _("can't be changed")})
 
 
 class TidyHtmlFields(IntegrityHook):
--- a/hooks/metadata.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/hooks/metadata.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/hooks/syncschema.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/hooks/syncschema.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -24,6 +24,7 @@
 """
 
 __docformat__ = "restructuredtext en"
+_ = unicode
 
 from copy import copy
 from yams.schema import BASE_TYPES, RelationSchema, RelationDefinitionSchema
@@ -31,7 +32,7 @@
 
 from logilab.common.decorators import clear_cache
 
-from cubicweb import ValidationError
+from cubicweb import validation_error
 from cubicweb.predicates import is_instance
 from cubicweb.schema import (SCHEMA_TYPES, META_RTYPES, VIRTUAL_RTYPES,
                              CONSTRAINTS, ETYPE_NAME_MAP, display_name)
@@ -127,10 +128,9 @@
         if attr in ro_attrs:
             origval, newval = entity.cw_edited.oldnewvalue(attr)
             if newval != origval:
-                errors[attr] = session._("can't change the %s attribute") % \
-                               display_name(session, attr)
+                errors[attr] = _("can't change this attribute")
     if errors:
-        raise ValidationError(entity.eid, errors)
+        raise validation_error(entity, errors)
 
 
 class _MockEntity(object): # XXX use a named tuple with python 2.6
@@ -219,7 +219,7 @@
             repo.set_schema(repo.schema, rebuildinfered=rebuildinfered)
             # CWUser class might have changed, update current session users
             cwuser_cls = self.session.vreg['etypes'].etype_class('CWUser')
-            for session in repo._sessions.values():
+            for session in repo._sessions.itervalues():
                 session.user.__class__ = cwuser_cls
         except Exception:
             self.critical('error while setting schema', exc_info=True)
@@ -375,7 +375,7 @@
             for etype in rschema.subjects():
                 try:
                     add_inline_relation_column(session, str(etype), rtype)
-                except Exception, ex:
+                except Exception as ex:
                     # the column probably already exists. this occurs when the
                     # entity's type has just been added or if the column has not
                     # been previously dropped (eg sqlite)
@@ -466,7 +466,7 @@
                                    % (table, column, attrtype)),
                                rollback_on_failure=False)
             self.info('added column %s to table %s', table, column)
-        except Exception, ex:
+        except Exception as ex:
             # the column probably already exists. this occurs when
             # the entity's type has just been added or if the column
             # has not been previously dropped
@@ -475,7 +475,7 @@
             try:
                 syssource.create_index(session, table, column,
                                       unique=extra_unique_index)
-            except Exception, ex:
+            except Exception as ex:
                 self.error('error while creating index for %s.%s: %s',
                            table, column, ex)
         # final relations are not infered, propagate
@@ -764,7 +764,7 @@
         for sql in sqls:
             try:
                 session.system_sql(sql)
-            except Exception, exc: # should be ProgrammingError
+            except Exception as exc: # should be ProgrammingError
                 if sql.startswith('DROP'):
                     self.error('execute of `%s` failed (cause: %s)', sql, exc)
                     continue
@@ -920,7 +920,7 @@
         # final entities can't be deleted, don't care about that
         name = self.entity.name
         if name in CORE_TYPES:
-            raise ValidationError(self.entity.eid, {None: self._cw._('can\'t be deleted')})
+            raise validation_error(self.entity, {None: _("can't be deleted")})
         # delete every entities of this type
         if name not in ETYPE_NAME_MAP:
             self._cw.execute('DELETE %s X' % name)
@@ -990,7 +990,7 @@
     def __call__(self):
         name = self.entity.name
         if name in CORE_TYPES:
-            raise ValidationError(self.entity.eid, {None: self._cw._('can\'t be deleted')})
+            raise validation_error(self.entity, {None: _("can't be deleted")})
         # delete relation definitions using this relation type
         self._cw.execute('DELETE CWAttribute X WHERE X relation_type Y, Y eid %(x)s',
                         {'x': self.entity.eid})
--- a/hooks/syncsession.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/hooks/syncsession.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -18,15 +18,15 @@
 """Core hooks: synchronize living session on persistent data changes"""
 
 __docformat__ = "restructuredtext en"
+_ = unicode
 
-from yams.schema import role_name
-from cubicweb import UnknownProperty, ValidationError, BadConnectionId
+from cubicweb import UnknownProperty, BadConnectionId, validation_error
 from cubicweb.predicates import is_instance
 from cubicweb.server import hook
 
 
 def get_user_sessions(repo, ueid):
-    for session in repo._sessions.values():
+    for session in repo._sessions.itervalues():
         if ueid == session.user.eid:
             yield session
 
@@ -165,13 +165,11 @@
         try:
             value = session.vreg.typed_value(key, value)
         except UnknownProperty:
-            qname = role_name('pkey', 'subject')
-            msg = session._('unknown property key %s') % key
-            raise ValidationError(self.entity.eid, {qname: msg})
-        except ValueError, ex:
-            qname = role_name('value', 'subject')
-            raise ValidationError(self.entity.eid,
-                                  {qname: session._(str(ex))})
+            msg = _('unknown property key %s')
+            raise validation_error(self.entity, {('pkey', 'subject'): msg}, (key,))
+        except ValueError as ex:
+            raise validation_error(self.entity,
+                                  {('value', 'subject'): str(ex)})
         if not session.user.matching_groups('managers'):
             session.add_relation(self.entity.eid, 'for_user', session.user.eid)
         else:
@@ -195,9 +193,8 @@
             value = session.vreg.typed_value(key, value)
         except UnknownProperty:
             return
-        except ValueError, ex:
-            qname = role_name('value', 'subject')
-            raise ValidationError(entity.eid, {qname: session._(str(ex))})
+        except ValueError as ex:
+            raise validation_error(entity, {('value', 'subject'): str(ex)})
         if entity.for_user:
             for session_ in get_user_sessions(session.repo, entity.for_user[0].eid):
                 _ChangeCWPropertyOp(session, cwpropdict=session_.user.properties,
@@ -237,10 +234,8 @@
         key, value = session.execute('Any K,V WHERE P eid %(x)s,P pkey K,P value V',
                                      {'x': eidfrom})[0]
         if session.vreg.property_info(key)['sitewide']:
-            qname = role_name('for_user', 'subject')
-            msg = session._("site-wide property can't be set for user")
-            raise ValidationError(eidfrom,
-                                  {qname: msg})
+            msg = _("site-wide property can't be set for user")
+            raise validation_error(eidfrom, {('for_user', 'subject'): msg})
         for session_ in get_user_sessions(session.repo, self.eidto):
             _ChangeCWPropertyOp(session, cwpropdict=session_.user.properties,
                               key=key, value=value)
--- a/hooks/syncsources.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/hooks/syncsources.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2010-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2010-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -17,12 +17,13 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """hooks for repository sources synchronization"""
 
+_ = unicode
+
 from socket import gethostname
 
 from logilab.common.decorators import clear_cache
-from yams.schema import role_name
 
-from cubicweb import ValidationError
+from cubicweb import validation_error
 from cubicweb.predicates import is_instance
 from cubicweb.server import SOURCE_TYPES, hook
 
@@ -46,12 +47,15 @@
         try:
             sourcecls = SOURCE_TYPES[self.entity.type]
         except KeyError:
-            msg = self._cw._('unknown source type')
-            raise ValidationError(self.entity.eid,
-                                  {role_name('type', 'subject'): msg})
-        sourcecls.check_conf_dict(self.entity.eid, self.entity.host_config,
-                                  fail_if_unknown=not self._cw.vreg.config.repairing)
-        SourceAddedOp(self._cw, entity=self.entity)
+            msg = _('Unknown source type')
+            raise validation_error(self.entity, {('type', 'subject'): msg})
+        # ignore creation of the system source done during database
+        # initialisation, as config for this source is in a file and handling
+        # is done separatly (no need for the operation either)
+        if self.entity.name != 'system':
+            sourcecls.check_conf_dict(self.entity.eid, self.entity.host_config,
+                                      fail_if_unknown=not self._cw.vreg.config.repairing)
+            SourceAddedOp(self._cw, entity=self.entity)
 
 
 class SourceRemovedOp(hook.Operation):
@@ -65,7 +69,8 @@
     events = ('before_delete_entity',)
     def __call__(self):
         if self.entity.name == 'system':
-            raise ValidationError(self.entity.eid, {None: 'cant remove system source'})
+            msg = _("You cannot remove the system source")
+            raise validation_error(self.entity, {None: msg})
         SourceRemovedOp(self._cw, uri=self.entity.name)
 
 
@@ -116,11 +121,18 @@
     __select__ = SourceHook.__select__ & is_instance('CWSource')
     events = ('before_update_entity',)
     def __call__(self):
-        if 'config' in self.entity.cw_edited:
-            SourceConfigUpdatedOp.get_instance(self._cw).add_data(self.entity)
         if 'name' in self.entity.cw_edited:
             oldname, newname = self.entity.cw_edited.oldnewvalue('name')
+            if oldname == 'system':
+                msg = _("You cannot rename the system source")
+                raise validation_error(self.entity, {('name', 'subject'): msg})
             SourceRenamedOp(self._cw, oldname=oldname, newname=newname)
+        if 'config' in self.entity.cw_edited:
+            if self.entity.name == 'system' and self.entity.config:
+                msg = _("Configuration of the system source goes to "
+                        "the 'sources' file, not in the database")
+                raise validation_error(self.entity, {('config', 'subject'): msg})
+            SourceConfigUpdatedOp.get_instance(self._cw).add_data(self.entity)
 
 
 class SourceHostConfigUpdatedHook(SourceHook):
@@ -154,8 +166,8 @@
     events = ('before_add_relation',)
     def __call__(self):
         if not self._cw.added_in_transaction(self.eidfrom):
-            msg = self._cw._("can't change this relation")
-            raise ValidationError(self.eidfrom, {self.rtype: msg})
+            msg = _("You can't change this relation")
+            raise validation_error(self.eidfrom, {self.rtype: msg})
 
 
 class SourceMappingChangedOp(hook.DataOperationMixIn, hook.Operation):
--- a/hooks/test/unittest_hooks.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/hooks/test/unittest_hooks.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -21,7 +21,6 @@
 Note:
   syncschema.py hooks are mostly tested in server/test/unittest_migrations.py
 """
-from __future__ import with_statement
 
 from datetime import datetime
 
@@ -67,10 +66,9 @@
         entity = self.request().create_entity('Workflow', name=u'wf1',
                                               description_format=u'text/html',
                                               description=u'yo')
-        entity.set_attributes(name=u'wf2')
+        entity.cw_set(name=u'wf2')
         self.assertEqual(entity.description, u'yo')
-        entity.set_attributes(description=u'R&D<p>yo')
-        entity.cw_attr_cache.pop('description')
+        entity.cw_set(description=u'R&D<p>yo')
         self.assertEqual(entity.description, u'R&amp;D<p>yo</p>')
 
     def test_metadata_cwuri(self):
@@ -166,13 +164,12 @@
                           self.execute, 'INSERT CWRType X: X name "in_group"')
 
     def test_validation_unique_constraint(self):
-        self.assertRaises(ValidationError,
-                          self.execute, 'INSERT CWUser X: X login "admin"')
-        try:
+        with self.assertRaises(ValidationError) as cm:
             self.execute('INSERT CWUser X: X login "admin"')
-        except ValidationError, ex:
-            self.assertIsInstance(ex.entity, int)
-            self.assertEqual(ex.errors, {'login-subject': 'the value "admin" is already used, use another one'})
+        ex = cm.exception
+        ex.translate(unicode)
+        self.assertIsInstance(ex.entity, int)
+        self.assertEqual(ex.errors, {'login-subject': 'the value "admin" is already used, use another one'})
 
 
 if __name__ == '__main__':
--- a/hooks/test/unittest_integrity.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/hooks/test/unittest_integrity.py	Wed Mar 20 17:40:25 2013 +0100
@@ -18,8 +18,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """functional tests for integrity hooks"""
 
-from __future__ import with_statement
-
 from cubicweb import ValidationError
 from cubicweb.devtools.testlib import CubicWebTC
 
--- a/hooks/test/unittest_syncschema.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/hooks/test/unittest_syncschema.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -323,7 +323,7 @@
     def test_change_fulltext_container(self):
         req = self.request()
         target = req.create_entity(u'EmailAddress', address=u'rick.roll@dance.com')
-        target.set_relations(reverse_use_email=req.user)
+        target.cw_set(reverse_use_email=req.user)
         self.commit()
         rset = req.execute('Any X WHERE X has_text "rick.roll"')
         self.assertIn(req.user.eid, [item[0] for item in rset])
--- a/hooks/test/unittest_syncsession.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/hooks/test/unittest_syncsession.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -21,7 +21,6 @@
 Note:
   syncschema.py hooks are mostly tested in server/test/unittest_migrations.py
 """
-from __future__ import with_statement
 
 from cubicweb import ValidationError
 from cubicweb.devtools.testlib import CubicWebTC
@@ -31,9 +30,11 @@
     def test_unexistant_cwproperty(self):
         with self.assertRaises(ValidationError) as cm:
             self.execute('INSERT CWProperty X: X pkey "bla.bla", X value "hop", X for_user U')
+        cm.exception.translate(unicode)
         self.assertEqual(cm.exception.errors, {'pkey-subject': 'unknown property key bla.bla'})
         with self.assertRaises(ValidationError) as cm:
             self.execute('INSERT CWProperty X: X pkey "bla.bla", X value "hop"')
+        cm.exception.translate(unicode)
         self.assertEqual(cm.exception.errors, {'pkey-subject': 'unknown property key bla.bla'})
 
     def test_site_wide_cwproperty(self):
--- a/hooks/workflow.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/hooks/workflow.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -18,12 +18,12 @@
 """Core hooks: workflow related hooks"""
 
 __docformat__ = "restructuredtext en"
+_ = unicode
 
 from datetime import datetime
 
-from yams.schema import role_name
 
-from cubicweb import RepositoryError, ValidationError
+from cubicweb import RepositoryError, validation_error
 from cubicweb.predicates import is_instance, adaptable
 from cubicweb.server import hook
 
@@ -92,9 +92,8 @@
         if mainwf.eid == self.wfeid:
             deststate = mainwf.initial
             if not deststate:
-                qname = role_name('custom_workflow', 'subject')
-                msg = session._('workflow has no initial state')
-                raise ValidationError(entity.eid, {qname: msg})
+                msg = _('workflow has no initial state')
+                raise validation_error(entity, {('custom_workflow', 'subject'): msg})
             if mainwf.state_by_eid(iworkflowable.current_state.eid):
                 # nothing to do
                 return
@@ -119,9 +118,8 @@
         outputs = set()
         for ep in tr.subworkflow_exit:
             if ep.subwf_state.eid in outputs:
-                qname = role_name('subworkflow_exit', 'subject')
-                msg = self.session._("can't have multiple exits on the same state")
-                raise ValidationError(self.treid, {qname: msg})
+                msg = _("can't have multiple exits on the same state")
+                raise validation_error(self.treid, {('subworkflow_exit', 'subject'): msg})
             outputs.add(ep.subwf_state.eid)
 
 
@@ -137,13 +135,12 @@
         wftr = iworkflowable.subworkflow_input_transition()
         if wftr is None:
             # inconsistency detected
-            qname = role_name('to_state', 'subject')
-            msg = session._("state doesn't belong to entity's current workflow")
-            raise ValidationError(self.trinfo.eid, {'to_state': msg})
+            msg = _("state doesn't belong to entity's current workflow")
+            raise validation_error(self.trinfo, {('to_state', 'subject'): msg})
         tostate = wftr.get_exit_point(forentity, trinfo.cw_attr_cache['to_state'])
         if tostate is not None:
             # reached an exit point
-            msg = session._('exiting from subworkflow %s')
+            msg = _('exiting from subworkflow %s')
             msg %= session._(iworkflowable.current_workflow.name)
             session.transaction_data[(forentity.eid, 'subwfentrytr')] = True
             iworkflowable.change_state(tostate, msg, u'text/plain', tr=wftr)
@@ -186,9 +183,8 @@
         try:
             foreid = entity.cw_attr_cache['wf_info_for']
         except KeyError:
-            qname = role_name('wf_info_for', 'subject')
-            msg = session._('mandatory relation')
-            raise ValidationError(entity.eid, {qname: msg})
+            msg = _('mandatory relation')
+            raise validation_error(entity, {('wf_info_for', 'subject'): msg})
         forentity = session.entity_from_eid(foreid)
         # see comment in the TrInfo entity definition
         entity.cw_edited['tr_count']=len(forentity.reverse_wf_info_for)
@@ -201,13 +197,13 @@
         else:
             wf = iworkflowable.current_workflow
         if wf is None:
-            msg = session._('related entity has no workflow set')
-            raise ValidationError(entity.eid, {None: msg})
+            msg = _('related entity has no workflow set')
+            raise validation_error(entity, {None: msg})
         # then check it has a state set
         fromstate = iworkflowable.current_state
         if fromstate is None:
-            msg = session._('related entity has no state')
-            raise ValidationError(entity.eid, {None: msg})
+            msg = _('related entity has no state')
+            raise validation_error(entity, {None: msg})
         # True if we are coming back from subworkflow
         swtr = session.transaction_data.pop((forentity.eid, 'subwfentrytr'), None)
         cowpowers = (session.user.is_in_group('managers')
@@ -219,47 +215,42 @@
             # no transition set, check user is a manager and destination state
             # is specified (and valid)
             if not cowpowers:
-                qname = role_name('by_transition', 'subject')
-                msg = session._('mandatory relation')
-                raise ValidationError(entity.eid, {qname: msg})
+                msg = _('mandatory relation')
+                raise validation_error(entity, {('by_transition', 'subject'): msg})
             deststateeid = entity.cw_attr_cache.get('to_state')
             if not deststateeid:
-                qname = role_name('by_transition', 'subject')
-                msg = session._('mandatory relation')
-                raise ValidationError(entity.eid, {qname: msg})
+                msg = _('mandatory relation')
+                raise validation_error(entity, {('by_transition', 'subject'): msg})
             deststate = wf.state_by_eid(deststateeid)
             if deststate is None:
-                qname = role_name('to_state', 'subject')
-                msg = session._("state doesn't belong to entity's workflow")
-                raise ValidationError(entity.eid, {qname: msg})
+                msg = _("state doesn't belong to entity's workflow")
+                raise validation_error(entity, {('to_state', 'subject'): msg})
         else:
             # check transition is valid and allowed, unless we're coming back
             # from subworkflow
             tr = session.entity_from_eid(treid)
             if swtr is None:
-                qname = role_name('by_transition', 'subject')
+                qname = ('by_transition', 'subject')
                 if tr is None:
-                    msg = session._("transition doesn't belong to entity's workflow")
-                    raise ValidationError(entity.eid, {qname: msg})
+                    msg = _("transition doesn't belong to entity's workflow")
+                    raise validation_error(entity, {qname: msg})
                 if not tr.has_input_state(fromstate):
-                    msg = session._("transition %(tr)s isn't allowed from %(st)s") % {
-                        'tr': session._(tr.name), 'st': session._(fromstate.name)}
-                    raise ValidationError(entity.eid, {qname: msg})
+                    msg = _("transition %(tr)s isn't allowed from %(st)s")
+                    raise validation_error(entity, {qname: msg}, {
+                            'tr': tr.name, 'st': fromstate.name}, ['tr', 'st'])
                 if not tr.may_be_fired(foreid):
-                    msg = session._("transition may not be fired")
-                    raise ValidationError(entity.eid, {qname: msg})
+                    msg = _("transition may not be fired")
+                    raise validation_error(entity, {qname: msg})
             deststateeid = entity.cw_attr_cache.get('to_state')
             if deststateeid is not None:
                 if not cowpowers and deststateeid != tr.destination(forentity).eid:
-                    qname = role_name('by_transition', 'subject')
-                    msg = session._("transition isn't allowed")
-                    raise ValidationError(entity.eid, {qname: msg})
+                    msg = _("transition isn't allowed")
+                    raise validation_error(entity, {('by_transition', 'subject'): msg})
                 if swtr is None:
                     deststate = session.entity_from_eid(deststateeid)
                     if not cowpowers and deststate is None:
-                        qname = role_name('to_state', 'subject')
-                        msg = session._("state doesn't belong to entity's workflow")
-                        raise ValidationError(entity.eid, {qname: msg})
+                        msg = _("state doesn't belong to entity's workflow")
+                        raise validation_error(entity, {('to_state', 'subject'): msg})
             else:
                 deststateeid = tr.destination(forentity).eid
         # everything is ok, add missing information on the trinfo entity
@@ -307,20 +298,18 @@
         iworkflowable = entity.cw_adapt_to('IWorkflowable')
         mainwf = iworkflowable.main_workflow
         if mainwf is None:
-            msg = session._('entity has no workflow set')
-            raise ValidationError(entity.eid, {None: msg})
+            msg = _('entity has no workflow set')
+            raise validation_error(entity, {None: msg})
         for wf in mainwf.iter_workflows():
             if wf.state_by_eid(self.eidto):
                 break
         else:
-            qname = role_name('in_state', 'subject')
-            msg = session._("state doesn't belong to entity's workflow. You may "
-                            "want to set a custom workflow for this entity first.")
-            raise ValidationError(self.eidfrom, {qname: msg})
+            msg = _("state doesn't belong to entity's workflow. You may "
+                    "want to set a custom workflow for this entity first.")
+            raise validation_error(self.eidfrom, {('in_state', 'subject'): msg})
         if iworkflowable.current_workflow and wf.eid != iworkflowable.current_workflow.eid:
-            qname = role_name('in_state', 'subject')
-            msg = session._("state doesn't belong to entity's current workflow")
-            raise ValidationError(self.eidfrom, {qname: msg})
+            msg = _("state doesn't belong to entity's current workflow")
+            raise validation_error(self.eidfrom, {('in_state', 'subject'): msg})
 
 
 class SetModificationDateOnStateChange(WorkflowHook):
@@ -335,8 +324,8 @@
             return
         entity = self._cw.entity_from_eid(self.eidfrom)
         try:
-            entity.set_attributes(modification_date=datetime.now())
-        except RepositoryError, ex:
+            entity.cw_set(modification_date=datetime.now())
+        except RepositoryError as ex:
             # usually occurs if entity is coming from a read-only source
             # (eg ldap user)
             self.warning('cant change modification date for %s: %s', entity, ex)
--- a/i18n.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/i18n.py	Wed Mar 20 17:40:25 2013 +0100
@@ -54,19 +54,16 @@
         w('msgid "%s"\n' % msgid[0])
     w('msgstr ""\n\n')
 
-
-def execute(cmd):
-    """display the command, execute it and raise an Exception if returned
-    status != 0
-    """
-    from subprocess import call
-    # use getcwdu as cmd may be unicode and cwd may contains non-ascii
-    # characters
-    print cmd.replace(os.getcwdu() + os.sep, '')
-    status = call(cmd, shell=True)
-    if status != 0:
-        raise Exception('status = %s' % status)
-
+def execute2(args):
+    # XXX replace this with check_output in Python 2.7
+    from subprocess import Popen, PIPE, CalledProcessError
+    p = Popen(args, stdout=PIPE, stderr=PIPE)
+    out, err = p.communicate()
+    if p.returncode != 0:
+        exc = CalledProcessError(p.returncode, args[0])
+        exc.cmd = args
+        exc.data = (out, err)
+        raise exc
 
 def available_catalogs(i18ndir=None):
     if i18ndir is None:
@@ -81,6 +78,7 @@
 def compile_i18n_catalogs(sourcedirs, destdir, langs):
     """generate .mo files for a set of languages into the `destdir` i18n directory
     """
+    from subprocess import CalledProcessError
     from logilab.common.fileutils import ensure_fs_mode
     print '-> compiling message catalogs to %s' % destdir
     errors = []
@@ -93,17 +91,21 @@
         mergedpo = join(destdir, '%s_merged.po' % lang)
         try:
             # merge instance/cubes messages catalogs with the stdlib's one
-            execute('msgcat --use-first --sort-output --strict -o "%s" %s'
-                    % (mergedpo, ' '.join('"%s"' % f for f in pofiles)))
+            cmd = ['msgcat', '--use-first', '--sort-output', '--strict',
+                   '-o', mergedpo] + pofiles
+            execute2(cmd)
             # make sure the .mo file is writeable and compiles with *msgfmt*
             applmo = join(destdir, lang, 'LC_MESSAGES', 'cubicweb.mo')
             try:
                 ensure_fs_mode(applmo)
             except OSError:
                 pass # suppose not exists
-            execute('msgfmt "%s" -o "%s"' % (mergedpo, applmo))
-        except Exception, ex:
-            errors.append('while handling language %s: %s' % (lang, ex))
+            execute2(['msgfmt', mergedpo, '-o', applmo])
+        except CalledProcessError as exc:
+            errors.append(u'while handling language %s:\ncmd:\n%s\nstdout:\n%s\nstderr:\n%s\n' %
+                          (lang, exc.cmd, repr(exc.data[0]), repr(exc.data[1])))
+        except Exception as exc:
+            errors.append(u'while handling language %s: %s' % (lang, exc))
         try:
             # clean everything
             os.unlink(mergedpo)
--- a/i18n/de.po	Tue Mar 19 16:56:46 2013 +0100
+++ b/i18n/de.po	Wed Mar 20 17:40:25 2013 +0100
@@ -50,6 +50,14 @@
 msgstr ""
 
 #, python-format
+msgid "%(KEY-cstr)s constraint failed for value %(KEY-value)r"
+msgstr ""
+
+#, python-format
+msgid "%(KEY-value)r doesn't match the %(KEY-regexp)r regular expression"
+msgstr ""
+
+#, python-format
 msgid "%(attr)s set to %(newvalue)s"
 msgstr "%(attr)s geändert in %(newvalue)s"
 
@@ -58,10 +66,6 @@
 msgstr "%(attr)s geändert von %(oldvalue)s in %(newvalue)s"
 
 #, python-format
-msgid "%(cstr)s constraint failed for value %(value)r"
-msgstr "%(cstr)s Einschränkung verletzt für Wert %(value)r"
-
-#, python-format
 msgid "%(etype)s by %(author)s"
 msgstr ""
 
@@ -74,10 +78,6 @@
 msgstr "%(subject)s %(etype)s #%(eid)s (%(login)s)"
 
 #, python-format
-msgid "%(value)r doesn't match the %(regexp)r regular expression"
-msgstr "%(value)r entspricht nicht dem regulären Ausdruck %(regexp)r"
-
-#, python-format
 msgid "%d days"
 msgstr "%d Tage"
 
@@ -427,6 +427,11 @@
 msgid "Click to sort on this column"
 msgstr ""
 
+msgid ""
+"Configuration of the system source goes to the 'sources' file, not in the "
+"database"
+msgstr ""
+
 #, python-format
 msgid "Created %(etype)s : %(entity)s"
 msgstr ""
@@ -902,6 +907,9 @@
 msgid "UniqueConstraint"
 msgstr "eindeutige Einschränkung"
 
+msgid "Unknown source type"
+msgstr ""
+
 msgid "Unreachable objects"
 msgstr "unzugängliche Objekte"
 
@@ -960,6 +968,15 @@
 msgid "You can use any of the following substitutions in your text"
 msgstr "Sie können die folgenden Ersetzungen in Ihrem Text verwenden:"
 
+msgid "You can't change this relation"
+msgstr ""
+
+msgid "You cannot remove the system source"
+msgstr ""
+
+msgid "You cannot rename the system source"
+msgstr ""
+
 msgid ""
 "You have no access to this view or it can not be used to display the current "
 "data."
@@ -1006,9 +1023,6 @@
 msgid "abstract base class for transitions"
 msgstr "abstrakte Basisklasse für Übergänge"
 
-msgid "action menu"
-msgstr ""
-
 msgid "action(s) on this selection"
 msgstr "Aktionen(en) bei dieser Auswahl"
 
@@ -1392,11 +1406,7 @@
 msgid "can't be deleted"
 msgstr "kann nicht entfernt werden"
 
-#, python-format
-msgid "can't change the %s attribute"
-msgstr "Kann das Attribut %s nicht ändern."
-
-msgid "can't change this relation"
+msgid "can't change this attribute"
 msgstr ""
 
 #, python-format
@@ -2569,6 +2579,9 @@
 msgid "foaf"
 msgstr "FOAF"
 
+msgid "focus on this selection"
+msgstr ""
+
 msgid "follow"
 msgstr "dem Link folgen"
 
@@ -2859,8 +2872,8 @@
 msgstr "Unzulässiger Wert für Überschrift"
 
 #, python-format
-msgid "incorrect value (%(value)s) for type \"%(type)s\""
-msgstr "Wert %(value)s ungültig für den Typ \"%(type)s\""
+msgid "incorrect value (%(KEY-value)r) for type \"%(KEY-type)s\""
+msgstr ""
 
 msgid "index this attribute's value in the plain text index"
 msgstr "indizieren des Wertes dieses Attributs im Volltext-Index"
@@ -2938,8 +2951,8 @@
 msgstr "Ungültige Aktion %r"
 
 #, python-format
-msgid "invalid value %(value)s, it must be one of %(choices)s"
-msgstr "Wert %(value)s ungültig, er muss zwischen %(choices)s"
+msgid "invalid value %(KEY-value)s, it must be one of %(KEY-choices)s"
+msgstr ""
 
 msgid "is"
 msgstr "vom Typ"
@@ -4174,6 +4187,9 @@
 msgid "toggle check boxes"
 msgstr "Kontrollkästchen umkehren"
 
+msgid "toggle filter"
+msgstr "filter verbergen/zeigen"
+
 msgid "tr_count"
 msgstr ""
 
@@ -4316,9 +4332,6 @@
 msgid "unknown property key %s"
 msgstr "Unbekannter Eigentumsschlüssel %s"
 
-msgid "unknown source type"
-msgstr ""
-
 msgid "unknown vocabulary:"
 msgstr "Unbekanntes Wörterbuch : "
 
@@ -4464,15 +4477,7 @@
 msgstr "Wert"
 
 #, python-format
-msgid "value %(value)s must be %(op)s %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be <= %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be >= %(boundary)s"
+msgid "value %(KEY-value)s must be %(KEY-op)s %(KEY-boundary)s"
 msgstr ""
 
 msgid "value associated to this key is not editable manually"
@@ -4481,11 +4486,11 @@
 "werden."
 
 #, python-format
-msgid "value should have maximum size of %s but found %s"
+msgid "value should have maximum size of %(KEY-max)s but found %(KEY-size)s"
 msgstr ""
 
 #, python-format
-msgid "value should have minimum size of %s but found %s"
+msgid "value should have minimum size of %(KEY-min)s but found %(KEY-size)s"
 msgstr ""
 
 msgid "vcard"
@@ -4630,6 +4635,12 @@
 msgid "you should un-inline relation %s which is supported and may be crossed "
 msgstr ""
 
+#~ msgid "%(cstr)s constraint failed for value %(value)r"
+#~ msgstr "%(cstr)s Einschränkung verletzt für Wert %(value)r"
+
+#~ msgid "%(value)r doesn't match the %(regexp)r regular expression"
+#~ msgstr "%(value)r entspricht nicht dem regulären Ausdruck %(regexp)r"
+
 #~ msgid ""
 #~ "Can't restore relation %(rtype)s of entity %(eid)s, this relation does "
 #~ "not exists anymore in the schema."
@@ -4637,8 +4648,11 @@
 #~ "Kann die Relation %(rtype)s der Entität %(eid)s nicht wieder herstellen, "
 #~ "diese Relation existiert nicht mehr in dem Schema."
 
-#~ msgid "log out first"
-#~ msgstr "Melden Sie sich zuerst ab."
-
-#~ msgid "week"
-#~ msgstr "Woche"
+#~ msgid "can't change the %s attribute"
+#~ msgstr "Kann das Attribut %s nicht ändern."
+
+#~ msgid "incorrect value (%(value)s) for type \"%(type)s\""
+#~ msgstr "Wert %(value)s ungültig für den Typ \"%(type)s\""
+
+#~ msgid "invalid value %(value)s, it must be one of %(choices)s"
+#~ msgstr "Wert %(value)s ungültig, er muss zwischen %(choices)s"
--- a/i18n/en.po	Tue Mar 19 16:56:46 2013 +0100
+++ b/i18n/en.po	Wed Mar 20 17:40:25 2013 +0100
@@ -42,6 +42,14 @@
 msgstr ""
 
 #, python-format
+msgid "%(KEY-cstr)s constraint failed for value %(KEY-value)r"
+msgstr ""
+
+#, python-format
+msgid "%(KEY-value)r doesn't match the %(KEY-regexp)r regular expression"
+msgstr ""
+
+#, python-format
 msgid "%(attr)s set to %(newvalue)s"
 msgstr ""
 
@@ -50,10 +58,6 @@
 msgstr ""
 
 #, python-format
-msgid "%(cstr)s constraint failed for value %(value)r"
-msgstr ""
-
-#, python-format
 msgid "%(etype)s by %(author)s"
 msgstr ""
 
@@ -66,10 +70,6 @@
 msgstr ""
 
 #, python-format
-msgid "%(value)r doesn't match the %(regexp)r regular expression"
-msgstr ""
-
-#, python-format
 msgid "%d days"
 msgstr ""
 
@@ -405,6 +405,11 @@
 msgid "Click to sort on this column"
 msgstr ""
 
+msgid ""
+"Configuration of the system source goes to the 'sources' file, not in the "
+"database"
+msgstr ""
+
 #, python-format
 msgid "Created %(etype)s : %(entity)s"
 msgstr ""
@@ -878,6 +883,9 @@
 msgid "UniqueConstraint"
 msgstr "unique constraint"
 
+msgid "Unknown source type"
+msgstr ""
+
 msgid "Unreachable objects"
 msgstr ""
 
@@ -929,6 +937,15 @@
 msgid "You can use any of the following substitutions in your text"
 msgstr ""
 
+msgid "You can't change this relation"
+msgstr ""
+
+msgid "You cannot remove the system source"
+msgstr ""
+
+msgid "You cannot rename the system source"
+msgstr ""
+
 msgid ""
 "You have no access to this view or it can not be used to display the current "
 "data."
@@ -968,9 +985,6 @@
 msgid "abstract base class for transitions"
 msgstr ""
 
-msgid "action menu"
-msgstr ""
-
 msgid "action(s) on this selection"
 msgstr ""
 
@@ -1349,11 +1363,7 @@
 msgid "can't be deleted"
 msgstr ""
 
-#, python-format
-msgid "can't change the %s attribute"
-msgstr ""
-
-msgid "can't change this relation"
+msgid "can't change this attribute"
 msgstr ""
 
 #, python-format
@@ -2516,6 +2526,9 @@
 msgid "foaf"
 msgstr ""
 
+msgid "focus on this selection"
+msgstr ""
+
 msgid "follow"
 msgstr ""
 
@@ -2788,7 +2801,7 @@
 msgstr ""
 
 #, python-format
-msgid "incorrect value (%(value)s) for type \"%(type)s\""
+msgid "incorrect value (%(KEY-value)r) for type \"%(KEY-type)s\""
 msgstr ""
 
 msgid "index this attribute's value in the plain text index"
@@ -2865,7 +2878,7 @@
 msgstr ""
 
 #, python-format
-msgid "invalid value %(value)s, it must be one of %(choices)s"
+msgid "invalid value %(KEY-value)s, it must be one of %(KEY-choices)s"
 msgstr ""
 
 msgid "is"
@@ -4074,6 +4087,9 @@
 msgid "toggle check boxes"
 msgstr ""
 
+msgid "toggle filter"
+msgstr ""
+
 msgid "tr_count"
 msgstr "transition number"
 
@@ -4216,9 +4232,6 @@
 msgid "unknown property key %s"
 msgstr ""
 
-msgid "unknown source type"
-msgstr ""
-
 msgid "unknown vocabulary:"
 msgstr ""
 
@@ -4355,26 +4368,18 @@
 msgstr ""
 
 #, python-format
-msgid "value %(value)s must be %(op)s %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be <= %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be >= %(boundary)s"
+msgid "value %(KEY-value)s must be %(KEY-op)s %(KEY-boundary)s"
 msgstr ""
 
 msgid "value associated to this key is not editable manually"
 msgstr ""
 
 #, python-format
-msgid "value should have maximum size of %s but found %s"
+msgid "value should have maximum size of %(KEY-max)s but found %(KEY-size)s"
 msgstr ""
 
 #, python-format
-msgid "value should have minimum size of %s but found %s"
+msgid "value should have minimum size of %(KEY-min)s but found %(KEY-size)s"
 msgstr ""
 
 msgid "vcard"
--- a/i18n/es.po	Tue Mar 19 16:56:46 2013 +0100
+++ b/i18n/es.po	Wed Mar 20 17:40:25 2013 +0100
@@ -51,6 +51,14 @@
 "\"role=subject\" o \"role=object\" debe ser especificado en las opciones"
 
 #, python-format
+msgid "%(KEY-cstr)s constraint failed for value %(KEY-value)r"
+msgstr ""
+
+#, python-format
+msgid "%(KEY-value)r doesn't match the %(KEY-regexp)r regular expression"
+msgstr ""
+
+#, python-format
 msgid "%(attr)s set to %(newvalue)s"
 msgstr "%(attr)s modificado a %(newvalue)s"
 
@@ -59,10 +67,6 @@
 msgstr "%(attr)s modificado de %(oldvalue)s a %(newvalue)s"
 
 #, python-format
-msgid "%(cstr)s constraint failed for value %(value)r"
-msgstr "el valor %(value)r no satisface la condición %(cstr)s"
-
-#, python-format
 msgid "%(etype)s by %(author)s"
 msgstr "%(etype)s por %(author)s"
 
@@ -75,10 +79,6 @@
 msgstr "%(subject)s %(etype)s #%(eid)s (%(login)s)"
 
 #, python-format
-msgid "%(value)r doesn't match the %(regexp)r regular expression"
-msgstr "%(value)r no corresponde a la expresión regular %(regexp)r"
-
-#, python-format
 msgid "%d days"
 msgstr "%d días"
 
@@ -427,6 +427,11 @@
 msgid "Click to sort on this column"
 msgstr ""
 
+msgid ""
+"Configuration of the system source goes to the 'sources' file, not in the "
+"database"
+msgstr ""
+
 #, python-format
 msgid "Created %(etype)s : %(entity)s"
 msgstr ""
@@ -905,6 +910,9 @@
 msgid "UniqueConstraint"
 msgstr "Restricción de Unicidad"
 
+msgid "Unknown source type"
+msgstr ""
+
 msgid "Unreachable objects"
 msgstr "Objetos inaccesibles"
 
@@ -965,6 +973,15 @@
 "Puede realizar cualquiera de las siguientes sustituciones en el contenido de "
 "su email."
 
+msgid "You can't change this relation"
+msgstr ""
+
+msgid "You cannot remove the system source"
+msgstr ""
+
+msgid "You cannot rename the system source"
+msgstr ""
+
 msgid ""
 "You have no access to this view or it can not be used to display the current "
 "data."
@@ -1016,9 +1033,6 @@
 msgid "abstract base class for transitions"
 msgstr "Clase de base abstracta para la transiciones"
 
-msgid "action menu"
-msgstr ""
-
 msgid "action(s) on this selection"
 msgstr "Acción(es) en esta selección"
 
@@ -1403,12 +1417,8 @@
 msgid "can't be deleted"
 msgstr "No puede ser eliminado"
 
-#, python-format
-msgid "can't change the %s attribute"
-msgstr "no puede modificar el atributo %s"
-
-msgid "can't change this relation"
-msgstr "no puede modificar esta relación"
+msgid "can't change this attribute"
+msgstr ""
 
 #, python-format
 msgid "can't connect to source %s, some data may be missing"
@@ -2611,6 +2621,9 @@
 msgid "foaf"
 msgstr "Amigo de un Amigo, FOAF"
 
+msgid "focus on this selection"
+msgstr ""
+
 msgid "follow"
 msgstr "Seguir la liga"
 
@@ -2901,8 +2914,8 @@
 msgstr "Valor del Captcha incorrecto"
 
 #, python-format
-msgid "incorrect value (%(value)s) for type \"%(type)s\""
-msgstr "valor %(value)s incorrecto para el tipo \"%(type)s\""
+msgid "incorrect value (%(KEY-value)r) for type \"%(KEY-type)s\""
+msgstr ""
 
 msgid "index this attribute's value in the plain text index"
 msgstr "Indexar el valor de este atributo en el índice de texto simple"
@@ -2981,8 +2994,8 @@
 msgstr "Acción %r invalida"
 
 #, python-format
-msgid "invalid value %(value)s, it must be one of %(choices)s"
-msgstr "Valor %(value)s incorrecto, debe estar entre %(choices)s"
+msgid "invalid value %(KEY-value)s, it must be one of %(KEY-choices)s"
+msgstr ""
 
 msgid "is"
 msgstr "es"
@@ -4224,6 +4237,9 @@
 msgid "toggle check boxes"
 msgstr "Cambiar valor"
 
+msgid "toggle filter"
+msgstr "esconder/mostrar el filtro"
+
 msgid "tr_count"
 msgstr "n° de transición"
 
@@ -4366,9 +4382,6 @@
 msgid "unknown property key %s"
 msgstr "Clave de Propiedad desconocida: %s"
 
-msgid "unknown source type"
-msgstr "tipo de fuente desconocida"
-
 msgid "unknown vocabulary:"
 msgstr "Vocabulario desconocido: "
 
@@ -4514,26 +4527,18 @@
 msgstr "Vampr"
 
 #, python-format
-msgid "value %(value)s must be %(op)s %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be <= %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be >= %(boundary)s"
+msgid "value %(KEY-value)s must be %(KEY-op)s %(KEY-boundary)s"
 msgstr ""
 
 msgid "value associated to this key is not editable manually"
 msgstr "El valor asociado a este elemento no es editable manualmente"
 
 #, python-format
-msgid "value should have maximum size of %s but found %s"
+msgid "value should have maximum size of %(KEY-max)s but found %(KEY-size)s"
 msgstr ""
 
 #, python-format
-msgid "value should have minimum size of %s but found %s"
+msgid "value should have minimum size of %(KEY-min)s but found %(KEY-size)s"
 msgstr ""
 
 msgid "vcard"
@@ -4681,6 +4686,12 @@
 "usted debe  quitar la puesta en línea de la relación %s que es aceptada y "
 "puede ser cruzada"
 
+#~ msgid "%(cstr)s constraint failed for value %(value)r"
+#~ msgstr "el valor %(value)r no satisface la condición %(cstr)s"
+
+#~ msgid "%(value)r doesn't match the %(regexp)r regular expression"
+#~ msgstr "%(value)r no corresponde a la expresión regular %(regexp)r"
+
 #~ msgid ""
 #~ "Can't restore relation %(rtype)s of entity %(eid)s, this relation does "
 #~ "not exists anymore in the schema."
@@ -4688,17 +4699,17 @@
 #~ "No puede restaurar la relación %(rtype)s de la entidad %(eid)s, esta "
 #~ "relación ya no existe en el esquema."
 
-#~ msgid "day"
-#~ msgstr "día"
-
-#~ msgid "log out first"
-#~ msgstr "Desconéctese primero"
-
-#~ msgid "month"
-#~ msgstr "mes"
-
-#~ msgid "today"
-#~ msgstr "hoy"
-
-#~ msgid "week"
-#~ msgstr "sem."
+#~ msgid "can't change the %s attribute"
+#~ msgstr "no puede modificar el atributo %s"
+
+#~ msgid "can't change this relation"
+#~ msgstr "no puede modificar esta relación"
+
+#~ msgid "incorrect value (%(value)s) for type \"%(type)s\""
+#~ msgstr "valor %(value)s incorrecto para el tipo \"%(type)s\""
+
+#~ msgid "invalid value %(value)s, it must be one of %(choices)s"
+#~ msgstr "Valor %(value)s incorrecto, debe estar entre %(choices)s"
+
+#~ msgid "unknown source type"
+#~ msgstr "tipo de fuente desconocida"
--- a/i18n/fr.po	Tue Mar 19 16:56:46 2013 +0100
+++ b/i18n/fr.po	Wed Mar 20 17:40:25 2013 +0100
@@ -50,6 +50,15 @@
 "\"role=subject\" ou \"role=object\" doit être specifié dans les options"
 
 #, python-format
+msgid "%(KEY-cstr)s constraint failed for value %(KEY-value)r"
+msgstr "la valeur %(KEY-value)r ne satisfait pas la contrainte %(KEY-cstr)s"
+
+#, python-format
+msgid "%(KEY-value)r doesn't match the %(KEY-regexp)r regular expression"
+msgstr ""
+"%(KEY-value)r ne correspond pas à l'expression régulière %(KEY-regexp)r"
+
+#, python-format
 msgid "%(attr)s set to %(newvalue)s"
 msgstr "%(attr)s modifié à %(newvalue)s"
 
@@ -58,10 +67,6 @@
 msgstr "%(attr)s modifié de %(oldvalue)s à %(newvalue)s"
 
 #, python-format
-msgid "%(cstr)s constraint failed for value %(value)r"
-msgstr "la valeur %(value)r ne satisfait pas la contrainte %(cstr)s"
-
-#, python-format
 msgid "%(etype)s by %(author)s"
 msgstr "%(etype)s par %(author)s"
 
@@ -74,10 +79,6 @@
 msgstr "%(subject)s %(etype)s #%(eid)s (%(login)s)"
 
 #, python-format
-msgid "%(value)r doesn't match the %(regexp)r regular expression"
-msgstr "%(value)r ne correspond pas à l'expression régulière %(regexp)r"
-
-#, python-format
 msgid "%d days"
 msgstr "%d jours"
 
@@ -427,6 +428,11 @@
 msgid "Click to sort on this column"
 msgstr "Cliquer pour trier sur cette colonne"
 
+msgid ""
+"Configuration of the system source goes to the 'sources' file, not in the "
+"database"
+msgstr ""
+
 #, python-format
 msgid "Created %(etype)s : %(entity)s"
 msgstr "Entité %(etype)s crée : %(entity)s"
@@ -907,6 +913,9 @@
 msgid "UniqueConstraint"
 msgstr "contrainte d'unicité"
 
+msgid "Unknown source type"
+msgstr "Type de source inconnue"
+
 msgid "Unreachable objects"
 msgstr "Objets inaccessibles"
 
@@ -967,6 +976,15 @@
 "Vous pouvez utiliser n'importe quelle substitution parmi la liste suivante "
 "dans le contenu de votre courriel."
 
+msgid "You can't change this relation"
+msgstr "Vous ne pouvez pas modifier cette relation"
+
+msgid "You cannot remove the system source"
+msgstr "Vous ne pouvez pas supprimer la source système"
+
+msgid "You cannot rename the system source"
+msgstr "Vous ne pouvez pas renommer la source système"
+
 msgid ""
 "You have no access to this view or it can not be used to display the current "
 "data."
@@ -1018,9 +1036,6 @@
 msgid "abstract base class for transitions"
 msgstr "classe de base abstraite pour les transitions"
 
-msgid "action menu"
-msgstr "actions"
-
 msgid "action(s) on this selection"
 msgstr "action(s) sur cette sélection"
 
@@ -1260,7 +1275,7 @@
 msgstr "anonyme"
 
 msgid "anyrsetview"
-msgstr "vues \"tous les rset\""
+msgstr "vues pour tout rset"
 
 msgid "april"
 msgstr "avril"
@@ -1311,7 +1326,7 @@
 msgstr "mauvaise valeur"
 
 msgid "badly formatted url"
-msgstr ""
+msgstr "URL mal formattée"
 
 msgid "base url"
 msgstr "url de base"
@@ -1399,7 +1414,7 @@
 msgstr "impossible d'interpréter les types d'entités :"
 
 msgid "can only have one url"
-msgstr ""
+msgstr "ne supporte qu'une seule URL"
 
 msgid "can't be changed"
 msgstr "ne peut-être modifié"
@@ -1407,12 +1422,8 @@
 msgid "can't be deleted"
 msgstr "ne peut-être supprimé"
 
-#, python-format
-msgid "can't change the %s attribute"
-msgstr "ne peut changer l'attribut %s"
-
-msgid "can't change this relation"
-msgstr "ne peut modifier cette relation"
+msgid "can't change this attribute"
+msgstr "cet attribut ne peut pas être modifié"
 
 #, python-format
 msgid "can't connect to source %s, some data may be missing"
@@ -2621,6 +2632,9 @@
 msgid "foaf"
 msgstr "foaf"
 
+msgid "focus on this selection"
+msgstr "afficher cette sélection"
+
 msgid "follow"
 msgstr "suivre le lien"
 
@@ -2909,8 +2923,8 @@
 msgstr "valeur de captcha incorrecte"
 
 #, python-format
-msgid "incorrect value (%(value)s) for type \"%(type)s\""
-msgstr "valeur %(value)s incorrecte pour le type \"%(type)s\""
+msgid "incorrect value (%(KEY-value)r) for type \"%(KEY-type)s\""
+msgstr "la valeur %(KEY-value)s est incorrecte pour le type \"%(KEY-type)s\""
 
 msgid "index this attribute's value in the plain text index"
 msgstr "indexer la valeur de cet attribut dans l'index plein texte"
@@ -2989,8 +3003,9 @@
 msgstr "action %r invalide"
 
 #, python-format
-msgid "invalid value %(value)s, it must be one of %(choices)s"
-msgstr "valeur %(value)s incorrect, doit être parmi %(choices)s"
+msgid "invalid value %(KEY-value)s, it must be one of %(KEY-choices)s"
+msgstr ""
+"la valeur %(KEY-value)s est incorrecte, elle doit être parmi %(KEY-choices)s"
 
 msgid "is"
 msgstr "de type"
@@ -4235,7 +4250,10 @@
 msgstr "à faire par"
 
 msgid "toggle check boxes"
-msgstr "inverser les cases à cocher"
+msgstr "afficher/masquer les cases à cocher"
+
+msgid "toggle filter"
+msgstr "afficher/masquer le filtre"
 
 msgid "tr_count"
 msgstr "n° de transition"
@@ -4379,14 +4397,11 @@
 msgid "unknown property key %s"
 msgstr "clé de propriété inconnue : %s"
 
-msgid "unknown source type"
-msgstr "type de source inconnu"
-
 msgid "unknown vocabulary:"
 msgstr "vocabulaire inconnu : "
 
 msgid "unsupported protocol"
-msgstr ""
+msgstr "protocole non supporté"
 
 msgid "upassword"
 msgstr "mot de passe"
@@ -4525,27 +4540,23 @@
 msgstr "valeur"
 
 #, python-format
-msgid "value %(value)s must be %(op)s %(boundary)s"
-msgstr "la valeur %(value)s doit être %(op)s %(boundary)s"
-
-#, python-format
-msgid "value %(value)s must be <= %(boundary)s"
-msgstr "la valeur %(value)s doit être <= %(boundary)s"
-
-#, python-format
-msgid "value %(value)s must be >= %(boundary)s"
-msgstr "la valeur %(value)s doit être >= %(boundary)s"
+msgid "value %(KEY-value)s must be %(KEY-op)s %(KEY-boundary)s"
+msgstr "la valeur %(KEY-value)s n'est pas %(KEY-op)s %(KEY-boundary)s"
 
 msgid "value associated to this key is not editable manually"
 msgstr "la valeur associée à cette clé n'est pas éditable manuellement"
 
 #, python-format
-msgid "value should have maximum size of %s but found %s"
-msgstr "la taille maximum est %s mais cette valeur est de taille %s"
+msgid "value should have maximum size of %(KEY-max)s but found %(KEY-size)s"
+msgstr ""
+"la taille maximum est %(KEY-max)s  mais cette valeur est de taille "
+"%(KEY-size)s"
 
 #, python-format
-msgid "value should have minimum size of %s but found %s"
-msgstr "la taille minimum est %s mais cette valeur est de taille %s"
+msgid "value should have minimum size of %(KEY-min)s but found %(KEY-size)s"
+msgstr ""
+"la taille minimum est %(KEY-min)s  mais cette valeur est de taille "
+"%(KEY-size)s"
 
 msgid "vcard"
 msgstr "vcard"
@@ -4699,6 +4710,9 @@
 #~ msgid "day"
 #~ msgstr "jour"
 
+#~ msgid "jump to selection"
+#~ msgstr "afficher cette sélection"
+
 #~ msgid "log out first"
 #~ msgstr "déconnecter vous d'abord"
 
--- a/mail.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/mail.py	Wed Mar 20 17:40:25 2013 +0100
@@ -212,7 +212,7 @@
                 subject = self.subject()
             except SkipEmail:
                 continue
-            except Exception, ex:
+            except Exception as ex:
                 # shouldn't make the whole transaction fail because of rendering
                 # error (unauthorized or such) XXX check it doesn't actually
                 # occurs due to rollback on such error
--- a/misc/cwdesklets/rqlsensor/__init__.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/misc/cwdesklets/rqlsensor/__init__.py	Wed Mar 20 17:40:25 2013 +0100
@@ -104,7 +104,7 @@
         output = self._new_output()
         try:
             self.__run_query(output)
-        except Exception, ex:
+        except Exception as ex:
             import traceback
             traceback.print_exc()
             output.set('layout', 'vertical, 10')
--- a/misc/migration/3.10.0_Any.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/misc/migration/3.10.0_Any.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,5 +1,3 @@
-from __future__ import with_statement
-
 from cubicweb.server.session import hooks_control
 
 for uri, cfg in config.sources().items():
@@ -34,5 +32,5 @@
 for x in rql('Any X,XK WHERE X pkey XK, '
              'X pkey ~= "boxes.%" OR '
              'X pkey ~= "contentnavigation.%"').entities():
-    x.set_attributes(pkey=u'ctxcomponents.' + x.pkey.split('.', 1)[1])
+    x.cw_set(pkey=u'ctxcomponents.' + x.pkey.split('.', 1)[1])
 
--- a/misc/migration/3.10.9_Any.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/misc/migration/3.10.9_Any.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,7 +1,5 @@
-from __future__ import with_statement
 import sys
 
-
 if confirm('fix some corrupted entities noticed on several instances?'):
     rql('DELETE CWConstraint X WHERE NOT E constrained_by X')
     rql('SET X is_instance_of Y WHERE X is Y, NOT X is_instance_of Y')
--- a/misc/migration/3.11.0_Any.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/misc/migration/3.11.0_Any.py	Wed Mar 20 17:40:25 2013 +0100
@@ -46,7 +46,7 @@
                    '%s relation should not be in support_relations' % rtype
         return mapping
     # for now, only pyrorql sources have a mapping
-    for source in repo.sources_by_uri.values():
+    for source in repo.sources_by_uri.itervalues():
         if not isinstance(source, PyroRQLSource):
             continue
         sourceentity = session.entity_from_eid(source.eid)
@@ -81,5 +81,5 @@
         rset = session.execute('Any V WHERE X is CWProperty, X value V, X pkey %(k)s',
                                {'k': pkey})
         timestamp = int(rset[0][0])
-        sourceentity.set_attributes(latest_retrieval=datetime.fromtimestamp(timestamp))
+        sourceentity.cw_set(latest_retrieval=datetime.fromtimestamp(timestamp))
         session.execute('DELETE CWProperty X WHERE X pkey %(k)s', {'k': pkey})
--- a/misc/migration/3.14.0_Any.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/misc/migration/3.14.0_Any.py	Wed Mar 20 17:40:25 2013 +0100
@@ -9,5 +9,5 @@
     expression = rqlcstr.value
     mainvars = guess_rrqlexpr_mainvars(expression)
     yamscstr = CONSTRAINTS[rqlcstr.type](expression, mainvars)
-    rqlcstr.set_attributes(value=yamscstr.serialize())
+    rqlcstr.cw_set(value=yamscstr.serialize())
     print 'updated', rqlcstr.type, rqlcstr.value.strip()
--- a/misc/migration/3.15.0_Any.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/misc/migration/3.15.0_Any.py	Wed Mar 20 17:40:25 2013 +0100
@@ -4,7 +4,7 @@
     config = source.dictconfig
     host = config.pop('host', u'ldap')
     protocol = config.pop('protocol', u'ldap')
-    source.set_attributes(url=u'%s://%s' % (protocol, host))
+    source.cw_set(url=u'%s://%s' % (protocol, host))
     source.update_config(skip_unknown=True, **config)
 
 commit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.16.0_Any.py	Wed Mar 20 17:40:25 2013 +0100
@@ -0,0 +1,13 @@
+sync_schema_props_perms('EmailAddress')
+
+for source in rql('CWSource X WHERE X type "pyrorql"').entities():
+    sconfig = source.dictconfig
+    nsid = sconfig.pop('pyro-ns-id', config.appid)
+    nshost = sconfig.pop('pyro-ns-host', '')
+    nsgroup = sconfig.pop('pyro-ns-group', ':cubicweb')
+    if nsgroup:
+        nsgroup += '.'
+    source.cw_set(url=u'pyro://%s/%s%s' % (nshost, nsgroup, nsid))
+    source.update_config(skip_unknown=True, **sconfig)
+
+commit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.16.1_Any.py	Wed Mar 20 17:40:25 2013 +0100
@@ -0,0 +1,2 @@
+sync_schema_props_perms(('State', 'state_of', 'Workflow'), commit=False)
+sync_schema_props_perms(('State', 'name', 'String'))
--- a/misc/migration/bootstrapmigration_repository.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/misc/migration/bootstrapmigration_repository.py	Wed Mar 20 17:40:25 2013 +0100
@@ -19,7 +19,6 @@
 
 it should only include low level schema changes
 """
-from __future__ import with_statement
 
 from cubicweb.server.session import hooks_control
 from cubicweb.server import schemaserial as ss
@@ -77,7 +76,7 @@
     with hooks_control(session, session.HOOKS_ALLOW_ALL, 'integrity'):
         for rschema in repo.schema.relations():
             rpermsdict = permsdict.get(rschema.eid, {})
-            for rdef in rschema.rdefs.values():
+            for rdef in rschema.rdefs.itervalues():
                 for action in rdef.ACTIONS:
                     actperms = []
                     for something in rpermsdict.get(action == 'update' and 'add' or action, ()):
--- a/misc/migration/postcreate.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/misc/migration/postcreate.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/misc/scripts/chpasswd.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/misc/scripts/chpasswd.py	Wed Mar 20 17:40:25 2013 +0100
@@ -42,7 +42,7 @@
 crypted = crypt_password(pass1)
 
 cwuser = rset.get_entity(0,0)
-cwuser.set_attributes(upassword=Binary(crypted))
+cwuser.cw_set(upassword=Binary(crypted))
 commit()
 
 print("password updated.")
--- a/misc/scripts/ldapuser2ldapfeed.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/misc/scripts/ldapuser2ldapfeed.py	Wed Mar 20 17:40:25 2013 +0100
@@ -87,7 +87,7 @@
 
 
 source_ent = rql('CWSource S WHERE S eid %(s)s', {'s': source.eid}).get_entity(0, 0)
-source_ent.set_attributes(type=u"ldapfeed", parser=u"ldapfeed")
+source_ent.cw_set(type=u"ldapfeed", parser=u"ldapfeed")
 
 
 if raw_input('Commit ?') in 'yY':
--- a/misc/scripts/pyroforge2datafeed.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/misc/scripts/pyroforge2datafeed.py	Wed Mar 20 17:40:25 2013 +0100
@@ -74,7 +74,7 @@
 
 # only cleanup entities table, remaining stuff should be cleaned by a c-c
 # db-check to be run after this script
-for entities in todelete.values():
+for entities in todelete.itervalues():
     system_source.delete_info_multi(session, entities, source_name)
 
 
@@ -87,7 +87,7 @@
     if schemaent.__regid__ != 'CWEType':
         assert schemaent.__regid__ == 'CWRType'
         sch = schema._eid_index[schemaent.eid]
-        for rdef in sch.rdefs.values():
+        for rdef in sch.rdefs.itervalues():
             if not source.support_entity(rdef.subject) \
                     or not source.support_entity(rdef.object):
                 continue
--- a/misc/scripts/repair_file_1-9_migration.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/misc/scripts/repair_file_1-9_migration.py	Wed Mar 20 17:40:25 2013 +0100
@@ -4,7 +4,6 @@
 * on our intranet on July 07 2010
 * on our extranet on July 16 2010
 """
-from __future__ import with_statement
 
 try:
     backupinstance, = __args__
--- a/predicates.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/predicates.py	Wed Mar 20 17:40:25 2013 +0100
@@ -737,12 +737,16 @@
     See :class:`~cubicweb.predicates.EClassPredicate` documentation for entity
     class lookup / score rules according to the input context.
 
-    .. note:: when interface is an entity class, the score will reflect class
-              proximity so the most specific object will be selected.
+    .. note::
+
+       when interface is an entity class, the score will reflect class
+       proximity so the most specific object will be selected.
 
-    .. note:: deprecated in cubicweb >= 3.9, use either
-              :class:`~cubicweb.predicates.is_instance` or
-              :class:`~cubicweb.predicates.adaptable`.
+    .. note::
+
+       deprecated in cubicweb >= 3.9, use either
+       :class:`~cubicweb.predicates.is_instance` or
+       :class:`~cubicweb.predicates.adaptable`.
     """
 
     def __init__(self, *expected_ifaces, **kwargs):
--- a/pylintext.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/pylintext.py	Wed Mar 20 17:40:25 2013 +0100
@@ -17,7 +17,7 @@
 def cubicweb_transform(module):
     # handle objectify_predicate decorator (and its former name until bw compat
     # is kept). Only look at module level functions, should be enough.
-    for assnodes in module.locals.values():
+    for assnodes in module.locals.itervalues():
         for node in assnodes:
             if isinstance(node, scoped_nodes.Function) and node.decorators:
                 for decorator in node.decorators.nodes:
--- a/req.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/req.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -62,6 +62,8 @@
     :attribute vreg.schema: the instance's schema
     :attribute vreg.config: the instance's configuration
     """
+    is_request = True # False for repository session
+
     def __init__(self, vreg):
         self.vreg = vreg
         try:
@@ -75,6 +77,17 @@
         self.local_perm_cache = {}
         self._ = unicode
 
+    def set_language(self, lang):
+        """install i18n configuration for `lang` translation.
+
+        Raises :exc:`KeyError` if translation doesn't exist.
+        """
+        self.lang = lang
+        gettext, pgettext = self.vreg.config.translations[lang]
+        # use _cw.__ to translate a message without registering it to the catalog
+        self._ = self.__ = gettext
+        self.pgettext = pgettext
+
     def get_option_value(self, option, foreid=None):
         raise NotImplementedError
 
@@ -308,21 +321,12 @@
     def user_data(self):
         """returns a dictionary with this user's information"""
         userinfo = {}
-        if self.is_internal_session:
-            userinfo['login'] = "cubicweb"
-            userinfo['name'] = "cubicweb"
-            userinfo['email'] = ""
-            return userinfo
         user = self.user
         userinfo['login'] = user.login
         userinfo['name'] = user.name()
         userinfo['email'] = user.cw_adapt_to('IEmailable').get_email()
         return userinfo
 
-    def is_internal_session(self):
-        """overrided on the server-side"""
-        return False
-
     # formating methods #######################################################
 
     def view(self, __vid, rset=None, __fallback_oid=None, __registry='views',
@@ -426,7 +430,7 @@
         """return the root url of the instance
         """
         if secure:
-            raise NotImplementedError()
+            return self.vreg.config.get('https-url', self.vreg.config['base-url'])
         return self.vreg.config['base-url']
 
     # abstract methods to override according to the web front-end #############
--- a/rqlrewrite.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/rqlrewrite.py	Wed Mar 20 17:40:25 2013 +0100
@@ -20,8 +20,6 @@
 
 This is used for instance for read security checking in the repository.
 """
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 
 from rql import nodes as n, stmts, TypeResolverException
--- a/rset.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/rset.py	Wed Mar 20 17:40:25 2013 +0100
@@ -642,7 +642,7 @@
                 try:
                     entity = self.get_entity(row, index)
                     return entity, rel.r_type
-                except NotAnEntity, exc:
+                except NotAnEntity as exc:
                     return None, None
         return None, None
 
--- a/rtags.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/rtags.py	Wed Mar 20 17:40:25 2013 +0100
@@ -38,17 +38,20 @@
 __docformat__ = "restructuredtext en"
 
 import logging
+from warnings import warn
 
 from logilab.common.logging_ext import set_log_methods
-
-RTAGS = []
-def register_rtag(rtag):
-    RTAGS.append(rtag)
+from logilab.common.registry import RegistrableInstance, yes
 
 def _ensure_str_key(key):
     return tuple(str(k) for k in key)
 
-class RelationTags(object):
+class RegistrableRtags(RegistrableInstance):
+    __registry__ = 'uicfg'
+    __select__ = yes()
+
+
+class RelationTags(RegistrableRtags):
     """a tag store for full relation definitions :
 
          (subject type, relation type, object type, tagged)
@@ -58,18 +61,17 @@
     This class associates a single tag to each key.
     """
     _allowed_values = None
-    _initfunc = None
-    def __init__(self, name=None, initfunc=None, allowed_values=None):
-        self._name = name or '<unknown>'
+    # _init expected to be a method (introduced in 3.17), while _initfunc a
+    # function given as __init__ argument and kept for bw compat
+    _init = _initfunc = None
+
+    def __init__(self):
         self._tagdefs = {}
-        if allowed_values is not None:
-            self._allowed_values = allowed_values
-        if initfunc is not None:
-            self._initfunc = initfunc
-        register_rtag(self)
 
     def __repr__(self):
-        return '%s: %s' % (self._name, repr(self._tagdefs))
+        # find a way to have more infos but keep it readable
+        # (in error messages in case of an ambiguity for instance)
+        return '%s (%s): %s' % (id(self), self.__regid__, self.__class__)
 
     # dict compat
     def __getitem__(self, key):
@@ -100,8 +102,8 @@
                                      (stype, rtype, otype, tagged), value, ertype)
                         self.del_rtag(stype, rtype, otype, tagged)
                         break
-        if self._initfunc is not None:
-            self.apply(schema, self._initfunc)
+        if self._init is not None:
+            self.apply(schema, self._init)
 
     def apply(self, schema, func):
         for eschema in schema.entities():
@@ -113,7 +115,7 @@
                         sschema, oschema = eschema, tschema
                     else:
                         sschema, oschema = tschema, eschema
-                    func(self, sschema, rschema, oschema, role)
+                    func(sschema, rschema, oschema, role)
 
     # rtag declaration api ####################################################
 
@@ -142,6 +144,17 @@
         self._tagdefs[_ensure_str_key(key)] = tag
         return tag
 
+    def _tag_etype_attr(self, etype, attr, desttype='*', *args, **kwargs):
+        if isinstance(attr, basestring):
+            attr, role = attr, 'subject'
+        else:
+            attr, role = attr
+        if role == 'subject':
+            self.tag_subject_of((etype, attr, desttype), *args, **kwargs)
+        else:
+            self.tag_object_of((desttype, attr, etype), *args, **kwargs)
+
+
     # rtag runtime api ########################################################
 
     def del_rtag(self, *key):
@@ -250,4 +263,6 @@
                 key = list(key)
             key[0] = '*'
         super(NoTargetRelationTagsDict, self).tag_relation(key, tag)
+
+
 set_log_methods(RelationTags, logging.getLogger('cubicweb.rtags'))
--- a/schema.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/schema.py	Wed Mar 20 17:40:25 2013 +0100
@@ -261,30 +261,34 @@
     return self.has_local_role(action) or self.has_perm(req, action)
 PermissionMixIn.may_have_permission = may_have_permission
 
-def has_perm(self, session, action, **kwargs):
+def has_perm(self, _cw, action, **kwargs):
     """return true if the action is granted globaly or localy"""
     try:
-        self.check_perm(session, action, **kwargs)
+        self.check_perm(_cw, action, **kwargs)
         return True
     except Unauthorized:
         return False
 PermissionMixIn.has_perm = has_perm
 
-def check_perm(self, session, action, **kwargs):
-    # NB: session may be a server session or a request object check user is
-    # in an allowed group, if so that's enough internal sessions should
-    # always stop there
+def check_perm(self, _cw, action, **kwargs):
+    # NB: _cw may be a server transaction or a request object.
+    #
+    # check user is in an allowed group, if so that's enough internal
+    # transactions should always stop there
     groups = self.get_groups(action)
-    if session.user.matching_groups(groups):
+    if _cw.user.matching_groups(groups):
         return
     # if 'owners' in allowed groups, check if the user actually owns this
     # object, if so that's enough
+    #
+    # NB: give _cw to user.owns since user is not be bound to a transaction on
+    # the repository side
     if 'owners' in groups and (
           kwargs.get('creating')
-          or ('eid' in kwargs and session.user.owns(kwargs['eid']))):
+          or ('eid' in kwargs and _cw.user.owns(kwargs['eid']))):
         return
     # else if there is some rql expressions, check them
-    if any(rqlexpr.check(session, **kwargs)
+    if any(rqlexpr.check(_cw, **kwargs)
            for rqlexpr in self.get_rqlexprs(action)):
         return
     raise Unauthorized(action, str(self))
@@ -467,45 +471,45 @@
                     return True
         return False
 
-    def has_perm(self, session, action, **kwargs):
+    def has_perm(self, _cw, action, **kwargs):
         """return true if the action is granted globaly or localy"""
         if self.final:
             assert not ('fromeid' in kwargs or 'toeid' in kwargs), kwargs
             assert action in ('read', 'update')
             if 'eid' in kwargs:
-                subjtype = session.describe(kwargs['eid'])[0]
+                subjtype = _cw.describe(kwargs['eid'])[0]
             else:
                 subjtype = objtype = None
         else:
             assert not 'eid' in kwargs, kwargs
             assert action in ('read', 'add', 'delete')
             if 'fromeid' in kwargs:
-                subjtype = session.describe(kwargs['fromeid'])[0]
+                subjtype = _cw.describe(kwargs['fromeid'])[0]
             elif 'frometype' in kwargs:
                 subjtype = kwargs.pop('frometype')
             else:
                 subjtype = None
             if 'toeid' in kwargs:
-                objtype = session.describe(kwargs['toeid'])[0]
+                objtype = _cw.describe(kwargs['toeid'])[0]
             elif 'toetype' in kwargs:
                 objtype = kwargs.pop('toetype')
             else:
                 objtype = None
         if objtype and subjtype:
-            return self.rdef(subjtype, objtype).has_perm(session, action, **kwargs)
+            return self.rdef(subjtype, objtype).has_perm(_cw, action, **kwargs)
         elif subjtype:
             for tschema in self.targets(subjtype, 'subject'):
                 rdef = self.rdef(subjtype, tschema)
-                if not rdef.has_perm(session, action, **kwargs):
+                if not rdef.has_perm(_cw, action, **kwargs):
                     return False
         elif objtype:
             for tschema in self.targets(objtype, 'object'):
                 rdef = self.rdef(tschema, objtype)
-                if not rdef.has_perm(session, action, **kwargs):
+                if not rdef.has_perm(_cw, action, **kwargs):
                     return False
         else:
             for rdef in self.rdefs.itervalues():
-                if not rdef.has_perm(session, action, **kwargs):
+                if not rdef.has_perm(_cw, action, **kwargs):
                     return False
         return True
 
@@ -754,17 +758,17 @@
             return rql, found, keyarg
         return rqlst.as_string(), None, None
 
-    def _check(self, session, **kwargs):
+    def _check(self, _cw, **kwargs):
         """return True if the rql expression is matching the given relation
         between fromeid and toeid
 
-        session may actually be a request as well
+        _cw may be a request or a server side transaction
         """
         creating = kwargs.get('creating')
         if not creating and self.eid is not None:
             key = (self.eid, tuple(sorted(kwargs.iteritems())))
             try:
-                return session.local_perm_cache[key]
+                return _cw.local_perm_cache[key]
             except KeyError:
                 pass
         rql, has_perm_defs, keyarg = self.transform_has_permission()
@@ -772,50 +776,50 @@
         if creating and 'X' in self.rqlst.defined_vars:
             return True
         if keyarg is None:
-            kwargs.setdefault('u', session.user.eid)
+            kwargs.setdefault('u', _cw.user.eid)
             try:
-                rset = session.execute(rql, kwargs, build_descr=True)
+                rset = _cw.execute(rql, kwargs, build_descr=True)
             except NotImplementedError:
                 self.critical('cant check rql expression, unsupported rql %s', rql)
                 if self.eid is not None:
-                    session.local_perm_cache[key] = False
+                    _cw.local_perm_cache[key] = False
                 return False
-            except TypeResolverException, ex:
+            except TypeResolverException as ex:
                 # some expression may not be resolvable with current kwargs
                 # (type conflict)
                 self.warning('%s: %s', rql, str(ex))
                 if self.eid is not None:
-                    session.local_perm_cache[key] = False
+                    _cw.local_perm_cache[key] = False
                 return False
-            except Unauthorized, ex:
+            except Unauthorized as ex:
                 self.debug('unauthorized %s: %s', rql, str(ex))
                 if self.eid is not None:
-                    session.local_perm_cache[key] = False
+                    _cw.local_perm_cache[key] = False
                 return False
         else:
-            rset = session.eid_rset(kwargs[keyarg])
+            rset = _cw.eid_rset(kwargs[keyarg])
         # if no special has_*_permission relation in the rql expression, just
         # check the result set contains something
         if has_perm_defs is None:
             if rset:
                 if self.eid is not None:
-                    session.local_perm_cache[key] = True
+                    _cw.local_perm_cache[key] = True
                 return True
         elif rset:
             # check every special has_*_permission relation is satisfied
-            get_eschema = session.vreg.schema.eschema
+            get_eschema = _cw.vreg.schema.eschema
             try:
                 for eaction, col in has_perm_defs:
                     for i in xrange(len(rset)):
                         eschema = get_eschema(rset.description[i][col])
-                        eschema.check_perm(session, eaction, eid=rset[i][col])
+                        eschema.check_perm(_cw, eaction, eid=rset[i][col])
                 if self.eid is not None:
-                    session.local_perm_cache[key] = True
+                    _cw.local_perm_cache[key] = True
                 return True
             except Unauthorized:
                 pass
         if self.eid is not None:
-            session.local_perm_cache[key] = False
+            _cw.local_perm_cache[key] = False
         return False
 
     @property
@@ -843,15 +847,15 @@
             rql += ', U eid %(u)s'
         return rql
 
-    def check(self, session, eid=None, creating=False, **kwargs):
+    def check(self, _cw, eid=None, creating=False, **kwargs):
         if 'X' in self.rqlst.defined_vars:
             if eid is None:
                 if creating:
-                    return self._check(session, creating=True, **kwargs)
+                    return self._check(_cw, creating=True, **kwargs)
                 return False
             assert creating == False
-            return self._check(session, x=eid, **kwargs)
-        return self._check(session, **kwargs)
+            return self._check(_cw, x=eid, **kwargs)
+        return self._check(_cw, **kwargs)
 
 
 def vargraph(rqlst):
@@ -904,7 +908,7 @@
             rql += ', U eid %(u)s'
         return rql
 
-    def check(self, session, fromeid=None, toeid=None):
+    def check(self, _cw, fromeid=None, toeid=None):
         kwargs = {}
         if 'S' in self.rqlst.defined_vars:
             if fromeid is None:
@@ -914,7 +918,7 @@
             if toeid is None:
                 return False
             kwargs['o'] = toeid
-        return self._check(session, **kwargs)
+        return self._check(_cw, **kwargs)
 
 
 # in yams, default 'update' perm for attributes granted to managers and owners.
@@ -1024,7 +1028,7 @@
                     'expression': self.expression}
             raise ValidationError(maineid, {qname: msg})
 
-    def exec_query(self, session, eidfrom, eidto):
+    def exec_query(self, _cw, eidfrom, eidto):
         if eidto is None:
             # checking constraint for an attribute relation
             expression = 'S eid %(s)s, ' + self.expression
@@ -1034,11 +1038,11 @@
             args = {'s': eidfrom, 'o': eidto}
         if 'U' in self.rqlst.defined_vars:
             expression = 'U eid %(u)s, ' + expression
-            args['u'] = session.user.eid
+            args['u'] = _cw.user.eid
         rql = 'Any %s WHERE %s' % (','.join(sorted(self.mainvars)), expression)
         if self.distinct_query:
             rql = 'DISTINCT ' + rql
-        return session.execute(rql, args, build_descr=False)
+        return _cw.execute(rql, args, build_descr=False)
 
 
 class RQLConstraint(RepoEnforcedRQLConstraintMixIn, RQLVocabularyConstraint):
@@ -1061,7 +1065,7 @@
     """
     # XXX turns mainvars into a required argument in __init__
     distinct_query = True
-
+ 
     def match_condition(self, session, eidfrom, eidto):
         return len(self.exec_query(session, eidfrom, eidto)) <= 1
 
--- a/server/__init__.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/__init__.py	Wed Mar 20 17:40:25 2013 +0100
@@ -21,8 +21,6 @@
 The server module contains functions to initialize a new repository.
 """
 
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 
 import sys
@@ -77,10 +75,14 @@
 DBG_REPO = 4
 #: multi-sources
 DBG_MS   = 8
+#: hooks
+DBG_HOOKS = 16
+#: operations
+DBG_OPS = 32
 #: more verbosity
-DBG_MORE = 16
+DBG_MORE = 64
 #: all level enabled
-DBG_ALL  = DBG_RQL + DBG_SQL + DBG_REPO + DBG_MS + DBG_MORE
+DBG_ALL  = DBG_RQL + DBG_SQL + DBG_REPO + DBG_MS + DBG_HOOKS + DBG_OPS + DBG_MORE
 
 #: current debug mode
 DEBUG = 0
@@ -165,6 +167,8 @@
     # on connection
     config.creating = True
     config.consider_user_state = False
+    config.cubicweb_appobject_path = set(('hooks', 'entities'))
+    config.cube_appobject_path = set(('hooks', 'entities'))
     # only enable the system source at initialization time
     repo = Repository(config, vreg=vreg)
     schema = repo.schema
@@ -179,7 +183,7 @@
         dropsql = sqldropschema(schema, driver)
         try:
             sqlexec(dropsql, execute, pbtitle=_title)
-        except Exception, ex:
+        except Exception as ex:
             print '-> drop failed, skipped (%s).' % ex
             sqlcnx.rollback()
     _title = '-> creating tables '
@@ -224,10 +228,6 @@
     config._cubes = None # avoid assertion error
     repo, cnx = in_memory_repo_cnx(config, login, password=pwd)
     repo.system_source.eid = ssource.eid # redo this manually
-    # trigger vreg initialisation of entity classes
-    config.cubicweb_appobject_path = set(('entities',))
-    config.cube_appobject_path = set(('entities',))
-    repo.vreg.set_schema(repo.schema)
     assert len(repo.sources) == 1, repo.sources
     handler = config.migration_handler(schema, interactive=False,
                                        cnx=cnx, repo=repo)
@@ -246,20 +246,21 @@
     # restore initial configuration
     config.creating = False
     config.consider_user_state = True
+    # (drop instance attribute to get back to class attribute)
+    del config.cubicweb_appobject_path
+    del config.cube_appobject_path
     print '-> database for instance %s initialized.' % config.appid
 
 
 def initialize_schema(config, schema, mhandler, event='create'):
     from cubicweb.server.schemaserial import serialize_schema
-    from cubicweb.server.session import hooks_control
     session = mhandler.session
     cubes = config.cubes()
     # deactivate every hooks but those responsible to set metadata
     # so, NO INTEGRITY CHECKS are done, to have quicker db creation.
     # Active integrity is kept else we may pb such as two default
     # workflows for one entity type.
-    with hooks_control(session, session.HOOKS_DENY_ALL, 'metadata',
-                       'activeintegrity'):
+    with session.deny_all_hooks_but('metadata', 'activeintegrity'):
         # execute cubicweb's pre<event> script
         mhandler.cmd_exec_event_script('pre%s' % event)
         # execute cubes pre<event> script if any
--- a/server/checkintegrity.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/checkintegrity.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -20,9 +20,6 @@
 * integrity of a CubicWeb repository. Hum actually only the system database is
   checked.
 """
-
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 
 import sys
@@ -32,7 +29,6 @@
 
 from cubicweb.schema import PURE_VIRTUAL_RTYPES, VIRTUAL_RTYPES
 from cubicweb.server.sqlutils import SQL_PREFIX
-from cubicweb.server.session import security_enabled
 
 def notify_fixed(fix):
     if fix:
@@ -289,7 +285,7 @@
             continue
         try:
             cursor = session.system_sql('SELECT eid_from FROM %s_relation;' % rschema)
-        except Exception, ex:
+        except Exception as ex:
             # usually because table doesn't exist
             print 'ERROR', ex
             continue
@@ -321,7 +317,7 @@
             continue
         smandatory = set()
         omandatory = set()
-        for rdef in rschema.rdefs.values():
+        for rdef in rschema.rdefs.itervalues():
             if rdef.cardinality[0] in '1+':
                 smandatory.add(rdef.subject)
             if rdef.cardinality[1] in '1+':
@@ -349,7 +345,7 @@
     for rschema in schema.relations():
         if not rschema.final or rschema in VIRTUAL_RTYPES:
             continue
-        for rdef in rschema.rdefs.values():
+        for rdef in rschema.rdefs.itervalues():
             if rdef.cardinality[0] in '1+':
                 rql = 'Any X WHERE X %s NULL, X is %s, X cw_source S, S name "system"' % (
                     rschema, rdef.subject)
@@ -394,18 +390,18 @@
     # yo, launch checks
     if checks:
         eids_cache = {}
-        with security_enabled(session, read=False, write=False): # ensure no read security
+        with session.security_enabled(read=False, write=False): # ensure no read security
             for check in checks:
                 check_func = globals()['check_%s' % check]
                 check_func(repo.schema, session, eids_cache, fix=fix)
         if fix:
-            cnx.commit()
+            session.commit()
         else:
             print
         if not fix:
             print 'WARNING: Diagnostic run, nothing has been corrected'
     if reindex:
-        cnx.rollback()
+        session.rollback()
         session.set_cnxset()
         reindex_entities(repo.schema, session, withpb=withpb)
-        cnx.commit()
+        session.commit()
--- a/server/cwzmq.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/cwzmq.py	Wed Mar 20 17:40:25 2013 +0100
@@ -31,6 +31,13 @@
 
 ctx = zmq.Context()
 
+def cwproto_to_zmqaddr(address):
+    """ converts a cw-zmq address (like zmqpickle-tcp://<ip>:<port>)
+    into a proper zmq address (tcp://<ip>:<port>)
+    """
+    assert address.startswith('zmqpickle-'), 'bad protocol string %s' % address
+    return address.split('-', 1)[1] # chop the `zmqpickle-` prefix
+
 class ZMQComm(object):
     """
     A simple ZMQ-based notification bus.
@@ -140,7 +147,7 @@
         self.events = []
 
     def connect(self, address):
-        self.address = address
+        self.address = cwproto_to_zmqaddr(address)
 
     def run(self):
         """enter the service loop"""
@@ -214,7 +221,7 @@
             for cmd in cmds:
                 result = self.process_cmd(cmd)
                 self.send_data(result)
-        except Exception, exc:
+        except Exception as exc:
             traceback.print_exc()
             self.send_data(exc)
 
@@ -228,7 +235,7 @@
             self.loop.add_callback(self.loop.stop)
             self.stream.on_recv(None)
             self.stream.close()
-        except Exception, e:
+        except Exception as e:
             print e
             pass
         if shutdown_repo and not self.repo.shutting_down:
--- a/server/edition.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/edition.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -16,9 +16,6 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """helper classes to handle server-side edition of entities"""
-
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 
 from copy import copy
@@ -103,6 +100,8 @@
         assert not self.saved, 'too late to modify edited attributes'
         super(EditedEntity, self).__setitem__(attr, value)
         self.entity.cw_attr_cache[attr] = value
+        # mark attribute as needing purge by the client
+        self.entity._cw_dont_cache_attribute(attr)
 
     def oldnewvalue(self, attr):
         """returns the couple (old attr value, new attr value)
@@ -141,9 +140,8 @@
                          for rtype in self]
         try:
             entity.e_schema.check(dict_protocol_catcher(entity),
-                                  creation=creation, _=entity._cw._,
-                                  relations=relations)
-        except ValidationError, ex:
+                                  creation=creation, relations=relations)
+        except ValidationError as ex:
             ex.entity = self.entity
             raise
 
--- a/server/hook.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/hook.py	Wed Mar 20 17:40:25 2013 +0100
@@ -152,7 +152,7 @@
 
   On those events, the entity has no `cw_edited` dictionary.
 
-.. note:: `self.entity.set_attributes(age=42)` will set the `age` attribute to
+.. note:: `self.entity.cw_set(age=42)` will set the `age` attribute to
   42. But to do so, it will generate a rql query that will have to be processed,
   hence may trigger some hooks, etc. This could lead to infinitely looping hooks.
 
@@ -197,14 +197,12 @@
 ~~~~~~~~~~~~~
 
 It is sometimes convenient to explicitly enable or disable some hooks. For
-instance if you want to disable some integrity checking hook.  This can be
+instance if you want to disable some integrity checking hook. This can be
 controlled more finely through the `category` class attribute, which is a string
 giving a category name.  One can then uses the
-:class:`~cubicweb.server.session.hooks_control` context manager to explicitly
-enable or disable some categories.
-
-.. autoclass:: cubicweb.server.session.hooks_control
-
+:meth:`~cubicweb.server.session.Session.deny_all_hooks_but` and
+:meth:`~cubicweb.server.session.Session.allow_all_hooks_but` context managers to
+explicitly enable or disable some categories.
 
 The existing categories are:
 
@@ -230,10 +228,8 @@
 * ``bookmark``, bookmark entities handling hooks
 
 
-Nothing precludes one to invent new categories and use the
-:class:`~cubicweb.server.session.hooks_control` context manager to
-filter them in or out. Note that ending the transaction with commit()
-or rollback() will restore the hooks.
+Nothing precludes one to invent new categories and use existing mechanisms to
+filter them in or out.
 
 
 Hooks specific predicates
@@ -249,9 +245,6 @@
 .. autoclass:: cubicweb.server.hook.LateOperation
 .. autoclass:: cubicweb.server.hook.DataOperationMixIn
 """
-
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 
 from warnings import warn
@@ -262,13 +255,12 @@
 from logilab.common.deprecation import deprecated, class_renamed
 from logilab.common.logging_ext import set_log_methods
 from logilab.common.registry import (Predicate, NotPredicate, OrPredicate,
-                                     classid, objectify_predicate, yes)
+                                     objectify_predicate, yes)
 
-from cubicweb import RegistryNotFound
+from cubicweb import RegistryNotFound, server
 from cubicweb.cwvreg import CWRegistry, CWRegistryStore
 from cubicweb.predicates import ExpectedValuePredicate, is_instance
 from cubicweb.appobject import AppObject
-from cubicweb.server.session import security_enabled
 
 ENTITIES_HOOKS = set(('before_add_entity',    'after_add_entity',
                       'before_update_entity', 'after_update_entity',
@@ -326,12 +318,15 @@
             pruned = self.get_pruned_hooks(session, event,
                                            entities, eids_from_to, kwargs)
             # by default, hooks are executed with security turned off
-            with security_enabled(session, read=False):
+            with session.security_enabled(read=False):
                 for _kwargs in _iter_kwargs(entities, eids_from_to, kwargs):
                     hooks = sorted(self.filtered_possible_objects(pruned, session, **_kwargs),
                                    key=lambda x: x.order)
-                    with security_enabled(session, write=False):
+                    debug = server.DEBUG & server.DBG_HOOKS
+                    with session.security_enabled(write=False):
                         for hook in hooks:
+                            if debug:
+                                print event, _kwargs, hook
                             hook()
 
     def get_pruned_hooks(self, session, event, entities, eids_from_to, kwargs):
@@ -770,7 +765,7 @@
         """delegate event handling to the opertaion"""
         if event == 'postcommit_event' and hasattr(self, 'commit_event'):
             warn('[3.10] %s: commit_event method has been replaced by postcommit_event'
-                 % classid(self.__class__), DeprecationWarning)
+                 % self.__class__, DeprecationWarning)
             self.commit_event() # pylint: disable=E1101
         getattr(self, event)()
 
--- a/server/ldaputils.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/ldaputils.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -136,6 +136,7 @@
     _conn = None
 
     def _entity_update(self, source_entity):
+        super(LDAPSourceMixIn, self)._entity_update(source_entity)
         if self.urls:
             if len(self.urls) > 1:
                 raise ValidationError(source_entity.eid, {'url': _('can only have one url')})
@@ -150,6 +151,7 @@
         """update configuration from source entity. `typedconfig` is config
         properly typed with defaults set
         """
+        super(LDAPSourceMixIn, self).update_config(source_entity, typedconfig)
         self.authmode = typedconfig['auth-mode']
         self._authenticate = getattr(self, '_auth_%s' % self.authmode)
         self.cnx_dn = typedconfig['data-cnx-dn']
@@ -210,7 +212,7 @@
         # check password by establishing a (unused) connection
         try:
             self._connect(user, password)
-        except ldap.LDAPError, ex:
+        except ldap.LDAPError as ex:
             # Something went wrong, most likely bad credentials
             self.info('while trying to authenticate %s: %s', user, ex)
             raise AuthenticationError()
@@ -303,7 +305,7 @@
             self.info('ldap NO SUCH OBJECT %s %s %s', base, scope, searchstr)
             self._process_no_such_object(session, base)
             return []
-        # except ldap.REFERRAL, e:
+        # except ldap.REFERRAL as e:
         #     cnx = self.handle_referral(e)
         #     try:
         #         res = cnx.search_s(base, scope, searchstr, attrs)
--- a/server/migractions.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/migractions.py	Wed Mar 20 17:40:25 2013 +0100
@@ -26,9 +26,6 @@
 * add an entity
 * execute raw RQL queries
 """
-
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 
 import sys
@@ -56,7 +53,7 @@
                              PURE_VIRTUAL_RTYPES,
                              CubicWebRelationSchema, order_eschemas)
 from cubicweb.cwvreg import CW_EVENT_MANAGER
-from cubicweb.dbapi import get_repository, repo_connect
+from cubicweb.dbapi import get_repository, _repo_connect
 from cubicweb.migration import MigrationHelper, yes
 from cubicweb.server import hook
 try:
@@ -132,7 +129,7 @@
 
     @cached
     def repo_connect(self):
-        self.repo = get_repository(method='inmemory', config=self.config)
+        self.repo = get_repository(config=self.config)
         return self.repo
 
     def cube_upgraded(self, cube, version):
@@ -158,7 +155,7 @@
         try:
             return super(ServerMigrationHelper, self).cmd_process_script(
                   migrscript, funcname, *args, **kwargs)
-        except ExecutionError, err:
+        except ExecutionError as err:
             sys.stderr.write("-> %s\n" % err)
         except BaseException:
             self.rollback()
@@ -196,7 +193,7 @@
             for source in repo.sources:
                 try:
                     source.backup(osp.join(tmpdir, source.uri), self.confirm, format=format)
-                except Exception, ex:
+                except Exception as ex:
                     print '-> error trying to backup %s [%s]' % (source.uri, ex)
                     if not self.confirm('Continue anyway?', default='n'):
                         raise SystemExit(1)
@@ -255,7 +252,7 @@
                 continue
             try:
                 source.restore(osp.join(tmpdir, source.uri), self.confirm, drop, format)
-            except Exception, exc:
+            except Exception as exc:
                 print '-> error trying to restore %s [%s]' % (source.uri, exc)
                 if not self.confirm('Continue anyway?', default='n'):
                     raise SystemExit(1)
@@ -279,7 +276,7 @@
                 login, pwd = manager_userpasswd()
             while True:
                 try:
-                    self._cnx = repo_connect(self.repo, login, password=pwd)
+                    self._cnx = _repo_connect(self.repo, login, password=pwd)
                     if not 'managers' in self._cnx.user(self.session).groups:
                         print 'migration need an account in the managers group'
                     else:
@@ -401,7 +398,7 @@
             try:
                 sqlexec(open(fpath).read(), self.session.system_sql, False,
                         delimiter=';;')
-            except Exception, exc:
+            except Exception as exc:
                 print '-> ERROR:', exc, ', skipping', fpath
 
     # schema synchronization internals ########################################
@@ -460,7 +457,7 @@
                                      {'x': expreid}, ask_confirm=False)
                 else:
                     newexprs.pop(expression)
-            for expression in newexprs.values():
+            for expression in newexprs.itervalues():
                 expr = expression.expression
                 if not confirm or self.confirm('Add %s expression for %s permission of %s?'
                                                % (expr, action, erschema)):
@@ -1321,7 +1318,7 @@
         except Exception:
             self.cmd_create_entity('CWProperty', pkey=unicode(pkey), value=value)
         else:
-            prop.set_attributes(value=value)
+            prop.cw_set(value=value)
 
     # other data migration commands ###########################################
 
@@ -1460,7 +1457,7 @@
             if not ask_confirm or self.confirm('Execute rql: %s ?' % msg):
                 try:
                     res = execute(rql, kwargs, build_descr=build_descr)
-                except Exception, ex:
+                except Exception as ex:
                     if self.confirm('Error: %s\nabort?' % ex, pdb=True):
                         raise
         return res
@@ -1554,7 +1551,7 @@
                 raise StopIteration
         try:
             return self._h._cw.execute(rql, kwargs)
-        except Exception, ex:
+        except Exception as ex:
             if self._h.confirm('Error: %s\nabort?' % ex):
                 raise
             else:
--- a/server/msplanner.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/msplanner.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1075,7 +1075,7 @@
         """return all sources supporting given term / solindices"""
         sources = [selected_source]
         sourcesterms = self._sourcesterms
-        for source in sourcesterms.keys():
+        for source in list(sourcesterms):
             if source is selected_source:
                 continue
             if not (term in sourcesterms[source] and
@@ -1099,9 +1099,9 @@
         # term has to belong to the same scope if there is more
         # than the system source remaining
         if len(sourcesterms) > 1 and not scope is self.rqlst:
-            candidates = (t for t in sourceterms.keys() if scope is ms_scope(t))
+            candidates = (t for t in sourceterms if scope is ms_scope(t))
         else:
-            candidates = sourceterms #.iterkeys()
+            candidates = sourceterms
         # we only want one unlinked term in each generated query
         candidates = [t for t in candidates
                       if isinstance(t, (Constant, Relation)) or
--- a/server/mssteps.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/mssteps.py	Wed Mar 20 17:40:25 2013 +0100
@@ -22,8 +22,6 @@
 * each step has is own members (this is not necessarily bad, but a bit messy
   for now)
 """
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 
 from rql.nodes import VariableRef, Variable, Function
--- a/server/pool.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/pool.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -63,13 +63,13 @@
         # FIXME: what happends if a commit fail
         # would need a two phases commit or like, but I don't know how to do
         # this using the db-api...
-        for source, cnx in self.source_cnxs.values():
+        for source, cnx in self.source_cnxs.itervalues():
             # let exception propagates
             cnx.commit()
 
     def rollback(self):
         """rollback the current transaction for this user"""
-        for source, cnx in self.source_cnxs.values():
+        for source, cnx in self.source_cnxs.itervalues():
             # catch exceptions, rollback other sources anyway
             try:
                 cnx.rollback()
@@ -83,12 +83,12 @@
         """close all connections in the set"""
         if i_know_what_i_do is not True: # unexpected closing safety belt
             raise RuntimeError('connections set shouldn\'t be closed')
-        for cu in self._cursors.values():
+        for cu in self._cursors.itervalues():
             try:
                 cu.close()
             except Exception:
                 continue
-        for _, cnx in self.source_cnxs.values():
+        for _, cnx in self.source_cnxs.itervalues():
             try:
                 cnx.close()
             except Exception:
@@ -102,7 +102,7 @@
 
     def cnxset_freed(self):
         """connections set is being freed from a session"""
-        for source, cnx in self.source_cnxs.values():
+        for source, cnx in self.source_cnxs.itervalues():
             source.cnxset_freed(cnx)
 
     def sources(self):
@@ -114,7 +114,7 @@
             if uri == 'system':
                 continue
             yield source
-        #return [source_cnx[0] for source_cnx in self.source_cnxs.values()]
+        #return [source_cnx[0] for source_cnx in self.source_cnxs.itervalues()]
 
     def source(self, uid):
         """return the source object with the given uri"""
--- a/server/querier.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/querier.py	Wed Mar 20 17:40:25 2013 +0100
@@ -18,29 +18,31 @@
 """Helper classes to execute RQL queries on a set of sources, performing
 security checking and data aggregation.
 """
-
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 
 from itertools import repeat
 
 from logilab.common.compat import any
-from rql import RQLSyntaxError
+from rql import RQLSyntaxError, CoercionError
 from rql.stmts import Union, Select
+from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj
 from rql.nodes import (Relation, VariableRef, Constant, SubQuery, Function,
                        Exists, Not)
+from yams import BASE_TYPES
 
 from cubicweb import ValidationError, Unauthorized, QueryError, UnknownEid
-from cubicweb import server, typed_eid
+from cubicweb import Binary, server, typed_eid
 from cubicweb.rset import ResultSet
 
-from cubicweb.utils import QueryCache
+from cubicweb.utils import QueryCache, RepeatList
 from cubicweb.server.utils import cleanup_solutions
 from cubicweb.server.rqlannotation import SQLGenAnnotator, set_qdata
 from cubicweb.server.ssplanner import READ_ONLY_RTYPES, add_types_restriction
 from cubicweb.server.edition import EditedEntity
-from cubicweb.server.session import security_enabled
+
+
+ETYPE_PYOBJ_MAP[Binary] = 'Bytes'
+
 
 def empty_rset(rql, args, rqlst=None):
     """build an empty result set object"""
@@ -256,7 +258,7 @@
                 cached = True
             else:
                 noinvariant = set()
-                with security_enabled(self.session, read=False):
+                with self.session.security_enabled(read=False):
                     self._insert_security(union, noinvariant)
                 if key is not None:
                     self.session.transaction_data[key] = (union, self.args)
@@ -397,7 +399,7 @@
         for solution in rqlst.solutions:
             try:
                 localcheck = check_read_access(session, rqlst, solution, self.args)
-            except Unauthorized, ex:
+            except Unauthorized as ex:
                 msg = 'remove %s from solutions since %s has no %s access to %s'
                 msg %= (solution, session.user.login, ex.args[0], ex.args[1])
                 msgs.append(msg)
@@ -499,7 +501,7 @@
                 self.e_defs.append(row)
         # now, see if this entity def is referenced as subject in some relation
         # definition
-        if self._r_subj_index.has_key(edef):
+        if edef in self._r_subj_index:
             for rdef in self._r_subj_index[edef]:
                 expanded = self._expanded(rdef)
                 result = []
@@ -509,7 +511,7 @@
                 self._expanded_r_defs[rdef] = result
         # and finally, see if this entity def is referenced as object in some
         # relation definition
-        if self._r_obj_index.has_key(edef):
+        if edef in self._r_obj_index:
             for rdef in self._r_obj_index[edef]:
                 expanded = self._expanded(rdef)
                 result = []
@@ -528,7 +530,7 @@
 
     def relation_defs(self):
         """return the list for relation definitions to insert"""
-        for rdefs in self._expanded_r_defs.values():
+        for rdefs in self._expanded_r_defs.itervalues():
             for rdef in rdefs:
                 yield rdef
         for rdef in self.r_defs:
@@ -751,14 +753,22 @@
         if build_descr:
             if rqlst.TYPE == 'select':
                 # sample selection
-                descr = session.build_description(orig_rqlst, args, results)
+                if len(rqlst.children) == 1 and len(rqlst.children[0].solutions) == 1:
+                    # easy, all lines are identical
+                    selected = rqlst.children[0].selection
+                    solution = rqlst.children[0].solutions[0]
+                    description = _make_description(selected, args, solution)
+                    descr = RepeatList(len(results), tuple(description))
+                else:
+                    # hard, delegate the work :o)
+                    descr = manual_build_descr(session, rqlst, args, results)
             elif rqlst.TYPE == 'insert':
                 # on insert plan, some entities may have been auto-casted,
                 # so compute description manually even if there is only
                 # one solution
                 basedescr = [None] * len(plan.selected)
                 todetermine = zip(xrange(len(plan.selected)), repeat(False))
-                descr = session._build_descr(results, basedescr, todetermine)
+                descr = _build_descr(session, results, basedescr, todetermine)
             # FIXME: get number of affected entities / relations on non
             # selection queries ?
         # return a result set object
@@ -772,3 +782,77 @@
 from cubicweb import set_log_methods
 LOGGER = getLogger('cubicweb.querier')
 set_log_methods(QuerierHelper, LOGGER)
+
+
+def manual_build_descr(tx, rqlst, args, result):
+    """build a description for a given result by analysing each row
+
+    XXX could probably be done more efficiently during execution of query
+    """
+    # not so easy, looks for variable which changes from one solution
+    # to another
+    unstables = rqlst.get_variable_indices()
+    basedescr = []
+    todetermine = []
+    for i in xrange(len(rqlst.children[0].selection)):
+        ttype = _selection_idx_type(i, rqlst, args)
+        if ttype is None or ttype == 'Any':
+            ttype = None
+            isfinal = True
+        else:
+            isfinal = ttype in BASE_TYPES
+        if ttype is None or i in unstables:
+            basedescr.append(None)
+            todetermine.append( (i, isfinal) )
+        else:
+            basedescr.append(ttype)
+    if not todetermine:
+        return RepeatList(len(result), tuple(basedescr))
+    return _build_descr(tx, result, basedescr, todetermine)
+
+def _build_descr(tx, result, basedescription, todetermine):
+    description = []
+    etype_from_eid = tx.describe
+    todel = []
+    for i, row in enumerate(result):
+        row_descr = basedescription[:]
+        for index, isfinal in todetermine:
+            value = row[index]
+            if value is None:
+                # None value inserted by an outer join, no type
+                row_descr[index] = None
+                continue
+            if isfinal:
+                row_descr[index] = etype_from_pyobj(value)
+            else:
+                try:
+                    row_descr[index] = etype_from_eid(value)[0]
+                except UnknownEid:
+                    tx.error('wrong eid %s in repository, you should '
+                             'db-check the database' % value)
+                    todel.append(i)
+                    break
+        else:
+            description.append(tuple(row_descr))
+    for i in reversed(todel):
+        del result[i]
+    return description
+
+def _make_description(selected, args, solution):
+    """return a description for a result set"""
+    description = []
+    for term in selected:
+        description.append(term.get_type(solution, args))
+    return description
+
+def _selection_idx_type(i, rqlst, args):
+    """try to return type of term at index `i` of the rqlst's selection"""
+    for select in rqlst.children:
+        term = select.selection[i]
+        for solution in select.solutions:
+            try:
+                ttype = term.get_type(solution, args)
+                if ttype is not None:
+                    return ttype
+            except CoercionError:
+                return None
--- a/server/repository.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/repository.py	Wed Mar 20 17:40:25 2013 +0100
@@ -26,9 +26,6 @@
 * handles session management
 * provides method for pyro registration, to call if pyro is enabled
 """
-
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 
 import sys
@@ -56,8 +53,7 @@
                       RepositoryError, UniqueTogetherError, typed_eid, onevent)
 from cubicweb import cwvreg, schema, server
 from cubicweb.server import ShuttingDown, utils, hook, pool, querier, sources
-from cubicweb.server.session import Session, InternalSession, InternalManager, \
-     security_enabled
+from cubicweb.server.session import Session, InternalSession, InternalManager
 from cubicweb.server.ssplanner import EditedEntity
 
 NO_CACHE_RELATIONS = set( [('owned_by', 'object'),
@@ -109,12 +105,12 @@
     # * we don't want read permissions to be applied but we want delete
     #   permission to be checked
     if card[0] in '1?':
-        with security_enabled(session, read=False):
+        with session.security_enabled(read=False):
             session.execute('DELETE X %s Y WHERE X eid %%(x)s, '
                             'NOT Y eid %%(y)s' % rtype,
                                 {'x': eidfrom, 'y': eidto})
     if card[1] in '1?':
-        with security_enabled(session, read=False):
+        with session.security_enabled(read=False):
             session.execute('DELETE X %s Y WHERE Y eid %%(y)s, '
                             'NOT X eid %%(x)s' % rtype,
                             {'x': eidfrom, 'y': eidto})
@@ -127,7 +123,7 @@
     relations = []
     activeintegrity = session.is_hook_category_activated('activeintegrity')
     eschema = entity.e_schema
-    for attr in entity.cw_edited.iterkeys():
+    for attr in entity.cw_edited:
         rschema = eschema.subjrels[attr]
         if not rschema.final: # inlined relation
             value = entity.cw_edited[attr]
@@ -135,7 +131,7 @@
             session.update_rel_cache_add(entity.eid, attr, value)
             rdef = session.rtype_eids_rdef(attr, entity.eid, value)
             if rdef.cardinality[1] in '1?' and activeintegrity:
-                with security_enabled(session, read=False):
+                with session.security_enabled(read=False):
                     session.execute('DELETE X %s Y WHERE Y eid %%(y)s' % attr,
                                     {'x': entity.eid, 'y': value})
     return relations
@@ -187,7 +183,7 @@
         self.shutting_down = False
         # sources (additional sources info in the system database)
         self.system_source = self.get_source('native', 'system',
-                                             config.sources()['system'])
+                                             config.sources()['system'].copy())
         self.sources = [self.system_source]
         self.sources_by_uri = {'system': self.system_source}
         # querier helper, need to be created after sources initialization
@@ -205,7 +201,7 @@
             # changed.  To any existing user object have a different class than
             # the new loaded one. We are hot fixing this.
             usercls = self.vreg['etypes'].etype_class('CWUser')
-            for session in self._sessions.values():
+            for session in self._sessions.itervalues():
                 if not isinstance(session.user, InternalManager):
                     session.user.__class__ = usercls
 
@@ -218,33 +214,21 @@
             # information (eg dump/restore/...)
             config._cubes = ()
             # only load hooks and entity classes in the registry
-            config.__class__.cube_appobject_path = set(('hooks', 'entities'))
-            config.__class__.cubicweb_appobject_path = set(('hooks', 'entities'))
+            config.cube_appobject_path = set(('hooks', 'entities'))
+            config.cubicweb_appobject_path = set(('hooks', 'entities'))
             self.set_schema(config.load_schema())
             config['connections-pool-size'] = 1
             # will be reinitialized later from cubes found in the database
             config._cubes = None
-        elif config.creating:
-            # repository creation
+        elif config.creating or not config.read_instance_schema:
+            if not config.creating:
+                # test start: use the file system schema (quicker)
+                self.warning("set fs instance'schema")
             config.bootstrap_cubes()
-            self.set_schema(config.load_schema(), resetvreg=False)
-            # need to load the Any and CWUser entity types
-            etdirectory = join(CW_SOFTWARE_ROOT, 'entities')
-            self.vreg.init_registration([etdirectory])
-            for modname in ('__init__', 'authobjs', 'wfobjs'):
-                self.vreg.load_file(join(etdirectory, '%s.py' % modname),
-                                    'cubicweb.entities.%s' % modname)
-            hooksdirectory = join(CW_SOFTWARE_ROOT, 'hooks')
-            self.vreg.load_file(join(hooksdirectory, 'metadata.py'),
-                                'cubicweb.hooks.metadata')
-        elif config.read_instance_schema:
+            self.set_schema(config.load_schema())
+        else:
             # normal start: load the instance schema from the database
             self.fill_schema()
-        else:
-            # test start: use the file system schema (quicker)
-            self.warning("set fs instance'schema")
-            config.bootstrap_cubes()
-            self.set_schema(config.load_schema())
         if not config.creating:
             self.init_sources_from_database()
             if 'CWProperty' in self.schema:
@@ -343,7 +327,7 @@
         self.querier.set_schema(schema)
         # don't use self.sources, we may want to give schema even to disabled
         # sources
-        for source in self.sources_by_uri.values():
+        for source in self.sources_by_uri.itervalues():
             source.set_schema(schema)
         self.schema = schema
 
@@ -359,7 +343,7 @@
                 deserialize_schema(appschema, session)
             except BadSchemaDefinition:
                 raise
-            except Exception, ex:
+            except Exception as ex:
                 import traceback
                 traceback.print_exc()
                 raise Exception('Is the database initialised ? (cause: %s)' %
@@ -431,7 +415,7 @@
         # XXX: session.cnxset is accessed from a local storage, would be interesting
         #      to see if there is a cnxset set in any thread specific data)
         return '%s: %s (%s)' % (self._cnxsets_pool.qsize(),
-                                ','.join(session.user.login for session in self._sessions.values()
+                                ','.join(session.user.login for session in self._sessions.itervalues()
                                          if session.cnxset),
                                 threading.currentThread())
     def shutdown(self):
@@ -745,7 +729,7 @@
                                      for rschema, _eschema in cwuser.attribute_definitions()
                                      if not rschema.meta)
         cwuserattrs = self._cwuser_attrs
-        for k in chain(fetch_attrs, query_attrs.iterkeys()):
+        for k in chain(fetch_attrs, query_attrs):
             if k not in cwuserattrs:
                 raise Exception('bad input for find_user')
         with self.internal_session() as session:
@@ -754,23 +738,20 @@
             rql = 'Any %s WHERE X is CWUser, ' % ','.join(var[1] for var in vars)
             rql += ','.join('X %s %s' % (var[0], var[1]) for var in vars) + ','
             rset = session.execute(rql + ','.join('X %s %%(%s)s' % (attr, attr)
-                                                  for attr in query_attrs.iterkeys()),
+                                                  for attr in query_attrs),
                                    query_attrs)
             return rset.rows
 
     def connect(self, login, **kwargs):
         """open a connection for a given user
 
-        base_url may be needed to send mails
-        cnxtype indicate if this is a pyro connection or a in-memory connection
-
         raise `AuthenticationError` if the authentication failed
         raise `ConnectionError` if we can't open a connection
         """
+        cnxprops = kwargs.pop('cnxprops', None)
         # use an internal connection
         with self.internal_session() as session:
             # try to get a user object
-            cnxprops = kwargs.pop('cnxprops', None)
             user = self.authenticate_user(session, login, **kwargs)
         session = Session(user, self, cnxprops)
         user._cw = user.cw_rset.req = session
@@ -804,7 +785,7 @@
                 return rset
             except (Unauthorized, RQLSyntaxError):
                 raise
-            except ValidationError, ex:
+            except ValidationError as ex:
                 # need ValidationError normalization here so error may pass
                 # through pyro
                 if hasattr(ex.entity, 'eid'):
@@ -921,22 +902,9 @@
         * update user information on each user's request (i.e. groups and
           custom properties)
         """
-        session = self._get_session(sessionid, setcnxset=False)
-        if props is not None:
-            self.set_session_props(sessionid, props)
-        user = session.user
+        user = self._get_session(sessionid, setcnxset=False).user
         return user.eid, user.login, user.groups, user.properties
 
-    def set_session_props(self, sessionid, props):
-        """this method should be used by client to:
-        * check session id validity
-        * update user information on each user's request (i.e. groups and
-          custom properties)
-        """
-        session = self._get_session(sessionid, setcnxset=False)
-        for prop, value in props.items():
-            session.change_property(prop, value)
-
     def undoable_transactions(self, sessionid, ueid=None, txid=None,
                               **actionfilters):
         """See :class:`cubicweb.dbapi.Connection.undoable_transactions`"""
@@ -994,7 +962,7 @@
 
     def close_sessions(self):
         """close every opened sessions"""
-        for sessionid in self._sessions.keys():
+        for sessionid in list(self._sessions):
             try:
                 self.close(sessionid, checkshuttingdown=False)
             except Exception: # XXX BaseException?
@@ -1008,7 +976,7 @@
         self.debug('cleaning session unused since %s',
                    strftime('%T', localtime(mintime)))
         nbclosed = 0
-        for session in self._sessions.values():
+        for session in self._sessions.itervalues():
             if session.timestamp < mintime:
                 self.close(session.id)
                 nbclosed += 1
@@ -1239,7 +1207,7 @@
             source = self.sources_by_eid[scleanup]
         # delete remaining relations: if user can delete the entity, he can
         # delete all its relations without security checking
-        with security_enabled(session, read=False, write=False):
+        with session.security_enabled(read=False, write=False):
             eid = entity.eid
             for rschema, _, role in entity.e_schema.relation_definitions():
                 rtype = rschema.type
@@ -1281,7 +1249,7 @@
             source = self.sources_by_eid[scleanup]
         # delete remaining relations: if user can delete the entity, he can
         # delete all its relations without security checking
-        with security_enabled(session, read=False, write=False):
+        with session.security_enabled(read=False, write=False):
             in_eids = ','.join([str(_e.eid) for _e in entities])
             for rschema, _, role in entities[0].e_schema.relation_definitions():
                 rtype = rschema.type
@@ -1389,7 +1357,7 @@
             edited.check(creation=True)
         try:
             source.add_entity(session, entity)
-        except UniqueTogetherError, exc:
+        except UniqueTogetherError as exc:
             userhdlr = session.vreg['adapters'].select(
                 'IUserFriendlyError', session, entity=entity, exc=exc)
             userhdlr.raise_user_exception()
@@ -1455,7 +1423,7 @@
             try:
                 source.update_entity(session, entity)
                 edited.saved = True
-            except UniqueTogetherError, exc:
+            except UniqueTogetherError as exc:
                 etype, rtypes = exc.args
                 problems = {}
                 for col in rtypes:
@@ -1567,7 +1535,7 @@
                 rdef = session.rtype_eids_rdef(rtype, subjeid, objeid)
                 card = rdef.cardinality
                 if card[0] in '?1':
-                    with security_enabled(session, read=False):
+                    with session.security_enabled(read=False):
                         session.execute('DELETE X %s Y WHERE X eid %%(x)s, '
                                         'NOT Y eid %%(y)s' % rtype,
                                         {'x': subjeid, 'y': objeid})
@@ -1578,7 +1546,7 @@
                         continue
                     subjects[subjeid] = len(relations_by_rtype[rtype]) - 1
                 if card[1] in '?1':
-                    with security_enabled(session, read=False):
+                    with session.security_enabled(read=False):
                         session.execute('DELETE X %s Y WHERE Y eid %%(y)s, '
                                         'NOT X eid %%(x)s' % rtype,
                                         {'x': subjeid, 'y': objeid})
--- a/server/schemaserial.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/schemaserial.py	Wed Mar 20 17:40:25 2013 +0100
@@ -442,11 +442,11 @@
 def _ervalues(erschema):
     try:
         type_ = unicode(erschema.type)
-    except UnicodeDecodeError, e:
+    except UnicodeDecodeError as e:
         raise Exception("can't decode %s [was %s]" % (erschema.type, e))
     try:
         desc = unicode(erschema.description) or u''
-    except UnicodeDecodeError, e:
+    except UnicodeDecodeError as e:
         raise Exception("can't decode %s [was %s]" % (erschema.description, e))
     return {
         'name': type_,
--- a/server/serverconfig.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/serverconfig.py	Wed Mar 20 17:40:25 2013 +0100
@@ -82,7 +82,9 @@
     """serialize a repository source configuration as text"""
     stream = StringIO()
     optsbysect = list(sconfig.options_by_section())
-    assert len(optsbysect) == 1, 'all options for a source should be in the same group'
+    assert len(optsbysect) == 1, (
+        'all options for a source should be in the same group, got %s'
+        % [x[0] for x in optsbysect])
     lgconfig.ini_format(stream, optsbysect[0][1], encoding)
     return stream.getvalue()
 
@@ -195,7 +197,7 @@
 notified of every changes.',
           'group': 'email', 'level': 2,
           }),
-        # pyro server.serverconfig
+        # pyro services config
         ('pyro-host',
          {'type' : 'string',
           'default': None,
@@ -204,23 +206,47 @@
 and if not set, it will be choosen randomly',
           'group': 'pyro', 'level': 3,
           }),
+        ('pyro-instance-id',
+         {'type' : 'string',
+          'default': lgconfig.Method('default_instance_id'),
+          'help': 'identifier of the CubicWeb instance in the Pyro name server',
+          'group': 'pyro', 'level': 1,
+          }),
+        ('pyro-ns-host',
+         {'type' : 'string',
+          'default': '',
+          'help': 'Pyro name server\'s host. If not set, will be detected by a \
+broadcast query. It may contains port information using <host>:<port> notation. \
+Use "NO_PYRONS" to create a Pyro server but not register to a pyro nameserver',
+          'group': 'pyro', 'level': 1,
+          }),
+        ('pyro-ns-group',
+         {'type' : 'string',
+          'default': 'cubicweb',
+          'help': 'Pyro name server\'s group where the repository will be \
+registered.',
+          'group': 'pyro', 'level': 1,
+          }),
         # zmq services config
         ('zmq-repository-address',
          {'type' : 'string',
           'default': None,
-          'help': 'ZMQ URI on which the repository will be bound to.',
+          'help': ('ZMQ URI on which the repository will be bound '
+                   'to (of the form `zmqpickle-tcp://<ipaddr><port>`).'),
           'group': 'zmq', 'level': 3,
           }),
          ('zmq-address-sub',
           {'type' : 'csv',
            'default' : None,
-           'help': ('List of ZMQ addresses to subscribe to (requires pyzmq)'),
+           'help': ('List of ZMQ addresses to subscribe to (requires pyzmq) '
+                    '(of the form `zmqpickle-tcp://<ipaddr><port>`)'),
            'group': 'zmq', 'level': 1,
            }),
          ('zmq-address-pub',
           {'type' : 'string',
            'default' : None,
-           'help': ('ZMQ address to use for publishing (requires pyzmq)'),
+           'help': ('ZMQ address to use for publishing (requires pyzmq) '
+                    '(of the form `zmqpickle-tcp://<ipaddr><port>`)'),
            'group': 'zmq', 'level': 1,
            }),
         ) + CubicWebConfiguration.options)
--- a/server/serverctl.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/serverctl.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -208,7 +208,7 @@
 def confirm_on_error_or_die(msg, func, *args, **kwargs):
     try:
         return func(*args, **kwargs)
-    except Exception, ex:
+    except Exception as ex:
         print 'ERROR', ex
         if not ASK.confirm('An error occurred while %s. Continue anyway?' % msg):
             raise ExecutionError(str(ex))
@@ -382,7 +382,7 @@
                 if automatic or ASK.confirm('Create language %s ?' % extlang):
                     try:
                         helper.create_language(cursor, extlang)
-                    except Exception, exc:
+                    except Exception as exc:
                         print '-> ERROR:', exc
                         print '-> could not create language %s, some stored procedures might be unusable' % extlang
                         cnx.rollback()
@@ -442,14 +442,14 @@
         config = ServerConfiguration.config_for(appid)
         try:
             system = config.sources()['system']
-            extra_args=system.get('db-extra-arguments')
+            extra_args = system.get('db-extra-arguments')
             extra = extra_args and {'extra_args': extra_args} or {}
             get_connection(
                 system['db-driver'], database=system['db-name'],
                 host=system.get('db-host'), port=system.get('db-port'),
                 user=system.get('db-user') or '', password=system.get('db-password') or '',
                 **extra)
-        except Exception, ex:
+        except Exception as ex:
             raise ConfigurationError(
                 'You seem to have provided wrong connection information in '\
                 'the %s file. Resolve this first (error: %s).'
@@ -552,7 +552,7 @@
         try:
             sqlexec(sqlgrants(schema, source['db-driver'], user,
                               set_owner=set_owner), cursor)
-        except Exception, ex:
+        except Exception as ex:
             cnx.rollback()
             import traceback
             traceback.print_exc()
@@ -620,7 +620,7 @@
             sconfig['password'] = pwd
             sourcescfg['admin'] = sconfig
             config.write_sources_file(sourcescfg)
-        except Exception, ex:
+        except Exception as ex:
             cnx.rollback()
             import traceback
             traceback.print_exc()
@@ -868,7 +868,7 @@
             if not self.config.no_drop:
                 try:
                     CWCTL.run(['db-create', '--automatic', appid])
-                except SystemExit, exc:
+                except SystemExit as exc:
                     # continue if the command exited with status 0 (success)
                     if exc.code:
                         raise
@@ -879,7 +879,7 @@
         if self.config.format == 'portable':
             try:
                 CWCTL.run(['db-rebuild-fti', appid])
-            except SystemExit, exc:
+            except SystemExit as exc:
                 if exc.code:
                     raise
 
--- a/server/session.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/session.py	Wed Mar 20 17:40:25 2013 +0100
@@ -16,9 +16,6 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Repository users' and internal' sessions."""
-
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 
 import sys
@@ -30,21 +27,15 @@
 from logilab.common.deprecation import deprecated
 from logilab.common.textutils import unormalize
 from logilab.common.registry import objectify_predicate
-from rql import CoercionError
-from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj
-from yams import BASE_TYPES
 
-from cubicweb import Binary, UnknownEid, QueryError, schema
+from cubicweb import UnknownEid, QueryError, schema, server
 from cubicweb.req import RequestSessionBase
-from cubicweb.dbapi import ConnectionProperties
-from cubicweb.utils import make_uid, RepeatList
+from cubicweb.utils import make_uid
 from cubicweb.rqlrewrite import RQLRewriter
 from cubicweb.server import ShuttingDown
 from cubicweb.server.edition import EditedEntity
 
 
-ETYPE_PYOBJ_MAP[Binary] = 'Bytes'
-
 NO_UNDO_TYPES = schema.SCHEMA_TYPES.copy()
 NO_UNDO_TYPES.add('CWCache')
 # is / is_instance_of are usually added by sql hooks except when using
@@ -55,25 +46,6 @@
 NO_UNDO_TYPES.add('cw_source')
 # XXX rememberme,forgotpwd,apycot,vcsfile
 
-def _make_description(selected, args, solution):
-    """return a description for a result set"""
-    description = []
-    for term in selected:
-        description.append(term.get_type(solution, args))
-    return description
-
-def selection_idx_type(i, rqlst, args):
-    """try to return type of term at index `i` of the rqlst's selection"""
-    for select in rqlst.children:
-        term = select.selection[i]
-        for solution in select.solutions:
-            try:
-                ttype = term.get_type(solution, args)
-                if ttype is not None:
-                    return ttype
-            except CoercionError:
-                return None
-
 @objectify_predicate
 def is_user_session(cls, req, **kwargs):
     """repository side only predicate returning 1 if the session is a regular
@@ -132,6 +104,11 @@
 
        with hooks_control(self.session, self.session.HOOKS_DENY_ALL, 'integrity'):
            # ... do stuff with none but 'integrity' hooks activated
+
+    This is an internal api, you should rather use
+    :meth:`~cubicweb.server.session.Session.deny_all_hooks_but` or
+    :meth:`~cubicweb.server.session.Session.allow_all_hooks_but` session
+    methods.
     """
     def __init__(self, session, mode, *categories):
         self.session = session
@@ -241,16 +218,18 @@
 
       :attr:`running_dbapi_query`, boolean flag telling if the executing query
       is coming from a dbapi connection or is a query from within the repository
+
+    .. automethod:: cubicweb.server.session.deny_all_hooks_but
+    .. automethod:: cubicweb.server.session.all_all_hooks_but
     """
+    is_request = False
     is_internal_session = False
 
     def __init__(self, user, repo, cnxprops=None, _id=None):
         super(Session, self).__init__(repo.vreg)
         self.id = _id or make_uid(unormalize(user.login).encode('UTF8'))
-        cnxprops = cnxprops or ConnectionProperties('inmemory')
         self.user = user
         self.repo = repo
-        self.cnxtype = cnxprops.cnxtype
         self.timestamp = time()
         self.default_mode = 'read'
         # undo support
@@ -264,7 +243,7 @@
         # and the rql server
         self.data = {}
         # i18n initialization
-        self.set_language(cnxprops.lang)
+        self.set_language(user.prefered_language())
         # internals
         self._tx_data = {}
         self.__threaddata = threading.local()
@@ -273,8 +252,8 @@
         self._closed_lock = threading.Lock()
 
     def __unicode__(self):
-        return '<%ssession %s (%s 0x%x)>' % (
-            self.cnxtype, unicode(self.user.login), self.id, id(self))
+        return '<session %s (%s 0x%x)>' % (
+            unicode(self.user.login), self.id, id(self))
 
     def transaction(self, free_cnxset=True):
         """return context manager to enter a transaction for the session: when
@@ -464,28 +443,6 @@
             self.cnxset.reconnect(source)
             return source.doexec(self, sql, args, rollback=rollback_on_failure)
 
-    def set_language(self, language):
-        """i18n configuration for translation"""
-        language = language or self.user.property_value('ui.language')
-        try:
-            gettext, pgettext = self.vreg.config.translations[language]
-            self._ = self.__ = gettext
-            self.pgettext = pgettext
-        except KeyError:
-            language = self.vreg.property_value('ui.language')
-            try:
-                gettext, pgettext = self.vreg.config.translations[language]
-                self._ = self.__ = gettext
-                self.pgettext = pgettext
-            except KeyError:
-                self._ = self.__ = unicode
-                self.pgettext = lambda x, y: y
-        self.lang = language
-
-    def change_property(self, prop, value):
-        assert prop == 'lang' # this is the only one changeable property for now
-        self.set_language(value)
-
     def deleted_in_transaction(self, eid):
         """return True if the entity of the given eid is being deleted in the
         current transaction
@@ -509,7 +466,7 @@
 
     DEFAULT_SECURITY = object() # evaluated to true by design
 
-    def security_enabled(self, read=False, write=False):
+    def security_enabled(self, read=None, write=None):
         return security_enabled(self, read=read, write=write)
 
     def init_security(self, read, write):
@@ -976,16 +933,21 @@
         # information:
         # - processed by the precommit/commit event or not
         # - if processed, is it the failed operation
+        debug = server.DEBUG & server.DBG_OPS
         try:
             # by default, operations are executed with security turned off
             with security_enabled(self, False, False):
                 processed = []
                 self.commit_state = 'precommit'
+                if debug:
+                    print self.commit_state, '*' * 20
                 try:
                     while self.pending_operations:
                         operation = self.pending_operations.pop(0)
                         operation.processed = 'precommit'
                         processed.append(operation)
+                        if debug:
+                            print operation
                         operation.handle_event('precommit_event')
                     self.pending_operations[:] = processed
                     self.debug('precommit session %s done', self.id)
@@ -1001,7 +963,11 @@
                     # instead of having to implements rollback, revertprecommit
                     # and revertcommit, that will be enough in mont case.
                     operation.failed = True
+                    if debug:
+                        print self.commit_state, '*' * 20
                     for operation in reversed(processed):
+                        if debug:
+                            print operation
                         try:
                             operation.handle_event('revertprecommit_event')
                         except BaseException:
@@ -1014,8 +980,12 @@
                     raise
                 self.cnxset.commit()
                 self.commit_state = 'postcommit'
+                if debug:
+                    print self.commit_state, '*' * 20
                 while self.pending_operations:
                     operation = self.pending_operations.pop(0)
+                    if debug:
+                        print operation
                     operation.processed = 'postcommit'
                     try:
                         operation.handle_event('postcommit_event')
@@ -1154,71 +1124,6 @@
             self._threaddata._rewriter = RQLRewriter(self)
             return self._threaddata._rewriter
 
-    def build_description(self, rqlst, args, result):
-        """build a description for a given result"""
-        if len(rqlst.children) == 1 and len(rqlst.children[0].solutions) == 1:
-            # easy, all lines are identical
-            selected = rqlst.children[0].selection
-            solution = rqlst.children[0].solutions[0]
-            description = _make_description(selected, args, solution)
-            return RepeatList(len(result), tuple(description))
-        # hard, delegate the work :o)
-        return self.manual_build_descr(rqlst, args, result)
-
-    def manual_build_descr(self, rqlst, args, result):
-        """build a description for a given result by analysing each row
-
-        XXX could probably be done more efficiently during execution of query
-        """
-        # not so easy, looks for variable which changes from one solution
-        # to another
-        unstables = rqlst.get_variable_indices()
-        basedescr = []
-        todetermine = []
-        for i in xrange(len(rqlst.children[0].selection)):
-            ttype = selection_idx_type(i, rqlst, args)
-            if ttype is None or ttype == 'Any':
-                ttype = None
-                isfinal = True
-            else:
-                isfinal = ttype in BASE_TYPES
-            if ttype is None or i in unstables:
-                basedescr.append(None)
-                todetermine.append( (i, isfinal) )
-            else:
-                basedescr.append(ttype)
-        if not todetermine:
-            return RepeatList(len(result), tuple(basedescr))
-        return self._build_descr(result, basedescr, todetermine)
-
-    def _build_descr(self, result, basedescription, todetermine):
-        description = []
-        etype_from_eid = self.describe
-        todel = []
-        for i, row in enumerate(result):
-            row_descr = basedescription[:]
-            for index, isfinal in todetermine:
-                value = row[index]
-                if value is None:
-                    # None value inserted by an outer join, no type
-                    row_descr[index] = None
-                    continue
-                if isfinal:
-                    row_descr[index] = etype_from_pyobj(value)
-                else:
-                    try:
-                        row_descr[index] = etype_from_eid(value)[0]
-                    except UnknownEid:
-                        self.error('wrong eid %s in repository, you should '
-                                   'db-check the database' % value)
-                        todel.append(i)
-                        break
-            else:
-                description.append(tuple(row_descr))
-        for i in reversed(todel):
-            del result[i]
-        return description
-
     # deprecated ###############################################################
 
     @deprecated('[3.13] use getattr(session.rtype_eids_rdef(rtype, eidfrom, eidto), prop)')
@@ -1274,7 +1179,6 @@
         super(InternalSession, self).__init__(InternalManager(), repo, cnxprops,
                                               _id='internal')
         self.user._cw = self # XXX remove when "vreg = user._cw.vreg" hack in entity.py is gone
-        self.cnxtype = 'inmemory'
         if not safe:
             self.disable_hook_categories('integrity')
 
@@ -1317,6 +1221,24 @@
             return 'en'
         return None
 
+    def prefered_language(self, language=None):
+        # mock CWUser.prefered_language, mainly for testing purpose
+        return self.property_value('ui.language')
+
+    # CWUser compat for notification ###########################################
+
+    def name(self):
+        return 'cubicweb'
+
+    class _IEmailable:
+        @staticmethod
+        def get_email():
+            return ''
+
+    def cw_adapt_to(self, iface):
+        if iface == 'IEmailable':
+            return self._IEmailable
+        return None
 
 from logging import getLogger
 from cubicweb import set_log_methods
--- a/server/sources/__init__.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/sources/__init__.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -133,6 +133,8 @@
         self.uri = source_config.pop('uri')
         set_log_methods(self, getLogger('cubicweb.sources.'+self.uri))
         source_config.pop('type')
+        self.update_config(None, self.check_conf_dict(eid, source_config,
+                                                      fail_if_unknown=False))
 
     def __repr__(self):
         return '<%s %s source %s @%#x>' % (self.uri, self.__class__.__name__,
@@ -175,7 +177,7 @@
                 # type check
                 try:
                     value = configuration.convert(value, optdict, optname)
-                except Exception, ex:
+                except Exception as ex:
                     msg = unicode(ex) # XXX internationalization
                     raise ValidationError(eid, {role_name('config', 'subject'): msg})
             processed[optname] = value
@@ -185,7 +187,7 @@
         except KeyError:
             pass
         # check for unknown options
-        if confdict and not confdict.keys() == ['adapter']:
+        if confdict and tuple(confdict) != ('adapter',):
             if fail_if_unknown:
                 msg = _('unknown options %s') % ', '.join(confdict)
                 raise ValidationError(eid, {role_name('config', 'subject'): msg})
@@ -206,7 +208,17 @@
         """update configuration from source entity. `typedconfig` is config
         properly typed with defaults set
         """
-        pass
+        if source_entity is not None:
+            self._entity_update(source_entity)
+        self.config = typedconfig
+
+    def _entity_update(self, source_entity):
+        source_entity.complete()
+        if source_entity.url:
+            self.urls = [url.strip() for url in source_entity.url.splitlines()
+                         if url.strip()]
+        else:
+            self.urls = []
 
     # source initialization / finalization #####################################
 
@@ -222,14 +234,15 @@
         """method called by the repository once ready to handle request.
         `activated` is a boolean flag telling if the source is activated or not.
         """
-        pass
+        if activated:
+            self._entity_update(source_entity)
 
     PUBLIC_KEYS = ('type', 'uri', 'use-cwuri-as-url')
     def remove_sensitive_information(self, sourcedef):
         """remove sensitive information such as login / password from source
         definition
         """
-        for key in sourcedef.keys():
+        for key in list(sourcedef):
             if not key in self.PUBLIC_KEYS:
                 sourcedef.pop(key)
 
--- a/server/sources/datafeed.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/sources/datafeed.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2010-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2010-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -18,7 +18,6 @@
 """datafeed sources: copy data from an external data stream into the system
 database
 """
-from __future__ import with_statement
 
 import urllib2
 import StringIO
@@ -81,43 +80,30 @@
           }),
         )
 
-    def __init__(self, repo, source_config, eid=None):
-        AbstractSource.__init__(self, repo, source_config, eid)
-        self.update_config(None, self.check_conf_dict(eid, source_config,
-                                                      fail_if_unknown=False))
-
     def check_config(self, source_entity):
         """check configuration of source entity"""
-        typedconfig = super(DataFeedSource, self).check_config(source_entity)
-        if typedconfig['synchronization-interval'] < 60:
+        typed_config = super(DataFeedSource, self).check_config(source_entity)
+        if typed_config['synchronization-interval'] < 60:
             _ = source_entity._cw._
             msg = _('synchronization-interval must be greater than 1 minute')
             raise ValidationError(source_entity.eid, {'config': msg})
-        return typedconfig
+        return typed_config
 
     def _entity_update(self, source_entity):
-        source_entity.complete()
+        super(DataFeedSource, self)._entity_update(source_entity)
         self.parser_id = source_entity.parser
         self.latest_retrieval = source_entity.latest_retrieval
-        if source_entity.url:
-            self.urls = [url.strip() for url in source_entity.url.splitlines()
-                         if url.strip()]
-        else:
-            self.urls = []
 
-    def update_config(self, source_entity, typedconfig):
-        """update configuration from source entity. `typedconfig` is config
+    def update_config(self, source_entity, typed_config):
+        """update configuration from source entity. `typed_config` is config
         properly typed with defaults set
         """
-        self.synchro_interval = timedelta(seconds=typedconfig['synchronization-interval'])
-        self.max_lock_lifetime = timedelta(seconds=typedconfig['max-lock-lifetime'])
-        if source_entity is not None:
-            self._entity_update(source_entity)
-        self.config = typedconfig
+        super(DataFeedSource, self).update_config(source_entity, typed_config)
+        self.synchro_interval = timedelta(seconds=typed_config['synchronization-interval'])
+        self.max_lock_lifetime = timedelta(seconds=typed_config['max-lock-lifetime'])
 
     def init(self, activated, source_entity):
-        if activated:
-            self._entity_update(source_entity)
+        super(DataFeedSource, self).init(activated, source_entity)
         self.parser_id = source_entity.parser
         self.load_mapping(source_entity._cw)
 
@@ -221,14 +207,14 @@
             try:
                 if parser.process(url, raise_on_error):
                     error = True
-            except IOError, exc:
+            except IOError as exc:
                 if raise_on_error:
                     raise
                 parser.import_log.record_error(
                     'could not pull data while processing %s: %s'
                     % (url, exc))
                 error = True
-            except Exception, exc:
+            except Exception as exc:
                 if raise_on_error:
                     raise
                 self.exception('error while processing %s: %s',
@@ -332,7 +318,7 @@
             eid = session.repo.extid2eid(source, str(uri), etype, session,
                                          complete=False, commit=False,
                                          sourceparams=sourceparams)
-        except ValidationError, ex:
+        except ValidationError as ex:
             # XXX use critical so they are seen during tests. Should consider
             # raise_on_error instead?
             self.source.critical('error while creating %s: %s', etype, ex)
@@ -406,7 +392,7 @@
             attrs = dict( (k, v) for k, v in attrs.iteritems()
                           if v != getattr(entity, k))
             if attrs:
-                entity.set_attributes(**attrs)
+                entity.cw_set(**attrs)
                 self.notify_updated(entity)
 
 
@@ -416,7 +402,7 @@
         """IDataFeedParser main entry point"""
         try:
             parsed = self.parse(url)
-        except Exception, ex:
+        except Exception as ex:
             if raise_on_error:
                 raise
             self.import_log.record_error(str(ex))
@@ -438,7 +424,7 @@
                 # other a chance to get our connections set
                 commit()
                 set_cnxset()
-            except ValidationError, exc:
+            except ValidationError as exc:
                 if raise_on_error:
                     raise
                 self.source.error('Skipping %s because of validation error %s'
@@ -469,7 +455,7 @@
         if extid.startswith('http'):
             try:
                 _OPENER.open(self.normalize_url(extid)) # XXX HTTP HEAD request
-            except urllib2.HTTPError, ex:
+            except urllib2.HTTPError as ex:
                 if ex.code == 404:
                     return True
         elif extid.startswith('file://'):
--- a/server/sources/extlite.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/sources/extlite.py	Wed Mar 20 17:40:25 2013 +0100
@@ -290,7 +290,7 @@
         try:
             # str(query) to avoid error if it's an unicode string
             cursor.execute(str(query), args)
-        except Exception, ex:
+        except Exception as ex:
             self.critical("sql: %r\n args: %s\ndbms message: %r",
                           query, args, ex.args[0])
             try:
--- a/server/sources/ldapfeed.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/sources/ldapfeed.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -17,6 +17,7 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb ldap feed source"""
 
+from cubicweb.cwconfig import merge_options
 from cubicweb.server.sources import datafeed
 from cubicweb.server import ldaputils
 
@@ -30,15 +31,7 @@
     support_entities = {'CWUser': False}
     use_cwuri_as_url = False
 
-    options = datafeed.DataFeedSource.options + ldaputils.LDAPSourceMixIn.options
+    options = merge_options(datafeed.DataFeedSource.options
+                            + ldaputils.LDAPSourceMixIn.options,
+                            optgroup='ldap-source')
 
-    def update_config(self, source_entity, typedconfig):
-        """update configuration from source entity. `typedconfig` is config
-        properly typed with defaults set
-        """
-        datafeed.DataFeedSource.update_config(self, source_entity, typedconfig)
-        ldaputils.LDAPSourceMixIn.update_config(self, source_entity, typedconfig)
-
-    def _entity_update(self, source_entity):
-        datafeed.DataFeedSource._entity_update(self, source_entity)
-        ldaputils.LDAPSourceMixIn._entity_update(self, source_entity)
--- a/server/sources/ldapuser.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/sources/ldapuser.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -67,26 +67,11 @@
 
     )
 
-    def __init__(self, repo, source_config, eid=None):
-        AbstractSource.__init__(self, repo, source_config, eid)
-        self.update_config(None, self.check_conf_dict(eid, source_config,
-                                                      fail_if_unknown=False))
-
-    def _entity_update(self, source_entity):
-        # XXX copy from datafeed source
-        if source_entity.url:
-            self.urls = [url.strip() for url in source_entity.url.splitlines()
-                         if url.strip()]
-        else:
-            self.urls = []
-        # /end XXX
-        ldaputils.LDAPSourceMixIn._entity_update(self, source_entity)
-
     def update_config(self, source_entity, typedconfig):
         """update configuration from source entity. `typedconfig` is config
         properly typed with defaults set
         """
-        ldaputils.LDAPSourceMixIn.update_config(self, source_entity, typedconfig)
+        super(LDAPUserSource, self).update_config(source_entity, typedconfig)
         self._interval = typedconfig['synchronization-interval']
         self._cache_ttl = max(71, typedconfig['cache-life-time'])
         self.reset_caches()
@@ -103,9 +88,9 @@
 
     def init(self, activated, source_entity):
         """method called by the repository once ready to handle request"""
+        super(LDAPUserSource, self).init(activated, source_entity)
         if activated:
             self.info('ldap init')
-            self._entity_update(source_entity)
             # set minimum period of 5min 1s (the additional second is to
             # minimize resonnance effet)
             if self.user_rev_attrs['email']:
@@ -253,13 +238,13 @@
             # handle restriction
             try:
                 eidfilters_, ldapfilter = generator.generate(rqlst, mainvar)
-            except GotDN, ex:
+            except GotDN as ex:
                 assert ex.dn, 'no dn!'
                 try:
                     res = [self._cache[ex.dn]]
                 except KeyError:
                     res = self._search(session, ex.dn, BASE)
-            except UnknownEid, ex:
+            except UnknownEid as ex:
                 # raised when we are looking for the dn of an eid which is not
                 # coming from this source
                 res = []
--- a/server/sources/native.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/sources/native.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -23,9 +23,6 @@
   string. This is because it should actually be Bytes but we want an index on
   it for fast querying.
 """
-
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 
 try:
@@ -86,7 +83,7 @@
             print 'exec', query, args
         try:
             self.cu.execute(str(query), args)
-        except Exception, ex:
+        except Exception as ex:
             print "sql: %r\n args: %s\ndbms message: %r" % (
                 query, args, ex.args[0])
             raise
@@ -409,12 +406,13 @@
 
 
     def init(self, activated, source_entity):
+        super(NativeSQLSource, self).init(activated, source_entity)
         self.init_creating(source_entity._cw.cnxset)
         try:
             # test if 'asource' column exists
             query = self.dbhelper.sql_add_limit_offset('SELECT asource FROM entities', 1)
             source_entity._cw.system_sql(query)
-        except Exception, ex:
+        except Exception as ex:
             self.eid_type_source = self.eid_type_source_pre_131
 
     def shutdown(self):
@@ -539,7 +537,7 @@
             self.warning("trying to reconnect")
             session.cnxset.reconnect(self)
             cursor = self.doexec(session, sql, args)
-        except (self.DbapiError,), exc:
+        except self.DbapiError as exc:
             # We get this one with pyodbc and SQL Server when connection was reset
             if exc.args[0] == '08S01' and session.mode != 'write':
                 self.warning("trying to reconnect")
@@ -739,7 +737,7 @@
         try:
             # str(query) to avoid error if it's an unicode string
             cursor.execute(str(query), args)
-        except Exception, ex:
+        except Exception as ex:
             if self.repo.config.mode != 'test':
                 # during test we get those message when trying to alter sqlite
                 # db schema
@@ -750,7 +748,7 @@
                     session.cnxset.connection(self.uri).rollback()
                     if self.repo.config.mode != 'test':
                         self.critical('transaction has been rollbacked')
-                except Exception, ex:
+                except Exception as ex:
                     pass
             if ex.__class__.__name__ == 'IntegrityError':
                 # need string comparison because of various backends
@@ -780,7 +778,7 @@
         try:
             # str(query) to avoid error if it's an unicode string
             cursor.executemany(str(query), args)
-        except Exception, ex:
+        except Exception as ex:
             if self.repo.config.mode != 'test':
                 # during test we get those message when trying to alter sqlite
                 # db schema
@@ -944,7 +942,7 @@
             self.warning("trying to reconnect create eid connection")
             self._eid_creation_cnx = None
             return self._create_eid() # pylint: disable=E1102
-        except (self.DbapiError,), exc:
+        except self.DbapiError as exc:
             # We get this one with pyodbc and SQL Server when connection was reset
             if exc.args[0] == '08S01':
                 self.warning("trying to reconnect create eid connection")
@@ -961,6 +959,14 @@
             cnx.commit()
             return eid
 
+    def _handle_is_relation_sql(self, session, sql, attrs):
+        """ Handler for specific is_relation sql that may be
+        overwritten in some stores"""
+        self.doexec(session, sql % attrs)
+
+    _handle_insert_entity_sql = doexec
+    _handle_is_instance_of_sql = _handle_source_relation_sql = _handle_is_relation_sql
+
     def add_info(self, session, entity, source, extid, complete):
         """add type and source info for an eid into the system table"""
         # begin by inserting eid/type/source/extid into the entities table
@@ -970,21 +976,22 @@
         uri = 'system' if source.copy_based_source else source.uri
         attrs = {'type': entity.__regid__, 'eid': entity.eid, 'extid': extid,
                  'source': uri, 'asource': source.uri, 'mtime': datetime.utcnow()}
-        self.doexec(session, self.sqlgen.insert('entities', attrs), attrs)
+        self._handle_insert_entity_sql(session, self.sqlgen.insert('entities', attrs), attrs)
         # insert core relations: is, is_instance_of and cw_source
         try:
-            self.doexec(session, 'INSERT INTO is_relation(eid_from,eid_to) VALUES (%s,%s)'
-                        % (entity.eid, eschema_eid(session, entity.e_schema)))
+            self._handle_is_relation_sql(session, 'INSERT INTO is_relation(eid_from,eid_to) VALUES (%s,%s)',
+                                         (entity.eid, eschema_eid(session, entity.e_schema)))
         except IndexError:
             # during schema serialization, skip
             pass
         else:
             for eschema in entity.e_schema.ancestors() + [entity.e_schema]:
-                self.doexec(session, 'INSERT INTO is_instance_of_relation(eid_from,eid_to) VALUES (%s,%s)'
-                           % (entity.eid, eschema_eid(session, eschema)))
+                self._handle_is_relation_sql(session,
+                                             'INSERT INTO is_instance_of_relation(eid_from,eid_to) VALUES (%s,%s)',
+                                             (entity.eid, eschema_eid(session, eschema)))
         if 'CWSource' in self.schema and source.eid is not None: # else, cw < 3.10
-            self.doexec(session, 'INSERT INTO cw_source_relation(eid_from,eid_to) '
-                        'VALUES (%s,%s)' % (entity.eid, source.eid))
+            self._handle_is_relation_sql(session, 'INSERT INTO cw_source_relation(eid_from,eid_to) VALUES (%s,%s)',
+                                         (entity.eid, source.eid))
         # now we can update the full text index
         if self.do_fti and self.need_fti_indexation(entity.__regid__):
             if complete:
@@ -1303,14 +1310,14 @@
         subj, rtype, obj = action.eid_from, action.rtype, action.eid_to
         try:
             sentity, oentity, rdef = _undo_rel_info(session, subj, rtype, obj)
-        except _UndoException, ex:
+        except _UndoException as ex:
             errors.append(unicode(ex))
         else:
             for role, entity in (('subject', sentity),
                                  ('object', oentity)):
                 try:
                     _undo_check_relation_target(entity, rdef, role)
-                except _UndoException, ex:
+                except _UndoException as ex:
                     errors.append(unicode(ex))
                     continue
         if not errors:
@@ -1386,7 +1393,7 @@
         subj, rtype, obj = action.eid_from, action.rtype, action.eid_to
         try:
             sentity, oentity, rdef = _undo_rel_info(session, subj, rtype, obj)
-        except _UndoException, ex:
+        except _UndoException as ex:
             errors.append(unicode(ex))
         else:
             rschema = rdef.rtype
--- a/server/sources/pyrorql.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/sources/pyrorql.py	Wed Mar 20 17:40:25 2013 +0100
@@ -23,9 +23,6 @@
 import threading
 from Pyro.errors import PyroError, ConnectionClosedError
 
-from logilab.common.configuration import REQUIRED
-
-from cubicweb import dbapi
 from cubicweb import ConnectionError
 from cubicweb.server.sources import ConnectionWrapper
 
@@ -34,48 +31,10 @@
 class PyroRQLSource(RemoteSource):
     """External repository source, using Pyro connection"""
 
-    CNX_TYPE = 'pyro'
-
-    options = RemoteSource.options + (
-        # XXX pyro-ns host/port
-        ('pyro-ns-id',
-         {'type' : 'string',
-          'default': REQUIRED,
-          'help': 'identifier of the repository in the pyro name server',
-          'group': 'remote-source', 'level': 0,
-          }),
-        ('pyro-ns-host',
-         {'type' : 'string',
-          'default': None,
-          'help': 'Pyro name server\'s host. If not set, default to the value \
-from all_in_one.conf. It may contains port information using <host>:<port> notation.',
-          'group': 'remote-source', 'level': 1,
-          }),
-        ('pyro-ns-group',
-         {'type' : 'string',
-          'default': None,
-          'help': 'Pyro name server\'s group where the repository will be \
-registered. If not set, default to the value from all_in_one.conf.',
-          'group': 'remote-source', 'level': 2,
-          }),
-    )
-
-    def _get_connection(self):
-        """open and return a connection to the source"""
-        nshost = self.config.get('pyro-ns-host') or self.repo.config['pyro-ns-host']
-        nsgroup = self.config.get('pyro-ns-group') or self.repo.config['pyro-ns-group']
-        self.info('connecting to instance :%s.%s for user %s',
-                  nsgroup, self.config['pyro-ns-id'], self.config['cubicweb-user'])
-        return dbapi.connect(database=self.config['pyro-ns-id'],
-                             login=self.config['cubicweb-user'],
-                             password=self.config['cubicweb-password'],
-                             host=nshost, group=nsgroup,
-                             setvreg=False)
-
     def get_connection(self):
         try:
             return self._get_connection()
-        except (ConnectionError, PyroError), ex:
+        except (ConnectionError, PyroError) as ex:
             self.critical("can't get connection to source %s: %s", self.uri, ex)
             return ConnectionWrapper()
 
--- a/server/sources/remoterql.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/sources/remoterql.py	Wed Mar 20 17:40:25 2013 +0100
@@ -49,8 +49,6 @@
 class RemoteSource(AbstractSource):
     """Generic external repository source"""
 
-    CNX_TYPE = None # Must be ovewritted !
-
     # boolean telling if modification hooks should be called when something is
     # modified in this source
     should_call_hooks = False
@@ -99,12 +97,11 @@
 
     def __init__(self, repo, source_config, eid=None):
         super(RemoteSource, self).__init__(repo, source_config, eid)
-        self.update_config(None, self.check_conf_dict(eid, source_config,
-                                                      fail_if_unknown=False))
         self._query_cache = TimedCache(1800)
 
     def update_config(self, source_entity, processed_config):
         """update configuration from source entity"""
+        super(RemoteSource, self).update_config(source_entity, processed_config)
         baseurl = processed_config.get('base-url')
         if baseurl and not baseurl.endswith('/'):
             processed_config['base-url'] += '/'
@@ -113,22 +110,25 @@
         if source_entity is not None:
             self.latest_retrieval = source_entity.latest_retrieval
 
-    def _get_connection(self):
-        """open and return a connection to the source"""
-        self.info('connecting to source %(base-url)s with user %(cubicweb-user)s',
-                  self.config)
-        cnxprops = ConnectionProperties(cnxtype=self.CNX_TYPE)
-        return dbapi.connect(login=self.config['cubicweb-user'],
-                             password=self.config['cubicweb-password'],
-                             cnxprops=cnxprops)
+    def _entity_update(self, source_entity):
+        super(RemoteSource, self)._entity_update(source_entity)
+        if self.urls and len(self.urls) > 1:
+            raise ValidationError(source_entity.eid, {'url': _('can only have one url')})
 
     def get_connection(self):
         try:
             return self._get_connection()
-        except ConnectionError, ex:
+        except ConnectionError as ex:
             self.critical("can't get connection to source %s: %s", self.uri, ex)
             return ConnectionWrapper()
 
+    def _get_connection(self):
+        """open and return a connection to the source"""
+        self.info('connecting to source %s as user %s',
+                  self.urls[0], self.config['cubicweb-user'])
+        # XXX check protocol according to source type (zmq / pyro)
+        return dbapi.connect(self.urls[0], login=self.config['cubicweb-user'],
+                             password=self.config['cubicweb-password'])
 
     def reset_caches(self):
         """method called during test to reset potential source caches"""
@@ -136,6 +136,7 @@
 
     def init(self, activated, source_entity):
         """method called by the repository once ready to handle request"""
+        super(RemoteSource, self).init(activated, source_entity)
         self.load_mapping(source_entity._cw)
         if activated:
             interval = self.config['synchronization-interval']
@@ -239,7 +240,7 @@
         """synchronize content known by this repository with content in the
         external repository
         """
-        self.info('synchronizing remote %s source %s', (self.CNX_TYPE, self.uri))
+        self.info('synchronizing remote source %s', self.uri)
         cnx = self.get_connection()
         try:
             extrepo = cnx._repo
@@ -247,11 +248,10 @@
             # fake connection wrapper returned when we can't connect to the
             # external source (hence we've no chance to synchronize...)
             return
-        etypes = self.support_entities.keys()
+        etypes = list(self.support_entities)
         if mtime is None:
             mtime = self.latest_retrieval
-        updatetime, modified, deleted = extrepo.entities_modified_since(
-            etypes, mtime)
+        updatetime, modified, deleted = extrepo.entities_modified_since(etypes, mtime)
         self._query_cache.clear()
         repo = self.repo
         session = repo.internal_session()
@@ -337,7 +337,7 @@
         translator = RQL2RQL(self)
         try:
             rql = translator.generate(session, union, args)
-        except UnknownEid, ex:
+        except UnknownEid as ex:
             if server.DEBUG:
                 print '  unknown eid', ex, 'no results'
             return []
@@ -345,7 +345,7 @@
             print '  translated rql', rql
         try:
             rset = cu.execute(rql, args)
-        except Exception, ex:
+        except Exception as ex:
             self.exception(str(ex))
             msg = session._("error while querying source %s, some data may be missing")
             session.set_shared_data('sources_error', msg % self.uri, txdata=True)
@@ -573,7 +573,7 @@
                 return
             # XXX what about optional relation or outer NOT EXISTS()
             raise
-        except ReplaceByInOperator, ex:
+        except ReplaceByInOperator as ex:
             rhs = 'IN (%s)' % ','.join(eid for eid in ex.eids)
         self.need_translation = False
         self.current_operator = None
@@ -600,7 +600,7 @@
         for child in node.children:
             try:
                 rql = child.accept(self)
-            except UnknownEid, ex:
+            except UnknownEid as ex:
                 continue
             res.append(rql)
         if not res:
--- a/server/sources/storages.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/sources/storages.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -144,7 +144,7 @@
         fpath = source.binary_to_str(value)
         try:
             return Binary.from_file(fpath)
-        except EnvironmentError, ex:
+        except EnvironmentError as ex:
             source.critical("can't open %s: %s", value, ex)
             return None
 
@@ -152,6 +152,7 @@
         """an entity using this storage for attr has been added"""
         if entity._cw.transaction_data.get('fs_importing'):
             binary = Binary.from_file(entity.cw_edited[attr].getvalue())
+            entity._cw_dont_cache_attribute(attr, repo_side=True)
         else:
             binary = entity.cw_edited.pop(attr)
             fpath = self.new_fs_path(entity, attr)
@@ -170,6 +171,7 @@
             # We do not need to create it but we need to fetch the content of
             # the file as the actual content of the attribute
             fpath = entity.cw_edited[attr].getvalue()
+            entity._cw_dont_cache_attribute(attr, repo_side=True)
             assert fpath is not None
             binary = Binary.from_file(fpath)
         else:
@@ -262,7 +264,7 @@
         for filepath in self.get_data():
             try:
                 unlink(filepath)
-            except Exception, ex:
+            except Exception as ex:
                 self.error('cant remove %s: %s' % (filepath, ex))
 
 class DeleteFileOp(hook.DataOperationMixIn, hook.Operation):
@@ -270,5 +272,5 @@
         for filepath in self.get_data():
             try:
                 unlink(filepath)
-            except Exception, ex:
+            except Exception as ex:
                 self.error('cant remove %s: %s' % (filepath, ex))
--- a/server/sources/zmqrql.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/sources/zmqrql.py	Wed Mar 20 17:40:25 2013 +0100
@@ -24,4 +24,3 @@
 
 class ZMQRQLSource(RemoteSource):
     """External repository source, using ZMQ sockets"""
-    CNX_TYPE = 'zmq'
--- a/server/ssplanner.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/ssplanner.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """plan execution of rql queries on a single source"""
 
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 
 from rql.stmts import Union, Select
@@ -27,7 +25,6 @@
 from cubicweb import QueryError, typed_eid
 from cubicweb.schema import VIRTUAL_RTYPES
 from cubicweb.rqlrewrite import add_types_restriction
-from cubicweb.server.session import security_enabled
 from cubicweb.server.edition import EditedEntity
 
 READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity'))
@@ -60,7 +57,7 @@
                 if attrtype == 'Password' and isinstance(value, unicode):
                     value = value.encode('UTF8')
                 edef.edited_attribute(rtype, value)
-            elif to_build.has_key(str(rhs)):
+            elif str(rhs) in to_build:
                 # create a relation between two newly created variables
                 plan.add_relation_def((edef, rtype, to_build[rhs.name]))
             else:
@@ -87,7 +84,7 @@
                 # the generated select substep if not emited (eg nothing
                 # to be selected)
                 if checkread and eid not in neweids:
-                    with security_enabled(session, read=False):
+                    with session.security_enabled(read=False):
                         eschema(session.describe(eid)[0]).check_perm(
                             session, 'read', eid=eid)
                 eidconsts[lhs.variable] = eid
--- a/server/test/unittest_checkintegrity.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/test/unittest_checkintegrity.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -29,8 +29,9 @@
         handler = get_test_db_handler(TestServerConfiguration(apphome=self.datadir))
         handler.build_db_cache()
         self.repo, self.cnx = handler.get_repo_and_cnx()
-        self.execute = self.cnx.cursor().execute
-        self.session = self.repo._sessions[self.cnx.sessionid]
+        session = self.repo._get_session(self.cnx.sessionid, setcnxset=True)
+        self.session = session
+        self.execute = session.execute
         sys.stderr = sys.stdout = StringIO()
 
     def tearDown(self):
--- a/server/test/unittest_datafeed.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/test/unittest_datafeed.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2011-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -15,7 +15,6 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-from __future__ import with_statement
 
 from datetime import timedelta
 
@@ -54,7 +53,7 @@
             stats = dfsource.pull_data(session, force=True)
             self.commit()
             # test import stats
-            self.assertEqual(sorted(stats.keys()), ['checked', 'created', 'updated'])
+            self.assertEqual(sorted(stats), ['checked', 'created', 'updated'])
             self.assertEqual(len(stats['created']), 1)
             entity = self.execute('Card X').get_entity(0, 0)
             self.assertIn(entity.eid, stats['created'])
--- a/server/test/unittest_hook.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/test/unittest_hook.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -18,17 +18,13 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit/functional tests for cubicweb.server.hook"""
 
-from __future__ import with_statement
-
 from logilab.common.testlib import TestCase, unittest_main, mock_object
 
-
 from cubicweb.devtools import TestServerConfiguration, fake
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.server import hook
 from cubicweb.hooks import integrity, syncschema
 
-
 def clean_session_ops(func):
     def wrapper(self, *args, **kwargs):
         try:
--- a/server/test/unittest_ldapuser.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/test/unittest_ldapuser.py	Wed Mar 20 17:40:25 2013 +0100
@@ -16,7 +16,6 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb.server.sources.ldapusers unit and functional tests"""
-from __future__ import with_statement
 
 import os
 import shutil
--- a/server/test/unittest_migractions.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/test/unittest_migractions.py	Wed Mar 20 17:40:25 2013 +0100
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for module cubicweb.server.migractions"""
 
-from __future__ import with_statement
-
 from copy import deepcopy
 from datetime import date
 from os.path import join
@@ -474,7 +472,7 @@
     def test_add_remove_cube_and_deps(self):
         cubes = set(self.config.cubes())
         schema = self.repo.schema
-        self.assertEqual(sorted((str(s), str(o)) for s, o in schema['see_also'].rdefs.keys()),
+        self.assertEqual(sorted((str(s), str(o)) for s, o in schema['see_also'].rdefs.iterkeys()),
                           sorted([('EmailThread', 'EmailThread'), ('Folder', 'Folder'),
                                   ('Bookmark', 'Bookmark'), ('Bookmark', 'Note'),
                                   ('Note', 'Note'), ('Note', 'Bookmark')]))
@@ -489,7 +487,7 @@
                 for ertype in ('Email', 'EmailThread', 'EmailPart', 'File',
                                'sender', 'in_thread', 'reply_to', 'data_format'):
                     self.assertFalse(ertype in schema, ertype)
-                self.assertEqual(sorted(schema['see_also'].rdefs.keys()),
+                self.assertEqual(sorted(schema['see_also'].rdefs.iterkeys()),
                                   sorted([('Folder', 'Folder'),
                                           ('Bookmark', 'Bookmark'),
                                           ('Bookmark', 'Note'),
@@ -512,7 +510,7 @@
             for ertype in ('Email', 'EmailThread', 'EmailPart', 'File',
                            'sender', 'in_thread', 'reply_to', 'data_format'):
                 self.assertTrue(ertype in schema, ertype)
-            self.assertEqual(sorted(schema['see_also'].rdefs.keys()),
+            self.assertEqual(sorted(schema['see_also'].rdefs.iterkeys()),
                               sorted([('EmailThread', 'EmailThread'), ('Folder', 'Folder'),
                                       ('Bookmark', 'Bookmark'),
                                       ('Bookmark', 'Note'),
--- a/server/test/unittest_msplanner.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/test/unittest_msplanner.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for module cubicweb.server.msplanner"""
 
-from __future__ import with_statement
-
 from logilab.common.decorators import clear_cache
 from yams.buildobjs import RelationDefinition
 from rql import BadRQLQuery
@@ -151,7 +149,7 @@
         plan.preprocess(union)
         ppi = PartPlanInformation(plan, union.children[0])
         for sourcevars in ppi._sourcesterms.itervalues():
-            for var in sourcevars.keys():
+            for var in list(sourcevars):
                 solindices = sourcevars.pop(var)
                 sourcevars[var._ms_table_key()] = solindices
         self.assertEqual(ppi._sourcesterms, sourcesterms)
--- a/server/test/unittest_multisources.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/test/unittest_multisources.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -33,7 +33,6 @@
 MTIME = datetime.utcnow() - timedelta(0, 10)
 
 EXTERN_SOURCE_CFG = u'''
-pyro-ns-id = extern
 cubicweb-user = admin
 cubicweb-password = gingkow
 base-url=http://extern.org/
@@ -60,9 +59,10 @@
 
 def pre_setup_database_multi(session, config):
     session.create_entity('CWSource', name=u'extern', type=u'pyrorql',
-                                 config=EXTERN_SOURCE_CFG)
+                          url=u'pyro:///extern', config=EXTERN_SOURCE_CFG)
     session.commit()
 
+
 class TwoSourcesTC(CubicWebTC):
     """Main repo -> extern-multi -> extern
                   \-------------/
@@ -90,7 +90,6 @@
         cls.cnx3.close()
         TestServerConfiguration.no_sqlite_wrap = False
 
-
     @classmethod
     def _init_repo(cls):
         repo2_handler = get_test_db_handler(cls._cfg2)
@@ -122,12 +121,11 @@
     def pre_setup_database(session, config):
         for uri, src_config in [('extern', EXTERN_SOURCE_CFG),
                             ('extern-multi', '''
-pyro-ns-id = extern-multi
 cubicweb-user = admin
 cubicweb-password = gingkow
 ''')]:
             source = session.create_entity('CWSource', name=unicode(uri),
-                                           type=u'pyrorql',
+                                           type=u'pyrorql', url=u'pyro:///extern-multi',
                                            config=unicode(src_config))
             session.commit()
             add_extern_mapping(source)
--- a/server/test/unittest_postgres.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/test/unittest_postgres.py	Wed Mar 20 17:40:25 2013 +0100
@@ -16,8 +16,6 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 
-from __future__ import with_statement
-
 import socket
 from datetime import datetime
 
--- a/server/test/unittest_querier.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/test/unittest_querier.py	Wed Mar 20 17:40:25 2013 +0100
@@ -18,7 +18,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for modules cubicweb.server.querier and cubicweb.server.ssplanner
 """
-from __future__ import with_statement
 
 from datetime import date, datetime, timedelta, tzinfo
 
@@ -29,10 +28,10 @@
 from cubicweb.server.sqlutils import SQL_PREFIX
 from cubicweb.server.utils import crypt_password
 from cubicweb.server.sources.native import make_schema
+from cubicweb.server.querier import manual_build_descr, _make_description
 from cubicweb.devtools import get_test_db_handler, TestServerConfiguration
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.devtools.repotest import tuplify, BaseQuerierTC
-from unittest_session import Variable
 
 class FixedOffset(tzinfo):
     def __init__(self, hours=0):
@@ -87,6 +86,30 @@
     del repo, cnx
 
 
+class Variable:
+    def __init__(self, name):
+        self.name = name
+        self.children = []
+
+    def get_type(self, solution, args=None):
+        return solution[self.name]
+    def as_string(self):
+        return self.name
+
+class Function:
+    def __init__(self, name, varname):
+        self.name = name
+        self.children = [Variable(varname)]
+    def get_type(self, solution, args=None):
+        return 'Int'
+
+class MakeDescriptionTC(TestCase):
+    def test_known_values(self):
+        solution = {'A': 'Int', 'B': 'CWUser'}
+        self.assertEqual(_make_description((Function('max', 'A'), Variable('B')), {}, solution),
+                          ['Int','CWUser'])
+
+
 class UtilsTC(BaseQuerierTC):
     setUpClass = classmethod(setUpClass)
     tearDownClass = classmethod(tearDownClass)
@@ -242,6 +265,28 @@
         rset = self.execute('Any %(x)s', {'x': u'str'})
         self.assertEqual(rset.description[0][0], 'String')
 
+    def test_build_descr1(self):
+        rset = self.execute('(Any U,L WHERE U login L) UNION (Any G,N WHERE G name N, G is CWGroup)')
+        rset.req = self.session
+        orig_length = len(rset)
+        rset.rows[0][0] = 9999999
+        description = manual_build_descr(rset.req, rset.syntax_tree(), None, rset.rows)
+        self.assertEqual(len(description), orig_length - 1)
+        self.assertEqual(len(rset.rows), orig_length - 1)
+        self.assertNotEqual(rset.rows[0][0], 9999999)
+
+    def test_build_descr2(self):
+        rset = self.execute('Any X,Y WITH X,Y BEING ((Any G,NULL WHERE G is CWGroup) UNION (Any U,G WHERE U in_group G))')
+        for x, y in rset.description:
+            if y is not None:
+                self.assertEqual(y, 'CWGroup')
+
+    def test_build_descr3(self):
+        rset = self.execute('(Any G,NULL WHERE G is CWGroup) UNION (Any U,G WHERE U in_group G)')
+        for x, y in rset.description:
+            if y is not None:
+                self.assertEqual(y, 'CWGroup')
+
 
 class QuerierTC(BaseQuerierTC):
     setUpClass = classmethod(setUpClass)
--- a/server/test/unittest_repository.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/test/unittest_repository.py	Wed Mar 20 17:40:25 2013 +0100
@@ -18,8 +18,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for module cubicweb.server.repository"""
 
-from __future__ import with_statement
-
 import os
 import sys
 import threading
@@ -36,7 +34,7 @@
                       UnknownEid, AuthenticationError, Unauthorized, QueryError)
 from cubicweb.predicates import is_instance
 from cubicweb.schema import CubicWebSchema, RQLConstraint
-from cubicweb.dbapi import connect, multiple_connections_unfix, ConnectionProperties
+from cubicweb.dbapi import connect, multiple_connections_unfix
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.devtools.repotest import tuplify
 from cubicweb.server import repository, hook
@@ -360,7 +358,8 @@
 
 
     def _pyro_client(self, done):
-        cnx = connect(self.repo.config.appid, u'admin', password='gingkow',
+        cnx = connect('pyro:///'+self.repo.config.appid,
+                      u'admin', password='gingkow',
                       initlog=False) # don't reset logging configuration
         try:
             cnx.load_appobjects(subpath=('entities',))
@@ -395,7 +394,7 @@
         t.start()
 
         zmq_server = ZMQRepositoryServer(self.repo)
-        zmq_server.connect('tcp://127.0.0.1:41415')
+        zmq_server.connect('zmqpickle-tcp://127.0.0.1:41415')
 
         t2 = threading.Thread(target=self._zmq_quit, args=(done, zmq_server,))
         t2.start()
@@ -414,10 +413,8 @@
         srv.quit()
 
     def _zmq_client(self, done):
-        cnxprops = ConnectionProperties('zmq')
         try:
-            cnx = connect('tcp://127.0.0.1:41415', u'admin', password=u'gingkow',
-                          cnxprops=cnxprops,
+            cnx = connect('zmqpickle-tcp://127.0.0.1:41415', u'admin', password=u'gingkow',
                           initlog=False) # don't reset logging configuration
             try:
                 cnx.load_appobjects(subpath=('entities',))
@@ -522,7 +519,7 @@
         self.commit()
         self.assertEqual(len(c.reverse_fiche), 1)
 
-    def test_set_attributes_in_before_update(self):
+    def test_cw_set_in_before_update(self):
         # local hook
         class DummyBeforeHook(Hook):
             __regid__ = 'dummy-before-hook'
@@ -534,31 +531,31 @@
                 pendings = self._cw.transaction_data.setdefault('pending', set())
                 if self.entity.eid not in pendings:
                     pendings.add(self.entity.eid)
-                    self.entity.set_attributes(alias=u'foo')
+                    self.entity.cw_set(alias=u'foo')
         with self.temporary_appobjects(DummyBeforeHook):
             req = self.request()
             addr = req.create_entity('EmailAddress', address=u'a@b.fr')
-            addr.set_attributes(address=u'a@b.com')
+            addr.cw_set(address=u'a@b.com')
             rset = self.execute('Any A,AA WHERE X eid %(x)s, X address A, X alias AA',
                                 {'x': addr.eid})
             self.assertEqual(rset.rows, [[u'a@b.com', u'foo']])
 
-    def test_set_attributes_in_before_add(self):
+    def test_cw_set_in_before_add(self):
         # local hook
         class DummyBeforeHook(Hook):
             __regid__ = 'dummy-before-hook'
             __select__ = Hook.__select__ & is_instance('EmailAddress')
             events = ('before_add_entity',)
             def __call__(self):
-                # set_attributes is forbidden within before_add_entity()
-                self.entity.set_attributes(alias=u'foo')
+                # cw_set is forbidden within before_add_entity()
+                self.entity.cw_set(alias=u'foo')
         with self.temporary_appobjects(DummyBeforeHook):
             req = self.request()
             # XXX will fail with python -O
             self.assertRaises(AssertionError, req.create_entity,
                               'EmailAddress', address=u'a@b.fr')
 
-    def test_multiple_edit_set_attributes(self):
+    def test_multiple_edit_cw_set(self):
         """make sure cw_edited doesn't get cluttered
         by previous entities on multiple set
         """
@@ -664,7 +661,7 @@
         self.commit()
         rset = req.execute('Any X WHERE X has_text %(t)s', {'t': 'toto'})
         self.assertEqual(rset.rows, [])
-        req.user.set_relations(use_email=toto)
+        req.user.cw_set(use_email=toto)
         self.commit()
         rset = req.execute('Any X WHERE X has_text %(t)s', {'t': 'toto'})
         self.assertEqual(rset.rows, [[req.user.eid]])
@@ -674,11 +671,11 @@
         rset = req.execute('Any X WHERE X has_text %(t)s', {'t': 'toto'})
         self.assertEqual(rset.rows, [])
         tutu = req.create_entity('EmailAddress', address=u'tutu@logilab.fr')
-        req.user.set_relations(use_email=tutu)
+        req.user.cw_set(use_email=tutu)
         self.commit()
         rset = req.execute('Any X WHERE X has_text %(t)s', {'t': 'tutu'})
         self.assertEqual(rset.rows, [[req.user.eid]])
-        tutu.set_attributes(address=u'hip@logilab.fr')
+        tutu.cw_set(address=u'hip@logilab.fr')
         self.commit()
         rset = req.execute('Any X WHERE X has_text %(t)s', {'t': 'tutu'})
         self.assertEqual(rset.rows, [])
@@ -790,7 +787,7 @@
             personnes.append(p)
         abraham = req.create_entity('Personne', nom=u'Abraham', prenom=u'John', sexe=u'M')
         for j in xrange(0, 2000, 100):
-            abraham.set_relations(personne_composite=personnes[j:j+100])
+            abraham.cw_set(personne_composite=personnes[j:j+100])
         t1 = time.time()
         self.info('creation: %.2gs', (t1 - t0))
         req.cnx.commit()
@@ -816,7 +813,7 @@
         t1 = time.time()
         self.info('creation: %.2gs', (t1 - t0))
         for j in xrange(100, 2000, 100):
-            abraham.set_relations(personne_composite=personnes[j:j+100])
+            abraham.cw_set(personne_composite=personnes[j:j+100])
         t2 = time.time()
         self.info('more relations: %.2gs', (t2-t1))
         req.cnx.commit()
@@ -836,7 +833,7 @@
         t1 = time.time()
         self.info('creation: %.2gs', (t1 - t0))
         for j in xrange(100, 2000, 100):
-            abraham.set_relations(personne_inlined=personnes[j:j+100])
+            abraham.cw_set(personne_inlined=personnes[j:j+100])
         t2 = time.time()
         self.info('more relations: %.2gs', (t2-t1))
         req.cnx.commit()
@@ -917,7 +914,7 @@
         p1 = req.create_entity('Personne', nom=u'Vincent')
         p2 = req.create_entity('Personne', nom=u'Florent')
         w = req.create_entity('Affaire', ref=u'wc')
-        w.set_relations(todo_by=[p1,p2])
+        w.cw_set(todo_by=[p1,p2])
         w.cw_clear_all_caches()
         self.commit()
         self.assertEqual(len(w.todo_by), 1)
@@ -928,9 +925,9 @@
         p1 = req.create_entity('Personne', nom=u'Vincent')
         p2 = req.create_entity('Personne', nom=u'Florent')
         w = req.create_entity('Affaire', ref=u'wc')
-        w.set_relations(todo_by=p1)
+        w.cw_set(todo_by=p1)
         self.commit()
-        w.set_relations(todo_by=p2)
+        w.cw_set(todo_by=p2)
         w.cw_clear_all_caches()
         self.commit()
         self.assertEqual(len(w.todo_by), 1)
--- a/server/test/unittest_rql2sql.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/test/unittest_rql2sql.py	Wed Mar 20 17:40:25 2013 +0100
@@ -35,7 +35,7 @@
     supported_backends = ('postgres', 'sqlite', 'mysql')
 try:
     register_function(stockproc)
-except AssertionError, ex:
+except AssertionError as ex:
     pass # already registered
 
 
@@ -1294,7 +1294,7 @@
                                             varmap=varmap)
             args.update(nargs)
             self.assertMultiLineEqual(strip(r % args), self._norm_sql(sql))
-        except Exception, ex:
+        except Exception as ex:
             if 'r' in locals():
                 try:
                     print (r%args).strip()
@@ -1319,7 +1319,7 @@
             rqlst = self._prepare(rql)
             r, args, cbs = self.o.generate(rqlst, args)
             self.assertEqual((r.strip(), args), sql)
-        except Exception, ex:
+        except Exception as ex:
             print rql
             if 'r' in locals():
                 print r.strip()
--- a/server/test/unittest_security.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/test/unittest_security.py	Wed Mar 20 17:40:25 2013 +0100
@@ -16,7 +16,6 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """functional tests for server'security"""
-from __future__ import with_statement
 
 import sys
 
@@ -473,9 +472,9 @@
             anon = cu.connection.user(self.session)
             # anonymous user can only read itself
             rset = cu.execute('Any L WHERE X owned_by U, U login L')
-            self.assertEqual(rset.rows, [['anon']])
+            self.assertEqual([['anon']], rset.rows)
             rset = cu.execute('CWUser X')
-            self.assertEqual(rset.rows, [[anon.eid]])
+            self.assertEqual([[anon.eid]], rset.rows)
             # anonymous user can read groups (necessary to check allowed transitions for instance)
             self.assert_(cu.execute('CWGroup X'))
             # should only be able to read the anonymous user, not another one
@@ -488,7 +487,7 @@
             #                  {'x': self.user.eid})
 
             rset = cu.execute('CWUser X WHERE X eid %(x)s', {'x': anon.eid})
-            self.assertEqual(rset.rows, [[anon.eid]])
+            self.assertEqual([[anon.eid]], rset.rows)
             # but can't modify it
             cu.execute('SET X login "toto" WHERE X eid %(x)s', {'x': anon.eid})
             self.assertRaises(Unauthorized, self.commit)
--- a/server/test/unittest_session.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/test/unittest_session.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -15,36 +15,8 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-from __future__ import with_statement
-
-from logilab.common.testlib import TestCase, unittest_main, mock_object
 
 from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.server.session import _make_description, hooks_control
-
-class Variable:
-    def __init__(self, name):
-        self.name = name
-        self.children = []
-
-    def get_type(self, solution, args=None):
-        return solution[self.name]
-    def as_string(self):
-        return self.name
-
-class Function:
-    def __init__(self, name, varname):
-        self.name = name
-        self.children = [Variable(varname)]
-    def get_type(self, solution, args=None):
-        return 'Int'
-
-class MakeDescriptionTC(TestCase):
-    def test_known_values(self):
-        solution = {'A': 'Int', 'B': 'CWUser'}
-        self.assertEqual(_make_description((Function('max', 'A'), Variable('B')), {}, solution),
-                          ['Int','CWUser'])
-
 
 class InternalSessionTC(CubicWebTC):
     def test_dbapi_query(self):
@@ -61,7 +33,7 @@
         self.assertEqual(session.disabled_hook_categories, set())
         self.assertEqual(session.enabled_hook_categories, set())
         self.assertEqual(len(session._tx_data), 1)
-        with hooks_control(session, session.HOOKS_DENY_ALL, 'metadata'):
+        with session.deny_all_hooks_but('metadata'):
             self.assertEqual(session.hooks_mode, session.HOOKS_DENY_ALL)
             self.assertEqual(session.disabled_hook_categories, set())
             self.assertEqual(session.enabled_hook_categories, set(('metadata',)))
@@ -73,7 +45,7 @@
             self.assertEqual(session.hooks_mode, session.HOOKS_DENY_ALL)
             self.assertEqual(session.disabled_hook_categories, set())
             self.assertEqual(session.enabled_hook_categories, set(('metadata',)))
-            with hooks_control(session, session.HOOKS_ALLOW_ALL, 'integrity'):
+            with session.allow_all_hooks_but('integrity'):
                 self.assertEqual(session.hooks_mode, session.HOOKS_ALLOW_ALL)
                 self.assertEqual(session.disabled_hook_categories, set(('integrity',)))
                 self.assertEqual(session.enabled_hook_categories, set(('metadata',))) # not changed in such case
@@ -88,27 +60,7 @@
         self.assertEqual(session.disabled_hook_categories, set())
         self.assertEqual(session.enabled_hook_categories, set())
 
-    def test_build_descr1(self):
-        rset = self.execute('(Any U,L WHERE U login L) UNION (Any G,N WHERE G name N, G is CWGroup)')
-        orig_length = len(rset)
-        rset.rows[0][0] = 9999999
-        description = self.session.build_description(rset.syntax_tree(), None, rset.rows)
-        self.assertEqual(len(description), orig_length - 1)
-        self.assertEqual(len(rset.rows), orig_length - 1)
-        self.assertFalse(rset.rows[0][0] == 9999999)
-
-    def test_build_descr2(self):
-        rset = self.execute('Any X,Y WITH X,Y BEING ((Any G,NULL WHERE G is CWGroup) UNION (Any U,G WHERE U in_group G))')
-        for x, y in rset.description:
-            if y is not None:
-                self.assertEqual(y, 'CWGroup')
-
-    def test_build_descr3(self):
-        rset = self.execute('(Any G,NULL WHERE G is CWGroup) UNION (Any U,G WHERE U in_group G)')
-        for x, y in rset.description:
-            if y is not None:
-                self.assertEqual(y, 'CWGroup')
-
 
 if __name__ == '__main__':
+    from logilab.common.testlib import unittest_main
     unittest_main()
--- a/server/test/unittest_storage.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/test/unittest_storage.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for module cubicweb.server.sources.storages"""
 
-from __future__ import with_statement
-
 from logilab.common.testlib import unittest_main, tag, Tags
 from cubicweb.devtools.testlib import CubicWebTC
 
@@ -99,7 +97,7 @@
         f1 = self.create_file()
         self.commit()
         self.assertEqual(file(expected_filepath).read(), 'the-data')
-        f1.set_attributes(data=Binary('the new data'))
+        f1.cw_set(data=Binary('the new data'))
         self.rollback()
         self.assertEqual(file(expected_filepath).read(), 'the-data')
         f1.cw_delete()
@@ -118,7 +116,7 @@
     def test_bfss_fs_importing_doesnt_touch_path(self):
         self.session.transaction_data['fs_importing'] = True
         filepath = osp.abspath(__file__)
-        f1 = self.session.create_entity('File', data=Binary(filepath),
+        f1 = self.request().create_entity('File', data=Binary(filepath),
                                         data_format=u'text/plain', data_name=u'foo')
         self.assertEqual(self.fspath(f1), filepath)
 
@@ -196,15 +194,18 @@
         filepath = osp.abspath(__file__)
         f1 = self.session.create_entity('File', data=Binary(filepath),
                                         data_format=u'text/plain', data_name=u'foo')
-        self.assertEqual(f1.data.getvalue(), file(filepath).read(),
-                          'files content differ')
+        cw_value = f1.data.getvalue()
+        fs_value = file(filepath).read()
+        if cw_value != fs_value:
+            self.fail('cw value %r is different from file content' % cw_value)
+
 
     @tag('update')
     def test_bfss_update_with_existing_data(self):
         # use self.session to use server-side cache
         f1 = self.session.create_entity('File', data=Binary('some data'),
                                         data_format=u'text/plain', data_name=u'foo')
-        # NOTE: do not use set_attributes() which would automatically
+        # NOTE: do not use cw_set() which would automatically
         #       update f1's local dict. We want the pure rql version to work
         self.execute('SET F data %(d)s WHERE F eid %(f)s',
                      {'d': Binary('some other data'), 'f': f1.eid})
@@ -218,7 +219,7 @@
         # use self.session to use server-side cache
         f1 = self.session.create_entity('File', data=Binary('some data'),
                                         data_format=u'text/plain', data_name=u'foo.txt')
-        # NOTE: do not use set_attributes() which would automatically
+        # NOTE: do not use cw_set() which would automatically
         #       update f1's local dict. We want the pure rql version to work
         self.commit()
         old_path = self.fspath(f1)
@@ -240,7 +241,7 @@
         # use self.session to use server-side cache
         f1 = self.session.create_entity('File', data=Binary('some data'),
                                         data_format=u'text/plain', data_name=u'foo.txt')
-        # NOTE: do not use set_attributes() which would automatically
+        # NOTE: do not use cw_set() which would automatically
         #       update f1's local dict. We want the pure rql version to work
         self.commit()
         old_path = self.fspath(f1)
@@ -265,7 +266,7 @@
         f = self.session.create_entity('Affaire', opt_attr=Binary('toto'))
         self.session.commit()
         self.session.set_cnxset()
-        f.set_attributes(opt_attr=None)
+        f.cw_set(opt_attr=None)
         self.session.commit()
 
     @tag('fs_importing', 'update')
--- a/server/test/unittest_undo.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/test/unittest_undo.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -16,7 +16,6 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-from __future__ import with_statement
 
 from cubicweb import ValidationError
 from cubicweb.devtools.testlib import CubicWebTC
@@ -53,7 +52,7 @@
             expected_errors = []
         try:
             self.cnx.undo_transaction(txuuid)
-        except UndoTransactionException, exn:
+        except UndoTransactionException as exn:
             errors = exn.errors
         else:
             errors = []
@@ -203,7 +202,7 @@
         c.cw_delete()
         txuuid = self.commit()
         c2 = session.create_entity('Card', title=u'hip', content=u'hip')
-        p.set_relations(fiche=c2)
+        p.cw_set(fiche=c2)
         self.commit()
         self.assertUndoTransaction(txuuid, [
             "Can't restore object relation fiche to entity "
@@ -217,7 +216,7 @@
         session = self.session
         g = session.create_entity('CWGroup', name=u'staff')
         session.execute('DELETE U in_group G WHERE U eid %(x)s', {'x': self.toto.eid})
-        self.toto.set_relations(in_group=g)
+        self.toto.cw_set(in_group=g)
         self.commit()
         self.toto.cw_delete()
         txuuid = self.commit()
@@ -228,6 +227,7 @@
             "%s doesn't exist anymore." % g.eid])
         with self.assertRaises(ValidationError) as cm:
             self.commit()
+        cm.exception.translate(unicode)
         self.assertEqual(cm.exception.entity, self.toto.eid)
         self.assertEqual(cm.exception.errors,
                           {'in_group-subject': u'at least one relation in_group is '
@@ -265,7 +265,7 @@
         email = self.request().create_entity('EmailAddress', address=u'tutu@cubicweb.org')
         prop = self.request().create_entity('CWProperty', pkey=u'ui.default-text-format',
                                             value=u'text/html')
-        tutu.set_relations(use_email=email, reverse_for_user=prop)
+        tutu.cw_set(use_email=email, reverse_for_user=prop)
         self.commit()
         with self.assertRaises(ValidationError) as cm:
             self.cnx.undo_transaction(txuuid)
@@ -278,7 +278,7 @@
         g = session.create_entity('CWGroup', name=u'staff')
         txuuid = self.commit()
         session.execute('DELETE U in_group G WHERE U eid %(x)s', {'x': self.toto.eid})
-        self.toto.set_relations(in_group=g)
+        self.toto.cw_set(in_group=g)
         self.commit()
         with self.assertRaises(ValidationError) as cm:
             self.cnx.undo_transaction(txuuid)
@@ -304,7 +304,7 @@
         c = session.create_entity('Card', title=u'hop', content=u'hop')
         p = session.create_entity('Personne', nom=u'louis', fiche=c)
         self.commit()
-        p.set_relations(fiche=None)
+        p.cw_set(fiche=None)
         txuuid = self.commit()
         self.assertUndoTransaction(txuuid)
         self.commit()
@@ -319,7 +319,7 @@
         c = session.create_entity('Card', title=u'hop', content=u'hop')
         p = session.create_entity('Personne', nom=u'louis', fiche=c)
         self.commit()
-        p.set_relations(fiche=None)
+        p.cw_set(fiche=None)
         txuuid = self.commit()
         c.cw_delete()
         self.commit()
@@ -339,7 +339,7 @@
         c = session.create_entity('Card', title=u'hop', content=u'hop')
         p = session.create_entity('Personne', nom=u'louis')
         self.commit()
-        p.set_relations(fiche=c)
+        p.cw_set(fiche=c)
         txuuid = self.commit()
         self.assertUndoTransaction(txuuid)
         self.commit()
@@ -354,7 +354,7 @@
         c = session.create_entity('Card', title=u'hop', content=u'hop')
         p = session.create_entity('Personne', nom=u'louis')
         self.commit()
-        p.set_relations(fiche=c)
+        p.cw_set(fiche=c)
         txuuid = self.commit()
         c.cw_delete()
         self.commit()
@@ -369,7 +369,7 @@
         c2 = session.create_entity('Card', title=u'hip', content=u'hip')
         p = session.create_entity('Personne', nom=u'louis', fiche=c1)
         self.commit()
-        p.set_relations(fiche=c2)
+        p.cw_set(fiche=c2)
         txuuid = self.commit()
         self.assertUndoTransaction(txuuid)
         self.commit()
@@ -385,7 +385,7 @@
         c2 = session.create_entity('Card', title=u'hip', content=u'hip')
         p = session.create_entity('Personne', nom=u'louis', fiche=c1)
         self.commit()
-        p.set_relations(fiche=c2)
+        p.cw_set(fiche=c2)
         txuuid = self.commit()
         c1.cw_delete()
         self.commit()
@@ -401,7 +401,7 @@
         p = session.create_entity('Personne', nom=u'toto')
         session.commit()
         self.session.set_cnxset()
-        p.set_attributes(nom=u'titi')
+        p.cw_set(nom=u'titi')
         txuuid = self.commit()
         self.assertUndoTransaction(txuuid)
         p.cw_clear_all_caches()
@@ -412,7 +412,7 @@
         p = session.create_entity('Personne', nom=u'toto')
         session.commit()
         self.session.set_cnxset()
-        p.set_attributes(nom=u'titi')
+        p.cw_set(nom=u'titi')
         txuuid = self.commit()
         p.cw_delete()
         self.commit()
--- a/server/utils.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/server/utils.py	Wed Mar 20 17:40:25 2013 +0100
@@ -93,7 +93,7 @@
 
 def cleanup_solutions(rqlst, solutions):
     for sol in solutions:
-        for vname in sol.keys():
+        for vname in list(sol):
             if not (vname in rqlst.defined_vars or vname in rqlst.aliases):
                 del sol[vname]
 
--- a/setup.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/setup.py	Wed Mar 20 17:40:25 2013 +0100
@@ -129,7 +129,7 @@
                 shutil.copy2(src, dest)
     try:
         os.mkdir(to_dir)
-    except OSError, ex:
+    except OSError as ex:
         # file exists ?
         import errno
         if ex.errno != errno.EEXIST:
--- a/skeleton/setup.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/skeleton/setup.py	Wed Mar 20 17:40:25 2013 +0100
@@ -115,7 +115,7 @@
                 shutil.copy2(src, dest)
     try:
         os.mkdir(to_dir)
-    except OSError, ex:
+    except OSError as ex:
         # file exists ?
         import errno
         if ex.errno != errno.EEXIST:
--- a/sobjects/__init__.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/sobjects/__init__.py	Wed Mar 20 17:40:25 2013 +0100
@@ -20,7 +20,7 @@
 import os.path as osp
 
 def registration_callback(vreg):
-    vreg.register_all(globals().values(), __name__)
+    vreg.register_all(globals().itervalues(), __name__)
     global URL_MAPPING
     URL_MAPPING = {}
     if vreg.config.apphome:
--- a/sobjects/ldapparser.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/sobjects/ldapparser.py	Wed Mar 20 17:40:25 2013 +0100
@@ -20,8 +20,6 @@
 unlike ldapuser source, this source is copy based and will import ldap content
 (beside passwords for authentication) into the system source.
 """
-from __future__ import with_statement
-
 from logilab.common.decorators import cached, cachedproperty
 from logilab.common.shellutils import generate_password
 
@@ -97,7 +95,7 @@
             attrs = dict( (k, v) for k, v in attrs.iteritems()
                           if v != getattr(entity, k))
             if attrs:
-                entity.set_attributes(**attrs)
+                entity.cw_set(**attrs)
                 self.notify_updated(entity)
 
     def ldap2cwattrs(self, sdict, tdict=None):
@@ -133,7 +131,7 @@
         groups = filter(None, [self._get_group(name)
                                for name in self.source.user_default_groups])
         if groups:
-            entity.set_relations(in_group=groups)
+            entity.cw_set(in_group=groups)
         self._process_email(entity, sourceparams)
 
     def is_deleted(self, extidplus, etype, eid):
@@ -162,9 +160,9 @@
                 email = self.extid2entity(emailextid, 'EmailAddress',
                                           address=emailaddr)
                 if entity.primary_email:
-                    entity.set_relations(use_email=email)
+                    entity.cw_set(use_email=email)
                 else:
-                    entity.set_relations(primary_email=email)
+                    entity.cw_set(primary_email=email)
             elif self.sourceuris:
                 # pop from sourceuris anyway, else email may be removed by the
                 # source once import is finished
--- a/sobjects/test/unittest_cwxmlparser.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/sobjects/test/unittest_cwxmlparser.py	Wed Mar 20 17:40:25 2013 +0100
@@ -16,12 +16,9 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 
-from __future__ import with_statement
-
 from datetime import datetime
 
 from cubicweb.devtools.testlib import CubicWebTC
-
 from cubicweb.sobjects.cwxmlparser import CWEntityXMLParser
 
 orig_parse = CWEntityXMLParser.parse
@@ -197,7 +194,7 @@
                           })
         session = self.repo.internal_session(safe=True)
         stats = dfsource.pull_data(session, force=True, raise_on_error=True)
-        self.assertEqual(sorted(stats.keys()), ['checked', 'created', 'updated'])
+        self.assertEqual(sorted(stats), ['checked', 'created', 'updated'])
         self.assertEqual(len(stats['created']), 2)
         self.assertEqual(stats['updated'], set())
 
--- a/test/unittest_cwconfig.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/test/unittest_cwconfig.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -101,10 +101,10 @@
         self.assertEqual(self.config.expand_cubes(('email', 'comment')),
                           ['email', 'comment', 'file'])
 
-    def test_vregistry_path(self):
+    def test_appobjects_path(self):
         self.config.__class__.CUBES_PATH = [CUSTOM_CUBES_DIR]
         self.config.adjust_sys_path()
-        self.assertEqual([unabsolutize(p) for p in self.config.vregistry_path()],
+        self.assertEqual([unabsolutize(p) for p in self.config.appobjects_path()],
                           ['entities', 'web/views', 'sobjects', 'hooks',
                            'file/entities', 'file/views.py', 'file/hooks',
                            'email/entities.py', 'email/views', 'email/hooks.py',
--- a/test/unittest_dbapi.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/test/unittest_dbapi.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unittest for cubicweb.dbapi"""
 
-from __future__ import with_statement
-
 from copy import copy
 
 from logilab.common import tempattr
--- a/test/unittest_entity.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/test/unittest_entity.py	Wed Mar 20 17:40:25 2013 +0100
@@ -18,8 +18,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for cubicweb.web.views.entities module"""
 
-from __future__ import with_statement
-
 from datetime import datetime
 
 from logilab.common import tempattr
@@ -129,11 +127,11 @@
         self.assertEqual(user._cw_related_cache, {})
         email = user.primary_email[0]
         self.assertEqual(sorted(user._cw_related_cache), ['primary_email_subject'])
-        self.assertEqual(email._cw_related_cache.keys(), ['primary_email_object'])
+        self.assertEqual(list(email._cw_related_cache), ['primary_email_object'])
         groups = user.in_group
         self.assertEqual(sorted(user._cw_related_cache), ['in_group_subject', 'primary_email_subject'])
         for group in groups:
-            self.assertFalse('in_group_subject' in group._cw_related_cache, group._cw_related_cache.keys())
+            self.assertFalse('in_group_subject' in group._cw_related_cache, list(group._cw_related_cache))
 
     def test_related_limit(self):
         req = self.request()
@@ -143,6 +141,9 @@
         self.execute('SET X tags Y WHERE X is Tag, Y is Personne')
         self.assertEqual(len(p.related('tags', 'object', limit=2)), 2)
         self.assertEqual(len(p.related('tags', 'object')), 4)
+        p.cw_clear_all_caches()
+        self.assertEqual(len(p.related('tags', 'object', entities=True, limit=2)), 2)
+        self.assertEqual(len(p.related('tags', 'object', entities=True)), 4)
 
     def test_cw_instantiate_relation(self):
         req = self.request()
@@ -701,23 +702,23 @@
         self.assertEqual(card4.rest_path(), unicode(card4.eid))
 
 
-    def test_set_attributes(self):
+    def test_cw_set_attributes(self):
         req = self.request()
         person = req.create_entity('Personne', nom=u'di mascio', prenom=u'adrien')
         self.assertEqual(person.prenom, u'adrien')
         self.assertEqual(person.nom, u'di mascio')
-        person.set_attributes(prenom=u'sylvain', nom=u'thénault')
+        person.cw_set(prenom=u'sylvain', nom=u'thénault')
         person = self.execute('Personne P').get_entity(0, 0) # XXX retreival needed ?
         self.assertEqual(person.prenom, u'sylvain')
         self.assertEqual(person.nom, u'thénault')
 
-    def test_set_relations(self):
+    def test_cw_set_relations(self):
         req = self.request()
         person = req.create_entity('Personne', nom=u'chauvat', prenom=u'nicolas')
         note = req.create_entity('Note', type=u'x')
-        note.set_relations(ecrit_par=person)
+        note.cw_set(ecrit_par=person)
         note = req.create_entity('Note', type=u'y')
-        note.set_relations(ecrit_par=person.eid)
+        note.cw_set(ecrit_par=person.eid)
         self.assertEqual(len(person.reverse_ecrit_par), 2)
 
     def test_metainformation_and_external_absolute_url(self):
--- a/test/unittest_migration.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/test/unittest_migration.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -28,7 +28,7 @@
 
 class Schema(dict):
     def has_entity(self, e_type):
-        return self.has_key(e_type)
+        return e_type in self
 
 SMIGRDIR = join(dirname(__file__), 'data', 'server_migration') + '/'
 TMIGRDIR = join(dirname(__file__), 'data', 'migration') + '/'
@@ -108,7 +108,13 @@
         self.assertEqual(source['db-driver'], 'sqlite')
         handler = get_test_db_handler(config)
         handler.init_test_database()
-
+        handler.build_db_cache()
+        repo, cnx = handler.get_repo_and_cnx()
+        cu = cnx.cursor()
+        self.assertEqual(cu.execute('Any SN WHERE X is CWUser, X login "admin", X in_state S, S name SN').rows,
+                          [['activated']])
+        cnx.close()
+        repo.shutdown()
 
 if __name__ == '__main__':
     unittest_main()
--- a/test/unittest_predicates.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/test/unittest_predicates.py	Wed Mar 20 17:40:25 2013 +0100
@@ -16,7 +16,6 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for selectors mechanism"""
-from __future__ import with_statement
 
 from operator import eq, lt, le, gt
 from logilab.common.testlib import TestCase, unittest_main
--- a/test/unittest_rset.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/test/unittest_rset.py	Wed Mar 20 17:40:25 2013 +0100
@@ -112,6 +112,17 @@
         #                  '%stask/title/go' % baseurl)
         # empty _restpath should not crash
         self.compare_urls(req.build_url('view', _restpath=''), baseurl)
+        self.assertNotIn('https', req.build_url('view', vid='foo', rql='yo',
+                                                  __secure__=True))
+        try:
+            self.config.global_set_option('https-url', 'https://testing.fr/')
+            self.assertTrue('https', req.build_url('view', vid='foo', rql='yo',
+                                                     __secure__=True))
+            self.compare_urls(req.build_url('view', vid='foo', rql='yo',
+                                            __secure__=True),
+                              '%sview?vid=foo&rql=yo' % req.base_url(secure=True))
+        finally:
+            self.config.global_set_option('https-url', None)
 
 
     def test_build(self):
@@ -334,9 +345,9 @@
         e = rset.get_entity(0, 0)
         # if any of the assertion below fails with a KeyError, the relation is not cached
         # related entities should be an empty list
-        self.assertEqual(e._cw_relation_cache('primary_email', 'subject', True), ())
+        self.assertEqual(e._cw_related_cache['primary_email_subject'][True], ())
         # related rset should be an empty rset
-        cached = e._cw_relation_cache('primary_email', 'subject', False)
+        cached = e._cw_related_cache['primary_email_subject'][False]
         self.assertIsInstance(cached, ResultSet)
         self.assertEqual(cached.rowcount, 0)
 
--- a/test/unittest_schema.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/test/unittest_schema.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for module cubicweb.schema"""
 
-from __future__ import with_statement
-
 import sys
 from os.path import join, isabs, basename, dirname
 
@@ -350,8 +348,8 @@
 
 class WorkflowShemaTC(CubicWebTC):
     def test_trinfo_default_format(self):
-         tr = self.session.user.cw_adapt_to('IWorkflowable').fire_transition('deactivate')
-         self.assertEqual(tr.comment_format, 'text/plain')
+        tr = self.request().user.cw_adapt_to('IWorkflowable').fire_transition('deactivate')
+        self.assertEqual(tr.comment_format, 'text/plain')
 
 if __name__ == '__main__':
     unittest_main()
--- a/test/unittest_utils.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/test/unittest_utils.py	Wed Mar 20 17:40:25 2013 +0100
@@ -26,7 +26,7 @@
 
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.utils import (make_uid, UStringIO, SizeConstrainedList,
-                            RepeatList, HTMLHead, QueryCache)
+                            RepeatList, HTMLHead, QueryCache, parse_repo_uri)
 from cubicweb.entity import Entity
 
 try:
@@ -50,6 +50,25 @@
                           'some numeric character, got %s' % uid)
             d.add(uid)
 
+
+class TestParseRepoUri(TestCase):
+
+    def test_parse_repo_uri(self):
+        self.assertEqual(('inmemory', None, 'myapp'),
+                         parse_repo_uri('myapp'))
+        self.assertEqual(('inmemory', None, 'myapp'),
+                         parse_repo_uri('inmemory://myapp'))
+        self.assertEqual(('pyro', 'pyro-ns-host:pyro-ns-port', '/myapp'),
+                         parse_repo_uri('pyro://pyro-ns-host:pyro-ns-port/myapp'))
+        self.assertEqual(('pyroloc', 'host:port', '/appkey'),
+                         parse_repo_uri('pyroloc://host:port/appkey'))
+        self.assertEqual(('zmqpickle-tcp', '127.0.0.1:666', ''),
+                         parse_repo_uri('zmqpickle-tcp://127.0.0.1:666'))
+        with self.assertRaises(NotImplementedError):
+            parse_repo_uri('foo://bar')
+
+
+
 class TestQueryCache(TestCase):
     def test_querycache(self):
         c = QueryCache(ceiling=20)
--- a/testfunc/test/windmill/test_connexion.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/testfunc/test/windmill/test_connexion.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,5 +1,5 @@
 from cubicweb.devtools import DEFAULT_SOURCES
-LOGIN, PASSWORD = DEFAULT_SOURCES['admin'].values()
+LOGIN, PASSWORD = DEFAULT_SOURCES['admin'].itervalues()
 
 # Generated by the windmill services transformer
 from windmill.authoring import WindmillTestClient
--- a/testfunc/test/windmill/test_creation.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/testfunc/test/windmill/test_creation.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,5 +1,5 @@
 from cubicweb.devtools import DEFAULT_SOURCES
-LOGIN, PASSWORD = DEFAULT_SOURCES['admin'].values()
+LOGIN, PASSWORD = DEFAULT_SOURCES['admin'].itervalues()
 
 # Generated by the windmill services transformer
 from windmill.authoring import WindmillTestClient
--- a/testfunc/test/windmill/test_edit_relation.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/testfunc/test/windmill/test_edit_relation.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,5 +1,5 @@
 from cubicweb.devtools import DEFAULT_SOURCES
-LOGIN, PASSWORD = DEFAULT_SOURCES['admin'].values()
+LOGIN, PASSWORD = DEFAULT_SOURCES['admin'].itervalues()
 
 # Generated by the windmill services transformer
 from windmill.authoring import WindmillTestClient
--- a/toolsutils.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/toolsutils.py	Wed Mar 20 17:40:25 2013 +0100
@@ -59,7 +59,7 @@
     try:
         makedirs(directory)
         print '-> created directory %s' % directory
-    except OSError, ex:
+    except OSError as ex:
         import errno
         if ex.errno != errno.EEXIST:
             raise
@@ -176,7 +176,7 @@
                 if option[0] == '[':
                     # start a section
                     section = option[1:-1]
-                    assert not config.has_key(section), \
+                    assert section not in config, \
                            'Section %s is defined more than once' % section
                     config[section] = current = {}
                     continue
@@ -185,7 +185,7 @@
             option = option.strip().replace(' ', '_')
             value = value.strip()
             current[option] = value or None
-    except IOError, ex:
+    except IOError as ex:
         if raise_if_unreadable:
             raise ExecutionError('%s. Are you logged with the correct user '
                                  'to use this instance?' % ex)
--- a/utils.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/utils.py	Wed Mar 20 17:40:25 2013 +0100
@@ -33,6 +33,7 @@
 from uuid import uuid4
 from warnings import warn
 from threading import Lock
+from urlparse import urlparse
 
 from logging import getLogger
 
@@ -574,6 +575,25 @@
     return dict1
 
 
+def parse_repo_uri(uri):
+    """ transform a command line uri into a (protocol, hostport, appid), e.g:
+    <myapp>                      -> 'inmemory', None, '<myapp>'
+    inmemory://<myapp>           -> 'inmemory', None, '<myapp>'
+    pyro://[host][:port]         -> 'pyro', 'host:port', None
+    zmqpickle://[host][:port]    -> 'zmqpickle', 'host:port', None
+    """
+    parseduri = urlparse(uri)
+    scheme = parseduri.scheme
+    if scheme == '':
+        return ('inmemory', None, parseduri.path)
+    if scheme == 'inmemory':
+        return (scheme, None, parseduri.netloc)
+    if scheme in ('pyro', 'pyroloc') or scheme.startswith('zmqpickle-'):
+        return (scheme, parseduri.netloc, parseduri.path)
+    raise NotImplementedError('URI protocol not implemented for `%s`' % uri)
+
+
+
 logger = getLogger('cubicweb.utils')
 
 class QueryCache(object):
--- a/view.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/view.py	Wed Mar 20 17:40:25 2013 +0100
@@ -26,7 +26,7 @@
 from functools import partial
 
 from logilab.common.deprecation import deprecated
-from logilab.common.registry import classid, yes
+from logilab.common.registry import yes
 from logilab.mtconverter import xml_escape
 
 from rql import nodes
@@ -608,7 +608,7 @@
             if hasattr(entity, func.__name__):
                 warn('[3.9] %s method is deprecated, define it on a custom '
                      '%s for %s instead' % (func.__name__, iface,
-                                            classid(entity.__class__)),
+                                            entity.__class__),
                      DeprecationWarning)
                 member = getattr(entity, func.__name__)
                 if callable(member):
--- a/web/application.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/application.py	Wed Mar 20 17:40:25 2013 +0100
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """CubicWeb web client application object"""
 
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 
 import sys
@@ -364,7 +362,7 @@
                 ### Try to generate the actual request content
                 content = self.core_handle(req, path)
             # Handle user log-out
-            except LogOut, ex:
+            except LogOut as ex:
                 # When authentification is handled by cookie the code that
                 # raised LogOut must has invalidated the cookie. We can just
                 # reload the original url without authentification
@@ -382,7 +380,7 @@
                     content = self.loggedout_content(req)
                     # let the explicitly reset http credential
                     raise AuthenticationError()
-        except Redirect, ex:
+        except Redirect as ex:
             # authentication needs redirection (eg openid)
             content = self.redirect_handler(req, ex)
         # Wrong, absent or Reseted credential
@@ -427,7 +425,7 @@
         """
         # don't log form values they may contains sensitive information
         self.debug('publish "%s" (%s, form params: %s)',
-                   path, req.session.sessionid, req.form.keys())
+                   path, req.session.sessionid, list(req.form))
         # remove user callbacks on a new request (except for json controllers
         # to avoid callbacks being unregistered before they could be called)
         tstart = clock()
@@ -443,12 +441,12 @@
                     raise Unauthorized(req._('not authorized'))
                 req.update_search_state()
                 result = controller.publish(rset=rset)
-            except StatusResponse, ex:
+            except StatusResponse as ex:
                 warn('StatusResponse is deprecated use req.status_out',
                      DeprecationWarning)
                 result = ex.content
                 req.status_out = ex.status
-            except Redirect, ex:
+            except Redirect as ex:
                 # Redirect may be raised by edit controller when everything went
                 # fine, so attempt to commit
                 result = self.redirect_handler(req, ex)
@@ -458,25 +456,24 @@
                 if txuuid is not None:
                     req.data['last_undoable_transaction'] = txuuid
         ### error case
-        except NotFound, ex:
+        except NotFound as ex:
             result = self.notfound_content(req)
             req.status_out = ex.status
-        except ValidationError, ex:
-            req.status_out = httplib.CONFLICT
+        except ValidationError as ex:
             result = self.validation_error_handler(req, ex)
-        except RemoteCallFailed, ex:
+        except RemoteCallFailed as ex:
             result = self.ajax_error_handler(req, ex)
-        except Unauthorized, ex:
+        except Unauthorized as ex:
             req.data['errmsg'] = req._('You\'re not authorized to access this page. '
                                        'If you think you should, please contact the site administrator.')
             req.status_out = httplib.UNAUTHORIZED
             result = self.error_handler(req, ex, tb=False)
-        except Forbidden, ex:
+        except Forbidden as ex:
             req.data['errmsg'] = req._('This action is forbidden. '
                                        'If you think it should be allowed, please contact the site administrator.')
             req.status_out = httplib.FORBIDDEN
             result = self.error_handler(req, ex, tb=False)
-        except (BadRQLQuery, RequestError), ex:
+        except (BadRQLQuery, RequestError) as ex:
             result = self.error_handler(req, ex, tb=False)
         ### pass through exception
         except DirectResponse:
@@ -486,8 +483,8 @@
         except (AuthenticationError, LogOut):
             # the rollback is handled in the finally
             raise
-        ### Last defence line
-        except BaseException, ex:
+        ### Last defense line
+        except BaseException as ex:
             result = self.error_handler(req, ex, tb=True)
         finally:
             if req.cnx and not commited:
@@ -517,7 +514,7 @@
         return ''
 
     def validation_error_handler(self, req, ex):
-        ex.errors = dict((k, v) for k, v in ex.errors.items())
+        ex.translate(req._) # translate messages using ui language
         if '__errorurl' in req.form:
             forminfo = {'error': ex,
                         'values': req.form,
@@ -532,6 +529,7 @@
             req.headers_out.setHeader('location', str(location))
             req.status_out = httplib.SEE_OTHER
             return ''
+        req.status_out = httplib.CONFLICT
         return self.error_handler(req, ex, tb=False)
 
     def error_handler(self, req, ex, tb=False):
--- a/web/component.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/component.py	Wed Mar 20 17:40:25 2013 +0100
@@ -258,7 +258,7 @@
         view = self.cw_extra_kwargs['view']
         try:
             view.init_rendering()
-        except Unauthorized, ex:
+        except Unauthorized as ex:
             self.warning("can't render %s: %s", view, ex)
             return False
         except EmptyComponent:
--- a/web/controller.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/controller.py	Wed Mar 20 17:40:25 2013 +0100
@@ -127,9 +127,9 @@
         msg = self._cw.message
         if msg:
             newparams['_cwmsgid'] = self._cw.set_redirect_message(msg)
-        if self._cw.form.has_key('__action_apply'):
+        if '__action_apply' in self._cw.form:
             self._return_to_edition_view(newparams)
-        if self._cw.form.has_key('__action_cancel'):
+        if '__action_cancel' in self._cw.form:
             self._return_to_lastpage(newparams)
         else:
             self._return_to_original_view(newparams)
@@ -152,7 +152,7 @@
                 and '_cwmsgid' in newparams):
                 # are we here on creation or modification?
                 if any(eid == self._edited_entity.eid
-                       for eid in self._cw.data.get('eidmap', {}).values()):
+                       for eid in self._cw.data.get('eidmap', {}).itervalues()):
                     msg = self._cw._('click here to see created entity')
                 else:
                     msg = self._cw._('click here to see edited entity')
--- a/web/data/cubicweb.ajax.js	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/data/cubicweb.ajax.js	Wed Mar 20 17:40:25 2013 +0100
@@ -70,7 +70,7 @@
                 callback.apply(null, args);
             }
         } catch(error) {
-            this.error(this.xhr, null, error);
+            this.error(this._req, null, error);
         }
     },
 
@@ -704,7 +704,7 @@
     var ajaxArgs = ['render', formparams, registry, compid];
     ajaxArgs = ajaxArgs.concat(cw.utils.sliceList(arguments, 4));
     var params = ajaxFuncArgs.apply(null, ajaxArgs);
-    return $('#'+domid).loadxhtml(AJAX_BASE_URL, params, null, 'swap');
+    return $('#'+domid).loadxhtml(AJAX_BASE_URL, params, null, 'swap', true);
 }
 
 /* ajax tabs ******************************************************************/
--- a/web/data/cubicweb.css	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/data/cubicweb.css	Wed Mar 20 17:40:25 2013 +0100
@@ -545,6 +545,16 @@
   padding-left: 2em;
 }
 
+/* actions around tables */
+.tableactions span {
+  padding: 0 18px;
+  height: 24px;
+  background: #F8F8F8;
+  border: 1px solid #DFDFDF;
+  border-bottom: none;
+  border-radius: 4px 4px 0 0;
+}
+
 /* custom boxes */
 
 .search_box div.boxBody {
--- a/web/data/cubicweb.facets.js	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/data/cubicweb.facets.js	Wed Mar 20 17:40:25 2013 +0100
@@ -11,7 +11,7 @@
 
 
 function copyParam(origparams, newparams, param) {
-    var index = jQuery.inArray(param, origparams[0]);
+    var index = $.inArray(param, origparams[0]);
     if (index > - 1) {
         newparams[param] = origparams[1][index];
     }
@@ -22,14 +22,14 @@
     var names = [];
     var values = [];
     $form.find('.facet').each(function() {
-        var facetName = jQuery(this).find('.facetTitle').attr('cubicweb:facetName');
+        var facetName = $(this).find('.facetTitle').attr('cubicweb:facetName');
         // FacetVocabularyWidget
-        jQuery(this).find('.facetValueSelected').each(function(x) {
+        $(this).find('.facetValueSelected').each(function(x) {
             names.push(facetName);
             values.push(this.getAttribute('cubicweb:value'));
         });
         // FacetStringWidget (e.g. has-text)
-        jQuery(this).find('input:text').each(function(){
+        $(this).find('input:text').each(function(){
             names.push(facetName);
             values.push(this.value);
         });
@@ -51,7 +51,7 @@
 
 // XXX deprecate vidargs once TableView is gone
 function buildRQL(divid, vid, paginate, vidargs) {
-    jQuery(CubicWeb).trigger('facets-content-loading', [divid, vid, paginate, vidargs]);
+    $(CubicWeb).trigger('facets-content-loading', [divid, vid, paginate, vidargs]);
     var $form = $('#' + divid + 'Form');
     var zipped = facetFormContent($form);
     zipped[0].push('facetargs');
@@ -59,7 +59,7 @@
     var d = loadRemote(AJAX_BASE_URL, ajaxFuncArgs('filter_build_rql', null, zipped[0], zipped[1]));
     d.addCallback(function(result) {
         var rql = result[0];
-        var $bkLink = jQuery('#facetBkLink');
+        var $bkLink = $('#facetBkLink');
         if ($bkLink.length) {
             var bkPath = 'view?rql=' + encodeURIComponent(rql);
             if (vid) {
@@ -68,6 +68,14 @@
             var bkUrl = $bkLink.attr('cubicweb:target') + '&path=' + encodeURIComponent(bkPath);
             $bkLink.attr('href', bkUrl);
         }
+        var $focusLink = $('#focusLink');
+        if ($focusLink.length) {
+            var url = baseuri()+ 'view?rql=' + encodeURIComponent(rql);
+            if (vid) {
+                url += '&vid=' + encodeURIComponent(vid);
+            }
+            $focusLink.attr('href', url);
+        }
         var toupdate = result[1];
         var extraparams = vidargs;
         if (paginate) { extraparams['paginate'] = '1'; } // XXX in vidargs
@@ -91,20 +99,20 @@
                                      null, 'swap');
         d.addCallback(function() {
             // XXX rql/vid in extraparams
-            jQuery(CubicWeb).trigger('facets-content-loaded', [divid, rql, vid, extraparams]);
+            $(CubicWeb).trigger('facets-content-loaded', [divid, rql, vid, extraparams]);
         });
         if (paginate) {
             // FIXME the edit box might not be displayed in which case we don't
             // know where to put the potential new one, just skip this case for
             // now
-            var $node = jQuery('#edit_box');
+            var $node = $('#edit_box');
             if ($node.length) {
                 $node.loadxhtml(AJAX_BASE_URL, ajaxFuncArgs('render', {
                     'rql': rql
                 },
                 'ctxcomponents', 'edit_box'));
             }
-            $node = jQuery('#breadcrumbs');
+            $node = $('#breadcrumbs');
             if ($node.length) {
                 $node.loadxhtml(AJAX_BASE_URL, ajaxFuncArgs('render', {
                     'rql': rql
@@ -113,7 +121,7 @@
             }
         }
         var mainvar = null;
-        var index = jQuery.inArray('mainvar', zipped[0]);
+        var index = $.inArray('mainvar', zipped[0]);
         if (index > - 1) {
             mainvar = zipped[1][index];
         }
@@ -126,13 +134,13 @@
                 //$form.find('div[cubicweb\\:facetName="' + facetName + '"] ~ div .facetCheckBox').each(function() {
                 $form.find('div').filter(function () {return $(this).attr('cubicweb:facetName') == facetName}).parent().find('.facetCheckBox').each(function() {
                     var value = this.getAttribute('cubicweb:value');
-                    if (jQuery.inArray(value, values) == -1) {
-                        if (!jQuery(this).hasClass('facetValueDisabled')) {
-                            jQuery(this).addClass('facetValueDisabled');
+                    if ($.inArray(value, values) == -1) {
+                        if (!$(this).hasClass('facetValueDisabled')) {
+                            $(this).addClass('facetValueDisabled');
                         }
                     } else {
-                        if (jQuery(this).hasClass('facetValueDisabled')) {
-                            jQuery(this).removeClass('facetValueDisabled');
+                        if ($(this).hasClass('facetValueDisabled')) {
+                            $(this).removeClass('facetValueDisabled');
                         }
                     }
                 });
@@ -145,8 +153,8 @@
 function initFacetBoxEvents(root) {
     // facetargs : (divid, vid, paginate, extraargs)
     root = root || document;
-    jQuery(root).find('form').each(function() {
-        var form = jQuery(this);
+    $(root).find('form').each(function() {
+        var form = $(this);
         // NOTE: don't evaluate facetargs here but in callbacks since its value
         //       may changes and we must send its value when the callback is
         //       called, not when the page is initialized
@@ -159,19 +167,19 @@
                 return false;
             });
             var divid = jsfacetargs[0];
-            if (jQuery('#'+divid).length) {
+            if ($('#'+divid).length) {
                 var $loadingDiv = $(DIV({id:'facetLoading'},
                                         facetLoadingMsg));
                 $loadingDiv.corner();
-                $(jQuery('#'+divid).get(0).parentNode).append($loadingDiv);
-           }
+                $($('#'+divid).get(0).parentNode).append($loadingDiv);
+            }
             form.find('div.facet').each(function() {
-                var facet = jQuery(this);
+                var facet = $(this);
                 facet.find('div.facetCheckBox').each(function(i) {
                     this.setAttribute('cubicweb:idx', i);
                 });
                 facet.find('div.facetCheckBox').click(function() {
-                    var $this = jQuery(this);
+                    var $this = $(this);
                     // NOTE : add test on the facet operator (i.e. OR, AND)
                     // if ($this.hasClass('facetValueDisabled')){
                     //          return
@@ -181,23 +189,22 @@
                         $this.find('img').each(function(i) {
                             if (this.getAttribute('cubicweb:unselimg')) {
                                 this.setAttribute('src', UNSELECTED_BORDER_IMG);
-                                this.setAttribute('alt', (_("not selected")));
                             }
                             else {
                                 this.setAttribute('src', UNSELECTED_IMG);
-                                this.setAttribute('alt', (_("not selected")));
                             }
+                            this.setAttribute('alt', (_("not selected")));
                         });
                         var index = parseInt($this.attr('cubicweb:idx'));
                         // we dont need to move the element when cubicweb:idx == 0
                         if (index > 0) {
-                            var shift = jQuery.grep(facet.find('.facetValueSelected'), function(n) {
+                            var shift = $.grep(facet.find('.facetValueSelected'), function(n) {
                                 var nindex = parseInt(n.getAttribute('cubicweb:idx'));
                                 return nindex > index;
                             }).length;
                             index += shift;
                             var parent = this.parentNode;
-                            var $insertAfter = jQuery(parent).find('.facetCheckBox:nth(' + index + ')');
+                            var $insertAfter = $(parent).find('.facetCheckBox:nth(' + index + ')');
                             if (! ($insertAfter.length == 1 && shift == 0)) {
                                 // only rearrange element if necessary
                                 $insertAfter.after(this);
@@ -209,10 +216,10 @@
                             lastSelected.after(this);
                         } else {
                             var parent = this.parentNode;
-                            jQuery(parent).prepend(this);
+                            $(parent).prepend(this);
                         }
-                        jQuery(this).addClass('facetValueSelected');
-                        var $img = jQuery(this).find('img');
+                        $(this).addClass('facetValueSelected');
+                        var $img = $(this).find('img');
                         $img.attr('src', SELECTED_IMG).attr('alt', (_("selected")));
                     }
                     buildRQL.apply(null, jsfacetargs);
@@ -229,7 +236,7 @@
                 });
                 facet.find('div.facetTitle.hideFacetBody').click(function() {
                     facet.find('div.facetBody').toggleClass('hidden').toggleClass('opened');
-                    jQuery(this).toggleClass('opened');
+                    $(this).toggleClass('opened');
                 });
 
             });
@@ -242,20 +249,20 @@
 // persistent search (eg crih)
 function reorderFacetsItems(root) {
     root = root || document;
-    jQuery(root).find('form').each(function() {
-        var form = jQuery(this);
+    $(root).find('form').each(function() {
+        var form = $(this);
         if (form.attr('cubicweb:facetargs')) {
             form.find('div.facet').each(function() {
-                var facet = jQuery(this);
+                var facet = $(this);
                 var lastSelected = null;
                 facet.find('div.facetCheckBox').each(function(i) {
-                    var $this = jQuery(this);
+                    var $this = $(this);
                     if ($this.hasClass('facetValueSelected')) {
                         if (lastSelected) {
                             lastSelected.after(this);
                         } else {
                             var parent = this.parentNode;
-                            jQuery(parent).prepend(this);
+                            $(parent).prepend(this);
                         }
                         lastSelected = $this;
                     }
@@ -282,18 +289,18 @@
 // argument or without any argument. If we use `initFacetBoxEvents` as the
 // direct callback on the jQuery.ready event, jQuery will pass some argument of
 // his, so we use this small anonymous function instead.
-jQuery(document).ready(function() {
+$(document).ready(function() {
     initFacetBoxEvents();
-    jQuery(cw).bind('facets-content-loaded', onFacetContentLoaded);
-    jQuery(cw).bind('facets-content-loading', onFacetFiltering);
-    jQuery(cw).bind('facets-content-loading', updateFacetTitles);
+    $(cw).bind('facets-content-loaded', onFacetContentLoaded);
+    $(cw).bind('facets-content-loading', onFacetFiltering);
+    $(cw).bind('facets-content-loading', updateFacetTitles);
 });
 
 function showFacetLoading(parentid) {
     var loadingWidth = 200; // px
     var loadingHeight = 100; // px
-    var $msg = jQuery('#facetLoading');
-    var $parent = jQuery('#' + parentid);
+    var $msg = $('#facetLoading');
+    var $parent = $('#' + parentid);
     var leftPos = $parent.offset().left + ($parent.width() - loadingWidth) / 2;
     $parent.fadeTo('normal', 0.2);
     $msg.css('left', leftPos).show();
@@ -304,11 +311,11 @@
 }
 
 function onFacetContentLoaded(event, divid, rql, vid, extraparams) {
-    jQuery('#facetLoading').hide();
+    $('#facetLoading').hide();
 }
 
-jQuery(document).ready(function () {
-    if (jQuery('div.facetBody').length) {
+$(document).ready(function () {
+    if ($('div.facetBody').length) {
         var $loadingDiv = $(DIV({id:'facetLoading'},
                                 facetLoadingMsg));
         $loadingDiv.corner();
--- a/web/data/cubicweb.old.css	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/data/cubicweb.old.css	Wed Mar 20 17:40:25 2013 +0100
@@ -899,6 +899,16 @@
   padding-left: 0.5em;
 }
 
+/* actions around tables */
+.tableactions span {
+  padding: 0 18px;
+  height: 24px;
+  background: #F8F8F8;
+  border: 1px solid #DFDFDF;
+  border-bottom: none;
+  border-radius: 4px 4px 0 0;
+}
+
 /***************************************/
 /* error view (views/management.py)    */
 /***************************************/
--- a/web/facet.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/facet.py	Wed Mar 20 17:40:25 2013 +0100
@@ -49,6 +49,7 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
+from functools import reduce
 from warnings import warn
 from copy import deepcopy
 from datetime import datetime, timedelta
@@ -154,7 +155,7 @@
     for term in select.selection[:]:
         select.remove_selected(term)
     # remove unbound variables which only have some type restriction
-    for dvar in select.defined_vars.values():
+    for dvar in list(select.defined_vars.itervalues()):
         if not (dvar is filtered_variable or dvar.stinfo['relations']):
             select.undefine_variable(dvar)
     # global tree config: DISTINCT, LIMIT, OFFSET
@@ -303,7 +304,7 @@
         # optional relation
         return ovar
     if all(rdef.cardinality[cardidx] in '1+'
-           for rdef in rschema.rdefs.values()):
+           for rdef in rschema.rdefs.itervalues()):
         # mandatory relation without any restriction on the other variable
         for orel in ovar.stinfo['relations']:
             if rel is orel:
--- a/web/formfields.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/formfields.py	Wed Mar 20 17:40:25 2013 +0100
@@ -79,8 +79,8 @@
 from cubicweb import Binary, tags, uilib
 from cubicweb.utils import support_args
 from cubicweb.web import INTERNAL_FIELD_VALUE, ProcessFormError, eid_param, \
-     formwidgets as fw, uicfg
-
+     formwidgets as fw
+from cubicweb.web.views import uicfg
 
 class UnmodifiedField(Exception):
     """raise this when a field has not actually been edited and you want to skip
@@ -465,8 +465,6 @@
             # attribute or relation
             return True
         # if it's a non final relation, we need the eids
-        # XXX underlying regression: getattr(ent, 'foo') used to return
-        #     a tuple, now we get a list
         if isinstance(previous_value, (list, tuple)):
             # widget should return a set of untyped eids
             previous_value = set(e.eid for e in previous_value)
@@ -990,7 +988,7 @@
                 return None
             try:
                 value = form._cw.parse_datetime(value, self.etype)
-            except ValueError, ex:
+            except ValueError as ex:
                 raise ProcessFormError(unicode(ex))
         return value
 
@@ -1163,7 +1161,7 @@
 
 _AFF_KWARGS = uicfg.autoform_field_kwargs
 
-def guess_field(eschema, rschema, role='subject', **kwargs):
+def guess_field(eschema, rschema, role='subject', req=None, **kwargs):
     """This function return the most adapted field to edit the given relation
     (`rschema`) where the given entity type (`eschema`) is the subject or object
     (`role`).
@@ -1211,12 +1209,16 @@
                     kwargs['max_length'] = cstr.max
             return StringField(**kwargs)
         if fieldclass is FileField:
+            if req:
+                aff_kwargs = req.vreg['uicfg'].select('autoform_field_kwargs', req)
+            else:
+                aff_kwargs = _AFF_KWARGS
             for metadata in KNOWN_METAATTRIBUTES:
                 metaschema = eschema.has_metadata(rschema, metadata)
                 if metaschema is not None:
-                    metakwargs = _AFF_KWARGS.etype_get(eschema, metaschema, 'subject')
+                    metakwargs = aff_kwargs.etype_get(eschema, metaschema, 'subject')
                     kwargs['%s_field' % metadata] = guess_field(eschema, metaschema,
-                                                                **metakwargs)
+                                                                req=req, **metakwargs)
         return fieldclass(**kwargs)
     return RelationField.fromcardinality(card, **kwargs)
 
--- a/web/formwidgets.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/formwidgets.py	Wed Mar 20 17:40:25 2013 +0100
@@ -94,6 +94,7 @@
 """
 __docformat__ = "restructuredtext en"
 
+from functools import reduce
 from datetime import date
 from warnings import warn
 
@@ -769,13 +770,13 @@
             return None
         try:
             date = todatetime(req.parse_datetime(datestr, 'Date'))
-        except ValueError, exc:
+        except ValueError as exc:
             raise ProcessFormError(unicode(exc))
         if timestr is None:
             return date
         try:
             time = req.parse_datetime(timestr, 'Time')
-        except ValueError, exc:
+        except ValueError as exc:
             raise ProcessFormError(unicode(exc))
         return date.replace(hour=time.hour, minute=time.minute, second=time.second)
 
--- a/web/http_headers.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/http_headers.py	Wed Mar 20 17:40:25 2013 +0100
@@ -27,7 +27,7 @@
 
 def casemappingify(d):
     global header_case_mapping
-    newd = dict([(key.lower(),key) for key in d.keys()])
+    newd = dict([(key.lower(),key) for key in d])
     header_case_mapping.update(newd)
 
 def lowerify(d):
@@ -77,7 +77,7 @@
                 header = p(header)
                 # if isinstance(h, types.GeneratorType):
                 #     h=list(h)
-        except ValueError,v:
+        except ValueError as v:
             # print v
             header=None
 
@@ -528,7 +528,7 @@
 def parseContentMD5(header):
     try:
         return base64.decodestring(header)
-    except Exception,e:
+    except Exception as e:
         raise ValueError(e)
 
 def parseContentRange(header):
@@ -1292,8 +1292,7 @@
 
     def __contains__(self, name):
         """Does a header with the given name exist?"""
-        name=name.lower()
-        return self._raw_headers.has_key(name)
+        return name.lower() in self._raw_headers
 
     hasHeader = __contains__
 
@@ -1377,7 +1376,7 @@
     def removeHeader(self, name):
         """Removes the header named."""
         name=name.lower()
-        if self._raw_headers.has_key(name):
+        if name in self._raw_headers:
             del self._raw_headers[name]
             del self._headers[name]
 
--- a/web/httpcache.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/httpcache.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/web/propertysheet.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/propertysheet.py	Wed Mar 20 17:40:25 2013 +0100
@@ -101,7 +101,7 @@
             # this in the source css file ?
             try:
                 content = self.compile(content)
-            except ValueError, ex:
+            except ValueError as ex:
                 self.error("can't process %s/%s: %s", rdirectory, rid, ex)
                 adirectory = rdirectory
             else:
--- a/web/request.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/request.py	Wed Mar 20 17:40:25 2013 +0100
@@ -170,7 +170,6 @@
     @property
     def authmode(self):
         """Authentification mode of the instance
-
         (see :ref:`WebServerConfig`)"""
         return self.vreg.config['auth-mode']
 
@@ -227,14 +226,6 @@
         # 3. default language
         self.set_default_language(vreg)
 
-    def set_language(self, lang):
-        gettext, self.pgettext = self.translations[lang]
-        self._ = self.__ = gettext
-        self.lang = lang
-        self.debug('request language: %s', lang)
-        if self.cnx:
-            self.cnx.set_session_props(lang=lang)
-
     # input form parameters management ########################################
 
     # common form parameters which should be protected against html values
@@ -366,7 +357,7 @@
     def update_search_state(self):
         """update the current search state"""
         searchstate = self.form.get('__mode')
-        if not searchstate and self.cnx:
+        if not searchstate:
             searchstate = self.session.data.get('search_state', 'normal')
         self.set_search_state(searchstate)
 
@@ -377,8 +368,7 @@
         else:
             self.search_state = ('linksearch', searchstate.split(':'))
             assert len(self.search_state[-1]) == 4
-        if self.cnx:
-            self.session.data['search_state'] = searchstate
+        self.session.data['search_state'] = searchstate
 
     def match_search_state(self, rset):
         """when searching an entity to create a relation, return True if entities in
@@ -469,7 +459,7 @@
 
     def clear_user_callbacks(self):
         if self.session is not None: # XXX
-            for key in self.session.data.keys():
+            for key in list(self.session.data):
                 if key.startswith('cb_'):
                     del self.session.data[key]
 
@@ -766,8 +756,7 @@
     def from_controller(self):
         """return the id (string) of the controller issuing the request"""
         controller = self.relative_path(False).split('/', 1)[0]
-        registered_controllers = self.vreg['controllers'].keys()
-        if controller in registered_controllers:
+        if controller in self.vreg['controllers']:
             return controller
         return 'view'
 
@@ -893,7 +882,7 @@
                 user, passwd = base64.decodestring(rest).split(":", 1)
                 # XXX HTTP header encoding: use email.Header?
                 return user.decode('UTF8'), passwd
-            except Exception, ex:
+            except Exception as ex:
                 self.debug('bad authorization %s (%s: %s)',
                            auth, ex.__class__.__name__, ex)
         return None, None
--- a/web/test/data/views.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/data/views.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -15,9 +15,7 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""
 
-"""
 from cubicweb.web import Redirect
 from cubicweb.web.application import CubicWebPublisher
 
--- a/web/test/unittest_application.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/unittest_application.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for cubicweb.web.application"""
 
-from __future__ import with_statement
-
 import base64, Cookie
 import sys
 from urllib import unquote
@@ -334,7 +332,7 @@
         self.assertAuthFailure(req)
         try:
             form = self.app_handle_request(req, 'login')
-        except Redirect, redir:
+        except Redirect as redir:
             self.fail('anonymous user should get login form')
         self.assertTrue('__login' in form)
         self.assertTrue('__password' in form)
--- a/web/test/unittest_form.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/unittest_form.py	Wed Mar 20 17:40:25 2013 +0100
@@ -59,23 +59,26 @@
         self.req = self.request()
         self.entity = self.user(self.req)
 
-    def test_form_field_vocabulary_unrelated(self):
+    def test_form_field_choices(self):
         b = self.req.create_entity('BlogEntry', title=u'di mascii code', content=u'a best-seller')
         t = self.req.create_entity('Tag', name=u'x')
         form1 = self.vreg['forms'].select('edition', self.req, entity=t)
-        unrelated = [reid for rview, reid in form1.field_by_name('tags', 'subject', t.e_schema).choices(form1)]
-        self.assertTrue(unicode(b.eid) in unrelated, unrelated)
+        choices = [reid for rview, reid in form1.field_by_name('tags', 'subject', t.e_schema).choices(form1)]
+        self.assertIn(unicode(b.eid), choices)
         form2 = self.vreg['forms'].select('edition', self.req, entity=b)
-        unrelated = [reid for rview, reid in form2.field_by_name('tags', 'object', t.e_schema).choices(form2)]
-        self.assertTrue(unicode(t.eid) in unrelated, unrelated)
+        choices = [reid for rview, reid in form2.field_by_name('tags', 'object', t.e_schema).choices(form2)]
+        self.assertIn(unicode(t.eid), choices)
+
+        b.cw_clear_all_caches()
+        t.cw_clear_all_caches()
         self.execute('SET X tags Y WHERE X is Tag, Y is BlogEntry')
-        unrelated = [reid for rview, reid in form1.field_by_name('tags', 'subject', t.e_schema).choices(form1)]
-        self.assertFalse(unicode(b.eid) in unrelated, unrelated)
-        unrelated = [reid for rview, reid in form2.field_by_name('tags', 'object', t.e_schema).choices(form2)]
-        self.assertFalse(unicode(t.eid) in unrelated, unrelated)
 
+        choices = [reid for rview, reid in form1.field_by_name('tags', 'subject', t.e_schema).choices(form1)]
+        self.assertIn(unicode(b.eid), choices)
+        choices = [reid for rview, reid in form2.field_by_name('tags', 'object', t.e_schema).choices(form2)]
+        self.assertIn(unicode(t.eid), choices)
 
-    def test_form_field_vocabulary_new_entity(self):
+    def test_form_field_choices_new_entity(self):
         e = self.vreg['etypes'].etype_class('CWUser')(self.request())
         form = self.vreg['forms'].select('edition', self.req, entity=e)
         unrelated = [rview for rview, reid in form.field_by_name('in_group', 'subject').choices(form)]
--- a/web/test/unittest_formfields.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/unittest_formfields.py	Wed Mar 20 17:40:25 2013 +0100
@@ -35,10 +35,14 @@
     config.bootstrap_cubes()
     schema = config.load_schema()
 
-class GuessFieldTC(TestCase):
+class GuessFieldTC(CubicWebTC):
+
+    def setUp(self):
+        super(GuessFieldTC, self).setUp()
+        self.req = self.request()
 
     def test_state_fields(self):
-        title_field = guess_field(schema['State'], schema['name'])
+        title_field = guess_field(schema['State'], schema['name'], req=self.req)
         self.assertIsInstance(title_field, StringField)
         self.assertEqual(title_field.required, True)
 
@@ -48,7 +52,7 @@
 #         self.assertEqual(synopsis_field.required, False)
 #         self.assertEqual(synopsis_field.help, 'an abstract for this state')
 
-        description_field = guess_field(schema['State'], schema['description'])
+        description_field = guess_field(schema['State'], schema['description'], req=self.req)
         self.assertIsInstance(description_field, RichTextField)
         self.assertEqual(description_field.required, False)
         self.assertEqual(description_field.format_field, None)
@@ -56,7 +60,8 @@
         # description_format_field = guess_field(schema['State'], schema['description_format'])
         # self.assertEqual(description_format_field, None)
 
-        description_format_field = guess_field(schema['State'], schema['description_format'])
+        description_format_field = guess_field(schema['State'], schema['description_format'],
+                                               req=self.req)
         self.assertEqual(description_format_field.internationalizable, True)
         self.assertEqual(description_format_field.sort, True)
 
@@ -66,22 +71,22 @@
 
 
     def test_cwuser_fields(self):
-        upassword_field = guess_field(schema['CWUser'], schema['upassword'])
+        upassword_field = guess_field(schema['CWUser'], schema['upassword'], req=self.req)
         self.assertIsInstance(upassword_field, StringField)
         self.assertIsInstance(upassword_field.widget, PasswordInput)
         self.assertEqual(upassword_field.required, True)
 
-        last_login_time_field = guess_field(schema['CWUser'], schema['last_login_time'])
+        last_login_time_field = guess_field(schema['CWUser'], schema['last_login_time'], req=self.req)
         self.assertIsInstance(last_login_time_field, DateTimeField)
         self.assertEqual(last_login_time_field.required, False)
 
-        in_group_field = guess_field(schema['CWUser'], schema['in_group'])
+        in_group_field = guess_field(schema['CWUser'], schema['in_group'], req=self.req)
         self.assertIsInstance(in_group_field, RelationField)
         self.assertEqual(in_group_field.required, True)
         self.assertEqual(in_group_field.role, 'subject')
         self.assertEqual(in_group_field.help, 'groups grant permissions to the user')
 
-        owned_by_field = guess_field(schema['CWUser'], schema['owned_by'], 'object')
+        owned_by_field = guess_field(schema['CWUser'], schema['owned_by'], 'object', req=self.req)
         self.assertIsInstance(owned_by_field, RelationField)
         self.assertEqual(owned_by_field.required, False)
         self.assertEqual(owned_by_field.role, 'object')
@@ -95,7 +100,7 @@
         # data_name_field = guess_field(schema['File'], schema['data_name'])
         # self.assertEqual(data_name_field, None)
 
-        data_field = guess_field(schema['File'], schema['data'])
+        data_field = guess_field(schema['File'], schema['data'], req=self.req)
         self.assertIsInstance(data_field, FileField)
         self.assertEqual(data_field.required, True)
         self.assertIsInstance(data_field.format_field, StringField)
@@ -103,7 +108,7 @@
         self.assertIsInstance(data_field.name_field, StringField)
 
     def test_constraints_priority(self):
-        salesterm_field = guess_field(schema['Salesterm'], schema['reason'])
+        salesterm_field = guess_field(schema['Salesterm'], schema['reason'], req=self.req)
         constraints = schema['reason'].rdef('Salesterm', 'String').constraints
         self.assertEqual([c.__class__ for c in constraints],
                           [SizeConstraint, StaticVocabularyConstraint])
@@ -112,7 +117,7 @@
 
 
     def test_bool_field_base(self):
-        field = guess_field(schema['CWAttribute'], schema['indexed'])
+        field = guess_field(schema['CWAttribute'], schema['indexed'], req=self.req)
         self.assertIsInstance(field, BooleanField)
         self.assertEqual(field.required, False)
         self.assertIsInstance(field.widget, Radio)
@@ -121,7 +126,7 @@
 
     def test_bool_field_explicit_choices(self):
         field = guess_field(schema['CWAttribute'], schema['indexed'],
-                            choices=[(u'maybe', '1'), (u'no', '')])
+                            choices=[(u'maybe', '1'), (u'no', '')], req=self.req)
         self.assertIsInstance(field.widget, Radio)
         self.assertEqual(field.vocabulary(mock(req=mock(_=unicode))),
                           [(u'maybe', '1'), (u'no', '')])
--- a/web/test/unittest_formwidgets.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/unittest_formwidgets.py	Wed Mar 20 17:40:25 2013 +0100
@@ -20,7 +20,7 @@
 from logilab.common.testlib import TestCase, unittest_main, mock_object as mock
 
 from cubicweb.devtools import TestServerConfiguration, fake
-from cubicweb.web import uicfg, formwidgets, formfields
+from cubicweb.web import formwidgets, formfields
 
 from cubes.file.entities import File
 
--- a/web/test/unittest_http.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/unittest_http.py	Wed Mar 20 17:40:25 2013 +0100
@@ -21,7 +21,7 @@
     status = None
     try:
         req.validate_cache()
-    except StatusResponse, ex:
+    except StatusResponse as ex:
         status = ex.status
     return status, req
 
--- a/web/test/unittest_idownloadable.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/unittest_idownloadable.py	Wed Mar 20 17:40:25 2013 +0100
@@ -16,7 +16,6 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-from __future__ import with_statement
 
 from functools import partial
 
--- a/web/test/unittest_magicsearch.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/unittest_magicsearch.py	Wed Mar 20 17:40:25 2013 +0100
@@ -230,5 +230,118 @@
         self.assertEqual(rset.rql, 'Any X ORDERBY FTIRANK(X) DESC WHERE X has_text %(text)s')
         self.assertEqual(rset.args, {'text': u'utilisateur Smith'})
 
+
+class RQLSuggestionsBuilderTC(CubicWebTC):
+    def suggestions(self, rql):
+        req = self.request()
+        rbs = self.vreg['components'].select('rql.suggestions', req)
+        return rbs.build_suggestions(rql)
+
+    def test_no_restrictions_rql(self):
+        self.assertListEqual([], self.suggestions(''))
+        self.assertListEqual([], self.suggestions('An'))
+        self.assertListEqual([], self.suggestions('Any X'))
+        self.assertListEqual([], self.suggestions('Any X, Y'))
+
+    def test_invalid_rql(self):
+        self.assertListEqual([], self.suggestions('blabla'))
+        self.assertListEqual([], self.suggestions('Any X WHERE foo, bar'))
+
+    def test_is_rql(self):
+        self.assertListEqual(['Any X WHERE X is %s' % eschema
+                              for eschema in sorted(self.vreg.schema.entities())
+                              if not eschema.final],
+                             self.suggestions('Any X WHERE X is'))
+
+        self.assertListEqual(['Any X WHERE X is Personne', 'Any X WHERE X is Project'],
+                             self.suggestions('Any X WHERE X is P'))
+
+        self.assertListEqual(['Any X WHERE X is Personne, Y is Personne',
+                              'Any X WHERE X is Personne, Y is Project'],
+                             self.suggestions('Any X WHERE X is Personne, Y is P'))
+
+
+    def test_relations_rql(self):
+        self.assertListEqual(['Any X WHERE X is Personne, X ass A',
+                              'Any X WHERE X is Personne, X datenaiss A',
+                              'Any X WHERE X is Personne, X description A',
+                              'Any X WHERE X is Personne, X fax A',
+                              'Any X WHERE X is Personne, X nom A',
+                              'Any X WHERE X is Personne, X prenom A',
+                              'Any X WHERE X is Personne, X promo A',
+                              'Any X WHERE X is Personne, X salary A',
+                              'Any X WHERE X is Personne, X sexe A',
+                              'Any X WHERE X is Personne, X tel A',
+                              'Any X WHERE X is Personne, X test A',
+                              'Any X WHERE X is Personne, X titre A',
+                              'Any X WHERE X is Personne, X travaille A',
+                              'Any X WHERE X is Personne, X web A',
+                              ],
+                             self.suggestions('Any X WHERE X is Personne, X '))
+        self.assertListEqual(['Any X WHERE X is Personne, X tel A',
+                              'Any X WHERE X is Personne, X test A',
+                              'Any X WHERE X is Personne, X titre A',
+                              'Any X WHERE X is Personne, X travaille A',
+                              ],
+                             self.suggestions('Any X WHERE X is Personne, X t'))
+        # try completion on selected
+        self.assertListEqual(['Any X WHERE X is Personne, Y is Societe, X tel A',
+                              'Any X WHERE X is Personne, Y is Societe, X test A',
+                              'Any X WHERE X is Personne, Y is Societe, X titre A',
+                              'Any X WHERE X is Personne, Y is Societe, X travaille Y',
+                              ],
+                             self.suggestions('Any X WHERE X is Personne, Y is Societe, X t'))
+        # invalid relation should not break
+        self.assertListEqual([],
+                             self.suggestions('Any X WHERE X is Personne, X asdasd'))
+
+    def test_attribute_vocabulary_rql(self):
+        self.assertListEqual(['Any X WHERE X is Personne, X promo "bon"',
+                              'Any X WHERE X is Personne, X promo "pasbon"',
+                              ],
+                             self.suggestions('Any X WHERE X is Personne, X promo "'))
+        self.assertListEqual(['Any X WHERE X is Personne, X promo "pasbon"',
+                              ],
+                             self.suggestions('Any X WHERE X is Personne, X promo "p'))
+        # "bon" should be considered complete, hence no suggestion
+        self.assertListEqual([],
+                             self.suggestions('Any X WHERE X is Personne, X promo "bon"'))
+        # no valid vocabulary starts with "po"
+        self.assertListEqual([],
+                             self.suggestions('Any X WHERE X is Personne, X promo "po'))
+
+    def test_attribute_value_rql(self):
+        # suggestions should contain any possible value for
+        # a given attribute (limited to 10)
+        req = self.request()
+        for i in xrange(15):
+            req.create_entity('Personne', nom=u'n%s' % i, prenom=u'p%s' % i)
+        self.assertListEqual(['Any X WHERE X is Personne, X nom "n0"',
+                              'Any X WHERE X is Personne, X nom "n1"',
+                              'Any X WHERE X is Personne, X nom "n10"',
+                              'Any X WHERE X is Personne, X nom "n11"',
+                              'Any X WHERE X is Personne, X nom "n12"',
+                              'Any X WHERE X is Personne, X nom "n13"',
+                              'Any X WHERE X is Personne, X nom "n14"',
+                              'Any X WHERE X is Personne, X nom "n2"',
+                              'Any X WHERE X is Personne, X nom "n3"',
+                              'Any X WHERE X is Personne, X nom "n4"',
+                              'Any X WHERE X is Personne, X nom "n5"',
+                              'Any X WHERE X is Personne, X nom "n6"',
+                              'Any X WHERE X is Personne, X nom "n7"',
+                              'Any X WHERE X is Personne, X nom "n8"',
+                              'Any X WHERE X is Personne, X nom "n9"',
+                              ],
+                             self.suggestions('Any X WHERE X is Personne, X nom "'))
+        self.assertListEqual(['Any X WHERE X is Personne, X nom "n1"',
+                              'Any X WHERE X is Personne, X nom "n10"',
+                              'Any X WHERE X is Personne, X nom "n11"',
+                              'Any X WHERE X is Personne, X nom "n12"',
+                              'Any X WHERE X is Personne, X nom "n13"',
+                              'Any X WHERE X is Personne, X nom "n14"',
+                              ],
+                             self.suggestions('Any X WHERE X is Personne, X nom "n1'))
+
+
 if __name__ == '__main__':
     unittest_main()
--- a/web/test/unittest_reledit.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/unittest_reledit.py	Wed Mar 20 17:40:25 2013 +0100
@@ -20,7 +20,7 @@
 """
 
 from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.web.uicfg import reledit_ctrl
+from cubicweb.web.views.uicfg import reledit_ctrl
 
 class ReleditMixinTC(object):
 
@@ -175,8 +175,8 @@
 
     def setup_database(self):
         super(ClickAndEditFormUICFGTC, self).setup_database()
-        self.tick.set_relations(concerns=self.proj)
-        self.proj.set_relations(manager=self.toto)
+        self.tick.cw_set(concerns=self.proj)
+        self.proj.cw_set(manager=self.toto)
 
     def test_with_uicfg(self):
         old_rctl = reledit_ctrl._tagdefs.copy()
--- a/web/test/unittest_uicfg.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/unittest_uicfg.py	Wed Mar 20 17:40:25 2013 +0100
@@ -18,7 +18,8 @@
 import copy
 from logilab.common.testlib import tag
 from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.web import uicfg, uihelper, formwidgets as fwdgs
+from cubicweb.web import uihelper, formwidgets as fwdgs
+from cubicweb.web.views import uicfg
 
 abaa = uicfg.actionbox_appearsin_addmenu
 
@@ -106,6 +107,24 @@
         self.assertEqual(afk_get('CWUser', 'firstname', 'String', 'subject'), {'order': 1})
 
 
+class UicfgRegistryTC(CubicWebTC):
+
+    def test_default_uicfg_object(self):
+        'CW default ui config objects must be registered in uicfg registry'
+        onames = ('autoform_field', 'autoform_section', 'autoform_field_kwargs')
+        for oname in onames:
+            obj = self.vreg['uicfg'].select_or_none(oname)
+            self.assertTrue(obj is not None, '%s not found in uicfg registry'
+                            % oname)
+
+    def test_custom_uicfg(self):
+        ASRT = uicfg.AutoformSectionRelationTags
+        custom_afs = ASRT()
+        custom_afs.__select__ = ASRT.__select__ & ASRT.__select__
+        self.vreg['uicfg'].register(custom_afs)
+        obj = self.vreg['uicfg'].select_or_none('autoform_section')
+        self.assertTrue(obj is custom_afs)
+
 
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
--- a/web/test/unittest_urlrewrite.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/unittest_urlrewrite.py	Wed Mar 20 17:40:25 2013 +0100
@@ -60,7 +60,6 @@
             ('/doc/images/(.+?)/?$', dict(fid='\\1', vid='wdocimages')),
             ('/doc/?$', dict(fid='main', vid='wdoc')),
             ('/doc/(.+?)/?$', dict(fid='\\1', vid='wdoc')),
-            ('/changelog/?$', dict(vid='changelog')),
             # now in SchemaBasedRewriter
             #('/search/(.+)$', dict(rql=r'Any X WHERE X has_text "\1"')),
             ])
@@ -105,9 +104,9 @@
     def setup_database(self):
         req = self.request()
         self.p1 = self.create_user(req, u'user1')
-        self.p1.set_attributes(firstname=u'joe', surname=u'Dalton')
+        self.p1.cw_set(firstname=u'joe', surname=u'Dalton')
         self.p2 = self.create_user(req, u'user2')
-        self.p2.set_attributes(firstname=u'jack', surname=u'Dalton')
+        self.p2.cw_set(firstname=u'jack', surname=u'Dalton')
 
     def test_rgx_action_with_transforms(self):
         class TestSchemaBasedRewriter(SchemaBasedRewriter):
--- a/web/test/unittest_views_apacherewrite.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/unittest_views_apacherewrite.py	Wed Mar 20 17:40:25 2013 +0100
@@ -40,7 +40,7 @@
         try:
             urlrewriter.rewrite('logilab.fr', '/whatever', req)
             self.fail('redirect exception expected')
-        except Redirect, ex:
+        except Redirect as ex:
             self.assertEqual(ex.location, 'http://www.logilab.fr/whatever')
         self.assertEqual(urlrewriter.rewrite('www.logilab.fr', '/whatever', req),
                           '/whatever')
--- a/web/test/unittest_views_basecontrollers.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/unittest_views_basecontrollers.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb.web.views.basecontrollers unit tests"""
 
-from __future__ import with_statement
-
 from urlparse import urlsplit, urlunsplit, urljoin
 # parse_qs is deprecated in cgi and has been moved to urlparse in Python 2.6
 try:
@@ -77,6 +75,7 @@
                     }
         with self.assertRaises(ValidationError) as cm:
             self.ctrl_publish(req)
+        cm.exception.translate(unicode)
         self.assertEqual(cm.exception.errors, {'login-subject': 'the value "admin" is already used, use another one'})
 
     def test_user_editing_itself(self):
@@ -249,6 +248,7 @@
                 }
         with self.assertRaises(ValidationError) as cm:
             self.ctrl_publish(req)
+        cm.exception.translate(unicode)
         self.assertEqual(cm.exception.errors, {'amount-subject': 'value -10 must be >= 0'})
         req = self.request(rollbackfirst=True)
         req.form = {'eid': ['X'],
@@ -259,6 +259,7 @@
                     }
         with self.assertRaises(ValidationError) as cm:
             self.ctrl_publish(req)
+        cm.exception.translate(unicode)
         self.assertEqual(cm.exception.errors, {'amount-subject': 'value 110 must be <= 100'})
         req = self.request(rollbackfirst=True)
         req.form = {'eid': ['X'],
--- a/web/test/unittest_views_basetemplates.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/unittest_views_basetemplates.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/web/test/unittest_views_baseviews.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/unittest_views_baseviews.py	Wed Mar 20 17:40:25 2013 +0100
@@ -16,8 +16,6 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 
-from __future__ import with_statement
-
 from logilab.common.testlib import unittest_main
 from logilab.mtconverter import html_unescape
 
--- a/web/test/unittest_views_editforms.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/unittest_views_editforms.py	Wed Mar 20 17:40:25 2013 +0100
@@ -22,7 +22,7 @@
 from logilab.common.compat import any
 
 from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.web import uicfg
+from cubicweb.web.views import uicfg
 from cubicweb.web.formwidgets import AutoCompletionWidget
 
 AFFK = uicfg.autoform_field_kwargs
--- a/web/test/unittest_views_errorform.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/unittest_views_errorform.py	Wed Mar 20 17:40:25 2013 +0100
@@ -16,8 +16,6 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 
-from __future__ import with_statement
-
 from logilab.common.testlib import unittest_main
 from logilab.mtconverter import html_unescape
 
@@ -52,7 +50,7 @@
         with self.temporary_appobjects(MyWrongView):
             try:
                 self.view('my-view')
-            except Exception, e:
+            except Exception as e:
                 import sys
                 self.req.data['excinfo'] = sys.exc_info()
                 self.req.data['ex'] = e
--- a/web/test/unittest_views_json.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/unittest_views_json.py	Wed Mar 20 17:40:25 2013 +0100
@@ -35,14 +35,14 @@
         rset = req.execute('Any GN,COUNT(X) GROUPBY GN ORDERBY GN WHERE X in_group G, G name GN')
         data = self.view('jsonexport', rset)
         self.assertEqual(req.headers_out.getRawHeaders('content-type'), ['application/json'])
-        self.assertEqual(data, '[["guests", 1], ["managers", 1]]')
+        self.assertListEqual(data, [["guests", 1], ["managers", 1]])
 
     def test_json_rsetexport_empty_rset(self):
         req = self.request()
         rset = req.execute('Any X WHERE X is CWUser, X login "foobarbaz"')
         data = self.view('jsonexport', rset)
         self.assertEqual(req.headers_out.getRawHeaders('content-type'), ['application/json'])
-        self.assertEqual(data, '[]')
+        self.assertListEqual(data, [])
 
     def test_json_rsetexport_with_jsonp(self):
         req = self.request()
@@ -68,7 +68,7 @@
     def test_json_ersetexport(self):
         req = self.request()
         rset = req.execute('Any G ORDERBY GN WHERE G is CWGroup, G name GN')
-        data = json.loads(self.view('ejsonexport', rset))
+        data = self.view('ejsonexport', rset)
         self.assertEqual(req.headers_out.getRawHeaders('content-type'), ['application/json'])
         self.assertEqual(data[0]['name'], 'guests')
         self.assertEqual(data[1]['name'], 'managers')
--- a/web/test/unittest_views_searchrestriction.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/unittest_views_searchrestriction.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -79,7 +79,7 @@
         select = self.parse('DISTINCT Any V,TN,L ORDERBY TN,L WHERE T nom TN, V connait T, T is Personne, V is CWUser,'
                             'NOT V in_state VS, VS name "published", V login L')
         rschema = self.schema['connait']
-        for rdefs in rschema.rdefs.values():
+        for rdefs in rschema.rdefs.itervalues():
             rdefs.cardinality =  '++'
         try:
             self.assertEqual(self._generate(select, 'in_state', 'subject', 'name'),
@@ -87,7 +87,7 @@
                               "NOT EXISTS(V in_state VS), VS name 'published', "
                               "V in_state A, A name B")
         finally:
-            for rdefs in rschema.rdefs.values():
+            for rdefs in rschema.rdefs.itervalues():
                 rdefs.cardinality =  '**'
 
     def test_nonregr3(self):
--- a/web/test/unittest_views_staticcontrollers.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/unittest_views_staticcontrollers.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,5 +1,3 @@
-from __future__ import with_statement
-
 from logilab.common.testlib import tag, Tags
 from cubicweb.devtools.testlib import CubicWebTC
 
--- a/web/test/unittest_viewselector.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/test/unittest_viewselector.py	Wed Mar 20 17:40:25 2013 +0100
@@ -39,7 +39,6 @@
                actions.LogoutAction]
 SITEACTIONS = [actions.ManageAction]
 FOOTERACTIONS = [wdoc.HelpAction,
-                 wdoc.ChangeLogAction,
                  wdoc.AboutAction,
                  actions.PoweredByAction]
 MANAGEACTIONS = [actions.SiteConfigurationAction,
@@ -79,11 +78,11 @@
             self.assertEqual(len(content), expected, content)
             return
         try:
-            self.assertSetEqual(content.keys(), expected)
+            self.assertSetEqual(list(content), expected)
         except Exception:
-            print registry, sorted(expected), sorted(content.keys())
-            print 'no more', [v for v in expected if not v in content.keys()]
-            print 'missing', [v for v in content.keys() if not v in expected]
+            print registry, sorted(expected), sorted(content)
+            print 'no more', [v for v in expected if not v in content]
+            print 'missing', [v for v in content if not v in expected]
             raise
 
     def setUp(self):
@@ -93,8 +92,7 @@
     def test_possible_views_none_rset(self):
         req = self.request()
         self.assertListEqual(self.pviews(req, None),
-                             [('changelog', wdoc.ChangeLogView),
-                              ('cw.sources-management', cwsources.CWSourcesManagementView),
+                             [('cw.sources-management', cwsources.CWSourcesManagementView),
                               ('cw.users-and-groups-management', cwuser.UsersAndGroupsManagementView),
                               ('gc', debug.GCView),
                               ('index', startup.IndexView),
@@ -488,14 +486,14 @@
 
 
     def test_properties(self):
-        self.assertEqual(sorted(k for k in self.vreg['propertydefs'].keys()
-                                 if k.startswith('ctxcomponents.edit_box')),
-                          ['ctxcomponents.edit_box.context',
-                           'ctxcomponents.edit_box.order',
-                           'ctxcomponents.edit_box.visible'])
-        self.assertEqual([k for k in self.vreg['propertyvalues'].keys()
-                           if not k.startswith('system.version')],
-                          [])
+        self.assertEqual(sorted(k for k in self.vreg['propertydefs'].iterkeys()
+                                if k.startswith('ctxcomponents.edit_box')),
+                         ['ctxcomponents.edit_box.context',
+                          'ctxcomponents.edit_box.order',
+                          'ctxcomponents.edit_box.visible'])
+        self.assertEqual([k for k in self.vreg['propertyvalues'].iterkeys()
+                          if not k.startswith('system.version')],
+                         [])
         self.assertEqual(self.vreg.property_value('ctxcomponents.edit_box.visible'), True)
         self.assertEqual(self.vreg.property_value('ctxcomponents.edit_box.order'), 2)
         self.assertEqual(self.vreg.property_value('ctxcomponents.possible_views_box.visible'), False)
--- a/web/uicfg.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/uicfg.py	Wed Mar 20 17:40:25 2013 +0100
@@ -15,430 +15,15 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""This module (``cubicweb.web.uicfg``) regroups a set of structures that may be
-used to configure various options of the generated web interface.
-
-To configure the interface generation, we use ``RelationTag`` objects.
-
-Index view configuration
-````````````````````````
-:indexview_etype_section:
-   entity type category in the index/manage page. May be one of:
-
-      * ``application``
-      * ``system``
-      * ``schema``
-      * ``subobject`` (not displayed by default)
-
-   By default only entities on the ``application`` category are shown.
-
-.. sourcecode:: python
-
-    from cubicweb.web import uicfg
-    # force hiding
-    uicfg.indexview_etype_section['HideMe'] = 'subobject'
-    # force display
-    uicfg.indexview_etype_section['ShowMe'] = 'application'
-
-
-Actions box configuration
-`````````````````````````
-:actionbox_appearsin_addmenu:
-  simple boolean relation tags used to control the "add entity" submenu.
-  Relations whose rtag is True will appears, other won't.
-
-.. sourcecode:: python
-
-   # Adds all subjects of the entry_of relation in the add menu of the ``Blog``
-   # primary view
-   uicfg.actionbox_appearsin_addmenu.tag_object_of(('*', 'entry_of', 'Blog'), True)
+"""
+This module is now deprecated, see web.views.uicfg.
 """
 __docformat__ = "restructuredtext en"
 
 from warnings import warn
-
-from logilab.common.compat import any
-
-from cubicweb import neg_role
-from cubicweb.rtags import (RelationTags, RelationTagsBool, RelationTagsSet,
-                            RelationTagsDict, NoTargetRelationTagsDict,
-                            register_rtag, _ensure_str_key)
-from cubicweb.schema import META_RTYPES, INTERNAL_TYPES, WORKFLOW_TYPES
-
-
-# primary view configuration ##################################################
-
-def init_primaryview_section(rtag, sschema, rschema, oschema, role):
-    if rtag.get(sschema, rschema, oschema, role) is None:
-        rdef = rschema.rdef(sschema, oschema)
-        if rschema.final:
-            if rschema.meta or sschema.is_metadata(rschema) \
-                    or oschema.type in ('Password', 'Bytes'):
-                section = 'hidden'
-            else:
-                section = 'attributes'
-        else:
-            if rdef.role_cardinality(role) in '1+':
-                section = 'attributes'
-            elif rdef.composite == neg_role(role):
-                section = 'relations'
-            else:
-                section = 'sideboxes'
-        rtag.tag_relation((sschema, rschema, oschema, role), section)
-
-primaryview_section = RelationTags('primaryview_section',
-                                   init_primaryview_section,
-                                   frozenset(('attributes', 'relations',
-                                              'sideboxes', 'hidden')))
-
-
-class DisplayCtrlRelationTags(NoTargetRelationTagsDict):
-    def __init__(self, *args, **kwargs):
-        super(DisplayCtrlRelationTags, self).__init__(*args, **kwargs)
-        self.counter = 0
-
-def init_primaryview_display_ctrl(rtag, sschema, rschema, oschema, role):
-    if role == 'subject':
-        oschema = '*'
-    else:
-        sschema = '*'
-    rtag.counter += 1
-    rtag.setdefault((sschema, rschema, oschema, role), 'order', rtag.counter)
-
-primaryview_display_ctrl = DisplayCtrlRelationTags('primaryview_display_ctrl',
-                                                   init_primaryview_display_ctrl)
+from cubicweb.web.views.uicfg import *
 
 
-# index view configuration ####################################################
-# entity type section in the index/manage page. May be one of
-# * 'application'
-# * 'system'
-# * 'schema'
-# * 'hidden'
-# * 'subobject' (not displayed by default)
-
-class InitializableDict(dict):
-    def __init__(self, *args, **kwargs):
-        super(InitializableDict, self).__init__(*args, **kwargs)
-        register_rtag(self)
-        self.__defaults = dict(self)
-
-    def init(self, schema, check=True):
-        self.update(self.__defaults)
-        for eschema in schema.entities():
-            if eschema.final:
-                continue
-            if eschema.schema_entity():
-                self.setdefault(eschema, 'schema')
-            elif eschema in INTERNAL_TYPES or eschema in WORKFLOW_TYPES:
-                self.setdefault(eschema, 'system')
-            elif eschema.is_subobject(strict=True):
-                self.setdefault(eschema, 'subobject')
-            else:
-                self.setdefault(eschema, 'application')
-
-indexview_etype_section = InitializableDict(
-    EmailAddress='subobject',
-    Bookmark='system',
-    # entity types in the 'system' table by default (managers only)
-    CWUser='system', CWGroup='system',
-    )
-
-# autoform.AutomaticEntityForm configuration ##################################
-
-def _formsections_as_dict(formsections):
-    result = {}
-    for formsection in formsections:
-        formtype, section = formsection.split('_', 1)
-        result[formtype] = section
-    return result
-
-def _card_and_comp(sschema, rschema, oschema, role):
-    rdef = rschema.rdef(sschema, oschema)
-    if role == 'subject':
-        card = rdef.cardinality[0]
-        composed = not rschema.final and rdef.composite == 'object'
-    else:
-        card = rdef.cardinality[1]
-        composed = not rschema.final and rdef.composite == 'subject'
-    return card, composed
-
-class AutoformSectionRelationTags(RelationTagsSet):
-    """autoform relations'section"""
-
-    bw_tag_map = {
-        'primary':   {'main': 'attributes', 'muledit': 'attributes'},
-        'secondary': {'main': 'attributes', 'muledit': 'hidden'},
-        'metadata':  {'main': 'metadata'},
-        'generic':   {'main': 'relations'},
-        'generated': {'main': 'hidden'},
-        }
-
-    _allowed_form_types = ('main', 'inlined', 'muledit')
-    _allowed_values = {'main': ('attributes', 'inlined', 'relations',
-                                'metadata', 'hidden'),
-                       'inlined': ('attributes', 'inlined', 'hidden'),
-                       'muledit': ('attributes', 'hidden'),
-                       }
-
-    def init(self, schema, check=True):
-        super(AutoformSectionRelationTags, self).init(schema, check)
-        self.apply(schema, self._initfunc_step2)
-
-    @staticmethod
-    def _initfunc(self, sschema, rschema, oschema, role):
-        formsections = self.init_get(sschema, rschema, oschema, role)
-        if formsections is None:
-            formsections = self.tag_container_cls()
-        if not any(tag.startswith('inlined') for tag in formsections):
-            if not rschema.final:
-                negsects = self.init_get(sschema, rschema, oschema, neg_role(role))
-                if 'main_inlined' in negsects:
-                    formsections.add('inlined_hidden')
-        key = _ensure_str_key( (sschema, rschema, oschema, role) )
-        self._tagdefs[key] = formsections
-
-    @staticmethod
-    def _initfunc_step2(self, sschema, rschema, oschema, role):
-        formsections = self.get(sschema, rschema, oschema, role)
-        sectdict = _formsections_as_dict(formsections)
-        if rschema in META_RTYPES:
-            sectdict.setdefault('main', 'hidden')
-            sectdict.setdefault('muledit', 'hidden')
-            sectdict.setdefault('inlined', 'hidden')
-        elif role == 'subject' and rschema in sschema.meta_attributes():
-            # meta attribute, usually embeded by the described attribute's field
-            # (eg RichTextField, FileField...)
-            sectdict.setdefault('main', 'hidden')
-            sectdict.setdefault('muledit', 'hidden')
-            sectdict.setdefault('inlined', 'hidden')
-        # ensure we have a tag for each form type
-        if not 'main' in sectdict:
-            if not rschema.final and (
-                sectdict.get('inlined') == 'attributes' or
-                'inlined_attributes' in self.init_get(sschema, rschema, oschema,
-                                                      neg_role(role))):
-                sectdict['main'] = 'hidden'
-            elif sschema.is_metadata(rschema):
-                sectdict['main'] = 'metadata'
-            else:
-                card, composed = _card_and_comp(sschema, rschema, oschema, role)
-                if card in '1+':
-                    sectdict['main'] = 'attributes'
-                    if not 'muledit' in sectdict:
-                        sectdict['muledit'] = 'attributes'
-                elif rschema.final:
-                    sectdict['main'] = 'attributes'
-                else:
-                    sectdict['main'] = 'relations'
-        if not 'muledit' in sectdict:
-            sectdict['muledit'] = 'hidden'
-            if sectdict['main'] == 'attributes':
-                card, composed = _card_and_comp(sschema, rschema, oschema, role)
-                if card in '1+' and not composed:
-                    sectdict['muledit'] = 'attributes'
-        if not 'inlined' in sectdict:
-            sectdict['inlined'] = sectdict['main']
-        # recompute formsections and set it to avoid recomputing
-        for formtype, section in sectdict.iteritems():
-            formsections.add('%s_%s' % (formtype, section))
-
-    def tag_relation(self, key, formtype, section):
-        if isinstance(formtype, tuple):
-            for ftype in formtype:
-                self.tag_relation(key, ftype, section)
-            return
-        assert formtype in self._allowed_form_types, \
-               'formtype should be in (%s), not %s' % (
-            ','.join(self._allowed_form_types), formtype)
-        assert section in self._allowed_values[formtype], \
-               'section for %s should be in (%s), not %s' % (
-            formtype, ','.join(self._allowed_values[formtype]), section)
-        rtags = self._tagdefs.setdefault(_ensure_str_key(key),
-                                         self.tag_container_cls())
-        # remove previous section for this form type if any
-        if rtags:
-            for tag in rtags.copy():
-                if tag.startswith(formtype):
-                    rtags.remove(tag)
-        rtags.add('%s_%s' % (formtype, section))
-        return rtags
+warn('[3.16] moved to cubicweb.web.views.uicfg',
+     DeprecationWarning, stacklevel=2)
 
-    def init_get(self, stype, rtype, otype, tagged):
-        key = (stype, rtype, otype, tagged)
-        rtags = {}
-        for key in self._get_keys(stype, rtype, otype, tagged):
-            tags = self._tagdefs.get(key, ())
-            for tag in tags:
-                assert '_' in tag, (tag, tags)
-                section, value = tag.split('_', 1)
-                rtags[section] = value
-        cls = self.tag_container_cls
-        rtags = cls('_'.join([section,value]) for section,value in rtags.iteritems())
-        return rtags
-
-
-    def get(self, *key):
-        # overriden to avoid recomputing done in parent classes
-        return self._tagdefs.get(key, ())
-
-    def relations_by_section(self, entity, formtype, section, permission,
-                             strict=False):
-        """return a list of (relation schema, target schemas, role) for the
-        given entity matching categories and permission.
-
-        `strict`:
-          bool telling if having local role is enough (strict = False) or not
-        """
-        tag = '%s_%s' % (formtype, section)
-        eschema  = entity.e_schema
-        permsoverrides = autoform_permissions_overrides
-        if entity.has_eid():
-            eid = entity.eid
-        else:
-            eid = None
-            strict = False
-        if permission == 'update':
-            assert section in ('attributes', 'metadata', 'hidden')
-            relpermission = 'add'
-        else:
-            assert section not in ('attributes', 'metadata', 'hidden')
-            relpermission = permission
-        cw = entity._cw
-        for rschema, targetschemas, role in eschema.relation_definitions(True):
-            _targetschemas = []
-            for tschema in targetschemas:
-                # check section's tag first, potentially lower cost than
-                # checking permission which may imply rql queries
-                if not tag in self.etype_get(eschema, rschema, role, tschema):
-                    continue
-                rdef = rschema.role_rdef(eschema, tschema, role)
-                if rschema.final:
-                    if not rdef.has_perm(cw, permission, eid=eid,
-                                         creating=eid is None):
-                        continue
-                elif strict or not rdef.has_local_role(relpermission):
-                    if role == 'subject':
-                        if not rdef.has_perm(cw, relpermission, fromeid=eid):
-                            continue
-                    elif role == 'object':
-                        if not rdef.has_perm(cw, relpermission, toeid=eid):
-                            continue
-                _targetschemas.append(tschema)
-            if not _targetschemas:
-                continue
-            targetschemas = _targetschemas
-            rdef = eschema.rdef(rschema, role=role, targettype=targetschemas[0])
-            # XXX tag allowing to hijack the permission machinery when
-            # permission is not verifiable until the entity is actually
-            # created...
-            if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role):
-                yield (rschema, targetschemas, role)
-                continue
-            if not rschema.final and role == 'subject':
-                # on relation with cardinality 1 or ?, we need delete perm as well
-                # if the relation is already set
-                if (relpermission == 'add'
-                    and rdef.role_cardinality(role) in '1?'
-                    and eid and entity.related(rschema.type, role)
-                    and not rdef.has_perm(cw, 'delete', fromeid=eid,
-                                          toeid=entity.related(rschema.type, role)[0][0])):
-                    continue
-            elif role == 'object':
-                # on relation with cardinality 1 or ?, we need delete perm as well
-                # if the relation is already set
-                if (relpermission == 'add'
-                    and rdef.role_cardinality(role) in '1?'
-                    and eid and entity.related(rschema.type, role)
-                    and not rdef.has_perm(cw, 'delete', toeid=eid,
-                                          fromeid=entity.related(rschema.type, role)[0][0])):
-                    continue
-            yield (rschema, targetschemas, role)
-
-autoform_section = AutoformSectionRelationTags('autoform_section')
-
-# relations'field class
-autoform_field = RelationTags('autoform_field')
-
-# relations'field explicit kwargs (given to field's __init__)
-autoform_field_kwargs = RelationTagsDict('autoform_field_kwargs')
-
-
-# set of tags of the form <action>_on_new on relations. <action> is a
-# schema action (add/update/delete/read), and when such a tag is found
-# permissions checking is by-passed and supposed to be ok
-autoform_permissions_overrides = RelationTagsSet('autoform_permissions_overrides')
-
-class ReleditTags(NoTargetRelationTagsDict):
-    """Associate to relation a dictionary to control `reledit` (e.g. edition of
-    attributes / relations from within views).
-
-    Possible keys and associated values are:
-
-    * `novalue_label`, alternative default value (shown when there is no value).
-
-    * `novalue_include_rtype`, when `novalue_label` is not specified, this boolean
-      flag control wether the generated default value should contains the
-      relation label or not. Will be the opposite of the `showlabel` value found
-      in the `primaryview_display_ctrl` rtag by default.
-
-    * `reload`, boolean, eid (to reload to) or function taking subject and
-      returning bool/eid. This is useful when editing a relation (or attribute)
-      that impacts the url or another parts of the current displayed
-      page. Defaults to False.
-
-    * `rvid`, alternative view id (as str) for relation or composite edition.
-      Default is 'autolimited'.
-
-    * `edit_target`, may be either 'rtype' (to edit the relation) or 'related'
-      (to edit the related entity).  This controls whether to edit the relation
-      or the target entity of the relation.  Currently only one-to-one relations
-      support target entity edition. By default, the 'related' option is taken
-      whenever the relation is composite.
-    """
-    _keys = frozenset('novalue_label novalue_include_rtype reload rvid edit_target'.split())
-
-    def tag_relation(self, key, tag):
-        for tagkey in tag.iterkeys():
-            assert tagkey in self._keys, 'tag %r not in accepted tags: %r' % (tag, self._keys)
-        return super(ReleditTags, self).tag_relation(key, tag)
-
-def init_reledit_ctrl(rtag, sschema, rschema, oschema, role):
-    values = rtag.get(sschema, rschema, oschema, role)
-    if not rschema.final:
-        composite = rschema.rdef(sschema, oschema).composite == role
-        if role == 'subject':
-            oschema = '*'
-        else:
-            sschema = '*'
-        edittarget = values.get('edit_target')
-        if edittarget not in (None, 'rtype', 'related'):
-            rtag.warning('reledit: wrong value for edit_target on relation %s: %s',
-                         rschema, edittarget)
-            edittarget = None
-        if not edittarget:
-            edittarget = 'related' if composite else 'rtype'
-            rtag.tag_relation((sschema, rschema, oschema, role),
-                              {'edit_target': edittarget})
-    if not 'novalue_include_rtype' in values:
-        showlabel = primaryview_display_ctrl.get(
-            sschema, rschema, oschema, role).get('showlabel', True)
-        rtag.tag_relation((sschema, rschema, oschema, role),
-                          {'novalue_include_rtype': not showlabel})
-
-reledit_ctrl = ReleditTags('reledit', init_reledit_ctrl)
-
-# boxes.EditBox configuration #################################################
-
-# 'link' / 'create' relation tags, used to control the "add entity" submenu
-def init_actionbox_appearsin_addmenu(rtag, sschema, rschema, oschema, role):
-    if rtag.get(sschema, rschema, oschema, role) is None:
-        if rschema in META_RTYPES:
-            rtag.tag_relation((sschema, rschema, oschema, role), False)
-            return
-        rdef = rschema.rdef(sschema, oschema)
-        if not rdef.role_cardinality(role) in '?1' and rdef.composite == role:
-            rtag.tag_relation((sschema, rschema, oschema, role), True)
-
-actionbox_appearsin_addmenu = RelationTagsBool('actionbox_appearsin_addmenu',
-                                               init_actionbox_appearsin_addmenu)
--- a/web/uihelper.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/uihelper.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2011-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -45,207 +45,30 @@
 """
 __docformat__ = "restructuredtext en"
 
-from cubicweb.web import uicfg
-from functools import partial
 
-def _tag_rel(rtag, etype, attr, desttype='*', *args, **kwargs):
-    if isinstance(attr, basestring):
-        attr, role = attr, 'subject'
-    else:
-        attr, role = attr
-    if role == 'subject':
-        rtag.tag_subject_of((etype, attr, desttype), *args, **kwargs)
-    else:
-        rtag.tag_object_of((desttype, attr, etype), *args, **kwargs)
+from logilab.common.deprecation import deprecated
+from cubicweb.web.views import uicfg
 
 
 ## generic uicfg helpers ######################################################
-def append_to_addmenu(etype, attr, createdtype='*'):
-    """adds `attr` in the actions box *addrelated* submenu of `etype`.
-
-    :param etype: the entity type as a string
-    :param attr: the name of the attribute or relation to hide
-
-    `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
-
-    """
-    _tag_rel(uicfg.actionbox_appearsin_addmenu, etype, attr, createdtype, True)
-
-def remove_from_addmenu(etype, attr, createdtype='*'):
-    """removes `attr` from the actions box *addrelated* submenu of `etype`.
-
-    :param etype: the entity type as a string
-    :param attr: the name of the attribute or relation to hide
-
-    `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
-    """
-    _tag_rel(uicfg.actionbox_appearsin_addmenu, etype, attr, createdtype, False)
-
-
-## form uicfg helpers ##########################################################
-def set_fields_order(etype, attrs):
-    """specify the field order in `etype` main edition form.
-
-    :param etype: the entity type as a string
-    :param attrs: the ordered list of attribute names (or relations)
-
-    `attrs` can be strings or 2-tuples (relname, role_of_etype_in_the_relation)
-
-    Unspecified fields will be displayed after specified ones, their
-    order being consistent with the schema definition.
-
-    Examples:
-
-.. sourcecode:: python
-
-  from cubicweb.web import uihelper
-  uihelper.set_fields_order('CWUser', ('firstname', 'surname', 'login'))
-  uihelper.set_fields_order('CWUser', ('firstname', ('in_group', 'subject'), 'surname', 'login'))
-
-    """
-    afk = uicfg.autoform_field_kwargs
-    for index, attr in enumerate(attrs):
-        _tag_rel(afk, etype, attr, '*', {'order': index})
-
-
-def hide_field(etype, attr, desttype='*', formtype='main'):
-    """hide `attr` in `etype` forms.
-
-    :param etype: the entity type as a string
-    :param attr: the name of the attribute or relation to hide
-    :param formtype: which form will be affected ('main', 'inlined', etc.), *main* by default.
-
-    `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
-
-    Examples:
-
-.. sourcecode:: python
-
-  from cubicweb.web import uihelper
-  uihelper.hide_field('CWUser', 'login')
-  uihelper.hide_field('*', 'name')
-  uihelper.hide_field('CWUser', 'use_email', formtype='inlined')
-
-    """
-    _tag_rel(uicfg.autoform_section, etype, attr, desttype,
-             formtype=formtype, section='hidden')
-
-
-def hide_fields(etype, attrs, formtype='main'):
-    """simple for-loop wrapper around :func:`hide_field`.
-
-    :param etype: the entity type as a string
-    :param attrs: the ordered list of attribute names (or relations)
-    :param formtype: which form will be affected ('main', 'inlined', etc.), *main* by default.
-
-    `attrs` can be strings or 2-tuples (relname, role_of_etype_in_the_relation)
-
-    Examples:
-
-.. sourcecode:: python
-
-  from cubicweb.web import uihelper
-  uihelper.hide_fields('CWUser', ('login', ('use_email', 'subject')), formtype='inlined')
-    """
-    for attr in attrs:
-        hide_field(etype, attr, formtype=formtype)
-
 
-def set_field_kwargs(etype, attr, **kwargs):
-    """tag `attr` field of `etype` with additional named paremeters.
-
-    :param etype: the entity type as a string
-    :param attr: the name of the attribute or relation
-
-    `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
-
-    Examples:
-
-.. sourcecode:: python
-
-  from cubicweb.web import uihelper, formwidgets as fwdgs
-
-  uihelper.set_field_kwargs('Person', 'works_for', widget=fwdgs.AutoCompletionWidget())
-  uihelper.set_field_kwargs('CWUser', 'login', label=_('login or email address'),
-                            widget=fwdgs.TextInput(attrs={'size': 30}))
-    """
-    _tag_rel(uicfg.autoform_field_kwargs, etype, attr, '*', kwargs)
-
-
-def set_field(etype, attr, field):
-    """sets the `attr` field of `etype`.
-
-    :param etype: the entity type as a string
-    :param attr: the name of the attribute or relation
-
-    `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
-
-    """
-    _tag_rel(uicfg.autoform_field, etype, attr, '*', field)
-
-
-def edit_inline(etype, attr, desttype='*', formtype=('main', 'inlined')):
-    """edit `attr` with and inlined form.
-
-    :param etype: the entity type as a string
-    :param attr: the name of the attribute or relation
-    :param desttype: the destination type(s) concerned, default is everything
-    :param formtype: which form will be affected ('main', 'inlined', etc.), *main* and *inlined* by default.
-
-    `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
-
-    Examples:
-
-.. sourcecode:: python
+backward_compat_funcs = (('append_to_addmenu', uicfg.actionbox_appearsin_addmenu),
+                         ('remove_from_addmenu', uicfg.actionbox_appearsin_addmenu),
+                         ('set_fields_order', uicfg.autoform_field_kwargs),
+                         ('hide_field', uicfg.autoform_section),
+                         ('hide_fields', uicfg.autoform_section),
+                         ('set_field_kwargs', uicfg.autoform_field_kwargs),
+                         ('set_field', uicfg.autoform_field),
+                         ('edit_inline', uicfg.autoform_section),
+                         ('edit_as_attr', uicfg.autoform_section),
+                         ('set_muledit_editable', uicfg.autoform_section),
+                         )
 
-  from cubicweb.web import uihelper
-
-  uihelper.edit_inline('*', 'use_email')
-  """
-    _tag_rel(uicfg.autoform_section, etype, attr, desttype,
-             formtype=formtype, section='inlined')
-
-
-def edit_as_attr(etype, attr, desttype='*', formtype=('main', 'muledit')):
-    """make `attr` appear in the *attributes* section of `etype` form.
-
-    :param etype: the entity type as a string
-    :param attr: the name of the attribute or relation
-    :param desttype: the destination type(s) concerned, default is everything
-    :param formtype: which form will be affected ('main', 'inlined', etc.), *main* and *muledit* by default.
-
-    `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
-
-    Examples:
-
-.. sourcecode:: python
-
-  from cubicweb.web import uihelper
-
-  uihelper.edit_as_attr('CWUser', 'in_group')
-    """
-    _tag_rel(uicfg.autoform_section, etype, attr, desttype,
-             formtype=formtype, section='attributes')
-
-
-def set_muledit_editable(etype, attrs):
-    """make `attrs` appear in muledit form of `etype`.
-
-    :param etype: the entity type as a string
-    :param attrs: the ordered list of attribute names (or relations)
-
-    `attrs` can be strings or 2-tuples (relname, role_of_etype_in_the_relation)
-
-    Examples:
-
-.. sourcecode:: python
-
-  from cubicweb.web import uihelper
-
-  uihelper.set_muledit_editable('CWUser', ('firstname', 'surname', 'in_group'))
-    """
-    for attr in attrs:
-        edit_as_attr(etype, attr, formtype='muledit')
+for funcname, tag in backward_compat_funcs:
+    msg = ('[3.16] uihelper.%(name)s is deprecated, please use '
+           'web.uicfg.%(classname)s.%(name)s' % dict(
+               name=funcname, classname=tag.__class__.__name__))
+    globals()[funcname] = deprecated(msg)(getattr(tag, funcname))
 
 
 class meta_formconfig(type):
@@ -253,17 +76,23 @@
     def __init__(cls, name, bases, classdict):
         if cls.etype is None:
             return
+        if cls.uicfg_afs is None:
+            uicfg_afs = uicfg.autoform_section
+        if cls.uicfg_aff is None:
+            uicfg_aff = uicfg.autoform_field
+        if cls.uicfg_affk is None:
+            uicfg_affk = uicfg.autoform_field_kwargs
         for attr_role in cls.hidden:
-            hide_field(cls.etype, attr_role, formtype=cls.formtype)
+            uicfg_afs.hide_field(cls.etype, attr_role, formtype=cls.formtype)
         for attr_role in cls.rels_as_attrs:
-            edit_as_attr(cls.etype, attr_role, formtype=cls.formtype)
+            uicfg_afs.edit_as_attr(cls.etype, attr_role, formtype=cls.formtype)
         for attr_role in cls.inlined:
-            edit_inline(cls.etype, attr_role, formtype=cls.formtype)
+            uicfg_afs.edit_inline(cls.etype, attr_role, formtype=cls.formtype)
         for rtype, widget in cls.widgets.items():
-            set_field_kwargs(cls.etype, rtype, widget=widget)
+            uicfg_affk.set_field_kwargs(cls.etype, rtype, widget=widget)
         for rtype, field in cls.fields.items():
-            set_field(cls.etype, rtype, field)
-        set_fields_order(cls.etype, cls.fields_order)
+            uicfg_aff.set_field(cls.etype, rtype, field)
+        uicfg_affk.set_fields_order(cls.etype, cls.fields_order)
         super(meta_formconfig, cls).__init__(name, bases, classdict)
 
 
@@ -303,6 +132,18 @@
     :attr:`fields`
       a dictionary mapping attribute names to field instances.
 
+    :attr:`uicfg_afs`
+      an instance of ``cubicweb.web.uicfg.AutoformSectionRelationTags``
+      Default is None, meaning ``cubicweb.web.uicfg.autoform_section`` is used.
+
+    :attr:`uicfg_aff`
+      an instance of ``cubicweb.web.uicfg.AutoformFieldTags``
+      Default is None, meaning ``cubicweb.web.uicfg.autoform_field`` is used.
+
+    :attr:`uicfg_affk`
+      an instance of ``cubicweb.web.uicfg.AutoformFieldKwargsTags``
+      Default is None, meaning ``cubicweb.web.uicfg.autoform_field_kwargs`` is used.
+
     Examples:
 
 .. sourcecode:: python
@@ -333,3 +174,6 @@
     fields_order = ()
     widgets = {}
     fields = {}
+    uicfg_afs = None
+    uicfg_aff = None
+    uicfg_affk = None
--- a/web/views/actions.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/actions.py	Wed Mar 20 17:40:25 2013 +0100
@@ -32,8 +32,8 @@
     authenticated_user, match_user_groups, match_search_state,
     has_permission, has_add_permission, is_instance, debug_mode,
     )
-from cubicweb.web import uicfg, controller, action
-from cubicweb.web.views import linksearch_select_url, vid_from_rset
+from cubicweb.web import controller, action
+from cubicweb.web.views import uicfg, linksearch_select_url, vid_from_rset
 
 
 class has_editable_relation(EntityPredicate):
@@ -291,7 +291,8 @@
         method to return an empty list. If you only want some, you can configure
         them by using uicfg.actionbox_appearsin_addmenu
         """
-        appearsin_addmenu = uicfg.actionbox_appearsin_addmenu
+        appearsin_addmenu = self._cw.vreg['uicfg'].select(
+            'actionbox_appearsin_addmenu', self._cw, entity=entity)
         req = self._cw
         eschema = entity.e_schema
         for role, rschemas in (('subject', eschema.subject_relations()),
--- a/web/views/ajaxcontroller.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/ajaxcontroller.py	Wed Mar 20 17:40:25 2013 +0100
@@ -28,7 +28,7 @@
 functions that can be called from the javascript world.
 
 To register a new remote function, either decorate your function
-with the :func:`cubicweb.web.views.ajaxcontroller.ajaxfunc` decorator:
+with the :func:`~cubicweb.web.views.ajaxcontroller.ajaxfunc` decorator:
 
 .. sourcecode:: python
 
@@ -39,7 +39,7 @@
     def list_users(self):
         return [u for (u,) in self._cw.execute('Any L WHERE U login L')]
 
-or inherit from :class:`cubicwbe.web.views.ajaxcontroller.AjaxFunction` and
+or inherit from :class:`~cubicweb.web.views.ajaxcontroller.AjaxFunction` and
 implement the ``__call__`` method:
 
 .. sourcecode:: python
@@ -135,7 +135,7 @@
             args = (args,)
         try:
             args = [json.loads(arg) for arg in args]
-        except ValueError, exc:
+        except ValueError as exc:
             self.exception('error while decoding json arguments for '
                            'js_%s: %s (err: %s)', fname, args, exc)
             raise RemoteCallFailed(exc_message(exc, self._cw.encoding))
@@ -143,7 +143,7 @@
             result = func(*args)
         except (RemoteCallFailed, DirectResponse):
             raise
-        except Exception, exc:
+        except Exception as exc:
             self.exception('an exception occurred while calling js_%s(%s): %s',
                            fname, args, exc)
             raise RemoteCallFailed(exc_message(exc, self._cw.encoding))
@@ -214,7 +214,7 @@
             self._cw.ensure_ro_rql(rql)
         try:
             return self._cw.execute(rql, args)
-        except Exception, ex:
+        except Exception as ex:
             self.exception("error in _exec(rql=%s): %s", rql, ex)
             return None
         return None
--- a/web/views/authentication.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/authentication.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """user authentication component"""
 
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 
 from threading import Lock
@@ -27,7 +25,7 @@
 
 from cubicweb import AuthenticationError, BadConnectionId
 from cubicweb.view import Component
-from cubicweb.dbapi import repo_connect, ConnectionProperties
+from cubicweb.dbapi import _repo_connect, ConnectionProperties
 from cubicweb.web import InvalidSession
 from cubicweb.web.application import AbstractAuthenticationManager
 
@@ -169,9 +167,8 @@
         raise AuthenticationError()
 
     def _authenticate(self, login, authinfo):
-        cnxprops = ConnectionProperties(self.vreg.config.repo_method,
-                                        close=False, log=self.log_queries)
-        cnx = repo_connect(self.repo, login, cnxprops=cnxprops, **authinfo)
+        cnxprops = ConnectionProperties(close=False, log=self.log_queries)
+        cnx = _repo_connect(self.repo, login, cnxprops=cnxprops, **authinfo)
         # decorate connection
         cnx.vreg = self.vreg
         return cnx
--- a/web/views/autoform.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/autoform.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -126,7 +126,6 @@
 from logilab.mtconverter import xml_escape
 from logilab.common.decorators import iclassmethod, cached
 from logilab.common.deprecation import deprecated
-from logilab.common.registry import classid
 
 from cubicweb import typed_eid, neg_role, uilib
 from cubicweb.schema import display_name
@@ -135,14 +134,11 @@
     match_kwargs, match_form_params, non_final_entity,
     specified_etype_implements)
 from cubicweb.utils import json_dumps
-from cubicweb.web import (stdmsgs, uicfg, eid_param,
+from cubicweb.web import (stdmsgs, eid_param,
                           form as f, formwidgets as fw, formfields as ff)
-from cubicweb.web.views import forms
+from cubicweb.web.views import uicfg, forms
 from cubicweb.web.views.ajaxcontroller import ajaxfunc
 
-_AFS = uicfg.autoform_section
-_AFFK = uicfg.autoform_field_kwargs
-
 
 # inlined form handling ########################################################
 
@@ -755,6 +751,8 @@
 
     def __init__(self, *args, **kwargs):
         super(AutomaticEntityForm, self).__init__(*args, **kwargs)
+        self.uicfg_afs = self._cw.vreg['uicfg'].select(
+            'autoform_section', self._cw, entity=self.edited_entity)
         entity = self.edited_entity
         if entity.has_eid():
             entity.complete()
@@ -820,8 +818,8 @@
 
     def _inlined_form_view_field(self, view):
         # XXX allow more customization
-        kwargs = _AFFK.etype_get(self.edited_entity.e_schema, view.rtype,
-                                 view.role, view.etype)
+        kwargs = self.uicfg_affk.etype_get(self.edited_entity.e_schema,
+                                           view.rtype, view.role, view.etype)
         if kwargs is None:
             kwargs = {}
         return InlinedFormField(view=view, **kwargs)
@@ -832,7 +830,7 @@
         """return a list of (relation schema, target schemas, role) matching
         given category(ies) and permission
         """
-        return _AFS.relations_by_section(
+        return self.uicfg_afs.relations_by_section(
             self.edited_entity, self.formtype, section, permission, strict)
 
     def editable_attributes(self, strict=False):
@@ -963,6 +961,7 @@
 
 ## default form ui configuration ##############################################
 
+_AFS = uicfg.autoform_section
 # use primary and not generated for eid since it has to be an hidden
 _AFS.tag_attribute(('*', 'eid'), 'main', 'attributes')
 _AFS.tag_attribute(('*', 'eid'), 'muledit', 'attributes')
@@ -994,6 +993,7 @@
 _AFS.tag_subject_of(('CWRelation', 'from_entity', '*'), 'main', 'inlined')
 _AFS.tag_subject_of(('CWRelation', 'to_entity', '*'), 'main', 'inlined')
 
+_AFFK = uicfg.autoform_field_kwargs
 _AFFK.tag_attribute(('RQLExpression', 'expression'),
                     {'widget': fw.TextInput})
 _AFFK.tag_subject_of(('TrInfo', 'wf_info_for', '*'),
@@ -1011,4 +1011,4 @@
             AutomaticEntityForm.error('field for %s %s may not be found in schema' % (rtype, role))
             return None
 
-    vreg.register_all(globals().values(), __name__)
+    vreg.register_all(globals().itervalues(), __name__)
--- a/web/views/basecomponents.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/basecomponents.py	Wed Mar 20 17:40:25 2013 +0100
@@ -20,8 +20,6 @@
 * the rql input form
 * the logged user link
 """
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 _ = unicode
 
@@ -36,7 +34,7 @@
 from cubicweb.schema import display_name
 from cubicweb.utils import wrap_on_write
 from cubicweb.uilib import toggle_action
-from cubicweb.web import component, uicfg
+from cubicweb.web import component
 from cubicweb.web.htmlwidgets import MenuWidget, PopupBoxMenu
 
 VISIBLE_PROP_DEF = {
@@ -59,6 +57,14 @@
         # display multilines query as one line
         rql = rset is not None and rset.printable_rql(encoded=False) or req.form.get('rql', '')
         rql = rql.replace(u"\n", u" ")
+        rql_suggestion_comp = self._cw.vreg['components'].select_or_none('rql.suggestions', self._cw)
+        if rql_suggestion_comp is not None:
+            # enable autocomplete feature only if the rql
+            # suggestions builder is available
+            self._cw.add_css('jquery.ui.css')
+            self._cw.add_js(('cubicweb.ajax.js', 'jquery.ui.js'))
+            self._cw.add_onload('$("#rql").autocomplete({source: "%s"});'
+                                % (req.build_url('json', fname='rql_suggest')))
         self.w(u'''<div id="rqlinput" class="%s"><form action="%s"><fieldset>
 <input type="text" id="rql" name="rql" value="%s"  title="%s" tabindex="%s" accesskey="q" class="searchField" />
 ''' % (not self.cw_propval('visible') and 'hidden' or '',
--- a/web/views/basecontrollers.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/basecontrollers.py	Wed Mar 20 17:40:25 2013 +0100
@@ -191,6 +191,7 @@
 
 def _validation_error(req, ex):
     req.cnx.rollback()
+    ex.translate(req._) # translate messages using ui language
     # XXX necessary to remove existant validation error?
     # imo (syt), it's not necessary
     req.session.data.pop(req.form.get('__errorurl'), None)
@@ -211,14 +212,14 @@
         return (False, {None: req._('not authorized')}, None)
     try:
         ctrl.publish(None)
-    except ValidationError, ex:
+    except ValidationError as ex:
         return (False, _validation_error(req, ex), ctrl._edited_entity)
-    except Redirect, ex:
+    except Redirect as ex:
         try:
             txuuid = req.cnx.commit() # ValidationError may be raised on commit
-        except ValidationError, ex:
+        except ValidationError as ex:
             return (False, _validation_error(req, ex), ctrl._edited_entity)
-        except Exception, ex:
+        except Exception as ex:
             req.cnx.rollback()
             req.exception('unexpected error while validating form')
             return (False, str(ex).decode('utf-8'), ctrl._edited_entity)
@@ -230,7 +231,7 @@
             if ctrl._edited_entity:
                 ctrl._edited_entity.complete()
             return (True, ex.location, ctrl._edited_entity)
-    except Exception, ex:
+    except Exception as ex:
         req.cnx.rollback()
         req.exception('unexpected error while validating form')
         return (False, str(ex).decode('utf-8'), ctrl._edited_entity)
@@ -297,7 +298,7 @@
         txuuid = self._cw.form['txuuid']
         try:
             self._cw.cnx.undo_transaction(txuuid)
-        except UndoTransactionException, exc:
+        except UndoTransactionException as exc:
             errors = exc.errors
             #This will cause a rollback in main_publish
             raise ValidationError(None, {None: '\n'.join(errors)})
--- a/web/views/basetemplates.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/basetemplates.py	Wed Mar 20 17:40:25 2013 +0100
@@ -92,7 +92,7 @@
         return 1
     if view.binary:
         return 0
-    if req.form.has_key('__notemplate'):
+    if '__notemplate' in req.form:
         return 0
     return view.templatable
 
@@ -105,7 +105,8 @@
     def call(self, view):
         view.set_request_content_type()
         view.set_stream()
-        if (self._cw.form.has_key('__notemplate') and view.templatable
+        if (('__notemplate' in self._cw.form)
+            and view.templatable
             and view.content_type == self._cw.html_content_type()):
             view.w(self._cw.document_surrounding_div())
             view.render()
@@ -497,7 +498,7 @@
         if config['auth-mode'] != 'http':
             self.login_form(id) # Cookie authentication
         w(u'</div>')
-        if self._cw.https and config.anonymous_user()[0]:
+        if self._cw.https and config.anonymous_user()[0] and config['https-deny-anonymous']:
             path = xml_escape(config['base-url'] + self._cw.relative_path())
             w(u'<div class="loginMessage"><a href="%s">%s</a></div>\n'
               % (path, self._cw._('No account? Try public access at %s') % path))
--- a/web/views/baseviews.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/baseviews.py	Wed Mar 20 17:40:25 2013 +0100
@@ -498,7 +498,7 @@
         for attr in entity.e_schema.indexable_attributes():
             try:
                 value = xml_escape(entity.printable_value(attr, format='text/plain').lower())
-            except TransformError, ex:
+            except TransformError as ex:
                 continue
             except Exception:
                 continue
--- a/web/views/bookmark.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/bookmark.py	Wed Mar 20 17:40:25 2013 +0100
@@ -24,9 +24,8 @@
 
 from cubicweb import Unauthorized, typed_eid
 from cubicweb.predicates import is_instance, one_line_rset
-from cubicweb.web import (action, component, uicfg, htmlwidgets,
-                          formwidgets as fw)
-from cubicweb.web.views import primary
+from cubicweb.web import action, component, htmlwidgets, formwidgets as fw
+from cubicweb.web.views import uicfg, primary
 from cubicweb.web.views.ajaxcontroller import ajaxfunc
 
 _abaa = uicfg.actionbox_appearsin_addmenu
--- a/web/views/boxes.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/boxes.py	Wed Mar 20 17:40:25 2013 +0100
@@ -25,8 +25,6 @@
 * possible views box
 * startup views box
 """
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 _ = unicode
 
--- a/web/views/cwproperties.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/cwproperties.py	Wed Mar 20 17:40:25 2013 +0100
@@ -28,12 +28,12 @@
 from cubicweb.predicates import (one_line_rset, none_rset, is_instance,
                                  match_user_groups, logged_user_in_rset)
 from cubicweb.view import StartupView
-from cubicweb.web import uicfg, stdmsgs
+from cubicweb.web import stdmsgs
 from cubicweb.web.form import FormViewMixIn
 from cubicweb.web.formfields import FIELDS, StringField
 from cubicweb.web.formwidgets import (Select, TextInput, Button, SubmitButton,
                                       FieldWidget)
-from cubicweb.web.views import primary, formrenderers, editcontroller
+from cubicweb.web.views import uicfg, primary, formrenderers, editcontroller
 from cubicweb.web.views.ajaxcontroller import ajaxfunc
 
 uicfg.primaryview_section.tag_object_of(('*', 'for_user', '*'), 'hidden')
@@ -349,7 +349,7 @@
             return
         try:
             pdef = form._cw.vreg.property_info(entity.pkey)
-        except UnknownProperty, ex:
+        except UnknownProperty as ex:
             form.warning('%s (you should probably delete that property '
                          'from the database)', ex)
             msg = form._cw._('you should probably delete that property')
--- a/web/views/cwsources.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/cwsources.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2010-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2010-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -33,9 +33,9 @@
                                 match_user_groups, match_kwargs, match_view)
 from cubicweb.view import EntityView, StartupView
 from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, display_name
-from cubicweb.web import uicfg, formwidgets as wdgs, facet
+from cubicweb.web import formwidgets as wdgs, facet
 from cubicweb.web.views import add_etype_button
-from cubicweb.web.views import (tabs, actions, ibreadcrumbs, navigation,
+from cubicweb.web.views import (uicfg, tabs, actions, ibreadcrumbs, navigation,
                                 tableview, pyviews)
 
 
--- a/web/views/cwuser.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/cwuser.py	Wed Mar 20 17:40:25 2013 +0100
@@ -28,8 +28,8 @@
 from cubicweb.schema import display_name
 from cubicweb.predicates import one_line_rset, is_instance, match_user_groups
 from cubicweb.view import EntityView, StartupView
-from cubicweb.web import action, uicfg, formwidgets
-from cubicweb.web.views import tabs, tableview, actions, add_etype_button
+from cubicweb.web import action, formwidgets
+from cubicweb.web.views import uicfg, tabs, tableview, actions, add_etype_button
 
 _pvs = uicfg.primaryview_section
 _pvs.tag_attribute(('CWUser', 'login'), 'hidden')
--- a/web/views/debug.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/debug.py	Wed Mar 20 17:40:25 2013 +0100
@@ -103,9 +103,9 @@
                    % (element, xml_escape(unicode(stats[element])),
                       element.endswith('percent') and '%' or '' ))
         w(u'</table>')
-        if req.cnx._cnxtype == 'inmemory' and req.user.is_in_group('managers'):
+        if req.cnx.is_repo_in_memory and req.user.is_in_group('managers'):
             w(u'<h3>%s</h3>' % _('opened sessions'))
-            sessions = repo._sessions.values()
+            sessions = repo._sessions.itervalues()
             if sessions:
                 w(u'<ul>')
                 for session in sessions:
--- a/web/views/editcontroller.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/editcontroller.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -132,7 +132,7 @@
                 # __type and eid
                 formparams = req.extract_entity_params(eid, minparams=2)
                 eid = self.edit_entity(formparams)
-        except (RequestError, NothingToEdit), ex:
+        except (RequestError, NothingToEdit) as ex:
             if '__linkto' in req.form and 'eid' in req.form:
                 self.execute_linkto()
             elif not ('__delete' in req.form or '__insert' in req.form):
@@ -145,7 +145,7 @@
         for querydef in self.relations_rql:
             self._cw.execute(*querydef)
         # XXX this processes *all* pending operations of *all* entities
-        if req.form.has_key('__delete'):
+        if '__delete' in req.form:
             todelete = req.list_form_param('__delete', req.form, pop=True)
             if todelete:
                 autoform.delete_relations(self._cw, todelete)
@@ -159,7 +159,7 @@
         try:
             entity = self._cw.execute(rql, rqlquery.kwargs).get_entity(0, 0)
             neweid = entity.eid
-        except ValidationError, ex:
+        except ValidationError as ex:
             self._to_create[eid] = ex.entity
             if self._cw.ajax_request: # XXX (syt) why?
                 ex.entity = eid
@@ -212,11 +212,11 @@
             self._update_entity(eid, rqlquery)
         if is_main_entity:
             self.notify_edited(entity)
-        if formparams.has_key('__delete'):
+        if '__delete' in formparams:
             # XXX deprecate?
             todelete = self._cw.list_form_param('__delete', formparams, pop=True)
             autoform.delete_relations(self._cw, todelete)
-        if formparams.has_key('__cloned_eid'):
+        if '__cloned_eid' in formparams:
             entity.copy_relations(typed_eid(formparams['__cloned_eid']))
         if is_main_entity: # only execute linkto for the main entity
             self.execute_linkto(entity.eid)
@@ -249,7 +249,7 @@
                     else:
                         self._pending_fields.add( (form, field) )
 
-        except ProcessFormError, exc:
+        except ProcessFormError as exc:
             self.errors.append((field, exc))
 
     def handle_inlined_relation(self, form, field, values, origvalues, rqlquery):
--- a/web/views/editforms.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/editforms.py	Wed Mar 20 17:40:25 2013 +0100
@@ -34,10 +34,10 @@
                                 specified_etype_implements, is_instance)
 from cubicweb.view import EntityView
 from cubicweb.schema import display_name
-from cubicweb.web import uicfg, stdmsgs, eid_param, \
+from cubicweb.web import stdmsgs, eid_param, \
      formfields as ff, formwidgets as fw
 from cubicweb.web.form import FormViewMixIn, FieldNotFound
-from cubicweb.web.views import forms, reledit
+from cubicweb.web.views import uicfg, forms, reledit
 
 _pvdc = uicfg.primaryview_display_ctrl
 
--- a/web/views/emailaddress.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/emailaddress.py	Wed Mar 20 17:40:25 2013 +0100
@@ -24,8 +24,7 @@
 from cubicweb.schema import display_name
 from cubicweb.predicates import is_instance
 from cubicweb import Unauthorized
-from cubicweb.web import uicfg
-from cubicweb.web.views import baseviews, primary, ibreadcrumbs
+from cubicweb.web.views import uicfg, baseviews, primary, ibreadcrumbs
 
 _pvs = uicfg.primaryview_section
 _pvs.tag_subject_of(('*', 'use_email', '*'), 'attributes')
--- a/web/views/embedding.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/embedding.py	Wed Mar 20 17:40:25 2013 +0100
@@ -94,7 +94,7 @@
                 body = embed_external_page(embedded_url, prefix,
                                            headers, req.form.get('custom_css'))
                 body = soup2xhtml(body, self._cw.encoding)
-            except HTTPError, err:
+            except HTTPError as err:
                 body = '<h2>%s</h2><h3>%s</h3>' % (
                     _('error while embedding page'), err)
         rset = self.process_rql()
@@ -127,7 +127,7 @@
     def url(self, row=0):
         entity = self.cw_rset.get_entity(row, 0)
         url = urljoin(self._cw.base_url(), entity.cw_adapt_to('IEmbedable').embeded_url())
-        if self._cw.form.has_key('rql'):
+        if 'rql' in self._cw.form:
             return self._cw.build_url('embed', url=url, rql=self._cw.form['rql'])
         return self._cw.build_url('embed', url=url)
 
--- a/web/views/facets.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/facets.py	Wed Mar 20 17:40:25 2013 +0100
@@ -26,6 +26,7 @@
 from logilab.common.decorators import cachedproperty
 from logilab.common.registry import objectify_predicate, yes
 
+from cubicweb import tags
 from cubicweb.predicates import (non_final_entity, multi_lines_rset,
                                  match_context_prop, relation_possible)
 from cubicweb.utils import json_dumps
@@ -234,6 +235,7 @@
             vid = req.form.get('vid')
         if self.bk_linkbox_template and req.vreg.schema['Bookmark'].has_perm(req, 'add'):
             w(self.bookmark_link(rset))
+        w(self.focus_link(rset))
         hiddens = {}
         for param in ('subvid', 'vtitle'):
             if param in req.form:
@@ -269,6 +271,9 @@
                 req._('bookmark this search'))
         return self.bk_linkbox_template % bk_link
 
+    def focus_link(self, rset):
+        return self.bk_linkbox_template % tags.a(self._cw._('focus on this selection'),
+                                                 href=self._cw.url(), id='focusLink')
 
 class FilterTable(FacetFilterMixIn, AnyRsetView):
     __regid__ = 'facet.filtertable'
--- a/web/views/formrenderers.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/formrenderers.py	Wed Mar 20 17:40:25 2013 +0100
@@ -238,8 +238,8 @@
         if form.fieldsets_in_order:
             fieldsets = form.fieldsets_in_order
         else:
-            fieldsets = byfieldset.keys()
-        for fieldset in fieldsets:
+            fieldsets = byfieldset.iterkeys()
+        for fieldset in list(fieldsets):
             try:
                 fields = byfieldset.pop(fieldset)
             except KeyError:
--- a/web/views/forms.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/forms.py	Wed Mar 20 17:40:25 2013 +0100
@@ -41,7 +41,6 @@
 
 but you'll use this one rarely.
 """
-from __future__ import with_statement
 __docformat__ = "restructuredtext en"
 
 from warnings import warn
@@ -56,7 +55,8 @@
 from cubicweb.utils import support_args
 from cubicweb.predicates import non_final_entity, match_kwargs, one_line_rset
 from cubicweb.web import RequestError, ProcessFormError
-from cubicweb.web import uicfg, form, formwidgets as fwdgs
+from cubicweb.web import form, formwidgets as fwdgs
+from cubicweb.web.views import uicfg
 from cubicweb.web.formfields import guess_field
 
 
@@ -293,7 +293,7 @@
                 try:
                     for field, value in field.process_posted(self):
                         processed[field.role_name()] = value
-                except ProcessFormError, exc:
+                except ProcessFormError as exc:
                     errors.append((field, exc))
             if errors:
                 errors = dict((f.role_name(), unicode(ex)) for f, ex in errors)
@@ -301,9 +301,6 @@
             return processed
 
 
-_AFF = uicfg.autoform_field
-_AFF_KWARGS = uicfg.autoform_field_kwargs
-
 class EntityFieldsForm(FieldsForm):
     """This class is designed for forms used to edit some entities. It should
     handle for you all the underlying stuff necessary to properly work with the
@@ -314,6 +311,8 @@
     __select__ = (match_kwargs('entity')
                   | (one_line_rset() & non_final_entity()))
     domid = 'entityForm'
+    uicfg_aff = uicfg.autoform_field
+    uicfg_affk = uicfg.autoform_field_kwargs
 
     @iclassmethod
     def field_by_name(cls_or_self, name, role=None, eschema=None):
@@ -329,15 +328,21 @@
             rschema = eschema.schema.rschema(name)
             # XXX use a sample target type. Document this.
             tschemas = rschema.targets(eschema, role)
-            fieldcls = _AFF.etype_get(eschema, rschema, role, tschemas[0])
-            kwargs = _AFF_KWARGS.etype_get(eschema, rschema, role, tschemas[0])
+            fieldcls = cls_or_self.uicfg_aff.etype_get(
+                eschema, rschema, role, tschemas[0])
+            kwargs = cls_or_self.uicfg_affk.etype_get(
+                eschema, rschema, role, tschemas[0])
             if kwargs is None:
                 kwargs = {}
             if fieldcls:
                 if not isinstance(fieldcls, type):
                     return fieldcls # already and instance
                 return fieldcls(name=name, role=role, eidparam=True, **kwargs)
-            field = guess_field(eschema, rschema, role, eidparam=True, **kwargs)
+            if isinstance(cls_or_self, type):
+                req = None
+            else:
+                req = cls_or_self._cw
+            field = guess_field(eschema, rschema, role, req=req, eidparam=True, **kwargs)
             if field is None:
                 raise
             return field
@@ -349,6 +354,10 @@
             self.edited_entity = rset.complete_entity(row or 0, col or 0)
         msg = kwargs.pop('submitmsg', None)
         super(EntityFieldsForm, self).__init__(_cw, rset, row, col, **kwargs)
+        self.uicfg_aff = self._cw.vreg['uicfg'].select(
+            'autoform_field', self._cw, entity=self.edited_entity)
+        self.uicfg_affk = self._cw.vreg['uicfg'].select(
+            'autoform_field_kwargs', self._cw, entity=self.edited_entity)
         self.add_hidden('__type', self.edited_entity.__regid__, eidparam=True)
         self.add_hidden('eid', self.edited_entity.eid)
         # mainform default to true in parent, hence default to True
--- a/web/views/idownloadable.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/idownloadable.py	Wed Mar 20 17:40:25 2013 +0100
@@ -153,7 +153,7 @@
             try:
                 self.w(entity._cw_mtc_transform(adapter.download_data(), sourcemt,
                                                 targetmt, adapter.download_encoding()))
-            except Exception, ex:
+            except Exception as ex:
                 self.exception('while rendering data for %s', entity)
                 msg = self._cw._("can't display data, unexpected error: %s") \
                       % xml_escape(unicode(ex))
--- a/web/views/json.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/json.py	Wed Mar 20 17:40:25 2013 +0100
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """json export views"""
 
-from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
 _ = unicode
 
--- a/web/views/magicsearch.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/magicsearch.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -15,19 +15,23 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""a query processor to handle quick search shortcuts for cubicweb"""
+"""a query processor to handle quick search shortcuts for cubicweb
+"""
 
 __docformat__ = "restructuredtext en"
 
 import re
 from logging import getLogger
-from warnings import warn
+
+from yams.interfaces import IVocabularyConstraint
 
 from rql import RQLSyntaxError, BadRQLQuery, parse
+from rql.utils import rqlvar_maker
 from rql.nodes import Relation
 
 from cubicweb import Unauthorized, typed_eid
 from cubicweb.view import Component
+from cubicweb.web.views.ajaxcontroller import ajaxfunc
 
 LOGGER = getLogger('cubicweb.magicsearch')
 
@@ -345,7 +349,7 @@
             try:
                 word1 = left_words[0]
                 return self._two_words_query(word1, quoted_part)
-            except BadRQLQuery, error:
+            except BadRQLQuery as error:
                 raise BadRQLQuery("unable to handle request %r" % ori_rql)
         # Case (2) : Company name "My own company";
         elif len(left_words) == 2:
@@ -396,10 +400,10 @@
                 # FIXME : we don't want to catch any exception type here !
                 except (RQLSyntaxError, BadRQLQuery):
                     pass
-                except Unauthorized, ex:
+                except Unauthorized as ex:
                     unauthorized = ex
                     continue
-                except Exception, ex:
+                except Exception as ex:
                     LOGGER.debug('%s: %s', ex.__class__.__name__, ex)
                     continue
             if unauthorized:
@@ -408,3 +412,247 @@
             # explicitly specified processor: don't try to catch the exception
             return proc.process_query(uquery)
         raise BadRQLQuery(self._cw._('sorry, the server is unable to handle this query'))
+
+
+
+## RQL suggestions builder ####################################################
+class RQLSuggestionsBuilder(Component):
+    """main entry point is `build_suggestions()` which takes
+    an incomplete RQL query and returns a list of suggestions to complete
+    the query.
+
+    This component is enabled by default and is used to provide autocompletion
+    in the RQL search bar. If you don't want this feature in your application,
+    just unregister it or make it unselectable.
+
+    .. automethod:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder.build_suggestions
+    .. automethod:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder.etypes_suggestion_set
+    .. automethod:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder.possible_etypes
+    .. automethod:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder.possible_relations
+    .. automethod:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder.vocabulary
+    """
+    __regid__ = 'rql.suggestions'
+
+    #: maximum number of results to fetch when suggesting attribute values
+    attr_value_limit = 20
+
+    def build_suggestions(self, user_rql):
+        """return a list of suggestions to complete `user_rql`
+
+        :param user_rql: an incomplete RQL query
+        """
+        req = self._cw
+        try:
+            if 'WHERE' not in user_rql: # don't try to complete if there's no restriction
+                return []
+            variables, restrictions = [part.strip() for part in user_rql.split('WHERE', 1)]
+            if ',' in restrictions:
+                restrictions, incomplete_part = restrictions.rsplit(',', 1)
+                user_rql = '%s WHERE %s' % (variables, restrictions)
+            else:
+                restrictions, incomplete_part = '', restrictions
+                user_rql = variables
+            select = parse(user_rql, print_errors=False).children[0]
+            req.vreg.rqlhelper.annotate(select)
+            req.vreg.solutions(req, select, {})
+            if restrictions:
+                return ['%s, %s' % (user_rql, suggestion)
+                        for suggestion in self.rql_build_suggestions(select, incomplete_part)]
+            else:
+                return ['%s WHERE %s' % (user_rql, suggestion)
+                        for suggestion in self.rql_build_suggestions(select, incomplete_part)]
+        except Exception as exc: # we never want to crash
+            self.debug('failed to build suggestions: %s', exc)
+            return []
+
+    ## actual completion entry points #########################################
+    def rql_build_suggestions(self, select, incomplete_part):
+        """
+        :param select: the annotated select node (rql syntax tree)
+        :param incomplete_part: the part of the rql query that needs
+                                to be completed, (e.g. ``X is Pr``, ``X re``)
+        """
+        chunks = incomplete_part.split(None, 2)
+        if not chunks: # nothing to complete
+            return []
+        if len(chunks) == 1: # `incomplete` looks like "MYVAR"
+            return self._complete_rqlvar(select, *chunks)
+        elif len(chunks) == 2: # `incomplete` looks like "MYVAR some_rel"
+            return self._complete_rqlvar_and_rtype(select, *chunks)
+        elif len(chunks) == 3: # `incomplete` looks like "MYVAR some_rel something"
+            return self._complete_relation_object(select, *chunks)
+        else: # would be anything else, hard to decide what to do here
+            return []
+
+    # _complete_* methods are considered private, at least while the API
+    # isn't stabilized.
+    def _complete_rqlvar(self, select, rql_var):
+        """return suggestions for "variable only" incomplete_part
+
+        as in :
+
+        - Any X WHERE X
+        - Any X WHERE X is Project, Y
+        - etc.
+        """
+        return ['%s %s %s' % (rql_var, rtype, dest_var)
+                for rtype, dest_var in self.possible_relations(select, rql_var)]
+
+    def _complete_rqlvar_and_rtype(self, select, rql_var, user_rtype):
+        """return suggestions for "variable + rtype" incomplete_part
+
+        as in :
+
+        - Any X WHERE X is
+        - Any X WHERE X is Person, X firstn
+        - etc.
+        """
+        # special case `user_type` == 'is', return every possible type.
+        if user_rtype == 'is':
+            return self._complete_is_relation(select, rql_var)
+        else:
+            return ['%s %s %s' % (rql_var, rtype, dest_var)
+                    for rtype, dest_var in self.possible_relations(select, rql_var)
+                    if rtype.startswith(user_rtype)]
+
+    def _complete_relation_object(self, select, rql_var, user_rtype, user_value):
+        """return suggestions for "variable + rtype + some_incomplete_value"
+
+        as in :
+
+        - Any X WHERE X is Per
+        - Any X WHERE X is Person, X firstname "
+        - Any X WHERE X is Person, X firstname "Pa
+        - etc.
+        """
+        # special case `user_type` == 'is', return every possible type.
+        if user_rtype == 'is':
+            return self._complete_is_relation(select, rql_var, user_value)
+        elif user_value:
+            if user_value[0] in ('"', "'"):
+                # if finished string, don't suggest anything
+                if len(user_value) > 1 and user_value[-1] == user_value[0]:
+                    return []
+                user_value = user_value[1:]
+                return ['%s %s "%s"' % (rql_var, user_rtype, value)
+                        for value in self.vocabulary(select, rql_var,
+                                                     user_rtype, user_value)]
+        return []
+
+    def _complete_is_relation(self, select, rql_var, prefix=''):
+        """return every possible types for rql_var
+
+        :param prefix: if specified, will only return entity types starting
+                       with the specified value.
+        """
+        return ['%s is %s' % (rql_var, etype)
+                for etype in self.possible_etypes(select, rql_var, prefix)]
+
+    def etypes_suggestion_set(self):
+        """returns the list of possible entity types to suggest
+
+        The default is to return any non-final entity type available
+        in the schema.
+
+        Can be overridden for instance if an application decides
+        to restrict this list to a meaningful set of business etypes.
+        """
+        schema = self._cw.vreg.schema
+        return set(eschema.type for eschema in schema.entities() if not eschema.final)
+
+    def possible_etypes(self, select, rql_var, prefix=''):
+        """return all possible etypes for `rql_var`
+
+        The returned list will always be a subset of meth:`etypes_suggestion_set`
+
+        :param select: the annotated select node (rql syntax tree)
+        :param rql_var: the variable name for which we want to know possible types
+        :param prefix: if specified, will only return etypes starting with it
+        """
+        available_etypes = self.etypes_suggestion_set()
+        possible_etypes = set()
+        for sol in select.solutions:
+            if rql_var in sol and sol[rql_var] in available_etypes:
+                possible_etypes.add(sol[rql_var])
+        if not possible_etypes:
+            # `Any X WHERE X is Person, Y is`
+            # -> won't have a solution, need to give all etypes
+            possible_etypes = available_etypes
+        return sorted(etype for etype in possible_etypes if etype.startswith(prefix))
+
+    def possible_relations(self, select, rql_var, include_meta=False):
+        """returns a list of couple (rtype, dest_var) for each possible
+        relations with `rql_var` as subject.
+
+        ``dest_var`` will be picked among availabel variables if types match,
+        otherwise a new one will be created.
+        """
+        schema = self._cw.vreg.schema
+        relations = set()
+        untyped_dest_var = rqlvar_maker(defined=select.defined_vars).next()
+        # for each solution
+        # 1. find each possible relation
+        # 2. for each relation:
+        #    2.1. if the relation is meta, skip it
+        #    2.2. for each possible destination type, pick up possible
+        #         variables for this type or use a new one
+        for sol in select.solutions:
+            etype = sol[rql_var]
+            sol_by_types = {}
+            for varname, var_etype in sol.items():
+                # don't push subject var to avoid "X relation X" suggestion
+                if varname != rql_var:
+                    sol_by_types.setdefault(var_etype, []).append(varname)
+            for rschema in schema[etype].subject_relations():
+                if include_meta or not rschema.meta:
+                    for dest in rschema.objects(etype):
+                        for varname in sol_by_types.get(dest.type, (untyped_dest_var,)):
+                            suggestion = (rschema.type, varname)
+                            if suggestion not in relations:
+                                relations.add(suggestion)
+        return sorted(relations)
+
+    def vocabulary(self, select, rql_var, user_rtype, rtype_incomplete_value):
+        """return acceptable vocabulary for `rql_var` + `user_rtype` in `select`
+
+        Vocabulary is either found from schema (Yams) definition or
+        directly from database.
+        """
+        schema = self._cw.vreg.schema
+        vocab = []
+        for sol in select.solutions:
+            # for each solution :
+            # - If a vocabulary constraint exists on `rql_var+user_rtype`, use it
+            #   to define possible values
+            # - Otherwise, query the database to fetch available values from
+            #   database (limiting results to `self.attr_value_limit`)
+            try:
+                eschema = schema.eschema(sol[rql_var])
+                rdef = eschema.rdef(user_rtype)
+            except KeyError: # unknown relation
+                continue
+            cstr = rdef.constraint_by_interface(IVocabularyConstraint)
+            if cstr is not None:
+                # a vocabulary is found, use it
+                vocab += [value for value in cstr.vocabulary()
+                          if value.startswith(rtype_incomplete_value)]
+            elif rdef.final:
+                # no vocab, query database to find possible value
+                vocab_rql = 'DISTINCT Any V LIMIT %s WHERE X is %s, X %s V' % (
+                    self.attr_value_limit, eschema.type, user_rtype)
+                vocab_kwargs = {}
+                if rtype_incomplete_value:
+                    vocab_rql += ', X %s LIKE %%(value)s' % user_rtype
+                    vocab_kwargs['value'] = '%s%%' % rtype_incomplete_value
+                vocab += [value for value, in
+                          self._cw.execute(vocab_rql, vocab_kwargs)]
+        return sorted(set(vocab))
+
+
+
+@ajaxfunc(output_type='json')
+def rql_suggest(self):
+    rql_builder = self._cw.vreg['components'].select_or_none('rql.suggestions', self._cw)
+    if rql_builder:
+        return rql_builder.build_suggestions(self._cw.form['term'])
+    return []
--- a/web/views/management.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/management.py	Wed Mar 20 17:40:25 2013 +0100
@@ -76,7 +76,9 @@
                                          domid='ownership%s' % entity.eid,
                                          __redirectvid='security',
                                          __redirectpath=entity.rest_path())
-        field = guess_field(entity.e_schema, self._cw.vreg.schema.rschema('owned_by'))
+        field = guess_field(entity.e_schema,
+                            self._cw.vreg.schema['owned_by'],
+                            req=self._cw)
         form.append_field(field)
         form.render(w=self.w, display_progress_div=False)
 
--- a/web/views/massmailing.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/massmailing.py	Wed Mar 20 17:40:25 2013 +0100
@@ -21,6 +21,7 @@
 _ = unicode
 
 import operator
+from functools import reduce
 
 from cubicweb.predicates import (is_instance, authenticated_user,
                                 adaptable, match_form_params)
@@ -43,7 +44,7 @@
 
     def url(self):
         params = {'vid': 'massmailing', '__force_display': 1}
-        if self._cw.form.has_key('rql'):
+        if 'rql' in self._cw.form:
             params['rql'] = self._cw.form['rql']
         return self._cw.build_url(self._cw.relative_path(includeparams=False),
                                   **params)
--- a/web/views/primary.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/primary.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -50,7 +50,8 @@
 from cubicweb.predicates import match_kwargs, match_context
 from cubicweb.view import EntityView
 from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, display_name
-from cubicweb.web import uicfg, component
+from cubicweb.web import component
+from cubicweb.web.views import uicfg
 
 
 class PrimaryView(EntityView):
@@ -96,8 +97,8 @@
     title = _('primary')
     show_attr_label = True
     show_rel_label = True
-    rsection = uicfg.primaryview_section
-    display_ctrl = uicfg.primaryview_display_ctrl
+    rsection = None
+    display_ctrl = None
     main_related_section = True
 
     def html_headers(self):
@@ -110,6 +111,13 @@
 
     def entity_call(self, entity):
         entity.complete()
+        uicfg_reg = self._cw.vreg['uicfg']
+        if self.rsection is None:
+            self.rsection = uicfg_reg.select('primaryview_section',
+                                             self._cw, entity=entity)
+        if self.display_ctrl is None:
+            self.display_ctrl = uicfg_reg.select('primaryview_display_ctrl',
+                                                 self._cw, entity=entity)
         self.render_entity(entity)
 
     def render_entity(self, entity):
@@ -226,6 +234,7 @@
 
     def render_entity_relations(self, entity):
         """Renders all relations in the 'relations' section."""
+        defaultlimit = self._cw.property_value('navigation.related-limit')
         for rschema, tschemas, role, dispctrl in self._section_def(entity, 'relations'):
             if rschema.final or dispctrl.get('rtypevid'):
                 vid = dispctrl.get('vid', 'reledit')
@@ -239,7 +248,9 @@
                 value = rview.render(row=entity.cw_row, col=entity.cw_col,
                                      rtype=rschema.type, role=role)
             else:
-                rset = self._relation_rset(entity, rschema, role, dispctrl)
+                vid = dispctrl.get('vid', 'autolimited')
+                limit = defaultlimit if vid == 'autolimited' else None
+                rset = self._relation_rset(entity, rschema, role, dispctrl, limit=limit)
                 if not rset:
                     continue
                 if hasattr(self, '_render_relation'):
@@ -249,7 +260,6 @@
                          'been renamed to render_relation, please update %s'
                          % self.__class__, DeprecationWarning)
                     continue
-                vid = dispctrl.get('vid', 'autolimited')
                 try:
                     rview = self._cw.vreg['views'].select(
                         vid, self._cw, rset=rset, dispctrl=dispctrl)
@@ -293,12 +303,14 @@
     def _prepare_side_boxes(self, entity):
         sideboxes = []
         boxesreg = self._cw.vreg['ctxcomponents']
+        defaultlimit = self._cw.property_value('navigation.related-limit')
         for rschema, tschemas, role, dispctrl in self._section_def(entity, 'sideboxes'):
-            rset = self._relation_rset(entity, rschema, role, dispctrl)
+            vid = dispctrl.get('vid', 'autolimited')
+            limit = defaultlimit if vid == 'autolimited' else None
+            rset = self._relation_rset(entity, rschema, role, dispctrl, limit=limit)
             if not rset:
                 continue
             label = self._rel_label(entity, rschema, role, dispctrl)
-            vid = dispctrl.get('vid', 'autolimited')
             box = boxesreg.select('rsetbox', self._cw, rset=rset,
                                   vid=vid, title=label, dispctrl=dispctrl,
                                   context='incontext')
@@ -331,9 +343,9 @@
                 rdefs.append( (rschema, matchtschemas, role, dispctrl) )
         return sorted(rdefs, key=lambda x: x[-1]['order'])
 
-    def _relation_rset(self, entity, rschema, role, dispctrl):
+    def _relation_rset(self, entity, rschema, role, dispctrl, limit=None):
         try:
-            rset = entity.related(rschema.type, role)
+            rset = entity.related(rschema.type, role, limit=limit)
         except Unauthorized:
             return
         if 'filter' in dispctrl:
--- a/web/views/reledit.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/reledit.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -34,7 +34,8 @@
 from cubicweb.utils import json, json_dumps
 from cubicweb.predicates import non_final_entity, match_kwargs
 from cubicweb.view import EntityView
-from cubicweb.web import uicfg, stdmsgs
+from cubicweb.web import stdmsgs
+from cubicweb.web.views import uicfg
 from cubicweb.web.form import FieldNotFound
 from cubicweb.web.formwidgets import Button, SubmitButton
 from cubicweb.web.views.ajaxcontroller import ajaxfunc
@@ -50,8 +51,6 @@
     def add_hidden(self, *args):
         pass
 
-rctrl = uicfg.reledit_ctrl
-
 class AutoClickAndEditFormView(EntityView):
     __regid__ = 'reledit'
     __select__ = non_final_entity() & match_kwargs('rtype')
@@ -90,6 +89,7 @@
         self._cw.add_js(('cubicweb.reledit.js', 'cubicweb.edition.js', 'cubicweb.ajax.js'))
         self.entity = entity
         rschema = self._cw.vreg.schema[rtype]
+        rctrl = self._cw.vreg['uicfg'].select('reledit', self._cw, entity=entity)
         self._rules = rctrl.etype_get(self.entity.e_schema.type, rschema.type, role, '*')
         if rvid is not None or default_value is not None:
             warn('[3.9] specifying rvid/default_value on select is deprecated, '
--- a/web/views/schema.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/schema.py	Wed Mar 20 17:40:25 2013 +0100
@@ -39,9 +39,9 @@
 from cubicweb.utils import make_uid
 from cubicweb.view import EntityView, StartupView
 from cubicweb import tags, uilib
-from cubicweb.web import action, facet, uicfg, schemaviewer
+from cubicweb.web import action, facet, schemaviewer
 from cubicweb.web.views import TmpFileViewMixin
-from cubicweb.web.views import primary, baseviews, tabs, tableview, ibreadcrumbs
+from cubicweb.web.views import uicfg, primary, baseviews, tabs, tableview, ibreadcrumbs
 
 ALWAYS_SKIP_TYPES = BASE_TYPES | SCHEMA_TYPES
 SKIP_TYPES  = (ALWAYS_SKIP_TYPES | META_RTYPES | SYSTEM_RTYPES | WORKFLOW_TYPES
--- a/web/views/sessions.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/sessions.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/web/views/sparql.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/sparql.py	Wed Mar 20 17:40:25 2013 +0100
@@ -58,11 +58,11 @@
         if sparql:
             try:
                 qinfo = Sparql2rqlTranslator(self._cw.vreg.schema).translate(sparql)
-            except TypeResolverException, exc:
+            except TypeResolverException as exc:
                 self.w(self._cw._('can not resolve entity types:') + u' ' + unicode(exc))
             except UnsupportedQuery:
                 self.w(self._cw._('we are not yet ready to handle this query'))
-            except xy.UnsupportedVocabulary, exc:
+            except xy.UnsupportedVocabulary as exc:
                 self.w(self._cw._('unknown vocabulary:') + u' ' + unicode(exc))
             else:
                 rql, args = qinfo.finalize()
@@ -140,4 +140,4 @@
 
 def registration_callback(vreg):
     if Sparql2rqlTranslator is not None:
-        vreg.register_all(globals().values(), __name__)
+        vreg.register_all(globals().itervalues(), __name__)
--- a/web/views/startup.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/startup.py	Wed Mar 20 17:40:25 2013 +0100
@@ -31,7 +31,8 @@
 from cubicweb.view import StartupView
 from cubicweb.predicates import match_user_groups, is_instance
 from cubicweb.schema import display_name
-from cubicweb.web import uicfg, httpcache
+from cubicweb.web import httpcache
+from cubicweb.web.views import uicfg
 
 class ManageView(StartupView):
     """:__regid__: *manage*
@@ -51,7 +52,7 @@
     title = _('manage')
     http_cache_manager = httpcache.EtagHTTPCacheManager
     add_etype_links = ()
-    skip_startup_views = set( ('index', 'manage', 'schema', 'owl', 'changelog',
+    skip_startup_views = set( ('index', 'manage', 'schema', 'owl', 
                                'systempropertiesform', 'propertiesform',
                                'loggedout', 'login',
                                'cw.users-and-groups-management', 'cw.groups-management', 
--- a/web/views/staticcontrollers.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/staticcontrollers.py	Wed Mar 20 17:40:25 2013 +0100
@@ -22,7 +22,6 @@
 - /fckeditor/...
 
 """
-from __future__ import with_statement
 
 import os
 import os.path as osp
--- a/web/views/tableview.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/tableview.py	Wed Mar 20 17:40:25 2013 +0100
@@ -290,20 +290,17 @@
         return attrs
 
     def render_actions(self, w, actions):
-        box = MenuWidget('', '', _class='tableActionsBox', islist=False)
-        label = tags.span(self._cw._('action menu'))
-        menu = PopupBoxMenu(label, isitem=False, link_class='actionsBox',
-                            ident='%sActions' % self.view.domid)
-        box.append(menu)
+        w(u'<div class="tableactions">')
         for action in actions:
-            menu.append(action)
-        box.render(w=w)
-        w(u'<div class="clear"></div>')
+            w(u'<span>')
+            action.render(w)
+            w(u'</span>')
+        w(u'</div>')
 
     def show_hide_filter_actions(self, currentlydisplayed=False):
         divid = self.view.domid
         showhide = u';'.join(toggle_action('%s%s' % (divid, what))[11:]
-                             for what in ('Form', 'Show', 'Hide', 'Actions'))
+                             for what in ('Form', 'Actions'))
         showhide = 'javascript:' + showhide
         self._cw.add_onload(u'''\
 $(document).ready(function() {
@@ -313,10 +310,8 @@
     $('#%(id)sShow').attr('class', 'hidden');
   }
 });''' % {'id': divid})
-        showlabel = self._cw._('show filter form')
-        hidelabel = self._cw._('hide filter form')
-        return [component.Link(showhide, showlabel, id='%sShow' % divid),
-                component.Link(showhide, hidelabel, id='%sHide' % divid)]
+        showlabel = self._cw._('toggle filter')
+        return [component.Link(showhide, showlabel, id='%sToggle' % divid)]
 
 
 class AbstractColumnRenderer(object):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/uicfg.py	Wed Mar 20 17:40:25 2013 +0100
@@ -0,0 +1,663 @@
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb 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 Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""This module (``cubicweb.web.views.uicfg``) regroups a set of structures that may be
+used to configure various options of the generated web interface.
+
+To configure the interface generation, we use ``RelationTag`` objects.
+
+Index view configuration
+````````````````````````
+:indexview_etype_section:
+   entity type category in the index/manage page. May be one of:
+
+      * ``application``
+      * ``system``
+      * ``schema``
+      * ``subobject`` (not displayed by default)
+
+   By default only entities on the ``application`` category are shown.
+
+.. sourcecode:: python
+
+    from cubicweb.web.views import uicfg
+    # force hiding
+    uicfg.indexview_etype_section['HideMe'] = 'subobject'
+    # force display
+    uicfg.indexview_etype_section['ShowMe'] = 'application'
+
+
+Actions box configuration
+`````````````````````````
+:actionbox_appearsin_addmenu:
+  simple boolean relation tags used to control the "add entity" submenu.
+  Relations whose rtag is True will appears, other won't.
+
+.. sourcecode:: python
+
+   # Adds all subjects of the entry_of relation in the add menu of the ``Blog``
+   # primary view
+   uicfg.actionbox_appearsin_addmenu.tag_object_of(('*', 'entry_of', 'Blog'), True)
+"""
+__docformat__ = "restructuredtext en"
+
+from warnings import warn
+
+from logilab.common.compat import any
+
+from cubicweb import neg_role
+from cubicweb.rtags import (RelationTags, RelationTagsBool, RelationTagsSet,
+                            RelationTagsDict, NoTargetRelationTagsDict,
+                            _ensure_str_key)
+from cubicweb.schema import META_RTYPES, INTERNAL_TYPES, WORKFLOW_TYPES
+
+
+# primary view configuration ##################################################
+
+class PrimaryViewSectionRelationTags(RelationTags):
+    """primary view section configuration"""
+    __regid__ = 'primaryview_section'
+
+    _allowed_values = frozenset(('attributes', 'relations',
+                                 'sideboxes', 'hidden'))
+
+    def _init(self, sschema, rschema, oschema, role):
+        if self.get(sschema, rschema, oschema, role) is None:
+            rdef = rschema.rdef(sschema, oschema)
+            if rschema.final:
+                if rschema.meta or sschema.is_metadata(rschema) \
+                        or oschema.type in ('Password', 'Bytes'):
+                    section = 'hidden'
+                else:
+                    section = 'attributes'
+            else:
+                if rdef.role_cardinality(role) in '1+':
+                    section = 'attributes'
+                elif rdef.composite == neg_role(role):
+                    section = 'relations'
+                else:
+                    section = 'sideboxes'
+            self.tag_relation((sschema, rschema, oschema, role), section)
+
+primaryview_section = PrimaryViewSectionRelationTags()
+
+
+class DisplayCtrlRelationTags(NoTargetRelationTagsDict):
+    """primary view display controller configuration"""
+    __regid__ = 'primaryview_display_ctrl'
+
+    def __init__(self, *args, **kwargs):
+        super(DisplayCtrlRelationTags, self).__init__(*args, **kwargs)
+        self.counter = 0
+
+    def _init(self, sschema, rschema, oschema, role):
+        if role == 'subject':
+            oschema = '*'
+        else:
+            sschema = '*'
+        self.counter += 1
+        self.setdefault((sschema, rschema, oschema, role),
+                        'order',
+                        self.counter)
+
+primaryview_display_ctrl = DisplayCtrlRelationTags()
+
+
+# index view configuration ####################################################
+# entity type section in the index/manage page. May be one of
+# * 'application'
+# * 'system'
+# * 'schema'
+# * 'hidden'
+# * 'subobject' (not displayed by default)
+
+class InitializableDict(dict): # XXX not a rtag. Turn into an appobject?
+    def __init__(self, *args, **kwargs):
+        super(InitializableDict, self).__init__(*args, **kwargs)
+        self.__defaults = dict(self)
+
+    def init(self, schema, check=True):
+        self.update(self.__defaults)
+        for eschema in schema.entities():
+            if eschema.final:
+                continue
+            if eschema.schema_entity():
+                self.setdefault(eschema, 'schema')
+            elif eschema in INTERNAL_TYPES or eschema in WORKFLOW_TYPES:
+                self.setdefault(eschema, 'system')
+            elif eschema.is_subobject(strict=True):
+                self.setdefault(eschema, 'subobject')
+            else:
+                self.setdefault(eschema, 'application')
+
+indexview_etype_section = InitializableDict(
+    EmailAddress='subobject',
+    Bookmark='system',
+    # entity types in the 'system' table by default (managers only)
+    CWUser='system', CWGroup='system',
+    )
+
+
+# autoform.AutomaticEntityForm configuration ##################################
+
+def _formsections_as_dict(formsections):
+    result = {}
+    for formsection in formsections:
+        formtype, section = formsection.split('_', 1)
+        result[formtype] = section
+    return result
+
+def _card_and_comp(sschema, rschema, oschema, role):
+    rdef = rschema.rdef(sschema, oschema)
+    if role == 'subject':
+        card = rdef.cardinality[0]
+        composed = not rschema.final and rdef.composite == 'object'
+    else:
+        card = rdef.cardinality[1]
+        composed = not rschema.final and rdef.composite == 'subject'
+    return card, composed
+
+class AutoformSectionRelationTags(RelationTagsSet):
+    """autoform relations'section"""
+    __regid__ = 'autoform_section'
+
+    _allowed_form_types = ('main', 'inlined', 'muledit')
+    _allowed_values = {'main': ('attributes', 'inlined', 'relations',
+                                'metadata', 'hidden'),
+                       'inlined': ('attributes', 'inlined', 'hidden'),
+                       'muledit': ('attributes', 'hidden'),
+                       }
+
+    def init(self, schema, check=True):
+        super(AutoformSectionRelationTags, self).init(schema, check)
+        self.apply(schema, self._initfunc_step2)
+
+    def _init(self, sschema, rschema, oschema, role):
+        formsections = self.init_get(sschema, rschema, oschema, role)
+        if formsections is None:
+            formsections = self.tag_container_cls()
+        if not any(tag.startswith('inlined') for tag in formsections):
+            if not rschema.final:
+                negsects = self.init_get(sschema, rschema, oschema, neg_role(role))
+                if 'main_inlined' in negsects:
+                    formsections.add('inlined_hidden')
+        key = _ensure_str_key( (sschema, rschema, oschema, role) )
+        self._tagdefs[key] = formsections
+
+    def _initfunc_step2(self, sschema, rschema, oschema, role):
+        formsections = self.get(sschema, rschema, oschema, role)
+        sectdict = _formsections_as_dict(formsections)
+        if rschema in META_RTYPES:
+            sectdict.setdefault('main', 'hidden')
+            sectdict.setdefault('muledit', 'hidden')
+            sectdict.setdefault('inlined', 'hidden')
+        elif role == 'subject' and rschema in sschema.meta_attributes():
+            # meta attribute, usually embeded by the described attribute's field
+            # (eg RichTextField, FileField...)
+            sectdict.setdefault('main', 'hidden')
+            sectdict.setdefault('muledit', 'hidden')
+            sectdict.setdefault('inlined', 'hidden')
+        # ensure we have a tag for each form type
+        if not 'main' in sectdict:
+            if not rschema.final and (
+                sectdict.get('inlined') == 'attributes' or
+                'inlined_attributes' in self.init_get(sschema, rschema, oschema,
+                                                      neg_role(role))):
+                sectdict['main'] = 'hidden'
+            elif sschema.is_metadata(rschema):
+                sectdict['main'] = 'metadata'
+            else:
+                card, composed = _card_and_comp(sschema, rschema, oschema, role)
+                if card in '1+':
+                    sectdict['main'] = 'attributes'
+                    if not 'muledit' in sectdict:
+                        sectdict['muledit'] = 'attributes'
+                elif rschema.final:
+                    sectdict['main'] = 'attributes'
+                else:
+                    sectdict['main'] = 'relations'
+        if not 'muledit' in sectdict:
+            sectdict['muledit'] = 'hidden'
+            if sectdict['main'] == 'attributes':
+                card, composed = _card_and_comp(sschema, rschema, oschema, role)
+                if card in '1+' and not composed:
+                    sectdict['muledit'] = 'attributes'
+        if not 'inlined' in sectdict:
+            sectdict['inlined'] = sectdict['main']
+        # recompute formsections and set it to avoid recomputing
+        for formtype, section in sectdict.iteritems():
+            formsections.add('%s_%s' % (formtype, section))
+
+    def tag_relation(self, key, formtype, section):
+        if isinstance(formtype, tuple):
+            for ftype in formtype:
+                self.tag_relation(key, ftype, section)
+            return
+        assert formtype in self._allowed_form_types, \
+               'formtype should be in (%s), not %s' % (
+            ','.join(self._allowed_form_types), formtype)
+        assert section in self._allowed_values[formtype], \
+               'section for %s should be in (%s), not %s' % (
+            formtype, ','.join(self._allowed_values[formtype]), section)
+        rtags = self._tagdefs.setdefault(_ensure_str_key(key),
+                                         self.tag_container_cls())
+        # remove previous section for this form type if any
+        if rtags:
+            for tag in rtags.copy():
+                if tag.startswith(formtype):
+                    rtags.remove(tag)
+        rtags.add('%s_%s' % (formtype, section))
+        return rtags
+
+    def init_get(self, stype, rtype, otype, tagged):
+        key = (stype, rtype, otype, tagged)
+        rtags = {}
+        for key in self._get_keys(stype, rtype, otype, tagged):
+            tags = self._tagdefs.get(key, ())
+            for tag in tags:
+                assert '_' in tag, (tag, tags)
+                section, value = tag.split('_', 1)
+                rtags[section] = value
+        cls = self.tag_container_cls
+        rtags = cls('_'.join([section,value])
+                    for section,value in rtags.iteritems())
+        return rtags
+
+    def get(self, *key):
+        # overriden to avoid recomputing done in parent classes
+        return self._tagdefs.get(key, ())
+
+    def relations_by_section(self, entity, formtype, section, permission,
+                             strict=False):
+        """return a list of (relation schema, target schemas, role) for the
+        given entity matching categories and permission.
+
+        `strict`:
+          bool telling if having local role is enough (strict = False) or not
+        """
+        tag = '%s_%s' % (formtype, section)
+        eschema  = entity.e_schema
+        cw = entity._cw
+        permsoverrides = cw.vreg['uicfg'].select('autoform_permissions_overrides', cw, entity=entity)
+        if entity.has_eid():
+            eid = entity.eid
+        else:
+            eid = None
+            strict = False
+        if permission == 'update':
+            assert section in ('attributes', 'metadata', 'hidden')
+            relpermission = 'add'
+        else:
+            assert section not in ('attributes', 'metadata', 'hidden')
+            relpermission = permission
+        for rschema, targetschemas, role in eschema.relation_definitions(True):
+            _targetschemas = []
+            for tschema in targetschemas:
+                # check section's tag first, potentially lower cost than
+                # checking permission which may imply rql queries
+                if not tag in self.etype_get(eschema, rschema, role, tschema):
+                    continue
+                rdef = rschema.role_rdef(eschema, tschema, role)
+                if rschema.final:
+                    if not rdef.has_perm(cw, permission, eid=eid,
+                                         creating=eid is None):
+                        continue
+                elif strict or not rdef.has_local_role(relpermission):
+                    if role == 'subject':
+                        if not rdef.has_perm(cw, relpermission, fromeid=eid):
+                            continue
+                    elif role == 'object':
+                        if not rdef.has_perm(cw, relpermission, toeid=eid):
+                            continue
+                _targetschemas.append(tschema)
+            if not _targetschemas:
+                continue
+            targetschemas = _targetschemas
+            rdef = eschema.rdef(rschema, role=role, targettype=targetschemas[0])
+            # XXX tag allowing to hijack the permission machinery when
+            # permission is not verifiable until the entity is actually
+            # created...
+            if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role):
+                yield (rschema, targetschemas, role)
+                continue
+            if not rschema.final and role == 'subject':
+                # on relation with cardinality 1 or ?, we need delete perm as well
+                # if the relation is already set
+                if (relpermission == 'add'
+                    and rdef.role_cardinality(role) in '1?'
+                    and eid and entity.related(rschema.type, role)
+                    and not rdef.has_perm(cw, 'delete', fromeid=eid,
+                                          toeid=entity.related(rschema.type, role)[0][0])):
+                    continue
+            elif role == 'object':
+                # on relation with cardinality 1 or ?, we need delete perm as well
+                # if the relation is already set
+                if (relpermission == 'add'
+                    and rdef.role_cardinality(role) in '1?'
+                    and eid and entity.related(rschema.type, role)
+                    and not rdef.has_perm(cw, 'delete', toeid=eid,
+                                          fromeid=entity.related(rschema.type, role)[0][0])):
+                    continue
+            yield (rschema, targetschemas, role)
+
+    def hide_field(self, etype, attr, desttype='*', formtype='main'):
+        """hide `attr` in `etype` forms.
+
+        :param etype: the entity type as a string
+        :param attr: the name of the attribute or relation to hide
+        :param formtype: which form will be affected ('main', 'inlined', etc.),
+         *main* by default.
+
+        `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_rel)
+
+        Examples:
+
+        .. sourcecode:: python
+
+          from cubicweb.web.views.uicfg import autoform_section as afs
+          afs.hide_field('CWUser', 'login')
+          afs.hide_field('*', 'name')
+          afs.hide_field('CWUser', 'use_email', formtype='inlined')
+
+        """
+        self._tag_etype_attr(etype, attr, desttype,
+                             formtype=formtype, section='hidden')
+
+    def hide_fields(self, etype, attrs, formtype='main'):
+        """simple for-loop wrapper around :func:`hide_field`.
+
+        :param etype: the entity type as a string
+        :param attrs: the ordered list of attribute names (or relations)
+        :param formtype: which form will be affected ('main', 'inlined', etc.),
+         *main* by default.
+
+        `attrs` can be strings or 2-tuples (relname, role_of_etype_in_the_rel)
+
+        Examples:
+
+        .. sourcecode:: python
+
+          from cubicweb.web.views.uicfg import autoform_section as afs
+          afs.hide_fields('CWUser', ('login', ('use_email', 'subject')),
+                          formtype='inlined')
+        """
+        for attr in attrs:
+            self.hide_field(etype, attr, formtype=formtype)
+
+    def edit_inline(self, etype, attr, desttype='*', formtype=('main', 'inlined')):
+        """edit `attr` with and inlined form.
+
+        :param etype: the entity type as a string
+        :param attr: the name of the attribute or relation
+        :param desttype: the destination type(s) concerned, default is everything
+        :param formtype: which form will be affected ('main', 'inlined', etc.),
+          *main* and *inlined* by default.
+
+        `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
+
+        Examples:
+
+        .. sourcecode:: python
+
+          from cubicweb.web.views.uicfg import autoform_section as afs
+
+          afs.edit_inline('*', 'use_email')
+      """
+        self._tag_etype_attr(etype, attr, desttype, formtype=formtype,
+                             section='inlined')
+
+    def edit_as_attr(self, etype, attr, desttype='*', formtype=('main', 'muledit')):
+        """make `attr` appear in the *attributes* section of `etype` form.
+
+        :param etype: the entity type as a string
+        :param attr: the name of the attribute or relation
+        :param desttype: the destination type(s) concerned, default is everything
+        :param formtype: which form will be affected ('main', 'inlined', etc.),
+          *main* and *muledit* by default.
+
+        `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
+
+        Examples:
+
+        .. sourcecode:: python
+
+          from cubicweb.web.views.uicfg import autoform_section as afs
+
+          afs.edit_as_attr('CWUser', 'in_group')
+        """
+        self._tag_etype_attr(etype, attr, desttype,
+                             formtype=formtype, section='attributes')
+
+    def set_muledit_editable(self, etype, attrs):
+        """make `attrs` appear in muledit form of `etype`.
+
+        :param etype: the entity type as a string
+        :param attrs: the ordered list of attribute names (or relations)
+
+        `attrs` can be strings or 2-tuples (relname, role_of_etype_in_the_relation)
+
+        Examples:
+
+        .. sourcecode:: python
+
+          from cubicweb.web.views.uicfg import autoform_section as afs
+
+          afs.set_muledit_editable('CWUser', ('firstname', 'surname', 'in_group'))
+        """
+        for attr in attrs:
+            self.edit_as_attr(self, etype, attr, formtype='muledit')
+
+autoform_section = AutoformSectionRelationTags()
+
+
+# relations'field class
+
+class AutoformFieldTags(RelationTags):
+    __regid__ = 'autoform_field'
+
+    def set_field(self, etype, attr, field):
+        """sets the `attr` field of `etype`.
+
+        :param etype: the entity type as a string
+        :param attr: the name of the attribute or relation
+
+        `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
+
+        """
+        self._tag_etype_attr(etype, attr, '*', field)
+
+autoform_field = AutoformFieldTags()
+
+
+# relations'field explicit kwargs (given to field's __init__)
+
+class AutoformFieldKwargsTags(RelationTagsDict):
+    __regid__ = 'autoform_field_kwargs'
+
+    def set_fields_order(self, etype, attrs):
+        """specify the field order in `etype` main edition form.
+
+        :param etype: the entity type as a string
+        :param attrs: the ordered list of attribute names (or relations)
+
+        `attrs` can be strings or 2-tuples (relname, role_of_etype_in_the_rel)
+
+        Unspecified fields will be displayed after specified ones, their
+        order being consistent with the schema definition.
+
+        Examples:
+
+        .. sourcecode:: python
+
+          from cubicweb.web.views.uicfg import autoform_field_kwargs as affk
+          affk.set_fields_order('CWUser', ('firstname', 'surname', 'login'))
+          affk.set_fields_order('CWUser', ('firstname', ('in_group', 'subject'),
+                                'surname', 'login'))
+
+        """
+        for index, attr in enumerate(attrs):
+            self._tag_etype_attr(etype, attr, '*', {'order': index})
+
+    def set_field_kwargs(self, etype, attr, **kwargs):
+        """tag `attr` field of `etype` with additional named paremeters.
+
+        :param etype: the entity type as a string
+        :param attr: the name of the attribute or relation
+
+        `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
+
+        Examples:
+
+        .. sourcecode:: python
+
+          from cubicweb.web.views.uicfg import autoform_field_kwargs as affk
+          affk.set_field_kwargs('Person', 'works_for', widget=fwdgs.AutoCompletionWidget())
+          affk.set_field_kwargs('CWUser', 'login', label=_('login or email address'),
+                                widget=fwdgs.TextInput(attrs={'size': 30}))
+        """
+        self._tag_etype_attr(etype, attr, '*', kwargs)
+
+
+autoform_field_kwargs = AutoformFieldKwargsTags()
+
+
+# set of tags of the form <action>_on_new on relations. <action> is a
+# schema action (add/update/delete/read), and when such a tag is found
+# permissions checking is by-passed and supposed to be ok
+class AutoFormPermissionsOverrides(RelationTagsSet):
+    __regid__ = 'autoform_permissions_overrides'
+
+autoform_permissions_overrides = AutoFormPermissionsOverrides()
+
+
+class ReleditTags(NoTargetRelationTagsDict):
+    """Associate to relation a dictionary to control `reledit` (e.g. edition of
+    attributes / relations from within views).
+
+    Possible keys and associated values are:
+
+    * `novalue_label`, alternative default value (shown when there is no value).
+
+    * `novalue_include_rtype`, when `novalue_label` is not specified, this boolean
+      flag control wether the generated default value should contains the
+      relation label or not. Will be the opposite of the `showlabel` value found
+      in the `primaryview_display_ctrl` rtag by default.
+
+    * `reload`, boolean, eid (to reload to) or function taking subject and
+      returning bool/eid. This is useful when editing a relation (or attribute)
+      that impacts the url or another parts of the current displayed
+      page. Defaults to False.
+
+    * `rvid`, alternative view id (as str) for relation or composite edition.
+      Default is 'autolimited'.
+
+    * `edit_target`, may be either 'rtype' (to edit the relation) or 'related'
+      (to edit the related entity).  This controls whether to edit the relation
+      or the target entity of the relation.  Currently only one-to-one relations
+      support target entity edition. By default, the 'related' option is taken
+      whenever the relation is composite.
+    """
+    __regid__ = 'reledit'
+    _keys = frozenset('novalue_label novalue_include_rtype reload rvid edit_target'.split())
+
+    def tag_relation(self, key, tag):
+        for tagkey in tag:
+            assert tagkey in self._keys, 'tag %r not in accepted tags: %r' % (tag, self._keys)
+        return super(ReleditTags, self).tag_relation(key, tag)
+
+    def _init(self, sschema, rschema, oschema, role):
+        values = self.get(sschema, rschema, oschema, role)
+        if not rschema.final:
+            composite = rschema.rdef(sschema, oschema).composite == role
+            if role == 'subject':
+                oschema = '*'
+            else:
+                sschema = '*'
+            edittarget = values.get('edit_target')
+            if edittarget not in (None, 'rtype', 'related'):
+                self.warning('reledit: wrong value for edit_target on relation %s: %s',
+                             rschema, edittarget)
+                edittarget = None
+            if not edittarget:
+                edittarget = 'related' if composite else 'rtype'
+                self.tag_relation((sschema, rschema, oschema, role),
+                                  {'edit_target': edittarget})
+        if not 'novalue_include_rtype' in values:
+            showlabel = primaryview_display_ctrl.get(
+                sschema, rschema, oschema, role).get('showlabel', True)
+            self.tag_relation((sschema, rschema, oschema, role),
+                              {'novalue_include_rtype': not showlabel})
+
+reledit_ctrl = ReleditTags()
+
+
+# boxes.EditBox configuration #################################################
+
+# 'link' / 'create' relation tags, used to control the "add entity" submenu
+
+class ActionBoxUicfg(RelationTagsBool):
+    __regid__ = 'actionbox_appearsin_addmenu'
+
+    def _init(self, sschema, rschema, oschema, role):
+        if self.get(sschema, rschema, oschema, role) is None:
+            if rschema in META_RTYPES:
+                self.tag_relation((sschema, rschema, oschema, role), False)
+                return
+            rdef = rschema.rdef(sschema, oschema)
+            if not rdef.role_cardinality(role) in '?1' and rdef.composite == role:
+                self.tag_relation((sschema, rschema, oschema, role), True)
+
+    def _tag_etype_attr(self, etype, attr, desttype='*', *args, **kwargs):
+        if isinstance(attr, basestring):
+            attr, role = attr, 'subject'
+        else:
+            attr, role = attr
+        if role == 'subject':
+            self.tag_subject_of((etype, attr, desttype), *args, **kwargs)
+        else:
+            self.tag_object_of((desttype, attr, etype), *args, **kwargs)
+
+    def append_to_addmenu(self, etype, attr, createdtype='*'):
+        """adds `attr` in the actions box *addrelated* submenu of `etype`.
+
+        :param etype: the entity type as a string
+        :param attr: the name of the attribute or relation to hide
+        :param createdtype: the target type of the relation (optional, defaults to '*' (all possible types))
+
+        `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
+
+        """
+        self._tag_etype_attr(etype, attr, createdtype, True)
+
+    def remove_from_addmenu(self, etype, attr, createdtype='*'):
+        """removes `attr` from the actions box *addrelated* submenu of `etype`.
+
+        :param etype: the entity type as a string
+        :param attr: the name of the attribute or relation to hide
+        :param createdtype: the target type of the relation (optional, defaults to '*' (all possible types))
+
+        `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
+        """
+        self._tag_etype_attr(etype, attr, createdtype, False)
+
+actionbox_appearsin_addmenu = ActionBoxUicfg()
+
+
+
+def registration_callback(vreg):
+    vreg.register_all(globals().itervalues(), __name__)
+    indexview_etype_section.init(vreg.schema)
--- a/web/views/urlrewrite.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/urlrewrite.py	Wed Mar 20 17:40:25 2013 +0100
@@ -109,7 +109,6 @@
         (rgx('/doc/images/(.+?)/?'), dict(vid='wdocimages', fid=r'\1')),
         (rgx('/doc/?'), dict(vid='wdoc', fid=r'main')),
         (rgx('/doc/(.+?)/?'), dict(vid='wdoc', fid=r'\1')),
-        (rgx('/changelog/?'), dict(vid='changelog')),
         ]
 
     def rewrite(self, req, uri):
--- a/web/views/wdoc.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/wdoc.py	Wed Mar 20 17:40:25 2013 +0100
@@ -205,60 +205,6 @@
         self.w(open(join(resourcedir, rid)).read())
 
 
-class ChangeLogView(StartupView):
-    __regid__ = 'changelog'
-    title = _('What\'s new?')
-    maxentries = 25
-
-    def call(self):
-        rid = 'ChangeLog_%s' % (self._cw.lang)
-        allentries = []
-        title = self._cw._(self.title)
-        restdata = ['.. -*- coding: utf-8 -*-', '', title, '='*len(title), '']
-        w = restdata.append
-        today = date.today()
-        for fpath in self._cw.vreg.config.locate_all_files(rid):
-            cl = ChangeLog(fpath)
-            encoding = 'utf-8'
-            # additional content may be found in title
-            for line in (cl.title + cl.additional_content).splitlines():
-                m = CHARSET_DECL_RGX.search(line)
-                if m is not None:
-                    encoding = m.group(1)
-                    continue
-                elif line.startswith('.. '):
-                    w(unicode(line, encoding))
-            for entry in cl.entries:
-                if entry.date:
-                    edate = todate(strptime(entry.date, '%Y-%m-%d'))
-                else:
-                    edate = today
-                messages = []
-                for msglines, submsgs in entry.messages:
-                    msgstr = unicode(' '.join(l.strip() for l in msglines), encoding)
-                    msgstr += u'\n\n'
-                    for submsglines in submsgs:
-                        msgstr += '     - ' + unicode(' '.join(l.strip() for l in submsglines), encoding)
-                        msgstr += u'\n'
-                    messages.append(msgstr)
-                entry = (edate, messages)
-                allentries.insert(bisect_right(allentries, entry), entry)
-        latestdate = None
-        i = 0
-        for edate, messages in reversed(allentries):
-            if latestdate != edate:
-                fdate = self._cw.format_date(edate)
-                w(u'\n%s' % fdate)
-                w('~' * len(fdate))
-                latestdate = edate
-            for msg in messages:
-                w(u'* %s' % msg)
-                i += 1
-                if i > self.maxentries:
-                    break
-        w('') # blank line
-        self.w(rest_publish(self, '\n'.join(restdata)))
-
 
 class HelpAction(action.Action):
     __regid__ = 'help'
@@ -271,17 +217,6 @@
     def url(self):
         return self._cw.build_url('doc/main')
 
-class ChangeLogAction(action.Action):
-    __regid__ = 'changelog'
-    __select__ = yes()
-
-    category = 'footer'
-    order = 1
-    title = ChangeLogView.title
-
-    def url(self):
-        return self._cw.build_url('changelog')
-
 
 class AboutAction(action.Action):
     __regid__ = 'about'
--- a/web/views/workflow.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/views/workflow.py	Wed Mar 20 17:40:25 2013 +0100
@@ -36,10 +36,10 @@
                                 score_entity, is_instance, adaptable)
 from cubicweb.view import EntityView
 from cubicweb.schema import display_name
-from cubicweb.web import uicfg, stdmsgs, action, component, form, action
+from cubicweb.web import stdmsgs, action, component, form, action
 from cubicweb.web import formfields as ff, formwidgets as fwdgs
 from cubicweb.web.views import TmpFileViewMixin
-from cubicweb.web.views import forms, primary, ibreadcrumbs
+from cubicweb.web.views import uicfg, forms, primary, ibreadcrumbs
 from cubicweb.web.views.tabs import TabbedPrimaryView, PrimaryTab
 from cubicweb.web.views.dotgraphview import DotGraphView, DotPropsHandler
 
--- a/web/wdoc/ChangeLog_en	Tue Mar 19 16:56:46 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-.. -*- coding: utf-8 -*-
-.. _`user preferences`: myprefs
-.. _here: sparql
-.. _SPARQL: http://www.w3.org/TR/rdf-sparql-query/
-.. _schema: schema
-.. _OWL: http://www.w3.org/TR/owl-features/
-.. _CWSource: cwetype/CWSource
-
-2010-10-17 --  3.10.0
-
-  * facets on optional relations now propose to search to entities
-    which miss the relation
-
-  * box and content navigation components have been merged, so this
-    simplify the configuration and you should be able to move
-    boxes/components in various places of the interface
-
-  * global / context dependant boxes should now be more
-    distinguishable to make the interface more intuitive
-
-  * upgraded jQuery and jQuery UI respectively to version 1.4.2 and
-    1.8.
-
-  * data sources are now stored as CWSource_ entities in the database.
-    This allows on multi-sources instance to filter search results
-    according to the source entities are coming from.
-
-
-2010-06-11  --  3.8.4
-   * support full text prefix search for instances using postgres > 8.4 as database: try it
-     by using search such as 'cubic*'
-
-
-2010-04-20  --  3.8.0
-   * nicer schema_ and workflow views (clickable image!)
-
-   * more power to undo, though not yet complete (missing entity updates, soon available...)
-
-   * pdf export functionnality moved to its own cube. If it's no more
-     present on this site while you found it useful, ask you site
-     administrator to install the pdfexport_ cube.
-
-
-2010-03-16  --  3.7.0
-   * experimental support for undoing of deletion. If you're not proposed to *undo*
-     deletion of one or several entities, ask you site administrator to activate
-     the feature.
-
-
-2010-02-10  --  3.6.0
-   * nice 'demo widget' to edit bookmark's path, e.g. a relative url, splitted
-     as path and parameters and dealing nicely with url encodings. Try to
-     edit your bookmarks!
-
-   * hell lot of refactorings, but you should hopefuly not see that from the outside
-
-2009-09-17  --  3.5.0
-
-    * selectable workflows: authorized users may change the workflow used
-      by some workflowable entities
-
-
-2009-08-07  --  3.4.0
-
-    * support for SPARQL_. Click here_ to test it.
-
-    * and another step toward the semantic web: new `ExternalUri` entity type
-      with its associated `same_as` relation. See
-      http://www.w3.org/TR/owl-ref/#sameAs-def for more information and check
-      this instance schema_ to see on which entity types it may be applied
-
-    * new "view workflow" and "view history" items in the workflow
-      sub-menu of the actions box
-
-    * you can now edit comments of workflow transition afterward (if authorized,
-      of course)
-
-    * modification date of an entity is updated when its state is changed
-
-
-2009-02-26  --  3.1.0
-
-    * schema may be exported as OWL_
-
-    * new ajax interface for site configuration / `user preferences`_
-
-
-2008-10-24  --  2.99.0
-    * cubicweb is now open source !
--- a/web/wdoc/ChangeLog_fr	Tue Mar 19 16:56:46 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-.. -*- coding: utf-8 -*-
-.. _`préférences utilisateurs`: myprefs#fieldset_ui
-.. _ici: sparql
-.. _SPARQL: http://www.w3.org/TR/rdf-sparql-query/
-.. _schema: schema
-.. _OWL: http://www.w3.org/TR/owl-features/
-.. _pdfexport: http://www.cubicweb.org/project/cubicweb-pdfexport
-.. _CWSource: cwetype/CWSource
-
-2010-10-07 --  3.10.0
-
-  * les facettes sur les relations optionnelles proposent maintenant
-    de filter les entité qui n'ont *pas* la relation
-
-  * les boîtes et composants contextuels ont été fusionnés, permettant
-    de simplifier la configuration et de placer ces nouveaux composants
-    comme vous le désirez
-
-  * les boîtes globales ou dépendantes du contexte sont plus
-    facilement distinguable pour rendre l'interface plus intuitive
-
-  * passage à jQuery 1.4.2, et jQuery UI 1.8
-
-  * les sources de données sont maintenant stockées dans la base de
-    données sous forme d'entités CWSource_. Cela permet sur les
-    instances multi-source de filter les résultats de recherche en
-    fonction de la source dont viennent les entités.
-
-    
-2010-06-11  --  3.8.4
-   * support pour la recherche de préfixe pour les instances utilisant postgres > 8.4 :
-     essayez en cherchant par ex. 'cubic*'
-
-2010-04-20  --  3.8.0
-
-   * amélioration des vues de schema_ et des vues de workflows
-     (images clickable !)
-
-   * meilleure support du "undo", mais il manque toujours le support
-     sur la modification d'entité (bientôt...)
-
-   * la fonctionnalité d'export d'pdf a été déplacé dans son propre
-     cube. Si cette fonctionalité n'est plus disponible sur ce site et
-     que vous la trouviez utile, demander à l'administrateur
-     d'installer le cube pdfexport_.
-
-
-2010-03-16  --  3.7.0
-
-   * support experimental pour l'annulation ("undo") de la
-     suppression. Si, après une suppression d'une ou plusieurs
-     entités, on ne vous propose pas d'annuler l'opération, demander à
-     l'administrateur d'activé la fonctionnalité
-
-
-2010-02-10  --  3.6.0
-
-   * nouvelle widget (de démonstration :) pour éditer le chemin des
-     signets. Celui-ci, une url relative finalement, est décomposée de
-     chemin et paramètres que vous pouvez éditer individuellement et
-     surtout lisiblement car la gestion de l'échappement de l'url est
-     géré de manière transparente
-
-   * beaucoup de refactoring, mais vous ne devriez rien remarquer :)
-
-2009-09-17  --  3.5.0
-
-    * workflow sélectionnable: les utilisateurs autorisés peuvent
-      changer le workflow à utilister pour les entités le supportant
-
-
-2009-08-07  --  3.4.0
-
-    * support de SPARQL_. Cliquez ici_ pour le tester.
-
-    * et encore un pas vers le web sémantique : un nouveau type d'entité
-      `ExternalUri` et la relation associée `same_as`. Voir
-      http://www.w3.org/TR/owl-ref/#sameAs-def pour plus d'information, ainsi
-      que le schema_ de cette instance pour voir à quels types d'entités cela
-      s'applique.
-
-    * nouveau liens "voir les états possibles" et "voir l'historique" dans le sous-menu
-      workflow de la boite actions
-
-    * vous pouvez dorénavant éditer les commentaires des passages de transition
-      depuis l'historique, pour peu que vous ayez les droits nécessaire bien sûr
-
-    * la date de modification d'une entité est mise à jour lorsque son état est changé
-
-
-2009-02-26  --  3.1.0
-
-    * le schéma peut être exporté en OWL_
-
-    * nouvelle interface ajax pour la configuration du site et les `préférences utilisateurs`_
-
-
-
--- a/web/webconfig.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/webconfig.py	Wed Mar 20 17:40:25 2013 +0100
@@ -84,6 +84,13 @@
     uiprops = {'FCKEDITOR_PATH': ''}
 
     options = merge_options(CubicWebConfiguration.options + (
+        ('repository-uri',
+         {'type' : 'string',
+          'default': 'inmemory://',
+          'help': 'see `cubicweb.dbapi.connect` documentation for possible value',
+          'group': 'web', 'level': 2,
+          }),
+
         ('anonymous-user',
          {'type' : 'string',
           'default': None,
@@ -238,10 +245,6 @@
                 continue
             yield key, pdef
 
-    # method used to connect to the repository: 'inmemory' / 'pyro'
-    # Pyro repository by default
-    repo_method = 'pyro'
-
     # don't use @cached: we want to be able to disable it while this must still
     # be cached
     def repository(self, vreg=None):
@@ -250,7 +253,7 @@
             return self.__repo
         except AttributeError:
             from cubicweb.dbapi import get_repository
-            repo = get_repository(self.repo_method, vreg=vreg, config=self)
+            repo = get_repository(config=self, vreg=vreg)
             self.__repo = repo
             return repo
 
--- a/web/webctl.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/web/webctl.py	Wed Mar 20 17:40:25 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -46,7 +46,7 @@
         if not automatic:
             print '\n' + underline_title('Generic web configuration')
             config = self.config
-            if config.repo_method == 'pyro' or config.pyro_enabled():
+            if config['repository-uri'].startswith('pyro://') or config.pyro_enabled():
                 print '\n' + underline_title('Pyro configuration')
                 config.input_config('pyro', inputlevel)
             config.input_config('web', inputlevel)
--- a/wsgi/handler.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/wsgi/handler.py	Wed Mar 20 17:40:25 2013 +0100
@@ -106,8 +106,8 @@
     def __init__(self, config, vreg=None):
         self.appli = CubicWebPublisher(config, vreg=vreg)
         self.config = config
-        self.base_url = config['base-url']
-        self.https_url = config['https-url']
+        self.base_url = self.config['base-url']
+        self.https_url = self.config['https-url']
         self.url_rewriter = self.appli.vreg['components'].select_or_none('urlrewriter')
 
     def _render(self, req):
@@ -118,7 +118,7 @@
         try:
             path = req.path
             result = self.appli.handle_request(req, path)
-        except DirectResponse, ex:
+        except DirectResponse as ex:
             return ex.response
         return WSGIResponse(req.status_out, req, result)
 
--- a/zmqclient.py	Tue Mar 19 16:56:46 2013 +0100
+++ b/zmqclient.py	Wed Mar 20 17:40:25 2013 +0100
@@ -23,6 +23,7 @@
 from functools import partial
 import zmq
 
+from cubicweb.server.cwzmq import cwproto_to_zmqaddr
 
 # XXX hack to overpass old zmq limitation that force to have
 # only one context per python process
@@ -33,9 +34,9 @@
 
 class ZMQRepositoryClient(object):
     """
-    This class delegate the overall repository stuff to a remote source.
+    This class delegates the overall repository stuff to a remote source.
 
-    So calling a method of this repository will results on calling the
+    So calling a method of this repository will result on calling the
     corresponding method of the remote source repository.
 
     Any raised exception on the remote source is propagated locally.
@@ -44,8 +45,13 @@
     """
 
     def __init__(self, zmq_address):
+        """A zmq address provided here will be like
+        `zmqpickle-tcp://127.0.0.1:42000`.  W
+
+        We chop the prefix to get a real zmq address.
+        """
         self.socket = ctx.socket(zmq.REQ)
-        self.socket.connect(zmq_address)
+        self.socket.connect(cwproto_to_zmqaddr(zmq_address))
 
     def __zmqcall__(self, name, *args, **kwargs):
          self.socket.send_pyobj([name, args, kwargs])