backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 12 Oct 2012 16:05:16 +0200
changeset 8573 ae0a567dff30
parent 8570 e20057a9ceea (diff)
parent 8572 e54b3bc39011 (current diff)
child 8576 c9c72ac10db3
backport stable
dbapi.py
server/repository.py
server/sources/datafeed.py
server/sources/native.py
server/test/unittest_repository.py
sobjects/ldapparser.py
--- a/__init__.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/__init__.py	Fri Oct 12 16:05:16 2012 +0200
@@ -199,3 +199,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.tr method)
+        substitutions = {}
+    for key in errors.keys():
+        if isinstance(key, tuple):
+            errors[rname(*key)] = errors.pop(key)
+    return ValidationError(getattr(entity, 'eid', entity), errors,
+                           substitutions, i18nvalues)
--- a/__pkginfo__.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/__pkginfo__.py	Fri Oct 12 16:05:16 2012 +0200
@@ -43,7 +43,7 @@
     'logilab-common': '>= 0.58.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 Oct 02 16:44:55 2012 +0200
+++ b/_exceptions.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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 #########################################################
 
--- a/cwconfig.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/cwconfig.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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.
@@ -671,54 +671,6 @@
     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 +718,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):
@@ -1177,14 +1174,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:
--- a/cwvreg.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/cwvreg.py	Fri Oct 12 16:05:16 2012 +0200
@@ -588,7 +588,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 +598,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 +614,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')
--- a/dbapi.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/dbapi.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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.
@@ -80,9 +80,8 @@
 
 
 class ConnectionProperties(object):
-    def __init__(self, cnxtype=None, lang=None, close=True, log=False):
+    def __init__(self, cnxtype=None, close=True, log=False):
         self.cnxtype = cnxtype or 'pyro'
-        self.lang = lang
         self.log_queries = log
         self.close_on_del = close
 
@@ -283,6 +282,8 @@
         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 +293,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 +302,7 @@
             # established
             self.session = None
             self.cnx = self.user = _NeedAuthAccessMock()
+        self.set_default_language(vreg)
 
     def from_controller(self):
         return 'view'
@@ -317,7 +316,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)
@@ -330,19 +329,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 #################################################
 
@@ -603,9 +598,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):
@@ -675,11 +670,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.
--- a/debian/control	Tue Oct 02 16:44:55 2012 +0200
+++ b/debian/control	Fri Oct 12 16:05:16 2012 +0200
@@ -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.58.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 Oct 02 16:44:55 2012 +0200
+++ b/devtools/__init__.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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.
@@ -355,7 +355,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):
@@ -466,7 +466,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 +722,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/devctl.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/devtools/devctl.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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,7 +91,7 @@
         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
--- a/devtools/fake.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/devtools/fake.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/repotest.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/devtools/repotest.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/devtools/testlib.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/devtools/testlib.py	Fri Oct 12 16:05:16 2012 +0200
@@ -47,7 +47,7 @@
 from cubicweb import cwconfig, dbapi, devtools, web, server
 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
