[merge] backport stable
authorAurelien Campeas <aurelien.campeas@logilab.fr>
Wed, 09 Jan 2013 16:06:20 +0100
changeset 8645 310040c668c0
parent 8644 97202ea671e4 (diff)
parent 8641 459d0c48dfaf (current diff)
child 8646 82c7c2e0f69f
[merge] backport stable
__pkginfo__.py
devtools/testlib.py
misc/scripts/ldapuser2ldapfeed.py
server/migractions.py
sobjects/ldapparser.py
web/request.py
web/test/unittest_viewselector.py
--- a/__init__.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/__init__.py	Wed Jan 09 16:06:20 2013 +0100
@@ -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.translate 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	Wed Jan 09 16:04:26 2013 +0100
+++ b/__pkginfo__.py	Wed Jan 09 16:06:20 2013 +0100
@@ -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	Wed Jan 09 16:04:26 2013 +0100
+++ b/_exceptions.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -19,7 +19,7 @@
 
 __docformat__ = "restructuredtext en"
 
-from yams import ValidationError
+from yams import ValidationError as ValidationError
 
 # abstract exceptions #########################################################
 
--- a/cwconfig.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/cwconfig.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -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	Wed Jan 09 16:04:26 2013 +0100
+++ b/cwvreg.py	Wed Jan 09 16:06:20 2013 +0100
@@ -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/dataimport.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/dataimport.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -66,13 +66,18 @@
 """
 __docformat__ = "restructuredtext en"
 
+import csv
 import sys
-import csv
+import threading
 import traceback
+import cPickle
 import os.path as osp
-from StringIO import StringIO
+from collections import defaultdict
+from contextlib import contextmanager
 from copy import copy
-from datetime import datetime
+from datetime import date, datetime
+from time import asctime
+from StringIO import StringIO
 
 from logilab.common import shellutils, attrdict
 from logilab.common.date import strptime
@@ -80,9 +85,11 @@
 from logilab.common.deprecation import deprecated
 
 from cubicweb import QueryError
+from cubicweb.utils import make_uid
 from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES
+from cubicweb.server.edition import EditedEntity
+from cubicweb.server.sqlutils import SQL_PREFIX
 from cubicweb.server.utils import eschema_eid
-from cubicweb.server.edition import EditedEntity
 
 
 def count_lines(stream_or_filename):
@@ -117,15 +124,33 @@
     print ' %s rows imported' % rowcount
 
 def ucsvreader(stream, encoding='utf-8', separator=',', quote='"',
-               skipfirst=False):
+               skipfirst=False, ignore_errors=False):
     """A csv reader that accepts files with any encoding and outputs unicode
     strings
     """
     it = iter(csv.reader(stream, delimiter=separator, quotechar=quote))
-    if skipfirst:
-        it.next()
-    for row in it:
-        yield [item.decode(encoding) for item in row]
+    if not ignore_errors:
+        if skipfirst:
+            it.next()
+        for row in it:
+            yield [item.decode(encoding) for item in row]
+    else:
+        # Skip first line
+        try:
+            row = it.next()
+        except csv.Error:
+            pass
+        # Safe version, that can cope with error in CSV file
+        while True:
+            try:
+                row = it.next()
+            # End of CSV, break
+            except StopIteration:
+                break
+            # Error in CSV, ignore line and continue
+            except csv.Error:
+                continue
+            yield [item.decode(encoding) for item in row]
 
 def callfunc_every(func, number, iterable):
     """yield items of `iterable` one by one and call function `func`
@@ -299,6 +324,149 @@
             if k is not None and len(v) > 1]
 
 
+# sql generator utility functions #############################################
+
+
+def _import_statements(sql_connect, statements, nb_threads=3,
+                       dump_output_dir=None,
+                       support_copy_from=True, encoding='utf-8'):
+    """
+    Import a bunch of sql statements, using different threads.
+    """
+    try:
+        chunksize = (len(statements) / nb_threads) + 1
+        threads = []
+        for i in xrange(nb_threads):
+            chunks = statements[i*chunksize:(i+1)*chunksize]
+            thread = threading.Thread(target=_execmany_thread,
+                                      args=(sql_connect, chunks,
+                                            dump_output_dir,
+                                            support_copy_from,
+                                            encoding))
+            thread.start()
+            threads.append(thread)
+        for t in threads:
+            t.join()
+    except Exception:
+        print 'Error in import statements'
+
+def _execmany_thread_not_copy_from(cu, statement, data, table=None,
+                                   columns=None, encoding='utf-8'):
+    """ Execute thread without copy from
+    """
+    cu.executemany(statement, data)
+
+def _execmany_thread_copy_from(cu, statement, data, table,
+                               columns, encoding='utf-8'):
+    """ Execute thread with copy from
+    """
+    buf = _create_copyfrom_buffer(data, columns, encoding)
+    if buf is None:
+        _execmany_thread_not_copy_from(cu, statement, data)
+    else:
+        if columns is None:
+            cu.copy_from(buf, table, null='NULL')
+        else:
+            cu.copy_from(buf, table, null='NULL', columns=columns)
+
+def _execmany_thread(sql_connect, statements, dump_output_dir=None,
+                     support_copy_from=True, encoding='utf-8'):
+    """
+    Execute sql statement. If 'INSERT INTO', try to use 'COPY FROM' command,
+    or fallback to execute_many.
+    """
+    if support_copy_from:
+        execmany_func = _execmany_thread_copy_from
+    else:
+        execmany_func = _execmany_thread_not_copy_from
+    cnx = sql_connect()
+    cu = cnx.cursor()
+    try:
+        for statement, data in statements:
+            table = None
+            columns = None
+            try:
+                if not statement.startswith('INSERT INTO'):
+                    cu.executemany(statement, data)
+                    continue
+                table = statement.split()[2]
+                if isinstance(data[0], (tuple, list)):
+                    columns = None
+                else:
+                    columns = data[0].keys()
+                execmany_func(cu, statement, data, table, columns, encoding)
+            except Exception:
+                print 'unable to copy data into table %s', table
+                # Error in import statement, save data in dump_output_dir
+                if dump_output_dir is not None:
+                    pdata = {'data': data, 'statement': statement,
+                             'time': asctime(), 'columns': columns}
+                    filename = make_uid()
+                    try:
+                        with open(osp.join(dump_output_dir,
+                                           '%s.pickle' % filename), 'w') as fobj:
+                            fobj.write(cPickle.dumps(pdata))
+                    except IOError:
+                        print 'ERROR while pickling in', dump_output_dir, filename+'.pickle'
+                        pass
+                cnx.rollback()
+                raise
+    finally:
+        cnx.commit()
+        cu.close()
+
+def _create_copyfrom_buffer(data, columns, encoding='utf-8', replace_sep=None):
+    """
+    Create a StringIO buffer for 'COPY FROM' command.
+    Deals with Unicode, Int, Float, Date...
+    """
+    # Create a list rather than directly create a StringIO
+    # to correctly write lines separated by '\n' in a single step
+    rows = []
+    if isinstance(data[0], (tuple, list)):
+        columns = range(len(data[0]))
+    for row in data:
+        # Iterate over the different columns and the different values
+        # and try to convert them to a correct datatype.
+        # If an error is raised, do not continue.
+        formatted_row = []
+        for col in columns:
+            value = row[col]
+            if value is None:
+                value = 'NULL'
+            elif isinstance(value, (long, int, float)):
+                value = str(value)
+            elif isinstance(value, (str, unicode)):
+                # Remove separators used in string formatting
+                for _char in (u'\t', u'\r', u'\n'):
+                    if _char in value:
+                        # If a replace_sep is given, replace
+                        # the separator instead of returning None
+                        # (and thus avoid empty buffer)
+                        if replace_sep:
+                            value = value.replace(_char, replace_sep)
+                        else:
+                            return
+                value = value.replace('\\', r'\\')
+                if value is None:
+                    return
+                if isinstance(value, unicode):
+                    value = value.encode(encoding)
+            elif isinstance(value, (date, datetime)):
+                # Do not use strftime, as it yields issue
+                # with date < 1900
+                value = '%04d-%02d-%02d' % (value.year,
+                                            value.month,
+                                            value.day)
+            else:
+                return None
+            # We push the value to the new formatted row
+            # if the value is not None and could be converted to a string.
+            formatted_row.append(value)
+        rows.append('\t'.join(formatted_row))
+    return StringIO('\n'.join(rows))
+
+
 # object stores #################################################################
 
 class ObjectStore(object):
@@ -753,3 +921,261 @@
         return self.session.user.eid
     def gen_owned_by(self, entity):
         return self.session.user.eid
+
+
+###########################################################################
+## SQL object store #######################################################
+###########################################################################
+class SQLGenObjectStore(NoHookRQLObjectStore):
+    """Controller of the data import process. This version is based
+    on direct insertions throught SQL command (COPY FROM or execute many).
+
+    >>> store = SQLGenObjectStore(session)
+    >>> store.create_entity('Person', ...)
+    >>> store.flush()
+    """
+
+    def __init__(self, session, dump_output_dir=None, nb_threads_statement=3):
+        """
+        Initialize a SQLGenObjectStore.
+
+        Parameters:
+
+          - session: session on the cubicweb instance
+          - dump_output_dir: a directory to dump failed statements
+            for easier recovery. Default is None (no dump).
+          - nb_threads_statement: number of threads used
+            for SQL insertion (default is 3).
+        """
+        super(SQLGenObjectStore, self).__init__(session)
+        ### hijack default source
+        self.source = SQLGenSourceWrapper(
+            self.source, session.vreg.schema,
+            dump_output_dir=dump_output_dir,
+            nb_threads_statement=nb_threads_statement)
+        ### XXX This is done in super().__init__(), but should be
+        ### redone here to link to the correct source
+        self.add_relation = self.source.add_relation
+        self.indexes_etypes = {}
+
+    def flush(self):
+        """Flush data to the database"""
+        self.source.flush()
+
+    def relate(self, subj_eid, rtype, obj_eid, subjtype=None):
+        if subj_eid is None or obj_eid is None:
+            return
+        # XXX Could subjtype be inferred ?
+        self.source.add_relation(self.session, subj_eid, rtype, obj_eid,
+                                 self.rschema(rtype).inlined, subjtype)
+
+    def drop_indexes(self, etype):
+        """Drop indexes for a given entity type"""
+        if etype not in self.indexes_etypes:
+            cu = self.session.cnxset['system']
+            def index_to_attr(index):
+                """turn an index name to (database) attribute name"""
+                return index.replace(etype.lower(), '').replace('idx', '').strip('_')
+            indices = [(index, index_to_attr(index))
+                       for index in self.source.dbhelper.list_indices(cu, etype)
+                       # Do not consider 'cw_etype_pkey' index
+                       if not index.endswith('key')]
+            self.indexes_etypes[etype] = indices
+        for index, attr in self.indexes_etypes[etype]:
+            self.session.system_sql('DROP INDEX %s' % index)
+
+    def create_indexes(self, etype):
+        """Recreate indexes for a given entity type"""
+        for index, attr in self.indexes_etypes.get(etype, []):
+            sql = 'CREATE INDEX %s ON cw_%s(%s)' % (index, etype, attr)
+            self.session.system_sql(sql)
+
+
+###########################################################################
+## SQL Source #############################################################
+###########################################################################
+
+class SQLGenSourceWrapper(object):
+
+    def __init__(self, system_source, schema,
+                 dump_output_dir=None, nb_threads_statement=3):
+        self.system_source = system_source
+        self._sql = threading.local()
+        # Explicitely backport attributes from system source
+        self._storage_handler = self.system_source._storage_handler
+        self.preprocess_entity = self.system_source.preprocess_entity
+        self.sqlgen = self.system_source.sqlgen
+        self.copy_based_source = self.system_source.copy_based_source
+        self.uri = self.system_source.uri
+        self.eid = self.system_source.eid
+        # Directory to write temporary files
+        self.dump_output_dir = dump_output_dir
+        # Allow to execute code with SQLite backend that does
+        # not support (yet...) copy_from
+        # XXX Should be dealt with in logilab.database
+        spcfrom = system_source.dbhelper.dbapi_module.support_copy_from
+        self.support_copy_from = spcfrom
+        self.dbencoding = system_source.dbhelper.dbencoding
+        self.nb_threads_statement = nb_threads_statement
+        # initialize thread-local data for main thread
+        self.init_thread_locals()
+        self._inlined_rtypes_cache = {}
+        self._fill_inlined_rtypes_cache(schema)
+        self.schema = schema
+        self.do_fti = False
+
+    def _fill_inlined_rtypes_cache(self, schema):
+        cache = self._inlined_rtypes_cache
+        for eschema in schema.entities():
+            for rschema in eschema.ordered_relations():
+                if rschema.inlined:
+                    cache[eschema.type] = SQL_PREFIX + rschema.type
+
+    def init_thread_locals(self):
+        """initializes thread-local data"""
+        self._sql.entities = defaultdict(list)
+        self._sql.relations = {}
+        self._sql.inlined_relations = {}
+        # keep track, for each eid of the corresponding data dict
+        self._sql.eid_insertdicts = {}
+
+    def flush(self):
+        print 'starting flush'
+        _entities_sql = self._sql.entities
+        _relations_sql = self._sql.relations
+        _inlined_relations_sql = self._sql.inlined_relations
+        _insertdicts = self._sql.eid_insertdicts
+        try:
+            # try, for each inlined_relation, to find if we're also creating
+            # the host entity (i.e. the subject of the relation).
+            # In that case, simply update the insert dict and remove
+            # the need to make the
+            # UPDATE statement
+            for statement, datalist in _inlined_relations_sql.iteritems():
+                new_datalist = []
+                # for a given inlined relation,
+                # browse each couple to be inserted
+                for data in datalist:
+                    keys = data.keys()
+                    # For inlined relations, it exists only two case:
+                    # (rtype, cw_eid) or (cw_eid, rtype)
+                    if keys[0] == 'cw_eid':
+                        rtype = keys[1]
+                    else:
+                        rtype = keys[0]
+                    updated_eid = data['cw_eid']
+                    if updated_eid in _insertdicts:
+                        _insertdicts[updated_eid][rtype] = data[rtype]
+                    else:
+                        # could not find corresponding insert dict, keep the
+                        # UPDATE query
+                        new_datalist.append(data)
+                _inlined_relations_sql[statement] = new_datalist
+            _import_statements(self.system_source.get_connection,
+                               _entities_sql.items()
+                               + _relations_sql.items()
+                               + _inlined_relations_sql.items(),
+                               dump_output_dir=self.dump_output_dir,
+                               nb_threads=self.nb_threads_statement,
+                               support_copy_from=self.support_copy_from,
+                               encoding=self.dbencoding)
+        except:
+            print 'failed to flush'
+        finally:
+            _entities_sql.clear()
+            _relations_sql.clear()
+            _insertdicts.clear()
+            _inlined_relations_sql.clear()
+            print 'flush done'
+
+    def add_relation(self, session, subject, rtype, object,
+                     inlined=False, subjtype=None):
+        if inlined:
+            _sql = self._sql.inlined_relations
+            data = {'cw_eid': subject, SQL_PREFIX + rtype: object}
+            if subjtype is None:
+                # Try to infer it
+                targets = [t.type for t in
+                           self.schema.rschema(rtype).targets()]
+                if len(targets) == 1:
+                    subjtype = targets[0]
+                else:
+                    raise ValueError('You should give the subject etype for '
+                                     'inlined relation %s'
+                                     ', as it cannot be inferred' % rtype)
+            statement = self.sqlgen.update(SQL_PREFIX + subjtype,
+                                           data, ['cw_eid'])
+        else:
+            _sql = self._sql.relations
+            data = {'eid_from': subject, 'eid_to': object}
+            statement = self.sqlgen.insert('%s_relation' % rtype, data)
+        if statement in _sql:
+            _sql[statement].append(data)
+        else:
+            _sql[statement] = [data]
+
+    def add_entity(self, session, entity):
+        with self._storage_handler(entity, 'added'):
+            attrs = self.preprocess_entity(entity)
+            rtypes = self._inlined_rtypes_cache.get(entity.__regid__, ())
+            if isinstance(rtypes, str):
+                rtypes = (rtypes,)
+            for rtype in rtypes:
+                if rtype not in attrs:
+                    attrs[rtype] = None
+            sql = self.sqlgen.insert(SQL_PREFIX + entity.__regid__, attrs)
+            self._sql.eid_insertdicts[entity.eid] = attrs
+            self._append_to_entities(sql, attrs)
+
+    def _append_to_entities(self, sql, attrs):
+        self._sql.entities[sql].append(attrs)
+
+    def _handle_insert_entity_sql(self, session, sql, attrs):
+        # We have to overwrite the source given in parameters
+        # as here, we directly use the system source
+        attrs['source'] = 'system'
+        attrs['asource'] = self.system_source.uri
+        self._append_to_entities(sql, attrs)
+
+    def _handle_is_relation_sql(self, session, sql, attrs):
+        self._append_to_entities(sql, attrs)
+
+    def _handle_is_instance_of_sql(self, session, sql, attrs):
+        self._append_to_entities(sql, attrs)
+
+    def _handle_source_relation_sql(self, session, sql, attrs):
+        self._append_to_entities(sql, attrs)
+
+    # XXX add_info is similar to the one in NativeSQLSource. It is rewritten
+    # here to correctly used the _handle_xxx of the SQLGenSourceWrapper. This
+    # part should be rewritten in a more clearly way.
+    def add_info(self, session, entity, source, extid, complete):
+        """add type and source info for an eid into the system table"""
+        # begin by inserting eid/type/source/extid into the entities table
+        if extid is not None:
+            assert isinstance(extid, str)
+            extid = b64encode(extid)
+        uri = 'system' if source.copy_based_source else source.uri
+        attrs = {'type': entity.__regid__, 'eid': entity.eid, 'extid': extid,
+                 'source': uri, 'asource': source.uri, 'mtime': datetime.utcnow()}
+        self._handle_insert_entity_sql(session, self.sqlgen.insert('entities', attrs), attrs)
+        # insert core relations: is, is_instance_of and cw_source
+        try:
+            self._handle_is_relation_sql(session, 'INSERT INTO is_relation(eid_from,eid_to) VALUES (%s,%s)',
+                                         (entity.eid, eschema_eid(session, entity.e_schema)))
+        except IndexError:
+            # during schema serialization, skip
+            pass
+        else:
+            for eschema in entity.e_schema.ancestors() + [entity.e_schema]:
+                self._handle_is_relation_sql(session,
+                                             'INSERT INTO is_instance_of_relation(eid_from,eid_to) VALUES (%s,%s)',
+                                             (entity.eid, eschema_eid(session, eschema)))
+        if 'CWSource' in self.schema and source.eid is not None: # else, cw < 3.10
+            self._handle_is_relation_sql(session, 'INSERT INTO cw_source_relation(eid_from,eid_to) VALUES (%s,%s)',
+                                         (entity.eid, source.eid))
+        # now we can update the full text index
+        if self.do_fti and self.need_fti_indexation(entity.__regid__):
+            if complete:
+                entity.complete(entity.e_schema.indexable_attributes())
+            self.index_entity(session, entity=entity)
--- a/dbapi.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/dbapi.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -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'
@@ -320,7 +319,7 @@
             self.cnx = session.cnx
             self.execute = session.cnx.cursor(self).execute
             if user is None:
-                user = self.cnx.user(self, {'lang': self.lang})
+                user = self.cnx.user(self)
         if user is not None:
             self.user = user
             self.set_entity_cache(user)
@@ -333,19 +332,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 #################################################
 
@@ -606,9 +601,9 @@
         # then init cubes
         config.init_cubes(cubes)
         # then load appobjects into the registry
-        vpath = config.build_vregistry_path(reversed(config.cubes_path()),
-                                            evobjpath=esubpath,
-                                            tvobjpath=subpath)
+        vpath = config.build_appobjects_path(reversed(config.cubes_path()),
+                                             evobjpath=esubpath,
+                                             tvobjpath=subpath)
         self.vreg.register_objects(vpath)
 
     def use_web_compatible_requests(self, baseurl, sitetitle=None):
@@ -678,11 +673,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	Wed Jan 09 16:04:26 2013 +0100
+++ b/debian/control	Wed Jan 09 16:06:20 2013 +0100
@@ -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	Wed Jan 09 16:04:26 2013 +0100
+++ b/devtools/__init__.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -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	Wed Jan 09 16:04:26 2013 +0100
+++ b/devtools/devctl.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -91,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
@@ -302,10 +302,11 @@
         from logilab.common.fileutils import ensure_fs_mode
         from logilab.common.shellutils import globfind, find, rm
         from logilab.common.modutils import get_module_files