@@ -445,7 +445,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:
@@ -1050,7 +1050,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):
--- a/doc/book/en/annexes/faq.rst	Tue Oct 02 16:44:55 2012 +0200
+++ b/doc/book/en/annexes/faq.rst	Fri Oct 12 16:05:16 2012 +0200
@@ -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/devrepo/entityclasses/application-logic.rst	Tue Oct 02 16:44:55 2012 +0200
+++ b/doc/book/en/devrepo/entityclasses/application-logic.rst	Fri Oct 12 16:05:16 2012 +0200
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/doc/book/en/devrepo/entityclasses/data-as-objects.rst	Fri Oct 12 16:05:16 2012 +0200
@@ -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/repo/hooks.rst	Tue Oct 02 16:44:55 2012 +0200
+++ b/doc/book/en/devrepo/repo/hooks.rst	Fri Oct 12 16:05:16 2012 +0200
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/doc/book/en/devrepo/testing.rst	Fri Oct 12 16:05:16 2012 +0200
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/doc/book/en/devweb/index.rst	Fri Oct 12 16:05:16 2012 +0200
@@ -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	Fri Oct 12 16:05:16 2012 +0200
@@ -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/tutorials/advanced/part02_security.rst	Tue Oct 02 16:44:55 2012 +0200
+++ b/doc/book/en/tutorials/advanced/part02_security.rst	Fri Oct 12 16:05:16 2012 +0200
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/doc/book/en/tutorials/advanced/part04_ui-base.rst	Fri Oct 12 16:05:16 2012 +0200
@@ -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/entities/authobjs.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/entities/authobjs.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/entities/sources.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/entities/test/unittest_base.py	Fri Oct 12 16:05:16 2012 +0200
@@ -70,7 +70,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 +104,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 Oct 02 16:44:55 2012 +0200
+++ b/entities/test/unittest_wfobjs.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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.
@@ -20,7 +20,6 @@
 
 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):
@@ -63,7 +62,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(cm.exception.errors, {'name-subject': 'workflow already have a state of that name'})
@@ -86,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'})
@@ -126,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()
 
@@ -154,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})
@@ -173,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')),
@@ -212,7 +212,7 @@
         req = self.request()
         iworkflowable = req.entity_from_eid(self.member.eid).cw_adapt_to('IWorkflowable')
         iworkflowable.fire_transition('deactivate')
-        cnx.commit()
+        req.cu.commit()
         with self.assertRaises(ValidationError) as cm:
             iworkflowable.fire_transition('activate')
         self.assertEqual(cm.exception.errors, {'by_transition-subject': "transition may not be fired"})
@@ -516,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()
@@ -556,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',
--- a/entity.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/entity.py	Fri Oct 12 16:05:16 2012 +0200
@@ -452,26 +452,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 +474,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 +493,52 @@
                 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)
+        created.cw_attr_cache.update(attrcache)
+        cls._cw_handle_pending_relations(created.eid, pendingrels, execute)
         return created
 
     def __init__(self, req, rset=None, row=None, col=0):
@@ -535,6 +558,24 @@
     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