-        from cubicweb.i18n import extract_from_tal, execute
+        from cubicweb.i18n import extract_from_tal, execute2
         tempdir = tempfile.mkdtemp(prefix='cw-')
         cwi18ndir = WebConfiguration.i18n_lib_dir()
-        print '-> extract schema messages.'
+        print '-> extract messages:',
+        print 'schema',
         schemapot = osp.join(tempdir, 'schema.pot')
         potfiles = [schemapot]
         potfiles.append(schemapot)
@@ -314,7 +315,7 @@
         schemapotstream = file(schemapot, 'w')
         generate_schema_pot(schemapotstream.write, cubedir=None)
         schemapotstream.close()
-        print '-> extract TAL messages.'
+        print 'TAL',
         tali18nfile = osp.join(tempdir, 'tali18n.py')
         extract_from_tal(find(osp.join(BASEDIR, 'web'), ('.py', '.pt')),
                          tali18nfile)
@@ -329,26 +330,29 @@
                                 ('tal', [tali18nfile], None),
                                 ('js', jsfiles, 'java'),
                                 ]:
-            cmd = 'xgettext --no-location --omit-header -k_ -o %s %s'
+            potfile = osp.join(tempdir, '%s.pot' % id)
+            cmd = ['xgettext', '--no-location', '--omit-header', '-k_']
             if lang is not None:
-                cmd += ' -L %s' % lang
-            potfile = osp.join(tempdir, '%s.pot' % id)
-            execute(cmd % (potfile, ' '.join('"%s"' % f for f in files)))
+                cmd.extend(['-L', lang])
+            cmd.extend(['-o', potfile])
+            cmd.extend(files)
+            execute2(cmd)
             if osp.exists(potfile):
                 potfiles.append(potfile)
             else:
                 print '-> WARNING: %s file was not generated' % potfile
         print '-> merging %i .pot files' % len(potfiles)
         cubicwebpot = osp.join(tempdir, 'cubicweb.pot')
-        execute('msgcat -o %s %s'
-                % (cubicwebpot, ' '.join('"%s"' % f for f in potfiles)))
+        cmd = ['msgcat', '-o', cubicwebpot] + potfiles
+        execute2(cmd)
         print '-> merging main pot file with existing translations.'
         chdir(cwi18ndir)
         toedit = []
         for lang in CubicWebNoAppConfiguration.cw_languages():
             target = '%s.po' % lang
-            execute('msgmerge -N --sort-output -o "%snew" "%s" "%s"'
-                    % (target, target, cubicwebpot))
+            cmd = ['msgmerge', '-N', '--sort-output', '-o',
+                   target+'new', target, cubicwebpot]
+            execute2(cmd)
             ensure_fs_mode(target)
             shutil.move('%snew' % target, target)
             toedit.append(osp.abspath(target))
@@ -382,16 +386,21 @@
 
 
 def update_cubes_catalogs(cubes):
+    from subprocess import CalledProcessError
     for cubedir in cubes:
         if not osp.isdir(cubedir):
             print '-> ignoring %s that is not a directory.' % cubedir
             continue
         try:
             toedit = update_cube_catalogs(cubedir)
+        except CalledProcessError, exc:
+            print '\n*** error while updating catalogs for cube', cubedir
+            print 'cmd:\n%s' % exc.cmd
+            print 'stdout:\n%s\nstderr:\n%s' % exc.data
         except Exception:
             import traceback
             traceback.print_exc()
-            print '-> error while updating catalogs for cube', cubedir
+            print '*** error while updating catalogs for cube', cubedir
             return False
         else:
             # instructions pour la suite
@@ -408,7 +417,7 @@
     import tempfile
     from logilab.common.fileutils import ensure_fs_mode
     from logilab.common.shellutils import find, rm
-    from cubicweb.i18n import extract_from_tal, execute
+    from cubicweb.i18n import extract_from_tal, execute2
     cube = osp.basename(osp.normpath(cubedir))
     tempdir = tempfile.mkdtemp()
     print underline_title('Updating i18n catalogs for cube %s' % cube)
@@ -421,7 +430,8 @@
         potfiles = [osp.join('i18n', 'static-messages.pot')]
     else:
         potfiles = []
-    print '-> extract schema messages'
+    print '-> extracting messages:',
+    print 'schema',
     schemapot = osp.join(tempdir, 'schema.pot')
     potfiles.append(schemapot)
     # explicit close necessary else the file may not be yet flushed when
@@ -429,50 +439,55 @@
     schemapotstream = file(schemapot, 'w')
     generate_schema_pot(schemapotstream.write, cubedir)
     schemapotstream.close()
-    print '-> extract TAL messages'
+    print 'TAL',
     tali18nfile = osp.join(tempdir, 'tali18n.py')
     ptfiles = find('.', ('.py', '.pt'), blacklist=STD_BLACKLIST+('test',))
     extract_from_tal(ptfiles, tali18nfile)
-    print '-> extract Javascript messages'
+    print 'Javascript'
     jsfiles =  [jsfile for jsfile in find('.', '.js')
                 if osp.basename(jsfile).startswith('cub')]
     if jsfiles:
         tmppotfile = osp.join(tempdir, 'js.pot')
-        execute('xgettext --no-location --omit-header -k_ -L java '
-                '--from-code=utf-8 -o %s %s' % (tmppotfile, ' '.join(jsfiles)))
+        cmd = ['xgettext', '--no-location', '--omit-header', '-k_', '-L', 'java',
+               '--from-code=utf-8', '-o', tmppotfile] + jsfiles
+        execute2(cmd)
         # no pot file created if there are no string to translate
         if osp.exists(tmppotfile):
             potfiles.append(tmppotfile)
-    print '-> create cube-specific catalog'
+    print '-> creating cube-specific catalog'
     tmppotfile = osp.join(tempdir, 'generated.pot')
     cubefiles = find('.', '.py', blacklist=STD_BLACKLIST+('test',))
     cubefiles.append(tali18nfile)
-    execute('xgettext --no-location --omit-header -k_ -o %s %s'
-            % (tmppotfile, ' '.join('"%s"' % f for f in cubefiles)))
+    cmd = ['xgettext', '--no-location', '--omit-header', '-k_', '-o', tmppotfile]
+    cmd.extend(cubefiles)
+    execute2(cmd)
     if osp.exists(tmppotfile): # doesn't exists of no translation string found
         potfiles.append(tmppotfile)
     potfile = osp.join(tempdir, 'cube.pot')
-    print '-> merging %i .pot files:' % len(potfiles)
-    execute('msgcat -o %s %s' % (potfile,
-                                 ' '.join('"%s"' % f for f in potfiles)))
+    print '-> merging %i .pot files' % len(potfiles)
+    cmd = ['msgcat', '-o', potfile]
+    cmd.extend(potfiles)
+    execute2(cmd)
     if not osp.exists(potfile):
         print 'no message catalog for cube', cube, 'nothing to translate'
         # cleanup
         rm(tempdir)
         return ()
-    print '-> merging main pot file with existing translations:'
+    print '-> merging main pot file with existing translations:',
     chdir('i18n')
     toedit = []
     for lang in CubicWebNoAppConfiguration.cw_languages():
-        print '-> language', lang
+        print lang,
         cubepo = '%s.po' % lang
         if not osp.exists(cubepo):
             shutil.copy(potfile, cubepo)
         else:
-            execute('msgmerge -N -s -o %snew %s %s' % (cubepo, cubepo, potfile))
+            cmd = ['msgmerge','-N','-s','-o', cubepo+'new', cubepo, potfile]
+            execute2(cmd)
             ensure_fs_mode(cubepo)
             shutil.move('%snew' % cubepo, cubepo)
         toedit.append(osp.abspath(cubepo))
+    print
     # cleanup
     rm(tempdir)
     return toedit
--- a/devtools/fake.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/devtools/fake.py	Wed Jan 09 16:06:20 2013 +0100
@@ -155,6 +155,12 @@
     def set_entity_cache(self, entity):
         pass
 
+    def security_enabled(self, read=False, write=False):
+        class FakeCM(object):
+            def __enter__(self): pass
+            def __exit__(self, exctype, exc, traceback): pass
+        return FakeCM()
+
     # for use with enabled_security context manager
     read_security = write_security = True
     def init_security(self, *args):
--- a/devtools/repotest.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/devtools/repotest.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/devtools/testlib.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/devtools/testlib.py	Wed Jan 09 16:06:20 2013 +0100
@@ -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:
@@ -1054,7 +1054,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):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/3.16.rst	Wed Jan 09 16:06:20 2013 +0100
@@ -0,0 +1,24 @@
+What's new in CubicWeb 3.16?
+============================
+
+New functionnalities
+--------------------
+
+* Add a new dataimport store (`SQLGenObjectStore`). This store enables a fast
+  import of data (entity creation, link creation) in CubicWeb, by directly
+  flushing information in SQL.  This may only be used with PostgreSQL, as it
+  requires the 'COPY FROM' command.
+
+API changes
+-----------
+
+Unintrusive API changes
+-----------------------
+
+
+
+User interface changes
+----------------------
+
+* Remove changelog view, as nor cubicweb nor known cubes/applications were properly
+  feeding related files
\ No newline at end of file
--- a/doc/book/en/annexes/faq.rst	Wed Jan 09 16:04:26 2013 +0100
+++ b/doc/book/en/annexes/faq.rst	Wed Jan 09 16:06:20 2013 +0100
@@ -364,7 +364,7 @@
     >>> crypted = crypt_password('joepass')
     >>> rset = rql('Any U WHERE U is CWUser, U login "joe"')
     >>> joe = rset.get_entity(0,0)
-    >>> joe.set_attributes(upassword=Binary(crypted))
+    >>> joe.cw_set(upassword=Binary(crypted))
 
 Please, refer to the script example is provided in the `misc/examples/chpasswd.py` file.
 
--- a/doc/book/en/annexes/rql/debugging.rst	Wed Jan 09 16:04:26 2013 +0100
+++ b/doc/book/en/annexes/rql/debugging.rst	Wed Jan 09 16:06:20 2013 +0100
@@ -15,6 +15,8 @@
 .. autodata:: cubicweb.server.DBG_SQL
 .. autodata:: cubicweb.server.DBG_REPO
 .. autodata:: cubicweb.server.DBG_MS
+.. autodata:: cubicweb.server.DBG_HOOKS
+.. autodata:: cubicweb.server.DBG_OPS
 .. autodata:: cubicweb.server.DBG_MORE
 .. autodata:: cubicweb.server.DBG_ALL
 
@@ -31,6 +33,14 @@
 
 .. autofunction:: cubicweb.server.set_debug
 
+Another example showing how to debug hooks at a specific code site:
+
+.. sourcecode:: python
+
+    from cubicweb.server import debuged, DBG_HOOKS
+    with debugged(DBG_HOOKS):
+        person.cw_set(works_for=company)
+
 
 Detect largest RQL queries
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/dataimport.rst	Wed Jan 09 16:06:20 2013 +0100
@@ -0,0 +1,58 @@
+. -*- coding: utf-8 -*-
+
+.. _dataimport:
+
+Dataimport
+==========
+
+*CubicWeb* is designed to manipulate huge of amount of data, and provides helper functions to do so.
+These functions insert data within different levels of the *CubicWeb* API,
+allowing different speed/security tradeoffs. Those keeping all the *CubicWeb* hooks
+and security will be slower but the possible errors in insertion
+(bad data types, integrity error, ...) will be raised.
+
+These dataimport function are provided in the file `dataimport.py`.
+
+All the stores have the following API::
+
+    >>> store = ObjectStore()
+    >>> user = store.create_entity('CWUser', login=u'johndoe')
+    >>> group = store.create_entity('CWUser', name=u'unknown')
+    >>> store.relate(user.eid, 'in_group', group.eid)
+
+
+ObjectStore
+-----------
+
+This store keeps objects in memory for *faster* validation. It may be useful
+in development mode. However, as it will not enforce the constraints of the schema,
+it may miss some problems.
+
+
+
+RQLObjectStore
+--------------
+
+This store works with an actual RQL repository, and it may be used in production mode.
+
+
+NoHookRQLObjectStore
+--------------------
+
+This store works similarly to the *RQLObjectStore* but bypasses some *CubicWeb* hooks to be faster.
+
+
+SQLGenObjectStore
+-----------------
+
+This store relies on *COPY FROM*/execute many sql commands to directly push data using SQL commands
+rather than using the whole *CubicWeb* API. For now, **it only works with PostgresSQL** as it requires
+the *COPY FROM* command.
+
+The API is similar to the other stores, but **it requires a flush** after some imports to copy data
+in the database (these flushes may be multiples through the processes, or be done only once at the
+end if there is no memory issue)::
+
+    >>> store = SQLGenObjectStore(session)
+    >>> store.create_entity('Person', ...)
+    >>> store.flush()
--- a/doc/book/en/devrepo/entityclasses/application-logic.rst	Wed Jan 09 16:04:26 2013 +0100
+++ b/doc/book/en/devrepo/entityclasses/application-logic.rst	Wed Jan 09 16:06:20 2013 +0100
@@ -38,7 +38,7 @@
 object was built.
 
 Setting an attribute or relation value can be done in the context of a
-Hook/Operation, using the obj.set_relations(x=42) notation or a plain
+Hook/Operation, using the obj.cw_set(x=42) notation or a plain
 RQL SET expression.
 
 In views, it would be preferable to encapsulate the necessary logic in
--- a/doc/book/en/devrepo/entityclasses/data-as-objects.rst	Wed Jan 09 16:04:26 2013 +0100
+++ b/doc/book/en/devrepo/entityclasses/data-as-objects.rst	Wed Jan 09 16:06:20 2013 +0100
@@ -16,50 +16,47 @@
 
 `Formatting and output generation`:
 
-* `view(__vid, __registry='views', **kwargs)`, applies the given view to the entity
+* :meth:`view(__vid, __registry='views', **kwargs)`, applies the given view to the entity
   (and returns an unicode string)
 
-* `absolute_url(*args, **kwargs)`, returns an absolute URL including the base-url
+* :meth:`absolute_url(*args, **kwargs)`, returns an absolute URL including the base-url
 
-* `rest_path()`, returns a relative REST URL to get the entity
+* :meth:`rest_path()`, returns a relative REST URL to get the entity
 
-* `printable_value(attr, value=_marker, attrtype=None, format='text/html', displaytime=True)`,
+* :meth:`printable_value(attr, value=_marker, attrtype=None, format='text/html', displaytime=True)`,
   returns a string enabling the display of an attribute value in a given format
   (the value is automatically recovered if necessary)
 
 `Data handling`:
 
-* `as_rset()`, converts the entity into an equivalent result set simulating the
+* :meth:`as_rset()`, converts the entity into an equivalent result set simulating the
   request `Any X WHERE X eid _eid_`
 
-* `complete(skip_bytes=True)`, executes a request that recovers at
+* :meth:`complete(skip_bytes=True)`, executes a request that recovers at
   once all the missing attributes of an entity
 
-* `get_value(name)`, returns the value associated to the attribute name given
+* :meth:`get_value(name)`, returns the value associated to the attribute name given
   in parameter
 
-* `related(rtype, role='subject', limit=None, entities=False)`,
+* :meth:`related(rtype, role='subject', limit=None, entities=False)`,
   returns a list of entities related to the current entity by the
   relation given in parameter
 
-* `unrelated(rtype, targettype, role='subject', limit=None)`,
+* :meth:`unrelated(rtype, targettype, role='subject', limit=None)`,
   returns a result set corresponding to the entities not (yet)
   related to the current entity by the relation given in parameter
   and satisfying its constraints
 
-* `set_attributes(**kwargs)`, updates the attributes list with the corresponding
-  values given named parameters
+* :meth:`cw_set(**kwargs)`, updates entity's attributes and/or relation with the
+  corresponding values given named parameters. To set a relation where this
+  entity is the object of the relation, use `reverse_<relation>` as argument
+  name.  Values may be an entity, a list of entities, or None (meaning that all
+  relations of the given type from or to this object should be deleted).
 
-* `set_relations(**kwargs)`, add relations to the given object. To
-  set a relation where this entity is the object of the relation,
-  use `reverse_<relation>` as argument name.  Values may be an
-  entity, a list of entities, or None (meaning that all relations of
-  the given type from or to this object should be deleted).
-
-* `copy_relations(ceid)`, copies the relations of the entities having the eid
+* :meth:`copy_relations(ceid)`, copies the relations of the entities having the eid
   given in the parameters on the current entity
 
-* `delete()` allows to delete the entity
+* :meth:`cw_delete()` allows to delete the entity
 
 
 The :class:`AnyEntity` class
@@ -81,40 +78,30 @@
 
 `Standard meta-data (Dublin Core)`:
 
-* `dc_title()`, returns a unicode string corresponding to the
+* :meth:`dc_title()`, returns a unicode string corresponding to the
   meta-data `Title` (used by default is the first non-meta attribute
   of the entity schema)
 
-* `dc_long_title()`, same as dc_title but can return a more
+* :meth:`dc_long_title()`, same as dc_title but can return a more
   detailed title
 
-* `dc_description(format='text/plain')`, returns a unicode string
+* :meth:`dc_description(format='text/plain')`, returns a unicode string
   corresponding to the meta-data `Description` (looks for a
   description attribute by default)
 
-* `dc_authors()`, returns a unicode string corresponding to the meta-data
+* :meth:`dc_authors()`, returns a unicode string corresponding to the meta-data
   `Authors` (owners by default)
 
-* `dc_creator()`, returns a unicode string corresponding to the
+* :meth:`dc_creator()`, returns a unicode string corresponding to the
   creator of the entity
 
-* `dc_date(date_format=None)`, returns a unicode string corresponding to
+* :meth:`dc_date(date_format=None)`, returns a unicode string corresponding to
   the meta-data `Date` (update date by default)
 
-* `dc_type(form='')`, returns a string to display the entity type by
+* :meth:`dc_type(form='')`, returns a string to display the entity type by
   specifying the preferred form (`plural` for a plural form)
 
-* `dc_language()`, returns the language used by the entity
-
-
-`Misc methods`:
-
-* `after_deletion_path`, return (path, parameters) which should be
-  used as redirect information when this entity is being deleted
-
-* `pre_web_edit`, callback called by the web editcontroller when an
-  entity will be created/modified, to let a chance to do some entity
-  specific stuff (does nothing by default)
+* :meth:`dc_language()`, returns the language used by the entity
 
 Inheritance
 -----------
--- a/doc/book/en/devrepo/repo/hooks.rst	Wed Jan 09 16:04:26 2013 +0100
+++ b/doc/book/en/devrepo/repo/hooks.rst	Wed Jan 09 16:06:20 2013 +0100
@@ -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	Wed Jan 09 16:04:26 2013 +0100
+++ b/doc/book/en/devrepo/testing.rst	Wed Jan 09 16:06:20 2013 +0100
@@ -70,13 +70,13 @@
 
         def test_cannot_create_cycles(self):
             # direct obvious cycle
-            self.assertRaises(ValidationError, self.kw1.set_relations,
+            self.assertRaises(ValidationError, self.kw1.cw_set,
                               subkeyword_of=self.kw1)
             # testing indirect cycles
             kw3 = self.execute('INSERT Keyword SK: SK name "kwgroup2", SK included_in C, '
                                'SK subkeyword_of K WHERE C name "classif1", K eid %s'
                                % self.kw1.eid).get_entity(0,0)
-            self.kw1.set_relations(subkeyword_of=kw3)
+            self.kw1.cw_set(subkeyword_of=kw3)
             self.assertRaises(ValidationError, self.commit)
 
 The test class defines a :meth:`setup_database` method which populates the
@@ -192,10 +192,10 @@
                                 description=u'cubicweb is beautiful')
             blog_entry_1 = req.create_entity('BlogEntry', title=u'hop',
                                              content=u'cubicweb hop')
-            blog_entry_1.set_relations(entry_of=cubicweb_blog)
+            blog_entry_1.cw_set(entry_of=cubicweb_blog)
             blog_entry_2 = req.create_entity('BlogEntry', title=u'yes',
                                              content=u'cubicweb yes')
-            blog_entry_2.set_relations(entry_of=cubicweb_blog)
+            blog_entry_2.cw_set(entry_of=cubicweb_blog)
             self.assertEqual(len(MAILBOX), 0)
             self.commit()
             self.assertEqual(len(MAILBOX), 2)