+        if self._cw.is_request:
+            for attr in self._cw.get_shared_data('%s.dont-cache-attrs' % self.eid,
+                                                 default=(), txdata=True, pop=True):
+                attrcache.pop(attr, None)
+                self.cw_attr_cache.pop(attr, None)
+        self.cw_attr_cache.update(attrcache)
+
+    def _cw_dont_cache_attribute(self, attr):
+        """repository side method called when some attribute have been
+        transformed by a hook, hence original value should not be cached by
+        client
+        """
+        self._cw.transaction_data.setdefault('%s.dont-cache-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
@@ -1215,54 +1256,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 +1305,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/ext/tal.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/ext/tal.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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.
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/hooks/__init__.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/bookmark.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/hooks/bookmark.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/hooks/email.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/hooks/integrity.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/hooks/metadata.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/hooks/syncschema.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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
@@ -913,7 +913,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)
@@ -983,7 +983,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 Oct 02 16:44:55 2012 +0200
+++ b/hooks/syncsession.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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,9 +18,9 @@
 """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
 
@@ -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})
+            msg = _('unknown property key %s')
+            raise validation_error(self.entity, {('pkey', 'subject'): msg}, (key,))
         except ValueError, ex:
-            qname = role_name('value', 'subject')
-            raise ValidationError(self.entity.eid,
-                                  {qname: session._(str(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:
@@ -196,8 +194,7 @@
         except UnknownProperty:
             return
         except ValueError, ex:
-            qname = role_name('value', 'subject')
-            raise ValidationError(entity.eid, {qname: session._(str(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 Oct 02 16:44:55 2012 +0200
+++ b/hooks/syncsources.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/hooks/test/unittest_hooks.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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.
@@ -67,10 +67,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):
@@ -171,6 +170,7 @@
         try:
             self.execute('INSERT CWUser X: X login "admin"')
         except ValidationError, ex:
+            ex.tr(unicode)
             self.assertIsInstance(ex.entity, int)
             self.assertEqual(ex.errors, {'login-subject': 'the value "admin" is already used, use another one'})
 
--- a/hooks/test/unittest_syncschema.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/hooks/test/unittest_syncschema.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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.
@@ -294,7 +294,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 Oct 02 16:44:55 2012 +0200
+++ b/hooks/test/unittest_syncsession.py	Fri Oct 12 16:05:16 2012 +0200
@@ -31,9 +31,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.tr(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.tr(unicode)
         self.assertEqual(cm.exception.errors, {'pkey-subject': 'unknown property key bla.bla'})
 
     def test_site_wide_cwproperty(self):
--- a/hooks/workflow.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/hooks/workflow.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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,7 +324,7 @@
             return
         entity = self._cw.entity_from_eid(self.eidfrom)
         try:
-            entity.set_attributes(modification_date=datetime.now())
+            entity.cw_set(modification_date=datetime.now())
         except RepositoryError, ex:
             # usually occurs if entity is coming from a read-only source
             # (eg ldap user)
--- a/i18n/de.po	Tue Oct 02 16:44:55 2012 +0200
+++ b/i18n/de.po	Fri Oct 12 16:05:16 2012 +0200
@@ -1001,9 +1001,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"
 
@@ -2564,6 +2561,9 @@
 msgid "foaf"
 msgstr "FOAF"
 
+msgid "focus on this selection"
+msgstr ""
+
 msgid "follow"
 msgstr "dem Link folgen"
 
@@ -4169,6 +4169,9 @@
 msgid "toggle check boxes"
 msgstr "Kontrollkästchen umkehren"
 
+msgid "toggle filter"
+msgstr "filter verbergen/zeigen"
+
 msgid "tr_count"
 msgstr ""
 
@@ -4631,9 +4634,3 @@
 #~ msgstr ""
 #~ "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"
--- a/i18n/en.po	Tue Oct 02 16:44:55 2012 +0200
+++ b/i18n/en.po	Fri Oct 12 16:05:16 2012 +0200
@@ -963,9 +963,6 @@
 msgid "abstract base class for transitions"
 msgstr ""
 
-msgid "action menu"
-msgstr ""
-
 msgid "action(s) on this selection"
 msgstr ""
 
@@ -2511,6 +2508,9 @@
 msgid "foaf"
 msgstr ""
 
+msgid "focus on this selection"
+msgstr ""
+
 msgid "follow"
 msgstr ""
 
@@ -4069,6 +4069,9 @@
 msgid "toggle check boxes"
 msgstr ""
 
+msgid "toggle filter"
+msgstr ""
+
 msgid "tr_count"
 msgstr "transition number"
 
--- a/i18n/es.po	Tue Oct 02 16:44:55 2012 +0200
+++ b/i18n/es.po	Fri Oct 12 16:05:16 2012 +0200
@@ -1011,9 +1011,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"
 
@@ -2606,6 +2603,9 @@
 msgid "foaf"
 msgstr "Amigo de un Amigo, FOAF"
 
+msgid "focus on this selection"
+msgstr ""
+
 msgid "follow"
 msgstr "Seguir la liga"
 
@@ -4219,6 +4219,9 @@
 msgid "toggle check boxes"
 msgstr "Cambiar valor"
 
+msgid "toggle filter"
+msgstr "esconder/mostrar el filtro"
+
 msgid "tr_count"
 msgstr "n° de transición"
 
@@ -4682,18 +4685,3 @@
 #~ msgstr ""
 #~ "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."
--- a/i18n/fr.po	Tue Oct 02 16:44:55 2012 +0200
+++ b/i18n/fr.po	Fri Oct 12 16:05:16 2012 +0200
@@ -1011,9 +1011,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"
 
@@ -1253,7 +1250,7 @@
 msgstr "anonyme"
 
 msgid "anyrsetview"
-msgstr "vues \"tous les rset\""
+msgstr "vues pour tout rset"
 
 msgid "april"
 msgstr "avril"
@@ -2614,6 +2611,9 @@
 msgid "foaf"
 msgstr "foaf"
 
+msgid "focus on this selection"
+msgstr "afficher cette sélection"
+
 msgid "follow"
 msgstr "suivre le lien"
 
@@ -4228,7 +4228,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"
@@ -4692,6 +4695,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/misc/migration/3.10.0_Any.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/misc/migration/3.10.0_Any.py	Fri Oct 12 16:05:16 2012 +0200
@@ -34,5 +34,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.11.0_Any.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/misc/migration/3.11.0_Any.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/misc/migration/3.14.0_Any.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/misc/migration/3.15.0_Any.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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()
--- a/misc/migration/postcreate.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/misc/migration/postcreate.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/misc/scripts/chpasswd.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/misc/scripts/ldapuser2ldapfeed.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/predicates.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/predicates.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/req.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/req.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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 property_value(self, key):
         """return value of the property with the given key, giving priority to
         user specific value if any, else using site value