--- a/doc/book/en/devweb/index.rst	Wed Jan 09 16:04:26 2013 +0100
+++ b/doc/book/en/devweb/index.rst	Wed Jan 09 16:06:20 2013 +0100
@@ -10,6 +10,7 @@
    publisher
    controllers
    request
+   searchbar
    views/index
    rtags
    ajax
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/searchbar.rst	Wed Jan 09 16:06:20 2013 +0100
@@ -0,0 +1,41 @@
+.. _searchbar:
+
+RQL search bar
+--------------
+
+The RQL search bar is a visual component, hidden by default, the tiny *search*
+input being enough for common use cases.
+
+An autocompletion helper is provided to help you type valid queries, both
+in terms of syntax and in terms of schema validity.
+
+.. autoclass:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder
+
+
+How search is performed
++++++++++++++++++++++++
+
+You can use the *rql search bar* to either type RQL queries, plain text queries
+or standard shortcuts such as *<EntityType>* or *<EntityType> <attrname> <value>*.
+
+Ultimately, all queries are translated to rql since it's the only
+language understood on the server (data) side. To transform the user
+query into RQL, CubicWeb uses the so-called *magicsearch component*,
+defined in :mod:`cubicweb.web.views.magicsearch`, which in turn
+delegates to a number of query preprocessor that are responsible of
+interpreting the user query and generating corresponding RQL.
+
+The code of the main processor loop is easy to understand:
+
+.. sourcecode:: python
+
+  for proc in self.processors:
+      try:
+          return proc.process_query(uquery, req)
+      except (RQLSyntaxError, BadRQLQuery):
+          pass
+
+The idea is simple: for each query processor, try to translate the
+query. If it fails, try with the next processor, if it succeeds,
+we're done and the RQL query will be executed.
+
--- a/doc/book/en/tutorials/advanced/part02_security.rst	Wed Jan 09 16:04:26 2013 +0100
+++ b/doc/book/en/tutorials/advanced/part02_security.rst	Wed Jan 09 16:06:20 2013 +0100
@@ -196,7 +196,7 @@
 	    for eid in self.get_data():
 		entity = self.session.entity_from_eid(eid)
 		if entity.visibility == 'parent':
-		    entity.set_attributes(visibility=u'authenticated')
+		    entity.cw_set(visibility=u'authenticated')
 
     class SetVisibilityHook(hook.Hook):
 	__regid__ = 'sytweb.setvisibility'
@@ -215,7 +215,7 @@
 	    parent = self._cw.entity_from_eid(self.eidto)
 	    child = self._cw.entity_from_eid(self.eidfrom)
 	    if child.visibility == 'parent':
-		child.set_attributes(visibility=parent.visibility)
+		child.cw_set(visibility=parent.visibility)
 
 Notice:
 
@@ -344,7 +344,7 @@
 	    self.assertEquals(len(req.execute('Folder X')), 0) # restricted...
 	    # may_be_read_by propagation
 	    self.restore_connection()
-	    folder.set_relations(may_be_read_by=toto)
+	    folder.cw_set(may_be_read_by=toto)
 	    self.commit()
 	    photo1.clear_all_caches()
 	    self.failUnless(photo1.may_be_read_by)
--- a/doc/book/en/tutorials/advanced/part04_ui-base.rst	Wed Jan 09 16:04:26 2013 +0100
+++ b/doc/book/en/tutorials/advanced/part04_ui-base.rst	Wed Jan 09 16:06:20 2013 +0100
@@ -294,6 +294,7 @@
 folder in which the current file (e.g. `self.entity`) is located.
 
 .. Note::
+
    The :class:`IBreadCrumbs` interface is a `breadcrumbs` method, but the default
    :class:`IBreadCrumbsAdapter` provides a default implementation for it that will look
    at the value returned by its `parent_entity` method. It also provides a
@@ -331,6 +332,7 @@
 navigate through the web site to see if everything is ok...
 
 .. Note::