--- a/schema.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/schema.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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:
                 # 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:
                 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 Oct 02 16:44:55 2012 +0200
+++ b/server/__init__.py	Fri Oct 12 16:05:16 2012 +0200
@@ -224,10 +224,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)
@@ -251,15 +247,13 @@
 
 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 Oct 02 16:44:55 2012 +0200
+++ b/server/checkintegrity.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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.
@@ -32,7 +32,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:
@@ -394,7 +393,7 @@
     # 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)
--- a/server/edition.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/server/edition.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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.
@@ -61,6 +61,8 @@
         # attributes, else we may accidentaly skip a desired security check
         if attr not in self:
             self.skip_security.add(attr)
+        # mark attribute as needing purge by the client
+        self.entity._cw_dont_cache_attribute(attr)
         self.edited_attribute(attr, value)
 
     def __delitem__(self, attr):
@@ -141,8 +143,7 @@
                          for rtype in self]
         try:
             entity.e_schema.check(dict_protocol_catcher(entity),
-                                  creation=creation, _=entity._cw._,
-                                  relations=relations)
+                                  creation=creation, relations=relations)
         except ValidationError, ex:
             ex.entity = self.entity
             raise
--- a/server/hook.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/server/hook.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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
@@ -268,7 +264,6 @@
 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,11 +321,11 @@
             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):
+                    with session.security_enabled(write=False):
                         for hook in hooks:
                             hook()
 
--- a/server/migractions.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/server/migractions.py	Fri Oct 12 16:05:16 2012 +0200
@@ -1321,7 +1321,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 ###########################################
 
--- a/server/pool.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/server/pool.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/server/querier.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/server/querier.py	Fri Oct 12 16:05:16 2012 +0200
@@ -26,21 +26,26 @@
 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 +261,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)
@@ -751,14 +756,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 +785,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 Oct 02 16:44:55 2012 +0200
+++ b/server/repository.py	Fri Oct 12 16:05:16 2012 +0200
@@ -56,8 +56,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 +108,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})
@@ -135,7 +134,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
@@ -218,8 +217,8 @@
             # 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
@@ -227,16 +226,10 @@
         elif config.creating:
             # repository creation
             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')
+            # trigger vreg initialisation of entity classes
+            config.cubicweb_appobject_path = set(('hooks', 'entities'))
+            config.cube_appobject_path = set(('hooks', 'entities'))
+            self.set_schema(config.load_schema())
         elif config.read_instance_schema:
             # normal start: load the instance schema from the database
             self.fill_schema()
@@ -767,10 +760,10 @@
         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