+
    In the 'cubicweb-ctl i18ncube' command, `sytweb` refers to the **cube**, while
    in the two other, it refers to the **instance** (if you can't see the
    difference, reread CubicWeb's concept chapter !).
@@ -363,4 +365,4 @@
 .. _`several improvments`: http://www.cubicweb.org/blogentry/1179899
 .. _`3.8`: http://www.cubicweb.org/blogentry/917107
 .. _`first blog of this series`: http://www.cubicweb.org/blogentry/824642
-.. _`an earlier post`: http://www.cubicweb.org/867464
\ No newline at end of file
+.. _`an earlier post`: http://www.cubicweb.org/867464
--- a/entities/authobjs.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/entities/authobjs.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -77,6 +77,19 @@
             self._properties = dict((p.pkey, p.value) for p in self.reverse_for_user)
             return self._properties
 
+    def prefered_language(self, language=None):
+        """return language used by this user, if explicitly defined (eg not
+        using http negociation)
+        """
+        language = language or self.property_value('ui.language')
+        vreg = self._cw.vreg
+        try:
+            vreg.config.translations[language]
+        except KeyError:
+            language = vreg.property_value('ui.language')
+            assert language in vreg.config.translations[language], language
+        return language
+
     def property_value(self, key):
         try:
             # properties stored on the user aren't correctly typed
@@ -101,7 +114,7 @@
                 kwargs['for_user'] = self
             self._cw.create_entity('CWProperty', **kwargs)
         else:
-            prop.set_attributes(value=value)
+            prop.cw_set(value=value)
 
     def matching_groups(self, groups):
         """return the number of the given group(s) in which the user is
--- a/entities/sources.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/entities/sources.py	Wed Jan 09 16:06:20 2013 +0100
@@ -51,7 +51,7 @@
                     continue
                 raise
         cfgstr = unicode(generate_source_config(sconfig), self._cw.encoding)
-        self.set_attributes(config=cfgstr)
+        self.cw_set(config=cfgstr)
 
 
 class CWSource(_CWSourceCfgMixIn, AnyEntity):
@@ -181,5 +181,5 @@
     def write_log(self, session, **kwargs):
         if 'status' not in kwargs:
             kwargs['status'] = getattr(self, '_status', u'success')
-        self.set_attributes(log=u'<br/>'.join(self._logs), **kwargs)
+        self.cw_set(log=u'<br/>'.join(self._logs), **kwargs)
         self._logs = []
--- a/entities/test/unittest_base.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/entities/test/unittest_base.py	Wed Jan 09 16:06:20 2013 +0100
@@ -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	Wed Jan 09 16:04:26 2013 +0100
+++ b/entities/test/unittest_wfobjs.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -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',
@@ -623,23 +622,22 @@
         cnx.close()
 
     def test_transition_checking3(self):
-        cnx = self.login('stduser')
-        session = self.session
-        user = cnx.user(session)
-        iworkflowable = user.cw_adapt_to('IWorkflowable')
-        iworkflowable.fire_transition('deactivate')
-        cnx.commit()
-        session.set_cnxset()
-        with self.assertRaises(ValidationError) as cm:
+        with self.login('stduser') as cnx:
+            session = self.session
+            user = cnx.user(session)
+            iworkflowable = user.cw_adapt_to('IWorkflowable')
             iworkflowable.fire_transition('deactivate')
-        self.assertEqual(self._cleanup_msg(cm.exception.errors['by_transition-subject']),
-                                            u"transition isn't allowed from")
-        cnx.rollback()
-        session.set_cnxset()
-        # get back now
-        iworkflowable.fire_transition('activate')
-        cnx.commit()
-        cnx.close()
+            session.commit()
+            session.set_cnxset()
+            with self.assertRaises(ValidationError) as cm:
+                iworkflowable.fire_transition('deactivate')
+            self.assertEqual(self._cleanup_msg(cm.exception.errors['by_transition-subject']),
+                                                u"transition isn't allowed from")
+            session.rollback()
+            session.set_cnxset()
+            # get back now
+            iworkflowable.fire_transition('activate')
+            session.commit()
 
 
 if __name__ == '__main__':
--- a/entity.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/entity.py	Wed Jan 09 16:06:20 2013 +0100
@@ -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,51 @@
                 if hasattr(value, 'eid'):
                     value = value.eid
             qargs[attr] = value
+        rql = u''
         if relations:
-            rql = '%s: %s' % (rql, ', '.join(relations))
+            rql += ', '.join(relations)
         if restrictions:
-            rql = '%s WHERE %s' % (rql, ', '.join(restrictions))
-        created = execute(rql, qargs).get_entity(0, 0)
-        for attr, role, values in pending_relations:
+            rql += ' WHERE %s' % ', '.join(restrictions)
+        return rql, qargs, pendingrels, attrcache
+
+    @classmethod
+    def _cw_handle_pending_relations(cls, eid, pendingrels, execute):
+        for attr, role, values in pendingrels:
             if role == 'object':
                 restr = 'Y %s X' % attr
             else:
                 restr = 'X %s Y' % attr
+            if values is None:
+                execute('DELETE %s WHERE X eid %%(x)s' % restr, {'x': eid})
+                continue
             execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
                 restr, ','.join(str(getattr(r, 'eid', r)) for r in values)),
-                    {'x': created.eid}, build_descr=False)
+                    {'x': eid}, build_descr=False)
+
+    @classmethod
+    def cw_instantiate(cls, execute, **kwargs):
+        """add a new entity of this given type
+
+        Example (in a shell session):
+
+        >>> companycls = vreg['etypes'].etype_class(('Company')
+        >>> personcls = vreg['etypes'].etype_class(('Person')
+        >>> c = companycls.cw_instantiate(session.execute, name=u'Logilab')
+        >>> p = personcls.cw_instantiate(session.execute, firstname=u'John', lastname=u'Doe',
+        ...                              works_for=c)
+
+        You can also set relations where the entity has 'object' role by
+        prefixing the relation name by 'reverse_'. Also, relation values may be
+        an entity or eid, a list of entities or eids.
+        """
+        rql, qargs, pendingrels, attrcache = cls._cw_build_entity_query(kwargs)
+        if rql:
+            rql = 'INSERT %s X: %s' % (cls.__regid__, rql)
+        else:
+            rql = 'INSERT %s X' % (cls.__regid__)
+        created = execute(rql, qargs).get_entity(0, 0)
+        created._cw_update_attr_cache(attrcache)
+        cls._cw_handle_pending_relations(created.eid, pendingrels, execute)
         return created
 
     def __init__(self, req, rset=None, row=None, col=0):
@@ -535,6 +557,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 +1255,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 +1304,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	Wed Jan 09 16:04:26 2013 +0100
+++ b/ext/tal.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -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/bookmark.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/bookmark.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/hooks/email.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/email.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -15,9 +15,8 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""hooks to ensure use_email / primary_email relations consistency
+"""hooks to ensure use_email / primary_email relations consistency"""
 
-"""
 __docformat__ = "restructuredtext en"
 
 from cubicweb.server import hook
--- a/hooks/integrity.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/integrity.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -20,12 +20,11 @@
 """
 
 __docformat__ = "restructuredtext en"
+_ = unicode
 
 from threading import Lock
 
-from yams.schema import role_name
-
-from cubicweb import ValidationError
+from cubicweb import validation_error
 from cubicweb.schema import (META_RTYPES, WORKFLOW_RTYPES,
                              RQLConstraint, RQLUniqueConstraint)
 from cubicweb.predicates import is_instance
@@ -87,11 +86,11 @@
                 continue
             if not session.execute(self.base_rql % rtype, {'x': eid}):
                 etype = session.describe(eid)[0]
-                _ = session._
                 msg = _('at least one relation %(rtype)s is required on '
                         '%(etype)s (%(eid)s)')
-                msg %= {'rtype': _(rtype), 'etype': _(etype), 'eid': eid}
-                raise ValidationError(eid, {role_name(rtype, self.role): msg})
+                raise validation_error(eid, {(rtype, self.role): msg},
+                                       {'rtype': rtype, 'etype': etype, 'eid': eid},
+                                       ['rtype', 'etype'])
 
 
 class _CheckSRelationOp(_CheckRequiredRelationOperation):
@@ -231,9 +230,9 @@
                 rql = '%s X WHERE X %s %%(val)s' % (entity.e_schema, attr)
                 rset = self._cw.execute(rql, {'val': val})
                 if rset and rset[0][0] != entity.eid:
-                    msg = self._cw._('the value "%s" is already used, use another one')
-                    qname = role_name(attr, 'subject')
-                    raise ValidationError(entity.eid, {qname: msg % val})
+                    msg = _('the value "%s" is already used, use another one')
+                    raise validation_error(entity, {(attr, 'subject'): msg},
+                                           (val,))
 
 
 class DontRemoveOwnersGroupHook(IntegrityHook):
@@ -246,15 +245,12 @@
     def __call__(self):
         entity = self.entity
         if self.event == 'before_delete_entity' and entity.name == 'owners':
-            msg = self._cw._('can\'t be deleted')
-            raise ValidationError(entity.eid, {None: msg})
+            raise validation_error(entity, {None: _("can't be deleted")})
         elif self.event == 'before_update_entity' \
                  and 'name' in entity.cw_edited:
             oldname, newname = entity.cw_edited.oldnewvalue('name')
             if oldname == 'owners' and newname != oldname:
-                qname = role_name('name', 'subject')
-                msg = self._cw._('can\'t be changed')
-                raise ValidationError(entity.eid, {qname: msg})
+                raise validation_error(entity, {('name', 'subject'): _("can't be changed")})
 
 
 class TidyHtmlFields(IntegrityHook):
--- a/hooks/metadata.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/metadata.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/hooks/syncschema.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/syncschema.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -24,6 +24,7 @@
 """
 
 __docformat__ = "restructuredtext en"
+_ = unicode
 
 from copy import copy
 from yams.schema import BASE_TYPES, RelationSchema, RelationDefinitionSchema
@@ -31,7 +32,7 @@
 
 from logilab.common.decorators import clear_cache
 
-from cubicweb import ValidationError
+from cubicweb import validation_error
 from cubicweb.predicates import is_instance
 from cubicweb.schema import (SCHEMA_TYPES, META_RTYPES, VIRTUAL_RTYPES,
                              CONSTRAINTS, ETYPE_NAME_MAP, display_name)
@@ -127,10 +128,9 @@
         if attr in ro_attrs:
             origval, newval = entity.cw_edited.oldnewvalue(attr)
             if newval != origval:
-                errors[attr] = session._("can't change the %s attribute") % \
-                               display_name(session, attr)
+                errors[attr] = _("can't change this attribute")
     if errors:
-        raise ValidationError(entity.eid, errors)
+        raise validation_error(entity, errors)
 
 
 class _MockEntity(object): # XXX use a named tuple with python 2.6
@@ -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	Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/syncsession.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -18,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	Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/syncsources.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2010-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2010-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -17,12 +17,13 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """hooks for repository sources synchronization"""
 
+_ = unicode
+
 from socket import gethostname
 
 from logilab.common.decorators import clear_cache
-from yams.schema import role_name
 
-from cubicweb import ValidationError
+from cubicweb import validation_error
 from cubicweb.predicates import is_instance
 from cubicweb.server import SOURCE_TYPES, hook
 
@@ -46,12 +47,15 @@
         try:
             sourcecls = SOURCE_TYPES[self.entity.type]
         except KeyError:
-            msg = self._cw._('unknown source type')
-            raise ValidationError(self.entity.eid,
-                                  {role_name('type', 'subject'): msg})
-        sourcecls.check_conf_dict(self.entity.eid, self.entity.host_config,
-                                  fail_if_unknown=not self._cw.vreg.config.repairing)
-        SourceAddedOp(self._cw, entity=self.entity)
+            msg = _('Unknown source type')
+            raise validation_error(self.entity, {('type', 'subject'): msg})
+        # ignore creation of the system source done during database
+        # initialisation, as config for this source is in a file and handling
+        # is done separatly (no need for the operation either)
+        if self.entity.name != 'system':
+            sourcecls.check_conf_dict(self.entity.eid, self.entity.host_config,
+                                      fail_if_unknown=not self._cw.vreg.config.repairing)
+            SourceAddedOp(self._cw, entity=self.entity)
 
 
 class SourceRemovedOp(hook.Operation):
@@ -65,7 +69,8 @@
     events = ('before_delete_entity',)
     def __call__(self):
         if self.entity.name == 'system':
-            raise ValidationError(self.entity.eid, {None: 'cant remove system source'})
+            msg = _("You cannot remove the system source")
+            raise validation_error(self.entity, {None: msg})
         SourceRemovedOp(self._cw, uri=self.entity.name)
 
 
@@ -116,11 +121,18 @@
     __select__ = SourceHook.__select__ & is_instance('CWSource')
     events = ('before_update_entity',)
     def __call__(self):
-        if 'config' in self.entity.cw_edited:
-            SourceConfigUpdatedOp.get_instance(self._cw).add_data(self.entity)
         if 'name' in self.entity.cw_edited:
             oldname, newname = self.entity.cw_edited.oldnewvalue('name')
+            if oldname == 'system':
+                msg = _("You cannot rename the system source")
+                raise validation_error(self.entity, {('name', 'subject'): msg})
             SourceRenamedOp(self._cw, oldname=oldname, newname=newname)
+        if 'config' in self.entity.cw_edited:
+            if self.entity.name == 'system' and self.entity.config:
+                msg = _("Configuration of the system source goes to "
+                        "the 'sources' file, not in the database")
+                raise validation_error(self.entity, {('config', 'subject'): msg})
+            SourceConfigUpdatedOp.get_instance(self._cw).add_data(self.entity)
 
 
 class SourceHostConfigUpdatedHook(SourceHook):
@@ -154,8 +166,8 @@
     events = ('before_add_relation',)
     def __call__(self):
         if not self._cw.added_in_transaction(self.eidfrom):
-            msg = self._cw._("can't change this relation")
-            raise ValidationError(self.eidfrom, {self.rtype: msg})
+            msg = _("You can't change this relation")
+            raise validation_error(self.eidfrom, {self.rtype: msg})
 
 
 class SourceMappingChangedOp(hook.DataOperationMixIn, hook.Operation):
--- a/hooks/test/unittest_hooks.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/test/unittest_hooks.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -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):
@@ -166,13 +165,12 @@
                           self.execute, 'INSERT CWRType X: X name "in_group"')
 
     def test_validation_unique_constraint(self):
-        self.assertRaises(ValidationError,
-                          self.execute, 'INSERT CWUser X: X login "admin"')
-        try:
+        with self.assertRaises(ValidationError) as cm:
             self.execute('INSERT CWUser X: X login "admin"')
-        except ValidationError, ex:
-            self.assertIsInstance(ex.entity, int)
-            self.assertEqual(ex.errors, {'login-subject': 'the value "admin" is already used, use another one'})
+        ex = cm.exception
+        ex.translate(unicode)
+        self.assertIsInstance(ex.entity, int)
+        self.assertEqual(ex.errors, {'login-subject': 'the value "admin" is already used, use another one'})
 
 
 if __name__ == '__main__':
--- a/hooks/test/unittest_syncschema.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/test/unittest_syncschema.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -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	Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/test/unittest_syncsession.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -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.translate(unicode)
         self.assertEqual(cm.exception.errors, {'pkey-subject': 'unknown property key bla.bla'})
         with self.assertRaises(ValidationError) as cm:
             self.execute('INSERT CWProperty X: X pkey "bla.bla", X value "hop"')
+        cm.exception.translate(unicode)
         self.assertEqual(cm.exception.errors, {'pkey-subject': 'unknown property key bla.bla'})
 
     def test_site_wide_cwproperty(self):
--- a/hooks/workflow.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/workflow.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -18,12 +18,12 @@
 """Core hooks: workflow related hooks"""
 
 __docformat__ = "restructuredtext en"
+_ = unicode
 
 from datetime import datetime
 
-from yams.schema import role_name
 
-from cubicweb import RepositoryError, ValidationError
+from cubicweb import RepositoryError, validation_error
 from cubicweb.predicates import is_instance, adaptable
 from cubicweb.server import hook
 
@@ -92,9 +92,8 @@
         if mainwf.eid == self.wfeid:
             deststate = mainwf.initial
             if not deststate:
-                qname = role_name('custom_workflow', 'subject')
-                msg = session._('workflow has no initial state')
-                raise ValidationError(entity.eid, {qname: msg})
+                msg = _('workflow has no initial state')
+                raise validation_error(entity, {('custom_workflow', 'subject'): msg})
             if mainwf.state_by_eid(iworkflowable.current_state.eid):
                 # nothing to do
                 return
@@ -119,9 +118,8 @@
         outputs = set()
         for ep in tr.subworkflow_exit:
             if ep.subwf_state.eid in outputs:
-                qname = role_name('subworkflow_exit', 'subject')
-                msg = self.session._("can't have multiple exits on the same state")
-                raise ValidationError(self.treid, {qname: msg})
+                msg = _("can't have multiple exits on the same state")
+                raise validation_error(self.treid, {('subworkflow_exit', 'subject'): msg})
             outputs.add(ep.subwf_state.eid)
 
 
@@ -137,13 +135,12 @@
         wftr = iworkflowable.subworkflow_input_transition()
         if wftr is None:
             # inconsistency detected
-            qname = role_name('to_state', 'subject')
-            msg = session._("state doesn't belong to entity's current workflow")
-            raise ValidationError(self.trinfo.eid, {'to_state': msg})
+            msg = _("state doesn't belong to entity's current workflow")
+            raise validation_error(self.trinfo, {('to_state', 'subject'): msg})
         tostate = wftr.get_exit_point(forentity, trinfo.cw_attr_cache['to_state'])
         if tostate is not None:
             # reached an exit point
-            msg = session._('exiting from subworkflow %s')
+            msg = _('exiting from subworkflow %s')
             msg %= session._(iworkflowable.current_workflow.name)
             session.transaction_data[(forentity.eid, 'subwfentrytr')] = True
             iworkflowable.change_state(tostate, msg, u'text/plain', tr=wftr)
@@ -186,9 +183,8 @@
         try:
             foreid = entity.cw_attr_cache['wf_info_for']
         except KeyError:
-            qname = role_name('wf_info_for', 'subject')
-            msg = session._('mandatory relation')
-            raise ValidationError(entity.eid, {qname: msg})
+            msg = _('mandatory relation')
+            raise validation_error(entity, {('wf_info_for', 'subject'): msg})
         forentity = session.entity_from_eid(foreid)
         # see comment in the TrInfo entity definition
         entity.cw_edited['tr_count']=len(forentity.reverse_wf_info_for)
@@ -201,13 +197,13 @@
         else:
             wf = iworkflowable.current_workflow
         if wf is None:
-            msg = session._('related entity has no workflow set')
-            raise ValidationError(entity.eid, {None: msg})
+            msg = _('related entity has no workflow set')
+            raise validation_error(entity, {None: msg})
         # then check it has a state set
         fromstate = iworkflowable.current_state
         if fromstate is None:
-            msg = session._('related entity has no state')
-            raise ValidationError(entity.eid, {None: msg})
+            msg = _('related entity has no state')
+            raise validation_error(entity, {None: msg})
         # True if we are coming back from subworkflow
         swtr = session.transaction_data.pop((forentity.eid, 'subwfentrytr'), None)
         cowpowers = (session.user.is_in_group('managers')
@@ -219,47 +215,42 @@
             # no transition set, check user is a manager and destination state
             # is specified (and valid)
             if not cowpowers:
-                qname = role_name('by_transition', 'subject')
-                msg = session._('mandatory relation')
-                raise ValidationError(entity.eid, {qname: msg})
+                msg = _('mandatory relation')
+                raise validation_error(entity, {('by_transition', 'subject'): msg})
             deststateeid = entity.cw_attr_cache.get('to_state')
             if not deststateeid:
-                qname = role_name('by_transition', 'subject')
-                msg = session._('mandatory relation')
-                raise ValidationError(entity.eid, {qname: msg})
+                msg = _('mandatory relation')
+                raise validation_error(entity, {('by_transition', 'subject'): msg})
             deststate = wf.state_by_eid(deststateeid)
             if deststate is None:
-                qname = role_name('to_state', 'subject')
-                msg = session._("state doesn't belong to entity's workflow")
-                raise ValidationError(entity.eid, {qname: msg})
+                msg = _("state doesn't belong to entity's workflow")
+                raise validation_error(entity, {('to_state', 'subject'): msg})
         else:
             # check transition is valid and allowed, unless we're coming back
             # from subworkflow
             tr = session.entity_from_eid(treid)
             if swtr is None:
-                qname = role_name('by_transition', 'subject')
+                qname = ('by_transition', 'subject')
                 if tr is None:
-                    msg = session._("transition doesn't belong to entity's workflow")
-                    raise ValidationError(entity.eid, {qname: msg})
+                    msg = _("transition doesn't belong to entity's workflow")
+                    raise validation_error(entity, {qname: msg})
                 if not tr.has_input_state(fromstate):
-                    msg = session._("transition %(tr)s isn't allowed from %(st)s") % {
-                        'tr': session._(tr.name), 'st': session._(fromstate.name)}
-                    raise ValidationError(entity.eid, {qname: msg})
+                    msg = _("transition %(tr)s isn't allowed from %(st)s")
+                    raise validation_error(entity, {qname: msg}, {
+                            'tr': tr.name, 'st': fromstate.name}, ['tr', 'st'])
                 if not tr.may_be_fired(foreid):
-                    msg = session._("transition may not be fired")
-                    raise ValidationError(entity.eid, {qname: msg})
+                    msg = _("transition may not be fired")
+                    raise validation_error(entity, {qname: msg})
             deststateeid = entity.cw_attr_cache.get('to_state')
             if deststateeid is not None:
                 if not cowpowers and deststateeid != tr.destination(forentity).eid:
-                    qname = role_name('by_transition', 'subject')
-                    msg = session._("transition isn't allowed")
-                    raise ValidationError(entity.eid, {qname: msg})
+                    msg = _("transition isn't allowed")
+                    raise validation_error(entity, {('by_transition', 'subject'): msg})
                 if swtr is None:
                     deststate = session.entity_from_eid(deststateeid)
                     if not cowpowers and deststate is None:
-                        qname = role_name('to_state', 'subject')
-                        msg = session._("state doesn't belong to entity's workflow")
-                        raise ValidationError(entity.eid, {qname: msg})
+                        msg = _("state doesn't belong to entity's workflow")
+                        raise validation_error(entity, {('to_state', 'subject'): msg})
             else:
                 deststateeid = tr.destination(forentity).eid
         # everything is ok, add missing information on the trinfo entity
@@ -307,20 +298,18 @@
         iworkflowable = entity.cw_adapt_to('IWorkflowable')
         mainwf = iworkflowable.main_workflow
         if mainwf is None:
-            msg = session._('entity has no workflow set')
-            raise ValidationError(entity.eid, {None: msg})
+            msg = _('entity has no workflow set')
+            raise validation_error(entity, {None: msg})
         for wf in mainwf.iter_workflows():
             if wf.state_by_eid(self.eidto):
                 break
         else:
-            qname = role_name('in_state', 'subject')
-            msg = session._("state doesn't belong to entity's workflow. You may "
-                            "want to set a custom workflow for this entity first.")
-            raise ValidationError(self.eidfrom, {qname: msg})
+            msg = _("state doesn't belong to entity's workflow. You may "
+                    "want to set a custom workflow for this entity first.")
+            raise validation_error(self.eidfrom, {('in_state', 'subject'): msg})
         if iworkflowable.current_workflow and wf.eid != iworkflowable.current_workflow.eid:
-            qname = role_name('in_state', 'subject')
-            msg = session._("state doesn't belong to entity's current workflow")
-            raise ValidationError(self.eidfrom, {qname: msg})
+            msg = _("state doesn't belong to entity's current workflow")
+            raise validation_error(self.eidfrom, {('in_state', 'subject'): msg})
 
 
 class SetModificationDateOnStateChange(WorkflowHook):
@@ -335,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.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/i18n.py	Wed Jan 09 16:06:20 2013 +0100
@@ -54,19 +54,16 @@
         w('msgid "%s"\n' % msgid[0])
     w('msgstr ""\n\n')
 
-
-def execute(cmd):
-    """display the command, execute it and raise an Exception if returned
-    status != 0
-    """
-    from subprocess import call
-    # use getcwdu as cmd may be unicode and cwd may contains non-ascii
-    # characters
-    print cmd.replace(os.getcwdu() + os.sep, '')
-    status = call(cmd, shell=True)
-    if status != 0:
-        raise Exception('status = %s' % status)
-
+def execute2(args):
+    # XXX replace this with check_output in Python 2.7
+    from subprocess import Popen, PIPE, CalledProcessError
+    p = Popen(args, stdout=PIPE, stderr=PIPE)
+    out, err = p.communicate()
+    if p.returncode != 0:
+        exc = CalledProcessError(p.returncode, args[0])
+        exc.cmd = args
+        exc.data = (out, err)
+        raise exc
 
 def available_catalogs(i18ndir=None):
     if i18ndir is None:
@@ -81,6 +78,7 @@
 def compile_i18n_catalogs(sourcedirs, destdir, langs):
     """generate .mo files for a set of languages into the `destdir` i18n directory
     """
+    from subprocess import CalledProcessError
     from logilab.common.fileutils import ensure_fs_mode
     print '-> compiling message catalogs to %s' % destdir
     errors = []
@@ -93,17 +91,21 @@
         mergedpo = join(destdir, '%s_merged.po' % lang)
         try:
             # merge instance/cubes messages catalogs with the stdlib's one
-            execute('msgcat --use-first --sort-output --strict -o "%s" %s'
-                    % (mergedpo, ' '.join('"%s"' % f for f in pofiles)))
+            cmd = ['msgcat', '--use-first', '--sort-output', '--strict',
+                   '-o', mergedpo] + pofiles
+            execute2(cmd)
             # make sure the .mo file is writeable and compiles with *msgfmt*
             applmo = join(destdir, lang, 'LC_MESSAGES', 'cubicweb.mo')
             try:
                 ensure_fs_mode(applmo)
             except OSError:
                 pass # suppose not exists
-            execute('msgfmt "%s" -o "%s"' % (mergedpo, applmo))
-        except Exception, ex:
-            errors.append('while handling language %s: %s' % (lang, ex))
+            execute2(['msgfmt', mergedpo, '-o', applmo])
+        except CalledProcessError, exc:
+            errors.append(u'while handling language %s:\ncmd:\n%s\nstdout:\n%s\nstderr:\n%s\n' %
+                          (lang, exc.cmd, repr(exc.data[0]), repr(exc.data[1])))
+        except Exception, exc:
+            errors.append(u'while handling language %s: %s' % (lang, exc))
         try:
             # clean everything
             os.unlink(mergedpo)
--- a/i18n/de.po	Wed Jan 09 16:04:26 2013 +0100
+++ b/i18n/de.po	Wed Jan 09 16:06:20 2013 +0100
@@ -50,6 +50,14 @@
 msgstr ""
 
 #, python-format
+msgid "%(KEY-cstr)s constraint failed for value %(KEY-value)r"
+msgstr ""
+
+#, python-format
+msgid "%(KEY-value)r doesn't match the %(KEY-regexp)r regular expression"
+msgstr ""
+
+#, python-format
 msgid "%(attr)s set to %(newvalue)s"
 msgstr "%(attr)s geändert in %(newvalue)s"
 
@@ -58,10 +66,6 @@
 msgstr "%(attr)s geändert von %(oldvalue)s in %(newvalue)s"
 
 #, python-format
-msgid "%(cstr)s constraint failed for value %(value)r"
-msgstr "%(cstr)s Einschränkung verletzt für Wert %(value)r"
-
-#, python-format
 msgid "%(etype)s by %(author)s"
 msgstr ""
 
@@ -74,10 +78,6 @@
 msgstr "%(subject)s %(etype)s #%(eid)s (%(login)s)"
 
 #, python-format
-msgid "%(value)r doesn't match the %(regexp)r regular expression"
-msgstr "%(value)r entspricht nicht dem regulären Ausdruck %(regexp)r"
-
-#, python-format
 msgid "%d days"
 msgstr "%d Tage"
 
@@ -427,6 +427,11 @@
 msgid "Click to sort on this column"
 msgstr ""
 
+msgid ""
+"Configuration of the system source goes to the 'sources' file, not in the "
+"database"
+msgstr ""
+
 #, python-format
 msgid "Created %(etype)s : %(entity)s"
 msgstr ""
@@ -902,6 +907,9 @@
 msgid "UniqueConstraint"
 msgstr "eindeutige Einschränkung"
 
+msgid "Unknown source type"
+msgstr ""
+
 msgid "Unreachable objects"
 msgstr "unzugängliche Objekte"
 
@@ -960,6 +968,15 @@
 msgid "You can use any of the following substitutions in your text"
 msgstr "Sie können die folgenden Ersetzungen in Ihrem Text verwenden:"
 
+msgid "You can't change this relation"
+msgstr ""
+
+msgid "You cannot remove the system source"
+msgstr ""
+
+msgid "You cannot rename the system source"
+msgstr ""
+
 msgid ""
 "You have no access to this view or it can not be used to display the current "
 "data."
@@ -1006,9 +1023,6 @@
 msgid "abstract base class for transitions"
 msgstr "abstrakte Basisklasse für Übergänge"
 
-msgid "action menu"
-msgstr ""
-
 msgid "action(s) on this selection"
 msgstr "Aktionen(en) bei dieser Auswahl"
 
@@ -1392,11 +1406,7 @@
 msgid "can't be deleted"
 msgstr "kann nicht entfernt werden"
 
-#, python-format
-msgid "can't change the %s attribute"
-msgstr "Kann das Attribut %s nicht ändern."
-
-msgid "can't change this relation"
+msgid "can't change this attribute"
 msgstr ""
 
 #, python-format
@@ -2569,6 +2579,9 @@
 msgid "foaf"
 msgstr "FOAF"
 
+msgid "focus on this selection"
+msgstr ""
+
 msgid "follow"
 msgstr "dem Link folgen"
 
@@ -2859,8 +2872,8 @@
 msgstr "Unzulässiger Wert für Überschrift"
 
 #, python-format
-msgid "incorrect value (%(value)s) for type \"%(type)s\""
-msgstr "Wert %(value)s ungültig für den Typ \"%(type)s\""
+msgid "incorrect value (%(KEY-value)r) for type \"%(KEY-type)s\""
+msgstr ""
 
 msgid "index this attribute's value in the plain text index"
 msgstr "indizieren des Wertes dieses Attributs im Volltext-Index"
@@ -2938,8 +2951,8 @@
 msgstr "Ungültige Aktion %r"
 
 #, python-format
-msgid "invalid value %(value)s, it must be one of %(choices)s"
-msgstr "Wert %(value)s ungültig, er muss zwischen %(choices)s"
+msgid "invalid value %(KEY-value)s, it must be one of %(KEY-choices)s"
+msgstr ""
 
 msgid "is"
 msgstr "vom Typ"
@@ -4174,6 +4187,9 @@
 msgid "toggle check boxes"
 msgstr "Kontrollkästchen umkehren"
 
+msgid "toggle filter"
+msgstr "filter verbergen/zeigen"
+
 msgid "tr_count"
 msgstr ""
 
@@ -4316,9 +4332,6 @@
 msgid "unknown property key %s"
 msgstr "Unbekannter Eigentumsschlüssel %s"
 
-msgid "unknown source type"
-msgstr ""
-
 msgid "unknown vocabulary:"
 msgstr "Unbekanntes Wörterbuch : "
 
@@ -4464,15 +4477,7 @@
 msgstr "Wert"
 
 #, python-format
-msgid "value %(value)s must be %(op)s %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be <= %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be >= %(boundary)s"
+msgid "value %(KEY-value)s must be %(KEY-op)s %(KEY-boundary)s"
 msgstr ""
 
 msgid "value associated to this key is not editable manually"
@@ -4481,11 +4486,11 @@
 "werden."
 
 #, python-format
-msgid "value should have maximum size of %s but found %s"
+msgid "value should have maximum size of %(KEY-max)s but found %(KEY-size)s"
 msgstr ""
 
 #, python-format
-msgid "value should have minimum size of %s but found %s"
+msgid "value should have minimum size of %(KEY-min)s but found %(KEY-size)s"
 msgstr ""
 
 msgid "vcard"
@@ -4630,6 +4635,12 @@
 msgid "you should un-inline relation %s which is supported and may be crossed "
 msgstr ""
 
+#~ msgid "%(cstr)s constraint failed for value %(value)r"
+#~ msgstr "%(cstr)s Einschränkung verletzt für Wert %(value)r"
+
+#~ msgid "%(value)r doesn't match the %(regexp)r regular expression"
+#~ msgstr "%(value)r entspricht nicht dem regulären Ausdruck %(regexp)r"
+
 #~ msgid ""
 #~ "Can't restore relation %(rtype)s of entity %(eid)s, this relation does "
 #~ "not exists anymore in the schema."
@@ -4637,8 +4648,11 @@
 #~ "Kann die Relation %(rtype)s der Entität %(eid)s nicht wieder herstellen, "
 #~ "diese Relation existiert nicht mehr in dem Schema."
 
-#~ msgid "log out first"
-#~ msgstr "Melden Sie sich zuerst ab."
-
-#~ msgid "week"
-#~ msgstr "Woche"
+#~ msgid "can't change the %s attribute"
+#~ msgstr "Kann das Attribut %s nicht ändern."
+
+#~ msgid "incorrect value (%(value)s) for type \"%(type)s\""
+#~ msgstr "Wert %(value)s ungültig für den Typ \"%(type)s\""
+
+#~ msgid "invalid value %(value)s, it must be one of %(choices)s"
+#~ msgstr "Wert %(value)s ungültig, er muss zwischen %(choices)s"
--- a/i18n/en.po	Wed Jan 09 16:04:26 2013 +0100
+++ b/i18n/en.po	Wed Jan 09 16:06:20 2013 +0100
@@ -42,6 +42,14 @@
 msgstr ""
 
 #, python-format
+msgid "%(KEY-cstr)s constraint failed for value %(KEY-value)r"
+msgstr ""
+
+#, python-format
+msgid "%(KEY-value)r doesn't match the %(KEY-regexp)r regular expression"
+msgstr ""
+
+#, python-format
 msgid "%(attr)s set to %(newvalue)s"
 msgstr ""
 
@@ -50,10 +58,6 @@
 msgstr ""
 
 #, python-format
-msgid "%(cstr)s constraint failed for value %(value)r"
-msgstr ""
-
-#, python-format
 msgid "%(etype)s by %(author)s"
 msgstr ""
 
@@ -66,10 +70,6 @@
 msgstr ""
 
 #, python-format
-msgid "%(value)r doesn't match the %(regexp)r regular expression"
-msgstr ""
-
-#, python-format
 msgid "%d days"
 msgstr ""
 
@@ -405,6 +405,11 @@
 msgid "Click to sort on this column"
 msgstr ""
 
+msgid ""
+"Configuration of the system source goes to the 'sources' file, not in the "
+"database"
+msgstr ""
+
 #, python-format
 msgid "Created %(etype)s : %(entity)s"
 msgstr ""
@@ -878,6 +883,9 @@
 msgid "UniqueConstraint"
 msgstr "unique constraint"
 
+msgid "Unknown source type"
+msgstr ""
+
 msgid "Unreachable objects"
 msgstr ""
 
@@ -929,6 +937,15 @@
 msgid "You can use any of the following substitutions in your text"
 msgstr ""
 
+msgid "You can't change this relation"
+msgstr ""
+
+msgid "You cannot remove the system source"
+msgstr ""
+
+msgid "You cannot rename the system source"
+msgstr ""
+
 msgid ""
 "You have no access to this view or it can not be used to display the current "
 "data."
@@ -968,9 +985,6 @@
 msgid "abstract base class for transitions"
 msgstr ""
 
-msgid "action menu"
-msgstr ""
-
 msgid "action(s) on this selection"
 msgstr ""
 
@@ -1349,11 +1363,7 @@
 msgid "can't be deleted"
 msgstr ""
 
-#, python-format
-msgid "can't change the %s attribute"
-msgstr ""
-
-msgid "can't change this relation"
+msgid "can't change this attribute"
 msgstr ""
 
 #, python-format
@@ -2516,6 +2526,9 @@
 msgid "foaf"
 msgstr ""
 
+msgid "focus on this selection"
+msgstr ""
+
 msgid "follow"
 msgstr ""
 
@@ -2788,7 +2801,7 @@
 msgstr ""
 
 #, python-format
-msgid "incorrect value (%(value)s) for type \"%(type)s\""
+msgid "incorrect value (%(KEY-value)r) for type \"%(KEY-type)s\""
 msgstr ""
 
 msgid "index this attribute's value in the plain text index"
@@ -2865,7 +2878,7 @@
 msgstr ""
 
 #, python-format
-msgid "invalid value %(value)s, it must be one of %(choices)s"
+msgid "invalid value %(KEY-value)s, it must be one of %(KEY-choices)s"
 msgstr ""
 
 msgid "is"
@@ -4074,6 +4087,9 @@
 msgid "toggle check boxes"
 msgstr ""
 
+msgid "toggle filter"
+msgstr ""
+
 msgid "tr_count"
 msgstr "transition number"
 
@@ -4216,9 +4232,6 @@
 msgid "unknown property key %s"
 msgstr ""
 
-msgid "unknown source type"
-msgstr ""
-
 msgid "unknown vocabulary:"
 msgstr ""
 
@@ -4355,26 +4368,18 @@
 msgstr ""
 
 #, python-format
-msgid "value %(value)s must be %(op)s %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be <= %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be >= %(boundary)s"
+msgid "value %(KEY-value)s must be %(KEY-op)s %(KEY-boundary)s"
 msgstr ""
 
 msgid "value associated to this key is not editable manually"
 msgstr ""
 
 #, python-format
-msgid "value should have maximum size of %s but found %s"
+msgid "value should have maximum size of %(KEY-max)s but found %(KEY-size)s"
 msgstr ""
 
 #, python-format
-msgid "value should have minimum size of %s but found %s"
+msgid "value should have minimum size of %(KEY-min)s but found %(KEY-size)s"
 msgstr ""
 
 msgid "vcard"
--- a/i18n/es.po	Wed Jan 09 16:04:26 2013 +0100
+++ b/i18n/es.po	Wed Jan 09 16:06:20 2013 +0100
@@ -51,6 +51,14 @@
 "\"role=subject\" o \"role=object\" debe ser especificado en las opciones"
 
 #, python-format
+msgid "%(KEY-cstr)s constraint failed for value %(KEY-value)r"
+msgstr ""
+
+#, python-format
+msgid "%(KEY-value)r doesn't match the %(KEY-regexp)r regular expression"
+msgstr ""
+
+#, python-format
 msgid "%(attr)s set to %(newvalue)s"
 msgstr "%(attr)s modificado a %(newvalue)s"
 
@@ -59,10 +67,6 @@
 msgstr "%(attr)s modificado de %(oldvalue)s a %(newvalue)s"
 
 #, python-format
-msgid "%(cstr)s constraint failed for value %(value)r"
-msgstr "el valor %(value)r no satisface la condición %(cstr)s"
-
-#, python-format
 msgid "%(etype)s by %(author)s"
 msgstr "%(etype)s por %(author)s"
 
@@ -75,10 +79,6 @@
 msgstr "%(subject)s %(etype)s #%(eid)s (%(login)s)"
 
 #, python-format
-msgid "%(value)r doesn't match the %(regexp)r regular expression"
-msgstr "%(value)r no corresponde a la expresión regular %(regexp)r"
-
-#, python-format
 msgid "%d days"
 msgstr "%d días"
 
@@ -427,6 +427,11 @@
 msgid "Click to sort on this column"
 msgstr ""
 
+msgid ""
+"Configuration of the system source goes to the 'sources' file, not in the "
+"database"
+msgstr ""
+
 #, python-format
 msgid "Created %(etype)s : %(entity)s"
 msgstr ""
@@ -905,6 +910,9 @@
 msgid "UniqueConstraint"
 msgstr "Restricción de Unicidad"
 
+msgid "Unknown source type"
+msgstr ""
+
 msgid "Unreachable objects"
 msgstr "Objetos inaccesibles"
 
@@ -965,6 +973,15 @@
 "Puede realizar cualquiera de las siguientes sustituciones en el contenido de "
 "su email."
 
+msgid "You can't change this relation"
+msgstr ""
+
+msgid "You cannot remove the system source"
+msgstr ""
+
+msgid "You cannot rename the system source"
+msgstr ""
+
 msgid ""
 "You have no access to this view or it can not be used to display the current "
 "data."
@@ -1016,9 +1033,6 @@
 msgid "abstract base class for transitions"
 msgstr "Clase de base abstracta para la transiciones"
 
-msgid "action menu"
-msgstr ""
-
 msgid "action(s) on this selection"
 msgstr "Acción(es) en esta selección"
 
@@ -1403,12 +1417,8 @@
 msgid "can't be deleted"
 msgstr "No puede ser eliminado"
 
-#, python-format
-msgid "can't change the %s attribute"
-msgstr "no puede modificar el atributo %s"
-
-msgid "can't change this relation"
-msgstr "no puede modificar esta relación"
+msgid "can't change this attribute"
+msgstr ""
 
 #, python-format
 msgid "can't connect to source %s, some data may be missing"
@@ -2611,6 +2621,9 @@
 msgid "foaf"
 msgstr "Amigo de un Amigo, FOAF"
 
+msgid "focus on this selection"
+msgstr ""
+
 msgid "follow"
 msgstr "Seguir la liga"
 
@@ -2901,8 +2914,8 @@
 msgstr "Valor del Captcha incorrecto"
 
 #, python-format
-msgid "incorrect value (%(value)s) for type \"%(type)s\""
-msgstr "valor %(value)s incorrecto para el tipo \"%(type)s\""
+msgid "incorrect value (%(KEY-value)r) for type \"%(KEY-type)s\""
+msgstr ""
 
 msgid "index this attribute's value in the plain text index"
 msgstr "Indexar el valor de este atributo en el índice de texto simple"
@@ -2981,8 +2994,8 @@
 msgstr "Acción %r invalida"
 
 #, python-format
-msgid "invalid value %(value)s, it must be one of %(choices)s"
-msgstr "Valor %(value)s incorrecto, debe estar entre %(choices)s"
+msgid "invalid value %(KEY-value)s, it must be one of %(KEY-choices)s"
+msgstr ""
 
 msgid "is"
 msgstr "es"
@@ -4224,6 +4237,9 @@
 msgid "toggle check boxes"
 msgstr "Cambiar valor"
 
+msgid "toggle filter"
+msgstr "esconder/mostrar el filtro"
+
 msgid "tr_count"
 msgstr "n° de transición"
 
@@ -4366,9 +4382,6 @@
 msgid "unknown property key %s"
 msgstr "Clave de Propiedad desconocida: %s"
 
-msgid "unknown source type"
-msgstr "tipo de fuente desconocida"
-
 msgid "unknown vocabulary:"
 msgstr "Vocabulario desconocido: "
 
@@ -4514,26 +4527,18 @@
 msgstr "Vampr"
 
 #, python-format
-msgid "value %(value)s must be %(op)s %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be <= %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be >= %(boundary)s"
+msgid "value %(KEY-value)s must be %(KEY-op)s %(KEY-boundary)s"
 msgstr ""
 
 msgid "value associated to this key is not editable manually"
 msgstr "El valor asociado a este elemento no es editable manualmente"
 
 #, python-format
-msgid "value should have maximum size of %s but found %s"
+msgid "value should have maximum size of %(KEY-max)s but found %(KEY-size)s"
 msgstr ""
 
 #, python-format
-msgid "value should have minimum size of %s but found %s"
+msgid "value should have minimum size of %(KEY-min)s but found %(KEY-size)s"
 msgstr ""
 
 msgid "vcard"
@@ -4681,6 +4686,12 @@
 "usted debe  quitar la puesta en línea de la relación %s que es aceptada y "
 "puede ser cruzada"
 
+#~ msgid "%(cstr)s constraint failed for value %(value)r"
+#~ msgstr "el valor %(value)r no satisface la condición %(cstr)s"
+
+#~ msgid "%(value)r doesn't match the %(regexp)r regular expression"
+#~ msgstr "%(value)r no corresponde a la expresión regular %(regexp)r"
+
 #~ msgid ""
 #~ "Can't restore relation %(rtype)s of entity %(eid)s, this relation does "
 #~ "not exists anymore in the schema."
@@ -4688,17 +4699,17 @@
 #~ "No puede restaurar la relación %(rtype)s de la entidad %(eid)s, esta "
 #~ "relación ya no existe en el esquema."
 
-#~ msgid "day"
-#~ msgstr "día"
-
-#~ msgid "log out first"
-#~ msgstr "Desconéctese primero"
-
-#~ msgid "month"
-#~ msgstr "mes"
-
-#~ msgid "today"
-#~ msgstr "hoy"
-
-#~ msgid "week"
-#~ msgstr "sem."
+#~ msgid "can't change the %s attribute"
+#~ msgstr "no puede modificar el atributo %s"
+
+#~ msgid "can't change this relation"
+#~ msgstr "no puede modificar esta relación"
+
+#~ msgid "incorrect value (%(value)s) for type \"%(type)s\""
+#~ msgstr "valor %(value)s incorrecto para el tipo \"%(type)s\""
+
+#~ msgid "invalid value %(value)s, it must be one of %(choices)s"
+#~ msgstr "Valor %(value)s incorrecto, debe estar entre %(choices)s"
+
+#~ msgid "unknown source type"
+#~ msgstr "tipo de fuente desconocida"
--- a/i18n/fr.po	Wed Jan 09 16:04:26 2013 +0100
+++ b/i18n/fr.po	Wed Jan 09 16:06:20 2013 +0100
@@ -50,6 +50,15 @@
 "\"role=subject\" ou \"role=object\" doit être specifié dans les options"
 
 #, python-format
+msgid "%(KEY-cstr)s constraint failed for value %(KEY-value)r"
+msgstr "la valeur %(KEY-value)r ne satisfait pas la contrainte %(KEY-cstr)s"
+
+#, python-format
+msgid "%(KEY-value)r doesn't match the %(KEY-regexp)r regular expression"
+msgstr ""
+"%(KEY-value)r ne correspond pas à l'expression régulière %(KEY-regexp)r"
+
+#, python-format
 msgid "%(attr)s set to %(newvalue)s"
 msgstr "%(attr)s modifié à %(newvalue)s"
 
@@ -58,10 +67,6 @@
 msgstr "%(attr)s modifié de %(oldvalue)s à %(newvalue)s"
 
 #, python-format
-msgid "%(cstr)s constraint failed for value %(value)r"
-msgstr "la valeur %(value)r ne satisfait pas la contrainte %(cstr)s"
-
-#, python-format
 msgid "%(etype)s by %(author)s"
 msgstr "%(etype)s par %(author)s"
 
@@ -74,10 +79,6 @@
 msgstr "%(subject)s %(etype)s #%(eid)s (%(login)s)"
 
 #, python-format
-msgid "%(value)r doesn't match the %(regexp)r regular expression"
-msgstr "%(value)r ne correspond pas à l'expression régulière %(regexp)r"
-
-#, python-format
 msgid "%d days"
 msgstr "%d jours"
 
@@ -427,6 +428,11 @@
 msgid "Click to sort on this column"
 msgstr "Cliquer pour trier sur cette colonne"
 
+msgid ""
+"Configuration of the system source goes to the 'sources' file, not in the "
+"database"
+msgstr ""
+
 #, python-format
 msgid "Created %(etype)s : %(entity)s"
 msgstr "Entité %(etype)s crée : %(entity)s"
@@ -907,6 +913,9 @@
 msgid "UniqueConstraint"
 msgstr "contrainte d'unicité"
 
+msgid "Unknown source type"
+msgstr "Type de source inconnue"
+
 msgid "Unreachable objects"
 msgstr "Objets inaccessibles"
 
@@ -967,6 +976,15 @@
 "Vous pouvez utiliser n'importe quelle substitution parmi la liste suivante "
 "dans le contenu de votre courriel."
 
+msgid "You can't change this relation"
+msgstr "Vous ne pouvez pas modifier cette relation"
+
+msgid "You cannot remove the system source"
+msgstr "Vous ne pouvez pas supprimer la source système"
+
+msgid "You cannot rename the system source"
+msgstr "Vous ne pouvez pas renommer la source système"
+
 msgid ""
 "You have no access to this view or it can not be used to display the current "
 "data."
@@ -1018,9 +1036,6 @@
 msgid "abstract base class for transitions"
 msgstr "classe de base abstraite pour les transitions"
 
-msgid "action menu"
-msgstr "actions"
-
 msgid "action(s) on this selection"
 msgstr "action(s) sur cette sélection"
 
@@ -1260,7 +1275,7 @@
 msgstr "anonyme"
 
 msgid "anyrsetview"
-msgstr "vues \"tous les rset\""
+msgstr "vues pour tout rset"
 
 msgid "april"
 msgstr "avril"
@@ -1311,7 +1326,7 @@
 msgstr "mauvaise valeur"
 
 msgid "badly formatted url"
-msgstr ""
+msgstr "URL mal formattée"
 
 msgid "base url"
 msgstr "url de base"
@@ -1399,7 +1414,7 @@
 msgstr "impossible d'interpréter les types d'entités :"
 
 msgid "can only have one url"
-msgstr ""
+msgstr "ne supporte qu'une seule URL"
 
 msgid "can't be changed"
 msgstr "ne peut-être modifié"
@@ -1407,12 +1422,8 @@
 msgid "can't be deleted"
 msgstr "ne peut-être supprimé"
 
-#, python-format
-msgid "can't change the %s attribute"
-msgstr "ne peut changer l'attribut %s"
-
-msgid "can't change this relation"
-msgstr "ne peut modifier cette relation"
+msgid "can't change this attribute"
+msgstr "cet attribut ne peut pas être modifié"
 
 #, python-format
 msgid "can't connect to source %s, some data may be missing"
@@ -2621,6 +2632,9 @@
 msgid "foaf"
 msgstr "foaf"
 
+msgid "focus on this selection"
+msgstr "afficher cette sélection"
+
 msgid "follow"
 msgstr "suivre le lien"
 
@@ -2909,8 +2923,8 @@
 msgstr "valeur de captcha incorrecte"
 
 #, python-format
-msgid "incorrect value (%(value)s) for type \"%(type)s\""
-msgstr "valeur %(value)s incorrecte pour le type \"%(type)s\""
+msgid "incorrect value (%(KEY-value)r) for type \"%(KEY-type)s\""
+msgstr "la valeur %(KEY-value)s est incorrecte pour le type \"%(KEY-type)s\""
 
 msgid "index this attribute's value in the plain text index"
 msgstr "indexer la valeur de cet attribut dans l'index plein texte"
@@ -2989,8 +3003,9 @@
 msgstr "action %r invalide"
 
 #, python-format
-msgid "invalid value %(value)s, it must be one of %(choices)s"
-msgstr "valeur %(value)s incorrect, doit être parmi %(choices)s"
+msgid "invalid value %(KEY-value)s, it must be one of %(KEY-choices)s"
+msgstr ""
+"la valeur %(KEY-value)s est incorrecte, elle doit être parmi %(KEY-choices)s"
 
 msgid "is"
 msgstr "de type"
@@ -4235,7 +4250,10 @@
 msgstr "à faire par"
 
 msgid "toggle check boxes"
-msgstr "inverser les cases à cocher"
+msgstr "afficher/masquer les cases à cocher"
+
+msgid "toggle filter"
+msgstr "afficher/masquer le filtre"
 
 msgid "tr_count"
 msgstr "n° de transition"
@@ -4379,14 +4397,11 @@
 msgid "unknown property key %s"
 msgstr "clé de propriété inconnue : %s"
 
-msgid "unknown source type"
-msgstr "type de source inconnu"
-
 msgid "unknown vocabulary:"
 msgstr "vocabulaire inconnu : "
 
 msgid "unsupported protocol"
-msgstr ""
+msgstr "protocole non supporté"
 
 msgid "upassword"
 msgstr "mot de passe"
@@ -4525,27 +4540,23 @@
 msgstr "valeur"
 
 #, python-format
-msgid "value %(value)s must be %(op)s %(boundary)s"
-msgstr "la valeur %(value)s doit être %(op)s %(boundary)s"
-
-#, python-format
-msgid "value %(value)s must be <= %(boundary)s"
-msgstr "la valeur %(value)s doit être <= %(boundary)s"
-
-#, python-format
-msgid "value %(value)s must be >= %(boundary)s"
-msgstr "la valeur %(value)s doit être >= %(boundary)s"
+msgid "value %(KEY-value)s must be %(KEY-op)s %(KEY-boundary)s"
+msgstr "la valeur %(KEY-value)s n'est pas %(KEY-op)s %(KEY-boundary)s"
 
 msgid "value associated to this key is not editable manually"
 msgstr "la valeur associée à cette clé n'est pas éditable manuellement"
 
 #, python-format
-msgid "value should have maximum size of %s but found %s"
-msgstr "la taille maximum est %s mais cette valeur est de taille %s"
+msgid "value should have maximum size of %(KEY-max)s but found %(KEY-size)s"
+msgstr ""
+"la taille maximum est %(KEY-max)s  mais cette valeur est de taille "
+"%(KEY-size)s"
 
 #, python-format
-msgid "value should have minimum size of %s but found %s"
-msgstr "la taille minimum est %s mais cette valeur est de taille %s"
+msgid "value should have minimum size of %(KEY-min)s but found %(KEY-size)s"
+msgstr ""
+"la taille minimum est %(KEY-min)s  mais cette valeur est de taille "
+"%(KEY-size)s"
 
 msgid "vcard"
 msgstr "vcard"
@@ -4699,6 +4710,9 @@
 #~ msgid "day"
 #~ msgstr "jour"
 
+#~ msgid "jump to selection"
+#~ msgstr "afficher cette sélection"
+
 #~ msgid "log out first"
 #~ msgstr "déconnecter vous d'abord"
 
--- a/misc/migration/3.10.0_Any.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/misc/migration/3.10.0_Any.py	Wed Jan 09 16:06:20 2013 +0100
@@ -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	Wed Jan 09 16:04:26 2013 +0100
+++ b/misc/migration/3.11.0_Any.py	Wed Jan 09 16:06:20 2013 +0100
@@ -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	Wed Jan 09 16:04:26 2013 +0100
+++ b/misc/migration/3.14.0_Any.py	Wed Jan 09 16:06:20 2013 +0100
@@ -9,5 +9,5 @@
     expression = rqlcstr.value
     mainvars = guess_rrqlexpr_mainvars(expression)
     yamscstr = CONSTRAINTS[rqlcstr.type](expression, mainvars)
-    rqlcstr.set_attributes(value=yamscstr.serialize())
+    rqlcstr.cw_set(value=yamscstr.serialize())
     print 'updated', rqlcstr.type, rqlcstr.value.strip()
--- a/misc/migration/3.15.0_Any.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/misc/migration/3.15.0_Any.py	Wed Jan 09 16:06:20 2013 +0100
@@ -4,7 +4,7 @@
     config = source.dictconfig
     host = config.pop('host', u'ldap')
     protocol = config.pop('protocol', u'ldap')
-    source.set_attributes(url=u'%s://%s' % (protocol, host))
+    source.cw_set(url=u'%s://%s' % (protocol, host))
     source.update_config(skip_unknown=True, **config)
 
 commit()
--- a/misc/migration/postcreate.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/misc/migration/postcreate.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/misc/scripts/chpasswd.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/misc/scripts/chpasswd.py	Wed Jan 09 16:06:20 2013 +0100
@@ -42,7 +42,7 @@
 crypted = crypt_password(pass1)
 
 cwuser = rset.get_entity(0,0)
-cwuser.set_attributes(upassword=Binary(crypted))
+cwuser.cw_set(upassword=Binary(crypted))
 commit()
 
 print("password updated.")
--- a/misc/scripts/ldapuser2ldapfeed.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/misc/scripts/ldapuser2ldapfeed.py	Wed Jan 09 16:06:20 2013 +0100
@@ -87,7 +87,7 @@
 
 
 source_ent = rql('CWSource S WHERE S eid %(s)s', {'s': source.eid}).get_entity(0, 0)
-source_ent.set_attributes(type=u"ldapfeed", parser=u"ldapfeed")
+source_ent.cw_set(type=u"ldapfeed", parser=u"ldapfeed")
 
 
 if raw_input('Commit ?') in 'yY':
--- a/predicates.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/predicates.py	Wed Jan 09 16:06:20 2013 +0100
@@ -737,12 +737,16 @@
     See :class:`~cubicweb.predicates.EClassPredicate` documentation for entity
     class lookup / score rules according to the input context.
 
-    .. note:: when interface is an entity class, the score will reflect class
-              proximity so the most specific object will be selected.
+    .. note::
+
+       when interface is an entity class, the score will reflect class
+       proximity so the most specific object will be selected.
 
-    .. note:: deprecated in cubicweb >= 3.9, use either
-              :class:`~cubicweb.predicates.is_instance` or
-              :class:`~cubicweb.predicates.adaptable`.
+    .. note::
+
+       deprecated in cubicweb >= 3.9, use either
+       :class:`~cubicweb.predicates.is_instance` or
+       :class:`~cubicweb.predicates.adaptable`.
     """
 
     def __init__(self, *expected_ifaces, **kwargs):
--- a/req.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/req.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -62,6 +62,8 @@
     :attribute vreg.schema: the instance's schema
     :attribute vreg.config: the instance's configuration
     """
+    is_request = True # False for repository session
+
     def __init__(self, vreg):
         self.vreg = vreg
         try:
@@ -75,6 +77,17 @@
         self.local_perm_cache = {}
         self._ = unicode
 
+    def set_language(self, lang):
+        """install i18n configuration for `lang` translation.
+
+        Raises :exc:`KeyError` if translation doesn't exist.
+        """
+        self.lang = lang
+        gettext, pgettext = self.vreg.config.translations[lang]
+        # use _cw.__ to translate a message without registering it to the catalog
+        self._ = self.__ = gettext
+        self.pgettext = pgettext
+
     def get_option_value(self, option, foreid=None):
         raise NotImplementedError
 
@@ -308,21 +321,12 @@
     def user_data(self):
         """returns a dictionary with this user's information"""
         userinfo = {}
-        if self.is_internal_session:
-            userinfo['login'] = "cubicweb"
-            userinfo['name'] = "cubicweb"
-            userinfo['email'] = ""
-            return userinfo
         user = self.user
         userinfo['login'] = user.login
         userinfo['name'] = user.name()
         userinfo['email'] = user.cw_adapt_to('IEmailable').get_email()
         return userinfo
 
-    def is_internal_session(self):
-        """overrided on the server-side"""
-        return False
-
     # formating methods #######################################################
 
     def view(self, __vid, rset=None, __fallback_oid=None, __registry='views',
@@ -426,7 +430,7 @@
         """return the root url of the instance
         """
         if secure:
-            raise NotImplementedError()
+            return self.vreg.config.get('https-url', self.vreg.config['base-url'])
         return self.vreg.config['base-url']
 
     # abstract methods to override according to the web front-end #############
--- a/schema.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/schema.py	Wed Jan 09 16:06:20 2013 +0100
@@ -261,30 +261,34 @@
     return self.has_local_role(action) or self.has_perm(req, action)
 PermissionMixIn.may_have_permission = may_have_permission
 
-def has_perm(self, session, action, **kwargs):
+def has_perm(self, _cw, action, **kwargs):
     """return true if the action is granted globaly or localy"""
     try:
-        self.check_perm(session, action, **kwargs)
+        self.check_perm(_cw, action, **kwargs)
         return True
     except Unauthorized:
         return False
 PermissionMixIn.has_perm = has_perm
 
-def check_perm(self, session, action, **kwargs):
-    # NB: session may be a server session or a request object check user is
-    # in an allowed group, if so that's enough internal sessions should
-    # always stop there
+def check_perm(self, _cw, action, **kwargs):
+    # NB: _cw may be a server transaction or a request object.
+    #
+    # check user is in an allowed group, if so that's enough internal
+    # transactions should always stop there
     groups = self.get_groups(action)
-    if session.user.matching_groups(groups):
+    if _cw.user.matching_groups(groups):
         return
     # if 'owners' in allowed groups, check if the user actually owns this
     # object, if so that's enough
+    #
+    # NB: give _cw to user.owns since user is not be bound to a transaction on
+    # the repository side
     if 'owners' in groups and (
           kwargs.get('creating')
-          or ('eid' in kwargs and session.user.owns(kwargs['eid']))):
+          or ('eid' in kwargs and _cw.user.owns(kwargs['eid']))):
         return
     # else if there is some rql expressions, check them
-    if any(rqlexpr.check(session, **kwargs)
+    if any(rqlexpr.check(_cw, **kwargs)
            for rqlexpr in self.get_rqlexprs(action)):
         return
     raise Unauthorized(action, str(self))
@@ -467,45 +471,45 @@
                     return True
         return False
 
-    def has_perm(self, session, action, **kwargs):
+    def has_perm(self, _cw, action, **kwargs):
         """return true if the action is granted globaly or localy"""
         if self.final:
             assert not ('fromeid' in kwargs or 'toeid' in kwargs), kwargs
             assert action in ('read', 'update')
             if 'eid' in kwargs:
-                subjtype = session.describe(kwargs['eid'])[0]
+                subjtype = _cw.describe(kwargs['eid'])[0]
             else:
                 subjtype = objtype = None
         else:
             assert not 'eid' in kwargs, kwargs
             assert action in ('read', 'add', 'delete')
             if 'fromeid' in kwargs:
-                subjtype = session.describe(kwargs['fromeid'])[0]
+                subjtype = _cw.describe(kwargs['fromeid'])[0]
             elif 'frometype' in kwargs:
                 subjtype = kwargs.pop('frometype')
             else:
                 subjtype = None
             if 'toeid' in kwargs:
-                objtype = session.describe(kwargs['toeid'])[0]
+                objtype = _cw.describe(kwargs['toeid'])[0]
             elif 'toetype' in kwargs:
                 objtype = kwargs.pop('toetype')
             else:
                 objtype = None
         if objtype and subjtype:
-            return self.rdef(subjtype, objtype).has_perm(session, action, **kwargs)
+            return self.rdef(subjtype, objtype).has_perm(_cw, action, **kwargs)
         elif subjtype:
             for tschema in self.targets(subjtype, 'subject'):
                 rdef = self.rdef(subjtype, tschema)
-                if not rdef.has_perm(session, action, **kwargs):
+                if not rdef.has_perm(_cw, action, **kwargs):
                     return False
         elif objtype:
             for tschema in self.targets(objtype, 'object'):
                 rdef = self.rdef(tschema, objtype)
-                if not rdef.has_perm(session, action, **kwargs):
+                if not rdef.has_perm(_cw, action, **kwargs):
                     return False
         else:
             for rdef in self.rdefs.itervalues():
-                if not rdef.has_perm(session, action, **kwargs):
+                if not rdef.has_perm(_cw, action, **kwargs):
                     return False
         return True
 
@@ -754,17 +758,17 @@
             return rql, found, keyarg
         return rqlst.as_string(), None, None
 
-    def _check(self, session, **kwargs):
+    def _check(self, _cw, **kwargs):
         """return True if the rql expression is matching the given relation
         between fromeid and toeid
 
-        session may actually be a request as well
+        _cw may be a request or a server side transaction
         """
         creating = kwargs.get('creating')
         if not creating and self.eid is not None:
             key = (self.eid, tuple(sorted(kwargs.iteritems())))
             try:
-                return session.local_perm_cache[key]
+                return _cw.local_perm_cache[key]
             except KeyError:
                 pass
         rql, has_perm_defs, keyarg = self.transform_has_permission()
@@ -772,50 +776,50 @@
         if creating and 'X' in self.rqlst.defined_vars:
             return True
         if keyarg is None:
-            kwargs.setdefault('u', session.user.eid)
+            kwargs.setdefault('u', _cw.user.eid)
             try:
-                rset = session.execute(rql, kwargs, build_descr=True)
+                rset = _cw.execute(rql, kwargs, build_descr=True)
             except NotImplementedError:
                 self.critical('cant check rql expression, unsupported rql %s', rql)
                 if self.eid is not None:
-                    session.local_perm_cache[key] = False
+                    _cw.local_perm_cache[key] = False
                 return False
             except TypeResolverException, ex:
                 # 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	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/__init__.py	Wed Jan 09 16:06:20 2013 +0100
@@ -77,10 +77,14 @@
 DBG_REPO = 4
 #: multi-sources
 DBG_MS   = 8
+#: hooks
+DBG_HOOKS = 16
+#: operations
+DBG_OPS = 32
 #: more verbosity
-DBG_MORE = 16
+DBG_MORE = 64
 #: all level enabled
-DBG_ALL  = DBG_RQL + DBG_SQL + DBG_REPO + DBG_MS + DBG_MORE
+DBG_ALL  = DBG_RQL + DBG_SQL + DBG_REPO + DBG_MS + DBG_HOOKS + DBG_OPS + DBG_MORE
 
 #: current debug mode
 DEBUG = 0
@@ -165,6 +169,8 @@
     # on connection
     config.creating = True
     config.consider_user_state = False
+    config.cubicweb_appobject_path = set(('hooks', 'entities'))
+    config.cube_appobject_path = set(('hooks', 'entities'))
     # only enable the system source at initialization time
     repo = Repository(config, vreg=vreg)
     schema = repo.schema
@@ -224,10 +230,6 @@
     config._cubes = None # avoid assertion error
     repo, cnx = in_memory_repo_cnx(config, login, password=pwd)
     repo.system_source.eid = ssource.eid # redo this manually
-    # trigger vreg initialisation of entity classes
-    config.cubicweb_appobject_path = set(('entities',))
-    config.cube_appobject_path = set(('entities',))
-    repo.vreg.set_schema(repo.schema)
     assert len(repo.sources) == 1, repo.sources
     handler = config.migration_handler(schema, interactive=False,
                                        cnx=cnx, repo=repo)
@@ -246,20 +248,21 @@
     # restore initial configuration
     config.creating = False
     config.consider_user_state = True
+    # (drop instance attribute to get back to class attribute)
+    del config.cubicweb_appobject_path
+    del config.cube_appobject_path
     print '-> database for instance %s initialized.' % config.appid
 
 
 def initialize_schema(config, schema, mhandler, event='create'):
     from cubicweb.server.schemaserial import serialize_schema
-    from cubicweb.server.session import hooks_control
     session = mhandler.session
     cubes = config.cubes()
     # deactivate every hooks but those responsible to set metadata
     # so, NO INTEGRITY CHECKS are done, to have quicker db creation.
     # Active integrity is kept else we may pb such as two default
     # workflows for one entity type.
-    with hooks_control(session, session.HOOKS_DENY_ALL, 'metadata',
-                       'activeintegrity'):
+    with session.deny_all_hooks_but('metadata', 'activeintegrity'):
         # execute cubicweb's pre<event> script
         mhandler.cmd_exec_event_script('pre%s' % event)
         # execute cubes pre<event> script if any
--- a/server/checkintegrity.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/checkintegrity.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -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,18 +393,18 @@
     # yo, launch checks
     if checks:
         eids_cache = {}
-        with security_enabled(session, read=False, write=False): # ensure no read security
+        with session.security_enabled(read=False, write=False): # ensure no read security
             for check in checks:
                 check_func = globals()['check_%s' % check]
                 check_func(repo.schema, session, eids_cache, fix=fix)
         if fix:
-            cnx.commit()
+            session.commit()
         else:
             print
         if not fix:
             print 'WARNING: Diagnostic run, nothing has been corrected'
     if reindex:
-        cnx.rollback()
+        session.rollback()
         session.set_cnxset()
         reindex_entities(repo.schema, session, withpb=withpb)
-        cnx.commit()
+        session.commit()
--- a/server/edition.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/edition.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -103,6 +103,8 @@
         assert not self.saved, 'too late to modify edited attributes'
         super(EditedEntity, self).__setitem__(attr, value)
         self.entity.cw_attr_cache[attr] = value
+        # mark attribute as needing purge by the client
+        self.entity._cw_dont_cache_attribute(attr)
 
     def oldnewvalue(self, attr):
         """returns the couple (old attr value, new attr value)
@@ -141,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	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/hook.py	Wed Jan 09 16:06:20 2013 +0100
@@ -152,7 +152,7 @@
 
   On those events, the entity has no `cw_edited` dictionary.
 
-.. note:: `self.entity.set_attributes(age=42)` will set the `age` attribute to
+.. note:: `self.entity.cw_set(age=42)` will set the `age` attribute to
   42. But to do so, it will generate a rql query that will have to be processed,
   hence may trigger some hooks, etc. This could lead to infinitely looping hooks.
 
@@ -197,14 +197,12 @@
 ~~~~~~~~~~~~~
 
 It is sometimes convenient to explicitly enable or disable some hooks. For
-instance if you want to disable some integrity checking hook.  This can be
+instance if you want to disable some integrity checking hook. This can be
 controlled more finely through the `category` class attribute, which is a string
 giving a category name.  One can then uses the
-:class:`~cubicweb.server.session.hooks_control` context manager to explicitly
-enable or disable some categories.
-
-.. autoclass:: cubicweb.server.session.hooks_control
-
+:meth:`~cubicweb.server.session.Session.deny_all_hooks_but` and
+:meth:`~cubicweb.server.session.Session.allow_all_hooks_but` context managers to
+explicitly enable or disable some categories.
 
 The existing categories are:
 
@@ -230,10 +228,8 @@
 * ``bookmark``, bookmark entities handling hooks
 
 
-Nothing precludes one to invent new categories and use the
-:class:`~cubicweb.server.session.hooks_control` context manager to
-filter them in or out. Note that ending the transaction with commit()
-or rollback() will restore the hooks.
+Nothing precludes one to invent new categories and use existing mechanisms to
+filter them in or out.
 
 
 Hooks specific predicates
@@ -264,11 +260,10 @@
 from logilab.common.registry import (Predicate, NotPredicate, OrPredicate,
                                      classid, objectify_predicate, yes)
 
-from cubicweb import RegistryNotFound
+from cubicweb import RegistryNotFound, server
 from cubicweb.cwvreg import CWRegistry, CWRegistryStore
 from cubicweb.predicates import ExpectedValuePredicate, is_instance
 from cubicweb.appobject import AppObject
-from cubicweb.server.session import security_enabled
 
 ENTITIES_HOOKS = set(('before_add_entity',    'after_add_entity',
                       'before_update_entity', 'after_update_entity',
@@ -326,12 +321,15 @@
             pruned = self.get_pruned_hooks(session, event,
                                            entities, eids_from_to, kwargs)
             # by default, hooks are executed with security turned off
-            with security_enabled(session, read=False):
+            with session.security_enabled(read=False):
                 for _kwargs in _iter_kwargs(entities, eids_from_to, kwargs):
                     hooks = sorted(self.filtered_possible_objects(pruned, session, **_kwargs),
                                    key=lambda x: x.order)
-                    with security_enabled(session, write=False):
+                    debug = server.DEBUG & server.DBG_HOOKS
+                    with session.security_enabled(write=False):
                         for hook in hooks:
+                            if debug:
+                                print event, _kwargs, hook
                             hook()
 
     def get_pruned_hooks(self, session, event, entities, eids_from_to, kwargs):
--- a/server/migractions.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/migractions.py	Wed Jan 09 16:06:20 2013 +0100
@@ -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	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/pool.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/server/querier.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/querier.py	Wed Jan 09 16:06:20 2013 +0100
@@ -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	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/repository.py	Wed Jan 09 16:06:20 2013 +0100
@@ -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,33 +217,21 @@
             # information (eg dump/restore/...)
             config._cubes = ()
             # only load hooks and entity classes in the registry
-            config.__class__.cube_appobject_path = set(('hooks', 'entities'))
-            config.__class__.cubicweb_appobject_path = set(('hooks', 'entities'))
+            config.cube_appobject_path = set(('hooks', 'entities'))
+            config.cubicweb_appobject_path = set(('hooks', 'entities'))
             self.set_schema(config.load_schema())
             config['connections-pool-size'] = 1
             # will be reinitialized later from cubes found in the database
             config._cubes = None
-        elif config.creating:
-            # repository creation
+        elif config.creating or not config.read_instance_schema:
+            if not config.creating:
+                # test start: use the file system schema (quicker)
+                self.warning("set fs instance'schema")
             config.bootstrap_cubes()
-            self.set_schema(config.load_schema(), resetvreg=False)
-            # need to load the Any and CWUser entity types
-            etdirectory = join(CW_SOFTWARE_ROOT, 'entities')
-            self.vreg.init_registration([etdirectory])
-            for modname in ('__init__', 'authobjs', 'wfobjs'):
-                self.vreg.load_file(join(etdirectory, '%s.py' % modname),
-                                    'cubicweb.entities.%s' % modname)
-            hooksdirectory = join(CW_SOFTWARE_ROOT, 'hooks')
-            self.vreg.load_file(join(hooksdirectory, 'metadata.py'),
-                                'cubicweb.hooks.metadata')
-        elif config.read_instance_schema:
+            self.set_schema(config.load_schema())
+        else:
             # normal start: load the instance schema from the database
             self.fill_schema()
-        else:
-            # test start: use the file system schema (quicker)
-            self.warning("set fs instance'schema")
-            config.bootstrap_cubes()
-            self.set_schema(config.load_schema())
         if not config.creating:
             self.init_sources_from_database()
             if 'CWProperty' in self.schema:
@@ -767,10 +754,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 +908,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 +1213,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 +1255,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 +1541,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 +1552,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	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/serverctl.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/server/session.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/session.py	Wed Jan 09 16:06:20 2013 +0100
@@ -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, server
 from cubicweb.req import RequestSessionBase
 from cubicweb.dbapi import ConnectionProperties
-from cubicweb.utils import make_uid, RepeatList
+from cubicweb.utils import make_uid
 from cubicweb.rqlrewrite import RQLRewriter
 from cubicweb.server import ShuttingDown
 from cubicweb.server.edition import EditedEntity
 
 
-ETYPE_PYOBJ_MAP[Binary] = 'Bytes'
-
 NO_UNDO_TYPES = schema.SCHEMA_TYPES.copy()
 NO_UNDO_TYPES.add('CWCache')
 # is / is_instance_of are usually added by sql hooks except when using
@@ -55,25 +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()
@@ -464,28 +449,6 @@
             self.cnxset.reconnect(source)
             return source.doexec(self, sql, args, rollback=rollback_on_failure)
 
-    def set_language(self, language):
-        """i18n configuration for translation"""
-        language = language or self.user.property_value('ui.language')
-        try:
-            gettext, pgettext = self.vreg.config.translations[language]
-            self._ = self.__ = gettext
-            self.pgettext = pgettext
-        except KeyError:
-            language = self.vreg.property_value('ui.language')
-            try:
-                gettext, pgettext = self.vreg.config.translations[language]
-                self._ = self.__ = gettext
-                self.pgettext = pgettext
-            except KeyError:
-                self._ = self.__ = unicode
-                self.pgettext = lambda x, y: y
-        self.lang = language
-
-    def change_property(self, prop, value):
-        assert prop == 'lang' # this is the only one changeable property for now
-        self.set_language(value)
-
     def deleted_in_transaction(self, eid):
         """return True if the entity of the given eid is being deleted in the
         current transaction
@@ -509,7 +472,7 @@
 
     DEFAULT_SECURITY = object() # evaluated to true by design
 
-    def security_enabled(self, read=False, write=False):
+    def security_enabled(self, read=None, write=None):
         return security_enabled(self, read=read, write=write)
 
     def init_security(self, read, write):
@@ -976,16 +939,21 @@
         # information:
         # - processed by the precommit/commit event or not
         # - if processed, is it the failed operation
+        debug = server.DEBUG & server.DBG_OPS
         try:
             # by default, operations are executed with security turned off
             with security_enabled(self, False, False):
                 processed = []
                 self.commit_state = 'precommit'
+                if debug:
+                    print self.commit_state, '*' * 20
                 try:
                     while self.pending_operations:
                         operation = self.pending_operations.pop(0)
                         operation.processed = 'precommit'
                         processed.append(operation)
+                        if debug:
+                            print operation
                         operation.handle_event('precommit_event')
                     self.pending_operations[:] = processed
                     self.debug('precommit session %s done', self.id)
@@ -1001,7 +969,11 @@
                     # instead of having to implements rollback, revertprecommit
                     # and revertcommit, that will be enough in mont case.
                     operation.failed = True
+                    if debug:
+                        print self.commit_state, '*' * 20
                     for operation in reversed(processed):
+                        if debug:
+                            print operation
                         try:
                             operation.handle_event('revertprecommit_event')
                         except BaseException:
@@ -1014,8 +986,12 @@
                     raise
                 self.cnxset.commit()
                 self.commit_state = 'postcommit'
+                if debug:
+                    print self.commit_state, '*' * 20
                 while self.pending_operations:
                     operation = self.pending_operations.pop(0)
+                    if debug:
+                        print operation
                     operation.processed = 'postcommit'
                     try:
                         operation.handle_event('postcommit_event')
@@ -1154,71 +1130,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)')
@@ -1317,6 +1228,24 @@
             return 'en'
         return None
 
+    def prefered_language(self, language=None):
+        # mock CWUser.prefered_language, mainly for testing purpose
+        return self.property_value('ui.language')
+
+    # CWUser compat for notification ###########################################
+
+    def name(self):
+        return 'cubicweb'
+
+    class _IEmailable:
+        @staticmethod
+        def get_email():
+            return ''
+
+    def cw_adapt_to(self, iface):
+        if iface == 'IEmailable':
+            return self._IEmailable
+        return None
 
 from logging import getLogger
 from cubicweb import set_log_methods
--- a/server/sources/__init__.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/sources/__init__.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/server/sources/datafeed.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/sources/datafeed.py	Wed Jan 09 16:06:20 2013 +0100
@@ -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	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/sources/native.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -961,6 +961,14 @@
             cnx.commit()
             return eid
 
+    def _handle_is_relation_sql(self, session, sql, attrs):
+        """ Handler for specific is_relation sql that may be
+        overwritten in some stores"""
+        self.doexec(session, sql % attrs)
+
+    _handle_insert_entity_sql = doexec
+    _handle_is_instance_of_sql = _handle_source_relation_sql = _handle_is_relation_sql
+
     def add_info(self, session, entity, source, extid, complete):
         """add type and source info for an eid into the system table"""
         # begin by inserting eid/type/source/extid into the entities table
@@ -970,21 +978,22 @@
         uri = 'system' if source.copy_based_source else source.uri
         attrs = {'type': entity.__regid__, 'eid': entity.eid, 'extid': extid,
                  'source': uri, 'asource': source.uri, 'mtime': datetime.utcnow()}
-        self.doexec(session, self.sqlgen.insert('entities', attrs), attrs)
+        self._handle_insert_entity_sql(session, self.sqlgen.insert('entities', attrs), attrs)
         # insert core relations: is, is_instance_of and cw_source
         try:
-            self.doexec(session, 'INSERT INTO is_relation(eid_from,eid_to) VALUES (%s,%s)'
-                        % (entity.eid, eschema_eid(session, entity.e_schema)))
+            self._handle_is_relation_sql(session, 'INSERT INTO is_relation(eid_from,eid_to) VALUES (%s,%s)',
+                                         (entity.eid, eschema_eid(session, entity.e_schema)))
         except IndexError:
             # during schema serialization, skip
             pass
         else:
             for eschema in entity.e_schema.ancestors() + [entity.e_schema]:
-                self.doexec(session, 'INSERT INTO is_instance_of_relation(eid_from,eid_to) VALUES (%s,%s)'
-                           % (entity.eid, eschema_eid(session, eschema)))
+                self._handle_is_relation_sql(session,
+                                             'INSERT INTO is_instance_of_relation(eid_from,eid_to) VALUES (%s,%s)',
+                                             (entity.eid, eschema_eid(session, eschema)))
         if 'CWSource' in self.schema and source.eid is not None: # else, cw < 3.10
-            self.doexec(session, 'INSERT INTO cw_source_relation(eid_from,eid_to) '
-                        'VALUES (%s,%s)' % (entity.eid, source.eid))
+            self._handle_is_relation_sql(session, 'INSERT INTO cw_source_relation(eid_from,eid_to) VALUES (%s,%s)',
+                                         (entity.eid, source.eid))
         # now we can update the full text index
         if self.do_fti and self.need_fti_indexation(entity.__regid__):
             if complete:
--- a/server/sources/storages.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/sources/storages.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/server/ssplanner.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/ssplanner.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -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	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_checkintegrity.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -29,8 +29,9 @@
         handler = get_test_db_handler(TestServerConfiguration(apphome=self.datadir))
         handler.build_db_cache()
         self.repo, self.cnx = handler.get_repo_and_cnx()
-        self.execute = self.cnx.cursor().execute
-        self.session = self.repo._sessions[self.cnx.sessionid]
+        session = self.repo._get_session(self.cnx.sessionid, setcnxset=True)
+        self.session = session
+        self.execute = session.execute
         sys.stderr = sys.stdout = StringIO()
 
     def tearDown(self):
--- a/server/test/unittest_datafeed.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_datafeed.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2011-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/server/test/unittest_hook.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_hook.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/server/test/unittest_msplanner.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_msplanner.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/server/test/unittest_multisources.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_multisources.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/server/test/unittest_querier.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_querier.py	Wed Jan 09 16:06:20 2013 +0100
@@ -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.session
+        orig_length = len(rset)
+        rset.rows[0][0] = 9999999
+        description = manual_build_descr(rset.req, rset.syntax_tree(), None, rset.rows)
+        self.assertEqual(len(description), orig_length - 1)
+        self.assertEqual(len(rset.rows), orig_length - 1)
+        self.assertNotEqual(rset.rows[0][0], 9999999)
+
+    def test_build_descr2(self):
+        rset = self.execute('Any X,Y WITH X,Y BEING ((Any G,NULL WHERE G is CWGroup) UNION (Any U,G WHERE U in_group G))')
+        for x, y in rset.description:
+            if y is not None:
+                self.assertEqual(y, 'CWGroup')
+
+    def test_build_descr3(self):
+        rset = self.execute('(Any G,NULL WHERE G is CWGroup) UNION (Any U,G WHERE U in_group G)')
+        for x, y in rset.description:
+            if y is not None:
+                self.assertEqual(y, 'CWGroup')
+
 
 class QuerierTC(BaseQuerierTC):
     setUpClass = classmethod(setUpClass)
--- a/server/test/unittest_repository.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_repository.py	Wed Jan 09 16:06:20 2013 +0100
@@ -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_security.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_security.py	Wed Jan 09 16:06:20 2013 +0100
@@ -473,9 +473,9 @@
             anon = cu.connection.user(self.session)
             # anonymous user can only read itself
             rset = cu.execute('Any L WHERE X owned_by U, U login L')
-            self.assertEqual(rset.rows, [['anon']])
+            self.assertEqual([['anon']], rset.rows)
             rset = cu.execute('CWUser X')
-            self.assertEqual(rset.rows, [[anon.eid]])
+            self.assertEqual([[anon.eid]], rset.rows)
             # anonymous user can read groups (necessary to check allowed transitions for instance)
             self.assert_(cu.execute('CWGroup X'))
             # should only be able to read the anonymous user, not another one
@@ -488,7 +488,7 @@
             #                  {'x': self.user.eid})
 
             rset = cu.execute('CWUser X WHERE X eid %(x)s', {'x': anon.eid})
-            self.assertEqual(rset.rows, [[anon.eid]])
+            self.assertEqual([[anon.eid]], rset.rows)
             # but can't modify it
             cu.execute('SET X login "toto" WHERE X eid %(x)s', {'x': anon.eid})
             self.assertRaises(Unauthorized, self.commit)
--- a/server/test/unittest_session.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_session.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -17,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	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_storage.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -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()
@@ -118,7 +118,7 @@
     def test_bfss_fs_importing_doesnt_touch_path(self):
         self.session.transaction_data['fs_importing'] = True
         filepath = osp.abspath(__file__)
-        f1 = self.session.create_entity('File', data=Binary(filepath),
+        f1 = self.request().create_entity('File', data=Binary(filepath),
                                         data_format=u'text/plain', data_name=u'foo')
         self.assertEqual(self.fspath(f1), filepath)
 
@@ -196,15 +196,18 @@
         filepath = osp.abspath(__file__)
         f1 = self.session.create_entity('File', data=Binary(filepath),
                                         data_format=u'text/plain', data_name=u'foo')
-        self.assertEqual(f1.data.getvalue(), file(filepath).read(),
-                          'files content differ')
+        cw_value = f1.data.getvalue()
+        fs_value = file(filepath).read()
+        if cw_value != fs_value:
+            self.fail('cw value %r is different from file content' % cw_value)
+
 
     @tag('update')
     def test_bfss_update_with_existing_data(self):
         # use self.session to use server-side cache
         f1 = self.session.create_entity('File', data=Binary('some data'),
                                         data_format=u'text/plain', data_name=u'foo')
-        # NOTE: do not use set_attributes() which would automatically
+        # NOTE: do not use cw_set() which would automatically
         #       update f1's local dict. We want the pure rql version to work
         self.execute('SET F data %(d)s WHERE F eid %(f)s',
                      {'d': Binary('some other data'), 'f': f1.eid})
@@ -218,7 +221,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 +243,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 +268,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	Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_undo.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -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.translate(unicode)
         self.assertEqual(cm.exception.entity, self.toto.eid)
         self.assertEqual(cm.exception.errors,
                           {'in_group-subject': u'at least one relation in_group is '
@@ -265,7 +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	Wed Jan 09 16:04:26 2013 +0100
+++ b/sobjects/ldapparser.py	Wed Jan 09 16:06:20 2013 +0100
@@ -97,7 +97,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):
@@ -131,7 +131,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, extidplus, etype, eid):
@@ -160,9 +160,9 @@
                 email = self.extid2entity(emailextid, 'EmailAddress',
                                           address=emailaddr)
                 if entity.primary_email:
-                    entity.set_relations(use_email=email)
+                    entity.cw_set(use_email=email)
                 else:
-                    entity.set_relations(primary_email=email)
+                    entity.cw_set(primary_email=email)
             elif self.sourceuris:
                 # pop from sourceuris anyway, else email may be removed by the
                 # source once import is finished
--- a/test/unittest_cwconfig.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/test/unittest_cwconfig.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -101,10 +101,10 @@
         self.assertEqual(self.config.expand_cubes(('email', 'comment')),
                           ['email', 'comment', 'file'])
 
-    def test_vregistry_path(self):
+    def test_appobjects_path(self):
         self.config.__class__.CUBES_PATH = [CUSTOM_CUBES_DIR]
         self.config.adjust_sys_path()
-        self.assertEqual([unabsolutize(p) for p in self.config.vregistry_path()],
+        self.assertEqual([unabsolutize(p) for p in self.config.appobjects_path()],
                           ['entities', 'web/views', 'sobjects', 'hooks',
                            'file/entities', 'file/views.py', 'file/hooks',
                            'email/entities.py', 'email/views', 'email/hooks.py',
--- a/test/unittest_dbapi.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/test/unittest_dbapi.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/test/unittest_entity.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/test/unittest_entity.py	Wed Jan 09 16:06:20 2013 +0100
@@ -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	Wed Jan 09 16:04:26 2013 +0100
+++ b/test/unittest_migration.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -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_rset.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/test/unittest_rset.py	Wed Jan 09 16:06:20 2013 +0100
@@ -112,6 +112,17 @@
         #                  '%stask/title/go' % baseurl)
         # empty _restpath should not crash
         self.compare_urls(req.build_url('view', _restpath=''), baseurl)
+        self.assertNotIn('https', req.build_url('view', vid='foo', rql='yo',
+                                                  __secure__=True))
+        try:
+            self.config.global_set_option('https-url', 'https://testing.fr/')
+            self.assertTrue('https', req.build_url('view', vid='foo', rql='yo',
+                                                     __secure__=True))
+            self.compare_urls(req.build_url('view', vid='foo', rql='yo',
+                                            __secure__=True),
+                              '%sview?vid=foo&rql=yo' % req.base_url(secure=True))
+        finally:
+            self.config.global_set_option('https-url', None)
 
 
     def test_build(self):
--- a/test/unittest_schema.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/test/unittest_schema.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -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	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/application.py	Wed Jan 09 16:06:20 2013 +0100
@@ -462,7 +462,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)
@@ -486,7 +485,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:
@@ -517,7 +516,7 @@
         return ''
 
     def validation_error_handler(self, req, ex):
-        ex.errors = dict((k, v) for k, v in ex.errors.items())
+        ex.translate(req._) # translate messages using ui language
         if '__errorurl' in req.form:
             forminfo = {'error': ex,
                         'values': req.form,
@@ -532,6 +531,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	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/data/cubicweb.ajax.js	Wed Jan 09 16:06:20 2013 +0100
@@ -70,7 +70,7 @@
                 callback.apply(null, args);
             }
         } catch(error) {
-            this.error(this.xhr, null, error);
+            this.error(this._req, null, error);
         }
     },
 
@@ -704,7 +704,7 @@
     var ajaxArgs = ['render', formparams, registry, compid];
     ajaxArgs = ajaxArgs.concat(cw.utils.sliceList(arguments, 4));
     var params = ajaxFuncArgs.apply(null, ajaxArgs);
-    return $('#'+domid).loadxhtml(AJAX_BASE_URL, params, null, 'swap');
+    return $('#'+domid).loadxhtml(AJAX_BASE_URL, params, null, 'swap', true);
 }
 
 /* ajax tabs ******************************************************************/
--- a/web/data/cubicweb.css	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/data/cubicweb.css	Wed Jan 09 16:06:20 2013 +0100
@@ -545,6 +545,16 @@
   padding-left: 2em;
 }
 
+/* actions around tables */
+.tableactions span {
+  padding: 0 18px;
+  height: 24px;
+  background: #F8F8F8;
+  border: 1px solid #DFDFDF;
+  border-bottom: none;
+  border-radius: 4px 4px 0 0;
+}
+
 /* custom boxes */
 
 .search_box div.boxBody {
--- a/web/data/cubicweb.facets.js	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/data/cubicweb.facets.js	Wed Jan 09 16:06:20 2013 +0100
@@ -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	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/data/cubicweb.old.css	Wed Jan 09 16:06:20 2013 +0100
@@ -899,6 +899,16 @@
   padding-left: 0.5em;
 }
 
+/* actions around tables */
+.tableactions span {
+  padding: 0 18px;
+  height: 24px;
+  background: #F8F8F8;
+  border: 1px solid #DFDFDF;
+  border-bottom: none;
+  border-radius: 4px 4px 0 0;
+}
+
 /***************************************/
 /* error view (views/management.py)    */
 /***************************************/
--- a/web/httpcache.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/httpcache.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/web/request.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/request.py	Wed Jan 09 16:06:20 2013 +0100
@@ -170,7 +170,6 @@
     @property
     def authmode(self):
         """Authentification mode of the instance
-
         (see :ref:`WebServerConfig`)"""
         return self.vreg.config['auth-mode']
 
@@ -227,14 +226,6 @@
         # 3. default language
         self.set_default_language(vreg)
 
-    def set_language(self, lang):
-        gettext, self.pgettext = self.translations[lang]
-        self._ = self.__ = gettext
-        self.lang = lang
-        self.debug('request language: %s', lang)
-        if self.cnx:
-            self.cnx.set_session_props(lang=lang)
-
     # input form parameters management ########################################
 
     # common form parameters which should be protected against html values
@@ -366,7 +357,7 @@
     def update_search_state(self):
         """update the current search state"""
         searchstate = self.form.get('__mode')
-        if not searchstate and self.cnx:
+        if not searchstate:
             searchstate = self.session.data.get('search_state', 'normal')
         self.set_search_state(searchstate)
 
@@ -377,8 +368,7 @@
         else:
             self.search_state = ('linksearch', searchstate.split(':'))
             assert len(self.search_state[-1]) == 4
-        if self.cnx:
-            self.session.data['search_state'] = searchstate
+        self.session.data['search_state'] = searchstate
 
     def match_search_state(self, rset):
         """when searching an entity to create a relation, return True if entities in
--- a/web/test/data/views.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/test/data/views.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -15,9 +15,7 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""
 
-"""
 from cubicweb.web import Redirect
 from cubicweb.web.application import CubicWebPublisher
 
--- a/web/test/unittest_application.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/test/unittest_application.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/web/test/unittest_magicsearch.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/test/unittest_magicsearch.py	Wed Jan 09 16:06:20 2013 +0100
@@ -230,5 +230,118 @@
         self.assertEqual(rset.rql, 'Any X ORDERBY FTIRANK(X) DESC WHERE X has_text %(text)s')
         self.assertEqual(rset.args, {'text': u'utilisateur Smith'})
 
+
+class RQLSuggestionsBuilderTC(CubicWebTC):
+    def suggestions(self, rql):
+        req = self.request()
+        rbs = self.vreg['components'].select('rql.suggestions', req)
+        return rbs.build_suggestions(rql)
+
+    def test_no_restrictions_rql(self):
+        self.assertListEqual([], self.suggestions(''))
+        self.assertListEqual([], self.suggestions('An'))
+        self.assertListEqual([], self.suggestions('Any X'))
+        self.assertListEqual([], self.suggestions('Any X, Y'))
+
+    def test_invalid_rql(self):
+        self.assertListEqual([], self.suggestions('blabla'))
+        self.assertListEqual([], self.suggestions('Any X WHERE foo, bar'))
+
+    def test_is_rql(self):
+        self.assertListEqual(['Any X WHERE X is %s' % eschema
+                              for eschema in sorted(self.vreg.schema.entities())
+                              if not eschema.final],
+                             self.suggestions('Any X WHERE X is'))
+
+        self.assertListEqual(['Any X WHERE X is Personne', 'Any X WHERE X is Project'],
+                             self.suggestions('Any X WHERE X is P'))
+
+        self.assertListEqual(['Any X WHERE X is Personne, Y is Personne',
+                              'Any X WHERE X is Personne, Y is Project'],
+                             self.suggestions('Any X WHERE X is Personne, Y is P'))
+
+
+    def test_relations_rql(self):
+        self.assertListEqual(['Any X WHERE X is Personne, X ass A',
+                              'Any X WHERE X is Personne, X datenaiss A',
+                              'Any X WHERE X is Personne, X description A',
+                              'Any X WHERE X is Personne, X fax A',
+                              'Any X WHERE X is Personne, X nom A',
+                              'Any X WHERE X is Personne, X prenom A',
+                              'Any X WHERE X is Personne, X promo A',
+                              'Any X WHERE X is Personne, X salary A',
+                              'Any X WHERE X is Personne, X sexe A',
+                              'Any X WHERE X is Personne, X tel A',
+                              'Any X WHERE X is Personne, X test A',
+                              'Any X WHERE X is Personne, X titre A',
+                              'Any X WHERE X is Personne, X travaille A',
+                              'Any X WHERE X is Personne, X web A',
+                              ],
+                             self.suggestions('Any X WHERE X is Personne, X '))
+        self.assertListEqual(['Any X WHERE X is Personne, X tel A',
+                              'Any X WHERE X is Personne, X test A',
+                              'Any X WHERE X is Personne, X titre A',
+                              'Any X WHERE X is Personne, X travaille A',
+                              ],
+                             self.suggestions('Any X WHERE X is Personne, X t'))
+        # try completion on selected
+        self.assertListEqual(['Any X WHERE X is Personne, Y is Societe, X tel A',
+                              'Any X WHERE X is Personne, Y is Societe, X test A',
+                              'Any X WHERE X is Personne, Y is Societe, X titre A',
+                              'Any X WHERE X is Personne, Y is Societe, X travaille Y',
+                              ],
+                             self.suggestions('Any X WHERE X is Personne, Y is Societe, X t'))
+        # invalid relation should not break
+        self.assertListEqual([],
+                             self.suggestions('Any X WHERE X is Personne, X asdasd'))
+
+    def test_attribute_vocabulary_rql(self):
+        self.assertListEqual(['Any X WHERE X is Personne, X promo "bon"',
+                              'Any X WHERE X is Personne, X promo "pasbon"',
+                              ],
+                             self.suggestions('Any X WHERE X is Personne, X promo "'))
+        self.assertListEqual(['Any X WHERE X is Personne, X promo "pasbon"',
+                              ],
+                             self.suggestions('Any X WHERE X is Personne, X promo "p'))
+        # "bon" should be considered complete, hence no suggestion
+        self.assertListEqual([],
+                             self.suggestions('Any X WHERE X is Personne, X promo "bon"'))
+        # no valid vocabulary starts with "po"
+        self.assertListEqual([],
+                             self.suggestions('Any X WHERE X is Personne, X promo "po'))
+
+    def test_attribute_value_rql(self):
+        # suggestions should contain any possible value for
+        # a given attribute (limited to 10)
+        req = self.request()
+        for i in xrange(15):
+            req.create_entity('Personne', nom=u'n%s' % i, prenom=u'p%s' % i)
+        self.assertListEqual(['Any X WHERE X is Personne, X nom "n0"',
+                              'Any X WHERE X is Personne, X nom "n1"',
+                              'Any X WHERE X is Personne, X nom "n10"',
+                              'Any X WHERE X is Personne, X nom "n11"',
+                              'Any X WHERE X is Personne, X nom "n12"',
+                              'Any X WHERE X is Personne, X nom "n13"',
+                              'Any X WHERE X is Personne, X nom "n14"',
+                              'Any X WHERE X is Personne, X nom "n2"',
+                              'Any X WHERE X is Personne, X nom "n3"',
+                              'Any X WHERE X is Personne, X nom "n4"',
+                              'Any X WHERE X is Personne, X nom "n5"',
+                              'Any X WHERE X is Personne, X nom "n6"',
+                              'Any X WHERE X is Personne, X nom "n7"',
+                              'Any X WHERE X is Personne, X nom "n8"',
+                              'Any X WHERE X is Personne, X nom "n9"',
+                              ],
+                             self.suggestions('Any X WHERE X is Personne, X nom "'))
+        self.assertListEqual(['Any X WHERE X is Personne, X nom "n1"',
+                              'Any X WHERE X is Personne, X nom "n10"',
+                              'Any X WHERE X is Personne, X nom "n11"',
+                              'Any X WHERE X is Personne, X nom "n12"',
+                              'Any X WHERE X is Personne, X nom "n13"',
+                              'Any X WHERE X is Personne, X nom "n14"',
+                              ],
+                             self.suggestions('Any X WHERE X is Personne, X nom "n1'))
+
+
 if __name__ == '__main__':
     unittest_main()
--- a/web/test/unittest_reledit.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/test/unittest_reledit.py	Wed Jan 09 16:06:20 2013 +0100
@@ -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	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/test/unittest_urlrewrite.py	Wed Jan 09 16:06:20 2013 +0100
@@ -60,7 +60,6 @@
             ('/doc/images/(.+?)/?$', dict(fid='\\1', vid='wdocimages')),
             ('/doc/?$', dict(fid='main', vid='wdoc')),
             ('/doc/(.+?)/?$', dict(fid='\\1', vid='wdoc')),
-            ('/changelog/?$', dict(vid='changelog')),
             # now in SchemaBasedRewriter
             #('/search/(.+)$', dict(rql=r'Any X WHERE X has_text "\1"')),
             ])
@@ -105,9 +104,9 @@
     def setup_database(self):
         req = self.request()
         self.p1 = self.create_user(req, u'user1')
-        self.p1.set_attributes(firstname=u'joe', surname=u'Dalton')
+        self.p1.cw_set(firstname=u'joe', surname=u'Dalton')
         self.p2 = self.create_user(req, u'user2')
-        self.p2.set_attributes(firstname=u'jack', surname=u'Dalton')
+        self.p2.cw_set(firstname=u'jack', surname=u'Dalton')
 
     def test_rgx_action_with_transforms(self):
         class TestSchemaBasedRewriter(SchemaBasedRewriter):
--- a/web/test/unittest_views_basecontrollers.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/test/unittest_views_basecontrollers.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -77,6 +77,7 @@
                     }
         with self.assertRaises(ValidationError) as cm:
             self.ctrl_publish(req)
+        cm.exception.translate(unicode)
         self.assertEqual(cm.exception.errors, {'login-subject': 'the value "admin" is already used, use another one'})
 
     def test_user_editing_itself(self):
@@ -249,6 +250,7 @@
                 }
         with self.assertRaises(ValidationError) as cm:
             self.ctrl_publish(req)
+        cm.exception.translate(unicode)
         self.assertEqual(cm.exception.errors, {'amount-subject': 'value -10 must be >= 0'})
         req = self.request(rollbackfirst=True)
         req.form = {'eid': ['X'],
@@ -259,6 +261,7 @@
                     }
         with self.assertRaises(ValidationError) as cm:
             self.ctrl_publish(req)