@@ -921,22 +914,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`"""
@@ -1239,7 +1219,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 +1261,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
@@ -1567,7 +1547,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 +1558,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/serverctl.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/server/serverctl.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/server/session.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/server/session.py	Fri Oct 12 16:05:16 2012 +0200
@@ -30,21 +30,16 @@
 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
 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 +50,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 +108,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,7 +222,11 @@
 
       :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):
@@ -264,7 +249,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()
@@ -462,28 +447,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
@@ -507,7 +470,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):
@@ -1152,71 +1115,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)')
@@ -1315,6 +1213,9 @@
             return 'en'
         return None
 
+    def prefered_language(self, language=None):
+        # mock CWUser.prefered_language, mainly for testing purpose
+        return self.property_value('ui.language')
 
 from logging import getLogger
 from cubicweb import set_log_methods
--- a/server/sources/__init__.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/server/sources/__init__.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/server/sources/datafeed.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/server/sources/datafeed.py	Fri Oct 12 16:05:16 2012 +0200
@@ -406,7 +406,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)
 
 
--- a/server/sources/native.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/server/sources/native.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/server/sources/storages.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/server/sources/storages.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/server/ssplanner.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/server/ssplanner.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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.
@@ -27,7 +27,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'))
@@ -87,7 +86,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 Oct 02 16:44:55 2012 +0200
+++ b/server/test/unittest_checkintegrity.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/server/test/unittest_datafeed.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/server/test/unittest_datafeed.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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.
--- a/server/test/unittest_hook.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/server/test/unittest_hook.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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.
--- a/server/test/unittest_msplanner.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/server/test/unittest_msplanner.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/server/test/unittest_multisources.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/server/test/unittest_multisources.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/server/test/unittest_querier.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/server/test/unittest_querier.py	Fri Oct 12 16:05:16 2012 +0200
@@ -29,10 +29,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 +87,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 +266,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.transaction
+        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 Oct 02 16:44:55 2012 +0200
+++ b/server/test/unittest_repository.py	Fri Oct 12 16:05:16 2012 +0200
@@ -522,7 +522,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 +534,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 +664,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 +674,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 +790,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 +816,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 +836,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 +917,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 +928,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_session.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/server/test/unittest_session.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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,33 +17,7 @@
 # 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):
@@ -61,7 +35,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 +47,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 +62,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 Oct 02 16:44:55 2012 +0200
+++ b/server/test/unittest_storage.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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.
@@ -99,7 +99,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()
@@ -204,7 +204,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')
-        # 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 +218,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 +240,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 +265,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 Oct 02 16:44:55 2012 +0200
+++ b/server/test/unittest_undo.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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.
@@ -203,7 +203,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 +217,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 +228,7 @@
             "%s doesn't exist anymore." % g.eid])
         with self.assertRaises(ValidationError) as cm:
             self.commit()
+        cm.exception.tr(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 +266,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 +279,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 +305,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 +320,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 +340,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 +355,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 +370,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 +386,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 +402,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 +413,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/sobjects/ldapparser.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/sobjects/ldapparser.py	Fri Oct 12 16:05:16 2012 +0200
@@ -84,7 +84,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):
@@ -118,7 +118,7 @@
         if entity.__regid__ == 'EmailAddress':
             return
         groups = [self._get_group(n) for n in self.source.user_default_groups]
-        entity.set_relations(in_group=groups)
+        entity.cw_set(in_group=groups)
         self._process_email(entity, sourceparams)
 
     def is_deleted(self, extid, etype, eid):
@@ -145,9 +145,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/test/unittest_cwconfig.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/test/unittest_cwconfig.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/test/unittest_dbapi.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/test/unittest_entity.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/test/unittest_entity.py	Fri Oct 12 16:05:16 2012 +0200
@@ -701,23 +701,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 Oct 02 16:44:55 2012 +0200
+++ b/test/unittest_migration.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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.
@@ -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_schema.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/test/unittest_schema.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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.
@@ -350,8 +350,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/web/application.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/web/application.py	Fri Oct 12 16:05:16 2012 +0200
@@ -461,7 +461,6 @@
             result = self.notfound_content(req)
             req.status_out = ex.status
         except ValidationError, ex:
-            req.status_out = httplib.CONFLICT
             result = self.validation_error_handler(req, ex)
         except RemoteCallFailed, ex:
             result = self.ajax_error_handler(req, ex)
@@ -480,7 +479,7 @@
         except (AuthenticationError, LogOut):
             # the rollback is handled in the finally
             raise
-        ### Last defence line
+        ### Last defense line
         except BaseException, ex:
             result = self.error_handler(req, ex, tb=True)
         finally:
@@ -511,7 +510,7 @@
         return ''
 
     def validation_error_handler(self, req, ex):
-        ex.errors = dict((k, v) for k, v in ex.errors.items())
+        ex.tr(req._) # translate messages using ui language
         if '__errorurl' in req.form:
             forminfo = {'error': ex,
                         'values': req.form,
@@ -526,6 +525,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/data/cubicweb.ajax.js	Tue Oct 02 16:44:55 2012 +0200
+++ b/web/data/cubicweb.ajax.js	Fri Oct 12 16:05:16 2012 +0200
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/web/data/cubicweb.css	Fri Oct 12 16:05:16 2012 +0200
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/web/data/cubicweb.facets.js	Fri Oct 12 16:05:16 2012 +0200
@@ -68,6 +68,14 @@
             var bkUrl = $bkLink.attr('cubicweb:target') + '&path=' + encodeURIComponent(bkPath);
             $bkLink.attr('href', bkUrl);
         }
+        var $focusLink = jQuery('#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
--- a/web/data/cubicweb.old.css	Tue Oct 02 16:44:55 2012 +0200
+++ b/web/data/cubicweb.old.css	Fri Oct 12 16:05:16 2012 +0200
@@ -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/httpcache.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/web/httpcache.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/request.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/web/request.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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
--- a/web/test/data/views.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/web/test/data/views.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/web/test/unittest_application.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/test/unittest_magicsearch.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/web/test/unittest_magicsearch.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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 Oct 02 16:44:55 2012 +0200
+++ b/web/test/unittest_reledit.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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_urlrewrite.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/web/test/unittest_urlrewrite.py	Fri Oct 12 16:05:16 2012 +0200
@@ -105,9 +105,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_basecontrollers.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/web/test/unittest_views_basecontrollers.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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,7 @@
                     }
         with self.assertRaises(ValidationError) as cm:
             self.ctrl_publish(req)
+        cm.exception.tr(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 +250,7 @@
                 }
         with self.assertRaises(ValidationError) as cm:
             self.ctrl_publish(req)
+        cm.exception.tr(unicode)
         self.assertEqual(cm.exception.errors, {'amount-subject': 'value -10 must be >= 0'})
         req = self.request(rollbackfirst=True)
         req.form = {'eid': ['X'],
@@ -259,6 +261,7 @@
                     }
         with self.assertRaises(ValidationError) as cm:
             self.ctrl_publish(req)
+        cm.exception.tr(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 Oct 02 16:44:55 2012 +0200
+++ b/web/test/unittest_views_basetemplates.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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_searchrestriction.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/web/test/unittest_views_searchrestriction.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/ajaxcontroller.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/web/views/ajaxcontroller.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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
--- a/web/views/authentication.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/web/views/authentication.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/views/basecomponents.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/web/views/basecomponents.py	Fri Oct 12 16:05:16 2012 +0200
@@ -59,6 +59,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 Oct 02 16:44:55 2012 +0200
+++ b/web/views/basecontrollers.py	Fri Oct 12 16:05:16 2012 +0200
@@ -190,6 +190,7 @@
 
 def _validation_error(req, ex):
     req.cnx.rollback()
+    ex.tr(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)
--- a/web/views/editcontroller.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/web/views/editcontroller.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/facets.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/web/views/facets.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/magicsearch.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/web/views/magicsearch.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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')
 
@@ -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, 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/sessions.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/web/views/sessions.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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/tableview.py	Tue Oct 02 16:44:55 2012 +0200
+++ b/web/views/tableview.py	Fri Oct 12 16:05:16 2012 +0200
@@ -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):