+        cm.exception.translate(unicode)
         self.assertEqual(cm.exception.errors, {'amount-subject': 'value 110 must be <= 100'})
         req = self.request(rollbackfirst=True)
         req.form = {'eid': ['X'],
--- a/web/test/unittest_views_basetemplates.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/test/unittest_views_basetemplates.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/web/test/unittest_views_searchrestriction.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/test/unittest_views_searchrestriction.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/web/test/unittest_viewselector.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/test/unittest_viewselector.py	Wed Jan 09 16:06:20 2013 +0100
@@ -39,7 +39,6 @@
                actions.LogoutAction]
 SITEACTIONS = [actions.ManageAction]
 FOOTERACTIONS = [wdoc.HelpAction,
-                 wdoc.ChangeLogAction,
                  wdoc.AboutAction,
                  actions.PoweredByAction]
 MANAGEACTIONS = [actions.SiteConfigurationAction,
@@ -93,8 +92,7 @@
     def test_possible_views_none_rset(self):
         req = self.request()
         self.assertListEqual(self.pviews(req, None),
-                             [('changelog', wdoc.ChangeLogView),
-                              ('cw.sources-management', cwsources.CWSourcesManagementView),
+                             [('cw.sources-management', cwsources.CWSourcesManagementView),
                               ('cw.users-and-groups-management', cwuser.UsersAndGroupsManagementView),
                               ('gc', debug.GCView),
                               ('index', startup.IndexView),
--- a/web/views/ajaxcontroller.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/ajaxcontroller.py	Wed Jan 09 16:06:20 2013 +0100
@@ -28,7 +28,7 @@
 functions that can be called from the javascript world.
 
 To register a new remote function, either decorate your function
-with the :func:`cubicweb.web.views.ajaxcontroller.ajaxfunc` decorator:
+with the :func:`~cubicweb.web.views.ajaxcontroller.ajaxfunc` decorator:
 
 .. sourcecode:: python
 
@@ -39,7 +39,7 @@
     def list_users(self):
         return [u for (u,) in self._cw.execute('Any L WHERE U login L')]
 
-or inherit from :class:`cubicwbe.web.views.ajaxcontroller.AjaxFunction` and
+or inherit from :class:`~cubicweb.web.views.ajaxcontroller.AjaxFunction` and
 implement the ``__call__`` method:
 
 .. sourcecode:: python
--- a/web/views/authentication.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/authentication.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/web/views/basecomponents.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/basecomponents.py	Wed Jan 09 16:06:20 2013 +0100
@@ -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	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/basecontrollers.py	Wed Jan 09 16:06:20 2013 +0100
@@ -191,6 +191,7 @@
 
 def _validation_error(req, ex):
     req.cnx.rollback()
+    ex.translate(req._) # translate messages using ui language
     # XXX necessary to remove existant validation error?
     # imo (syt), it's not necessary
     req.session.data.pop(req.form.get('__errorurl'), None)
--- a/web/views/editcontroller.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/editcontroller.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/web/views/facets.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/facets.py	Wed Jan 09 16:06:20 2013 +0100
@@ -26,6 +26,7 @@
 from logilab.common.decorators import cachedproperty
 from logilab.common.registry import objectify_predicate, yes
 
+from cubicweb import tags
 from cubicweb.predicates import (non_final_entity, multi_lines_rset,
                                  match_context_prop, relation_possible)
 from cubicweb.utils import json_dumps
@@ -234,6 +235,7 @@
             vid = req.form.get('vid')
         if self.bk_linkbox_template and req.vreg.schema['Bookmark'].has_perm(req, 'add'):
             w(self.bookmark_link(rset))
+        w(self.focus_link(rset))
         hiddens = {}
         for param in ('subvid', 'vtitle'):
             if param in req.form:
@@ -269,6 +271,9 @@
                 req._('bookmark this search'))
         return self.bk_linkbox_template % bk_link
 
+    def focus_link(self, rset):
+        return self.bk_linkbox_template % tags.a(self._cw._('focus on this selection'),
+                                                 href=self._cw.url(), id='focusLink')
 
 class FilterTable(FacetFilterMixIn, AnyRsetView):
     __regid__ = 'facet.filtertable'
--- a/web/views/magicsearch.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/magicsearch.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -15,19 +15,23 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""a query processor to handle quick search shortcuts for cubicweb"""
+"""a query processor to handle quick search shortcuts for cubicweb
+"""
 
 __docformat__ = "restructuredtext en"
 
 import re
 from logging import getLogger
-from warnings import warn
+
+from yams.interfaces import IVocabularyConstraint
 
 from rql import RQLSyntaxError, BadRQLQuery, parse
+from rql.utils import rqlvar_maker
 from rql.nodes import Relation
 
 from cubicweb import Unauthorized, typed_eid
 from cubicweb.view import Component
+from cubicweb.web.views.ajaxcontroller import ajaxfunc
 
 LOGGER = getLogger('cubicweb.magicsearch')
 
@@ -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	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/sessions.py	Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/web/views/startup.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/startup.py	Wed Jan 09 16:06:20 2013 +0100
@@ -51,7 +51,7 @@
     title = _('manage')
     http_cache_manager = httpcache.EtagHTTPCacheManager
     add_etype_links = ()
-    skip_startup_views = set( ('index', 'manage', 'schema', 'owl', 'changelog',
+    skip_startup_views = set( ('index', 'manage', 'schema', 'owl', 
                                'systempropertiesform', 'propertiesform',
                                'loggedout', 'login',
                                'cw.users-and-groups-management', 'cw.groups-management', 
--- a/web/views/tableview.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/tableview.py	Wed Jan 09 16:06:20 2013 +0100
@@ -290,20 +290,17 @@
         return attrs
 
     def render_actions(self, w, actions):
-        box = MenuWidget('', '', _class='tableActionsBox', islist=False)
-        label = tags.span(self._cw._('action menu'))
-        menu = PopupBoxMenu(label, isitem=False, link_class='actionsBox',
-                            ident='%sActions' % self.view.domid)
-        box.append(menu)
+        w(u'<div class="tableactions">')
         for action in actions:
-            menu.append(action)
-        box.render(w=w)
-        w(u'<div class="clear"></div>')
+            w(u'<span>')
+            action.render(w)
+            w(u'</span>')
+        w(u'</div>')
 
     def show_hide_filter_actions(self, currentlydisplayed=False):
         divid = self.view.domid
         showhide = u';'.join(toggle_action('%s%s' % (divid, what))[11:]
-                             for what in ('Form', 'Show', 'Hide', 'Actions'))
+                             for what in ('Form', 'Actions'))
         showhide = 'javascript:' + showhide
         self._cw.add_onload(u'''\
 $(document).ready(function() {
@@ -313,10 +310,8 @@
     $('#%(id)sShow').attr('class', 'hidden');
   }
 });''' % {'id': divid})
-        showlabel = self._cw._('show filter form')
-        hidelabel = self._cw._('hide filter form')
-        return [component.Link(showhide, showlabel, id='%sShow' % divid),
-                component.Link(showhide, hidelabel, id='%sHide' % divid)]
+        showlabel = self._cw._('toggle filter')
+        return [component.Link(showhide, showlabel, id='%sToggle' % divid)]
 
 
 class AbstractColumnRenderer(object):
--- a/web/views/urlrewrite.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/urlrewrite.py	Wed Jan 09 16:06:20 2013 +0100
@@ -109,7 +109,6 @@
         (rgx('/doc/images/(.+?)/?'), dict(vid='wdocimages', fid=r'\1')),
         (rgx('/doc/?'), dict(vid='wdoc', fid=r'main')),
         (rgx('/doc/(.+?)/?'), dict(vid='wdoc', fid=r'\1')),
-        (rgx('/changelog/?'), dict(vid='changelog')),
         ]
 
     def rewrite(self, req, uri):
--- a/web/views/wdoc.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/wdoc.py	Wed Jan 09 16:06:20 2013 +0100
@@ -205,60 +205,6 @@
         self.w(open(join(resourcedir, rid)).read())
 
 
-class ChangeLogView(StartupView):
-    __regid__ = 'changelog'
-    title = _('What\'s new?')
-    maxentries = 25
-
-    def call(self):
-        rid = 'ChangeLog_%s' % (self._cw.lang)
-        allentries = []
-        title = self._cw._(self.title)
-        restdata = ['.. -*- coding: utf-8 -*-', '', title, '='*len(title), '']
-        w = restdata.append
-        today = date.today()
-        for fpath in self._cw.vreg.config.locate_all_files(rid):
-            cl = ChangeLog(fpath)
-            encoding = 'utf-8'
-            # additional content may be found in title
-            for line in (cl.title + cl.additional_content).splitlines():
-                m = CHARSET_DECL_RGX.search(line)
-                if m is not None:
-                    encoding = m.group(1)
-                    continue
-                elif line.startswith('.. '):
-                    w(unicode(line, encoding))
-            for entry in cl.entries:
-                if entry.date:
-                    edate = todate(strptime(entry.date, '%Y-%m-%d'))
-                else:
-                    edate = today
-                messages = []
-                for msglines, submsgs in entry.messages:
-                    msgstr = unicode(' '.join(l.strip() for l in msglines), encoding)
-                    msgstr += u'\n\n'
-                    for submsglines in submsgs:
-                        msgstr += '     - ' + unicode(' '.join(l.strip() for l in submsglines), encoding)
-                        msgstr += u'\n'
-                    messages.append(msgstr)
-                entry = (edate, messages)
-                allentries.insert(bisect_right(allentries, entry), entry)
-        latestdate = None
-        i = 0
-        for edate, messages in reversed(allentries):
-            if latestdate != edate:
-                fdate = self._cw.format_date(edate)
-                w(u'\n%s' % fdate)
-                w('~' * len(fdate))
-                latestdate = edate
-            for msg in messages:
-                w(u'* %s' % msg)
-                i += 1
-                if i > self.maxentries:
-                    break
-        w('') # blank line
-        self.w(rest_publish(self, '\n'.join(restdata)))
-
 
 class HelpAction(action.Action):
     __regid__ = 'help'
@@ -271,17 +217,6 @@
     def url(self):
         return self._cw.build_url('doc/main')
 
-class ChangeLogAction(action.Action):
-    __regid__ = 'changelog'
-    __select__ = yes()
-
-    category = 'footer'
-    order = 1
-    title = ChangeLogView.title
-
-    def url(self):
-        return self._cw.build_url('changelog')
-
 
 class AboutAction(action.Action):
     __regid__ = 'about'
--- a/web/wdoc/ChangeLog_en	Wed Jan 09 16:04:26 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-.. -*- coding: utf-8 -*-
-.. _`user preferences`: myprefs
-.. _here: sparql
-.. _SPARQL: http://www.w3.org/TR/rdf-sparql-query/
-.. _schema: schema
-.. _OWL: http://www.w3.org/TR/owl-features/
-.. _CWSource: cwetype/CWSource
-
-2010-10-17 --  3.10.0
-
-  * facets on optional relations now propose to search to entities
-    which miss the relation
-
-  * box and content navigation components have been merged, so this
-    simplify the configuration and you should be able to move
-    boxes/components in various places of the interface
-
-  * global / context dependant boxes should now be more
-    distinguishable to make the interface more intuitive
-
-  * upgraded jQuery and jQuery UI respectively to version 1.4.2 and
-    1.8.
-
-  * data sources are now stored as CWSource_ entities in the database.
-    This allows on multi-sources instance to filter search results
-    according to the source entities are coming from.
-
-
-2010-06-11  --  3.8.4
-   * support full text prefix search for instances using postgres > 8.4 as database: try it
-     by using search such as 'cubic*'
-
-
-2010-04-20  --  3.8.0
-   * nicer schema_ and workflow views (clickable image!)
-
-   * more power to undo, though not yet complete (missing entity updates, soon available...)
-
-   * pdf export functionnality moved to its own cube. If it's no more
-     present on this site while you found it useful, ask you site
-     administrator to install the pdfexport_ cube.
-
-
-2010-03-16  --  3.7.0
-   * experimental support for undoing of deletion. If you're not proposed to *undo*
-     deletion of one or several entities, ask you site administrator to activate
-     the feature.
-
-
-2010-02-10  --  3.6.0
-   * nice 'demo widget' to edit bookmark's path, e.g. a relative url, splitted
-     as path and parameters and dealing nicely with url encodings. Try to
-     edit your bookmarks!
-
-   * hell lot of refactorings, but you should hopefuly not see that from the outside
-
-2009-09-17  --  3.5.0
-
-    * selectable workflows: authorized users may change the workflow used
-      by some workflowable entities
-
-
-2009-08-07  --  3.4.0
-
-    * support for SPARQL_. Click here_ to test it.
-
-    * and another step toward the semantic web: new `ExternalUri` entity type
-      with its associated `same_as` relation. See
-      http://www.w3.org/TR/owl-ref/#sameAs-def for more information and check
-      this instance schema_ to see on which entity types it may be applied
-
-    * new "view workflow" and "view history" items in the workflow
-      sub-menu of the actions box
-
-    * you can now edit comments of workflow transition afterward (if authorized,
-      of course)
-
-    * modification date of an entity is updated when its state is changed
-
-
-2009-02-26  --  3.1.0
-
-    * schema may be exported as OWL_
-
-    * new ajax interface for site configuration / `user preferences`_
-
-
-2008-10-24  --  2.99.0
-    * cubicweb is now open source !
--- a/web/wdoc/ChangeLog_fr	Wed Jan 09 16:04:26 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-.. -*- coding: utf-8 -*-
-.. _`préférences utilisateurs`: myprefs#fieldset_ui
-.. _ici: sparql
-.. _SPARQL: http://www.w3.org/TR/rdf-sparql-query/
-.. _schema: schema
-.. _OWL: http://www.w3.org/TR/owl-features/
-.. _pdfexport: http://www.cubicweb.org/project/cubicweb-pdfexport
-.. _CWSource: cwetype/CWSource
-
-2010-10-07 --  3.10.0
-
-  * les facettes sur les relations optionnelles proposent maintenant
-    de filter les entité qui n'ont *pas* la relation
-
-  * les boîtes et composants contextuels ont été fusionnés, permettant
-    de simplifier la configuration et de placer ces nouveaux composants
-    comme vous le désirez
-
-  * les boîtes globales ou dépendantes du contexte sont plus
-    facilement distinguable pour rendre l'interface plus intuitive
-
-  * passage à jQuery 1.4.2, et jQuery UI 1.8
-
-  * les sources de données sont maintenant stockées dans la base de
-    données sous forme d'entités CWSource_. Cela permet sur les
-    instances multi-source de filter les résultats de recherche en
-    fonction de la source dont viennent les entités.
-
-    
-2010-06-11  --  3.8.4
-   * support pour la recherche de préfixe pour les instances utilisant postgres > 8.4 :
-     essayez en cherchant par ex. 'cubic*'
-
-2010-04-20  --  3.8.0
-
-   * amélioration des vues de schema_ et des vues de workflows
-     (images clickable !)
-
-   * meilleure support du "undo", mais il manque toujours le support
-     sur la modification d'entité (bientôt...)
-
-   * la fonctionnalité d'export d'pdf a été déplacé dans son propre
-     cube. Si cette fonctionalité n'est plus disponible sur ce site et
-     que vous la trouviez utile, demander à l'administrateur
-     d'installer le cube pdfexport_.
-
-
-2010-03-16  --  3.7.0
-
-   * support experimental pour l'annulation ("undo") de la
-     suppression. Si, après une suppression d'une ou plusieurs
-     entités, on ne vous propose pas d'annuler l'opération, demander à
-     l'administrateur d'activé la fonctionnalité
-
-
-2010-02-10  --  3.6.0
-
-   * nouvelle widget (de démonstration :) pour éditer le chemin des
-     signets. Celui-ci, une url relative finalement, est décomposée de
-     chemin et paramètres que vous pouvez éditer individuellement et
-     surtout lisiblement car la gestion de l'échappement de l'url est
-     géré de manière transparente
-
-   * beaucoup de refactoring, mais vous ne devriez rien remarquer :)
-
-2009-09-17  --  3.5.0
-
-    * workflow sélectionnable: les utilisateurs autorisés peuvent
-      changer le workflow à utilister pour les entités le supportant
-
-
-2009-08-07  --  3.4.0
-
-    * support de SPARQL_. Cliquez ici_ pour le tester.
-
-    * et encore un pas vers le web sémantique : un nouveau type d'entité
-      `ExternalUri` et la relation associée `same_as`. Voir
-      http://www.w3.org/TR/owl-ref/#sameAs-def pour plus d'information, ainsi
-      que le schema_ de cette instance pour voir à quels types d'entités cela
-      s'applique.
-
-    * nouveau liens "voir les états possibles" et "voir l'historique" dans le sous-menu
-      workflow de la boite actions
-
-    * vous pouvez dorénavant éditer les commentaires des passages de transition
-      depuis l'historique, pour peu que vous ayez les droits nécessaire bien sûr
-
-    * la date de modification d'une entité est mise à jour lorsque son état est changé
-
-
-2009-02-26  --  3.1.0
-
-    * le schéma peut être exporté en OWL_
-
-    * nouvelle interface ajax pour la configuration du site et les `préférences utilisateurs`_
-
-
-
--- a/wsgi/handler.py	Wed Jan 09 16:04:26 2013 +0100
+++ b/wsgi/handler.py	Wed Jan 09 16:06:20 2013 +0100
@@ -106,8 +106,8 @@
     def __init__(self, config, vreg=None):
         self.appli = CubicWebPublisher(config, vreg=vreg)
         self.config = config
-        self.base_url = config['base-url']
-        self.https_url = config['https-url']
+        self.base_url = self.config['base-url']
+        self.https_url = self.config['https-url']
         self.url_rewriter = self.appli.vreg['components'].select_or_none('urlrewriter')
 
     def _render(self, req):