backport stable branch
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 30 Sep 2009 18:57:42 +0200
changeset 3536 f6c9a5df80fb
parent 3524 a3431f4e2f40 (diff)
parent 3535 106b95ec1144 (current diff)
child 3589 a5432f99f2d9
backport stable branch
entities/test/unittest_wfobjs.py
entities/wfobjs.py
hooks/notification.py
hooks/syncschema.py
hooks/test/unittest_hooks.py
hooks/workflow.py
schemas/workflow.py
sobjects/notification.py
web/formfields.py
web/test/unittest_formfields.py
web/views/editforms.py
--- a/__init__.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/__init__.py	Wed Sep 30 18:57:42 2009 +0200
@@ -7,7 +7,6 @@
 :license: Library General Public License version 2 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
-from cubicweb.__pkginfo__ import version as __version__
 
 import __builtin__
 # '_' is available in builtins to mark internationalized string but should
@@ -19,9 +18,7 @@
 
 import sys, os, logging
 from StringIO import StringIO
-from urllib import quote as urlquote, unquote as urlunquote
 
-from logilab.common.decorators import cached
 from logilab.common.logging_ext import set_log_methods
 
 if os.environ.get('APYCOT_ROOT'):
@@ -29,6 +26,8 @@
 else:
     logging.basicConfig()
 
+from cubicweb.__pkginfo__ import version as __version__
+
 
 set_log_methods(sys.modules[__name__], logging.getLogger('cubicweb'))
 
@@ -56,187 +55,7 @@
                "Binary objects must use raw strings, not %s" % data.__class__
         StringIO.write(self, data)
 
-
-class RequestSessionMixIn(object):
-    """mixin class containing stuff shared by server session and web request
-    """
-    def __init__(self, vreg):
-        self.vreg = vreg
-        try:
-            encoding = vreg.property_value('ui.encoding')
-        except: # no vreg or property not registered
-            encoding = 'utf-8'
-        self.encoding = encoding
-        # cache result of execution for (rql expr / eids),
-        # should be emptied on commit/rollback of the server session / web
-        # connection
-        self.local_perm_cache = {}
-
-    def property_value(self, key):
-        if self.user:
-            return self.user.property_value(key)
-        return self.vreg.property_value(key)
-
-    def etype_rset(self, etype, size=1):
-        """return a fake result set for a particular entity type"""
-        from cubicweb.rset import ResultSet
-        rset = ResultSet([('A',)]*size, '%s X' % etype,
-                         description=[(etype,)]*size)
-        def get_entity(row, col=0, etype=etype, req=self, rset=rset):
-            return req.vreg.etype_class(etype)(req, rset, row, col)
-        rset.get_entity = get_entity
-        return self.decorate_rset(rset)
-
-    def eid_rset(self, eid, etype=None):
-        """return a result set for the given eid without doing actual query
-        (we have the eid, we can suppose it exists and user has access to the
-        entity)
-        """
-        from cubicweb.rset import ResultSet
-        eid = typed_eid(eid)
-        if etype is None:
-            etype = self.describe(eid)[0]
-        rset = ResultSet([(eid,)], 'Any X WHERE X eid %(x)s', {'x': eid},
-                         [(etype,)])
-        return self.decorate_rset(rset)
-
-    def empty_rset(self):
-        """return a result set for the given eid without doing actual query
-        (we have the eid, we can suppose it exists and user has access to the
-        entity)
-        """
-        from cubicweb.rset import ResultSet
-        return self.decorate_rset(ResultSet([], 'Any X WHERE X eid -1'))
-
-    def entity_from_eid(self, eid, etype=None):
-        try:
-            return self.entity_cache(eid)
-        except KeyError:
-            rset = self.eid_rset(eid, etype)
-            entity = rset.get_entity(0, 0)
-            self.set_entity_cache(entity)
-            return entity
-
-    def entity_cache(self, eid):
-        raise KeyError
-    def set_entity_cache(self, entity):
-        pass
-
-    def create_entity(self, etype, *args, **kwargs):
-        """add a new entity of the given type"""
-        rql = 'INSERT %s X' % etype
-        relations = []
-        restrictions = []
-        cachekey = []
-        for rtype, rvar in args:
-            relations.append('X %s %s' % (rtype, rvar))
-            restrictions.append('%s eid %%(%s)s' % (rvar, rvar))
-            cachekey.append(rvar)
-        for attr in kwargs:
-            if attr in cachekey:
-                continue
-            relations.append('X %s %%(%s)s' % (attr, attr))
-        if relations:
-            rql = '%s: %s' % (rql, ', '.join(relations))
-        if restrictions:
-            rql = '%s WHERE %s' % (rql, ', '.join(restrictions))
-        return self.execute(rql, kwargs, cachekey).get_entity(0, 0)
-
-    # url generation methods ##################################################
-
-    def build_url(self, *args, **kwargs):
-        """return an absolute URL using params dictionary key/values as URL
-        parameters. Values are automatically URL quoted, and the
-        publishing method to use may be specified or will be guessed.
-        """
-        # use *args since we don't want first argument to be "anonymous" to
-        # avoid potential clash with kwargs
-        assert len(args) == 1, 'only 0 or 1 non-named-argument expected'
-        method = args[0]
-        base_url = kwargs.pop('base_url', None)
-        if base_url is None:
-            base_url = self.base_url()
-        if '_restpath' in kwargs:
-            assert method == 'view', method
-            path = kwargs.pop('_restpath')
-        else:
-            path = method
-        if not kwargs:
-            return u'%s%s' % (base_url, path)
-        return u'%s%s?%s' % (base_url, path, self.build_url_params(**kwargs))
-
-
-    def build_url_params(self, **kwargs):
-        """return encoded params to incorporate them in an URL"""
-        args = []
-        for param, values in kwargs.items():
-            if not isinstance(values, (list, tuple)):
-                values = (values,)
-            for value in values:
-                args.append(u'%s=%s' % (param, self.url_quote(value)))
-        return '&'.join(args)
-
-    def url_quote(self, value, safe=''):
-        """urllib.quote is not unicode safe, use this method to do the
-        necessary encoding / decoding. Also it's designed to quote each
-        part of a url path and so the '/' character will be encoded as well.
-        """
-        if isinstance(value, unicode):
-            quoted = urlquote(value.encode(self.encoding), safe=safe)
-            return unicode(quoted, self.encoding)
-        return urlquote(str(value), safe=safe)
-
-    def url_unquote(self, quoted):
-        """returns a unicode unquoted string
-
-        decoding is based on `self.encoding` which is the encoding
-        used in `url_quote`
-        """
-        if isinstance(quoted, unicode):
-            quoted = quoted.encode(self.encoding)
-        try:
-            return unicode(urlunquote(quoted), self.encoding)
-        except UnicodeDecodeError: # might occurs on manually typed URLs
-            return unicode(urlunquote(quoted), 'iso-8859-1')
-
-
-    # session's user related methods #####################################
-
-    @cached
-    def user_data(self):
-        """returns a dictionnary with this user's information"""
-        userinfo = {}
-        if self.is_internal_session:
-            userinfo['login'] = "cubicweb"
-            userinfo['name'] = "cubicweb"
-            userinfo['email'] = ""
-            return userinfo
-        user = self.actual_session().user
-        userinfo['login'] = user.login
-        userinfo['name'] = user.name()
-        userinfo['email'] = user.get_email()
-        return userinfo
-
-    def is_internal_session(self):
-        """overrided on the server-side"""
-        return False
-
-    # abstract methods to override according to the web front-end #############
-
-    def base_url(self):
-        """return the root url of the instance"""
-        raise NotImplementedError
-
-    def decorate_rset(self, rset):
-        """add vreg/req (at least) attributes to the given result set """
-        raise NotImplementedError
-
-    def describe(self, eid):
-        """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
-        raise NotImplementedError
-
-
-# XXX 2.45 is allowing nicer entity type names, use this map for bw compat
+# use this dictionary for renaming of entity types while keeping bw compath
 ETYPE_NAME_MAP = {# 3.2 migration
                   'ECache': 'CWCache',
                   'EUser': 'CWUser',
@@ -249,31 +68,19 @@
                   'EConstraintType': 'CWConstraintType',
                   'EConstraint': 'CWConstraint',
                   'EPermission': 'CWPermission',
-                   # 2.45 migration
-                  'Eetype': 'CWEType',
-                  'Ertype': 'CWRType',
-                  'Efrdef': 'CWAttribute',
-                  'Enfrdef': 'CWRelation',
-                  'Econstraint': 'CWConstraint',
-                  'Econstrainttype': 'CWConstraintType',
-                  'Epermission': 'CWPermission',
-                  'Egroup': 'CWGroup',
-                  'Euser': 'CWUser',
-                  'Eproperty': 'CWProperty',
-                  'Emailaddress': 'EmailAddress',
-                  'Rqlexpression': 'RQLExpression',
-                  'Trinfo': 'TrInfo',
                   }
 
 
 
 # XXX cubic web cube migration map
 CW_MIGRATION_MAP = {'erudi': 'cubicweb',
-
                     'eaddressbook': 'addressbook',
                     'ebasket': 'basket',
                     'eblog': 'blog',
                     'ebook': 'book',
+                    'eclassschemes': 'keyword',
+                    'eclassfolders': 'folder',
+                    'eclasstags': 'tag',
                     'ecomment': 'comment',
                     'ecompany': 'company',
                     'econference':  'conference',
@@ -293,20 +100,6 @@
                     'ezone': 'zone',
                     'i18ncontent': 'i18ncontent',
                     'svnfile': 'vcsfile',
-
-                    'eclassschemes': 'keyword',
-                    'eclassfolders': 'folder',
-                    'eclasstags': 'tag',
-
-                    'jpl': 'jpl',
-                    'jplintra': 'jplintra',
-                    'jplextra': 'jplextra',
-                    'jplorg': 'jplorg',
-                    'jplrecia': 'jplrecia',
-                    'crm': 'crm',
-                    'agueol': 'agueol',
-                    'docaster': 'docaster',
-                    'asteretud': 'asteretud',
                     }
 
 def neg_role(role):
@@ -326,9 +119,6 @@
     except AttributeError:
         return neg_role(obj.role)
 
-def underline_title(title, car='-'):
-    return title+'\n'+(car*len(title))
-
 
 class CubicWebEventManager(object):
     """simple event / callback manager.
--- a/appobject.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/appobject.py	Wed Sep 30 18:57:42 2009 +0200
@@ -11,28 +11,13 @@
 
 import types
 from logging import getLogger
-from datetime import datetime, timedelta, time
+from warnings import warn
 
 from logilab.common.decorators import classproperty
 from logilab.common.deprecation import deprecated
 from logilab.common.logging_ext import set_log_methods
 
-from rql.nodes import VariableRef, SubQuery
-from rql.stmts import Union, Select
-
 from cubicweb import Unauthorized, NoSelectableObject
-from cubicweb.utils import UStringIO, ustrftime, strptime, todate, todatetime
-
-ONESECOND = timedelta(0, 1, 0)
-CACHE_REGISTRY = {}
-
-
-class Cache(dict):
-    def __init__(self):
-        super(Cache, self).__init__()
-        _now = datetime.now()
-        self.cache_creation_date = _now
-        self.latest_cache_lookup = _now
 
 
 # selector base classes and operations ########################################
@@ -100,11 +85,15 @@
         return AndSelector(self, other)
     def __rand__(self, other):
         return AndSelector(other, self)
+    def __iand__(self, other):
+        raise NotImplementedError('cant use inplace & (binary and)')
 
     def __or__(self, other):
         return OrSelector(self, other)
     def __ror__(self, other):
         return OrSelector(other, self)
+    def __ior__(self, other):
+        raise NotImplementedError('cant use inplace | (binary or)')
 
     def __invert__(self):
         return NotSelector(self)
@@ -220,7 +209,7 @@
     :__registry__:
       name of the registry for this object (string like 'views',
       'templates'...)
-    :id:
+    :__regid__:
       object's identifier in the registry (string like 'main',
       'primary', 'folder_box')
     :__select__:
@@ -229,343 +218,195 @@
     Moreover, the `__abstract__` attribute may be set to True to indicate
     that a appobject is abstract and should not be registered.
 
-    At registration time, the following attributes are set on the class:
-    :vreg:
-      the instance's registry
-    :schema:
-      the instance's schema
-    :config:
-      the instance's configuration
+    At selection time, the following attributes are set on the instance:
+
+    :_cw:
+      current request
+    :cw_extra_kwargs:
+      other received arguments
 
-    At selection time, the following attributes are set on the instance:
-    :req:
-      current request
-    :rset:
+    only if rset is found in arguments (in which case rset/row/col will be
+    removed from cwextra_kwargs):
+
+    :cw_rset:
       context result set or None
-    :row:
+    :cw_row:
       if a result set is set and the context is about a particular cell in the
       result set, and not the result set as a whole, specify the row number we
       are interested in, else None
-    :col:
+    :cw_col:
       if a result set is set and the context is about a particular cell in the
       result set, and not the result set as a whole, specify the col number we
       are interested in, else None
     """
     __registry__ = None
-    id = None
+    __regid__ = None
     __select__ = yes()
 
     @classmethod
-    def classid(cls):
-        """returns a unique identifier for the appobject"""
-        return '%s.%s' % (cls.__module__, cls.__name__)
-
-    # XXX bw compat code
-    @classmethod
-    def build___select__(cls):
-        for klass in cls.mro():
-            if klass.__name__ == 'AppObject':
-                continue # the bw compat __selector__ is there
-            klassdict = klass.__dict__
-            if ('__select__' in klassdict and '__selectors__' in klassdict
-                and '__selgenerated__' not in klassdict):
-                raise TypeError("__select__ and __selectors__ can't be used together on class %s" % cls)
-            if '__selectors__' in klassdict and '__selgenerated__' not in klassdict:
-                cls.__selgenerated__ = True
-                # case where __selectors__ is defined locally (but __select__
-                # is in a parent class)
-                selectors = klassdict['__selectors__']
-                if len(selectors) == 1:
-                    # micro optimization: don't bother with AndSelector if there's
-                    # only one selector
-                    select = _instantiate_selector(selectors[0])
-                else:
-                    select = AndSelector(*selectors)
-                cls.__select__ = select
-
-    @classmethod
-    def registered(cls, registry):
+    def __registered__(cls, registry):
         """called by the registry when the appobject has been registered.
 
         It must return the object that will be actually registered (this may be
         the right hook to create an instance for example). By default the
         appobject is returned without any transformation.
         """
-        cls.build___select__()
-        cls.vreg = registry.vreg
-        cls.schema = registry.schema
-        cls.config = registry.config
-        cls.register_properties()
+        try:
+            pdefs = cls.property_defs
+        except AttributeError:
+            pdefs = getattr(cls, 'cw_property_defs', {})
+        else:
+            warn('property_defs is deprecated, use cw_property_defs in %s'
+                 % cls, DeprecationWarning)
+        for propid, pdef in pdefs.items():
+            pdef = pdef.copy() # may be shared
+            pdef['default'] = getattr(cls, propid, pdef['default'])
+            pdef['sitewide'] = getattr(cls, 'site_wide', pdef.get('sitewide'))
+            registry.vreg.register_property(cls._cwpropkey(propid), **pdef)
         return cls
 
-    @classmethod
-    def vreg_initialization_completed(cls):
-        pass
+    def __init__(self, req, **extra):
+        super(AppObject, self).__init__()
+        self._cw = req
+        try:
+            self.cw_rset = extra.pop('rset')
+            self.cw_row = extra.pop('row', None)
+            self.cw_col = extra.pop('col', None)
+        except KeyError:
+            pass
+        self.cw_extra_kwargs = extra
 
-    # Eproperties definition:
-    # key: id of the property (the actual CWProperty key is build using
-    #      <registry name>.<obj id>.<property id>
-    # value: tuple (property type, vocabfunc, default value, property description)
-    #        possible types are those used by `logilab.common.configuration`
+    # persistent class properties ##############################################
+    #
+    # optional `cw_property_defs` dict on a class defines available persistent
+    # properties for this class:
+    #
+    # * key: id of the property (the actual CWProperty key is build using
+    #        <registry name>.<obj id>.<property id>
+    # * value: tuple (property type, vocabfunc, default value, property description)
+    #         possible types are those used by `logilab.common.configuration`
     #
     # notice that when it exists multiple objects with the same id (adaptation,
     # overriding) only the first encountered definition is considered, so those
     # objects can't try to have different default values for instance.
-
-    property_defs = {}
-
-    @classmethod
-    def register_properties(cls):
-        for propid, pdef in cls.property_defs.items():
-            pdef = pdef.copy() # may be shared
-            pdef['default'] = getattr(cls, propid, pdef['default'])
-            pdef['sitewide'] = getattr(cls, 'site_wide', pdef.get('sitewide'))
-            cls.vreg.register_property(cls.propkey(propid), **pdef)
+    #
+    # you can then access to a property value using self.cw_propval, where self
+    # is an instance of class
 
     @classmethod
-    def propkey(cls, propid):
-        return '%s.%s.%s' % (cls.__registry__, cls.id, propid)
-
-    @classproperty
-    @deprecated('use __select__ and & or | operators')
-    def __selectors__(cls):
-        selector = cls.__select__
-        if isinstance(selector, AndSelector):
-            return tuple(selector.selectors)
-        if not isinstance(selector, tuple):
-            selector = (selector,)
-        return selector
-
-    def __init__(self, req=None, rset=None, row=None, col=None, **extra):
-        super(AppObject, self).__init__()
-        self.req = req
-        self.rset = rset
-        self.row = row
-        self.col = col
-        self.extra_kwargs = extra
-
-    def get_cache(self, cachename):
-        """
-        NOTE: cachename should be dotted names as in :
-        - cubicweb.mycache
-        - cubes.blog.mycache
-        - etc.
-        """
-        if cachename in CACHE_REGISTRY:
-            cache = CACHE_REGISTRY[cachename]
-        else:
-            cache = CACHE_REGISTRY[cachename] = Cache()
-        _now = datetime.now()
-        if _now > cache.latest_cache_lookup + ONESECOND:
-            ecache = self.req.execute('Any C,T WHERE C is CWCache, C name %(name)s, C timestamp T',
-                                      {'name':cachename}).get_entity(0,0)
-            cache.latest_cache_lookup = _now
-            if not ecache.valid(cache.cache_creation_date):
-                cache.clear()
-                cache.cache_creation_date = _now
-        return cache
-
-    def propval(self, propid):
-        assert self.req
-        return self.req.property_value(self.propkey(propid))
-
-    def limited_rql(self):
-        """return a printable rql for the result set associated to the object,
-        with limit/offset correctly set according to maximum page size and
-        currently displayed page when necessary
+    def _cwpropkey(cls, propid):
+        """return cw property key for the property of the given id for this
+        class
         """
-        # try to get page boundaries from the navigation component
-        # XXX we should probably not have a ref to this component here (eg in
-        #     cubicweb.common)
-        nav = self.vreg['components'].select_object('navigation', self.req,
-                                                    rset=self.rset)
-        if nav:
-            start, stop = nav.page_boundaries()
-            rql = self._limit_offset_rql(stop - start, start)
-        # result set may have be limited manually in which case navigation won't
-        # apply
-        elif self.rset.limited:
-            rql = self._limit_offset_rql(*self.rset.limited)
-        # navigation component doesn't apply and rset has not been limited, no
-        # need to limit query
-        else:
-            rql = self.rset.printable_rql()
-        return rql
+        return '%s.%s.%s' % (cls.__registry__, cls.__regid__, propid)
 
-    def _limit_offset_rql(self, limit, offset):
-        rqlst = self.rset.syntax_tree()
-        if len(rqlst.children) == 1:
-            select = rqlst.children[0]
-            olimit, ooffset = select.limit, select.offset
-            select.limit, select.offset = limit, offset
-            rql = rqlst.as_string(kwargs=self.rset.args)
-            # restore original limit/offset
-            select.limit, select.offset = olimit, ooffset
-        else:
-            newselect = Select()
-            newselect.limit = limit
-            newselect.offset = offset
-            aliases = [VariableRef(newselect.get_variable(vref.name, i))
-                       for i, vref in enumerate(rqlst.selection)]
-            newselect.set_with([SubQuery(aliases, rqlst)], check=False)
-            newunion = Union()
-            newunion.append(newselect)
-            rql = rqlst.as_string(kwargs=self.rset.args)
-            rqlst.parent = None
-        return rql
+    def cw_propval(self, propid):
+        """return cw property value associated to key
 
-    def view(self, __vid, rset=None, __fallback_oid=None, __registry='views',
-             **kwargs):
-        """shortcut to self.vreg.view method avoiding to pass self.req"""
-        return self.vreg[__registry].render(__vid, self.req, __fallback_oid,
-                                            rset=rset, **kwargs)
-
-    def initialize_varmaker(self):
-        varmaker = self.req.get_page_data('rql_varmaker')
-        if varmaker is None:
-            varmaker = self.req.varmaker
-            self.req.set_page_data('rql_varmaker', varmaker)
-        self.varmaker = varmaker
-
-    # url generation methods ##################################################
-
-    controller = 'view'
-
-    def build_url(self, *args, **kwargs):
-        """return an absolute URL using params dictionary key/values as URL
-        parameters. Values are automatically URL quoted, and the
-        publishing method to use may be specified or will be guessed.
+        <cls.__registry__>.<cls.id>.<propid>
         """
-        # use *args since we don't want first argument to be "anonymous" to
-        # avoid potential clash with kwargs
-        if args:
-            assert len(args) == 1, 'only 0 or 1 non-named-argument expected'
-            method = args[0]
-        else:
-            method = None
-        # XXX I (adim) think that if method is passed explicitly, we should
-        #     not try to process it and directly call req.build_url()
-        if method is None:
-            method = self.controller
-            if method == 'view' and self.req.from_controller() == 'view' and \
-                   not '_restpath' in kwargs:
-                method = self.req.relative_path(includeparams=False) or 'view'
-        return self.req.build_url(method, **kwargs)
+        return self._cw.property_value(self._cwpropkey(propid))
+
+    # deprecated ###############################################################
+
+    @property
+    @deprecated('[3.6] use self._cw.vreg')
+    def vreg(self):
+        return self._cw.vreg
 
-    # various resources accessors #############################################
-
-    def entity(self, row, col=0):
-        """short cut to get an entity instance for a particular row/column
-        (col default to 0)
-        """
-        return self.rset.get_entity(row, col)
+    @property
+    @deprecated('[3.6] use self._cw.vreg.schema')
+    def schema(self):
+        return self._cw.vreg.schema
 
-    def complete_entity(self, row, col=0, skip_bytes=True):
-        """short cut to get an completed entity instance for a particular
-        row (all instance's attributes have been fetched)
-        """
-        entity = self.entity(row, col)
-        entity.complete(skip_bytes=skip_bytes)
-        return entity
+    @property
+    @deprecated('[3.6] use self._cw.vreg.config')
+    def config(self):
+        return self._cw.vreg.config
+
+    @property
+    @deprecated('[3.6] use self._cw')
+    def req(self):
+        return self._cw
 
-    def user_rql_callback(self, args, msg=None):
-        """register a user callback to execute some rql query and return an url
-        to call it ready to be inserted in html
-        """
-        def rqlexec(req, rql, args=None, key=None):
-            req.execute(rql, args, key)
-        return self.user_callback(rqlexec, args, msg)
+    @deprecated('[3.6] use self.cw_rset')
+    def get_rset(self):
+        return self.cw_rset
+    @deprecated('[3.6] use self.cw_rset')
+    def set_rset(self, rset):
+        self.cw_rset = rset
+    rset = property(get_rset, set_rset)
+
+    @property
+    @deprecated('[3.6] use self.cw_row')
+    def row(self):
+        return self.cw_row
 
-    def user_callback(self, cb, args, msg=None, nonify=False):
-        """register the given user callback and return an url to call it ready to be
-        inserted in html
-        """
-        from simplejson import dumps
-        self.req.add_js('cubicweb.ajax.js')
-        cbname = self.req.register_onetime_callback(cb, *args)
-        msg = dumps(msg or '')
-        return "javascript:userCallbackThenReloadPage('%s', %s)" % (
-            cbname, msg)
-
-    # formating methods #######################################################
+    @property
+    @deprecated('[3.6] use self.cw_col')
+    def col(self):
+        return self.cw_col
 
-    def tal_render(self, template, variables):
-        """render a precompiled page template with variables in the given
-        dictionary as context
-        """
-        from cubicweb.ext.tal import CubicWebContext
-        context = CubicWebContext()
-        context.update({'self': self, 'rset': self.rset, '_' : self.req._,
-                        'req': self.req, 'user': self.req.user})
-        context.update(variables)
-        output = UStringIO()
-        template.expand(context, output)
-        return output.getvalue()
+    @property
+    @deprecated('[3.6] use self.cw_extra_kwargs')
+    def extra_kwargs(self):
+        return self.cw_extra_kwargs
+
+    @deprecated('[3.6] use self._cw.view')
+    def view(self, *args, **kwargs):
+        return self._cw.view(*args, **kwargs)
 
+    @property
+    @deprecated('[3.6] use self._cw.varmaker')
+    def varmaker(self):
+        return self._cw.varmaker
+
+    @deprecated('[3.6] use self._cw.get_cache')
+    def get_cache(self, cachename):
+        return self._cw.get_cache(cachename)
+
+    @deprecated('[3.6] use self._cw.build_url')
+    def build_url(self, *args, **kwargs):
+        return self._cw.build_url(*args, **kwargs)
+
+    @deprecated('[3.6] use self.cw_rset.limited_rql')
+    def limited_rql(self):
+        return self.cw_rset.limited_rql()
+
+    @deprecated('[3.6] use self.cw_rset.complete_entity(row,col) instead')
+    def complete_entity(self, row, col=0, skip_bytes=True):
+        return self.cw_rset.complete_entity(row, col, skip_bytes)
+
+    @deprecated('[3.6] use self.cw_rset.get_entity(row,col) instead')
+    def entity(self, row, col=0):
+        return self.cw_rset.get_entity(row, col)
+
+    @deprecated('[3.6] use self._cw.user_rql_callback')
+    def user_rql_callback(self, args, msg=None):
+        return self._cw.user_rql_callback(args, msg)
+
+    @deprecated('[3.6] use self._cw.user_callback')
+    def user_callback(self, cb, args, msg=None, nonify=False):
+        return self._cw.user_callback(cb, args, msg, nonify)
+
+    @deprecated('[3.6] use self._cw.format_date')
     def format_date(self, date, date_format=None, time=False):
-        """return a string for a date time according to instance's
-        configuration
-        """
-        if date:
-            if date_format is None:
-                if time:
-                    date_format = self.req.property_value('ui.datetime-format')
-                else:
-                    date_format = self.req.property_value('ui.date-format')
-            return ustrftime(date, date_format)
-        return u''
+        return self._cw.format_date(date, date_format, time)
 
+    @deprecated('[3.6] use self._cw.format_timoe')
     def format_time(self, time):
-        """return a string for a time according to instance's
-        configuration
-        """
-        if time:
-            return ustrftime(time, self.req.property_value('ui.time-format'))
-        return u''
+        return self._cw.format_time(time)
 
+    @deprecated('[3.6] use self._cw.format_float')
     def format_float(self, num):
-        """return a string for floating point number according to instance's
-        configuration
-        """
-        if num:
-            return self.req.property_value('ui.float-format') % num
-        return u''
+        return self._cw.format_float(num)
 
+    @deprecated('[3.6] use self._cw.parse_datetime')
     def parse_datetime(self, value, etype='Datetime'):
-        """get a datetime or time from a string (according to etype)
-        Datetime formatted as Date are accepted
-        """
-        assert etype in ('Datetime', 'Date', 'Time'), etype
-        # XXX raise proper validation error
-        if etype == 'Datetime':
-            format = self.req.property_value('ui.datetime-format')
-            try:
-                return todatetime(strptime(value, format))
-            except ValueError:
-                pass
-        elif etype == 'Time':
-            format = self.req.property_value('ui.time-format')
-            try:
-                # (adim) I can't find a way to parse a Time with a custom format
-                date = strptime(value, format) # this returns a DateTime
-                return time(date.hour, date.minute, date.second)
-            except ValueError:
-                raise ValueError('can\'t parse %r (expected %s)' % (value, format))
-        try:
-            format = self.req.property_value('ui.date-format')
-            dt = strptime(value, format)
-            if etype == 'Datetime':
-                return todatetime(dt)
-            return todate(dt)
-        except ValueError:
-            raise ValueError('can\'t parse %r (expected %s)' % (value, format))
+        return self._cw.parse_datetime(value, etype)
 
-    # security related methods ################################################
-
-    def ensure_ro_rql(self, rql):
-        """raise an exception if the given rql is not a select query"""
-        first = rql.split(' ', 1)[0].lower()
-        if first in ('insert', 'set', 'delete'):
-            raise Unauthorized(self.req._('only select queries are authorized'))
+    @deprecated('[3.6] use self.cw_propval')
+    def propval(self, propid):
+        return self._cw.property_value(self._cwpropkey(propid))
 
 set_log_methods(AppObject, getLogger('cubicweb.appobject'))
--- a/common/i18n.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/common/i18n.py	Wed Sep 30 18:57:42 2009 +0200
@@ -48,8 +48,8 @@
     """display the command, execute it and raise an Exception if returned
     status != 0
     """
+    from subprocess import call
     print cmd.replace(os.getcwd() + os.sep, '')
-    from subprocess import call
     status = call(cmd, shell=True)
     if status != 0:
         raise Exception('status = %s' % status)
--- a/common/mail.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/common/mail.py	Wed Sep 30 18:57:42 2009 +0200
@@ -155,17 +155,17 @@
                 self.send_now(recipients, msg)
 
     def cell_call(self, row, col=0, **kwargs):
-        self.w(self.req._(self.content) % self.context(**kwargs))
+        self.w(self._cw._(self.content) % self.context(**kwargs))
 
     def render_emails(self, **kwargs):
         """generate and send emails for this view (one per recipient)"""
         self._kwargs = kwargs
         recipients = self.recipients()
         if not recipients:
-            self.info('skipping %s notification, no recipients', self.id)
+            self.info('skipping %s notification, no recipients', self.__regid__)
             return
-        if self.rset is not None:
-            entity = self.entity(self.row or 0, self.col or 0)
+        if self.cw_rset is not None:
+            entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
             # if the view is using timestamp in message ids, no way to reference
             # previous email
             if not self.msgid_timestamp:
@@ -177,17 +177,17 @@
         else:
             refs = ()
             msgid = None
-        req = self.req
+        req = self._cw
         self.user_data = req.user_data()
         origlang = req.lang
         for something in recipients:
             if isinstance(something, Entity):
-                # hi-jack self.req to get a session for the returned user
-                self.req = self.req.hijack_user(something)
+                # hi-jack self._cw to get a session for the returned user
+                self._cw = self._cw.hijack_user(something)
                 emailaddr = something.get_email()
             else:
                 emailaddr, lang = something
-                self.req.set_language(lang)
+                self._cw.set_language(lang)
             # since the same view (eg self) may be called multiple time and we
             # need a fresh stream at each iteration, reset it explicitly
             self.w = None
@@ -199,7 +199,7 @@
             except SkipEmail:
                 continue
             msg = format_mail(self.user_data, [emailaddr], content, subject,
-                              config=self.config, msgid=msgid, references=refs)
+                              config=self._cw.vreg.config, msgid=msgid, references=refs)
             yield [emailaddr], msg
         # restore language
         req.set_language(origlang)
@@ -213,17 +213,17 @@
         # use super_session when available, we don't want to consider security
         # when selecting recipients_finder
         try:
-            req = self.req.super_session
+            req = self._cw.super_session
         except AttributeError:
-            req = self.req
-        finder = self.vreg['components'].select('recipients_finder', req,
-                                                rset=self.rset,
-                                                row=self.row or 0,
-                                                col=self.col or 0)
+            req = self._cw
+        finder = self._cw.vreg['components'].select('recipients_finder', req,
+                                                    rset=self.cw_rset,
+                                                    row=self.cw_row or 0,
+                                                    col=self.cw_col or 0)
         return finder.recipients()
 
     def send_now(self, recipients, msg):
-        self.config.sendmails([(msg, recipients)])
+        self._cw.vreg.config.sendmails([(msg, recipients)])
 
     def send_on_commit(self, recipients, msg):
         raise NotImplementedError
@@ -233,7 +233,7 @@
     # email generation helpers #################################################
 
     def construct_message_id(self, eid):
-        return construct_message_id(self.config.appid, eid, self.msgid_timestamp)
+        return construct_message_id(self._cw.vreg.config.appid, eid, self.msgid_timestamp)
 
     def format_field(self, attr, value):
         return ':%(attr)s: %(value)s' % {'attr': attr, 'value': value}
@@ -243,18 +243,18 @@
             'attr': attr, 'ul': '-'*len(attr), 'value': value}
 
     def subject(self):
-        entity = self.entity(self.row or 0, self.col or 0)
-        subject = self.req._(self.message)
+        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+        subject = self._cw._(self.message)
         etype = entity.dc_type()
         eid = entity.eid
         login = self.user_data['login']
-        return self.req._('%(subject)s %(etype)s #%(eid)s (%(login)s)') % locals()
+        return self._cw._('%(subject)s %(etype)s #%(eid)s (%(login)s)') % locals()
 
     def context(self, **kwargs):
-        entity = self.entity(self.row or 0, self.col or 0)
+        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
         for key, val in kwargs.iteritems():
             if val and isinstance(val, unicode) and val.strip():
-               kwargs[key] = self.req._(val)
+               kwargs[key] = self._cw._(val)
         kwargs.update({'user': self.user_data['login'],
                        'eid': entity.eid,
                        'etype': entity.dc_type(),
--- a/common/mixins.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/common/mixins.py	Wed Sep 30 18:57:42 2009 +0200
@@ -38,7 +38,7 @@
                            entities=entities)
         if entities:
             return [e for e in res if e.e_schema != self.e_schema]
-        return res.filtered_rset(lambda x: x.e_schema != self.e_schema, self.col)
+        return res.filtered_rset(lambda x: x.e_schema != self.e_schema, self.cw_col)
 
     def same_type_children(self, entities=True):
         """return children entities of the same type as this entity.
@@ -50,14 +50,14 @@
                            entities=entities)
         if entities:
             return [e for e in res if e.e_schema == self.e_schema]
-        return res.filtered_rset(lambda x: x.e_schema == self.e_schema, self.col)
+        return res.filtered_rset(lambda x: x.e_schema == self.e_schema, self.cw_col)
 
     def iterchildren(self, _done=None):
         if _done is None:
             _done = set()
         for child in self.children():
             if child.eid in _done:
-                self.error('loop in %s tree', self.id.lower())
+                self.error('loop in %s tree', self.__regid__.lower())
                 continue
             yield child
             _done.add(child.eid)
@@ -83,7 +83,7 @@
         parent = self
         while parent:
             if parent.eid in path:
-                self.error('loop in %s tree', self.id.lower())
+                self.error('loop in %s tree', self.__regid__.lower())
                 break
             path.append(parent.eid)
             try:
@@ -155,7 +155,7 @@
 
     def root(self):
         """return the root object"""
-        return self.req.entity_from_eid(self.path()[0])
+        return self._cw.entity_from_eid(self.path()[0])
 
 
 class EmailableMixIn(object):
@@ -207,9 +207,9 @@
     """handle an infinite recursion safety belt"""
     if done is None:
         done = set()
-    entity = view.entity(row, col)
+    entity = view.cw_rset.get_entity(row, col)
     if entity.eid in done:
-        msg = entity.req._('loop in %(rel)s relation (%(eid)s)') % {
+        msg = entity._cw._('loop in %(rel)s relation (%(eid)s)') % {
             'rel': entity.tree_attribute,
             'eid': entity.eid
             }
@@ -220,7 +220,7 @@
 
 class TreeViewMixIn(object):
     """a recursive tree view"""
-    id = 'tree'
+    __regid__ = 'tree'
     item_vid = 'treeitem'
     __select__ = implements(ITree)
 
@@ -238,18 +238,18 @@
         self.open_item(entity)
         entity.view(vid or self.item_vid, w=self.w, **kwargs)
         relatedrset = entity.children(entities=False)
-        self.wview(self.id, relatedrset, 'null', done=done, **kwargs)
+        self.wview(self.__regid__, relatedrset, 'null', done=done, **kwargs)
         self.close_item(entity)
 
     def open_item(self, entity):
-        self.w(u'<li class="%s">\n' % entity.id.lower())
+        self.w(u'<li class="%s">\n' % entity.__regid__.lower())
     def close_item(self, entity):
         self.w(u'</li>\n')
 
 
 class TreePathMixIn(object):
     """a recursive path view"""
-    id = 'path'
+    __regid__ = 'path'
     item_vid = 'oneline'
     separator = u'&#160;&gt;&#160;'
 
@@ -266,7 +266,7 @@
             return
         parent = entity.parent()
         if parent:
-            parent.view(self.id, w=self.w, done=done)
+            parent.view(self.__regid__, w=self.w, done=done)
             self.w(self.separator)
         entity.view(vid or self.item_vid, w=self.w)
 
--- a/common/mttransforms.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/common/mttransforms.py	Wed Sep 30 18:57:42 2009 +0200
@@ -15,6 +15,7 @@
                                  register_pil_transforms,
                                  register_pygments_transforms)
 
+from cubicweb.utils import UStringIO
 from cubicweb.common.uilib import rest_publish, html_publish
 
 HTML_MIMETYPES = ('text/html', 'text/xhtml', 'application/xhtml+xml')
@@ -43,7 +44,7 @@
 ENGINE.add_transform(html_to_html())
 
 try:
-    from cubicweb.ext.tal import compile_template
+    from cubicweb.ext.tal import CubicWebContext, compile_template
 except ImportError:
     HAS_TAL = False
     from cubicweb import schema
@@ -57,8 +58,16 @@
         output = 'text/html'
         output_encoding = 'utf-8'
         def _convert(self, trdata):
-            value = trdata.encode(self.output_encoding)
-            return trdata.appobject.tal_render(compile_template(value), {})
+            context = CubicWebContext()
+            appobject = trdata.appobject
+            context.update({'self': appobject, 'rset': appobject.cw_rset,
+                            'req': appobject._cw,
+                            '_' : appobject._cw._,
+                            'user': appobject._cw.user})
+            output = UStringIO()
+            template = compile_template(trdata.encode(self.output_encoding))
+            template.expand(context, output)
+            return output.getvalue()
 
     ENGINE.add_transform(ept_to_html())
 
@@ -79,7 +88,7 @@
     def patch_convert(cls):
         def _convert(self, trdata, origconvert=cls._convert):
             try:
-                trdata.appobject.req.add_css('pygments.css')
+                trdata.appobject._cw.add_css('pygments.css')
             except AttributeError: # session has no add_css, only http request
                 pass
             return origconvert(self, trdata)
--- a/common/test/unittest_mail.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/common/test/unittest_mail.py	Wed Sep 30 18:57:42 2009 +0200
@@ -13,7 +13,7 @@
 from logilab.common.testlib import unittest_main
 from logilab.common.umessage import message_from_string
 
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.common.mail import format_mail
 
 
@@ -29,7 +29,7 @@
         return os.environ.get('USERNAME')
 
 
-class EmailTC(EnvBasedTC):
+class EmailTC(CubicWebTC):
 
     def test_format_mail(self):
         self.set_option('sender-addr', 'bim@boum.fr')
--- a/common/test/unittest_migration.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/common/test/unittest_migration.py	Wed Sep 30 18:57:42 2009 +0200
@@ -10,8 +10,6 @@
 from logilab.common.testlib import TestCase, unittest_main
 
 from cubicweb.devtools import TestServerConfiguration
-from cubicweb.devtools.apptest import TestEnvironment
-
 from cubicweb.cwconfig import CubicWebConfiguration
 from cubicweb.common.migration import MigrationHelper, filter_scripts
 from cubicweb.server.migractions import ServerMigrationHelper
@@ -65,7 +63,6 @@
                                ((0, 0, 4), TMIGRDIR+'0.0.4_Any.py')])
 
     def test_filter_scripts_for_mode(self):
-        self.assertIsInstance(self.config.migration_handler(), ServerMigrationHelper)
         config = CubicWebConfiguration('data')
         config.verbosity = 0
         self.assert_(not isinstance(config.migration_handler(), ServerMigrationHelper))
@@ -98,8 +95,8 @@
         config = ApptestConfiguration('data')
         source = config.sources()['system']
         self.assertEquals(source['db-driver'], 'sqlite')
-        cleanup_sqlite(source['db-name'], removecube=True)
-        init_test_database(driver=source['db-driver'], config=config)
+        cleanup_sqlite(source['db-name'], removetemplate=True)
+        init_test_database(config=config)
 
 
 if __name__ == '__main__':
--- a/common/uilib.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/common/uilib.py	Wed Sep 30 18:57:42 2009 +0200
@@ -81,7 +81,7 @@
 REF_PROG = re.compile(r"<ref\s+rql=([\'\"])([^\1]*?)\1\s*>([^<]*)</ref>", re.U)
 def _subst_rql(view, obj):
     delim, rql, descr = obj.groups()
-    return u'<a href="%s">%s</a>' % (view.build_url(rql=rql), descr)
+    return u'<a href="%s">%s</a>' % (view._cw.build_url(rql=rql), descr)
 
 def html_publish(view, text):
     """replace <ref rql=''> links by <a href="...">"""
--- a/cwctl.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/cwctl.py	Wed Sep 30 18:57:42 2009 +0200
@@ -17,9 +17,10 @@
 from logilab.common.clcommands import register_commands, pop_arg
 from logilab.common.shellutils import ASK
 
-from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage, underline_title
+from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage
 from cubicweb.cwconfig import CubicWebConfiguration as cwcfg, CONFIGURATIONS
-from cubicweb.toolsutils import Command, main_run,  rm, create_dir
+from cubicweb.toolsutils import Command, main_run, rm, create_dir, underline_title
+
 
 def wait_process_end(pid, maxtry=10, waittime=1):
     """wait for a process to actually die"""
--- a/cwvreg.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/cwvreg.py	Wed Sep 30 18:57:42 2009 +0200
@@ -18,7 +18,7 @@
                       ObjectNotFound, NoSelectableObject, RegistryNotFound,
                       RegistryOutOfDate, CW_EVENT_MANAGER, onevent)
 from cubicweb.utils import dump_class
-from cubicweb.vregistry import VRegistry, Registry
+from cubicweb.vregistry import VRegistry, Registry, class_regid
 from cubicweb.rtags import RTAGS
 
 
@@ -59,12 +59,9 @@
         return self.vreg.schema
 
     def initialization_completed(self):
-        # call vreg_initialization_completed on appobjects and print
-        # registry content
-        for appobjects in self.itervalues():
-            for appobject in appobjects:
-                appobject.vreg_initialization_completed()
+        pass
 
+    @deprecated('[3.6] select object, then use obj.render()')
     def render(self, __oid, req, __fallback_oid=None, rset=None, **kwargs):
         """select object, or fallback object if specified and the first one
         isn't selectable, then render it
@@ -77,20 +74,22 @@
             obj = self.select(__fallback_oid, req, rset=rset, **kwargs)
         return obj.render(**kwargs)
 
+    @deprecated('[3.6] use select_or_none and test for obj.cw_propval("visible")')
     def select_vobject(self, oid, *args, **kwargs):
-        selected = self.select_object(oid, *args, **kwargs)
-        if selected and selected.propval('visible'):
+        selected = self.select_or_none(oid, *args, **kwargs)
+        if selected and selected.cw_propval('visible'):
             return selected
         return None
 
-    def possible_vobjects(self, *args, **kwargs):
+    def poss_visible_objects(self, *args, **kwargs):
         """return an ordered list of possible app objects in a given registry,
         supposing they support the 'visible' and 'order' properties (as most
         visualizable objects)
         """
         return sorted([x for x in self.possible_objects(*args, **kwargs)
-                       if x.propval('visible')],
-                      key=lambda x: x.propval('order'))
+                       if x.cw_propval('visible')],
+                      key=lambda x: x.cw_propval('order'))
+    possible_vobjects = deprecated('[3.6] use poss_visible_objects()')(poss_visible_objects)
 
 
 VRegistry.REGISTRY_FACTORY[None] = CWRegistry
@@ -106,15 +105,24 @@
         clear_cache(self, 'etype_class')
 
     def register(self, obj, **kwargs):
-        oid = kwargs.get('oid') or obj.id
+        oid = kwargs.get('oid') or class_regid(obj)
         if oid != 'Any' and not oid in self.schema:
             self.error('don\'t register %s, %s type not defined in the '
-                       'schema', obj, obj.id)
+                       'schema', obj, oid)
             return
         kwargs['clear'] = True
         super(ETypeRegistry, self).register(obj, **kwargs)
 
     @cached
+    def parent_classes(self, etype):
+        if etype == 'Any':
+            return [self.etype_class('Any')]
+        eschema = self.schema.eschema(etype)
+        parents = [self.etype_class(e.type) for e in eschema.ancestors()]
+        parents.append(self.etype_class('Any'))
+        return parents
+
+    @cached
     def etype_class(self, etype):
         """return an entity class for the given entity type.
 
@@ -130,37 +138,35 @@
         baseschemas = [eschema] + eschema.ancestors()
         # browse ancestors from most specific to most generic and try to find an
         # associated custom entity class
-        cls = None
         for baseschema in baseschemas:
             try:
                 btype = ETYPE_NAME_MAP[baseschema]
             except KeyError:
                 btype = str(baseschema)
-            if cls is None:
-                try:
-                    objects = self[btype]
-                    assert len(objects) == 1, objects
-                    if btype == etype:
-                        cls = objects[0]
-                    else:
-                        cls = self.etype_class(btype)
-                except ObjectNotFound:
-                    continue
-            else:
-                # ensure parent classes are built first
-                self.etype_class(btype)
-        if cls is None:
+            try:
+                objects = self[btype]
+                assert len(objects) == 1, objects
+                if btype == etype:
+                    cls = objects[0]
+                else:
+                    # recurse to ensure issubclass(etype_class('Child'),
+                    #                              etype_class('Parent'))
+                    cls = self.etype_class(btype)
+                break
+            except ObjectNotFound:
+                pass
+        else:
             # no entity class for any of the ancestors, fallback to the default
             # one
             objects = self['Any']
             assert len(objects) == 1, objects
             cls = objects[0]
-        # make a copy event if cls.id == etype, else we may have pb for client
-        # application using multiple connections to different repositories (eg
-        # shingouz)
+        # make a copy event if cls.__regid__ == etype, else we may have pb for
+        # client application using multiple connections to different
+        # repositories (eg shingouz)
         cls = dump_class(cls, etype)
-        cls.id = etype
-        cls.__initialize__()
+        cls.__regid__ = etype
+        cls.__initialize__(self.schema)
         return cls
 
 VRegistry.REGISTRY_FACTORY['etypes'] = ETypeRegistry
@@ -188,7 +194,7 @@
             if vid[0] == '_':
                 continue
             try:
-                view = self.select_best(views, req, rset=rset, **kwargs)
+                view = self._select_best(views, req, rset=rset, **kwargs)
                 if view.linkable():
                     yield view
             except NoSelectableObject:
@@ -204,7 +210,7 @@
 
     def possible_actions(self, req, rset=None, **kwargs):
         if rset is None:
-            actions = self.possible_vobjects(req, rset=rset, **kwargs)
+            actions = self.poss_visible_objects(req, rset=rset, **kwargs)
         else:
             actions = rset.possible_actions(**kwargs) # cached implementation
         result = {}
@@ -372,6 +378,8 @@
             implemented_interfaces = set()
             if 'Any' in self.get('etypes', ()):
                 for etype in self.schema.entities():
+                    if etype.is_final():
+                        continue
                     cls = self['etypes'].etype_class(etype)
                     for iface in cls.__implements__:
                         implemented_interfaces.update(iface.__mro__)
--- a/dbapi.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/dbapi.py	Wed Sep 30 18:57:42 2009 +0200
@@ -19,8 +19,9 @@
 from logilab.common.decorators import monkeypatch
 from logilab.common.deprecation import deprecated
 
-from cubicweb import ETYPE_NAME_MAP, ConnectionError, RequestSessionMixIn
-from cubicweb import cwvreg, cwconfig
+from cubicweb import ETYPE_NAME_MAP, ConnectionError, cwvreg, cwconfig
+from cubicweb.req import RequestSessionBase
+
 
 _MARKER = object()
 
@@ -42,9 +43,9 @@
     registries.
     """
     defaultcls = cwvreg.VRegistry.REGISTRY_FACTORY[None]
-    orig_select_best = defaultcls.orig_select_best = defaultcls.select_best
+    orig_select_best = defaultcls.orig_select_best = defaultcls._select_best
     @monkeypatch(defaultcls)
-    def select_best(self, appobjects, *args, **kwargs):
+    def _select_best(self, appobjects, *args, **kwargs):
         """return an instance of the most specific object according
         to parameters
 
@@ -174,7 +175,7 @@
     return repo, cnx
 
 
-class DBAPIRequest(RequestSessionMixIn):
+class DBAPIRequest(RequestSessionBase):
 
     def __init__(self, vreg, cnx=None):
         super(DBAPIRequest, self).__init__(vreg)
--- a/debian/cubicweb-dev.install.in	Wed Sep 30 17:57:02 2009 +0200
+++ b/debian/cubicweb-dev.install.in	Wed Sep 30 18:57:42 2009 +0200
@@ -6,6 +6,7 @@
 debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/ext/test usr/lib/PY_VERSION/site-packages/cubicweb/ext/
 debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/server/test usr/lib/PY_VERSION/site-packages/cubicweb/server/
 debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/sobjects/test usr/lib/PY_VERSION/site-packages/cubicweb/sobjects/
+debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/hooks/test usr/lib/PY_VERSION/site-packages/cubicweb/sobjects/
 debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/web/test usr/lib/PY_VERSION/site-packages/cubicweb/web/
 debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/etwist/test usr/lib/PY_VERSION/site-packages/cubicweb/etwist/
 debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/goa/test usr/lib/PY_VERSION/site-packages/cubicweb/goa/
--- a/debian/cubicweb-server.install.in	Wed Sep 30 17:57:02 2009 +0200
+++ b/debian/cubicweb-server.install.in	Wed Sep 30 18:57:42 2009 +0200
@@ -1,4 +1,5 @@
 debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/server/ usr/lib/PY_VERSION/site-packages/cubicweb
+debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/hooks/ usr/lib/PY_VERSION/site-packages/cubicweb
 debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/sobjects/ usr/lib/PY_VERSION/site-packages/cubicweb
 debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/schemas/ usr/lib/PY_VERSION/site-packages/cubicweb
 debian/tmp/usr/share/cubicweb/migration/ usr/share/cubicweb/
--- a/debian/rules	Wed Sep 30 17:57:02 2009 +0200
+++ b/debian/rules	Wed Sep 30 18:57:42 2009 +0200
@@ -48,6 +48,7 @@
 
 	# Remove unittests directory (should be available in cubicweb-dev only)
 	rm -rf debian/cubicweb-server/usr/lib/${PY_VERSION}/site-packages/cubicweb/server/test
+	rm -rf debian/cubicweb-server/usr/lib/${PY_VERSION}/site-packages/cubicweb/hooks/test
 	rm -rf debian/cubicweb-server/usr/lib/${PY_VERSION}/site-packages/cubicweb/sobjects/test
 	rm -rf debian/cubicweb-web/usr/lib/${PY_VERSION}/site-packages/cubicweb/web/test
 	rm -rf debian/cubicweb-twisted/usr/lib/${PY_VERSION}/site-packages/cubicweb/etwist/test
--- a/devtools/__init__.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/devtools/__init__.py	Wed Sep 30 18:57:42 2009 +0200
@@ -13,20 +13,55 @@
 from os.path import (abspath, join, exists, basename, dirname, normpath, split,
                      isfile, isabs)
 
-from cubicweb import CW_SOFTWARE_ROOT, ConfigurationError
+from cubicweb import CW_SOFTWARE_ROOT, ConfigurationError, schema, cwconfig
 from cubicweb.utils import strptime
-from cubicweb.toolsutils import read_config
-from cubicweb.cwconfig import CubicWebConfiguration, merge_options
 from cubicweb.server.serverconfig import ServerConfiguration
 from cubicweb.etwist.twconfig import TwistedConfiguration
 
+cwconfig.CubicWebConfiguration.cls_adjust_sys_path()
+
+# db auto-population configuration #############################################
+
+SYSTEM_ENTITIES = schema.SCHEMA_TYPES | set((
+    'CWGroup', 'CWUser', 'CWProperty',
+    'Workflow', 'State', 'BaseTransition', 'Transition', 'WorkflowTransition',
+    'TrInfo', 'SubWorkflowExitPoint',
+    ))
+
+SYSTEM_RELATIONS = schema.META_RTYPES | set((
+    # workflow related
+    'workflow_of', 'state_of', 'transition_of', 'initial_state', 'allowed_transition',
+    'destination_state', 'in_state', 'wf_info_for', 'from_state', 'to_state',
+    'condition', 'subworkflow', 'subworkflow_state', 'subworkflow_exit',
+    # cwproperty
+    'for_user',
+    # schema definition
+    'specializes',
+    'relation_type', 'from_entity', 'to_entity',
+    'constrained_by', 'cstrtype', 'widget',
+    'read_permission', 'update_permission', 'delete_permission', 'add_permission',
+    # permission
+    'in_group', 'require_group', 'require_permission',
+    # deducted from other relations
+    'primary_email',
+    ))
+
+# content validation configuration #############################################
+
 # validators are used to validate (XML, DTD, whatever) view's content
 # validators availables are :
 #  'dtd' : validates XML + declared DTD
 #  'xml' : guarantees XML is well formed
 #  None : do not try to validate anything
+
+# {'vid': validator}
 VIEW_VALIDATORS = {}
+
+
+# cubicweb test configuration ##################################################
+
 BASE_URL = 'http://testing.fr/cubicweb/'
+
 DEFAULT_SOURCES = {'system': {'adapter' : 'native',
                               'db-encoding' : 'UTF-8', #'ISO-8859-1',
                               'db-user' : u'admin',
@@ -40,13 +75,14 @@
                               },
                    }
 
+
 class TestServerConfiguration(ServerConfiguration):
     mode = 'test'
     set_language = False
     read_instance_schema = False
     bootstrap_schema = False
     init_repository = True
-    options = merge_options(ServerConfiguration.options + (
+    options = cwconfig.merge_options(ServerConfiguration.options + (
         ('anonymous-user',
          {'type' : 'string',
           'default': None,
@@ -66,10 +102,11 @@
 
     def __init__(self, appid, log_threshold=logging.CRITICAL+10):
         ServerConfiguration.__init__(self, appid)
-        self.global_set_option('log-file', None)
         self.init_log(log_threshold, force=True)
         # need this, usually triggered by cubicweb-ctl
         self.load_cwctl_plugins()
+        self.global_set_option('anonymous-user', 'anon')
+        self.global_set_option('anonymous-password', 'anon')
 
     anonymous_user = TwistedConfiguration.anonymous_user.im_func
 
@@ -81,6 +118,11 @@
         return abspath('..')
     appdatahome = apphome
 
+    def load_configuration(self):
+        super(TestServerConfiguration, self).load_configuration()
+        self.global_set_option('anonymous-user', 'anon')
+        self.global_set_option('anonymous-password', 'anon')
+
     def main_config_file(self):
         """return instance's control configuration file"""
         return join(self.apphome, '%s.conf' % self.name)
@@ -118,30 +160,11 @@
             sources = DEFAULT_SOURCES
         return sources
 
-    def load_defaults(self):
-        super(TestServerConfiguration, self).load_defaults()
-        # note: don't call global set option here, OptionManager may not yet be initialized
-        # add anonymous user
-        self.set_option('anonymous-user', 'anon')
-        self.set_option('anonymous-password', 'anon')
-        # uncomment the line below if you want rql queries to be logged
-        #self.set_option('query-log-file', '/tmp/test_rql_log.' + `os.getpid()`)
-        self.set_option('sender-name', 'cubicweb-test')
-        self.set_option('sender-addr', 'cubicweb-test@logilab.fr')
-        try:
-            send_to =  '%s@logilab.fr' % os.getlogin()
-        except (OSError, AttributeError):
-            send_to =  '%s@logilab.fr' % (os.environ.get('USER')
-                                          or os.environ.get('USERNAME')
-                                          or os.environ.get('LOGNAME'))
-        self.set_option('sender-addr', send_to)
-        self.set_option('default-dest-addrs', send_to)
-        self.set_option('base-url', BASE_URL)
-
 
 class BaseApptestConfiguration(TestServerConfiguration, TwistedConfiguration):
     repo_method = 'inmemory'
-    options = merge_options(TestServerConfiguration.options + TwistedConfiguration.options)
+    options = cwconfig.merge_options(TestServerConfiguration.options
+                                     + TwistedConfiguration.options)
     cubicweb_appobject_path = TestServerConfiguration.cubicweb_appobject_path | TwistedConfiguration.cubicweb_appobject_path
     cube_appobject_path = TestServerConfiguration.cube_appobject_path | TwistedConfiguration.cube_appobject_path
 
@@ -163,98 +186,84 @@
         BaseApptestConfiguration.__init__(self, appid, log_threshold=log_threshold)
         self.init_repository = sourcefile is None
         self.sourcefile = sourcefile
-        import re
-        self.global_set_option('embed-allowed', re.compile('.*'))
 
 
-class RealDatabaseConfiguration(ApptestConfiguration):
-    init_repository = False
-    sourcesdef =  {'system': {'adapter' : 'native',
-                              'db-encoding' : 'UTF-8', #'ISO-8859-1',
-                              'db-user' : u'admin',
-                              'db-password' : 'gingkow',
-                              'db-name' : 'seotest',
-                              'db-driver' : 'postgres',
-                              'db-host' : None,
-                              },
-                   'admin' : {'login': u'admin',
-                              'password': u'gingkow',
-                              },
-                   }
+# test database handling #######################################################
 
-    def __init__(self, appid, log_threshold=logging.CRITICAL, sourcefile=None):
-        ApptestConfiguration.__init__(self, appid)
-        self.init_repository = False
+def init_test_database(config=None, configdir='data'):
+    """init a test database for a specific driver"""
+    from cubicweb.dbapi import in_memory_cnx
+    config = config or TestServerConfiguration(configdir)
+    sources = config.sources()
+    driver = sources['system']['db-driver']
+    if driver == 'sqlite':
+        init_test_database_sqlite(config)
+    elif driver == 'postgres':
+        init_test_database_postgres(config)
+    else:
+        raise ValueError('no initialization function for driver %r' % driver)
+    config._cubes = None # avoid assertion error
+    repo, cnx = in_memory_cnx(config, unicode(sources['admin']['login']),
+                              sources['admin']['password'] or 'xxx')
+    if driver == 'sqlite':
+        install_sqlite_patch(repo.querier)
+    return repo, cnx
 
 
-    def sources(self):
-        """
-        By default, we run tests with the sqlite DB backend.
-        One may use its own configuration by just creating a
-        'sources' file in the test directory from wich tests are
-        launched.
-        """
-        self._sources = self.sourcesdef
-        return self._sources
+def reset_test_database(config):
+    """init a test database for a specific driver"""
+    driver = config.sources()['system']['db-driver']
+    if driver == 'sqlite':
+        reset_test_database_sqlite(config)
+    else:
+        raise ValueError('no reset function for driver %r' % driver)
 
 
-def buildconfig(dbuser, dbpassword, dbname, adminuser, adminpassword, dbhost=None):
-    """convenience function that builds a real-db configuration class"""
-    sourcesdef =  {'system': {'adapter' : 'native',
-                              'db-encoding' : 'UTF-8', #'ISO-8859-1',
-                              'db-user' : dbuser,
-                              'db-password' : dbpassword,
-                              'db-name' : dbname,
-                              'db-driver' : 'postgres',
-                              'db-host' : dbhost,
-                              },
-                   'admin' : {'login': adminuser,
-                              'password': adminpassword,
-                              },
-                   }
-    return type('MyRealDBConfig', (RealDatabaseConfiguration,),
-                {'sourcesdef': sourcesdef})
+### postgres test database handling ############################################
 
-def loadconfig(filename):
-    """convenience function that builds a real-db configuration class
-    from a file
-    """
-    return type('MyRealDBConfig', (RealDatabaseConfiguration,),
-                {'sourcesdef': read_config(filename)})
+def init_test_database_postgres(config):
+    """initialize a fresh sqlite databse used for testing purpose"""
+    if config.init_repository:
+        from cubicweb.server import init_repository
+        init_repository(config, interactive=False, drop=True)
 
 
-class LivetestConfiguration(BaseApptestConfiguration):
-    init_repository = False
+### sqlite test database handling ##############################################
+
+def cleanup_sqlite(dbfile, removetemplate=False):
+    try:
+        os.remove(dbfile)
+        os.remove('%s-journal' % dbfile)
+    except OSError:
+        pass
+    if removetemplate:
+        try:
+            os.remove('%s-template' % dbfile)
+        except OSError:
+            pass
 
-    def __init__(self, cube=None, sourcefile=None, pyro_name=None,
-                 log_threshold=logging.CRITICAL):
-        TestServerConfiguration.__init__(self, cube, log_threshold=log_threshold)
-        self.appid = pyro_name or cube
-        # don't change this, else some symlink problems may arise in some
-        # environment (e.g. mine (syt) ;o)
-        # XXX I'm afraid this test will prevent to run test from a production
-        # environment
-        self._sources = None
-        # instance cube test
-        if cube is not None:
-            self.apphome = self.cube_dir(cube)
-        elif 'web' in os.getcwd().split(os.sep):
-            # web test
-            self.apphome = join(normpath(join(dirname(__file__), '..')), 'web')
-        else:
-            # cube test
-            self.apphome = abspath('..')
-        self.sourcefile = sourcefile
-        self.global_set_option('realm', '')
-        self.use_pyro = pyro_name is not None
+def reset_test_database_sqlite(config):
+    import shutil
+    dbfile = config.sources()['system']['db-name']
+    cleanup_sqlite(dbfile)
+    template = '%s-template' % dbfile
+    if exists(template):
+        shutil.copy(template, dbfile)
+        return True
+    return False
 
-    def pyro_enabled(self):
-        if self.use_pyro:
-            return True
-        else:
-            return False
+def init_test_database_sqlite(config):
+    """initialize a fresh sqlite databse used for testing purpose"""
+    # remove database file if it exists
+    dbfile = config.sources()['system']['db-name']
+    if not reset_test_database_sqlite(config):
+        # initialize the database
+        import shutil
+        from cubicweb.server import init_repository
+        init_repository(config, interactive=False)
+        dbfile = config.sources()['system']['db-name']
+        shutil.copy(dbfile, '%s-template' % dbfile)
 
-CubicWebConfiguration.cls_adjust_sys_path()
 
 def install_sqlite_patch(querier):
     """This patch hotfixes the following sqlite bug :
@@ -293,57 +302,3 @@
         return new_execute
     querier.__class__.execute = wrap_execute(querier.__class__.execute)
     querier.__class__._devtools_sqlite_patched = True
-
-def init_test_database(driver='sqlite', configdir='data', config=None,
-                       vreg=None):
-    """init a test database for a specific driver"""
-    from cubicweb.dbapi import in_memory_cnx
-    if vreg and not config:
-        config = vreg.config
-    config = config or TestServerConfiguration(configdir)
-    source = config.sources()
-    if driver == 'sqlite':
-        init_test_database_sqlite(config, source)
-    elif driver == 'postgres':
-        init_test_database_postgres(config, source)
-    else:
-        raise ValueError('no initialization function for driver %r' % driver)
-    config._cubes = None # avoid assertion error
-    repo, cnx = in_memory_cnx(vreg or config, unicode(source['admin']['login']),
-                              source['admin']['password'] or 'xxx')
-    if driver == 'sqlite':
-        install_sqlite_patch(repo.querier)
-    return repo, cnx
-
-def init_test_database_postgres(config, source, vreg=None):
-    """initialize a fresh sqlite databse used for testing purpose"""
-    if config.init_repository:
-        from cubicweb.server import init_repository
-        init_repository(config, interactive=False, drop=True, vreg=vreg)
-
-def cleanup_sqlite(dbfile, removecube=False):
-    try:
-        os.remove(dbfile)
-        os.remove('%s-journal' % dbfile)
-    except OSError:
-        pass
-    if removecube:
-        try:
-            os.remove('%s-template' % dbfile)
-        except OSError:
-            pass
-
-def init_test_database_sqlite(config, source, vreg=None):
-    """initialize a fresh sqlite databse used for testing purpose"""
-    import shutil
-    # remove database file if it exists (actually I know driver == 'sqlite' :)
-    dbfile = source['system']['db-name']
-    cleanup_sqlite(dbfile)
-    template = '%s-template' % dbfile
-    if exists(template):
-        shutil.copy(template, dbfile)
-    else:
-        # initialize the database
-        from cubicweb.server import init_repository
-        init_repository(config, interactive=False, vreg=vreg)
-        shutil.copy(dbfile, template)
--- a/devtools/_apptest.py	Wed Sep 30 17:57:02 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,207 +0,0 @@
-"""Hidden internals for the devtools.apptest module
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-import sys, traceback
-
-from logilab.common.pytest import pause_tracing, resume_tracing
-
-import yams.schema
-
-from cubicweb.dbapi import repo_connect, ConnectionProperties, ProgrammingError
-from cubicweb.cwvreg import CubicWebVRegistry
-
-from cubicweb.web.application import CubicWebPublisher
-from cubicweb.web import Redirect
-
-from cubicweb.devtools import ApptestConfiguration, init_test_database
-from cubicweb.devtools.fake import FakeRequest
-
-SYSTEM_ENTITIES = ('CWGroup', 'CWUser',
-                   'CWAttribute', 'CWRelation',
-                   'CWConstraint', 'CWConstraintType', 'CWProperty',
-                   'CWEType', 'CWRType',
-                   'Workflow', 'State', 'BaseTransition', 'Transition', 'WorkflowTransition', 'TrInfo', 'SubWorkflowExitPoint',
-                   'RQLExpression',
-                   )
-SYSTEM_RELATIONS = (
-    # virtual relation
-    'identity',
-    # metadata
-    'is', 'is_instance_of', 'owned_by', 'created_by', 'specializes',
-    # workflow related
-    'workflow_of', 'state_of', 'transition_of', 'initial_state', 'allowed_transition',
-    'destination_state', 'in_state', 'wf_info_for', 'from_state', 'to_state',
-    'condition', 'subworkflow', 'subworkflow_state', 'subworkflow_exit',
-    # permission
-    'in_group', 'require_group', 'require_permission',
-    'read_permission', 'update_permission', 'delete_permission', 'add_permission',
-    # eproperty
-    'for_user',
-    # schema definition
-    'relation_type', 'from_entity', 'to_entity',
-    'constrained_by', 'cstrtype', 'widget',
-    # deducted from other relations
-    'primary_email',
-                    )
-
-def unprotected_entities(app_schema, strict=False):
-    """returned a Set of each non final entity type, excluding CWGroup, and CWUser...
-    """
-    if strict:
-        protected_entities = yams.schema.BASE_TYPES
-    else:
-        protected_entities = yams.schema.BASE_TYPES.union(set(SYSTEM_ENTITIES))
-    entities = set(app_schema.entities())
-    return entities - protected_entities
-
-
-def ignore_relations(*relations):
-    global SYSTEM_RELATIONS
-    SYSTEM_RELATIONS += relations
-
-
-class TestEnvironment(object):
-    """TestEnvironment defines a context (e.g. a config + a given connection) in
-    which the tests are executed
-    """
-
-    def __init__(self, appid, reporter=None, verbose=False,
-                 configcls=ApptestConfiguration, requestcls=FakeRequest):
-        config = configcls(appid)
-        self.requestcls = requestcls
-        self.cnx = None
-        config.db_perms = False
-        source = config.sources()['system']
-        if verbose:
-            print "init test database ..."
-        self.vreg = vreg = CubicWebVRegistry(config)
-        self.admlogin = source['db-user']
-        # restore database <=> init database
-        self.restore_database()
-        if verbose:
-            print "init done"
-        config.repository = lambda x=None: self.repo
-        self.app = CubicWebPublisher(config, vreg=vreg)
-        self.verbose = verbose
-        schema = self.vreg.schema
-        # else we may run into problems since email address are ususally share in app tests
-        # XXX should not be necessary anymore
-        schema.rschema('primary_email').set_rproperty('CWUser', 'EmailAddress', 'composite', False)
-        self.deletable_entities = unprotected_entities(schema)
-
-    def restore_database(self):
-        """called by unittests' tearDown to restore the original database
-        """
-        try:
-            pause_tracing()
-            if self.cnx:
-                self.cnx.close()
-            source = self.vreg.config.sources()['system']
-            self.repo, self.cnx = init_test_database(driver=source['db-driver'],
-                                                     vreg=self.vreg)
-            self._orig_cnx = self.cnx
-            resume_tracing()
-        except:
-            resume_tracing()
-            traceback.print_exc()
-            sys.exit(1)
-        # XXX cnx decoration is usually done by the repository authentication manager,
-        # necessary in authentication tests
-        self.cnx.vreg = self.vreg
-        self.cnx.login = source['db-user']
-        self.cnx.password = source['db-password']
-
-
-    def create_user(self, login, groups=('users',), req=None):
-        req = req or self.create_request()
-        cursor = self._orig_cnx.cursor(req)
-        rset = cursor.execute('INSERT CWUser X: X login %(login)s, X upassword %(passwd)s',
-                              {'login': unicode(login), 'passwd': login.encode('utf8')})
-        user = rset.get_entity(0, 0)
-        cursor.execute('SET X in_group G WHERE X eid %%(x)s, G name IN(%s)'
-                       % ','.join(repr(g) for g in groups),
-                       {'x': user.eid}, 'x')
-        user.clear_related_cache('in_group', 'subject')
-        self._orig_cnx.commit()
-        return user
-
-    def login(self, login, password=None):
-        if login == self.admlogin:
-            self.restore_connection()
-        else:
-            self.cnx = repo_connect(self.repo, unicode(login),
-                                    password or str(login),
-                                    ConnectionProperties('inmemory'))
-        if login == self.vreg.config.anonymous_user()[0]:
-            self.cnx.anonymous_connection = True
-        return self.cnx
-
-    def restore_connection(self):
-        if not self.cnx is self._orig_cnx:
-            try:
-                self.cnx.close()
-            except ProgrammingError:
-                pass # already closed
-        self.cnx = self._orig_cnx
-
-    ############################################################################
-
-    def execute(self, rql, args=None, eidkey=None, req=None):
-        """executes <rql>, builds a resultset, and returns a couple (rset, req)
-        where req is a FakeRequest
-        """
-        req = req or self.create_request(rql=rql)
-        return self.cnx.cursor(req).execute(unicode(rql), args, eidkey)
-
-    def create_request(self, rql=None, **kwargs):
-        """executes <rql>, builds a resultset, and returns a
-        couple (rset, req) where req is a FakeRequest
-        """
-        if rql:
-            kwargs['rql'] = rql
-        req = self.requestcls(self.vreg, form=kwargs)
-        req.set_connection(self.cnx)
-        return req
-
-    def get_rset_and_req(self, rql, optional_args=None, args=None, eidkey=None):
-        """executes <rql>, builds a resultset, and returns a
-        couple (rset, req) where req is a FakeRequest
-        """
-        return (self.execute(rql, args, eidkey),
-                self.create_request(rql=rql, **optional_args or {}))
-
-
-class ExistingTestEnvironment(TestEnvironment):
-
-    def __init__(self, appid, sourcefile, verbose=False):
-        config = ApptestConfiguration(appid, sourcefile=sourcefile)
-        if verbose:
-            print "init test database ..."
-        source = config.sources()['system']
-        self.vreg = CubicWebVRegistry(config)
-        self.cnx = init_test_database(driver=source['db-driver'],
-                                      vreg=self.vreg)[1]
-        if verbose:
-            print "init done"
-        self.app = CubicWebPublisher(config, vreg=self.vreg)
-        self.verbose = verbose
-        # this is done when the publisher is opening a connection
-        self.cnx.vreg = self.vreg
-
-    def setup(self, config=None):
-        """config is passed by TestSuite but is ignored in this environment"""
-        cursor = self.cnx.cursor()
-        self.last_eid = cursor.execute('Any X WHERE X creation_date D ORDERBY D DESC LIMIT 1').rows[0][0]
-
-    def cleanup(self):
-        """cancel inserted elements during tests"""
-        cursor = self.cnx.cursor()
-        cursor.execute('DELETE Any X WHERE X eid > %(x)s', {'x' : self.last_eid}, eid_key='x')
-        print "cleaning done"
-        self.cnx.commit()
--- a/devtools/apptest.py	Wed Sep 30 17:57:02 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,522 +0,0 @@
-"""This module provides misc utilities to test instances
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from copy import deepcopy
-
-import simplejson
-
-from logilab.common.testlib import TestCase
-from logilab.common.pytest import nocoverage
-from logilab.common.umessage import message_from_string
-
-from logilab.common.deprecation import deprecated
-
-from cubicweb.devtools import init_test_database, TestServerConfiguration, ApptestConfiguration
-from cubicweb.devtools._apptest import TestEnvironment
-from cubicweb.devtools.fake import FakeRequest
-
-from cubicweb.dbapi import repo_connect, ConnectionProperties, ProgrammingError
-
-
-MAILBOX = []
-class Email:
-    def __init__(self, recipients, msg):
-        self.recipients = recipients
-        self.msg = msg
-
-    @property
-    def message(self):
-        return message_from_string(self.msg)
-
-    @property
-    def subject(self):
-        return self.message.get('Subject')
-
-    @property
-    def content(self):
-        return self.message.get_payload(decode=True)
-
-    def __repr__(self):
-        return '<Email to %s with subject %s>' % (','.join(self.recipients),
-                                                  self.message.get('Subject'))
-
-class MockSMTP:
-    def __init__(self, server, port):
-        pass
-    def close(self):
-        pass
-    def sendmail(self, helo_addr, recipients, msg):
-        MAILBOX.append(Email(recipients, msg))
-
-from cubicweb import cwconfig
-cwconfig.SMTP = MockSMTP
-
-
-def get_versions(self, checkversions=False):
-    """return the a dictionary containing cubes used by this instance
-    as key with their version as value, including cubicweb version. This is a
-    public method, not requiring a session id.
-
-    replace Repository.get_versions by this method if you don't want versions
-    checking
-    """
-    vcconf = {'cubicweb': self.config.cubicweb_version()}
-    self.config.bootstrap_cubes()
-    for pk in self.config.cubes():
-        version = self.config.cube_version(pk)
-        vcconf[pk] = version
-    self.config._cubes = None
-    return vcconf
-
-
-@property
-def late_binding_env(self):
-    """builds TestEnvironment as late as possible"""
-    if not hasattr(self, '_env'):
-        self.__class__._env = TestEnvironment('data', configcls=self.configcls,
-                                              requestcls=self.requestcls)
-    return self._env
-
-
-class autoenv(type):
-    """automatically set environment on EnvBasedTC subclasses if necessary
-    """
-    def __new__(mcs, name, bases, classdict):
-        env = classdict.get('env')
-        # try to find env in one of the base classes
-        if env is None:
-            for base in bases:
-                env = getattr(base, 'env', None)
-                if env is not None:
-                    classdict['env'] = env
-                    break
-        if not classdict.get('__abstract__')  and not classdict.get('env'):
-            classdict['env'] = late_binding_env
-        return super(autoenv, mcs).__new__(mcs, name, bases, classdict)
-
-
-class EnvBasedTC(TestCase):
-    """abstract class for test using an apptest environment
-    """
-    __metaclass__ = autoenv
-    __abstract__ = True
-    env = None
-    configcls = ApptestConfiguration
-    requestcls = FakeRequest
-
-    # user / session management ###############################################
-
-    def user(self, req=None):
-        if req is None:
-            req = self.env.create_request()
-            return self.env.cnx.user(req)
-        else:
-            return req.user
-
-    def create_user(self, *args, **kwargs):
-        return self.env.create_user(*args, **kwargs)
-
-    def login(self, login, password=None):
-        return self.env.login(login, password)
-
-    def restore_connection(self):
-        self.env.restore_connection()
-
-    # db api ##################################################################
-
-    @nocoverage
-    def cursor(self, req=None):
-        return self.env.cnx.cursor(req or self.request())
-
-    @nocoverage
-    def execute(self, *args, **kwargs):
-        return self.env.execute(*args, **kwargs)
-
-    @nocoverage
-    def commit(self):
-        self.env.cnx.commit()
-
-    @nocoverage
-    def rollback(self):
-        try:
-            self.env.cnx.rollback()
-        except ProgrammingError:
-            pass
-
-    # other utilities #########################################################
-    def set_debug(self, debugmode):
-        from cubicweb.server import set_debug
-        set_debug(debugmode)
-
-    @property
-    def config(self):
-        return self.vreg.config
-
-    def session(self):
-        """return current server side session (using default manager account)"""
-        return self.env.repo._sessions[self.env.cnx.sessionid]
-
-    def request(self, *args, **kwargs):
-        """return a web interface request"""
-        return self.env.create_request(*args, **kwargs)
-
-    @nocoverage
-    def rset_and_req(self, *args, **kwargs):
-        return self.env.get_rset_and_req(*args, **kwargs)
-
-    def entity(self, rql, args=None, eidkey=None, req=None):
-        return self.execute(rql, args, eidkey, req=req).get_entity(0, 0)
-
-    def etype_instance(self, etype, req=None):
-        req = req or self.request()
-        e = self.env.vreg['etypes'].etype_class(etype)(req)
-        e.eid = None
-        return e
-
-    def add_entity(self, etype, **kwargs):
-        rql = ['INSERT %s X' % etype]
-
-        # dict for replacement in RQL Request
-        rql_args = {}
-
-        if kwargs: #
-            rql.append(':')
-            # dict to define new entities variables
-            entities = {}
-
-            # assignement part of the request
-            sub_rql = []
-            for key, value in kwargs.iteritems():
-                # entities
-                if hasattr(value, 'eid'):
-                    new_value = "%s__" % key.upper()
-
-                    entities[new_value] = value.eid
-                    rql_args[new_value] = value.eid
-
-                    sub_rql.append("X %s %s" % (key, new_value))
-                # final attributes
-                else:
-                    sub_rql.append('X %s %%(%s)s' % (key, key))
-                    rql_args[key] = value
-            rql.append(', '.join(sub_rql))
-
-
-            if entities:
-                rql.append('WHERE')
-                # WHERE part of the request (to link entity to they eid)
-                sub_rql = []
-                for key, value in entities.iteritems():
-                    sub_rql.append("%s eid %%(%s)s" % (key, key))
-                rql.append(', '.join(sub_rql))
-
-        rql = ' '.join(rql)
-        rset = self.execute(rql, rql_args)
-        return rset.get_entity(0, 0)
-
-    def set_option(self, optname, value):
-        self.vreg.config.global_set_option(optname, value)
-
-    def pviews(self, req, rset):
-        return sorted((a.id, a.__class__) for a in self.vreg['views'].possible_views(req, rset=rset))
-
-    def pactions(self, req, rset, skipcategories=('addrelated', 'siteactions', 'useractions')):
-        return [(a.id, a.__class__) for a in self.vreg['actions'].possible_vobjects(req, rset=rset)
-                if a.category not in skipcategories]
-
-    def action_submenu(self, req, rset, id):
-        return self._test_action(self.vreg['actions'].select(id, req, rset=rset))
-
-    def _test_action(self, action):
-        class fake_menu(list):
-            @property
-            def items(self):
-                return self
-        class fake_box(object):
-            def mk_action(self, label, url, **kwargs):
-                return (label, url)
-            def box_action(self, action, **kwargs):
-                return (action.title, action.url())
-        submenu = fake_menu()
-        action.fill_menu(fake_box(), submenu)
-        return submenu
-
-    def pactions_by_cats(self, req, rset, categories=('addrelated',)):
-        return [(a.id, a.__class__) for a in self.vreg['actions'].possible_vobjects(req, rset=rset)
-                if a.category in categories]
-
-    paddrelactions = deprecated()(pactions_by_cats)
-
-    def pactionsdict(self, req, rset, skipcategories=('addrelated', 'siteactions', 'useractions')):
-        res = {}
-        for a in self.vreg['actions'].possible_vobjects(req, rset=rset):
-            if a.category not in skipcategories:
-                res.setdefault(a.category, []).append(a.__class__)
-        return res
-
-
-    def remote_call(self, fname, *args):
-        """remote call simulation"""
-        dump = simplejson.dumps
-        args = [dump(arg) for arg in args]
-        req = self.request(fname=fname, pageid='123', arg=args)
-        ctrl = self.vreg['controllers'].select('json', req)
-        return ctrl.publish(), req
-
-    # default test setup and teardown #########################################
-
-    def setup_database(self):
-        pass
-
-    def setUp(self):
-        self.restore_connection()
-        session = self.session()
-        #self.maxeid = self.execute('Any MAX(X)')
-        session.set_pool()
-        self.maxeid = session.system_sql('SELECT MAX(eid) FROM entities').fetchone()[0]
-        self.app = self.env.app
-        self.vreg = self.env.app.vreg
-        self.schema = self.vreg.schema
-        self.vreg.config.mode = 'test'
-        # set default-dest-addrs to a dumb email address to avoid mailbox or
-        # mail queue pollution
-        self.set_option('default-dest-addrs', ['whatever'])
-        self.setup_database()
-        self.commit()
-        MAILBOX[:] = [] # reset mailbox
-
-    @nocoverage
-    def tearDown(self):
-        self.rollback()
-        # self.env.restore_database()
-        self.env.restore_connection()
-        self.session().unsafe_execute('DELETE Any X WHERE X eid > %s' % self.maxeid)
-        self.commit()
-
-    # global resources accessors ###############################################
-
-# XXX
-try:
-    from cubicweb.web import Redirect
-    from urllib import unquote
-except ImportError:
-    pass # cubicweb-web not installed
-else:
-    class ControllerTC(EnvBasedTC):
-        def setUp(self):
-            super(ControllerTC, self).setUp()
-            self.req = self.request()
-            self.ctrl = self.vreg['controllers'].select('edit', self.req)
-
-        def publish(self, req):
-            assert req is self.ctrl.req
-            try:
-                result = self.ctrl.publish()
-                req.cnx.commit()
-            except Redirect:
-                req.cnx.commit()
-                raise
-            return result
-
-        def expect_redirect_publish(self, req=None):
-            if req is not None:
-                self.ctrl = self.vreg['controllers'].select('edit', req)
-            else:
-                req = self.req
-            try:
-                res = self.publish(req)
-            except Redirect, ex:
-                try:
-                    path, params = ex.location.split('?', 1)
-                except:
-                    path, params = ex.location, ""
-                req._url = path
-                cleanup = lambda p: (p[0], unquote(p[1]))
-                params = dict(cleanup(p.split('=', 1)) for p in params.split('&') if p)
-                return req.relative_path(False), params # path.rsplit('/', 1)[-1], params
-            else:
-                self.fail('expected a Redirect exception')
-
-
-def make_late_binding_repo_property(attrname):
-    @property
-    def late_binding(self):
-        """builds cnx as late as possible"""
-        if not hasattr(self, attrname):
-            # sets explicit test mode here to avoid autoreload
-            from cubicweb.cwconfig import CubicWebConfiguration
-            CubicWebConfiguration.mode = 'test'
-            cls = self.__class__
-            config = self.repo_config or TestServerConfiguration('data')
-            cls._repo, cls._cnx = init_test_database('sqlite',  config=config)
-        return getattr(self, attrname)
-    return late_binding
-
-
-class autorepo(type):
-    """automatically set repository on RepositoryBasedTC subclasses if necessary
-    """
-    def __new__(mcs, name, bases, classdict):
-        repo = classdict.get('repo')
-        # try to find repo in one of the base classes
-        if repo is None:
-            for base in bases:
-                repo = getattr(base, 'repo', None)
-                if repo is not None:
-                    classdict['repo'] = repo
-                    break
-        if name != 'RepositoryBasedTC' and not classdict.get('repo'):
-            classdict['repo'] = make_late_binding_repo_property('_repo')
-            classdict['cnx'] = make_late_binding_repo_property('_cnx')
-        return super(autorepo, mcs).__new__(mcs, name, bases, classdict)
-
-
-class RepositoryBasedTC(TestCase):
-    """abstract class for test using direct repository connections
-    """
-    __metaclass__ = autorepo
-    repo_config = None # set a particular config instance if necessary
-
-    # user / session management ###############################################
-
-    def create_user(self, user, groups=('users',), password=None, commit=True):
-        if password is None:
-            password = user
-        eid = self.execute('INSERT CWUser X: X login %(x)s, X upassword %(p)s',
-                            {'x': unicode(user), 'p': password})[0][0]
-        groups = ','.join(repr(group) for group in groups)
-        self.execute('SET X in_group Y WHERE X eid %%(x)s, Y name IN (%s)' % groups,
-                      {'x': eid})
-        if commit:
-            self.commit()
-        self.session.reset_pool()
-        return eid
-
-    def login(self, login, password=None):
-        cnx = repo_connect(self.repo, unicode(login), password or login,
-                           ConnectionProperties('inmemory'))
-        self.cnxs.append(cnx)
-        return cnx
-
-    def current_session(self):
-        return self.repo._sessions[self.cnxs[-1].sessionid]
-
-    def restore_connection(self):
-        assert len(self.cnxs) == 1, self.cnxs
-        cnx = self.cnxs.pop()
-        try:
-            cnx.close()
-        except Exception, ex:
-            print "exception occured while closing connection", ex
-
-    # db api ##################################################################
-
-    def execute(self, rql, args=None, eid_key=None):
-        assert self.session.id == self.cnxid
-        rset = self.__execute(self.cnxid, rql, args, eid_key)
-        rset.vreg = self.vreg
-        rset.req = self.session
-        # call to set_pool is necessary to avoid pb when using
-        # instance entities for convenience
-        self.session.set_pool()
-        return rset
-
-    def commit(self):
-        self.__commit(self.cnxid)
-        self.session.set_pool()
-
-    def rollback(self):
-        self.__rollback(self.cnxid)
-        self.session.set_pool()
-
-    def close(self):
-        self.__close(self.cnxid)
-
-    # other utilities #########################################################
-
-    def set_debug(self, debugmode):
-        from cubicweb.server import set_debug
-        set_debug(debugmode)
-
-    def set_option(self, optname, value):
-        self.vreg.config.global_set_option(optname, value)
-
-    def add_entity(self, etype, **kwargs):
-        restrictions = ', '.join('X %s %%(%s)s' % (key, key) for key in kwargs)
-        rql = 'INSERT %s X' % etype
-        if kwargs:
-            rql += ': %s' % ', '.join('X %s %%(%s)s' % (key, key) for key in kwargs)
-        rset = self.execute(rql, kwargs)
-        return rset.get_entity(0, 0)
-
-    def default_user_password(self):
-        config = self.repo.config #TestConfiguration('data')
-        user = unicode(config.sources()['system']['db-user'])
-        passwd = config.sources()['system']['db-password']
-        return user, passwd
-
-    def close_connections(self):
-        for cnx in self.cnxs:
-            try:
-                cnx.rollback()
-                cnx.close()
-            except:
-                continue
-        self.cnxs = []
-
-    pactions = EnvBasedTC.pactions.im_func
-    pactionsdict = EnvBasedTC.pactionsdict.im_func
-
-    # default test setup and teardown #########################################
-
-    def _prepare(self):
-        MAILBOX[:] = [] # reset mailbox
-        if hasattr(self, 'cnxid'):
-            return
-        repo = self.repo
-        self.__execute = repo.execute
-        self.__commit = repo.commit
-        self.__rollback = repo.rollback
-        self.__close = repo.close
-        self.cnxid = self.cnx.sessionid
-        self.session = repo._sessions[self.cnxid]
-        self.cnxs = []
-        # reset caches, they may introduce bugs among tests
-        repo._type_source_cache = {}
-        repo._extid_cache = {}
-        repo.querier._rql_cache = {}
-        for source in repo.sources:
-            source.reset_caches()
-        for s in repo.sources:
-            if hasattr(s, '_cache'):
-                s._cache = {}
-
-    @property
-    def config(self):
-        return self.repo.config
-
-    @property
-    def vreg(self):
-        return self.repo.vreg
-
-    @property
-    def schema(self):
-        return self.repo.schema
-
-    def setUp(self):
-        self._prepare()
-        self.session.set_pool()
-        self.maxeid = self.session.system_sql('SELECT MAX(eid) FROM entities').fetchone()[0]
-
-    def tearDown(self):
-        self.close_connections()
-        self.rollback()
-        self.session.unsafe_execute('DELETE Any X WHERE X eid > %(x)s', {'x': self.maxeid})
-        self.commit()
-
--- a/devtools/devctl.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/devtools/devctl.py	Wed Sep 30 18:57:42 2009 +0200
@@ -21,13 +21,13 @@
 from logilab.common.shellutils import ASK
 from logilab.common.clcommands import register_commands
 
-from cubicweb import (CW_SOFTWARE_ROOT as BASEDIR, BadCommandUsage,
-                      underline_title)
+from cubicweb import CW_SOFTWARE_ROOT as BASEDIR, BadCommandUsage
 from cubicweb.__pkginfo__ import version as cubicwebversion
-from cubicweb.toolsutils import Command, copy_skeleton
+from cubicweb.toolsutils import Command, copy_skeleton, underline_title
 from cubicweb.web.webconfig import WebConfiguration
 from cubicweb.server.serverconfig import ServerConfiguration
 
+
 class DevCubeConfiguration(ServerConfiguration, WebConfiguration):
     """dummy config to get full library schema and entities"""
     creating = True
@@ -124,19 +124,19 @@
     if libconfig is not None:
         from cubicweb.cwvreg import CubicWebVRegistry, clear_rtag_objects
         libschema = libconfig.load_schema(remove_unused_rtypes=False)
-        rinlined = deepcopy(uicfg.autoform_is_inlined)
+        afs = deepcopy(uicfg.autoform_section)
         appearsin_addmenu = deepcopy(uicfg.actionbox_appearsin_addmenu)
         clear_rtag_objects()
         cleanup_sys_modules(libconfig)
         libvreg = CubicWebVRegistry(libconfig)
         libvreg.set_schema(libschema) # trigger objects registration
-        librinlined = uicfg.autoform_is_inlined
+        libafs = uicfg.autoform_section
         libappearsin_addmenu = uicfg.actionbox_appearsin_addmenu
         # prefill vregdone set
         list(_iter_vreg_objids(libvreg, vregdone))
     else:
         libschema = {}
-        rinlined = uicfg.autoform_is_inlined
+        afs = uicfg.autoform_section
         appearsin_addmenu = uicfg.actionbox_appearsin_addmenu
     done = set()
     for eschema in sorted(schema.entities()):
@@ -154,9 +154,11 @@
             continue
         for rschema, targetschemas, role in eschema.relation_definitions(True):
             for tschema in targetschemas:
-                if rinlined.etype_get(eschema, rschema, role, tschema) and \
+                fsections = afs.etype_get(eschema, rschema, role, tschema)
+                if 'inlined_attributes' in fsections and \
                        (libconfig is None or not
-                        librinlined.etype_get(eschema, rschema, role, tschema)):
+                        'inlined_attributes' in libafs.etype_get(
+                            eschema, rschema, role, tschema)):
                     add_msg(w, 'add a %s' % tschema,
                             'inlined:%s.%s.%s' % (etype, rschema, role))
                     add_msg(w, 'remove this %s' % tschema,
@@ -212,6 +214,7 @@
         add_msg(w, '%s_description' % objid)
         add_msg(w, objid)
 
+
 def _iter_vreg_objids(vreg, done, prefix=None):
     for reg, objdict in vreg.items():
         for objects in objdict.values():
@@ -219,7 +222,7 @@
                 objid = '%s_%s' % (reg, obj.id)
                 if objid in done:
                     break
-                if obj.property_defs:
+                if obj.cw_property_defs:
                     yield objid
                     done.add(objid)
                     break
--- a/devtools/fake.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/devtools/fake.py	Wed Sep 30 18:57:42 2009 +0200
@@ -7,12 +7,11 @@
 """
 __docformat__ = "restructuredtext en"
 
-from logilab.common.testlib import mock_object as Mock
 from logilab.common.adbh import get_adv_func_helper
 
 from indexer import get_indexer
 
-from cubicweb import RequestSessionMixIn
+from cubicweb.req import RequestSessionBase
 from cubicweb.cwvreg import CubicWebVRegistry
 from cubicweb.web.request import CubicWebRequestBase
 from cubicweb.devtools import BASE_URL, BaseApptestConfiguration
@@ -48,6 +47,8 @@
         super(FakeRequest, self).__init__(*args, **kwargs)
         self._session_data = {}
         self._headers = {}
+        self.config = self.vreg.config
+        self.schema = self.vreg.schema
 
     def header_accept_language(self):
         """returns an ordered list of preferred languages"""
@@ -130,7 +131,7 @@
         return True
 
 
-class FakeSession(RequestSessionMixIn):
+class FakeSession(RequestSessionBase):
     def __init__(self, repo=None, user=None):
         self.repo = repo
         self.vreg = getattr(self.repo, 'vreg', CubicWebVRegistry(FakeConfig(), initlog=False))
--- a/devtools/htmlparser.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/devtools/htmlparser.py	Wed Sep 30 18:57:42 2009 +0200
@@ -174,3 +174,5 @@
                 except KeyError:
                     continue
         return False
+
+VALMAP = {None: None, 'dtd': DTDValidator, 'xml': SaxOnlyValidator}
--- a/devtools/livetest.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/devtools/livetest.py	Wed Sep 30 18:57:42 2009 +0200
@@ -6,9 +6,10 @@
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
+import os
 import socket
 import logging
-from os.path import join, dirname, exists
+from os.path import join, dirname, normpath, abspath
 from StringIO import StringIO
 
 #from twisted.application import service, strports
@@ -21,10 +22,9 @@
 
 from logilab.common.testlib import TestCase
 
-import cubicweb.web
 from cubicweb.dbapi import in_memory_cnx
 from cubicweb.etwist.server import CubicWebRootResource
-from cubicweb.devtools import LivetestConfiguration, init_test_database
+from cubicweb.devtools import BaseApptestConfiguration, init_test_database
 
 
 
@@ -50,25 +50,57 @@
 
 
 
+class LivetestConfiguration(BaseApptestConfiguration):
+    init_repository = False
+
+    def __init__(self, cube=None, sourcefile=None, pyro_name=None,
+                 log_threshold=logging.CRITICAL):
+        BaseApptestConfiguration.__init__(self, cube, log_threshold=log_threshold)
+        self.appid = pyro_name or cube
+        # don't change this, else some symlink problems may arise in some
+        # environment (e.g. mine (syt) ;o)
+        # XXX I'm afraid this test will prevent to run test from a production
+        # environment
+        self._sources = None
+        # instance cube test
+        if cube is not None:
+            self.apphome = self.cube_dir(cube)
+        elif 'web' in os.getcwd().split(os.sep):
+            # web test
+            self.apphome = join(normpath(join(dirname(__file__), '..')), 'web')
+        else:
+            # cube test
+            self.apphome = abspath('..')
+        self.sourcefile = sourcefile
+        self.global_set_option('realm', '')
+        self.use_pyro = pyro_name is not None
+
+    def pyro_enabled(self):
+        if self.use_pyro:
+            return True
+        else:
+            return False
+
+
+
 def make_site(cube, options=None):
     from cubicweb.etwist import twconfig # trigger configuration registration
-    sourcefile = options.sourcefile
-    config = LivetestConfiguration(cube, sourcefile,
+    config = LivetestConfiguration(cube, options.sourcefile,
                                    pyro_name=options.pyro_name,
                                    log_threshold=logging.DEBUG)
-    source = config.sources()['system']
-    init_test_database(driver=source['db-driver'], config=config)
+    init_test_database(config=config)
     # if '-n' in sys.argv: # debug mode
     cubicweb = LivetestResource(config, debug=True)
     toplevel = cubicweb
     website = server.Site(toplevel)
     cube_dir = config.cube_dir(cube)
+    source = config.sources()['system']
     for port in xrange(7777, 7798):
         try:
             reactor.listenTCP(port, channel.HTTPFactory(website))
             saveconf(cube_dir, port, source['db-user'], source['db-password'])
             break
-        except CannotListenError, exc:
+        except CannotListenError:
             print "port %s already in use, I will try another one" % port
     else:
         raise
--- a/devtools/migrtest.py	Wed Sep 30 17:57:02 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-"""Migration test script
-
-* migration will be played into a chroot of the local machine
-* the database server used can be configured
-* test tested instance may be on another host
-
-
-We are using postgres'.pgpass file. Here is a copy of postgres documentation
-about that:
-
-The file .pgpass in a user's home directory or the file referenced by
-PGPASSFILE can contain passwords to be used if the connection requires
-a password (and no password has been specified otherwise).
-
-
-This file should contain lines of the following format:
-
-hostname:port:database:username:password
-
-Each of the first four fields may be a literal value, or *, which
-matches anything. The password field from the first line that matches
-the current connection parameters will be used. (Therefore, put
-more-specific entries first when you are using wildcards.) If an entry
-needs to contain : or \, escape this character with \. A hostname of
-localhost matches both host (TCP) and local (Unix domain socket)
-connections coming from the local machine.
-
-The permissions on .pgpass must disallow any access to world or group;
-achieve this by the command chmod 0600 ~/.pgpass. If the permissions
-are less strict than this, the file will be ignored.
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from os import system
-from os.path import join, basename
-
-from logilab.common.shellutils import cp, rm
-
-from cubicweb.toolsutils import read_config
-from cubicweb.server.utils import generate_sources_file
-
-# XXXX use db-copy instead
-
-# test environment configuration
-chrootpath = '/sandbox/cubicwebtest'
-tmpdbhost = 'crater'
-tmpdbuser = 'syt'
-tmpdbpasswd = 'syt'
-
-def play_migration(applhome, applhost='', sudo=False):
-    applid = dbname = basename(applhome)
-    testapplhome = join(chrootpath, applhome)
-    # copy instance into the chroot
-    if applhost:
-        system('scp -r %s:%s %s' % (applhost, applhome, testapplhome))
-    else:
-        cp(applhome, testapplhome)
-##     # extract db parameters
-##     sources = read_config(join(testapplhome, 'sources'))
-##     dbname = sources['system']['db-name']
-##     dbhost = sources['system'].get('db-host') or ''
-##     dbuser = sources['system'].get('db-user') or ''
-##     dbpasswd = sources['system'].get('db-password') or ''
-    # generate sources file
-    # XXX multisources
-    sources = {'system': {}}
-    sources['system']['db-encoding'] = 'UTF8' # XXX
-    sources['system']['db-name'] = dbname
-    sources['system']['db-host'] = None
-    sources['system']['db-user'] = tmpdbuser
-    sources['system']['db-password'] = None
-    generate_sources_file(join(testapplhome, 'sources'), sources)
-##     # create postgres password file so we won't need anymore passwords
-##     # XXX may exist!
-##     pgpassfile = expanduser('~/.pgpass')
-##     pgpass = open(pgpassfile, 'w')
-##     if dbpasswd:
-##         pgpass.write('%s:*:%s:%s:%s\n' % (dbhost or applhost or 'localhost',
-##                                           dbname, dbuser, dbpasswd))
-##     if tmpdbpasswd:
-##         pgpass.write('%s:*:%s:%s:%s\n' % (tmpdbhost or 'localhost', dbname,
-##                                           tmpdbuser, tmpdbpasswd))
-##     pgpass.close()
-##     chmod(pgpassfile, 0600)
-    # dump db
-##     dumpcmd = 'pg_dump -Fc -U %s -f /tmp/%s.dump %s' % (
-##         dbuser, dbname, dbname)
-##     if dbhost:
-##         dumpcmd += ' -h %s' % dbhost
-    dumpfile = '/tmp/%s.dump' % applid
-    dumpcmd = 'cubicweb-ctl db-dump --output=%s %s' % (dumpfile, applid)
-    if sudo:
-        dumpcmd = 'sudo %s' % dumpcmd
-    if applhost:
-        dumpcmd = 'ssh %s "%s"' % (applhost, dumpcmd)
-    if system(dumpcmd):
-        raise Exception('error while dumping the database')
-##     if not dbhost and applhost:
-    if applhost:
-        # retrieve the dump
-        if system('scp %s:%s %s' % (applhost, dumpfile, dumpfile)):
-            raise Exception('error while retreiving the dump')
-    # move the dump into the chroot
-    system('mv %s %s%s' % (dumpfile, chrootpath, dumpfile))
-    # locate installed versions
-    vcconf = read_config(join(testapplhome, 'vc.conf'))
-    template = vcconf['TEMPLATE']
-    cubicwebversion = vcconf['CW']
-    templversion = vcconf['TEMPLATE_VERSION']
-    # install the same versions cubicweb and template versions into the chroot
-    system('sudo chroot %s apt-get update' % chrootpath)
-    system('sudo chroot %s apt-get install cubicweb-server=%s cubicweb-client=%s'
-           % (chrootpath, cubicwebversion, cubicwebversion))
-    system('sudo chroot %s apt-get install cubicweb-%s-appl-server=%s cubicweb-%s-appl-client=%s'
-           % (chrootpath, template, templversion, template, templversion))
-    # update and upgrade to the latest version
-    system('sudo chroot %s apt-get install cubicweb-server cubicweb-client' % chrootpath)
-    system('sudo chroot %s apt-get install cubicweb-%s-appl-server cubicweb-%s-appl-client'
-           % (chrootpath, template, template))
-    # create and fill the database
-    system('sudo chroot cubicweb-ctl db-restore %s %s' % (applid, dumpfile))
-##     if not tmpdbhost:
-##         system('createdb -U %s -T template0 -E UTF8 %s' % (tmpdbuser, dbname))
-##         system('pg_restore -U %s -O -Fc -d %s /tmp/%s.dump'
-##                % (tmpdbuser, dbname, dbname))
-##     else:
-##         system('createdb -h %s -U %s -T template0 -E UTF8 %s'
-##                % (tmpdbhost, tmpdbuser, dbname))
-##         system('pg_restore -h %s -U %s -O -Fc -d %s /tmp/%s.dump'
-##                % (tmpdbhost, tmpdbuser, dbname, dbname))
-    # launch upgrade
-    system('sudo chroot %s cubicweb-ctl upgrade %s' % (chrootpath, applid))
-
-    # cleanup
-    rm(testapplhome)
-##     rm(pgpassfile)
-##     if tmpdbhost:
-##         system('dropdb -h %s -U %s %s' % (tmpdbuser, tmpdbhost, dbname))
-##     else:
-##         system('dropdb -U %s %s' % (tmpdbuser, dbname))
-##     if not dbhost and applhost:
-    if applhost:
-        system('ssh %s rm %s' % (applhost, dumpfile))
-    rm('%s%s' % (chrootpath, dumpfile))
-
-
-if __name__ == '__main__':
-    play_migration('/etc/cubicweb.d/jpl', 'lepus')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/devtools/realdbtest.py	Wed Sep 30 18:57:42 2009 +0200
@@ -0,0 +1,43 @@
+import logging
+from cubicweb import toolsutils
+from cubicweb.devtools import DEFAULT_SOURCES, BaseApptestConfiguration
+
+class RealDatabaseConfiguration(BaseApptestConfiguration):
+    init_repository = False
+    sourcesdef =  DEFAULT_SOURCES.copy()
+
+    def sources(self):
+        """
+        By default, we run tests with the sqlite DB backend.
+        One may use its own configuration by just creating a
+        'sources' file in the test directory from wich tests are
+        launched.
+        """
+        self._sources = self.sourcesdef
+        return self._sources
+
+
+def buildconfig(dbuser, dbpassword, dbname, adminuser, adminpassword, dbhost=None):
+    """convenience function that builds a real-db configuration class"""
+    sourcesdef =  {'system': {'adapter' : 'native',
+                              'db-encoding' : 'UTF-8', #'ISO-8859-1',
+                              'db-user' : dbuser,
+                              'db-password' : dbpassword,
+                              'db-name' : dbname,
+                              'db-driver' : 'postgres',
+                              'db-host' : dbhost,
+                              },
+                   'admin' : {'login': adminuser,
+                              'password': adminpassword,
+                              },
+                   }
+    return type('MyRealDBConfig', (RealDatabaseConfiguration,),
+                {'sourcesdef': sourcesdef})
+
+
+def loadconfig(filename):
+    """convenience function that builds a real-db configuration class
+    from a file
+    """
+    return type('MyRealDBConfig', (RealDatabaseConfiguration,),
+                {'sourcesdef': toolsutils.read_config(filename)})
--- a/devtools/test/unittest_testlib.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/devtools/test/unittest_testlib.py	Wed Sep 30 18:57:42 2009 +0200
@@ -10,11 +10,11 @@
 from unittest import TestSuite
 
 
-from logilab.common.testlib import (TestCase, unittest_main, mock_object,
+from logilab.common.testlib import (TestCase, unittest_main, 
                                     SkipAwareTextTestRunner)
+
 from cubicweb.devtools import htmlparser
-
-from cubicweb.devtools.testlib import WebTest, EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 
 class WebTestTC(TestCase):
 
@@ -23,7 +23,7 @@
         self.runner = SkipAwareTextTestRunner(stream=output)
 
     def test_error_raised(self):
-        class MyWebTest(WebTest):
+        class MyWebTest(CubicWebTC):
 
             def test_error_view(self):
                 self.add_entity('Bug', title=u"bt")
@@ -39,7 +39,7 @@
         self.assertEquals(len(result.failures), 1)
 
 
-class TestLibTC(EnvBasedTC):
+class TestLibTC(CubicWebTC):
     def test_add_entity_with_relation(self):
         bug = self.add_entity(u'Bug', title=u"toto")
         self.add_entity(u'Bug', title=u"tata", identical_to=bug)
--- a/devtools/testlib.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/devtools/testlib.py	Wed Sep 30 18:57:42 2009 +0200
@@ -1,4 +1,4 @@
-"""this module contains base classes for web tests
+"""this module contains base classes and utilities for cubicweb tests
 
 :organization: Logilab
 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
@@ -7,84 +7,523 @@
 """
 __docformat__ = "restructuredtext en"
 
+import os
 import sys
+import re
+from urllib import unquote
 from math import log
 
-from logilab.common.debugger import Debugger
-from logilab.common.testlib import InnerTest
-from logilab.common.pytest import nocoverage
+import simplejson
+
+import yams.schema
 
-from cubicweb.devtools import VIEW_VALIDATORS
-from cubicweb.devtools.apptest import EnvBasedTC
-from cubicweb.devtools._apptest import unprotected_entities, SYSTEM_RELATIONS
-from cubicweb.devtools.htmlparser import DTDValidator, SaxOnlyValidator, HTMLValidator
-from cubicweb.devtools.fill import insert_entity_queries, make_relations_queries
+from logilab.common.testlib import TestCase, InnerTest
+from logilab.common.pytest import nocoverage, pause_tracing, resume_tracing
+from logilab.common.debugger import Debugger
+from logilab.common.umessage import message_from_string
+from logilab.common.decorators import cached, classproperty
+from logilab.common.deprecation import deprecated
 
-from cubicweb.sobjects.notification import NotificationView
-
-from cubicweb.vregistry import NoSelectableObject
+from cubicweb import NoSelectableObject, cwconfig, devtools, web, server
+from cubicweb.dbapi import repo_connect, ConnectionProperties, ProgrammingError
+from cubicweb.sobjects import notification
+from cubicweb.web import application
+from cubicweb.devtools import SYSTEM_ENTITIES, SYSTEM_RELATIONS, VIEW_VALIDATORS
+from cubicweb.devtools import fake, htmlparser
 
 
-## TODO ###############
-# creation tests: make sure an entity was actually created
-# Existing Test Environment
+# low-level utilities ##########################################################
 
 class CubicWebDebugger(Debugger):
-
+    """special debugger class providing a 'view' function which saves some
+    html into a temporary file and open a web browser to examinate it.
+    """
     def do_view(self, arg):
         import webbrowser
         data = self._getval(arg)
         file('/tmp/toto.html', 'w').write(data)
         webbrowser.open('file:///tmp/toto.html')
 
-def how_many_dict(schema, cursor, how_many, skip):
-    """compute how many entities by type we need to be able to satisfy relations
-    cardinality
-    """
-    # compute how many entities by type we need to be able to satisfy relation constraint
-    relmap = {}
-    for rschema in schema.relations():
-        if rschema.is_final():
-            continue
-        for subj, obj in rschema.iter_rdefs():
-            card = rschema.rproperty(subj, obj, 'cardinality')
-            if card[0] in '1?' and len(rschema.subjects(obj)) == 1:
-                relmap.setdefault((rschema, subj), []).append(str(obj))
-            if card[1] in '1?' and len(rschema.objects(subj)) == 1:
-                relmap.setdefault((rschema, obj), []).append(str(subj))
-    unprotected = unprotected_entities(schema)
-    for etype in skip:
-        unprotected.add(etype)
-    howmanydict = {}
-    for etype in unprotected_entities(schema, strict=True):
-        howmanydict[str(etype)] = cursor.execute('Any COUNT(X) WHERE X is %s' % etype)[0][0]
-        if etype in unprotected:
-            howmanydict[str(etype)] += how_many
-    for (rschema, etype), targets in relmap.iteritems():
-        # XXX should 1. check no cycle 2. propagate changes
-        relfactor = sum(howmanydict[e] for e in targets)
-        howmanydict[str(etype)] = max(relfactor, howmanydict[etype])
-    return howmanydict
-
 
 def line_context_filter(line_no, center, before=3, after=None):
     """return true if line are in context
-    if after is None: after = before"""
+
+    if after is None: after = before
+    """
     if after is None:
         after = before
     return center - before <= line_no <= center + after
 
-## base webtest class #########################################################
-VALMAP = {None: None, 'dtd': DTDValidator, 'xml': SaxOnlyValidator}
+
+def unprotected_entities(schema, strict=False):
+    """returned a set of each non final entity type, excluding "system" entities
+    (eg CWGroup, CWUser...)
+    """
+    if strict:
+        protected_entities = yams.schema.BASE_TYPES
+    else:
+        protected_entities = yams.schema.BASE_TYPES.union(SYSTEM_ENTITIES)
+    return set(schema.entities()) - protected_entities
+
+
+def get_versions(self, checkversions=False):
+    """return the a dictionary containing cubes used by this instance
+    as key with their version as value, including cubicweb version. This is a
+    public method, not requiring a session id.
+
+    replace Repository.get_versions by this method if you don't want versions
+    checking
+    """
+    vcconf = {'cubicweb': self.config.cubicweb_version()}
+    self.config.bootstrap_cubes()
+    for pk in self.config.cubes():
+        version = self.config.cube_version(pk)
+        vcconf[pk] = version
+    self.config._cubes = None
+    return vcconf
+
+
+def refresh_repo(repo):
+    devtools.reset_test_database(repo.config)
+    for pool in repo.pools:
+        pool.reconnect()
+    repo._type_source_cache = {}
+    repo._extid_cache = {}
+    repo.querier._rql_cache = {}
+    for source in repo.sources:
+        source.reset_caches()
+
+
+# email handling, to test emails sent by an application ########################
+
+MAILBOX = []
+
+class Email:
+    """you'll get instances of Email into MAILBOX during tests that trigger
+    some notification.
+
+    * `msg` is the original message object
+
+    * `recipients` is a list of email address which are the recipients of this
+      message
+    """
+    def __init__(self, recipients, msg):
+        self.recipients = recipients
+        self.msg = msg
+
+    @property
+    def message(self):
+        return message_from_string(self.msg)
+
+    @property
+    def subject(self):
+        return self.message.get('Subject')
+
+    @property
+    def content(self):
+        return self.message.get_payload(decode=True)
+
+    def __repr__(self):
+        return '<Email to %s with subject %s>' % (','.join(self.recipients),
+                                                  self.message.get('Subject'))
+
+# the trick to get email into MAILBOX instead of actually sent: monkey patch
+# cwconfig.SMTP object
+class MockSMTP:
+    def __init__(self, server, port):
+        pass
+    def close(self):
+        pass
+    def sendmail(self, helo_addr, recipients, msg):
+        MAILBOX.append(Email(recipients, msg))
+
+cwconfig.SMTP = MockSMTP
+
+
+# base class for cubicweb tests requiring a full cw environments ###############
+
+class CubicWebTC(TestCase):
+    """abstract class for test using an apptest environment
+
+    attributes:
+    `vreg`, the vregistry
+    `schema`, self.vreg.schema
+    `config`, cubicweb configuration
+    `cnx`, dbapi connection to the repository using an admin user
+    `session`, server side session associated to `cnx`
+    `app`, the cubicweb publisher (for web testing)
+    `repo`, the repository object
+
+    `admlogin`, login of the admin user
+    `admpassword`, password of the admin user
+
+    """
+    appid = 'data'
+    configcls = devtools.ApptestConfiguration
+
+    @classproperty
+    def config(cls):
+        """return the configuration object. Configuration is cached on the test
+        class.
+        """
+        try:
+            return cls.__dict__['_config']
+        except KeyError:
+            config = cls._config = cls.configcls(cls.appid)
+            config.mode = 'test'
+            return config
+
+    @classmethod
+    def init_config(cls, config):
+        """configuration initialization hooks. You may want to override this."""
+        source = config.sources()['system']
+        cls.admlogin = unicode(source['db-user'])
+        cls.admpassword = source['db-password']
+        # uncomment the line below if you want rql queries to be logged
+        #config.global_set_option('query-log-file',
+        #                         '/tmp/test_rql_log.' + `os.getpid()`)
+        config.global_set_option('log-file', None)
+        # set default-dest-addrs to a dumb email address to avoid mailbox or
+        # mail queue pollution
+        config.global_set_option('default-dest-addrs', ['whatever'])
+        try:
+            send_to =  '%s@logilab.fr' % os.getlogin()
+            # AttributeError since getlogin not available under all platforms
+        except (OSError, AttributeError):
+            send_to =  '%s@logilab.fr' % (os.environ.get('USER')
+                                          or os.environ.get('USERNAME')
+                                          or os.environ.get('LOGNAME'))
+        config.global_set_option('sender-addr', send_to)
+        config.global_set_option('default-dest-addrs', send_to)
+        config.global_set_option('sender-name', 'cubicweb-test')
+        config.global_set_option('sender-addr', 'cubicweb-test@logilab.fr')
+        # web resources
+        config.global_set_option('base-url', devtools.BASE_URL)
+        try:
+            config.global_set_option('embed-allowed', re.compile('.*'))
+        except: # not in server only configuration
+            pass
+
+    @classmethod
+    def _init_repo(cls):
+        """init the repository and connection to it.
+
+        Repository and connection are cached on the test class. Once
+        initialized, we simply reset connections and repository caches.
+        """
+        if not 'repo' in cls.__dict__:
+            cls._build_repo()
+        else:
+            cls.cnx.rollback()
+            cls._refresh_repo()
+
+    @classmethod
+    def _build_repo(cls):
+        cls.repo, cls.cnx = devtools.init_test_database(config=cls.config)
+        cls.init_config(cls.config)
+        cls.vreg = cls.repo.vreg
+        cls._orig_cnx = cls.cnx
+        cls.config.repository = lambda x=None: cls.repo
+        # necessary for authentication tests
+        cls.cnx.login = cls.admlogin
+        cls.cnx.password = cls.admpassword
+
+    @classmethod
+    def _refresh_repo(cls):
+        refresh_repo(cls.repo)
+
+    # global resources accessors ###############################################
+
+    @property
+    def schema(self):
+        """return the application schema"""
+        return self.vreg.schema
+
+    @property
+    def session(self):
+        """return current server side session (using default manager account)"""
+        return self.repo._sessions[self.cnx.sessionid]
+
+    @property
+    def adminsession(self):
+        """return current server side session (using default manager account)"""
+        return self.repo._sessions[self._orig_cnx.sessionid]
+
+    def set_option(self, optname, value):
+        self.config.global_set_option(optname, value)
+
+    def set_debug(self, debugmode):
+        server.set_debug(debugmode)
+
+    # default test setup and teardown #########################################
+
+    def setUp(self):
+        pause_tracing()
+        self._init_repo()
+        resume_tracing()
+        self.setup_database()
+        self.commit()
+        MAILBOX[:] = [] # reset mailbox
+
+    def setup_database(self):
+        """add your database setup code by overriding this method"""
+
+    # user / session management ###############################################
+
+    def user(self, req=None):
+        """return the application schema"""
+        if req is None:
+            req = self.request()
+            return self.cnx.user(req)
+        else:
+            return req.user
 
-class WebTest(EnvBasedTC):
-    """base class for web tests"""
-    __abstract__ = True
+    def create_user(self, login, groups=('users',), password=None, req=None,
+                    commit=True):
+        """create and return a new user entity"""
+        if password is None:
+            password = login.encode('utf8')
+        cursor = self._orig_cnx.cursor(req or self.request())
+        rset = cursor.execute('INSERT CWUser X: X login %(login)s, X upassword %(passwd)s',
+                              {'login': unicode(login), 'passwd': password})
+        user = rset.get_entity(0, 0)
+        cursor.execute('SET X in_group G WHERE X eid %%(x)s, G name IN(%s)'
+                       % ','.join(repr(g) for g in groups),
+                       {'x': user.eid}, 'x')
+        user.clear_related_cache('in_group', 'subject')
+        if commit:
+            self._orig_cnx.commit()
+        return user
+
+    def login(self, login, password=None):
+        """return a connection for the given login/password"""
+        if login == self.admlogin:
+            self.restore_connection()
+        else:
+            self.cnx = repo_connect(self.repo, unicode(login),
+                                    password or str(login),
+                                    ConnectionProperties('inmemory'))
+        if login == self.vreg.config.anonymous_user()[0]:
+            self.cnx.anonymous_connection = True
+        return self.cnx
+
+    def restore_connection(self):
+        if not self.cnx is self._orig_cnx:
+            try:
+                self.cnx.close()
+            except ProgrammingError:
+                pass # already closed
+        self.cnx = self._orig_cnx
+
+    # db api ##################################################################
+
+    @nocoverage
+    def cursor(self, req=None):
+        return self.cnx.cursor(req or self.request())
+
+    @nocoverage
+    def execute(self, rql, args=None, eidkey=None, req=None):
+        """executes <rql>, builds a resultset, and returns a couple (rset, req)
+        where req is a FakeRequest
+        """
+        req = req or self.request(rql=rql)
+        return self.cnx.cursor(req).execute(unicode(rql), args, eidkey)
+
+    @nocoverage
+    def commit(self):
+        self.cnx.commit()
+
+    @nocoverage
+    def rollback(self):
+        try:
+            self.cnx.rollback()
+        except ProgrammingError:
+            pass
+
+    # # server side db api #######################################################
+
+    def sexecute(self, rql, args=None, eid_key=None):
+        self.session.set_pool()
+        return self.session.execute(rql, args, eid_key)
+
+    # other utilities #########################################################
+
+    def entity(self, rql, args=None, eidkey=None, req=None):
+        return self.execute(rql, args, eidkey, req=req).get_entity(0, 0)
+
+    def add_entity(self, etype, req=None, **kwargs):
+        rql = ['INSERT %s X' % etype]
+        # dict for replacement in RQL Request
+        args = {}
+        if kwargs:
+            rql.append(':')
+            # dict to define new entities variables
+            entities = {}
+            # assignement part of the request
+            sub_rql = []
+            for key, value in kwargs.iteritems():
+                # entities
+                if hasattr(value, 'eid'):
+                    new_value = "%s__" % key.upper()
+                    entities[new_value] = value.eid
+                    args[new_value] = value.eid
+
+                    sub_rql.append("X %s %s" % (key, new_value))
+                # final attributes
+                else:
+                    sub_rql.append('X %s %%(%s)s' % (key, key))
+                    args[key] = value
+            rql.append(', '.join(sub_rql))
+            if entities:
+                rql.append('WHERE')
+                # WHERE part of the request (to link entity to they eid)
+                sub_rql = []
+                for key, value in entities.iteritems():
+                    sub_rql.append("%s eid %%(%s)s" % (key, key))
+                rql.append(', '.join(sub_rql))
+        return self.execute(' '.join(rql), args, req=req).get_entity(0, 0)
+
+    # vregistry inspection utilities ###########################################
+
+    def pviews(self, req, rset):
+        return sorted((a.__regid__, a.__class__)
+                      for a in self.vreg['views'].possible_views(req, rset=rset))
+
+    def pactions(self, req, rset,
+                 skipcategories=('addrelated', 'siteactions', 'useractions')):
+        return [(a.__regid__, a.__class__)
+                for a in self.vreg['actions'].poss_visible_objects(req, rset=rset)
+                if a.category not in skipcategories]
+
+    def pactions_by_cats(self, req, rset, categories=('addrelated',)):
+        return [(a.__regid__, a.__class__)
+                for a in self.vreg['actions'].poss_visible_objects(req, rset=rset)
+                if a.category in categories]
 
-    pdbclass = CubicWebDebugger
-    # this is a hook to be able to define a list of rql queries
-    # that are application dependent and cannot be guessed automatically
-    application_rql = []
+    def pactionsdict(self, req, rset,
+                     skipcategories=('addrelated', 'siteactions', 'useractions')):
+        res = {}
+        for a in self.vreg['actions'].poss_visible_objects(req, rset=rset):
+            if a.category not in skipcategories:
+                res.setdefault(a.category, []).append(a.__class__)
+        return res
+
+    def action_submenu(self, req, rset, id):
+        return self._test_action(self.vreg['actions'].select(id, req, rset=rset))
+
+    def _test_action(self, action):
+        class fake_menu(list):
+            @property
+            def items(self):
+                return self
+        class fake_box(object):
+            def mk_action(self, label, url, **kwargs):
+                return (label, url)
+            def box_action(self, action, **kwargs):
+                return (action.title, action.url())
+        submenu = fake_menu()
+        action.fill_menu(fake_box(), submenu)
+        return submenu
+
+    def list_views_for(self, rset):
+        """returns the list of views that can be applied on `rset`"""
+        req = rset.req
+        only_once_vids = ('primary', 'secondary', 'text')
+        req.data['ex'] = ValueError("whatever")
+        viewsvreg = self.vreg['views']
+        for vid, views in viewsvreg.items():
+            if vid[0] == '_':
+                continue
+            if rset.rowcount > 1 and vid in only_once_vids:
+                continue
+            views = [view for view in views
+                     if view.category != 'startupview'
+                     and not issubclass(view, notification.NotificationView)]
+            if views:
+                try:
+                    view = viewsvreg._select_best(views, req, rset=rset)
+                    if view.linkable():
+                        yield view
+                    else:
+                        not_selected(self.vreg, view)
+                    # else the view is expected to be used as subview and should
+                    # not be tested directly
+                except NoSelectableObject:
+                    continue
+
+    def list_actions_for(self, rset):
+        """returns the list of actions that can be applied on `rset`"""
+        req = rset.req
+        for action in self.vreg['actions'].possible_objects(req, rset=rset):
+            yield action
+
+    def list_boxes_for(self, rset):
+        """returns the list of boxes that can be applied on `rset`"""
+        req = rset.req
+        for box in self.vreg['boxes'].possible_objects(req, rset=rset):
+            yield box
+
+    def list_startup_views(self):
+        """returns the list of startup views"""
+        req = self.request()
+        for view in self.vreg['views'].possible_views(req, None):
+            if view.category == 'startupview':
+                yield view.__regid__
+            else:
+                not_selected(self.vreg, view)
+
+    # web ui testing utilities #################################################
+
+    @property
+    @cached
+    def app(self):
+        """return a cubicweb publisher"""
+        return application.CubicWebPublisher(self.config, vreg=self.vreg)
+
+    requestcls = fake.FakeRequest
+    def request(self, *args, **kwargs):
+        """return a web ui request"""
+        req = self.requestcls(self.vreg, form=kwargs)
+        req.set_connection(self.cnx)
+        return req
+
+    def remote_call(self, fname, *args):
+        """remote json call simulation"""
+        dump = simplejson.dumps
+        args = [dump(arg) for arg in args]
+        req = self.request(fname=fname, pageid='123', arg=args)
+        ctrl = self.vreg['controllers'].select('json', req)
+        return ctrl.publish(), req
+
+    def publish(self, req):
+        """call the publish method of the edit controller"""
+        ctrl = self.vreg['controllers'].select('edit', req)
+        try:
+            result = ctrl.publish()
+            req.cnx.commit()
+        except web.Redirect:
+            req.cnx.commit()
+            raise
+        return result
+
+    def expect_redirect_publish(self, req):
+        """call the publish method of the edit controller, expecting to get a
+        Redirect exception."""
+        try:
+            self.publish(req)
+        except web.Redirect, ex:
+            try:
+                path, params = ex.location.split('?', 1)
+            except:
+                path, params = ex.location, ""
+            req._url = path
+            cleanup = lambda p: (p[0], unquote(p[1]))
+            params = dict(cleanup(p.split('=', 1)) for p in params.split('&') if p)
+            return req.relative_path(False), params # path.rsplit('/', 1)[-1], params
+        else:
+            self.fail('expected a Redirect exception')
+
+    # content validation #######################################################
 
     # validators are used to validate (XML, DTD, whatever) view's content
     # validators availables are :
@@ -99,8 +538,8 @@
         # snippets
         #'text/html': DTDValidator,
         #'application/xhtml+xml': DTDValidator,
-        'application/xml': SaxOnlyValidator,
-        'text/xml': SaxOnlyValidator,
+        'application/xml': htmlparser.SaxOnlyValidator,
+        'text/xml': htmlparser.SaxOnlyValidator,
         'text/plain': None,
         'text/comma-separated-values': None,
         'text/x-vcard': None,
@@ -109,68 +548,9 @@
         'image/png': None,
         }
     # maps vid : validator name (override content_type_validators)
-    vid_validators = dict((vid, VALMAP[valkey])
+    vid_validators = dict((vid, htmlparser.VALMAP[valkey])
                           for vid, valkey in VIEW_VALIDATORS.iteritems())
 
-    no_auto_populate = ()
-    ignored_relations = ()
-
-    def custom_populate(self, how_many, cursor):
-        pass
-
-    def post_populate(self, cursor):
-        pass
-
-    @nocoverage
-    def auto_populate(self, how_many):
-        """this method populates the database with `how_many` entities
-        of each possible type. It also inserts random relations between them
-        """
-        cu = self.cursor()
-        self.custom_populate(how_many, cu)
-        vreg = self.vreg
-        howmanydict = how_many_dict(self.schema, cu, how_many, self.no_auto_populate)
-        for etype in unprotected_entities(self.schema):
-            if etype in self.no_auto_populate:
-                continue
-            nb = howmanydict.get(etype, how_many)
-            for rql, args in insert_entity_queries(etype, self.schema, vreg, nb):
-                cu.execute(rql, args)
-        edict = {}
-        for etype in unprotected_entities(self.schema, strict=True):
-            rset = cu.execute('%s X' % etype)
-            edict[str(etype)] = set(row[0] for row in rset.rows)
-        existingrels = {}
-        ignored_relations = SYSTEM_RELATIONS + self.ignored_relations
-        for rschema in self.schema.relations():
-            if rschema.is_final() or rschema in ignored_relations:
-                continue
-            rset = cu.execute('DISTINCT Any X,Y WHERE X %s Y' % rschema)
-            existingrels.setdefault(rschema.type, set()).update((x, y) for x, y in rset)
-        q = make_relations_queries(self.schema, edict, cu, ignored_relations,
-                                   existingrels=existingrels)
-        for rql, args in q:
-            cu.execute(rql, args)
-        self.post_populate(cu)
-        self.commit()
-
-    @nocoverage
-    def _check_html(self, output, view, template='main-template'):
-        """raises an exception if the HTML is invalid"""
-        try:
-            validatorclass = self.vid_validators[view.id]
-        except KeyError:
-            if template is None:
-                default_validator = HTMLValidator
-            else:
-                default_validator = DTDValidator
-            validatorclass = self.content_type_validators.get(view.content_type,
-                                                              default_validator)
-        if validatorclass is None:
-            return None
-        validator = validatorclass()
-        return validator.parse_string(output.strip())
-
 
     def view(self, vid, rset=None, req=None, template='main-template',
              **kwargs):
@@ -225,9 +605,9 @@
             # is not an AssertionError
             klass, exc, tcbk = sys.exc_info()
             try:
-                msg = '[%s in %s] %s' % (klass, view.id, exc)
+                msg = '[%s in %s] %s' % (klass, view.__regid__, exc)
             except:
-                msg = '[%s in %s] undisplayable exception' % (klass, view.id)
+                msg = '[%s in %s] undisplayable exception' % (klass, view.__regid__)
             if output is not None:
                 position = getattr(exc, "position", (0,))[0]
                 if position:
@@ -244,9 +624,145 @@
             raise AssertionError, msg, tcbk
 
 
+    @nocoverage
+    def _check_html(self, output, view, template='main-template'):
+        """raises an exception if the HTML is invalid"""
+        try:
+            validatorclass = self.vid_validators[view.__regid__]
+        except KeyError:
+            if template is None:
+                default_validator = htmlparser.HTMLValidator
+            else:
+                default_validator = htmlparser.DTDValidator
+            validatorclass = self.content_type_validators.get(view.content_type,
+                                                              default_validator)
+        if validatorclass is None:
+            return None
+        validator = validatorclass()
+        return validator.parse_string(output.strip())
+
+    # deprecated ###############################################################
+
+    @deprecated('[3.4] use self.vreg["etypes"].etype_class(etype)(self.request())')
+    def etype_instance(self, etype, req=None):
+        req = req or self.request()
+        e = self.vreg['etypes'].etype_class(etype)(req)
+        e.eid = None
+        return e
+
+    @nocoverage
+    @deprecated('[3.4] use req = self.request(); rset = req.execute()')
+    def rset_and_req(self, rql, optional_args=None, args=None, eidkey=None):
+        """executes <rql>, builds a resultset, and returns a
+        couple (rset, req) where req is a FakeRequest
+        """
+        return (self.execute(rql, args, eidkey),
+                self.request(rql=rql, **optional_args or {}))
+
+
+# auto-populating test classes and utilities ###################################
+
+from cubicweb.devtools.fill import insert_entity_queries, make_relations_queries
+
+def how_many_dict(schema, cursor, how_many, skip):
+    """compute how many entities by type we need to be able to satisfy relations
+    cardinality
+    """
+    # compute how many entities by type we need to be able to satisfy relation constraint
+    relmap = {}
+    for rschema in schema.relations():
+        if rschema.is_final():
+            continue
+        for subj, obj in rschema.iter_rdefs():
+            card = rschema.rproperty(subj, obj, 'cardinality')
+            if card[0] in '1?' and len(rschema.subjects(obj)) == 1:
+                relmap.setdefault((rschema, subj), []).append(str(obj))
+            if card[1] in '1?' and len(rschema.objects(subj)) == 1:
+                relmap.setdefault((rschema, obj), []).append(str(subj))
+    unprotected = unprotected_entities(schema)
+    for etype in skip:
+        unprotected.add(etype)
+    howmanydict = {}
+    for etype in unprotected_entities(schema, strict=True):
+        howmanydict[str(etype)] = cursor.execute('Any COUNT(X) WHERE X is %s' % etype)[0][0]
+        if etype in unprotected:
+            howmanydict[str(etype)] += how_many
+    for (rschema, etype), targets in relmap.iteritems():
+        # XXX should 1. check no cycle 2. propagate changes
+        relfactor = sum(howmanydict[e] for e in targets)
+        howmanydict[str(etype)] = max(relfactor, howmanydict[etype])
+    return howmanydict
+
+
+class AutoPopulateTest(CubicWebTC):
+    """base class for test with auto-populating of the database"""
+    __abstract__ = True
+
+    pdbclass = CubicWebDebugger
+    # this is a hook to be able to define a list of rql queries
+    # that are application dependent and cannot be guessed automatically
+    application_rql = []
+
+    no_auto_populate = ()
+    ignored_relations = ()
+
     def to_test_etypes(self):
         return unprotected_entities(self.schema, strict=True)
 
+    def custom_populate(self, how_many, cursor):
+        pass
+
+    def post_populate(self, cursor):
+        pass
+
+    @nocoverage
+    def auto_populate(self, how_many):
+        """this method populates the database with `how_many` entities
+        of each possible type. It also inserts random relations between them
+        """
+        cu = self.cursor()
+        self.custom_populate(how_many, cu)
+        vreg = self.vreg
+        howmanydict = how_many_dict(self.schema, cu, how_many, self.no_auto_populate)
+        for etype in unprotected_entities(self.schema):
+            if etype in self.no_auto_populate:
+                continue
+            nb = howmanydict.get(etype, how_many)
+            for rql, args in insert_entity_queries(etype, self.schema, vreg, nb):
+                cu.execute(rql, args)
+        edict = {}
+        for etype in unprotected_entities(self.schema, strict=True):
+            rset = cu.execute('%s X' % etype)
+            edict[str(etype)] = set(row[0] for row in rset.rows)
+        existingrels = {}
+        ignored_relations = SYSTEM_RELATIONS | set(self.ignored_relations)
+        for rschema in self.schema.relations():
+            if rschema.is_final() or rschema in ignored_relations:
+                continue
+            rset = cu.execute('DISTINCT Any X,Y WHERE X %s Y' % rschema)
+            existingrels.setdefault(rschema.type, set()).update((x, y) for x, y in rset)
+        q = make_relations_queries(self.schema, edict, cu, ignored_relations,
+                                   existingrels=existingrels)
+        for rql, args in q:
+            cu.execute(rql, args)
+        self.post_populate(cu)
+        self.commit()
+
+    def iter_individual_rsets(self, etypes=None, limit=None):
+        etypes = etypes or self.to_test_etypes()
+        for etype in etypes:
+            if limit:
+                rql = 'Any X LIMIT %s WHERE X is %s' % (limit, etype)
+            else:
+                rql = 'Any X WHERE X is %s' % etype
+            rset = self.execute(rql)
+            for row in xrange(len(rset)):
+                if limit and row > limit:
+                    break
+                # XXX iirk
+                rset2 = rset.limit(limit=1, offset=row)
+                yield rset2
+
     def iter_automatic_rsets(self, limit=10):
         """generates basic resultsets for each entity type"""
         etypes = self.to_test_etypes()
@@ -267,54 +783,6 @@
         for rql in self.application_rql:
             yield self.execute(rql)
 
-
-    def list_views_for(self, rset):
-        """returns the list of views that can be applied on `rset`"""
-        req = rset.req
-        only_once_vids = ('primary', 'secondary', 'text')
-        req.data['ex'] = ValueError("whatever")
-        viewsvreg = self.vreg['views']
-        for vid, views in viewsvreg.items():
-            if vid[0] == '_':
-                continue
-            if rset.rowcount > 1 and vid in only_once_vids:
-                continue
-            views = [view for view in views
-                     if view.category != 'startupview'
-                     and not issubclass(view, NotificationView)]
-            if views:
-                try:
-                    view = viewsvreg.select_best(views, req, rset=rset)
-                    if view.linkable():
-                        yield view
-                    else:
-                        not_selected(self.vreg, view)
-                    # else the view is expected to be used as subview and should
-                    # not be tested directly
-                except NoSelectableObject:
-                    continue
-
-    def list_actions_for(self, rset):
-        """returns the list of actions that can be applied on `rset`"""
-        req = rset.req
-        for action in self.vreg['actions'].possible_objects(req, rset=rset):
-            yield action
-
-    def list_boxes_for(self, rset):
-        """returns the list of boxes that can be applied on `rset`"""
-        req = rset.req
-        for box in self.vreg['boxes'].possible_objects(req, rset=rset):
-            yield box
-
-    def list_startup_views(self):
-        """returns the list of startup views"""
-        req = self.request()
-        for view in self.vreg['views'].possible_views(req, None):
-            if view.category == 'startupview':
-                yield view.id
-            else:
-                not_selected(self.vreg, view)
-
     def _test_everything_for(self, rset):
         """this method tries to find everything that can be tested
         for `rset` and yields a callable test (as needed in generative tests)
@@ -326,24 +794,32 @@
                 propdefs[k]['default'] = True
         for view in self.list_views_for(rset):
             backup_rset = rset._prepare_copy(rset.rows, rset.description)
-            yield InnerTest(self._testname(rset, view.id, 'view'),
-                            self.view, view.id, rset,
+            yield InnerTest(self._testname(rset, view.__regid__, 'view'),
+                            self.view, view.__regid__, rset,
                             rset.req.reset_headers(), 'main-template')
             # We have to do this because some views modify the
             # resultset's syntax tree
             rset = backup_rset
         for action in self.list_actions_for(rset):
-            yield InnerTest(self._testname(rset, action.id, 'action'), self._test_action, action)
+            yield InnerTest(self._testname(rset, action.__regid__, 'action'), self._test_action, action)
         for box in self.list_boxes_for(rset):
-            yield InnerTest(self._testname(rset, box.id, 'box'), box.render)
+            yield InnerTest(self._testname(rset, box.__regid__, 'box'), box.render)
 
     @staticmethod
     def _testname(rset, objid, objtype):
         return '%s_%s_%s' % ('_'.join(rset.column_types(0)), objid, objtype)
 
 
-class AutomaticWebTest(WebTest):
+# concrete class for automated application testing  ############################
+
+class AutomaticWebTest(AutoPopulateTest):
     """import this if you wan automatic tests to be ran"""
+    def setUp(self):
+        AutoPopulateTest.setUp(self)
+        # access to self.app for proper initialization of the authentication
+        # machinery (else some views may fail)
+        self.app
+
     ## one each
     def test_one_each_config(self):
         self.auto_populate(1)
@@ -365,17 +841,7 @@
             yield self.view, vid, None, req
 
 
-class RealDBTest(WebTest):
-
-    def iter_individual_rsets(self, etypes=None, limit=None):
-        etypes = etypes or unprotected_entities(self.schema, strict=True)
-        for etype in etypes:
-            rset = self.execute('Any X WHERE X is %s' % etype)
-            for row in xrange(len(rset)):
-                if limit and row > limit:
-                    break
-                rset2 = rset.limit(limit=1, offset=row)
-                yield rset2
+# registry instrumentization ###################################################
 
 def not_selected(vreg, appobject):
     try:
@@ -383,16 +849,17 @@
     except (KeyError, AttributeError):
         pass
 
+
 def vreg_instrumentize(testclass):
+    # XXX broken
     from cubicweb.devtools.apptest import TestEnvironment
-    env = testclass._env = TestEnvironment('data', configcls=testclass.configcls,
-                                           requestcls=testclass.requestcls)
+    env = testclass._env = TestEnvironment('data', configcls=testclass.configcls)
     for reg in env.vreg.values():
         reg._selected = {}
         try:
             orig_select_best = reg.__class__.__orig_select_best
         except:
-            orig_select_best = reg.__class__.select_best
+            orig_select_best = reg.__class__._select_best
         def instr_select_best(self, *args, **kwargs):
             selected = orig_select_best(self, *args, **kwargs)
             try:
@@ -402,9 +869,10 @@
             except AttributeError:
                 pass # occurs on reg used to restore database
             return selected
-        reg.__class__.select_best = instr_select_best
+        reg.__class__._select_best = instr_select_best
         reg.__class__.__orig_select_best = orig_select_best
 
+
 def print_untested_objects(testclass, skipregs=('hooks', 'etypes')):
     for regname, reg in testclass._env.vreg.iteritems():
         if regname in skipregs:
--- a/doc/book/en/development/devweb/views.rst	Wed Sep 30 17:57:02 2009 +0200
+++ b/doc/book/en/development/devweb/views.rst	Wed Sep 30 18:57:42 2009 +0200
@@ -162,7 +162,7 @@
              self.w(u'<p>%s</p>' % entry.view('inblogcontext'))
 
  class BlogEntryInBlogView(EntityView):
-     'inblogcontext'
+     id = 'inblogcontext'
      __select__ = implements('BlogEntry')
 
      def cell_call(self, row, col):
--- a/doc/book/en/development/webstdlib/basetemplates.rst	Wed Sep 30 17:57:02 2009 +0200
+++ b/doc/book/en/development/webstdlib/basetemplates.rst	Wed Sep 30 18:57:42 2009 +0200
@@ -126,8 +126,8 @@
                       title=False, message=False)
 
     def get_searchbox(self, view, context):
-        boxes = list(self.vreg.possible_vobjects('boxes', self.req, self.rset,
-                                                 view=view, context=context))
+        boxes = list(self.vreg.poss_visible_objects('boxes', self.req, self.rset,
+                                                    view=view, context=context))
         if boxes:
             for box in boxes:
                 if box.id == 'search_box':
--- a/doc/book/fr/01-introduction.fr.txt	Wed Sep 30 17:57:02 2009 +0200
+++ b/doc/book/fr/01-introduction.fr.txt	Wed Sep 30 18:57:42 2009 +0200
@@ -6,13 +6,13 @@
 ===========================
 
 `CubicWeb` nous permet de développer des instances d'applications web
-basées sur un ou plusieurs `cube`. 
+basées sur un ou plusieurs `cube`.
 
-Ce à quoi nous réferrons en parlant de `cube` est un modèle définissant 
-des types de données et des vues. Un `cube` est un composant re-utilisable 
+Ce à quoi nous réferrons en parlant de `cube` est un modèle définissant
+des types de données et des vues. Un `cube` est un composant re-utilisable
 regroupé avec d'autres cubes sur le système de fichiers.
 
-Un `instance` réferre à une installation spécifique d'un ou plusieurs cubes 
+Un `instance` réferre à une installation spécifique d'un ou plusieurs cubes
 où sont regroupés tous les fichiers de configuration de l'application web finale.
 
 Dans ce document, nous allons vous montrer comment créer un cube et l'utiliser
@@ -32,7 +32,7 @@
   |
   |-- data/
   |   |-- cubes.blog.css
-  |   |-- cubes.blog.js  
+  |   |-- cubes.blog.js
   |   |-- external_resources
   |
   |-- debian/
@@ -77,7 +77,7 @@
   |-- views.py
 
 Toute modification apportée à votre modele de données devra
-etre effectué dans ce répertoire. 
+etre effectué dans ce répertoire.
 
 
 
@@ -102,17 +102,17 @@
     title = String(required=True, fulltextindexed=True, maxsize=256)
     publish_date = Date(default='TODAY')
     content = String(required=True, fulltextindexed=True)
-    entry_of = SubjectRelation('Blog', cardinality='?*') 
+    entry_of = SubjectRelation('Blog', cardinality='?*')
 
-Un ``Blog`` a un titre et une description. Le titre est une chaîne 
+Un ``Blog`` a un titre et une description. Le titre est une chaîne
 de caractères requise par la classe parente EntityType et ne doit
-pas excéder 50 caractères. La description est une chaîne de 
+pas excéder 50 caractères. La description est une chaîne de
 caractères sans contraintes.
 
 Une ``BlogEntry`` a un titre, une date de publication et du texte
-étant son contenu. Le titre est une chaîne de caractères qui ne 
+étant son contenu. Le titre est une chaîne de caractères qui ne
 doit pas excéder 100 caractères. La date de publication est de type Date et a
-pour valeur par défaut TODAY, ce qui signifie que lorsqu'une 
+pour valeur par défaut TODAY, ce qui signifie que lorsqu'une
 ``BlogEntry`` sera créée, sa date de publication sera la date
 courante a moins de modifier ce champ. Le texte est une chaîne de
 caractères qui sera indexée en plein texte et sans contraintes.
@@ -121,7 +121,7 @@
 relie à un ``Blog``. La cardinalité ``?*`` signifie que BlogEntry
 peut faire partie de zero a un Blog (``?`` signifie `zero ou un`) et
 qu'un Blog peut avoir une infinité de BlogEntry (``*`` signifie
-`n'importe quel nombre incluant zero`). 
+`n'importe quel nombre incluant zero`).
 Par soucis de complétude, nous rappellerons que ``+`` signifie
 `un ou plus`.
 
@@ -130,7 +130,7 @@
 --------------------
 
 ::
-  
+
   cubicweb-ctl create blog blogdemo
 
 Cette commande va créer un répertoire ``~/etc/cubicweb.d/blogdemo``
@@ -150,7 +150,7 @@
 Vous pouvez à présent accéder  à votre application web vous permettant de
 créer des blogs et d'y poster des messages en visitant l'URL http://localhost:8080/.
 Un premier formulaire d'authentification va vous être proposé. Par défaut,
-l'application n'autorisera pas d'utilisateur anonyme à accéder a votre 
+l'application n'autorisera pas d'utilisateur anonyme à accéder a votre
 application. Vous devrez donc utiliser l'utilisateur administrateur que
 vous aurez crée lors de l'initialisation de votre base de données via
 ``cubicweb-ctl create``.
@@ -166,7 +166,7 @@
 
 Rappelez-vous que pour le moment, tout a été géré par la plate-forme
 `CubicWeb` et que la seule chose qui a été fournie est le schéma de
-données. 
+données.
 
 Créons des entités
 ------------------
@@ -186,7 +186,7 @@
    :alt: from to create blog
 
 En cliquant sur le logo situé dans le coin gauche de la fenêtre,
-vous allez être redirigé vers la page d'accueil. Ensuite, si vous allez 
+vous allez être redirigé vers la page d'accueil. Ensuite, si vous allez
 sur le lien Blog, vous devriez voir la liste des entités Blog, en particulier
 celui que vous venez juste de créer ``Tech-Blog``.
 
@@ -212,7 +212,7 @@
 un peut de texte avant de ``Valider``. Vous venez d'ajouter un article
 sans avoir précisé à quel Blog il appartenait. Dans la colonne de gauche
 se trouve une boite intitulé ``actions``, cliquez sur le menu ``modifier``.
-Vous êtes de retour sur le formulaire d'édition de l'article que vous 
+Vous êtes de retour sur le formulaire d'édition de l'article que vous
 venez de créer, à ceci près que ce formulaire a maintenant une nouvelle
 section intitulée ``ajouter relation``. Choisissez ``entry_of`` dans ce menu,
 cela va faire apparaitre une deuxième menu déroulant dans lequel vous
@@ -225,7 +225,7 @@
    :alt: editing a blog entry to add a relation to a blog
 
 Validez vos modifications en cliquant sur ``Valider``. L'entité article
-qui est listée contient maintenant un lien vers le Blog auquel il 
+qui est listée contient maintenant un lien vers le Blog auquel il
 appartient, ``MyLife``.
 
 .. image:: images/cbw-detail-one-blogentry.fr.png
@@ -248,7 +248,7 @@
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Une vue est une classe Python qui inclut:
-  
+
   - un identifiant (tous les objets dans `CubicWeb` sont listés
     dans un registre et cet identifiant est utilisé comme la clé)
 
@@ -262,32 +262,32 @@
 des entités que nous cherchons à appliquer. `CubicWeb` utilise un
 sélecteur qui permet de calculer un score et d'identifier la vue
 la plus adaptée au `result set` que nous voulons afficher. La librarie
-standard des sélecteurs se trouve dans ``cubicweb.common.selector`` 
-et une librairie des méthodes utilisées pour calculer les scores 
+standard des sélecteurs se trouve dans ``cubicweb.common.selector``
+et une librairie des méthodes utilisées pour calculer les scores
 est dans ``cubicweb.vregistry.vreq``.
 
 
 Il est possible de définir plusieurs vues ayant le meme identifiant
-et d'y associer des sélecteurs et des filtres afin de permettre à 
-l'application de s'adapter au mieux aux données que nous avons 
+et d'y associer des sélecteurs et des filtres afin de permettre à
+l'application de s'adapter au mieux aux données que nous avons
 besoin d'afficher. Nous verrons cela plus en détails dans :ref:`DefinitionVues`.
 
 On peut citer l'exemple de la vue nommée ``primary`` qui est celle utilisée
-pour visualiser une entité seule. Nous allons vous montrer comment modifier 
+pour visualiser une entité seule. Nous allons vous montrer comment modifier
 cette vue.
 
 Modification des vues
 ~~~~~~~~~~~~~~~~~~~~~
 Si vous souhaitez modifier la manière dont est rendue un article (`Blogentry`),
-vous devez surcharger la vue ``primary`` définie dans le module ``views`` de 
+vous devez surcharger la vue ``primary`` définie dans le module ``views`` de
 votre cube, ``cubes/blog/views.py``.
 
 Nous pourrions par exemple ajouter devant la date de publication un préfixe
 indiquant que la date visualisée est la date de publication.
 
-Pour cela appliquez les modifications suivantes: 
+Pour cela appliquez les modifications suivantes:
 
-:: 
+::
 
   from cubicweb.web.views import baseviews
 
@@ -303,13 +303,13 @@
         return entity.view('reledit', rtype='content_format')
 
     def cell_call(self, row, col):
-        entity = self.entity(row, col)
+        entity = self.rset.get_entity(row, col)
 
         # display entity attributes with prefixes
         self.w(u'<h1>%s</h1>' % entity.title)
         self.w(u'<p>published on %s</p>' % entity.publish_date.strftime('%Y-%m-%d'))
         self.w(u'<p>%s</p>' % entity.content)
-        
+
         # display relations
         siderelations = []
         if self.main_related_section:
@@ -329,10 +329,10 @@
 
 
 
-Le code que nous avons modifié définit une vue primaire pour une entité de 
-type `BlogEntry`. 
+Le code que nous avons modifié définit une vue primaire pour une entité de
+type `BlogEntry`.
 
-Etant donné que les vues sont appliquées sur des `result sets` et que 
+Etant donné que les vues sont appliquées sur des `result sets` et que
 les `result sets` peuvent être des tableaux de données, il est indispensable
 de récupérer l'entité selon ses coordonnées (row,col).
 
--- a/doc/book/fr/05-define-views.fr.txt	Wed Sep 30 17:57:02 2009 +0200
+++ b/doc/book/fr/05-define-views.fr.txt	Wed Sep 30 18:57:42 2009 +0200
@@ -20,17 +20,17 @@
 * `cell_call(row, col, **kwargs)`, appelle la vue pour une cellule donnée d'un
   result set
 * `url()`, retourne l'url permettant d'obtenir cette vue avec le result set en
-  cours 
+  cours
 * `view(__vid, rset, __fallback_vid=None, **kwargs)`, appelle la vue
   d'identificant `__vid` sur le result set donné. Il est possible de données un
   identificant de vue de "fallback" qui sera utilisé si la vue demandée n'est
   pas applicable au result set
-  
+
 * `wview(__vid, rset, __fallback_vid=None, **kwargs)`, pareil que `view` mais
   passe automatiquement le flux en argument
-  
+
 * `html_headers()`, retourne une liste d'en-tête HTML à placer par le template
-  principal 
+  principal
 
 * `page_title()`, retourne le titre à utiliser dans l'en tête HTML `title`
 
@@ -55,7 +55,7 @@
 
 [FROM-LAX-BOOK]
 
-Tip: when modifying views, you do not need to restart the local 
+Tip: when modifying views, you do not need to restart the local
 server. Just save the file in your editor and reload the page in your
 browser to see the changes.
 
@@ -63,7 +63,7 @@
 
 - an identifier (all objects in `LAX` are entered in a registry
   and this identifier will be used as a key)
-  
+
 - a filter to select the resulsets it can be applied to
 
 `LAX` provides a lot of standard views, for a complete list, you
@@ -83,14 +83,14 @@
   05.     accepts = ('BlogEntry',)
   06.
   07.     def cell_call(self, row, col):
-  08.         entity = self.entity(row, col)
+  08.         entity = self.rset.get_entity(row, col)
   09.         self.w(u'<h1>%s</h1>' % entity.title)
   10.         self.w(u'<p>published on %s in category %s</p>' % \
   11.                (entity.publish_date.strftime('%Y-%m-%d'), entity.category))
   12.         self.w(u'<p>%s</p>' % entity.text)
 
 The above source code defines a new primary view (`line 03`) for
-``BlogEntry`` (`line 05`). 
+``BlogEntry`` (`line 05`).
 
 Since views are applied to resultsets and resulsets can be tables of
 data, it is needed to recover the entity from its (row,col)
@@ -108,11 +108,11 @@
 Let us now improve the primary view of a blog ::
 
   01. class BlogPrimaryView(baseviews.PrimaryView):
-  02. 
+  02.
   03.     accepts = ('Blog',)
   04.
   05.     def cell_call(self, row, col):
-  06.         entity = self.entity(row, col)
+  06.         entity = self.rset.get_entity(row, col)
   07.         self.w(u'<h1>%s</h1>' % entity.title)
   08.         self.w(u'<p>%s</p>' % entity.description)
   09.         rset = self.req.execute('Any E WHERE E entry_of B, B eid "%s"' % entity.eid)
@@ -127,9 +127,9 @@
 about the schema and infer that such entities have to be of the
 ``BlogEntry`` kind and retrieves them.
 
-The request returns a selection of data called a resultset. At 
+The request returns a selection of data called a resultset. At
 `line 10` the view 'primary' is applied to this resultset to output
-HTML. 
+HTML.
 
 **This is to be compared to interfaces and protocols in object-oriented
 languages. Applying a given view to all the entities of a resultset only
@@ -159,7 +159,7 @@
 
 * create view "blogentry table" with title, publish_date, category
 
-We will show that by default the view that displays 
+We will show that by default the view that displays
 "Any E,D,C WHERE E publish_date D, E category C" is the table view.
 Of course, the same can be obtained by calling
 self.wview('table',rset)
@@ -244,4 +244,4 @@
 ----------------------------------
 Certains navigateurs (dont firefox) n'aime pas les `<div>` vides (par vide
 j'entend sans contenu dans la balise, il peut y avoir des attributs), faut
-toujours mettre `<div></div>` même s'il n'y a rien dedans, et non `<div/>`. 
+toujours mettre `<div></div>` même s'il n'y a rien dedans, et non `<div/>`.
--- a/doc/book/fr/20-01-intro.fr.txt	Wed Sep 30 17:57:02 2009 +0200
+++ b/doc/book/fr/20-01-intro.fr.txt	Wed Sep 30 18:57:42 2009 +0200
@@ -37,7 +37,7 @@
 données manipulées. La syntaxe de la définition est la même que celle
 proposée par `Google AppEngine` mais il faut remplacer la ligne
 d'import::
-  
+
   from google.appengine.ext import db
 
 par celle-ci::
@@ -48,7 +48,7 @@
 Un exemple de schéma de données pour un ``Blog`` pourrait être::
 
   from ginco.goa import db
-  
+
   class BlogEntry(db.Model):
       # un titre à donner à l'entrée
       title = db.StringProperty(required=True)
@@ -57,8 +57,8 @@
       # le contenu de l'entrée
       content = db.TextProperty()
       # une entrée peut en citer une autre
-      cites = db.SelfReferenceProperty() 
-      
+      cites = db.SelfReferenceProperty()
+
 
 Personnalisation des vues
 -------------------------
@@ -75,7 +75,7 @@
 - un identifiant (tous les objets dans `LAX` sont enregistrés
   dans un registre et cet identifiant sert de clé pour y retrouver
   la vue)
-  
+
 - une description des types de données auxquels elle s'applique
 
 Il existe dans `LAX` des vues prédéfinies et utilisées par le moteur
@@ -87,17 +87,17 @@
 Par exemple, si on souhaite modifier la page principale d'une entrée de
 blog, il faut surcharger la vue ``primary`` des objets ``BlogEntry`` dans
 le fichier ``myapp/views.py``::
-  
+
   from ginco.web.views import baseviews
-  
+
   class BlogEntryPrimaryView(baseviews.PrimaryView):
       accepts = ('BlogEntry',)
-      
+
       def cell_call(self, row, col):
-          entity = self.entity(row, col)
+          entity = self.rset.get_entity(row, col)
           self.w(u'<h1>%s</h1>' % entity.title)
           self.w(u'<div>%s</div>' entity.content)
-    
+
 
 Génération du graphique de schéma
 ---------------------------------
@@ -105,13 +105,13 @@
 Il existe une vue ``schema`` qui permet d'afficher un graphique
 représantant les différents types d'entités définis dans le schéma
 ainsi que les relations entre ces types. Ce graphique doit être généré
-statiquement. Le script à utiliser pour générer ce schéma est 
+statiquement. Le script à utiliser pour générer ce schéma est
 dans ``myapp/tools``. Ce script nécessite d'avoir accès aux
 bibliothèques fournies par le SDK de ``Google AppEngine``. Il faut
 donc modifier son PYTHONPATH::
 
   $ export PYTHONPATH=GAE_ROOT/google:GAE_ROOT/lib/yaml
-  $ python tools/generate_schema_img.py 
+  $ python tools/generate_schema_img.py
 
 
 Génération des fichiers de traduction
--- a/doc/book/fr/20-04-develop-views.fr.txt	Wed Sep 30 17:57:02 2009 +0200
+++ b/doc/book/fr/20-04-develop-views.fr.txt	Wed Sep 30 18:57:42 2009 +0200
@@ -13,7 +13,7 @@
 
 - an identifier (all objects in `LAX` are entered in a registry
   and this identifier will be used as a key)
-  
+
 - a filter to select the resulsets it can be applied to
 
 `LAX` provides a lot of standard views, for a complete list, you
@@ -25,13 +25,13 @@
 override the view ``primary`` in ``BlogDemo/views.py`` ::
 
   from ginco.web.views import baseviews
-  
+
   class BlogEntryPrimaryView(baseviews.PrimaryView):
 
       accepts = ('BlogEntry',)
-      
+
       def cell_call(self, row, col):
-          entity = self.entity(row, col)
+          entity = self.rset.get_entity(row, col)
           self.w(u'<h1>%s</h1>' % entity.title)
           self.w(u'<div>%s</div>' % entity.publish_date)
           self.w(u'<div>%s</div>' % entity.category)
@@ -91,7 +91,7 @@
 
 [WRITE ME]
 
-* show how urls are mapped to selections and views and explain URLRewriting 
+* show how urls are mapped to selections and views and explain URLRewriting
 
 Security
 =========
--- a/entities/__init__.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/entities/__init__.py	Wed Sep 30 18:57:42 2009 +0200
@@ -22,7 +22,7 @@
     """an entity instance has e_schema automagically set on the class and
     instances have access to their issuing cursor
     """
-    id = 'Any'
+    __regid__ = 'Any'
     __implements__ = (IBreadCrumbs, IFeed)
 
     fetch_attrs = ('modification_date',)
@@ -82,11 +82,11 @@
 
     def dc_date(self, date_format=None):# XXX default to ISO 8601 ?
         """return latest modification date of this entity"""
-        return self.format_date(self.modification_date, date_format=date_format)
+        return self._cw.format_date(self.modification_date, date_format=date_format)
 
     def dc_type(self, form=''):
         """return the display name for the type of this entity (translated)"""
-        return self.e_schema.display_name(self.req, form)
+        return self.e_schema.display_name(self._cw, form)
 
     def dc_language(self):
         """return language used by this entity (translated)"""
@@ -95,8 +95,8 @@
         for rschema, attrschema in self.e_schema.attribute_definitions():
             if rschema.rproperty(self.e_schema, attrschema,
                                  'internationalizable'):
-                return self.req._(self.req.user.property_value('ui.language'))
-        return self.req._(self.vreg.property_value('ui.language'))
+                return self._cw._(self._cw.user.property_value('ui.language'))
+        return self._cw._(self._cw.vreg.property_value('ui.language'))
 
     @property
     def creator(self):
@@ -122,11 +122,11 @@
                     path = parent.breadcrumbs(view) + [self]
         if not recurs:
             if view is None:
-                if 'vtitle' in self.req.form:
+                if 'vtitle' in self._cw.form:
                     # embeding for instance
-                    path.append( self.req.form['vtitle'] )
-            elif view.id != 'primary' and hasattr(view, 'title'):
-                path.append( self.req._(view.title) )
+                    path.append( self._cw.form['vtitle'] )
+            elif view.__regid__ != 'primary' and hasattr(view, 'title'):
+                path.append( self._cw._(view.title) )
         return path
 
     ## IFeed interface ########################################################
@@ -163,7 +163,7 @@
             self.__linkto = {}
         except KeyError:
             pass
-        linktos = list(self.req.list_form_param('__linkto'))
+        linktos = list(self._cw.list_form_param('__linkto'))
         linkedto = []
         for linkto in linktos[:]:
             ltrtype, eid, ltrole = linkto.split(':')
@@ -172,7 +172,7 @@
                 # hidden input
                 if remove:
                     linktos.remove(linkto)
-                    self.req.form['__linkto'] = linktos
+                    self._cw.form['__linkto'] = linktos
                 linkedto.append(typed_eid(eid))
         self.__linkto[(rtype, role)] = linkedto
         return linkedto
@@ -204,85 +204,6 @@
         """
         return ()
 
-    # XXX deprecates, may be killed once old widgets system is gone ###########
-
-    @classmethod
-    def get_widget(cls, rschema, x='subject'):
-        """return a widget to view or edit a relation
-
-        notice that when the relation support multiple target types, the widget
-        is necessarily the same for all those types
-        """
-        # let ImportError propage if web par isn't available
-        from cubicweb.web.widgets import widget
-        if isinstance(rschema, basestring):
-            rschema = cls.schema.rschema(rschema)
-        if x == 'subject':
-            tschema = rschema.objects(cls.e_schema)[0]
-            wdg = widget(cls.vreg, cls, rschema, tschema, 'subject')
-        else:
-            tschema = rschema.subjects(cls.e_schema)[0]
-            wdg = widget(cls.vreg, tschema, rschema, cls, 'object')
-        return wdg
-
-    @deprecated('use EntityFieldsForm.subject_relation_vocabulary')
-    def subject_relation_vocabulary(self, rtype, limit):
-        form = self.vreg.select('forms', 'edition', self.req, entity=self)
-        return form.subject_relation_vocabulary(rtype, limit)
-
-    @deprecated('use EntityFieldsForm.object_relation_vocabulary')
-    def object_relation_vocabulary(self, rtype, limit):
-        form = self.vreg.select('forms', 'edition', self.req, entity=self)
-        return form.object_relation_vocabulary(rtype, limit)
-
-    @deprecated('use AutomaticEntityForm.[e]relations_by_category')
-    def relations_by_category(self, categories=None, permission=None):
-        from cubicweb.web.views.autoform import AutomaticEntityForm
-        return AutomaticEntityForm.erelations_by_category(self, categories, permission)
-
-    @deprecated('use AutomaticEntityForm.[e]srelations_by_category')
-    def srelations_by_category(self, categories=None, permission=None):
-        from cubicweb.web.views.autoform import AutomaticEntityForm
-        return AutomaticEntityForm.esrelations_by_category(self, categories, permission)
-
-    def attribute_values(self, attrname):
-        if self.has_eid() or attrname in self:
-            try:
-                values = self[attrname]
-            except KeyError:
-                values = getattr(self, attrname)
-            # actual relation return a list of entities
-            if isinstance(values, list):
-                return [v.eid for v in values]
-            return (values,)
-        # the entity is being created, try to find default value for
-        # this attribute
-        try:
-            values = self.req.form[attrname]
-        except KeyError:
-            try:
-                values = self[attrname] # copying
-            except KeyError:
-                values = getattr(self, 'default_%s' % attrname,
-                                 self.e_schema.default(attrname))
-                if callable(values):
-                    values = values()
-        if values is None:
-            values = ()
-        elif not isinstance(values, (list, tuple)):
-            values = (values,)
-        return values
-
-    def use_fckeditor(self, attr):
-        """return True if fckeditor should be used to edit entity's attribute named
-        `attr`, according to user preferences
-        """
-        if self.req.use_fckeditor() and self.e_schema.has_metadata(attr, 'format'):
-            if self.has_eid() or '%s_format' % attr in self:
-                return self.attr_metadata(attr, 'format') == 'text/html'
-            return self.req.property_value('ui.default-text-format') == 'text/html'
-        return False
-
 # XXX:  store a reference to the AnyEntity class since it is hijacked in goa
 #       configuration and we need the actual reference to avoid infinite loops
 #       in mro
--- a/entities/authobjs.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/entities/authobjs.py	Wed Sep 30 18:57:42 2009 +0200
@@ -13,7 +13,7 @@
 from cubicweb.entities import AnyEntity, fetch_config
 
 class CWGroup(AnyEntity):
-    id = 'CWGroup'
+    __regid__ = 'CWGroup'
     fetch_attrs, fetch_order = fetch_config(['name'])
     fetch_unrelated_order = fetch_order
 
@@ -22,7 +22,7 @@
         return self.get('name')
 
 class CWUser(AnyEntity):
-    id = 'CWUser'
+    __regid__ = 'CWUser'
     fetch_attrs, fetch_order = fetch_config(['login', 'firstname', 'surname'])
     fetch_unrelated_order = fetch_order
 
@@ -59,12 +59,13 @@
         try:
             # properties stored on the user aren't correctly typed
             # (e.g. all values are unicode string)
-            return self.vreg.typed_value(key, self.properties[key])
+            return self._cw.vreg.typed_value(key, self.properties[key])
         except KeyError:
             pass
         except ValueError:
-            self.warning('incorrect value for eproperty %s of user %s', key, self.login)
-        return self.vreg.property_value(key)
+            self.warning('incorrect value for eproperty %s of user %s',
+                         key, self.login)
+        return self._cw.vreg.property_value(key)
 
     def matching_groups(self, groups):
         """return the number of the given group(s) in which the user is
@@ -91,12 +92,12 @@
         return self.groups == frozenset(('guests', ))
 
     def owns(self, eid):
-        if hasattr(self.req, 'unsafe_execute'):
+        if hasattr(self._cw, 'unsafe_execute'):
             # use unsafe_execute on the repository side, in case
             # session's user doesn't have access to CWUser
-            execute = self.req.unsafe_execute
+            execute = self._cw.unsafe_execute
         else:
-            execute = self.req.execute
+            execute = self._cw.execute
         try:
             return execute('Any X WHERE X eid %(x)s, X owned_by U, U eid %(u)s',
                            {'x': eid, 'u': self.eid}, 'x')
@@ -114,7 +115,7 @@
             kwargs['x'] = contexteid
             cachekey = 'x'
         try:
-            return self.req.execute(rql, kwargs, cachekey)
+            return self._cw.execute(rql, kwargs, cachekey)
         except Unauthorized:
             return False
 
@@ -124,7 +125,7 @@
         """construct a name using firstname / surname or login if not defined"""
 
         if self.firstname and self.surname:
-            return self.req._('%(firstname)s %(surname)s') % {
+            return self._cw._('%(firstname)s %(surname)s') % {
                 'firstname': self.firstname, 'surname' : self.surname}
         if self.firstname:
             return self.firstname
--- a/entities/lib.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/entities/lib.py	Wed Sep 30 18:57:42 2009 +0200
@@ -24,7 +24,7 @@
     return '%s at %s' % (name, host.replace('.', ' dot '))
 
 class EmailAddress(AnyEntity):
-    id = 'EmailAddress'
+    __regid__ = 'EmailAddress'
     fetch_attrs, fetch_order = fetch_config(['address', 'alias'])
 
     def dc_title(self):
@@ -51,7 +51,7 @@
         if not ('sender' in subjrels and 'recipients' in subjrels):
             return
         rql = 'DISTINCT Any X, S, D ORDERBY D DESC WHERE X sender Y or X recipients Y, X subject S, X date D, Y eid %(y)s'
-        rset = self.req.execute(rql, {'y': self.eid}, 'y')
+        rset = self._cw.execute(rql, {'y': self.eid}, 'y')
         if skipeids is None:
             skipeids = set()
         for i in xrange(len(rset)):
@@ -62,7 +62,7 @@
             yield rset.get_entity(i, 0)
 
     def display_address(self):
-        if self.vreg.config['mangle-emails']:
+        if self._cw.vreg.config['mangle-emails']:
             return mangle_email(self.address)
         return self.address
 
@@ -82,23 +82,38 @@
         return super(EmailAddress, self).after_deletion_path()
 
 
-from logilab.common.deprecation import class_renamed
-Emailaddress = class_renamed('Emailaddress', EmailAddress)
-Emailaddress.id = 'Emailaddress'
+class Bookmark(AnyEntity):
+    """customized class for Bookmark entities"""
+    __regid__ = 'Bookmark'
+    fetch_attrs, fetch_order = fetch_config(['title', 'path'])
+
+    def actual_url(self):
+        url = self._cw.build_url(self.path)
+        if self.title:
+            urlparts = list(urlsplit(url))
+            if urlparts[3]:
+                urlparts[3] += '&vtitle=%s' % self._cw.url_quote(self.title)
+            else:
+                urlparts[3] = 'vtitle=%s' % self._cw.url_quote(self.title)
+            url = urlunsplit(urlparts)
+        return url
+
+    def action_url(self):
+        return self.absolute_url() + '/follow'
 
 
 class CWProperty(AnyEntity):
-    id = 'CWProperty'
+    __regid__ = 'CWProperty'
 
     fetch_attrs, fetch_order = fetch_config(['pkey', 'value'])
     rest_attr = 'pkey'
 
     def typed_value(self):
-        return self.vreg.typed_value(self.pkey, self.value)
+        return self._cw.vreg.typed_value(self.pkey, self.value)
 
     def dc_description(self, format='text/plain'):
         try:
-            return self.req._(self.vreg.property_info(self.pkey)['help'])
+            return self._cw._(self._cw.vreg.property_info(self.pkey)['help'])
         except UnknownProperty:
             return u''
 
@@ -109,33 +124,13 @@
         return 'view', {}
 
 
-class Bookmark(AnyEntity):
-    """customized class for Bookmark entities"""
-    id = 'Bookmark'
-    fetch_attrs, fetch_order = fetch_config(['title', 'path'])
-
-    def actual_url(self):
-        url = self.req.build_url(self.path)
-        if self.title:
-            urlparts = list(urlsplit(url))
-            if urlparts[3]:
-                urlparts[3] += '&vtitle=%s' % self.req.url_quote(self.title)
-            else:
-                urlparts[3] = 'vtitle=%s' % self.req.url_quote(self.title)
-            url = urlunsplit(urlparts)
-        return url
-
-    def action_url(self):
-        return self.absolute_url() + '/follow'
-
-
 class CWCache(AnyEntity):
     """Cache"""
-    id = 'CWCache'
+    __regid__ = 'CWCache'
     fetch_attrs, fetch_order = fetch_config(['name'])
 
     def touch(self):
-        self.req.execute('SET X timestamp %(t)s WHERE X eid %(x)s',
+        self._cw.execute('SET X timestamp %(t)s WHERE X eid %(x)s',
                          {'t': datetime.now(), 'x': self.eid}, 'x')
 
     def valid(self, date):
--- a/entities/schemaobjs.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/entities/schemaobjs.py	Wed Sep 30 18:57:42 2009 +0200
@@ -16,15 +16,15 @@
 
 
 class CWEType(AnyEntity):
-    id = 'CWEType'
+    __regid__ = 'CWEType'
     fetch_attrs, fetch_order = fetch_config(['name'])
 
     def dc_title(self):
-        return u'%s (%s)' % (self.name, self.req._(self.name))
+        return u'%s (%s)' % (self.name, self._cw._(self.name))
 
     def dc_long_title(self):
         stereotypes = []
-        _ = self.req._
+        _ = self._cw._
         if self.final:
             stereotypes.append(_('final'))
         if stereotypes:
@@ -37,15 +37,15 @@
 
 
 class CWRType(AnyEntity):
-    id = 'CWRType'
+    __regid__ = 'CWRType'
     fetch_attrs, fetch_order = fetch_config(['name'])
 
     def dc_title(self):
-        return u'%s (%s)' % (self.name, self.req._(self.name))
+        return u'%s (%s)' % (self.name, self._cw._(self.name))
 
     def dc_long_title(self):
         stereotypes = []
-        _ = self.req._
+        _ = self._cw._
         if self.symetric:
             stereotypes.append(_('symetric'))
         if self.inlined:
@@ -63,7 +63,7 @@
         * raise ValidationError if inlining is'nt possible
         * eventually return True
         """
-        rschema = self.schema.rschema(self.name)
+        rschema = self._cw.vreg.schema.rschema(self.name)
         if inlined == rschema.inlined:
             return False
         if inlined:
@@ -75,7 +75,7 @@
                     rtype = self.name
                     stype = rdef.stype
                     otype = rdef.otype
-                    msg = self.req._("can't set inlined=%(inlined)s, "
+                    msg = self._cw._("can't set inlined=%(inlined)s, "
                                      "%(stype)s %(rtype)s %(otype)s "
                                      "has cardinality=%(card)s")
                     raise ValidationError(self.eid, {'inlined': msg % locals()})
@@ -87,7 +87,7 @@
 
 
 class CWRelation(AnyEntity):
-    id = 'CWRelation'
+    __regid__ = 'CWRelation'
     fetch_attrs = fetch_config(['cardinality'])[0]
 
     def dc_title(self):
@@ -130,7 +130,7 @@
 
 
 class CWAttribute(CWRelation):
-    id = 'CWAttribute'
+    __regid__ = 'CWAttribute'
 
     def dc_long_title(self):
         card = self.cardinality
@@ -144,7 +144,7 @@
 
 
 class CWConstraint(AnyEntity):
-    id = 'CWConstraint'
+    __regid__ = 'CWConstraint'
     fetch_attrs, fetch_order = fetch_config(['value'])
 
     def dc_title(self):
@@ -164,7 +164,7 @@
 
 
 class RQLExpression(AnyEntity):
-    id = 'RQLExpression'
+    __regid__ = 'RQLExpression'
     fetch_attrs, fetch_order = fetch_config(['exprtype', 'mainvars', 'expression'])
 
     def dc_title(self):
@@ -198,13 +198,13 @@
 
 
 class CWPermission(AnyEntity):
-    id = 'CWPermission'
+    __regid__ = 'CWPermission'
     fetch_attrs, fetch_order = fetch_config(['name', 'label'])
 
     def dc_title(self):
         if self.label:
-            return '%s (%s)' % (self.req._(self.name), self.label)
-        return self.req._(self.name)
+            return '%s (%s)' % (self._cw._(self.name), self.label)
+        return self._cw._(self.name)
 
     def after_deletion_path(self):
         """return (path, parameters) which should be used as redirect
--- a/entities/test/unittest_base.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/entities/test/unittest_base.py	Wed Sep 30 18:57:42 2009 +0200
@@ -11,15 +11,14 @@
 from logilab.common.decorators import clear_cache
 from logilab.common.interface import implements
 
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 
 from cubicweb import ValidationError
 from cubicweb.interfaces import IMileStone, IWorkflowable
 from cubicweb.entities import AnyEntity
-from cubicweb.web.widgets import AutoCompletionWidget
 
 
-class BaseEntityTC(EnvBasedTC):
+class BaseEntityTC(CubicWebTC):
 
     def setup_database(self):
         self.member = self.create_user('member')
@@ -95,7 +94,7 @@
         self.failIf(e.matching_groups(('xyz', 'abcd')))
 
 
-class InterfaceTC(EnvBasedTC):
+class InterfaceTC(CubicWebTC):
 
     def test_nonregr_subclasses_and_mixins_interfaces(self):
         CWUser = self.vreg['etypes'].etype_class('CWUser')
@@ -115,7 +114,7 @@
         self.failIf(implements(MyUser, IWorkflowable))
 
 
-class SpecializedEntityClassesTC(EnvBasedTC):
+class SpecializedEntityClassesTC(CubicWebTC):
 
     def select_eclass(self, etype):
         # clear selector cache
@@ -132,7 +131,7 @@
         self.vreg._loadedmods[__name__] = {}
         for etype in ('Company', 'Division', 'SubDivision'):
             class Foo(AnyEntity):
-                id = etype
+                __regid__ = etype
             self.vreg.register_appobject_class(Foo)
             eclass = self.select_eclass('SubDivision')
             self.failUnless(eclass.__autogenerated__)
@@ -143,7 +142,7 @@
                 self.assertEquals(eclass.__bases__[0].__bases__, (Foo,))
         # check Division eclass is still selected for plain Division entities
         eclass = self.select_eclass('Division')
-        self.assertEquals(eclass.id, 'Division')
+        self.assertEquals(eclass.__regid__, 'Division')
 
 if __name__ == '__main__':
     unittest_main()
--- a/entities/test/unittest_wfobjs.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/entities/test/unittest_wfobjs.py	Wed Sep 30 18:57:42 2009 +0200
@@ -1,4 +1,4 @@
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb import ValidationError
 
 def add_wf(self, etype, name=None, default=False):
@@ -18,7 +18,7 @@
             for ti in wfhist]
 
 
-class WorkflowBuildingTC(EnvBasedTC):
+class WorkflowBuildingTC(CubicWebTC):
 
     def test_wf_construction(self):
         wf = add_wf(self, 'Company')
@@ -55,7 +55,7 @@
         self.assertEquals(ex.errors, {'transition_of': 'unique constraint S name N, Y transition_of O, Y name N failed'})
 
 
-class WorkflowTC(EnvBasedTC):
+class WorkflowTC(CubicWebTC):
 
     def setup_database(self):
         rschema = self.schema['in_state']
@@ -110,7 +110,7 @@
         wf = add_wf(self, 'CWUser')
         s = wf.add_state(u'foo', initial=True)
         self.commit()
-        ex = self.assertRaises(ValidationError, self.session().unsafe_execute,
+        ex = self.assertRaises(ValidationError, self.session.unsafe_execute,
                                'SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
                                {'x': self.user().eid, 's': s.eid}, 'x')
         self.assertEquals(ex.errors, {'in_state': "state doesn't belong to entity's workflow. "
@@ -256,15 +256,11 @@
         self.assertEquals(ex.errors, {'subworkflow_exit': u"can't have multiple exits on the same state"})
 
 
-class CustomWorkflowTC(EnvBasedTC):
+class CustomWorkflowTC(CubicWebTC):
 
     def setup_database(self):
         self.member = self.create_user('member')
 
-    def tearDown(self):
-        super(CustomWorkflowTC, self).tearDown()
-        self.execute('DELETE X custom_workflow WF')
-
     def test_custom_wf_replace_state_no_history(self):
         """member in inital state with no previous history, state is simply
         redirected when changing workflow
@@ -343,8 +339,7 @@
                            ('asleep', 'activated', None, 'workflow changed to "default user workflow"'),])
 
 
-
-class AutoTransitionTC(EnvBasedTC):
+class AutoTransitionTC(CubicWebTC):
 
     def setup_database(self):
         self.wf = add_wf(self, 'CWUser')
@@ -384,18 +379,17 @@
                            ('asleep', 'dead', 'sick', None),])
 
 
-from cubicweb.devtools.apptest import RepositoryBasedTC
-
-class WorkflowHooksTC(RepositoryBasedTC):
+class WorkflowHooksTC(CubicWebTC):
 
     def setUp(self):
-        RepositoryBasedTC.setUp(self)
+        CubicWebTC.setUp(self)
         self.wf = self.session.user.current_workflow
+        self.session.set_pool()
         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)
-        ueid = self.create_user('stduser', commit=False)
+        ueid = self.create_user('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',
                             {'x' : ueid})
@@ -412,13 +406,6 @@
                      {'wf': self.wf.eid})
         self.commit()
 
-    def tearDown(self):
-        self.execute('DELETE X require_group G '
-                     'WHERE G name "users", X transition_of WF, WF eid %(wf)s',
-                     {'wf': self.wf.eid})
-        self.commit()
-        RepositoryBasedTC.tearDown(self)
-
     # XXX currently, we've to rely on hooks to set initial state, or to use unsafe_execute
     # def test_initial_state(self):
     #     cnx = self.login('stduser')
@@ -435,7 +422,7 @@
     # test that the workflow is correctly enforced
     def test_transition_checking1(self):
         cnx = self.login('stduser')
-        user = cnx.user(self.current_session())
+        user = cnx.user(self.session)
         ex = self.assertRaises(ValidationError,
                                user.fire_transition, 'activate')
         self.assertEquals(ex.errors, {'by_transition': u"transition isn't allowed"})
@@ -443,8 +430,7 @@
 
     def test_transition_checking2(self):
         cnx = self.login('stduser')
-        user = cnx.user(self.current_session())
-        assert user.state == 'activated'
+        user = cnx.user(self.session)
         ex = self.assertRaises(ValidationError,
                                user.fire_transition, 'dummy')
         self.assertEquals(ex.errors, {'by_transition': u"transition isn't allowed"})
@@ -452,7 +438,7 @@
 
     def test_transition_checking3(self):
         cnx = self.login('stduser')
-        session = self.current_session()
+        session = self.session
         user = cnx.user(session)
         user.fire_transition('deactivate')
         cnx.commit()
--- a/entities/wfobjs.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/entities/wfobjs.py	Wed Sep 30 18:57:42 2009 +0200
@@ -19,7 +19,7 @@
 class WorkflowException(Exception): pass
 
 class Workflow(AnyEntity):
-    id = 'Workflow'
+    __regid__ = 'Workflow'
 
     @property
     def initial(self):
@@ -50,7 +50,7 @@
             _done = set()
         yield self
         _done.add(self.eid)
-        for tr in self.req.execute('Any T WHERE T is WorkflowTransition, '
+        for tr in self._cw.execute('Any T WHERE T is WorkflowTransition, '
                                    'T transition_of WF, WF eid %(wf)s',
                                    {'wf': self.eid}).entities():
             if tr.subwf.eid in _done:
@@ -61,7 +61,7 @@
     # state / transitions accessors ############################################
 
     def state_by_name(self, statename):
-        rset = self.req.execute('Any S, SN WHERE S name SN, S name %(n)s, '
+        rset = self._cw.execute('Any S, SN WHERE S name SN, S name %(n)s, '
                                 'S state_of WF, WF eid %(wf)s',
                                 {'n': statename, 'wf': self.eid}, 'wf')
         if rset:
@@ -69,7 +69,7 @@
         return None
 
     def state_by_eid(self, eid):
-        rset = self.req.execute('Any S, SN WHERE S name SN, S eid %(s)s, '
+        rset = self._cw.execute('Any S, SN WHERE S name SN, S eid %(s)s, '
                                 'S state_of WF, WF eid %(wf)s',
                                 {'s': eid, 'wf': self.eid}, ('wf', 's'))
         if rset:
@@ -77,7 +77,7 @@
         return None
 
     def transition_by_name(self, trname):
-        rset = self.req.execute('Any T, TN WHERE T name TN, T name %(n)s, '
+        rset = self._cw.execute('Any T, TN WHERE T name TN, T name %(n)s, '
                                 'T transition_of WF, WF eid %(wf)s',
                                 {'n': trname, 'wf': self.eid}, 'wf')
         if rset:
@@ -85,7 +85,7 @@
         return None
 
     def transition_by_eid(self, eid):
-        rset = self.req.execute('Any T, TN WHERE T name TN, T eid %(t)s, '
+        rset = self._cw.execute('Any T, TN WHERE T name TN, T eid %(t)s, '
                                 'T transition_of WF, WF eid %(wf)s',
                                 {'t': eid, 'wf': self.eid}, ('wf', 't'))
         if rset:
@@ -96,20 +96,20 @@
 
     def add_state(self, name, initial=False, **kwargs):
         """add a state to this workflow"""
-        state = self.req.create_entity('State', name=unicode(name), **kwargs)
-        self.req.execute('SET S state_of WF WHERE S eid %(s)s, WF eid %(wf)s',
+        state = self._cw.create_entity('State', name=unicode(name), **kwargs)
+        self._cw.execute('SET S state_of WF WHERE S eid %(s)s, WF eid %(wf)s',
                          {'s': state.eid, 'wf': self.eid}, ('s', 'wf'))
         if initial:
             assert not self.initial
-            self.req.execute('SET WF initial_state S '
+            self._cw.execute('SET WF initial_state S '
                              'WHERE S eid %(s)s, WF eid %(wf)s',
                              {'s': state.eid, 'wf': self.eid}, ('s', 'wf'))
         return state
 
     def _add_transition(self, trtype, name, fromstates,
                         requiredgroups=(), conditions=(), **kwargs):
-        tr = self.req.create_entity(trtype, name=unicode(name), **kwargs)
-        self.req.execute('SET T transition_of WF '
+        tr = self._cw.create_entity(trtype, name=unicode(name), **kwargs)
+        self._cw.execute('SET T transition_of WF '
                          'WHERE T eid %(t)s, WF eid %(wf)s',
                          {'t': tr.eid, 'wf': self.eid}, ('t', 'wf'))
         assert fromstates, fromstates
@@ -118,7 +118,7 @@
         for state in fromstates:
             if hasattr(state, 'eid'):
                 state = state.eid
-            self.req.execute('SET S allowed_transition T '
+            self._cw.execute('SET S allowed_transition T '
                              'WHERE S eid %(s)s, T eid %(t)s',
                              {'s': state, 't': tr.eid}, ('s', 't'))
         tr.set_transition_permissions(requiredgroups, conditions, reset=False)
@@ -131,7 +131,7 @@
                                   requiredgroups, conditions, **kwargs)
         if hasattr(tostate, 'eid'):
             tostate = tostate.eid
-        self.req.execute('SET T destination_state S '
+        self._cw.execute('SET T destination_state S '
                          'WHERE S eid %(s)s, T eid %(t)s',
                          {'t': tr.eid, 's': tostate}, ('s', 't'))
         return tr
@@ -143,7 +143,7 @@
                                   requiredgroups, conditions, **kwargs)
         if hasattr(subworkflow, 'eid'):
             subworkflow = subworkflow.eid
-        self.req.execute('SET T subworkflow WF WHERE WF eid %(wf)s,T eid %(t)s',
+        self._cw.execute('SET T subworkflow WF WHERE WF eid %(wf)s,T eid %(t)s',
                          {'t': tr.eid, 'wf': subworkflow}, ('wf', 't'))
         for fromstate, tostate in exitpoints:
             tr.add_exit_point(fromstate, tostate)
@@ -156,11 +156,11 @@
     provides a specific may_be_fired method to check if the relation may be
     fired by the logged user
     """
-    id = 'BaseTransition'
+    __regid__ = 'BaseTransition'
     fetch_attrs, fetch_order = fetch_config(['name'])
 
     def __init__(self, *args, **kwargs):
-        if self.id == 'BaseTransition':
+        if self.__regid__ == 'BaseTransition':
             raise WorkflowException('should not be instantiated')
         super(BaseTransition, self).__init__(*args, **kwargs)
 
@@ -178,7 +178,7 @@
 
         `eid` is the eid of the object on which we may fire the transition
         """
-        user = self.req.user
+        user = self._cw.user
         # check user is at least in one of the required groups if any
         groups = frozenset(g.name for g in self.require_group)
         if groups:
@@ -190,7 +190,7 @@
         # check one of the rql expression conditions matches if any
         if self.condition:
             for rqlexpr in self.condition:
-                if rqlexpr.check_expression(self.req, eid):
+                if rqlexpr.check_expression(self._cw, eid):
                     return True
         if self.condition or groups:
             return False
@@ -210,12 +210,12 @@
         transition
         """
         if reset:
-            self.req.execute('DELETE T require_group G WHERE T eid %(x)s',
+            self._cw.execute('DELETE T require_group G WHERE T eid %(x)s',
                              {'x': self.eid}, 'x')
-            self.req.execute('DELETE T condition R WHERE T eid %(x)s',
+            self._cw.execute('DELETE T condition R WHERE T eid %(x)s',
                              {'x': self.eid}, 'x')
         for gname in requiredgroups:
-            rset = self.req.execute('SET T require_group G '
+            rset = self._cw.execute('SET T require_group G '
                                     'WHERE T eid %(x)s, G name %(gn)s',
                                     {'x': self.eid, 'gn': gname}, 'x')
             assert rset, '%s is not a known group' % gname
@@ -228,15 +228,16 @@
                 kwargs = expr
             kwargs['x'] = self.eid
             kwargs.setdefault('mainvars', u'X')
-            self.req.execute('INSERT RQLExpression X: X exprtype "ERQLExpression", '
-                             'X expression %(expr)s, X mainvars %(mainvars)s, '
+            self._cw.execute('INSERT RQLExpression X: X exprtype "ERQLExpression", '
+                             'X expression %(expr)s, T condition X '
+                             'WHERE T eid %(x)s',
                              'T condition X WHERE T eid %(x)s', kwargs, 'x')
         # XXX clear caches?
 
 
 class Transition(BaseTransition):
     """customized class for Transition entities"""
-    id = 'Transition'
+    __regid__ = 'Transition'
 
     def destination(self):
         return self.destination_state[0]
@@ -244,7 +245,7 @@
 
 class WorkflowTransition(BaseTransition):
     """customized class for WorkflowTransition entities"""
-    id = 'WorkflowTransition'
+    __regid__ = 'WorkflowTransition'
 
     @property
     def subwf(self):
@@ -258,7 +259,7 @@
             fromstate = fromstate.eid
         if hasattr(tostate, 'eid'):
             tostate = tostate.eid
-        self.req.execute('INSERT SubWorkflowExitPoint X: T subworkflow_exit X, '
+        self._cw.execute('INSERT SubWorkflowExitPoint X: T subworkflow_exit X, '
                          'X subworkflow_state FS, X destination_state TS '
                          'WHERE T eid %(t)s, FS eid %(fs)s, TS eid %(ts)s',
                          {'t': self.eid, 'fs': fromstate, 'ts': tostate},
@@ -270,7 +271,7 @@
             state = state.eid
         stateeid = self.exit_points().get(state)
         if stateeid is not None:
-            return self.req.entity_from_eid(stateeid)
+            return self._cw.entity_from_eid(stateeid)
         return None
 
     @cached
@@ -287,7 +288,7 @@
 
 class SubWorkflowExitPoint(AnyEntity):
     """customized class for SubWorkflowExitPoint entities"""
-    id = 'SubWorkflowExitPoint'
+    __regid__ = 'SubWorkflowExitPoint'
 
     @property
     def subwf_state(self):
@@ -300,7 +301,7 @@
 
 class State(AnyEntity):
     """customized class for State entities"""
-    id = 'State'
+    __regid__ = 'State'
     fetch_attrs, fetch_order = fetch_config(['name'])
     rest_attr = 'eid'
 
@@ -321,7 +322,7 @@
 class TrInfo(AnyEntity):
     """customized class for Transition information entities
     """
-    id = 'TrInfo'
+    __regid__ = 'TrInfo'
     fetch_attrs, fetch_order = fetch_config(['creation_date', 'comment'],
                                             pclass=None) # don't want modification_date
     @property
@@ -387,7 +388,7 @@
         """return current state name translated to context's language"""
         state = self.current_state
         if state:
-            return self.req._(state.name)
+            return self._cw._(state.name)
         return u''
 
     @property
@@ -405,17 +406,17 @@
     def cwetype_workflow(self):
         """return the default workflow for entities of this type"""
         # XXX CWEType method
-        wfrset = self.req.execute('Any WF WHERE X is ET, X eid %(x)s, '
+        wfrset = self._cw.execute('Any WF WHERE X is ET, X eid %(x)s, '
                                   'WF workflow_of ET', {'x': self.eid}, 'x')
         if len(wfrset) == 1:
             return wfrset.get_entity(0, 0)
         if len(wfrset) > 1:
             for wf in wfrset.entities():
-                if wf.is_default_workflow_of(self.id):
+                if wf.is_default_workflow_of(self.__regid__):
                     return wf
-            self.warning("can't find default workflow for %s", self.id)
+            self.warning("can't find default workflow for %s", self.__regid__)
         else:
-            self.warning("can't find any workflow for %s", self.id)
+            self.warning("can't find any workflow for %s", self.__regid__)
         return None
 
     def possible_transitions(self, type='normal'):
@@ -424,11 +425,18 @@
         """
         if self.current_state is None or self.current_workflow is None:
             return
+<<<<<<< /home/syt/src/fcubicweb/cubicweb/entities/wfobjs.py
+        rset = self._cw.execute(
+            'Any T,N WHERE S allowed_transition T, S eid %(x)s, '
+            'T name N, T transition_of WF, WF eid %(wfeid)s',
+            {'x': self.current_state.eid,
+=======
         rset = self.req.execute(
             'Any T,TT, TN WHERE S allowed_transition T, S eid %(x)s, '
             'T type TT, T type %(type)s, '
             'T name TN, T transition_of WF, WF eid %(wfeid)s',
             {'x': self.current_state.eid, 'type': type,
+>>>>>>> /tmp/wfobjs.py~other.TyHPqT
              'wfeid': self.current_workflow.eid}, 'x')
         for tr in rset.entities():
             if tr.may_be_fired(self.eid):
@@ -448,16 +456,23 @@
         if tseid is not None:
             args.append( ('to_state', 'S') )
             kwargs['S'] = tseid
-        return self.req.create_entity('TrInfo', *args, **kwargs)
+        return self._cw.create_entity('TrInfo', *args, **kwargs)
 
     def fire_transition(self, tr, comment=None, commentformat=None):
         """change the entity's state by firing transition of the given name in
         entity's workflow
         """
         assert self.current_workflow
+<<<<<<< /home/syt/src/fcubicweb/cubicweb/entities/wfobjs.py
+        tr = self.current_workflow.transition_by_name(trname)
+        if tr is None:
+            raise WorkflowException('not a %s transition: %s' % (self.__regid__,
+                                                                 trname))
+=======
         if isinstance(tr, basestring):
             tr = self.current_workflow.transition_by_name(tr)
         assert tr is not None, 'not a %s transition: %s' % (self.id, tr)
+>>>>>>> /tmp/wfobjs.py~other.TyHPqT
         return self._add_trinfo(comment, commentformat, tr.eid)
 
     def change_state(self, statename, comment=None, commentformat=None, tr=None):
@@ -476,7 +491,7 @@
             else:
                 state = self.current_workflow.state_by_name(statename)
             if state is None:
-                raise WorkflowException('not a %s state: %s' % (self.id,
+                raise WorkflowException('not a %s state: %s' % (self.__regid__,
                                                                 statename))
             stateeid = state.eid
         # XXX try to find matching transition?
@@ -507,7 +522,7 @@
         super(WorkflowableMixIn, self).clear_all_caches()
         clear_cache(self, 'cwetype_workflow')
 
-    @deprecated('get transition from current workflow and use its may_be_fired method')
+    @deprecated('[3.5] get transition from current workflow and use its may_be_fired method')
     def can_pass_transition(self, trname):
         """return the Transition instance if the current user can fire the
         transition with the given name, else None
@@ -517,8 +532,8 @@
             return tr
 
     @property
-    @deprecated('use printable_state')
+    @deprecated('[3.5] use printable_state')
     def displayable_state(self):
-        return self.req._(self.state)
+        return self._cw._(self.state)
 
 MI_REL_TRIGGERS[('in_state', 'subject')] = WorkflowableMixIn
--- a/entity.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/entity.py	Wed Sep 30 18:57:42 2009 +0200
@@ -22,8 +22,8 @@
 from cubicweb.rset import ResultSet
 from cubicweb.selectors import yes
 from cubicweb.appobject import AppObject
+from cubicweb.schema import RQLVocabularyConstraint, RQLConstraint
 from cubicweb.rqlrewrite import RQLRewriter
-from cubicweb.schema import RQLVocabularyConstraint, RQLConstraint, bw_normalize_etype
 
 from cubicweb.common.uilib import printable_value, soup2xhtml
 from cubicweb.common.mixins import MI_REL_TRIGGERS
@@ -40,101 +40,6 @@
     return '1'
 
 
-_MODE_TAGS = set(('link', 'create'))
-_CATEGORY_TAGS = set(('primary', 'secondary', 'generic', 'generated')) # , 'metadata'))
-
-try:
-    from cubicweb.web import formwidgets, uicfg
-
-    def _dispatch_rtags(tags, rtype, role, stype, otype):
-        for tag in tags:
-            if tag in _MODE_TAGS:
-                uicfg.actionbox_appearsin_addmenu.tag_relation(
-                    (stype, rtype, otype, role), tag == 'create')
-            elif tag in _CATEGORY_TAGS:
-                uicfg.autoform_section.tag_relation((stype, rtype, otype, role),
-                                                    tag)
-            elif tag == 'inlineview':
-                uicfg.autoform_is_inlined.tag_relation((stype, rtype, otype, role), True)
-            else:
-                raise ValueError(tag)
-
-except ImportError:
-
-    _dispatch_rtags = None
-
-def _get_etype(bases, classdict):
-    try:
-        return classdict['id']
-    except KeyError:
-        for base in bases:
-            etype = getattr(base, 'id', None)
-            if etype and etype != 'Any':
-                return etype
-
-def _get_defs(attr, name, bases, classdict):
-    try:
-        yield name, classdict.pop(attr)
-    except KeyError:
-        for base in bases:
-            try:
-                value = getattr(base, attr)
-                delattr(base, attr)
-                yield base.__name__, value
-            except AttributeError:
-                continue
-
-
-class _metaentity(type):
-    """this metaclass sets the relation tags on the entity class
-    and deals with the `widgets` attribute
-    """
-    def __new__(mcs, name, bases, classdict):
-        # collect baseclass' rtags
-        etype = _get_etype(bases, classdict)
-        if etype and _dispatch_rtags is not None:
-            for name, rtags in _get_defs('__rtags__', name, bases, classdict):
-                warn('%s: __rtags__ is deprecated' % name, DeprecationWarning)
-                for relation, tags in rtags.iteritems():
-                    # tags must become an iterable
-                    if isinstance(tags, basestring):
-                        tags = (tags,)
-                    # relation must become a 3-uple (rtype, targettype, role)
-                    if isinstance(relation, basestring):
-                        _dispatch_rtags(tags, relation, 'subject', etype, '*')
-                        _dispatch_rtags(tags, relation, 'object', '*', etype)
-                    elif len(relation) == 1: # useful ?
-                        _dispatch_rtags(tags, relation[0], 'subject', etype, '*')
-                        _dispatch_rtags(tags, relation[0], 'object', '*', etype)
-                    elif len(relation) == 2:
-                        rtype, ttype = relation
-                        ttype = bw_normalize_etype(ttype) # XXX bw compat
-                        _dispatch_rtags(tags, rtype, 'subject', etype, ttype)
-                        _dispatch_rtags(tags, rtype, 'object', ttype, etype)
-                    elif len(relation) == 3:
-                        rtype, ttype, role = relation
-                        ttype = bw_normalize_etype(ttype)
-                        if role == 'subject':
-                            _dispatch_rtags(tags, rtype, 'subject', etype, ttype)
-                        else:
-                            _dispatch_rtags(tags, rtype, 'object', ttype, etype)
-                    else:
-                        raise ValueError('bad rtag definition (%r)' % (relation,))
-            for name, widgets in _get_defs('widgets', name, bases, classdict):
-                warn('%s: widgets is deprecated' % name, DeprecationWarning)
-                for rtype, wdgname in widgets.iteritems():
-                    if wdgname in ('URLWidget', 'EmbededURLWidget', 'RawDynamicComboBoxWidget'):
-                        warn('%s widget is deprecated' % wdgname, DeprecationWarning)
-                        continue
-                    if wdgname == 'StringWidget':
-                        wdgname = 'TextInput'
-                    widget = getattr(formwidgets, wdgname)
-                    assert hasattr(widget, 'render')
-                    uicfg.autoform_field_kwargs.tag_subject_of(
-                        (etype, rtype, '*'), {'widget': widget})
-        return super(_metaentity, mcs).__new__(mcs, name, bases, classdict)
-
-
 class Entity(AppObject, dict):
     """an entity instance has e_schema automagically set on
     the class and instances has access to their issuing cursor.
@@ -158,28 +63,24 @@
                          as composite relations or relations that have '?1' as object
                          cardinality
     """
-    __metaclass__ = _metaentity
     __registry__ = 'etypes'
     __select__ = yes()
 
     # class attributes that must be set in class definition
-    id = None
     rest_attr = None
     fetch_attrs = None
     skip_copy_for = ('in_state',)
     # class attributes set automatically at registration time
     e_schema = None
 
-    MODE_TAGS = set(('link', 'create'))
-    CATEGORY_TAGS = set(('primary', 'secondary', 'generic', 'generated')) # , 'metadata'))
     @classmethod
-    def __initialize__(cls):
+    def __initialize__(cls, schema):
         """initialize a specific entity class by adding descriptors to access
         entity type's attributes and relations
         """
-        etype = cls.id
+        etype = cls.__regid__
         assert etype != 'Any', etype
-        cls.e_schema = eschema = cls.schema.eschema(etype)
+        cls.e_schema = eschema = schema.eschema(etype)
         for rschema, _ in eschema.attribute_definitions():
             if rschema.type == 'eid':
                 continue
@@ -208,7 +109,7 @@
         """return a rql to fetch all entities of the class type"""
         restrictions = restriction or []
         if settype:
-            restrictions.append('%s is %s' % (mainvar, cls.id))
+            restrictions.append('%s is %s' % (mainvar, cls.__regid__))
         if fetchattrs is None:
             fetchattrs = cls.fetch_attrs
         selection = [mainvar]
@@ -241,7 +142,7 @@
                 rschema = eschema.subject_relation(attr)
             except KeyError:
                 cls.warning('skipping fetch_attr %s defined in %s (not found in schema)',
-                            attr, cls.id)
+                            attr, cls.__regid__)
                 continue
             if not user.matching_groups(rschema.get_groups('read')):
                 continue
@@ -264,7 +165,8 @@
                 # that case the relation may still be missing. As we miss this
                 # later information here, systematically add it.
                 restrictions[-1] += '?'
-                destcls = cls.vreg['etypes'].etype_class(desttype)
+                # XXX user.req.vreg iiiirk
+                destcls = user._cw.vreg['etypes'].etype_class(desttype)
                 destcls._fetch_restrictions(var, varmaker, destcls.fetch_attrs,
                                             selection, orderby, restrictions,
                                             user, ordermethod, visited=visited)
@@ -275,14 +177,6 @@
 
     @classmethod
     @cached
-    def parent_classes(cls):
-        parents = [cls.vreg['etypes'].etype_class(e.type)
-                   for e in cls.e_schema.ancestors()]
-        parents.append(cls.vreg['etypes'].etype_class('Any'))
-        return parents
-
-    @classmethod
-    @cached
     def _rest_attr_info(cls):
         mainattr, needcheck = 'eid', True
         if cls.rest_attr:
@@ -299,7 +193,7 @@
         return mainattr, needcheck
 
     def __init__(self, req, rset=None, row=None, col=0):
-        AppObject.__init__(self, req, rset, row, col)
+        AppObject.__init__(self, req, rset=rset, row=row, col=col)
         dict.__init__(self)
         self._related_cache = {}
         if rset is not None:
@@ -353,24 +247,26 @@
 
     @cached
     def metainformation(self):
-        res = dict(zip(('type', 'source', 'extid'), self.req.describe(self.eid)))
-        res['source'] = self.req.source_defs()[res['source']]
+        res = dict(zip(('type', 'source', 'extid'), self._cw.describe(self.eid)))
+        res['source'] = self._cw.source_defs()[res['source']]
         return res
 
     def clear_local_perm_cache(self, action):
         for rqlexpr in self.e_schema.get_rqlexprs(action):
-            self.req.local_perm_cache.pop((rqlexpr.eid, (('x', self.eid),)), None)
+            self._cw.local_perm_cache.pop((rqlexpr.eid, (('x', self.eid),)), None)
 
     def check_perm(self, action):
-        self.e_schema.check_perm(self.req, action, self.eid)
+        self.e_schema.check_perm(self._cw, action, self.eid)
 
     def has_perm(self, action):
-        return self.e_schema.has_perm(self.req, action, self.eid)
+        return self.e_schema.has_perm(self._cw, action, self.eid)
 
     def view(self, vid, __registry='views', **kwargs):
         """shortcut to apply a view on this entity"""
-        return self.vreg[__registry].render(vid, self.req, rset=self.rset,
-                                            row=self.row, col=self.col, **kwargs)
+        view = self._cw.vreg[__registry].select(vid, self._cw, rset=self.cw_rset,
+                                                row=self.cw_row, col=self.cw_col,
+                                                **kwargs)
+        return view.render(row=self.cw_row, col=self.cw_col, **kwargs)
 
     def absolute_url(self, *args, **kwargs):
         """return an absolute url to view this entity"""
@@ -384,18 +280,18 @@
         # in linksearch mode, we don't want external urls else selecting
         # the object for use in the relation is tricky
         # XXX search_state is web specific
-        if getattr(self.req, 'search_state', ('normal',))[0] == 'normal':
+        if getattr(self._cw, 'search_state', ('normal',))[0] == 'normal':
             kwargs['base_url'] = self.metainformation()['source'].get('base-url')
         if method in (None, 'view'):
             try:
                 kwargs['_restpath'] = self.rest_path(kwargs.get('base_url'))
             except TypeError:
-                warn('%s: rest_path() now take use_ext_eid argument, '
-                     'please update' % self.id, DeprecationWarning)
+                warn('[3.4] %s: rest_path() now take use_ext_eid argument, '
+                     'please update' % self.__regid__, DeprecationWarning)
                 kwargs['_restpath'] = self.rest_path()
         else:
             kwargs['rql'] = 'Any X WHERE X eid %s' % self.eid
-        return self.build_url(method, **kwargs)
+        return self._cw.build_url(method, **kwargs)
 
     def rest_path(self, use_ext_eid=False):
         """returns a REST-like (relative) path for this entity"""
@@ -411,7 +307,7 @@
                 # make sure url is not ambiguous
                 rql = 'Any COUNT(X) WHERE X is %s, X %s %%(value)s' % (
                     etype, mainattr)
-                nbresults = self.req.execute(rql, {'value' : value})[0][0]
+                nbresults = self._cw.execute(rql, {'value' : value})[0][0]
                 if nbresults != 1: # ambiguity?
                     mainattr = 'eid'
                     path += '/eid'
@@ -420,13 +316,13 @@
                 value = self.metainformation()['extid']
             else:
                 value = self.eid
-        return '%s/%s' % (path, self.req.url_quote(value))
+        return '%s/%s' % (path, self._cw.url_quote(value))
 
     def attr_metadata(self, attr, metadata):
         """return a metadata for an attribute (None if unspecified)"""
         value = getattr(self, '%s_%s' % (attr, metadata), None)
         if value is None and metadata == 'encoding':
-            value = self.vreg.property_value('ui.encoding')
+            value = self._cw.vreg.property_value('ui.encoding')
         return value
 
     def printable_value(self, attr, value=_marker, attrtype=None,
@@ -448,11 +344,11 @@
             # internalinalized *and* formatted string such as schema
             # description...
             if props.get('internationalizable'):
-                value = self.req._(value)
+                value = self._cw._(value)
             attrformat = self.attr_metadata(attr, 'format')
             if attrformat:
                 return self.mtc_transform(value, attrformat, format,
-                                          self.req.encoding)
+                                          self._cw.encoding)
         elif attrtype == 'Bytes':
             attrformat = self.attr_metadata(attr, 'format')
             if attrformat:
@@ -460,7 +356,7 @@
                 return self.mtc_transform(value.getvalue(), attrformat, format,
                                           encoding)
             return u''
-        value = printable_value(self.req, attrtype, value, props,
+        value = printable_value(self._cw, attrtype, value, props,
                                 displaytime=displaytime)
         if format == 'text/html':
             value = xml_escape(value)
@@ -471,7 +367,7 @@
         trdata = TransformData(data, format, encoding, appobject=self)
         data = _engine.convert(trdata, target_format).decode()
         if format == 'text/html':
-            data = soup2xhtml(data, self.req.encoding)
+            data = soup2xhtml(data, self._cw.encoding)
         return data
 
     # entity cloning ##########################################################
@@ -483,7 +379,7 @@
         Overrides this if you want another behaviour
         """
         assert self.has_eid()
-        execute = self.req.execute
+        execute = self._cw.execute
         for rschema in self.e_schema.subject_relations():
             if rschema.is_final() or rschema.meta:
                 continue
@@ -527,8 +423,8 @@
     def as_rset(self):
         """returns a resultset containing `self` information"""
         rset = ResultSet([(self.eid,)], 'Any X WHERE X eid %(x)s',
-                         {'x': self.eid}, [(self.id,)])
-        return self.req.decorate_rset(rset)
+                         {'x': self.eid}, [(self.__regid__,)])
+        return self._cw.decorate_rset(rset)
 
     def to_complete_relations(self):
         """by default complete final relations to when calling .complete()"""
@@ -540,7 +436,7 @@
                 # outer join correctly in this case
                 continue
             if rschema.inlined:
-                matching_groups = self.req.user.matching_groups
+                matching_groups = self._cw.user.matching_groups
                 if matching_groups(rschema.get_groups('read')) and \
                    all(matching_groups(es.get_groups('read'))
                        for es in rschema.objects(self.e_schema)):
@@ -555,7 +451,7 @@
             if attr == 'eid':
                 continue
             # password retreival is blocked at the repository server level
-            if not self.req.user.matching_groups(rschema.get_groups('read')) \
+            if not self._cw.user.matching_groups(rschema.get_groups('read')) \
                    or attrschema.type == 'Password':
                 self[attr] = None
                 continue
@@ -615,7 +511,7 @@
             # if some outer join are included to fetch inlined relations
             rql = 'Any %s,%s %s' % (V, ','.join(var for attr, var in selected),
                                     ','.join(rql))
-            execute = getattr(self.req, 'unsafe_execute', self.req.execute)
+            execute = getattr(self._cw, 'unsafe_execute', self._cw.execute)
             rset = execute(rql, {'x': self.eid}, 'x', build_descr=False)[0]
             # handle attributes
             for i in xrange(1, lastattr):
@@ -626,9 +522,9 @@
                 value = rset[i]
                 if value is None:
                     rrset = ResultSet([], rql, {'x': self.eid})
-                    self.req.decorate_rset(rrset)
+                    self._cw.decorate_rset(rrset)
                 else:
-                    rrset = self.req.eid_rset(value)
+                    rrset = self._cw.eid_rset(value)
                 self.set_related_cache(rtype, role, rrset)
 
     def get_value(self, name):
@@ -646,7 +542,7 @@
             rql = "Any A WHERE X eid %%(x)s, X %s A" % name
             # XXX should we really use unsafe_execute here? I think so (syt),
             # see #344874
-            execute = getattr(self.req, 'unsafe_execute', self.req.execute)
+            execute = getattr(self._cw, 'unsafe_execute', self._cw.execute)
             try:
                 rset = execute(rql, {'x': self.eid}, 'x')
             except Unauthorized:
@@ -661,7 +557,7 @@
                                   name, self.eid)
                     if self.e_schema.destination(name) == 'String':
                         # XXX (syt) imo emtpy string is better
-                        self[name] = value = self.req._('unaccessible')
+                        self[name] = value = self._cw._('unaccessible')
                     else:
                         self[name] = value = None
         return value
@@ -681,13 +577,13 @@
         rql = self.related_rql(rtype, role)
         # XXX should we really use unsafe_execute here? I think so (syt),
         # see #344874
-        execute = getattr(self.req, 'unsafe_execute', self.req.execute)
+        execute = getattr(self._cw, 'unsafe_execute', self._cw.execute)
         rset = execute(rql, {'x': self.eid}, 'x')
         self.set_related_cache(rtype, role, rset)
         return self.related(rtype, role, limit, entities)
 
     def related_rql(self, rtype, role='subject', targettypes=None):
-        rschema = self.schema[rtype]
+        rschema = self._cw.vreg.schema[rtype]
         if role == 'subject':
             if targettypes is None:
                 targettypes = rschema.objects(self.e_schema)
@@ -701,14 +597,14 @@
         if len(targettypes) > 1:
             fetchattrs_list = []
             for ttype in targettypes:
-                etypecls = self.vreg['etypes'].etype_class(ttype)
+                etypecls = self._cw.vreg['etypes'].etype_class(ttype)
                 fetchattrs_list.append(set(etypecls.fetch_attrs))
             fetchattrs = reduce(set.intersection, fetchattrs_list)
-            rql = etypecls.fetch_rql(self.req.user, [restriction], fetchattrs,
+            rql = etypecls.fetch_rql(self._cw.user, [restriction], fetchattrs,
                                      settype=False)
         else:
-            etypecls = self.vreg['etypes'].etype_class(targettypes[0])
-            rql = etypecls.fetch_rql(self.req.user, [restriction], settype=False)
+            etypecls = self._cw.vreg['etypes'].etype_class(targettypes[0])
+            rql = etypecls.fetch_rql(self._cw.user, [restriction], settype=False)
         # optimisation: remove ORDERBY if cardinality is 1 or ? (though
         # greater_card return 1 for those both cases)
         if card == '1':
@@ -732,7 +628,7 @@
         """
         ordermethod = ordermethod or 'fetch_unrelated_order'
         if isinstance(rtype, basestring):
-            rtype = self.schema.rschema(rtype)
+            rtype = self._cw.vreg.schema.rschema(rtype)
         if role == 'subject':
             evar, searchedvar = 'S', 'O'
             subjtype, objtype = self.e_schema, targettype
@@ -751,7 +647,7 @@
             args = {}
             securitycheck_args = {}
         insertsecurity = (rtype.has_local_role('add') and not
-                          rtype.has_perm(self.req, 'add', **securitycheck_args))
+                          rtype.has_perm(self._cw, 'add', **securitycheck_args))
         constraints = rtype.rproperty(subjtype, objtype, 'constraints')
         if vocabconstraints:
             # RQLConstraint is a subclass for RQLVocabularyConstraint, so they
@@ -761,8 +657,8 @@
         else:
             restriction += [cstr.restriction for cstr in constraints
                             if isinstance(cstr, RQLConstraint)]
-        etypecls = self.vreg['etypes'].etype_class(targettype)
-        rql = etypecls.fetch_rql(self.req.user, restriction,
+        etypecls = self._cw.vreg['etypes'].etype_class(targettype)
+        rql = etypecls.fetch_rql(self._cw.user, restriction,
                                  mainvar=searchedvar, ordermethod=ordermethod)
         # ensure we have an order defined
         if not ' ORDERBY ' in rql:
@@ -770,8 +666,8 @@
             rql = '%s ORDERBY %s WHERE %s' % (before, searchedvar, after)
         if insertsecurity:
             rqlexprs = rtype.get_rqlexprs('add')
-            rewriter = RQLRewriter(self.req)
-            rqlst = self.req.vreg.parse(self.req, rql, args)
+            rewriter = RQLRewriter(self._cw)
+            rqlst = self._cw.vreg.parse(self._cw, rql, args)
             for select in rqlst.children:
                 rewriter.rewrite(select, [((searchedvar, searchedvar), rqlexprs)],
                                  select.solutions, args)
@@ -786,11 +682,11 @@
         try:
             rql, args = self.unrelated_rql(rtype, targettype, role, ordermethod)
         except Unauthorized:
-            return self.req.empty_rset()
+            return self._cw.empty_rset()
         if limit is not None:
             before, after = rql.split(' WHERE ', 1)
             rql = '%s LIMIT %s WHERE %s' % (before, limit, after)
-        return self.req.execute(rql, args, tuple(args))
+        return self._cw.execute(rql, args, tuple(args))
 
     # relations cache handling ################################################
 
@@ -815,7 +711,7 @@
         """set cached values for the given relation"""
         if rset:
             related = list(rset.entities(col))
-            rschema = self.schema.rschema(rtype)
+            rschema = self._cw.vreg.schema.rschema(rtype)
             if role == 'subject':
                 rcard = rschema.rproperty(self.e_schema, related[0].e_schema,
                                           'cardinality')[1]
@@ -858,15 +754,15 @@
         # and now update the database
         kwargs['x'] = self.eid
         if _cw_unsafe:
-            self.req.unsafe_execute(
+            self._cw.unsafe_execute(
                 'SET %s WHERE X eid %%(x)s' % ','.join(relations), kwargs, 'x')
         else:
-            self.req.execute('SET %s WHERE X eid %%(x)s' % ','.join(relations),
+            self._cw.execute('SET %s WHERE X eid %%(x)s' % ','.join(relations),
                              kwargs, 'x')
 
     def delete(self):
         assert self.has_eid(), self.eid
-        self.req.execute('DELETE %s X WHERE X eid %%(x)s' % self.e_schema,
+        self._cw.execute('DELETE %s X WHERE X eid %%(x)s' % self.e_schema,
                          {'x': self.eid})
 
     # server side utilities ###################################################
@@ -885,10 +781,10 @@
         """
         # necessary since eid is handled specifically and yams require it to be
         # in the dictionary
-        if self.req is None:
+        if self._cw is None:
             _ = unicode
         else:
-            _ = self.req._
+            _ = self._cw._
         self.e_schema.check(self, creation=creation, _=_)
 
     def fti_containers(self, _done=None):
@@ -946,20 +842,6 @@
                     words += entity.get_words()
         return words
 
-    @deprecated('[3.2] see new form api')
-    def vocabulary(self, rtype, role='subject', limit=None):
-        """vocabulary functions must return a list of couples
-        (label, eid) that will typically be used to fill the
-        edition view's combobox.
-
-        If `eid` is None in one of these couples, it should be
-        interpreted as a separator in case vocabulary results are grouped
-        """
-        from logilab.common.testlib import mock_object
-        form = self.vreg.select('forms', 'edition', self.req, entity=self)
-        field = mock_object(name=rtype, role=role)
-        return form.form_field_vocabulary(field, limit)
-
 
 # attribute and relation descriptors ##########################################
 
--- a/etwist/server.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/etwist/server.py	Wed Sep 30 18:57:42 2009 +0200
@@ -127,7 +127,7 @@
         start_task(interval, self.appli.session_handler.clean_sessions)
 
     def set_url_rewriter(self):
-        self.url_rewriter = self.appli.vreg['components'].select_object('urlrewriter')
+        self.url_rewriter = self.appli.vreg['components'].select_or_none('urlrewriter')
 
     def shutdown_event(self):
         """callback fired when the server is shutting down to properly
--- a/etwist/test/unittest_server.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/etwist/test/unittest_server.py	Wed Sep 30 18:57:42 2009 +0200
@@ -5,11 +5,11 @@
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.etwist.server import host_prefixed_baseurl
 
 
-class HostPrefixedBaseURLTC(EnvBasedTC):
+class HostPrefixedBaseURLTC(CubicWebTC):
 
     def _check(self, baseurl, host, waited):
         self.assertEquals(host_prefixed_baseurl(baseurl, host), waited,
--- a/etwist/twctl.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/etwist/twctl.py	Wed Sep 30 18:57:42 2009 +0200
@@ -8,7 +8,6 @@
 
 import sys
 
-from cubicweb import underline_title
 from cubicweb.toolsutils import CommandHandler
 
 # trigger configuration registration
--- a/ext/rest.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/ext/rest.py	Wed Sep 30 18:57:42 2009 +0200
@@ -70,10 +70,10 @@
     # Base URL mainly used by inliner.pep_reference; so this is correct:
     context = inliner.document.settings.context
     try:
-        refedentity = context.req.entity_from_eid(eid_num)
+        refedentity = context._cw.entity_from_eid(eid_num)
     except UnknownEid:
         ref = '#'
-        rest += u' ' + context.req._('(UNEXISTANT EID)')
+        rest += u' ' + context._cw._('(UNEXISTANT EID)')
     else:
         ref = refedentity.absolute_url()
     set_classes(options)
@@ -210,7 +210,7 @@
     :return:
       the data formatted as HTML or the original data if an error occured
     """
-    req = context.req
+    req = context._cw
     if isinstance(data, unicode):
         encoding = 'unicode'
         # remove unprintable characters unauthorized in xml
--- a/ext/test/unittest_rest.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/ext/test/unittest_rest.py	Wed Sep 30 18:57:42 2009 +0200
@@ -6,11 +6,11 @@
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from logilab.common.testlib import unittest_main
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 
 from cubicweb.ext.rest import rest_publish
 
-class RestTC(EnvBasedTC):
+class RestTC(CubicWebTC):
     def context(self):
         return self.execute('CWUser X WHERE X login "admin"').get_entity(0, 0)
 
--- a/goa/appobjects/components.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/goa/appobjects/components.py	Wed Sep 30 18:57:42 2009 +0200
@@ -28,7 +28,7 @@
     __select__ = one_line_rset() & match_search_state('linksearch') & accept
 
     def cell_call(self, row, col):
-        entity = self.entity(0, 0)
+        entity = self.rset.get_entity(0, 0)
         role, eid, rtype, etype = self.req.search_state[1]
         assert entity.eid == typed_eid(eid)
         rset = entity.unrelated(rtype, etype, role, ordermethod='fetch_order')
--- a/goa/appobjects/sessions.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/goa/appobjects/sessions.py	Wed Sep 30 18:57:42 2009 +0200
@@ -73,9 +73,9 @@
 class GAEPersistentSessionManager(AbstractSessionManager):
     """manage session data associated to a session identifier"""
 
-    def __init__(self, *args, **kwargs):
-        super(GAEPersistentSessionManager, self).__init__(*args, **kwargs)
-        self._repo = self.config.repository(vreg=self.vreg)
+    def __init__(self, vreg, *args, **kwargs):
+        super(GAEPersistentSessionManager, self).__init__(vreg, *args, **kwargs)
+        self._repo = self.config.repository(vreg=vreg)
 
     def get_session(self, req, sessionid):
         """return existing session for the given session identifier"""
--- a/goa/db.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/goa/db.py	Wed Sep 30 18:57:42 2009 +0200
@@ -35,7 +35,8 @@
 
 from logilab.common.decorators import cached, iclassmethod
 
-from cubicweb import RequestSessionMixIn, Binary, entities
+from cubicweb import Binary, entities
+from cubicweb.req import RequestSessionBase
 from cubicweb.rset import ResultSet
 from cubicweb.entity import metaentity
 from cubicweb.server.utils import crypt_password
@@ -92,7 +93,7 @@
 def needrequest(wrapped):
     def wrapper(cls, *args, **kwargs):
         req = kwargs.pop('req', None)
-        if req is None and args and isinstance(args[0], RequestSessionMixIn):
+        if req is None and args and isinstance(args[0], RequestSessionBase):
             args = list(args)
             req = args.pop(0)
         if req is None:
@@ -155,7 +156,7 @@
         #
         # Entity prototype:
         #   __init__(self, req, rset, row=None, col=0)
-        if args and isinstance(args[0], RequestSessionMixIn) or 'req' in kwargs:
+        if args and isinstance(args[0], RequestSessionBase) or 'req' in kwargs:
             super(Model, self).__init__(*args, **kwargs)
             self._gaeinitargs = None
         else:
--- a/goa/doc/quickstart.txt	Wed Sep 30 17:57:02 2009 +0200
+++ b/goa/doc/quickstart.txt	Wed Sep 30 18:57:42 2009 +0200
@@ -1,3 +1,5 @@
+.. -*- coding: utf-8 -*-
+
 Introduction
 =============
 
@@ -11,7 +13,7 @@
   application.
 
 *result set*
-  objet qui encaspule les résultats d'une requête adressée à l'entrepôt 
+  objet qui encaspule les résultats d'une requête adressée à l'entrepôt
   de données et des informations sur cette requête.
 
 *vue*
@@ -23,8 +25,8 @@
 Définition d'une application de Blog
 ====================================
 
-La première chose à faire est de copier le squelette depuis le répertoire 
-``lax/skel`` vers un nouveau répertoire qui sera votre application 
+La première chose à faire est de copier le squelette depuis le répertoire
+``lax/skel`` vers un nouveau répertoire qui sera votre application
 ``Google AppEngine``::
 
   $ cp -r lax/skel myapp
@@ -36,7 +38,7 @@
 données manipulées. La syntaxe de la définition est la même que celle
 proposée par `Google AppEngine`_ mais il faut remplacer la ligne
 d'import::
-  
+
   from google.appengine.ext import db
 
 par celle-ci::
@@ -47,7 +49,7 @@
 Un exemple de schéma de données pour un ``Blog`` pourrait être::
 
   from cubicweb.goa import db
-  
+
   class Blog(db.Model):
       # un titre à donner à l'entrée
       title = db.StringProperty(required=True)
@@ -56,15 +58,15 @@
       # le contenu de l'entrée
       content = db.TextProperty()
       # une entrée peut en citer une autre
-      cites = db.SelfReferenceProperty() 
-      
+      cites = db.SelfReferenceProperty()
+
 
 Personnalisation des vues
 -------------------------
 
 ``LAX`` permet de générer directement, à partir de la définition
-du schéma, des vues de consultation, d'ajout et de modification 
-pour tous les types de donées manipulés. Il est toutefois 
+du schéma, des vues de consultation, d'ajout et de modification
+pour tous les types de donées manipulés. Il est toutefois
 généralement souhaitable de personnaliser les vues de consultations.
 
 Dans ``LAX``, les vues sont représentées par des classes Python.
@@ -74,7 +76,7 @@
 - un identifiant (tous les objets dans ``LAX`` sont enregistrés
   dans un registre et cet identifiant sert de clé pour y retrouver
   la vue)
-  
+
 - une description des types de données auxquels elle s'applique
 
 Il existe dans ``LAX`` des vues prédéfinies et utilisées par le moteur
@@ -86,17 +88,17 @@
 Par exemple, si on souhaite modifier la page principale d'une entrée de
 blog, il faut surcharger la vue ``primary`` des objets ``Blog`` dans
 le fichier ``myapp/views.py``::
-  
+
   from cubicweb.web.views import baseviews
-  
+
   class BlogPrimaryView(baseviews.PrimaryView):
       accepts = ('Blog',)
-      
+
       def cell_call(self, row, col):
-          entity = self.entity(row, col)
+          entity = self.rset.get_entity(row, col)
           self.w(u'<h1>%s</h1>' % entity.title)
           self.w(u'<div>%s</div>' entity.content)
-    
+
 
 Génération du graphique de schéma
 ---------------------------------
@@ -104,13 +106,13 @@
 Il existe une vue ``schema`` qui permet d'afficher un graphique
 représantant les différents types d'entités définis dans le schéma
 ainsi que les relations entre ces types. Ce graphique doit être généré
-statiquement. Le script à utiliser pour générer ce schéma est 
+statiquement. Le script à utiliser pour générer ce schéma est
 dans ``myapp/tools``. Ce script nécessite d'avoir accès aux
 bibliothèques fournies par le SDK de ``Google AppEngine``. Il faut
 donc modifier son PYTHONPATH::
 
   $ export PYTHONPATH=GAE_ROOT/google:GAE_ROOT/lib/yaml
-  $ python tools/generate_schema_img.py 
+  $ python tools/generate_schema_img.py
 
 
 Génération des fichiers de traduction
--- a/goa/goactl.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/goa/goactl.py	Wed Sep 30 18:57:42 2009 +0200
@@ -117,7 +117,6 @@
     'web/httpcache.py',
     'web/request.py',
     'web/webconfig.py',
-    'web/widgets.py',
 
     'web/views/__init__.py',
     'web/views/actions.py',
--- a/goa/skel/views.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/goa/skel/views.py	Wed Sep 30 18:57:42 2009 +0200
@@ -19,7 +19,7 @@
     accepts = ('BlogEntry',)
 
     def cell_call(self, row, col):
-        entity = self.entity(row, col)
+        entity = self.rset.get_entity(row, col)
         self.w(u'<h1>%s</h1>' % entity.dc_title())
         entity.view('metadata', w=self.w)
         self.w(entity.printable_value('text'))
--- a/goa/test/unittest_editcontroller.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/goa/test/unittest_editcontroller.py	Wed Sep 30 18:57:42 2009 +0200
@@ -401,11 +401,11 @@
             #    which fires a Redirect
             # 2/ When re-publishing the copy form, the publisher implicitly commits
             try:
-                self.env.app.publish('edit', self.req)
+                self.app.publish('edit', self.req)
             except Redirect:
                 self.req.form['rql'] = 'Any X WHERE X eid %s' % p.eid
                 self.req.form['vid'] = 'copy'
-                self.env.app.publish('view', self.req)
+                self.app.publish('view', self.req)
             rset = self.req.execute('CWUser P WHERE P surname "Boom"')
             self.assertEquals(len(rset), 0)
         finally:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/__init__.py	Wed Sep 30 18:57:42 2009 +0200
@@ -0,0 +1,1 @@
+"""core hooks"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/bookmark.py	Wed Sep 30 18:57:42 2009 +0200
@@ -0,0 +1,31 @@
+"""bookmark related hooks
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb.selectors import entity_implements
+from cubicweb.server import hook
+
+
+class AutoDeleteBookmarkOp(hook.Operation):
+    bookmark = None # make pylint happy
+    def precommit_event(self):
+        if not self.session.deleted_in_transaction(self.bookmark.eid):
+            if not self.bookmark.bookmarked_by:
+                self.bookmark.delete()
+
+
+class DelBookmarkedByHook(hook.Hook):
+    """ensure user logins are stripped"""
+    __regid__ = 'autodelbookmark'
+    __select__ = hook.Hook.__select__ & entity_implements('bookmarked_by',)
+    category = 'bookmark'
+    events = ('after_delete_relation',)
+
+    def __call__(self):
+        AutoDeleteBookmarkOp(self._cw,
+                             bookmark=self._cw.entity_from_eid(self.eidfrom))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/email.py	Wed Sep 30 18:57:42 2009 +0200
@@ -0,0 +1,69 @@
+"""hooks to ensure use_email / primary_email relations consistency
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb.server import hook
+from cubicweb.server.repository import ensure_card_respected
+
+class SetUseEmailRelationOp(hook.Operation):
+    """delay this operation to commit to avoid conflict with a late rql query
+    already setting the relation
+    """
+    rtype = 'use_email'
+    entity = email = None # make pylint happy
+
+    def condition(self):
+        """check entity has use_email set for the email address"""
+        return not any(e for e in self.entity.use_email
+                       if self.email.eid == e.eid)
+
+    def precommit_event(self):
+        if self.condition():
+            # we've to handle cardinaly by ourselves since we're using unsafe_execute
+            # but use session.execute and not session.unsafe_execute to check we
+            # can change the relation
+            ensure_card_respected(self.session.execute, self.session,
+                                  self.entity.eid, self.rtype, self.email.eid)
+            self.session.unsafe_execute(
+                'SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % self.rtype,
+                {'x': self.entity.eid, 'y': self.email.eid}, 'x')
+
+
+class SetPrimaryEmailRelationOp(SetUseEmailRelationOp):
+    rtype = 'primary_email'
+
+    def condition(self):
+        """check entity has no primary_email set"""
+        return not self.entity.primary_email
+
+
+class SetPrimaryEmailHook(hook.Hook):
+    """notify when a bug or story or version has its state modified"""
+    __regid__ = 'setprimaryemail'
+    __select__ = hook.Hook.__select__ & hook.match_rtype('use_email')
+    category = 'email'
+    events = ('after_add_relation',)
+
+    def __call__(self):
+        entity = self._cw.entity_from_eid(self.eidfrom)
+        if 'primary_email' in entity.e_schema.subject_relations():
+            SetPrimaryEmailRelationOp(self._cw, entity=entity,
+                                      email=self._cw.entity_from_eid(self.eidto))
+
+class SetUseEmailHook(hook.Hook):
+    """notify when a bug or story or version has its state modified"""
+    __regid__ = 'setprimaryemail'
+    __select__ = hook.Hook.__select__ & hook.match_rtype('primary_email')
+    category = 'email'
+    events = ('after_add_relation',)
+
+    def __call__(self):
+        entity = self._cw.entity_from_eid(self.eidfrom)
+        if 'use_email' in entity.e_schema.subject_relations():
+            SetUseEmailRelationOp(self._cw, entity=entity,
+                                  email=self._cw.entity_from_eid(self.eidto))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/integrity.py	Wed Sep 30 18:57:42 2009 +0200
@@ -0,0 +1,257 @@
+"""Core hooks: check for data integrity according to the instance'schema
+validity
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb import ValidationError
+from cubicweb.selectors import entity_implements
+from cubicweb.common.uilib import soup2xhtml
+from cubicweb.server import hook
+
+# special relations that don't have to be checked for integrity, usually
+# because they are handled internally by hooks (so we trust ourselves)
+DONT_CHECK_RTYPES_ON_ADD = set(('owned_by', 'created_by',
+                                'is', 'is_instance_of',
+                                'wf_info_for', 'from_state', 'to_state'))
+DONT_CHECK_RTYPES_ON_DEL = set(('is', 'is_instance_of',
+                                'wf_info_for', 'from_state', 'to_state'))
+
+
+class _CheckRequiredRelationOperation(hook.LateOperation):
+    """checking relation cardinality has to be done after commit in
+    case the relation is being replaced
+    """
+    eid, rtype = None, None
+
+    def precommit_event(self):
+        # recheck pending eids
+        if self.session.deleted_in_transaction(self.eid):
+            return
+        if self.session.unsafe_execute(*self._rql()).rowcount < 1:
+            etype = self.session.describe(self.eid)[0]
+            _ = self.session._
+            msg = _('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)')
+            msg %= {'rtype': _(self.rtype), 'etype': _(etype), 'eid': self.eid}
+            raise ValidationError(self.eid, {self.rtype: msg})
+
+    def commit_event(self):
+        pass
+
+    def _rql(self):
+        raise NotImplementedError()
+
+
+class _CheckSRelationOp(_CheckRequiredRelationOperation):
+    """check required subject relation"""
+    def _rql(self):
+        return 'Any O WHERE S eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x'
+
+
+class _CheckORelationOp(_CheckRequiredRelationOperation):
+    """check required object relation"""
+    def _rql(self):
+        return 'Any S WHERE O eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x'
+
+
+class IntegrityHook(hook.Hook):
+    __abstract__ = True
+    category = 'integrity'
+
+
+class CheckCardinalityHook(IntegrityHook):
+    """check cardinalities are satisfied"""
+    __regid__ = 'checkcard'
+    events = ('after_add_entity', 'before_delete_relation')
+
+    def __call__(self):
+        getattr(self, self.event)()
+
+    def checkrel_if_necessary(self, opcls, rtype, eid):
+        """check an equivalent operation has not already been added"""
+        for op in self._cw.pending_operations:
+            if isinstance(op, opcls) and op.rtype == rtype and op.eid == eid:
+                break
+        else:
+            opcls(self._cw, rtype=rtype, eid=eid)
+
+    def after_add_entity(self):
+        eid = self.entity.eid
+        eschema = self.entity.e_schema
+        for rschema, targetschemas, x in eschema.relation_definitions():
+            # skip automatically handled relations
+            if rschema.type in DONT_CHECK_RTYPES_ON_ADD:
+                continue
+            if x == 'subject':
+                subjtype = eschema
+                objtype = targetschemas[0].type
+                cardindex = 0
+                opcls = _CheckSRelationOp
+            else:
+                subjtype = targetschemas[0].type
+                objtype = eschema
+                cardindex = 1
+                opcls = _CheckORelationOp
+            card = rschema.rproperty(subjtype, objtype, 'cardinality')
+            if card[cardindex] in '1+':
+                self.checkrel_if_necessary(opcls, rschema.type, eid)
+
+    def before_delete_relation(self):
+        rtype = self.rtype
+        if rtype in DONT_CHECK_RTYPES_ON_DEL:
+            return
+        session = self._cw
+        eidfrom, eidto = self.eidfrom, self.eidto
+        card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality')
+        pendingrdefs = session.transaction_data.get('pendingrdefs', ())
+        if (session.describe(eidfrom)[0], rtype, session.describe(eidto)[0]) in pendingrdefs:
+            return
+        if card[0] in '1+' and not session.deleted_in_transaction(eidfrom):
+            self.checkrel_if_necessary(_CheckSRelationOp, rtype, eidfrom)
+        if card[1] in '1+' and not session.deleted_in_transaction(eidto):
+            self.checkrel_if_necessary(_CheckORelationOp, rtype, eidto)
+
+
+class _CheckConstraintsOp(hook.LateOperation):
+    """check a new relation satisfy its constraints
+    """
+    def precommit_event(self):
+        eidfrom, rtype, eidto = self.rdef
+        # first check related entities have not been deleted in the same
+        # transaction
+        if self.session.deleted_in_transaction(eidfrom):
+            return
+        if self.session.deleted_in_transaction(eidto):
+            return
+        for constraint in self.constraints:
+            try:
+                constraint.repo_check(self.session, eidfrom, rtype, eidto)
+            except NotImplementedError:
+                self.critical('can\'t check constraint %s, not supported',
+                              constraint)
+
+    def commit_event(self):
+        pass
+
+
+class CheckConstraintHook(IntegrityHook):
+    """check the relation satisfy its constraints
+
+    this is delayed to a precommit time operation since other relation which
+    will make constraint satisfied may be added later.
+    """
+    __regid__ = 'checkconstraint'
+    events = ('after_add_relation',)
+
+    def __call__(self):
+        constraints = self._cw.schema_rproperty(self.rtype, self.eidfrom, self.eidto,
+                                'constraints')
+        if constraints:
+            _CheckConstraintsOp(self._cw, constraints=constraints,
+                               rdef=(self.eidfrom, self.rtype, self.eidto))
+
+
+class CheckUniqueHook(IntegrityHook):
+    __regid__ = 'checkunique'
+    events = ('before_add_entity', 'before_update_entity')
+
+    def __call__(self):
+        entity = self.entity
+        eschema = entity.e_schema
+        for attr in entity.edited_attributes:
+            if eschema.subject_relation(attr).is_final() and \
+                   eschema.has_unique_values(attr):
+                val = entity[attr]
+                if val is None:
+                    continue
+                rql = '%s X WHERE X %s %%(val)s' % (entity.e_schema, attr)
+                rset = self._cw.unsafe_execute(rql, {'val': val})
+                if rset and rset[0][0] != entity.eid:
+                    msg = self._cw._('the value "%s" is already used, use another one')
+                    raise ValidationError(entity.eid, {attr: msg % val})
+
+
+class _DelayedDeleteOp(hook.Operation):
+    """delete the object of composite relation except if the relation
+    has actually been redirected to another composite
+    """
+
+    def precommit_event(self):
+        session = self.session
+        # don't do anything if the entity is being created or deleted
+        if not (session.deleted_in_transaction(self.eid) or
+                session.added_in_transaction(self.eid)):
+            etype = session.describe(self.eid)[0]
+            session.unsafe_execute('DELETE %s X WHERE X eid %%(x)s, NOT %s'
+                                   % (etype, self.relation),
+                                   {'x': self.eid}, 'x')
+
+
+class DeleteCompositeOrphanHook(IntegrityHook):
+    """delete the composed of a composite relation when this relation is deleted
+    """
+    __regid__ = 'deletecomposite'
+    events = ('before_delete_relation',)
+
+    def __call__(self):
+        composite = self._cw.schema_rproperty(self.rtype, self.eidfrom, self.eidto,
+                                                 'composite')
+        if composite == 'subject':
+            _DelayedDeleteOp(self._cw, eid=self.eidto,
+                             relation='Y %s X' % self.rtype)
+        elif composite == 'object':
+            _DelayedDeleteOp(self._cw, eid=self.eidfrom,
+                             relation='X %s Y' % self.rtype)
+
+
+class DontRemoveOwnersGroupHook(IntegrityHook):
+    """delete the composed of a composite relation when this relation is deleted
+    """
+    __regid__ = 'checkownersgroup'
+    __select__ = IntegrityHook.__select__ & entity_implements('CWGroup')
+    events = ('before_delete_entity', 'before_update_entity')
+
+    def __call__(self):
+        if self.event == 'before_delete_entity' and self.entity.name == 'owners':
+            raise ValidationError(self.entity.eid, {None: self._cw._('can\'t be deleted')})
+        elif self.event == 'before_update_entity' and 'name' in self.entity.edited_attributes:
+            newname = self.entity.pop('name')
+            oldname = self.entity.name
+            if oldname == 'owners' and newname != oldname:
+                raise ValidationError(self.entity.eid, {'name': self._cw._('can\'t be changed')})
+            self.entity['name'] = newname
+
+
+class TidyHtmlFields(IntegrityHook):
+    """tidy HTML in rich text strings"""
+    __regid__ = 'htmltidy'
+    events = ('before_add_entity', 'before_update_entity')
+
+    def __call__(self):
+        entity = self.entity
+        metaattrs = entity.e_schema.meta_attributes()
+        for metaattr, (metadata, attr) in metaattrs.iteritems():
+            if metadata == 'format' and attr in entity.edited_attributes:
+                try:
+                    value = entity[attr]
+                except KeyError:
+                    continue # no text to tidy
+                if isinstance(value, unicode): # filter out None and Binary
+                    if getattr(entity, str(metaattr)) == 'text/html':
+                        entity[attr] = soup2xhtml(value, self._cw.encoding)
+
+
+class StripCWUserLoginHook(IntegrityHook):
+    """ensure user logins are stripped"""
+    __regid__ = 'stripuserlogin'
+    __select__ = IntegrityHook.__select__ & entity_implements('CWUser')
+    events = ('before_add_entity', 'before_update_entity',)
+
+    def __call__(self):
+        user = self.entity
+        if 'login' in user.edited_attributes and user.login:
+            user.login = user.login.strip()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/metadata.py	Wed Sep 30 18:57:42 2009 +0200
@@ -0,0 +1,162 @@
+"""Core hooks: set generic metadata
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+
+from datetime import datetime
+
+from cubicweb.selectors import entity_implements
+from cubicweb.server import hook
+from cubicweb.server.repository import FTIndexEntityOp
+
+
+def eschema_type_eid(session, etype):
+    """get eid of the CWEType entity for the given yams type"""
+    eschema = session.repo.schema.eschema(etype)
+    # eschema.eid is None if schema has been readen from the filesystem, not
+    # from the database (eg during tests)
+    if eschema.eid is None:
+        eschema.eid = session.unsafe_execute(
+            'Any X WHERE X is CWEType, X name %(name)s',
+            {'name': str(etype)})[0][0]
+    return eschema.eid
+
+
+class MetaDataHook(hook.Hook):
+    __abstract__ = True
+    category = 'metadata'
+
+
+class InitMetaAttrsHook(MetaDataHook):
+    """before create a new entity -> set creation and modification date
+
+    this is a conveniency hook, you shouldn't have to disable it
+    """
+    __regid__ = 'metaattrsinit'
+    events = ('before_add_entity',)
+
+    def __call__(self):
+        timestamp = datetime.now()
+        self.entity.setdefault('creation_date', timestamp)
+        self.entity.setdefault('modification_date', timestamp)
+        if not self._cw.get_shared_data('do-not-insert-cwuri'):
+            cwuri = u'%seid/%s' % (self._cw.base_url(), self.entity.eid)
+            self.entity.setdefault('cwuri', cwuri)
+
+
+class UpdateMetaAttrsHook(MetaDataHook):
+    """update an entity -> set modification date"""
+    __regid__ = 'metaattrsupdate'
+    events = ('before_update_entity',)
+
+    def __call__(self):
+        self.entity.setdefault('modification_date', datetime.now())
+
+
+class _SetCreatorOp(hook.Operation):
+
+    def precommit_event(self):
+        session = self.session
+        if session.deleted_in_transaction(self.entity.eid):
+            # entity have been created and deleted in the same transaction
+            return
+        if not self.entity.created_by:
+            session.add_relation(self.entity.eid, 'created_by', session.user.eid)
+
+
+class SetIsHook(MetaDataHook):
+    """create a new entity -> set is relation"""
+    __regid__ = 'setis'
+    events = ('after_add_entity',)
+
+    def __call__(self):
+        if hasattr(self.entity, '_cw_recreating'):
+            return
+        session = self._cw
+        entity = self.entity
+        try:
+            session.add_relation(entity.eid, 'is',
+                                 eschema_type_eid(session, entity.__regid__))
+        except IndexError:
+            # during schema serialization, skip
+            return
+        for etype in entity.e_schema.ancestors() + [entity.e_schema]:
+            session.add_relation(entity.eid, 'is_instance_of',
+                                 eschema_type_eid(session, etype))
+
+
+class SetOwnershipHook(MetaDataHook):
+    """create a new entity -> set owner and creator metadata"""
+    __regid__ = 'setowner'
+    events = ('after_add_entity',)
+
+    def __call__(self):
+        asession = self._cw.actual_session()
+        if not asession.is_internal_session:
+            self._cw.add_relation(self.entity.eid, 'owned_by', asession.user.eid)
+            _SetCreatorOp(asession, entity=self.entity)
+
+
+class _SyncOwnersOp(hook.Operation):
+    def precommit_event(self):
+        self.session.unsafe_execute('SET X owned_by U WHERE C owned_by U, C eid %(c)s,'
+                                    'NOT EXISTS(X owned_by U, X eid %(x)s)',
+                                    {'c': self.compositeeid, 'x': self.composedeid},
+                                    ('c', 'x'))
+
+
+class SyncCompositeOwner(MetaDataHook):
+    """when adding composite relation, the composed should have the same owners
+    has the composite
+    """
+    __regid__ = 'synccompositeowner'
+    events = ('after_add_relation',)
+
+    def __call__(self):
+        if self.rtype == 'wf_info_for':
+            # skip this special composite relation # XXX (syt) why?
+            return
+        eidfrom, eidto = self.eidfrom, self.eidto
+        composite = self._cw.schema_rproperty(self.rtype, eidfrom, eidto, 'composite')
+        if composite == 'subject':
+            _SyncOwnersOp(self._cw, compositeeid=eidfrom, composedeid=eidto)
+        elif composite == 'object':
+            _SyncOwnersOp(self._cw, compositeeid=eidto, composedeid=eidfrom)
+
+
+class FixUserOwnershipHook(MetaDataHook):
+    """when a user has been created, add owned_by relation on itself"""
+    __regid__ = 'fixuserowner'
+    __select__ = MetaDataHook.__select__ & entity_implements('CWUser')
+    events = ('after_add_entity',)
+
+    def __call__(self):
+        self._cw.add_relation(self.entity.eid, 'owned_by', self.entity.eid)
+
+
+class UpdateFTIHook(MetaDataHook):
+    """sync fulltext index when relevant relation is added / removed
+    """
+    __regid__ = 'updateftirel'
+    events = ('after_add_relation', 'after_delete_relation')
+
+    def __call__(self):
+        rtype = self.rtype
+        session = self._cw
+        if self.event == 'after_add_relation':
+            # Reindexing the contained entity is enough since it will implicitly
+            # reindex the container entity.
+            ftcontainer = session.vreg.schema.rschema(rtype).fulltext_container
+            if ftcontainer == 'subject':
+                FTIndexEntityOp(session, entity=session.entity_from_eid(self.eidto))
+            elif ftcontainer == 'object':
+                FTIndexEntityOp(session, entity=session.entity_from_eid(self.eidfrom))
+        elif session.repo.schema.rschema(rtype).fulltext_container:
+            FTIndexEntityOp(session, entity=session.entity_from_eid(self.eidto))
+            FTIndexEntityOp(session, entity=session.entity_from_eid(self.eidfrom))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/notification.py	Wed Sep 30 18:57:42 2009 +0200
@@ -0,0 +1,185 @@
+"""some hooks to handle notification on entity's changes
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from logilab.common.textutils import normalize_text
+
+from cubicweb import RegistryException
+from cubicweb.selectors import entity_implements
+from cubicweb.server import hook
+from cubicweb.sobjects.supervising import SupervisionMailOp
+
+class RenderAndSendNotificationView(hook.Operation):
+    """delay rendering of notification view until precommit"""
+    def precommit_event(self):
+        view = self.view
+        if view.cw_rset is not None and not view.cw_rset:
+            return # entity added and deleted in the same transaction (cache effect)
+        if view.cw_rset and self.session.deleted_in_transaction(view.cw_rset[view.cw_row or 0][view.cw_col or 0]):
+            return # entity added and deleted in the same transaction
+        self.view.render_and_send(**getattr(self, 'viewargs', {}))
+
+
+class NotificationHook(hook.Hook):
+    __abstract__ = True
+    category = 'notification'
+
+    def select_view(self, vid, rset, row=0, col=0):
+        return self._cw.vreg['views'].select_or_none(vid, self._cw,
+                                                     rset=rset, row=0, col=0)
+
+
+class StatusChangeHook(NotificationHook):
+    """notify when a workflowable entity has its state modified"""
+    __regid__ = 'notifystatuschange'
+    __select__ = NotificationHook.__select__ & entity_implements('TrInfo')
+    events = ('after_add_entity',)
+
+    def __call__(self):
+        entity = self.entity
+        if not entity.from_state: # not a transition
+            return
+        rset = entity.related('wf_info_for')
+        view = self.select_view('notif_status_change', rset=rset, row=0)
+        if view is None:
+            return
+        comment = entity.printable_value('comment', format='text/plain')
+        # XXX don't try to wrap rest until we've a proper transformation (see
+        # #103822)
+        if comment and entity.comment_format != 'text/rest':
+            comment = normalize_text(comment, 80)
+        RenderAndSendNotificationView(self._cw, view=view, viewargs={
+            'comment': comment, 'previous_state': entity.previous_state.name,
+            'current_state': entity.new_state.name})
+
+
+class RelationChangeHook(NotificationHook):
+    __regid__ = 'notifyrelationchange'
+    events = ('before_add_relation', 'after_add_relation',
+              'before_delete_relation', 'after_delete_relation')
+
+    def __call__(self):
+        """if a notification view is defined for the event, send notification
+        email defined by the view
+        """
+        rset = self._cw.eid_rset(self.eidfrom)
+        view = self.select_view('notif_%s_%s' % (self.event,  self.rtype),
+                                rset=rset, row=0)
+        if view is None:
+            return
+        RenderAndSendNotificationView(self._cw, view=view)
+
+
+class EntityChangeHook(NotificationHook):
+    """if a notification view is defined for the event, send notification
+    email defined by the view
+    """
+    __regid__ = 'notifyentitychange'
+    events = ('after_add_entity', 'after_update_entity')
+
+    def __call__(self):
+        rset = self.entity.as_rset()
+        view = self.select_view('notif_%s' % self.event, rset=rset, row=0)
+        if view is None:
+            return
+        RenderAndSendNotificationView(self._cw, view=view)
+
+
+class EntityUpdatedNotificationOp(hook.SingleLastOperation):
+
+    def precommit_event(self):
+        session = self.session
+        for eid in session.transaction_data['changes']:
+            view = session.vreg['views'].select('notif_entity_updated', session,
+                                                rset=session.eid_rset(eid),
+                                                row=0)
+            RenderAndSendNotificationView(session, view=view)
+
+
+class EntityUpdateHook(NotificationHook):
+    __regid__ = 'notifentityupdated'
+    __abstract__ = True # do not register by default
+
+    events = ('before_update_entity',)
+    skip_attrs = set()
+
+    def __call__(self):
+        session = self._cw
+        if self.entity.eid in session.transaction_data.get('neweids', ()):
+            return # entity is being created
+        if session.is_super_session:
+            return # ignore changes triggered by hooks
+        # then compute changes
+        changes = session.transaction_data.setdefault('changes', {})
+        thisentitychanges = changes.setdefault(self.entity.eid, set())
+        attrs = [k for k in self.entity.edited_attributes if not k in self.skip_attrs]
+        if not attrs:
+            return
+        rqlsel, rqlrestr = [], ['X eid %(x)s']
+        for i, attr in enumerate(attrs):
+            var = chr(65+i)
+            rqlsel.append(var)
+            rqlrestr.append('X %s %s' % (attr, var))
+        rql = 'Any %s WHERE %s' % (','.join(rqlsel), ','.join(rqlrestr))
+        rset = session.execute(rql, {'x': self.entity.eid}, 'x')
+        for i, attr in enumerate(attrs):
+            oldvalue = rset[0][i]
+            newvalue = self.entity[attr]
+            if oldvalue != newvalue:
+                thisentitychanges.add((attr, oldvalue, newvalue))
+        if thisentitychanges:
+            EntityUpdatedNotificationOp(session)
+
+
+# supervising ##################################################################
+
+class SomethingChangedHook(NotificationHook):
+    __regid__ = 'supervising'
+    events = ('before_add_relation', 'before_delete_relation',
+              'after_add_entity', 'before_update_entity')
+
+    def __call__(self):
+        # XXX use proper selectors
+        if self._cw.is_super_session or self._cw.repo.config.repairing:
+            return # ignore changes triggered by hooks or maintainance shell
+        dest = self._cw.vreg.config['supervising-addrs']
+        if not dest: # no supervisors, don't do this for nothing...
+            return
+        if self._call():
+            SupervisionMailOp(self._cw)
+
+    def _call(self):
+        event = self.event.split('_', 1)[1]
+        if event == 'update_entity':
+            if self._cw.added_in_transaction(self.entity.eid):
+                return False
+            if self.entity.e_schema == 'CWUser':
+                if not (self.entity.edited_attributes - frozenset(('eid', 'modification_date',
+                                                                   'last_login_time'))):
+                    # don't record last_login_time update which are done
+                    # automatically at login time
+                    return False
+        self._cw.transaction_data.setdefault('pendingchanges', []).append(
+            (event, self))
+        return True
+
+
+class EntityDeleteHook(SomethingChangedHook):
+    __regid__ = 'supervisingentitydel'
+    events = ('before_delete_entity',)
+
+    def _call(self):
+        try:
+            title = self.entity.dc_title()
+        except:
+            # may raise an error during deletion process, for instance due to
+            # missing required relation
+            title = '#%s' % eid
+        self._cw.transaction_data.setdefault('pendingchanges', []).append(
+            ('delete_entity', (self.entity.eid, str(self.entity.e_schema), title)))
+        return True
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/security.py	Wed Sep 30 18:57:42 2009 +0200
@@ -0,0 +1,130 @@
+"""Security hooks: check permissions to add/delete/update entities according to
+the user connected to a session
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb import Unauthorized
+from cubicweb.server import BEFORE_ADD_RELATIONS, ON_COMMIT_ADD_RELATIONS, hook
+
+
+def check_entity_attributes(session, entity):
+    eid = entity.eid
+    eschema = entity.e_schema
+    # ._default_set is only there on entity creation to indicate unspecified
+    # attributes which has been set to a default value defined in the schema
+    defaults = getattr(entity, '_default_set', ())
+    try:
+        editedattrs = entity.edited_attributes
+    except AttributeError:
+        editedattrs = entity
+    for attr in editedattrs:
+        if attr in defaults:
+            continue
+        rschema = eschema.subject_relation(attr)
+        if rschema.is_final(): # non final relation are checked by other hooks
+            # add/delete should be equivalent (XXX: unify them into 'update' ?)
+            rschema.check_perm(session, 'add', eid)
+
+
+class _CheckEntityPermissionOp(hook.LateOperation):
+    def precommit_event(self):
+        #print 'CheckEntityPermissionOp', self.session.user, self.entity, self.action
+        self.entity.check_perm(self.action)
+        check_entity_attributes(self.session, self.entity)
+
+    def commit_event(self):
+        pass
+
+
+class _CheckRelationPermissionOp(hook.LateOperation):
+    def precommit_event(self):
+        self.rschema.check_perm(self.session, self.action, self.eidfrom, self.eidto)
+
+    def commit_event(self):
+        pass
+
+
+class SecurityHook(hook.Hook):
+    __abstract__ = True
+    category = 'security'
+    __select__ = hook.Hook.__select__ & hook.regular_session()
+
+
+class AfterAddEntitySecurityHook(SecurityHook):
+    __regid__ = 'securityafteraddentity'
+    events = ('after_add_entity',)
+
+    def __call__(self):
+        _CheckEntityPermissionOp(self._cw, entity=self.entity, action='add')
+
+
+class AfterUpdateEntitySecurityHook(SecurityHook):
+    __regid__ = 'securityafterupdateentity'
+    events = ('after_update_entity',)
+
+    def __call__(self):
+        try:
+            # check user has permission right now, if not retry at commit time
+            self.entity.check_perm('update')
+            check_entity_attributes(self._cw, self.entity)
+        except Unauthorized:
+            self.entity.clear_local_perm_cache('update')
+            _CheckEntityPermissionOp(self._cw, entity=self.entity, action='update')
+
+
+class BeforeDelEntitySecurityHook(SecurityHook):
+    __regid__ = 'securitybeforedelentity'
+    events = ('before_delete_entity',)
+
+    def __call__(self):
+        self.entity.check_perm('delete')
+
+
+class BeforeAddRelationSecurityHook(SecurityHook):
+    __regid__ = 'securitybeforeaddrelation'
+    events = ('before_add_relation',)
+
+    def __call__(self):
+        if self.rtype in BEFORE_ADD_RELATIONS:
+            nocheck = self._cw.transaction_data.get('skip-security', ())
+            if (self.eidfrom, self.rtype, self.eidto) in nocheck:
+                return
+            rschema = self._cw.repo.schema[self.rtype]
+            rschema.check_perm(self._cw, 'add', self.eidfrom, self.eidto)
+
+
+class AfterAddRelationSecurityHook(SecurityHook):
+    __regid__ = 'securityafteraddrelation'
+    events = ('after_add_relation',)
+
+    def __call__(self):
+        if not self.rtype in BEFORE_ADD_RELATIONS:
+            nocheck = self._cw.transaction_data.get('skip-security', ())
+            if (self.eidfrom, self.rtype, self.eidto) in nocheck:
+                return
+            rschema = self._cw.repo.schema[self.rtype]
+            if self.rtype in ON_COMMIT_ADD_RELATIONS:
+                _CheckRelationPermissionOp(self._cw, action='add',
+                                           rschema=rschema,
+                                           eidfrom=self.eidfrom,
+                                           eidto=self.eidto)
+            else:
+                rschema.check_perm(self._cw, 'add', self.eidfrom, self.eidto)
+
+
+class BeforeDelRelationSecurityHook(SecurityHook):
+    __regid__ = 'securitybeforedelrelation'
+    events = ('before_delete_relation',)
+
+    def __call__(self):
+        nocheck = self._cw.transaction_data.get('skip-security', ())
+        if (self.eidfrom, self.rtype, self.eidto) in nocheck:
+            return
+        self._cw.repo.schema[self.rtype].check_perm(self._cw, 'delete',
+                                                       self.eidfrom, self.eidto)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/syncschema.py	Wed Sep 30 18:57:42 2009 +0200
@@ -0,0 +1,1153 @@
+"""schema hooks:
+
+- synchronize the living schema object with the persistent schema
+- perform physical update on the source when necessary
+
+checking for schema consistency is done in hooks.py
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from yams.schema import BASE_TYPES
+from yams.buildobjs import EntityType, RelationType, RelationDefinition
+from yams.schema2sql import eschema2sql, rschema2sql, type_from_constraints
+
+from cubicweb import ValidationError, RepositoryError
+from cubicweb.selectors import entity_implements
+from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, CONSTRAINTS
+from cubicweb.server import hook, schemaserial as ss
+from cubicweb.server.sqlutils import SQL_PREFIX
+
+
+TYPE_CONVERTER = { # XXX
+    'Boolean': bool,
+    'Int': int,
+    'Float': float,
+    'Password': str,
+    'String': unicode,
+    'Date' : unicode,
+    'Datetime' : unicode,
+    'Time' : unicode,
+    }
+
+# core entity and relation types which can't be removed
+CORE_ETYPES = list(BASE_TYPES) + ['CWEType', 'CWRType', 'CWUser', 'CWGroup',
+                                  'CWConstraint', 'CWAttribute', 'CWRelation']
+CORE_RTYPES = ['eid', 'creation_date', 'modification_date', 'cwuri',
+               'login', 'upassword', 'name',
+               'is', 'instanceof', 'owned_by', 'created_by', 'in_group',
+               'relation_type', 'from_entity', 'to_entity',
+               'constrainted_by',
+               'read_permission', 'add_permission',
+               'delete_permission', 'updated_permission',
+               ]
+
+def get_constraints(session, entity):
+    constraints = []
+    for cstreid in session.transaction_data.get(entity.eid, ()):
+        cstrent = session.entity_from_eid(cstreid)
+        cstr = CONSTRAINTS[cstrent.type].deserialize(cstrent.value)
+        cstr.eid = cstreid
+        constraints.append(cstr)
+    return constraints
+
+def add_inline_relation_column(session, etype, rtype):
+    """add necessary column and index for an inlined relation"""
+    table = SQL_PREFIX + etype
+    column = SQL_PREFIX + rtype
+    try:
+        session.system_sql(str('ALTER TABLE %s ADD COLUMN %s integer'
+                               % (table, column)), rollback_on_failure=False)
+        session.info('added column %s to table %s', column, table)
+    except:
+        # silent exception here, if this error has not been raised because the
+        # column already exists, index creation will fail anyway
+        session.exception('error while adding column %s to table %s',
+                          table, column)
+    # create index before alter table which may expectingly fail during test
+    # (sqlite) while index creation should never fail (test for index existence
+    # is done by the dbhelper)
+    session.pool.source('system').create_index(session, table, column)
+    session.info('added index on %s(%s)', table, column)
+    session.transaction_data.setdefault('createdattrs', []).append(
+        '%s.%s' % (etype, rtype))
+
+def check_valid_changes(session, entity, ro_attrs=('name', 'final')):
+    errors = {}
+    # don't use getattr(entity, attr), we would get the modified value if any
+    for attr in entity.edited_attributes:
+        if attr in ro_attrs:
+            newval = entity.pop(attr)
+            origval = getattr(entity, attr)
+            if newval != origval:
+                errors[attr] = session._("can't change the %s attribute") % \
+                               display_name(session, attr)
+            entity[attr] = newval
+    if errors:
+        raise ValidationError(entity.eid, errors)
+
+
+# operations for low-level database alteration  ################################
+
+class DropTable(hook.Operation):
+    """actually remove a database from the instance's schema"""
+    table = None # make pylint happy
+    def precommit_event(self):
+        dropped = self.session.transaction_data.setdefault('droppedtables',
+                                                           set())
+        if self.table in dropped:
+            return # already processed
+        dropped.add(self.table)
+        self.session.system_sql('DROP TABLE %s' % self.table)
+        self.info('dropped table %s', self.table)
+
+
+class DropRelationTable(DropTable):
+    def __init__(self, session, rtype):
+        super(DropRelationTable, self).__init__(
+            session, table='%s_relation' % rtype)
+        session.transaction_data.setdefault('pendingrtypes', set()).add(rtype)
+
+
+class DropColumn(hook.Operation):
+    """actually remove the attribut's column from entity table in the system
+    database
+    """
+    table = column = None # make pylint happy
+    def precommit_event(self):
+        session, table, column = self.session, self.table, self.column
+        # drop index if any
+        session.pool.source('system').drop_index(session, table, column)
+        try:
+            session.system_sql('ALTER TABLE %s DROP COLUMN %s'
+                               % (table, column), rollback_on_failure=False)
+            self.info('dropped column %s from table %s', column, table)
+        except Exception, ex:
+            # not supported by sqlite for instance
+            self.error('error while altering table %s: %s', table, ex)
+
+
+# base operations for in-memory schema synchronization  ########################
+
+class MemSchemaNotifyChanges(hook.SingleLastOperation):
+    """the update schema operation:
+
+    special operation which should be called once and after all other schema
+    operations. It will trigger internal structures rebuilding to consider
+    schema changes.
+    """
+
+    def __init__(self, session):
+        hook.SingleLastOperation.__init__(self, session)
+
+    def commit_event(self):
+        rebuildinfered = self.session.data.get('rebuild-infered', True)
+        repo = self.session.repo
+        repo.set_schema(repo.schema, rebuildinfered=rebuildinfered)
+
+
+class MemSchemaOperation(hook.Operation):
+    """base class for schema operations"""
+    def __init__(self, session, kobj=None, **kwargs):
+        self.kobj = kobj
+        # once Operation.__init__ has been called, event may be triggered, so
+        # do this last !
+        hook.Operation.__init__(self, session, **kwargs)
+        # every schema operation is triggering a schema update
+        MemSchemaNotifyChanges(session)
+
+    def prepare_constraints(self, subjtype, rtype, objtype):
+        constraints = rtype.rproperty(subjtype, objtype, 'constraints')
+        self.constraints = list(constraints)
+        rtype.set_rproperty(subjtype, objtype, 'constraints', self.constraints)
+
+
+class MemSchemaEarlyOperation(MemSchemaOperation):
+    def insert_index(self):
+        """schema operation which are inserted at the begining of the queue
+        (typically to add/remove entity or relation types)
+        """
+        i = -1
+        for i, op in enumerate(self.session.pending_operations):
+            if not isinstance(op, MemSchemaEarlyOperation):
+                return i
+        return i + 1
+
+
+class MemSchemaPermOperation(MemSchemaOperation):
+    """base class to synchronize schema permission definitions"""
+    def __init__(self, session, perm, etype_eid):
+        self.perm = perm
+        try:
+            self.name = session.entity_from_eid(etype_eid).name
+        except IndexError:
+            self.error('changing permission of a no more existant type #%s',
+                etype_eid)
+        else:
+            hook.Operation.__init__(self, session)
+
+
+# operations for high-level source database alteration  ########################
+
+class SourceDbCWETypeRename(hook.Operation):
+    """this operation updates physical storage accordingly"""
+    oldname = newname = None # make pylint happy
+
+    def precommit_event(self):
+        # we need sql to operate physical changes on the system database
+        sqlexec = self.session.system_sql
+        sqlexec('ALTER TABLE %s%s RENAME TO %s%s' % (SQL_PREFIX, self.oldname,
+                                                     SQL_PREFIX, self.newname))
+        self.info('renamed table %s to %s', self.oldname, self.newname)
+        sqlexec('UPDATE entities SET type=%s WHERE type=%s',
+                (self.newname, self.oldname))
+        sqlexec('UPDATE deleted_entities SET type=%s WHERE type=%s',
+                (self.newname, self.oldname))
+
+
+class SourceDbCWRTypeUpdate(hook.Operation):
+    """actually update some properties of a relation definition"""
+    rschema = values = entity = None # make pylint happy
+
+    def precommit_event(self):
+        session = self.session
+        rschema = self.rschema
+        if rschema.is_final() or not 'inlined' in self.values:
+            return # nothing to do
+        inlined = self.values['inlined']
+        entity = self.entity
+        # check in-lining is necessary / possible
+        if not entity.inlined_changed(inlined):
+            return # nothing to do
+        # inlined changed, make necessary physical changes!
+        sqlexec = self.session.system_sql
+        rtype = rschema.type
+        eidcolumn = SQL_PREFIX + 'eid'
+        if not inlined:
+            # need to create the relation if it has not been already done by
+            # another event of the same transaction
+            if not rschema.type in session.transaction_data.get('createdtables', ()):
+                tablesql = rschema2sql(rschema)
+                # create the necessary table
+                for sql in tablesql.split(';'):
+                    if sql.strip():
+                        sqlexec(sql)
+                session.transaction_data.setdefault('createdtables', []).append(
+                    rschema.type)
+            # copy existant data
+            column = SQL_PREFIX + rtype
+            for etype in rschema.subjects():
+                table = SQL_PREFIX + str(etype)
+                sqlexec('INSERT INTO %s_relation SELECT %s, %s FROM %s WHERE NOT %s IS NULL'
+                        % (rtype, eidcolumn, column, table, column))
+            # drop existant columns
+            for etype in rschema.subjects():
+                DropColumn(session, table=SQL_PREFIX + str(etype),
+                             column=SQL_PREFIX + rtype)
+        else:
+            for etype in rschema.subjects():
+                try:
+                    add_inline_relation_column(session, str(etype), rtype)
+                except Exception, ex:
+                    # the column probably already exists. this occurs when the
+                    # entity's type has just been added or if the column has not
+                    # been previously dropped
+                    self.error('error while altering table %s: %s', etype, ex)
+                # copy existant data.
+                # XXX don't use, it's not supported by sqlite (at least at when i tried it)
+                #sqlexec('UPDATE %(etype)s SET %(rtype)s=eid_to '
+                #        'FROM %(rtype)s_relation '
+                #        'WHERE %(etype)s.eid=%(rtype)s_relation.eid_from'
+                #        % locals())
+                table = SQL_PREFIX + str(etype)
+                cursor = sqlexec('SELECT eid_from, eid_to FROM %(table)s, '
+                                 '%(rtype)s_relation WHERE %(table)s.%(eidcolumn)s='
+                                 '%(rtype)s_relation.eid_from' % locals())
+                args = [{'val': eid_to, 'x': eid} for eid, eid_to in cursor.fetchall()]
+                if args:
+                    column = SQL_PREFIX + rtype
+                    cursor.executemany('UPDATE %s SET %s=%%(val)s WHERE %s=%%(x)s'
+                                       % (table, column, eidcolumn), args)
+                # drop existant table
+                DropRelationTable(session, rtype)
+
+
+class SourceDbCWAttributeAdd(hook.Operation):
+    """an attribute relation (CWAttribute) has been added:
+    * add the necessary column
+    * set default on this column if any and possible
+    * register an operation to add the relation definition to the
+      instance's schema on commit
+
+    constraints are handled by specific hooks
+    """
+    entity = None # make pylint happy
+
+    def init_rdef(self, **kwargs):
+        entity = self.entity
+        fromentity = entity.stype
+        self.session.execute('SET X ordernum Y+1 '
+                             'WHERE X from_entity SE, SE eid %(se)s, X ordernum Y, '
+                             'X ordernum >= %(order)s, NOT X eid %(x)s',
+                             {'x': entity.eid, 'se': fromentity.eid,
+                              'order': entity.ordernum or 0})
+        subj = str(fromentity.name)
+        rtype = entity.rtype.name
+        obj = str(entity.otype.name)
+        constraints = get_constraints(self.session, entity)
+        rdef = RelationDefinition(subj, rtype, obj,
+                                  description=entity.description,
+                                  cardinality=entity.cardinality,
+                                  constraints=constraints,
+                                  order=entity.ordernum,
+                                  eid=entity.eid,
+                                  **kwargs)
+        MemSchemaRDefAdd(self.session, rdef)
+        return rdef
+
+    def precommit_event(self):
+        session = self.session
+        entity = self.entity
+        # entity.defaultval is a string or None, but we need a correctly typed
+        # value
+        default = entity.defaultval
+        if default is not None:
+            default = TYPE_CONVERTER[entity.otype.name](default)
+        props = {'default': default,
+                 'indexed': entity.indexed,
+                 'fulltextindexed': entity.fulltextindexed,
+                 'internationalizable': entity.internationalizable}
+        rdef = self.init_rdef(**props)
+        sysource = session.pool.source('system')
+        attrtype = type_from_constraints(sysource.dbhelper, rdef.object,
+                                         rdef.constraints)
+        # XXX should be moved somehow into lgc.adbh: sqlite doesn't support to
+        # add a new column with UNIQUE, it should be added after the ALTER TABLE
+        # using ADD INDEX
+        if sysource.dbdriver == 'sqlite' and 'UNIQUE' in attrtype:
+            extra_unique_index = True
+            attrtype = attrtype.replace(' UNIQUE', '')
+        else:
+            extra_unique_index = False
+        # added some str() wrapping query since some backend (eg psycopg) don't
+        # allow unicode queries
+        table = SQL_PREFIX + rdef.subject
+        column = SQL_PREFIX + rdef.name
+        try:
+            session.system_sql(str('ALTER TABLE %s ADD COLUMN %s %s'
+                                   % (table, column, attrtype)),
+                               rollback_on_failure=False)
+            self.info('added column %s to table %s', table, column)
+        except Exception, ex:
+            # the column probably already exists. this occurs when
+            # the entity's type has just been added or if the column
+            # has not been previously dropped
+            self.error('error while altering table %s: %s', table, ex)
+        if extra_unique_index or entity.indexed:
+            try:
+                sysource.create_index(session, table, column,
+                                      unique=extra_unique_index)
+            except Exception, ex:
+                self.error('error while creating index for %s.%s: %s',
+                           table, column, ex)
+        # final relations are not infered, propagate
+        try:
+            eschema = self.schema.eschema(rdef.subject)
+        except KeyError:
+            return # entity type currently being added
+        rschema = self.schema.rschema(rdef.name)
+        props.update({'constraints': rdef.constraints,
+                      'description': rdef.description,
+                      'cardinality': rdef.cardinality,
+                      'constraints': rdef.constraints,
+                      'order': rdef.order})
+        for specialization in eschema.specialized_by(False):
+            if rschema.has_rdef(specialization, rdef.object):
+                continue
+            for rql, args in ss.frdef2rql(rschema, str(specialization),
+                                          rdef.object, props):
+                session.execute(rql, args)
+
+
+class SourceDbCWRelationAdd(SourceDbCWAttributeAdd):
+    """an actual relation has been added:
+    * if this is an inlined relation, add the necessary column
+      else if it's the first instance of this relation type, add the
+      necessary table and set default permissions
+    * register an operation to add the relation definition to the
+      instance's schema on commit
+
+    constraints are handled by specific hooks
+    """
+    entity = None # make pylint happy
+
+    def precommit_event(self):
+        session = self.session
+        entity = self.entity
+        rdef = self.init_rdef(composite=entity.composite)
+        schema = session.vreg.schema
+        rtype = rdef.name
+        rschema = session.vreg.schema.rschema(rtype)
+        # this have to be done before permissions setting
+        if rschema.inlined:
+            # need to add a column if the relation is inlined and if this is the
+            # first occurence of "Subject relation Something" whatever Something
+            # and if it has not been added during other event of the same
+            # transaction
+            key = '%s.%s' % (rdef.subject, rtype)
+            try:
+                alreadythere = bool(rschema.objects(rdef.subject))
+            except KeyError:
+                alreadythere = False
+            if not (alreadythere or
+                    key in session.transaction_data.get('createdattrs', ())):
+                add_inline_relation_column(session, rdef.subject, rtype)
+        else:
+            # need to create the relation if no relation definition in the
+            # schema and if it has not been added during other event of the same
+            # transaction
+            if not (rschema.subjects() or
+                    rtype in session.transaction_data.get('createdtables', ())):
+                try:
+                    rschema = session.vreg.schema.rschema(rtype)
+                    tablesql = rschema2sql(rschema)
+                except KeyError:
+                    # fake we add it to the schema now to get a correctly
+                    # initialized schema but remove it before doing anything
+                    # more dangerous...
+                    rschema = session.vreg.schema.add_relation_type(rdef)
+                    tablesql = rschema2sql(rschema)
+                    session.vreg.schema.del_relation_type(rtype)
+                # create the necessary table
+                for sql in tablesql.split(';'):
+                    if sql.strip():
+                        session.system_sql(sql)
+                session.transaction_data.setdefault('createdtables', []).append(
+                    rtype)
+
+
+class SourceDbRDefUpdate(hook.Operation):
+    """actually update some properties of a relation definition"""
+    rschema = values = None # make pylint happy
+
+    def precommit_event(self):
+        etype = self.kobj[0]
+        table = SQL_PREFIX + etype
+        column = SQL_PREFIX + self.rschema.type
+        if 'indexed' in self.values:
+            sysource = self.session.pool.source('system')
+            if self.values['indexed']:
+                sysource.create_index(self.session, table, column)
+            else:
+                sysource.drop_index(self.session, table, column)
+        if 'cardinality' in self.values and self.rschema.is_final():
+            adbh = self.session.pool.source('system').dbhelper
+            if not adbh.alter_column_support:
+                # not supported (and NOT NULL not set by yams in that case, so
+                # no worry)
+                return
+            atype = self.rschema.objects(etype)[0]
+            constraints = self.rschema.rproperty(etype, atype, 'constraints')
+            coltype = type_from_constraints(adbh, atype, constraints,
+                                            creating=False)
+            # XXX check self.values['cardinality'][0] actually changed?
+            sql = adbh.sql_set_null_allowed(table, column, coltype,
+                                            self.values['cardinality'][0] != '1')
+            self.session.system_sql(sql)
+
+
+class SourceDbCWConstraintAdd(hook.Operation):
+    """actually update constraint of a relation definition"""
+    entity = None # make pylint happy
+    cancelled = False
+
+    def precommit_event(self):
+        rdef = self.entity.reverse_constrained_by[0]
+        session = self.session
+        # when the relation is added in the same transaction, the constraint
+        # object is created by the operation adding the attribute or relation,
+        # so there is nothing to do here
+        if session.added_in_transaction(rdef.eid):
+            return
+        subjtype, rtype, objtype = session.vreg.schema.schema_by_eid(rdef.eid)
+        cstrtype = self.entity.type
+        oldcstr = rtype.constraint_by_type(subjtype, objtype, cstrtype)
+        newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
+        table = SQL_PREFIX + str(subjtype)
+        column = SQL_PREFIX + str(rtype)
+        # alter the physical schema on size constraint changes
+        if newcstr.type() == 'SizeConstraint' and (
+            oldcstr is None or oldcstr.max != newcstr.max):
+            adbh = self.session.pool.source('system').dbhelper
+            card = rtype.rproperty(subjtype, objtype, 'cardinality')
+            coltype = type_from_constraints(adbh, objtype, [newcstr],
+                                            creating=False)
+            sql = adbh.sql_change_col_type(table, column, coltype, card != '1')
+            try:
+                session.system_sql(sql, rollback_on_failure=False)
+                self.info('altered column %s of table %s: now VARCHAR(%s)',
+                          column, table, newcstr.max)
+            except Exception, ex:
+                # not supported by sqlite for instance
+                self.error('error while altering table %s: %s', table, ex)
+        elif cstrtype == 'UniqueConstraint' and oldcstr is None:
+            session.pool.source('system').create_index(
+                self.session, table, column, unique=True)
+
+
+class SourceDbCWConstraintDel(hook.Operation):
+    """actually remove a constraint of a relation definition"""
+    rtype = subjtype = objtype = None # make pylint happy
+
+    def precommit_event(self):
+        cstrtype = self.cstr.type()
+        table = SQL_PREFIX + str(self.subjtype)
+        column = SQL_PREFIX + str(self.rtype)
+        # alter the physical schema on size/unique constraint changes
+        if cstrtype == 'SizeConstraint':
+            try:
+                self.session.system_sql('ALTER TABLE %s ALTER COLUMN %s TYPE TEXT'
+                                        % (table, column),
+                                        rollback_on_failure=False)
+                self.info('altered column %s of table %s: now TEXT',
+                          column, table)
+            except Exception, ex:
+                # not supported by sqlite for instance
+                self.error('error while altering table %s: %s', table, ex)
+        elif cstrtype == 'UniqueConstraint':
+            self.session.pool.source('system').drop_index(
+                self.session, table, column, unique=True)
+
+
+# operations for in-memory schema synchronization  #############################
+
+class MemSchemaCWETypeAdd(MemSchemaEarlyOperation):
+    """actually add the entity type to the instance's schema"""
+    eid = None # make pylint happy
+    def commit_event(self):
+        self.session.vreg.schema.add_entity_type(self.kobj)
+
+
+class MemSchemaCWETypeRename(MemSchemaOperation):
+    """this operation updates physical storage accordingly"""
+    oldname = newname = None # make pylint happy
+
+    def commit_event(self):
+        self.session.vreg.schema.rename_entity_type(self.oldname, self.newname)
+
+
+class MemSchemaCWETypeDel(MemSchemaOperation):
+    """actually remove the entity type from the instance's schema"""
+    def commit_event(self):
+        try:
+            # del_entity_type also removes entity's relations
+            self.session.vreg.schema.del_entity_type(self.kobj)
+        except KeyError:
+            # s/o entity type have already been deleted
+            pass
+
+
+class MemSchemaCWRTypeAdd(MemSchemaEarlyOperation):
+    """actually add the relation type to the instance's schema"""
+    eid = None # make pylint happy
+    def commit_event(self):
+        rschema = self.session.vreg.schema.add_relation_type(self.kobj)
+        rschema.set_default_groups()
+
+
+class MemSchemaCWRTypeUpdate(MemSchemaOperation):
+    """actually update some properties of a relation definition"""
+    rschema = values = None # make pylint happy
+
+    def commit_event(self):
+        # structure should be clean, not need to remove entity's relations
+        # at this point
+        self.rschema.__dict__.update(self.values)
+
+
+class MemSchemaCWRTypeDel(MemSchemaOperation):
+    """actually remove the relation type from the instance's schema"""
+    def commit_event(self):
+        try:
+            self.session.vreg.schema.del_relation_type(self.kobj)
+        except KeyError:
+            # s/o entity type have already been deleted
+            pass
+
+
+class MemSchemaRDefAdd(MemSchemaEarlyOperation):
+    """actually add the attribute relation definition to the instance's
+    schema
+    """
+    def commit_event(self):
+        self.session.vreg.schema.add_relation_def(self.kobj)
+
+
+class MemSchemaRDefUpdate(MemSchemaOperation):
+    """actually update some properties of a relation definition"""
+    rschema = values = None # make pylint happy
+
+    def commit_event(self):
+        # structure should be clean, not need to remove entity's relations
+        # at this point
+        self.rschema._rproperties[self.kobj].update(self.values)
+
+
+class MemSchemaRDefDel(MemSchemaOperation):
+    """actually remove the relation definition from the instance's schema"""
+    def commit_event(self):
+        subjtype, rtype, objtype = self.kobj
+        try:
+            self.session.vreg.schema.del_relation_def(subjtype, rtype, objtype)
+        except KeyError:
+            # relation type may have been already deleted
+            pass
+
+
+class MemSchemaCWConstraintAdd(MemSchemaOperation):
+    """actually update constraint of a relation definition
+
+    has to be called before SourceDbCWConstraintAdd
+    """
+    cancelled = False
+
+    def precommit_event(self):
+        rdef = self.entity.reverse_constrained_by[0]
+        # when the relation is added in the same transaction, the constraint
+        # object is created by the operation adding the attribute or relation,
+        # so there is nothing to do here
+        if self.session.added_in_transaction(rdef.eid):
+            self.cancelled = True
+            return
+        subjtype, rtype, objtype = self.session.vreg.schema.schema_by_eid(rdef.eid)
+        self.prepare_constraints(subjtype, rtype, objtype)
+        cstrtype = self.entity.type
+        self.cstr = rtype.constraint_by_type(subjtype, objtype, cstrtype)
+        self.newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
+        self.newcstr.eid = self.entity.eid
+
+    def commit_event(self):
+        if self.cancelled:
+            return
+        # in-place modification
+        if not self.cstr is None:
+            self.constraints.remove(self.cstr)
+        self.constraints.append(self.newcstr)
+
+
+class MemSchemaCWConstraintDel(MemSchemaOperation):
+    """actually remove a constraint of a relation definition
+
+    has to be called before SourceDbCWConstraintDel
+    """
+    rtype = subjtype = objtype = None # make pylint happy
+    def precommit_event(self):
+        self.prepare_constraints(self.subjtype, self.rtype, self.objtype)
+
+    def commit_event(self):
+        self.constraints.remove(self.cstr)
+
+
+class MemSchemaPermCWGroupAdd(MemSchemaPermOperation):
+    """synchronize schema when a *_permission relation has been added on a group
+    """
+    def __init__(self, session, perm, etype_eid, group_eid):
+        self.group = session.entity_from_eid(group_eid).name
+        super(MemSchemaPermCWGroupAdd, self).__init__(
+            session, perm, etype_eid)
+
+    def commit_event(self):
+        """the observed connections pool has been commited"""
+        try:
+            erschema = self.session.vreg.schema[self.name]
+        except KeyError:
+            # duh, schema not found, log error and skip operation
+            self.error('no schema for %s', self.name)
+            return
+        groups = list(erschema.get_groups(self.perm))
+        try:
+            groups.index(self.group)
+            self.warning('group %s already have permission %s on %s',
+                         self.group, self.perm, erschema.type)
+        except ValueError:
+            groups.append(self.group)
+            erschema.set_groups(self.perm, groups)
+
+
+class MemSchemaPermCWGroupDel(MemSchemaPermCWGroupAdd):
+    """synchronize schema when a *_permission relation has been deleted from a
+    group
+    """
+
+    def commit_event(self):
+        """the observed connections pool has been commited"""
+        try:
+            erschema = self.session.vreg.schema[self.name]
+        except KeyError:
+            # duh, schema not found, log error and skip operation
+            self.error('no schema for %s', self.name)
+            return
+        groups = list(erschema.get_groups(self.perm))
+        try:
+            groups.remove(self.group)
+            erschema.set_groups(self.perm, groups)
+        except ValueError:
+            self.error('can\'t remove permission %s on %s to group %s',
+                self.perm, erschema.type, self.group)
+
+
+class MemSchemaPermRQLExpressionAdd(MemSchemaPermOperation):
+    """synchronize schema when a *_permission relation has been added on a rql
+    expression
+    """
+    def __init__(self, session, perm, etype_eid, expression):
+        self.expr = expression
+        super(MemSchemaPermRQLExpressionAdd, self).__init__(
+            session, perm, etype_eid)
+
+    def commit_event(self):
+        """the observed connections pool has been commited"""
+        try:
+            erschema = self.session.vreg.schema[self.name]
+        except KeyError:
+            # duh, schema not found, log error and skip operation
+            self.error('no schema for %s', self.name)
+            return
+        exprs = list(erschema.get_rqlexprs(self.perm))
+        exprs.append(erschema.rql_expression(self.expr))
+        erschema.set_rqlexprs(self.perm, exprs)
+
+
+class MemSchemaPermRQLExpressionDel(MemSchemaPermRQLExpressionAdd):
+    """synchronize schema when a *_permission relation has been deleted from an
+    rql expression
+    """
+
+    def commit_event(self):
+        """the observed connections pool has been commited"""
+        try:
+            erschema = self.session.vreg.schema[self.name]
+        except KeyError:
+            # duh, schema not found, log error and skip operation
+            self.error('no schema for %s', self.name)
+            return
+        rqlexprs = list(erschema.get_rqlexprs(self.perm))
+        for i, rqlexpr in enumerate(rqlexprs):
+            if rqlexpr.expression == self.expr:
+                rqlexprs.pop(i)
+                break
+        else:
+            self.error('can\'t remove permission %s on %s for expression %s',
+                self.perm, erschema.type, self.expr)
+            return
+        erschema.set_rqlexprs(self.perm, rqlexprs)
+
+
+class MemSchemaSpecializesAdd(MemSchemaOperation):
+
+    def commit_event(self):
+        eschema = self.session.vreg.schema.schema_by_eid(self.etypeeid)
+        parenteschema = self.session.vreg.schema.schema_by_eid(self.parentetypeeid)
+        eschema._specialized_type = parenteschema.type
+        parenteschema._specialized_by.append(eschema.type)
+
+
+class MemSchemaSpecializesDel(MemSchemaOperation):
+
+    def commit_event(self):
+        try:
+            eschema = self.session.vreg.schema.schema_by_eid(self.etypeeid)
+            parenteschema = self.session.vreg.schema.schema_by_eid(self.parentetypeeid)
+        except KeyError:
+            # etype removed, nothing to do
+            return
+        eschema._specialized_type = None
+        parenteschema._specialized_by.remove(eschema.type)
+
+
+class SyncSchemaHook(hook.Hook):
+    __abstract__ = True
+    category = 'syncschema'
+
+
+# CWEType hooks ################################################################
+
+class DelCWETypeHook(SyncSchemaHook):
+    """before deleting a CWEType entity:
+    * check that we don't remove a core entity type
+    * cascade to delete related CWAttribute and CWRelation entities
+    * instantiate an operation to delete the entity type on commit
+    """
+    __regid__ = 'syncdelcwetype'
+    __select__ = SyncSchemaHook.__select__ & entity_implements('CWEType')
+    events = ('before_delete_entity',)
+
+    def __call__(self):
+        # final entities can't be deleted, don't care about that
+        name = self.entity.name
+        if name in CORE_ETYPES:
+            raise ValidationError(self.entity.eid, {None: self._cw._('can\'t be deleted')})
+        # delete every entities of this type
+        self._cw.unsafe_execute('DELETE %s X' % name)
+        DropTable(self._cw, table=SQL_PREFIX + name)
+        MemSchemaCWETypeDel(self._cw, name)
+
+
+class AfterDelCWETypeHook(DelCWETypeHook):
+    __regid__ = 'wfcleanup'
+    events = ('after_delete_entity',)
+
+    def __call__(self):
+        # workflow cleanup
+        self._cw.execute('DELETE Workflow X WHERE NOT X workflow_of Y')
+
+
+class AfterAddCWETypeHook(DelCWETypeHook):
+    """after adding a CWEType entity:
+    * create the necessary table
+    * set creation_date and modification_date by creating the necessary
+      CWAttribute entities
+    * add owned_by relation by creating the necessary CWRelation entity
+    * register an operation to add the entity type to the instance's
+      schema on commit
+    """
+    __regid__ = 'syncaddcwetype'
+    events = ('after_add_entity',)
+
+    def __call__(self):
+        entity = self.entity
+        if entity.get('final'):
+            return
+        schema = self._cw.vreg.schema
+        name = entity['name']
+        etype = EntityType(name=name, description=entity.get('description'),
+                           meta=entity.get('meta')) # don't care about final
+        # fake we add it to the schema now to get a correctly initialized schema
+        # but remove it before doing anything more dangerous...
+        schema = self._cw.vreg.schema
+        eschema = schema.add_entity_type(etype)
+        eschema.set_default_groups()
+        # generate table sql and rql to add metadata
+        tablesql = eschema2sql(self._cw.pool.source('system').dbhelper, eschema,
+                               prefix=SQL_PREFIX)
+        relrqls = []
+        for rtype in (META_RTYPES - VIRTUAL_RTYPES):
+            rschema = schema[rtype]
+            sampletype = rschema.subjects()[0]
+            desttype = rschema.objects()[0]
+            props = rschema.rproperties(sampletype, desttype)
+            relrqls += list(ss.rdef2rql(rschema, name, desttype, props))
+        # now remove it !
+        schema.del_entity_type(name)
+        # create the necessary table
+        for sql in tablesql.split(';'):
+            if sql.strip():
+                self._cw.system_sql(sql)
+        # register operation to modify the schema on commit
+        # this have to be done before adding other relations definitions
+        # or permission settings
+        etype.eid = entity.eid
+        MemSchemaCWETypeAdd(self._cw, etype)
+        # add meta relations
+        for rql, kwargs in relrqls:
+            self._cw.execute(rql, kwargs)
+
+
+class BeforeUpdateCWETypeHook(DelCWETypeHook):
+    """check name change, handle final"""
+    __regid__ = 'syncupdatecwetype'
+    events = ('before_update_entity',)
+
+    def __call__(self):
+        entity = self.entity
+        check_valid_changes(self._cw, entity, ro_attrs=('final',))
+        # don't use getattr(entity, attr), we would get the modified value if any
+        if 'name' in entity.edited_attributes:
+            newname = entity.pop('name')
+            oldname = entity.name
+            if newname.lower() != oldname.lower():
+                SourceDbCWETypeRename(self._cw, oldname=oldname, newname=newname)
+                MemSchemaCWETypeRename(self._cw, oldname=oldname, newname=newname)
+            entity['name'] = newname
+
+
+# CWRType hooks ################################################################
+
+class DelCWRTypeHook(SyncSchemaHook):
+    """before deleting a CWRType entity:
+    * check that we don't remove a core relation type
+    * cascade to delete related CWAttribute and CWRelation entities
+    * instantiate an operation to delete the relation type on commit
+    """
+    __regid__ = 'syncdelcwrtype'
+    __select__ = SyncSchemaHook.__select__ & entity_implements('CWRType')
+    events = ('before_delete_entity',)
+
+    def __call__(self):
+        name = self.entity.name
+        if name in CORE_RTYPES:
+            raise ValidationError(self.entity.eid, {None: self._cw._('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})
+        self._cw.execute('DELETE CWRelation X WHERE X relation_type Y, Y eid %(x)s',
+                        {'x': self.entity.eid})
+        MemSchemaCWRTypeDel(self._cw, name)
+
+
+class AfterAddCWRTypeHook(DelCWRTypeHook):
+    """after a CWRType entity has been added:
+    * register an operation to add the relation type to the instance's
+      schema on commit
+
+    We don't know yet this point if a table is necessary
+    """
+    __regid__ = 'syncaddcwrtype'
+    events = ('after_add_entity',)
+
+    def __call__(self):
+        entity = self.entity
+        rtype = RelationType(name=entity.name,
+                             description=entity.get('description'),
+                             meta=entity.get('meta', False),
+                             inlined=entity.get('inlined', False),
+                             symetric=entity.get('symetric', False),
+                             eid=entity.eid)
+        MemSchemaCWRTypeAdd(self._cw, rtype)
+
+
+class BeforeUpdateCWRTypeHook(DelCWRTypeHook):
+    """check name change, handle final"""
+    __regid__ = 'checkupdatecwrtype'
+    events = ('before_update_entity',)
+
+    def __call__(self):
+        check_valid_changes(self._cw, self.entity)
+
+
+class AfterUpdateCWRTypeHook(DelCWRTypeHook):
+    __regid__ = 'syncupdatecwrtype'
+    events = ('after_update_entity',)
+
+    def __call__(self):
+        entity = self.entity
+        rschema = self._cw.vreg.schema.rschema(entity.name)
+        newvalues = {}
+        for prop in ('meta', 'symetric', 'inlined'):
+            if prop in entity:
+                newvalues[prop] = entity[prop]
+        if newvalues:
+            MemSchemaCWRTypeUpdate(self._cw, rschema=rschema, values=newvalues)
+            SourceDbCWRTypeUpdate(self._cw, rschema=rschema, values=newvalues,
+                                  entity=entity)
+
+
+# relation_type hooks ##########################################################
+
+class AfterDelRelationTypeHook(SyncSchemaHook):
+    """before deleting a CWAttribute or CWRelation entity:
+    * if this is a final or inlined relation definition, instantiate an
+      operation to drop necessary column, else if this is the last instance
+      of a non final relation, instantiate an operation to drop necessary
+      table
+    * instantiate an operation to delete the relation definition on commit
+    * delete the associated relation type when necessary
+    """
+    __regid__ = 'syncdelrelationtype'
+    __select__ = SyncSchemaHook.__select__ & hook.match_rtype('relation_type')
+    events = ('after_delete_relation',)
+
+    def __call__(self):
+        session = self._cw
+        subjschema, rschema, objschema = session.vreg.schema.schema_by_eid(self.eidfrom)
+        pendings = session.transaction_data.get('pendingeids', ())
+        # first delete existing relation if necessary
+        if rschema.is_final():
+            rdeftype = 'CWAttribute'
+        else:
+            rdeftype = 'CWRelation'
+            if not (subjschema.eid in pendings or objschema.eid in pendings):
+                pending = session.transaction_data.setdefault('pendingrdefs', set())
+                pending.add((subjschema, rschema, objschema))
+                session.execute('DELETE X %s Y WHERE X is %s, Y is %s'
+                                % (rschema, subjschema, objschema))
+        execute = session.unsafe_execute
+        rteid = self.eidto
+        rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R,'
+                       'R eid %%(x)s' % rdeftype, {'x': rteid})
+        lastrel = rset[0][0] == 0
+        # we have to update physical schema systematically for final and inlined
+        # relations, but only if it's the last instance for this relation type
+        # for other relations
+
+        if (rschema.is_final() or rschema.inlined):
+            rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R, '
+                           'R eid %%(x)s, X from_entity E, E name %%(name)s'
+                           % rdeftype, {'x': rteid, 'name': str(subjschema)})
+            if rset[0][0] == 0 and not subjschema.eid in pendings:
+                ptypes = session.transaction_data.setdefault('pendingrtypes', set())
+                ptypes.add(rschema.type)
+                DropColumn(session, table=SQL_PREFIX + subjschema.type,
+                             column=SQL_PREFIX + rschema.type)
+        elif lastrel:
+            DropRelationTable(session, rschema.type)
+        # if this is the last instance, drop associated relation type
+        if lastrel and not rteid in pendings:
+            execute('DELETE CWRType X WHERE X eid %(x)s', {'x': rteid}, 'x')
+        MemSchemaRDefDel(session, (subjschema, rschema, objschema))
+
+
+# CWAttribute / CWRelation hooks ###############################################
+
+class AfterAddCWAttributeHook(SyncSchemaHook):
+    __regid__ = 'syncaddcwattribute'
+    __select__ = SyncSchemaHook.__select__ & entity_implements('CWAttribute')
+    events = ('after_add_entity',)
+
+    def __call__(self):
+        SourceDbCWAttributeAdd(self._cw, entity=self.entity)
+
+
+class AfterAddCWRelationHook(AfterAddCWAttributeHook):
+    __regid__ = 'syncaddcwrelation'
+    __select__ = SyncSchemaHook.__select__ & entity_implements('CWRelation')
+
+    def __call__(self):
+        SourceDbCWRelationAdd(self._cw, entity=self.entity)
+
+
+class AfterUpdateCWRDefHook(SyncSchemaHook):
+    __regid__ = 'syncaddcwattribute'
+    __select__ = SyncSchemaHook.__select__ & entity_implements('CWAttribute',
+                                                               'CWRelation')
+    events = ('after_update_entity',)
+
+    def __call__(self):
+        entity = self.entity
+        if self._cw.deleted_in_transaction(entity.eid):
+            return
+        desttype = entity.otype.name
+        rschema = self._cw.vreg.schema[entity.rtype.name]
+        newvalues = {}
+        for prop in rschema.rproperty_defs(desttype):
+            if prop == 'constraints':
+                continue
+            if prop == 'order':
+                prop = 'ordernum'
+            if prop in entity.edited_attributes:
+                newvalues[prop] = entity[prop]
+        if newvalues:
+            subjtype = entity.stype.name
+            MemSchemaRDefUpdate(self._cw, kobj=(subjtype, desttype),
+                                rschema=rschema, values=newvalues)
+            SourceDbRDefUpdate(self._cw, kobj=(subjtype, desttype),
+                               rschema=rschema, values=newvalues)
+
+
+# constraints synchronization hooks ############################################
+
+class AfterAddCWConstraintHook(SyncSchemaHook):
+    __regid__ = 'syncaddcwconstraint'
+    __select__ = SyncSchemaHook.__select__ & entity_implements('CWConstraint')
+    events = ('after_add_entity', 'after_update_entity')
+
+    def __call__(self):
+        MemSchemaCWConstraintAdd(self._cw, entity=self.entity)
+        SourceDbCWConstraintAdd(self._cw, entity=self.entity)
+
+
+class AfterAddConstrainedByHook(SyncSchemaHook):
+    __regid__ = 'syncdelconstrainedby'
+    __select__ = SyncSchemaHook.__select__ & hook.match_rtype('constrained_by')
+    events = ('after_add_relation',)
+
+    def __call__(self):
+        if self._cw.added_in_transaction(self.eidfrom):
+            self._cw.transaction_data.setdefault(self.eidfrom, []).append(self.eidto)
+
+
+class BeforeDeleteConstrainedByHook(AfterAddConstrainedByHook):
+    __regid__ = 'syncdelconstrainedby'
+    events = ('before_delete_relation',)
+
+    def __call__(self):
+        if self._cw.deleted_in_transaction(self.eidfrom):
+            return
+        schema = self._cw.vreg.schema
+        entity = self._cw.entity_from_eid(self.eidto)
+        subjtype, rtype, objtype = schema.schema_by_eid(self.eidfrom)
+        try:
+            cstr = rtype.constraint_by_type(subjtype, objtype,
+                                            entity.cstrtype[0].name)
+        except IndexError:
+            self._cw.critical('constraint type no more accessible')
+        else:
+            SourceDbCWConstraintDel(self._cw, subjtype=subjtype, rtype=rtype,
+                                    objtype=objtype, cstr=cstr)
+            MemSchemaCWConstraintDel(self._cw, subjtype=subjtype, rtype=rtype,
+                                     objtype=objtype, cstr=cstr)
+
+
+# permissions synchronization hooks ############################################
+
+class AfterAddPermissionHook(SyncSchemaHook):
+    """added entity/relation *_permission, need to update schema"""
+    __regid__ = 'syncaddperm'
+    __select__ = SyncSchemaHook.__select__ & hook.match_rtype(
+        'read_permission', 'add_permission', 'delete_permission',
+        'update_permission')
+    events = ('after_add_relation',)
+
+    def __call__(self):
+        perm = self.rtype.split('_', 1)[0]
+        if self._cw.describe(self.eidto)[0] == 'CWGroup':
+            MemSchemaPermCWGroupAdd(self._cw, perm, self.eidfrom, self.eidto)
+        else: # RQLExpression
+            expr = self._cw.entity_from_eid(self.eidto).expression
+            MemSchemaPermRQLExpressionAdd(self._cw, perm, self.eidfrom, expr)
+
+
+class BeforeDelPermissionHook(AfterAddPermissionHook):
+    """delete entity/relation *_permission, need to update schema
+
+    skip the operation if the related type is being deleted
+    """
+    __regid__ = 'syncdelperm'
+    events = ('before_delete_relation',)
+
+    def __call__(self):
+        if self._cw.deleted_in_transaction(self.eidfrom):
+            return
+        perm = self.rtype.split('_', 1)[0]
+        if self._cw.describe(self.eidto)[0] == 'CWGroup':
+            MemSchemaPermCWGroupDel(self._cw, perm, self.eidfrom, self.eidto)
+        else: # RQLExpression
+            expr = self._cw.entity_from_eid(self.eidto).expression
+            MemSchemaPermRQLExpressionDel(self._cw, perm, self.eidfrom, expr)
+
+
+# specializes synchronization hooks ############################################
+
+
+class AfterAddSpecializesHook(SyncSchemaHook):
+    __regid__ = 'syncaddspecializes'
+    __select__ = SyncSchemaHook.__select__ & hook.match_rtype('specializes')
+    events = ('after_add_relation',)
+
+    def __call__(self):
+        MemSchemaSpecializesAdd(self._cw, etypeeid=self.eidfrom,
+                                parentetypeeid=self.eidto)
+
+
+class AfterDelSpecializesHook(SyncSchemaHook):
+    __regid__ = 'syncdelspecializes'
+    __select__ = SyncSchemaHook.__select__ & hook.match_rtype('specializes')
+    events = ('after_delete_relation',)
+
+    def __call__(self):
+        MemSchemaSpecializesDel(self._cw, etypeeid=self.eidfrom,
+                                parentetypeeid=self.eidto)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/syncsession.py	Wed Sep 30 18:57:42 2009 +0200
@@ -0,0 +1,235 @@
+"""Core hooks: synchronize living session on persistent data changes
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb import UnknownProperty, ValidationError, BadConnectionId
+from cubicweb.selectors import entity_implements
+from cubicweb.server import hook
+
+
+def get_user_sessions(repo, ueid):
+    for session in repo._sessions.values():
+        if ueid == session.user.eid:
+            yield session
+
+
+class SyncSessionHook(hook.Hook):
+    __abstract__ = True
+    category = 'syncsession'
+
+
+# user/groups synchronisation #################################################
+
+class _GroupOperation(hook.Operation):
+    """base class for group operation"""
+    geid = None
+    def __init__(self, session, *args, **kwargs):
+        """override to get the group name before actual groups manipulation:
+
+        we may temporarily loose right access during a commit event, so
+        no query should be emitted while comitting
+        """
+        rql = 'Any N WHERE G eid %(x)s, G name N'
+        result = session.execute(rql, {'x': kwargs['geid']}, 'x', build_descr=False)
+        hook.Operation.__init__(self, session, *args, **kwargs)
+        self.group = result[0][0]
+
+
+class _DeleteGroupOp(_GroupOperation):
+    """synchronize user when a in_group relation has been deleted"""
+    def commit_event(self):
+        """the observed connections pool has been commited"""
+        groups = self.cnxuser.groups
+        try:
+            groups.remove(self.group)
+        except KeyError:
+            self.error('user %s not in group %s',  self.cnxuser, self.group)
+            return
+
+
+class _AddGroupOp(_GroupOperation):
+    """synchronize user when a in_group relation has been added"""
+    def commit_event(self):
+        """the observed connections pool has been commited"""
+        groups = self.cnxuser.groups
+        if self.group in groups:
+            self.warning('user %s already in group %s', self.cnxuser,
+                         self.group)
+            return
+        groups.add(self.group)
+
+
+class SyncInGroupHook(SyncSessionHook):
+    __regid__ = 'syncingroup'
+    __select__ = SyncSessionHook.__select__ & hook.match_rtype('in_group')
+    events = ('after_delete_relation', 'after_add_relation')
+
+    def __call__(self):
+        if self.event == 'after_delete_relation':
+            opcls = _DeleteGroupOp
+        else:
+            opcls = _AddGroupOp
+        for session in get_user_sessions(self._cw.repo, self.eidfrom):
+            opcls(self._cw, cnxuser=session.user, geid=self.eidto)
+
+
+class _DelUserOp(hook.Operation):
+    """close associated user's session when it is deleted"""
+    def __init__(self, session, cnxid):
+        self.cnxid = cnxid
+        hook.Operation.__init__(self, session)
+
+    def commit_event(self):
+        """the observed connections pool has been commited"""
+        try:
+            self.session.repo.close(self.cnxid)
+        except BadConnectionId:
+            pass # already closed
+
+
+class CloseDeletedUserSessionsHook(SyncSessionHook):
+    __regid__ = 'closession'
+    __select__ = SyncSessionHook.__select__ & entity_implements('CWUser')
+    events = ('after_delete_entity',)
+
+    def __call__(self):
+        """modify user permission, need to update users"""
+        for session in get_user_sessions(self._cw.repo, self.entity.eid):
+            _DelUserOp(self._cw, session.id)
+
+
+# CWProperty hooks #############################################################
+
+
+class _DelCWPropertyOp(hook.Operation):
+    """a user's custom properties has been deleted"""
+
+    def commit_event(self):
+        """the observed connections pool has been commited"""
+        try:
+            del self.cwpropdict[self.key]
+        except KeyError:
+            self.error('%s has no associated value', self.key)
+
+
+class _ChangeCWPropertyOp(hook.Operation):
+    """a user's custom properties has been added/changed"""
+
+    def commit_event(self):
+        """the observed connections pool has been commited"""
+        self.cwpropdict[self.key] = self.value
+
+
+class _AddCWPropertyOp(hook.Operation):
+    """a user's custom properties has been added/changed"""
+
+    def commit_event(self):
+        """the observed connections pool has been commited"""
+        cwprop = self.cwprop
+        if not cwprop.for_user:
+            self.session.vreg['propertyvalues'][cwprop.pkey] = cwprop.value
+        # if for_user is set, update is handled by a ChangeCWPropertyOp operation
+
+
+class AddCWPropertyHook(SyncSessionHook):
+    __regid__ = 'addcwprop'
+    __select__ = SyncSessionHook.__select__ & entity_implements('CWProperty')
+    events = ('after_add_entity',)
+
+    def __call__(self):
+        key, value = self.entity.pkey, self.entity.value
+        session = self._cw
+        try:
+            value = session.vreg.typed_value(key, value)
+        except UnknownProperty:
+            raise ValidationError(self.entity.eid,
+                                  {'pkey': session._('unknown property key')})
+        except ValueError, ex:
+            raise ValidationError(self.entity.eid,
+                                  {'value': session._(str(ex))})
+        if not session.user.matching_groups('managers'):
+            session.add_relation(entity.eid, 'for_user', session.user.eid)
+        else:
+            _AddCWPropertyOp(session, cwprop=self.entity)
+
+
+class UpdateCWPropertyHook(AddCWPropertyHook):
+    __regid__ = 'updatecwprop'
+    events = ('after_update_entity',)
+
+    def __call__(self):
+        entity = self.entity
+        if not ('pkey' in entity.edited_attributes or
+                'value' in entity.edited_attributes):
+            return
+        key, value = entity.pkey, entity.value
+        session = self._cw
+        try:
+            value = session.vreg.typed_value(key, value)
+        except UnknownProperty:
+            return
+        except ValueError, ex:
+            raise ValidationError(entity.eid, {'value': session._(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,
+                                  key=key, value=value)
+        else:
+            # site wide properties
+            _ChangeCWPropertyOp(session, cwpropdict=session.vreg['propertyvalues'],
+                              key=key, value=value)
+
+
+class DeleteCWPropertyHook(AddCWPropertyHook):
+    __regid__ = 'delcwprop'
+    events = ('before_delete_entity',)
+
+    def __call__(self):
+        eid = self.entity.eid
+        session = self._cw
+        for eidfrom, rtype, eidto in session.transaction_data.get('pendingrelations', ()):
+            if rtype == 'for_user' and eidfrom == self.entity.eid:
+                # if for_user was set, delete has already been handled
+                break
+        else:
+            _DelCWPropertyOp(session, cwpropdict=session.vreg['propertyvalues'],
+                             key=self.entity.pkey)
+
+
+class AddForUserRelationHook(SyncSessionHook):
+    __regid__ = 'addcwpropforuser'
+    __select__ = SyncSessionHook.__select__ & hook.match_rtype('for_user')
+    events = ('after_add_relation',)
+
+    def __call__(self):
+        session = self._cw
+        eidfrom = self.eidfrom
+        if not session.describe(eidfrom)[0] == 'CWProperty':
+            return
+        key, value = session.execute('Any K,V WHERE P eid %(x)s,P pkey K,P value V',
+                                     {'x': eidfrom}, 'x')[0]
+        if session.vreg.property_info(key)['sitewide']:
+            raise ValidationError(eidfrom,
+                                  {'for_user': session._("site-wide property can't be set for user")})
+        for session_ in get_user_sessions(session.repo, self.eidto):
+            _ChangeCWPropertyOp(session, cwpropdict=session_.user.properties,
+                              key=key, value=value)
+
+
+class DelForUserRelationHook(AddForUserRelationHook):
+    __regid__ = 'delcwpropforuser'
+    events = ('after_delete_relation',)
+
+    def __call__(self):
+        session = self._cw
+        key = session.execute('Any K WHERE P eid %(x)s, P pkey K',
+                              {'x': self.eidfrom}, 'x')[0][0]
+        session.transaction_data.setdefault('pendingrelations', []).append(
+            (self.eidfrom, self.rtype, self.eidto))
+        for session_ in get_user_sessions(session.repo, self.eidto):
+            _DelCWPropertyOp(session, cwpropdict=session_.user.properties, key=key)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/test/data/bootstrap_cubes	Wed Sep 30 18:57:42 2009 +0200
@@ -0,0 +1,1 @@
+email
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/test/unittest_hooks.py	Wed Sep 30 18:57:42 2009 +0200
@@ -0,0 +1,484 @@
+# -*- coding: utf-8 -*-
+"""functional tests for core hooks
+
+note: most schemahooks.py hooks are actually tested in unittest_migrations.py
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+
+from logilab.common.testlib import TestCase, unittest_main
+
+from datetime import datetime
+
+from cubicweb import (ConnectionError, ValidationError, AuthenticationError,
+                      BadConnectionId)
+from cubicweb.devtools.testlib import CubicWebTC, get_versions
+
+from cubicweb.server.sqlutils import SQL_PREFIX
+from cubicweb.server.repository import Repository
+
+orig_get_versions = Repository.get_versions
+
+def setup_module(*args):
+    Repository.get_versions = get_versions
+
+def teardown_module(*args):
+    Repository.get_versions = orig_get_versions
+
+
+
+class CoreHooksTC(CubicWebTC):
+
+    def test_delete_internal_entities(self):
+        self.assertRaises(ValidationError, self.execute,
+                          'DELETE CWEType X WHERE X name "CWEType"')
+        self.assertRaises(ValidationError, self.execute,
+                          'DELETE CWRType X WHERE X name "relation_type"')
+        self.assertRaises(ValidationError, self.execute,
+                          'DELETE CWGroup X WHERE X name "owners"')
+
+    def test_delete_required_relations_subject(self):
+        self.execute('INSERT CWUser X: X login "toto", X upassword "hop", X in_group Y '
+                     'WHERE Y name "users"')
+        self.commit()
+        self.execute('DELETE X in_group Y WHERE X login "toto", Y name "users"')
+        self.assertRaises(ValidationError, self.commit)
+        self.execute('DELETE X in_group Y WHERE X login "toto"')
+        self.execute('SET X in_group Y WHERE X login "toto", Y name "guests"')
+        self.commit()
+
+    def test_delete_required_relations_object(self):
+        self.skip('no sample in the schema ! YAGNI ? Kermaat ?')
+
+    def test_static_vocabulary_check(self):
+        self.assertRaises(ValidationError,
+                          self.execute,
+                          'SET X composite "whatever" WHERE X from_entity FE, FE name "CWUser", X relation_type RT, RT name "in_group"')
+
+    def test_missing_required_relations_subject_inline(self):
+        # missing in_group relation
+        self.execute('INSERT CWUser X: X login "toto", X upassword "hop"')
+        self.assertRaises(ValidationError,
+                          self.commit)
+
+    def test_inlined(self):
+        self.assertEquals(self.repo.schema['sender'].inlined, True)
+        self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')
+        self.execute('INSERT EmailPart X: X content_format "text/plain", X ordernum 1, X content "this is a test"')
+        eeid = self.execute('INSERT Email X: X messageid "<1234>", X subject "test", X sender Y, X recipients Y, X parts P '
+                            'WHERE Y is EmailAddress, P is EmailPart')[0][0]
+        self.execute('SET X sender Y WHERE X is Email, Y is EmailAddress')
+        rset = self.execute('Any S WHERE X sender S, X eid %s' % eeid)
+        self.assertEquals(len(rset), 1)
+
+    def test_composite_1(self):
+        self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')
+        self.execute('INSERT EmailPart X: X content_format "text/plain", X ordernum 1, X content "this is a test"')
+        self.execute('INSERT Email X: X messageid "<1234>", X subject "test", X sender Y, X recipients Y, X parts P '
+                     'WHERE Y is EmailAddress, P is EmailPart')
+        self.failUnless(self.execute('Email X WHERE X sender Y'))
+        self.commit()
+        self.execute('DELETE Email X')
+        rset = self.execute('Any X WHERE X is EmailPart')
+        self.assertEquals(len(rset), 1)
+        self.commit()
+        rset = self.execute('Any X WHERE X is EmailPart')
+        self.assertEquals(len(rset), 0)
+
+    def test_composite_2(self):
+        self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')
+        self.execute('INSERT EmailPart X: X content_format "text/plain", X ordernum 1, X content "this is a test"')
+        self.execute('INSERT Email X: X messageid "<1234>", X subject "test", X sender Y, X recipients Y, X parts P '
+                     'WHERE Y is EmailAddress, P is EmailPart')
+        self.commit()
+        self.execute('DELETE Email X')
+        self.execute('DELETE EmailPart X')
+        self.commit()
+        rset = self.execute('Any X WHERE X is EmailPart')
+        self.assertEquals(len(rset), 0)
+
+    def test_composite_redirection(self):
+        self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')
+        self.execute('INSERT EmailPart X: X content_format "text/plain", X ordernum 1, X content "this is a test"')
+        self.execute('INSERT Email X: X messageid "<1234>", X subject "test", X sender Y, X recipients Y, X parts P '
+                     'WHERE Y is EmailAddress, P is EmailPart')
+        self.execute('INSERT Email X: X messageid "<2345>", X subject "test2", X sender Y, X recipients Y '
+                     'WHERE Y is EmailAddress')
+        self.commit()
+        self.execute('DELETE X parts Y WHERE X messageid "<1234>"')
+        self.execute('SET X parts Y WHERE X messageid "<2345>"')
+        self.commit()
+        rset = self.execute('Any X WHERE X is EmailPart')
+        self.assertEquals(len(rset), 1)
+        self.assertEquals(rset.get_entity(0, 0).reverse_parts[0].messageid, '<2345>')
+
+    def test_unsatisfied_constraints(self):
+        self.execute('INSERT CWRelation X: X from_entity FE, X relation_type RT, X to_entity TE '
+                     'WHERE FE name "CWUser", RT name "in_group", TE name "String"')
+        ex = self.assertRaises(ValidationError,
+                               self.commit)
+        self.assertEquals(str(ex), '612 (to_entity): constraint O final FALSE failed')
+
+    def test_html_tidy_hook(self):
+        entity = self.add_entity('Workflow', name=u'wf1', description_format=u'text/html',
+                                 description=u'yo')
+        self.assertEquals(entity.description, u'yo')
+        entity = self.add_entity('Workflow', name=u'wf2', description_format=u'text/html',
+                                 description=u'<b>yo')
+        self.assertEquals(entity.description, u'<b>yo</b>')
+        entity = self.add_entity('Workflow', name=u'wf3', description_format=u'text/html',
+                                 description=u'<b>yo</b>')
+        self.assertEquals(entity.description, u'<b>yo</b>')
+        entity = self.add_entity('Workflow', name=u'wf4', description_format=u'text/html',
+                                 description=u'<b>R&D</b>')
+        self.assertEquals(entity.description, u'<b>R&amp;D</b>')
+        entity = self.add_entity('Workflow', name=u'wf5', description_format=u'text/html',
+                                 description=u"<div>c&apos;est <b>l'ét&eacute;")
+        self.assertEquals(entity.description, u"<div>c'est <b>l'été</b></div>")
+
+    def test_nonregr_html_tidy_hook_no_update(self):
+        entity = self.add_entity('Workflow', name=u'wf1', description_format=u'text/html',
+                                 description=u'yo')
+        entity.set_attributes(name=u'wf2')
+        self.assertEquals(entity.description, u'yo')
+        entity.set_attributes(description=u'R&D<p>yo')
+        entity.pop('description')
+        self.assertEquals(entity.description, u'R&amp;D<p>yo</p>')
+
+
+    def test_metadata_cwuri(self):
+        entity = self.add_entity('Workflow', name=u'wf1')
+        self.assertEquals(entity.cwuri, self.repo.config['base-url'] + 'eid/%s' % entity.eid)
+
+    def test_metadata_creation_modification_date(self):
+        _now = datetime.now()
+        entity = self.add_entity('Workflow', name=u'wf1')
+        self.assertEquals((entity.creation_date - _now).seconds, 0)
+        self.assertEquals((entity.modification_date - _now).seconds, 0)
+
+    def test_metadata_created_by(self):
+        entity = self.add_entity('Bookmark', title=u'wf1', path=u'/view')
+        self.commit() # fire operations
+        self.assertEquals(len(entity.created_by), 1) # make sure we have only one creator
+        self.assertEquals(entity.created_by[0].eid, self.session.user.eid)
+
+    def test_metadata_owned_by(self):
+        entity = self.add_entity('Bookmark', title=u'wf1', path=u'/view')
+        self.commit() # fire operations
+        self.assertEquals(len(entity.owned_by), 1) # make sure we have only one owner
+        self.assertEquals(entity.owned_by[0].eid, self.session.user.eid)
+
+
+
+class UserGroupHooksTC(CubicWebTC):
+
+    def test_user_synchronization(self):
+        self.create_user('toto', password='hop', commit=False)
+        self.assertRaises(AuthenticationError,
+                          self.repo.connect, u'toto', 'hop')
+        self.commit()
+        cnxid = self.repo.connect(u'toto', 'hop')
+        self.failIfEqual(cnxid, self.session.id)
+        self.execute('DELETE CWUser X WHERE X login "toto"')
+        self.repo.execute(cnxid, 'State X')
+        self.commit()
+        self.assertRaises(BadConnectionId,
+                          self.repo.execute, cnxid, 'State X')
+
+    def test_user_group_synchronization(self):
+        user = self.session.user
+        self.assertEquals(user.groups, set(('managers',)))
+        self.execute('SET X in_group G WHERE X eid %s, G name "guests"' % user.eid)
+        self.assertEquals(user.groups, set(('managers',)))
+        self.commit()
+        self.assertEquals(user.groups, set(('managers', 'guests')))
+        self.execute('DELETE X in_group G WHERE X eid %s, G name "guests"' % user.eid)
+        self.assertEquals(user.groups, set(('managers', 'guests')))
+        self.commit()
+        self.assertEquals(user.groups, set(('managers',)))
+
+    def test_user_composite_owner(self):
+        ueid = self.create_user('toto').eid
+        # composite of euser should be owned by the euser regardless of who created it
+        self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", U use_email X '
+                     'WHERE U login "toto"')
+        self.commit()
+        self.assertEquals(self.execute('Any A WHERE X owned_by U, U use_email X,'
+                                       'U login "toto", X address A')[0][0],
+                          'toto@logilab.fr')
+
+    def test_no_created_by_on_deleted_entity(self):
+        eid = self.execute('INSERT EmailAddress X: X address "toto@logilab.fr"')[0][0]
+        self.execute('DELETE EmailAddress X WHERE X eid %s' % eid)
+        self.commit()
+        self.failIf(self.execute('Any X WHERE X created_by Y, X eid >= %(x)s', {'x': eid}))
+
+
+class CWPropertyHooksTC(CubicWebTC):
+
+    def test_unexistant_eproperty(self):
+        ex = self.assertRaises(ValidationError,
+                          self.execute, 'INSERT CWProperty X: X pkey "bla.bla", X value "hop", X for_user U')
+        self.assertEquals(ex.errors, {'pkey': 'unknown property key'})
+        ex = self.assertRaises(ValidationError,
+                          self.execute, 'INSERT CWProperty X: X pkey "bla.bla", X value "hop"')
+        self.assertEquals(ex.errors, {'pkey': 'unknown property key'})
+
+    def test_site_wide_eproperty(self):
+        ex = self.assertRaises(ValidationError,
+                               self.execute, 'INSERT CWProperty X: X pkey "ui.site-title", X value "hop", X for_user U')
+        self.assertEquals(ex.errors, {'for_user': "site-wide property can't be set for user"})
+
+    def test_bad_type_eproperty(self):
+        ex = self.assertRaises(ValidationError,
+                               self.execute, 'INSERT CWProperty X: X pkey "ui.language", X value "hop", X for_user U')
+        self.assertEquals(ex.errors, {'value': u'unauthorized value'})
+        ex = self.assertRaises(ValidationError,
+                          self.execute, 'INSERT CWProperty X: X pkey "ui.language", X value "hop"')
+        self.assertEquals(ex.errors, {'value': u'unauthorized value'})
+
+
+class SchemaHooksTC(CubicWebTC):
+
+    def test_duplicate_etype_error(self):
+        # check we can't add a CWEType or CWRType entity if it already exists one
+        # with the same name
+        self.assertRaises(ValidationError,
+                          self.execute, 'INSERT CWEType X: X name "CWUser"')
+        self.assertRaises(ValidationError,
+                          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:
+            self.execute('INSERT CWUser X: X login "admin"')
+        except ValidationError, ex:
+            self.assertIsInstance(ex.entity, int)
+            self.assertEquals(ex.errors, {'login': 'the value "admin" is already used, use another one'})
+
+
+class SchemaModificationHooksTC(CubicWebTC):
+
+    @classmethod
+    def init_config(cls, config):
+        super(SchemaModificationHooksTC, cls).init_config(config)
+        config._cubes = None
+        cls.repo.fill_schema()
+
+    def index_exists(self, etype, attr, unique=False):
+        self.session.set_pool()
+        dbhelper = self.session.pool.source('system').dbhelper
+        sqlcursor = self.session.pool['system']
+        return dbhelper.index_exists(sqlcursor, SQL_PREFIX + etype, SQL_PREFIX + attr, unique=unique)
+
+    def test_base(self):
+        schema = self.repo.schema
+        self.session.set_pool()
+        dbhelper = self.session.pool.source('system').dbhelper
+        sqlcursor = self.session.pool['system']
+        self.failIf(schema.has_entity('Societe2'))
+        self.failIf(schema.has_entity('concerne2'))
+        # schema should be update on insertion (after commit)
+        self.execute('INSERT CWEType X: X name "Societe2", X description "", X final FALSE')
+        self.execute('INSERT CWRType X: X name "concerne2", X description "", X final FALSE, X symetric FALSE')
+        self.failIf(schema.has_entity('Societe2'))
+        self.failIf(schema.has_entity('concerne2'))
+        self.execute('SET X read_permission G WHERE X is CWEType, X name "Societe2", G is CWGroup')
+        self.execute('SET X read_permission G WHERE X is CWRType, X name "concerne2", G is CWGroup')
+        self.execute('SET X add_permission G WHERE X is CWEType, X name "Societe2", G is CWGroup, G name "managers"')
+        self.execute('SET X add_permission G WHERE X is CWRType, X name "concerne2", G is CWGroup, G name "managers"')
+        self.execute('SET X delete_permission G WHERE X is CWEType, X name "Societe2", G is CWGroup, G name "owners"')
+        self.execute('SET X delete_permission G WHERE X is CWRType, X name "concerne2", G is CWGroup, G name "owners"')
+        # have to commit before adding definition relations
+        self.commit()
+        self.failUnless(schema.has_entity('Societe2'))
+        self.failUnless(schema.has_relation('concerne2'))
+        self.execute('INSERT CWAttribute X: X cardinality "11", X defaultval "noname", X indexed TRUE, X relation_type RT, X from_entity E, X to_entity F '
+                     'WHERE RT name "name", E name "Societe2", F name "String"')
+        concerne2_rdef_eid = self.execute(
+            'INSERT CWRelation X: X cardinality "**", X relation_type RT, X from_entity E, X to_entity E '
+            'WHERE RT name "concerne2", E name "Societe2"')[0][0]
+        self.failIf('name' in schema['Societe2'].subject_relations())
+        self.failIf('concerne2' in schema['Societe2'].subject_relations())
+        self.failIf(self.index_exists('Societe2', 'name'))
+        self.commit()
+        self.failUnless('name' in schema['Societe2'].subject_relations())
+        self.failUnless('concerne2' in schema['Societe2'].subject_relations())
+        self.failUnless(self.index_exists('Societe2', 'name'))
+        # now we should be able to insert and query Societe2
+        s2eid = self.execute('INSERT Societe2 X: X name "logilab"')[0][0]
+        self.execute('Societe2 X WHERE X name "logilab"')
+        self.execute('SET X concerne2 X WHERE X name "logilab"')
+        rset = self.execute('Any X WHERE X concerne2 Y')
+        self.assertEquals(rset.rows, [[s2eid]])
+        # check that when a relation definition is deleted, existing relations are deleted
+        self.execute('INSERT CWRelation X: X cardinality "**", X relation_type RT, X from_entity E, X to_entity E '
+                     'WHERE RT name "concerne2", E name "CWUser"')
+        self.commit()
+        self.execute('DELETE CWRelation X WHERE X eid %(x)s', {'x': concerne2_rdef_eid}, 'x')
+        self.commit()
+        self.failUnless('concerne2' in schema['CWUser'].subject_relations())
+        self.failIf('concerne2' in schema['Societe2'].subject_relations())
+        self.failIf(self.execute('Any X WHERE X concerne2 Y'))
+        # schema should be cleaned on delete (after commit)
+        self.execute('DELETE CWEType X WHERE X name "Societe2"')
+        self.execute('DELETE CWRType X WHERE X name "concerne2"')
+        self.failUnless(self.index_exists('Societe2', 'name'))
+        self.failUnless(schema.has_entity('Societe2'))
+        self.failUnless(schema.has_relation('concerne2'))
+        self.commit()
+        self.failIf(self.index_exists('Societe2', 'name'))
+        self.failIf(schema.has_entity('Societe2'))
+        self.failIf(schema.has_entity('concerne2'))
+        self.failIf('concerne2' in schema['CWUser'].subject_relations())
+
+    def test_is_instance_of_insertions(self):
+        seid = self.execute('INSERT Transition T: T name "subdiv"')[0][0]
+        is_etypes = [etype for etype, in self.execute('Any ETN WHERE X eid %s, X is ET, ET name ETN' % seid)]
+        self.assertEquals(is_etypes, ['Transition'])
+        instanceof_etypes = [etype for etype, in self.execute('Any ETN WHERE X eid %s, X is_instance_of ET, ET name ETN' % seid)]
+        self.assertEquals(sorted(instanceof_etypes), ['BaseTransition', 'Transition'])
+        snames = [name for name, in self.execute('Any N WHERE S is BaseTransition, S name N')]
+        self.failIf('subdiv' in snames)
+        snames = [name for name, in self.execute('Any N WHERE S is_instance_of BaseTransition, S name N')]
+        self.failUnless('subdiv' in snames)
+
+
+    def test_perms_synchronization_1(self):
+        schema = self.repo.schema
+        self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users')))
+        self.failUnless(self.execute('Any X, Y WHERE X is CWEType, X name "CWUser", Y is CWGroup, Y name "users"')[0])
+        self.execute('DELETE X read_permission Y WHERE X is CWEType, X name "CWUser", Y name "users"')
+        self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users', )))
+        self.commit()
+        self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', )))
+        self.execute('SET X read_permission Y WHERE X is CWEType, X name "CWUser", Y name "users"')
+        self.commit()
+        self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users',)))
+
+    def test_perms_synchronization_2(self):
+        schema = self.repo.schema['in_group']
+        self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
+        self.execute('DELETE X read_permission Y WHERE X is CWRType, X name "in_group", Y name "guests"')
+        self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
+        self.commit()
+        self.assertEquals(schema.get_groups('read'), set(('managers', 'users')))
+        self.execute('SET X read_permission Y WHERE X is CWRType, X name "in_group", Y name "guests"')
+        self.assertEquals(schema.get_groups('read'), set(('managers', 'users')))
+        self.commit()
+        self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
+
+    def test_nonregr_user_edit_itself(self):
+        ueid = self.session.user.eid
+        groupeids = [eid for eid, in self.execute('CWGroup G WHERE G name in ("managers", "users")')]
+        self.execute('DELETE X in_group Y WHERE X eid %s' % ueid)
+        self.execute('SET X surname "toto" WHERE X eid %s' % ueid)
+        self.execute('SET X in_group Y WHERE X eid %s, Y name "managers"' % ueid)
+        self.commit()
+        eeid = self.execute('Any X WHERE X is CWEType, X name "CWEType"')[0][0]
+        self.execute('DELETE X read_permission Y WHERE X eid %s' % eeid)
+        self.execute('SET X final FALSE WHERE X eid %s' % eeid)
+        self.execute('SET X read_permission Y WHERE X eid %s, Y eid in (%s, %s)'
+                     % (eeid, groupeids[0], groupeids[1]))
+        self.commit()
+        self.execute('Any X WHERE X is CWEType, X name "CWEType"')
+
+    # schema modification hooks tests #########################################
+
+    def test_uninline_relation(self):
+        self.session.set_pool()
+        dbhelper = self.session.pool.source('system').dbhelper
+        sqlcursor = self.session.pool['system']
+        self.failUnless(self.schema['state_of'].inlined)
+        try:
+            self.execute('SET X inlined FALSE WHERE X name "state_of"')
+            self.failUnless(self.schema['state_of'].inlined)
+            self.commit()
+            self.failIf(self.schema['state_of'].inlined)
+            self.failIf(self.index_exists('State', 'state_of'))
+            rset = self.execute('Any X, Y WHERE X state_of Y')
+            self.assertEquals(len(rset), 2) # user states
+        finally:
+            self.execute('SET X inlined TRUE WHERE X name "state_of"')
+            self.failIf(self.schema['state_of'].inlined)
+            self.commit()
+            self.failUnless(self.schema['state_of'].inlined)
+            self.failUnless(self.index_exists('State', 'state_of'))
+            rset = self.execute('Any X, Y WHERE X state_of Y')
+            self.assertEquals(len(rset), 2)
+
+    def test_indexed_change(self):
+        self.session.set_pool()
+        dbhelper = self.session.pool.source('system').dbhelper
+        sqlcursor = self.session.pool['system']
+        try:
+            self.execute('SET X indexed FALSE WHERE X relation_type R, R name "name"')
+            self.failUnless(self.schema['name'].rproperty('Workflow', 'String', 'indexed'))
+            self.failUnless(self.index_exists('Workflow', 'name'))
+            self.commit()
+            self.failIf(self.schema['name'].rproperty('Workflow', 'String', 'indexed'))
+            self.failIf(self.index_exists('Workflow', 'name'))
+        finally:
+            self.execute('SET X indexed TRUE WHERE X relation_type R, R name "name"')
+            self.failIf(self.schema['name'].rproperty('Workflow', 'String', 'indexed'))
+            self.failIf(self.index_exists('Workflow', 'name'))
+            self.commit()
+            self.failUnless(self.schema['name'].rproperty('Workflow', 'String', 'indexed'))
+            self.failUnless(self.index_exists('Workflow', 'name'))
+
+    def test_unique_change(self):
+        self.session.set_pool()
+        dbhelper = self.session.pool.source('system').dbhelper
+        sqlcursor = self.session.pool['system']
+        try:
+            self.execute('INSERT CWConstraint X: X cstrtype CT, DEF constrained_by X '
+                         'WHERE CT name "UniqueConstraint", DEF relation_type RT, DEF from_entity E,'
+                         'RT name "name", E name "Workflow"')
+            self.failIf(self.schema['Workflow'].has_unique_values('name'))
+            self.failIf(self.index_exists('Workflow', 'name', unique=True))
+            self.commit()
+            self.failUnless(self.schema['Workflow'].has_unique_values('name'))
+            self.failUnless(self.index_exists('Workflow', 'name', unique=True))
+        finally:
+            self.execute('DELETE DEF constrained_by X WHERE X cstrtype CT, '
+                         'CT name "UniqueConstraint", DEF relation_type RT, DEF from_entity E,'
+                         'RT name "name", E name "Workflow"')
+            self.failUnless(self.schema['Workflow'].has_unique_values('name'))
+            self.failUnless(self.index_exists('Workflow', 'name', unique=True))
+            self.commit()
+            self.failIf(self.schema['Workflow'].has_unique_values('name'))
+            self.failIf(self.index_exists('Workflow', 'name', unique=True))
+
+    def test_required_change_1(self):
+        self.execute('SET DEF cardinality "?1" '
+                     'WHERE DEF relation_type RT, DEF from_entity E,'
+                     'RT name "title", E name "Bookmark"')
+        self.commit()
+        # should now be able to add bookmark without title
+        self.execute('INSERT Bookmark X: X path "/view"')
+        self.commit()
+
+    def test_required_change_2(self):
+        self.execute('SET DEF cardinality "11" '
+                     'WHERE DEF relation_type RT, DEF from_entity E,'
+                     'RT name "surname", E name "CWUser"')
+        self.commit()
+        # should not be able anymore to add cwuser without surname
+        self.assertRaises(ValidationError, self.create_user, "toto")
+        self.execute('SET DEF cardinality "?1" '
+                     'WHERE DEF relation_type RT, DEF from_entity E,'
+                     'RT name "surname", E name "CWUser"')
+        self.commit()
+
+
+    def test_add_attribute_to_base_class(self):
+        self.execute('INSERT CWAttribute X: X cardinality "11", X defaultval "noname", X indexed TRUE, X relation_type RT, X from_entity E, X to_entity F '
+                     'WHERE RT name "nom", E name "BaseTransition", F name "String"')
+        self.commit()
+        self.schema.rebuild_infered_relations()
+        self.failUnless('Transition' in self.schema['nom'].subjects())
+        self.failUnless('WorkflowTransition' in self.schema['nom'].subjects())
+        self.execute('Any X WHERE X is_instance_of BaseTransition, X nom "hop"')
+
+if __name__ == '__main__':
+    unittest_main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/workflow.py	Wed Sep 30 18:57:42 2009 +0200
@@ -0,0 +1,351 @@
+"""Core hooks: workflow related hooks
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from datetime import datetime
+
+from cubicweb import RepositoryError, ValidationError
+from cubicweb.interfaces import IWorkflowable
+from cubicweb.selectors import entity_implements
+from cubicweb.server import hook
+from cubicweb.entities.wfobjs import WorkflowTransition
+
+
+def _change_state(session, x, oldstate, newstate):
+    nocheck = session.transaction_data.setdefault('skip-security', set())
+    nocheck.add((x, 'in_state', oldstate))
+    nocheck.add((x, 'in_state', newstate))
+    # delete previous state first in case we're using a super session,
+    # unless in_state isn't stored in the system source
+    fromsource = session.describe(x)[1]
+    if fromsource == 'system' or \
+           not session.repo.sources_by_uri[fromsource].support_relation('in_state'):
+        session.delete_relation(x, 'in_state', oldstate)
+    session.add_relation(x, 'in_state', newstate)
+
+
+# operations ###################################################################
+
+class _SetInitialStateOp(hook.Operation):
+    """make initial state be a default state"""
+
+    def precommit_event(self):
+        session = self.session
+        entity = self.entity
+        # if there is an initial state and the entity's state is not set,
+        # use the initial state as a default state
+        if not (session.deleted_in_transaction(entity.eid) or entity.in_state) \
+               and entity.current_workflow:
+            state = entity.current_workflow.initial
+            if state:
+                # use super session to by-pass security checks
+                session.super_session.add_relation(entity.eid, 'in_state',
+                                                   state.eid)
+
+
+class _FireAutotransitionOp(PreCommitOperation):
+    """try to fire auto transition after state changes"""
+
+    def precommit_event(self):
+        session = self.session
+        entity = self.entity
+        autotrs = list(entity.possible_transitions('auto'))
+        if autotrs:
+            assert len(autotrs) == 1
+            entity.fire_transition(autotrs[0])
+
+
+class _WorkflowChangedOp(hook.Operation):
+    """fix entity current state when changing its workflow"""
+
+    def precommit_event(self):
+        # notice that enforcement that new workflow apply to the entity's type is
+        # done by schema rule, no need to check it here
+        session = self.session
+        pendingeids = session.transaction_data.get('pendingeids', ())
+        if self.eid in pendingeids:
+            return
+        entity = session.entity_from_eid(self.eid)
+        # check custom workflow has not been rechanged to another one in the same
+        # transaction
+        mainwf = entity.main_workflow
+        if mainwf.eid == self.wfeid:
+            deststate = mainwf.initial
+            if not deststate:
+                msg = session._('workflow has no initial state')
+                raise ValidationError(entity.eid, {'custom_workflow': msg})
+            if mainwf.state_by_eid(entity.current_state.eid):
+                # nothing to do
+                return
+            # if there are no history, simply go to new workflow's initial state
+            if not entity.workflow_history:
+                if entity.current_state.eid != deststate.eid:
+                    _change_state(session, entity.eid,
+                                  entity.current_state.eid, deststate.eid)
+                return
+            msg = session._('workflow changed to "%s"')
+            msg %= session._(mainwf.name)
+            session.transaction_data[(entity.eid, 'customwf')] = self.wfeid
+            entity.change_state(deststate, msg, u'text/plain')
+
+
+class _CheckTrExitPoint(hook.Operation):
+
+    def precommit_event(self):
+        tr = self.session.entity_from_eid(self.treid)
+        outputs = set()
+        for ep in tr.subworkflow_exit:
+            if ep.subwf_state.eid in outputs:
+                msg = self.session._("can't have multiple exits on the same state")
+                raise ValidationError(self.treid, {'subworkflow_exit': msg})
+            outputs.add(ep.subwf_state.eid)
+
+
+# hooks ########################################################################
+
+class WorkflowHook(hook.Hook):
+    __abstract__ = True
+    category = 'worfklow'
+
+
+class SetInitialStateHook(WorkflowHook):
+    __regid__ = 'wfsetinitial'
+    __select__ = WorkflowHook.__select__ & entity_implements(IWorkflowable)
+    events = ('after_add_entity',)
+
+    def __call__(self):
+        _SetInitialStateOp(self._cw, entity=self.entity)
+
+
+class PrepareStateChangeHook(WorkflowHook):
+    """record previous state information"""
+    __regid__ = 'cwdelstate'
+    __select__ = WorkflowHook.__select__ & hook.match_rtype('in_state')
+    events = ('before_delete_relation',)
+
+    def __call__(self):
+        self._cw.transaction_data.setdefault('pendingrelations', []).append(
+            (self.eidfrom, self.rtype, self.eidto))
+
+
+class FireTransitionHook(WorkflowHook):
+    """check the transition is allowed, add missing information. Expect that:
+    * wf_info_for inlined relation is set
+    * by_transition or to_state (managers only) inlined relation is set
+    """
+    __regid__ = 'wffiretransition'
+    __select__ = WorkflowHook.__select__ & entity_implements('TrInfo')
+    events = ('before_add_entity',)
+
+    def __call__(self):
+        session = self._cw
+        entity = self.entity
+        # first retreive entity to which the state change apply
+        try:
+            foreid = entity['wf_info_for']
+        except KeyError:
+            msg = session._('mandatory relation')
+            raise ValidationError(entity.eid, {'wf_info_for': msg})
+        forentity = session.entity_from_eid(foreid)
+        # then check it has a workflow set, unless we're in the process of changing
+        # entity's workflow
+        if session.transaction_data.get((forentity.eid, 'customwf')):
+            wfeid = session.transaction_data[(forentity.eid, 'customwf')]
+            wf = session.entity_from_eid(wfeid)
+        else:
+            wf = forentity.current_workflow
+        if wf is None:
+            msg = session._('related entity has no workflow set')
+            raise ValidationError(entity.eid, {None: msg})
+        # then check it has a state set
+        fromstate = forentity.current_state
+        if fromstate is None:
+            msg = session._('related entity has no state')
+            raise ValidationError(entity.eid, {None: msg})
+        # True if we are coming back from subworkflow
+        swtr = session.transaction_data.pop((forentity.eid, 'subwfentrytr'), None)
+        cowpowers = session.is_super_session or 'managers' in session.user.groups
+        # no investigate the requested state change...
+        try:
+            treid = entity['by_transition']
+        except KeyError:
+            # no transition set, check user is a manager and destination state is
+            # specified (and valid)
+            if not cowpowers:
+                msg = session._('mandatory relation')
+                raise ValidationError(entity.eid, {'by_transition': msg})
+            deststateeid = entity.get('to_state')
+            if not deststateeid:
+                msg = session._('mandatory relation')
+                raise ValidationError(entity.eid, {'by_transition': msg})
+            deststate = wf.state_by_eid(deststateeid)
+            if not cowpowers and deststate is None:
+                msg = session._("state doesn't belong to entity's workflow")
+                raise ValidationError(entity.eid, {'to_state': 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:
+                if tr is None:
+                    msg = session._("transition doesn't belong to entity's workflow")
+                    raise ValidationError(entity.eid, {'by_transition': msg})
+                if not tr.has_input_state(fromstate):
+                    msg = session._("transition isn't allowed")
+                    raise ValidationError(entity.eid, {'by_transition': msg})
+                if not tr.may_be_fired(foreid):
+                    msg = session._("transition may not be fired")
+                    raise ValidationError(entity.eid, {'by_transition': msg})
+            if entity.get('to_state'):
+                deststateeid = entity['to_state']
+                if not cowpowers and deststateeid != tr.destination().eid:
+                    msg = session._("transition isn't allowed")
+                    raise ValidationError(entity.eid, {'by_transition': msg})
+                if swtr is None:
+                    deststate = session.entity_from_eid(deststateeid)
+                    if not cowpowers and deststate is None:
+                        msg = session._("state doesn't belong to entity's workflow")
+                        raise ValidationError(entity.eid, {'to_state': msg})
+            else:
+                deststateeid = tr.destination().eid
+        # everything is ok, add missing information on the trinfo entity
+        entity['from_state'] = fromstate.eid
+        entity['to_state'] = deststateeid
+        nocheck = session.transaction_data.setdefault('skip-security', set())
+        nocheck.add((entity.eid, 'from_state', fromstate.eid))
+        nocheck.add((entity.eid, 'to_state', deststateeid))
+        FireAutotransitionOp(session, entity=forentity)
+
+
+class FiredTransitionHook(WorkflowHook):
+    """change related entity state"""
+    __regid__ = 'wffiretransition'
+    __select__ = WorkflowHook.__select__ & entity_implements('TrInfo')
+    events = ('after_add_entity',)
+
+    def __call__(self):
+        session = self._cw
+        entity = self.entity
+        _change_state(session, entity['wf_info_for'],
+                      entity['from_state'], entity['to_state'])
+        forentity = session.entity_from_eid(entity['wf_info_for'])
+        assert forentity.current_state.eid == entity['to_state']
+        if forentity.main_workflow.eid != forentity.current_workflow.eid:
+            # we're in a subworkflow, check if we've reached an exit point
+            wftr = forentity.subworkflow_input_transition()
+            if wftr is None:
+                # inconsistency detected
+                msg = session._("state doesn't belong to entity's current workflow")
+                raise ValidationError(entity.eid, {'to_state': msg})
+            tostate = wftr.get_exit_point(entity['to_state'])
+            if tostate is not None:
+                # reached an exit point
+                msg = session._('exiting from subworkflow %s')
+                msg %= session._(forentity.current_workflow.name)
+                session.transaction_data[(forentity.eid, 'subwfentrytr')] = True
+                # XXX iirk
+                req = forentity._cw
+                forentity._cw = session.super_session
+                try:
+                    trinfo = forentity.change_state(tostate, msg, u'text/plain',
+                                                    tr=wftr)
+                finally:
+                    forentity._cw = req
+
+
+class CheckInStateChangeAllowed(WorkflowHook):
+    """check state apply, in case of direct in_state change using unsafe_execute
+    """
+    __regid__ = 'wfcheckinstate'
+    __select__ = WorkflowHook.__select__ & hook.match_rtype('in_state')
+    events = ('before_add_relation',)
+
+    def __call__(self):
+        session = self._cw
+        nocheck = session.transaction_data.setdefault('skip-security', ())
+        if (self.eidfrom, 'in_state', self.eidto) in nocheck:
+            # state changed through TrInfo insertion, so we already know it's ok
+            return
+        entity = session.entity_from_eid(self.eidfrom)
+        mainwf = entity.main_workflow
+        if mainwf is None:
+            msg = session._('entity has no workflow set')
+            raise ValidationError(entity.eid, {None: msg})
+        for wf in mainwf.iter_workflows():
+            if wf.state_by_eid(self.eidto):
+                break
+        else:
+            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, {'in_state': msg})
+        if entity.current_workflow and wf.eid != entity.current_workflow.eid:
+            msg = session._("state doesn't belong to entity's current workflow")
+            raise ValidationError(self.eidfrom, {'in_state': msg})
+
+
+class SetModificationDateOnStateChange(WorkflowHook):
+    """update entity's modification date after changing its state"""
+    __regid__ = 'wfsyncmdate'
+    __select__ = WorkflowHook.__select__ & hook.match_rtype('in_state')
+    events = ('after_add_relation',)
+
+    def __call__(self):
+        if self._cw.added_in_transaction(self.eidfrom):
+            # new entity, not needed
+            return
+        entity = self._cw.entity_from_eid(self.eidfrom)
+        try:
+            entity.set_attributes(modification_date=datetime.now(),
+                                  _cw_unsafe=True)
+        except RepositoryError, ex:
+            # usually occurs if entity is coming from a read-only source
+            # (eg ldap user)
+            self.warning('cant change modification date for %s: %s', entity, ex)
+
+
+class CheckWorkflowTransitionExitPoint(WorkflowHook):
+    """check that there is no multiple exits from the same state"""
+    __regid__ = 'wfcheckwftrexit'
+    __select__ = WorkflowHook.__select__ & hook.match_rtype('subworkflow_exit')
+    events = ('after_add_relation',)
+
+    def __call__(self):
+        _CheckTrExitPoint(self._cw, treid=self.eidfrom)
+
+
+class SetCustomWorkflow(WorkflowHook):
+    __regid__ = 'wfsetcustom'
+    __select__ = WorkflowHook.__select__ & hook.match_rtype('custom_workflow')
+    events = ('after_add_relation',)
+
+    def __call__(self):
+        _WorkflowChangedOp(self._cw, eid=self.eidfrom, wfeid=self.eidto)
+
+
+class DelCustomWorkflow(SetCustomWorkflow):
+    __regid__ = 'wfdelcustom'
+    events = ('after_delete_relation',)
+
+    def __call__(self):
+        entity = self._cw.entity_from_eid(self.eidfrom)
+        typewf = entity.cwetype_workflow()
+        if typewf is not None:
+            _WorkflowChangedOp(self._cw, eid=self.eidfrom, wfeid=typewf.eid)
+
+
+
+class DelWorkflowHook(WorkflowHook):
+    __regid__ = 'wfdel'
+    __select__ = WorkflowHook.__select__ & entity_implements('Workflow')
+    events = ('after_delete_entity',)
+
+    def __call__(self):
+        # cleanup unused state and transition
+        self._cw.execute('DELETE State X WHERE NOT X state_of Y')
+        self._cw.execute('DELETE Transition X WHERE NOT X transition_of Y')
+
--- a/misc/migration/bootstrapmigration_repository.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/misc/migration/bootstrapmigration_repository.py	Wed Sep 30 18:57:42 2009 +0200
@@ -11,11 +11,9 @@
 applcubicwebversion, cubicwebversion = versions_map['cubicweb']
 
 if applcubicwebversion < (3, 4, 0) and cubicwebversion >= (3, 4, 0):
-    from cubicweb import RepositoryError
-    from cubicweb.server.hooks import uniquecstrcheck_before_modification
+
     session.set_shared_data('do-not-insert-cwuri', True)
-    repo.hm.unregister_hook(uniquecstrcheck_before_modification, 'before_add_entity', '')
-    repo.hm.unregister_hook(uniquecstrcheck_before_modification, 'before_update_entity', '')
+    deactivate_verification_hooks()
     add_relation_type('cwuri')
     base_url = session.base_url()
     # use an internal session since some entity might forbid modifications to admin
@@ -26,8 +24,7 @@
             isession.execute('SET X cwuri %(u)s WHERE X eid %(x)s',
                              {'x': eid, 'u': base_url + u'eid/%s' % eid})
     isession.commit()
-    repo.hm.register_hook(uniquecstrcheck_before_modification, 'before_add_entity', '')
-    repo.hm.register_hook(uniquecstrcheck_before_modification, 'before_update_entity', '')
+    reactivate_verification_hooks()
     session.set_shared_data('do-not-insert-cwuri', False)
 
 if applcubicwebversion < (3, 5, 0) and cubicwebversion >= (3, 5, 0):
@@ -38,14 +35,22 @@
     # drop explicit 'State allowed_transition Transition' since it should be
     # infered due to yams inheritance.  However we've to disable the schema
     # sync hook first to avoid to destroy existing data...
-    from cubicweb.server.schemahooks import after_del_relation_type
-    repo.hm.unregister_hook(after_del_relation_type,
-                            'after_delete_relation', 'relation_type')
     try:
-        drop_relation_definition('State', 'allowed_transition', 'Transition')
-    finally:
-        repo.hm.register_hook(after_del_relation_type,
-                              'after_delete_relation', 'relation_type')
+        from cubicweb.hooks import syncschema
+        repo.vreg.unregister(syncschema.AfterDelRelationTypeHook)
+        try:
+            drop_relation_definition('State', 'allowed_transition', 'Transition')
+        finally:
+            repo.vreg.register(syncschema.AfterDelRelationTypeHook)
+    except ImportError: # syncschema is in CW >= 3.6 only
+        from cubicweb.server.schemahooks import after_del_relation_type
+        repo.hm.unregister_hook(after_del_relation_type,
+                                'after_delete_relation', 'relation_type')
+        try:
+            drop_relation_definition('State', 'allowed_transition', 'Transition')
+        finally:
+            repo.hm.register_hook(after_del_relation_type,
+                                  'after_delete_relation', 'relation_type')
     schema.rebuild_infered_relations() # need to be explicitly called once everything is in place
 
     for et in rql('DISTINCT Any ET,ETN WHERE S state_of ET, ET name ETN',
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/req.py	Wed Sep 30 18:57:42 2009 +0200
@@ -0,0 +1,324 @@
+"""Base class for request/session
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: Library General Public License version 2 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from urllib import quote as urlquote, unquote as urlunquote
+from datetime import time, datetime, timedelta
+
+from logilab.common.decorators import cached
+
+from cubicweb import Unauthorized, typed_eid
+from cubicweb.rset import ResultSet
+from cubicweb.utils import ustrftime, strptime, todate, todatetime
+
+ONESECOND = timedelta(0, 1, 0)
+CACHE_REGISTRY = {}
+
+
+class Cache(dict):
+    def __init__(self):
+        super(Cache, self).__init__()
+        _now = datetime.now()
+        self.cache_creation_date = _now
+        self.latest_cache_lookup = _now
+
+
+class RequestSessionBase(object):
+    """base class containing stuff shared by server session and web request
+
+    request/session is the main resources accessor, mainly through it's vreg
+    attribute:
+    :vreg:
+      the instance's registry
+    :vreg.schema:
+      the instance's schema
+    :vreg.config:
+      the instance's configuration
+    """
+    def __init__(self, vreg):
+        self.vreg = vreg
+        try:
+            encoding = vreg.property_value('ui.encoding')
+        except: # no vreg or property not registered
+            encoding = 'utf-8'
+        self.encoding = encoding
+        # cache result of execution for (rql expr / eids),
+        # should be emptied on commit/rollback of the server session / web
+        # connection
+        self.local_perm_cache = {}
+        self._ = unicode
+
+    def property_value(self, key):
+        """return value of the property with the given key, giving priority to
+        user specific value if any, else using site value
+        """
+        if self.user:
+            return self.user.property_value(key)
+        return self.vreg.property_value(key)
+
+    def etype_rset(self, etype, size=1):
+        """return a fake result set for a particular entity type"""
+        rset = ResultSet([('A',)]*size, '%s X' % etype,
+                         description=[(etype,)]*size)
+        def get_entity(row, col=0, etype=etype, req=self, rset=rset):
+            return req.vreg.etype_class(etype)(req, rset, row, col)
+        rset.get_entity = get_entity
+        return self.decorate_rset(rset)
+
+    def eid_rset(self, eid, etype=None):
+        """return a result set for the given eid without doing actual query
+        (we have the eid, we can suppose it exists and user has access to the
+        entity)
+        """
+        eid = typed_eid(eid)
+        if etype is None:
+            etype = self.describe(eid)[0]
+        rset = ResultSet([(eid,)], 'Any X WHERE X eid %(x)s', {'x': eid},
+                         [(etype,)])
+        return self.decorate_rset(rset)
+
+    def empty_rset(self):
+        """return a result set for the given eid without doing actual query
+        (we have the eid, we can suppose it exists and user has access to the
+        entity)
+        """
+        return self.decorate_rset(ResultSet([], 'Any X WHERE X eid -1'))
+
+    def entity_from_eid(self, eid, etype=None):
+        """return an entity instance for the given eid. No query is done"""
+        try:
+            return self.entity_cache(eid)
+        except KeyError:
+            rset = self.eid_rset(eid, etype)
+            entity = rset.get_entity(0, 0)
+            self.set_entity_cache(entity)
+            return entity
+
+    def entity_cache(self, eid):
+        raise KeyError
+
+    def set_entity_cache(self, entity):
+        pass
+
+    # XXX move to CWEntityManager or even better as factory method (unclear
+    # where yet...)
+    def create_entity(self, etype, *args, **kwargs):
+        """add a new entity of the given type"""
+        rql = 'INSERT %s X' % etype
+        relations = []
+        restrictions = []
+        cachekey = []
+        for rtype, rvar in args:
+            relations.append('X %s %s' % (rtype, rvar))
+            restrictions.append('%s eid %%(%s)s' % (rvar, rvar))
+            cachekey.append(rvar)
+        for attr in kwargs:
+            if attr in cachekey:
+                continue
+            relations.append('X %s %%(%s)s' % (attr, attr))
+        if relations:
+            rql = '%s: %s' % (rql, ', '.join(relations))
+        if restrictions:
+            rql = '%s WHERE %s' % (rql, ', '.join(restrictions))
+        return self.execute(rql, kwargs, cachekey).get_entity(0, 0)
+
+    def ensure_ro_rql(self, rql):
+        """raise an exception if the given rql is not a select query"""
+        first = rql.split(' ', 1)[0].lower()
+        if first in ('insert', 'set', 'delete'):
+            raise Unauthorized(self._('only select queries are authorized'))
+
+    def get_cache(self, cachename):
+        """
+        NOTE: cachename should be dotted names as in :
+        - cubicweb.mycache
+        - cubes.blog.mycache
+        - etc.
+        """
+        if cachename in CACHE_REGISTRY:
+            cache = CACHE_REGISTRY[cachename]
+        else:
+            cache = CACHE_REGISTRY[cachename] = Cache()
+        _now = datetime.now()
+        if _now > cache.latest_cache_lookup + ONESECOND:
+            ecache = self.execute(
+                'Any C,T WHERE C is CWCache, C name %(name)s, C timestamp T',
+                {'name':cachename}).get_entity(0,0)
+            cache.latest_cache_lookup = _now
+            if not ecache.valid(cache.cache_creation_date):
+                cache.clear()
+                cache.cache_creation_date = _now
+        return cache
+
+    # url generation methods ##################################################
+
+    def build_url(self, *args, **kwargs):
+        """return an absolute URL using params dictionary key/values as URL
+        parameters. Values are automatically URL quoted, and the
+        publishing method to use may be specified or will be guessed.
+        """
+        # use *args since we don't want first argument to be "anonymous" to
+        # avoid potential clash with kwargs
+        if args:
+            assert len(args) == 1, 'only 0 or 1 non-named-argument expected'
+            method = args[0]
+        else:
+            method = None
+        # XXX I (adim) think that if method is passed explicitly, we should
+        #     not try to process it and directly call req.build_url()
+        if method is None:
+            if self.from_controller() == 'view' and not '_restpath' in kwargs:
+                method = self.relative_path(includeparams=False) or 'view'
+            else:
+                method = 'view'
+        base_url = kwargs.pop('base_url', None)
+        if base_url is None:
+            base_url = self.base_url()
+        if '_restpath' in kwargs:
+            assert method == 'view', method
+            path = kwargs.pop('_restpath')
+        else:
+            path = method
+        if not kwargs:
+            return u'%s%s' % (base_url, path)
+        return u'%s%s?%s' % (base_url, path, self.build_url_params(**kwargs))
+
+
+    def build_url_params(self, **kwargs):
+        """return encoded params to incorporate them in an URL"""
+        args = []
+        for param, values in kwargs.items():
+            if not isinstance(values, (list, tuple)):
+                values = (values,)
+            for value in values:
+                args.append(u'%s=%s' % (param, self.url_quote(value)))
+        return '&'.join(args)
+
+    def url_quote(self, value, safe=''):
+        """urllib.quote is not unicode safe, use this method to do the
+        necessary encoding / decoding. Also it's designed to quote each
+        part of a url path and so the '/' character will be encoded as well.
+        """
+        if isinstance(value, unicode):
+            quoted = urlquote(value.encode(self.encoding), safe=safe)
+            return unicode(quoted, self.encoding)
+        return urlquote(str(value), safe=safe)
+
+    def url_unquote(self, quoted):
+        """returns a unicode unquoted string
+
+        decoding is based on `self.encoding` which is the encoding
+        used in `url_quote`
+        """
+        if isinstance(quoted, unicode):
+            quoted = quoted.encode(self.encoding)
+        try:
+            return unicode(urlunquote(quoted), self.encoding)
+        except UnicodeDecodeError: # might occurs on manually typed URLs
+            return unicode(urlunquote(quoted), 'iso-8859-1')
+
+    # bound user related methods ###############################################
+
+    @cached
+    def user_data(self):
+        """returns a dictionnary with this user's information"""
+        userinfo = {}
+        if self.is_internal_session:
+            userinfo['login'] = "cubicweb"
+            userinfo['name'] = "cubicweb"
+            userinfo['email'] = ""
+            return userinfo
+        user = self.actual_session().user
+        userinfo['login'] = user.login
+        userinfo['name'] = user.name()
+        userinfo['email'] = user.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',
+             **kwargs):
+        """shortcut to self.vreg.view method avoiding to pass the request"""
+        return self.vreg[__registry].render(__vid, self, __fallback_oid,
+                                            rset=rset, **kwargs)
+
+    def format_date(self, date, date_format=None, time=False):
+        """return a string for a date time according to instance's
+        configuration
+        """
+        if date:
+            if date_format is None:
+                if time:
+                    date_format = self.property_value('ui.datetime-format')
+                else:
+                    date_format = self.property_value('ui.date-format')
+            return ustrftime(date, date_format)
+        return u''
+
+    def format_time(self, time):
+        """return a string for a time according to instance's
+        configuration
+        """
+        if time:
+            return ustrftime(time, self.property_value('ui.time-format'))
+        return u''
+
+    def format_float(self, num):
+        """return a string for floating point number according to instance's
+        configuration
+        """
+        if num:
+            return self.property_value('ui.float-format') % num
+        return u''
+
+    def parse_datetime(self, value, etype='Datetime'):
+        """get a datetime or time from a string (according to etype)
+        Datetime formatted as Date are accepted
+        """
+        assert etype in ('Datetime', 'Date', 'Time'), etype
+        # XXX raise proper validation error
+        if etype == 'Datetime':
+            format = self.property_value('ui.datetime-format')
+            try:
+                return todatetime(strptime(value, format))
+            except ValueError:
+                pass
+        elif etype == 'Time':
+            format = self.property_value('ui.time-format')
+            try:
+                # (adim) I can't find a way to parse a Time with a custom format
+                date = strptime(value, format) # this returns a DateTime
+                return time(date.hour, date.minute, date.second)
+            except ValueError:
+                raise ValueError('can\'t parse %r (expected %s)' % (value, format))
+        try:
+            format = self.property_value('ui.date-format')
+            dt = strptime(value, format)
+            if etype == 'Datetime':
+                return todatetime(dt)
+            return todate(dt)
+        except ValueError:
+            raise ValueError('can\'t parse %r (expected %s)' % (value, format))
+
+    # abstract methods to override according to the web front-end #############
+
+    def base_url(self):
+        """return the root url of the instance"""
+        raise NotImplementedError
+
+    def decorate_rset(self, rset):
+        """add vreg/req (at least) attributes to the given result set """
+        raise NotImplementedError
+
+    def describe(self, eid):
+        """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
+        raise NotImplementedError
--- a/rset.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/rset.py	Wed Sep 30 18:57:42 2009 +0200
@@ -9,7 +9,7 @@
 
 from logilab.common.decorators import cached, clear_cache, copy_cache
 
-from rql import nodes
+from rql import nodes, stmts
 
 from cubicweb import NotAnEntity
 
@@ -83,7 +83,7 @@
         try:
             return self._rsetactions[key]
         except KeyError:
-            actions = self.vreg['actions'].possible_vobjects(
+            actions = self.vreg['actions'].poss_visible_objects(
                 self.req, rset=self, **kwargs)
             self._rsetactions[key] = actions
             return actions
@@ -240,8 +240,6 @@
                 rset = mapping[key]
             rset.rows.append(self.rows[idx])
             rset.description.append(self.description[idx])
-
-
         for rset in result:
             rset.rowcount = len(rset.rows)
         if return_dict:
@@ -249,6 +247,51 @@
         else:
             return result
 
+    def limited_rql(self):
+        """return a printable rql for the result set associated to the object,
+        with limit/offset correctly set according to maximum page size and
+        currently displayed page when necessary
+        """
+        # try to get page boundaries from the navigation component
+        # XXX we should probably not have a ref to this component here (eg in
+        #     cubicweb.common)
+        nav = self.vreg['components'].select_or_none('navigation', self.req,
+                                                     rset=self)
+        if nav:
+            start, stop = nav.page_boundaries()
+            rql = self._limit_offset_rql(stop - start, start)
+        # result set may have be limited manually in which case navigation won't
+        # apply
+        elif self.limited:
+            rql = self._limit_offset_rql(*self.limited)
+        # navigation component doesn't apply and rset has not been limited, no
+        # need to limit query
+        else:
+            rql = self.printable_rql()
+        return rql
+
+    def _limit_offset_rql(self, limit, offset):
+        rqlst = self.syntax_tree()
+        if len(rqlst.children) == 1:
+            select = rqlst.children[0]
+            olimit, ooffset = select.limit, select.offset
+            select.limit, select.offset = limit, offset
+            rql = rqlst.as_string(kwargs=self.args)
+            # restore original limit/offset
+            select.limit, select.offset = olimit, ooffset
+        else:
+            newselect = stmts.Select()
+            newselect.limit = limit
+            newselect.offset = offset
+            aliases = [nodes.VariableRef(newselect.get_variable(vref.name, i))
+                       for i, vref in enumerate(rqlst.selection)]
+            newselect.set_with([nodes.SubQuery(aliases, rqlst)], check=False)
+            newunion = stmts.Union()
+            newunion.append(newselect)
+            rql = rqlst.as_string(kwargs=self.args)
+            rqlst.parent = None
+        return rql
+
     def limit(self, limit, offset=0, inplace=False):
         """limit the result set to the given number of rows optionaly starting
         from an index different than 0
@@ -279,7 +322,7 @@
             # we also have to fix/remove from the request entity cache entities
             # which get a wrong rset reference by this limit call
             for entity in self.req.cached_entities():
-                if entity.rset is self:
+                if entity.cw_rset is self:
                     if offset <= entity.row < stop:
                         entity.row = entity.row - offset
                     else:
@@ -318,6 +361,14 @@
             if self.rows[i][col] is not None:
                 yield self.get_entity(i, col)
 
+    def complete_entity(self, row, col=0, skip_bytes=True):
+        """short cut to get an completed entity instance for a particular
+        row (all instance's attributes have been fetched)
+        """
+        entity = self.get_entity(row, col)
+        entity.complete(skip_bytes=skip_bytes)
+        return entity
+
     @cached
     def get_entity(self, row, col=None):
         """special method for query retreiving a single entity, returns a
@@ -335,7 +386,7 @@
         """
         if col is None:
             from warnings import warn
-            msg = 'col parameter will become mandatory in future version'
+            msg = '[3.2] col parameter will become mandatory in future version'
             warn(msg, DeprecationWarning, stacklevel=3)
             col = 0
         etype = self.description[row][col]
@@ -371,16 +422,17 @@
         #     new attributes found in this resultset ?
         try:
             entity = req.entity_cache(eid)
-            if entity.rset is None:
-                # entity has no rset set, this means entity has been cached by
-                # the repository (req is a repository session) which had no rset
-                # info. Add id.
-                entity.rset = self
-                entity.row = row
-                entity.col = col
-            return entity
         except KeyError:
             pass
+        else:
+            if entity.cw_rset is None:
+                # entity has no rset set, this means entity has been created by
+                # the querier (req is a repository session) and so jas no rset
+                # info. Add it.
+                entity.cw_rset = self
+                entity.cw_row = row
+                entity.cw_col = col
+            return entity
         # build entity instance
         etype = self.description[row][col]
         entity = self.vreg['etypes'].etype_class(etype)(req, rset=self,
--- a/rtags.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/rtags.py	Wed Sep 30 18:57:42 2009 +0200
@@ -15,6 +15,9 @@
 def register_rtag(rtag):
     RTAGS.append(rtag)
 
+def _ensure_str_key(key):
+    return tuple(str(k) for k in key)
+
 class RelationTags(object):
     """a tag store for full relation definitions :
 
@@ -25,12 +28,14 @@
     This class associates a single tag to each key.
     """
     _allowed_values = None
+    _initfunc = None
     def __init__(self, name=None, initfunc=None, allowed_values=None):
         self._name = name or '<unknown>'
         self._tagdefs = {}
         if allowed_values is not None:
             self._allowed_values = allowed_values
-        self._initfunc = initfunc
+        if initfunc is not None:
+            self._initfunc = initfunc
         register_rtag(self)
 
     def __repr__(self):
@@ -45,22 +50,22 @@
         self._tagdefs.clear()
 
     def _get_keys(self, stype, rtype, otype, tagged):
-        keys = [(rtype, tagged, '*', '*'),
-                (rtype, tagged, '*', otype),
-                (rtype, tagged, stype, '*'),
-                (rtype, tagged, stype, otype)]
+        keys = [('*', rtype, '*', tagged),
+                ('*', rtype, otype, tagged),
+                (stype, rtype, '*', tagged),
+                (stype, rtype, otype, tagged)]
         if stype == '*' or otype == '*':
-            keys.remove((rtype, tagged, '*', '*'))
+            keys.remove( ('*', rtype, '*', tagged) )
             if stype == '*':
-                keys.remove((rtype, tagged, '*', otype))
+                keys.remove( ('*', rtype, otype, tagged) )
             if otype == '*':
-                keys.remove((rtype, tagged, stype, '*'))
+                keys.remove( (stype, rtype, '*', tagged) )
         return keys
 
     def init(self, schema, check=True):
         # XXX check existing keys against schema
         if check:
-            for (rtype, tagged, stype, otype), value in self._tagdefs.items():
+            for (stype, rtype, otype, tagged), value in self._tagdefs.items():
                 for ertype in (stype, rtype, otype):
                     if ertype != '*' and not ertype in schema:
                         self.warning('removing rtag %s: %s, %s undefined in schema',
@@ -79,40 +84,36 @@
 
     # rtag declaration api ####################################################
 
-    def tag_attribute(self, key, tag):
+    def tag_attribute(self, key, *args, **kwargs):
         key = list(key)
         key.append('*')
-        self.tag_subject_of(key, tag)
+        self.tag_subject_of(key, *args, **kwargs)
 
-    def tag_subject_of(self, key, tag):
+    def tag_subject_of(self, key, *args, **kwargs):
         key = list(key)
         key.append('subject')
-        self.tag_relation(key, tag)
+        self.tag_relation(key, *args, **kwargs)
 
-    def tag_object_of(self, key, tag):
+    def tag_object_of(self, key, *args, **kwargs):
         key = list(key)
         key.append('object')
-        self.tag_relation(key, tag)
+        self.tag_relation(key, *args, **kwargs)
 
     def tag_relation(self, key, tag):
-        #if isinstance(key, basestring):
-        #    stype, rtype, otype = key.split()
-        #else:
-        stype, rtype, otype, tagged = [str(k) for k in key]
         if self._allowed_values is not None:
             assert tag in self._allowed_values, \
                    '%r is not an allowed tag (should be in %s)' % (
                 tag, self._allowed_values)
-        self._tagdefs[(rtype, tagged, stype, otype)] = tag
+        self._tagdefs[_ensure_str_key(key)] = tag
         return tag
 
     # rtag runtime api ########################################################
 
-    def del_rtag(self, stype, rtype, otype, tagged):
-        del self._tagdefs[(rtype, tagged, stype, otype)]
+    def del_rtag(self, *key):
+        del self._tagdefs[key]
 
-    def get(self, stype, rtype, otype, tagged):
-        for key in reversed(self._get_keys(stype, rtype, otype, tagged)):
+    def get(self, *key):
+        for key in reversed(self._get_keys(*key)):
             try:
                 return self._tagdefs[key]
             except KeyError:
@@ -132,8 +133,7 @@
     tag_container_cls = set
 
     def tag_relation(self, key, tag):
-        stype, rtype, otype, tagged = [str(k) for k in key]
-        rtags = self._tagdefs.setdefault((rtype, tagged, stype, otype),
+        rtags = self._tagdefs.setdefault(_ensure_str_key(key),
                                          self.tag_container_cls())
         rtags.add(tag)
         return rtags
@@ -153,24 +153,24 @@
     tag_container_cls = dict
 
     def tag_relation(self, key, tag):
-        stype, rtype, otype, tagged = [str(k) for k in key]
+        key = _ensure_str_key(key)
         try:
-            rtags = self._tagdefs[(rtype, tagged, stype, otype)]
+            rtags = self._tagdefs[key]
             rtags.update(tag)
             return rtags
         except KeyError:
-            self._tagdefs[(rtype, tagged, stype, otype)] = tag
+            self._tagdefs[key] = tag
             return tag
 
     def setdefault(self, key, tagkey, tagvalue):
-        stype, rtype, otype, tagged = [str(k) for k in key]
+        key = _ensure_str_key(key)
         try:
-            rtags = self._tagdefs[(rtype, tagged, stype, otype)]
+            rtags = self._tagdefs[key]
             rtags.setdefault(tagkey, tagvalue)
             return rtags
         except KeyError:
-            self._tagdefs[(rtype, tagged, stype, otype)] = {tagkey: tagvalue}
-            return self._tagdefs[(rtype, tagged, stype, otype)]
+            self._tagdefs[key] = {tagkey: tagvalue}
+            return self._tagdefs[key]
 
 
 class RelationTagsBool(RelationTags):
--- a/schema.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/schema.py	Wed Sep 30 18:57:42 2009 +0200
@@ -117,13 +117,13 @@
     else:
         return unicode(req._(key)).lower()
 
-__builtins__['display_name'] = deprecated('display_name should be imported from cubicweb.schema')(display_name)
+__builtins__['display_name'] = deprecated('[3.4] display_name should be imported from cubicweb.schema')(display_name)
 
-def ERSchema_display_name(self, req, form=''):
+def ERSchema_display_name(self, req, form='', context=None):
     """return a internationalized string for the entity/relation type name in
     a given form
     """
-    return display_name(req, self.type, form)
+    return display_name(req, self.type, form, context)
 ERSchema.display_name = ERSchema_display_name
 
 @cached
@@ -959,12 +959,12 @@
 
 @monkeypatch(FormatConstraint)
 def vocabulary(self, entity=None, form=None):
-    req = None
+    cw = None
     if form is None and entity is not None:
-        req = entity.req
+        cw = entity._cw
     elif form is not None:
-        req = form.req
-    if req is not None and req.user.has_permission(PERM_USE_TEMPLATE_FORMAT):
+        cw = form._cw
+    if cw is not None and cw.user.has_permission(PERM_USE_TEMPLATE_FORMAT):
         return self.regular_formats + tuple(NEED_PERM_FORMATS)
     return self.regular_formats
 
@@ -999,5 +999,6 @@
 
 # XXX deprecated
 from yams.constraints import format_constraint
+format_constraint = deprecated('[3.4] use RichString instead of format_constraint')(format_constraint)
 from yams.buildobjs import RichString
 PyFileReader.context['format_constraint'] = format_constraint
--- a/schemas/base.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/schemas/base.py	Wed Sep 30 18:57:42 2009 +0200
@@ -134,31 +134,7 @@
     subject = '*'
     object = 'String'
 
-
-class CWProperty(EntityType):
-    """used for cubicweb configuration. Once a property has been created you
-    can't change the key.
-    """
-    permissions = {
-        'read':   ('managers', 'users', 'guests'),
-        'add':    ('managers', 'users',),
-        'update': ('managers', 'owners',),
-        'delete': ('managers', 'owners',),
-        }
-    # key is a reserved word for mysql
-    pkey = String(required=True, internationalizable=True, maxsize=256,
-                  description=_('defines what\'s the property is applied for. '
-                                'You must select this first to be able to set '
-                                'value'))
-    value = String(internationalizable=True, maxsize=256)
-
-    for_user = SubjectRelation('CWUser', cardinality='?*', composite='object',
-                               description=_('user for which this property is '
-                                             'applying. If this relation is not '
-                                             'set, the property is considered as'
-                                             ' a global property'))
-
-
+# XXX find a better relation name
 class for_user(RelationType):
     """link a property to the user which want this property customization. Unless
     you're a site manager, this relation will be handled automatically.
@@ -169,7 +145,10 @@
         'delete': ('managers',),
         }
     inlined = True
-
+    subject = 'CWProperty'
+    object = 'CWUser'
+    composite = 'object'
+    cardinality = '?*'
 
 class CWPermission(EntityType):
     """entity type that may be used to construct some advanced security configuration
--- a/schemas/bootstrap.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/schemas/bootstrap.py	Wed Sep 30 18:57:42 2009 +0200
@@ -173,6 +173,23 @@
 
 
 
+class CWProperty(EntityType):
+    """used for cubicweb configuration. Once a property has been created you
+    can't change the key.
+    """
+    permissions = {
+        'read':   ('managers', 'users', 'guests'),
+        'add':    ('managers', 'users',),
+        'update': ('managers', 'owners',),
+        'delete': ('managers', 'owners',),
+        }
+    # key is a reserved word for mysql
+    pkey = String(required=True, internationalizable=True, maxsize=256,
+                  description=_('defines what\'s the property is applied for. '
+                                'You must select this first to be able to set '
+                                'value'))
+    value = String(internationalizable=True, maxsize=256)
+
 class relation_type(RelationType):
     """link a relation definition to its relation type"""
     permissions = META_RTYPE_PERMS
--- a/schemas/workflow.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/schemas/workflow.py	Wed Sep 30 18:57:42 2009 +0200
@@ -163,10 +163,12 @@
 class state_of(RelationType):
     """link a state to one or more workflow"""
     permissions = META_RTYPE_PERMS
+    inlined = True
 
 class transition_of(RelationType):
     """link a transition to one or more workflow"""
     permissions = META_RTYPE_PERMS
+    inlined = True
 
 class subworkflow(RelationType):
     """link a transition to one or more workflow"""
--- a/selectors.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/selectors.py	Wed Sep 30 18:57:42 2009 +0200
@@ -46,7 +46,6 @@
 from warnings import warn
 
 from logilab.common.compat import all
-from logilab.common.deprecation import deprecated
 from logilab.common.interface import implements as implements_iface
 
 from yams import BASE_TYPES
@@ -55,6 +54,7 @@
                       role, typed_eid)
 # even if not used, let yes here so it's importable through this module
 from cubicweb.appobject import Selector, objectify_selector, yes
+from cubicweb.vregistry import class_regid
 from cubicweb.cwconfig import CubicWebConfiguration
 from cubicweb.schema import split_expression
 
@@ -75,7 +75,7 @@
         else:
             selname = selector.__name__
             vobj = cls
-        oid = vobj.id
+        oid = class_regid(vobj)
         ret = selector(cls, *args, **kwargs)
         if TRACED_OIDS == 'all' or oid in TRACED_OIDS:
             #SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls)
@@ -113,13 +113,13 @@
         return traceback is None
 
 
-def score_interface(cls_or_inst, cls, iface):
+def score_interface(etypesreg, cls_or_inst, cls, iface):
     """Return XXX if the give object (maybe an instance or class) implements
     the interface.
     """
     if getattr(iface, '__registry__', None) == 'etypes':
         # adjust score if the interface is an entity class
-        parents = cls_or_inst.parent_classes()
+        parents = etypesreg.parent_classes(cls_or_inst.__regid__)
         if iface is cls:
             return len(parents) + 4
         if iface is parents[-1]: # Any
@@ -158,17 +158,18 @@
         return '%s(%s)' % (self.__class__.__name__,
                            ','.join(str(s) for s in self.expected_ifaces))
 
-    def score_interfaces(self, cls_or_inst, cls):
+    def score_interfaces(self, req, cls_or_inst, cls):
         score = 0
-        vreg, eschema = cls_or_inst.vreg, cls_or_inst.e_schema
+        etypesreg = req.vreg['etypes']
+        eschema = cls_or_inst.e_schema
         for iface in self.expected_ifaces:
             if isinstance(iface, basestring):
                 # entity type
                 try:
-                    iface = vreg['etypes'].etype_class(iface)
+                    iface = etypesreg.etype_class(iface)
                 except KeyError:
                     continue # entity type not in the schema
-            score += score_interface(cls_or_inst, cls, iface)
+            score += score_interface(etypesreg, cls_or_inst, cls, iface)
         return score
 
 
@@ -216,7 +217,7 @@
     def score(self, cls, req, etype):
         if etype in BASE_TYPES:
             return 0
-        return self.score_class(cls.vreg['etypes'].etype_class(etype), req)
+        return self.score_class(req.vreg['etypes'].etype_class(etype), req)
 
     def score_class(self, eclass, req):
         raise NotImplementedError()
@@ -430,7 +431,8 @@
     * context (`basestring`) is matching the context property value for the
       given cls
     """
-    propval = req.property_value('%s.%s.context' % (cls.__registry__, cls.id))
+    propval = req.property_value('%s.%s.context' % (cls.__registry__,
+                                                    cls.__regid__))
     if not propval:
         propval = cls.context
     if context is not None and propval and context != propval:
@@ -545,7 +547,7 @@
     """
     @lltrace
     def __call__(self, cls, req, rset=None, row=None, col=0, view=None, **kwargs):
-        if view is None or not view.id in self.expected:
+        if view is None or not view.__regid__ in self.expected:
             return 0
         return 1
 
@@ -563,7 +565,7 @@
 
     def __call__(self, cls, req, **kwargs):
         try:
-            cls.vreg[self.registry].select(self.oid, req, **kwargs)
+            req.vreg[self.registry].select(self.oid, req, **kwargs)
             return 1
         except NoSelectableObject:
             return 0
@@ -587,7 +589,7 @@
           proximity so the most specific object'll be selected
     """
     def score_class(self, eclass, req):
-        return self.score_interfaces(eclass, eclass)
+        return self.score_interfaces(req, eclass, eclass)
 
 
 class specified_etype_implements(implements):
@@ -617,11 +619,11 @@
                 # only check this is a known type if etype comes from req.form,
                 # else we want the error to propagate
                 try:
-                    etype = cls.vreg.case_insensitive_etypes[etype.lower()]
+                    etype = req.vreg.case_insensitive_etypes[etype.lower()]
                     req.form['etype'] = etype
                 except KeyError:
                     return 0
-        return self.score_class(cls.vreg['etypes'].etype_class(etype), req)
+        return self.score_class(req.vreg['etypes'].etype_class(etype), req)
 
 
 class entity_implements(ImplementsMixIn, EntitySelector):
@@ -640,7 +642,7 @@
           proximity so the most specific object'll be selected
     """
     def score_entity(self, entity):
-        return self.score_interfaces(entity, entity.__class__)
+        return self.score_interfaces(entity._cw, entity, entity.__class__)
 
 
 class relation_possible(EClassSelector):
@@ -667,7 +669,7 @@
 
     @lltrace
     def __call__(self, cls, req, *args, **kwargs):
-        rschema = cls.schema.rschema(self.rtype)
+        rschema = req.vreg.schema.rschema(self.rtype)
         if not (rschema.has_perm(req, self.action)
                 or rschema.has_local_role(self.action)):
             return 0
@@ -745,9 +747,9 @@
     def score_entity(self, entity):
         rschema = entity.schema.rschema(self.rtype)
         if self.role == 'subject':
-            if not rschema.has_perm(entity.req, 'add', fromeid=entity.eid):
+            if not rschema.has_perm(entity._cw, 'add', fromeid=entity.eid):
                 return 0
-        elif not rschema.has_perm(entity.req, 'add', toeid=entity.eid):
+        elif not rschema.has_perm(entity._cw, 'add', toeid=entity.eid):
             return 0
         return 1
 
@@ -798,7 +800,7 @@
 
     def score_entity(self, entity):
         relpossel = relation_possible(self.rtype, self.role, self.target_etype)
-        if not relpossel.score_class(entity.__class__, entity.req):
+        if not relpossel.score_class(entity.__class__, entity._cw):
             return 0
         rset = entity.related(self.rtype, self.role)
         if self.target_etype:
@@ -860,7 +862,7 @@
         if row is None:
             score = 0
             need_local_check = []
-            geteschema = cls.schema.eschema
+            geteschema = req.vreg.schema.eschema
             for etype in rset.column_types(0):
                 if etype in BASE_TYPES:
                     return 0
@@ -898,7 +900,7 @@
     See `EClassSelector` documentation for behaviour when row is not specified.
     """
     def score(self, cls, req, etype):
-        eschema = cls.schema.eschema(etype)
+        eschema = req.vreg.schema.eschema(etype)
         if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
                and eschema.has_perm(req, 'add'):
             return 1
@@ -974,200 +976,3 @@
                 return score
             return 1
         self.score_entity = intscore
-
-
-# XXX DEPRECATED ##############################################################
-from cubicweb.vregistry import chainall
-
-yes_selector = deprecated()(yes)
-norset_selector = deprecated()(none_rset)
-rset_selector = deprecated()(any_rset)
-anyrset_selector = deprecated()(nonempty_rset)
-emptyrset_selector = deprecated()(empty_rset)
-onelinerset_selector = deprecated()(one_line_rset)
-twolinerset_selector = deprecated()(two_lines_rset)
-twocolrset_selector = deprecated()(two_cols_rset)
-largerset_selector = deprecated()(paginated_rset)
-sortedrset_selector = deprecated()(sorted_rset)
-oneetyperset_selector = deprecated()(one_etype_rset)
-multitype_selector = deprecated()(two_etypes_rset)
-anonymous_selector = deprecated()(anonymous_user)
-not_anonymous_selector = deprecated()(authenticated_user)
-primaryview_selector = deprecated()(primary_view)
-contextprop_selector = deprecated()(match_context_prop)
-
-@deprecated('use non_final_entity instead of %s')
-def nfentity_selector(cls, req, rset=None, row=None, col=0, **kwargs):
-    return non_final_entity()(cls, req, rset, row, col)
-
-@deprecated('use implements instead of %s')
-def implement_interface(cls, req, rset=None, row=None, col=0, **kwargs):
-    return implements(*cls.accepts_interfaces)(cls, req, rset, row, col)
-_interface_selector = deprecated()(implement_interface)
-interface_selector = deprecated()(implement_interface)
-
-@deprecated('use specified_etype_implements instead of %s')
-def accept_etype(cls, req, *args, **kwargs):
-    """check etype presence in request form *and* accepts conformance"""
-    return specified_etype_implements(*cls.accepts)(cls, req, *args)
-etype_form_selector = accept_etype
-
-@deprecated('use match_search_state instead of %s')
-def searchstate_selector(cls, req, rset=None, row=None, col=0, **kwargs):
-    return match_search_state(cls.search_states)(cls, req, rset, row, col)
-
-@deprecated('use match_user_groups instead of %s')
-def match_user_group(cls, req, rset=None, row=None, col=0, **kwargs):
-    return match_user_groups(*cls.require_groups)(cls, req, rset, row, col, **kwargs)
-in_group_selector = match_user_group
-
-@deprecated('use relation_possible instead of %s')
-def has_relation(cls, req, rset=None, row=None, col=0, **kwargs):
-    return relation_possible(cls.rtype, role(cls), cls.etype,
-                             getattr(cls, 'require_permission', 'read'))(cls, req, rset, row, col, **kwargs)
-
-@deprecated('use relation_possible instead of %s')
-def one_has_relation(cls, req, rset=None, row=None, col=0, **kwargs):
-    return relation_possible(cls.rtype, role(cls), cls.etype,
-                             getattr(cls, 'require_permission', 'read',
-                                     once_is_enough=True))(cls, req, rset, row, col, **kwargs)
-
-@deprecated('use implements instead of %s')
-def accept_rset(cls, req, rset=None, row=None, col=0, **kwargs):
-    """simply delegate to cls.accept_rset method"""
-    return implements(*cls.accepts)(cls, req, rset, row=row, col=col)
-accept_rset_selector = accept_rset
-
-accept = chainall(non_final_entity(), accept_rset, name='accept')
-accept = deprecated('use implements selector')(accept)
-accept_selector = deprecated()(accept)
-
-accept_one = deprecated()(chainall(one_line_rset, accept,
-                                   name='accept_one'))
-accept_one_selector = deprecated()(accept_one)
-
-
-def _rql_condition(cls, req, rset=None, row=None, col=0, **kwargs):
-    if cls.condition:
-        return rql_condition(cls.condition)(cls, req, rset, row, col)
-    return 1
-_rqlcondition_selector = deprecated()(_rql_condition)
-
-rqlcondition_selector = deprecated()(chainall(non_final_entity(), one_line_rset, _rql_condition,
-                         name='rql_condition'))
-
-@deprecated('use but_etype instead of %s')
-def but_etype_selector(cls, req, rset=None, row=None, col=0, **kwargs):
-    return but_etype(cls.etype)(cls, req, rset, row, col)
-
-@lltrace
-def etype_rtype_selector(cls, req, rset=None, row=None, col=0, **kwargs):
-    schema = cls.schema
-    perm = getattr(cls, 'require_permission', 'read')
-    if hasattr(cls, 'etype'):
-        eschema = schema.eschema(cls.etype)
-        if not (eschema.has_perm(req, perm) or eschema.has_local_role(perm)):
-            return 0
-    if hasattr(cls, 'rtype'):
-        rschema = schema.rschema(cls.rtype)
-        if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)):
-            return 0
-    return 1
-etype_rtype_selector = deprecated()(etype_rtype_selector)
-
-#req_form_params_selector = deprecated()(match_form_params) # form_params
-#kwargs_selector = deprecated()(match_kwargs) # expected_kwargs
-
-# compound selectors ##########################################################
-
-searchstate_accept = chainall(nonempty_rset(), accept,
-                              name='searchstate_accept')
-searchstate_accept_selector = deprecated()(searchstate_accept)
-
-searchstate_accept_one = chainall(one_line_rset, accept, _rql_condition,
-                                  name='searchstate_accept_one')
-searchstate_accept_one_selector = deprecated()(searchstate_accept_one)
-
-searchstate_accept = deprecated()(searchstate_accept)
-searchstate_accept_one = deprecated()(searchstate_accept_one)
-
-# end of deprecation section ##################################################
-
-def unbind_method(selector):
-    def new_selector(registered):
-        # get the unbound method
-        if hasattr(registered, 'im_func'):
-            registered = registered.im_func
-        # don't rebind since it will be done automatically during
-        # the assignment, inside the destination class body
-        return selector(registered)
-    new_selector.__name__ = selector.__name__
-    return new_selector
-
-
-def deprecate(registered, msg):
-    # get the unbound method
-    if hasattr(registered, 'im_func'):
-        registered = registered.im_func
-    def _deprecate(cls, vreg):
-        warn(msg, DeprecationWarning)
-        return registered(cls, vreg)
-    return _deprecate
-
-@unbind_method
-def require_group_compat(registered):
-    def plug_selector(cls, vreg):
-        cls = registered(cls, vreg)
-        if getattr(cls, 'require_groups', None):
-            warn('use "match_user_groups(group1, group2)" instead of using require_groups',
-                 DeprecationWarning)
-            cls.__select__ &= match_user_groups(cls.require_groups)
-        return cls
-    return plug_selector
-
-@unbind_method
-def accepts_compat(registered):
-    def plug_selector(cls, vreg):
-        cls = registered(cls, vreg)
-        if getattr(cls, 'accepts', None):
-            warn('use "implements("EntityType", IFace)" instead of using accepts on %s'
-                 % cls,
-                 DeprecationWarning)
-            cls.__select__ &= implements(*cls.accepts)
-        return cls
-    return plug_selector
-
-@unbind_method
-def accepts_etype_compat(registered):
-    def plug_selector(cls, vreg):
-        cls = registered(cls, vreg)
-        if getattr(cls, 'accepts', None):
-            warn('use "specified_etype_implements("EntityType", IFace)" instead of using accepts',
-                 DeprecationWarning)
-            cls.__select__ &= specified_etype_implements(*cls.accepts)
-        return cls
-    return plug_selector
-
-@unbind_method
-def condition_compat(registered):
-    def plug_selector(cls, vreg):
-        cls = registered(cls, vreg)
-        if getattr(cls, 'condition', None):
-            warn('use "use rql_condition(expression)" instead of using condition',
-                 DeprecationWarning)
-            cls.__select__ &= rql_condition(cls.condition)
-        return cls
-    return plug_selector
-
-@unbind_method
-def has_relation_compat(registered):
-    def plug_selector(cls, vreg):
-        cls = registered(cls, vreg)
-        if getattr(cls, 'etype', None):
-            warn('use relation_possible selector instead of using etype_rtype',
-                 DeprecationWarning)
-            cls.__select__ &= relation_possible(cls.rtype, role(cls),
-                                                getattr(cls, 'etype', None),
-                                                action=getattr(cls, 'require_permission', 'read'))
-        return cls
-    return plug_selector
--- a/server/checkintegrity.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/checkintegrity.py	Wed Sep 30 18:57:42 2009 +0200
@@ -68,8 +68,6 @@
     """reindex all entities in the repository"""
     # deactivate modification_date hook since we don't want them
     # to be updated due to the reindexation
-    from cubicweb.server.hooks import (setmtime_before_update_entity,
-                                       uniquecstrcheck_before_modification)
     from cubicweb.server.repository import FTIndexEntityOp
     repo = session.repo
     cursor = session.pool['system']
@@ -80,10 +78,8 @@
         # XXX indexer.init_fti(cursor) once index 0.7 is out
         indexer.init_extensions(cursor)
         cursor.execute(indexer.sql_init_fti())
-    repo.hm.unregister_hook(setmtime_before_update_entity,
-                            'before_update_entity', '')
-    repo.hm.unregister_hook(uniquecstrcheck_before_modification,
-                            'before_update_entity', '')
+    repo.config.disabled_hooks_categories.add('metadata')
+    repo.config.disabled_hooks_categories.add('integrity')
     repo.do_fti = True  # ensure full-text indexation is activated
     etypes = set()
     for eschema in schema.entities():
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/hook.py	Wed Sep 30 18:57:42 2009 +0200
@@ -0,0 +1,447 @@
+"""Hooks management
+
+This module defined the `Hook` class and registry and a set of abstract classes
+for operations.
+
+
+Hooks are called before / after any individual update of entities / relations
+in the repository and on special events such as server startup or shutdown.
+
+
+Operations may be registered by hooks during a transaction, which will  be
+fired when the pool is commited or rollbacked.
+
+
+Entity hooks (eg before_add_entity, after_add_entity, before_update_entity,
+after_update_entity, before_delete_entity, after_delete_entity) all have an
+`entity` attribute
+
+Relation (eg before_add_relation, after_add_relation, before_delete_relation,
+after_delete_relation) all have `eidfrom`, `rtype`, `eidto` attributes.
+
+Server start/stop hooks (eg server_startup, server_shutdown) have a `repo`
+attribute, but *their `_cw` attribute is None*.
+
+Backup/restore hooks (eg server_backup, server_restore) have a `repo` and a
+`timestamp` attributes, but *their `_cw` attribute is None*.
+
+Session hooks (eg session_open, session_close) have no special attribute.
+
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from warnings import warn
+from logging import getLogger
+
+from logilab.common.decorators import classproperty
+from logilab.common.deprecation import deprecated
+from logilab.common.logging_ext import set_log_methods
+
+from cubicweb.cwvreg import CWRegistry, VRegistry
+from cubicweb.selectors import (objectify_selector, lltrace, match_search_state,
+                                entity_implements)
+from cubicweb.appobject import AppObject
+
+
+ENTITIES_HOOKS = set(('before_add_entity',    'after_add_entity',
+                      'before_update_entity', 'after_update_entity',
+                      'before_delete_entity', 'after_delete_entity'))
+RELATIONS_HOOKS = set(('before_add_relation',   'after_add_relation' ,
+                       'before_delete_relation','after_delete_relation'))
+SYSTEM_HOOKS = set(('server_backup', 'server_restore',
+                    'server_startup', 'server_shutdown',
+                    'session_open', 'session_close'))
+ALL_HOOKS = ENTITIES_HOOKS | RELATIONS_HOOKS | SYSTEM_HOOKS
+
+
+class HooksRegistry(CWRegistry):
+
+    def register(self, obj, **kwargs):
+        try:
+            iter(obj.events)
+        except AttributeError:
+            raise
+        except:
+            raise Exception('bad .events attribute %s on %s.%s' % (
+                obj.events, obj.__module__, obj.__name__))
+        for event in obj.events:
+            if event not in ALL_HOOKS:
+                raise Exception('bad event %s on %s.%s' % (
+                    event, obj.__module__, obj.__name__))
+        super(HooksRegistry, self).register(obj, **kwargs)
+
+    def call_hooks(self, event, req=None, **kwargs):
+        kwargs['event'] = event
+        for hook in sorted(self.possible_objects(req, **kwargs), key=lambda x: x.order):
+            if hook.enabled:
+                hook()
+            else:
+                warn('[3.6] %s: enabled is deprecated' % cls)
+
+VRegistry.REGISTRY_FACTORY['hooks'] = HooksRegistry
+
+
+# some hook specific selectors #################################################
+
+@objectify_selector
+@lltrace
+def match_event(cls, req, **kwargs):
+    if kwargs.get('event') in cls.events:
+        return 1
+    return 0
+
+@objectify_selector
+@lltrace
+def enabled_category(cls, req, **kwargs):
+    if req is None:
+        # server startup / shutdown event
+        config = kwargs['repo'].config
+    else:
+        config = req.vreg.config
+    if cls.category in config.disabled_hooks_categories:
+        return 0
+    return 1
+
+@objectify_selector
+@lltrace
+def regular_session(cls, req, **kwargs):
+    if req is None or req.is_super_session:
+        return 0
+    return 1
+
+class match_rtype(match_search_state):
+    """accept if parameters specified as initializer arguments are specified
+    in named arguments given to the selector
+
+    :param *expected: parameters (eg `basestring`) which are expected to be
+                      found in named arguments (kwargs)
+    """
+
+    @lltrace
+    def __call__(self, cls, req, *args, **kwargs):
+        return kwargs.get('rtype') in self.expected
+
+
+# base class for hook ##########################################################
+
+class Hook(AppObject):
+    __registry__ = 'hooks'
+    __select__ = match_event() & enabled_category()
+    # set this in derivated classes
+    events = None
+    category = None
+    order = 0
+    # XXX deprecated
+    enabled = True
+
+    @classproperty
+    def __regid__(cls):
+        warn('[3.6] %s.%s: please specify an id for your hook'
+             % (cls.__module__, cls.__name__), DeprecationWarning)
+        return str(id(cls))
+
+    @classmethod
+    def __registered__(cls, vreg):
+        super(Hook, cls).__registered__(vreg)
+        if getattr(cls, 'accepts', None):
+            warn('[3.6] %s.%s: accepts is deprecated, define proper __select__'
+                 % (cls.__module__, cls.__name__), DeprecationWarning)
+            rtypes = []
+            for ertype in cls.accepts:
+                if ertype.islower():
+                    rtypes.append(ertype)
+                else:
+                    cls.__select__ = cls.__select__ & entity_implements(ertype)
+            if rtypes:
+                cls.__select__ = cls.__select__ & match_rtype(*rtypes)
+        return cls
+
+    known_args = set(('entity', 'rtype', 'eidfrom', 'eidto', 'repo', 'timestamp'))
+    def __init__(self, req, event, **kwargs):
+        for arg in self.known_args:
+            if arg in kwargs:
+                setattr(self, arg, kwargs.pop(arg))
+        super(Hook, self).__init__(req, **kwargs)
+        self.event = event
+
+    def __call__(self):
+        if hasattr(self, 'call'):
+            cls = self.__class__
+            warn('[3.6] %s.%s: call is deprecated, implements __call__'
+                 % (cls.__module__, cls.__name__), DeprecationWarning)
+            if self.event.endswith('_relation'):
+                self.call(self._cw, self.eidfrom, self.rtype, self.eidto)
+            elif 'delete' in self.event:
+                self.call(self._cw, self.entity.eid)
+            elif self.event.startswith('server_'):
+                self.call(self.repo)
+            elif self.event.startswith('session_'):
+                self.call(self._cw)
+            else:
+                self.call(self._cw, self.entity)
+
+set_log_methods(Hook, getLogger('cubicweb.hook'))
+
+
+# base classes for relation propagation ########################################
+
+class PropagateSubjectRelationHook(Hook):
+    """propagate permissions and nosy list when new entity are added"""
+    events = ('after_add_relation',)
+    # to set in concrete class
+    rtype = None
+    subject_relations = None
+    object_relations = None
+    accepts = None # subject_relations + object_relations
+
+    def call(self, session, fromeid, rtype, toeid):
+        for eid in (fromeid, toeid):
+            etype = session.describe(eid)[0]
+            if not self.schema.eschema(etype).has_subject_relation(self.rtype):
+                return
+        if rtype in self.subject_relations:
+            meid, seid = fromeid, toeid
+        else:
+            assert rtype in self.object_relations
+            meid, seid = toeid, fromeid
+        session.unsafe_execute(
+            'SET E %s P WHERE X %s P, X eid %%(x)s, E eid %%(e)s, NOT E %s P'\
+            % (self.rtype, self.rtype, self.rtype),
+            {'x': meid, 'e': seid}, ('x', 'e'))
+
+
+class PropagateSubjectRelationAddHook(Hook):
+    """propagate on existing entities when a permission or nosy list is added"""
+    events = ('after_add_relation',)
+    # to set in concrete class
+    rtype = None
+    subject_relations = None
+    object_relations = None
+    accepts = None # (self.rtype,)
+
+    def call(self, session, fromeid, rtype, toeid):
+        eschema = self.schema.eschema(session.describe(fromeid)[0])
+        execute = session.unsafe_execute
+        for rel in self.subject_relations:
+            if eschema.has_subject_relation(rel):
+                execute('SET R %s P WHERE X eid %%(x)s, P eid %%(p)s, '
+                        'X %s R, NOT R %s P' % (rtype, rel, rtype),
+                        {'x': fromeid, 'p': toeid}, 'x')
+        for rel in self.object_relations:
+            if eschema.has_object_relation(rel):
+                execute('SET R %s P WHERE X eid %%(x)s, P eid %%(p)s, '
+                        'R %s X, NOT R %s P' % (rtype, rel, rtype),
+                        {'x': fromeid, 'p': toeid}, 'x')
+
+
+class PropagateSubjectRelationDelHook(Hook):
+    """propagate on existing entities when a permission is deleted"""
+    events = ('after_delete_relation',)
+    # to set in concrete class
+    rtype = None
+    subject_relations = None
+    object_relations = None
+    accepts = None # (self.rtype,)
+
+    def call(self, session, fromeid, rtype, toeid):
+        eschema = self.schema.eschema(session.describe(fromeid)[0])
+        execute = session.unsafe_execute
+        for rel in self.subject_relations:
+            if eschema.has_subject_relation(rel):
+                execute('DELETE R %s P WHERE X eid %%(x)s, P eid %%(p)s, '
+                        'X %s R' % (rtype, rel),
+                        {'x': fromeid, 'p': toeid}, 'x')
+        for rel in self.object_relations:
+            if eschema.has_object_relation(rel):
+                execute('DELETE R %s P WHERE X eid %%(x)s, P eid %%(p)s, '
+                        'R %s X' % (rtype, rel),
+                        {'x': fromeid, 'p': toeid}, 'x')
+
+
+# abstract classes for operation ###############################################
+
+class Operation(object):
+    """an operation is triggered on connections pool events related to
+    commit / rollback transations. Possible events are:
+
+    precommit:
+      the pool is preparing to commit. You shouldn't do anything things which
+      has to be reverted if the commit fail at this point, but you can freely
+      do any heavy computation or raise an exception if the commit can't go.
+      You can add some new operation during this phase but their precommit
+      event won't be triggered
+
+    commit:
+      the pool is preparing to commit. You should avoid to do to expensive
+      stuff or something that may cause an exception in this event
+
+    revertcommit:
+      if an operation failed while commited, this event is triggered for
+      all operations which had their commit event already to let them
+      revert things (including the operation which made fail the commit)
+
+    rollback:
+      the transaction has been either rollbacked either
+      * intentionaly
+      * a precommit event failed, all operations are rollbacked
+      * a commit event failed, all operations which are not been triggered for
+        commit are rollbacked
+
+    order of operations may be important, and is controlled according to:
+    * operation's class
+    """
+
+    def __init__(self, session, **kwargs):
+        self.session = session
+        self.__dict__.update(kwargs)
+        self.register(session)
+        # execution information
+        self.processed = None # 'precommit', 'commit'
+        self.failed = False
+
+    def register(self, session):
+        session.add_operation(self, self.insert_index())
+
+    def insert_index(self):
+        """return the index of  the lastest instance which is not a
+        LateOperation instance
+        """
+        for i, op in enumerate(self.session.pending_operations):
+            if isinstance(op, (LateOperation, SingleLastOperation)):
+                return i
+        return None
+
+    def handle_event(self, event):
+        """delegate event handling to the opertaion"""
+        getattr(self, event)()
+
+    def precommit_event(self):
+        """the observed connections pool is preparing a commit"""
+
+    def revertprecommit_event(self):
+        """an error went when pre-commiting this operation or a later one
+
+        should revert pre-commit's changes but take care, they may have not
+        been all considered if it's this operation which failed
+        """
+
+    def commit_event(self):
+        """the observed connections pool is commiting"""
+
+    def revertcommit_event(self):
+        """an error went when commiting this operation or a later one
+
+        should revert commit's changes but take care, they may have not
+        been all considered if it's this operation which failed
+        """
+
+    def rollback_event(self):
+        """the observed connections pool has been rollbacked
+
+        do nothing by default, the operation will just be removed from the pool
+        operation list
+        """
+
+    @property
+    @deprecated('[3.6] use self.session.user')
+    def user(self):
+        return self.session.user
+
+    @property
+    @deprecated('[3.6] use self.session.repo')
+    def repo(self):
+        return self.session.repo
+
+    @property
+    @deprecated('[3.6] use self.session.vreg.schema')
+    def schema(self):
+        return self.session.repo.schema
+
+    @property
+    @deprecated('[3.6] use self.session.vreg.config')
+    def config(self):
+        return self.session.repo.config
+
+set_log_methods(Operation, getLogger('cubicweb.session'))
+
+
+class LateOperation(Operation):
+    """special operation which should be called after all possible (ie non late)
+    operations
+    """
+    def insert_index(self):
+        """return the index of  the lastest instance which is not a
+        SingleLastOperation instance
+        """
+        for i, op in enumerate(self.session.pending_operations):
+            if isinstance(op, SingleLastOperation):
+                return i
+        return None
+
+
+class SingleOperation(Operation):
+    """special operation which should be called once"""
+    def register(self, session):
+        """override register to handle cases where this operation has already
+        been added
+        """
+        operations = session.pending_operations
+        index = self.equivalent_index(operations)
+        if index is not None:
+            equivalent = operations.pop(index)
+        else:
+            equivalent = None
+        session.add_operation(self, self.insert_index())
+        return equivalent
+
+    def equivalent_index(self, operations):
+        """return the index of the equivalent operation if any"""
+        equivalents = [i for i, op in enumerate(operations)
+                       if op.__class__ is self.__class__]
+        if equivalents:
+            return equivalents[0]
+        return None
+
+
+class SingleLastOperation(SingleOperation):
+    """special operation which should be called once and after all other
+    operations
+    """
+    def insert_index(self):
+        return None
+
+
+class SendMailOp(SingleLastOperation):
+    def __init__(self, session, msg=None, recipients=None, **kwargs):
+        # may not specify msg yet, as
+        # `cubicweb.sobjects.supervision.SupervisionMailOp`
+        if msg is not None:
+            assert recipients
+            self.to_send = [(msg, recipients)]
+        else:
+            assert recipients is None
+            self.to_send = []
+        super(SendMailOp, self).__init__(session, **kwargs)
+
+    def register(self, session):
+        previous = super(SendMailOp, self).register(session)
+        if previous:
+            self.to_send = previous.to_send + self.to_send
+
+    def commit_event(self):
+        self.session.repo.threaded_task(self.sendmails)
+
+    def sendmails(self):
+        self.session.vreg.config.sendmails(self.to_send)
+
+
+class RQLPrecommitOperation(Operation):
+    def precommit_event(self):
+        execute = self.session.unsafe_execute
+        for rql in self.rqls:
+            execute(*rql)
--- a/server/hookhelper.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/hookhelper.py	Wed Sep 30 18:57:42 2009 +0200
@@ -7,83 +7,19 @@
 """
 __docformat__ = "restructuredtext en"
 
-from cubicweb import RepositoryError
-from cubicweb.server.pool import SingleLastOperation
-
-
-def entity_name(session, eid):
-    """return the "name" attribute of the entity with the given eid"""
-    return entity_attr(session, eid, 'name')
-
-def entity_attr(session, eid, attr):
-    """return an arbitrary attribute of the entity with the given eid"""
-    return getattr(session.entity_from_eid(eid), attr)
+from logilab.common.deprecation import deprecated, class_moved
 
-def rproperty(session, rtype, eidfrom, eidto, rprop):
-    rschema = session.repo.schema[rtype]
-    subjtype = session.describe(eidfrom)[0]
-    objtype = session.describe(eidto)[0]
-    return rschema.rproperty(subjtype, objtype, rprop)
-
-def check_internal_entity(session, eid, internal_names):
-    """check that the entity's name is not in the internal_names list.
-    raise a RepositoryError if so, else return the entity's name
-    """
-    name = entity_name(session, eid)
-    if name in internal_names:
-        raise RepositoryError('%s entity can\'t be deleted' % name)
-    return name
-
-def get_user_sessions(repo, ueid):
-    for session in repo._sessions.values():
-        if ueid == session.user.eid:
-            yield session
+from cubicweb import RepositoryError
 
 
-# mail related ################################################################
-
-class SendMailOp(SingleLastOperation):
-    def __init__(self, session, msg=None, recipients=None, **kwargs):
-        # may not specify msg yet, as
-        # `cubicweb.sobjects.supervision.SupervisionMailOp`
-        if msg is not None:
-            assert recipients
-            self.to_send = [(msg, recipients)]
-        else:
-            assert recipients is None
-            self.to_send = []
-        super(SendMailOp, self).__init__(session, **kwargs)
-
-    def register(self, session):
-        previous = super(SendMailOp, self).register(session)
-        if previous:
-            self.to_send = previous.to_send + self.to_send
-
-    def commit_event(self):
-        self.repo.threaded_task(self.sendmails)
+@deprecated('[3.6] entity_name is deprecated, use entity.name')
+def entity_name(session, eid):
+    """return the "name" attribute of the entity with the given eid"""
+    return session.entity_from_eid(eid).name
 
-    def sendmails(self):
-        self.config.sendmails(self.to_send)
-
-
-# state related ###############################################################
+@deprecated('[3.6] rproperty is deprecated, use session.schema_rproperty')
+def rproperty(session, rtype, eidfrom, eidto, rprop):
+    return session.rproperty(rtype, eidfrom, eidto, rprop)
 
-def previous_state(session, eid):
-    """return the state of the entity with the given eid,
-    usually since it's changing in the current transaction. Due to internal
-    relation hooks, the relation may has been deleted at this point, so
-    we have handle that
-    """
-    # don't check eid in session.transaction_data.get('neweids', ()), we don't
-    # want to miss previous state of entity whose state change in the same
-    # transaction as it's being created
-    pending = session.transaction_data.get('pendingrelations', ())
-    for eidfrom, rtype, eidto in reversed(pending):
-        if rtype == 'in_state' and eidfrom == eid:
-            rset = session.execute('Any S,N WHERE S eid %(x)s, S name N',
-                                   {'x': eidto}, 'x')
-            return rset.get_entity(0, 0)
-    rset = session.execute('Any S,N WHERE X eid %(x)s, X in_state S, S name N',
-                           {'x': eid}, 'x')
-    if rset:
-        return rset.get_entity(0, 0)
+from cubicweb.server.hook import SendMailOp
+SendMailOp = class_moved(SendMailOp)
--- a/server/hooks.py	Wed Sep 30 17:57:02 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,796 +0,0 @@
-"""Core hooks: check schema validity, unsure we are not deleting necessary
-entities...
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from datetime import datetime
-
-from cubicweb import UnknownProperty, ValidationError, BadConnectionId
-
-from cubicweb.server.pool import Operation, LateOperation, PreCommitOperation
-from cubicweb.server.hookhelper import (check_internal_entity, 
-                                        get_user_sessions, rproperty)
-from cubicweb.server.repository import FTIndexEntityOp
-
-# special relations that don't have to be checked for integrity, usually
-# because they are handled internally by hooks (so we trust ourselves)
-DONT_CHECK_RTYPES_ON_ADD = set(('owned_by', 'created_by',
-                                'is', 'is_instance_of',
-                                'wf_info_for', 'from_state', 'to_state'))
-DONT_CHECK_RTYPES_ON_DEL = set(('is', 'is_instance_of',
-                                'wf_info_for', 'from_state', 'to_state'))
-
-
-def relation_deleted(session, eidfrom, rtype, eidto):
-    session.transaction_data.setdefault('pendingrelations', []).append(
-        (eidfrom, rtype, eidto))
-
-def eschema_type_eid(session, etype):
-    """get eid of the CWEType entity for the given yams type"""
-    eschema = session.repo.schema.eschema(etype)
-    # eschema.eid is None if schema has been readen from the filesystem, not
-    # from the database (eg during tests)
-    if eschema.eid is None:
-        eschema.eid = session.unsafe_execute(
-            'Any X WHERE X is CWEType, X name %(name)s',
-            {'name': str(etype)})[0][0]
-    return eschema.eid
-
-
-# base meta-data handling ######################################################
-
-def setctime_before_add_entity(session, entity):
-    """before create a new entity -> set creation and modification date
-
-    this is a conveniency hook, you shouldn't have to disable it
-    """
-    timestamp = datetime.now()
-    entity.setdefault('creation_date', timestamp)
-    entity.setdefault('modification_date', timestamp)
-    if not session.get_shared_data('do-not-insert-cwuri'):
-        entity.setdefault('cwuri', u'%seid/%s' % (session.base_url(), entity.eid))
-
-
-def setmtime_before_update_entity(session, entity):
-    """update an entity -> set modification date"""
-    entity.setdefault('modification_date', datetime.now())
-
-
-class SetCreatorOp(PreCommitOperation):
-
-    def precommit_event(self):
-        session = self.session
-        if self.entity.eid in session.transaction_data.get('pendingeids', ()):
-            # entity have been created and deleted in the same transaction
-            return
-        if not self.entity.created_by:
-            session.add_relation(self.entity.eid, 'created_by', session.user.eid)
-
-
-def setowner_after_add_entity(session, entity):
-    """create a new entity -> set owner and creator metadata"""
-    asession = session.actual_session()
-    if not asession.is_internal_session:
-        session.add_relation(entity.eid, 'owned_by', asession.user.eid)
-        SetCreatorOp(asession, entity=entity)
-
-
-def setis_after_add_entity(session, entity):
-    """create a new entity -> set is relation"""
-    if hasattr(entity, '_cw_recreating'):
-        return
-    try:
-        session.add_relation(entity.eid, 'is',
-                             eschema_type_eid(session, entity.id))
-    except IndexError:
-        # during schema serialization, skip
-        return
-    # XXX < 2.50 bw compat
-    if not session.get_shared_data('do-not-insert-is_instance_of'):
-        for etype in entity.e_schema.ancestors() + [entity.e_schema]:
-            session.add_relation(entity.eid, 'is_instance_of',
-                                 eschema_type_eid(session, etype))
-
-
-def setowner_after_add_user(session, entity):
-    """when a user has been created, add owned_by relation on itself"""
-    session.add_relation(entity.eid, 'owned_by', entity.eid)
-
-
-def fti_update_after_add_relation(session, eidfrom, rtype, eidto):
-    """sync fulltext index when relevant relation is added. Reindexing the
-    contained entity is enough since it will implicitly reindex the container
-    entity.
-    """
-    ftcontainer = session.repo.schema.rschema(rtype).fulltext_container
-    if ftcontainer == 'subject':
-        FTIndexEntityOp(session, entity=session.entity_from_eid(eidto))
-    elif ftcontainer == 'object':
-        FTIndexEntityOp(session, entity=session.entity_from_eid(eidfrom))
-
-
-def fti_update_after_delete_relation(session, eidfrom, rtype, eidto):
-    """sync fulltext index when relevant relation is deleted. Reindexing both
-    entities is necessary.
-    """
-    if session.repo.schema.rschema(rtype).fulltext_container:
-        FTIndexEntityOp(session, entity=session.entity_from_eid(eidto))
-        FTIndexEntityOp(session, entity=session.entity_from_eid(eidfrom))
-
-
-class SyncOwnersOp(PreCommitOperation):
-
-    def precommit_event(self):
-        self.session.unsafe_execute('SET X owned_by U WHERE C owned_by U, C eid %(c)s,'
-                                    'NOT EXISTS(X owned_by U, X eid %(x)s)',
-                                    {'c': self.compositeeid, 'x': self.composedeid},
-                                    ('c', 'x'))
-
-
-def sync_owner_after_add_composite_relation(session, eidfrom, rtype, eidto):
-    """when adding composite relation, the composed should have the same owners
-    has the composite
-    """
-    if rtype == 'wf_info_for':
-        # skip this special composite relation # XXX (syt) why?
-        return
-    composite = rproperty(session, rtype, eidfrom, eidto, 'composite')
-    if composite == 'subject':
-        SyncOwnersOp(session, compositeeid=eidfrom, composedeid=eidto)
-    elif composite == 'object':
-        SyncOwnersOp(session, compositeeid=eidto, composedeid=eidfrom)
-
-
-def _register_metadata_hooks(hm):
-    """register meta-data related hooks on the hooks manager"""
-    hm.register_hook(setctime_before_add_entity, 'before_add_entity', '')
-    hm.register_hook(setmtime_before_update_entity, 'before_update_entity', '')
-    hm.register_hook(setowner_after_add_entity, 'after_add_entity', '')
-    hm.register_hook(sync_owner_after_add_composite_relation, 'after_add_relation', '')
-    hm.register_hook(fti_update_after_add_relation, 'after_add_relation', '')
-    hm.register_hook(fti_update_after_delete_relation, 'after_delete_relation', '')
-    if 'is' in hm.schema:
-        hm.register_hook(setis_after_add_entity, 'after_add_entity', '')
-    if 'CWUser' in hm.schema:
-        hm.register_hook(setowner_after_add_user, 'after_add_entity', 'CWUser')
-
-
-# core hooks ##################################################################
-
-class DelayedDeleteOp(PreCommitOperation):
-    """delete the object of composite relation except if the relation
-    has actually been redirected to another composite
-    """
-
-    def precommit_event(self):
-        session = self.session
-        # don't do anything if the entity is being created or deleted
-        if not (self.eid in session.transaction_data.get('pendingeids', ()) or
-                self.eid in session.transaction_data.get('neweids', ())):
-            etype = session.describe(self.eid)[0]
-            session.unsafe_execute('DELETE %s X WHERE X eid %%(x)s, NOT %s'
-                                   % (etype, self.relation),
-                                   {'x': self.eid}, 'x')
-
-
-def handle_composite_before_del_relation(session, eidfrom, rtype, eidto):
-    """delete the object of composite relation"""
-    composite = rproperty(session, rtype, eidfrom, eidto, 'composite')
-    if composite == 'subject':
-        DelayedDeleteOp(session, eid=eidto, relation='Y %s X' % rtype)
-    elif composite == 'object':
-        DelayedDeleteOp(session, eid=eidfrom, relation='X %s Y' % rtype)
-
-
-def before_del_group(session, eid):
-    """check that we don't remove the owners group"""
-    check_internal_entity(session, eid, ('owners',))
-
-
-# schema validation hooks #####################################################
-
-class CheckConstraintsOperation(LateOperation):
-    """check a new relation satisfy its constraints
-    """
-    def precommit_event(self):
-        eidfrom, rtype, eidto = self.rdef
-        # first check related entities have not been deleted in the same
-        # transaction
-        pending = self.session.transaction_data.get('pendingeids', ())
-        if eidfrom in pending:
-            return
-        if eidto in pending:
-            return
-        for constraint in self.constraints:
-            try:
-                constraint.repo_check(self.session, eidfrom, rtype, eidto)
-            except NotImplementedError:
-                self.critical('can\'t check constraint %s, not supported',
-                              constraint)
-
-    def commit_event(self):
-        pass
-
-
-def cstrcheck_after_add_relation(session, eidfrom, rtype, eidto):
-    """check the relation satisfy its constraints
-
-    this is delayed to a precommit time operation since other relation which
-    will make constraint satisfied may be added later.
-    """
-    constraints = rproperty(session, rtype, eidfrom, eidto, 'constraints')
-    if constraints:
-        CheckConstraintsOperation(session, constraints=constraints,
-                                  rdef=(eidfrom, rtype, eidto))
-
-def uniquecstrcheck_before_modification(session, entity):
-    eschema = entity.e_schema
-    for attr, val in entity.items():
-        if val is None:
-            continue
-        if eschema.subject_relation(attr).is_final() and \
-               eschema.has_unique_values(attr):
-            rql = '%s X WHERE X %s %%(val)s' % (entity.e_schema, attr)
-            rset = session.unsafe_execute(rql, {'val': val})
-            if rset and rset[0][0] != entity.eid:
-                msg = session._('the value "%s" is already used, use another one')
-                raise ValidationError(entity.eid, {attr: msg % val})
-
-
-class CheckRequiredRelationOperation(LateOperation):
-    """checking relation cardinality has to be done after commit in
-    case the relation is being replaced
-    """
-    eid, rtype = None, None
-
-    def precommit_event(self):
-        # recheck pending eids
-        if self.eid in self.session.transaction_data.get('pendingeids', ()):
-            return
-        if self.session.unsafe_execute(*self._rql()).rowcount < 1:
-            etype = self.session.describe(self.eid)[0]
-            _ = self.session._
-            msg = _('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)')
-            msg %= {'rtype': _(self.rtype), 'etype': _(etype), 'eid': self.eid}
-            raise ValidationError(self.eid, {self.rtype: msg})
-
-    def commit_event(self):
-        pass
-
-    def _rql(self):
-        raise NotImplementedError()
-
-
-class CheckSRelationOp(CheckRequiredRelationOperation):
-    """check required subject relation"""
-    def _rql(self):
-        return 'Any O WHERE S eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x'
-
-
-class CheckORelationOp(CheckRequiredRelationOperation):
-    """check required object relation"""
-    def _rql(self):
-        return 'Any S WHERE O eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x'
-
-
-def checkrel_if_necessary(session, opcls, rtype, eid):
-    """check an equivalent operation has not already been added"""
-    for op in session.pending_operations:
-        if isinstance(op, opcls) and op.rtype == rtype and op.eid == eid:
-            break
-    else:
-        opcls(session, rtype=rtype, eid=eid)
-
-
-def cardinalitycheck_after_add_entity(session, entity):
-    """check cardinalities are satisfied"""
-    eid = entity.eid
-    for rschema, targetschemas, x in entity.e_schema.relation_definitions():
-        # skip automatically handled relations
-        if rschema.type in DONT_CHECK_RTYPES_ON_ADD:
-            continue
-        if x == 'subject':
-            subjtype = entity.e_schema
-            objtype = targetschemas[0].type
-            cardindex = 0
-            opcls = CheckSRelationOp
-        else:
-            subjtype = targetschemas[0].type
-            objtype = entity.e_schema
-            cardindex = 1
-            opcls = CheckORelationOp
-        card = rschema.rproperty(subjtype, objtype, 'cardinality')
-        if card[cardindex] in '1+':
-            checkrel_if_necessary(session, opcls, rschema.type, eid)
-
-
-def cardinalitycheck_before_del_relation(session, eidfrom, rtype, eidto):
-    """check cardinalities are satisfied"""
-    if rtype in DONT_CHECK_RTYPES_ON_DEL:
-        return
-    card = rproperty(session, rtype, eidfrom, eidto, 'cardinality')
-    pendingrdefs = session.transaction_data.get('pendingrdefs', ())
-    if (session.describe(eidfrom)[0], rtype, session.describe(eidto)[0]) in pendingrdefs:
-        return
-    pendingeids = session.transaction_data.get('pendingeids', ())
-    if card[0] in '1+' and not eidfrom in pendingeids:
-        checkrel_if_necessary(session, CheckSRelationOp, rtype, eidfrom)
-    if card[1] in '1+' and not eidto in pendingeids:
-        checkrel_if_necessary(session, CheckORelationOp, rtype, eidto)
-
-
-def _register_core_hooks(hm):
-    hm.register_hook(handle_composite_before_del_relation, 'before_delete_relation', '')
-    hm.register_hook(before_del_group, 'before_delete_entity', 'CWGroup')
-
-    #hm.register_hook(cstrcheck_before_update_entity, 'before_update_entity', '')
-    hm.register_hook(cardinalitycheck_after_add_entity, 'after_add_entity', '')
-    hm.register_hook(cardinalitycheck_before_del_relation, 'before_delete_relation', '')
-    hm.register_hook(cstrcheck_after_add_relation, 'after_add_relation', '')
-    hm.register_hook(uniquecstrcheck_before_modification, 'before_add_entity', '')
-    hm.register_hook(uniquecstrcheck_before_modification, 'before_update_entity', '')
-
-
-# user/groups synchronisation #################################################
-
-class GroupOperation(Operation):
-    """base class for group operation"""
-    geid = None
-    def __init__(self, session, *args, **kwargs):
-        """override to get the group name before actual groups manipulation:
-
-        we may temporarily loose right access during a commit event, so
-        no query should be emitted while comitting
-        """
-        rql = 'Any N WHERE G eid %(x)s, G name N'
-        result = session.execute(rql, {'x': kwargs['geid']}, 'x', build_descr=False)
-        Operation.__init__(self, session, *args, **kwargs)
-        self.group = result[0][0]
-
-
-class DeleteGroupOp(GroupOperation):
-    """synchronize user when a in_group relation has been deleted"""
-    def commit_event(self):
-        """the observed connections pool has been commited"""
-        groups = self.cnxuser.groups
-        try:
-            groups.remove(self.group)
-        except KeyError:
-            self.error('user %s not in group %s',  self.cnxuser, self.group)
-            return
-
-
-def after_del_in_group(session, fromeid, rtype, toeid):
-    """modify user permission, need to update users"""
-    for session_ in get_user_sessions(session.repo, fromeid):
-        DeleteGroupOp(session, cnxuser=session_.user, geid=toeid)
-
-
-class AddGroupOp(GroupOperation):
-    """synchronize user when a in_group relation has been added"""
-    def commit_event(self):
-        """the observed connections pool has been commited"""
-        groups = self.cnxuser.groups
-        if self.group in groups:
-            self.warning('user %s already in group %s', self.cnxuser,
-                         self.group)
-            return
-        groups.add(self.group)
-
-
-def after_add_in_group(session, fromeid, rtype, toeid):
-    """modify user permission, need to update users"""
-    for session_ in get_user_sessions(session.repo, fromeid):
-        AddGroupOp(session, cnxuser=session_.user, geid=toeid)
-
-
-class DelUserOp(Operation):
-    """synchronize user when a in_group relation has been added"""
-    def __init__(self, session, cnxid):
-        self.cnxid = cnxid
-        Operation.__init__(self, session)
-
-    def commit_event(self):
-        """the observed connections pool has been commited"""
-        try:
-            self.repo.close(self.cnxid)
-        except BadConnectionId:
-            pass # already closed
-
-
-def after_del_user(session, eid):
-    """modify user permission, need to update users"""
-    for session_ in get_user_sessions(session.repo, eid):
-        DelUserOp(session, session_.id)
-
-
-def _register_usergroup_hooks(hm):
-    """register user/group related hooks on the hooks manager"""
-    hm.register_hook(after_del_user, 'after_delete_entity', 'CWUser')
-    hm.register_hook(after_add_in_group, 'after_add_relation', 'in_group')
-    hm.register_hook(after_del_in_group, 'after_delete_relation', 'in_group')
-
-
-# workflow handling ###########################################################
-
-from cubicweb.entities.wfobjs import WorkflowTransition, WorkflowException
-
-def _change_state(session, x, oldstate, newstate):
-    nocheck = session.transaction_data.setdefault('skip-security', set())
-    nocheck.add((x, 'in_state', oldstate))
-    nocheck.add((x, 'in_state', newstate))
-    # delete previous state first in case we're using a super session
-    fromsource = session.describe(x)[1]
-    # don't try to remove previous state if in_state isn't stored in the system
-    # source
-    if fromsource == 'system' or \
-       not session.repo.sources_by_uri[fromsource].support_relation('in_state'):
-        session.delete_relation(x, 'in_state', oldstate)
-    session.add_relation(x, 'in_state', newstate)
-
-
-class FireAutotransitionOp(PreCommitOperation):
-    """try to fire auto transition after state changes"""
-
-    def precommit_event(self):
-        session = self.session
-        entity = self.entity
-        autotrs = list(entity.possible_transitions('auto'))
-        if autotrs:
-            assert len(autotrs) == 1
-            entity.fire_transition(autotrs[0])
-
-
-def before_add_trinfo(session, entity):
-    """check the transition is allowed, add missing information. Expect that:
-    * wf_info_for inlined relation is set
-    * by_transition or to_state (managers only) inlined relation is set
-    """
-    # first retreive entity to which the state change apply
-    try:
-        foreid = entity['wf_info_for']
-    except KeyError:
-        msg = session._('mandatory relation')
-        raise ValidationError(entity.eid, {'wf_info_for': msg})
-    forentity = session.entity_from_eid(foreid)
-    # then check it has a workflow set, unless we're in the process of changing
-    # entity's workflow
-    if session.transaction_data.get((forentity.eid, 'customwf')):
-        wfeid = session.transaction_data[(forentity.eid, 'customwf')]
-        wf = session.entity_from_eid(wfeid)
-    else:
-        wf = forentity.current_workflow
-    if wf is None:
-        msg = session._('related entity has no workflow set')
-        raise ValidationError(entity.eid, {None: msg})
-    # then check it has a state set
-    fromstate = forentity.current_state
-    if fromstate is None:
-        msg = session._('related entity has no state')
-        raise ValidationError(entity.eid, {None: msg})
-    # True if we are coming back from subworkflow
-    swtr = session.transaction_data.pop((forentity.eid, 'subwfentrytr'), None)
-    cowpowers = session.is_super_session or 'managers' in session.user.groups
-    # no investigate the requested state change...
-    try:
-        treid = entity['by_transition']
-    except KeyError:
-        # no transition set, check user is a manager and destination state is
-        # specified (and valid)
-        if not cowpowers:
-            msg = session._('mandatory relation')
-            raise ValidationError(entity.eid, {'by_transition': msg})
-        deststateeid = entity.get('to_state')
-        if not deststateeid:
-            msg = session._('mandatory relation')
-            raise ValidationError(entity.eid, {'by_transition': msg})
-        deststate = wf.state_by_eid(deststateeid)
-        if not cowpowers and deststate is None:
-            msg = entity.req._("state doesn't belong to entity's workflow")
-            raise ValidationError(entity.eid, {'to_state': 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:
-            if tr is None:
-                msg = session._("transition doesn't belong to entity's workflow")
-                raise ValidationError(entity.eid, {'by_transition': msg})
-            if not tr.has_input_state(fromstate):
-                msg = session._("transition isn't allowed")
-                raise ValidationError(entity.eid, {'by_transition': msg})
-            if not tr.may_be_fired(foreid):
-                msg = session._("transition may not be fired")
-                raise ValidationError(entity.eid, {'by_transition': msg})
-        if entity.get('to_state'):
-            deststateeid = entity['to_state']
-            if not cowpowers and deststateeid != tr.destination().eid:
-                msg = session._("transition isn't allowed")
-                raise ValidationError(entity.eid, {'by_transition': msg})
-            if swtr is None:
-                deststate = session.entity_from_eid(deststateeid)
-                if not cowpowers and deststate is None:
-                    msg = entity.req._("state doesn't belong to entity's workflow")
-                    raise ValidationError(entity.eid, {'to_state': msg})
-        else:
-            deststateeid = tr.destination().eid
-    # everything is ok, add missing information on the trinfo entity
-    entity['from_state'] = fromstate.eid
-    entity['to_state'] = deststateeid
-    nocheck = session.transaction_data.setdefault('skip-security', set())
-    nocheck.add((entity.eid, 'from_state', fromstate.eid))
-    nocheck.add((entity.eid, 'to_state', deststateeid))
-    FireAutotransitionOp(session, entity=forentity)
-
-
-def after_add_trinfo(session, entity):
-    """change related entity state"""
-    _change_state(session, entity['wf_info_for'],
-                  entity['from_state'], entity['to_state'])
-    forentity = session.entity_from_eid(entity['wf_info_for'])
-    assert forentity.current_state.eid == entity['to_state'], forentity.current_state.name
-    if forentity.main_workflow.eid != forentity.current_workflow.eid:
-        # we're in a subworkflow, check if we've reached an exit point
-        wftr = forentity.subworkflow_input_transition()
-        if wftr is None:
-            # inconsistency detected
-            msg = entity.req._("state doesn't belong to entity's current workflow")
-            raise ValidationError(entity.eid, {'to_state': msg})
-        tostate = wftr.get_exit_point(entity['to_state'])
-        if tostate is not None:
-            # reached an exit point
-            msg = session._('exiting from subworkflow %s')
-            msg %= session._(forentity.current_workflow.name)
-            session.transaction_data[(forentity.eid, 'subwfentrytr')] = True
-            # XXX iirk
-            req = forentity.req
-            forentity.req = session.super_session
-            try:
-                trinfo = forentity.change_state(tostate, msg, u'text/plain',
-                                                tr=wftr)
-            finally:
-                forentity.req = req
-
-
-class SetInitialStateOp(PreCommitOperation):
-    """make initial state be a default state"""
-
-    def precommit_event(self):
-        session = self.session
-        entity = self.entity
-        # if there is an initial state and the entity's state is not set,
-        # use the initial state as a default state
-        pendingeids = session.transaction_data.get('pendingeids', ())
-        if not entity.eid in pendingeids and not entity.in_state and \
-               entity.main_workflow:
-            state = entity.main_workflow.initial
-            if state:
-                # use super session to by-pass security checks
-                session.super_session.add_relation(entity.eid, 'in_state',
-                                                   state.eid)
-
-
-def set_initial_state_after_add(session, entity):
-    SetInitialStateOp(session, entity=entity)
-
-
-def before_add_in_state(session, eidfrom, rtype, eidto):
-    """check state apply, in case of direct in_state change using unsafe_execute
-    """
-    nocheck = session.transaction_data.setdefault('skip-security', set())
-    if (eidfrom, 'in_state', eidto) in nocheck:
-        # state changed through TrInfo insertion, so we already know it's ok
-        return
-    entity = session.entity_from_eid(eidfrom)
-    mainwf = entity.main_workflow
-    if mainwf is None:
-        msg = session._('entity has no workflow set')
-        raise ValidationError(entity.eid, {None: msg})
-    for wf in mainwf.iter_workflows():
-        if wf.state_by_eid(eidto):
-            break
-    else:
-        msg = session._("state doesn't belong to entity's workflow. You may "
-                        "want to set a custom workflow for this entity first.")
-        raise ValidationError(eidfrom, {'in_state': msg})
-    if entity.current_workflow and wf.eid != entity.current_workflow.eid:
-        msg = session._("state doesn't belong to entity's current workflow")
-        raise ValidationError(eidfrom, {'in_state': msg})
-
-
-class CheckTrExitPoint(PreCommitOperation):
-
-    def precommit_event(self):
-        tr = self.session.entity_from_eid(self.treid)
-        outputs = set()
-        for ep in tr.subworkflow_exit:
-            if ep.subwf_state.eid in outputs:
-                msg = self.session._("can't have multiple exits on the same state")
-                raise ValidationError(self.treid, {'subworkflow_exit': msg})
-            outputs.add(ep.subwf_state.eid)
-
-
-def after_add_subworkflow_exit(session, eidfrom, rtype, eidto):
-    CheckTrExitPoint(session, treid=eidfrom)
-
-
-class WorkflowChangedOp(PreCommitOperation):
-    """fix entity current state when changing its workflow"""
-
-    def precommit_event(self):
-        # notice that enforcement that new workflow apply to the entity's type is
-        # done by schema rule, no need to check it here
-        session = self.session
-        pendingeids = session.transaction_data.get('pendingeids', ())
-        if self.eid in pendingeids:
-            return
-        entity = session.entity_from_eid(self.eid)
-        # check custom workflow has not been rechanged to another one in the same
-        # transaction
-        mainwf = entity.main_workflow
-        if mainwf.eid == self.wfeid:
-            deststate = mainwf.initial
-            if not deststate:
-                msg = session._('workflow has no initial state')
-                raise ValidationError(entity.eid, {'custom_workflow': msg})
-            if mainwf.state_by_eid(entity.current_state.eid):
-                # nothing to do
-                return
-            # if there are no history, simply go to new workflow's initial state
-            if not entity.workflow_history:
-                if entity.current_state.eid != deststate.eid:
-                    _change_state(session, entity.eid,
-                                  entity.current_state.eid, deststate.eid)
-                return
-            msg = session._('workflow changed to "%s"')
-            msg %= session._(mainwf.name)
-            session.transaction_data[(entity.eid, 'customwf')] = self.wfeid
-            entity.change_state(deststate, msg, u'text/plain')
-
-
-def set_custom_workflow(session, eidfrom, rtype, eidto):
-    WorkflowChangedOp(session, eid=eidfrom, wfeid=eidto)
-
-
-def del_custom_workflow(session, eidfrom, rtype, eidto):
-    entity = session.entity_from_eid(eidfrom)
-    typewf = entity.cwetype_workflow()
-    if typewf is not None:
-        WorkflowChangedOp(session, eid=eidfrom, wfeid=typewf.eid)
-
-
-def after_del_workflow(session, eid):
-    # workflow cleanup
-    session.execute('DELETE State X WHERE NOT X state_of Y')
-    session.execute('DELETE Transition X WHERE NOT X transition_of Y')
-
-
-def _register_wf_hooks(hm):
-    """register workflow related hooks on the hooks manager"""
-    if 'in_state' in hm.schema:
-        hm.register_hook(before_add_trinfo, 'before_add_entity', 'TrInfo')
-        hm.register_hook(after_add_trinfo, 'after_add_entity', 'TrInfo')
-        #hm.register_hook(relation_deleted, 'before_delete_relation', 'in_state')
-        for eschema in hm.schema.entities():
-            if 'in_state' in eschema.subject_relations():
-                hm.register_hook(set_initial_state_after_add, 'after_add_entity',
-                                 str(eschema))
-        hm.register_hook(set_custom_workflow, 'after_add_relation', 'custom_workflow')
-        hm.register_hook(del_custom_workflow, 'after_delete_relation', 'custom_workflow')
-        hm.register_hook(after_del_workflow, 'after_delete_entity', 'Workflow')
-        hm.register_hook(before_add_in_state, 'before_add_relation', 'in_state')
-        hm.register_hook(after_add_subworkflow_exit, 'after_add_relation', 'subworkflow_exit')
-
-
-# CWProperty hooks #############################################################
-
-
-class DelCWPropertyOp(Operation):
-    """a user's custom properties has been deleted"""
-
-    def commit_event(self):
-        """the observed connections pool has been commited"""
-        try:
-            del self.epropdict[self.key]
-        except KeyError:
-            self.error('%s has no associated value', self.key)
-
-
-class ChangeCWPropertyOp(Operation):
-    """a user's custom properties has been added/changed"""
-
-    def commit_event(self):
-        """the observed connections pool has been commited"""
-        self.epropdict[self.key] = self.value
-
-
-class AddCWPropertyOp(Operation):
-    """a user's custom properties has been added/changed"""
-
-    def commit_event(self):
-        """the observed connections pool has been commited"""
-        eprop = self.eprop
-        if not eprop.for_user:
-            self.repo.vreg.eprop_values[eprop.pkey] = eprop.value
-        # if for_user is set, update is handled by a ChangeCWPropertyOp operation
-
-
-def after_add_eproperty(session, entity):
-    key, value = entity.pkey, entity.value
-    try:
-        value = session.vreg.typed_value(key, value)
-    except UnknownProperty:
-        raise ValidationError(entity.eid, {'pkey': session._('unknown property key')})
-    except ValueError, ex:
-        raise ValidationError(entity.eid, {'value': session._(str(ex))})
-    if not session.user.matching_groups('managers'):
-        session.add_relation(entity.eid, 'for_user', session.user.eid)
-    else:
-        AddCWPropertyOp(session, eprop=entity)
-
-
-def after_update_eproperty(session, entity):
-    if not ('pkey' in entity.edited_attributes or
-            'value' in entity.edited_attributes):
-        return
-    key, value = entity.pkey, entity.value
-    try:
-        value = session.vreg.typed_value(key, value)
-    except UnknownProperty:
-        return
-    except ValueError, ex:
-        raise ValidationError(entity.eid, {'value': session._(str(ex))})
-    if entity.for_user:
-        for session_ in get_user_sessions(session.repo, entity.for_user[0].eid):
-            ChangeCWPropertyOp(session, epropdict=session_.user.properties,
-                              key=key, value=value)
-    else:
-        # site wide properties
-        ChangeCWPropertyOp(session, epropdict=session.vreg.eprop_values,
-                          key=key, value=value)
-
-
-def before_del_eproperty(session, eid):
-    for eidfrom, rtype, eidto in session.transaction_data.get('pendingrelations', ()):
-        if rtype == 'for_user' and eidfrom == eid:
-            # if for_user was set, delete has already been handled
-            break
-    else:
-        key = session.execute('Any K WHERE P eid %(x)s, P pkey K',
-                              {'x': eid}, 'x')[0][0]
-        DelCWPropertyOp(session, epropdict=session.vreg.eprop_values, key=key)
-
-
-def after_add_for_user(session, fromeid, rtype, toeid):
-    if not session.describe(fromeid)[0] == 'CWProperty':
-        return
-    key, value = session.execute('Any K,V WHERE P eid %(x)s,P pkey K,P value V',
-                                 {'x': fromeid}, 'x')[0]
-    if session.vreg.property_info(key)['sitewide']:
-        raise ValidationError(fromeid,
-                              {'for_user': session._("site-wide property can't be set for user")})
-    for session_ in get_user_sessions(session.repo, toeid):
-        ChangeCWPropertyOp(session, epropdict=session_.user.properties,
-                          key=key, value=value)
-
-
-def before_del_for_user(session, fromeid, rtype, toeid):
-    key = session.execute('Any K WHERE P eid %(x)s, P pkey K',
-                          {'x': fromeid}, 'x')[0][0]
-    relation_deleted(session, fromeid, rtype, toeid)
-    for session_ in get_user_sessions(session.repo, toeid):
-        DelCWPropertyOp(session, epropdict=session_.user.properties, key=key)
-
-
-def _register_eproperty_hooks(hm):
-    """register workflow related hooks on the hooks manager"""
-    hm.register_hook(after_add_eproperty, 'after_add_entity', 'CWProperty')
-    hm.register_hook(after_update_eproperty, 'after_update_entity', 'CWProperty')
-    hm.register_hook(before_del_eproperty, 'before_delete_entity', 'CWProperty')
-    hm.register_hook(after_add_for_user, 'after_add_relation', 'for_user')
-    hm.register_hook(before_del_for_user, 'before_delete_relation', 'for_user')
--- a/server/hooksmanager.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/hooksmanager.py	Wed Sep 30 18:57:42 2009 +0200
@@ -1,347 +1,10 @@
-"""Hooks management
-
-Hooks are called before / after any individual update of entities / relations
-in the repository.
-
-Here is the prototype of the different hooks:
-
-* filtered on the entity's type:
-
-  before_add_entity    (session, entity)
-  after_add_entity     (session, entity)
-  before_update_entity (session, entity)
-  after_update_entity  (session, entity)
-  before_delete_entity (session, eid)
-  after_delete_entity  (session, eid)
-
-* filtered on the relation's type:
-
-  before_add_relation    (session, fromeid, rtype, toeid)
-  after_add_relation     (session, fromeid, rtype, toeid)
-  before_delete_relation (session, fromeid, rtype, toeid)
-  after_delete_relation  (session, fromeid, rtype, toeid)
-
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-ENTITIES_HOOKS = ('before_add_entity',    'after_add_entity',
-                  'before_update_entity', 'after_update_entity',
-                  'before_delete_entity', 'after_delete_entity')
-RELATIONS_HOOKS = ('before_add_relation',   'after_add_relation' ,
-                   'before_delete_relation','after_delete_relation')
-SYSTEM_HOOKS = ('server_backup', 'server_restore',
-                'server_startup', 'server_shutdown',
-                'session_open', 'session_close')
-
-ALL_HOOKS = frozenset(ENTITIES_HOOKS + RELATIONS_HOOKS + SYSTEM_HOOKS)
-
-class HooksManager(object):
-    """handle hooks registration and calls
-    """
-    verification_hooks_activated = True
-
-    def __init__(self, schema):
-        self.set_schema(schema)
-
-    def set_schema(self, schema):
-        self._hooks = {}
-        self.schema = schema
-        self._init_hooks(schema)
-
-    def register_hooks(self, hooks):
-        """register a dictionary of hooks :
-
-             {'event': {'entity or relation type': [callbacks list]}}
-        """
-        for event, subevents in hooks.items():
-            for subevent, callbacks in subevents.items():
-                for callback in callbacks:
-                    self.register_hook(callback, event, subevent)
-
-    def register_hook(self, function, event, etype=''):
-        """register a function to call when <event> occurs
-
-        <etype> is an entity/relation type or an empty string.
-
-        If etype is the empty string, the function will be called at each event,
-        else the function will be called only when event occurs on an entity or
-        relation of the given type.
-        """
-        assert event in ALL_HOOKS, '%r NOT IN %r' % (event, ALL_HOOKS)
-        assert (not event in SYSTEM_HOOKS or not etype), (event, etype)
-        etype = etype or ''
-        try:
-            self._hooks[event][etype].append(function)
-            self.debug('registered hook %s on %s (%s)', event, etype or 'any',
-                       function.func_name)
-
-        except KeyError:
-            self.error('can\'t register hook %s on %s (%s)',
-                       event, etype or 'any', function.func_name)
-
-    def unregister_hook(self, function_or_cls, event=None, etype=''):
-        """unregister a function to call when <event> occurs, or a Hook subclass.
-        In the later case, event/type information are extracted from the given
-        class.
-        """
-        if isinstance(function_or_cls, type) and issubclass(function_or_cls, Hook):
-            for event, ertype in function_or_cls.register_to():
-                for hook in self._hooks[event][ertype]:
-                    if getattr(hook, 'im_self', None).__class__ is function_or_cls:
-                        self._hooks[event][ertype].remove(hook)
-                        self.info('unregister hook %s on %s (%s)', event, etype,
-                                  function_or_cls.__name__)
-                        break
-                else:
-                    self.warning("can't unregister hook %s on %s (%s), not found",
-                                 event, etype, function_or_cls.__name__)
-        else:
-            assert event in ALL_HOOKS, event
-            etype = etype or ''
-            self.info('unregister hook %s on %s (%s)', event, etype,
-                      function_or_cls.func_name)
-            self._hooks[event][etype].remove(function_or_cls)
-
-    def call_hooks(self, __event, __type='', *args, **kwargs):
-        """call hook matching event and optional type"""
-        if __type:
-            self.info('calling hooks for event %s (%s)', __event, __type)
-        else:
-            self.info('calling hooks for event %s', __event)
-        # call generic hooks first
-        for hook in self._hooks[__event]['']:
-            #print '[generic]', hook.__name__
-            hook(*args, **kwargs)
-        if __type:
-            for hook in self._hooks[__event][__type]:
-                #print '[%s]'%__type, hook.__name__
-                hook(*args, **kwargs)
-
-    def _init_hooks(self, schema):
-        """initialize the hooks map"""
-        for hook_event in ENTITIES_HOOKS:
-            self._hooks[hook_event] = {'': []}
-            for etype in schema.entities():
-                self._hooks[hook_event][etype] = []
-        for hook_event in RELATIONS_HOOKS:
-            self._hooks[hook_event] = {'': []}
-            for r_type in schema.relations():
-                self._hooks[hook_event][r_type] = []
-        for hook_event in SYSTEM_HOOKS:
-            self._hooks[hook_event] = {'': []}
-
-    def register_system_hooks(self, config):
-        """register system hooks according to the configuration"""
-        self.info('register core hooks')
-        from cubicweb.server.hooks import _register_metadata_hooks, _register_wf_hooks
-        _register_metadata_hooks(self)
-        self.info('register workflow hooks')
-        _register_wf_hooks(self)
-        if config.core_hooks:
-            from cubicweb.server.hooks import _register_core_hooks
-            _register_core_hooks(self)
-        if config.schema_hooks:
-            from cubicweb.server.schemahooks import _register_schema_hooks
-            self.info('register schema hooks')
-            _register_schema_hooks(self)
-        if config.usergroup_hooks:
-            from cubicweb.server.hooks import _register_usergroup_hooks
-            from cubicweb.server.hooks import _register_eproperty_hooks
-            self.info('register user/group hooks')
-            _register_usergroup_hooks(self)
-            _register_eproperty_hooks(self)
-        if config.security_hooks:
-            from cubicweb.server.securityhooks import register_security_hooks
-            self.info('register security hooks')
-            register_security_hooks(self)
-        if not self.verification_hooks_activated:
-            self.deactivate_verification_hooks()
-
-    def deactivate_verification_hooks(self):
-        from cubicweb.server.hooks import (cardinalitycheck_after_add_entity,
-                                        cardinalitycheck_before_del_relation,
-                                        cstrcheck_after_add_relation,
-                                        uniquecstrcheck_before_modification)
-        self.warning('deactivating verification hooks')
-        self.verification_hooks_activated = False
-        self.unregister_hook(cardinalitycheck_after_add_entity, 'after_add_entity', '')
-        self.unregister_hook(cardinalitycheck_before_del_relation, 'before_delete_relation', '')
-        self.unregister_hook(cstrcheck_after_add_relation, 'after_add_relation', '')
-        self.unregister_hook(uniquecstrcheck_before_modification, 'before_add_entity', '')
-        self.unregister_hook(uniquecstrcheck_before_modification, 'before_update_entity', '')
-#         self.unregister_hook(tidy_html_fields('before_add_entity'), 'before_add_entity', '')
-#         self.unregister_hook(tidy_html_fields('before_update_entity'), 'before_update_entity', '')
-
-    def reactivate_verification_hooks(self):
-        from cubicweb.server.hooks import (cardinalitycheck_after_add_entity,
-                                        cardinalitycheck_before_del_relation,
-                                        cstrcheck_after_add_relation,
-                                        uniquecstrcheck_before_modification)
-        self.warning('reactivating verification hooks')
-        self.verification_hooks_activated = True
-        self.register_hook(cardinalitycheck_after_add_entity, 'after_add_entity', '')
-        self.register_hook(cardinalitycheck_before_del_relation, 'before_delete_relation', '')
-        self.register_hook(cstrcheck_after_add_relation, 'after_add_relation', '')
-        self.register_hook(uniquecstrcheck_before_modification, 'before_add_entity', '')
-        self.register_hook(uniquecstrcheck_before_modification, 'before_update_entity', '')
-#         self.register_hook(tidy_html_fields('before_add_entity'), 'before_add_entity', '')
-#         self.register_hook(tidy_html_fields('before_update_entity'), 'before_update_entity', '')
-
-from cubicweb.selectors import yes
-from cubicweb.appobject import AppObject
-
-class autoid(type):
-    """metaclass to create an unique 'id' attribute on the class using it"""
-    # XXX is this metaclass really necessary ?
-    def __new__(mcs, name, bases, classdict):
-        cls = super(autoid, mcs).__new__(mcs, name, bases, classdict)
-        cls.id = str(id(cls))
-        return cls
-
-class Hook(AppObject):
-    __metaclass__ = autoid
-    __registry__ = 'hooks'
-    __select__ = yes()
-    # set this in derivated classes
-    events = None
-    accepts = None
-    enabled = True
-
-    def __init__(self, event=None):
-        super(Hook, self).__init__()
-        self.event = event
-
-    @classmethod
-    def registered(cls, vreg):
-        super(Hook, cls).registered(vreg)
-        return cls()
-
-    @classmethod
-    def register_to(cls):
-        if not cls.enabled:
-            cls.warning('%s hook has been disabled', cls)
-            return
-        done = set()
-        assert isinstance(cls.events, (tuple, list)), \
-               '%s: events is expected to be a tuple, not %s' % (
-            cls, type(cls.events))
-        for event in cls.events:
-            if event in SYSTEM_HOOKS:
-                assert not cls.accepts or cls.accepts == ('Any',), \
-                       '%s doesnt make sense on %s' % (cls.accepts, event)
-                cls.accepts = ('Any',)
-            for ertype in cls.accepts:
-                if (event, ertype) in done:
-                    continue
-                yield event, ertype
-                done.add((event, ertype))
-                try:
-                    eschema = cls.schema.eschema(ertype)
-                except KeyError:
-                    # relation schema
-                    pass
-                else:
-                    for eetype in eschema.specialized_by():
-                        if (event, eetype) in done:
-                            continue
-                        yield event, str(eetype)
-                        done.add((event, eetype))
-
-
-    def make_callback(self, event):
-        if len(self.events) == 1:
-            return self.call
-        return self.__class__(event=event).call
-
-    def call(self):
-        raise NotImplementedError
-
-class SystemHook(Hook):
-    accepts = ()
-
-from logging import getLogger
-from cubicweb import set_log_methods
-set_log_methods(HooksManager, getLogger('cubicweb.hooksmanager'))
-set_log_methods(Hook, getLogger('cubicweb.hooks'))
-
-# base classes for relation propagation ########################################
-
-from cubicweb.server.pool import PreCommitOperation
-
-
-class PropagateSubjectRelationHook(Hook):
-    """propagate permissions and nosy list when new entity are added"""
-    events = ('after_add_relation',)
-    # to set in concrete class
-    rtype = None
-    subject_relations = None
-    object_relations = None
-    accepts = None # subject_relations + object_relations
-
-    def call(self, session, fromeid, rtype, toeid):
-        for eid in (fromeid, toeid):
-            etype = session.describe(eid)[0]
-            if not self.schema.eschema(etype).has_subject_relation(self.rtype):
-                return
-        if rtype in self.subject_relations:
-            meid, seid = fromeid, toeid
-        else:
-            assert rtype in self.object_relations
-            meid, seid = toeid, fromeid
-        session.unsafe_execute(
-            'SET E %s P WHERE X %s P, X eid %%(x)s, E eid %%(e)s, NOT E %s P'\
-            % (self.rtype, self.rtype, self.rtype),
-            {'x': meid, 'e': seid}, ('x', 'e'))
-
-
-class PropagateSubjectRelationAddHook(Hook):
-    """propagate on existing entities when a permission or nosy list is added"""
-    events = ('after_add_relation',)
-    # to set in concrete class
-    rtype = None
-    subject_relations = None
-    object_relations = None
-    accepts = None # (self.rtype,)
-
-    def call(self, session, fromeid, rtype, toeid):
-        eschema = self.schema.eschema(session.describe(fromeid)[0])
-        execute = session.unsafe_execute
-        for rel in self.subject_relations:
-            if eschema.has_subject_relation(rel):
-                execute('SET R %s P WHERE X eid %%(x)s, P eid %%(p)s, '
-                        'X %s R, NOT R %s P' % (rtype, rel, rtype),
-                        {'x': fromeid, 'p': toeid}, 'x')
-        for rel in self.object_relations:
-            if eschema.has_object_relation(rel):
-                execute('SET R %s P WHERE X eid %%(x)s, P eid %%(p)s, '
-                        'R %s X, NOT R %s P' % (rtype, rel, rtype),
-                        {'x': fromeid, 'p': toeid}, 'x')
-
-
-class PropagateSubjectRelationDelHook(Hook):
-    """propagate on existing entities when a permission is deleted"""
-    events = ('after_delete_relation',)
-    # to set in concrete class
-    rtype = None
-    subject_relations = None
-    object_relations = None
-    accepts = None # (self.rtype,)
-
-    def call(self, session, fromeid, rtype, toeid):
-        eschema = self.schema.eschema(session.describe(fromeid)[0])
-        execute = session.unsafe_execute
-        for rel in self.subject_relations:
-            if eschema.has_subject_relation(rel):
-                execute('DELETE R %s P WHERE X eid %%(x)s, P eid %%(p)s, '
-                        'X %s R' % (rtype, rel),
-                        {'x': fromeid, 'p': toeid}, 'x')
-        for rel in self.object_relations:
-            if eschema.has_object_relation(rel):
-                execute('DELETE R %s P WHERE X eid %%(x)s, P eid %%(p)s, '
-                        'R %s X' % (rtype, rel),
-                        {'x': fromeid, 'p': toeid}, 'x')
+from logilab.common.deprecation import class_renamed, class_moved
+from cubicweb.server import hook
+SystemHook = class_renamed('SystemHook', hook.Hook)
+PropagateSubjectRelationHook = class_renamed('PropagateSubjectRelationHook',
+                                             hook.PropagateSubjectRelationHook)
+PropagateSubjectRelationAddHook = class_renamed('PropagateSubjectRelationAddHook',
+                                                hook.PropagateSubjectRelationAddHook)
+PropagateSubjectRelationDelHook = class_renamed('PropagateSubjectRelationDelHook',
+                                                hook.PropagateSubjectRelationDelHook)
+Hook = class_moved(hook.Hook)
--- a/server/migractions.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/migractions.py	Wed Sep 30 18:57:42 2009 +0200
@@ -259,9 +259,9 @@
                         'fsschema': self.fs_schema,
                         'session' : self.session,
                         'repo' : self.repo,
-                        'synchronize_schema': deprecated()(self.cmd_sync_schema_props_perms),
-                        'synchronize_eschema': deprecated()(self.cmd_sync_schema_props_perms),
-                        'synchronize_rschema': deprecated()(self.cmd_sync_schema_props_perms),
+                        'synchronize_schema': deprecated()(self.cmd_sync_schema_props_perms), # 3.4
+                        'synchronize_eschema': deprecated()(self.cmd_sync_schema_props_perms), # 3.4
+                        'synchronize_rschema': deprecated()(self.cmd_sync_schema_props_perms), # 3.4
                         })
         return context
 
@@ -282,7 +282,7 @@
                 from cubicweb.server.hooks import setowner_after_add_entity
                 self.repo.hm.unregister_hook(setowner_after_add_entity,
                                              'after_add_entity', '')
-                self.deactivate_verification_hooks()
+                self.cmd_deactivate_verification_hooks()
             self.info('executing %s', apc)
             confirm = self.confirm
             execscript_confirm = self.execscript_confirm
@@ -296,7 +296,7 @@
                 if self.config.free_wheel:
                     self.repo.hm.register_hook(setowner_after_add_entity,
                                                'after_add_entity', '')
-                    self.reactivate_verification_hooks()
+                    self.cmd_reactivate_verification_hooks()
 
     # schema synchronization internals ########################################
 
@@ -673,8 +673,8 @@
                 continue
             if instspschema.specializes() != eschema:
                 self.rqlexec('SET D specializes P WHERE D eid %(d)s, P name %(pn)s',
-                              {'d': instspschema.eid,
-                               'pn': eschema.type}, ask_confirm=confirm)
+                             {'d': instspschema.eid,
+                              'pn': eschema.type}, ask_confirm=confirm)
                 for rschema, tschemas, role in spschema.relation_definitions(True):
                     for tschema in tschemas:
                         if not tschema in instschema:
@@ -954,7 +954,7 @@
         if commit:
             self.commit()
 
-    @deprecated('use sync_schema_props_perms(ertype, syncprops=False)')
+    @deprecated('[3.4] use sync_schema_props_perms(ertype, syncprops=False)')
     def cmd_synchronize_permissions(self, ertype, commit=True):
         self.cmd_sync_schema_props_perms(ertype, syncprops=False, commit=commit)
 
@@ -1113,10 +1113,10 @@
         return ForRqlIterator(self, rql, None, ask_confirm)
 
     def cmd_deactivate_verification_hooks(self):
-        self.repo.hm.deactivate_verification_hooks()
+        self.config.disabled_hooks_categories.add('integrity')
 
     def cmd_reactivate_verification_hooks(self):
-        self.repo.hm.reactivate_verification_hooks()
+        self.config.disabled_hooks_categories.remove('integrity')
 
     # broken db commands ######################################################
 
--- a/server/pool.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/pool.py	Wed Sep 30 18:57:42 2009 +0200
@@ -1,13 +1,7 @@
-"""CubicWeb server connections pool :
-
-* the rql repository has a limited number of connections pools, each of them
-  dealing with a set of connections on each source used by the repository
-
-* operation may be registered by hooks during a transaction, which will  be
-  fired when the pool is commited or rollbacked
-
-This module defined the `ConnectionsPool` class and a set of abstract classes
-for operation.
+"""CubicWeb server connections pool : the repository has a limited number of
+connections pools, each of them dealing with a set of connections on each source
+used by the repository. A connections pools (`ConnectionsPool`) is an
+abstraction for a group of connection to each source.
 
 
 :organization: Logilab
@@ -129,152 +123,11 @@
         self.source_cnxs[source.uri] = (source, cnx)
         self._cursors.pop(source.uri, None)
 
-
-class Operation(object):
-    """an operation is triggered on connections pool events related to
-    commit / rollback transations. Possible events are:
-
-    precommit:
-      the pool is preparing to commit. You shouldn't do anything things which
-      has to be reverted if the commit fail at this point, but you can freely
-      do any heavy computation or raise an exception if the commit can't go.
-      You can add some new operation during this phase but their precommit
-      event won't be triggered
-
-    commit:
-      the pool is preparing to commit. You should avoid to do to expensive
-      stuff or something that may cause an exception in this event
-
-    revertcommit:
-      if an operation failed while commited, this event is triggered for
-      all operations which had their commit event already to let them
-      revert things (including the operation which made fail the commit)
-
-    rollback:
-      the transaction has been either rollbacked either
-      * intentionaly
-      * a precommit event failed, all operations are rollbacked
-      * a commit event failed, all operations which are not been triggered for
-        commit are rollbacked
-
-    order of operations may be important, and is controlled according to:
-    * operation's class
-    """
-
-    def __init__(self, session, **kwargs):
-        self.session = session
-        self.user = session.user
-        self.repo = session.repo
-        self.schema = session.repo.schema
-        self.config = session.repo.config
-        self.__dict__.update(kwargs)
-        self.register(session)
-        # execution information
-        self.processed = None # 'precommit', 'commit'
-        self.failed = False
-
-    def register(self, session):
-        session.add_operation(self, self.insert_index())
-
-    def insert_index(self):
-        """return the index of  the lastest instance which is not a
-        LateOperation instance
-        """
-        for i, op in enumerate(self.session.pending_operations):
-            if isinstance(op, (LateOperation, SingleLastOperation)):
-                return i
-        return None
-
-    def handle_event(self, event):
-        """delegate event handling to the opertaion"""
-        getattr(self, event)()
-
-    def precommit_event(self):
-        """the observed connections pool is preparing a commit"""
-
-    def revertprecommit_event(self):
-        """an error went when pre-commiting this operation or a later one
-
-        should revert pre-commit's changes but take care, they may have not
-        been all considered if it's this operation which failed
-        """
-
-    def commit_event(self):
-        """the observed connections pool is commiting"""
-        raise NotImplementedError()
-
-    def revertcommit_event(self):
-        """an error went when commiting this operation or a later one
-
-        should revert commit's changes but take care, they may have not
-        been all considered if it's this operation which failed
-        """
-
-    def rollback_event(self):
-        """the observed connections pool has been rollbacked
-
-        do nothing by default, the operation will just be removed from the pool
-        operation list
-        """
-
-
-class PreCommitOperation(Operation):
-    """base class for operation only defining a precommit operation
-    """
-
-    def precommit_event(self):
-        """the observed connections pool is preparing a commit"""
-        raise NotImplementedError()
-
-    def commit_event(self):
-        """the observed connections pool is commiting"""
-
-
-class LateOperation(Operation):
-    """special operation which should be called after all possible (ie non late)
-    operations
-    """
-    def insert_index(self):
-        """return the index of  the lastest instance which is not a
-        SingleLastOperation instance
-        """
-        for i, op in enumerate(self.session.pending_operations):
-            if isinstance(op, SingleLastOperation):
-                return i
-        return None
-
-
-class SingleOperation(Operation):
-    """special operation which should be called once"""
-    def register(self, session):
-        """override register to handle cases where this operation has already
-        been added
-        """
-        operations = session.pending_operations
-        index = self.equivalent_index(operations)
-        if index is not None:
-            equivalent = operations.pop(index)
-        else:
-            equivalent = None
-        session.add_operation(self, self.insert_index())
-        return equivalent
-
-    def equivalent_index(self, operations):
-        """return the index of the equivalent operation if any"""
-        equivalents = [i for i, op in enumerate(operations)
-                       if op.__class__ is self.__class__]
-        if equivalents:
-            return equivalents[0]
-        return None
-
-
-class SingleLastOperation(SingleOperation):
-    """special operation which should be called once and after all other
-    operations
-    """
-    def insert_index(self):
-        return None
-
-from logging import getLogger
-from cubicweb import set_log_methods
-set_log_methods(Operation, getLogger('cubicweb.session'))
+from cubicweb.server.hook import (Operation, LateOperation, SingleOperation,
+                                  SingleLastOperation)
+from logilab.common.deprecation import class_moved, class_renamed
+Operation = class_moved(Operation)
+PreCommitOperation = class_renamed('PreCommitOperation', Operation)
+LateOperation = class_moved(LateOperation)
+SingleOperation = class_moved(SingleOperation)
+SingleLastOperation = class_moved(SingleLastOperation)
--- a/server/repository.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/repository.py	Wed Sep 30 18:57:42 2009 +0200
@@ -33,19 +33,12 @@
                       ETypeNotSupportedBySources, MultiSourcesError,
                       BadConnectionId, Unauthorized, ValidationError,
                       typed_eid)
-from cubicweb.cwvreg import CubicWebVRegistry
-from cubicweb.schema import VIRTUAL_RTYPES, CubicWebSchema
-from cubicweb import server
-from cubicweb.server.utils import RepoThread, LoopTask
-from cubicweb.server.pool import ConnectionsPool, LateOperation, SingleLastOperation
+from cubicweb import cwvreg, schema, server
+from cubicweb.server import utils, hook, pool, querier, sources
 from cubicweb.server.session import Session, InternalSession
-from cubicweb.server.querier import QuerierHelper
-from cubicweb.server.sources import get_source
-from cubicweb.server.hooksmanager import HooksManager
-from cubicweb.server.hookhelper import rproperty
 
 
-class CleanupEidTypeCacheOp(SingleLastOperation):
+class CleanupEidTypeCacheOp(hook.SingleLastOperation):
     """on rollback of a insert query or commit of delete query, we have to
     clear repository's cache from no more valid entries
 
@@ -61,7 +54,8 @@
         remove inserted eid from repository type/source cache
         """
         try:
-            self.repo.clear_caches(self.session.transaction_data['pendingeids'])
+            self.session.repo.clear_caches(
+                self.session.transaction_data['pendingeids'])
         except KeyError:
             pass
 
@@ -70,12 +64,13 @@
         remove inserted eid from repository type/source cache
         """
         try:
-            self.repo.clear_caches(self.session.transaction_data['neweids'])
+            self.session.repo.clear_caches(
+                self.session.transaction_data['neweids'])
         except KeyError:
             pass
 
 
-class FTIndexEntityOp(LateOperation):
+class FTIndexEntityOp(hook.LateOperation):
     """operation to delay entity full text indexation to commit
 
     since fti indexing may trigger discovery of other entities, it should be
@@ -111,7 +106,7 @@
     ensure_card_respected(session.unsafe_execute, session, eidfrom, rtype, eidto)
 
 def ensure_card_respected(execute, session, eidfrom, rtype, eidto):
-    card = rproperty(session, rtype, eidfrom, eidto, 'cardinality')
+    card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality')
     # one may be tented to check for neweids but this may cause more than one
     # relation even with '1?'  cardinality if thoses relations are added in the
     # same transaction where the entity is being created. This never occurs from
@@ -137,7 +132,7 @@
     def __init__(self, config, vreg=None, debug=False):
         self.config = config
         if vreg is None:
-            vreg = CubicWebVRegistry(config, debug)
+            vreg = cwvreg.CubicWebVRegistry(config, debug)
         self.vreg = vreg
         self.pyro_registered = False
         self.info('starting repository from %s', self.config.apphome)
@@ -148,10 +143,10 @@
         # list of running threads
         self._running_threads = []
         # initial schema, should be build or replaced latter
-        self.schema = CubicWebSchema(config.appid)
+        self.schema = schema.CubicWebSchema(config.appid)
         self.vreg.schema = self.schema # until actual schema is loaded...
         # querier helper, need to be created after sources initialization
-        self.querier = QuerierHelper(self, self.schema)
+        self.querier = querier.QuerierHelper(self, self.schema)
         # should we reindex in changes?
         self.do_fti = not config['delay-full-text-indexation']
         # sources
@@ -174,16 +169,21 @@
         self._type_source_cache = {}
         # cache (extid, source uri) -> eid
         self._extid_cache = {}
-        # create the hooks manager
-        self.hm = HooksManager(self.schema)
         # open some connections pools
         if config.open_connections_pools:
             self.open_connections_pools()
 
+    def _boostrap_hook_registry(self):
+        """called during bootstrap since we need the metadata hooks"""
+        hooksdirectory = join(CW_SOFTWARE_ROOT, 'hooks')
+        self.vreg.init_registration([hooksdirectory])
+        self.vreg.load_file(join(hooksdirectory, 'metadata.py'),
+                            'cubicweb.hooks.metadata')
+
     def open_connections_pools(self):
         config = self.config
         self._available_pools = Queue.Queue()
-        self._available_pools.put_nowait(ConnectionsPool(self.sources))
+        self._available_pools.put_nowait(pool.ConnectionsPool(self.sources))
         if config.read_instance_schema:
             # normal start: load the instance schema from the database
             self.fill_schema()
@@ -191,16 +191,14 @@
             # usually during repository creation
             self.warning("set fs instance'schema as bootstrap schema")
             config.bootstrap_cubes()
-            self.set_bootstrap_schema(config.load_schema())
+            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])
-            self.vreg.load_file(join(etdirectory, '__init__.py'),
-                                'cubicweb.entities.__init__')
-            self.vreg.load_file(join(etdirectory, 'authobjs.py'),
-                                'cubicweb.entities.authobjs')
-            self.vreg.load_file(join(etdirectory, 'wfobjs.py'),
-                                'cubicweb.entities.wfobjs')
+            for modname in ('__init__', 'authobjs', 'wfobjs'):
+                self.vreg.load_file(join(etdirectory, '%s.py' % modname),
+                                'cubicweb.entities.%s' % modname)
+            self._boostrap_hook_registry()
         else:
             # test start: use the file system schema (quicker)
             self.warning("set fs instance'schema")
@@ -226,27 +224,29 @@
         # list of available pools (we can't iterated on Queue instance)
         self.pools = []
         for i in xrange(config['connections-pool-size']):
-            self.pools.append(ConnectionsPool(self.sources))
+            self.pools.append(pool.ConnectionsPool(self.sources))
             self._available_pools.put_nowait(self.pools[-1])
         self._shutting_down = False
+        self.hm = self.vreg['hooks']
         if not (config.creating or config.repairing):
             # call instance level initialisation hooks
             self.hm.call_hooks('server_startup', repo=self)
             # register a task to cleanup expired session
             self.looping_task(config['session-time']/3., self.clean_sessions)
-        CW_EVENT_MANAGER.bind('after-registry-reload', self.reset_hooks)
 
     # internals ###############################################################
 
     def get_source(self, uri, source_config):
         source_config['uri'] = uri
-        return get_source(source_config, self.schema, self)
+        return sources.get_source(source_config, self.schema, self)
 
     def set_schema(self, schema, resetvreg=True, rebuildinfered=True):
         if rebuildinfered:
             schema.rebuild_infered_relations()
         self.info('set schema %s %#x', schema.name, id(schema))
         if resetvreg:
+            if self.config._cubes is None:
+                self.config.init_cubes(self.get_cubes())
             # full reload of all appobjects
             self.vreg.reset()
             self.vreg.set_schema(schema)
@@ -256,22 +256,13 @@
         for source in self.sources:
             source.set_schema(schema)
         self.schema = schema
-        self.reset_hooks()
-
-    def reset_hooks(self):
-        self.hm.set_schema(self.schema)
-        self.hm.register_system_hooks(self.config)
-        # instance specific hooks
-        if self.config.instance_hooks:
-            self.info('loading instance hooks')
-            self.hm.register_hooks(self.config.load_hooks(self.vreg))
 
     def fill_schema(self):
         """lod schema from the repository"""
         from cubicweb.server.schemaserial import deserialize_schema
         self.info('loading schema from the repository')
-        appschema = CubicWebSchema(self.config.appid)
-        self.set_bootstrap_schema(self.config.load_bootstrap_schema())
+        appschema = schema.CubicWebSchema(self.config.appid)
+        self.set_schema(self.config.load_bootstrap_schema(), resetvreg=False)
         self.debug('deserializing db schema into %s %#x', appschema.name, id(appschema))
         session = self.internal_session()
         try:
@@ -285,44 +276,14 @@
                 raise Exception('Is the database initialised ? (cause: %s)' %
                                 (ex.args and ex.args[0].strip() or 'unknown')), \
                                 None, sys.exc_info()[-1]
-            self.info('set the actual schema')
-            # XXX have to do this since CWProperty isn't in the bootstrap schema
-            #     it'll be redone in set_schema
-            self.set_bootstrap_schema(appschema)
-            # 2.49 migration
-            if exists(join(self.config.apphome, 'vc.conf')):
-                session.set_pool()
-                if not 'template' in file(join(self.config.apphome, 'vc.conf')).read():
-                    # remaning from cubicweb < 2.38...
-                    session.execute('DELETE CWProperty X WHERE X pkey "system.version.template"')
-                    session.commit()
         finally:
             session.close()
-        self.config.init_cubes(self.get_cubes())
         self.set_schema(appschema)
 
-    def set_bootstrap_schema(self, schema):
-        """disable hooks when setting a bootstrap schema, but restore
-        the configuration for the next time
-        """
-        config = self.config
-        # XXX refactor
-        config.core_hooks = False
-        config.usergroup_hooks = False
-        config.schema_hooks = False
-        config.notification_hooks = False
-        config.instance_hooks = False
-        self.set_schema(schema, resetvreg=False)
-        config.core_hooks = True
-        config.usergroup_hooks = True
-        config.schema_hooks = True
-        config.notification_hooks = True
-        config.instance_hooks = True
-
     def start_looping_tasks(self):
         assert isinstance(self._looping_tasks, list), 'already started'
         for i, (interval, func, args) in enumerate(self._looping_tasks):
-            self._looping_tasks[i] = task = LoopTask(interval, func, args)
+            self._looping_tasks[i] = task = utils.LoopTask(interval, func, args)
             self.info('starting task %s with interval %.2fs', task.name,
                       interval)
             task.start()
@@ -342,7 +303,7 @@
 
     def threaded_task(self, func):
         """start function in a separated thread"""
-        t = RepoThread(func, self._running_threads)
+        t = utils.RepoThread(func, self._running_threads)
         t.start()
 
     #@locked
@@ -582,11 +543,11 @@
         finally:
             session.close()
         session = Session(user, self, cnxprops)
-        user.req = user.rset.req = session
+        user._cw = user.cw_rset.req = session
         user.clear_related_cache()
         self._sessions[session.id] = session
         self.info('opened %s', session)
-        self.hm.call_hooks('session_open', session=session)
+        self.hm.call_hooks('session_open', session)
         # commit session at this point in case write operation has been done
         # during `session_open` hooks
         session.commit()
@@ -677,7 +638,7 @@
                                     checkshuttingdown=checkshuttingdown)
         # operation uncommited before close are rollbacked before hook is called
         session.rollback()
-        self.hm.call_hooks('session_close', session=session)
+        self.hm.call_hooks('session_close', session)
         # commit session at this point in case write operation has been done
         # during `session_close` hooks
         session.commit()
@@ -858,11 +819,11 @@
                 entity = source.before_entity_insertion(session, extid, etype, eid)
                 entity._cw_recreating = True
                 if source.should_call_hooks:
-                    self.hm.call_hooks('before_add_entity', etype, session, entity)
+                    self.hm.call_hooks('before_add_entity', session, entity=entity)
                 # XXX add fti op ?
                 source.after_entity_insertion(session, extid, entity)
                 if source.should_call_hooks:
-                    self.hm.call_hooks('after_add_entity', etype, session, entity)
+                    self.hm.call_hooks('after_add_entity', session, entity=entity)
             if reset_pool:
                 session.reset_pool()
             return eid
@@ -883,16 +844,17 @@
             self._type_source_cache[eid] = (etype, source.uri, extid)
             entity = source.before_entity_insertion(session, extid, etype, eid)
             if source.should_call_hooks:
-                self.hm.call_hooks('before_add_entity', etype, session, entity)
+                entity.edited_attributes = set(entity)
+                self.hm.call_hooks('before_add_entity', session, entity=entity)
             # XXX call add_info with complete=False ?
             self.add_info(session, entity, source, extid)
             source.after_entity_insertion(session, extid, entity)
             if source.should_call_hooks:
-                self.hm.call_hooks('after_add_entity', etype, session, entity)
+                self.hm.call_hooks('after_add_entity', session, entity=entity)
             else:
                 # minimal meta-data
                 session.execute('SET X is E WHERE X eid %(x)s, E name %(name)s',
-                                {'x': entity.eid, 'name': entity.id}, 'x')
+                                {'x': entity.eid, 'name': entity.__regid__}, 'x')
             session.commit(reset_pool)
             return eid
         except:
@@ -947,7 +909,7 @@
         pendingrtypes = session.transaction_data.get('pendingrtypes', ())
         for rschema, targetschemas, x in eschema.relation_definitions():
             rtype = rschema.type
-            if rtype in VIRTUAL_RTYPES or rtype in pendingrtypes:
+            if rtype in schema.VIRTUAL_RTYPES or rtype in pendingrtypes:
                 continue
             var = '%s%s' % (rtype.upper(), x.upper())
             if x == 'subject':
@@ -1016,7 +978,7 @@
         # init edited_attributes before calling before_add_entity hooks
         entity.edited_attributes = set(entity)
         if source.should_call_hooks:
-            self.hm.call_hooks('before_add_entity', etype, session, entity)
+            self.hm.call_hooks('before_add_entity', session, entity=entity)
         # XXX use entity.keys here since edited_attributes is not updated for
         # inline relations
         for attr in entity.keys():
@@ -1037,7 +999,7 @@
         session.set_entity_cache(entity)
         for rschema in eschema.subject_relations():
             rtype = str(rschema)
-            if rtype in VIRTUAL_RTYPES:
+            if rtype in schema.VIRTUAL_RTYPES:
                 continue
             if rschema.is_final():
                 entity.setdefault(rtype, None)
@@ -1045,7 +1007,7 @@
                 entity.set_related_cache(rtype, 'subject', session.empty_rset())
         for rschema in eschema.object_relations():
             rtype = str(rschema)
-            if rtype in VIRTUAL_RTYPES:
+            if rtype in schema.VIRTUAL_RTYPES:
                 continue
             entity.set_related_cache(rtype, 'object', session.empty_rset())
         # set inline relation cache before call to after_add_entity
@@ -1053,13 +1015,13 @@
             session.update_rel_cache_add(entity.eid, attr, value)
         # trigger after_add_entity after after_add_relation
         if source.should_call_hooks:
-            self.hm.call_hooks('after_add_entity', etype, session, entity)
+            self.hm.call_hooks('after_add_entity', session, entity=entity)
             # call hooks for inlined relations
             for attr, value in relations:
-                self.hm.call_hooks('before_add_relation', attr, session,
-                                   entity.eid, attr, value)
-                self.hm.call_hooks('after_add_relation', attr, session,
-                                   entity.eid, attr, value)
+                self.hm.call_hooks('before_add_relation', session,
+                                    eidfrom=entity.eid, rtype=attr, eidto=value)
+                self.hm.call_hooks('after_add_relation', session,
+                                    eidfrom=entity.eid, rtype=attr, eidto=value)
         return entity.eid
 
     def glob_update_entity(self, session, entity, edited_attributes):
@@ -1092,19 +1054,18 @@
                     if previous_value == entity[attr]:
                         previous_value = None
                     else:
-                        self.hm.call_hooks('before_delete_relation', attr,
-                                           session, entity.eid, attr,
-                                           previous_value)
+                        self.hm.call_hooks('before_delete_relation', session,
+                                           eidfrom=entity.eid, rtype=attr,
+                                           eidto=previous_value)
                 relations.append((attr, entity[attr], previous_value))
         source = self.source_from_eid(entity.eid, session)
         if source.should_call_hooks:
             # call hooks for inlined relations
             for attr, value, _ in relations:
-                self.hm.call_hooks('before_add_relation', attr, session,
-                                    entity.eid, attr, value)
+                self.hm.call_hooks('before_add_relation', session,
+                                    eidfrom=entity.eid, rtype=attr, eidto=value)
             if not only_inline_rels:
-                self.hm.call_hooks('before_update_entity', etype, session,
-                                    entity)
+                self.hm.call_hooks('before_update_entity', session, entity=entity)
         source.update_entity(session, entity)
         if not only_inline_rels:
             if need_fti_update and self.do_fti:
@@ -1112,15 +1073,14 @@
                 # one indexable attribute
                 FTIndexEntityOp(session, entity=entity)
             if source.should_call_hooks:
-                self.hm.call_hooks('after_update_entity', etype, session,
-                                    entity)
+                self.hm.call_hooks('after_update_entity', session, entity=entity)
         if source.should_call_hooks:
             for attr, value, prevvalue in relations:
                 # if the relation is already cached, update existant cache
                 relcache = entity.relation_cached(attr, 'subject')
                 if prevvalue is not None:
-                    self.hm.call_hooks('after_delete_relation', attr, session,
-                                       entity.eid, attr, prevvalue)
+                    self.hm.call_hooks('after_delete_relation', session,
+                                       eidfrom=entity.eid, rtype=attr, eidto=prevvalue)
                     if relcache is not None:
                         session.update_rel_cache_del(entity.eid, attr, prevvalue)
                 del_existing_rel_if_needed(session, entity.eid, attr, value)
@@ -1129,8 +1089,8 @@
                 else:
                     entity.set_related_cache(attr, 'subject',
                                              session.eid_rset(value))
-                self.hm.call_hooks('after_add_relation', attr, session,
-                                    entity.eid, attr, value)
+                self.hm.call_hooks('after_add_relation', session,
+                                    eidfrom=entity.eid, rtype=attr, eidto=value)
 
     def glob_delete_entity(self, session, eid):
         """delete an entity and all related entities from the repository"""
@@ -1143,11 +1103,12 @@
                 server.DEBUG |= (server.DBG_SQL | server.DBG_RQL | server.DBG_MORE)
         source = self.sources_by_uri[uri]
         if source.should_call_hooks:
-            self.hm.call_hooks('before_delete_entity', etype, session, eid)
+            entity = session.entity_from_eid(eid)
+            self.hm.call_hooks('before_delete_entity', session, entity=entity)
         self._delete_info(session, eid)
         source.delete_entity(session, etype, eid)
         if source.should_call_hooks:
-            self.hm.call_hooks('after_delete_entity', etype, session, eid)
+            self.hm.call_hooks('after_delete_entity', session, entity=entity)
         # don't clear cache here this is done in a hook on commit
 
     def glob_add_relation(self, session, subject, rtype, object):
@@ -1157,14 +1118,14 @@
         source = self.locate_relation_source(session, subject, rtype, object)
         if source.should_call_hooks:
             del_existing_rel_if_needed(session, subject, rtype, object)
-            self.hm.call_hooks('before_add_relation', rtype, session,
-                               subject, rtype, object)
+            self.hm.call_hooks('before_add_relation', session,
+                               eidfrom=subject, rtype=rtype, eidto=object)
         source.add_relation(session, subject, rtype, object)
         rschema = self.schema.rschema(rtype)
         session.update_rel_cache_add(subject, rtype, object, rschema.symetric)
         if source.should_call_hooks:
-            self.hm.call_hooks('after_add_relation', rtype, session,
-                               subject, rtype, object)
+            self.hm.call_hooks('after_add_relation', session,
+                               eidfrom=subject, rtype=rtype, eidto=object)
 
     def glob_delete_relation(self, session, subject, rtype, object):
         """delete a relation from the repository"""
@@ -1172,8 +1133,8 @@
             print 'DELETE relation', subject, rtype, object
         source = self.locate_relation_source(session, subject, rtype, object)
         if source.should_call_hooks:
-            self.hm.call_hooks('before_delete_relation', rtype, session,
-                               subject, rtype, object)
+            self.hm.call_hooks('before_delete_relation', session,
+                               eidfrom=subject, rtype=rtype, eidto=object)
         source.delete_relation(session, subject, rtype, object)
         rschema = self.schema.rschema(rtype)
         session.update_rel_cache_del(subject, rtype, object, rschema.symetric)
@@ -1182,8 +1143,8 @@
             # stored so try to delete both
             source.delete_relation(session, object, rtype, subject)
         if source.should_call_hooks:
-            self.hm.call_hooks('after_delete_relation', rtype, session,
-                               subject, rtype, object)
+            self.hm.call_hooks('after_delete_relation', session,
+                               eidfrom=subject, rtype=rtype, eidto=object)
 
 
     # pyro handling ###########################################################
--- a/server/schemahooks.py	Wed Sep 30 17:57:02 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1097 +0,0 @@
-"""schema hooks:
-
-- synchronize the living schema object with the persistent schema
-- perform physical update on the source when necessary
-
-checking for schema consistency is done in hooks.py
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from yams.schema import BASE_TYPES
-from yams.buildobjs import EntityType, RelationType, RelationDefinition
-from yams.schema2sql import eschema2sql, rschema2sql, type_from_constraints
-
-
-from cubicweb import ValidationError, RepositoryError
-from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, CONSTRAINTS
-from cubicweb.server import schemaserial as ss
-from cubicweb.server.sqlutils import SQL_PREFIX
-from cubicweb.server.pool import Operation, SingleLastOperation, PreCommitOperation
-from cubicweb.server.hookhelper import (entity_attr, entity_name,
-                                        check_internal_entity)
-
-
-TYPE_CONVERTER = { # XXX
-    'Boolean': bool,
-    'Int': int,
-    'Float': float,
-    'Password': str,
-    'String': unicode,
-    'Date' : unicode,
-    'Datetime' : unicode,
-    'Time' : unicode,
-    }
-
-# core entity and relation types which can't be removed
-CORE_ETYPES = list(BASE_TYPES) + ['CWEType', 'CWRType', 'CWUser', 'CWGroup',
-                                  'CWConstraint', 'CWAttribute', 'CWRelation']
-CORE_RTYPES = ['eid', 'creation_date', 'modification_date', 'cwuri',
-               'login', 'upassword', 'name',
-               'is', 'instanceof', 'owned_by', 'created_by', 'in_group',
-               'relation_type', 'from_entity', 'to_entity',
-               'constrainted_by',
-               'read_permission', 'add_permission',
-               'delete_permission', 'updated_permission',
-               ]
-
-def get_constraints(session, entity):
-    constraints = []
-    for cstreid in session.transaction_data.get(entity.eid, ()):
-        cstrent = session.entity_from_eid(cstreid)
-        cstr = CONSTRAINTS[cstrent.type].deserialize(cstrent.value)
-        cstr.eid = cstreid
-        constraints.append(cstr)
-    return constraints
-
-def add_inline_relation_column(session, etype, rtype):
-    """add necessary column and index for an inlined relation"""
-    table = SQL_PREFIX + etype
-    column = SQL_PREFIX + rtype
-    try:
-        session.system_sql(str('ALTER TABLE %s ADD COLUMN %s integer'
-                               % (table, column)), rollback_on_failure=False)
-        session.info('added column %s to table %s', column, table)
-    except:
-        # silent exception here, if this error has not been raised because the
-        # column already exists, index creation will fail anyway
-        session.exception('error while adding column %s to table %s',
-                          table, column)
-    # create index before alter table which may expectingly fail during test
-    # (sqlite) while index creation should never fail (test for index existence
-    # is done by the dbhelper)
-    session.pool.source('system').create_index(session, table, column)
-    session.info('added index on %s(%s)', table, column)
-    session.transaction_data.setdefault('createdattrs', []).append(
-        '%s.%s' % (etype, rtype))
-
-
-# operations for low-level database alteration  ################################
-
-class DropTable(PreCommitOperation):
-    """actually remove a database from the instance's schema"""
-    table = None # make pylint happy
-    def precommit_event(self):
-        dropped = self.session.transaction_data.setdefault('droppedtables',
-                                                           set())
-        if self.table in dropped:
-            return # already processed
-        dropped.add(self.table)
-        self.session.system_sql('DROP TABLE %s' % self.table)
-        self.info('dropped table %s', self.table)
-
-
-class DropRelationTable(DropTable):
-    def __init__(self, session, rtype):
-        super(DropRelationTable, self).__init__(
-            session, table='%s_relation' % rtype)
-        session.transaction_data.setdefault('pendingrtypes', set()).add(rtype)
-
-
-class DropColumn(PreCommitOperation):
-    """actually remove the attribut's column from entity table in the system
-    database
-    """
-    table = column = None # make pylint happy
-    def precommit_event(self):
-        session, table, column = self.session, self.table, self.column
-        # drop index if any
-        session.pool.source('system').drop_index(session, table, column)
-        try:
-            session.system_sql('ALTER TABLE %s DROP COLUMN %s'
-                               % (table, column), rollback_on_failure=False)
-            self.info('dropped column %s from table %s', column, table)
-        except Exception, ex:
-            # not supported by sqlite for instance
-            self.error('error while altering table %s: %s', table, ex)
-
-
-# base operations for in-memory schema synchronization  ########################
-
-class MemSchemaNotifyChanges(SingleLastOperation):
-    """the update schema operation:
-
-    special operation which should be called once and after all other schema
-    operations. It will trigger internal structures rebuilding to consider
-    schema changes
-    """
-
-    def __init__(self, session):
-        self.repo = session.repo
-        SingleLastOperation.__init__(self, session)
-
-    def commit_event(self):
-        rebuildinfered = self.session.data.get('rebuild-infered', True)
-        self.repo.set_schema(self.repo.schema, rebuildinfered=rebuildinfered)
-
-
-class MemSchemaOperation(Operation):
-    """base class for schema operations"""
-    def __init__(self, session, kobj=None, **kwargs):
-        self.schema = session.schema
-        self.kobj = kobj
-        # once Operation.__init__ has been called, event may be triggered, so
-        # do this last !
-        Operation.__init__(self, session, **kwargs)
-        # every schema operation is triggering a schema update
-        MemSchemaNotifyChanges(session)
-
-    def prepare_constraints(self, subjtype, rtype, objtype):
-        constraints = rtype.rproperty(subjtype, objtype, 'constraints')
-        self.constraints = list(constraints)
-        rtype.set_rproperty(subjtype, objtype, 'constraints', self.constraints)
-
-
-class MemSchemaEarlyOperation(MemSchemaOperation):
-    def insert_index(self):
-        """schema operation which are inserted at the begining of the queue
-        (typically to add/remove entity or relation types)
-        """
-        i = -1
-        for i, op in enumerate(self.session.pending_operations):
-            if not isinstance(op, MemSchemaEarlyOperation):
-                return i
-        return i + 1
-
-
-class MemSchemaPermissionOperation(MemSchemaOperation):
-    """base class to synchronize schema permission definitions"""
-    def __init__(self, session, perm, etype_eid):
-        self.perm = perm
-        try:
-            self.name = entity_name(session, etype_eid)
-        except IndexError:
-            self.error('changing permission of a no more existant type #%s',
-                etype_eid)
-        else:
-            Operation.__init__(self, session)
-
-
-# operations for high-level source database alteration  ########################
-
-class SourceDbCWETypeRename(PreCommitOperation):
-    """this operation updates physical storage accordingly"""
-    oldname = newname = None # make pylint happy
-
-    def precommit_event(self):
-        # we need sql to operate physical changes on the system database
-        sqlexec = self.session.system_sql
-        sqlexec('ALTER TABLE %s%s RENAME TO %s%s' % (SQL_PREFIX, self.oldname,
-                                                     SQL_PREFIX, self.newname))
-        self.info('renamed table %s to %s', self.oldname, self.newname)
-        sqlexec('UPDATE entities SET type=%s WHERE type=%s',
-                (self.newname, self.oldname))
-        sqlexec('UPDATE deleted_entities SET type=%s WHERE type=%s',
-                (self.newname, self.oldname))
-
-
-class SourceDbCWRTypeUpdate(PreCommitOperation):
-    """actually update some properties of a relation definition"""
-    rschema = values = entity = None # make pylint happy
-
-    def precommit_event(self):
-        session = self.session
-        rschema = self.rschema
-        if rschema.is_final() or not 'inlined' in self.values:
-            return # nothing to do
-        inlined = self.values['inlined']
-        entity = self.entity
-        # check in-lining is necessary / possible
-        if not entity.inlined_changed(inlined):
-            return # nothing to do
-        # inlined changed, make necessary physical changes!
-        sqlexec = self.session.system_sql
-        rtype = rschema.type
-        eidcolumn = SQL_PREFIX + 'eid'
-        if not inlined:
-            # need to create the relation if it has not been already done by
-            # another event of the same transaction
-            if not rschema.type in session.transaction_data.get('createdtables', ()):
-                tablesql = rschema2sql(rschema)
-                # create the necessary table
-                for sql in tablesql.split(';'):
-                    if sql.strip():
-                        sqlexec(sql)
-                session.transaction_data.setdefault('createdtables', []).append(
-                    rschema.type)
-            # copy existant data
-            column = SQL_PREFIX + rtype
-            for etype in rschema.subjects():
-                table = SQL_PREFIX + str(etype)
-                sqlexec('INSERT INTO %s_relation SELECT %s, %s FROM %s WHERE NOT %s IS NULL'
-                        % (rtype, eidcolumn, column, table, column))
-            # drop existant columns
-            for etype in rschema.subjects():
-                DropColumn(session, table=SQL_PREFIX + str(etype),
-                             column=SQL_PREFIX + rtype)
-        else:
-            for etype in rschema.subjects():
-                try:
-                    add_inline_relation_column(session, str(etype), rtype)
-                except Exception, ex:
-                    # the column probably already exists. this occurs when the
-                    # entity's type has just been added or if the column has not
-                    # been previously dropped
-                    self.error('error while altering table %s: %s', etype, ex)
-                # copy existant data.
-                # XXX don't use, it's not supported by sqlite (at least at when i tried it)
-                #sqlexec('UPDATE %(etype)s SET %(rtype)s=eid_to '
-                #        'FROM %(rtype)s_relation '
-                #        'WHERE %(etype)s.eid=%(rtype)s_relation.eid_from'
-                #        % locals())
-                table = SQL_PREFIX + str(etype)
-                cursor = sqlexec('SELECT eid_from, eid_to FROM %(table)s, '
-                                 '%(rtype)s_relation WHERE %(table)s.%(eidcolumn)s='
-                                 '%(rtype)s_relation.eid_from' % locals())
-                args = [{'val': eid_to, 'x': eid} for eid, eid_to in cursor.fetchall()]
-                if args:
-                    column = SQL_PREFIX + rtype
-                    cursor.executemany('UPDATE %s SET %s=%%(val)s WHERE %s=%%(x)s'
-                                       % (table, column, eidcolumn), args)
-                # drop existant table
-                DropRelationTable(session, rtype)
-
-
-class SourceDbCWAttributeAdd(PreCommitOperation):
-    """an attribute relation (CWAttribute) has been added:
-    * add the necessary column
-    * set default on this column if any and possible
-    * register an operation to add the relation definition to the
-      instance's schema on commit
-
-    constraints are handled by specific hooks
-    """
-    entity = None # make pylint happy
-
-    def init_rdef(self, **kwargs):
-        entity = self.entity
-        fromentity = entity.stype
-        self.session.execute('SET X ordernum Y+1 '
-                             'WHERE X from_entity SE, SE eid %(se)s, X ordernum Y, '
-                             'X ordernum >= %(order)s, NOT X eid %(x)s',
-                             {'x': entity.eid, 'se': fromentity.eid,
-                              'order': entity.ordernum or 0})
-        subj = str(fromentity.name)
-        rtype = entity.rtype.name
-        obj = str(entity.otype.name)
-        constraints = get_constraints(self.session, entity)
-        rdef = RelationDefinition(subj, rtype, obj,
-                                  description=entity.description,
-                                  cardinality=entity.cardinality,
-                                  constraints=constraints,
-                                  order=entity.ordernum,
-                                  eid=entity.eid,
-                                  **kwargs)
-        MemSchemaRDefAdd(self.session, rdef)
-        return rdef
-
-    def precommit_event(self):
-        session = self.session
-        entity = self.entity
-        # entity.defaultval is a string or None, but we need a correctly typed
-        # value
-        default = entity.defaultval
-        if default is not None:
-            default = TYPE_CONVERTER[entity.otype.name](default)
-        props = {'default': default,
-                 'indexed': entity.indexed,
-                 'fulltextindexed': entity.fulltextindexed,
-                 'internationalizable': entity.internationalizable}
-        rdef = self.init_rdef(**props)
-        sysource = session.pool.source('system')
-        attrtype = type_from_constraints(sysource.dbhelper, rdef.object,
-                                         rdef.constraints)
-        # XXX should be moved somehow into lgc.adbh: sqlite doesn't support to
-        # add a new column with UNIQUE, it should be added after the ALTER TABLE
-        # using ADD INDEX
-        if sysource.dbdriver == 'sqlite' and 'UNIQUE' in attrtype:
-            extra_unique_index = True
-            attrtype = attrtype.replace(' UNIQUE', '')
-        else:
-            extra_unique_index = False
-        # added some str() wrapping query since some backend (eg psycopg) don't
-        # allow unicode queries
-        table = SQL_PREFIX + rdef.subject
-        column = SQL_PREFIX + rdef.name
-        try:
-            session.system_sql(str('ALTER TABLE %s ADD COLUMN %s %s'
-                                   % (table, column, attrtype)),
-                               rollback_on_failure=False)
-            self.info('added column %s to table %s', table, column)
-        except Exception, ex:
-            # the column probably already exists. this occurs when
-            # the entity's type has just been added or if the column
-            # has not been previously dropped
-            self.error('error while altering table %s: %s', table, ex)
-        if extra_unique_index or entity.indexed:
-            try:
-                sysource.create_index(session, table, column,
-                                      unique=extra_unique_index)
-            except Exception, ex:
-                self.error('error while creating index for %s.%s: %s',
-                           table, column, ex)
-        # final relations are not infered, propagate
-        try:
-            eschema = self.schema.eschema(rdef.subject)
-        except KeyError:
-            return # entity type currently being added
-        rschema = self.schema.rschema(rdef.name)
-        props.update({'constraints': rdef.constraints,
-                      'description': rdef.description,
-                      'cardinality': rdef.cardinality,
-                      'constraints': rdef.constraints,
-                      'order': rdef.order})
-        for specialization in eschema.specialized_by(False):
-            if rschema.has_rdef(specialization, rdef.object):
-                continue
-            for rql, args in ss.frdef2rql(rschema, str(specialization),
-                                          rdef.object, props):
-                session.execute(rql, args)
-
-
-class SourceDbCWRelationAdd(SourceDbCWAttributeAdd):
-    """an actual relation has been added:
-    * if this is an inlined relation, add the necessary column
-      else if it's the first instance of this relation type, add the
-      necessary table and set default permissions
-    * register an operation to add the relation definition to the
-      instance's schema on commit
-
-    constraints are handled by specific hooks
-    """
-    entity = None # make pylint happy
-
-    def precommit_event(self):
-        session = self.session
-        entity = self.entity
-        rdef = self.init_rdef(composite=entity.composite)
-        schema = session.schema
-        rtype = rdef.name
-        rschema = session.schema.rschema(rtype)
-        # this have to be done before permissions setting
-        if rschema.inlined:
-            # need to add a column if the relation is inlined and if this is the
-            # first occurence of "Subject relation Something" whatever Something
-            # and if it has not been added during other event of the same
-            # transaction
-            key = '%s.%s' % (rdef.subject, rtype)
-            try:
-                alreadythere = bool(rschema.objects(rdef.subject))
-            except KeyError:
-                alreadythere = False
-            if not (alreadythere or
-                    key in session.transaction_data.get('createdattrs', ())):
-                add_inline_relation_column(session, rdef.subject, rtype)
-        else:
-            # need to create the relation if no relation definition in the
-            # schema and if it has not been added during other event of the same
-            # transaction
-            if not (rschema.subjects() or
-                    rtype in session.transaction_data.get('createdtables', ())):
-                try:
-                    rschema = session.schema.rschema(rtype)
-                    tablesql = rschema2sql(rschema)
-                except KeyError:
-                    # fake we add it to the schema now to get a correctly
-                    # initialized schema but remove it before doing anything
-                    # more dangerous...
-                    rschema = session.schema.add_relation_type(rdef)
-                    tablesql = rschema2sql(rschema)
-                    session.schema.del_relation_type(rtype)
-                # create the necessary table
-                for sql in tablesql.split(';'):
-                    if sql.strip():
-                        session.system_sql(sql)
-                session.transaction_data.setdefault('createdtables', []).append(
-                    rtype)
-
-
-class SourceDbRDefUpdate(PreCommitOperation):
-    """actually update some properties of a relation definition"""
-    rschema = values = None # make pylint happy
-
-    def precommit_event(self):
-        etype = self.kobj[0]
-        table = SQL_PREFIX + etype
-        column = SQL_PREFIX + self.rschema.type
-        if 'indexed' in self.values:
-            sysource = self.session.pool.source('system')
-            if self.values['indexed']:
-                sysource.create_index(self.session, table, column)
-            else:
-                sysource.drop_index(self.session, table, column)
-        if 'cardinality' in self.values and self.rschema.is_final():
-            adbh = self.session.pool.source('system').dbhelper
-            if not adbh.alter_column_support:
-                # not supported (and NOT NULL not set by yams in that case, so
-                # no worry)
-                return
-            atype = self.rschema.objects(etype)[0]
-            constraints = self.rschema.rproperty(etype, atype, 'constraints')
-            coltype = type_from_constraints(adbh, atype, constraints,
-                                            creating=False)
-            # XXX check self.values['cardinality'][0] actually changed?
-            sql = adbh.sql_set_null_allowed(table, column, coltype,
-                                            self.values['cardinality'][0] != '1')
-            self.session.system_sql(sql)
-
-
-class SourceDbCWConstraintAdd(PreCommitOperation):
-    """actually update constraint of a relation definition"""
-    entity = None # make pylint happy
-    cancelled = False
-
-    def precommit_event(self):
-        rdef = self.entity.reverse_constrained_by[0]
-        session = self.session
-        # when the relation is added in the same transaction, the constraint
-        # object is created by the operation adding the attribute or relation,
-        # so there is nothing to do here
-        if rdef.eid in session.transaction_data.get('neweids', ()):
-            return
-        subjtype, rtype, objtype = session.schema.schema_by_eid(rdef.eid)
-        cstrtype = self.entity.type
-        oldcstr = rtype.constraint_by_type(subjtype, objtype, cstrtype)
-        newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
-        table = SQL_PREFIX + str(subjtype)
-        column = SQL_PREFIX + str(rtype)
-        # alter the physical schema on size constraint changes
-        if newcstr.type() == 'SizeConstraint' and (
-            oldcstr is None or oldcstr.max != newcstr.max):
-            adbh = self.session.pool.source('system').dbhelper
-            card = rtype.rproperty(subjtype, objtype, 'cardinality')
-            coltype = type_from_constraints(adbh, objtype, [newcstr],
-                                            creating=False)
-            sql = adbh.sql_change_col_type(table, column, coltype, card != '1')
-            try:
-                session.system_sql(sql, rollback_on_failure=False)
-                self.info('altered column %s of table %s: now VARCHAR(%s)',
-                          column, table, newcstr.max)
-            except Exception, ex:
-                # not supported by sqlite for instance
-                self.error('error while altering table %s: %s', table, ex)
-        elif cstrtype == 'UniqueConstraint' and oldcstr is None:
-            session.pool.source('system').create_index(
-                self.session, table, column, unique=True)
-
-
-class SourceDbCWConstraintDel(PreCommitOperation):
-    """actually remove a constraint of a relation definition"""
-    rtype = subjtype = objtype = None # make pylint happy
-
-    def precommit_event(self):
-        cstrtype = self.cstr.type()
-        table = SQL_PREFIX + str(self.subjtype)
-        column = SQL_PREFIX + str(self.rtype)
-        # alter the physical schema on size/unique constraint changes
-        if cstrtype == 'SizeConstraint':
-            try:
-                self.session.system_sql('ALTER TABLE %s ALTER COLUMN %s TYPE TEXT'
-                                        % (table, column),
-                                        rollback_on_failure=False)
-                self.info('altered column %s of table %s: now TEXT',
-                          column, table)
-            except Exception, ex:
-                # not supported by sqlite for instance
-                self.error('error while altering table %s: %s', table, ex)
-        elif cstrtype == 'UniqueConstraint':
-            self.session.pool.source('system').drop_index(
-                self.session, table, column, unique=True)
-
-
-# operations for in-memory schema synchronization  #############################
-
-class MemSchemaCWETypeAdd(MemSchemaEarlyOperation):
-    """actually add the entity type to the instance's schema"""
-    eid = None # make pylint happy
-    def commit_event(self):
-        self.schema.add_entity_type(self.kobj)
-
-
-class MemSchemaCWETypeRename(MemSchemaOperation):
-    """this operation updates physical storage accordingly"""
-    oldname = newname = None # make pylint happy
-
-    def commit_event(self):
-        self.session.schema.rename_entity_type(self.oldname, self.newname)
-
-
-class MemSchemaCWETypeDel(MemSchemaOperation):
-    """actually remove the entity type from the instance's schema"""
-    def commit_event(self):
-        try:
-            # del_entity_type also removes entity's relations
-            self.schema.del_entity_type(self.kobj)
-        except KeyError:
-            # s/o entity type have already been deleted
-            pass
-
-
-class MemSchemaCWRTypeAdd(MemSchemaEarlyOperation):
-    """actually add the relation type to the instance's schema"""
-    eid = None # make pylint happy
-    def commit_event(self):
-        rschema = self.schema.add_relation_type(self.kobj)
-        rschema.set_default_groups()
-
-
-class MemSchemaCWRTypeUpdate(MemSchemaOperation):
-    """actually update some properties of a relation definition"""
-    rschema = values = None # make pylint happy
-
-    def commit_event(self):
-        # structure should be clean, not need to remove entity's relations
-        # at this point
-        self.rschema.__dict__.update(self.values)
-
-
-class MemSchemaCWRTypeDel(MemSchemaOperation):
-    """actually remove the relation type from the instance's schema"""
-    def commit_event(self):
-        try:
-            self.schema.del_relation_type(self.kobj)
-        except KeyError:
-            # s/o entity type have already been deleted
-            pass
-
-
-class MemSchemaRDefAdd(MemSchemaEarlyOperation):
-    """actually add the attribute relation definition to the instance's
-    schema
-    """
-    def commit_event(self):
-        self.schema.add_relation_def(self.kobj)
-
-
-class MemSchemaRDefUpdate(MemSchemaOperation):
-    """actually update some properties of a relation definition"""
-    rschema = values = None # make pylint happy
-
-    def commit_event(self):
-        # structure should be clean, not need to remove entity's relations
-        # at this point
-        self.rschema._rproperties[self.kobj].update(self.values)
-
-
-class MemSchemaRDefDel(MemSchemaOperation):
-    """actually remove the relation definition from the instance's schema"""
-    def commit_event(self):
-        subjtype, rtype, objtype = self.kobj
-        try:
-            self.schema.del_relation_def(subjtype, rtype, objtype)
-        except KeyError:
-            # relation type may have been already deleted
-            pass
-
-
-class MemSchemaCWConstraintAdd(MemSchemaOperation):
-    """actually update constraint of a relation definition
-
-    has to be called before SourceDbCWConstraintAdd
-    """
-    cancelled = False
-
-    def precommit_event(self):
-        rdef = self.entity.reverse_constrained_by[0]
-        # when the relation is added in the same transaction, the constraint
-        # object is created by the operation adding the attribute or relation,
-        # so there is nothing to do here
-        if rdef.eid in self.session.transaction_data.get('neweids', ()):
-            self.cancelled = True
-            return
-        subjtype, rtype, objtype = self.session.schema.schema_by_eid(rdef.eid)
-        self.prepare_constraints(subjtype, rtype, objtype)
-        cstrtype = self.entity.type
-        self.cstr = rtype.constraint_by_type(subjtype, objtype, cstrtype)
-        self.newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
-        self.newcstr.eid = self.entity.eid
-
-    def commit_event(self):
-        if self.cancelled:
-            return
-        # in-place modification
-        if not self.cstr is None:
-            self.constraints.remove(self.cstr)
-        self.constraints.append(self.newcstr)
-
-
-class MemSchemaCWConstraintDel(MemSchemaOperation):
-    """actually remove a constraint of a relation definition
-
-    has to be called before SourceDbCWConstraintDel
-    """
-    rtype = subjtype = objtype = None # make pylint happy
-    def precommit_event(self):
-        self.prepare_constraints(self.subjtype, self.rtype, self.objtype)
-
-    def commit_event(self):
-        self.constraints.remove(self.cstr)
-
-
-class MemSchemaPermissionCWGroupAdd(MemSchemaPermissionOperation):
-    """synchronize schema when a *_permission relation has been added on a group
-    """
-    def __init__(self, session, perm, etype_eid, group_eid):
-        self.group = entity_name(session, group_eid)
-        super(MemSchemaPermissionCWGroupAdd, self).__init__(
-            session, perm, etype_eid)
-
-    def commit_event(self):
-        """the observed connections pool has been commited"""
-        try:
-            erschema = self.schema[self.name]
-        except KeyError:
-            # duh, schema not found, log error and skip operation
-            self.error('no schema for %s', self.name)
-            return
-        groups = list(erschema.get_groups(self.perm))
-        try:
-            groups.index(self.group)
-            self.warning('group %s already have permission %s on %s',
-                         self.group, self.perm, erschema.type)
-        except ValueError:
-            groups.append(self.group)
-            erschema.set_groups(self.perm, groups)
-
-
-class MemSchemaPermissionCWGroupDel(MemSchemaPermissionCWGroupAdd):
-    """synchronize schema when a *_permission relation has been deleted from a
-    group
-    """
-
-    def commit_event(self):
-        """the observed connections pool has been commited"""
-        try:
-            erschema = self.schema[self.name]
-        except KeyError:
-            # duh, schema not found, log error and skip operation
-            self.error('no schema for %s', self.name)
-            return
-        groups = list(erschema.get_groups(self.perm))
-        try:
-            groups.remove(self.group)
-            erschema.set_groups(self.perm, groups)
-        except ValueError:
-            self.error('can\'t remove permission %s on %s to group %s',
-                self.perm, erschema.type, self.group)
-
-
-class MemSchemaPermissionRQLExpressionAdd(MemSchemaPermissionOperation):
-    """synchronize schema when a *_permission relation has been added on a rql
-    expression
-    """
-    def __init__(self, session, perm, etype_eid, expression):
-        self.expr = expression
-        super(MemSchemaPermissionRQLExpressionAdd, self).__init__(
-            session, perm, etype_eid)
-
-    def commit_event(self):
-        """the observed connections pool has been commited"""
-        try:
-            erschema = self.schema[self.name]
-        except KeyError:
-            # duh, schema not found, log error and skip operation
-            self.error('no schema for %s', self.name)
-            return
-        exprs = list(erschema.get_rqlexprs(self.perm))
-        exprs.append(erschema.rql_expression(self.expr))
-        erschema.set_rqlexprs(self.perm, exprs)
-
-
-class MemSchemaPermissionRQLExpressionDel(MemSchemaPermissionRQLExpressionAdd):
-    """synchronize schema when a *_permission relation has been deleted from an
-    rql expression
-    """
-
-    def commit_event(self):
-        """the observed connections pool has been commited"""
-        try:
-            erschema = self.schema[self.name]
-        except KeyError:
-            # duh, schema not found, log error and skip operation
-            self.error('no schema for %s', self.name)
-            return
-        rqlexprs = list(erschema.get_rqlexprs(self.perm))
-        for i, rqlexpr in enumerate(rqlexprs):
-            if rqlexpr.expression == self.expr:
-                rqlexprs.pop(i)
-                break
-        else:
-            self.error('can\'t remove permission %s on %s for expression %s',
-                self.perm, erschema.type, self.expr)
-            return
-        erschema.set_rqlexprs(self.perm, rqlexprs)
-
-
-class MemSchemaSpecializesAdd(MemSchemaOperation):
-
-    def commit_event(self):
-        eschema = self.session.schema.schema_by_eid(self.etypeeid)
-        parenteschema = self.session.schema.schema_by_eid(self.parentetypeeid)
-        eschema._specialized_type = parenteschema.type
-        parenteschema._specialized_by.append(eschema.type)
-
-
-class MemSchemaSpecializesDel(MemSchemaOperation):
-
-    def commit_event(self):
-        try:
-            eschema = self.session.schema.schema_by_eid(self.etypeeid)
-            parenteschema = self.session.schema.schema_by_eid(self.parentetypeeid)
-        except KeyError:
-            # etype removed, nothing to do
-            return
-        eschema._specialized_type = None
-        parenteschema._specialized_by.remove(eschema.type)
-
-
-# deletion hooks ###############################################################
-
-def before_del_eetype(session, eid):
-    """before deleting a CWEType entity:
-    * check that we don't remove a core entity type
-    * cascade to delete related CWAttribute and CWRelation entities
-    * instantiate an operation to delete the entity type on commit
-    """
-    # final entities can't be deleted, don't care about that
-    name = check_internal_entity(session, eid, CORE_ETYPES)
-    # delete every entities of this type
-    session.unsafe_execute('DELETE %s X' % name)
-    DropTable(session, table=SQL_PREFIX + name)
-    MemSchemaCWETypeDel(session, name)
-
-
-def after_del_eetype(session, eid):
-    # workflow cleanup
-    session.execute('DELETE Workflow X WHERE NOT X workflow_of Y')
-
-
-def before_del_ertype(session, eid):
-    """before deleting a CWRType entity:
-    * check that we don't remove a core relation type
-    * cascade to delete related CWAttribute and CWRelation entities
-    * instantiate an operation to delete the relation type on commit
-    """
-    name = check_internal_entity(session, eid, CORE_RTYPES)
-    # delete relation definitions using this relation type
-    session.execute('DELETE CWAttribute X WHERE X relation_type Y, Y eid %(x)s',
-                    {'x': eid})
-    session.execute('DELETE CWRelation X WHERE X relation_type Y, Y eid %(x)s',
-                    {'x': eid})
-    MemSchemaCWRTypeDel(session, name)
-
-
-def after_del_relation_type(session, rdefeid, rtype, rteid):
-    """before deleting a CWAttribute or CWRelation entity:
-    * if this is a final or inlined relation definition, instantiate an
-      operation to drop necessary column, else if this is the last instance
-      of a non final relation, instantiate an operation to drop necessary
-      table
-    * instantiate an operation to delete the relation definition on commit
-    * delete the associated relation type when necessary
-    """
-    subjschema, rschema, objschema = session.schema.schema_by_eid(rdefeid)
-    pendings = session.transaction_data.get('pendingeids', ())
-    # first delete existing relation if necessary
-    if rschema.is_final():
-        rdeftype = 'CWAttribute'
-    else:
-        rdeftype = 'CWRelation'
-        if not (subjschema.eid in pendings or objschema.eid in pendings):
-            pending = session.transaction_data.setdefault('pendingrdefs', set())
-            pending.add((subjschema, rschema, objschema))
-            session.execute('DELETE X %s Y WHERE X is %s, Y is %s'
-                            % (rschema, subjschema, objschema))
-    execute = session.unsafe_execute
-    rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R,'
-                   'R eid %%(x)s' % rdeftype, {'x': rteid})
-    lastrel = rset[0][0] == 0
-    # we have to update physical schema systematically for final and inlined
-    # relations, but only if it's the last instance for this relation type
-    # for other relations
-
-    if (rschema.is_final() or rschema.inlined):
-        rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R, '
-                       'R eid %%(x)s, X from_entity E, E name %%(name)s'
-                       % rdeftype, {'x': rteid, 'name': str(subjschema)})
-        if rset[0][0] == 0 and not subjschema.eid in pendings:
-            ptypes = session.transaction_data.setdefault('pendingrtypes', set())
-            ptypes.add(rschema.type)
-            DropColumn(session, table=SQL_PREFIX + subjschema.type,
-                         column=SQL_PREFIX + rschema.type)
-    elif lastrel:
-        DropRelationTable(session, rschema.type)
-    # if this is the last instance, drop associated relation type
-    if lastrel and not rteid in pendings:
-        execute('DELETE CWRType X WHERE X eid %(x)s', {'x': rteid}, 'x')
-    MemSchemaRDefDel(session, (subjschema, rschema, objschema))
-
-
-# addition hooks ###############################################################
-
-def before_add_eetype(session, entity):
-    """before adding a CWEType entity:
-    * check that we are not using an existing entity type,
-    """
-    name = entity['name']
-    schema = session.schema
-    if name in schema and schema[name].eid is not None:
-        raise RepositoryError('an entity type %s already exists' % name)
-
-def after_add_eetype(session, entity):
-    """after adding a CWEType entity:
-    * create the necessary table
-    * set creation_date and modification_date by creating the necessary
-      CWAttribute entities
-    * add owned_by relation by creating the necessary CWRelation entity
-    * register an operation to add the entity type to the instance's
-      schema on commit
-    """
-    if entity.get('final'):
-        return
-    schema = session.schema
-    name = entity['name']
-    etype = EntityType(name=name, description=entity.get('description'),
-                       meta=entity.get('meta')) # don't care about final
-    # fake we add it to the schema now to get a correctly initialized schema
-    # but remove it before doing anything more dangerous...
-    schema = session.schema
-    eschema = schema.add_entity_type(etype)
-    eschema.set_default_groups()
-    # generate table sql and rql to add metadata
-    tablesql = eschema2sql(session.pool.source('system').dbhelper, eschema,
-                           prefix=SQL_PREFIX)
-    relrqls = []
-    for rtype in (META_RTYPES - VIRTUAL_RTYPES):
-        rschema = schema[rtype]
-        sampletype = rschema.subjects()[0]
-        desttype = rschema.objects()[0]
-        props = rschema.rproperties(sampletype, desttype)
-        relrqls += list(ss.rdef2rql(rschema, name, desttype, props))
-    # now remove it !
-    schema.del_entity_type(name)
-    # create the necessary table
-    for sql in tablesql.split(';'):
-        if sql.strip():
-            session.system_sql(sql)
-    # register operation to modify the schema on commit
-    # this have to be done before adding other relations definitions
-    # or permission settings
-    etype.eid = entity.eid
-    MemSchemaCWETypeAdd(session, etype)
-    # add meta relations
-    for rql, kwargs in relrqls:
-        session.execute(rql, kwargs)
-
-
-def before_add_ertype(session, entity):
-    """before adding a CWRType entity:
-    * check that we are not using an existing relation type,
-    * register an operation to add the relation type to the instance's
-      schema on commit
-
-    We don't know yeat this point if a table is necessary
-    """
-    name = entity['name']
-    if name in session.schema.relations():
-        raise RepositoryError('a relation type %s already exists' % name)
-
-
-def after_add_ertype(session, entity):
-    """after a CWRType entity has been added:
-    * register an operation to add the relation type to the instance's
-      schema on commit
-    We don't know yeat this point if a table is necessary
-    """
-    rtype = RelationType(name=entity['name'],
-                         description=entity.get('description'),
-                         meta=entity.get('meta', False),
-                         inlined=entity.get('inlined', False),
-                         symetric=entity.get('symetric', False))
-    rtype.eid = entity.eid
-    MemSchemaCWRTypeAdd(session, rtype)
-
-
-def after_add_efrdef(session, entity):
-    SourceDbCWAttributeAdd(session, entity=entity)
-
-def after_add_enfrdef(session, entity):
-    SourceDbCWRelationAdd(session, entity=entity)
-
-
-# update hooks #################################################################
-
-def check_valid_changes(session, entity, ro_attrs=('name', 'final')):
-    errors = {}
-    # don't use getattr(entity, attr), we would get the modified value if any
-    for attr in ro_attrs:
-        origval = entity_attr(session, entity.eid, attr)
-        if entity.get(attr, origval) != origval:
-            errors[attr] = session._("can't change the %s attribute") % \
-                           display_name(session, attr)
-    if errors:
-        raise ValidationError(entity.eid, errors)
-
-def before_update_eetype(session, entity):
-    """check name change, handle final"""
-    check_valid_changes(session, entity, ro_attrs=('final',))
-    # don't use getattr(entity, attr), we would get the modified value if any
-    oldname = entity_attr(session, entity.eid, 'name')
-    newname = entity.get('name', oldname)
-    if newname.lower() != oldname.lower():
-        SourceDbCWETypeRename(session, oldname=oldname, newname=newname)
-        MemSchemaCWETypeRename(session, oldname=oldname, newname=newname)
-
-def before_update_ertype(session, entity):
-    """check name change, handle final"""
-    check_valid_changes(session, entity)
-
-
-def after_update_erdef(session, entity):
-    if entity.eid in session.transaction_data.get('pendingeids', ()):
-        return
-    desttype = entity.otype.name
-    rschema = session.schema[entity.rtype.name]
-    newvalues = {}
-    for prop in rschema.rproperty_defs(desttype):
-        if prop == 'constraints':
-            continue
-        if prop == 'order':
-            prop = 'ordernum'
-        if prop in entity.edited_attributes:
-            newvalues[prop] = entity[prop]
-    if newvalues:
-        subjtype = entity.stype.name
-        MemSchemaRDefUpdate(session, kobj=(subjtype, desttype),
-                            rschema=rschema, values=newvalues)
-        SourceDbRDefUpdate(session, kobj=(subjtype, desttype),
-                           rschema=rschema, values=newvalues)
-
-def after_update_ertype(session, entity):
-    rschema = session.schema.rschema(entity.name)
-    newvalues = {}
-    for prop in ('meta', 'symetric', 'inlined'):
-        if prop in entity:
-            newvalues[prop] = entity[prop]
-    if newvalues:
-        MemSchemaCWRTypeUpdate(session, rschema=rschema, values=newvalues)
-        SourceDbCWRTypeUpdate(session, rschema=rschema, values=newvalues,
-                              entity=entity)
-
-# constraints synchronization hooks ############################################
-
-def after_add_econstraint(session, entity):
-    MemSchemaCWConstraintAdd(session, entity=entity)
-    SourceDbCWConstraintAdd(session, entity=entity)
-
-
-def after_update_econstraint(session, entity):
-    MemSchemaCWConstraintAdd(session, entity=entity)
-    SourceDbCWConstraintAdd(session, entity=entity)
-
-
-def before_delete_constrained_by(session, fromeid, rtype, toeid):
-    if not fromeid in session.transaction_data.get('pendingeids', ()):
-        schema = session.schema
-        entity = session.entity_from_eid(toeid)
-        subjtype, rtype, objtype = schema.schema_by_eid(fromeid)
-        try:
-            cstr = rtype.constraint_by_type(subjtype, objtype,
-                                            entity.cstrtype[0].name)
-        except IndexError:
-            session.critical('constraint type no more accessible')
-        else:
-            SourceDbCWConstraintDel(session, subjtype=subjtype, rtype=rtype,
-                                    objtype=objtype, cstr=cstr)
-            MemSchemaCWConstraintDel(session, subjtype=subjtype, rtype=rtype,
-                                     objtype=objtype, cstr=cstr)
-
-
-def after_add_constrained_by(session, fromeid, rtype, toeid):
-    if fromeid in session.transaction_data.get('neweids', ()):
-        session.transaction_data.setdefault(fromeid, []).append(toeid)
-
-
-# permissions synchronization hooks ############################################
-
-def after_add_permission(session, subject, rtype, object):
-    """added entity/relation *_permission, need to update schema"""
-    perm = rtype.split('_', 1)[0]
-    if session.describe(object)[0] == 'CWGroup':
-        MemSchemaPermissionCWGroupAdd(session, perm, subject, object)
-    else: # RQLExpression
-        expr = session.execute('Any EXPR WHERE X eid %(x)s, X expression EXPR',
-                               {'x': object}, 'x')[0][0]
-        MemSchemaPermissionRQLExpressionAdd(session, perm, subject, expr)
-
-
-def before_del_permission(session, subject, rtype, object):
-    """delete entity/relation *_permission, need to update schema
-
-    skip the operation if the related type is being deleted
-    """
-    if subject in session.transaction_data.get('pendingeids', ()):
-        return
-    perm = rtype.split('_', 1)[0]
-    if session.describe(object)[0] == 'CWGroup':
-        MemSchemaPermissionCWGroupDel(session, perm, subject, object)
-    else: # RQLExpression
-        expr = session.execute('Any EXPR WHERE X eid %(x)s, X expression EXPR',
-                               {'x': object}, 'x')[0][0]
-        MemSchemaPermissionRQLExpressionDel(session, perm, subject, expr)
-
-
-def after_add_specializes(session, subject, rtype, object):
-    MemSchemaSpecializesAdd(session, etypeeid=subject, parentetypeeid=object)
-
-def after_del_specializes(session, subject, rtype, object):
-    MemSchemaSpecializesDel(session, etypeeid=subject, parentetypeeid=object)
-
-
-def _register_schema_hooks(hm):
-    """register schema related hooks on the hooks manager"""
-    # schema synchronisation #####################
-    # before/after add
-    hm.register_hook(before_add_eetype, 'before_add_entity', 'CWEType')
-    hm.register_hook(before_add_ertype, 'before_add_entity', 'CWRType')
-    hm.register_hook(after_add_eetype, 'after_add_entity', 'CWEType')
-    hm.register_hook(after_add_ertype, 'after_add_entity', 'CWRType')
-    hm.register_hook(after_add_efrdef, 'after_add_entity', 'CWAttribute')
-    hm.register_hook(after_add_enfrdef, 'after_add_entity', 'CWRelation')
-    # before/after update
-    hm.register_hook(before_update_eetype, 'before_update_entity', 'CWEType')
-    hm.register_hook(before_update_ertype, 'before_update_entity', 'CWRType')
-    hm.register_hook(after_update_ertype, 'after_update_entity', 'CWRType')
-    hm.register_hook(after_update_erdef, 'after_update_entity', 'CWAttribute')
-    hm.register_hook(after_update_erdef, 'after_update_entity', 'CWRelation')
-    # before/after delete
-    hm.register_hook(before_del_eetype, 'before_delete_entity', 'CWEType')
-    hm.register_hook(after_del_eetype, 'after_delete_entity', 'CWEType')
-    hm.register_hook(before_del_ertype, 'before_delete_entity', 'CWRType')
-    hm.register_hook(after_del_relation_type, 'after_delete_relation', 'relation_type')
-    hm.register_hook(after_add_specializes, 'after_add_relation', 'specializes')
-    hm.register_hook(after_del_specializes, 'after_delete_relation', 'specializes')
-    # constraints synchronization hooks
-    hm.register_hook(after_add_econstraint, 'after_add_entity', 'CWConstraint')
-    hm.register_hook(after_update_econstraint, 'after_update_entity', 'CWConstraint')
-    hm.register_hook(before_delete_constrained_by, 'before_delete_relation', 'constrained_by')
-    hm.register_hook(after_add_constrained_by, 'after_add_relation', 'constrained_by')
-    # permissions synchronisation ################
-    for perm in ('read_permission', 'add_permission',
-                 'delete_permission', 'update_permission'):
-        hm.register_hook(after_add_permission, 'after_add_relation', perm)
-        hm.register_hook(before_del_permission, 'before_delete_relation', perm)
--- a/server/securityhooks.py	Wed Sep 30 17:57:02 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-"""Security hooks: check permissions to add/delete/update entities according to
-the user connected to a session
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from cubicweb import Unauthorized
-from cubicweb.server.pool import LateOperation
-from cubicweb.server import BEFORE_ADD_RELATIONS, ON_COMMIT_ADD_RELATIONS
-
-def check_entity_attributes(session, entity):
-    eid = entity.eid
-    eschema = entity.e_schema
-    # ._default_set is only there on entity creation to indicate unspecified
-    # attributes which has been set to a default value defined in the schema
-    defaults = getattr(entity, '_default_set', ())
-    try:
-        editedattrs = entity.edited_attributes
-    except AttributeError:
-        editedattrs = entity.keys()
-    for attr in editedattrs:
-        if attr in defaults:
-            continue
-        rschema = eschema.subject_relation(attr)
-        if rschema.is_final(): # non final relation are checked by other hooks
-            # add/delete should be equivalent (XXX: unify them into 'update' ?)
-            rschema.check_perm(session, 'add', eid)
-
-
-class CheckEntityPermissionOp(LateOperation):
-    def precommit_event(self):
-        #print 'CheckEntityPermissionOp', self.session.user, self.entity, self.action
-        self.entity.check_perm(self.action)
-        check_entity_attributes(self.session, self.entity)
-
-    def commit_event(self):
-        pass
-
-
-class CheckRelationPermissionOp(LateOperation):
-    def precommit_event(self):
-        self.rschema.check_perm(self.session, self.action, self.fromeid, self.toeid)
-
-    def commit_event(self):
-        pass
-
-def after_add_entity(session, entity):
-    if not session.is_super_session:
-        CheckEntityPermissionOp(session, entity=entity, action='add')
-
-def after_update_entity(session, entity):
-    if not session.is_super_session:
-        try:
-            # check user has permission right now, if not retry at commit time
-            entity.check_perm('update')
-            check_entity_attributes(session, entity)
-        except Unauthorized:
-            entity.clear_local_perm_cache('update')
-            CheckEntityPermissionOp(session, entity=entity, action='update')
-
-def before_del_entity(session, eid):
-    if not session.is_super_session:
-        eschema = session.repo.schema[session.describe(eid)[0]]
-        eschema.check_perm(session, 'delete', eid)
-
-
-def before_add_relation(session, fromeid, rtype, toeid):
-    if rtype in BEFORE_ADD_RELATIONS and not session.is_super_session:
-        nocheck = session.transaction_data.get('skip-security', ())
-        if (fromeid, rtype, toeid) in nocheck:
-            return
-        rschema = session.repo.schema[rtype]
-        rschema.check_perm(session, 'add', fromeid, toeid)
-
-def after_add_relation(session, fromeid, rtype, toeid):
-    if not rtype in BEFORE_ADD_RELATIONS and not session.is_super_session:
-        nocheck = session.transaction_data.get('skip-security', ())
-        if (fromeid, rtype, toeid) in nocheck:
-            return
-        rschema = session.repo.schema[rtype]
-        if rtype in ON_COMMIT_ADD_RELATIONS:
-            CheckRelationPermissionOp(session, action='add', rschema=rschema,
-                                      fromeid=fromeid, toeid=toeid)
-        else:
-            rschema.check_perm(session, 'add', fromeid, toeid)
-
-def before_del_relation(session, fromeid, rtype, toeid):
-    if not session.is_super_session:
-        nocheck = session.transaction_data.get('skip-security', ())
-        if (fromeid, rtype, toeid) in nocheck:
-            return
-        session.repo.schema[rtype].check_perm(session, 'delete', fromeid, toeid)
-
-def register_security_hooks(hm):
-    """register meta-data related hooks on the hooks manager"""
-    hm.register_hook(after_add_entity, 'after_add_entity', '')
-    hm.register_hook(after_update_entity, 'after_update_entity', '')
-    hm.register_hook(before_del_entity, 'before_delete_entity', '')
-    hm.register_hook(before_add_relation, 'before_add_relation', '')
-    hm.register_hook(after_add_relation, 'after_add_relation', '')
-    hm.register_hook(before_del_relation, 'before_delete_relation', '')
-
--- a/server/serverconfig.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/serverconfig.py	Wed Sep 30 18:57:42 2009 +0200
@@ -82,7 +82,7 @@
     else:
         BACKUP_DIR = '/var/lib/cubicweb/backup/'
 
-    cubicweb_appobject_path = CubicWebConfiguration.cubicweb_appobject_path | set(['sobjects'])
+    cubicweb_appobject_path = CubicWebConfiguration.cubicweb_appobject_path | set(['sobjects', 'hooks'])
     cube_appobject_path = CubicWebConfiguration.cube_appobject_path | set(['sobjects', 'hooks'])
 
     options = merge_options((
@@ -189,14 +189,9 @@
     # check user's state at login time
     consider_user_state = True
 
-    # hooks registration configuration
+    # hooks activation configuration
     # all hooks should be activated during normal execution
-    core_hooks = True
-    usergroup_hooks = True
-    schema_hooks = True
-    notification_hooks = True
-    security_hooks = True
-    instance_hooks = True
+    disabled_hooks_categories = set()
 
     # should some hooks be deactivated during [pre|post]create script execution
     free_wheel = False
@@ -260,20 +255,6 @@
         """pyro is always enabled in standalone repository configuration"""
         return True
 
-    def load_hooks(self, vreg):
-        hooks = {}
-        try:
-            apphookdefs = vreg['hooks'].all_objects()
-        except RegistryNotFound:
-            return hooks
-        for hookdef in apphookdefs:
-            for event, ertype in hookdef.register_to():
-                if ertype == 'Any':
-                    ertype = ''
-                cb = hookdef.make_callback(event)
-                hooks.setdefault(event, {}).setdefault(ertype, []).append(cb)
-        return hooks
-
     def load_schema(self, expand_cubes=False, **kwargs):
         from cubicweb.schema import CubicWebSchemaLoader
         if expand_cubes:
--- a/server/serverctl.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/serverctl.py	Wed Sep 30 18:57:42 2009 +0200
@@ -14,8 +14,8 @@
 from logilab.common.clcommands import register_commands, cmd_run, pop_arg
 from logilab.common.shellutils import ASK
 
-from cubicweb import AuthenticationError, ExecutionError, ConfigurationError, underline_title
-from cubicweb.toolsutils import Command, CommandHandler
+from cubicweb import AuthenticationError, ExecutionError, ConfigurationError
+from cubicweb.toolsutils import Command, CommandHandler, underline_title
 from cubicweb.server import SOURCE_TYPES
 from cubicweb.server.utils import ask_source_config
 from cubicweb.server.serverconfig import USER_OPTIONS, ServerConfiguration
--- a/server/session.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/session.py	Wed Sep 30 18:57:42 2009 +0200
@@ -15,7 +15,8 @@
 from rql.nodes import VariableRef, Function, ETYPE_PYOBJ_MAP, etype_from_pyobj
 from yams import BASE_TYPES
 
-from cubicweb import RequestSessionMixIn, Binary, UnknownEid
+from cubicweb import Binary, UnknownEid
+from cubicweb.req import RequestSessionBase
 from cubicweb.dbapi import ConnectionProperties
 from cubicweb.utils import make_uid
 from cubicweb.rqlrewrite import RQLRewriter
@@ -41,7 +42,7 @@
     return description
 
 
-class Session(RequestSessionMixIn):
+class Session(RequestSessionBase):
     """tie session id, user, connections pool and other session data all
     together
     """
@@ -74,10 +75,6 @@
         return '<%ssession %s (%s 0x%x)>' % (self.cnxtype, self.user.login,
                                              self.id, id(self))
 
-    @property
-    def schema(self):
-        return self.repo.schema
-
     def hijack_user(self, user):
         """return a fake request/session using specified user"""
         session = Session(user, self.repo)
@@ -166,10 +163,10 @@
                 rset.description = list(rset.description)
             rset.description.append([self.describe(targeteid)[0]])
             targetentity = self.entity_from_eid(targeteid)
-            if targetentity.rset is None:
-                targetentity.rset = rset
-                targetentity.row = rset.rowcount
-                targetentity.col = 0
+            if targetentity.cw_rset is None:
+                targetentity.cw_rset = rset
+                targetentity.cw_row = rset.rowcount
+                targetentity.cw_col = 0
             rset.rowcount += 1
             entities.append(targetentity)
 
@@ -228,6 +225,18 @@
         assert prop == 'lang' # this is the only one changeable property for now
         self.set_language(value)
 
+    def deleted_in_transaction(self, eid):
+        return eid in self.transaction_data.get('pendingeids', ())
+
+    def added_in_transaction(self, eid):
+        return eid in self.transaction_data.get('neweids', ())
+
+    def schema_rproperty(self, rtype, eidfrom, eidto, rprop):
+        rschema = self.repo.schema[rtype]
+        subjtype = self.describe(eidfrom)[0]
+        objtype = self.describe(eidto)[0]
+        return rschema.rproperty(subjtype, objtype, rprop)
+
     # connection management ###################################################
 
     def keep_pool_mode(self, mode):
@@ -325,6 +334,11 @@
 
     # request interface #######################################################
 
+    @property
+    def cursor(self):
+        """return a rql cursor"""
+        return self
+
     def set_entity_cache(self, entity):
         # XXX session level caching may be a pb with multiple repository
         #     instances, but 1. this is probably not the only one :$ and 2. it
@@ -411,11 +425,6 @@
         return self.super_session.execute(rql, kwargs, eid_key, build_descr,
                                           propagate)
 
-    @property
-    def cursor(self):
-        """return a rql cursor"""
-        return self
-
     def execute(self, rql, kwargs=None, eid_key=None, build_descr=True,
                 propagate=False):
         """db-api like method directly linked to the querier execute method
@@ -532,7 +541,6 @@
             self._threaddata.pending_operations = []
             return self._threaddata.pending_operations
 
-
     def add_operation(self, operation, index=None):
         """add an observer"""
         assert self.commit_state != 'commit'
@@ -612,12 +620,19 @@
             description.append(tuple(row_descr))
         return description
 
-    @deprecated("use vreg['etypes'].etype_class(etype)")
+    # deprecated ###############################################################
+
+    @property
+    @deprecated("[3.6] use session.vreg.schema")
+    def schema(self):
+        return self.repo.schema
+
+    @deprecated("[3.4] use vreg['etypes'].etype_class(etype)")
     def etype_class(self, etype):
         """return an entity class for the given entity type"""
         return self.vreg['etypes'].etype_class(etype)
 
-    @deprecated('use direct access to session.transaction_data')
+    @deprecated('[3.4] use direct access to session.transaction_data')
     def query_data(self, key, default=None, setdefault=False, pop=False):
         if setdefault:
             assert not pop
@@ -627,7 +642,7 @@
         else:
             return self.transaction_data.get(key, default)
 
-    @deprecated('use entity_from_eid(eid, etype=None)')
+    @deprecated('[3.4] use entity_from_eid(eid, etype=None)')
     def entity(self, eid):
         """return a result set for the given eid"""
         return self.entity_from_eid(eid)
@@ -645,6 +660,7 @@
         # session which has created this one
         self.parent_session = parent_session
         self.user = InternalManager()
+        self.user.req = self # XXX remove when "vreg = user.req.vreg" hack in entity.py is gone
         self.repo = parent_session.repo
         self.vreg = parent_session.vreg
         self.data = parent_session.data
@@ -714,8 +730,9 @@
     """special session created internaly by the repository"""
 
     def __init__(self, repo, cnxprops=None):
-        super(InternalSession, self).__init__(_IMANAGER, repo, cnxprops,
+        super(InternalSession, self).__init__(InternalManager(), repo, cnxprops,
                                               _id='internal')
+        self.user.req = self # XXX remove when "vreg = user.req.vreg" hack in entity.py is gone
         self.cnxtype = 'inmemory'
         self.is_internal_session = True
         self.is_super_session = True
@@ -752,7 +769,6 @@
             return 'en'
         return None
 
-_IMANAGER = InternalManager()
 
 from logging import getLogger
 from cubicweb import set_log_methods
--- a/server/sources/native.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/sources/native.py	Wed Sep 30 18:57:42 2009 +0200
@@ -531,7 +531,7 @@
         if extid is not None:
             assert isinstance(extid, str)
             extid = b64encode(extid)
-        attrs = {'type': entity.id, 'eid': entity.eid, 'extid': extid,
+        attrs = {'type': entity.__regid__, 'eid': entity.eid, 'extid': extid,
                  'source': source.uri, 'mtime': datetime.now()}
         session.system_sql(self.sqlgen.insert('entities', attrs), attrs)
 
--- a/server/test/data/hooks.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/test/data/hooks.py	Wed Sep 30 18:57:42 2009 +0200
@@ -5,27 +5,31 @@
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
-from cubicweb.server.hooksmanager import SystemHook
+from cubicweb.server.hook import Hook
 
 CALLED_EVENTS = {}
 
-class StartupHook(SystemHook):
+class StartupHook(Hook):
+    __regid__ = 'mystartup'
     events = ('server_startup',)
-    def call(self, repo):
+    def __call__(self):
         CALLED_EVENTS['server_startup'] = True
 
-class ShutdownHook(SystemHook):
+class ShutdownHook(Hook):
+    __regid__ = 'myshutdown'
     events = ('server_shutdown',)
-    def call(self, repo):
+    def __call__(self):
         CALLED_EVENTS['server_shutdown'] = True
 
 
-class LoginHook(SystemHook):
+class LoginHook(Hook):
+    __regid__ = 'mylogin'
     events = ('session_open',)
-    def call(self, session):
-        CALLED_EVENTS['session_open'] = session.user.login
+    def __call__(self):
+        CALLED_EVENTS['session_open'] = self._cw.user.login
 
-class LogoutHook(SystemHook):
+class LogoutHook(Hook):
+    __regid__ = 'mylogout'
     events = ('session_close',)
-    def call(self, session):
-        CALLED_EVENTS['session_close'] = session.user.login
+    def __call__(self):
+        CALLED_EVENTS['session_close'] = self._cw.user.login
--- a/server/test/unittest_checkintegrity.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/test/unittest_checkintegrity.py	Wed Sep 30 18:57:42 2009 +0200
@@ -13,7 +13,7 @@
 
 from cubicweb.server.checkintegrity import check
 
-repo, cnx = init_test_database('sqlite')
+repo, cnx = init_test_database()
 
 class CheckIntegrityTC(TestCase):
     def test(self):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/unittest_hook.py	Wed Sep 30 18:57:42 2009 +0200
@@ -0,0 +1,190 @@
+# -*- coding: utf-8 -*-
+"""unit/functional tests for cubicweb.server.hook
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+
+from logilab.common.testlib import TestCase, unittest_main, mock_object
+
+
+from cubicweb.devtools import TestServerConfiguration
+from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.selectors import implements
+from cubicweb.server import hook
+from cubicweb.hooks import integrity, syncschema
+
+
+def clean_session_ops(func):
+    def wrapper(self, *args, **kwargs):
+        try:
+            return func(self, *args, **kwargs)
+        finally:
+            self.session.pending_operations[:] = []
+    return wrapper
+
+class OperationsTC(CubicWebTC):
+
+    def setUp(self):
+        CubicWebTC.setUp(self)
+        self.hm = self.repo.hm
+
+    @clean_session_ops
+    def test_late_operation(self):
+        session = self.session
+        l1 = hook.LateOperation(session)
+        l2 = hook.LateOperation(session)
+        l3 = hook.Operation(session)
+        self.assertEquals(session.pending_operations, [l3, l1, l2])
+
+    @clean_session_ops
+    def test_single_last_operation(self):
+        session = self.session
+        l0 = hook.SingleLastOperation(session)
+        l1 = hook.LateOperation(session)
+        l2 = hook.LateOperation(session)
+        l3 = hook.Operation(session)
+        self.assertEquals(session.pending_operations, [l3, l1, l2, l0])
+        l4 = hook.SingleLastOperation(session)
+        self.assertEquals(session.pending_operations, [l3, l1, l2, l4])
+
+    @clean_session_ops
+    def test_global_operation_order(self):
+        session = self.session
+        op1 = integrity._DelayedDeleteOp(session)
+        op2 = syncschema.MemSchemaRDefDel(session)
+        # equivalent operation generated by op2 but replace it here by op3 so we
+        # can check the result...
+        op3 = syncschema.MemSchemaNotifyChanges(session)
+        op4 = integrity._DelayedDeleteOp(session)
+        op5 = integrity._CheckORelationOp(session)
+        self.assertEquals(session.pending_operations, [op1, op2, op4, op5, op3])
+
+
+class HookCalled(Exception): pass
+
+config = TestServerConfiguration('data')
+config.bootstrap_cubes()
+schema = config.load_schema()
+
+class AddAnyHook(hook.Hook):
+    __regid__ = 'addany'
+    category = 'cat1'
+    events = ('before_add_entity',)
+    def __call__(self):
+        raise HookCalled()
+
+
+class HooksManagerTC(TestCase):
+
+    def setUp(self):
+        """ called before each test from this class """
+        self.vreg = mock_object(config=config, schema=schema)
+        self.o = hook.HooksRegistry(self.vreg)
+
+    def test_register_bad_hook1(self):
+        class _Hook(hook.Hook):
+            events = ('before_add_entiti',)
+        ex = self.assertRaises(Exception, self.o.register, _Hook)
+        self.assertEquals(str(ex), 'bad event before_add_entiti on unittest_hook._Hook')
+
+    def test_register_bad_hook2(self):
+        class _Hook(hook.Hook):
+            events = None
+        ex = self.assertRaises(Exception, self.o.register, _Hook)
+        self.assertEquals(str(ex), 'bad .events attribute None on unittest_hook._Hook')
+
+    def test_register_bad_hook3(self):
+        class _Hook(hook.Hook):
+            events = 'before_add_entity'
+        ex = self.assertRaises(Exception, self.o.register, _Hook)
+        self.assertEquals(str(ex), 'bad event b on unittest_hook._Hook')
+
+    def test_call_hook(self):
+        self.o.register(AddAnyHook)
+        cw = mock_object(vreg=self.vreg)
+        self.assertRaises(HookCalled, self.o.call_hooks, 'before_add_entity', cw)
+        self.o.call_hooks('before_delete_entity', cw) # nothing to call
+        config.disabled_hooks_categories.add('cat1')
+        self.o.call_hooks('before_add_entity', cw) # disabled hooks category, not called
+        config.disabled_hooks_categories.remove('cat1')
+        self.assertRaises(HookCalled, self.o.call_hooks, 'before_add_entity', cw)
+        self.o.unregister(AddAnyHook)
+        self.o.call_hooks('before_add_entity', cw) # nothing to call
+
+
+class SystemHooksTC(CubicWebTC):
+
+    def test_startup_shutdown(self):
+        import hooks # cubicweb/server/test/data/hooks.py
+        self.assertEquals(hooks.CALLED_EVENTS['server_startup'], True)
+        # don't actually call repository.shutdown !
+        self.repo.hm.call_hooks('server_shutdown', repo=self.repo)
+        self.assertEquals(hooks.CALLED_EVENTS['server_shutdown'], True)
+
+    def test_session_open_close(self):
+        import hooks # cubicweb/server/test/data/hooks.py
+        cnx = self.login('anon')
+        self.assertEquals(hooks.CALLED_EVENTS['session_open'], 'anon')
+        cnx.close()
+        self.assertEquals(hooks.CALLED_EVENTS['session_close'], 'anon')
+
+
+# class RelationHookTC(TestCase):
+#     """testcase for relation hooks grouping"""
+#     def setUp(self):
+#         """ called before each test from this class """
+#         self.o = HooksManager(schema)
+#         self.called = []
+
+#     def test_before_add_relation(self):
+#         """make sure before_xxx_relation hooks are called directly"""
+#         self.o.register(self._before_relation_hook,
+#                              'before_add_relation', 'concerne')
+#         self.assertEquals(self.called, [])
+#         self.o.call_hooks('before_add_relation', 'concerne', 'USER',
+#                           1, 'concerne', 2)
+#         self.assertEquals(self.called, [(1, 'concerne', 2)])
+
+#     def test_after_add_relation(self):
+#         """make sure after_xxx_relation hooks are deferred"""
+#         self.o.register(self._after_relation_hook,
+#                              'after_add_relation', 'concerne')
+#         self.assertEquals(self.called, [])
+#         self.o.call_hooks('after_add_relation', 'concerne', 'USER',
+#                           1, 'concerne', 2)
+#         self.o.call_hooks('after_add_relation', 'concerne', 'USER',
+#                           3, 'concerne', 4)
+#         self.assertEquals(self.called, [(1, 'concerne', 2), (3, 'concerne', 4)])
+
+#     def test_before_delete_relation(self):
+#         """make sure before_xxx_relation hooks are called directly"""
+#         self.o.register(self._before_relation_hook,
+#                              'before_delete_relation', 'concerne')
+#         self.assertEquals(self.called, [])
+#         self.o.call_hooks('before_delete_relation', 'concerne', 'USER',
+#                           1, 'concerne', 2)
+#         self.assertEquals(self.called, [(1, 'concerne', 2)])
+
+#     def test_after_delete_relation(self):
+#         """make sure after_xxx_relation hooks are deferred"""
+#         self.o.register(self._after_relation_hook,
+#                         'after_delete_relation', 'concerne')
+#         self.o.call_hooks('after_delete_relation', 'concerne', 'USER',
+#                           1, 'concerne', 2)
+#         self.o.call_hooks('after_delete_relation', 'concerne', 'USER',
+#                           3, 'concerne', 4)
+#         self.assertEquals(self.called, [(1, 'concerne', 2), (3, 'concerne', 4)])
+
+
+#     def _before_relation_hook(self, pool, subject, r_type, object):
+#         self.called.append((subject, r_type, object))
+
+#     def _after_relation_hook(self, pool, subject, r_type, object):
+#         self.called.append((subject, r_type, object))
+
+
+if __name__ == '__main__':
+    unittest_main()
--- a/server/test/unittest_hookhelper.py	Wed Sep 30 17:57:02 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-# -*- coding: utf-8 -*-
-"""unit/functional tests for cubicweb.server.hookhelper
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-
-from logilab.common.testlib import unittest_main
-from cubicweb.devtools.apptest import RepositoryBasedTC
-
-from cubicweb.server.pool import LateOperation, Operation, SingleLastOperation
-from cubicweb.server.hookhelper import *
-
-
-class HookHelpersTC(RepositoryBasedTC):
-
-    def setUp(self):
-        RepositoryBasedTC.setUp(self)
-        self.hm = self.repo.hm
-
-    def test_late_operation(self):
-        session = self.session
-        l1 = LateOperation(session)
-        l2 = LateOperation(session)
-        l3 = Operation(session)
-        self.assertEquals(session.pending_operations, [l3, l1, l2])
-
-    def test_single_last_operation(self):
-        session = self.session
-        l0 = SingleLastOperation(session)
-        l1 = LateOperation(session)
-        l2 = LateOperation(session)
-        l3 = Operation(session)
-        self.assertEquals(session.pending_operations, [l3, l1, l2, l0])
-        l4 = SingleLastOperation(session)
-        self.assertEquals(session.pending_operations, [l3, l1, l2, l4])
-
-    def test_global_operation_order(self):
-        from cubicweb.server import hooks, schemahooks
-        session = self.session
-        op1 = hooks.DelayedDeleteOp(session)
-        op2 = schemahooks.MemSchemaRDefDel(session)
-        # equivalent operation generated by op2 but replace it here by op3 so we
-        # can check the result...
-        op3 = schemahooks.MemSchemaNotifyChanges(session)
-        op4 = hooks.DelayedDeleteOp(session)
-        op5 = hooks.CheckORelationOp(session)
-        self.assertEquals(session.pending_operations, [op1, op2, op4, op5, op3])
-
-if __name__ == '__main__':
-    unittest_main()
--- a/server/test/unittest_hooks.py	Wed Sep 30 17:57:02 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,516 +0,0 @@
-# -*- coding: utf-8 -*-
-"""functional tests for core hooks
-
-note: most schemahooks.py hooks are actually tested in unittest_migrations.py
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-
-from logilab.common.testlib import TestCase, unittest_main
-
-from datetime import datetime
-
-from cubicweb import (ConnectionError, RepositoryError, ValidationError,
-                      AuthenticationError, BadConnectionId)
-from cubicweb.devtools.apptest import RepositoryBasedTC, get_versions
-
-from cubicweb.server.sqlutils import SQL_PREFIX
-from cubicweb.server.repository import Repository
-
-orig_get_versions = Repository.get_versions
-
-def setup_module(*args):
-    Repository.get_versions = get_versions
-
-def teardown_module(*args):
-    Repository.get_versions = orig_get_versions
-
-
-
-class CoreHooksTC(RepositoryBasedTC):
-
-    def test_delete_internal_entities(self):
-        self.assertRaises(RepositoryError, self.execute,
-                          'DELETE CWEType X WHERE X name "CWEType"')
-        self.assertRaises(RepositoryError, self.execute,
-                          'DELETE CWRType X WHERE X name "relation_type"')
-        self.assertRaises(RepositoryError, self.execute,
-                          'DELETE CWGroup X WHERE X name "owners"')
-
-    def test_delete_required_relations_subject(self):
-        self.execute('INSERT CWUser X: X login "toto", X upassword "hop", X in_group Y '
-                     'WHERE Y name "users"')
-        self.commit()
-        self.execute('DELETE X in_group Y WHERE X login "toto", Y name "users"')
-        self.assertRaises(ValidationError, self.commit)
-        self.execute('DELETE X in_group Y WHERE X login "toto"')
-        self.execute('SET X in_group Y WHERE X login "toto", Y name "guests"')
-        self.commit()
-
-    def test_delete_required_relations_object(self):
-        self.skip('no sample in the schema ! YAGNI ? Kermaat ?')
-
-    def test_static_vocabulary_check(self):
-        self.assertRaises(ValidationError,
-                          self.execute,
-                          'SET X composite "whatever" WHERE X from_entity FE, FE name "CWUser", X relation_type RT, RT name "in_group"')
-
-    def test_missing_required_relations_subject_inline(self):
-        # missing in_group relation
-        self.execute('INSERT CWUser X: X login "toto", X upassword "hop"')
-        self.assertRaises(ValidationError,
-                          self.commit)
-
-    def test_inlined(self):
-        self.assertEquals(self.repo.schema['sender'].inlined, True)
-        self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')
-        self.execute('INSERT EmailPart X: X content_format "text/plain", X ordernum 1, X content "this is a test"')
-        eeid = self.execute('INSERT Email X: X messageid "<1234>", X subject "test", X sender Y, X recipients Y, X parts P '
-                            'WHERE Y is EmailAddress, P is EmailPart')[0][0]
-        self.execute('SET X sender Y WHERE X is Email, Y is EmailAddress')
-        rset = self.execute('Any S WHERE X sender S, X eid %s' % eeid)
-        self.assertEquals(len(rset), 1)
-
-    def test_composite_1(self):
-        self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')
-        self.execute('INSERT EmailPart X: X content_format "text/plain", X ordernum 1, X content "this is a test"')
-        self.execute('INSERT Email X: X messageid "<1234>", X subject "test", X sender Y, X recipients Y, X parts P '
-                     'WHERE Y is EmailAddress, P is EmailPart')
-        self.failUnless(self.execute('Email X WHERE X sender Y'))
-        self.commit()
-        self.execute('DELETE Email X')
-        rset = self.execute('Any X WHERE X is EmailPart')
-        self.assertEquals(len(rset), 1)
-        self.commit()
-        rset = self.execute('Any X WHERE X is EmailPart')
-        self.assertEquals(len(rset), 0)
-
-    def test_composite_2(self):
-        self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')
-        self.execute('INSERT EmailPart X: X content_format "text/plain", X ordernum 1, X content "this is a test"')
-        self.execute('INSERT Email X: X messageid "<1234>", X subject "test", X sender Y, X recipients Y, X parts P '
-                     'WHERE Y is EmailAddress, P is EmailPart')
-        self.commit()
-        self.execute('DELETE Email X')
-        self.execute('DELETE EmailPart X')
-        self.commit()
-        rset = self.execute('Any X WHERE X is EmailPart')
-        self.assertEquals(len(rset), 0)
-
-    def test_composite_redirection(self):
-        self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')
-        self.execute('INSERT EmailPart X: X content_format "text/plain", X ordernum 1, X content "this is a test"')
-        self.execute('INSERT Email X: X messageid "<1234>", X subject "test", X sender Y, X recipients Y, X parts P '
-                     'WHERE Y is EmailAddress, P is EmailPart')
-        self.execute('INSERT Email X: X messageid "<2345>", X subject "test2", X sender Y, X recipients Y '
-                     'WHERE Y is EmailAddress')
-        self.commit()
-        self.execute('DELETE X parts Y WHERE X messageid "<1234>"')
-        self.execute('SET X parts Y WHERE X messageid "<2345>"')
-        self.commit()
-        rset = self.execute('Any X WHERE X is EmailPart')
-        self.assertEquals(len(rset), 1)
-        self.assertEquals(rset.get_entity(0, 0).reverse_parts[0].messageid, '<2345>')
-
-    def test_unsatisfied_constraints(self):
-        self.execute('INSERT CWRelation X: X from_entity FE, X relation_type RT, X to_entity TE '
-                     'WHERE FE name "Affaire", RT name "concerne", TE name "String"')
-        self.assertRaises(ValidationError,
-                          self.commit)
-
-
-    def test_html_tidy_hook(self):
-        entity = self.execute('INSERT Affaire A: A descr_format "text/html", A descr "yo"').get_entity(0, 0)
-        self.assertEquals(entity.descr, u'yo')
-        entity = self.execute('INSERT Affaire A: A descr_format "text/html", A descr "<b>yo"').get_entity(0, 0)
-        self.assertEquals(entity.descr, u'<b>yo</b>')
-        entity = self.execute('INSERT Affaire A: A descr_format "text/html", A descr "<b>yo</b>"').get_entity(0, 0)
-        self.assertEquals(entity.descr, u'<b>yo</b>')
-        entity = self.execute('INSERT Affaire A: A descr_format "text/html", A descr "<b>R&D</b>"').get_entity(0, 0)
-        self.assertEquals(entity.descr, u'<b>R&amp;D</b>')
-        xml = u"<div>c&apos;est <b>l'ét&eacute;"
-        entity = self.execute('INSERT Affaire A: A descr_format "text/html", A descr %(d)s',
-                              {'d': xml}).get_entity(0, 0)
-        self.assertEquals(entity.descr, u"<div>c'est <b>l'été</b></div>")
-
-    def test_nonregr_html_tidy_hook_no_update(self):
-        entity = self.execute('INSERT Affaire A: A descr_format "text/html", A descr "yo"').get_entity(0, 0)
-        self.assertEquals(entity.descr, u'yo')
-        self.execute('SET A ref "REF" WHERE A eid %s' % entity.eid)
-        entity = self.execute('Any A WHERE A eid %s' % entity.eid).get_entity(0, 0)
-        self.assertEquals(entity.descr, u'yo')
-        self.execute('SET A descr "R&D<p>yo" WHERE A eid %s' % entity.eid)
-        entity = self.execute('Any A WHERE A eid %s' % entity.eid).get_entity(0, 0)
-        self.assertEquals(entity.descr, u'R&amp;D<p>yo</p>')
-
-
-    def test_metadata_cwuri(self):
-        eid = self.execute('INSERT Note X')[0][0]
-        cwuri = self.execute('Any U WHERE X eid %s, X cwuri U' % eid)[0][0]
-        self.assertEquals(cwuri, self.repo.config['base-url'] + 'eid/%s' % eid)
-
-    def test_metadata_creation_modification_date(self):
-        _now = datetime.now()
-        eid = self.execute('INSERT Note X')[0][0]
-        creation_date, modification_date = self.execute('Any CD, MD WHERE X eid %s, '
-                                                        'X creation_date CD, '
-                                                        'X modification_date MD' % eid)[0]
-        self.assertEquals((creation_date - _now).seconds, 0)
-        self.assertEquals((modification_date - _now).seconds, 0)
-
-    def test_metadata__date(self):
-        _now = datetime.now()
-        eid = self.execute('INSERT Note X')[0][0]
-        creation_date = self.execute('Any D WHERE X eid %s, X creation_date D' % eid)[0][0]
-        self.assertEquals((creation_date - _now).seconds, 0)
-
-    def test_metadata_created_by(self):
-        eid = self.execute('INSERT Note X')[0][0]
-        self.commit() # fire operations
-        rset = self.execute('Any U WHERE X eid %s, X created_by U' % eid)
-        self.assertEquals(len(rset), 1) # make sure we have only one creator
-        self.assertEquals(rset[0][0], self.session.user.eid)
-
-    def test_metadata_owned_by(self):
-        eid = self.execute('INSERT Note X')[0][0]
-        self.commit() # fire operations
-        rset = self.execute('Any U WHERE X eid %s, X owned_by U' % eid)
-        self.assertEquals(len(rset), 1) # make sure we have only one owner
-        self.assertEquals(rset[0][0], self.session.user.eid)
-
-
-class UserGroupHooksTC(RepositoryBasedTC):
-
-    def test_user_synchronization(self):
-        self.create_user('toto', password='hop', commit=False)
-        self.assertRaises(AuthenticationError,
-                          self.repo.connect, u'toto', 'hop')
-        self.commit()
-        cnxid = self.repo.connect(u'toto', 'hop')
-        self.failIfEqual(cnxid, self.cnxid)
-        self.execute('DELETE CWUser X WHERE X login "toto"')
-        self.repo.execute(cnxid, 'State X')
-        self.commit()
-        self.assertRaises(BadConnectionId,
-                          self.repo.execute, cnxid, 'State X')
-
-    def test_user_group_synchronization(self):
-        user = self.session.user
-        self.assertEquals(user.groups, set(('managers',)))
-        self.execute('SET X in_group G WHERE X eid %s, G name "guests"' % user.eid)
-        self.assertEquals(user.groups, set(('managers',)))
-        self.commit()
-        self.assertEquals(user.groups, set(('managers', 'guests')))
-        self.execute('DELETE X in_group G WHERE X eid %s, G name "guests"' % user.eid)
-        self.assertEquals(user.groups, set(('managers', 'guests')))
-        self.commit()
-        self.assertEquals(user.groups, set(('managers',)))
-
-    def test_user_composite_owner(self):
-        ueid = self.create_user('toto')
-        # composite of euser should be owned by the euser regardless of who created it
-        self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", U use_email X '
-                     'WHERE U login "toto"')
-        self.commit()
-        self.assertEquals(self.execute('Any A WHERE X owned_by U, U use_email X,'
-                                       'U login "toto", X address A')[0][0],
-                          'toto@logilab.fr')
-
-    def test_no_created_by_on_deleted_entity(self):
-        eid = self.execute('INSERT EmailAddress X: X address "toto@logilab.fr"')[0][0]
-        self.execute('DELETE EmailAddress X WHERE X eid %s' % eid)
-        self.commit()
-        self.failIf(self.execute('Any X WHERE X created_by Y, X eid >= %(x)s', {'x': eid}))
-
-
-class CWPropertyHooksTC(RepositoryBasedTC):
-
-    def test_unexistant_eproperty(self):
-        ex = self.assertRaises(ValidationError,
-                          self.execute, 'INSERT CWProperty X: X pkey "bla.bla", X value "hop", X for_user U')
-        self.assertEquals(ex.errors, {'pkey': 'unknown property key'})
-        ex = self.assertRaises(ValidationError,
-                          self.execute, 'INSERT CWProperty X: X pkey "bla.bla", X value "hop"')
-        self.assertEquals(ex.errors, {'pkey': 'unknown property key'})
-
-    def test_site_wide_eproperty(self):
-        ex = self.assertRaises(ValidationError,
-                               self.execute, 'INSERT CWProperty X: X pkey "ui.site-title", X value "hop", X for_user U')
-        self.assertEquals(ex.errors, {'for_user': "site-wide property can't be set for user"})
-
-    def test_bad_type_eproperty(self):
-        ex = self.assertRaises(ValidationError,
-                               self.execute, 'INSERT CWProperty X: X pkey "ui.language", X value "hop", X for_user U')
-        self.assertEquals(ex.errors, {'value': u'unauthorized value'})
-        ex = self.assertRaises(ValidationError,
-                          self.execute, 'INSERT CWProperty X: X pkey "ui.language", X value "hop"')
-        self.assertEquals(ex.errors, {'value': u'unauthorized value'})
-
-
-class SchemaHooksTC(RepositoryBasedTC):
-
-    def test_duplicate_etype_error(self):
-        # check we can't add a CWEType or CWRType entity if it already exists one
-        # with the same name
-        #
-        # according to hook order, we'll get a repository or validation error
-        self.assertRaises((ValidationError, RepositoryError),
-                          self.execute, 'INSERT CWEType X: X name "Societe"')
-        self.assertRaises((ValidationError, RepositoryError),
-                          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:
-            self.execute('INSERT CWUser X: X login "admin"')
-        except ValidationError, ex:
-            self.assertIsInstance(ex.entity, int)
-            self.assertEquals(ex.errors, {'login': 'the value "admin" is already used, use another one'})
-
-
-class SchemaModificationHooksTC(RepositoryBasedTC):
-
-    def setUp(self):
-        if not hasattr(self, '_repo'):
-            # first initialization
-            repo = self.repo # set by the RepositoryBasedTC metaclass
-            # force to read schema from the database to get proper eid set on schema instances
-            repo.config._cubes = None
-            repo.fill_schema()
-        RepositoryBasedTC.setUp(self)
-
-    def index_exists(self, etype, attr, unique=False):
-        dbhelper = self.session.pool.source('system').dbhelper
-        sqlcursor = self.session.pool['system']
-        return dbhelper.index_exists(sqlcursor, SQL_PREFIX + etype, SQL_PREFIX + attr, unique=unique)
-
-    def test_base(self):
-        schema = self.repo.schema
-        dbhelper = self.session.pool.source('system').dbhelper
-        sqlcursor = self.session.pool['system']
-        self.failIf(schema.has_entity('Societe2'))
-        self.failIf(schema.has_entity('concerne2'))
-        # schema should be update on insertion (after commit)
-        self.execute('INSERT CWEType X: X name "Societe2", X description "", X final FALSE')
-        self.execute('INSERT CWRType X: X name "concerne2", X description "", X final FALSE, X symetric FALSE')
-        self.failIf(schema.has_entity('Societe2'))
-        self.failIf(schema.has_entity('concerne2'))
-        self.execute('SET X read_permission G WHERE X is CWEType, X name "Societe2", G is CWGroup')
-        self.execute('SET X read_permission G WHERE X is CWRType, X name "concerne2", G is CWGroup')
-        self.execute('SET X add_permission G WHERE X is CWEType, X name "Societe2", G is CWGroup, G name "managers"')
-        self.execute('SET X add_permission G WHERE X is CWRType, X name "concerne2", G is CWGroup, G name "managers"')
-        self.execute('SET X delete_permission G WHERE X is CWEType, X name "Societe2", G is CWGroup, G name "owners"')
-        self.execute('SET X delete_permission G WHERE X is CWRType, X name "concerne2", G is CWGroup, G name "owners"')
-        # have to commit before adding definition relations
-        self.commit()
-        self.failUnless(schema.has_entity('Societe2'))
-        self.failUnless(schema.has_relation('concerne2'))
-        self.execute('INSERT CWAttribute X: X cardinality "11", X defaultval "noname", X indexed TRUE, X relation_type RT, X from_entity E, X to_entity F '
-                     'WHERE RT name "nom", E name "Societe2", F name "String"')
-        concerne2_rdef_eid = self.execute(
-            'INSERT CWRelation X: X cardinality "**", X relation_type RT, X from_entity E, X to_entity E '
-            'WHERE RT name "concerne2", E name "Societe2"')[0][0]
-        self.execute('INSERT CWRelation X: X cardinality "?*", X relation_type RT, X from_entity E, X to_entity C '
-                     'WHERE RT name "comments", E name "Societe2", C name "Comment"')
-        self.failIf('nom' in schema['Societe2'].subject_relations())
-        self.failIf('concerne2' in schema['Societe2'].subject_relations())
-        self.failIf(self.index_exists('Societe2', 'nom'))
-        self.commit()
-        self.failUnless('nom' in schema['Societe2'].subject_relations())
-        self.failUnless('concerne2' in schema['Societe2'].subject_relations())
-        self.failUnless(self.index_exists('Societe2', 'nom'))
-        # now we should be able to insert and query Societe2
-        s2eid = self.execute('INSERT Societe2 X: X nom "logilab"')[0][0]
-        self.execute('Societe2 X WHERE X nom "logilab"')
-        self.execute('SET X concerne2 X WHERE X nom "logilab"')
-        rset = self.execute('Any X WHERE X concerne2 Y')
-        self.assertEquals(rset.rows, [[s2eid]])
-        # check that when a relation definition is deleted, existing relations are deleted
-        self.execute('INSERT CWRelation X: X cardinality "**", X relation_type RT, X from_entity E, X to_entity E '
-                     'WHERE RT name "concerne2", E name "Societe"')
-        self.commit()
-        self.execute('DELETE CWRelation X WHERE X eid %(x)s', {'x': concerne2_rdef_eid}, 'x')
-        self.commit()
-        self.failUnless('concerne2' in schema['Societe'].subject_relations())
-        self.failIf('concerne2' in schema['Societe2'].subject_relations())
-        self.failIf(self.execute('Any X WHERE X concerne2 Y'))
-        # schema should be cleaned on delete (after commit)
-        self.execute('DELETE CWEType X WHERE X name "Societe2"')
-        self.execute('DELETE CWRType X WHERE X name "concerne2"')
-        self.failUnless(self.index_exists('Societe2', 'nom'))
-        self.failUnless(schema.has_entity('Societe2'))
-        self.failUnless(schema.has_relation('concerne2'))
-        self.commit()
-        self.failIf(self.index_exists('Societe2', 'nom'))
-        self.failIf(schema.has_entity('Societe2'))
-        self.failIf(schema.has_entity('concerne2'))
-
-    def test_is_instance_of_insertions(self):
-        seid = self.execute('INSERT SubDivision S: S nom "subdiv"')[0][0]
-        is_etypes = [etype for etype, in self.execute('Any ETN WHERE X eid %s, X is ET, ET name ETN' % seid)]
-        self.assertEquals(is_etypes, ['SubDivision'])
-        instanceof_etypes = [etype for etype, in self.execute('Any ETN WHERE X eid %s, X is_instance_of ET, ET name ETN' % seid)]
-        self.assertEquals(sorted(instanceof_etypes), ['Division', 'Societe', 'SubDivision'])
-        snames = [name for name, in self.execute('Any N WHERE S is Societe, S nom N')]
-        self.failIf('subdiv' in snames)
-        snames = [name for name, in self.execute('Any N WHERE S is Division, S nom N')]
-        self.failIf('subdiv' in snames)
-        snames = [name for name, in self.execute('Any N WHERE S is_instance_of Societe, S nom N')]
-        self.failUnless('subdiv' in snames)
-        snames = [name for name, in self.execute('Any N WHERE S is_instance_of Division, S nom N')]
-        self.failUnless('subdiv' in snames)
-
-
-    def test_perms_synchronization_1(self):
-        schema = self.repo.schema
-        self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users')))
-        self.failUnless(self.execute('Any X, Y WHERE X is CWEType, X name "CWUser", Y is CWGroup, Y name "users"')[0])
-        self.execute('DELETE X read_permission Y WHERE X is CWEType, X name "CWUser", Y name "users"')
-        self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users', )))
-        self.commit()
-        self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', )))
-        self.execute('SET X read_permission Y WHERE X is CWEType, X name "CWUser", Y name "users"')
-        self.commit()
-        self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users',)))
-
-    def test_perms_synchronization_2(self):
-        schema = self.repo.schema['in_group']
-        self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
-        self.execute('DELETE X read_permission Y WHERE X is CWRType, X name "in_group", Y name "guests"')
-        self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
-        self.commit()
-        self.assertEquals(schema.get_groups('read'), set(('managers', 'users')))
-        self.execute('SET X read_permission Y WHERE X is CWRType, X name "in_group", Y name "guests"')
-        self.assertEquals(schema.get_groups('read'), set(('managers', 'users')))
-        self.commit()
-        self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
-
-    def test_nonregr_user_edit_itself(self):
-        ueid = self.session.user.eid
-        groupeids = [eid for eid, in self.execute('CWGroup G WHERE G name in ("managers", "users")')]
-        self.execute('DELETE X in_group Y WHERE X eid %s' % ueid)
-        self.execute('SET X surname "toto" WHERE X eid %s' % ueid)
-        self.execute('SET X in_group Y WHERE X eid %s, Y name "managers"' % ueid)
-        self.commit()
-        eeid = self.execute('Any X WHERE X is CWEType, X name "CWEType"')[0][0]
-        self.execute('DELETE X read_permission Y WHERE X eid %s' % eeid)
-        self.execute('SET X final FALSE WHERE X eid %s' % eeid)
-        self.execute('SET X read_permission Y WHERE X eid %s, Y eid in (%s, %s)'
-                     % (eeid, groupeids[0], groupeids[1]))
-        self.commit()
-        self.execute('Any X WHERE X is CWEType, X name "CWEType"')
-
-    # schema modification hooks tests #########################################
-
-    def test_uninline_relation(self):
-        dbhelper = self.session.pool.source('system').dbhelper
-        sqlcursor = self.session.pool['system']
-        # Personne inline2 Affaire inline
-        # insert a person without inline2 relation (not mandatory)
-        self.execute('INSERT Personne X: X nom "toto"')
-        peid = self.execute('INSERT Personne X: X nom "tutu"')[0][0]
-        aeid = self.execute('INSERT Affaire X: X ref "tata"')[0][0]
-        self.execute('SET X inline2 Y WHERE X eid %(x)s, Y eid %(y)s', {'x': peid, 'y': aeid})
-        self.failUnless(self.schema['inline2'].inlined)
-        try:
-            try:
-                self.execute('SET X inlined FALSE WHERE X name "inline2"')
-                self.failUnless(self.schema['inline2'].inlined)
-                self.commit()
-                self.failIf(self.schema['inline2'].inlined)
-                self.failIf(self.index_exists('Personne', 'inline2'))
-                rset = self.execute('Any X, Y WHERE X inline2 Y')
-                self.assertEquals(len(rset), 1)
-                self.assertEquals(rset.rows[0], [peid, aeid])
-            except:
-                import traceback
-                traceback.print_exc()
-                raise
-        finally:
-            self.execute('SET X inlined TRUE WHERE X name "inline2"')
-            self.failIf(self.schema['inline2'].inlined)
-            self.commit()
-            self.failUnless(self.schema['inline2'].inlined)
-            self.failUnless(self.index_exists('Personne', 'inline2'))
-            rset = self.execute('Any X, Y WHERE X inline2 Y')
-            self.assertEquals(len(rset), 1)
-            self.assertEquals(rset.rows[0], [peid, aeid])
-
-    def test_indexed_change(self):
-        dbhelper = self.session.pool.source('system').dbhelper
-        sqlcursor = self.session.pool['system']
-        try:
-            self.execute('SET X indexed TRUE WHERE X relation_type R, R name "sujet"')
-            self.failIf(self.schema['sujet'].rproperty('Affaire', 'String', 'indexed'))
-            self.failIf(self.index_exists('Affaire', 'sujet'))
-            self.commit()
-            self.failUnless(self.schema['sujet'].rproperty('Affaire', 'String', 'indexed'))
-            self.failUnless(self.index_exists('Affaire', 'sujet'))
-        finally:
-            self.execute('SET X indexed FALSE WHERE X relation_type R, R name "sujet"')
-            self.failUnless(self.schema['sujet'].rproperty('Affaire', 'String', 'indexed'))
-            self.failUnless(self.index_exists('Affaire', 'sujet'))
-            self.commit()
-            self.failIf(self.schema['sujet'].rproperty('Affaire', 'String', 'indexed'))
-            self.failIf(self.index_exists('Affaire', 'sujet'))
-
-    def test_unique_change(self):
-        dbhelper = self.session.pool.source('system').dbhelper
-        sqlcursor = self.session.pool['system']
-        try:
-            try:
-                self.execute('INSERT CWConstraint X: X cstrtype CT, DEF constrained_by X '
-                             'WHERE CT name "UniqueConstraint", DEF relation_type RT, DEF from_entity E,'
-                             'RT name "sujet", E name "Affaire"')
-                self.failIf(self.schema['Affaire'].has_unique_values('sujet'))
-                self.failIf(self.index_exists('Affaire', 'sujet', unique=True))
-                self.commit()
-                self.failUnless(self.schema['Affaire'].has_unique_values('sujet'))
-                self.failUnless(self.index_exists('Affaire', 'sujet', unique=True))
-            except:
-                import traceback
-                traceback.print_exc()
-                raise
-        finally:
-            self.execute('DELETE DEF constrained_by X WHERE X cstrtype CT, '
-                         'CT name "UniqueConstraint", DEF relation_type RT, DEF from_entity E,'
-                         'RT name "sujet", E name "Affaire"')
-            self.failUnless(self.schema['Affaire'].has_unique_values('sujet'))
-            self.failUnless(self.index_exists('Affaire', 'sujet', unique=True))
-            self.commit()
-            self.failIf(self.schema['Affaire'].has_unique_values('sujet'))
-            self.failIf(self.index_exists('Affaire', 'sujet', unique=True))
-
-    def test_required_change_1(self):
-        self.execute('SET DEF cardinality "?1" '
-                     'WHERE DEF relation_type RT, DEF from_entity E,'
-                     'RT name "nom", E name "Personne"')
-        self.commit()
-        # should now be able to add personne without nom
-        self.execute('INSERT Personne X')
-        self.commit()
-
-    def test_required_change_2(self):
-        self.execute('SET DEF cardinality "11" '
-                     'WHERE DEF relation_type RT, DEF from_entity E,'
-                     'RT name "prenom", E name "Personne"')
-        self.commit()
-        # should not be able anymore to add personne without prenom
-        self.assertRaises(ValidationError, self.execute, 'INSERT Personne X: X nom "toto"')
-        self.execute('SET DEF cardinality "?1" '
-                     'WHERE DEF relation_type RT, DEF from_entity E,'
-                     'RT name "prenom", E name "Personne"')
-        self.commit()
-
-
-    def test_add_attribute_to_base_class(self):
-        self.execute('INSERT CWAttribute X: X cardinality "11", X defaultval "noname", X indexed TRUE, X relation_type RT, X from_entity E, X to_entity F '
-                     'WHERE RT name "nom", E name "BaseTransition", F name "String"')
-        self.commit()
-        self.schema.rebuild_infered_relations()
-        self.failUnless('Transition' in self.schema['nom'].subjects())
-        self.failUnless('WorkflowTransition' in self.schema['nom'].subjects())
-        self.execute('Any X WHERE X is_instance_of BaseTransition, X nom "hop"')
-
-if __name__ == '__main__':
-    unittest_main()
--- a/server/test/unittest_hooksmanager.py	Wed Sep 30 17:57:02 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,179 +0,0 @@
-"""unit tests for the hooks manager
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-
-from logilab.common.testlib import TestCase, unittest_main
-
-from cubicweb.server.hooksmanager import HooksManager, Hook
-from cubicweb.devtools import TestServerConfiguration
-from cubicweb.devtools.apptest import RepositoryBasedTC
-
-class HookCalled(Exception): pass
-
-config = TestServerConfiguration('data')
-config.bootstrap_cubes()
-schema = config.load_schema()
-
-class HooksManagerTC(TestCase):
-    args = (None,)
-    kwargs = {'a': 1}
-
-    def setUp(self):
-        """ called before each test from this class """
-        self.o = HooksManager(schema)
-
-    def test_register_hook_raise_keyerror(self):
-        self.assertRaises(AssertionError,
-                          self.o.register_hook, self._hook, 'before_add_entiti')
-        self.assertRaises(AssertionError,
-                          self.o.register_hook, self._hook, 'session_login', 'CWEType')
-        self.assertRaises(AssertionError,
-                          self.o.register_hook, self._hook, 'session_logout', 'CWEType')
-        self.assertRaises(AssertionError,
-                          self.o.register_hook, self._hook, 'server_startup', 'CWEType')
-        self.assertRaises(AssertionError,
-                          self.o.register_hook, self._hook, 'server_shutdown', 'CWEType')
-
-    def test_register_hook1(self):
-        self.o.register_hook(self._hook, 'before_add_entity')
-        self.o.register_hook(self._hook, 'before_delete_entity', 'Personne')
-        self._test_called_hooks()
-
-    def test_register_hook2(self):
-        self.o.register_hook(self._hook, 'before_add_entity', '')
-        self.o.register_hook(self._hook, 'before_delete_entity', 'Personne')
-        self._test_called_hooks()
-
-    def test_register_hook3(self):
-        self.o.register_hook(self._hook, 'before_add_entity', None)
-        self.o.register_hook(self._hook, 'before_delete_entity', 'Personne')
-        self._test_called_hooks()
-
-    def test_register_hooks(self):
-        self.o.register_hooks({'before_add_entity' : {'': [self._hook]},
-                               'before_delete_entity' : {'Personne': [self._hook]},
-                               })
-        self._test_called_hooks()
-
-    def test_unregister_hook(self):
-        self.o.register_hook(self._hook, 'after_delete_entity', 'Personne')
-        self.assertRaises(HookCalled,
-                          self.o.call_hooks, 'after_delete_entity', 'Personne',
-                          *self.args, **self.kwargs)
-        self.o.unregister_hook(self._hook, 'after_delete_entity', 'Personne')
-        # no hook should be called there
-        self.o.call_hooks('after_delete_entity', 'Personne')
-
-
-    def _test_called_hooks(self):
-        self.assertRaises(HookCalled,
-                          self.o.call_hooks, 'before_add_entity', '',
-                          *self.args, **self.kwargs)
-        self.assertRaises(HookCalled,
-                          self.o.call_hooks, 'before_add_entity', None,
-                          *self.args, **self.kwargs)
-        self.assertRaises(HookCalled,
-                          self.o.call_hooks, 'before_add_entity', 'Personne',
-                          *self.args, **self.kwargs)
-        self.assertRaises(HookCalled,
-                          self.o.call_hooks, 'before_delete_entity', 'Personne',
-                          *self.args, **self.kwargs)
-        # no hook should be called there
-        self.o.call_hooks('before_delete_entity', None)
-        self.o.call_hooks('before_delete_entity', 'Societe')
-
-
-    def _hook(self, *args, **kwargs):
-        # check arguments
-        self.assertEqual(args, self.args)
-        self.assertEqual(kwargs, self.kwargs)
-        raise HookCalled()
-
-
-class RelationHookTC(TestCase):
-    """testcase for relation hooks grouping"""
-    def setUp(self):
-        """ called before each test from this class """
-        self.o = HooksManager(schema)
-        self.called = []
-
-    def test_before_add_relation(self):
-        """make sure before_xxx_relation hooks are called directly"""
-        self.o.register_hook(self._before_relation_hook,
-                             'before_add_relation', 'concerne')
-        self.assertEquals(self.called, [])
-        self.o.call_hooks('before_add_relation', 'concerne', 'USER',
-                          1, 'concerne', 2)
-        self.assertEquals(self.called, [(1, 'concerne', 2)])
-
-    def test_after_add_relation(self):
-        """make sure after_xxx_relation hooks are deferred"""
-        self.o.register_hook(self._after_relation_hook,
-                             'after_add_relation', 'concerne')
-        self.assertEquals(self.called, [])
-        self.o.call_hooks('after_add_relation', 'concerne', 'USER',
-                          1, 'concerne', 2)
-        self.o.call_hooks('after_add_relation', 'concerne', 'USER',
-                          3, 'concerne', 4)
-        self.assertEquals(self.called, [(1, 'concerne', 2), (3, 'concerne', 4)])
-
-    def test_before_delete_relation(self):
-        """make sure before_xxx_relation hooks are called directly"""
-        self.o.register_hook(self._before_relation_hook,
-                             'before_delete_relation', 'concerne')
-        self.assertEquals(self.called, [])
-        self.o.call_hooks('before_delete_relation', 'concerne', 'USER',
-                          1, 'concerne', 2)
-        self.assertEquals(self.called, [(1, 'concerne', 2)])
-
-    def test_after_delete_relation(self):
-        """make sure after_xxx_relation hooks are deferred"""
-        self.o.register_hook(self._after_relation_hook,
-                             'after_delete_relation', 'concerne')
-        self.o.call_hooks('after_delete_relation', 'concerne', 'USER',
-                          1, 'concerne', 2)
-        self.o.call_hooks('after_delete_relation', 'concerne', 'USER',
-                          3, 'concerne', 4)
-        self.assertEquals(self.called, [(1, 'concerne', 2), (3, 'concerne', 4)])
-
-
-    def _before_relation_hook(self, pool, subject, r_type, object):
-        self.called.append((subject, r_type, object))
-
-    def _after_relation_hook(self, pool, subject, r_type, object):
-        self.called.append((subject, r_type, object))
-
-
-class SystemHooksTC(RepositoryBasedTC):
-
-    def test_startup_shutdown(self):
-        import hooks # cubicweb/server/test/data/hooks.py
-        self.assertEquals(hooks.CALLED_EVENTS['server_startup'], True)
-        # don't actually call repository.shutdown !
-        self.repo.hm.call_hooks('server_shutdown', repo=None)
-        self.assertEquals(hooks.CALLED_EVENTS['server_shutdown'], True)
-
-    def test_session_open_close(self):
-        import hooks # cubicweb/server/test/data/hooks.py
-        cnx = self.login('anon')
-        self.assertEquals(hooks.CALLED_EVENTS['session_open'], 'anon')
-        cnx.close()
-        self.assertEquals(hooks.CALLED_EVENTS['session_close'], 'anon')
-
-
-from itertools import repeat
-
-class MyHook(Hook):
-    schema = schema # set for actual hooks at registration time
-    events = ('whatever', 'another')
-    accepts = ('Societe', 'Division')
-
-class HookTC(RepositoryBasedTC):
-    def test_inheritance(self):
-        self.assertEquals(list(MyHook.register_to()),
-                          zip(repeat('whatever'), ('Societe', 'Division', 'SubDivision'))
-                          + zip(repeat('another'), ('Societe', 'Division', 'SubDivision')))
-
-
-if __name__ == '__main__':
-    unittest_main()
--- a/server/test/unittest_ldapuser.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/test/unittest_ldapuser.py	Wed Sep 30 18:57:42 2009 +0200
@@ -7,8 +7,8 @@
 """
 
 from logilab.common.testlib import TestCase, unittest_main, mock_object
-from cubicweb.devtools import init_test_database, TestServerConfiguration
-from cubicweb.devtools.apptest import RepositoryBasedTC
+from cubicweb.devtools import TestServerConfiguration
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.devtools.repotest import RQLGeneratorTC
 
 from cubicweb.server.sources.ldapuser import *
@@ -34,36 +34,31 @@
 
 
 
-config = TestServerConfiguration('data')
-config.sources_file = lambda : 'data/sourcesldap'
-repo, cnx = init_test_database('sqlite', config=config)
-
-class LDAPUserSourceTC(RepositoryBasedTC):
-    repo, cnx = repo, cnx
+class LDAPUserSourceTC(CubicWebTC):
+    config = TestServerConfiguration('data')
+    config.sources_file = lambda : 'data/sourcesldap'
 
     def patch_authenticate(self):
         self._orig_authenticate = LDAPUserSource.authenticate
         LDAPUserSource.authenticate = nopwd_authenticate
 
-    def setUp(self):
-        self._prepare()
+    def setup_database(self):
         # XXX: need this first query else we get 'database is locked' from
         # sqlite since it doesn't support multiple connections on the same
         # database
         # so doing, ldap inserted users don't get removed between each test
-        rset = self.execute('CWUser X')
-        self.commit()
+        rset = self.sexecute('CWUser X')
         # check we get some users from ldap
         self.assert_(len(rset) > 1)
-        self.maxeid = self.execute('Any MAX(X)')[0][0]
 
     def tearDown(self):
         if hasattr(self, '_orig_authenticate'):
             LDAPUserSource.authenticate = self._orig_authenticate
-        RepositoryBasedTC.tearDown(self)
+        CubicWebTC.tearDown(self)
 
     def test_authenticate(self):
         source = self.repo.sources_by_uri['ldapuser']
+        self.session.set_pool()
         self.assertRaises(AuthenticationError,
                           source.authenticate, self.session, 'toto', 'toto')
 
@@ -73,7 +68,7 @@
 
     def test_base(self):
         # check a known one
-        e = self.execute('CWUser X WHERE X login "syt"').get_entity(0, 0)
+        e = self.sexecute('CWUser X WHERE X login "syt"').get_entity(0, 0)
         self.assertEquals(e.login, 'syt')
         e.complete()
         self.assertEquals(e.creation_date, None)
@@ -85,73 +80,73 @@
         self.assertEquals(e.created_by, [])
         self.assertEquals(e.primary_email[0].address, 'Sylvain Thenault')
         # email content should be indexed on the user
-        rset = self.execute('CWUser X WHERE X has_text "thenault"')
+        rset = self.sexecute('CWUser X WHERE X has_text "thenault"')
         self.assertEquals(rset.rows, [[e.eid]])
 
     def test_not(self):
-        eid = self.execute('CWUser X WHERE X login "syt"')[0][0]
-        rset = self.execute('CWUser X WHERE NOT X eid %s' % eid)
+        eid = self.sexecute('CWUser X WHERE X login "syt"')[0][0]
+        rset = self.sexecute('CWUser X WHERE NOT X eid %s' % eid)
         self.assert_(rset)
         self.assert_(not eid in (r[0] for r in rset))
 
     def test_multiple(self):
-        seid = self.execute('CWUser X WHERE X login "syt"')[0][0]
-        aeid = self.execute('CWUser X WHERE X login "adim"')[0][0]
-        rset = self.execute('CWUser X, Y WHERE X login "syt", Y login "adim"')
+        seid = self.sexecute('CWUser X WHERE X login "syt"')[0][0]
+        aeid = self.sexecute('CWUser X WHERE X login "adim"')[0][0]
+        rset = self.sexecute('CWUser X, Y WHERE X login "syt", Y login "adim"')
         self.assertEquals(rset.rows, [[seid, aeid]])
-        rset = self.execute('Any X,Y,L WHERE X login L, X login "syt", Y login "adim"')
+        rset = self.sexecute('Any X,Y,L WHERE X login L, X login "syt", Y login "adim"')
         self.assertEquals(rset.rows, [[seid, aeid, 'syt']])
 
     def test_in(self):
-        seid = self.execute('CWUser X WHERE X login "syt"')[0][0]
-        aeid = self.execute('CWUser X WHERE X login "adim"')[0][0]
-        rset = self.execute('Any X,L ORDERBY L WHERE X login IN("syt", "adim"), X login L')
+        seid = self.sexecute('CWUser X WHERE X login "syt"')[0][0]
+        aeid = self.sexecute('CWUser X WHERE X login "adim"')[0][0]
+        rset = self.sexecute('Any X,L ORDERBY L WHERE X login IN("syt", "adim"), X login L')
         self.assertEquals(rset.rows, [[aeid, 'adim'], [seid, 'syt']])
 
     def test_relations(self):
-        eid = self.execute('CWUser X WHERE X login "syt"')[0][0]
-        rset = self.execute('Any X,E WHERE X is CWUser, X login L, X primary_email E')
+        eid = self.sexecute('CWUser X WHERE X login "syt"')[0][0]
+        rset = self.sexecute('Any X,E WHERE X is CWUser, X login L, X primary_email E')
         self.assert_(eid in (r[0] for r in rset))
-        rset = self.execute('Any X,L,E WHERE X is CWUser, X login L, X primary_email E')
+        rset = self.sexecute('Any X,L,E WHERE X is CWUser, X login L, X primary_email E')
         self.assert_('syt' in (r[1] for r in rset))
 
     def test_count(self):
-        nbusers = self.execute('Any COUNT(X) WHERE X is CWUser')[0][0]
+        nbusers = self.sexecute('Any COUNT(X) WHERE X is CWUser')[0][0]
         # just check this is a possible number
         self.assert_(nbusers > 1, nbusers)
         self.assert_(nbusers < 30, nbusers)
 
     def test_upper(self):
-        eid = self.execute('CWUser X WHERE X login "syt"')[0][0]
-        rset = self.execute('Any UPPER(L) WHERE X eid %s, X login L' % eid)
+        eid = self.sexecute('CWUser X WHERE X login "syt"')[0][0]
+        rset = self.sexecute('Any UPPER(L) WHERE X eid %s, X login L' % eid)
         self.assertEquals(rset[0][0], 'SYT')
 
     def test_unknown_attr(self):
-        eid = self.execute('CWUser X WHERE X login "syt"')[0][0]
-        rset = self.execute('Any L,C,M WHERE X eid %s, X login L, '
+        eid = self.sexecute('CWUser X WHERE X login "syt"')[0][0]
+        rset = self.sexecute('Any L,C,M WHERE X eid %s, X login L, '
                             'X creation_date C, X modification_date M' % eid)
         self.assertEquals(rset[0][0], 'syt')
         self.assertEquals(rset[0][1], None)
         self.assertEquals(rset[0][2], None)
 
     def test_sort(self):
-        logins = [l for l, in self.execute('Any L ORDERBY L WHERE X login L')]
+        logins = [l for l, in self.sexecute('Any L ORDERBY L WHERE X login L')]
         self.assertEquals(logins, sorted(logins))
 
     def test_lower_sort(self):
-        logins = [l for l, in self.execute('Any L ORDERBY lower(L) WHERE X login L')]
+        logins = [l for l, in self.sexecute('Any L ORDERBY lower(L) WHERE X login L')]
         self.assertEquals(logins, sorted(logins))
 
     def test_or(self):
-        rset = self.execute('DISTINCT Any X WHERE X login "syt" OR (X in_group G, G name "managers")')
+        rset = self.sexecute('DISTINCT Any X WHERE X login "syt" OR (X in_group G, G name "managers")')
         self.assertEquals(len(rset), 2, rset.rows) # syt + admin
 
     def test_nonregr_set_owned_by(self):
         # test that when a user coming from ldap is triggering a transition
         # the related TrInfo has correct owner information
-        self.execute('SET X in_group G WHERE X login "syt", G name "managers"')
+        self.sexecute('SET X in_group G WHERE X login "syt", G name "managers"')
         self.commit()
-        syt = self.execute('CWUser X WHERE X login "syt"').get_entity(0, 0)
+        syt = self.sexecute('CWUser X WHERE X login "syt"').get_entity(0, 0)
         self.assertEquals([g.name for g in syt.in_group], ['managers', 'users'])
         self.patch_authenticate()
         cnx = self.login('syt', 'dummypassword')
@@ -160,12 +155,12 @@
         alf.fire_transition('deactivate')
         try:
             cnx.commit()
-            alf = self.execute('CWUser X WHERE X login "alf"').get_entity(0, 0)
+            alf = self.sexecute('CWUser X WHERE X login "alf"').get_entity(0, 0)
             self.assertEquals(alf.in_state[0].name, 'deactivated')
             trinfo = alf.latest_trinfo()
             self.assertEquals(trinfo.owned_by[0].login, 'syt')
             # select from_state to skip the user's creation TrInfo
-            rset = self.execute('Any U ORDERBY D DESC WHERE WF wf_info_for X,'
+            rset = self.sexecute('Any U ORDERBY D DESC WHERE WF wf_info_for X,'
                                 'WF creation_date D, WF from_state FS,'
                                 'WF owned_by U?, X eid %(x)s',
                                 {'x': alf.eid}, 'x')
@@ -173,77 +168,77 @@
         finally:
             # restore db state
             self.restore_connection()
-            alf = self.execute('Any X WHERE X login "alf"').get_entity(0, 0)
+            alf = self.sexecute('Any X WHERE X login "alf"').get_entity(0, 0)
             alf.fire_transition('activate')
-            self.execute('DELETE X in_group G WHERE X login "syt", G name "managers"')
+            self.sexecute('DELETE X in_group G WHERE X login "syt", G name "managers"')
 
     def test_same_column_names(self):
-        self.execute('Any X, Y WHERE X copain Y, X login "comme", Y login "cochon"')
+        self.sexecute('Any X, Y WHERE X copain Y, X login "comme", Y login "cochon"')
 
     def test_multiple_entities_from_different_sources(self):
-        self.create_user('cochon')
-        self.failUnless(self.execute('Any X,Y WHERE X login "syt", Y login "cochon"'))
+        self.create_user('cochon', req=self.session)
+        self.failUnless(self.sexecute('Any X,Y WHERE X login "syt", Y login "cochon"'))
 
     def test_exists1(self):
-        self.add_entity('CWGroup', name=u'bougloup1')
-        self.add_entity('CWGroup', name=u'bougloup2')
-        self.execute('SET U in_group G WHERE G name ~= "bougloup%", U login "admin"')
-        self.execute('SET U in_group G WHERE G name = "bougloup1", U login "syt"')
-        rset = self.execute('Any L,SN ORDERBY L WHERE X in_state S, S name SN, X login L, EXISTS(X in_group G, G name ~= "bougloup%")')
+        self.add_entity('CWGroup', name=u'bougloup1', req=self.session)
+        self.add_entity('CWGroup', name=u'bougloup2', req=self.session)
+        self.sexecute('SET U in_group G WHERE G name ~= "bougloup%", U login "admin"')
+        self.sexecute('SET U in_group G WHERE G name = "bougloup1", U login "syt"')
+        rset = self.sexecute('Any L,SN ORDERBY L WHERE X in_state S, S name SN, X login L, EXISTS(X in_group G, G name ~= "bougloup%")')
         self.assertEquals(rset.rows, [['admin', 'activated'], ['syt', 'activated']])
 
     def test_exists2(self):
-        self.create_user('comme')
-        self.create_user('cochon')
-        self.execute('SET X copain Y WHERE X login "comme", Y login "cochon"')
-        rset = self.execute('Any GN ORDERBY GN WHERE X in_group G, G name GN, (G name "managers" OR EXISTS(X copain T, T login in ("comme", "cochon")))')
+        self.create_user('comme', req=self.session)
+        self.create_user('cochon', req=self.session)
+        self.sexecute('SET X copain Y WHERE X login "comme", Y login "cochon"')
+        rset = self.sexecute('Any GN ORDERBY GN WHERE X in_group G, G name GN, (G name "managers" OR EXISTS(X copain T, T login in ("comme", "cochon")))')
         self.assertEquals(rset.rows, [['managers'], ['users']])
 
     def test_exists3(self):
-        self.create_user('comme')
-        self.create_user('cochon')
-        self.execute('SET X copain Y WHERE X login "comme", Y login "cochon"')
-        self.failUnless(self.execute('Any X, Y WHERE X copain Y, X login "comme", Y login "cochon"'))
-        self.execute('SET X copain Y WHERE X login "syt", Y login "cochon"')
-        self.failUnless(self.execute('Any X, Y WHERE X copain Y, X login "syt", Y login "cochon"'))
-        rset = self.execute('Any GN,L WHERE X in_group G, X login L, G name GN, G name "managers" OR EXISTS(X copain T, T login in ("comme", "cochon"))')
+        self.create_user('comme', req=self.session)
+        self.create_user('cochon', req=self.session)
+        self.sexecute('SET X copain Y WHERE X login "comme", Y login "cochon"')
+        self.failUnless(self.sexecute('Any X, Y WHERE X copain Y, X login "comme", Y login "cochon"'))
+        self.sexecute('SET X copain Y WHERE X login "syt", Y login "cochon"')
+        self.failUnless(self.sexecute('Any X, Y WHERE X copain Y, X login "syt", Y login "cochon"'))
+        rset = self.sexecute('Any GN,L WHERE X in_group G, X login L, G name GN, G name "managers" OR EXISTS(X copain T, T login in ("comme", "cochon"))')
         self.assertEquals(sorted(rset.rows), [['managers', 'admin'], ['users', 'comme'], ['users', 'syt']])
 
     def test_exists4(self):
-        self.create_user('comme')
-        self.create_user('cochon', groups=('users', 'guests'))
-        self.create_user('billy')
-        self.execute('SET X copain Y WHERE X login "comme", Y login "cochon"')
-        self.execute('SET X copain Y WHERE X login "cochon", Y login "cochon"')
-        self.execute('SET X copain Y WHERE X login "comme", Y login "billy"')
-        self.execute('SET X copain Y WHERE X login "syt", Y login "billy"')
+        self.create_user('comme', req=self.session)
+        self.create_user('cochon', groups=('users', 'guests'), req=self.session)
+        self.create_user('billy', req=self.session)
+        self.sexecute('SET X copain Y WHERE X login "comme", Y login "cochon"')
+        self.sexecute('SET X copain Y WHERE X login "cochon", Y login "cochon"')
+        self.sexecute('SET X copain Y WHERE X login "comme", Y login "billy"')
+        self.sexecute('SET X copain Y WHERE X login "syt", Y login "billy"')
         # search for group name, login where
         #   CWUser copain with "comme" or "cochon" AND same login as the copain
         # OR
         #   CWUser in_state activated AND not copain with billy
         #
         # SO we expect everybody but "comme" and "syt"
-        rset= self.execute('Any GN,L WHERE X in_group G, X login L, G name GN, '
+        rset= self.sexecute('Any GN,L WHERE X in_group G, X login L, G name GN, '
                            'EXISTS(X copain T, T login L, T login in ("comme", "cochon")) OR '
                            'EXISTS(X in_state S, S name "activated", NOT X copain T2, T2 login "billy")')
-        all = self.execute('Any GN, L WHERE X in_group G, X login L, G name GN')
+        all = self.sexecute('Any GN, L WHERE X in_group G, X login L, G name GN')
         all.rows.remove(['users', 'comme'])
         all.rows.remove(['users', 'syt'])
         self.assertEquals(sorted(rset.rows), sorted(all.rows))
 
     def test_exists5(self):
-        self.create_user('comme')
-        self.create_user('cochon', groups=('users', 'guests'))
-        self.create_user('billy')
-        self.execute('SET X copain Y WHERE X login "comme", Y login "cochon"')
-        self.execute('SET X copain Y WHERE X login "cochon", Y login "cochon"')
-        self.execute('SET X copain Y WHERE X login "comme", Y login "billy"')
-        self.execute('SET X copain Y WHERE X login "syt", Y login "cochon"')
-        rset= self.execute('Any L WHERE X login L, '
+        self.create_user('comme', req=self.session)
+        self.create_user('cochon', groups=('users', 'guests'), req=self.session)
+        self.create_user('billy', req=self.session)
+        self.sexecute('SET X copain Y WHERE X login "comme", Y login "cochon"')
+        self.sexecute('SET X copain Y WHERE X login "cochon", Y login "cochon"')
+        self.sexecute('SET X copain Y WHERE X login "comme", Y login "billy"')
+        self.sexecute('SET X copain Y WHERE X login "syt", Y login "cochon"')
+        rset= self.sexecute('Any L WHERE X login L, '
                            'EXISTS(X copain T, T login in ("comme", "cochon")) AND '
                            'NOT EXISTS(X copain T2, T2 login "billy")')
         self.assertEquals(sorted(rset.rows), [['cochon'], ['syt']])
-        rset= self.execute('Any GN,L WHERE X in_group G, X login L, G name GN, '
+        rset= self.sexecute('Any GN,L WHERE X in_group G, X login L, G name GN, '
                            'EXISTS(X copain T, T login in ("comme", "cochon")) AND '
                            'NOT EXISTS(X copain T2, T2 login "billy")')
         self.assertEquals(sorted(rset.rows), [['guests', 'cochon'],
@@ -251,18 +246,20 @@
                                               ['users', 'syt']])
 
     def test_cd_restriction(self):
-        rset = self.execute('CWUser X WHERE X creation_date > "2009-02-01"')
-        self.assertEquals(len(rset), 2) # admin/anon but no ldap user since it doesn't support creation_date
+        rset = self.sexecute('CWUser X WHERE X creation_date > "2009-02-01"')
+        # admin/anon but no ldap user since it doesn't support creation_date
+        self.assertEquals(sorted(e.login for e in rset.entities()),
+                          ['admin', 'anon'])
 
     def test_union(self):
-        afeids = self.execute('State X')
-        ueids = self.execute('CWUser X')
-        rset = self.execute('(Any X WHERE X is State) UNION (Any X WHERE X is CWUser)')
+        afeids = self.sexecute('State X')
+        ueids = self.sexecute('CWUser X')
+        rset = self.sexecute('(Any X WHERE X is State) UNION (Any X WHERE X is CWUser)')
         self.assertEquals(sorted(r[0] for r in rset.rows),
                           sorted(r[0] for r in afeids + ueids))
 
     def _init_security_test(self):
-        self.create_user('iaminguestsgrouponly', groups=('guests',))
+        self.create_user('iaminguestsgrouponly', groups=('guests',), req=self.session)
         cnx = self.login('iaminguestsgrouponly')
         return cnx.cursor()
 
@@ -288,33 +285,33 @@
         self.assertEquals(rset.rows, [[None]])
 
     def test_nonregr1(self):
-        self.execute('Any X,AA ORDERBY AA DESC WHERE E eid %(x)s, E owned_by X, '
+        self.sexecute('Any X,AA ORDERBY AA DESC WHERE E eid %(x)s, E owned_by X, '
                      'X modification_date AA',
-                     {'x': cnx.user(self.session).eid})
+                     {'x': self.session.user.eid})
 
     def test_nonregr2(self):
-        self.execute('Any X,L,AA WHERE E eid %(x)s, E owned_by X, '
+        self.sexecute('Any X,L,AA WHERE E eid %(x)s, E owned_by X, '
                      'X login L, X modification_date AA',
-                     {'x': cnx.user(self.session).eid})
+                     {'x': self.session.user.eid})
 
     def test_nonregr3(self):
-        self.execute('Any X,AA ORDERBY AA DESC WHERE E eid %(x)s, '
+        self.sexecute('Any X,AA ORDERBY AA DESC WHERE E eid %(x)s, '
                      'X modification_date AA',
-                     {'x': cnx.user(self.session).eid})
+                     {'x': self.session.user.eid})
 
     def test_nonregr4(self):
-        emaileid = self.execute('INSERT EmailAddress X: X address "toto@logilab.org"')[0][0]
-        self.execute('Any X,AA WHERE X use_email Y, Y eid %(x)s, X modification_date AA',
+        emaileid = self.sexecute('INSERT EmailAddress X: X address "toto@logilab.org"')[0][0]
+        self.sexecute('Any X,AA WHERE X use_email Y, Y eid %(x)s, X modification_date AA',
                      {'x': emaileid})
 
     def test_nonregr5(self):
         # original jpl query:
         # Any X, NOW - CD, P WHERE P is Project, U interested_in P, U is CWUser, U login "sthenault", X concerns P, X creation_date CD ORDERBY CD DESC LIMIT 5
         rql = 'Any X, NOW - CD, P ORDERBY CD DESC LIMIT 5 WHERE P bookmarked_by U, U login "%s", P is X, X creation_date CD' % self.session.user.login
-        self.execute(rql, )#{'x': })
+        self.sexecute(rql, )#{'x': })
 
     def test_nonregr6(self):
-        self.execute('Any B,U,UL GROUPBY B,U,UL WHERE B created_by U?, B is File '
+        self.sexecute('Any B,U,UL GROUPBY B,U,UL WHERE B created_by U?, B is File '
                      'WITH U,UL BEING (Any U,UL WHERE ME eid %(x)s, (EXISTS(U identity ME) '
                      'OR (EXISTS(U in_group G, G name IN("managers", "staff")))) '
                      'OR (EXISTS(U in_group H, ME in_group H, NOT H name "users")), U login UL, U is CWUser)',
@@ -355,6 +352,9 @@
         res = trfunc.apply([[1, 2], [2, 4], [3, 6], [1, 5]])
         self.assertEquals(res, [[1, 5], [2, 4], [3, 6]])
 
+# XXX
+LDAPUserSourceTC._init_repo()
+repo = LDAPUserSourceTC.repo
 
 class RQL2LDAPFilterTC(RQLGeneratorTC):
     schema = repo.schema
--- a/server/test/unittest_migractions.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/test/unittest_migractions.py	Wed Sep 30 18:57:42 2009 +0200
@@ -2,13 +2,14 @@
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
+from copy import deepcopy
 from datetime import date
 from os.path import join
 
 from logilab.common.testlib import TestCase, unittest_main
 
 from cubicweb import ConfigurationError
-from cubicweb.devtools.apptest import RepositoryBasedTC, get_versions
+from cubicweb.devtools.testlib import CubicWebTC, get_versions
 from cubicweb.schema import CubicWebSchemaLoader
 from cubicweb.server.sqlutils import SQL_PREFIX
 from cubicweb.server.repository import Repository
@@ -23,22 +24,28 @@
     Repository.get_versions = orig_get_versions
 
 
-class MigrationCommandsTC(RepositoryBasedTC):
+class MigrationCommandsTC(CubicWebTC):
+
+    @classmethod
+    def init_config(cls, config):
+        super(MigrationCommandsTC, cls).init_config(config)
+        config._cubes = None
+        cls.repo.fill_schema()
+        cls.origschema = deepcopy(cls.repo.schema)
+        # hack to read the schema from data/migrschema
+        config.appid = join('data', 'migratedapp')
+        global migrschema
+        migrschema = config.load_schema()
+        config.appid = 'data'
+        assert 'Folder' in migrschema
+
+    @classmethod
+    def _refresh_repo(cls):
+        super(MigrationCommandsTC, cls)._refresh_repo()
+        cls.repo.schema = cls.vreg.schema = deepcopy(cls.origschema)
 
     def setUp(self):
-        if not hasattr(self, '_repo'):
-            # first initialization
-            repo = self.repo # set by the RepositoryBasedTC metaclass
-            # force to read schema from the database
-            repo.config._cubes = None
-            repo.fill_schema()
-            # hack to read the schema from data/migrschema
-            self.repo.config.appid = join('data', 'migratedapp')
-            global migrschema
-            migrschema = self.repo.config.load_schema()
-            self.repo.config.appid = 'data'
-            assert 'Folder' in migrschema
-        RepositoryBasedTC.setUp(self)
+        CubicWebTC.setUp(self)
         self.mh = ServerMigrationHelper(self.repo.config, migrschema,
                                         repo=self.repo, cnx=self.cnx,
                                         interactive=False)
--- a/server/test/unittest_msplanner.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/test/unittest_msplanner.py	Wed Sep 30 18:57:42 2009 +0200
@@ -59,7 +59,7 @@
 
 
 # keep cnx so it's not garbage collected and the associated session is closed
-repo, cnx = init_test_database('sqlite')
+repo, cnx = init_test_database()
 
 class BaseMSPlannerTC(BasePlannerTC):
     """test planner related feature on a 3-sources repository:
--- a/server/test/unittest_multisources.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/test/unittest_multisources.py	Wed Sep 30 18:57:42 2009 +0200
@@ -11,7 +11,7 @@
 from logilab.common.decorators import cached
 
 from cubicweb.devtools import TestServerConfiguration, init_test_database
-from cubicweb.devtools.apptest import RepositoryBasedTC
+from cubicweb.devtools.testlib import CubicWebTC, refresh_repo
 from cubicweb.devtools.repotest import do_monkey_patch, undo_monkey_patch
 
 TestServerConfiguration.no_sqlite_wrap = True
@@ -26,16 +26,9 @@
 class ExternalSource2Configuration(TestServerConfiguration):
     sourcefile = 'sources_multi2'
 
-repo2, cnx2 = init_test_database('sqlite', config=ExternalSource1Configuration('data'))
-cu = cnx2.cursor()
-ec1 = cu.execute('INSERT Card X: X title "C3: An external card", X wikiid "aaa"')[0][0]
-cu.execute('INSERT Card X: X title "C4: Ze external card", X wikiid "zzz"')
-aff1 = cu.execute('INSERT Affaire X: X ref "AFFREF"')[0][0]
-cnx2.commit()
-
 MTIME = datetime.now() - timedelta(0, 10)
-
-repo3, cnx3 = init_test_database('sqlite', config=ExternalSource2Configuration('data'))
+repo2, cnx2 = init_test_database(config=ExternalSource1Configuration('data'))
+repo3, cnx3 = init_test_database(config=ExternalSource2Configuration('data'))
 
 # XXX, access existing connection, no pyro connection
 from cubicweb.server.sources.pyrorql import PyroRQLSource
@@ -45,38 +38,47 @@
 from cubicweb.dbapi import Connection
 Connection.close = lambda x: None
 
-class TwoSourcesTC(RepositoryBasedTC):
-    repo_config = TwoSourcesConfiguration('data')
+class TwoSourcesTC(CubicWebTC):
+    config = TwoSourcesConfiguration('data')
+
+    @classmethod
+    def _refresh_repo(cls):
+        super(TwoSourcesTC, cls)._refresh_repo()
+        cnx2.rollback()
+        refresh_repo(repo2)
+        cnx3.rollback()
+        refresh_repo(repo3)
 
     def setUp(self):
-        RepositoryBasedTC.setUp(self)
-        self.repo.sources[-1]._query_cache.clear()
-        self.repo.sources[-2]._query_cache.clear()
-        # trigger discovery
-        self.execute('Card X')
-        self.execute('Affaire X')
-        self.execute('State X')
-        self.commit()
-        # don't delete external entities!
-        self.maxeid = self.session.system_sql('SELECT MAX(eid) FROM entities').fetchone()[0]
-        # add some entities
-        self.ic1 = self.execute('INSERT Card X: X title "C1: An internal card", X wikiid "aaai"')[0][0]
-        self.ic2 = self.execute('INSERT Card X: X title "C2: Ze internal card", X wikiid "zzzi"')[0][0]
-        self.commit()
+        CubicWebTC.setUp(self)
         do_monkey_patch()
 
     def tearDown(self):
-        RepositoryBasedTC.tearDown(self)
+        CubicWebTC.tearDown(self)
         undo_monkey_patch()
 
+    def setup_database(self):
+        cu = cnx2.cursor()
+        self.ec1 = cu.execute('INSERT Card X: X title "C3: An external card", X wikiid "aaa"')[0][0]
+        cu.execute('INSERT Card X: X title "C4: Ze external card", X wikiid "zzz"')
+        self.aff1 = cu.execute('INSERT Affaire X: X ref "AFFREF"')[0][0]
+        cnx2.commit()
+        # trigger discovery
+        self.sexecute('Card X')
+        self.sexecute('Affaire X')
+        self.sexecute('State X')
+        # add some entities
+        self.ic1 = self.sexecute('INSERT Card X: X title "C1: An internal card", X wikiid "aaai"')[0][0]
+        self.ic2 = self.sexecute('INSERT Card X: X title "C2: Ze internal card", X wikiid "zzzi"')[0][0]
+
     def test_eid_comp(self):
-        rset = self.execute('Card X WHERE X eid > 1')
+        rset = self.sexecute('Card X WHERE X eid > 1')
         self.assertEquals(len(rset), 4)
-        rset = self.execute('Any X,T WHERE X title T, X eid > 1')
+        rset = self.sexecute('Any X,T WHERE X title T, X eid > 1')
         self.assertEquals(len(rset), 4)
 
     def test_metainformation(self):
-        rset = self.execute('Card X ORDERBY T WHERE X title T')
+        rset = self.sexecute('Card X ORDERBY T WHERE X title T')
         # 2 added to the system source, 2 added to the external source
         self.assertEquals(len(rset), 4)
         # since they are orderd by eid, we know the 3 first one is coming from the system source
@@ -89,28 +91,28 @@
         self.assertEquals(metainf['source'], {'adapter': 'pyrorql', 'base-url': 'http://extern.org/', 'uri': 'extern'})
         self.assertEquals(metainf['type'], 'Card')
         self.assert_(metainf['extid'])
-        etype = self.execute('Any ETN WHERE X is ET, ET name ETN, X eid %(x)s',
+        etype = self.sexecute('Any ETN WHERE X is ET, ET name ETN, X eid %(x)s',
                              {'x': externent.eid}, 'x')[0][0]
         self.assertEquals(etype, 'Card')
 
     def test_order_limit_offset(self):
-        rsetbase = self.execute('Any W,X ORDERBY W,X WHERE X wikiid W')
+        rsetbase = self.sexecute('Any W,X ORDERBY W,X WHERE X wikiid W')
         self.assertEquals(len(rsetbase), 4)
         self.assertEquals(sorted(rsetbase.rows), rsetbase.rows)
-        rset = self.execute('Any W,X ORDERBY W,X LIMIT 2 OFFSET 2 WHERE X wikiid W')
+        rset = self.sexecute('Any W,X ORDERBY W,X LIMIT 2 OFFSET 2 WHERE X wikiid W')
         self.assertEquals(rset.rows, rsetbase.rows[2:4])
 
     def test_has_text(self):
         self.repo.sources_by_uri['extern'].synchronize(MTIME) # in case fti_update has been run before
-        self.failUnless(self.execute('Any X WHERE X has_text "affref"'))
-        self.failUnless(self.execute('Affaire X WHERE X has_text "affref"'))
+        self.failUnless(self.sexecute('Any X WHERE X has_text "affref"'))
+        self.failUnless(self.sexecute('Affaire X WHERE X has_text "affref"'))
 
     def test_anon_has_text(self):
         self.repo.sources_by_uri['extern'].synchronize(MTIME) # in case fti_update has been run before
-        self.execute('INSERT Affaire X: X ref "no readable card"')[0][0]
-        aff1 = self.execute('INSERT Affaire X: X ref "card"')[0][0]
+        self.sexecute('INSERT Affaire X: X ref "no readable card"')[0][0]
+        aff1 = self.sexecute('INSERT Affaire X: X ref "card"')[0][0]
         # grant read access
-        self.execute('SET X owned_by U WHERE X eid %(x)s, U login "anon"', {'x': aff1}, 'x')
+        self.sexecute('SET X owned_by U WHERE X eid %(x)s, U login "anon"', {'x': aff1}, 'x')
         self.commit()
         cnx = self.login('anon')
         cu = cnx.cursor()
@@ -120,79 +122,81 @@
 
     def test_synchronization(self):
         cu = cnx2.cursor()
-        assert cu.execute('Any X WHERE X eid %(x)s', {'x': aff1}, 'x')
-        cu.execute('SET X ref "BLAH" WHERE X eid %(x)s', {'x': aff1}, 'x')
+        assert cu.execute('Any X WHERE X eid %(x)s', {'x': self.aff1}, 'x')
+        cu.execute('SET X ref "BLAH" WHERE X eid %(x)s', {'x': self.aff1}, 'x')
         aff2 = cu.execute('INSERT Affaire X: X ref "AFFREUX"')[0][0]
         cnx2.commit()
         try:
             # force sync
             self.repo.sources_by_uri['extern'].synchronize(MTIME)
-            self.failUnless(self.execute('Any X WHERE X has_text "blah"'))
-            self.failUnless(self.execute('Any X WHERE X has_text "affreux"'))
+            self.failUnless(self.sexecute('Any X WHERE X has_text "blah"'))
+            self.failUnless(self.sexecute('Any X WHERE X has_text "affreux"'))
             cu.execute('DELETE Affaire X WHERE X eid %(x)s', {'x': aff2})
             cnx2.commit()
             self.repo.sources_by_uri['extern'].synchronize(MTIME)
-            rset = self.execute('Any X WHERE X has_text "affreux"')
+            rset = self.sexecute('Any X WHERE X has_text "affreux"')
             self.failIf(rset)
         finally:
             # restore state
-            cu.execute('SET X ref "AFFREF" WHERE X eid %(x)s', {'x': aff1}, 'x')
+            cu.execute('SET X ref "AFFREF" WHERE X eid %(x)s', {'x': self.aff1}, 'x')
             cnx2.commit()
 
     def test_simplifiable_var(self):
-        affeid = self.execute('Affaire X WHERE X ref "AFFREF"')[0][0]
-        rset = self.execute('Any X,AA,AB WHERE E eid %(x)s, E in_state X, X name AA, X modification_date AB',
+        affeid = self.sexecute('Affaire X WHERE X ref "AFFREF"')[0][0]
+        rset = self.sexecute('Any X,AA,AB WHERE E eid %(x)s, E in_state X, X name AA, X modification_date AB',
                             {'x': affeid}, 'x')
         self.assertEquals(len(rset), 1)
         self.assertEquals(rset[0][1], "pitetre")
 
     def test_simplifiable_var_2(self):
-        affeid = self.execute('Affaire X WHERE X ref "AFFREF"')[0][0]
-        rset = self.execute('Any E WHERE E eid %(x)s, E in_state S, NOT S name "moved"',
+        affeid = self.sexecute('Affaire X WHERE X ref "AFFREF"')[0][0]
+        rset = self.sexecute('Any E WHERE E eid %(x)s, E in_state S, NOT S name "moved"',
                             {'x': affeid, 'u': self.session.user.eid}, 'x')
         self.assertEquals(len(rset), 1)
 
     def test_sort_func(self):
-        self.execute('Affaire X ORDERBY DUMB_SORT(RF) WHERE X ref RF')
+        self.sexecute('Affaire X ORDERBY DUMB_SORT(RF) WHERE X ref RF')
 
     def test_sort_func_ambigous(self):
-        self.execute('Any X ORDERBY DUMB_SORT(RF) WHERE X title RF')
+        self.sexecute('Any X ORDERBY DUMB_SORT(RF) WHERE X title RF')
 
     def test_in_eid(self):
-        iec1 = self.repo.extid2eid(self.repo.sources_by_uri['extern'], str(ec1),
+        iec1 = self.repo.extid2eid(self.repo.sources_by_uri['extern'], str(self.ec1),
                                    'Card', self.session)
-        rset = self.execute('Any X WHERE X eid IN (%s, %s)' % (iec1, self.ic1))
+        rset = self.sexecute('Any X WHERE X eid IN (%s, %s)' % (iec1, self.ic1))
         self.assertEquals(sorted(r[0] for r in rset.rows), sorted([iec1, self.ic1]))
 
     def test_greater_eid(self):
-        rset = self.execute('Any X WHERE X eid > %s' % self.maxeid)
+        rset = self.sexecute('Any X WHERE X eid > %s' % (self.ic1 - 1))
         self.assertEquals(len(rset.rows), 2) # self.ic1 and self.ic2
+        cu = cnx2.cursor()
         ec2 = cu.execute('INSERT Card X: X title "glup"')[0][0]
         cnx2.commit()
         # 'X eid > something' should not trigger discovery
-        rset = self.execute('Any X WHERE X eid > %s' % self.maxeid)
+        rset = self.sexecute('Any X WHERE X eid > %s' % (self.ic1 - 1))
         self.assertEquals(len(rset.rows), 2)
         # trigger discovery using another query
-        crset = self.execute('Card X WHERE X title "glup"')
+        crset = self.sexecute('Card X WHERE X title "glup"')
         self.assertEquals(len(crset.rows), 1)
-        rset = self.execute('Any X WHERE X eid > %s' % self.maxeid)
+        rset = self.sexecute('Any X WHERE X eid > %s' % (self.ic1 - 1))
         self.assertEquals(len(rset.rows), 3)
-        rset = self.execute('Any MAX(X)')
+        rset = self.sexecute('Any MAX(X)')
         self.assertEquals(len(rset.rows), 1)
         self.assertEquals(rset.rows[0][0], crset[0][0])
 
     def test_attr_unification_1(self):
-        n1 = self.execute('INSERT Note X: X type "AFFREF"')[0][0]
-        n2 = self.execute('INSERT Note X: X type "AFFREU"')[0][0]
-        rset = self.execute('Any X,Y WHERE X is Note, Y is Affaire, X type T, Y ref T')
+        n1 = self.sexecute('INSERT Note X: X type "AFFREF"')[0][0]
+        n2 = self.sexecute('INSERT Note X: X type "AFFREU"')[0][0]
+        rset = self.sexecute('Any X,Y WHERE X is Note, Y is Affaire, X type T, Y ref T')
         self.assertEquals(len(rset), 1, rset.rows)
 
     def test_attr_unification_2(self):
+        cu = cnx2.cursor()
         ec2 = cu.execute('INSERT Card X: X title "AFFREF"')[0][0]
         cnx2.commit()
         try:
-            c1 = self.execute('INSERT Card C: C title "AFFREF"')[0][0]
-            rset = self.execute('Any X,Y WHERE X is Card, Y is Affaire, X title T, Y ref T')
+            c1 = self.sexecute('INSERT Card C: C title "AFFREF"')[0][0]
+            rset = self.sexecute('Any X,Y WHERE X is Card, Y is Affaire, X title T, Y ref T')
             self.assertEquals(len(rset), 2, rset.rows)
         finally:
             cu.execute('DELETE Card X WHERE X eid %(x)s', {'x': ec2}, 'x')
@@ -200,83 +204,88 @@
 
     def test_attr_unification_neq_1(self):
         # XXX complete
-        self.execute('Any X,Y WHERE X is Note, Y is Affaire, X creation_date D, Y creation_date > D')
+        self.sexecute('Any X,Y WHERE X is Note, Y is Affaire, X creation_date D, Y creation_date > D')
 
     def test_attr_unification_neq_2(self):
         # XXX complete
-        self.execute('Any X,Y WHERE X is Card, Y is Affaire, X creation_date D, Y creation_date > D')
+        self.sexecute('Any X,Y WHERE X is Card, Y is Affaire, X creation_date D, Y creation_date > D')
 
     def test_union(self):
-        afeids = self.execute('Affaire X')
-        ueids = self.execute('CWUser X')
-        rset = self.execute('(Any X WHERE X is Affaire) UNION (Any X WHERE X is CWUser)')
+        afeids = self.sexecute('Affaire X')
+        ueids = self.sexecute('CWUser X')
+        rset = self.sexecute('(Any X WHERE X is Affaire) UNION (Any X WHERE X is CWUser)')
         self.assertEquals(sorted(r[0] for r in rset.rows),
                           sorted(r[0] for r in afeids + ueids))
 
     def test_subquery1(self):
-        rsetbase = self.execute('Any W,X WITH W,X BEING (Any W,X ORDERBY W,X WHERE X wikiid W)')
+        rsetbase = self.sexecute('Any W,X WITH W,X BEING (Any W,X ORDERBY W,X WHERE X wikiid W)')
         self.assertEquals(len(rsetbase), 4)
         self.assertEquals(sorted(rsetbase.rows), rsetbase.rows)
-        rset = self.execute('Any W,X LIMIT 2 OFFSET 2 WITH W,X BEING (Any W,X ORDERBY W,X WHERE X wikiid W)')
+        rset = self.sexecute('Any W,X LIMIT 2 OFFSET 2 WITH W,X BEING (Any W,X ORDERBY W,X WHERE X wikiid W)')
         self.assertEquals(rset.rows, rsetbase.rows[2:4])
-        rset = self.execute('Any W,X ORDERBY W,X LIMIT 2 OFFSET 2 WITH W,X BEING (Any W,X WHERE X wikiid W)')
+        rset = self.sexecute('Any W,X ORDERBY W,X LIMIT 2 OFFSET 2 WITH W,X BEING (Any W,X WHERE X wikiid W)')
         self.assertEquals(rset.rows, rsetbase.rows[2:4])
-        rset = self.execute('Any W,X WITH W,X BEING (Any W,X ORDERBY W,X LIMIT 2 OFFSET 2 WHERE X wikiid W)')
+        rset = self.sexecute('Any W,X WITH W,X BEING (Any W,X ORDERBY W,X LIMIT 2 OFFSET 2 WHERE X wikiid W)')
         self.assertEquals(rset.rows, rsetbase.rows[2:4])
 
     def test_subquery2(self):
-        affeid = self.execute('Affaire X WHERE X ref "AFFREF"')[0][0]
-        rset = self.execute('Any X,AA,AB WITH X,AA,AB BEING (Any X,AA,AB WHERE E eid %(x)s, E in_state X, X name AA, X modification_date AB)',
+        affeid = self.sexecute('Affaire X WHERE X ref "AFFREF"')[0][0]
+        rset = self.sexecute('Any X,AA,AB WITH X,AA,AB BEING (Any X,AA,AB WHERE E eid %(x)s, E in_state X, X name AA, X modification_date AB)',
                             {'x': affeid})
         self.assertEquals(len(rset), 1)
         self.assertEquals(rset[0][1], "pitetre")
 
     def test_not_relation(self):
-        states = set(tuple(x) for x in self.execute('Any S,SN WHERE S is State, S name SN'))
+        states = set(tuple(x) for x in self.sexecute('Any S,SN WHERE S is State, S name SN'))
         self.session.user.clear_all_caches()
         userstate = self.session.user.in_state[0]
         states.remove((userstate.eid, userstate.name))
-        notstates = set(tuple(x) for x in self.execute('Any S,SN WHERE S is State, S name SN, NOT X in_state S, X eid %(x)s',
+        notstates = set(tuple(x) for x in self.sexecute('Any S,SN WHERE S is State, S name SN, NOT X in_state S, X eid %(x)s',
                                                        {'x': self.session.user.eid}, 'x'))
         self.assertSetEquals(notstates, states)
-        aff1 = self.execute('Any X WHERE X is Affaire, X ref "AFFREF"')[0][0]
-        aff1stateeid, aff1statename = self.execute('Any S,SN WHERE X eid %(x)s, X in_state S, S name SN', {'x': aff1}, 'x')[0]
+        aff1 = self.sexecute('Any X WHERE X is Affaire, X ref "AFFREF"')[0][0]
+        aff1stateeid, aff1statename = self.sexecute('Any S,SN WHERE X eid %(x)s, X in_state S, S name SN', {'x': aff1}, 'x')[0]
         self.assertEquals(aff1statename, 'pitetre')
         states.add((userstate.eid, userstate.name))
         states.remove((aff1stateeid, aff1statename))
-        notstates = set(tuple(x) for x in self.execute('Any S,SN WHERE S is State, S name SN, NOT X in_state S, X eid %(x)s',
+        notstates = set(tuple(x) for x in self.sexecute('Any S,SN WHERE S is State, S name SN, NOT X in_state S, X eid %(x)s',
                                                        {'x': aff1}, 'x'))
         self.assertSetEquals(notstates, states)
 
     def test_absolute_url_base_url(self):
+        cu = cnx2.cursor()
         ceid = cu.execute('INSERT Card X: X title "without wikiid to get eid based url"')[0][0]
         cnx2.commit()
-        lc = self.execute('Card X WHERE X title "without wikiid to get eid based url"').get_entity(0, 0)
+        lc = self.sexecute('Card X WHERE X title "without wikiid to get eid based url"').get_entity(0, 0)
         self.assertEquals(lc.absolute_url(), 'http://extern.org/card/eid/%s' % ceid)
+        cu.execute('DELETE Card X WHERE X eid %(x)s', {'x':ceid})
+        cnx2.commit()
 
     def test_absolute_url_no_base_url(self):
         cu = cnx3.cursor()
         ceid = cu.execute('INSERT Card X: X title "without wikiid to get eid based url"')[0][0]
         cnx3.commit()
-        lc = self.execute('Card X WHERE X title "without wikiid to get eid based url"').get_entity(0, 0)
+        lc = self.sexecute('Card X WHERE X title "without wikiid to get eid based url"').get_entity(0, 0)
         self.assertEquals(lc.absolute_url(), 'http://testing.fr/cubicweb/card/eid/%s' % lc.eid)
+        cu.execute('DELETE Card X WHERE X eid %(x)s', {'x':ceid})
+        cnx3.commit()
 
     def test_nonregr1(self):
         ueid = self.session.user.eid
-        affaire = self.execute('Affaire X WHERE X ref "AFFREF"').get_entity(0, 0)
-        self.execute('Any U WHERE U in_group G, (G name IN ("managers", "logilab") OR (X require_permission P?, P name "bla", P require_group G)), X eid %(x)s, U eid %(u)s',
+        affaire = self.sexecute('Affaire X WHERE X ref "AFFREF"').get_entity(0, 0)
+        self.sexecute('Any U WHERE U in_group G, (G name IN ("managers", "logilab") OR (X require_permission P?, P name "bla", P require_group G)), X eid %(x)s, U eid %(u)s',
                      {'x': affaire.eid, 'u': ueid})
 
     def test_nonregr2(self):
         self.session.user.fire_transition('deactivate')
         treid = self.session.user.latest_trinfo().eid
-        rset = self.execute('Any X ORDERBY D DESC WHERE E eid %(x)s, E wf_info_for X, X modification_date D',
+        rset = self.sexecute('Any X ORDERBY D DESC WHERE E eid %(x)s, E wf_info_for X, X modification_date D',
                             {'x': treid})
         self.assertEquals(len(rset), 1)
         self.assertEquals(rset.rows[0], [self.session.user.eid])
 
     def test_nonregr3(self):
-        self.execute('DELETE Card X WHERE X eid %(x)s, NOT X multisource_inlined_rel Y', {'x': self.ic1})
+        self.sexecute('DELETE Card X WHERE X eid %(x)s, NOT X multisource_inlined_rel Y', {'x': self.ic1})
 
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
--- a/server/test/unittest_querier.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/test/unittest_querier.py	Wed Sep 30 18:57:42 2009 +0200
@@ -46,7 +46,7 @@
                           ('C0 text,C1 integer', {'A': 'table0.C0', 'B': 'table0.C1'}))
 
 
-repo, cnx = init_test_database('sqlite')
+repo, cnx = init_test_database()
 
 
 
--- a/server/test/unittest_repository.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/test/unittest_repository.py	Wed Sep 30 18:57:42 2009 +0200
@@ -18,12 +18,13 @@
 
 from yams.constraints import UniqueConstraint
 
-from cubicweb import BadConnectionId, RepositoryError, ValidationError, UnknownEid, AuthenticationError
+from cubicweb import (BadConnectionId, RepositoryError, ValidationError,
+                      UnknownEid, AuthenticationError)
 from cubicweb.schema import CubicWebSchema, RQLConstraint
 from cubicweb.dbapi import connect, repo_connect, multiple_connections_unfix
-from cubicweb.devtools.apptest import RepositoryBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.devtools.repotest import tuplify
-from cubicweb.server import repository
+from cubicweb.server import repository, hook
 from cubicweb.server.sqlutils import SQL_PREFIX
 
 
@@ -31,48 +32,35 @@
 os.system('pyro-ns >/dev/null 2>/dev/null &')
 
 
-class RepositoryTC(RepositoryBasedTC):
+class RepositoryTC(CubicWebTC):
     """ singleton providing access to a persistent storage for entities
     and relation
     """
 
-#     def setUp(self):
-#         pass
-
-#     def tearDown(self):
-#         self.repo.config.db_perms = True
-#         cnxid = self.repo.connect(*self.default_user_password())
-#         for etype in ('Affaire', 'Note', 'Societe', 'Personne'):
-#             self.repo.execute(cnxid, 'DELETE %s X' % etype)
-#             self.repo.commit(cnxid)
-#         self.repo.close(cnxid)
-
     def test_fill_schema(self):
         self.repo.schema = CubicWebSchema(self.repo.config.appid)
         self.repo.config._cubes = None # avoid assertion error
+        self.repo.config.repairing = True # avoid versions checking
         self.repo.fill_schema()
-        pool = self.repo._get_pool()
         table = SQL_PREFIX + 'CWEType'
         namecol = SQL_PREFIX + 'name'
         finalcol = SQL_PREFIX + 'final'
-        try:
-            cu = self.session.system_sql('SELECT %s FROM %s WHERE %s is NULL' % (
-                namecol, table, finalcol))
-            self.assertEquals(cu.fetchall(), [])
-            cu = self.session.system_sql('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s'
-                              % (namecol, table, finalcol, namecol), {'final': 'TRUE'})
-            self.assertEquals(cu.fetchall(), [(u'Boolean',), (u'Bytes',),
-                                                     (u'Date',), (u'Datetime',),
-                                                     (u'Decimal',),(u'Float',),
-                                                     (u'Int',),
-                                                     (u'Interval',), (u'Password',),
-                                                     (u'String',), (u'Time',)])
-        finally:
-            self.repo._free_pool(pool)
+        self.session.set_pool()
+        cu = self.session.system_sql('SELECT %s FROM %s WHERE %s is NULL' % (
+            namecol, table, finalcol))
+        self.assertEquals(cu.fetchall(), [])
+        cu = self.session.system_sql('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s'
+                          % (namecol, table, finalcol, namecol), {'final': 'TRUE'})
+        self.assertEquals(cu.fetchall(), [(u'Boolean',), (u'Bytes',),
+                                          (u'Date',), (u'Datetime',),
+                                          (u'Decimal',),(u'Float',),
+                                          (u'Int',),
+                                          (u'Interval',), (u'Password',),
+                                          (u'String',), (u'Time',)])
 
     def test_schema_has_owner(self):
         repo = self.repo
-        cnxid = repo.connect(*self.default_user_password())
+        cnxid = repo.connect(self.admlogin, self.admpassword)
         self.failIf(repo.execute(cnxid, 'CWEType X WHERE NOT X owned_by U'))
         self.failIf(repo.execute(cnxid, 'CWRType X WHERE NOT X owned_by U'))
         self.failIf(repo.execute(cnxid, 'CWAttribute X WHERE NOT X owned_by U'))
@@ -81,18 +69,17 @@
         self.failIf(repo.execute(cnxid, 'CWConstraintType X WHERE NOT X owned_by U'))
 
     def test_connect(self):
-        login, passwd = self.default_user_password()
-        self.assert_(self.repo.connect(login, passwd))
+        self.assert_(self.repo.connect(self.admlogin, self.admpassword))
         self.assertRaises(AuthenticationError,
-                          self.repo.connect, login, 'nimportnawak')
+                          self.repo.connect, self.admlogin, 'nimportnawak')
         self.assertRaises(AuthenticationError,
-                          self.repo.connect, login, None)
+                          self.repo.connect, self.admlogin, None)
         self.assertRaises(AuthenticationError,
                           self.repo.connect, None, None)
 
     def test_execute(self):
         repo = self.repo
-        cnxid = repo.connect(*self.default_user_password())
+        cnxid = repo.connect(self.admlogin, self.admpassword)
         repo.execute(cnxid, 'Any X')
         repo.execute(cnxid, 'Any X where X is Personne')
         repo.execute(cnxid, 'Any X where X is Personne, X nom ~= "to"')
@@ -101,7 +88,7 @@
 
     def test_login_upassword_accent(self):
         repo = self.repo
-        cnxid = repo.connect(*self.default_user_password())
+        cnxid = repo.connect(self.admlogin, self.admpassword)
         repo.execute(cnxid, 'INSERT CWUser X: X login %(login)s, X upassword %(passwd)s, X in_group G WHERE G name "users"',
                      {'login': u"barnabé", 'passwd': u"héhéhé".encode('UTF8')})
         repo.commit(cnxid)
@@ -110,7 +97,7 @@
 
     def test_invalid_entity_rollback(self):
         repo = self.repo
-        cnxid = repo.connect(*self.default_user_password())
+        cnxid = repo.connect(self.admlogin, self.admpassword)
         # no group
         repo.execute(cnxid, 'INSERT CWUser X: X login %(login)s, X upassword %(passwd)s',
                      {'login': u"tutetute", 'passwd': 'tutetute'})
@@ -120,7 +107,7 @@
 
     def test_close(self):
         repo = self.repo
-        cnxid = repo.connect(*self.default_user_password())
+        cnxid = repo.connect(self.admlogin, self.admpassword)
         self.assert_(cnxid)
         repo.close(cnxid)
         self.assertRaises(BadConnectionId, repo.execute, cnxid, 'Any X')
@@ -131,9 +118,9 @@
 
     def test_shared_data(self):
         repo = self.repo
-        cnxid = repo.connect(*self.default_user_password())
+        cnxid = repo.connect(self.admlogin, self.admpassword)
         repo.set_shared_data(cnxid, 'data', 4)
-        cnxid2 = repo.connect(*self.default_user_password())
+        cnxid2 = repo.connect(self.admlogin, self.admpassword)
         self.assertEquals(repo.get_shared_data(cnxid, 'data'), 4)
         self.assertEquals(repo.get_shared_data(cnxid2, 'data'), None)
         repo.set_shared_data(cnxid2, 'data', 5)
@@ -151,14 +138,14 @@
 
     def test_check_session(self):
         repo = self.repo
-        cnxid = repo.connect(*self.default_user_password())
+        cnxid = repo.connect(self.admlogin, self.admpassword)
         self.assertEquals(repo.check_session(cnxid), None)
         repo.close(cnxid)
         self.assertRaises(BadConnectionId, repo.check_session, cnxid)
 
     def test_transaction_base(self):
         repo = self.repo
-        cnxid = repo.connect(*self.default_user_password())
+        cnxid = repo.connect(self.admlogin, self.admpassword)
         # check db state
         result = repo.execute(cnxid, 'Personne X')
         self.assertEquals(result.rowcount, 0)
@@ -177,7 +164,7 @@
 
     def test_transaction_base2(self):
         repo = self.repo
-        cnxid = repo.connect(*self.default_user_password())
+        cnxid = repo.connect(self.admlogin, self.admpassword)
         # rollback relation insertion
         repo.execute(cnxid, "SET U in_group G WHERE U login 'admin', G name 'guests'")
         result = repo.execute(cnxid, "Any U WHERE U in_group G, U login 'admin', G name 'guests'")
@@ -188,7 +175,7 @@
 
     def test_transaction_base3(self):
         repo = self.repo
-        cnxid = repo.connect(*self.default_user_password())
+        cnxid = repo.connect(self.admlogin, self.admpassword)
         # rollback state change which trigger TrInfo insertion
         user = repo._get_session(cnxid).user
         user.fire_transition('deactivate')
@@ -203,7 +190,7 @@
 
     def test_close_wait_processing_request(self):
         repo = self.repo
-        cnxid = repo.connect(*self.default_user_password())
+        cnxid = repo.connect(self.admlogin, self.admpassword)
         repo.execute(cnxid, 'INSERT CWUser X: X login "toto", X upassword "tutu", X in_group G WHERE G name "users"')
         repo.commit(cnxid)
         # close has to be in the thread due to sqlite limitations
@@ -287,7 +274,7 @@
 
     def test_internal_api(self):
         repo = self.repo
-        cnxid = repo.connect(*self.default_user_password())
+        cnxid = repo.connect(self.admlogin, self.admpassword)
         session = repo._get_session(cnxid, setpool=True)
         self.assertEquals(repo.type_and_source_from_eid(1, session),
                           ('CWGroup', 'system', None))
@@ -305,7 +292,7 @@
 
     def test_session_api(self):
         repo = self.repo
-        cnxid = repo.connect(*self.default_user_password())
+        cnxid = repo.connect(self.admlogin, self.admpassword)
         self.assertEquals(repo.user_info(cnxid), (5, 'admin', set([u'managers']), {}))
         self.assertEquals(repo.describe(cnxid, 1), (u'CWGroup', u'system', None))
         repo.close(cnxid)
@@ -314,7 +301,7 @@
 
     def test_shared_data_api(self):
         repo = self.repo
-        cnxid = repo.connect(*self.default_user_password())
+        cnxid = repo.connect(self.admlogin, self.admpassword)
         self.assertEquals(repo.get_shared_data(cnxid, 'data'), None)
         repo.set_shared_data(cnxid, 'data', 4)
         self.assertEquals(repo.get_shared_data(cnxid, 'data'), 4)
@@ -356,37 +343,34 @@
         self.assertEquals(rset.rows[0][0], p2.eid)
 
 
-class DataHelpersTC(RepositoryBasedTC):
-
-    def setUp(self):
-        """ called before each test from this class """
-        cnxid = self.repo.connect(*self.default_user_password())
-        self.session = self.repo._sessions[cnxid]
-        self.session.set_pool()
-
-    def tearDown(self):
-        self.session.rollback()
+class DataHelpersTC(CubicWebTC):
 
     def test_create_eid(self):
+        self.session.set_pool()
         self.assert_(self.repo.system_source.create_eid(self.session))
 
     def test_source_from_eid(self):
+        self.session.set_pool()
         self.assertEquals(self.repo.source_from_eid(1, self.session),
                           self.repo.sources_by_uri['system'])
 
     def test_source_from_eid_raise(self):
+        self.session.set_pool()
         self.assertRaises(UnknownEid, self.repo.source_from_eid, -2, self.session)
 
     def test_type_from_eid(self):
+        self.session.set_pool()
         self.assertEquals(self.repo.type_from_eid(1, self.session), 'CWGroup')
 
     def test_type_from_eid_raise(self):
+        self.session.set_pool()
         self.assertRaises(UnknownEid, self.repo.type_from_eid, -2, self.session)
 
     def test_add_delete_info(self):
         entity = self.repo.vreg['etypes'].etype_class('Personne')(self.session)
         entity.eid = -1
         entity.complete = lambda x: None
+        self.session.set_pool()
         self.repo.add_info(self.session, entity, self.repo.sources_by_uri['system'])
         cu = self.session.system_sql('SELECT * FROM entities WHERE eid = -1')
         data = cu.fetchall()
@@ -401,13 +385,14 @@
         self.assertEquals(data, [])
 
 
-class FTITC(RepositoryBasedTC):
+class FTITC(CubicWebTC):
 
     def test_reindex_and_modified_since(self):
         eidp = self.execute('INSERT Personne X: X nom "toto", X prenom "tutu"')[0][0]
         self.commit()
         ts = datetime.now()
         self.assertEquals(len(self.execute('Personne X WHERE X has_text "tutu"')), 1)
+        self.session.set_pool()
         cu = self.session.system_sql('SELECT mtime, eid FROM entities WHERE eid = %s' % eidp)
         omtime = cu.fetchone()[0]
         # our sqlite datetime adapter is ignore seconds fraction, so we have to
@@ -416,6 +401,7 @@
         self.execute('SET X nom "tata" WHERE X eid %(x)s', {'x': eidp}, 'x')
         self.commit()
         self.assertEquals(len(self.execute('Personne X WHERE X has_text "tutu"')), 1)
+        self.session.set_pool()
         cu = self.session.system_sql('SELECT mtime FROM entities WHERE eid = %s' % eidp)
         mtime = cu.fetchone()[0]
         self.failUnless(omtime < mtime)
@@ -453,7 +439,7 @@
         self.assertEquals(rset.rows, [[self.session.user.eid]])
 
 
-class DBInitTC(RepositoryBasedTC):
+class DBInitTC(CubicWebTC):
 
     def test_versions_inserted(self):
         inserted = [r[0] for r in self.execute('Any K ORDERBY K WHERE P pkey K, P pkey ~= "system.version.%"')]
@@ -463,70 +449,42 @@
                            u'system.version.file', u'system.version.folder',
                            u'system.version.tag'])
 
-class InlineRelHooksTC(RepositoryBasedTC):
+CALLED = []
+class EcritParHook(hook.Hook):
+    __regid__ = 'inlinedrelhook'
+    __select__ = hook.Hook.__select__ & hook.match_rtype('ecrit_par')
+    events = ('before_add_relation', 'after_add_relation',
+              'before_delete_relation', 'after_delete_relation')
+    def __call__(self):
+        CALLED.append((self.event, self.eidfrom, self.rtype, self.eidto))
+
+class InlineRelHooksTC(CubicWebTC):
     """test relation hooks are called for inlined relations
     """
     def setUp(self):
-        RepositoryBasedTC.setUp(self)
+        CubicWebTC.setUp(self)
         self.hm = self.repo.hm
-        self.called = []
-
-    def _before_relation_hook(self, pool, fromeid, rtype, toeid):
-        self.called.append((fromeid, rtype, toeid))
+        CALLED[:] = ()
 
     def _after_relation_hook(self, pool, fromeid, rtype, toeid):
         self.called.append((fromeid, rtype, toeid))
 
-    def test_before_add_inline_relation(self):
-        """make sure before_<event>_relation hooks are called directly"""
-        self.hm.register_hook(self._before_relation_hook,
-                             'before_add_relation', 'ecrit_par')
+    def test_inline_relation(self):
+        """make sure <event>_relation hooks are called for inlined relation"""
+        self.hm.register(EcritParHook)
         eidp = self.execute('INSERT Personne X: X nom "toto"')[0][0]
         eidn = self.execute('INSERT Note X: X type "T"')[0][0]
         self.execute('SET N ecrit_par Y WHERE N type "T", Y nom "toto"')
-        self.assertEquals(self.called, [(eidn, 'ecrit_par', eidp)])
-
-    def test_after_add_inline_relation(self):
-        """make sure after_<event>_relation hooks are deferred"""
-        self.hm.register_hook(self._after_relation_hook,
-                             'after_add_relation', 'ecrit_par')
-        eidp = self.execute('INSERT Personne X: X nom "toto"')[0][0]
-        eidn = self.execute('INSERT Note X: X type "T"')[0][0]
-        self.assertEquals(self.called, [])
-        self.execute('SET N ecrit_par Y WHERE N type "T", Y nom "toto"')
-        self.assertEquals(self.called, [(eidn, 'ecrit_par', eidp,)])
-
-    def test_after_add_inline(self):
-        """make sure after_<event>_relation hooks are deferred"""
-        p1 = self.add_entity('Personne', nom=u'toto')
-        self.hm.register_hook(self._after_relation_hook,
-                             'after_add_relation', 'ecrit_par')
+        self.assertEquals(CALLED, [('before_add_relation', eidn, 'ecrit_par', eidp),
+                                   ('after_add_relation', eidn, 'ecrit_par', eidp)])
+        CALLED[:] = ()
+        self.execute('DELETE N ecrit_par Y WHERE N type "T", Y nom "toto"')
+        self.assertEquals(CALLED, [('before_delete_relation', eidn, 'ecrit_par', eidp),
+                                   ('after_delete_relation', eidn, 'ecrit_par', eidp)])
+        CALLED[:] = ()
         eidn = self.execute('INSERT Note N: N ecrit_par P WHERE P nom "toto"')[0][0]
-        self.assertEquals(self.called, [(eidn, 'ecrit_par', p1.eid,)])
-
-    def test_before_delete_inline_relation(self):
-        """make sure before_<event>_relation hooks are called directly"""
-        self.hm.register_hook(self._before_relation_hook,
-                             'before_delete_relation', 'ecrit_par')
-        eidp = self.execute('INSERT Personne X: X nom "toto"')[0][0]
-        eidn = self.execute('INSERT Note X: X type "T"')[0][0]
-        self.execute('SET N ecrit_par Y WHERE N type "T", Y nom "toto"')
-        self.execute('DELETE N ecrit_par Y WHERE N type "T", Y nom "toto"')
-        self.assertEquals(self.called, [(eidn, 'ecrit_par', eidp)])
-        rset = self.execute('Any Y where N ecrit_par Y, N type "T", Y nom "toto"')
-        # make sure the relation is really deleted
-        self.failUnless(len(rset) == 0, "failed to delete inline relation")
-
-    def test_after_delete_inline_relation(self):
-        """make sure after_<event>_relation hooks are deferred"""
-        self.hm.register_hook(self._after_relation_hook,
-                             'after_delete_relation', 'ecrit_par')
-        eidp = self.execute('INSERT Personne X: X nom "toto"')[0][0]
-        eidn = self.execute('INSERT Note X: X type "T"')[0][0]
-        self.execute('SET N ecrit_par Y WHERE N type "T", Y nom "toto"')
-        self.assertEquals(self.called, [])
-        self.execute('DELETE N ecrit_par Y WHERE N type "T", Y nom "toto"')
-        self.assertEquals(self.called, [(eidn, 'ecrit_par', eidp,)])
+        self.assertEquals(CALLED, [('before_add_relation', eidn, 'ecrit_par', eidp),
+                                   ('after_add_relation', eidn, 'ecrit_par', eidp)])
 
 
 if __name__ == '__main__':
--- a/server/test/unittest_rql2sql.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/test/unittest_rql2sql.py	Wed Sep 30 18:57:42 2009 +0200
@@ -34,6 +34,7 @@
 config.bootstrap_cubes()
 schema = config.load_schema()
 schema['in_state'].inlined = True
+schema['state_of'].inlined = False
 schema['comments'].inlined = False
 
 PARSER = [
--- a/server/test/unittest_rqlannotation.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/test/unittest_rqlannotation.py	Wed Sep 30 18:57:42 2009 +0200
@@ -6,7 +6,7 @@
 from cubicweb.devtools import init_test_database
 from cubicweb.devtools.repotest import BaseQuerierTC
 
-repo, cnx = init_test_database('sqlite')
+repo, cnx = init_test_database()
 
 class SQLGenAnnotatorTC(BaseQuerierTC):
     repo = repo
--- a/server/test/unittest_security.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/test/unittest_security.py	Wed Sep 30 18:57:42 2009 +0200
@@ -4,21 +4,21 @@
 import sys
 
 from logilab.common.testlib import unittest_main, TestCase
-from cubicweb.devtools.apptest import RepositoryBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 
 from cubicweb import Unauthorized, ValidationError
 from cubicweb.server.querier import check_read_access
 
-class BaseSecurityTC(RepositoryBasedTC):
+class BaseSecurityTC(CubicWebTC):
 
     def setUp(self):
-        RepositoryBasedTC.setUp(self)
+        CubicWebTC.setUp(self)
         self.create_user('iaminusersgrouponly')
         self.readoriggroups = self.schema['Personne'].get_groups('read')
         self.addoriggroups = self.schema['Personne'].get_groups('add')
 
     def tearDown(self):
-        RepositoryBasedTC.tearDown(self)
+        CubicWebTC.tearDown(self)
         self.schema['Personne'].set_groups('read', self.readoriggroups)
         self.schema['Personne'].set_groups('add', self.addoriggroups)
 
@@ -37,7 +37,7 @@
         cu = cnx.cursor()
         self.assertRaises(Unauthorized,
                           check_read_access,
-                          self.schema, cnx.user(self.current_session()), rqlst, solution)
+                          self.schema, cnx.user(self.session), rqlst, solution)
         self.assertRaises(Unauthorized, cu.execute, rql)
 
     def test_upassword_not_selectable(self):
@@ -165,7 +165,7 @@
 
     def test_insert_relation_rql_permission(self):
         cnx = self.login('iaminusersgrouponly')
-        session = self.current_session()
+        session = self.session
         cu = cnx.cursor(session)
         cu.execute("SET A concerne S WHERE A is Affaire, S is Societe")
         # should raise Unauthorized since user don't own S
@@ -210,7 +210,7 @@
 
 
     def test_user_can_change_its_upassword(self):
-        ueid = self.create_user('user')
+        ueid = self.create_user('user').eid
         cnx = self.login('user')
         cu = cnx.cursor()
         cu.execute('SET X upassword %(passwd)s WHERE X eid %(x)s',
@@ -220,7 +220,7 @@
         cnx = self.login('user', 'newpwd')
 
     def test_user_cant_change_other_upassword(self):
-        ueid = self.create_user('otheruser')
+        ueid = self.create_user('otheruser').eid
         cnx = self.login('iaminusersgrouponly')
         cu = cnx.cursor()
         cu.execute('SET X upassword %(passwd)s WHERE X eid %(x)s',
@@ -415,7 +415,7 @@
 
     def test_users_and_groups_non_readable_by_guests(self):
         cnx = self.login('anon')
-        anon = cnx.user(self.current_session())
+        anon = cnx.user(self.session)
         cu = cnx.cursor()
         # anonymous user can only read itself
         rset = cu.execute('Any L WHERE X owned_by U, U login L')
@@ -425,7 +425,7 @@
         # 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
-        origuser = self.session.user
+        origuser = self.adminsession.user
         self.assertRaises(Unauthorized,
                           cu.execute, 'CWUser X WHERE X eid %(x)s', {'x': origuser.eid}, 'x')
         # nothing selected, nothing updated, no exception raised
@@ -461,7 +461,7 @@
         self.commit()
         cnx = self.login('anon')
         cu = cnx.cursor()
-        anoneid = self.current_session().user.eid
+        anoneid = self.session.user.eid
         self.assertEquals(cu.execute('Any T,P ORDERBY lower(T) WHERE B is Bookmark,B title T,B path P,'
                                      'B bookmarked_by U, U eid %s' % anoneid).rows,
                           [['index', '?vid=index']])
@@ -490,7 +490,7 @@
         eid = self.execute('INSERT Affaire X: X ref "ARCT01"')[0][0]
         self.commit()
         cnx = self.login('iaminusersgrouponly')
-        session = self.current_session()
+        session = self.session
         # needed to avoid check_perm error
         session.set_pool()
         # needed to remove rql expr granting update perm to the user
@@ -504,7 +504,7 @@
             aff.fire_transition('abort')
             cnx.commit()
             # though changing a user state (even logged user) is reserved to managers
-            user = cnx.user(self.current_session())
+            user = cnx.user(self.session)
             # XXX wether it should raise Unauthorized or ValidationError is not clear
             # the best would probably ValidationError if the transition doesn't exist
             # from the current state but Unauthorized if it exists but user can't pass it
--- a/server/test/unittest_ssplanner.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/server/test/unittest_ssplanner.py	Wed Sep 30 18:57:42 2009 +0200
@@ -10,7 +10,7 @@
 from cubicweb.server.ssplanner import SSPlanner
 
 # keep cnx so it's not garbage collected and the associated session closed
-repo, cnx = init_test_database('sqlite')
+repo, cnx = init_test_database()
 
 class SSPlannerTC(BasePlannerTC):
     repo = repo
--- a/sobjects/email.py	Wed Sep 30 17:57:02 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-"""hooks to ensure use_email / primary_email relations consistency
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from cubicweb.server.hooksmanager import Hook
-from cubicweb.server.pool import PreCommitOperation
-from cubicweb.server.repository import ensure_card_respected
-
-class SetUseEmailRelationOp(PreCommitOperation):
-    """delay this operation to commit to avoid conflict with a late rql query
-    already setting the relation
-    """
-    rtype = 'use_email'
-    fromeid = toeid = None # make pylint happy
-
-    def condition(self):
-        """check entity has use_email set for the email address"""
-        return not self.session.unsafe_execute(
-            'Any X WHERE X eid %(x)s, X use_email Y, Y eid %(y)s',
-            {'x': self.fromeid, 'y': self.toeid}, 'x')
-
-    def precommit_event(self):
-        session = self.session
-        if self.condition():
-            # we've to handle cardinaly by ourselves since we're using unsafe_execute
-            # but use session.execute and not session.unsafe_execute to check we
-            # can change the relation
-            ensure_card_respected(session.execute, session,
-                                  self.fromeid, self.rtype, self.toeid)
-            session.unsafe_execute(
-                'SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % self.rtype,
-                {'x': self.fromeid, 'y': self.toeid}, 'x')
-
-class SetPrimaryEmailRelationOp(SetUseEmailRelationOp):
-    rtype = 'primary_email'
-
-    def condition(self):
-        """check entity has no primary_email set"""
-        return not self.session.unsafe_execute(
-            'Any X WHERE X eid %(x)s, X primary_email Y',
-            {'x': self.fromeid}, 'x')
-
-
-class SetPrimaryEmailHook(Hook):
-    """notify when a bug or story or version has its state modified"""
-    events = ('after_add_relation',)
-    accepts = ('use_email',)
-
-    def call(self, session, fromeid, rtype, toeid):
-        subjtype = session.describe(fromeid)[0]
-        eschema = self.vreg.schema[subjtype]
-        if 'primary_email' in eschema.subject_relations():
-            SetPrimaryEmailRelationOp(session, vreg=self.vreg,
-                                      fromeid=fromeid, toeid=toeid)
-
-class SetUseEmailHook(Hook):
-    """notify when a bug or story or version has its state modified"""
-    events = ('after_add_relation',)
-    accepts = ('primary_email',)
-
-    def call(self, session, fromeid, rtype, toeid):
-        subjtype = session.describe(fromeid)[0]
-        eschema = self.vreg.schema[subjtype]
-        if 'use_email' in eschema.subject_relations():
-            SetUseEmailRelationOp(session, vreg=self.vreg,
-                                  fromeid=fromeid, toeid=toeid)
--- a/sobjects/hooks.py	Wed Sep 30 17:57:02 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +0,0 @@
-"""various library content hooks
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from datetime import datetime
-
-from cubicweb import RepositoryError
-from cubicweb.common.uilib import soup2xhtml
-from cubicweb.server.hooksmanager import Hook
-from cubicweb.server.pool import PreCommitOperation
-
-
-class SetModificationDateOnStateChange(Hook):
-    """update entity's modification date after changing its state"""
-    events = ('after_add_relation',)
-    accepts = ('in_state',)
-
-    def call(self, session, fromeid, rtype, toeid):
-        if fromeid in session.transaction_data.get('neweids', ()):
-            # new entity, not needed
-            return
-        entity = session.entity_from_eid(fromeid)
-        try:
-            entity.set_attributes(modification_date=datetime.now(),
-                                  _cw_unsafe=True)
-        except RepositoryError, ex:
-            # usually occurs if entity is coming from a read-only source
-            # (eg ldap user)
-            self.warning('cant change modification date for %s: %s', entity, ex)
-
-
-class AddUpdateCWUserHook(Hook):
-    """ensure user logins are stripped"""
-    events = ('before_add_entity', 'before_update_entity',)
-    accepts = ('CWUser',)
-
-    def call(self, session, entity):
-        if 'login' in entity and entity['login']:
-            entity['login'] = entity['login'].strip()
-
-
-class AutoDeleteBookmark(PreCommitOperation):
-    beid = None # make pylint happy
-    def precommit_event(self):
-        session = self.session
-        if not self.beid in session.transaction_data.get('pendingeids', ()):
-            if not session.unsafe_execute('Any X WHERE X bookmarked_by U, X eid %(x)s',
-                                          {'x': self.beid}, 'x'):
-                session.unsafe_execute('DELETE Bookmark X WHERE X eid %(x)s',
-                                       {'x': self.beid}, 'x')
-
-class DelBookmarkedByHook(Hook):
-    """ensure user logins are stripped"""
-    events = ('after_delete_relation',)
-    accepts = ('bookmarked_by',)
-
-    def call(self, session, subj, rtype, obj):
-        AutoDeleteBookmark(session, beid=subj)
-
-
-class TidyHtmlFields(Hook):
-    """tidy HTML in rich text strings"""
-    events = ('before_add_entity', 'before_update_entity')
-    accepts = ('Any',)
-
-    def call(self, session, entity):
-        metaattrs = entity.e_schema.meta_attributes()
-        for metaattr, (metadata, attr) in metaattrs.iteritems():
-            if metadata == 'format':
-                try:
-                    value = entity[attr]
-                except KeyError:
-                    continue # no text to tidy
-                if isinstance(value, unicode): # filter out None and Binary
-                    if self.event == 'before_add_entity':
-                        fmt = entity.get(metaattr)
-                    else:
-                        fmt = entity.get_value(metaattr)
-                    if fmt == 'text/html':
-                        entity[attr] = soup2xhtml(value, session.encoding)
--- a/sobjects/notification.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/sobjects/notification.py	Wed Sep 30 18:57:42 2009 +0200
@@ -1,4 +1,4 @@
-"""some hooks and views to handle notification on entity's changes
+"""some views to handle notification on data changes
 
 :organization: Logilab
 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
@@ -13,13 +13,11 @@
 from logilab.common.textutils import normalize_text
 from logilab.common.deprecation import class_renamed, deprecated
 
-from cubicweb import RegistryException
-from cubicweb.selectors import implements, yes
+from cubicweb.selectors import yes
 from cubicweb.view import Component
-from cubicweb.common.mail import NotificationView, parse_message_id
-from cubicweb.server.pool import PreCommitOperation, SingleLastOperation
-from cubicweb.server.hookhelper import SendMailOp
-from cubicweb.server.hooksmanager import Hook
+from cubicweb.common.mail import format_mail
+from cubicweb.common.mail import NotificationView
+from cubicweb.server.hook import SendMailOp
 
 parse_message_id = deprecated('parse_message_id is now defined in cubicweb.common.mail')(parse_message_id)
 
@@ -30,155 +28,40 @@
     by default user's with their email set are notified if any, else the default
     email addresses specified in the configuration are used
     """
-    id = 'recipients_finder'
+    __regid__ = 'recipients_finder'
     __select__ = yes()
     user_rql = ('Any X,E,A WHERE X is CWUser, X in_state S, S name "activated",'
                 'X primary_email E, E address A')
 
     def recipients(self):
-        mode = self.config['default-recipients-mode']
+        mode = self._cw.vreg.config['default-recipients-mode']
         if mode == 'users':
             # use unsafe execute else we may don't have the right to see users
             # to notify...
-            execute = self.req.unsafe_execute
+            execute = self._cw.unsafe_execute
             dests = [(u.get_email(), u.property_value('ui.language'))
                      for u in execute(self.user_rql, build_descr=True, propagate=True).entities()]
         elif mode == 'default-dest-addrs':
-            lang = self.vreg.property_value('ui.language')
-            dests = zip(self.config['default-dest-addrs'], repeat(lang))
+            lang = self._cw.vreg.property_value('ui.language')
+            dests = zip(self._cw.vreg.config['default-dest-addrs'], repeat(lang))
         else: # mode == 'none'
             dests = []
         return dests
 
 
-# hooks #######################################################################
-
-class EntityUpdatedNotificationOp(SingleLastOperation):
-
-    def precommit_event(self):
-        session = self.session
-        for eid in session.transaction_data['changes']:
-            view = session.vreg['views'].select('notif_entity_updated', session,
-                                                rset=session.eid_rset(eid),
-                                                row=0)
-            RenderAndSendNotificationView(session, view=view)
-
-    def commit_event(self):
-        pass
-
-
-class RenderAndSendNotificationView(PreCommitOperation):
-    """delay rendering of notification view until precommit"""
-    def precommit_event(self):
-        view = self.view
-        if view.rset is not None and not view.rset:
-            return # entity added and deleted in the same transaction (cache effect)
-        if view.rset and view.rset[0][0] in self.session.transaction_data.get('pendingeids', ()):
-            return # entity added and deleted in the same transaction
-        self.view.render_and_send(**getattr(self, 'viewargs', {}))
-
-
-class StatusChangeHook(Hook):
-    """notify when a workflowable entity has its state modified"""
-    events = ('after_add_entity',)
-    accepts = ('TrInfo',)
-
-    def call(self, session, entity):
-        if not entity.from_state: # not a transition
-            return
-        rset = entity.related('wf_info_for')
-        try:
-            view = session.vreg['views'].select('notif_status_change', session,
-                                                rset=rset, row=0)
-        except RegistryException:
-            return
-        comment = entity.printable_value('comment', format='text/plain')
-        # XXX don't try to wrap rest until we've a proper transformation (see
-        # #103822)
-        if comment and entity.comment_format != 'text/rest':
-            comment = normalize_text(comment, 80)
-        RenderAndSendNotificationView(session, view=view, viewargs={
-            'comment': comment, 'previous_state': entity.previous_state.name,
-            'current_state': entity.new_state.name})
-
-
-class RelationChangeHook(Hook):
-    events = ('before_add_relation', 'after_add_relation',
-              'before_delete_relation', 'after_delete_relation')
-    accepts = ('Any',)
-    def call(self, session, fromeid, rtype, toeid):
-        """if a notification view is defined for the event, send notification
-        email defined by the view
-        """
-        rset = session.eid_rset(fromeid)
-        vid = 'notif_%s_%s' % (self.event,  rtype)
-        try:
-            view = session.vreg['views'].select(vid, session, rset=rset, row=0)
-        except RegistryException:
-            return
-        RenderAndSendNotificationView(session, view=view)
-
-
-class EntityChangeHook(Hook):
-    events = ('after_add_entity',
-              'after_update_entity')
-    accepts = ('Any',)
-    def call(self, session, entity):
-        """if a notification view is defined for the event, send notification
-        email defined by the view
-        """
-        rset = entity.as_rset()
-        vid = 'notif_%s' % self.event
-        try:
-            view = session.vreg['views'].select(vid, session, rset=rset, row=0)
-        except RegistryException:
-            return
-        RenderAndSendNotificationView(session, view=view)
-
-class EntityUpdateHook(Hook):
-    events = ('before_update_entity',)
-    accepts = ()
-    skip_attrs = set()
-
-    def call(self, session, entity):
-        if entity.eid in session.transaction_data.get('neweids', ()):
-            return # entity is being created
-        if session.is_super_session:
-            return # ignore changes triggered by hooks
-        # then compute changes
-        changes = session.transaction_data.setdefault('changes', {})
-        thisentitychanges = changes.setdefault(entity.eid, set())
-        attrs = [k for k in entity.edited_attributes if not k in self.skip_attrs]
-        if not attrs:
-            return
-        rqlsel, rqlrestr = [], ['X eid %(x)s']
-        for i, attr in enumerate(attrs):
-            var = chr(65+i)
-            rqlsel.append(var)
-            rqlrestr.append('X %s %s' % (attr, var))
-        rql = 'Any %s WHERE %s' % (','.join(rqlsel), ','.join(rqlrestr))
-        rset = session.execute(rql, {'x': entity.eid}, 'x')
-        for i, attr in enumerate(attrs):
-            oldvalue = rset[0][i]
-            newvalue = entity[attr]
-            if oldvalue != newvalue:
-                thisentitychanges.add((attr, oldvalue, newvalue))
-        if thisentitychanges:
-            EntityUpdatedNotificationOp(session)
-
 # abstract or deactivated notification views and mixin ########################
 
 class NotificationView(NotificationView):
     """overriden to delay actual sending of mails to a commit operation by
     default
     """
-
     def send_on_commit(self, recipients, msg):
-        SendMailOp(self.req, recipients=recipients, msg=msg)
+        SendMailOp(self._cw, recipients=recipients, msg=msg)
     send = send_on_commit
 
+
 class StatusChangeMixIn(object):
-    id = 'notif_status_change'
+    __regid__ = 'notif_status_change'
     msgid_timestamp = True
     message = _('status changed')
     content = _("""
@@ -208,7 +91,7 @@
       override call)
     """
     __abstract__ = True
-    id = 'notif_after_add_entity'
+    __regid__ = 'notif_after_add_entity'
     msgid_timestamp = False
     message = _('new')
     content = """
@@ -220,7 +103,7 @@
 """
 
     def context(self, **kwargs):
-        entity = self.entity(self.row or 0, self.col or 0)
+        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
         content = entity.printable_value(self.content_attr, format='text/plain')
         if content:
             contentformat = getattr(entity, self.content_attr + '_format',
@@ -232,17 +115,17 @@
         return super(ContentAddedView, self).context(content=content, **kwargs)
 
     def subject(self):
-        entity = self.entity(self.row or 0, self.col or 0)
-        return  u'%s #%s (%s)' % (self.req.__('New %s' % entity.e_schema),
+        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+        return  u'%s #%s (%s)' % (self._cw.__('New %s' % entity.e_schema),
                                   entity.eid, self.user_data['login'])
 
-NormalizedTextView = class_renamed('NormalizedTextView', ContentAddedView)
 
 def format_value(value):
     if isinstance(value, unicode):
         return u'"%s"' % value
     return value
 
+
 class EntityUpdatedNotificationView(NotificationView):
     """abstract class for notification on entity/relation
 
@@ -299,3 +182,13 @@
         entity = self.entity(self.row or 0, self.col or 0)
         return  u'%s #%s (%s)' % (self.req.__('Updated %s' % entity.e_schema),
                                   entity.eid, self.user_data['login'])
+
+
+from logilab.common.deprecation import class_renamed, class_moved, deprecated
+from cubicweb.hooks.notification import RenderAndSendNotificationView
+from cubicweb.common.mail import parse_message_id
+
+NormalizedTextView = class_renamed('NormalizedTextView', ContentAddedView)
+RenderAndSendNotificationView = class_moved(RenderAndSendNotificationView)
+parse_message_id = deprecated('parse_message_id is now defined in cubicweb.common.mail')(parse_message_id)
+
--- a/sobjects/supervising.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/sobjects/supervising.py	Wed Sep 30 18:57:42 2009 +0200
@@ -13,58 +13,7 @@
 from cubicweb.schema import display_name
 from cubicweb.view import Component
 from cubicweb.common.mail import format_mail
-from cubicweb.server.hooksmanager import Hook
-from cubicweb.server.hookhelper import SendMailOp
-
-
-class SomethingChangedHook(Hook):
-    events = ('before_add_relation', 'before_delete_relation',
-              'after_add_entity', 'before_update_entity')
-    accepts = ('Any',)
-
-    def call(self, session, *args):
-        if session.is_super_session or session.repo.config.repairing:
-            return # ignore changes triggered by hooks or maintainance shell
-        dest = self.config['supervising-addrs']
-        if not dest: # no supervisors, don't do this for nothing...
-            return
-        self.session = session
-        if self._call(*args):
-            SupervisionMailOp(session)
-
-    def _call(self, *args):
-        if self._event() == 'update_entity':
-            if args[0].eid in self.session.transaction_data.get('neweids', ()):
-                return False
-            if args[0].e_schema == 'CWUser':
-                updated = set(args[0].iterkeys())
-                if not (updated - frozenset(('eid', 'modification_date',
-                                             'last_login_time'))):
-                    # don't record last_login_time update which are done
-                    # automatically at login time
-                    return False
-        self.session.transaction_data.setdefault('pendingchanges', []).append(
-            (self._event(), args))
-        return True
-
-    def _event(self):
-        return self.event.split('_', 1)[1]
-
-
-class EntityDeleteHook(SomethingChangedHook):
-    events = ('before_delete_entity',)
-
-    def _call(self, eid):
-        entity = self.session.entity_from_eid(eid)
-        try:
-            title = entity.dc_title()
-        except:
-            # may raise an error during deletion process, for instance due to
-            # missing required relation
-            title = '#%s' % eid
-        self.session.transaction_data.setdefault('pendingchanges', []).append(
-            ('delete_entity', (eid, str(entity.e_schema), title)))
-        return True
+from cubicweb.server.hook import SendMailOp
 
 
 def filter_changes(changes):
@@ -81,7 +30,7 @@
     for change in changes[:]:
         event, changedescr = change
         if event == 'add_entity':
-            entity = changedescr[0]
+            entity = changedescr.entity
             added.add(entity.eid)
             if entity.e_schema == 'TrInfo':
                 changes.remove(change)
@@ -95,7 +44,7 @@
         index.setdefault(event, set()).add(change)
     for key in ('delete_relation', 'add_relation'):
         for change in index.get(key, {}).copy():
-            if change[1][1] == 'in_state':
+            if change[1].rtype == 'in_state':
                 index[key].remove(change)
     # filter changes
     for eid in added:
@@ -104,26 +53,24 @@
                 changedescr = change[1]
                 # skip meta-relations which are set automatically
                 # XXX generate list below using rtags (category = 'generated')
-                if changedescr[1] in ('created_by', 'owned_by', 'is', 'is_instance_of',
+                if changedescr.rtype in ('created_by', 'owned_by', 'is', 'is_instance_of',
                                       'from_state', 'to_state', 'by_transition',
                                       'wf_info_for') \
-                       and changedescr[0] == eid:
+                       and changedescr.eidfrom == eid:
                     index['add_relation'].remove(change)
-
         except KeyError:
             break
     for eid in deleted:
         try:
             for change in index['delete_relation'].copy():
-                fromeid, rtype, toeid = change[1]
-                if fromeid == eid:
+                if change[1].eidfrom == eid:
                     index['delete_relation'].remove(change)
-                elif toeid == eid:
+                elif change[1].eidto == eid:
                     index['delete_relation'].remove(change)
-                    if rtype == 'wf_info_for':
-                        for change in index['delete_entity'].copy():
-                            if change[1][0] == fromeid:
-                                index['delete_entity'].remove(change)
+                    if change[1].rtype == 'wf_info_for':
+                        for change_ in index['delete_entity'].copy():
+                            if change_[1].eidfrom == change[1].eidfrom:
+                                index['delete_entity'].remove(change_)
         except KeyError:
             break
     for change in changes:
@@ -135,22 +82,22 @@
 class SupervisionEmailView(Component):
     """view implementing the email API for data changes supervision notification
     """
+    __regid__ = 'supervision_notif'
     __select__ = none_rset()
-    id = 'supervision_notif'
 
     def recipients(self):
-        return self.config['supervising-addrs']
+        return self._cw.vreg.config['supervising-addrs']
 
     def subject(self):
-        return self.req._('[%s supervision] changes summary') % self.config.appid
+        return self._cw._('[%s supervision] changes summary') % self._cw.vreg.config.appid
 
     def call(self, changes):
-        user = self.req.actual_session().user
-        self.w(self.req._('user %s has made the following change(s):\n\n')
+        user = self._cw.actual_session().user
+        self.w(self._cw._('user %s has made the following change(s):\n\n')
                % user.login)
         for event, changedescr in filter_changes(changes):
             self.w(u'* ')
-            getattr(self, event)(*changedescr)
+            getattr(self, event)(changedescr)
             self.w(u'\n\n')
 
     def _entity_context(self, entity):
@@ -158,32 +105,32 @@
                 'etype': entity.dc_type().lower(),
                 'title': entity.dc_title()}
 
-    def add_entity(self, entity):
-        msg = self.req._('added %(etype)s #%(eid)s (%(title)s)')
-        self.w(u'%s\n' % (msg % self._entity_context(entity)))
-        self.w(u'  %s' % entity.absolute_url())
+    def add_entity(self, changedescr):
+        msg = self._cw._('added %(etype)s #%(eid)s (%(title)s)')
+        self.w(u'%s\n' % (msg % self._entity_context(changedescr.entity)))
+        self.w(u'  %s' % changedescr.entity.absolute_url())
 
-    def update_entity(self, entity):
-        msg = self.req._('updated %(etype)s #%(eid)s (%(title)s)')
-        self.w(u'%s\n' % (msg % self._entity_context(entity)))
+    def update_entity(self, changedescr):
+        msg = self._cw._('updated %(etype)s #%(eid)s (%(title)s)')
+        self.w(u'%s\n' % (msg % self._entity_context(changedescr.entity)))
         # XXX print changes
-        self.w(u'  %s' % entity.absolute_url())
+        self.w(u'  %s' % changedescr.entity.absolute_url())
 
-    def delete_entity(self, eid, etype, title):
-        msg = self.req._('deleted %(etype)s #%(eid)s (%(title)s)')
-        etype = display_name(self.req, etype).lower()
+    def delete_entity(self, (eid, etype, title)):
+        msg = self._cw._('deleted %(etype)s #%(eid)s (%(title)s)')
+        etype = display_name(self._cw, etype).lower()
         self.w(msg % locals())
 
-    def change_state(self, entity, fromstate, tostate):
-        msg = self.req._('changed state of %(etype)s #%(eid)s (%(title)s)')
+    def change_state(self, (entity, fromstate, tostate)):
+        msg = self._cw._('changed state of %(etype)s #%(eid)s (%(title)s)')
         self.w(u'%s\n' % (msg % self._entity_context(entity)))
         self.w(_('  from state %(fromstate)s to state %(tostate)s\n' %
                  {'fromstate': _(fromstate.name), 'tostate': _(tostate.name)}))
         self.w(u'  %s' % entity.absolute_url())
 
-    def _relation_context(self, fromeid, rtype, toeid):
-        _ = self.req._
-        session = self.req.actual_session()
+    def _relation_context(self, changedescr):
+        _ = self._cw._
+        session = self._cw.actual_session()
         def describe(eid):
             try:
                 return _(session.describe(eid)[0]).lower()
@@ -191,19 +138,20 @@
                 # may occurs when an entity has been deleted from an external
                 # source and we're cleaning its relation
                 return _('unknown external entity')
+        eidfrom, rtype, eidto = changedescr.eidfrom, changedescr.rtype, changedescr.eidto
         return {'rtype': _(rtype),
-                'fromeid': fromeid,
-                'frometype': describe(fromeid),
-                'toeid': toeid,
-                'toetype': describe(toeid)}
+                'eidfrom': eidfrom,
+                'frometype': describe(eidfrom),
+                'eidto': eidto,
+                'toetype': describe(eidto)}
 
-    def add_relation(self, fromeid, rtype, toeid):
-        msg = self.req._('added relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%(toeid)s')
-        self.w(msg % self._relation_context(fromeid, rtype, toeid))
+    def add_relation(self, changedescr):
+        msg = self._cw._('added relation %(rtype)s from %(frometype)s #%(eidfrom)s to %(toetype)s #%(eidto)s')
+        self.w(msg % self._relation_context(changedescr))
 
-    def delete_relation(self, fromeid, rtype, toeid):
-        msg = self.req._('deleted relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%(toeid)s')
-        self.w(msg % self._relation_context(fromeid, rtype, toeid))
+    def delete_relation(self, changedescr):
+        msg = self._cw._('deleted relation %(rtype)s from %(frometype)s #%(eidfrom)s to %(toetype)s #%(eidto)s')
+        self.w(msg % self._relation_context(changedescr))
 
 
 class SupervisionMailOp(SendMailOp):
--- a/sobjects/test/unittest_email.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/sobjects/test/unittest_email.py	Wed Sep 30 18:57:42 2009 +0200
@@ -5,10 +5,11 @@
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
+
 from cubicweb import Unauthorized
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 
-class EmailAddressHooksTC(EnvBasedTC):
+class EmailAddressHooksTC(CubicWebTC):
 
     def test_use_email_set_primary_email(self):
         self.execute('INSERT EmailAddress X: X address "admin@logilab.fr", U use_email X WHERE U login "admin"')
--- a/sobjects/test/unittest_hooks.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/sobjects/test/unittest_hooks.py	Wed Sep 30 18:57:42 2009 +0200
@@ -6,9 +6,9 @@
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from logilab.common.testlib import unittest_main
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 
-class HooksTC(EnvBasedTC):
+class HooksTC(CubicWebTC):
 
     def test_euser_login_stripped(self):
         u = self.create_user('  joe  ')
--- a/sobjects/test/unittest_notification.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/sobjects/test/unittest_notification.py	Wed Sep 30 18:57:42 2009 +0200
@@ -9,7 +9,7 @@
 from socket import gethostname
 
 from logilab.common.testlib import unittest_main, TestCase
-from cubicweb.devtools.apptest import EnvBasedTC, MAILBOX
+from cubicweb.devtools.testlib import CubicWebTC, MAILBOX
 
 from cubicweb.common.mail import construct_message_id, parse_message_id
 
@@ -48,7 +48,7 @@
             self.assertNotEquals(msgid1, '<@testapp.%s>' % gethostname())
 
 
-class RecipientsFinderTC(EnvBasedTC):
+class RecipientsFinderTC(CubicWebTC):
     def test(self):
         urset = self.execute('CWUser X WHERE X login "admin"')
         self.execute('INSERT EmailAddress X: X address "admin@logilab.fr", U primary_email X '
@@ -67,12 +67,11 @@
         self.assertEquals(finder.recipients(), [('abcd@logilab.fr', 'en'), ('efgh@logilab.fr', 'en')])
 
 
-class StatusChangeViewsTC(EnvBasedTC):
+class StatusChangeViewsTC(CubicWebTC):
 
     def test_status_change_view(self):
-        req = self.session()
-        u = self.create_user('toto', req=req)#, commit=False) XXX in cw 3.6, and remove set_pool
-        req.set_pool()
+        req = self.request()
+        u = self.create_user('toto', req=req)
         u.fire_transition('deactivate', comment=u'yeah')
         self.failIf(MAILBOX)
         self.commit()
--- a/sobjects/test/unittest_supervising.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/sobjects/test/unittest_supervising.py	Wed Sep 30 18:57:42 2009 +0200
@@ -9,12 +9,12 @@
 import re
 
 from logilab.common.testlib import unittest_main
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 
 from cubicweb.sobjects.supervising import SendMailOp, SupervisionMailOp
 
 
-class SupervisingTC(EnvBasedTC):
+class SupervisingTC(CubicWebTC):
 
     def setup_database(self):
         self.add_entity('Card', title=u"une news !", content=u"cubicweb c'est beau")
@@ -26,7 +26,7 @@
 
 
     def test_supervision(self):
-        session = self.session()
+        session = self.session
         # do some modification
         user = self.execute('INSERT CWUser X: X login "toto", X upassword "sosafe", X in_group G '
                             'WHERE G name "users"').get_entity(0, 0)
@@ -88,7 +88,7 @@
                               data)
 
     def test_nonregr1(self):
-        session = self.session()
+        session = self.session
         # do some unlogged modification
         self.execute('SET X last_login_time NOW WHERE X eid %(x)s', {'x': session.user.eid}, 'x')
         self.commit() # no crash
--- a/test/data/entities.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/test/data/entities.py	Wed Sep 30 18:57:42 2009 +0200
@@ -8,15 +8,15 @@
 from cubicweb.entities import AnyEntity, fetch_config
 
 class Societe(AnyEntity):
-    id = 'Societe'
+    __regid__ = 'Societe'
     fetch_attrs = ('nom',)
 
 class Personne(Societe):
     """customized class forne Person entities"""
-    id = 'Personne'
+    __regid__ = 'Personne'
     fetch_attrs, fetch_order = fetch_config(['nom', 'prenom'])
     rest_attr = 'nom'
 
 
 class Note(AnyEntity):
-    id = 'Note'
+    __regid__ = 'Note'
--- a/test/unittest_cwconfig.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/test/unittest_cwconfig.py	Wed Sep 30 18:57:42 2009 +0200
@@ -77,7 +77,7 @@
 
     def test_vregistry_path(self):
         self.assertEquals([unabsolutize(p) for p in self.config.vregistry_path()],
-                          ['entities', 'web/views', 'sobjects',
+                          ['entities', 'web/views', 'sobjects', 'hooks',
                            'file/entities.py', 'file/views', 'file/hooks.py',
                            'email/entities.py', 'email/views', 'email/hooks.py',
                            'test/data/entities.py'])
--- a/test/unittest_dbapi.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/test/unittest_dbapi.py	Wed Sep 30 18:57:42 2009 +0200
@@ -7,24 +7,21 @@
 """
 from cubicweb import ConnectionError
 from cubicweb.dbapi import ProgrammingError
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 
 
-class DBAPITC(EnvBasedTC):
-    @property
-    def cnx(self):
-        return self.login('anon')
+class DBAPITC(CubicWebTC):
 
     def test_public_repo_api(self):
-        cnx = self.cnx
-        self.assertEquals(cnx.get_schema(), self.env.repo.schema)
+        cnx = self.login('anon')
+        self.assertEquals(cnx.get_schema(), self.repo.schema)
         self.assertEquals(cnx.source_defs(), {'system': {'adapter': 'native', 'uri': 'system'}})
         self.restore_connection() # proper way to close cnx
         self.assertRaises(ProgrammingError, cnx.get_schema)
         self.assertRaises(ProgrammingError, cnx.source_defs)
 
     def test_db_api(self):
-        cnx = self.cnx
+        cnx = self.login('anon')
         self.assertEquals(cnx.rollback(), None)
         self.assertEquals(cnx.commit(), None)
         self.restore_connection() # proper way to close cnx
@@ -34,7 +31,7 @@
         self.assertRaises(ProgrammingError, cnx.close)
 
     def test_api(self):
-        cnx = self.cnx
+        cnx = self.login('anon')
         self.assertEquals(cnx.user(None).login, 'anon')
         self.assertEquals(cnx.describe(1), (u'CWGroup', u'system', None))
         self.restore_connection() # proper way to close cnx
@@ -42,7 +39,7 @@
         self.assertRaises(ConnectionError, cnx.describe, 1)
 
     def test_session_data_api(self):
-        cnx = self.cnx
+        cnx = self.login('anon')
         self.assertEquals(cnx.get_session_data('data'), None)
         self.assertEquals(cnx.session_data(), {})
         cnx.set_session_data('data', 4)
@@ -57,7 +54,7 @@
         self.assertEquals(cnx.session_data(), {'data': 4})
 
     def test_shared_data_api(self):
-        cnx = self.cnx
+        cnx = self.login('anon')
         self.assertEquals(cnx.get_shared_data('data'), None)
         cnx.set_shared_data('data', 4)
         self.assertEquals(cnx.get_shared_data('data'), 4)
@@ -71,19 +68,6 @@
         self.assertRaises(ConnectionError, cnx.set_shared_data, 'data', 0)
         self.assertRaises(ConnectionError, cnx.get_shared_data, 'data')
 
-
-# class DBAPICursorTC(EnvBasedTC):
-
-#     @property
-#     def cursor(self):
-#         return self.env.cnx.cursor()
-
-#     def test_api(self):
-#         cu = self.cursor
-#         self.assertEquals(cu.describe(1), (u'CWGroup', u'system', None))
-#         #cu.close()
-#         #self.assertRaises(ConnectionError, cu.describe, 1)
-
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
     unittest_main()
--- a/test/unittest_entity.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/test/unittest_entity.py	Wed Sep 30 18:57:42 2009 +0200
@@ -10,32 +10,25 @@
 from datetime import datetime
 
 from cubicweb import Binary, Unauthorized
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.common.mttransforms import HAS_TAL
 from cubicweb.entities import fetch_config
 
-class EntityTC(EnvBasedTC):
-
-##     def setup_database(self):
-##         self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien')
-##         self.add_entity('Task', title=u'fait ca !', description=u'et plus vite', start=now())
-##         self.add_entity('Tag', name=u'x')
-##         self.add_entity('Link', title=u'perdu', url=u'http://www.perdu.com',
-##                         embed=False)
+class EntityTC(CubicWebTC):
 
     def test_boolean_value(self):
-        e = self.etype_instance('CWUser')
+        e = self.vreg['etypes'].etype_class('CWUser')(self.request())
         self.failUnless(e)
 
     def test_yams_inheritance(self):
         from entities import Note
-        e = self.etype_instance('SubNote')
+        e = self.vreg['etypes'].etype_class('SubNote')(self.request())
         self.assertIsInstance(e, Note)
-        e2 = self.etype_instance('SubNote')
+        e2 = self.vreg['etypes'].etype_class('SubNote')(self.request())
         self.assertIs(e.__class__, e2.__class__)
 
     def test_has_eid(self):
-        e = self.etype_instance('CWUser')
+        e = self.vreg['etypes'].etype_class('CWUser')(self.request())
         self.assertEquals(e.eid, None)
         self.assertEquals(e.has_eid(), False)
         e.eid = 'X'
@@ -102,7 +95,7 @@
         user = self.entity('Any X WHERE X eid %(x)s', {'x':self.user().eid}, 'x')
         adeleid = self.execute('INSERT EmailAddress X: X address "toto@logilab.org", U use_email X WHERE U login "admin"')[0][0]
         self.commit()
-        self.assertEquals(user._related_cache.keys(), [])
+        self.assertEquals(user._related_cache, {})
         email = user.primary_email[0]
         self.assertEquals(sorted(user._related_cache), ['primary_email_subject'])
         self.assertEquals(email._related_cache.keys(), ['primary_email_object'])
@@ -273,7 +266,7 @@
         self.assertEquals([x.address for x in rset.entities()], [])
 
     def test_unrelated_new_entity(self):
-        e = self.etype_instance('CWUser')
+        e = self.vreg['etypes'].etype_class('CWUser')(self.request())
         unrelated = [r[0] for r in e.unrelated('in_group', 'CWGroup', 'subject')]
         # should be default groups but owners, i.e. managers, users, guests
         self.assertEquals(len(unrelated), 3)
@@ -292,7 +285,6 @@
         self.assertEquals(e.printable_value('content'),
                           '<p>\ndu *texte*\n</p>')
         e['title'] = 'zou'
-        #e = self.etype_instance('Task')
         e['content'] = '''\
 a title
 =======
@@ -380,7 +372,7 @@
 
 
     def test_fulltextindex(self):
-        e = self.etype_instance('File')
+        e = self.vreg['etypes'].etype_class('File')(self.request())
         e['name'] = 'an html file'
         e['description'] = 'du <em>html</em>'
         e['description_format'] = 'text/html'
@@ -399,7 +391,7 @@
         self.failUnless(not p1.reverse_evaluee)
 
     def test_complete_relation(self):
-        session = self.session()
+        session = self.session
         eid = session.unsafe_execute(
             'INSERT TrInfo X: X comment "zou", X wf_info_for U, X from_state S1, X to_state S2 '
             'WHERE U login "admin", S1 name "activated", S2 name "deactivated"')[0][0]
--- a/test/unittest_rset.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/test/unittest_rset.py	Wed Sep 30 18:57:42 2009 +0200
@@ -10,7 +10,7 @@
 
 from logilab.common.testlib import TestCase, unittest_main
 
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.selectors import traced_selection
 
 from urlparse import urlsplit
@@ -55,7 +55,7 @@
 
 
 
-class ResultSetTC(EnvBasedTC):
+class ResultSetTC(CubicWebTC):
 
     def setUp(self):
         super(ResultSetTC, self).setUp()
@@ -100,12 +100,12 @@
                        'Any U,L where U is CWUser, U login L',
                        description=[['CWUser', 'String']] * 3)
         rs.req = self.request()
-        rs.vreg = self.env.vreg
+        rs.vreg = self.vreg
 
         self.assertEquals(rs.limit(2).rows, [[12000, 'adim'], [13000, 'syt']])
         rs2 = rs.limit(2, offset=1)
         self.assertEquals(rs2.rows, [[13000, 'syt'], [14000, 'nico']])
-        self.assertEquals(rs2.get_entity(0, 0).row, 0)
+        self.assertEquals(rs2.get_entity(0, 0).cw_row, 0)
         self.assertEquals(rs.limit(2, offset=2).rows, [[14000, 'nico']])
         self.assertEquals(rs.limit(2, offset=3).rows, [])
 
@@ -115,7 +115,7 @@
                        'Any U,L where U is CWUser, U login L',
                        description=[['CWUser', 'String']] * 3)
         rs.req = self.request()
-        rs.vreg = self.env.vreg
+        rs.vreg = self.vreg
         def test_filter(entity):
             return entity.login != 'nico'
 
@@ -140,7 +140,7 @@
                        'Any U,L where U is CWUser, U login L',
                        description=[['CWUser', 'String']] * 3)
         rs.req = self.request()
-        rs.vreg = self.env.vreg
+        rs.vreg = self.vreg
 
         rs2 = rs.sorted_rset(lambda e:e['login'])
         self.assertEquals(len(rs2), 3)
@@ -170,7 +170,7 @@
                        'D created_by U, D title T',
                        description=[['CWUser', 'String', 'String']] * 5)
         rs.req = self.request()
-        rs.vreg = self.env.vreg
+        rs.vreg = self.vreg
 
         rsets = rs.split_rset(lambda e:e['login'])
         self.assertEquals(len(rsets), 3)
@@ -229,16 +229,16 @@
         rset = self.execute('Any X,Y,XT,YN WHERE X bookmarked_by Y, X title XT, Y login YN')
 
         e = rset.get_entity(0, 0)
-        self.assertEquals(e.row, 0)
-        self.assertEquals(e.col, 0)
+        self.assertEquals(e.cw_row, 0)
+        self.assertEquals(e.cw_col, 0)
         self.assertEquals(e['title'], 'zou')
         self.assertRaises(KeyError, e.__getitem__, 'path')
         self.assertEquals(e.view('text'), 'zou')
         self.assertEquals(pprelcachedict(e._related_cache), [])
 
         e = rset.get_entity(0, 1)
-        self.assertEquals(e.row, 0)
-        self.assertEquals(e.col, 1)
+        self.assertEquals(e.cw_row, 0)
+        self.assertEquals(e.cw_col, 1)
         self.assertEquals(e['login'], 'anon')
         self.assertRaises(KeyError, e.__getitem__, 'firstname')
         self.assertEquals(pprelcachedict(e._related_cache),
@@ -304,8 +304,8 @@
                     ('Bookmark', 'manger'), ('CWGroup', 'owners'),
                     ('CWGroup', 'users'))
         for entity in rset.entities(): # test get_entity for each row actually
-            etype, n = expected[entity.row]
-            self.assertEquals(entity.id, etype)
+            etype, n = expected[entity.cw_row]
+            self.assertEquals(entity.__regid__, etype)
             attr = etype == 'Bookmark' and 'title' or 'name'
             self.assertEquals(entity[attr], n)
 
@@ -326,7 +326,7 @@
         self.assertEquals(entity.eid, e.eid)
         self.assertEquals(rtype, 'title')
         entity, rtype = rset.related_entity(1, 1)
-        self.assertEquals(entity.id, 'CWGroup')
+        self.assertEquals(entity.__regid__, 'CWGroup')
         self.assertEquals(rtype, 'name')
         #
         rset = self.execute('Any X,N ORDERBY N WHERE X is Bookmark WITH X,N BEING '
--- a/test/unittest_selectors.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/test/unittest_selectors.py	Wed Sep 30 18:57:42 2009 +0200
@@ -8,7 +8,7 @@
 
 from logilab.common.testlib import TestCase, unittest_main
 
-from cubicweb.devtools.testlib import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.appobject import Selector, AndSelector, OrSelector
 from cubicweb.selectors import implements, match_user_groups
 from cubicweb.interfaces import IDownloadable
@@ -88,7 +88,7 @@
         self.assertIs(csel.search_selector(implements), sel)
 
 
-class ImplementsSelectorTC(EnvBasedTC):
+class ImplementsSelectorTC(CubicWebTC):
     def test_etype_priority(self):
         req = self.request()
         cls = self.vreg['etypes'].etype_class('File')
@@ -103,11 +103,11 @@
         self.failIf(implements('Societe').score_class(cls, self.request()))
 
 
-class MatchUserGroupsTC(EnvBasedTC):
+class MatchUserGroupsTC(CubicWebTC):
     def test_owners_group(self):
         """tests usage of 'owners' group with match_user_group"""
         class SomeAction(action.Action):
-            id = 'yo'
+            __regid__ = 'yo'
             category = 'foo'
             __select__ = match_user_groups('owners')
         self.vreg._loadedmods[__name__] = {}
@@ -118,19 +118,22 @@
             self.create_user('john')
             self.login('john')
             # it should not be possible to use SomeAction not owned objects
-            rset, req = self.env.get_rset_and_req('Any G WHERE G is CWGroup, G name "managers"')
+            req = self.request()
+            rset = req.execute('Any G WHERE G is CWGroup, G name "managers"')
             self.failIf('yo' in dict(self.pactions(req, rset)))
             # insert a new card, and check that we can use SomeAction on our object
             self.execute('INSERT Card C: C title "zoubidou"')
             self.commit()
-            rset, req = self.env.get_rset_and_req('Card C WHERE C title "zoubidou"')
+            req = self.request()
+            rset = req.execute('Card C WHERE C title "zoubidou"')
             self.failUnless('yo' in dict(self.pactions(req, rset)), self.pactions(req, rset))
             # make sure even managers can't use the action
             self.restore_connection()
-            rset, req = self.env.get_rset_and_req('Card C WHERE C title "zoubidou"')
+            req = self.request()
+            rset = req.execute('Card C WHERE C title "zoubidou"')
             self.failIf('yo' in dict(self.pactions(req, rset)))
         finally:
-            del self.vreg[SomeAction.__registry__][SomeAction.id]
+            del self.vreg[SomeAction.__registry__][SomeAction.__regid__]
 
 if __name__ == '__main__':
     unittest_main()
--- a/test/unittest_vregistry.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/test/unittest_vregistry.py	Wed Sep 30 18:57:42 2009 +0200
@@ -40,14 +40,6 @@
         self.vreg.initialization_completed()
         self.assertEquals(len(self.vreg['views']['primary']), 1)
 
-    def test___selectors__compat(self):
-        myselector1 = lambda *args: 1
-        myselector2 = lambda *args: 1
-        class AnAppObject(AppObject):
-            __selectors__ = (myselector1, myselector2)
-        AnAppObject.build___select__()
-        self.assertEquals(AnAppObject.__select__(AnAppObject), 2)
-
     def test_properties(self):
         self.failIf('system.version.cubicweb' in self.vreg['propertydefs'])
         self.failUnless(self.vreg.property_info('system.version.cubicweb'))
--- a/toolsutils.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/toolsutils.py	Wed Sep 30 18:57:42 2009 +0200
@@ -27,6 +27,9 @@
 from cubicweb import warning
 from cubicweb import ConfigurationError, ExecutionError
 
+def underline_title(title, car='-'):
+    return title+'\n'+(car*len(title))
+
 def iter_dir(directory, condition_file=None, ignore=()):
     """iterate on a directory"""
     for sub in listdir(directory):
--- a/utils.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/utils.py	Wed Sep 30 18:57:42 2009 +0200
@@ -18,6 +18,7 @@
 from time import time, mktime
 from random import randint, seed
 from calendar import monthrange
+import decimal
 
 # initialize random seed from current time
 seed()
--- a/view.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/view.py	Wed Sep 30 18:57:42 2009 +0200
@@ -17,7 +17,6 @@
 
 from cubicweb import NotAnEntity
 from cubicweb.selectors import yes, non_final_entity, nonempty_rset, none_rset
-from cubicweb.selectors import require_group_compat, accepts_compat
 from cubicweb.appobject import AppObject
 from cubicweb.utils import UStringIO, HTMLStream
 from cubicweb.schema import display_name
@@ -93,7 +92,6 @@
     time to a write function to use.
     """
     __registry__ = 'views'
-    registered = require_group_compat(AppObject.registered)
 
     templatable = True
     need_navigation = True
@@ -103,12 +101,12 @@
     category = 'view'
 
     def __init__(self, req=None, rset=None, **kwargs):
-        super(View, self).__init__(req, rset, **kwargs)
+        super(View, self).__init__(req, rset=rset, **kwargs)
         self.w = None
 
     @property
     def content_type(self):
-        return self.req.html_content_type()
+        return self._cw.html_content_type()
 
     def set_stream(self, w=None):
         if self.w is not None:
@@ -151,7 +149,20 @@
         if stream is not None:
             return self._stream.getvalue()
 
-    dispatch = deprecated('.dispatch is deprecated, use .render')(render)
+    def tal_render(self, template, variables):
+        """render a precompiled page template with variables in the given
+        dictionary as context
+        """
+        from cubicweb.ext.tal import CubicWebContext
+        context = CubicWebContext()
+        context.update({'self': self, 'rset': self.cw_rset, '_' : self._cw._,
+                        'req': self._cw, 'user': self._cw.user})
+        context.update(variables)
+        output = UStringIO()
+        template.expand(context, output)
+        return output.getvalue()
+
+    dispatch = deprecated('[3.4] .dispatch is deprecated, use .render')(render)
 
     # should default .call() method add a <div classs="section"> around each
     # rset item
@@ -164,14 +175,14 @@
 
         Views applicable on None result sets have to override this method
         """
-        rset = self.rset
+        rset = self.cw_rset
         if rset is None:
             raise NotImplementedError, self
         wrap = self.templatable and len(rset) > 1 and self.add_div_section
         for i in xrange(len(rset)):
             if wrap:
                 self.w(u'<div class="section">')
-            self.wview(self.id, rset, row=i, **kwargs)
+            self.wview(self.__regid__, rset, row=i, **kwargs)
             if wrap:
                 self.w(u"</div>")
 
@@ -189,23 +200,23 @@
         return True
 
     def is_primary(self):
-        return self.id == 'primary'
+        return self.__regid__ == 'primary'
 
     def url(self):
         """return the url associated with this view. Should not be
         necessary for non linkable views, but a default implementation
         is provided anyway.
         """
-        rset = self.rset
+        rset = self.cw_rset
         if rset is None:
-            return self.build_url('view', vid=self.id)
+            return self._cw.build_url('view', vid=self.__regid__)
         coltypes = rset.column_types(0)
         if len(coltypes) == 1:
             etype = iter(coltypes).next()
-            if not self.schema.eschema(etype).is_final():
+            if not self._cw.schema.eschema(etype).is_final():
                 if len(rset) == 1:
                     entity = rset.get_entity(0, 0)
-                    return entity.absolute_url(vid=self.id)
+                    return entity.absolute_url(vid=self.__regid__)
             # don't want to generate /<etype> url if there is some restriction
             # on something else than the entity type
             restr = rset.syntax_tree().children[0].where
@@ -215,25 +226,25 @@
             norestriction = (isinstance(restr, nodes.Relation) and
                              restr.is_types_restriction())
             if norestriction:
-                return self.build_url(etype.lower(), vid=self.id)
-        return self.build_url('view', rql=rset.printable_rql(), vid=self.id)
+                return self._cw.build_url(etype.lower(), vid=self.__regid__)
+        return self._cw.build_url('view', rql=rset.printable_rql(), vid=self.__regid__)
 
     def set_request_content_type(self):
         """set the content type returned by this view"""
-        self.req.set_content_type(self.content_type)
+        self._cw.set_content_type(self.content_type)
 
     # view utilities ##########################################################
 
     def wview(self, __vid, rset=None, __fallback_vid=None, **kwargs):
         """shortcut to self.view method automatically passing self.w as argument
         """
-        self.view(__vid, rset, __fallback_vid, w=self.w, **kwargs)
+        self._cw.view(__vid, rset, __fallback_vid, w=self.w, **kwargs)
 
     # XXX Template bw compat
-    template = deprecated('.template is deprecated, use .view')(wview)
+    template = deprecated('[3.4] .template is deprecated, use .view')(wview)
 
     def whead(self, data):
-        self.req.html_headers.write(data)
+        self._cw.html_headers.write(data)
 
     def wdata(self, data):
         """simple helper that escapes `data` and writes into `self.w`"""
@@ -251,34 +262,34 @@
         """returns a title according to the result set - used for the
         title in the HTML header
         """
-        vtitle = self.req.form.get('vtitle')
+        vtitle = self._cw.form.get('vtitle')
         if vtitle:
-            return self.req._(vtitle)
+            return self._cw._(vtitle)
         # class defined title will only be used if the resulting title doesn't
         # seem clear enough
         vtitle = getattr(self, 'title', None) or u''
         if vtitle:
-            vtitle = self.req._(vtitle)
-        rset = self.rset
+            vtitle = self._cw._(vtitle)
+        rset = self.cw_rset
         if rset and rset.rowcount:
             if rset.rowcount == 1:
                 try:
-                    entity = self.complete_entity(0)
+                    entity = rset.complete_entity(0, 0)
                     # use long_title to get context information if any
                     clabel = entity.dc_long_title()
                 except NotAnEntity:
-                    clabel = display_name(self.req, rset.description[0][0])
+                    clabel = display_name(self._cw, rset.description[0][0])
                     clabel = u'%s (%s)' % (clabel, vtitle)
             else :
                 etypes = rset.column_types(0)
                 if len(etypes) == 1:
                     etype = iter(etypes).next()
-                    clabel = display_name(self.req, etype, 'plural')
+                    clabel = display_name(self._cw, etype, 'plural')
                 else :
                     clabel = u'#[*] (%s)' % vtitle
         else:
             clabel = vtitle
-        return u'%s (%s)' % (clabel, self.req.property_value('ui.site-title'))
+        return u'%s (%s)' % (clabel, self._cw.property_value('ui.site-title'))
 
     def output_url_builder( self, name, url, args ):
         self.w(u'<script language="JavaScript"><!--\n' \
@@ -294,7 +305,7 @@
 
     def create_url(self, etype, **kwargs):
         """ return the url of the entity creation form for a given entity type"""
-        return self.req.build_url('add/%s'%etype, **kwargs)
+        return self._cw.build_url('add/%s'%etype, **kwargs)
 
     def field(self, label, value, row=True, show_label=True, w=None, tr=True):
         """ read-only field """
@@ -304,7 +315,7 @@
             w(u'<div class="row">')
         if show_label and label:
             if tr:
-                label = display_name(self.req, label)
+                label = display_name(self._cw, label)
             w(u'<span class="label">%s</span>' % label)
         w(u'<div class="field">%s</div>' % value)
         if row:
@@ -317,8 +328,6 @@
 class EntityView(View):
     """base class for views applying on an entity (i.e. uniform result set)"""
     __select__ = non_final_entity()
-    registered = accepts_compat(View.registered)
-
     category = 'entityview'
 
 
@@ -327,7 +336,6 @@
     displayed (so they can always be displayed !)
     """
     __select__ = none_rset()
-    registered = require_group_compat(View.registered)
 
     category = 'startupview'
 
@@ -349,7 +357,7 @@
     default_rql = None
 
     def __init__(self, req, rset=None, **kwargs):
-        super(EntityStartupView, self).__init__(req, rset, **kwargs)
+        super(EntityStartupView, self).__init__(req, rset=rset, **kwargs)
         if rset is None:
             # this instance is not in the "entityview" category
             self.category = 'startupview'
@@ -362,11 +370,11 @@
         """override call to execute rql returned by the .startup_rql method if
         necessary
         """
-        if self.rset is None:
-            self.rset = self.req.execute(self.startup_rql())
-        rset = self.rset
+        rset = self.cw_rset
+        if rset is None:
+            rset = self.cw_rset = self._cw.execute(self.startup_rql())
         for i in xrange(len(rset)):
-            self.wview(self.id, rset, row=i, **kwargs)
+            self.wview(self.__regid__, rset, row=i, **kwargs)
 
 
 class AnyRsetView(View):
@@ -377,18 +385,18 @@
 
     def columns_labels(self, mainindex=0, tr=True):
         if tr:
-            translate = lambda val, req=self.req: display_name(req, val)
+            translate = lambda val, req=self._cw: display_name(req, val)
         else:
             translate = lambda val: val
         # XXX [0] because of missing Union support
-        rqlstdescr = self.rset.syntax_tree().get_description(mainindex,
-                                                             translate)[0]
+        rqlstdescr = self.cw_rset.syntax_tree().get_description(mainindex,
+                                                                translate)[0]
         labels = []
         for colindex, label in enumerate(rqlstdescr):
             # compute column header
             if label == 'Any': # find a better label
                 label = ','.join(translate(et)
-                                 for et in self.rset.column_types(colindex))
+                                 for et in self.cw_rset.column_types(colindex))
             labels.append(label)
         return labels
 
@@ -400,11 +408,10 @@
     There is usually at least a regular main template and a simple fallback
     one to display error if the first one failed
     """
-    registered = require_group_compat(View.registered)
 
     @property
     def doctype(self):
-        if self.req.xhtml_browser():
+        if self._cw.xhtml_browser():
             return STRICT_DOCTYPE
         return STRICT_DOCTYPE_NOEXT
 
@@ -415,7 +422,7 @@
             if self.binary:
                 self._stream = stream = StringIO()
             else:
-                self._stream = stream = HTMLStream(self.req)
+                self._stream = stream = HTMLStream(self._cw)
             w = stream.write
         else:
             stream = None
@@ -440,18 +447,18 @@
         """register the given user callback and return an url to call it ready to be
         inserted in html
         """
-        self.req.add_js('cubicweb.ajax.js')
+        self._cw.add_js('cubicweb.ajax.js')
         if nonify:
             _cb = cb
             def cb(*args):
                 _cb(*args)
-        cbname = self.req.register_onetime_callback(cb, *args)
+        cbname = self._cw.register_onetime_callback(cb, *args)
         return self.build_js(cbname, xml_escape(msg or ''))
 
     def build_update_js_call(self, cbname, msg):
-        rql = xml_escape(self.rset.printable_rql())
+        rql = xml_escape(self.cw_rset.printable_rql())
         return "javascript:userCallbackThenUpdateUI('%s', '%s', '%s', '%s', '%s', '%s')" % (
-            cbname, self.id, rql, msg, self.__registry__, self.div_id())
+            cbname, self.__regid__, rql, msg, self.__registry__, self.div_id())
 
     def build_reload_js_call(self, cbname, msg):
         return "javascript:userCallbackThenReloadPage('%s', '%s')" % (cbname, msg)
@@ -466,12 +473,12 @@
     """base class for components"""
     __registry__ = 'components'
     __select__ = yes()
-    property_defs = {}
 
     # XXX huummm, much probably useless
     htmlclass = 'mainRelated'
     def div_class(self):
-        return '%s %s' % (self.htmlclass, self.id)
-    # XXX a generic '%s%s' % (self.id, self.__registry__.capitalize()) would probably be nicer
+        return '%s %s' % (self.htmlclass, self.__regid__)
+
+    # XXX a generic '%s%s' % (self.__regid__, self.__registry__.capitalize()) would probably be nicer
     def div_id(self):
-        return '%sComponent' % self.id
+        return '%sComponent' % self.__regid__
--- a/vregistry.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/vregistry.py	Wed Sep 30 18:57:42 2009 +0200
@@ -54,6 +54,21 @@
     return _toload
 
 
+def classid(cls):
+    """returns a unique identifier for an appobject class"""
+    return '%s.%s' % (cls.__module__, cls.__name__)
+
+def class_regid(cls):
+    """returns a unique identifier for an appobject class"""
+    if 'id' in cls.__dict__:
+        warn('[3.6] %s.%s: id is deprecated, use __regid__'
+             % (cls.__module__, cls.__name__), DeprecationWarning)
+        cls.__regid__ = cls.id
+    if hasattr(cls, 'id'):
+        return cls.id
+    return cls.__regid__
+
+
 class Registry(dict):
 
     def __init__(self, config):
@@ -72,15 +87,13 @@
     def register(self, obj, oid=None, clear=False):
         """base method to add an object in the registry"""
         assert not '__abstract__' in obj.__dict__
-        oid = oid or obj.id
+        oid = oid or class_regid(obj)
         assert oid
         if clear:
             appobjects = self[oid] =  []
         else:
             appobjects = self.setdefault(oid, [])
-        # registered() is technically a classmethod but is not declared
-        # as such because we need to compose registered in some cases
-        appobject = obj.registered.im_func(obj, self)
+        appobject = obj.__registered__(self)
         assert not appobject in appobjects, \
                'object %s is already registered' % appobject
         assert callable(appobject.__select__), appobject
@@ -90,11 +103,11 @@
         # XXXFIXME this is a duplication of unregister()
         # remove register_and_replace in favor of unregister + register
         # or simplify by calling unregister then register here
-        if hasattr(replaced, 'classid'):
-            replaced = replaced.classid()
-        registered_objs = self.get(obj.id, ())
+        if not isinstance(replaced, basestring):
+            replaced = classid(replaced)
+        registered_objs = self.get(class_regid(obj), ())
         for index, registered in enumerate(registered_objs):
-            if registered.classid() == replaced:
+            if classid(registered) == replaced:
                 del registered_objs[index]
                 break
         else:
@@ -103,17 +116,18 @@
         self.register(obj)
 
     def unregister(self, obj):
-        oid = obj.classid()
-        for registered in self.get(obj.id, ()):
+        clsid = classid(obj)
+        oid = class_regid(obj)
+        for registered in self.get(oid, ()):
             # use classid() to compare classes because vreg will probably
             # have its own version of the class, loaded through execfile
-            if registered.classid() == oid:
+            if classid(registered) == clsid:
                 # XXX automatic reloading management
-                self[obj.id].remove(registered)
+                self[oid].remove(registered)
                 break
         else:
             self.warning('can\'t remove %s, no id %s in the registry',
-                         oid, obj.id)
+                         clsid, oid)
 
     def all_objects(self):
         """return a list containing all objects in this registry.
@@ -125,6 +139,7 @@
 
     # dynamic selection methods ################################################
 
+    @deprecated('[3.6] use select instead of object_by_id')
     def object_by_id(self, oid, *args, **kwargs):
         """return object with the given oid. Only one object is expected to be
         found.
@@ -143,9 +158,9 @@
         raise `ObjectNotFound` if not object with id <oid> in <registry>
         raise `NoSelectableObject` if not object apply
         """
-        return self.select_best(self[oid], *args, **kwargs)
+        return self._select_best(self[oid], *args, **kwargs)
 
-    def select_object(self, oid, *args, **kwargs):
+    def select_or_none(self, oid, *args, **kwargs):
         """return the most specific object among those with the given oid
         according to the given context, or None if no object applies.
         """
@@ -153,6 +168,8 @@
             return self.select(oid, *args, **kwargs)
         except (NoSelectableObject, ObjectNotFound):
             return None
+    select_object = deprecated('[3.6] use select_or_none instead of select_object'
+                               )(select_or_none)
 
     def possible_objects(self, *args, **kwargs):
         """return an iterator on possible objects in this registry for the given
@@ -160,11 +177,11 @@
         """
         for appobjects in self.itervalues():
             try:
-                yield self.select_best(appobjects, *args, **kwargs)
+                yield self._select_best(appobjects, *args, **kwargs)
             except NoSelectableObject:
                 continue
 
-    def select_best(self, appobjects, *args, **kwargs):
+    def _select_best(self, appobjects, *args, **kwargs):
         """return an instance of the most specific object according
         to parameters
 
@@ -195,6 +212,8 @@
         # return the result of calling the appobject
         return winners[0](*args, **kwargs)
 
+    select_best = deprecated('[3.6] select_best is now private')(_select_best)
+
 
 class VRegistry(dict):
     """class responsible to register, propose and select the various
@@ -223,7 +242,7 @@
 
     # dynamic selection methods ################################################
 
-    @deprecated('use vreg[registry].object_by_id(oid, *args, **kwargs)')
+    @deprecated('[3.4] use vreg[registry].object_by_id(oid, *args, **kwargs)')
     def object_by_id(self, registry, oid, *args, **kwargs):
         """return object in <registry>.<oid>
 
@@ -232,7 +251,7 @@
         """
         return self[registry].object_by_id(oid)
 
-    @deprecated('use vreg[registry].select(oid, *args, **kwargs)')
+    @deprecated('[3.4] use vreg[registry].select(oid, *args, **kwargs)')
     def select(self, registry, oid, *args, **kwargs):
         """return the most specific object in <registry>.<oid> according to
         the given context
@@ -242,14 +261,14 @@
         """
         return self[registry].select(oid, *args, **kwargs)
 
-    @deprecated('use vreg[registry].select_object(oid, *args, **kwargs)')
+    @deprecated('[3.4] use vreg[registry].select_or_none(oid, *args, **kwargs)')
     def select_object(self, registry, oid, *args, **kwargs):
         """return the most specific object in <registry>.<oid> according to
         the given context, or None if no object apply
         """
-        return self[registry].select_object(oid, *args, **kwargs)
+        return self[registry].select_or_none(oid, *args, **kwargs)
 
-    @deprecated('use vreg[registry].possible_objects(*args, **kwargs)')
+    @deprecated('[3.4] use vreg[registry].possible_objects(*args, **kwargs)')
     def possible_objects(self, registry, *args, **kwargs):
         """return an iterator on possible objects in <registry> for the given
         context
@@ -283,7 +302,7 @@
             try:
                 if obj.__module__ != modname or obj in butclasses:
                     continue
-                oid = obj.id
+                oid = class_regid(obj)
                 registryname = obj.__registry__
             except AttributeError:
                 continue
@@ -301,8 +320,8 @@
         except AttributeError:
             vname = obj.__class__.__name__
         self.debug('registered appobject %s in registry %s with id %s',
-                   vname, registryname, oid or obj.id)
-        self._loadedmods[obj.__module__]['%s.%s' % (obj.__module__, oid)] = obj
+                   vname, registryname, oid or class_regid(obj))
+        self._loadedmods[obj.__module__][classid(obj)] = obj
 
     def unregister(self, obj, registryname=None):
         self[registryname or obj.__registry__].unregister(obj)
@@ -391,10 +410,10 @@
                 return
         except TypeError:
             return
-        objname = '%s.%s' % (modname, obj.__name__)
-        if objname in self._loadedmods[modname]:
+        clsid = classid(obj)
+        if clsid in self._loadedmods[modname]:
             return
-        self._loadedmods[modname][objname] = obj
+        self._loadedmods[modname][clsid] = obj
         for parent in obj.__bases__:
             self._load_ancestors_then_object(modname, parent)
         self.load_object(obj)
@@ -418,10 +437,10 @@
         to a non empty string to be registered.
         """
         if (cls.__dict__.get('__abstract__') or cls.__name__[0] == '_'
-            or not cls.__registry__ or not cls.id):
+            or not cls.__registry__ or not class_regid(cls)):
             return
         regname = cls.__registry__
-        if '%s.%s' % (regname, cls.id) in self.config['disable-appobjects']:
+        if '%s.%s' % (regname, class_regid(cls)) in self.config['disable-appobjects']:
             return
         self.register(cls)
 
@@ -434,11 +453,11 @@
 
 from cubicweb.appobject import objectify_selector, AndSelector, OrSelector, Selector
 
-objectify_selector = deprecated('objectify_selector has been moved to appobject module')(objectify_selector)
+objectify_selector = deprecated('[3.4] objectify_selector has been moved to appobject module')(objectify_selector)
 
 Selector = class_moved(Selector)
 
-@deprecated('use & operator (binary and)')
+@deprecated('[3.4] use & operator (binary and)')
 def chainall(*selectors, **kwargs):
     """return a selector chaining given selectors. If one of
     the selectors fail, selection will fail, else the returned score
@@ -451,7 +470,7 @@
         selector.__name__ = kwargs['name']
     return selector
 
-@deprecated('use | operator (binary or)')
+@deprecated('[3.4] use | operator (binary or)')
 def chainfirst(*selectors, **kwargs):
     """return a selector chaining given selectors. If all
     the selectors fail, selection will fail, else the returned score
--- a/web/__init__.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/__init__.py	Wed Sep 30 18:57:42 2009 +0200
@@ -13,10 +13,10 @@
 from decimal import Decimal
 from datetime import datetime, date, timedelta
 from simplejson import dumps
+from urllib import quote as urlquote
 
 from logilab.common.deprecation import deprecated
 
-from urllib import quote as urlquote
 from cubicweb.web._exceptions import *
 
 
@@ -65,7 +65,7 @@
         return json_dumps(function(*args, **kwargs))
     return newfunc
 
-@deprecated('use req.build_ajax_replace_url() instead')
+@deprecated('[3.4] use req.build_ajax_replace_url() instead')
 def ajax_replace_url(nodeid, rql, vid=None, swap=False, **extraparams):
     """builds a replacePageChunk-like url
     >>> ajax_replace_url('foo', 'Person P')
--- a/web/_exceptions.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/_exceptions.py	Wed Sep 30 18:57:42 2009 +0200
@@ -19,6 +19,10 @@
 class NothingToEdit(RequestError):
     """raised when an edit request doesn't specify any eid to edit"""
 
+class ProcessFormError(RequestError):
+    """raised when posted data can't be processed by the corresponding field
+    """
+
 class NotFound(RequestError):
     """raised when a 404 error should be returned"""
 
--- a/web/action.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/action.py	Wed Sep 30 18:57:42 2009 +0200
@@ -10,8 +10,7 @@
 
 from cubicweb import target
 from cubicweb.selectors import (partial_relation_possible, match_search_state,
-                                one_line_rset, partial_may_add_relation, yes,
-                                accepts_compat, condition_compat, deprecate)
+                                one_line_rset, partial_may_add_relation, yes)
 from cubicweb.appobject import AppObject
 
 
@@ -22,7 +21,7 @@
     __registry__ = 'actions'
     __select__ = match_search_state('normal')
 
-    property_defs = {
+    cw_property_defs = {
         'visible':  dict(type='Boolean', default=True,
                          help=_('display the action or not')),
         'order':    dict(type='Int', default=99,
@@ -50,13 +49,13 @@
         raise NotImplementedError
 
     def html_class(self):
-        if self.req.selected(self.url()):
+        if self._cw.selected(self.url()):
             return 'selected'
         if self.category:
             return 'box' + self.category.capitalize()
 
     def build_action(self, title, path, **kwargs):
-        return UnregisteredAction(self.req, self.rset, title, path, **kwargs)
+        return UnregisteredAction(self._cw, self.cw_rset, title, path, **kwargs)
 
 
 class UnregisteredAction(Action):
@@ -67,7 +66,7 @@
     id = None
 
     def __init__(self, req, rset, title, path, **kwargs):
-        Action.__init__(self, req, rset)
+        Action.__init__(self, req, rset=rset)
         self.title = req._(title)
         self._path = path
         self.__dict__.update(kwargs)
@@ -86,21 +85,13 @@
     __select__ = (match_search_state('normal') & one_line_rset()
                   & partial_relation_possible(action='add')
                   & partial_may_add_relation())
-    registered = accepts_compat(Action.registered)
 
     submenu = 'addrelated'
 
     def url(self):
-        current_entity = self.rset.get_entity(self.row or 0, self.col or 0)
+        current_entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
         linkto = '%s:%s:%s' % (self.rtype, current_entity.eid, target(self))
-        return self.build_url('add/%s' % self.etype, __linkto=linkto,
-                              __redirectpath=current_entity.rest_path(), # should not be url quoted!
-                              __redirectvid=self.req.form.get('__redirectvid', ''))
+        return self._cw.build_url('add/%s' % self.etype, __linkto=linkto,
+                                  __redirectpath=current_entity.rest_path(), # should not be url quoted!
+                                  __redirectvid=self._cw.form.get('__redirectvid', ''))
 
-class EntityAction(Action):
-    """DEPRECATED / BACKWARD COMPAT
-    """
-    registered = deprecate(condition_compat(accepts_compat(Action.registered)),
-                           msg='EntityAction is deprecated, use Action with '
-                           'appropriate selectors')
-
--- a/web/application.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/application.py	Wed Sep 30 18:57:42 2009 +0200
@@ -29,19 +29,19 @@
 
 class AbstractSessionManager(component.Component):
     """manage session data associated to a session identifier"""
-    id = 'sessionmanager'
+    __regid__ = 'sessionmanager'
 
-    def __init__(self):
-        self.session_time = self.vreg.config['http-session-time'] or None
+    def __init__(self, vreg):
+        self.session_time = vreg.config['http-session-time'] or None
         assert self.session_time is None or self.session_time > 0
-        self.cleanup_session_time = self.vreg.config['cleanup-session-time'] or 43200
+        self.cleanup_session_time = vreg.config['cleanup-session-time'] or 43200
         assert self.cleanup_session_time > 0
-        self.cleanup_anon_session_time = self.vreg.config['cleanup-anonymous-session-time'] or 120
+        self.cleanup_anon_session_time = vreg.config['cleanup-anonymous-session-time'] or 120
         assert self.cleanup_anon_session_time > 0
         if self.session_time:
             assert self.cleanup_session_time < self.session_time
             assert self.cleanup_anon_session_time < self.session_time
-        self.authmanager = self.vreg['components'].select('authmanager')
+        self.authmanager = vreg['components'].select('authmanager', vreg=vreg)
 
     def clean_sessions(self):
         """cleanup sessions which has not been unused since a given amount of
@@ -92,6 +92,10 @@
 class AbstractAuthenticationManager(component.Component):
     """authenticate user associated to a request and check session validity"""
     id = 'authmanager'
+    vreg = None # XXX necessary until property for deprecation warning is on appobject
+
+    def __init__(self, vreg):
+        self.vreg = vreg
 
     def authenticate(self, req):
         """authenticate user and return corresponding user object
@@ -113,7 +117,8 @@
 
     def __init__(self, appli):
         self.vreg = appli.vreg
-        self.session_manager = self.vreg['components'].select('sessionmanager')
+        self.session_manager = self.vreg['components'].select('sessionmanager',
+                                                              vreg=self.vreg)
         global SESSION_MANAGER
         SESSION_MANAGER = self.session_manager
         if not 'last_login_time' in self.vreg.schema:
@@ -122,7 +127,8 @@
 
     def reset_session_manager(self):
         data = self.session_manager.dump_data()
-        self.session_manager = self.vreg['components'].select('sessionmanager')
+        self.session_manager = self.vreg['components'].select('sessionmanager',
+                                                              vreg=self.vreg)
         self.session_manager.restore_data(data)
         global SESSION_MANAGER
         SESSION_MANAGER = self.session_manager
@@ -252,7 +258,8 @@
         CW_EVENT_MANAGER.bind('after-registry-reload', self.set_urlresolver)
 
     def set_urlresolver(self):
-        self.url_resolver = self.vreg['components'].select('urlpublisher')
+        self.url_resolver = self.vreg['components'].select('urlpublisher',
+                                                           vreg=self.vreg)
 
     def connect(self, req):
         """return a connection for a logged user object according to existing
@@ -285,7 +292,7 @@
             finally:
                 self._logfile_lock.release()
 
-    @deprecated("use vreg.select('controllers', ...)")
+    @deprecated("[3.4] use vreg['controllers'].select(...)")
     def select_controller(self, oid, req):
         try:
             return self.vreg['controllers'].select(oid, req=req, appli=self)
--- a/web/box.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/box.py	Wed Sep 30 18:57:42 2009 +0200
@@ -13,9 +13,7 @@
 from cubicweb import Unauthorized, role as get_role, target as get_target
 from cubicweb.schema import display_name
 from cubicweb.selectors import (one_line_rset,  primary_view,
-                                match_context_prop, partial_has_related_entities,
-                                accepts_compat, has_relation_compat,
-                                condition_compat, require_group_compat)
+                                match_context_prop, partial_has_related_entities)
 from cubicweb.view import View, ReloadableMixIn
 
 from cubicweb.web.htmlwidgets import (BoxLink, BoxWidget, SideBoxWidget,
@@ -39,10 +37,9 @@
     """
     __registry__ = 'boxes'
     __select__ = match_context_prop()
-    registered = classmethod(require_group_compat(View.registered))
 
     categories_in_order = ()
-    property_defs = {
+    cw_property_defs = {
         _('visible'): dict(type='Boolean', default=True,
                            help=_('display the box or not')),
         _('order'):   dict(type='Int', default=99,
@@ -80,18 +77,18 @@
         return self.box_action(self._action(title, path, **kwargs))
 
     def _action(self, title, path, **kwargs):
-        return UnregisteredAction(self.req, self.rset, title, path, **kwargs)
+        return UnregisteredAction(self._cw, self.cw_rset, title, path, **kwargs)
 
     # formating callbacks
 
     def boxitem_link_tooltip(self, action):
-        if action.id:
-            return u'keyword: %s' % action.id
+        if action.__regid__:
+            return u'keyword: %s' % action.__regid__
         return u''
 
     def box_action(self, action):
         cls = getattr(action, 'html_class', lambda: None)() or self.htmlitemclass
-        return BoxLink(action.url(), self.req._(action.title),
+        return BoxLink(action.url(), self._cw._(action.title),
                        cls, self.boxitem_link_tooltip(action))
 
 
@@ -108,18 +105,18 @@
     rql  = None
 
     def to_display_rql(self):
-        assert self.rql is not None, self.id
+        assert self.rql is not None, self.__regid__
         return (self.rql,)
 
     def call(self, **kwargs):
         try:
-            rset = self.req.execute(*self.to_display_rql())
+            rset = self._cw.execute(*self.to_display_rql())
         except Unauthorized:
             # can't access to something in the query, forget this box
             return
         if len(rset) == 0:
             return
-        box = BoxWidget(self.req._(self.title), self.id)
+        box = BoxWidget(self._cw._(self.title), self.__regid__)
         for i, (teid, tname) in enumerate(rset):
             entity = rset.get_entity(i, 0)
             box.append(self.mk_action(tname, entity.absolute_url()))
@@ -132,14 +129,13 @@
     """
 
     def to_display_rql(self):
-        assert self.rql is not None, self.id
-        return (self.rql, {'x': self.req.user.eid}, 'x')
+        assert self.rql is not None, self.__regid__
+        return (self.rql, {'x': self._cw.user.eid}, 'x')
 
 
 class EntityBoxTemplate(BoxTemplate):
     """base class for boxes related to a single entity"""
     __select__ = BoxTemplate.__select__ & one_line_rset() & primary_view()
-    registered = accepts_compat(has_relation_compat(condition_compat(BoxTemplate.registered)))
     context = 'incontext'
 
     def call(self, row=0, col=0, **kwargs):
@@ -151,12 +147,12 @@
     __select__ = EntityBoxTemplate.__select__ & partial_has_related_entities()
 
     def cell_call(self, row, col, **kwargs):
-        entity = self.rset.get_entity(row, col)
-        limit = self.req.property_value('navigation.related-limit') + 1
+        entity = self.cw_rset.get_entity(row, col)
+        limit = self._cw.property_value('navigation.related-limit') + 1
         role = get_role(self)
         self.w(u'<div class="sideBox">')
         self.wview('sidebox', entity.related(self.rtype, role, limit=limit),
-                   title=display_name(self.req, self.rtype, role))
+                   title=display_name(self._cw, self.rtype, role))
         self.w(u'</div>')
 
 
@@ -169,9 +165,9 @@
     """
 
     def cell_call(self, row, col, view=None, **kwargs):
-        self.req.add_js('cubicweb.ajax.js')
-        entity = self.rset.get_entity(row, col)
-        box = SideBoxWidget(display_name(self.req, self.rtype), self.id)
+        self._cw.add_js('cubicweb.ajax.js')
+        entity = self.cw_rset.get_entity(row, col)
+        box = SideBoxWidget(display_name(self._cw, self.rtype), self.__regid__)
         related = self.related_boxitems(entity)
         unrelated = self.unrelated_boxitems(entity)
         box.extend(related)
@@ -181,7 +177,7 @@
         box.render(self.w)
 
     def div_id(self):
-        return self.id
+        return self.__regid__
 
     def box_item(self, entity, etarget, rql, label):
         """builds HTML link to edit relation between `entity` and `etarget`
@@ -222,12 +218,12 @@
             return entity.unrelated(self.rtype, self.etype, get_role(self)).entities()
         # in other cases, use vocabulary functions
         entities = []
-        form = self.vreg['forms'].select('edition', self.req, rset=self.rset,
-                                         row=self.row or 0)
+        form = self.vreg['forms'].select('edition', self._cw, rset=self.cw_rset,
+                                         row=self.cw_row or 0)
         field = form.field_by_name(self.rtype, get_role(self), entity.e_schema)
         for _, eid in form.form_field_vocabulary(field):
             if eid is not None:
-                rset = self.req.eid_rset(eid)
+                rset = self._cw.eid_rset(eid)
                 entities.append(rset.get_entity(0, 0))
         return entities
 
--- a/web/component.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/component.py	Wed Sep 30 18:57:42 2009 +0200
@@ -15,11 +15,10 @@
 
 from cubicweb import role
 from cubicweb.utils import merge_dicts
-from cubicweb.view import View, Component
+from cubicweb.view import Component
 from cubicweb.selectors import (
     paginated_rset, one_line_rset, primary_view, match_context_prop,
-    partial_has_related_entities, condition_compat, accepts_compat,
-    has_relation_compat)
+    partial_has_related_entities)
 
 
 class EntityVComponent(Component):
@@ -35,9 +34,8 @@
 
     __registry__ = 'contentnavigation'
     __select__ = one_line_rset() & primary_view() & match_context_prop()
-    registered = accepts_compat(has_relation_compat(condition_compat(View.registered)))
 
-    property_defs = {
+    cw_property_defs = {
         _('visible'):  dict(type='Boolean', default=True,
                             help=_('display the component or not')),
         _('order'):    dict(type='Int', default=99,
@@ -60,10 +58,10 @@
 
 class NavigationComponent(Component):
     """abstract base class for navigation components"""
-    id = 'navigation'
+    __regid__ = 'navigation'
     __select__ = paginated_rset()
 
-    property_defs = {
+    cw_property_defs = {
         _('visible'):  dict(type='Boolean', default=True,
                             help=_('display the component or not')),
         }
@@ -78,7 +76,7 @@
     no_next_page_link = u'&gt;&gt;'
 
     def __init__(self, req, rset, **kwargs):
-        super(NavigationComponent, self).__init__(req, rset, **kwargs)
+        super(NavigationComponent, self).__init__(req, rset=rset, **kwargs)
         self.starting_from = 0
         self.total = rset.rowcount
 
@@ -86,12 +84,12 @@
         try:
             return self._page_size
         except AttributeError:
-            page_size = self.extra_kwargs.get('page_size')
+            page_size = self.cw_extra_kwargs.get('page_size')
             if page_size is None:
-                if 'page_size' in self.req.form:
-                    page_size = int(self.req.form['page_size'])
+                if 'page_size' in self._cw.form:
+                    page_size = int(self._cw.form['page_size'])
                 else:
-                    page_size = self.req.property_value(self.page_size_property)
+                    page_size = self._cw.property_value(self.page_size_property)
             self._page_size = page_size
             return page_size
 
@@ -102,8 +100,8 @@
 
     def page_boundaries(self):
         try:
-            stop = int(self.req.form[self.stop_param]) + 1
-            start = int(self.req.form[self.start_param])
+            stop = int(self._cw.form[self.stop_param]) + 1
+            start = int(self._cw.form[self.start_param])
         except KeyError:
             start, stop = 0, self.page_size
         self.starting_from = start
@@ -119,13 +117,13 @@
         params = merge_dicts(params, {self.start_param : start,
                                       self.stop_param : stop,})
         if path == 'json':
-            rql = params.pop('rql', self.rset.printable_rql())
+            rql = params.pop('rql', self.cw_rset.printable_rql())
             # latest 'true' used for 'swap' mode
             url = 'javascript: replacePageChunk(%s, %s, %s, %s, true)' % (
                 dumps(params.get('divid', 'paginated-content')),
                 dumps(rql), dumps(params.pop('vid', None)), dumps(params))
         else:
-            url = self.build_url(path, **params)
+            url = self._cw.build_url(path, **params)
         return url
 
     def page_link(self, path, params, start, stop, content):
@@ -165,15 +163,15 @@
     def cell_call(self, row, col, view=None):
         rql = self.rql()
         if rql is None:
-            entity = self.rset.get_entity(row, col)
+            entity = self.cw_rset.get_entity(row, col)
             rset = entity.related(self.rtype, role(self))
         else:
-            eid = self.rset[row][col]
-            rset = self.req.execute(self.rql(), {'x': eid}, 'x')
+            eid = self.cw_rset[row][col]
+            rset = self._cw.execute(self.rql(), {'x': eid}, 'x')
         if not rset.rowcount:
             return
         self.w(u'<div class="%s">' % self.div_class())
-        self.wview(self.vid, rset, title=self.req._(self.title).capitalize())
+        self.wview(self.vid, rset, title=self._cw._(self.title).capitalize())
         self.w(u'</div>')
 
 
--- a/web/controller.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/controller.py	Wed Sep 30 18:57:42 2009 +0200
@@ -11,7 +11,7 @@
 import datetime
 
 from cubicweb import typed_eid
-from cubicweb.selectors import yes, require_group_compat
+from cubicweb.selectors import yes
 from cubicweb.appobject import AppObject
 from cubicweb.web import LOGGER, Redirect, RequestError
 
@@ -68,7 +68,6 @@
     """
     __registry__ = 'controllers'
     __select__ = yes()
-    registered = require_group_compat(AppObject.registered)
 
     def __init__(self, *args, **kwargs):
         self.appli = kwargs.pop('appli', None)
@@ -88,15 +87,15 @@
     def process_rql(self, rql):
         """execute rql if specified"""
         # XXX assigning to self really necessary?
-        self.rset = None
+        self.cw_rset = None
         if rql:
-            self.ensure_ro_rql(rql)
+            self.req.ensure_ro_rql(rql)
             if not isinstance(rql, unicode):
                 rql = unicode(rql, self.req.encoding)
-            pp = self.vreg['components'].select_object('magicsearch', self.req)
+            pp = self.vreg['components'].select_or_none('magicsearch', self.req)
             if pp is not None:
-                self.rset = pp.process_query(rql, self.req)
-        return self.rset
+                self.cw_rset = pp.process_query(rql, self.req)
+        return self.cw_rset
 
     def check_expected_params(self, params):
         """check that the given list of parameters are specified in the form
@@ -199,7 +198,7 @@
             path = self._edited_entity.rest_path()
         else:
             path = 'view'
-        url = self.build_url(path, **newparams)
+        url = self._cw.build_url(path, **newparams)
         url = append_url_params(url, self.req.form.get('__redirectparams'))
         raise Redirect(url)
 
@@ -224,7 +223,7 @@
         for redirectparam in NAV_FORM_PARAMETERS:
             if redirectparam in form:
                 newparams[redirectparam] = form[redirectparam]
-        raise Redirect(self.build_url(path, **newparams))
+        raise Redirect(self._cw.build_url(path, **newparams))
 
 
     def _return_to_lastpage(self, newparams):
@@ -236,7 +235,7 @@
         if '__redirectpath' in self.req.form:
             # if redirect path was explicitly specified in the form, use it
             path = self.req.form['__redirectpath']
-            url = self.build_url(path, **newparams)
+            url = self._cw.build_url(path, **newparams)
             url = append_url_params(url, self.req.form.get('__redirectparams'))
         else:
             url = self.req.last_visited_page()
--- a/web/facet.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/facet.py	Wed Sep 30 18:57:42 2009 +0200
@@ -64,8 +64,8 @@
 
 
 def get_facet(req, facetid, rqlst, mainvar):
-    return req.vreg['facets'].object_by_id(facetid, req, rqlst=rqlst,
-                                           filtered_variable=mainvar)
+    return req.vreg['facets'].select(facetid, req, rqlst=rqlst,
+                                     filtered_variable=mainvar)
 
 
 def filter_hiddens(w, **kwargs):
@@ -253,7 +253,7 @@
 class AbstractFacet(AppObject):
     __abstract__ = True
     __registry__ = 'facets'
-    property_defs = {
+    cw_property_defs = {
         _('visible'): dict(type='Boolean', default=True,
                            help=_('display the box or not')),
         _('order'):   dict(type='Int', default=99,
@@ -270,7 +270,7 @@
 
     def __init__(self, req, rset=None, rqlst=None, filtered_variable=None,
                  **kwargs):
-        super(AbstractFacet, self).__init__(req, rset, **kwargs)
+        super(AbstractFacet, self).__init__(req, rset=rset, **kwargs)
         assert rset is not None or rqlst is not None
         assert filtered_variable
         # facet retreived using `object_by_id` from an ajax call
@@ -282,7 +282,7 @@
         self.filtered_variable = filtered_variable
 
     def init_from_rset(self):
-        self.rqlst = self.rset.syntax_tree().children[0]
+        self.rqlst = self.cw_rset.syntax_tree().children[0]
 
     def init_from_form(self, rqlst):
         self.rqlst = rqlst
@@ -290,7 +290,7 @@
     @property
     def operator(self):
         # OR between selected values by default
-        return self.req.form.get(self.id + '_andor', 'OR')
+        return self._cw.form.get(self.__regid__ + '_andor', 'OR')
 
     def get_widget(self):
         """return the widget instance to use to display this facet
@@ -315,12 +315,12 @@
         if len(vocab) <= 1:
             return None
         wdg = FacetVocabularyWidget(self)
-        selected = frozenset(typed_eid(eid) for eid in self.req.list_form_param(self.id))
+        selected = frozenset(typed_eid(eid) for eid in self._cw.list_form_param(self.__regid__))
         for label, value in vocab:
             if value is None:
                 wdg.append(FacetSeparator(label))
             else:
-                wdg.append(FacetItem(self.req, label, value, value in selected))
+                wdg.append(FacetItem(self._cw, label, value, value in selected))
         return wdg
 
     def vocabulary(self):
@@ -339,7 +339,7 @@
 
     def rqlexec(self, rql, args=None, cachekey=None):
         try:
-            return self.req.execute(rql, args, cachekey)
+            return self._cw.execute(rql, args, cachekey)
         except Unauthorized:
             return []
 
@@ -360,7 +360,7 @@
 
     @property
     def title(self):
-        return display_name(self.req, self.rtype, form=self.role)
+        return display_name(self._cw, self.rtype, form=self.role)
 
     def vocabulary(self):
         """return vocabulary for this facet, eg a list of 2-uple (label, value)
@@ -376,7 +376,7 @@
             insert_attr_select_relation(rqlst, mainvar, self.rtype, self.role,
                                         self.target_attr, self.sortfunc, sort)
             try:
-                rset = self.rqlexec(rqlst.as_string(), self.rset.args, self.rset.cachekey)
+                rset = self.rqlexec(rqlst.as_string(), self.cw_rset.args, self.cw_rset.cachekey)
             except:
                 self.exception('error while getting vocabulary for %s, rql: %s',
                                self, rqlst.as_string())
@@ -400,7 +400,7 @@
 
     def rset_vocabulary(self, rset):
         if self.label_vid is None:
-            _ = self.req._
+            _ = self._cw._
             return [(_(label), eid) for eid, label in rset]
         if self.sortfunc is None:
             return sorted((entity.view(self.label_vid), entity.eid)
@@ -410,13 +410,13 @@
 
     @cached
     def support_and(self):
-        rschema = self.schema.rschema(self.rtype)
+        rschema = self._cw.vreg.schema.rschema(self.rtype)
         if self.role == 'subject':
             cardidx = 0
         else:
             cardidx = 1
         # XXX when called via ajax, no rset to compute possible types
-        possibletypes = self.rset and self.rset.column_types(0)
+        possibletypes = self.cw_rset and self.cw_rset.column_types(0)
         for subjtype, objtype in rschema.iter_rdefs():
             if possibletypes is not None:
                 if self.role == 'subject':
@@ -430,7 +430,7 @@
 
     def add_rql_restrictions(self):
         """add restriction for this facet into the rql syntax tree"""
-        value = self.req.form.get(self.id)
+        value = self._cw.form.get(self.__regid__)
         if not value:
             return
         mainvar = self.filtered_variable
@@ -469,7 +469,7 @@
             newvar = _prepare_vocabulary_rqlst(rqlst, mainvar, self.rtype, self.role)
             _set_orderby(rqlst, newvar, self.sortasc, self.sortfunc)
             try:
-                rset = self.rqlexec(rqlst.as_string(), self.rset.args, self.rset.cachekey)
+                rset = self.rqlexec(rqlst.as_string(), self.cw_rset.args, self.cw_rset.cachekey)
             except:
                 self.exception('error while getting vocabulary for %s, rql: %s',
                                self, rqlst.as_string())
@@ -479,7 +479,7 @@
         return self.rset_vocabulary(rset)
 
     def rset_vocabulary(self, rset):
-        _ = self.req._
+        _ = self._cw._
         return [(_(value), value) for value, in rset]
 
     def support_and(self):
@@ -487,7 +487,7 @@
 
     def add_rql_restrictions(self):
         """add restriction for this facet into the rql syntax tree"""
-        value = self.req.form.get(self.id)
+        value = self._cw.form.get(self.__regid__)
         if not value:
             return
         mainvar = self.filtered_variable
@@ -499,16 +499,16 @@
     """called by javascript to get a rql string from filter form"""
 
     def __init__(self, req):
-        self.req = req
+        self._cw = req
 
     def build_rql(self):#, tablefilter=False):
-        form = self.req.form
+        form = self._cw.form
         facetids = form['facets'].split(',')
         select = parse(form['baserql']).children[0] # XXX Union unsupported yet
         mainvar = filtered_variable(select)
         toupdate = []
         for facetid in facetids:
-            facet = get_facet(self.req, facetid, select, mainvar)
+            facet = get_facet(self._cw, facetid, select, mainvar)
             facet.add_rql_restrictions()
             if facet.needs_update:
                 toupdate.append(facetid)
@@ -529,10 +529,10 @@
         return self.wdgclass(self, min(values), max(values))
 
     def infvalue(self):
-        return self.req.form.get('%s_inf' % self.id)
+        return self._cw.form.get('%s_inf' % self.__regid__)
 
     def supvalue(self):
-        return self.req.form.get('%s_sup' % self.id)
+        return self._cw.form.get('%s_sup' % self.__regid__)
 
     def formatvalue(self, value):
         """format `value` before in order to insert it in the RQL query"""
@@ -571,20 +571,20 @@
 
     @property
     def title(self):
-        return display_name(self.req, self.rtype, self.role)
+        return display_name(self._cw, self.rtype, self.role)
 
     def support_and(self):
         return False
 
     def get_widget(self):
-        return CheckBoxFacetWidget(self.req, self,
+        return CheckBoxFacetWidget(self._cw, self,
                                    '%s:%s' % (self.rtype, self),
-                                   self.req.form.get(self.id))
+                                   self._cw.form.get(self.__regid__))
 
     def add_rql_restrictions(self):
         """add restriction for this facet into the rql syntax tree"""
         self.rqlst.set_distinct(True) # XXX
-        value = self.req.form.get(self.id)
+        value = self._cw.form.get(self.__regid__)
         if not value: # no value sent for this facet
             return
         var = self.rqlst.make_variable()
@@ -607,12 +607,12 @@
 
     def _render(self):
         title = xml_escape(self.facet.title)
-        facetid = xml_escape(self.facet.id)
+        facetid = xml_escape(self.facet.__regid__)
         self.w(u'<div id="%s" class="facet">\n' % facetid)
         self.w(u'<div class="facetTitle" cubicweb:facetName="%s">%s</div>\n' %
                (xml_escape(facetid), title))
         if self.facet.support_and():
-            _ = self.facet.req._
+            _ = self.facet._cw._
             self.w(u'''<select name="%s" class="radio facetOperator" title="%s">
   <option value="OR">%s</option>
   <option value="AND">%s</option>
@@ -637,7 +637,7 @@
 
     def _render(self):
         title = xml_escape(self.facet.title)
-        facetid = xml_escape(self.facet.id)
+        facetid = xml_escape(self.facet.__regid__)
         self.w(u'<div id="%s" class="facet">\n' % facetid)
         self.w(u'<div class="facetTitle" cubicweb:facetName="%s">%s</div>\n' %
                (facetid, title))
@@ -677,11 +677,11 @@
 
     def _render(self):
         facet = self.facet
-        facet.req.add_js('ui.slider.js')
-        facet.req.add_css('ui.all.css')
+        facet._cw.add_js('ui.slider.js')
+        facet._cw.add_css('ui.all.css')
         sliderid = make_uid('theslider')
-        facetid = xml_escape(self.facet.id)
-        facet.req.html_headers.add_onload(self.onload % {
+        facetid = xml_escape(self.facet.__regid__)
+        facet._cw.html_headers.add_onload(self.onload % {
             'sliderid': sliderid,
             'facetid': facetid,
             'minvalue': self.minvalue,
@@ -715,8 +715,8 @@
         super(DateFacetRangeWidget, self).__init__(facet,
                                                    datetime2ticks(minvalue),
                                                    datetime2ticks(maxvalue))
-        fmt = facet.req.property_value('ui.date-format')
-        facet.req.html_headers.define_var('DATE_FMT', fmt)
+        fmt = facet._cw.property_value('ui.date-format')
+        facet._cw.html_headers.define_var('DATE_FMT', fmt)
 
 
 class FacetItem(HTMLWidget):
@@ -725,7 +725,7 @@
     unselected_img = "no-check-no-border.png"
 
     def __init__(self, req, label, value, selected=False):
-        self.req = req
+        self._cw = req
         self.label = label
         self.value = value
         self.selected = selected
@@ -733,12 +733,12 @@
     def _render(self):
         if self.selected:
             cssclass = ' facetValueSelected'
-            imgsrc = self.req.datadir_url + self.selected_img
-            imgalt = self.req._('selected')
+            imgsrc = self._cw.datadir_url + self.selected_img
+            imgalt = self._cw._('selected')
         else:
             cssclass = ''
-            imgsrc = self.req.datadir_url + self.unselected_img
-            imgalt = self.req._('not selected')
+            imgsrc = self._cw.datadir_url + self.unselected_img
+            imgalt = self._cw._('not selected')
         self.w(u'<div class="facetValue facetCheckBox%s" cubicweb:value="%s">\n'
                % (cssclass, xml_escape(unicode(self.value))))
         self.w(u'<img src="%s" alt="%s"/>&#160;' % (imgsrc, imgalt))
@@ -751,23 +751,23 @@
     unselected_img = "black-uncheck.png"
 
     def __init__(self, req, facet, value, selected):
-        self.req = req
+        self._cw = req
         self.facet = facet
         self.value = value
         self.selected = selected
 
     def _render(self):
         title = xml_escape(self.facet.title)
-        facetid = xml_escape(self.facet.id)
+        facetid = xml_escape(self.facet.__regid__)
         self.w(u'<div id="%s" class="facet">\n' % facetid)
         if self.selected:
             cssclass = ' facetValueSelected'
-            imgsrc = self.req.datadir_url + self.selected_img
-            imgalt = self.req._('selected')
+            imgsrc = self._cw.datadir_url + self.selected_img
+            imgalt = self._cw._('selected')
         else:
             cssclass = ''
-            imgsrc = self.req.datadir_url + self.unselected_img
-            imgalt = self.req._('not selected')
+            imgsrc = self._cw.datadir_url + self.unselected_img
+            imgalt = self._cw._('not selected')
         self.w(u'<div class="facetValue facetCheckBox%s" cubicweb:value="%s">\n'
                % (cssclass, xml_escape(unicode(self.value))))
         self.w(u'<div class="facetCheckBoxWidget">')
--- a/web/form.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/form.py	Wed Sep 30 18:57:42 2009 +0200
@@ -18,70 +18,6 @@
 class FormViewMixIn(object):
     """abstract form view mix-in"""
     category = 'form'
-    controller = 'edit'
-    http_cache_manager = httpcache.NoHTTPCacheManager
-    add_to_breadcrumbs = False
-
-    def html_headers(self):
-        """return a list of html headers (eg something to be inserted between
-        <head> and </head> of the returned page
-
-        by default forms are neither indexed nor followed
-        """
-        return [NOINDEX, NOFOLLOW]
-
-    def linkable(self):
-        """override since forms are usually linked by an action,
-        so we don't want them to be listed by appli.possible_views
-        """
-        return False
-
-
-# XXX should disappear
-class FormMixIn(object):
-    """abstract form mix-in
-    XXX: you should inherit from this FIRST (obscure pb with super call)
-    """
-
-    def session_key(self):
-        """return the key that may be used to store / retreive data about a
-        previous post which failed because of a validation error
-        """
-        return '%s#%s' % (self.req.url(), self.domid)
-
-    def __init__(self, req, rset, **kwargs):
-        super(FormMixIn, self).__init__(req, rset, **kwargs)
-        self.restore_previous_post(self.session_key())
-
-    def restore_previous_post(self, sessionkey):
-        # get validation session data which may have been previously set.
-        # deleting validation errors here breaks form reloading (errors are
-        # no more available), they have to be deleted by application's publish
-        # method on successful commit
-        forminfo = self.req.get_session_data(sessionkey, pop=True)
-        if forminfo:
-            # XXX remove req.data assigment once cw.web.widget is killed
-            self.req.data['formvalues'] = self.form_previous_values = forminfo['values']
-            self.req.data['formerrors'] = self.form_valerror = forminfo['errors']
-            self.req.data['displayederrors'] = self.form_displayed_errors = set()
-            # if some validation error occured on entity creation, we have to
-            # get the original variable name from its attributed eid
-            foreid = self.form_valerror.entity
-            for var, eid in forminfo['eidmap'].items():
-                if foreid == eid:
-                    self.form_valerror.eid = var
-                    break
-            else:
-                self.form_valerror.eid = foreid
-        else:
-            self.form_previous_values = {}
-            self.form_valerror = None
-
-    # XXX deprecated with new form system. Should disappear
-
-    domid = 'entityForm'
-    category = 'form'
-    controller = 'edit'
     http_cache_manager = httpcache.NoHTTPCacheManager
     add_to_breadcrumbs = False
 
@@ -100,86 +36,6 @@
         return False
 
 
-    def button(self, label, klass='validateButton', tabindex=None, **kwargs):
-        if tabindex is None:
-            tabindex = self.req.next_tabindex()
-        return tags.input(value=label, klass=klass, **kwargs)
-
-    def action_button(self, label, onclick=None, __action=None, **kwargs):
-        if onclick is None:
-            onclick = "postForm('__action_%s', \'%s\', \'%s\')" % (
-                __action, label, self.domid)
-        return self.button(label, onclick=onclick, **kwargs)
-
-    def button_ok(self, label=None, type='submit', name='defaultsubmit',
-                  **kwargs):
-        label = self.req._(label or stdmsgs.BUTTON_OK).capitalize()
-        return self.button(label, name=name, type=type, **kwargs)
-
-    def button_apply(self, label=None, type='button', **kwargs):
-        label = self.req._(label or stdmsgs.BUTTON_APPLY).capitalize()
-        return self.action_button(label, __action='apply', type=type, **kwargs)
-
-    def button_delete(self, label=None, type='button', **kwargs):
-        label = self.req._(label or stdmsgs.BUTTON_DELETE).capitalize()
-        return self.action_button(label, __action='delete', type=type, **kwargs)
-
-    def button_cancel(self, label=None, type='button', **kwargs):
-        label = self.req._(label or stdmsgs.BUTTON_CANCEL).capitalize()
-        return self.action_button(label, __action='cancel', type=type, **kwargs)
-
-    def button_reset(self, label=None, type='reset', name='__action_cancel',
-                     **kwargs):
-        label = self.req._(label or stdmsgs.BUTTON_CANCEL).capitalize()
-        return self.button(label, type=type, **kwargs)
-
-    def need_multipart(self, entity, categories=('primary', 'secondary')):
-        """return a boolean indicating if form's enctype should be multipart
-        """
-        for rschema, _, x in entity.relations_by_category(categories):
-            if entity.get_widget(rschema, x).need_multipart:
-                return True
-        # let's find if any of our inlined entities needs multipart
-        for rschema, targettypes, x in entity.relations_by_category('inlineview'):
-            assert len(targettypes) == 1, \
-                   "I'm not able to deal with several targets and inlineview"
-            ttype = targettypes[0]
-            inlined_entity = self.vreg.etype_class(ttype)(self.req, None, None)
-            for irschema, _, x in inlined_entity.relations_by_category(categories):
-                if inlined_entity.get_widget(irschema, x).need_multipart:
-                    return True
-        return False
-
-    def error_message(self):
-        """return formatted error message
-
-        This method should be called once inlined field errors has been consumed
-        """
-        errex = self.req.data.get('formerrors') or self.form_valerror
-        # get extra errors
-        if errex is not None:
-            errormsg = self.req._('please correct the following errors:')
-            displayed = self.req.data.get('displayederrors') or self.form_displayed_errors
-            errors = sorted((field, err) for field, err in errex.errors.items()
-                            if not field in displayed)
-            if errors:
-                if len(errors) > 1:
-                    templstr = '<li>%s</li>\n'
-                else:
-                    templstr = '&#160;%s\n'
-                for field, err in errors:
-                    if field is None:
-                        errormsg += templstr % err
-                    else:
-                        errormsg += templstr % '%s: %s' % (self.req._(field), err)
-                if len(errors) > 1:
-                    errormsg = '<ul>%s</ul>' % errormsg
-            return u'<div class="errorMessage">%s</div>' % errormsg
-        return u''
-
-
-###############################################################################
-
 class metafieldsform(type):
     """metaclass for FieldsForm to retrieve fields defined as class attributes
     and put them into a single ordered list: '_fields_'.
@@ -204,12 +60,16 @@
     found
     """
 
-class Form(FormMixIn, AppObject):
+class Form(AppObject):
     __metaclass__ = metafieldsform
     __registry__ = 'forms'
 
     parent_form = None
 
+    def __init__(self, req, rset, **kwargs):
+        super(Form, self).__init__(req, rset=rset, **kwargs)
+        self.restore_previous_post(self.session_key())
+
     @property
     def root_form(self):
         """return the root form"""
@@ -262,3 +122,33 @@
         field = cls_or_self.field_by_name(name, role)
         fields = cls_or_self._fieldsattr()
         fields.insert(fields.index(field)+1, new_field)
+
+    def session_key(self):
+        """return the key that may be used to store / retreive data about a
+        previous post which failed because of a validation error
+        """
+        return '%s#%s' % (self._cw.url(), self.domid)
+
+    def restore_previous_post(self, sessionkey):
+        # get validation session data which may have been previously set.
+        # deleting validation errors here breaks form reloading (errors are
+        # no more available), they have to be deleted by application's publish
+        # method on successful commit
+        forminfo = self._cw.get_session_data(sessionkey, pop=True)
+        if forminfo:
+            # XXX remove req.data assigment once cw.web.widget is killed
+            self._cw.data['formvalues'] = self.form_previous_values = forminfo['values']
+            self._cw.data['formerrors'] = self.form_valerror = forminfo['errors']
+            self._cw.data['displayederrors'] = self.form_displayed_errors = set()
+            # if some validation error occured on entity creation, we have to
+            # get the original variable name from its attributed eid
+            foreid = self.form_valerror.entity
+            for var, eid in forminfo['eidmap'].items():
+                if foreid == eid:
+                    self.form_valerror.eid = var
+                    break
+            else:
+                self.form_valerror.eid = foreid
+        else:
+            self.form_previous_values = {}
+            self.form_valerror = None
--- a/web/formfields.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/formfields.py	Wed Sep 30 18:57:42 2009 +0200
@@ -195,7 +195,7 @@
                 except TypeError:
                     warn('[3.3] vocabulary method (eg field.choices) should now take '
                          'the form instance as argument', DeprecationWarning)
-                    vocab = self.choices(req=form.req)
+                    vocab = self.choices(req=form._cw)
             else:
                 vocab = self.choices
             if vocab and not isinstance(vocab[0], (list, tuple)):
@@ -203,7 +203,7 @@
         else:
             vocab = form.form_field_vocabulary(self)
         if self.internationalizable:
-            vocab = [(form.req._(label), value) for label, value in vocab]
+            vocab = [(form._cw._(label), value) for label, value in vocab]
         if self.sort:
             vocab = vocab_sort(vocab)
         return vocab
@@ -214,6 +214,21 @@
         """
         pass
 
+    def process_form_value(self, form):
+        """process posted form and return correctly typed value"""
+        widget = self.get_widget(form)
+        return widget.process_field_data(form, self)
+
+    def process_posted(self, form):
+        for field in self.actual_fields(form):
+            if field is self:
+                yield field.name, field.process_form_value(form)
+            else:
+                # recursive function: we might have compound fields
+                # of compound fields (of compound fields of ...)
+                for fieldname, value in field.process_posted(form):
+                    yield fieldname, value
+
 
 class StringField(Field):
     widget = TextArea
@@ -269,7 +284,7 @@
             return self.format_field
         # we have to cache generated field since it's use as key in the
         # context dictionnary
-        req = form.req
+        req = form._cw
         try:
             return req.data[self]
         except KeyError:
@@ -286,6 +301,7 @@
                 fkwargs['choices'] = fcstr.vocabulary(form=form)
                 fkwargs['internationalizable'] = True
                 fkwargs['initial'] = lambda f: f.form_field_format(self)
+            fkwargs['eidparam'] = self.eidparam
             field = StringField(name=self.name + '_format', **fkwargs)
             req.data[self] = field
             return field
@@ -300,7 +316,7 @@
         """return True if fckeditor should be used to edit entity's attribute named
         `attr`, according to user preferences
         """
-        if form.req.use_fckeditor():
+        if form._cw.use_fckeditor():
             return form.form_field_format(self) == 'text/html'
         return False
 
@@ -342,9 +358,9 @@
             divid = '%s-advanced' % form.context[self]['name']
             wdgs.append(u'<a href="%s" title="%s"><img src="%s" alt="%s"/></a>' %
                         (xml_escape(uilib.toggle_action(divid)),
-                         form.req._('show advanced fields'),
-                         xml_escape(form.req.build_url('data/puce_down.png')),
-                         form.req._('show advanced fields')))
+                         form._cw._('show advanced fields'),
+                         xml_escape(form._cw.build_url('data/puce_down.png')),
+                         form._cw._('show advanced fields')))
             wdgs.append(u'<div id="%s" class="hidden">' % divid)
             if self.name_field:
                 wdgs.append(self.render_subfield(form, self.name_field, renderer))
@@ -358,7 +374,7 @@
             wdgs.append(u'<br/>')
             wdgs.append(tags.input(name=u'%s__detach' % form.context[self]['name'],
                                    type=u'checkbox'))
-            wdgs.append(form.req._('detach attached file'))
+            wdgs.append(form._cw._('detach attached file'))
         return u'\n'.join(wdgs)
 
     def render_subfield(self, form, field, renderer):
@@ -367,6 +383,25 @@
                 + renderer.render_help(form, field)
                 + u'<br/>')
 
+    def process_form_value(self, form):
+        posted = form._cw.form
+        value = posted.get(form.form_field_name(self))
+        formkey = form.form_field_name(self)
+        if ('%s__detach' % form.context[self]['name']) in posted:
+            # drop current file value
+            value = None
+        # no need to check value when nor explicit detach nor new file
+        # submitted, since it will think the attribute is not modified
+        elif value:
+            filename, _, stream = value
+            # value is a  3-uple (filename, mimetype, stream)
+            value = Binary(stream.read())
+            if not val.getvalue(): # usually an unexistant file
+                value = None
+            else:
+                value.filename = filename
+        return value
+
 
 class EditableFileField(FileField):
     editable_formats = ('text/plain', 'text/html', 'text/rest')
@@ -383,13 +418,13 @@
                     pass
                 else:
                     if not self.required:
-                        msg = form.req._(
+                        msg = form._cw._(
                             'You can either submit a new file using the browse button above'
                             ', or choose to remove already uploaded file by checking the '
                             '"detach attached file" check-box, or edit file content online '
                             'with the widget below.')
                     else:
-                        msg = form.req._(
+                        msg = form._cw._(
                             'You can either submit a new file using the browse button above'
                             ', or edit file content online with the widget below.')
                     wdgs.append(u'<p><b>%s</b></p>' % msg)
@@ -397,6 +432,14 @@
                     # XXX restore form context?
         return '\n'.join(wdgs)
 
+    def process_form_value(self, form):
+        value = form._cw.form.get(form.form_field_name(self))
+        if isinstance(value, unicode):
+            # file modified using a text widget
+            encoding = form.form_field_encoding(self)
+            return Binary(value.encode(encoding))
+        return super(EditableFileField, self).process_form_value(form)
+
 
 class IntField(Field):
     def __init__(self, min=None, max=None, **kwargs):
@@ -407,6 +450,8 @@
             self.widget.attrs.setdefault('size', 5)
             self.widget.attrs.setdefault('maxlength', 15)
 
+    def process_form_value(self, form):
+        return int(Field.process_form_value(self, form))
 
 class BooleanField(Field):
     widget = Radio
@@ -414,8 +459,10 @@
     def vocabulary(self, form):
         if self.choices:
             return self.choices
-        return [(form.req._('yes'), '1'), (form.req._('no'), '')]
+        return [(form._cw._('yes'), '1'), (form._cw._('no'), '')]
 
+    def process_form_value(self, form):
+        return bool(Field.process_form_value(self, form))
 
 class FloatField(IntField):
     def format_single_value(self, req, value):
@@ -427,6 +474,8 @@
     def render_example(self, req):
         return self.format_single_value(req, 1.234)
 
+    def process_form_value(self, form):
+        return float(Field.process_form_value(self, form))
 
 class DateField(StringField):
     format_prop = 'ui.date-format'
@@ -438,26 +487,39 @@
     def render_example(self, req):
         return self.format_single_value(req, datetime.now())
 
+    def process_form_value(self, form):
+        # widget is supposed to return a date as a correctly formatted string
+        date = Field.process_form_value(self, form)
+        # but for some widgets, it might be simpler to return date objects
+        # directly, so handle that case :
+        if isinstance(date, basestring):
+            date = form.parse_date(wdgdate, 'Date')
+        return date
 
 class DateTimeField(DateField):
     format_prop = 'ui.datetime-format'
 
+    def process_form_value(self, form):
+        # widget is supposed to return a date as a correctly formatted string
+        date = Field.process_form_value(self, form)
+        # but for some widgets, it might be simpler to return date objects
+        # directly, so handle that case :
+        if isinstance(date, basestring):
+            date = form.parse_datetime(wdgdate, 'Datetime')
+        return date
 
 class TimeField(DateField):
     format_prop = 'ui.time-format'
     widget = TextInput
 
-
-class HiddenInitialValueField(Field):
-    def __init__(self, visible_field):
-        name = 'edit%s-%s' % (visible_field.role[0], visible_field.name)
-        super(HiddenInitialValueField, self).__init__(
-            name=name, widget=HiddenInput, eidparam=True)
-        self.visible_field = visible_field
-
-    def format_single_value(self, req, value):
-        return self.visible_field.format_single_value(req, value)
-
+    def process_form_value(self, form):
+        # widget is supposed to return a date as a correctly formatted string
+        time = Field.process_form_value(self, form)
+        # but for some widgets, it might be simpler to return time objects
+        # directly, so handle that case :
+        if isinstance(time, basestring):
+            time = form.parse_time(wdgdate, 'Time')
+        return time
 
 class RelationField(Field):
     # XXX (syt): iirc, we originaly don't sort relation vocabulary since we want
@@ -478,7 +540,7 @@
 
     def vocabulary(self, form):
         entity = form.edited_entity
-        req = entity.req
+        req = entity._cw
         # first see if its specified by __linkto form parameters
         linkedto = entity.linked_to(self.name, self.role)
         if linkedto:
@@ -539,6 +601,7 @@
         kwargs['label'] = (eschema.type + '_object', rschema.type)
     else:
         kwargs['label'] = (eschema.type, rschema.type)
+    kwargs['eidparam'] = True
     kwargs.setdefault('help', help)
     if rschema.is_final():
         if skip_meta_attr and rschema in eschema.meta_attributes():
--- a/web/formwidgets.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/formwidgets.py	Wed Sep 30 18:57:42 2009 +0200
@@ -11,7 +11,7 @@
 from warnings import warn
 
 from cubicweb.common import tags, uilib
-from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE
+from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE, ProcessFormError
 
 
 class FieldWidget(object):
@@ -39,9 +39,9 @@
     def add_media(self, form):
         """adds media (CSS & JS) required by this widget"""
         if self.needs_js:
-            form.req.add_js(self.needs_js)
+            form._cw.add_js(self.needs_js)
         if self.needs_css:
-            form.req.add_css(self.needs_css)
+            form._cw.add_css(self.needs_css)
 
     def render(self, form, field, renderer):
         """render the widget for the given `field` of `form`.
@@ -60,9 +60,13 @@
         if self.setdomid:
             attrs['id'] = form.context[field]['id']
         if self.settabindex and not 'tabindex' in attrs:
-            attrs['tabindex'] = form.req.next_tabindex()
+            attrs['tabindex'] = form._cw.next_tabindex()
         return name, values, attrs
 
+    def process_field_data(self, form, field):
+        formkey = form.form_field_name(field)
+        posted = form._cw.form
+        return posted.get(formkey)
 
 class Input(FieldWidget):
     """abstract widget class for <input> tag based widgets"""
@@ -110,15 +114,29 @@
                   '<br/>',
                   tags.input(name=confirmname, value=values[0], type=self.type,
                              **attrs),
-                  '&#160;', tags.span(form.req._('confirm password'),
+                  '&#160;', tags.span(form._cw._('confirm password'),
                                       **{'class': 'emphasis'})]
         return u'\n'.join(inputs)
 
+    def process_field_data(self, form, field):
+        passwd1 = super(PasswordInput, self).process_field_data(form, field)
+        fieldname = form.form_field_name(field)
+        passwd2 = form._cw.form[fieldname+'-confirm']
+        if passwd1 == passwd2:
+            if passwd1 is None:
+                return None
+            return passwd1.encode('utf-8')
+        raise ProcessFormError(form._cw._("password and confirmation don't match"))
 
 class PasswordSingleInput(Input):
     """<input type='password'> without a confirmation field"""
     type = 'password'
 
+    def process_field_data(self, form, field):
+        value = super(PasswordSingleInput, self).process_field_data(form, field)
+        if value is not None:
+            return value.encode('utf-8')
+        return value
 
 class FileInput(Input):
     """<input type='file'>"""
@@ -174,7 +192,7 @@
         self.attrs['cubicweb:type'] = 'wysiwyg'
 
     def render(self, form, field, renderer):
-        form.req.fckeditor_config()
+        form._cw.fckeditor_config()
         return super(FCKEditor, self).render(form, field, renderer)
 
 
@@ -275,9 +293,9 @@
         actual_fields = field.fields
         assert len(actual_fields) == 2
         return u'<div>%s %s %s %s</div>' % (
-            form.req._('from_interval_start'),
+            form._cw._('from_interval_start'),
             actual_fields[0].render(form, renderer),
-            form.req._('to_interval_end'),
+            form._cw._('to_interval_end'),
             actual_fields[1].render(form, renderer),
             )
 
@@ -324,7 +342,7 @@
 
     def render(self, form, field, renderer):
         txtwidget = super(DateTimePicker, self).render(form, field, renderer)
-        self.add_localized_infos(form.req)
+        self.add_localized_infos(form._cw)
         cal_button = self._render_calendar_popup(form, field)
         return txtwidget + cal_button
 
@@ -338,8 +356,8 @@
         return (u"""<a onclick="toggleCalendar('%s', '%s', %s, %s);" class="calhelper">
 <img src="%s" title="%s" alt="" /></a><div class="calpopup hidden" id="%s"></div>"""
                 % (helperid, inputid, year, month,
-                   form.req.external_resource('CALENDAR_ICON'),
-                   form.req._('calendar'), helperid) )
+                   form._cw.external_resource('CALENDAR_ICON'),
+                   form._cw._('calendar'), helperid) )
 
 
 
@@ -401,8 +419,8 @@
             fname = entity.autocomplete_initfuncs[field.name]
         else:
             fname = self.autocomplete_initfunc
-        return entity.req.build_url('json', fname=fname, mode='remote',
-                                    pageid=entity.req.pageid)
+        return entity._cw.build_url('json', fname=fname, mode='remote',
+                                    pageid=entity._cw.pageid)
 
 
 class StaticFileAutoCompletionWidget(AutoCompletionWidget):
@@ -415,7 +433,7 @@
             fname = entity.autocomplete_initfuncs[field.name]
         else:
             fname = self.autocomplete_initfunc
-        return entity.req.datadir_url + fname
+        return entity._cw.datadir_url + fname
 
 
 class RestrictedAutoCompletionWidget(AutoCompletionWidget):
@@ -462,7 +480,7 @@
         self.attrs.setdefault('klass', 'validateButton')
 
     def render(self, form, field=None, renderer=None):
-        label = form.req._(self.label)
+        label = form._cw._(self.label)
         attrs = self.attrs.copy()
         if self.cwaction:
             assert self.onclick is None
@@ -475,7 +493,7 @@
             if self.setdomid:
                 attrs['id'] = self.name
         if self.settabindex and not 'tabindex' in attrs:
-            attrs['tabindex'] = form.req.next_tabindex()
+            attrs['tabindex'] = form._cw.next_tabindex()
         return tags.input(value=label, type=self.type, **attrs)
 
 
@@ -505,8 +523,8 @@
         self.label = label
 
     def render(self, form, field=None, renderer=None):
-        label = form.req._(self.label)
-        imgsrc = form.req.external_resource(self.imgressource)
+        label = form._cw._(self.label)
+        imgsrc = form._cw.external_resource(self.imgressource)
         return '<a id="%(domid)s" href="%(href)s">'\
                '<img src="%(imgsrc)s" alt="%(label)s"/>%(label)s</a>' % {
             'label': label, 'imgsrc': imgsrc,
--- a/web/httpcache.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/httpcache.py	Wed Sep 30 18:57:42 2009 +0200
@@ -17,8 +17,8 @@
     """default cache manager: set no-cache cache control policy"""
     def __init__(self, view):
         self.view = view
-        self.req = view.req
-        self.rset = view.rset
+        self.req = view._cw
+        self.cw_rset = view.cw_rset
 
     def set_headers(self):
         self.req.set_header('Cache-control', 'no-cache')
@@ -42,7 +42,7 @@
     date_format = "%a, %d %b %Y %H:%M:%S GMT"
 
     def etag(self):
-        return self.view.id + '/' + ','.join(sorted(self.req.user.groups))
+        return self.view.__regid__ + '/' + ','.join(sorted(self.req.user.groups))
 
     def max_age(self):
         # 0 to actually force revalidation
@@ -72,12 +72,12 @@
       with a modification time to consider) using the `last_modified` method
     """
     def etag(self):
-        if self.rset is None or len(self.rset) == 0: # entity startup view for instance
+        if self.cw_rset is None or len(self.cw_rset) == 0: # entity startup view for instance
             return super(EntityHTTPCacheManager, self).etag()
-        if len(self.rset) > 1:
+        if len(self.cw_rset) > 1:
             raise NoEtag()
         etag = super(EntityHTTPCacheManager, self).etag()
-        eid = self.rset[0][0]
+        eid = self.cw_rset[0][0]
         if self.req.user.owns(eid):
             etag += ',owners'
         return str(eid) + '/' + etag
@@ -108,7 +108,7 @@
     # XXX check view module's file modification time in dev mod ?
     ctime = datetime.utcnow()
     if self.cache_max_age:
-        mtime = self.req.header_if_modified_since()
+        mtime = self._cw.header_if_modified_since()
         if mtime:
             tdelta = (ctime - mtime)
             if tdelta.days * 24*60*60 + tdelta.seconds > self.cache_max_age:
--- a/web/request.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/request.py	Wed Sep 30 18:57:42 2009 +0200
@@ -15,11 +15,12 @@
 from urlparse import urlsplit
 from itertools import count
 
+from simplejson import dumps
+
 from rql.utils import rqlvar_maker
 
 from logilab.common.decorators import cached
 from logilab.common.deprecation import deprecated
-
 from logilab.mtconverter import xml_escape
 
 from cubicweb.dbapi import DBAPIRequest
@@ -85,9 +86,16 @@
         self.next_tabindex = self.tabindexgen.next
         # page id, set by htmlheader template
         self.pageid = None
-        self.varmaker = rqlvar_maker()
         self.datadir_url = self._datadir_url()
 
+    @property
+    def varmaker(self):
+        varmaker = self.get_page_data('rql_varmaker')
+        if varmaker is None:
+            varmaker = rqlvar_maker()
+            self.set_page_data('rql_varmaker', varmaker)
+        return varmaker
+
     def set_connection(self, cnx, user=None):
         """method called by the session handler when the user is authenticated
         or an anonymous connection is open
@@ -258,6 +266,24 @@
             return breadcrumbs.pop()
         return self.base_url()
 
+    def user_rql_callback(self, args, msg=None):
+        """register a user callback to execute some rql query and return an url
+        to call it ready to be inserted in html
+        """
+        def rqlexec(req, rql, args=None, key=None):
+            req.execute(rql, args, key)
+        return self.user_callback(rqlexec, args, msg)
+
+    def user_callback(self, cb, args, msg=None, nonify=False):
+        """register the given user callback and return an url to call it ready to be
+        inserted in html
+        """
+        self.add_js('cubicweb.ajax.js')
+        cbname = self.register_onetime_callback(cb, *args)
+        msg = dumps(msg or '')
+        return "javascript:userCallbackThenReloadPage('%s', %s)" % (
+            cbname, msg)
+
     def register_onetime_callback(self, func, *args):
         cbname = 'cb_%s' % (
             sha.sha('%s%s%s%s' % (time.time(), func.__name__,
@@ -630,7 +656,7 @@
                            auth, ex.__class__.__name__, ex)
         return None, None
 
-    @deprecated("use parse_accept_header('Accept-Language')")
+    @deprecated("[3.4] use parse_accept_header('Accept-Language')")
     def header_accept_language(self):
         """returns an ordered list of preferred languages"""
         return [value.split('-')[0] for value in
--- a/web/test/test_views.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/test/test_views.py	Wed Sep 30 18:57:42 2009 +0200
@@ -6,7 +6,7 @@
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
-from cubicweb.devtools.testlib import WebTest, AutomaticWebTest
+from cubicweb.devtools.testlib import CubicWebTC, AutoPopulateTest, AutomaticWebTest
 from cubicweb.view import AnyRsetView
 
 AutomaticWebTest.application_rql = [
@@ -15,7 +15,7 @@
     'Any COUNT(X) WHERE X is CWUser',
     ]
 
-class ComposityCopy(WebTest):
+class ComposityCopy(CubicWebTC):
 
     def test_regr_copy_view(self):
         """regression test: make sure we can ask a copy of a
@@ -27,14 +27,14 @@
 
 
 class SomeView(AnyRsetView):
-    id = 'someview'
+    __regid__ = 'someview'
 
     def call(self):
-        self.req.add_js('spam.js')
-        self.req.add_js('spam.js')
+        self._cw.add_js('spam.js')
+        self._cw.add_js('spam.js')
 
 
-class ManualWebTests(WebTest):
+class ManualCubicWebTCs(AutoPopulateTest):
     def setup_database(self):
         self.auto_populate(10)
 
@@ -59,7 +59,7 @@
 
 
 
-class ExplicitViewsTest(WebTest):
+class ExplicitViewsTest(CubicWebTC):
 
     def test_unrelateddivs(self):
         rset = self.execute('Any X WHERE X is CWUser, X login "admin"')
--- a/web/test/unittest_application.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/test/unittest_application.py	Wed Sep 30 18:57:42 2009 +0200
@@ -14,7 +14,7 @@
 from logilab.common.testlib import TestCase, unittest_main
 from logilab.common.decorators import clear_cache
 
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.devtools.fake import FakeRequest
 from cubicweb.web import Redirect, AuthenticationError, ExplicitLogin, INTERNAL_FIELD_VALUE
 from cubicweb.web.views.basecontrollers import ViewController
@@ -37,25 +37,25 @@
 class FakeController(ViewController):
 
     def __init__(self, form=None):
-        self.req = FakeRequest()
-        self.req.form = form or {}
-        self._cursor = self.req.cursor = MockCursor()
+        self._cw = FakeRequest()
+        self._cw.form = form or {}
+        self._cursor = self._cw.cursor = MockCursor()
 
     def new_cursor(self):
-        self._cursor = self.req.cursor = MockCursor()
+        self._cursor = self._cw.cursor = MockCursor()
 
     def set_form(self, form):
-        self.req.form = form
+        self._cw.form = form
 
 
 class RequestBaseTC(TestCase):
     def setUp(self):
-        self.req = FakeRequest()
+        self._cw = FakeRequest()
 
 
     def test_list_arg(self):
         """tests the list_arg() function"""
-        list_arg = self.req.list_form_param
+        list_arg = self._cw.list_form_param
         self.assertEquals(list_arg('arg3', {}), [])
         d = {'arg1' : "value1",
              'arg2' : ('foo', INTERNAL_FIELD_VALUE,),
@@ -69,8 +69,8 @@
 
 
     def test_from_controller(self):
-        self.req.vreg['controllers'] = {'view': 1, 'login': 1}
-        self.assertEquals(self.req.from_controller(), 'view')
+        self._cw.vreg['controllers'] = {'view': 1, 'login': 1}
+        self.assertEquals(self._cw.from_controller(), 'view')
         req = FakeRequest(url='project?vid=list')
         req.vreg['controllers'] = {'view': 1, 'login': 1}
         # this assertion is just to make sure that relative_path can be
@@ -123,7 +123,7 @@
 
 
         self.ctrl.new_cursor()
-        self.ctrl.req.form = {'__linkto' : 'works_for:12_13_14:object'}
+        self.ctrl._cw.form = {'__linkto' : 'works_for:12_13_14:object'}
         self.ctrl.execute_linkto(eid=8)
         self.assertEquals(self.ctrl._cursor.executed,
                           ['SET Y works_for X WHERE X eid 8, Y eid %s' % i
@@ -137,7 +137,7 @@
                            for i in (12, 13, 14)])
 
 
-class ApplicationTC(EnvBasedTC):
+class ApplicationTC(CubicWebTC):
     def setUp(self):
         super(ApplicationTC, self).setUp()
         def raise_hdlr(*args, **kwargs):
--- a/web/test/unittest_breadcrumbs.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/test/unittest_breadcrumbs.py	Wed Sep 30 18:57:42 2009 +0200
@@ -1,6 +1,6 @@
-from cubicweb.devtools.testlib import WebTest
+from cubicweb.devtools.testlib import CubicWebTC
 
-class BreadCrumbsTC(WebTest):
+class BreadCrumbsTC(CubicWebTC):
 
     def test_base(self):
         f1 = self.add_entity('Folder', name=u'par&ent')
--- a/web/test/unittest_controller.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/test/unittest_controller.py	Wed Sep 30 18:57:42 2009 +0200
@@ -7,37 +7,35 @@
 
 from logilab.common.testlib import unittest_main
 
-from cubicweb.devtools import apptest
+from cubicweb.devtools import testlib
 
-class BaseControllerTC(apptest.ControllerTC):
+class BaseControllerTC(testlib.CubicWebTC):
 
     def test_parse_datetime_ok(self):
-        self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24 12:18'),
-                              datetime)
-        self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24'),
-                              date)
-        self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24 12:18', 'Datetime'),
-                              datetime)
-        self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24', 'Datetime'),
-                              datetime)
-        self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24', 'Date'),
-                              date)
-        self.assertIsInstance(self.ctrl.parse_datetime('12:18', 'Time'),
-                              time)
+        ctrl = self.vreg['controllers'].select('view', self.request())
+        pd = ctrl._cw.parse_datetime
+        self.assertIsInstance(pd('2006/06/24 12:18'), datetime)
+        self.assertIsInstance(pd('2006/06/24'), date)
+        self.assertIsInstance(pd('2006/06/24 12:18', 'Datetime'), datetime)
+        self.assertIsInstance(pd('2006/06/24', 'Datetime'), datetime)
+        self.assertIsInstance(pd('2006/06/24', 'Date'), date)
+        self.assertIsInstance(pd('12:18', 'Time'), time)
 
     def test_parse_datetime_ko(self):
+        ctrl = self.vreg['controllers'].select('view', self.request())
+        pd = ctrl._cw.parse_datetime
         self.assertRaises(ValueError,
-                          self.ctrl.parse_datetime, '2006/06/24 12:188', 'Datetime')
+                          pd, '2006/06/24 12:188', 'Datetime')
         self.assertRaises(ValueError,
-                          self.ctrl.parse_datetime, '2006/06/240', 'Datetime')
+                          pd, '2006/06/240', 'Datetime')
         self.assertRaises(ValueError,
-                          self.ctrl.parse_datetime, '2006/06/24 12:18', 'Date')
+                          pd, '2006/06/24 12:18', 'Date')
         self.assertRaises(ValueError,
-                          self.ctrl.parse_datetime, '2006/24/06', 'Date')
+                          pd, '2006/24/06', 'Date')
         self.assertRaises(ValueError,
-                          self.ctrl.parse_datetime, '2006/06/240', 'Date')
+                          pd, '2006/06/240', 'Date')
         self.assertRaises(ValueError,
-                          self.ctrl.parse_datetime, '12:188', 'Time')
+                          pd, '12:188', 'Time')
 
 if __name__ == '__main__':
     unittest_main()
--- a/web/test/unittest_form.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/test/unittest_form.py	Wed Sep 30 18:57:42 2009 +0200
@@ -12,7 +12,7 @@
 from logilab.common.testlib import unittest_main, mock_object
 
 from cubicweb import Binary
-from cubicweb.devtools.testlib import WebTest
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.web.formfields import (IntField, StringField, RichTextField,
                                      DateTimeField, DateTimePicker,
                                      FileField, EditableFileField)
@@ -22,7 +22,7 @@
 from cubicweb.web.views.formrenderers import FormRenderer
 
 
-class FieldsFormTC(WebTest):
+class FieldsFormTC(CubicWebTC):
 
     def test_form_field_format(self):
         form = FieldsForm(self.request(), None)
@@ -32,7 +32,7 @@
         self.assertEquals(form.form_field_format(None), 'text/rest')
 
 
-class EntityFieldsFormTC(WebTest):
+class EntityFieldsFormTC(CubicWebTC):
 
     def setUp(self):
         super(EntityFieldsFormTC, self).setUp()
@@ -56,8 +56,8 @@
 
 
     def test_form_field_vocabulary_new_entity(self):
-        e = self.etype_instance('CWUser')
-        form = EntityFieldsForm(self.request(), None, entity=e)
+        e = self.vreg['etypes'].etype_class('CWUser')(self.request())
+        form = EntityFieldsForm(e.req, None, entity=e)
         unrelated = [rview for rview, reid in form.subject_relation_vocabulary('in_group')]
         # should be default groups but owners, i.e. managers, users, guests
         self.assertEquals(unrelated, [u'guests', u'managers', u'users'])
@@ -77,7 +77,7 @@
     #     self.assertEquals(states[0][0], u'deactivated') # list of (combobox view, state eid)
 
     def test_consider_req_form_params(self):
-        e = self.etype_instance('CWUser')
+        e = self.vreg['etypes'].etype_class('CWUser')(self.request())
         e.eid = 'A'
         form = EntityFieldsForm(self.request(login=u'toto'), None, entity=e)
         field = StringField(name='login', eidparam=True)
@@ -87,7 +87,7 @@
 
 
     def test_linkto_field_duplication(self):
-        e = self.etype_instance('CWUser')
+        e = self.vreg['etypes'].etype_class('CWUser')(self.request())
         e.eid = 'A'
         e.req = self.req
         geid = self.execute('CWGroup X WHERE X name "users"')[0][0]
--- a/web/test/unittest_formfields.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/test/unittest_formfields.py	Wed Sep 30 18:57:42 2009 +0200
@@ -11,7 +11,7 @@
 from yams.constraints import StaticVocabularyConstraint, SizeConstraint
 
 from cubicweb.devtools import TestServerConfiguration
-from cubicweb.devtools.testlib import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.web.formwidgets import PasswordInput, TextArea, Select, Radio
 from cubicweb.web.formfields import *
 from cubicweb.web.views.forms import EntityFieldsForm
@@ -115,11 +115,11 @@
                           [(u'maybe', '1'), (u'no', '')])
 
 
-class MoreFieldsTC(EnvBasedTC):
+class MoreFieldsTC(CubicWebTC):
     def test_rtf_format_field(self):
         req = self.request()
         req.use_fckeditor = lambda: False
-        e = self.etype_instance('State')
+        e = self.vreg['etypes'].etype_class('State')(req)
         form = EntityFieldsForm(req, entity=e)
         description_field = guess_field(schema['State'], schema['description'])
         description_format_field = description_field.get_format_field(form)
--- a/web/test/unittest_magicsearch.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/test/unittest_magicsearch.py	Wed Sep 30 18:57:42 2009 +0200
@@ -13,21 +13,17 @@
 
 from rql import BadRQLQuery, RQLSyntaxError
 
-from cubicweb.devtools.apptest import EnvBasedTC, TestEnvironment
+from cubicweb.devtools.testlib import CubicWebTC
 
 
 translations = {
     u'CWUser' : u"Utilisateur",
-#    u'Workcase' : u"Affaire",
     u'EmailAddress' : u"Adresse",
-#    u'Division' : u"Division",
-#    u'Comment' : u"Commentaire",
     u'name' : u"nom",
     u'alias' : u"nom",
     u'surname' : u"nom",
     u'firstname' : u"prénom",
     u'state' : u"état",
-#    u'subject' : u"sujet",
     u'address' : u"adresse",
     u'use_email' : u"adel",
     }
@@ -40,12 +36,12 @@
 
 from cubicweb.web.views.magicsearch import translate_rql_tree, QSPreProcessor, QueryTranslator
 
-class QueryTranslatorTC(EnvBasedTC):
+class QueryTranslatorTC(CubicWebTC):
     """test suite for QueryTranslatorTC"""
 
     def setUp(self):
         super(QueryTranslatorTC, self).setUp()
-        self.req = self.env.create_request()
+        self.req = self.request()
         self.vreg.config.translations = {'en': (_translate, _ctxtranslate)}
         proc = self.vreg['components'].select('magicsearch', self.req)
         self.proc = [p for p in proc.processors if isinstance(p, QueryTranslator)][0]
@@ -53,20 +49,20 @@
     def test_basic_translations(self):
         """tests basic translations (no ambiguities)"""
         rql = "Any C WHERE C is Adresse, P adel C, C adresse 'Logilab'"
-        rql, = self.proc.preprocess_query(rql, self.req)
+        rql, = self.proc.preprocess_query(rql)
         self.assertEquals(rql, "Any C WHERE C is EmailAddress, P use_email C, C address 'Logilab'")
 
     def test_ambiguous_translations(self):
         """tests possibly ambiguous translations"""
         rql = "Any P WHERE P adel C, C is EmailAddress, C nom 'Logilab'"
-        rql, = self.proc.preprocess_query(rql, self.req)
+        rql, = self.proc.preprocess_query(rql)
         self.assertEquals(rql, "Any P WHERE P use_email C, C is EmailAddress, C alias 'Logilab'")
         rql = "Any P WHERE P is Utilisateur, P adel C, P nom 'Smith'"
-        rql, = self.proc.preprocess_query(rql, self.req)
+        rql, = self.proc.preprocess_query(rql)
         self.assertEquals(rql, "Any P WHERE P is CWUser, P use_email C, P surname 'Smith'")
 
 
-class QSPreProcessorTC(EnvBasedTC):
+class QSPreProcessorTC(CubicWebTC):
     """test suite for QSPreProcessor"""
     def setUp(self):
         super(QSPreProcessorTC, self).setUp()
@@ -74,7 +70,7 @@
         self.req = self.request()
         proc = self.vreg['components'].select('magicsearch', self.req)
         self.proc = [p for p in proc.processors if isinstance(p, QSPreProcessor)][0]
-        self.proc.req = self.req
+        self.proc._cw = self.req
 
     def test_entity_translation(self):
         """tests QSPreProcessor._get_entity_name()"""
@@ -91,7 +87,6 @@
         eschema = self.schema.eschema('CWUser')
         self.assertEquals(translate(u'prénom', eschema), "firstname")
         self.assertEquals(translate(u'nom', eschema), 'surname')
-        #self.assert_(translate(u'nom') in ('name', 'surname'))
         eschema = self.schema.eschema('EmailAddress')
         self.assertEquals(translate(u'adresse', eschema), "address")
         self.assertEquals(translate(u'nom', eschema), 'alias')
@@ -128,7 +123,6 @@
         self.assertEquals(transform(u'adresse', 'Logi%'),
                           ('EmailAddress E WHERE E alias LIKE %(text)s', {'text': 'Logi%'}))
         self.assertRaises(BadRQLQuery, transform, "pers", "taratata")
-        #self.assertEquals(transform('CWUser', '%mi'), 'CWUser E WHERE P surname LIKE "%mi"')
 
     def test_three_words_query(self):
         """tests the 'three words shortcut queries'"""
@@ -174,16 +168,16 @@
             (u'CWUser prénom cubicweb', (u'CWUser C WHERE C firstname %(text)s', {'text': 'cubicweb'},)),
             ]
         for query, expected in queries:
-            self.assertEquals(self.proc.preprocess_query(query, self.req), expected)
+            self.assertEquals(self.proc.preprocess_query(query), expected)
         self.assertRaises(BadRQLQuery,
-                          self.proc.preprocess_query, 'Any X WHERE X is Something', self.req)
+                          self.proc.preprocess_query, 'Any X WHERE X is Something')
 
 
 
 ## Processor Chains tests ############################################
 
 
-class ProcessorChainTC(EnvBasedTC):
+class ProcessorChainTC(CubicWebTC):
     """test suite for magic_search's processor chains"""
 
     def setUp(self):
@@ -198,7 +192,7 @@
             (u'foo',
              ("Any X WHERE X has_text %(text)s", {'text': u'foo'})),
             # XXX this sounds like a language translator test...
-            # and it fail
+            # and it fails
             (u'Utilisateur Smith',
              ('CWUser C WHERE C has_text %(text)s', {'text': u'Smith'})),
             (u'utilisateur nom Smith',
@@ -207,21 +201,21 @@
              ('Any P WHERE P is CWUser, P surname "Smith"', None)),
             ]
         for query, expected in queries:
-            rset = self.proc.process_query(query, self.req)
+            rset = self.proc.process_query(query)
             self.assertEquals((rset.rql, rset.args), expected)
 
     def test_iso88591_fulltext(self):
         """we must be able to type accentuated characters in the search field"""
-        rset = self.proc.process_query(u'écrire', self.req)
+        rset = self.proc.process_query(u'écrire')
         self.assertEquals(rset.rql, "Any X WHERE X has_text %(text)s")
         self.assertEquals(rset.args, {'text': u'écrire'})
 
     def test_explicit_component(self):
         self.assertRaises(RQLSyntaxError,
-                          self.proc.process_query, u'rql: CWUser E WHERE E noattr "Smith",', self.req)
+                          self.proc.process_query, u'rql: CWUser E WHERE E noattr "Smith",')
         self.assertRaises(BadRQLQuery,
-                          self.proc.process_query, u'rql: CWUser E WHERE E noattr "Smith"', self.req)
-        rset = self.proc.process_query(u'text: utilisateur Smith', self.req)
+                          self.proc.process_query, u'rql: CWUser E WHERE E noattr "Smith"')
+        rset = self.proc.process_query(u'text: utilisateur Smith')
         self.assertEquals(rset.rql, 'Any X WHERE X has_text %(text)s')
         self.assertEquals(rset.args, {'text': u'utilisateur Smith'})
 
--- a/web/test/unittest_uicfg.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/test/unittest_uicfg.py	Wed Sep 30 18:57:42 2009 +0200
@@ -1,7 +1,7 @@
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.web import uicfg
 
-class UICFGTC(EnvBasedTC):
+class UICFGTC(CubicWebTC):
 
     def test_autoform_section_inlined(self):
         self.assertEquals(uicfg.autoform_is_inlined.etype_get('CWUser', 'use_email', 'subject', 'EmailAddress'),
--- a/web/test/unittest_urlpublisher.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/test/unittest_urlpublisher.py	Wed Sep 30 18:57:42 2009 +0200
@@ -11,15 +11,14 @@
 
 from logilab.common.testlib import unittest_main
 
-from cubicweb.devtools.apptest import EnvBasedTC
-from cubicweb.devtools._apptest import FakeRequest
-
 from cubicweb.rset import ResultSet
+from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.devtools.fake import FakeRequest
 from cubicweb.web import NotFound, Redirect
 from cubicweb.web.views.urlrewrite import SimpleReqRewriter
 
 
-class URLPublisherTC(EnvBasedTC):
+class URLPublisherTC(CubicWebTC):
     """test suite for QSPreProcessor"""
 
     def setup_database(self):
@@ -30,7 +29,7 @@
 
     def process(self, url):
         req = self.req = self.request()
-        return self.env.app.url_resolver.process(req, url)
+        return self.app.url_resolver.process(req, url)
 
     def test_raw_path(self):
         """tests raw path resolution'"""
--- a/web/test/unittest_urlrewrite.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/test/unittest_urlrewrite.py	Wed Sep 30 18:57:42 2009 +0200
@@ -7,13 +7,13 @@
 """
 from logilab.common.testlib import TestCase, unittest_main
 
-from cubicweb.devtools._apptest import FakeRequest
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.devtools.fake import FakeRequest
 
 from cubicweb.web.views.urlrewrite import SimpleReqRewriter, SchemaBasedRewriter, rgx, rgx_action
 
 
-class UrlRewriteTC(TestCase):
+class UrlRewriteTC(CubicWebTC):
 
     def test_auto_extend_rules(self):
         class Rewriter(SimpleReqRewriter):
@@ -64,8 +64,8 @@
 
     def test_basic_transformation(self):
         """test simple string-based rewrite"""
-        rewriter = SimpleReqRewriter()
         req = FakeRequest()
+        rewriter = SimpleReqRewriter(req)
         self.assertRaises(KeyError, rewriter.rewrite, req, '/view?vid=whatever')
         self.assertEquals(req.form, {})
         rewriter.rewrite(req, '/index')
@@ -73,8 +73,8 @@
 
     def test_regexp_transformation(self):
         """test regexp-based rewrite"""
-        rewriter = SimpleReqRewriter()
         req = FakeRequest()
+        rewriter = SimpleReqRewriter(req)
         rewriter.rewrite(req, '/add/Task')
         self.assertEquals(req.form, {'vid' : "creation", 'etype' : "Task"})
         req = FakeRequest()
@@ -84,7 +84,7 @@
 
 
 
-class RgxActionRewriteTC(EnvBasedTC):
+class RgxActionRewriteTC(CubicWebTC):
 
     def setup_database(self):
         self.p1 = self.create_user(u'user1')
@@ -100,8 +100,8 @@
                                                                              transforms={'sn' : unicode.capitalize,
                                                                                          'fn' : unicode.lower,})),
                 ]
-        rewriter = TestSchemaBasedRewriter()
         req = self.request()
+        rewriter = TestSchemaBasedRewriter(req)
         pmid, rset = rewriter.rewrite(req, u'/DaLToN/JoE')
         self.assertEquals(len(rset), 1)
         self.assertEquals(rset[0][0], self.p1.eid)
@@ -125,8 +125,8 @@
                 ),
                 ]
 
-        rewriter = Rewriter()
         req = self.request()
+        rewriter = Rewriter(req)
         pmid, rset = rewriter.rewrite(req, '/collector')
         self.assertEquals(rset.rql, RQL1)
         self.assertEquals(req.form, {'vid' : "baseindex"})
@@ -159,8 +159,8 @@
                 ),
                 ]
 
-        rewriter = Rewriter()
         req = self.request()
+        rewriter = Rewriter(req)
         pmid, rset = rewriter.rewrite(req, '/collector')
         self.assertEquals(rset.rql, RQL2)
         self.assertEquals(req.form, {'vid' : "index"})
--- a/web/test/unittest_views_actions.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/test/unittest_views_actions.py	Wed Sep 30 18:57:42 2009 +0200
@@ -7,26 +7,26 @@
 """
 from logilab.common.testlib import unittest_main
 
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 
-class ActionsTC(EnvBasedTC):
+class ActionsTC(CubicWebTC):
     def test_view_action(self):
         req = self.request(__message='bla bla bla', vid='rss', rql='CWUser X')
         rset = self.execute('CWUser X')
-        vaction = [action for action in self.vreg['actions'].possible_vobjects(req, rset=rset)
-                   if action.id == 'view'][0]
+        actions = self.vreg['actions'].poss_visible_objects(req, rset=rset)
+        vaction = [action for action in actions if action.__regid__ == 'view'][0]
         self.assertEquals(vaction.url(), 'http://testing.fr/cubicweb/view?rql=CWUser%20X')
 
     def test_sendmail_action(self):
         req = self.request()
         rset = self.execute('Any X WHERE X login "admin"', req=req)
-        self.failUnless([action for action in self.vreg['actions'].possible_vobjects(req, rset=rset)
-                         if action.id == 'sendemail'])
+        actions = self.vreg['actions'].poss_visible_objects(req, rset=rset)
+        self.failUnless([action for action in actions if action.__regid__ == 'sendemail'])
         self.login('anon')
         req = self.request()
         rset = self.execute('Any X WHERE X login "anon"', req=req)
-        self.failIf([action for action in self.vreg['actions'].possible_vobjects(req, rset=rset)
-                     if action.id == 'sendemail'])
+        actions = self.vreg['actions'].poss_visible_objects(req, rset=rset)
+        self.failIf([action for action in actions if action.__regid__ == 'sendemail'])
 
 if __name__ == '__main__':
     unittest_main()
--- a/web/test/unittest_views_basecontrollers.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/test/unittest_views_basecontrollers.py	Wed Sep 30 18:57:42 2009 +0200
@@ -8,42 +8,39 @@
 import simplejson
 
 from logilab.common.testlib import unittest_main, mock_object
-from cubicweb.devtools.apptest import EnvBasedTC, ControllerTC
 
 from cubicweb import Binary, NoSelectableObject, ValidationError
 from cubicweb.view import STRICT_DOCTYPE
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.common.uilib import rql_for_eid
-
 from cubicweb.web import INTERNAL_FIELD_VALUE, Redirect, RequestError
-
 from cubicweb.entities.authobjs import CWUser
 
 
-class EditControllerTC(ControllerTC):
+class EditControllerTC(CubicWebTC):
     def setUp(self):
-        ControllerTC.setUp(self)
+        CubicWebTC.setUp(self)
         self.failUnless('users' in self.schema.eschema('CWGroup').get_groups('read'))
 
     def tearDown(self):
-        ControllerTC.tearDown(self)
+        CubicWebTC.tearDown(self)
         self.failUnless('users' in self.schema.eschema('CWGroup').get_groups('read'))
 
     def test_noparam_edit(self):
         """check behaviour of this controller without any form parameter
         """
-
-        self.req.form = {}
-        self.assertRaises(ValidationError, self.publish, self.req)
+        self.assertRaises(ValidationError, self.publish, self.request())
 
     def test_validation_unique(self):
         """test creation of two linked entities
         """
         user = self.user()
-        self.req.form = {'eid': 'X', '__type:X': 'CWUser',
-                         'login:X': u'admin', 'edits-login:X': u'',
-                         'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'',
-                         }
-        self.assertRaises(ValidationError, self.publish, self.req)
+        req = self.request()
+        req.form = {'eid': 'X', '__type:X': 'CWUser',
+                    'login:X': u'admin', 'edits-login:X': u'',
+                    'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'',
+                    }
+        self.assertRaises(ValidationError, self.publish, req)
 
 
     def test_user_editing_itself(self):
@@ -54,7 +51,8 @@
         groupeids = [eid for eid, in self.execute('CWGroup G WHERE G name in ("managers", "users")')]
         groups = [str(eid) for eid in groupeids]
         stateeid = [eid for eid, in self.execute('State S WHERE S name "activated"')][0]
-        self.req.form = {
+        req = self.request()
+        req.form = {
             'eid':       `user.eid`,
             '__type:'+`user.eid`:    'CWUser',
             'login:'+`user.eid`:     unicode(user.login),
@@ -69,7 +67,7 @@
             'edits-in_group:'+`user.eid`:  basegroups,
             'edits-in_state:'+`user.eid`:  `stateeid`,
             }
-        path, params = self.expect_redirect_publish()
+        path, params = self.expect_redirect_publish(req)
         e = self.execute('Any X WHERE X eid %(x)s', {'x': user.eid}, 'x').get_entity(0, 0)
         self.assertEquals(e.firstname, u'Th\xe9nault')
         self.assertEquals(e.surname, u'Sylvain')
@@ -81,8 +79,6 @@
         user = self.create_user('user')
         cnx = self.login('user')
         req = self.request()
-        #self.assertEquals(self.ctrl.schema['CWUser']._groups['read'],
-        #                  ('managers', 'users'))
         req.form = {
             'eid': `user.eid`, '__type:'+`user.eid`: 'CWUser',
             '__maineid' : str(user.eid),
@@ -101,7 +97,8 @@
         """
         user = self.user()
         groupeids = [eid for eid, in self.execute('CWGroup G WHERE X in_group G, X eid %(x)s', {'x': user.eid})]
-        self.req.form = {
+        req = self.request()
+        req.form = {
             'eid':       `user.eid`,
             '__type:'+`user.eid`:    'CWUser',
             'login:'+`user.eid`:     unicode(user.login),
@@ -112,7 +109,7 @@
             'edits-firstname:'+`user.eid`: u'',
             'edits-surname:'+`user.eid`:   u'',
             }
-        path, params = self.expect_redirect_publish()
+        path, params = self.expect_redirect_publish(req)
         e = self.execute('Any X WHERE X eid %(x)s', {'x': user.eid}, 'x').get_entity(0, 0)
         self.assertEquals(e.login, user.login)
         self.assertEquals(e.firstname, u'Th\xe9nault')
@@ -124,21 +121,22 @@
 
     def test_create_multiple_linked(self):
         gueid = self.execute('CWGroup G WHERE G name "users"')[0][0]
-        self.req.form = {'eid': ['X', 'Y'],
-
-                         '__type:X': 'CWUser',
-                         '__maineid' : 'X',
-                         'login:X': u'adim', 'edits-login:X': u'',
-                         'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'',
-                         'surname:X': u'Di Mascio', 'edits-surname:X': '',
+        req = self.request()
+        req.form = {'eid': ['X', 'Y'],
 
-                         'in_group:X': `gueid`, 'edits-in_group:X': INTERNAL_FIELD_VALUE,
+                    '__type:X': 'CWUser',
+                    '__maineid' : 'X',
+                    'login:X': u'adim', 'edits-login:X': u'',
+                    'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'',
+                    'surname:X': u'Di Mascio', 'edits-surname:X': '',
 
-                         '__type:Y': 'EmailAddress',
-                         'address:Y': u'dima@logilab.fr', 'edits-address:Y': '',
-                         'use_email:X': 'Y', 'edits-use_email:X': INTERNAL_FIELD_VALUE,
-                         }
-        path, params = self.expect_redirect_publish()
+                    'in_group:X': `gueid`, 'edits-in_group:X': INTERNAL_FIELD_VALUE,
+
+                    '__type:Y': 'EmailAddress',
+                    'address:Y': u'dima@logilab.fr', 'edits-address:Y': '',
+                    'use_email:X': 'Y', 'edits-use_email:X': INTERNAL_FIELD_VALUE,
+                    }
+        path, params = self.expect_redirect_publish(req)
         # should be redirected on the created person
         self.assertEquals(path, 'cwuser/adim')
         e = self.execute('Any P WHERE P surname "Di Mascio"').get_entity(0, 0)
@@ -148,17 +146,18 @@
 
     def test_edit_multiple_linked(self):
         peid = self.create_user('adim').eid
-        self.req.form = {'eid': [`peid`, 'Y'],
-                         '__type:%s'%peid: 'CWUser',
-                         'surname:%s'%peid: u'Di Masci', 'edits-surname:%s'%peid: '',
+        req = self.request()
+        req.form = {'eid': [`peid`, 'Y'],
+                    '__type:%s'%peid: 'CWUser',
+                    'surname:%s'%peid: u'Di Masci', 'edits-surname:%s'%peid: '',
 
-                         '__type:Y': 'EmailAddress',
-                         'address:Y': u'dima@logilab.fr', 'edits-address:Y': '',
-                         'use_email:%s'%peid: 'Y', 'edits-use_email:%s'%peid: INTERNAL_FIELD_VALUE,
+                    '__type:Y': 'EmailAddress',
+                    'address:Y': u'dima@logilab.fr', 'edits-address:Y': '',
+                    'use_email:%s'%peid: 'Y', 'edits-use_email:%s'%peid: INTERNAL_FIELD_VALUE,
 
-                         '__redirectrql': 'Any X WHERE X eid %s'%peid,
-                         }
-        path, params = self.expect_redirect_publish()
+                    '__redirectrql': 'Any X WHERE X eid %s'%peid,
+                    }
+        path, params = self.expect_redirect_publish(req)
         # should be redirected on the created person
         eid = params['rql'].split()[-1]
         e = self.execute('Any X WHERE X eid %(x)s', {'x': eid}, 'x').get_entity(0, 0)
@@ -167,7 +166,8 @@
         self.assertEquals(email.address, 'dima@logilab.fr')
 
         emaileid = email.eid
-        self.req.form = {'eid': [`peid`, `emaileid`],
+        req = self.request()
+        req.form = {'eid': [`peid`, `emaileid`],
                          '__type:%s'%peid: 'CWUser',
                          'surname:%s'%peid: u'Di Masci', 'edits-surname:%s'%peid: 'Di Masci',
                          '__type:%s'%emaileid: 'EmailAddress',
@@ -175,7 +175,7 @@
                          'use_email:%s'%peid: `emaileid`, 'edits-use_email:%s'%peid: `emaileid`,
                          '__redirectrql': 'Any X WHERE X eid %s'%peid,
                          }
-        path, params = self.expect_redirect_publish()
+        path, params = self.expect_redirect_publish(req)
         # should be redirected on the created person
         eid = params['rql'].split()[-1]
         e = self.execute('Any X WHERE X eid %(x)s', {'x': eid}, 'x').get_entity(0, 0)
@@ -188,41 +188,47 @@
         """test creation of two linked entities
         """
         user = self.user()
-        self.req.form = {'__cloned_eid:X': user.eid,
-                         'eid': 'X', '__type:X': 'CWUser',
-                         'login:X': u'toto', 'edits-login:X': u'',
-                         'upassword:X': u'toto', 'edits-upassword:X': u'',
-                         }
-        self.assertRaises(ValidationError, self.publish, self.req)
-        self.req.form = {'__cloned_eid:X': user.eid,
-                         'eid': 'X', '__type:X': 'CWUser',
-                         'login:X': u'toto', 'edits-login:X': u'',
-                         'upassword:X': u'toto', 'upassword-confirm:X': u'tutu', 'edits-upassword:X': u'',
-                         }
-        self.assertRaises(ValidationError, self.publish, self.req)
+        req = self.request()
+        req.form = {'__cloned_eid:X': user.eid,
+                    'eid': 'X', '__type:X': 'CWUser',
+                    'login:X': u'toto', 'edits-login:X': u'',
+                    'upassword:X': u'toto', 'edits-upassword:X': u'',
+                    }
+        self.assertRaises(ValidationError, self.publish, req)
+        req = self.request()
+        req.form = {'__cloned_eid:X': user.eid,
+                    'eid': 'X', '__type:X': 'CWUser',
+                    'login:X': u'toto', 'edits-login:X': u'',
+                    'upassword:X': u'toto',
+                    'upassword-confirm:X': u'tutu', 'edits-upassword:X': u'',
+                    }
+        self.assertRaises(ValidationError, self.publish, req)
 
 
     def test_interval_bound_constraint_success(self):
         feid = self.execute('INSERT File X: X name "toto.txt", X data %(data)s',
                             {'data': Binary('yo')})[0][0]
-        self.req.form = {'eid': ['X'],
-                         '__type:X': 'Salesterm',
-                         'amount:X': u'-10', 'edits-amount:X': '',
-                         'described_by_test:X': str(feid), 'edits-described_by_test:X': INTERNAL_FIELD_VALUE,
-                         }
-        self.assertRaises(ValidationError, self.publish, self.req)
-        self.req.form = {'eid': ['X'],
-                         '__type:X': 'Salesterm',
-                         'amount:X': u'110', 'edits-amount:X': '',
-                         'described_by_test:X': str(feid), 'edits-described_by_test:X': INTERNAL_FIELD_VALUE,
-                         }
-        self.assertRaises(ValidationError, self.publish, self.req)
-        self.req.form = {'eid': ['X'],
-                         '__type:X': 'Salesterm',
-                         'amount:X': u'10', 'edits-amount:X': '',
-                         'described_by_test:X': str(feid), 'edits-described_by_test:X': INTERNAL_FIELD_VALUE,
-                         }
-        self.expect_redirect_publish()
+        req = self.request()
+        req.form = {'eid': ['X'],
+                    '__type:X': 'Salesterm',
+                    'amount:X': u'-10', 'edits-amount:X': '',
+                    'described_by_test:X': str(feid), 'edits-described_by_test:X': INTERNAL_FIELD_VALUE,
+                }
+        self.assertRaises(ValidationError, self.publish, req)
+        req = self.request()
+        req.form = {'eid': ['X'],
+                    '__type:X': 'Salesterm',
+                    'amount:X': u'110', 'edits-amount:X': '',
+                    'described_by_test:X': str(feid), 'edits-described_by_test:X': INTERNAL_FIELD_VALUE,
+                    }
+        self.assertRaises(ValidationError, self.publish, req)
+        req = self.request()
+        req.form = {'eid': ['X'],
+                    '__type:X': 'Salesterm',
+                    'amount:X': u'10', 'edits-amount:X': '',
+                    'described_by_test:X': str(feid), 'edits-described_by_test:X': INTERNAL_FIELD_VALUE,
+                    }
+        self.expect_redirect_publish(req)
         # should be redirected on the created
         #eid = params['rql'].split()[-1]
         e = self.execute('Salesterm X').get_entity(0, 0)
@@ -232,12 +238,13 @@
         """make sure req's pending insertions are taken into account"""
         tmpgroup = self.add_entity('CWGroup', name=u"test")
         user = self.user()
-        self.req.set_session_data('pending_insert', set([(user.eid, 'in_group', tmpgroup.eid)]))
-        path, params = self.expect_redirect_publish()
+        req = self.request()
+        req.set_session_data('pending_insert', set([(user.eid, 'in_group', tmpgroup.eid)]))
+        path, params = self.expect_redirect_publish(req)
         usergroups = [gname for gname, in
                       self.execute('Any N WHERE G name N, U in_group G, U eid %(u)s', {'u': user.eid})]
         self.assertUnorderedIterableEquals(usergroups, ['managers', 'test'])
-        self.assertEquals(self.req.get_pending_inserts(), [])
+        self.assertEquals(req.get_pending_inserts(), [])
 
 
     def test_req_pending_delete(self):
@@ -250,12 +257,13 @@
         # just make sure everything was set correctly
         self.assertUnorderedIterableEquals(usergroups, ['managers', 'test'])
         # now try to delete the relation
-        self.req.set_session_data('pending_delete', set([(user.eid, 'in_group', groupeid)]))
-        path, params = self.expect_redirect_publish()
+        req = self.request()
+        req.set_session_data('pending_delete', set([(user.eid, 'in_group', groupeid)]))
+        path, params = self.expect_redirect_publish(req)
         usergroups = [gname for gname, in
                       self.execute('Any N WHERE G name N, U in_group G, U eid %(u)s', {'u': user.eid})]
         self.assertUnorderedIterableEquals(usergroups, ['managers'])
-        self.assertEquals(self.req.get_pending_deletes(), [])
+        self.assertEquals(req.get_pending_deletes(), [])
 
     def test_custom_attribute_handler(self):
         def custom_login_edit(self, formparams, value, relations):
@@ -265,13 +273,14 @@
         try:
             user = self.user()
             eid = repr(user.eid)
-            self.req.form = {
+            req = self.request()
+            req.form = {
                 'eid': eid,
                 '__type:'+eid:  'CWUser',
                 'login:'+eid: u'foo',
                 'edits-login:'+eid:  unicode(user.login),
                 }
-            path, params = self.expect_redirect_publish()
+            path, params = self.expect_redirect_publish(req)
             rset = self.execute('Any L WHERE X eid %(x)s, X login L', {'x': user.eid}, 'x')
             self.assertEquals(rset[0][0], 'FOO')
         finally:
@@ -279,18 +288,19 @@
 
     def test_redirect_apply_button(self):
         redirectrql = rql_for_eid(4012) # whatever
-        self.req.form = {
-                         'eid': 'A', '__type:A': 'BlogEntry',
-                         '__maineid' : 'A',
-                         'content:A': u'"13:03:43"', 'edits-content:A': '',
-                         'title:A': u'huuu', 'edits-title:A': '',
-                         '__redirectrql': redirectrql,
-                         '__redirectvid': 'primary',
-                         '__redirectparams': 'toto=tutu&tata=titi',
-                         '__form_id': 'edition',
-                         '__action_apply': '',
-                         }
-        path, params = self.expect_redirect_publish()
+        req = self.request()
+        req.form = {
+            'eid': 'A', '__type:A': 'BlogEntry',
+            '__maineid' : 'A',
+            'content:A': u'"13:03:43"', 'edits-content:A': '',
+            'title:A': u'huuu', 'edits-title:A': '',
+            '__redirectrql': redirectrql,
+            '__redirectvid': 'primary',
+            '__redirectparams': 'toto=tutu&tata=titi',
+            '__form_id': 'edition',
+            '__action_apply': '',
+            }
+        path, params = self.expect_redirect_publish(req)
         self.failUnless(path.startswith('blogentry/'))
         eid = path.split('/')[1]
         self.assertEquals(params['vid'], 'edition')
@@ -301,17 +311,18 @@
 
     def test_redirect_ok_button(self):
         redirectrql = rql_for_eid(4012) # whatever
-        self.req.form = {
-                         'eid': 'A', '__type:A': 'BlogEntry',
-                         '__maineid' : 'A',
-                         'content:A': u'"13:03:43"', 'edits-content:A': '',
-                         'title:A': u'huuu', 'edits-title:A': '',
-                         '__redirectrql': redirectrql,
-                         '__redirectvid': 'primary',
-                         '__redirectparams': 'toto=tutu&tata=titi',
-                         '__form_id': 'edition',
-                         }
-        path, params = self.expect_redirect_publish()
+        req = self.request()
+        req.form = {
+            'eid': 'A', '__type:A': 'BlogEntry',
+            '__maineid' : 'A',
+            'content:A': u'"13:03:43"', 'edits-content:A': '',
+            'title:A': u'huuu', 'edits-title:A': '',
+            '__redirectrql': redirectrql,
+            '__redirectvid': 'primary',
+            '__redirectparams': 'toto=tutu&tata=titi',
+            '__form_id': 'edition',
+            }
+        path, params = self.expect_redirect_publish(req)
         self.assertEquals(path, 'view')
         self.assertEquals(params['rql'], redirectrql)
         self.assertEquals(params['vid'], 'primary')
@@ -320,27 +331,30 @@
 
     def test_redirect_delete_button(self):
         eid = self.add_entity('BlogEntry', title=u'hop', content=u'hop').eid
-        self.req.form = {'eid': str(eid), '__type:%s'%eid: 'BlogEntry',
-                         '__action_delete': ''}
-        path, params = self.expect_redirect_publish()
+        req = self.request()
+        req.form = {'eid': str(eid), '__type:%s'%eid: 'BlogEntry',
+                    '__action_delete': ''}
+        path, params = self.expect_redirect_publish(req)
         self.assertEquals(path, 'blogentry')
         self.assertEquals(params, {u'__message': u'entity deleted'})
         eid = self.add_entity('EmailAddress', address=u'hop@logilab.fr').eid
         self.execute('SET X use_email E WHERE E eid %(e)s, X eid %(x)s',
-                     {'x': self.session().user.eid, 'e': eid}, 'x')
+                     {'x': self.session.user.eid, 'e': eid}, 'x')
         self.commit()
-        self.req.form = {'eid': str(eid), '__type:%s'%eid: 'EmailAddress',
-                         '__action_delete': ''}
-        path, params = self.expect_redirect_publish()
+        req = self.request()
+        req.form = {'eid': str(eid), '__type:%s'%eid: 'EmailAddress',
+                    '__action_delete': ''}
+        path, params = self.expect_redirect_publish(req)
         self.assertEquals(path, 'cwuser/admin')
         self.assertEquals(params, {u'__message': u'entity deleted'})
         eid1 = self.add_entity('BlogEntry', title=u'hop', content=u'hop').eid
         eid2 = self.add_entity('EmailAddress', address=u'hop@logilab.fr').eid
-        self.req.form = {'eid': [str(eid1), str(eid2)],
-                         '__type:%s'%eid1: 'BlogEntry',
-                         '__type:%s'%eid2: 'EmailAddress',
-                         '__action_delete': ''}
-        path, params = self.expect_redirect_publish()
+        req = self.request()
+        req.form = {'eid': [str(eid1), str(eid2)],
+                    '__type:%s'%eid1: 'BlogEntry',
+                    '__type:%s'%eid2: 'EmailAddress',
+                    '__action_delete': ''}
+        path, params = self.expect_redirect_publish(req)
         self.assertEquals(path, 'view')
         self.assertEquals(params, {u'__message': u'entities deleted'})
 
@@ -351,23 +365,24 @@
         groups = [str(eid) for eid in groupeids]
         eeetypeeid = self.execute('CWEType X WHERE X name "CWGroup"')[0][0]
         basegroups = [str(eid) for eid, in self.execute('CWGroup G WHERE X read_permission G, X eid %(x)s', {'x': eeetypeeid})]
-        self.req.form = {
-                'eid':      `eeetypeeid`,
-                '__type:'+`eeetypeeid`:   'CWEType',
-                'name:'+`eeetypeeid`:     u'CWGroup',
-                'final:'+`eeetypeeid`:    False,
-                'meta:'+`eeetypeeid`:     True,
-                'description:'+`eeetypeeid`:     u'users group',
-                'read_permission:'+`eeetypeeid`:  groups,
-                #
-                'edits-name:'+`eeetypeeid`:     u'CWGroup',
-                'edits-final:'+`eeetypeeid`:    False,
-                'edits-meta:'+`eeetypeeid`:     True,
-                'edits-description:'+`eeetypeeid`:     u'users group',
-                'edits-read_permission:'+`eeetypeeid`:  basegroups,
-                }
+        req = self.request()
+        req.form = {
+            'eid':      `eeetypeeid`,
+            '__type:'+`eeetypeeid`:   'CWEType',
+            'name:'+`eeetypeeid`:     u'CWGroup',
+            'final:'+`eeetypeeid`:    False,
+            'meta:'+`eeetypeeid`:     True,
+            'description:'+`eeetypeeid`:     u'users group',
+            'read_permission:'+`eeetypeeid`:  groups,
+            #
+            'edits-name:'+`eeetypeeid`:     u'CWGroup',
+            'edits-final:'+`eeetypeeid`:    False,
+            'edits-meta:'+`eeetypeeid`:     True,
+            'edits-description:'+`eeetypeeid`:     u'users group',
+            'edits-read_permission:'+`eeetypeeid`:  basegroups,
+            }
         try:
-            path, params = self.expect_redirect_publish()
+            path, params = self.expect_redirect_publish(req)
             e = self.execute('Any X WHERE X eid %(x)s', {'x': eeetypeeid}, 'x').get_entity(0, 0)
             self.assertEquals(e.name, 'CWGroup')
             self.assertEquals([g.eid for g in e.read_permission], groupeids)
@@ -383,23 +398,24 @@
         groups = [str(eid) for eid in groupeids]
         eeetypeeid = self.execute('CWEType X WHERE X name "CWEType"')[0][0]
         basegroups = [str(eid) for eid, in self.execute('CWGroup G WHERE X read_permission G, X eid %(x)s', {'x': eeetypeeid})]
-        self.req.form = {
-                'eid':      `eeetypeeid`,
-                '__type:'+`eeetypeeid`:  'CWEType',
-                'name:'+`eeetypeeid`:     u'CWEType',
-                'final:'+`eeetypeeid`:    False,
-                'meta:'+`eeetypeeid`:     True,
-                'description:'+`eeetypeeid`:     u'users group',
-                'read_permission:'+`eeetypeeid`:  groups,
+        req = self.request()
+        req.form = {
+            'eid':      `eeetypeeid`,
+            '__type:'+`eeetypeeid`:  'CWEType',
+            'name:'+`eeetypeeid`:     u'CWEType',
+            'final:'+`eeetypeeid`:    False,
+            'meta:'+`eeetypeeid`:     True,
+            'description:'+`eeetypeeid`:     u'users group',
+            'read_permission:'+`eeetypeeid`:  groups,
 
-                'edits-name:'+`eeetypeeid`:     u'CWEType',
-                'edits-final:'+`eeetypeeid`:    False,
-                'edits-meta:'+`eeetypeeid`:     True,
-                'edits-description:'+`eeetypeeid`:     u'users group',
-                'edits-read_permission:'+`eeetypeeid`:  basegroups,
-                }
+            'edits-name:'+`eeetypeeid`:     u'CWEType',
+            'edits-final:'+`eeetypeeid`:    False,
+            'edits-meta:'+`eeetypeeid`:     True,
+            'edits-description:'+`eeetypeeid`:     u'users group',
+            'edits-read_permission:'+`eeetypeeid`:  basegroups,
+            }
         try:
-            path, params = self.expect_redirect_publish()
+            path, params = self.expect_redirect_publish(req)
             e = self.execute('Any X WHERE X eid %(x)s', {'x': eeetypeeid}, 'x').get_entity(0, 0)
             self.assertEquals(e.name, 'CWEType')
             self.assertEquals(sorted(g.eid for g in e.read_permission), groupeids)
@@ -413,12 +429,13 @@
 
         this seems to be postgres (tsearch?) specific
         """
-        self.req.form = {
-                         'eid': 'A', '__type:A': 'BlogEntry',
-                         '__maineid' : 'A',
-                         'title:A': u'"13:03:40"', 'edits-title:A': '',
-                         'content:A': u'"13:03:43"', 'edits-content:A': ''}
-        path, params = self.expect_redirect_publish()
+        req = self.request()
+        req.form = {
+            'eid': 'A', '__type:A': 'BlogEntry',
+            '__maineid' : 'A',
+            'title:A': u'"13:03:40"', 'edits-title:A': '',
+            'content:A': u'"13:03:43"', 'edits-content:A': ''}
+        path, params = self.expect_redirect_publish(req)
         self.failUnless(path.startswith('blogentry/'))
         eid = path.split('/')[1]
         e = self.execute('Any C, T WHERE C eid %(x)s, C content T', {'x': eid}, 'x').get_entity(0, 0)
@@ -428,29 +445,31 @@
 
     def test_nonregr_multiple_empty_email_addr(self):
         gueid = self.execute('CWGroup G WHERE G name "users"')[0][0]
-        self.req.form = {'eid': ['X', 'Y'],
-
-                         '__type:X': 'CWUser',
-                         'login:X': u'adim', 'edits-login:X': u'',
-                         'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'',
-                         'in_group:X': `gueid`, 'edits-in_group:X': INTERNAL_FIELD_VALUE,
+        req = self.request()
+        req.form = {'eid': ['X', 'Y'],
 
-                         '__type:Y': 'EmailAddress',
-                         'address:Y': u'', 'edits-address:Y': '',
-                         'alias:Y': u'', 'edits-alias:Y': '',
-                         'use_email:X': 'Y', 'edits-use_email:X': INTERNAL_FIELD_VALUE,
-                         }
-        self.assertRaises(ValidationError, self.publish, self.req)
+                    '__type:X': 'CWUser',
+                    'login:X': u'adim', 'edits-login:X': u'',
+                    'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'',
+                    'in_group:X': `gueid`, 'edits-in_group:X': INTERNAL_FIELD_VALUE,
+
+                    '__type:Y': 'EmailAddress',
+                    'address:Y': u'', 'edits-address:Y': '',
+                    'alias:Y': u'', 'edits-alias:Y': '',
+                    'use_email:X': 'Y', 'edits-use_email:X': INTERNAL_FIELD_VALUE,
+                    }
+        self.assertRaises(ValidationError, self.publish, req)
 
     def test_nonregr_copy(self):
         user = self.user()
-        self.req.form = {'__cloned_eid:X': user.eid,
-                         'eid': 'X', '__type:X': 'CWUser',
-                         '__maineid' : 'X',
-                         'login:X': u'toto', 'edits-login:X': u'',
-                         'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'',
-                         }
-        path, params = self.expect_redirect_publish()
+        req = self.request()
+        req.form = {'__cloned_eid:X': user.eid,
+                    'eid': 'X', '__type:X': 'CWUser',
+                    '__maineid' : 'X',
+                    'login:X': u'toto', 'edits-login:X': u'',
+                    'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'',
+                    }
+        path, params = self.expect_redirect_publish(req)
         self.assertEquals(path, 'cwuser/toto')
         e = self.execute('Any X WHERE X is CWUser, X login "toto"').get_entity(0, 0)
         self.assertEquals(e.login, 'toto')
@@ -466,29 +485,30 @@
             e = self.add_entity('EmailAddress', address=u'doe@doe.com')
             self.execute('SET P use_email E, P primary_email E WHERE P eid %(p)s, E eid %(e)s',
                          {'p' : p.eid, 'e' : e.eid})
-            self.req.form = {'__cloned_eid:X': p.eid,
-                             'eid': 'X', '__type:X': 'CWUser',
-                             'login': u'dodo', 'edits-login': u'dodo',
-                             'surname:X': u'Boom', 'edits-surname:X': u'',
-                             '__errorurl' : "whatever but required",
+            req = self.request()
+            req.form = {'__cloned_eid:X': p.eid,
+                        'eid': 'X', '__type:X': 'CWUser',
+                        'login': u'dodo', 'edits-login': u'dodo',
+                        'surname:X': u'Boom', 'edits-surname:X': u'',
+                        '__errorurl' : "whatever but required",
                              }
             # try to emulate what really happens in the web application
             # 1/ validate form => EditController.publish raises a ValidationError
             #    which fires a Redirect
             # 2/ When re-publishing the copy form, the publisher implicitly commits
             try:
-                self.env.app.publish('edit', self.req)
+                self.app.publish('edit', req)
             except Redirect:
-                self.req.form['rql'] = 'Any X WHERE X eid %s' % p.eid
-                self.req.form['vid'] = 'copy'
-                self.env.app.publish('view', self.req)
+                req.form['rql'] = 'Any X WHERE X eid %s' % p.eid
+                req.form['vid'] = 'copy'
+                self.app.publish('view', req)
             rset = self.execute('CWUser P WHERE P surname "Boom"')
             self.assertEquals(len(rset), 0)
         finally:
             p.__class__.skip_copy_for = old_skips
 
 
-class EmbedControllerTC(EnvBasedTC):
+class EmbedControllerTC(CubicWebTC):
 
     def test_nonregr_embed_publish(self):
         # This test looks a bit stupid but at least it will probably
@@ -500,23 +520,23 @@
         result = controller.publish(rset=None)
 
 
-class ReportBugControllerTC(EnvBasedTC):
+class ReportBugControllerTC(CubicWebTC):
 
     def test_usable_by_guets(self):
-        req = self.request()
-        self.vreg['controllers'].select('reportbug', req)
+        self.login('anon')
+        self.vreg['controllers'].select('reportbug', self.request())
 
 
-class SendMailControllerTC(EnvBasedTC):
+class SendMailControllerTC(CubicWebTC):
 
     def test_not_usable_by_guets(self):
         self.login('anon')
-        req = self.request()
-        self.assertRaises(NoSelectableObject, self.env.vreg['controllers'].select, 'sendmail', req)
+        self.assertRaises(NoSelectableObject,
+                          self.vreg['controllers'].select, 'sendmail', self.request())
 
 
 
-class JSONControllerTC(EnvBasedTC):
+class JSONControllerTC(CubicWebTC):
 
     def ctrl(self, req=None):
         req = req or self.request(url='http://whatever.fr/')
--- a/web/test/unittest_views_basetemplates.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/test/unittest_views_basetemplates.py	Wed Sep 30 18:57:42 2009 +0200
@@ -5,11 +5,11 @@
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
-from cubicweb.devtools.testlib import WebTest
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.devtools.htmlparser import DTDValidator
 
 
-class LogFormTemplateTC(WebTest):
+class LogFormTemplateTC(CubicWebTC):
 
     def _login_labels(self):
         valid = self.content_type_validators.get('text/html', DTDValidator)()
--- a/web/test/unittest_views_baseviews.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/test/unittest_views_baseviews.py	Wed Sep 30 18:57:42 2009 +0200
@@ -10,7 +10,7 @@
 from logilab.common.testlib import unittest_main
 from logilab.mtconverter import html_unescape
 
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 
 from cubicweb.web.htmlwidgets import TableWidget
 from cubicweb.web.views import vid_from_rset
@@ -18,7 +18,7 @@
 def loadjson(value):
     return loads(html_unescape(value))
 
-class VidFromRsetTC(EnvBasedTC):
+class VidFromRsetTC(CubicWebTC):
 
     def test_no_rset(self):
         req = self.request()
@@ -84,7 +84,7 @@
         self.assertEquals(vid_from_rset(req, rset, self.schema), 'table')
 
 
-class TableViewTC(EnvBasedTC):
+class TableViewTC(CubicWebTC):
 
     def _prepare_entity(self):
         e = self.add_entity("State", name=u'<toto>', description=u'loo"ong blabla')
--- a/web/test/unittest_views_editforms.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/test/unittest_views_editforms.py	Wed Sep 30 18:57:42 2009 +0200
@@ -6,14 +6,13 @@
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from logilab.common.testlib import unittest_main
-from cubicweb.devtools.apptest import EnvBasedTC
-from cubicweb.devtools.testlib import WebTest
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.web.views.autoform import AutomaticEntityForm as AEF
 from cubicweb.web.formwidgets import AutoCompletionWidget
 def rbc(entity, category):
     return [(rschema.type, x) for rschema, tschemas, x in AEF.erelations_by_category(entity, category)]
 
-class AutomaticEntityFormTC(EnvBasedTC):
+class AutomaticEntityFormTC(CubicWebTC):
 
     def test_custom_widget(self):
         AEF.rfields_kwargs.tag_subject_of(('CWUser', 'login', '*'),
@@ -29,7 +28,7 @@
         #for (rtype, role, stype, otype), tag in AEF.rcategories._tagdefs.items():
         #    if rtype == 'tags':
         #        print rtype, role, stype, otype, ':', tag
-        e = self.etype_instance('CWUser')
+        e = self.vreg['etypes'].etype_class('CWUser')(self.request())
         # see custom configuration in views.cwuser
         self.assertEquals(rbc(e, 'primary'),
                           [('login', 'subject'),
@@ -77,7 +76,7 @@
         self.failIf(AEF.rinlined.etype_get('CWUser', 'primary_email', 'subject'))
 
     def test_personne_relations_by_category(self):
-        e = self.etype_instance('Personne')
+        e = self.vreg['etypes'].etype_class('Personne')(self.request())
         self.assertListEquals(rbc(e, 'primary'),
                               [('nom', 'subject'),
                                ('eid', 'subject')
@@ -125,7 +124,7 @@
         self.failIf(any(f for f in form.fields if f is None))
 
 
-class FormViewsTC(WebTest):
+class FormViewsTC(CubicWebTC):
     def test_delete_conf_formview(self):
         rset = self.execute('CWGroup X')
         self.view('deleteconf', rset, template=None).source
--- a/web/test/unittest_views_navigation.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/test/unittest_views_navigation.py	Wed Sep 30 18:57:42 2009 +0200
@@ -7,14 +7,14 @@
 """
 
 from logilab.common.testlib import unittest_main, mock_object
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 
 from cubicweb.web.views.navigation import PageNavigation, SortedNavigation
 from cubicweb.web.views.ibreadcrumbs import BreadCrumbEntityVComponent
 
 BreadCrumbEntityVComponent.visible = True
 
-class NavigationTC(EnvBasedTC):
+class NavigationTC(CubicWebTC):
 
     def test_navigation_selection_whatever(self):
         req = self.request()
@@ -40,10 +40,10 @@
     def test_navigation_selection_not_enough(self):
         req = self.request()
         rset = self.execute('Any X,N LIMIT 10 WHERE X name N')
-        navcomp = self.vreg['components'].select_object('navigation', req, rset=rset)
+        navcomp = self.vreg['components'].select_or_none('navigation', req, rset=rset)
         self.assertEquals(navcomp, None)
         req.set_search_state('W:X:Y:Z')
-        navcomp = self.vreg['components'].select_object('navigation', req, rset=rset)
+        navcomp = self.vreg['components'].select_or_none('navigation', req, rset=rset)
         self.assertEquals(navcomp, None)
         req.set_search_state('normal')
 
@@ -96,36 +96,35 @@
 
 
 # XXX deactivate, contextual component has been removed
-# class ContentNavigationTC(EnvBasedTC):
+# class ContentNavigationTC(CubicWebTC):
+    # def test_component_context(self):
+    #     view = mock_object(is_primary=lambda x: True)
+    #     rset = self.execute('CWUser X LIMIT 1')
+    #     req = self.request()
+    #     objs = self.vreg['contentnavigation'].poss_visible_objects(
+    #         req, rset=rset, view=view, context='navtop')
+    #     # breadcrumbs should be in headers by default
+    #     clsids = set(obj.id for obj in objs)
+    #     self.failUnless('breadcrumbs' in clsids)
+    #     objs = self.vreg['contentnavigation'].poss_visible_objects(
+    #         req, rset=rset, view=view, context='navbottom')
+    #     # breadcrumbs should _NOT_ be in footers by default
+    #     clsids = set(obj.id for obj in objs)
+    #     self.failIf('breadcrumbs' in clsids)
+    #     self.execute('INSERT CWProperty P: P pkey "contentnavigation.breadcrumbs.context", '
+    #                  'P value "navbottom"')
+    #     # breadcrumbs should now be in footers
+    #     req.cnx.commit()
+    #     objs = self.vreg['contentnavigation'].poss_visible_objects(
+    #         req, rset=rset, view=view, context='navbottom')
 
-#     def test_component_context(self):
-#         view = mock_object(is_primary=lambda x: True)
-#         rset = self.execute('CWUser X LIMIT 1')
-#         req = self.request()
-#         objs = self.vreg['contentnavigation'].possible_vobjects(
-#             req, rset=rset, view=view, context='navtop')
-#         # breadcrumbs should be in headers by default
-#         clsids = set(obj.id for obj in objs)
-#         self.failUnless('breadcrumbs' in clsids)
-#         objs = self.vreg['contentnavigation'].possible_vobjects(
-#             req, rset=rset, view=view, context='navbottom')
-#         # breadcrumbs should _NOT_ be in footers by default
-#         clsids = set(obj.id for obj in objs)
-#         self.failIf('breadcrumbs' in clsids)
-#         self.execute('INSERT CWProperty P: P pkey "contentnavigation.breadcrumbs.context", '
-#                      'P value "navbottom"')
-#         # breadcrumbs should now be in footers
-#         req.cnx.commit()
-#         objs = self.vreg['contentnavigation'].possible_vobjects(
-#             req, rset=rset, view=view, context='navbottom')
+    #     clsids = [obj.id for obj in objs]
+    #     self.failUnless('breadcrumbs' in clsids)
+    #     objs = self.vreg['contentnavigation'].poss_visible_objects(
+    #         req, rset=rset, view=view, context='navtop')
 
-#         clsids = [obj.id for obj in objs]
-#         self.failUnless('breadcrumbs' in clsids)
-#         objs = self.vreg['contentnavigation'].possible_vobjects(
-#             req, rset=rset, view=view, context='navtop')
-
-#         clsids = [obj.id for obj in objs]
-#         self.failIf('breadcrumbs' in clsids)
+    #     clsids = [obj.id for obj in objs]
+    #     self.failIf('breadcrumbs' in clsids)
 
 
 if __name__ == '__main__':
--- a/web/test/unittest_views_pyviews.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/test/unittest_views_pyviews.py	Wed Sep 30 18:57:42 2009 +0200
@@ -1,12 +1,13 @@
 from logilab.common.testlib import unittest_main
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 
-class PyViewsTC(EnvBasedTC):
+class PyViewsTC(CubicWebTC):
 
     def test_pyvaltable(self):
-        content = self.vreg['views'].render('pyvaltable', self.request(),
-                                            pyvalue=[[1, 'a'], [2, 'b']],
-                                            headers=['num', 'char'])
+        view = self.vreg['views'].select('pyvaltable', self.request(),
+                                         pyvalue=[[1, 'a'], [2, 'b']])
+        content = view.render(pyvalue=[[1, 'a'], [2, 'b']],
+                              headers=['num', 'char'])
         self.assertEquals(content.strip(), '''<table class="listing">
 <tr><th>num</th><th>char</th></tr>
 <tr><td>1</td><td>a</td></tr>
@@ -14,8 +15,9 @@
 </table>''')
 
     def test_pyvallist(self):
-        content = self.vreg['views'].render('pyvallist', self.request(),
-                                            pyvalue=[1, 'a'])
+        view = self.vreg['views'].select('pyvallist', self.request(),
+                                         pyvalue=[1, 'a'])
+        content = view.render(pyvalue=[1, 'a'])
         self.assertEquals(content.strip(), '''<ul>
 <li>1</li>
 <li>a</li>
--- a/web/test/unittest_views_searchrestriction.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/test/unittest_views_searchrestriction.py	Wed Sep 30 18:57:42 2009 +0200
@@ -5,11 +5,11 @@
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.web.facet import insert_attr_select_relation, prepare_facets_rqlst
 
 
-class InsertAttrRelationTC(EnvBasedTC):
+class InsertAttrRelationTC(CubicWebTC):
 
     def parse(self, query):
         rqlst = self.vreg.parse(self.session, query)
--- a/web/test/unittest_viewselector.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/test/unittest_viewselector.py	Wed Sep 30 18:57:42 2009 +0200
@@ -4,7 +4,7 @@
 """
 from logilab.common.testlib import unittest_main
 
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb import CW_SOFTWARE_ROOT as BASE, Binary
 from cubicweb.selectors import (match_user_groups, implements,
                                 specified_etype_implements, rql_condition,
@@ -19,16 +19,16 @@
 
 from cubes.folder import views as folderviews
 
-USERACTIONS = [('myprefs', actions.UserPreferencesAction),
-               ('myinfos', actions.UserInfoAction),
-               ('logout', actions.LogoutAction)]
-SITEACTIONS = [('siteconfig', actions.SiteConfigurationAction),
-               ('manage', actions.ManageAction),
-               ('schema', schema.ViewSchemaAction),
-               ('siteinfo', actions.SiteInfoAction),
+USERACTIONS = [actions.UserPreferencesAction,
+               actions.UserInfoAction,
+               actions.LogoutAction]
+SITEACTIONS = [actions.SiteConfigurationAction,
+               actions.ManageAction,
+               schema.ViewSchemaAction,
+               actions.SiteInfoAction,
                ]
 
-class ViewSelectorTC(EnvBasedTC):
+class ViewSelectorTC(CubicWebTC):
 
     def setup_database(self):
         self.add_entity('BlogEntry', title=u"une news !", content=u"cubicweb c'est beau")
@@ -36,13 +36,6 @@
         self.add_entity('EmailAddress', address=u"devel@logilab.fr", alias=u'devel')
         self.add_entity('Tag', name=u'x')
 
-    def pactions(self, req, rset):
-        resdict = self.vreg['actions'].possible_actions(req, rset)
-        for cat, actions in resdict.items():
-            resdict[cat] = [(a.id, a.__class__) for a in actions]
-        return resdict
-
-
 class VRegistryTC(ViewSelectorTC):
     """test the view selector"""
 
@@ -76,19 +69,21 @@
                               ('manage', startup.ManageView),
                               ('owl', owl.OWLView),
                               ('propertiesform', cwproperties.CWPropertiesForm),
-                              ('registry', startup.RegistryView),
+                              ('registry', debug.RegistryView),
                               ('schema', schema.SchemaView),
                               ('systempropertiesform', cwproperties.SystemCWPropertiesForm),
                               ('tree', folderviews.FolderTreeView),
                               ])
 
     def test_possible_views_noresult(self):
-        rset, req = self.env.get_rset_and_req('Any X WHERE X eid 999999')
+        req = self.request()
+        rset = req.execute('Any X WHERE X eid 999999')
         self.assertListEqual(self.pviews(req, rset),
                              [])
 
     def test_possible_views_one_egroup(self):
-        rset, req = self.env.get_rset_and_req('CWGroup X WHERE X name "managers"')
+        req = self.request()
+        rset = req.execute('CWGroup X WHERE X name "managers"')
         self.assertListEqual(self.pviews(req, rset),
                              [('adaptedlist', baseviews.AdaptedListView),
                               ('csvexport', csvexport.CSVRsetView),
@@ -111,7 +106,8 @@
                               ])
 
     def test_possible_views_multiple_egroups(self):
-        rset, req = self.env.get_rset_and_req('CWGroup X')
+        req = self.request()
+        rset = req.execute('CWGroup X')
         self.assertListEqual(self.pviews(req, rset),
                              [('adaptedlist', baseviews.AdaptedListView),
                               ('csvexport', csvexport.CSVRsetView),
@@ -135,16 +131,20 @@
 
     def test_propertiesform_admin(self):
         assert self.vreg['views']['propertiesform']
-        rset1, req1 = self.env.get_rset_and_req('CWUser X WHERE X login "admin"')
-        rset2, req2 = self.env.get_rset_and_req('CWUser X WHERE X login "anon"')
+        req1 = self.request()
+        req2 = self.request()
+        rset1 = req1.execute('CWUser X WHERE X login "admin"')
+        rset2 = req2.execute('CWUser X WHERE X login "anon"')
         self.failUnless(self.vreg['views'].select('propertiesform', req1, rset=None))
         self.failUnless(self.vreg['views'].select('propertiesform', req1, rset=rset1))
         self.failUnless(self.vreg['views'].select('propertiesform', req2, rset=rset2))
 
     def test_propertiesform_anon(self):
         self.login('anon')
-        rset1, req1 = self.env.get_rset_and_req('CWUser X WHERE X login "admin"')
-        rset2, req2 = self.env.get_rset_and_req('CWUser X WHERE X login "anon"')
+        req1 = self.request()
+        req2 = self.request()
+        rset1 = req1.execute('CWUser X WHERE X login "admin"')
+        rset2 = req2.execute('CWUser X WHERE X login "anon"')
         self.assertRaises(NoSelectableObject, self.vreg['views'].select, 'propertiesform', req1, rset=None)
         self.assertRaises(NoSelectableObject, self.vreg['views'].select, 'propertiesform', req1, rset=rset1)
         self.assertRaises(NoSelectableObject, self.vreg['views'].select, 'propertiesform', req1, rset=rset2)
@@ -152,14 +152,17 @@
     def test_propertiesform_jdoe(self):
         self.create_user('jdoe')
         self.login('jdoe')
-        rset1, req1 = self.env.get_rset_and_req('CWUser X WHERE X login "admin"')
-        rset2, req2 = self.env.get_rset_and_req('CWUser X WHERE X login "jdoe"')
+        req1 = self.request()
+        req2 = self.request()
+        rset1 = req1.execute('CWUser X WHERE X login "admin"')
+        rset2 = req2.execute('CWUser X WHERE X login "jdoe"')
         self.failUnless(self.vreg['views'].select('propertiesform', req1, rset=None))
         self.assertRaises(NoSelectableObject, self.vreg['views'].select, 'propertiesform', req1, rset=rset1)
         self.failUnless(self.vreg['views'].select('propertiesform', req2, rset=rset2))
 
     def test_possible_views_multiple_different_types(self):
-        rset, req = self.env.get_rset_and_req('Any X')
+        req = self.request()
+        rset = req.execute('Any X')
         self.assertListEqual(self.pviews(req, rset),
                              [('csvexport', csvexport.CSVRsetView),
                               ('ecsvexport', csvexport.CSVEntityView),
@@ -181,7 +184,8 @@
                               ])
 
     def test_possible_views_any_rset(self):
-        rset, req = self.env.get_rset_and_req('Any N, X WHERE X in_group Y, Y name N')
+        req = self.request()
+        rset = req.execute('Any N, X WHERE X in_group Y, Y name N')
         self.assertListEqual(self.pviews(req, rset),
                              [('csvexport', csvexport.CSVRsetView),
                               ('editable-table', tableview.EditableTableView),
@@ -190,7 +194,8 @@
                               ])
 
     def test_possible_views_multiple_eusers(self):
-        rset, req = self.env.get_rset_and_req('CWUser X')
+        req = self.request()
+        rset = req.execute('CWUser X')
         self.assertListEqual(self.pviews(req, rset),
                              [('adaptedlist', baseviews.AdaptedListView),
                               ('csvexport', csvexport.CSVRsetView),
@@ -217,52 +222,57 @@
 
     def test_possible_actions_none_rset(self):
         req = self.request()
-        self.assertDictEqual(self.pactions(req, None),
+        self.assertDictEqual(self.pactionsdict(req, None, skipcategories=()),
                              {'useractions': USERACTIONS,
                               'siteactions': SITEACTIONS,
 
                               })
     def test_possible_actions_no_entity(self):
-        rset, req = self.env.get_rset_and_req('Any X WHERE X eid 999999')
-        self.assertDictEqual(self.pactions(req, rset),
+        req = self.request()
+        rset = req.execute('Any X WHERE X eid 999999')
+        self.assertDictEqual(self.pactionsdict(req, rset, skipcategories=()),
                              {'useractions': USERACTIONS,
                               'siteactions': SITEACTIONS,
                               })
 
     def test_possible_actions_same_type_entities(self):
-        rset, req = self.env.get_rset_and_req('CWGroup X')
-        self.assertDictEqual(self.pactions(req, rset),
+        req = self.request()
+        rset = req.execute('CWGroup X')
+        self.assertDictEqual(self.pactionsdict(req, rset, skipcategories=()),
                              {'useractions': USERACTIONS,
                               'siteactions': SITEACTIONS,
-                              'mainactions': [('muledit', actions.MultipleEditAction)],
-                              'moreactions': [('delete', actions.DeleteAction),
-                                              ('addentity', actions.AddNewAction)],
+                              'mainactions': [actions.MultipleEditAction],
+                              'moreactions': [actions.DeleteAction,
+                                              actions.AddNewAction],
                               })
 
     def test_possible_actions_different_types_entities(self):
-        rset, req = self.env.get_rset_and_req('Any X')
-        self.assertDictEqual(self.pactions(req, rset),
+        req = self.request()
+        rset = req.execute('Any X')
+        self.assertDictEqual(self.pactionsdict(req, rset, skipcategories=()),
                              {'useractions': USERACTIONS,
                               'siteactions': SITEACTIONS,
-                              'moreactions': [('delete', actions.DeleteAction)],
+                              'moreactions': [actions.DeleteAction],
                               })
 
     def test_possible_actions_final_entities(self):
-        rset, req = self.env.get_rset_and_req('Any N, X WHERE X in_group Y, Y name N')
-        self.assertDictEqual(self.pactions(req, rset),
+        req = self.request()
+        rset = req.execute('Any N, X WHERE X in_group Y, Y name N')
+        self.assertDictEqual(self.pactionsdict(req, rset, skipcategories=()),
                              {'useractions': USERACTIONS,
                               'siteactions': SITEACTIONS})
 
     def test_possible_actions_eetype_cwuser_entity(self):
-        rset, req = self.env.get_rset_and_req('CWEType X WHERE X name "CWUser"')
-        self.assertDictEqual(self.pactions(req, rset),
+        req = self.request()
+        rset = req.execute('CWEType X WHERE X name "CWUser"')
+        self.assertDictEqual(self.pactionsdict(req, rset, skipcategories=()),
                              {'useractions': USERACTIONS,
                               'siteactions': SITEACTIONS,
-                              'mainactions': [('edit', actions.ModifyAction)],
-                              'moreactions': [('managepermission', actions.ManagePermissionsAction),
-                                              ('addrelated', actions.AddRelatedActions),
-                                              ('delete', actions.DeleteAction),
-                                              ('copy', actions.CopyAction),
+                              'mainactions': [actions.ModifyAction],
+                              'moreactions': [actions.ManagePermissionsAction,
+                                              actions.AddRelatedActions,
+                                              actions.DeleteAction,
+                                              actions.CopyAction,
                                               ],
                               })
 
@@ -296,7 +306,8 @@
                              self.vreg['views'].select, 'table', req, rset=rset)
 
         # no entity
-        rset, req = self.env.get_rset_and_req('Any X WHERE X eid 999999')
+        req = self.request()
+        rset = req.execute('Any X WHERE X eid 999999')
         self.failUnlessRaises(NoSelectableObject,
                               self.vreg['views'].select, 'index', req, rset=rset)
         self.failUnlessRaises(NoSelectableObject,
@@ -306,7 +317,8 @@
         self.failUnlessRaises(NoSelectableObject,
                              self.vreg['views'].select, 'table', req, rset=rset)
         # one entity
-        rset, req = self.env.get_rset_and_req('CWGroup X WHERE X name "managers"')
+        req = self.request()
+        rset = req.execute('CWGroup X WHERE X name "managers"')
         self.assertIsInstance(self.vreg['views'].select('primary', req, rset=rset),
                              primary.PrimaryView)
         self.assertIsInstance(self.vreg['views'].select('list', req, rset=rset),
@@ -320,7 +332,8 @@
         self.failUnlessRaises(NoSelectableObject,
                               self.vreg['views'].select, 'index', req, rset=rset)
         # list of entities of the same type
-        rset, req = self.env.get_rset_and_req('CWGroup X')
+        req = self.request()
+        rset = req.execute('CWGroup X')
         self.assertIsInstance(self.vreg['views'].select('primary', req, rset=rset),
                              primary.PrimaryView)
         self.assertIsInstance(self.vreg['views'].select('list', req, rset=rset),
@@ -330,7 +343,8 @@
         self.failUnlessRaises(NoSelectableObject,
                               self.vreg['views'].select, 'creation', req, rset=rset)
         # list of entities of different types
-        rset, req = self.env.get_rset_and_req('Any X')
+        req = self.request()
+        rset = req.execute('Any X')
         self.assertIsInstance(self.vreg['views'].select('primary', req, rset=rset),
                                   primary.PrimaryView)
         self.assertIsInstance(self.vreg['views'].select('list', req, rset=rset),
@@ -342,7 +356,8 @@
         self.failUnlessRaises(NoSelectableObject,
                               self.vreg['views'].select, 'index', req, rset=rset)
         # whatever
-        rset, req = self.env.get_rset_and_req('Any N, X WHERE X in_group Y, Y name N')
+        req = self.request()
+        rset = req.execute('Any N, X WHERE X in_group Y, Y name N')
         self.assertIsInstance(self.vreg['views'].select('table', req, rset=rset),
                                   tableview.TableView)
         self.failUnlessRaises(NoSelectableObject,
@@ -356,7 +371,8 @@
         self.failUnlessRaises(NoSelectableObject,
                              self.vreg['views'].select, 'edition', req, rset=rset)
         # mixed query
-        rset, req = self.env.get_rset_and_req('Any U,G WHERE U is CWUser, G is CWGroup')
+        req = self.request()
+        rset = req.execute('Any U,G WHERE U is CWUser, G is CWGroup')
         self.failUnlessRaises(NoSelectableObject,
                               self.vreg['views'].select, 'edition', req, rset=rset)
         self.failUnlessRaises(NoSelectableObject,
@@ -367,7 +383,8 @@
     def test_interface_selector(self):
         image = self.add_entity('Image', name=u'bim.png', data=Binary('bim'))
         # image primary view priority
-        rset, req = self.env.get_rset_and_req('Image X WHERE X name "bim.png"')
+        req = self.request()
+        rset = req.execute('Image X WHERE X name "bim.png"')
         self.assertIsInstance(self.vreg['views'].select('primary', req, rset=rset),
                               idownloadable.IDownloadablePrimaryView)
 
@@ -375,12 +392,14 @@
     def test_score_entity_selector(self):
         image = self.add_entity('Image', name=u'bim.png', data=Binary('bim'))
         # image primary view priority
-        rset, req = self.env.get_rset_and_req('Image X WHERE X name "bim.png"')
+        req = self.request()
+        rset = req.execute('Image X WHERE X name "bim.png"')
         self.assertIsInstance(self.vreg['views'].select('image', req, rset=rset),
                               idownloadable.ImageView)
         fileobj = self.add_entity('File', name=u'bim.txt', data=Binary('bim'))
         # image primary view priority
-        rset, req = self.env.get_rset_and_req('File X WHERE X name "bim.txt"')
+        req = self.request()
+        rset = req.execute('File X WHERE X name "bim.txt"')
         self.assertRaises(NoSelectableObject, self.vreg['views'].select, 'image', req, rset=rset)
 
 
@@ -390,7 +409,8 @@
             rset = None
             req = self.request()
         else:
-            rset, req = self.env.get_rset_and_req(rql)
+            req = self.request()
+            rset = req.execute(rql)
         try:
             self.vreg['views'].render(vid, req, rset=rset, **args)
         except:
@@ -427,7 +447,7 @@
 
 
 class CWETypeRQLAction(Action):
-    id = 'testaction'
+    __regid__ = 'testaction'
     __select__ = implements('CWEType') & rql_condition('X name "CWEType"')
     title = 'bla'
 
@@ -443,27 +463,29 @@
         del self.vreg['actions']['testaction']
 
     def test(self):
-        rset, req = self.env.get_rset_and_req('CWEType X WHERE X name "CWEType"')
-        self.assertDictEqual(self.pactions(req, rset),
+        req = self.request()
+        rset = req.execute('CWEType X WHERE X name "CWEType"')
+        self.assertDictEqual(self.pactionsdict(req, rset, skipcategories=()),
                              {'useractions': USERACTIONS,
                               'siteactions': SITEACTIONS,
-                              'mainactions': [('edit', actions.ModifyAction)],
-                              'moreactions': [('managepermission', actions.ManagePermissionsAction),
-                                              ('addrelated', actions.AddRelatedActions),
-                                              ('delete', actions.DeleteAction),
-                                              ('copy', actions.CopyAction),
-                                              ('testaction', CWETypeRQLAction),
+                              'mainactions': [actions.ModifyAction],
+                              'moreactions': [actions.ManagePermissionsAction,
+                                              actions.AddRelatedActions,
+                                              actions.DeleteAction,
+                                              actions.CopyAction,
+                                              CWETypeRQLAction,
                                               ],
                               })
-        rset, req = self.env.get_rset_and_req('CWEType X WHERE X name "CWRType"')
-        self.assertDictEqual(self.pactions(req, rset),
+        req = self.request()
+        rset = req.execute('CWEType X WHERE X name "CWRType"')
+        self.assertDictEqual(self.pactionsdict(req, rset, skipcategories=()),
                              {'useractions': USERACTIONS,
                               'siteactions': SITEACTIONS,
-                              'mainactions': [('edit', actions.ModifyAction)],
-                              'moreactions': [('managepermission', actions.ManagePermissionsAction),
-                                              ('addrelated', actions.AddRelatedActions),
-                                              ('delete', actions.DeleteAction),
-                                              ('copy', actions.CopyAction),
+                              'mainactions': [actions.ModifyAction],
+                              'moreactions': [actions.ManagePermissionsAction,
+                                              actions.AddRelatedActions,
+                                              actions.DeleteAction,
+                                              actions.CopyAction,
                                               ],
                               })
 
--- a/web/test/unittest_webconfig.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/test/unittest_webconfig.py	Wed Sep 30 18:57:42 2009 +0200
@@ -9,8 +9,7 @@
 
 from logilab.common.testlib import TestCase, unittest_main
 
-from cubicweb.devtools._apptest import FakeRequest
-from cubicweb.devtools import ApptestConfiguration
+from cubicweb.devtools import ApptestConfiguration, fake
 
 class WebconfigTC(TestCase):
     def setUp(self):
@@ -21,7 +20,7 @@
     def test_nonregr_print_css_as_list(self):
         """make sure PRINT_CSS *must* is a list"""
         config = self.config
-        req = FakeRequest()
+        req = fake.FakeRequest()
         print_css = req.external_resource('STYLESHEETS_PRINT')
         self.failUnless(isinstance(print_css, list))
         ie_css = req.external_resource('IE_STYLESHEETS')
--- a/web/uicfg.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/uicfg.py	Wed Sep 30 18:57:42 2009 +0200
@@ -57,8 +57,45 @@
   simple boolean relation tags used to control the "add entity" submenu.
   Relations whose rtag is True will appears, other won't.
 
+
 Automatic form configuration
 ````````````````````````````
+:autoform_section:
+   where to display a relation in entity form, according to form type.
+   `tag_attribute`, `tag_subject_of` and `tag_object_of` methods for this
+   relation tags expect two arguments additionaly to the relation key: a
+   `formtype` and a `section`.
+
+   formtype may be one of:
+   * 'main', the main entity form
+   * 'inlined', the form for an entity inlined into another's one
+   * 'muledit', the multiple entity (table) form
+
+   section may be one of:
+   * 'hidden', don't display
+   * 'attributes', display in the attributes section
+   * 'relations', display in the relations section, using the generic relation
+     selector combobox (available in main form only, and not for attribute
+     relation)
+   * 'inlined', display target entity of the relation in an inlined form
+     (available in main form only, and not for attribute relation)
+   * 'metadata', display in a special metadata form (NOT YET IMPLEMENTED,
+     subject to changes)
+
+:autoform_field:
+  specify a custom field instance to use for a relation
+
+:autoform_field_kwargs:
+  specify a dictionnary of arguments to give to the field constructor for a
+  relation. You usually want to use either `autoform_field` or
+  `autoform_field_kwargs`, not both. The later won't have any effect if the
+  former is specified for a relation.
+
+:autoform_permissions_overrides:
+
+  provide a way to by-pass security checking for dark-corner case where it can't
+  be verified properly. XXX documents.
+
 
 :organization: Logilab
 :copyright: 2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
@@ -67,9 +104,12 @@
 """
 __docformat__ = "restructuredtext en"
 
+from warnings import warn
+
 from cubicweb import neg_role
-from cubicweb.rtags import (RelationTags, RelationTagsBool,
-                            RelationTagsSet, RelationTagsDict)
+from cubicweb.rtags import (RelationTags, RelationTagsBool, RelationTagsSet,
+                            RelationTagsDict, register_rtag, _ensure_str_key)
+from cubicweb.schema import META_RTYPES
 from cubicweb.web import formwidgets
 
 
@@ -144,6 +184,7 @@
 primaryview_display_ctrl = DisplayCtrlRelationTags('primaryview_display_ctrl',
                                                    init_primaryview_display_ctrl)
 
+
 # index view configuration ####################################################
 # entity type section in the index/manage page. May be one of
 # * 'application'
@@ -152,6 +193,20 @@
 # * 'hidden'
 # * 'subobject' (not displayed by default)
 
+class InitializableDict(dict):
+    def __init__(self, *args, **kwargs):
+        super(InitializableDict, self).__init__(*args, **kwargs)
+        register_rtag(self)
+
+    def init(self, schema, check=True):
+        for eschema in schema.entities():
+            if eschema.schema_entity():
+                self.setdefault(eschema, 'schema')
+            elif eschema.is_subobject(strict=True):
+                self.setdefault(eschema, 'subobject')
+            else:
+                self.setdefault(eschema, 'application')
+
 indexview_etype_section = {'EmailAddress': 'subobject',
                            'CWUser': 'system',
                            'CWGroup': 'system',
@@ -164,41 +219,191 @@
                            'WorkflowTransition': 'hidden',
                            }
 
+indexview_etype_section = InitializableDict(EmailAddress='subobject',
+                                            CWUser='system',
+                                            CWGroup='system',
+                                            CWPermission='system',
+                                            CWCache='system',
+                                            Workflow='system',
+                                            )
 
 # autoform.AutomaticEntityForm configuration ##################################
 
-# relations'section (eg primary/secondary/generic/metadata/generated)
+def _formsections_as_dict(formsections):
+    result = {}
+    for formsection in formsections:
+        formtype, section = formsection.split('_', 1)
+        result[formtype] = section
+    return result
+
+def _card_and_comp(sschema, rschema, oschema, role):
+    if role == 'subject':
+        card = rschema.rproperty(sschema, oschema, 'cardinality')[0]
+        composed = rschema.rproperty(sschema, oschema, 'composite') == 'object'
+    else:
+        card = rschema.rproperty(sschema, oschema, 'cardinality')[1]
+        composed = rschema.rproperty(sschema, oschema, 'composite') == 'subject'
+    return card, composed
+
+class AutoformSectionRelationTags(RelationTagsSet):
+    """autoform relations'section"""
 
-def init_autoform_section(rtag, sschema, rschema, oschema, role):
-    if rtag.get(sschema, rschema, oschema, role) is None:
-        if autoform_is_inlined.get(sschema, rschema, oschema, role) or \
-               autoform_is_inlined.get(sschema, rschema, oschema, neg_role(role)):
-            section = 'generated'
-        elif sschema.is_metadata(rschema):
-            section = 'metadata'
-        else:
-            if role == 'subject':
-                card = rschema.rproperty(sschema, oschema, 'cardinality')[0]
-                composed = rschema.rproperty(sschema, oschema, 'composite') == 'object'
+    bw_tag_map = {
+        'primary':   {'main': 'attributes', 'muledit': 'attributes'},
+        'secondary': {'main': 'attributes', 'muledit': 'hidden'},
+        'metadata':  {'main': 'metadata'},
+        'generic':   {'main': 'relations'},
+        'generated': {'main': 'hidden'},
+        }
+
+    _allowed_form_types = ('main', 'inlined', 'muledit')
+    _allowed_values = {'main': ('attributes', 'relations', 'metadata', 'hidden'),
+                       'inlined': ('attributes', 'hidden'),
+                       'muledit': ('attributes', 'hidden'),
+                       }
+
+    @staticmethod
+    def _initfunc(self, sschema, rschema, oschema, role):
+        formsections = self.init_get(sschema, rschema, oschema, role)
+        if formsections is None:
+            formsections = self.tag_container_cls()
+        sectdict = _formsections_as_dict(formsections)
+        if rschema in META_RTYPES:
+            sectdict.setdefault('main', 'hidden')
+            sectdict.setdefault('muledit', 'hidden')
+            sectdict.setdefault('inlined', 'hidden')
+        # ensure we have a tag for each form type
+        if not 'main' in sectdict:
+            if not rschema.is_final() and (
+                sectdict.get('inlined') == 'attributes' or
+                'inlined_attributes' in self.init_get(sschema, rschema, oschema,
+                                                      neg_role(role))):
+                sectdict['main'] = 'hidden'
+            elif sschema.is_metadata(rschema):
+                sectdict['main'] = 'metadata'
             else:
-                card = rschema.rproperty(sschema, oschema, 'cardinality')[1]
-                composed = rschema.rproperty(sschema, oschema, 'composite') == 'subject'
-            if card in '1+':
-                if not rschema.is_final() and composed:
-                    # XXX why? probably because we want it unlined, though this
-                    # is not the case by default
-                    section = 'generated'
+                card, composed = _card_and_comp(sschema, rschema, oschema, role)
+                if card in '1+':
+                    if not rschema.is_final() and composed:
+                        # XXX why? probably because we want it unlined, though
+                        # this is not the case by default
+                        sectdict['main'] = 'hidden'
+                    else:
+                        sectdict['main'] = 'attributes'
+                        if not 'muledit' in sectdict:
+                            sectdict['muledit'] = 'attributes'
+                elif rschema.is_final():
+                    sectdict['main'] = 'attributes'
                 else:
-                    section = 'primary'
-            elif rschema.is_final():
-                section = 'secondary'
-            else:
-                section = 'generic'
-        rtag.tag_relation((sschema, rschema, oschema, role), section)
+                    sectdict['main'] = 'relations'
+        if not 'muledit' in sectdict:
+            sectdict['muledit'] = 'hidden'
+            if sectdict['main'] == 'attributes':
+                card, composed = _card_and_comp(sschema, rschema, oschema, role)
+                if card in '1+' and not composed:
+                    sectdict['muledit'] = 'attributes'
+        if not 'inlined' in sectdict:
+            sectdict['inlined'] = 'hidden'
+        # recompute formsections and set it to avoid recomputing
+        for formtype, section in sectdict.iteritems():
+            formsections.add('%s_%s' % (formtype, section))
+        key = _ensure_str_key( (sschema, rschema, oschema, role) )
+        self._tagdefs[key] = formsections
+
+    def tag_relation(self, key, formtype, section=None):
+        if section is None:
+            tag = formtype
+            for formtype, section in self.bw_tag_map[tag].iteritems():
+                warn('[3.6] add tag to autoform section by specifying form '
+                     'type and tag. Replace %s by formtype=%s, section=%s'
+                     % (tag, formtype, section), DeprecationWarning, stacklevel=2)
+                self.tag_relation(key, formtype, section)
+        assert formtype in self._allowed_form_types, \
+               'formtype should be in (%s), not %s' % (
+            ','.join(self._allowed_form_types), formtype)
+        assert section in self._allowed_values[formtype], \
+               'section for %s should be in (%s), not %s' % (
+            formtype, ','.join(self._allowed_values[formtype]), section)
+        rtags = self._tagdefs.setdefault(_ensure_str_key(key),
+                                         self.tag_container_cls())
+        # remove previous section for this form type if any
+        if rtags:
+            for tag in rtags.copy():
+                if tag.startswith(formtype):
+                    rtags.remove(tag)
+        rtags.add('%s_%s' % (formtype, section))
+        return rtags
+
+    def init_get(self, *key):
+        return super(AutoformSectionRelationTags, self).get(*key)
+
+    def get(self, *key):
+        # overriden to avoid recomputing done in parent classes
+        return self._tagdefs[key]
+
+    def relations_by_section(self, entity, formtype, section,
+                             permission=None, strict=False):
+        """return a list of (relation schema, target schemas, role) for the
+        given entity matching categories and permission.
 
-autoform_section = RelationTags('autoform_section', init_autoform_section,
-                                set(('primary', 'secondary', 'generic',
-                                     'metadata', 'generated')))
+        `strict`:
+          bool telling if having local role is enough (strict = False) or not
+        """
+        tag = '%s_%s' % (formtype, section)
+        eschema  = entity.e_schema
+        permsoverrides = autoform_permissions_overrides
+        if entity.has_eid():
+            eid = entity.eid
+        else:
+            eid = None
+            strict = False
+        for rschema, targetschemas, role in eschema.relation_definitions(True):
+            # check category first, potentially lower cost than checking
+            # permission which may imply rql queries
+            if tag is not None:
+                targetschemas = [tschema for tschema in targetschemas
+                                 if tag in self.etype_get(eschema, rschema,
+                                                          role, tschema)]
+            if not targetschemas:
+                continue
+            if permission is not None:
+                # tag allowing to hijack the permission machinery when
+                # permission is not verifiable until the entity is actually
+                # created...
+                if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role):
+                    yield (rschema, targetschemas, role)
+                    continue
+                if rschema.is_final():
+                    if not rschema.has_perm(entity._cw, permission, eid):
+                        continue
+                elif role == 'subject':
+                    if not ((not strict and rschema.has_local_role(permission)) or
+                            rschema.has_perm(entity._cw, permission, fromeid=eid)):
+                        continue
+                    # on relation with cardinality 1 or ?, we need delete perm as well
+                    # if the relation is already set
+                    if (permission == 'add'
+                        and rschema.cardinality(eschema, targetschemas[0], role) in '1?'
+                        and eid and entity.related(rschema.type, role)
+                        and not rschema.has_perm(entity._cw, 'delete', fromeid=eid,
+                                                 toeid=entity.related(rschema.type, role)[0][0])):
+                        continue
+                elif role == 'object':
+                    if not ((not strict and rschema.has_local_role(permission)) or
+                            rschema.has_perm(entity._cw, permission, toeid=eid)):
+                        continue
+                    # on relation with cardinality 1 or ?, we need delete perm as well
+                    # if the relation is already set
+                    if (permission == 'add'
+                        and rschema.cardinality(targetschemas[0], eschema, role) in '1?'
+                        and eid and entity.related(rschema.type, role)
+                        and not rschema.has_perm(entity._cw, 'delete', toeid=eid,
+                                                 fromeid=entity.related(rschema.type, role)[0][0])):
+                        continue
+            yield (rschema, targetschemas, role)
+
+
+autoform_section = AutoformSectionRelationTags('autoform_section')
 
 # relations'field class
 autoform_field = RelationTags('autoform_field')
@@ -206,11 +411,6 @@
 # relations'field explicit kwargs (given to field's __init__)
 autoform_field_kwargs = RelationTagsDict()
 
-# inlined view flag for non final relations: when True for an entry, the
-# entity(ies) at the other end of the relation will be editable from the
-# form of the edited entity
-autoform_is_inlined = RelationTagsBool('autoform_is_inlined')
-
 
 # set of tags of the form <action>_on_new on relations. <action> is a
 # schema action (add/update/delete/read), and when such a tag is found
@@ -222,6 +422,9 @@
 # 'link' / 'create' relation tags, used to control the "add entity" submenu
 def init_actionbox_appearsin_addmenu(rtag, sschema, rschema, oschema, role):
     if rtag.get(sschema, rschema, oschema, role) is None:
+        if rschema in META_RTYPES:
+            rtag.tag_relation((sschema, rschema, oschema, role), False)
+            return
         card = rschema.rproperty(sschema, oschema, 'cardinality')[role == 'object']
         if not card in '?1' and \
                rschema.rproperty(sschema, oschema, 'composite') == role:
@@ -229,3 +432,20 @@
 
 actionbox_appearsin_addmenu = RelationTagsBool('actionbox_appearsin_addmenu',
                                                init_actionbox_appearsin_addmenu)
+
+
+# deprecated ###################################################################
+
+class AutoformIsInlined(RelationTags):
+    """XXX for < 3.6 bw compat"""
+    def tag_relation(self, key, tag):
+        warn('autoform_is_inlined rtag is deprecated, use autoform_section '
+             'with inlined formtype and "attributes" or "hidden" section',
+             DeprecationWarning, stacklevel=2)
+        section = tag and 'attributes' or 'hidden'
+        autoform_section.tag_relation(key, 'inlined', section)
+
+# inlined view flag for non final relations: when True for an entry, the
+# entity(ies) at the other end of the relation will be editable from the
+# form of the edited entity
+autoform_is_inlined = AutoformIsInlined('autoform_is_inlined')
--- a/web/views/__init__.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/__init__.py	Wed Sep 30 18:57:42 2009 +0200
@@ -110,7 +110,7 @@
         self.cell_call()
 
     def cell_call(self, row=0, col=0):
-        self.row, self.col = row, col # in case one needs it
+        self.cw_row, self.cw_col = row, col # in case one needs it
         fd, tmpfile = tempfile.mkstemp('.png')
         os.close(fd)
         self._generate(tmpfile)
--- a/web/views/actions.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/actions.py	Wed Sep 30 18:57:42 2009 +0200
@@ -15,10 +15,8 @@
     authenticated_user, match_user_groups, match_search_state,
     has_permission, has_add_permission,
     )
-from cubicweb.web import uicfg, controller
-from cubicweb.web.action import Action
+from cubicweb.web import uicfg, controller, action
 from cubicweb.web.views import linksearch_select_url, vid_from_rset
-from cubicweb.web.views.autoform import AutomaticEntityForm
 
 
 class has_editable_relation(EntitySelector):
@@ -31,11 +29,17 @@
     def score_entity(self, entity):
         # if user has no update right but it can modify some relation,
         # display action anyway
-        for dummy in AutomaticEntityForm.esrelations_by_category(
-            entity, 'generic', 'add', strict=True):
+        form = entity._cw.vreg['forms'].select('edition', entity._cw,
+                                             entity=entity)
+        for dummy in form.editable_relations():
             return 1
-        for rschema, targetschemas, role in AutomaticEntityForm.erelations_by_category(
-            entity, ('primary', 'secondary'), 'add', strict=True):
+        try:
+            editableattrs = form.editable_attributes(strict=True)
+        except TypeError:
+            warn('[3.6] %s: editable_attributes now take strict=False as '
+                 'optional argument', DeprecationWarning)
+            editableattrs = form.editable_attributes()
+        for rschema, targetschemas, role in editableattrs:
             if not rschema.is_final():
                 return 1
         return 0
@@ -48,7 +52,7 @@
 def view_is_not_default_view(cls, req, rset=None, **kwargs):
     # interesting if it propose another view than the current one
     vid = req.form.get('vid')
-    if vid and vid != vid_from_rset(req, rset, cls.schema):
+    if vid and vid != vid_from_rset(req, rset, req.vreg.schema):
         return 1
     return 0
 
@@ -61,7 +65,7 @@
         select = rqlst.children[0]
         if len(select.defined_vars) == 1 and len(select.solutions) == 1:
             rset._searched_etype = select.solutions[0].itervalues().next()
-            eschema = cls.schema.eschema(rset._searched_etype)
+            eschema = req.schema.eschema(rset._searched_etype)
             if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
                    and eschema.has_perm(req, 'add'):
                 return 1
@@ -69,24 +73,25 @@
 
 # generic 'main' actions #######################################################
 
-class SelectAction(Action):
+class SelectAction(action.Action):
     """base class for link search actions. By default apply on
     any size entity result search it the current state is 'linksearch'
     if accept match.
     """
-    id = 'select'
-    __select__ = match_search_state('linksearch') & nonempty_rset() & match_searched_etype()
+    __regid__ = 'select'
+    __select__ = (match_search_state('linksearch') & nonempty_rset()
+                  & match_searched_etype())
 
     title = _('select')
     category = 'mainactions'
     order = 0
 
     def url(self):
-        return linksearch_select_url(self.req, self.rset)
+        return linksearch_select_url(self._cw, self.cw_rset)
 
 
-class CancelSelectAction(Action):
-    id = 'cancel'
+class CancelSelectAction(action.Action):
+    __regid__ = 'cancel'
     __select__ = match_search_state('linksearch')
 
     title = _('cancel select')
@@ -94,14 +99,14 @@
     order = 10
 
     def url(self):
-        target, eid, r_type, searched_type = self.req.search_state[1]
-        return self.build_url(str(eid),
-                              vid='edition', __mode='normal')
+        target, eid, r_type, searched_type = self._cw.search_state[1]
+        return self._cw.build_url(str(eid),
+                                  vid='edition', __mode='normal')
 
 
-class ViewAction(Action):
-    id = 'view'
-    __select__ = (match_search_state('normal') &
+class ViewAction(action.Action):
+    __regid__ = 'view'
+    __select__ = (action.Action.__select__ &
                   match_user_groups('users', 'managers') &
                   view_is_not_default_view() &
                   non_final_entity())
@@ -111,17 +116,16 @@
     order = 0
 
     def url(self):
-        params = self.req.form.copy()
+        params = self._cw.form.copy()
         for param in ('vid', '__message') + controller.NAV_FORM_PARAMETERS:
             params.pop(param, None)
-        return self.build_url(self.req.relative_path(includeparams=False),
-                              **params)
+        return self._cw.build_url(self._cw.relative_path(includeparams=False),
+                                  **params)
 
 
-class ModifyAction(Action):
-    id = 'edit'
-    __select__ = (match_search_state('normal') &
-                  one_line_rset() &
+class ModifyAction(action.Action):
+    __regid__ = 'edit'
+    __select__ = (action.Action.__select__ & one_line_rset() &
                   (has_permission('update') | has_editable_relation('add')))
 
     title = _('modify')
@@ -129,73 +133,73 @@
     order = 10
 
     def url(self):
-        entity = self.rset.get_entity(self.row or 0, self.col or 0)
+        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
         return entity.absolute_url(vid='edition')
 
 
-class MultipleEditAction(Action):
-    id = 'muledit' # XXX get strange conflicts if id='edit'
-    __select__ = (match_search_state('normal') &
-                  two_lines_rset() & one_etype_rset() &
-                  has_permission('update'))
+class MultipleEditAction(action.Action):
+    __regid__ = 'muledit' # XXX get strange conflicts if id='edit'
+    __select__ = (action.Action.__select__ & two_lines_rset() &
+                  one_etype_rset() & has_permission('update'))
 
     title = _('modify')
     category = 'mainactions'
     order = 10
 
     def url(self):
-        return self.build_url('view', rql=self.rset.rql, vid='muledit')
+        return self._cw.build_url('view', rql=self.cw_rset.rql, vid='muledit')
 
 
 # generic "more" actions #######################################################
 
-class ManagePermissionsAction(Action):
-    id = 'managepermission'
-    __select__ = one_line_rset() & non_final_entity() & match_user_groups('managers')
+class ManagePermissionsAction(action.Action):
+    __regid__ = 'managepermission'
+    __select__ = (action.Action.__select__ & one_line_rset() &
+                  non_final_entity() & match_user_groups('managers'))
 
     title = _('manage permissions')
     category = 'moreactions'
     order = 15
 
     @classmethod
-    def registered(cls, vreg):
-        super(ManagePermissionsAction, cls).registered(vreg)
+    def __registered__(cls, vreg):
         if 'require_permission' in vreg.schema:
             cls.__select__ = (one_line_rset() & non_final_entity() &
                               (match_user_groups('managers')
                                | relation_possible('require_permission', 'subject', 'CWPermission',
                                                    action='add')))
-        return super(ManagePermissionsAction, cls).registered(vreg)
+        return super(ManagePermissionsAction, cls).__registered__(vreg)
 
     def url(self):
-        return self.rset.get_entity(self.row or 0, self.col or 0).absolute_url(vid='security')
+        return self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0).absolute_url(vid='security')
 
 
-class DeleteAction(Action):
-    id = 'delete'
-    __select__ = has_permission('delete')
+class DeleteAction(action.Action):
+    __regid__ = 'delete'
+    __select__ = action.Action.__select__ & has_permission('delete')
 
     title = _('delete')
     category = 'moreactions'
     order = 20
 
     def url(self):
-        if len(self.rset) == 1:
-            entity = self.rset.get_entity(self.row or 0, self.col or 0)
-            return self.build_url(entity.rest_path(), vid='deleteconf')
-        return self.build_url(rql=self.rset.printable_rql(), vid='deleteconf')
+        if len(self.cw_rset) == 1:
+            entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+            return self._cw.build_url(entity.rest_path(), vid='deleteconf')
+        return self._cw.build_url(rql=self.cw_rset.printable_rql(), vid='deleteconf')
 
 
-class CopyAction(Action):
-    id = 'copy'
-    __select__ = one_line_rset() & has_permission('add')
+class CopyAction(action.Action):
+    __regid__ = 'copy'
+    __select__ = (action.Action.__select__ & one_line_rset()
+                  & has_permission('add'))
 
     title = _('copy')
     category = 'moreactions'
     order = 30
 
     def url(self):
-        entity = self.rset.get_entity(self.row or 0, self.col or 0)
+        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
         return entity.absolute_url(vid='copy')
 
 
@@ -203,8 +207,8 @@
     """when we're seeing more than one entity with the same type, propose to
     add a new one
     """
-    id = 'addentity'
-    __select__ = (match_search_state('normal') &
+    __regid__ = 'addentity'
+    __select__ = (action.Action.__select__ &
                   (addable_etype_empty_rset()
                    | (two_lines_rset() & one_etype_rset & has_add_permission()))
                   )
@@ -214,22 +218,22 @@
 
     @property
     def rsettype(self):
-        if self.rset:
-            return self.rset.description[0][0]
-        return self.rset._searched_etype
+        if self.cw_rset:
+            return self.cw_rset.description[0][0]
+        return self.cw_rset._searched_etype
 
     @property
     def title(self):
-        return self.req.__('add a %s' % self.rsettype) # generated msgid
+        return self._cw.__('add a %s' % self.rsettype) # generated msgid
 
     def url(self):
-        return self.build_url('add/%s' % self.rsettype)
+        return self._cw.build_url('add/%s' % self.rsettype)
 
 
-class AddRelatedActions(Action):
+class AddRelatedActions(action.Action):
     """fill 'addrelated' sub-menu of the actions box"""
-    id = 'addrelated'
-    __select__ = Action.__select__ & one_line_rset() & non_final_entity()
+    __regid__ = 'addrelated'
+    __select__ = action.Action.__select__ & one_line_rset() & non_final_entity()
 
     submenu = _('addrelated')
     order = 20
@@ -237,11 +241,11 @@
     def fill_menu(self, box, menu):
         # when there is only one item in the sub-menu, replace the sub-menu by
         # item's title prefixed by 'add'
-        menu.label_prefix = self.req._('add')
+        menu.label_prefix = self._cw._('add')
         super(AddRelatedActions, self).fill_menu(box, menu)
 
     def actual_actions(self):
-        entity = self.rset.get_entity(self.row or 0, self.col or 0)
+        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
         eschema = entity.e_schema
         for rschema, teschema, x in self.add_related_schemas(entity):
             if x == 'subject':
@@ -250,7 +254,7 @@
             else:
                 label = 'add %s %s %s %s' % (teschema, rschema, eschema, x)
                 url = self.linkto_url(entity, rschema, teschema, 'subject')
-            yield self.build_action(self.req._(label), url)
+            yield self.build_action(self._cw._(label), url)
 
     def add_related_schemas(self, entity):
         """this is actually used ui method to generate 'addrelated' actions from
@@ -261,7 +265,7 @@
         them by using uicfg.actionbox_appearsin_addmenu
         """
         appearsin_addmenu = uicfg.actionbox_appearsin_addmenu
-        req = self.req
+        req = self._cw
         eschema = entity.e_schema
         for role, rschemas in (('subject', eschema.subject_relations()),
                                ('object', eschema.object_relations())):
@@ -285,16 +289,16 @@
                         yield rschema, teschema, role
 
     def linkto_url(self, entity, rtype, etype, target):
-        return self.build_url('add/%s' % etype,
-                              __linkto='%s:%s:%s' % (rtype, entity.eid, target),
-                              __redirectpath=entity.rest_path(), # should not be url quoted!
-                              __redirectvid=self.req.form.get('vid', ''))
+        return self._cw.build_url('add/%s' % etype,
+                                  __linkto='%s:%s:%s' % (rtype, entity.eid, target),
+                                  __redirectpath=entity.rest_path(), # should not be url quoted!
+                                  __redirectvid=self._cw.form.get('vid', ''))
 
 
 # logged user actions #########################################################
 
-class UserPreferencesAction(Action):
-    id = 'myprefs'
+class UserPreferencesAction(action.Action):
+    __regid__ = 'myprefs'
     __select__ = authenticated_user()
 
     title = _('user preferences')
@@ -302,11 +306,11 @@
     order = 10
 
     def url(self):
-        return self.build_url(self.id)
+        return self._cw.build_url(self.__regid__)
 
 
-class UserInfoAction(Action):
-    id = 'myinfos'
+class UserInfoAction(action.Action):
+    __regid__ = 'myinfos'
     __select__ = authenticated_user()
 
     title = _('personnal informations')
@@ -314,11 +318,11 @@
     order = 20
 
     def url(self):
-        return self.build_url('cwuser/%s'%self.req.user.login, vid='edition')
+        return self._cw.build_url('cwuser/%s'%self._cw.user.login, vid='edition')
 
 
-class LogoutAction(Action):
-    id = 'logout'
+class LogoutAction(action.Action):
+    __regid__ = 'logout'
     __select__ = authenticated_user()
 
     title = _('logout')
@@ -326,40 +330,42 @@
     order = 30
 
     def url(self):
-        return self.build_url(self.id)
+        return self._cw.build_url(self.__regid__)
 
 
 # site actions ################################################################
 
-class ManagersAction(Action):
+class ManagersAction(action.Action):
     __abstract__ = True
     __select__ = match_user_groups('managers')
 
     category = 'siteactions'
 
     def url(self):
-        return self.build_url(self.id)
+        return self._cw.build_url(self.__regid__)
 
 
 class SiteConfigurationAction(ManagersAction):
-    id = 'siteconfig'
+    __regid__ = 'siteconfig'
     title = _('site configuration')
     order = 10
 
 
 class ManageAction(ManagersAction):
-    id = 'manage'
+    __regid__ = 'manage'
     title = _('manage')
     order = 20
 
 class SiteInfoAction(ManagersAction):
-    id = 'siteinfo'
+    __regid__ = 'siteinfo'
+    __select__ = match_user_groups('users','managers')
     title = _('info')
     order = 30
-    __select__ = match_user_groups('users','managers')
 
 
-class PoweredByAction(Action):
+# footer actions ###############################################################
+
+class PoweredByAction(action.Action):
     id = 'poweredby'
     __select__ = yes()
 
@@ -371,21 +377,9 @@
         return 'http://www.cubicweb.org'
 
 
-from logilab.common.deprecation import class_moved
-from cubicweb.web.views.bookmark import FollowAction
-FollowAction = class_moved(FollowAction)
-
 ## default actions ui configuration ###########################################
 
 addmenu = uicfg.actionbox_appearsin_addmenu
-addmenu.tag_subject_of(('*', 'is', '*'), False)
-addmenu.tag_object_of(('*', 'is', '*'), False)
-addmenu.tag_subject_of(('*', 'is_instance_of', '*'), False)
-addmenu.tag_object_of(('*', 'is_instance_of', '*'), False)
-addmenu.tag_subject_of(('*', 'identity', '*'), False)
-addmenu.tag_object_of(('*', 'identity', '*'), False)
-addmenu.tag_subject_of(('*', 'owned_by', '*'), False)
-addmenu.tag_subject_of(('*', 'created_by', '*'), False)
 addmenu.tag_subject_of(('*', 'require_permission', '*'), False)
 addmenu.tag_subject_of(('*', 'wf_info_for', '*'), False)
 addmenu.tag_object_of(('*', 'wf_info_for', '*'), False)
@@ -395,8 +389,6 @@
 addmenu.tag_object_of(('*', 'from_entity', 'CWEType'), False)
 addmenu.tag_object_of(('*', 'to_entity', 'CWEType'), False)
 addmenu.tag_object_of(('*', 'in_group', 'CWGroup'), True)
-addmenu.tag_object_of(('*', 'owned_by', 'CWUser'), False)
-addmenu.tag_object_of(('*', 'created_by', 'CWUser'), False)
 addmenu.tag_object_of(('*', 'bookmarked_by', 'CWUser'), True)
 addmenu.tag_subject_of(('Transition', 'destination_state', '*'), True)
 addmenu.tag_object_of(('*', 'allowed_transition', 'Transition'), True)
--- a/web/views/ajaxedit.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/ajaxedit.py	Wed Sep 30 18:57:42 2009 +0200
@@ -19,20 +19,20 @@
     class attributes.
     """
     __registry__ = 'views'
+    __regid__ = 'xaddrelation'
     __select__ = (match_form_params('rtype', 'target')
                   | match_kwargs('rtype', 'target'))
-    property_defs = {} # don't want to inherit this from Box
-    id = 'xaddrelation'
+    cw_property_defs = {} # don't want to inherit this from Box
     expected_kwargs = form_params = ('rtype', 'target')
 
     build_js = EditRelationBoxTemplate.build_reload_js_call
 
     def cell_call(self, row, col, rtype=None, target=None, etype=None):
-        self.rtype = rtype or self.req.form['rtype']
-        self.target = target or self.req.form['target']
-        self.etype = etype or self.req.form.get('etype')
-        entity = self.entity(row, col)
-        rschema = self.schema.rschema(self.rtype)
+        self.rtype = rtype or self._cw.form['rtype']
+        self.target = target or self._cw.form['target']
+        self.etype = etype or self._cw.form.get('etype')
+        entity = self.cw_rset.get_entity(row, col)
+        rschema = self._cw.schema.rschema(self.rtype)
         if not self.etype:
             if self.target == 'object':
                 etypes = rschema.objects(entity.e_schema)
@@ -40,9 +40,9 @@
                 etypes = rschema.subjects(entity.e_schema)
             if len(etypes) == 1:
                 self.etype = etypes[0]
-        self.w(u'<div id="%s">' % self.id)
-        self.w(u'<h1>%s</h1>' % self.req._('relation %(relname)s of %(ent)s')
-               % {'relname': rschema.display_name(self.req, role(self)),
+        self.w(u'<div id="%s">' % self.__regid__)
+        self.w(u'<h1>%s</h1>' % self._cw._('relation %(relname)s of %(ent)s')
+               % {'relname': rschema.display_name(self._cw, role(self)),
                   'ent': entity.view('incontext')})
         self.w(u'<ul>')
         for boxitem in self.unrelated_boxitems(entity):
@@ -59,13 +59,13 @@
         if getattr(self, 'etype', None):
             rset = entity.unrelated(self.rtype, self.etype, role(self),
                                     ordermethod='fetch_order')
-            self.pagination(self.req, rset, w=self.w)
+            self.pagination(self._cw, rset, w=self.w)
             return rset.entities()
         # in other cases, use vocabulary functions
         entities = []
         # XXX to update for 3.2
         for _, eid in entity.vocabulary(self.rtype, role(self)):
             if eid is not None:
-                rset = self.req.eid_rset(eid)
+                rset = self._cw.eid_rset(eid)
                 entities.append(rset.get_entity(0, 0))
         return entities
--- a/web/views/apacherewrite.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/apacherewrite.py	Wed Sep 30 18:57:42 2009 +0200
@@ -85,7 +85,7 @@
         ]
     """
     __abstract__ = True
-    id = 'urlrewriter'
+    __regid__ = 'urlrewriter'
     rules = []
 
     def get_rules(self, req):
--- a/web/views/authentication.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/authentication.py	Wed Sep 30 18:57:42 2009 +0200
@@ -18,9 +18,10 @@
 class RepositoryAuthenticationManager(AbstractAuthenticationManager):
     """authenticate user associated to a request and check session validity"""
 
-    def __init__(self):
-        self.repo = self.config.repository(self.vreg)
-        self.log_queries = self.config['query-log-file']
+    def __init__(self, vreg):
+        super(RepositoryAuthenticationManager, self).__init__(vreg)
+        self.repo = vreg.config.repository(vreg)
+        self.log_queries = vreg.config['query-log-file']
 
     def validate_session(self, req, session):
         """check session validity, and return eventually hijacked session
--- a/web/views/autoform.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/autoform.py	Wed Sep 30 18:57:42 2009 +0200
@@ -16,20 +16,21 @@
 from cubicweb.web.formfields import guess_field
 from cubicweb.web.views import forms, editforms
 
+_afs = uicfg.autoform_section
 
 class AutomaticEntityForm(forms.EntityFieldsForm):
     """base automatic form to edit any entity.
 
     Designed to be fully generated from schema but highly configurable through:
-    * rtags (rcategories, rfields, rwidgets, inlined, rpermissions)
+
+    * uicfg (autoform_* relation tags)
     * various standard form parameters
-
-    XXX s/rtags/uicfg/ ?
+    * overriding
 
     You can also easily customise it by adding/removing fields in
-    AutomaticEntityForm instances.
+    AutomaticEntityForm instances or by inheriting from it.
     """
-    id = 'edition'
+    __regid__ = 'edition'
 
     cwtarget = 'eformframe'
     cssclass = 'entityForm'
@@ -37,100 +38,18 @@
     form_buttons = [fwdgs.SubmitButton(),
                     fwdgs.Button(stdmsgs.BUTTON_APPLY, cwaction='apply'),
                     fwdgs.Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
-    attrcategories = ('primary', 'secondary')
+    # for attributes selection when searching in uicfg.autoform_section
+    formtype = 'main'
+    # set this to a list of [(relation, role)] if you want to explictily tell
+    # which relations should be edited
+    display_fields = None
     # class attributes below are actually stored in the uicfg module since we
     # don't want them to be reloaded
-    rcategories = uicfg.autoform_section
     rfields = uicfg.autoform_field
     rfields_kwargs = uicfg.autoform_field_kwargs
-    rinlined = uicfg.autoform_is_inlined
-    rpermissions_overrides = uicfg.autoform_permissions_overrides
 
     # class methods mapping schema relations to fields in the form ############
 
-    @classmethod
-    def erelations_by_category(cls, entity, categories=None, permission=None,
-                               rtags=None, strict=False):
-        """return a list of (relation schema, target schemas, role) matching
-        categories and permission
-
-        `strict`:
-          bool telling if having local role is enough (strict = False) or not
-        """
-        if categories is not None:
-            if not isinstance(categories, (list, tuple, set, frozenset)):
-                categories = (categories,)
-            if not isinstance(categories, (set, frozenset)):
-                categories = frozenset(categories)
-        eschema  = entity.e_schema
-        if rtags is None:
-            rtags = cls.rcategories
-        permsoverrides = cls.rpermissions_overrides
-        if entity.has_eid():
-            eid = entity.eid
-        else:
-            eid = None
-            strict = False
-        for rschema, targetschemas, role in eschema.relation_definitions(True):
-            # check category first, potentially lower cost than checking
-            # permission which may imply rql queries
-            if categories is not None:
-                targetschemas = [tschema for tschema in targetschemas
-                                 if rtags.etype_get(eschema, rschema, role, tschema) in categories]
-                if not targetschemas:
-                    continue
-            if permission is not None:
-                # tag allowing to hijack the permission machinery when
-                # permission is not verifiable until the entity is actually
-                # created...
-                if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role):
-                    yield (rschema, targetschemas, role)
-                    continue
-                if rschema.is_final():
-                    if not rschema.has_perm(entity.req, permission, eid):
-                        continue
-                elif role == 'subject':
-                    if not ((not strict and rschema.has_local_role(permission)) or
-                            rschema.has_perm(entity.req, permission, fromeid=eid)):
-                        continue
-                    # on relation with cardinality 1 or ?, we need delete perm as well
-                    # if the relation is already set
-                    if (permission == 'add'
-                        and rschema.cardinality(eschema, targetschemas[0], role) in '1?'
-                        and eid and entity.related(rschema.type, role)
-                        and not rschema.has_perm(entity.req, 'delete', fromeid=eid,
-                                                 toeid=entity.related(rschema.type, role)[0][0])):
-                        continue
-                elif role == 'object':
-                    if not ((not strict and rschema.has_local_role(permission)) or
-                            rschema.has_perm(entity.req, permission, toeid=eid)):
-                        continue
-                    # on relation with cardinality 1 or ?, we need delete perm as well
-                    # if the relation is already set
-                    if (permission == 'add'
-                        and rschema.cardinality(targetschemas[0], eschema, role) in '1?'
-                        and eid and entity.related(rschema.type, role)
-                        and not rschema.has_perm(entity.req, 'delete', toeid=eid,
-                                                 fromeid=entity.related(rschema.type, role)[0][0])):
-                        continue
-            yield (rschema, targetschemas, role)
-
-    @classmethod
-    def esrelations_by_category(cls, entity, categories=None, permission=None,
-                                strict=False):
-        """filter out result of relations_by_category(categories, permission) by
-        removing final relations
-
-        return a sorted list of (relation's label, relation'schema, role)
-        """
-        result = []
-        for rschema, ttypes, role in cls.erelations_by_category(
-            entity, categories, permission, strict=strict):
-            if rschema.is_final():
-                continue
-            result.append((rschema.display_name(entity.req, role), rschema, role))
-        return sorted(result)
-
     @iclassmethod
     def field_by_name(cls_or_self, name, role='subject', eschema=None):
         """return field with the given name and role. If field is not explicitly
@@ -140,9 +59,9 @@
         try:
             return super(AutomaticEntityForm, cls_or_self).field_by_name(name, role)
         except form.FieldNotFound:
-            if eschema is None or not name in cls_or_self.schema:
+            if eschema is None or not name in cls_or_self._cw.schema:
                 raise
-            rschema = cls_or_self.schema.rschema(name)
+            rschema = cls_or_self._cw.schema.rschema(name)
             # XXX use a sample target type. Document this.
             tschemas = rschema.targets(eschema, role)
             fieldcls = cls_or_self.rfields.etype_get(eschema, rschema, role,
@@ -167,21 +86,21 @@
         entity = self.edited_entity
         if entity.has_eid():
             entity.complete()
-        for rschema, role in self.editable_attributes():
+        for rtype, role in self.editable_attributes():
             try:
-                self.field_by_name(rschema.type, role)
+                self.field_by_name(str(rtype), role)
                 continue # explicitly specified
             except form.FieldNotFound:
                 # has to be guessed
                 try:
-                    field = self.field_by_name(rschema.type, role,
+                    field = self.field_by_name(str(rtype), role,
                                                eschema=entity.e_schema)
                     self.fields.append(field)
                 except form.FieldNotFound:
                     # meta attribute such as <attr>_format
                     continue
-        self.maxrelitems = self.req.property_value('navigation.related-limit')
-        self.force_display = bool(self.req.form.get('__force_display'))
+        self.maxrelitems = self._cw.property_value('navigation.related-limit')
+        self.force_display = bool(self._cw.form.get('__force_display'))
 
     @property
     def related_limit(self):
@@ -224,8 +143,9 @@
                 continue
             _tested.add(targettype)
             if self.should_inline_relation_form(rschema, targettype, role):
-                entity = self.vreg['etypes'].etype_class(targettype)(self.req)
-                subform = self.vreg['forms'].select('edition', self.req, entity=entity)
+                entity = self._cw.vreg['etypes'].etype_class(targettype)(self._cw)
+                subform = self._cw.vreg['forms'].select('edition', self._cw,
+                                                        entity=entity)
                 if hasattr(subform, '_subform_needs_multipart'):
                     needs_multipart = subform._subform_needs_multipart(_tested)
                 else:
@@ -241,7 +161,7 @@
         try:
             return self._action
         except AttributeError:
-            return self.build_url('validateform')
+            return self._cw.build_url('validateform')
 
     def set_action(self, value):
         """override default action"""
@@ -251,36 +171,38 @@
 
     # methods mapping edited entity relations to fields in the form ############
 
-    def relations_by_category(self, categories=None, permission=None):
+    def _relations_by_section(self, section, permission='add', strict=False):
         """return a list of (relation schema, target schemas, role) matching
         given category(ies) and permission
         """
-        return self.erelations_by_category(self.edited_entity, categories,
-                                           permission)
+        return _afs.relations_by_section(
+            self.edited_entity, self.formtype, section, permission, strict)
+
+    def editable_attributes(self, strict=False):
+        """return a list of (relation schema, role) to edit for the entity"""
+        if self.display_fields is not None:
+            return self.display_fields
+        # XXX we should simply put eid in the generated section, no?
+        return [(rtype, role) for rtype, _, role in self._relations_by_section(
+            'attributes', strict=strict) if rtype != 'eid']
+
+    def editable_relations(self):
+        """return a sorted list of (relation's label, relation'schema, role) for
+        relations in the 'relations' section
+        """
+        result = []
+        for rschema, _, role in self._relations_by_section('relations',
+                                                           strict=True):
+            result.append( (rschema.display_name(self.edited_entity._cw, role,
+                                                 self.edited_entity.__regid__),
+                            rschema, role) )
+        return sorted(result)
 
     def inlined_relations(self):
         """return a list of (relation schema, target schemas, role) matching
         given category(ies) and permission
         """
-        # we'll need an initialized varmaker if there are some inlined relation
-        self.initialize_varmaker()
-        return self.erelations_by_category(self.edited_entity, True, 'add',
-                                           self.rinlined)
-
-    def srelations_by_category(self, categories=None, permission=None,
-                               strict=False):
-        """filter out result of relations_by_category(categories, permission) by
-        removing final relations
-
-        return a sorted list of (relation's label, relation'schema, role)
-        """
-        return self.esrelations_by_category(self.edited_entity, categories,
-                                           permission, strict=strict)
-
-    def editable_attributes(self):
-        """return a list of (relation schema, role) to edit for the entity"""
-        return [(rschema, role) for rschema, _, role in self.relations_by_category(
-                self.attrcategories, 'add') if rschema != 'eid']
+        return self._relations_by_section('inlined')
 
     # generic relations modifier ###############################################
 
@@ -293,11 +215,10 @@
           - oneline view of related entity
         """
         entity = self.edited_entity
-        pending_deletes = self.req.get_pending_deletes(entity.eid)
-        for label, rschema, role in self.srelations_by_category('generic', 'add',
-                                                                strict=True):
+        pending_deletes = self._cw.get_pending_deletes(entity.eid)
+        for label, rschema, role in self.editable_relations():
             relatedrset = entity.related(rschema, role, limit=self.related_limit)
-            if rschema.has_perm(self.req, 'delete'):
+            if rschema.has_perm(self._cw, 'delete'):
                 toggleable_rel_link_func = editforms.toggleable_relation_link
             else:
                 toggleable_rel_link_func = lambda x, y, z: u''
@@ -312,7 +233,7 @@
                     status = u''
                     label = 'x'
                 dellink = toggleable_rel_link_func(entity.eid, nodeid, label)
-                eview = self.view('oneline', relatedrset, row=row)
+                eview = self._cw.view('oneline', relatedrset, row=row)
                 related.append((nodeid, dellink, status, eview))
             yield (rschema, role, related)
 
@@ -322,22 +243,24 @@
         """
         eid = self.edited_entity.eid
         cell = cell and "div_insert_" or "tr"
-        pending_inserts = set(self.req.get_pending_inserts(eid))
+        pending_inserts = set(self._cw.get_pending_inserts(eid))
         for pendingid in pending_inserts:
             eidfrom, rtype, eidto = pendingid.split(':')
             if typed_eid(eidfrom) == eid: # subject
-                label = display_name(self.req, rtype, 'subject')
+                label = display_name(self._cw, rtype, 'subject',
+                                     self.edited_entity.__regid__)
                 reid = eidto
             else:
-                label = display_name(self.req, rtype, 'object')
+                label = display_name(self._cw, rtype, 'object',
+                                     self.edited_entity.__regid__)
                 reid = eidfrom
             jscall = "javascript: cancelPendingInsert('%s', '%s', null, %s);" \
                      % (pendingid, cell, eid)
-            rset = self.req.eid_rset(reid)
-            eview = self.view('text', rset, row=0)
+            rset = self._cw.eid_rset(reid)
+            eview = self._cw.view('text', rset, row=0)
             # XXX find a clean way to handle baskets
             if rset.description[0][0] == 'Basket':
-                eview = '%s (%s)' % (eview, display_name(self.req, 'Basket'))
+                eview = '%s (%s)' % (eview, display_name(self._cw, 'Basket'))
             yield rtype, pendingid, jscall, label, reid, eview
 
     # inlined forms support ####################################################
@@ -385,16 +308,18 @@
     def should_inline_relation_form(self, rschema, targettype, role):
         """return true if the given relation with entity has role and a
         targettype target should be inlined
+
+        At this point we now relation has inlined_attributes tag (eg is returned
+        by `inlined_relations()`. Overrides this for more finer control.
         """
-        return self.rinlined.etype_get(self.edited_entity.id, rschema, role,
-                                       targettype)
+        return True
 
     def should_display_inline_creation_form(self, rschema, existant, card):
         """return true if a creation form should be inlined
 
         by default true if there is no related entity and we need at least one
         """
-        return not existant and card in '1+' or self.req.form.has_key('force_%s_display' % rschema)
+        return not existant and card in '1+' or self._cw.form.has_key('force_%s_display' % rschema)
 
     def should_display_add_new_relation_link(self, rschema, existant, card):
         """return true if we should add a link to add a new creation form
@@ -420,11 +345,11 @@
         entity = self.edited_entity
         related = entity.has_eid() and entity.related(rschema, role)
         if related:
-            vvreg = self.vreg['views']
+            vvreg = self._cw.vreg['views']
             # display inline-edition view for all existing related entities
             for i, relentity in enumerate(related.entities()):
                 if relentity.has_perm('update'):
-                    yield vvreg.select('inline-edition', self.req, related,
+                    yield vvreg.select('inline-edition', self._cw, related,
                                        row=i, col=0, rtype=rschema, role=role,
                                        peid=entity.eid, pform=self)
 
@@ -432,9 +357,9 @@
         """yield inline form views to a newly related (hence created) entity
         through the given relation
         """
-        yield self.vreg['views'].select('inline-creation', self.req,
-                                        etype=ttype, rtype=rschema, role=role,
-                                        peid=self.edited_entity.eid, pform=self)
+        yield self._cw.vreg['views'].select('inline-creation', self._cw,
+                                            etype=ttype, rtype=rschema, role=role,
+                                            peid=self.edited_entity.eid, pform=self)
 
 
 def etype_relation_field(etype, rtype, role='subject'):
@@ -454,12 +379,6 @@
 uicfg.autoform_section.tag_subject_of(('*', 'in_state', '*'), 'generated')
 uicfg.autoform_section.tag_subject_of(('*', 'owned_by', '*'), 'metadata')
 uicfg.autoform_section.tag_subject_of(('*', 'created_by', '*'), 'metadata')
-uicfg.autoform_section.tag_subject_of(('*', 'is', '*'), 'generated')
-uicfg.autoform_section.tag_object_of(('*', 'is', '*'), 'generated')
-uicfg.autoform_section.tag_subject_of(('*', 'is_instance_of', '*'), 'generated')
-uicfg.autoform_section.tag_object_of(('*', 'is_instance_of', '*'), 'generated')
-uicfg.autoform_section.tag_subject_of(('*', 'identity', '*'), 'generated')
-uicfg.autoform_section.tag_object_of(('*', 'identity', '*'), 'generated')
 uicfg.autoform_section.tag_subject_of(('*', 'require_permission', '*'), 'generated')
 uicfg.autoform_section.tag_subject_of(('*', 'by_transition', '*'), 'primary')
 uicfg.autoform_section.tag_object_of(('*', 'by_transition', '*'), 'generated')
@@ -476,8 +395,6 @@
 uicfg.autoform_section.tag_attribute(('CWUser', 'surname'), 'secondary')
 uicfg.autoform_section.tag_attribute(('CWUser', 'last_login_time'), 'metadata')
 uicfg.autoform_section.tag_subject_of(('CWUser', 'in_group', '*'), 'primary')
-uicfg.autoform_section.tag_object_of(('*', 'owned_by', 'CWUser'), 'generated')
-uicfg.autoform_section.tag_object_of(('*', 'created_by', 'CWUser'), 'generated')
 uicfg.autoform_section.tag_object_of(('*', 'bookmarked_by', 'CWUser'), 'metadata')
 uicfg.autoform_section.tag_attribute(('Bookmark', 'path'), 'primary')
 uicfg.autoform_section.tag_subject_of(('*', 'primary_email', '*'), 'generic')
--- a/web/views/basecomponents.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/basecomponents.py	Wed Sep 30 18:57:42 2009 +0200
@@ -29,29 +29,29 @@
 
 class RQLInputForm(component.Component):
     """build the rql input form, usually displayed in the header"""
-    id = 'rqlinput'
-    property_defs = VISIBLE_PROP_DEF
+    __regid__ = 'rqlinput'
+    cw_property_defs = VISIBLE_PROP_DEF
     visible = False
 
     def call(self, view=None):
         if hasattr(view, 'filter_box_context_info'):
             rset = view.filter_box_context_info()[0]
         else:
-            rset = self.rset
+            rset = self.cw_rset
         # display multilines query as one line
-        rql = rset is not None and rset.printable_rql(encoded=False) or self.req.form.get('rql', '')
+        rql = rset is not None and rset.printable_rql(encoded=False) or self._cw.form.get('rql', '')
         rql = rql.replace(u"\n", u" ")
-        req = self.req
+        req = self._cw
         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" />
 <input type="submit" value="" class="rqlsubmit" tabindex="%s" />
 </fieldset>
-''' % (not self.propval('visible') and 'hidden' or '',
-       self.build_url('view'), xml_escape(rql), req._('full text or RQL query'), req.next_tabindex(),
+''' % (not self.cw_propval('visible') and 'hidden' or '',
+       self._cw.build_url('view'), xml_escape(rql), req._('full text or RQL query'), req.next_tabindex(),
         req.next_tabindex()))
-        if self.req.search_state[0] != 'normal':
+        if self._cw.search_state[0] != 'normal':
             self.w(u'<input type="hidden" name="__mode" value="%s"/>'
                    % ':'.join(req.search_state[1]))
         self.w(u'</form></div>')
@@ -59,63 +59,63 @@
 
 class ApplLogo(component.Component):
     """build the instance logo, usually displayed in the header"""
-    id = 'logo'
-    property_defs = VISIBLE_PROP_DEF
+    __regid__ = 'logo'
+    cw_property_defs = VISIBLE_PROP_DEF
     # don't want user to hide this component using an cwproperty
     site_wide = True
 
     def call(self):
         self.w(u'<a href="%s"><img class="logo" src="%s" alt="logo"/></a>'
-               % (self.req.base_url(), self.req.external_resource('LOGO')))
+               % (self._cw.base_url(), self._cw.external_resource('LOGO')))
 
 
 class ApplHelp(component.Component):
     """build the help button, usually displayed in the header"""
-    id = 'help'
-    property_defs = VISIBLE_PROP_DEF
+    __regid__ = 'help'
+    cw_property_defs = VISIBLE_PROP_DEF
     def call(self):
         self.w(u'<a href="%s" class="help" title="%s">&#160;</a>'
-               % (self.build_url(_restpath='doc/main'),
-                  self.req._(u'help'),))
+               % (self._cw.build_url(_restpath='doc/main'),
+                  self._cw._(u'help'),))
 
 
 class UserLink(component.Component):
     """if the user is the anonymous user, build a link to login
     else a link to the connected user object with a loggout link
     """
-    property_defs = VISIBLE_PROP_DEF
+    cw_property_defs = VISIBLE_PROP_DEF
     # don't want user to hide this component using an cwproperty
     site_wide = True
-    id = 'loggeduserlink'
+    __regid__ = 'loggeduserlink'
 
     def call(self):
-        if not self.req.cnx.anonymous_connection:
+        if not self._cw.cnx.anonymous_connection:
             # display useractions and siteactions
-            actions = self.vreg['actions'].possible_actions(self.req, rset=self.rset)
+            actions = self._cw.vreg['actions'].possible_actions(self._cw, rset=self.cw_rset)
             box = MenuWidget('', 'userActionsBox', _class='', islist=False)
-            menu = PopupBoxMenu(self.req.user.login, isitem=False)
+            menu = PopupBoxMenu(self._cw.user.login, isitem=False)
             box.append(menu)
             for action in actions.get('useractions', ()):
-                menu.append(BoxLink(action.url(), self.req._(action.title),
+                menu.append(BoxLink(action.url(), self._cw._(action.title),
                                     action.html_class()))
             if actions.get('useractions') and actions.get('siteactions'):
                 menu.append(BoxSeparator())
             for action in actions.get('siteactions', ()):
-                menu.append(BoxLink(action.url(), self.req._(action.title),
+                menu.append(BoxLink(action.url(), self._cw._(action.title),
                                     action.html_class()))
             box.render(w=self.w)
         else:
             self.anon_user_link()
 
     def anon_user_link(self):
-        if self.config['auth-mode'] == 'cookie':
-            self.w(self.req._('anonymous'))
+        if self._cw.config['auth-mode'] == 'cookie':
+            self.w(self._cw._('anonymous'))
             self.w(u'''&#160;[<a class="logout" href="javascript: popupLoginBox();">%s</a>]'''
-                   % (self.req._('i18n_login_popup')))
+                   % (self._cw._('i18n_login_popup')))
         else:
-            self.w(self.req._('anonymous'))
+            self.w(self._cw._('anonymous'))
             self.w(u'&#160;[<a class="logout" href="%s">%s</a>]'
-                   % (self.build_url('login'), self.req._('login')))
+                   % (self._cw.build_url('login'), self._cw._('login')))
 
 
 class ApplicationMessage(component.Component):
@@ -123,13 +123,13 @@
     section
     """
     __select__ = yes()
-    id = 'applmessages'
+    __regid__ = 'applmessages'
     # don't want user to hide this component using an cwproperty
-    property_defs = {}
+    cw_property_defs = {}
 
     def call(self):
-        msgs = [msg for msg in (self.req.get_shared_data('sources_error', pop=True),
-                                self.req.message) if msg]
+        msgs = [msg for msg in (self._cw.get_shared_data('sources_error', pop=True),
+                                self._cw.message) if msg]
         self.w(u'<div id="appMsg" onclick="%s" class="%s">\n' %
                (toggle_action('appMsg'), (msgs and ' ' or 'hidden')))
         for msg in msgs:
@@ -140,21 +140,21 @@
 
 class ApplicationName(component.Component):
     """display the instance name"""
-    id = 'appliname'
-    property_defs = VISIBLE_PROP_DEF
+    __regid__ = 'appliname'
+    cw_property_defs = VISIBLE_PROP_DEF
     # don't want user to hide this component using an cwproperty
     site_wide = True
 
     def call(self):
-        title = self.req.property_value('ui.site-title')
+        title = self._cw.property_value('ui.site-title')
         if title:
             self.w(u'<span id="appliName"><a href="%s">%s</a></span>' % (
-                self.req.base_url(), xml_escape(title)))
+                self._cw.base_url(), xml_escape(title)))
 
 
 class SeeAlsoVComponent(component.RelatedObjectsVComponent):
     """display any entity's see also"""
-    id = 'seealso'
+    __regid__ = 'seealso'
     context = 'navcontentbottom'
     rtype = 'see_also'
     role = 'subject'
@@ -168,29 +168,29 @@
     """displays the list of entity types contained in the resultset
     to be able to filter accordingly.
     """
-    id = 'etypenavigation'
+    __regid__ = 'etypenavigation'
     __select__ = two_etypes_rset() | match_form_params('__restrtype', '__restrtypes',
                                                        '__restrrql')
-    property_defs = VISIBLE_PROP_DEF
+    cw_property_defs = VISIBLE_PROP_DEF
     # don't want user to hide this component using an cwproperty
     site_wide = True
     visible = False # disabled by default
 
     def call(self):
-        _ = self.req._
+        _ = self._cw._
         self.w(u'<div id="etyperestriction">')
-        restrtype = self.req.form.get('__restrtype')
-        restrtypes = self.req.form.get('__restrtypes', '').split(',')
-        restrrql = self.req.form.get('__restrrql')
+        restrtype = self._cw.form.get('__restrtype')
+        restrtypes = self._cw.form.get('__restrtypes', '').split(',')
+        restrrql = self._cw.form.get('__restrrql')
         if not restrrql:
-            rqlst = self.rset.syntax_tree()
-            restrrql = rqlst.as_string(self.req.encoding, self.rset.args)
-            restrtypes = self.rset.column_types(0)
+            rqlst = self.cw_rset.syntax_tree()
+            restrrql = rqlst.as_string(self._cw.encoding, self.cw_rset.args)
+            restrtypes = self.cw_rset.column_types(0)
         else:
             rqlst = parse(restrrql)
         html = []
         on_etype = False
-        etypes = sorted((display_name(self.req, etype).capitalize(), etype)
+        etypes = sorted((display_name(self._cw, etype).capitalize(), etype)
                         for etype in restrtypes)
         for elabel, etype in etypes:
             if etype == restrtype:
@@ -200,14 +200,14 @@
                 rqlst.save_state()
                 for select in rqlst.children:
                     select.add_type_restriction(select.selection[0], etype)
-                newrql = rqlst.as_string(self.req.encoding, self.rset.args)
-                url = self.build_url(rql=newrql, __restrrql=restrrql,
-                                     __restrtype=etype, __restrtypes=','.join(restrtypes))
+                newrql = rqlst.as_string(self._cw.encoding, self.cw_rset.args)
+                url = self._cw.build_url(rql=newrql, __restrrql=restrrql,
+                                         __restrtype=etype, __restrtypes=','.join(restrtypes))
                 html.append(u'<span><a href="%s">%s</a></span>' % (
                         xml_escape(url), elabel))
                 rqlst.recover()
         if on_etype:
-            url = self.build_url(rql=restrrql)
+            url = self._cw.build_url(rql=restrrql)
             html.insert(0, u'<span><a href="%s">%s</a></span>' % (
                     url, _('Any')))
         else:
@@ -216,11 +216,11 @@
         self.w(u'</div>')
 
 class PdfViewComponent(component.Component):
-    id = 'pdfview'
+    __regid__ = 'pdfview'
     __select__ = yes()
 
     context = 'header'
-    property_defs = {
+    cw_property_defs = {
         _('visible'):  dict(type='Boolean', default=True,
                             help=_('display the pdf icon or not')),
     }
@@ -229,7 +229,7 @@
         entity = self.entity(0,0)
         url = entity.absolute_url(vid=vid, __template='pdf-main-template')
         self.w(u'<a href="%s" class="otherView"><img src="data/pdf_icon.gif" alt="%s"/></a>' %
-               (xml_escape(url), self.req._('download page as pdf')))
+               (xml_escape(url), self._cw._('download page as pdf')))
 
 
 
--- a/web/views/basecontrollers.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/basecontrollers.py	Wed Sep 30 18:57:42 2009 +0200
@@ -36,7 +36,7 @@
     results
     """
     def wrapper(self, *args, **kwargs):
-        self.req.set_content_type('application/json')
+        self._cw.set_content_type('application/json')
         return json_dumps(func(self, *args, **kwargs))
     wrapper.__name__ = func.__name__
     return wrapper
@@ -44,9 +44,9 @@
 def xhtmlize(func):
     """decorator to sets correct content_type and calls `xmlize` on results"""
     def wrapper(self, *args, **kwargs):
-        self.req.set_content_type(self.req.html_content_type())
+        self._cw.set_content_type(self._cw.html_content_type())
         result = func(self, *args, **kwargs)
-        return ''.join((self.req.document_surrounding_div(), result.strip(),
+        return ''.join((self._cw.document_surrounding_div(), result.strip(),
                         u'</div>'))
     wrapper.__name__ = func.__name__
     return wrapper
@@ -56,32 +56,32 @@
     user's session data
     """
     def wrapper(self, *args, **kwargs):
-        data = self.req.get_session_data(self.req.pageid)
+        data = self._cw.get_session_data(self._cw.pageid)
         if data is None:
-            raise RemoteCallFailed(self.req._('pageid-not-found'))
+            raise RemoteCallFailed(self._cw._('pageid-not-found'))
         return func(self, *args, **kwargs)
     return wrapper
 
 
 class LoginController(Controller):
-    id = 'login'
+    __regid__ = 'login'
 
     def publish(self, rset=None):
         """log in the instance"""
-        if self.config['auth-mode'] == 'http':
+        if self._cw.config['auth-mode'] == 'http':
             # HTTP authentication
             raise ExplicitLogin()
         else:
             # Cookie authentication
-            return self.appli.need_login_content(self.req)
+            return self.appli.need_login_content(self._cw)
 
 
 class LogoutController(Controller):
-    id = 'logout'
+    __regid__ = 'logout'
 
     def publish(self, rset=None):
         """logout from the instance"""
-        return self.appli.session_handler.logout(self.req)
+        return self.appli.session_handler.logout(self._cw)
 
 
 class ViewController(Controller):
@@ -89,7 +89,7 @@
     - build result set
     - select and call main template
     """
-    id = 'view'
+    __regid__ = 'view'
     template = 'main-template'
 
     def publish(self, rset=None):
@@ -97,12 +97,12 @@
         view, rset = self._select_view_and_rset(rset)
         self.add_to_breadcrumbs(view)
         self.validate_cache(view)
-        template = self.appli.main_template_id(self.req)
-        return self.vreg['views'].main_template(self.req, template,
+        template = self.appli.main_template_id(self._cw)
+        return self._cw.vreg['views'].main_template(self._cw, template,
                                                 rset=rset, view=view)
 
     def _select_view_and_rset(self, rset):
-        req = self.req
+        req = self._cw
         if rset is None and not hasattr(req, '_rql_processed'):
             req._rql_processed = True
             rset = self.process_rql(req.form.get('rql'))
@@ -116,14 +116,14 @@
             except Exception, ex:
                 self.exception('while handling __method')
                 req.set_message(req._("error while handling __method: %s") % req._(ex))
-        vid = req.form.get('vid') or vid_from_rset(req, rset, self.schema)
+        vid = req.form.get('vid') or vid_from_rset(req, rset, self._cw.schema)
         try:
-            view = self.vreg['views'].select(vid, req, rset=rset)
+            view = self._cw.vreg['views'].select(vid, req, rset=rset)
         except ObjectNotFound:
             self.warning("the view %s could not be found", vid)
             req.set_message(req._("The view %s could not be found") % vid)
-            vid = vid_from_rset(req, rset, self.schema)
-            view = self.vreg['views'].select(vid, req, rset=rset)
+            vid = vid_from_rset(req, rset, self._cw.schema)
+            view = self._cw.vreg['views'].select(vid, req, rset=rset)
         except NoSelectableObject:
             if rset:
                 req.set_message(req._("The view %s can not be applied to this query") % vid)
@@ -131,8 +131,8 @@
                 req.set_message(req._("You have no access to this view or it can not "
                                       "be used to display the current data."))
             self.warning("the view %s can not be applied to this query", vid)
-            vid = vid_from_rset(req, rset, self.schema)
-            view = self.vreg['views'].select(vid, req, rset=rset)
+            vid = vid_from_rset(req, rset, self._cw.schema)
+            view = self._cw.vreg['views'].select(vid, req, rset=rset)
         return view, rset
 
     def add_to_breadcrumbs(self, view):
@@ -140,11 +140,11 @@
         # specifies explicitly it should not be added to breadcrumb or the
         # view is a binary view
         if view.add_to_breadcrumbs and not view.binary:
-            self.req.update_breadcrumbs()
+            self._cw.update_breadcrumbs()
 
     def validate_cache(self, view):
         view.set_http_cache_headers()
-        self.req.validate_cache()
+        self._cw.validate_cache()
 
     def execute_linkto(self, eid=None):
         """XXX __linkto parameter may cause security issue
@@ -152,7 +152,7 @@
         defined here since custom application controller inheriting from this
         one use this method?
         """
-        req = self.req
+        req = self._cw
         if not '__linkto' in req.form:
             return
         if eid is None:
@@ -208,13 +208,13 @@
 
 
 class FormValidatorController(Controller):
-    id = 'validateform'
+    __regid__ = 'validateform'
 
     def response(self, domid, status, args, entity):
-        callback = str(self.req.form.get('__onsuccess', 'null'))
-        errback = str(self.req.form.get('__onfailure', 'null'))
-        cbargs = str(self.req.form.get('__cbargs', 'null'))
-        self.req.set_content_type('text/html')
+        callback = str(self._cw.form.get('__onsuccess', 'null'))
+        errback = str(self._cw.form.get('__onfailure', 'null'))
+        cbargs = str(self._cw.form.get('__cbargs', 'null'))
+        self._cw.set_content_type('text/html')
         jsargs = simplejson.dumps((status, args, entity), cls=CubicWebJsonEncoder)
         return """<script type="text/javascript">
  wp = window.parent;
@@ -222,17 +222,17 @@
 </script>""" %  (domid, callback, errback, jsargs, cbargs)
 
     def publish(self, rset=None):
-        self.req.json_request = True
+        self._cw.json_request = True
         # XXX unclear why we have a separated controller here vs
         # js_validate_form on the json controller
-        status, args, entity = _validate_form(self.req, self.vreg)
-        domid = self.req.form.get('__domid', 'entityForm').encode(
-            self.req.encoding)
+        status, args, entity = _validate_form(self._cw, self._cw.vreg)
+        domid = self._cw.form.get('__domid', 'entityForm').encode(
+            self._cw.encoding)
         return self.response(domid, status, args, entity)
 
 
 class JSonController(Controller):
-    id = 'json'
+    __regid__ = 'json'
 
     def publish(self, rset=None):
         """call js_* methods. Expected form keys:
@@ -243,17 +243,17 @@
         note: it's the responsability of js_* methods to set the correct
         response content type
         """
-        self.req.json_request = True
-        self.req.pageid = self.req.form.get('pageid')
+        self._cw.json_request = True
+        self._cw.pageid = self._cw.form.get('pageid')
         try:
-            fname = self.req.form['fname']
+            fname = self._cw.form['fname']
             func = getattr(self, 'js_%s' % fname)
         except KeyError:
             raise RemoteCallFailed('no method specified')
         except AttributeError:
             raise RemoteCallFailed('no %s method' % fname)
         # no <arg> attribute means the callback takes no argument
-        args = self.req.form.get('arg', ())
+        args = self._cw.form.get('arg', ())
         if not isinstance(args, (list, tuple)):
             args = (args,)
         args = [simplejson.loads(arg) for arg in args]
@@ -271,7 +271,7 @@
             return ''
         # get unicode on @htmlize methods, encoded string on @jsonize methods
         elif isinstance(result, unicode):
-            return result.encode(self.req.encoding)
+            return result.encode(self._cw.encoding)
         return result
 
     def _rebuild_posted_form(self, names, values, action=None):
@@ -297,16 +297,16 @@
     def _exec(self, rql, args=None, eidkey=None, rocheck=True):
         """json mode: execute RQL and return resultset as json"""
         if rocheck:
-            self.ensure_ro_rql(rql)
+            self._cw.ensure_ro_rql(rql)
         try:
-            return self.req.execute(rql, args, eidkey)
+            return self._cw.execute(rql, args, eidkey)
         except Exception, ex:
             self.exception("error in _exec(rql=%s): %s", rql, ex)
             return None
         return None
 
     def _call_view(self, view, **kwargs):
-        req = self.req
+        req = self._cw
         divid = req.form.get('divid', 'pageContent')
         # we need to call pagination before with the stream set
         stream = view.set_stream()
@@ -314,7 +314,7 @@
             if divid == 'pageContent':
                 # mimick main template behaviour
                 stream.write(u'<div id="pageContent">')
-                vtitle = self.req.form.get('vtitle')
+                vtitle = self._cw.form.get('vtitle')
                 if vtitle:
                     stream.write(u'<h1 class="vtitle">%s</h1>\n' % vtitle)
             view.paginate()
@@ -333,30 +333,30 @@
     @xhtmlize
     def js_view(self):
         # XXX try to use the page-content template
-        req = self.req
+        req = self._cw
         rql = req.form.get('rql')
         if rql:
             rset = self._exec(rql)
         else:
             rset = None
-        vid = req.form.get('vid') or vid_from_rset(req, rset, self.schema)
+        vid = req.form.get('vid') or vid_from_rset(req, rset, self._cw.schema)
         try:
-            view = self.vreg['views'].select(vid, req, rset=rset)
+            view = self._cw.vreg['views'].select(vid, req, rset=rset)
         except NoSelectableObject:
             vid = req.form.get('fallbackvid', 'noresult')
-            view = self.vreg['views'].select(vid, req, rset=rset)
+            view = self._cw.vreg['views'].select(vid, req, rset=rset)
         return self._call_view(view)
 
     @xhtmlize
     def js_prop_widget(self, propkey, varname, tabindex=None):
         """specific method for CWProperty handling"""
-        entity = self.vreg['etypes'].etype_class('CWProperty')(self.req)
+        entity = self._cw.vreg['etypes'].etype_class('CWProperty')(self._cw)
         entity.eid = varname
         entity['pkey'] = propkey
-        form = self.vreg['forms'].select('edition', self.req, entity=entity)
+        form = self._cw.vreg['forms'].select('edition', self._cw, entity=entity)
         form.build_context()
         vfield = form.field_by_name('value')
-        renderer = FormRenderer(self.req)
+        renderer = FormRenderer(self._cw)
         return vfield.render(form, renderer, tabindex=tabindex) \
                + renderer.render_help(form, vfield)
 
@@ -366,7 +366,7 @@
             rset = self._exec(rql)
         else:
             rset = None
-        comp = self.vreg[registry].select(compid, self.req, rset=rset)
+        comp = self._cw.vreg[registry].select(compid, self._cw, rset=rset)
         if extraargs is None:
             extraargs = {}
         else: # we receive unicode keys which is not supported by the **syntax
@@ -378,7 +378,7 @@
     @check_pageid
     @xhtmlize
     def js_inline_creation_form(self, peid, ttype, rtype, role, i18nctx):
-        view = self.vreg['views'].select('inline-creation', self.req,
+        view = self._cw.vreg['views'].select('inline-creation', self._cw,
                                          etype=ttype, peid=peid, rtype=rtype,
                                          role=role)
         return self._call_view(view, i18nctx=i18nctx)
@@ -388,8 +388,8 @@
         return self.validate_form(action, names, values)
 
     def validate_form(self, action, names, values):
-        self.req.form = self._rebuild_posted_form(names, values, action)
-        return _validate_form(self.req, self.vreg)
+        self._cw.form = self._rebuild_posted_form(names, values, action)
+        return _validate_form(self._cw, self._cw.vreg)
 
     @jsonize
     def js_edit_field(self, action, names, values, rtype, eid, default):
@@ -397,7 +397,7 @@
         if success:
             # Any X,N where we don't seem to use N is an optimisation
             # printable_value won't need to query N again
-            rset = self.req.execute('Any X,N WHERE X eid %%(x)s, X %s N' % rtype,
+            rset = self._cw.execute('Any X,N WHERE X eid %%(x)s, X %s N' % rtype,
                                     {'x': eid}, 'x')
             entity = rset.get_entity(0, 0)
             value = entity.printable_value(rtype) or default
@@ -408,59 +408,59 @@
     @jsonize
     def js_reledit_form(self, eid, rtype, role, default, lzone):
         """XXX we should get rid of this and use loadxhtml"""
-        entity = self.req.entity_from_eid(eid)
+        entity = self._cw.entity_from_eid(eid)
         return entity.view('reledit', rtype=rtype, role=role,
                            default=default, landing_zone=lzone)
 
     @jsonize
     def js_i18n(self, msgids):
         """returns the translation of `msgid`"""
-        return [self.req._(msgid) for msgid in msgids]
+        return [self._cw._(msgid) for msgid in msgids]
 
     @jsonize
     def js_format_date(self, strdate):
         """returns the formatted date for `msgid`"""
         date = strptime(strdate, '%Y-%m-%d %H:%M:%S')
-        return self.format_date(date)
+        return self._cw.format_date(date)
 
     @jsonize
     def js_external_resource(self, resource):
         """returns the URL of the external resource named `resource`"""
-        return self.req.external_resource(resource)
+        return self._cw.external_resource(resource)
 
     @check_pageid
     @jsonize
     def js_user_callback(self, cbname):
-        page_data = self.req.get_session_data(self.req.pageid, {})
+        page_data = self._cw.get_session_data(self._cw.pageid, {})
         try:
             cb = page_data[cbname]
         except KeyError:
             return None
-        return cb(self.req)
+        return cb(self._cw)
 
     if HAS_SEARCH_RESTRICTION:
         @jsonize
         def js_filter_build_rql(self, names, values):
             form = self._rebuild_posted_form(names, values)
-            self.req.form = form
-            builder = FilterRQLBuilder(self.req)
+            self._cw.form = form
+            builder = FilterRQLBuilder(self._cw)
             return builder.build_rql()
 
         @jsonize
         def js_filter_select_content(self, facetids, rql):
-            rqlst = self.vreg.parse(self.req, rql) # XXX Union unsupported yet
+            rqlst = self._cw.vreg.parse(self._cw, rql) # XXX Union unsupported yet
             mainvar = prepare_facets_rqlst(rqlst)[0]
             update_map = {}
             for facetid in facetids:
-                facet = get_facet(self.req, facetid, rqlst.children[0], mainvar)
+                facet = get_facet(self._cw, facetid, rqlst.children[0], mainvar)
                 update_map[facetid] = facet.possible_values()
             return update_map
 
     def js_unregister_user_callback(self, cbname):
-        self.req.unregister_callback(self.req.pageid, cbname)
+        self._cw.unregister_callback(self._cw.pageid, cbname)
 
     def js_unload_page_data(self):
-        self.req.del_session_data(self.req.pageid)
+        self._cw.del_session_data(self._cw.pageid)
 
     def js_cancel_edition(self, errorurl):
         """cancelling edition from javascript
@@ -469,21 +469,21 @@
           - errorurl
           - pending insertions / deletions
         """
-        self.req.cancel_edition(errorurl)
+        self._cw.cancel_edition(errorurl)
 
     def js_delete_bookmark(self, beid):
         rql = 'DELETE B bookmarked_by U WHERE B eid %(b)s, U eid %(u)s'
-        self.req.execute(rql, {'b': typed_eid(beid), 'u' : self.req.user.eid})
+        self._cw.execute(rql, {'b': typed_eid(beid), 'u' : self._cw.user.eid})
 
     def js_node_clicked(self, treeid, nodeeid):
         """add/remove eid in treestate cookie"""
         from cubicweb.web.views.treeview import treecookiename
-        cookies = self.req.get_cookie()
+        cookies = self._cw.get_cookie()
         statename = treecookiename(treeid)
         treestate = cookies.get(statename)
         if treestate is None:
             cookies[statename] = nodeeid
-            self.req.set_cookie(cookies, statename)
+            self._cw.set_cookie(cookies, statename)
         else:
             marked = set(filter(None, treestate.value.split(';')))
             if nodeeid in marked:
@@ -491,28 +491,28 @@
             else:
                 marked.add(nodeeid)
             cookies[statename] = ';'.join(marked)
-            self.req.set_cookie(cookies, statename)
+            self._cw.set_cookie(cookies, statename)
 
     def js_set_cookie(self, cookiename, cookievalue):
         # XXX we should consider jQuery.Cookie
         cookiename, cookievalue = str(cookiename), str(cookievalue)
-        cookies = self.req.get_cookie()
+        cookies = self._cw.get_cookie()
         cookies[cookiename] = cookievalue
-        self.req.set_cookie(cookies, cookiename)
+        self._cw.set_cookie(cookies, cookiename)
 
     # relations edition stuff ##################################################
 
     def _add_pending(self, eidfrom, rel, eidto, kind):
         key = 'pending_%s' % kind
-        pendings = self.req.get_session_data(key, set())
+        pendings = self._cw.get_session_data(key, set())
         pendings.add( (typed_eid(eidfrom), rel, typed_eid(eidto)) )
-        self.req.set_session_data(key, pendings)
+        self._cw.set_session_data(key, pendings)
 
     def _remove_pending(self, eidfrom, rel, eidto, kind):
         key = 'pending_%s' % kind
-        pendings = self.req.get_session_data(key)
+        pendings = self._cw.get_session_data(key)
         pendings.remove( (typed_eid(eidfrom), rel, typed_eid(eidto)) )
-        self.req.set_session_data(key, pendings)
+        self._cw.set_session_data(key, pendings)
 
     def js_remove_pending_insert(self, (eidfrom, rel, eidto)):
         self._remove_pending(eidfrom, rel, eidto, 'insert')
@@ -531,24 +531,24 @@
     @jsonize
     def js_add_and_link_new_entity(self, etype_to, rel, eid_to, etype_from, value_from):
         # create a new entity
-        eid_from = self.req.execute('INSERT %s T : T name "%s"' % ( etype_from, value_from ))[0][0]
+        eid_from = self._cw.execute('INSERT %s T : T name "%s"' % ( etype_from, value_from ))[0][0]
         # link the new entity to the main entity
         rql = 'SET F %(rel)s T WHERE F eid %(eid_to)s, T eid %(eid_from)s' % {'rel' : rel, 'eid_to' : eid_to, 'eid_from' : eid_from}
         return eid_from
 
 
 class SendMailController(Controller):
-    id = 'sendmail'
+    __regid__ = 'sendmail'
     __select__ = match_user_groups('managers', 'users')
 
     def recipients(self):
         """returns an iterator on email's recipients as entities"""
-        eids = self.req.form['recipient']
+        eids = self._cw.form['recipient']
         # make sure we have a list even though only one recipient was specified
         if isinstance(eids, basestring):
             eids = (eids,)
         rql = 'Any X WHERE X eid in (%s)' % (','.join(eids))
-        rset = self.req.execute(rql)
+        rset = self._cw.execute(rql)
         for entity in rset.entities():
             entity.complete() # XXX really?
             yield entity
@@ -556,43 +556,43 @@
     @property
     @cached
     def smtp(self):
-        mailhost, port = self.config['smtp-host'], self.config['smtp-port']
+        mailhost, port = self._cw.config['smtp-host'], self._cw.config['smtp-port']
         try:
             return SMTP(mailhost, port)
         except Exception, ex:
             self.exception("can't connect to smtp server %s:%s (%s)",
                              mailhost, port, ex)
-            url = self.build_url(__message=self.req._('could not connect to the SMTP server'))
+            url = self._cw.build_url(__message=self._cw._('could not connect to the SMTP server'))
             raise Redirect(url)
 
     def sendmail(self, recipient, subject, body):
-        helo_addr = '%s <%s>' % (self.config['sender-name'],
-                                 self.config['sender-addr'])
-        msg = format_mail({'email' : self.req.user.get_email(),
-                           'name' : self.req.user.dc_title(),},
+        helo_addr = '%s <%s>' % (self._cw.config['sender-name'],
+                                 self._cw.config['sender-addr'])
+        msg = format_mail({'email' : self._cw.user.get_email(),
+                           'name' : self._cw.user.dc_title(),},
                           [recipient], body, subject)
         self.smtp.sendmail(helo_addr, [recipient], msg.as_string())
 
     def publish(self, rset=None):
         # XXX this allows users with access to an cubicweb instance to use it as
         # a mail relay
-        body = self.req.form['mailbody']
-        subject = self.req.form['subject']
+        body = self._cw.form['mailbody']
+        subject = self._cw.form['subject']
         for recipient in self.recipients():
             text = body % recipient.as_email_context()
             self.sendmail(recipient.get_email(), subject, text)
-        # breadcrumbs = self.req.get_session_data('breadcrumbs', None)
-        url = self.build_url(__message=self.req._('emails successfully sent'))
+        # breadcrumbs = self._cw.get_session_data('breadcrumbs', None)
+        url = self._cw.build_url(__message=self._cw._('emails successfully sent'))
         raise Redirect(url)
 
 
 class MailBugReportController(SendMailController):
-    id = 'reportbug'
+    __regid__ = 'reportbug'
     __select__ = yes()
 
     def publish(self, rset=None):
-        body = self.req.form['description']
-        self.sendmail(self.config['submit-mail'], _('%s error report') % self.config.appid, body)
-        url = self.build_url(__message=self.req._('bug report sent'))
+        body = self._cw.form['description']
+        self.sendmail(self._cw.config['submit-mail'], _('%s error report') % self._cw.config.appid, body)
+        url = self._cw.build_url(__message=self._cw._('bug report sent'))
         raise Redirect(url)
 
--- a/web/views/baseforms.py	Wed Sep 30 17:57:02 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,608 +0,0 @@
-"""Set of HTML automatic forms to create, delete, copy or edit a single entity
-or a list of entities of the same type
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from copy import copy
-
-from simplejson import dumps
-
-from logilab.mtconverter import xml_escape
-from logilab.common.decorators import cached
-
-from cubicweb.selectors import (specified_etype_implements, accepts_etype_compat,
-                                non_final_entity, match_kwargs, one_line_rset)
-from cubicweb.view import View, EntityView
-from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param
-from cubicweb.web.controller import NAV_FORM_PARAMETERS
-from cubicweb.web.widgets import checkbox, InputWidget, ComboBoxWidget
-from cubicweb.web.form import FormMixIn
-from cubicweb.web.views.autoform import AutomaticEntityForm
-
-_ = unicode
-
-
-class EditionForm(FormMixIn, EntityView):
-    """primary entity edition form
-
-    When generating a new attribute_input, the editor will look for a method
-    named 'default_ATTRNAME' on the entity instance, where ATTRNAME is the
-    name of the attribute being edited. You may use this feature to compute
-    dynamic default values such as the 'tomorrow' date or the user's login
-    being connected
-    """
-    id = 'edition'
-    __select__ = one_line_rset() & non_final_entity()
-
-    title = _('edition')
-    controller = 'edit'
-    skip_relations = set()
-
-    EDITION_BODY = u'''\
- %(errormsg)s
-<form id="%(formid)s" class="entityForm" cubicweb:target="eformframe"
-      method="post" onsubmit="%(onsubmit)s" enctype="%(enctype)s" action="%(action)s">
- %(title)s
- <div id="progress">%(inprogress)s</div>
- <div class="iformTitle"><span>%(mainattrs_label)s</span></div>
- <div class="formBody"><fieldset>
- %(base)s
- %(attrform)s
- %(relattrform)s
-</fieldset>
- %(relform)s
- </div>
- <table width="100%%">
-  <tbody>
-   <tr><td align="center">
-     %(validate)s
-   </td><td style="align: right; width: 50%%;">
-     %(apply)s
-     %(cancel)s
-   </td></tr>
-  </tbody>
- </table>
-</form>
-'''
-
-    def cell_call(self, row, col, **kwargs):
-        self.req.add_js( ('cubicweb.ajax.js', ) )
-        entity = self.complete_entity(row, col)
-        self.edit_form(entity, kwargs)
-
-    def edit_form(self, entity, kwargs):
-        varmaker = self.req.get_page_data('rql_varmaker')
-        if varmaker is None:
-            varmaker = self.req.varmaker
-            self.req.set_page_data('rql_varmaker', varmaker)
-        self.varmaker = varmaker
-        self.w(self.EDITION_BODY % self.form_context(entity, kwargs))
-
-    def form_context(self, entity, kwargs):
-        """returns the dictionnary used to fill the EDITION_BODY template
-
-        If you create your own edition form, you can probably just override
-        `EDITION_BODY` and `form_context`
-        """
-        if self.need_multipart(entity):
-            enctype = 'multipart/form-data'
-        else:
-            enctype = 'application/x-www-form-urlencoded'
-        self._hiddens = []
-        if entity.eid is None:
-            entity.eid = self.varmaker.next()
-        # XXX (hack) action_title might need __linkto req's original value
-        #            and widgets such as DynamicComboWidget might change it
-        #            so we need to compute title before calling atttributes_form
-        formtitle = self.action_title(entity)
-        # be sure to call .*_form first so tabindexes are correct and inlined
-        # fields errors are consumed
-        if not entity.has_eid() or entity.has_perm('update'):
-            attrform = self.attributes_form(entity, kwargs)
-        else:
-            attrform = ''
-        inlineform = self.inline_entities_form(entity, kwargs)
-        relform = self.relations_form(entity, kwargs)
-        vindex = self.req.next_tabindex()
-        aindex = self.req.next_tabindex()
-        cindex = self.req.next_tabindex()
-        self.add_hidden_web_behaviour_params(entity)
-        _ = self.req._
-        return {
-            'formid'   : self.domid,
-            'onsubmit' : self.on_submit(entity),
-            'enctype'  : enctype,
-            'errormsg' : self.error_message(),
-            'action'   : self.build_url('validateform'),
-            'eids'     : entity.has_eid() and [entity.eid] or [],
-            'inprogress': _('validating...'),
-            'title'    : formtitle,
-            'mainattrs_label' : _('main informations'),
-            'reseturl' : self.redirect_url(entity),
-            'attrform' : attrform,
-            'relform'  : relform,
-            'relattrform': inlineform,
-            'base'     : self.base_form(entity, kwargs),
-            'validate' : self.button_ok(tabindex=vindex),
-            'apply'    : self.button_apply(tabindex=aindex),
-            'cancel'   : self.button_cancel(tabindex=cindex),
-            }
-
-    @property
-    def formid(self):
-        return self.id
-
-    def action_title(self, entity):
-        """form's title"""
-        ptitle = self.req._(self.title)
-        return u'<div class="formTitle"><span>%s %s</span></div>' % (
-            entity.dc_type(), ptitle and '(%s)' % ptitle)
-
-
-    def base_form(self, entity, kwargs):
-        output = []
-        for name, value, iid in self._hiddens:
-            if isinstance(value, basestring):
-                value = xml_escape(value)
-            if iid:
-                output.append(u'<input id="%s" type="hidden" name="%s" value="%s" />'
-                              % (iid, name, value))
-            else:
-                output.append(u'<input type="hidden" name="%s" value="%s" />'
-                              % (name, value))
-        return u'\n'.join(output)
-
-    def add_hidden_web_behaviour_params(self, entity):
-        """inserts hidden params controlling how errors and redirection
-        should be handled
-        """
-        req = self.req
-        self._hiddens.append( (u'__maineid', entity.eid, u'') )
-        self._hiddens.append( (u'__errorurl', req.url(), u'errorurl') )
-        self._hiddens.append( (u'__form_id', self.formid, u'') )
-        for param in NAV_FORM_PARAMETERS:
-            value = req.form.get(param)
-            if value:
-                self._hiddens.append( (param, value, u'') )
-        msg = self.submited_message()
-        # If we need to directly attach the new object to another one
-        for linkto in req.list_form_param('__linkto'):
-            self._hiddens.append( ('__linkto', linkto, '') )
-            msg = '%s %s' % (msg, self.req._('and linked'))
-        self._hiddens.append( ('__message', msg, '') )
-
-
-    def attributes_form(self, entity, kwargs, include_eid=True):
-        """create a form to edit entity's attributes"""
-        html = []
-        w = html.append
-        eid = entity.eid
-        wdg = entity.get_widget
-        lines = (wdg(rschema, x) for rschema, x in self.editable_attributes(entity))
-        if include_eid:
-            self._hiddens.append( ('eid', entity.eid, '') )
-        self._hiddens.append( (eid_param('__type', eid), entity.e_schema, '') )
-        w(u'<table id="%s" class="%s" style="width:100%%;">' %
-          (kwargs.get('tab_id', 'entityForm%s' % eid),
-           kwargs.get('tab_class', 'attributeForm')))
-        for widget in lines:
-            w(u'<tr>\n<th class="labelCol">%s</th>' % widget.render_label(entity))
-            error = widget.render_error(entity)
-            if error:
-                w(u'<td class="error" style="width:100%;">')
-            else:
-                w(u'<td style="width:100%;">')
-            if error:
-                w(error)
-            w(widget.edit_render(entity))
-            w(widget.render_help(entity))
-            w(u'</td>\n</tr>')
-        w(u'</table>')
-        return u'\n'.join(html)
-
-    def editable_attributes(self, entity):
-        # XXX both (add, delete)
-        return [(rschema, x) for rschema, _, x in entity.relations_by_category(('primary', 'secondary'), 'add')
-                if rschema != 'eid']
-
-    def relations_form(self, entity, kwargs):
-        srels_by_cat = entity.srelations_by_category(('generic', 'metadata'), 'add')
-        if not srels_by_cat:
-            return u''
-        req = self.req
-        _ = self.req._
-        __ = _
-        label = u'%s :' % __('This %s' % entity.e_schema).capitalize()
-        eid = entity.eid
-        html = []
-        w = html.append
-        w(u'<fieldset class="subentity">')
-        w(u'<legend class="iformTitle">%s</legend>' % label)
-        w(u'<table id="relatedEntities">')
-        for row in self.relations_table(entity):
-            # already linked entities
-            if row[2]:
-                w(u'<tr><th class="labelCol">%s</th>' % row[0].display_name(req, row[1]))
-                w(u'<td>')
-                w(u'<ul>')
-                for viewparams in row[2]:
-                    w(u'<li class="invisible">%s<div id="span%s" class="%s">%s</div></li>'
-                      % (viewparams[1], viewparams[0], viewparams[2], viewparams[3]))
-                if not self.force_display and self.maxrelitems < len(row[2]):
-                    w(u'<li class="invisible">%s</li>' % self.force_display_link())
-                w(u'</ul>')
-                w(u'</td>')
-                w(u'</tr>')
-        pendings = list(self.restore_pending_inserts(entity))
-        if not pendings:
-            w(u'<tr><th>&#160;</th><td>&#160;</td></tr>')
-        else:
-            for row in pendings:
-                # soon to be linked to entities
-                w(u'<tr id="tr%s">' % row[1])
-                w(u'<th>%s</th>' % row[3])
-                w(u'<td>')
-                w(u'<a class="handle" title="%s" href="%s">[x]</a>' %
-                  (_('cancel this insert'), row[2]))
-                w(u'<a id="a%s" class="editionPending" href="%s">%s</a>'
-                  % (row[1], row[4], xml_escape(row[5])))
-                w(u'</td>')
-                w(u'</tr>')
-        w(u'<tr id="relationSelectorRow_%s" class="separator">' % eid)
-        w(u'<th class="labelCol">')
-        w(u'<span>%s</span>' % _('add relation'))
-        w(u'<select id="relationSelector_%s" tabindex="%s" onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
-          % (eid, req.next_tabindex(), xml_escape(dumps(eid))))
-        w(u'<option value="">%s</option>' % _('select a relation'))
-        for i18nrtype, rschema, target in srels_by_cat:
-            # more entities to link to
-            w(u'<option value="%s_%s">%s</option>' % (rschema, target, i18nrtype))
-        w(u'</select>')
-        w(u'</th>')
-        w(u'<td id="unrelatedDivs_%s"></td>' % eid)
-        w(u'</tr>')
-        w(u'</table>')
-        w(u'</fieldset>')
-        return '\n'.join(html)
-
-    def inline_entities_form(self, entity, kwargs):
-        """create a form to edit entity's inlined relations"""
-        result = []
-        _ = self.req._
-        for rschema, targettypes, x in entity.relations_by_category('inlineview', 'add'):
-            # show inline forms only if there's one possible target type
-            # for rschema
-            if len(targettypes) != 1:
-                self.warning('entity related by the %s relation should have '
-                             'inlined form but there is multiple target types, '
-                             'dunno what to do', rschema)
-                continue
-            targettype = targettypes[0].type
-            if self.should_inline_relation_form(entity, rschema, targettype, x):
-                result.append(u'<div id="inline%sslot">' % rschema)
-                existant = entity.has_eid() and entity.related(rschema)
-                if existant:
-                    # display inline-edition view for all existing related entities
-                    result.append(self.view('inline-edition', existant,
-                                            ptype=entity.e_schema, peid=entity.eid,
-                                            rtype=rschema, role=x, **kwargs))
-                if x == 'subject':
-                    card = rschema.rproperty(entity.e_schema, targettype, 'cardinality')[0]
-                else:
-                    card = rschema.rproperty(targettype, entity.e_schema, 'cardinality')[1]
-                # there is no related entity and we need at least one : we need to
-                # display one explicit inline-creation view
-                if self.should_display_inline_relation_form(rschema, existant, card):
-                    result.append(self.view('inline-creation', None, etype=targettype,
-                                            peid=entity.eid, ptype=entity.e_schema,
-                                            rtype=rschema, role=x, **kwargs))
-                # we can create more than one related entity, we thus display a link
-                # to add new related entities
-                if self.should_display_add_inline_relation_link(rschema, existant, card):
-                    divid = "addNew%s%s%s:%s" % (targettype, rschema, x, entity.eid)
-                    result.append(u'<div class="inlinedform" id="%s" cubicweb:limit="true">'
-                                  % divid)
-                    js = "addInlineCreationForm('%s', '%s', '%s', '%s', '%s')" % (
-                        entity.eid, entity.e_schema, targettype, rschema, x)
-                    if card in '1?':
-                        js = "toggleVisibility('%s'); %s" % (divid, js)
-                    result.append(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
-                                  % (rschema, entity.eid, js,
-                                     self.req.__('add a %s' % targettype)))
-                    result.append(u'</div>')
-                    result.append(u'<div class="trame_grise">&#160;</div>')
-                result.append(u'</div>')
-        return '\n'.join(result)
-
-    # should_* method extracted to allow overriding
-
-    def should_inline_relation_form(self, entity, rschema, targettype, role):
-        return AutomaticEntityForm.rinlined.etype_get(entity.id, rschema, role,
-                                                      targettype)
-
-    def should_display_inline_relation_form(self, rschema, existant, card):
-        return not existant and card in '1+'
-
-    def should_display_add_inline_relation_link(self, rschema, existant, card):
-        return not existant or card in '+*'
-
-    def reset_url(self, entity):
-        return entity.absolute_url()
-
-    def on_submit(self, entity):
-        return u'return freezeFormButtons(\'%s\')' % (self.domid)
-
-    def submited_message(self):
-        return self.req._('element edited')
-
-
-
-class CreationForm(EditionForm):
-    __select__ = specified_etype_implements('Any')
-    # XXX bw compat, use View.registered since we don't want accept_compat
-    #    wrapper set in EntityView
-    registered = accepts_etype_compat(View.registered)
-    id = 'creation'
-    title = _('creation')
-
-    def call(self, **kwargs):
-        """creation view for an entity"""
-        self.req.add_js( ('cubicweb.ajax.js',) )
-        self.initialize_varmaker()
-        etype = kwargs.pop('etype', self.req.form.get('etype'))
-        try:
-            entity = self.vreg.etype_class(etype)(self.req, None, None)
-        except:
-            self.w(self.req._('no such entity type %s') % etype)
-        else:
-            entity.eid = self.varmaker.next()
-            self.edit_form(entity, kwargs)
-
-    def action_title(self, entity):
-        """custom form title if creating a entity with __linkto"""
-        if '__linkto' in self.req.form:
-            if isinstance(self.req.form['__linkto'], list):
-                # XXX which one should be considered (case: add a ticket to a version in jpl)
-                rtype, linkto_eid, role = self.req.form['__linkto'][0].split(':')
-            else:
-                rtype, linkto_eid, role = self.req.form['__linkto'].split(':')
-            linkto_rset = self.req.eid_rset(linkto_eid)
-            linkto_type = linkto_rset.description[0][0]
-            if role == 'subject':
-                title = self.req.__('creating %s (%s %s %s %%(linkto)s)' % (
-                    entity.e_schema, entity.e_schema, rtype, linkto_type))
-            else:
-                title = self.req.__('creating %s (%s %%(linkto)s %s %s)' % (
-                    entity.e_schema, linkto_type, rtype, entity.e_schema))
-            msg = title % {'linkto' : self.view('incontext', linkto_rset)}
-            return u'<div class="formTitle notransform"><span>%s</span></div>' % msg
-        else:
-            return super(CreationForm, self).action_title(entity)
-
-    @property
-    def formid(self):
-        return 'edition'
-
-    def relations_form(self, entity, kwargs):
-        return u''
-
-    def reset_url(self, entity=None):
-        return self.build_url(self.req.form.get('etype', '').lower())
-
-    def submited_message(self):
-        return self.req._('element created')
-
-    def url(self):
-        """return the url associated with this view"""
-        return self.create_url(self.req.form.get('etype'))
-
-
-class InlineFormMixIn(object):
-
-    @cached
-    def card(self, etype):
-        return self.rschema.rproperty(self.parent_schema, etype, 'cardinality')[0]
-
-    def action_title(self, entity):
-        return self.rschema.display_name(self.req, self.role)
-
-    def add_hidden_web_behaviour_params(self, entity):
-        pass
-
-    def edit_form(self, entity, ptype, peid, rtype,
-                  role='subject', **kwargs):
-        self.rschema = self.schema.rschema(rtype)
-        self.role = role
-        self.parent_schema = self.schema.eschema(ptype)
-        self.parent_eid = peid
-        super(InlineFormMixIn, self).edit_form(entity, kwargs)
-
-    def should_inline_relation_form(self, entity, rschema, targettype, role):
-        if rschema == self.rschema:
-            return False
-        return AutomaticEntityForm.rinlined.etype_get(entity.id, rschema, role,
-                                                      targettype)
-
-    @cached
-    def keep_entity(self, entity):
-        req = self.req
-        # are we regenerating form because of a validation error ?
-        erroneous_post = req.data.get('formvalues')
-        if erroneous_post:
-            cdvalues = req.list_form_param('%s:%s' % (self.rschema,
-                                                      self.parent_eid),
-                                           erroneous_post)
-            if unicode(entity.eid) not in cdvalues:
-                return False
-        return True
-
-    def form_context(self, entity, kwargs):
-        ctx = super(InlineFormMixIn, self).form_context(entity, kwargs)
-        _ = self.req._
-        local_ctx = {'createmsg' : self.req.__('add a %s' % entity.e_schema),
-                     'so': self.role[0], # 's' for subject, 'o' for object
-                     'eid' : entity.eid,
-                     'rtype' : self.rschema,
-                     'parenteid' : self.parent_eid,
-                     'parenttype' : self.parent_schema,
-                     'etype' : entity.e_schema,
-                     'novalue' : INTERNAL_FIELD_VALUE,
-                     'removemsg' : self.req.__('remove this %s' % entity.e_schema),
-                     'notice' : self.req._('click on the box to cancel the deletion'),
-                     }
-        ctx.update(local_ctx)
-        return ctx
-
-
-class CopyEditionForm(EditionForm):
-    id = 'copy'
-    title = _('copy edition')
-
-    def cell_call(self, row, col, **kwargs):
-        self.req.add_js(('cubicweb.ajax.js',))
-        entity = self.complete_entity(row, col, skip_bytes=True)
-        # make a copy of entity to avoid altering the entity in the
-        # request's cache.
-        self.newentity = copy(entity)
-        self.copying = self.newentity.eid
-        self.newentity.eid = None
-        self.edit_form(self.newentity, kwargs)
-        del self.newentity
-
-    def action_title(self, entity):
-        """form's title"""
-        msg = super(CopyEditionForm, self).action_title(entity)
-        return msg + (u'<script type="text/javascript">updateMessage("%s");</script>\n'
-                      % self.req._('Please note that this is only a shallow copy'))
-        # XXX above message should have style of a warning
-
-    @property
-    def formid(self):
-        return 'edition'
-
-    def relations_form(self, entity, kwargs):
-        return u''
-
-    def reset_url(self, entity):
-        return self.build_url('view', rql='Any X WHERE X eid %s' % self.copying)
-
-    def attributes_form(self, entity, kwargs, include_eid=True):
-        # we don't want __clone_eid on inlined edited entities
-        if entity.eid == self.newentity.eid:
-            self._hiddens.append((eid_param('__cloned_eid', entity.eid), self.copying, ''))
-        return EditionForm.attributes_form(self, entity, kwargs, include_eid)
-
-    def submited_message(self):
-        return self.req._('element copied')
-
-
-class TableEditForm(FormMixIn, EntityView):
-    id = 'muledit'
-    title = _('multiple edit')
-
-    EDITION_BODY = u'''<form method="post" id="entityForm" onsubmit="return validateForm('entityForm', null);" action="%(action)s">
-  %(error)s
-  <div id="progress">%(progress)s</div>
-  <fieldset>
-  <input type="hidden" name="__errorurl" value="%(url)s" />
-  <input type="hidden" name="__form_id" value="%(formid)s" />
-  <input type="hidden" name="__redirectvid" value="%(redirectvid)s" />
-  <input type="hidden" name="__redirectrql" value="%(redirectrql)s" />
-  <table class="listing">
-    <tr class="header">
-      <th align="left"><input type="checkbox" onclick="setCheckboxesState('eid', this.checked)" value="" title="toggle check boxes" /></th>
-      %(attrheaders)s
-    </tr>
-    %(lines)s
-  </table>
-  <table width="100%%">
-    <tr>
-      <td align="left">
-        <input class="validateButton" type="submit"  value="%(okvalue)s" title="%(oktitle)s" />
-        <input class="validateButton" type="reset" name="__action_cancel" value="%(cancelvalue)s" title="%(canceltitle)s" />
-      </td>
-    </tr>
-  </table>
-  </fieldset>
-</form>
-'''
-
-    WIDGET_CELL = u'''\
-<td%(csscls)s>
-  %(error)s
-  <div>%(widget)s</div>
-</td>'''
-
-    def call(self, **kwargs):
-        """a view to edit multiple entities of the same type
-        the first column should be the eid
-        """
-        req = self.req
-        form = req.form
-        _ = req._
-        sampleentity = self.complete_entity(0)
-        attrheaders = [u'<th>%s</th>' % rdef[0].display_name(req, rdef[-1])
-                       for rdef in sampleentity.relations_by_category('primary', 'add')
-                       if rdef[0].type != 'eid']
-        ctx = {'action' : self.build_url('edit'),
-               'error': self.error_message(),
-               'progress': _('validating...'),
-               'url': xml_escape(req.url()),
-               'formid': self.id,
-               'redirectvid': xml_escape(form.get('__redirectvid', 'list')),
-               'redirectrql': xml_escape(form.get('__redirectrql', self.rset.printable_rql())),
-               'attrheaders': u'\n'.join(attrheaders),
-               'lines': u'\n'.join(self.edit_form(ent) for ent in self.rset.entities()),
-               'okvalue': _('button_ok').capitalize(),
-               'oktitle': _('validate modifications on selected items').capitalize(),
-               'cancelvalue': _('button_reset').capitalize(),
-               'canceltitle': _('revert changes').capitalize(),
-               }
-        self.w(self.EDITION_BODY % ctx)
-
-
-    def reset_url(self, entity=None):
-        self.build_url('view', rql=self.rset.printable_rql())
-
-    def edit_form(self, entity):
-        html = []
-        w = html.append
-        entity.complete()
-        eid = entity.eid
-        values = self.req.data.get('formvalues', ())
-        qeid = eid_param('eid', eid)
-        checked = qeid in values
-        w(u'<tr class="%s">' % (entity.row % 2 and u'even' or u'odd'))
-        w(u'<td>%s<input type="hidden" name="__type:%s" value="%s" /></td>'
-          % (checkbox('eid', eid, checked=checked), eid, entity.e_schema))
-        # attribute relations (skip eid which is handled by the checkbox
-        wdg = entity.get_widget
-        wdgfactories = [wdg(rschema, x) for rschema, _, x in entity.relations_by_category('primary', 'add')
-                        if rschema.type != 'eid'] # XXX both (add, delete)
-        seid = xml_escape(dumps(eid))
-        for wobj in wdgfactories:
-            if isinstance(wobj, ComboBoxWidget):
-                wobj.attrs['onchange'] = "setCheckboxesState2('eid', %s, 'checked')" % seid
-            elif isinstance(wobj, InputWidget):
-                wobj.attrs['onkeypress'] = "setCheckboxesState2('eid', %s, 'checked')" % seid
-            error = wobj.render_error(entity)
-            if error:
-                csscls = u' class="error"'
-            else:
-                csscls = u''
-            w(self.WIDGET_CELL % {'csscls': csscls, 'error': error,
-                                  'widget': wobj.edit_render(entity)})
-        w(u'</tr>')
-        return '\n'.join(html)
-
-
-# XXX bw compat
-
-from logilab.common.deprecation import class_moved
-from cubicweb.web.views import editviews
-ComboboxView = class_moved(editviews.ComboboxView)
--- a/web/views/basetemplates.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/basetemplates.py	Wed Sep 30 18:57:42 2009 +0200
@@ -24,7 +24,7 @@
         self.set_request_content_type()
         w = self.w
         self.write_doctype()
-        self.template_header('text/html', self.req._('login_action'))
+        self.template_header('text/html', self._cw._('login_action'))
         w(u'<body>\n')
         self.content(w)
         w(u'</body>')
@@ -32,37 +32,37 @@
     def template_header(self, content_type, view=None, page_title='', additional_headers=()):
         w = self.whead
         # explictly close the <base> tag to avoid IE 6 bugs while browsing DOM
-        w(u'<base href="%s"></base>' % xml_escape(self.req.base_url()))
+        w(u'<base href="%s"></base>' % xml_escape(self._cw.base_url()))
         w(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
-          % (content_type, self.req.encoding))
+          % (content_type, self._cw.encoding))
         w(NOINDEX)
         w(NOFOLLOW)
         w(u'\n'.join(additional_headers) + u'\n')
-        self.wview('htmlheader', rset=self.rset)
+        self.wview('htmlheader', rset=self.cw_rset)
         w(u'<title>%s</title>\n' % xml_escape(page_title))
 
 
 class LogInTemplate(LogInOutTemplate):
-    id = 'login'
+    __regid__ = 'login'
     title = 'log in'
 
     def content(self, w):
-        self.wview('logform', rset=self.rset, id='loginBox', klass='')
+        self.wview('logform', rset=self.cw_rset, id='loginBox', klass='')
 
 
 class LoggedOutTemplate(LogInOutTemplate):
-    id = 'loggedout'
+    __regid__ = 'loggedout'
     title = 'logged out'
 
     def content(self, w):
         # FIXME Deprecated code ?
-        msg = self.req._('you have been logged out')
+        msg = self._cw._('you have been logged out')
         w(u'<h2>%s</h2>\n' % msg)
-        if self.config['anonymous-user']:
-            indexurl = self.build_url('view', vid='index', __message=msg)
+        if self._cw.config['anonymous-user']:
+            indexurl = self._cw.build_url('view', vid='index', __message=msg)
             w(u'<p><a href="%s">%s</a><p>' % (
                 xml_escape(indexurl),
-                self.req._('go back to the index page')))
+                self._cw._('go back to the index page')))
 
 @objectify_selector
 def templatable_view(cls, req, rset, *args, **kwargs):
@@ -78,15 +78,15 @@
 
 class NonTemplatableViewTemplate(MainTemplate):
     """main template for any non templatable views (xml, binaries, etc.)"""
-    id = 'main-template'
+    __regid__ = 'main-template'
     __select__ = ~templatable_view()
 
     def call(self, view):
         view.set_request_content_type()
         view.set_stream()
-        if (self.req.form.has_key('__notemplate') and view.templatable
-            and view.content_type == self.req.html_content_type()):
-            view.w(self.req.document_surrounding_div())
+        if (self._cw.form.has_key('__notemplate') and view.templatable
+            and view.content_type == self._cw.html_content_type()):
+            view.w(self._cw.document_surrounding_div())
             view.render()
             view.w(u'</div>')
         else:
@@ -101,7 +101,7 @@
 
     - call header / footer templates
     """
-    id = 'main-template'
+    __regid__ = 'main-template'
     __select__ = templatable_view()
 
     def call(self, view):
@@ -109,13 +109,13 @@
         self.template_header(self.content_type, view)
         w = self.w
         w(u'<div id="pageContent">\n')
-        vtitle = self.req.form.get('vtitle')
+        vtitle = self._cw.form.get('vtitle')
         if vtitle:
             w(u'<h1 class="vtitle">%s</h1>\n' % xml_escape(vtitle))
         # display entity type restriction component
-        etypefilter = self.vreg['components'].select_vobject(
-            'etypenavigation', self.req, rset=self.rset)
-        if etypefilter:
+        etypefilter = self._cw.vreg['components'].select_or_none(
+            'etypenavigation', self._cw, rset=self.cw_rset)
+        if etypefilter and etypefilter.cw_propval('visible'):
             etypefilter.render(w=w)
         self.nav_html = UStringIO()
         if view and view.need_navigation:
@@ -136,30 +136,29 @@
 
     def template_html_header(self, content_type, page_title, additional_headers=()):
         w = self.whead
-        lang = self.req.lang
+        lang = self._cw.lang
         self.write_doctype()
         # explictly close the <base> tag to avoid IE 6 bugs while browsing DOM
-        w(u'<base href="%s"></base>' % xml_escape(self.req.base_url()))
+        w(u'<base href="%s"></base>' % xml_escape(self._cw.base_url()))
         w(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
-          % (content_type, self.req.encoding))
+          % (content_type, self._cw.encoding))
         w(u'\n'.join(additional_headers) + u'\n')
-        self.wview('htmlheader', rset=self.rset)
+        self.wview('htmlheader', rset=self.cw_rset)
         if page_title:
             w(u'<title>%s</title>\n' % xml_escape(page_title))
 
     def template_body_header(self, view):
         w = self.w
         w(u'<body>\n')
-        self.wview('header', rset=self.rset, view=view)
+        self.wview('header', rset=self.cw_rset, view=view)
         w(u'<div id="page"><table width="100%" border="0" id="mainLayout"><tr>\n')
         self.nav_column(view, 'left')
         w(u'<td id="contentcol">\n')
-        rqlcomp = self.vreg['components'].select_object('rqlinput', self.req,
-                                                        rset=self.rset)
+        components = self._cw.vreg['components']
+        rqlcomp = components.select_or_none('rqlinput', self._cw, rset=self.cw_rset)
         if rqlcomp:
             rqlcomp.render(w=self.w, view=view)
-        msgcomp = self.vreg['components'].select_object('applmessages',
-                                                        self.req, rset=self.rset)
+        msgcomp = components.select_or_none('applmessages', self._cw, rset=self.cw_rset)
         if msgcomp:
             msgcomp.render(w=self.w)
         self.content_header(view)
@@ -169,12 +168,12 @@
         self.w(u'</td>\n')
         self.nav_column(view, 'right')
         self.w(u'</tr></table></div>\n')
-        self.wview('footer', rset=self.rset)
+        self.wview('footer', rset=self.cw_rset)
         self.w(u'</body>')
 
     def nav_column(self, view, context):
-        boxes = list(self.vreg['boxes'].possible_vobjects(
-            self.req, rset=self.rset, view=view, context=context))
+        boxes = list(self._cw.vreg['boxes'].poss_visible_objects(
+            self._cw, rset=self.cw_rset, view=view, context=context))
         if boxes:
             self.w(u'<td class="navcol"><div class="navboxes">\n')
             for box in boxes:
@@ -183,10 +182,10 @@
 
     def content_header(self, view=None):
         """by default, display informal messages in content header"""
-        self.wview('contentheader', rset=self.rset, view=view)
+        self.wview('contentheader', rset=self.cw_rset, view=view)
 
     def content_footer(self, view=None):
-        self.wview('contentfooter', rset=self.rset, view=view)
+        self.wview('contentfooter', rset=self.cw_rset, view=view)
 
 
 class ErrorTemplate(TheMainTemplate):
@@ -194,26 +193,26 @@
     main template. This template may be called for authentication error,
     which means that req.cnx and req.user may not be set.
     """
-    id = 'error-template'
+    __regid__ = 'error-template'
 
     def call(self):
         """display an unexpected error"""
         self.set_request_content_type()
-        self.req.reset_headers()
-        view = self.vreg['views'].select('error', self.req, rset=self.rset)
-        self.template_header(self.content_type, view, self.req._('an error occured'),
+        self._cw.reset_headers()
+        view = self._cw.vreg['views'].select('error', self._cw, rset=self.cw_rset)
+        self.template_header(self.content_type, view, self._cw._('an error occured'),
                              [NOINDEX, NOFOLLOW])
         view.render(w=self.w)
         self.template_footer(view)
 
     def template_header(self, content_type, view=None, page_title='', additional_headers=()):
         w = self.whead
-        lang = self.req.lang
+        lang = self._cw.lang
         self.write_doctype()
         w(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
-          % (content_type, self.req.encoding))
+          % (content_type, self._cw.encoding))
         w(u'\n'.join(additional_headers))
-        self.wview('htmlheader', rset=self.rset)
+        self.wview('htmlheader', rset=self.cw_rset)
         w(u'<title>%s</title>\n' % xml_escape(page_title))
         self.w(u'<body>\n')
 
@@ -223,18 +222,18 @@
 
 class SimpleMainTemplate(TheMainTemplate):
 
-    id = 'main-no-top'
+    __regid__ = 'main-no-top'
 
     def template_header(self, content_type, view=None, page_title='', additional_headers=()):
         page_title = page_title or view.page_title()
         additional_headers = additional_headers or view.html_headers()
         whead = self.whead
-        lang = self.req.lang
+        lang = self._cw.lang
         self.write_doctype()
         whead(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
-              % (content_type, self.req.encoding))
+              % (content_type, self._cw.encoding))
         whead(u'\n'.join(additional_headers) + u'\n')
-        self.wview('htmlheader', rset=self.rset)
+        self.wview('htmlheader', rset=self.cw_rset)
         w = self.w
         w(u'<title>%s</title>\n' % xml_escape(page_title))
         w(u'<body>\n')
@@ -242,8 +241,8 @@
         w(u'<table width="100%" height="100%" border="0"><tr>\n')
         w(u'<td class="navcol">\n')
         self.topleft_header()
-        boxes = list(self.vreg['boxes'].possible_vobjects(
-            self.req, rset=self.rset, view=view, context='left'))
+        boxes = list(self._cw.vreg['boxes'].poss_visible_objects(
+            self._cw, rset=self.cw_rset, view=view, context='left'))
         if boxes:
             w(u'<div class="navboxes">\n')
             for box in boxes:
@@ -252,14 +251,14 @@
         w(u'</td>')
         w(u'<td id="contentcol" rowspan="2">')
         w(u'<div id="pageContent">\n')
-        vtitle = self.req.form.get('vtitle')
+        vtitle = self._cw.form.get('vtitle')
         if vtitle:
             w(u'<h1 class="vtitle">%s</h1>' % xml_escape(vtitle))
 
     def topleft_header(self):
-        logo = self.vreg['components'].select_vobject('logo', self.req,
-                                                      rset=self.rset)
-        if logo:
+        logo = self._cw.vreg['components'].select_or_none('logo', self._cw,
+                                                      rset=self.cw_rset)
+        if logo and logo.cw_propval('visible'):
             self.w(u'<table id="header"><tr>\n')
             self.w(u'<td>')
             logo.render(w=self.w)
@@ -274,15 +273,15 @@
     from cubicweb.ext.xhtml2fo import ReportTransformer
 
     class PdfMainTemplate(TheMainTemplate):
-        id = 'pdf-main-template'
+        __regid__ = 'pdf-main-template'
 
         def call(self, view):
             """build the standard view, then when it's all done, convert xhtml to pdf
             """
             super(PdfMainTemplate, self).call(view)
-            section = self.req.form.pop('section', 'contentmain')
+            section = self._cw.form.pop('section', 'contentmain')
             pdf = self.to_pdf(self._stream, section)
-            self.req.set_content_type('application/pdf', filename='report.pdf')
+            self._cw.set_content_type('application/pdf', filename='report.pdf')
             self.binary = True
             self.w = None
             self.set_stream()
@@ -309,7 +308,7 @@
 
 class HTMLHeader(View):
     """default html headers"""
-    id = 'htmlheader'
+    __regid__ = 'htmlheader'
 
     def call(self, **kwargs):
         self.favicon()
@@ -319,12 +318,12 @@
         self.pageid()
 
     def favicon(self):
-        favicon = self.req.external_resource('FAVICON', None)
+        favicon = self._cw.external_resource('FAVICON', None)
         if favicon:
             self.whead(u'<link rel="shortcut icon" href="%s"/>\n' % favicon)
 
     def stylesheets(self):
-        req = self.req
+        req = self._cw
         add_css = req.add_css
         for css in req.external_resource('STYLESHEETS'):
             add_css(css, localfile=False)
@@ -334,18 +333,18 @@
             add_css(css, localfile=False, ieonly=True)
 
     def javascripts(self):
-        for jscript in self.req.external_resource('JAVASCRIPTS'):
-            self.req.add_js(jscript, localfile=False)
+        for jscript in self._cw.external_resource('JAVASCRIPTS'):
+            self._cw.add_js(jscript, localfile=False)
 
     def alternates(self):
-        urlgetter = self.vreg['components'].select_object('rss_feed_url',
-                                            self.req, rset=self.rset)
+        urlgetter = self._cw.vreg['components'].select_or_none('rss_feed_url',
+                                                           self._cw, rset=self.cw_rset)
         if urlgetter is not None:
             self.whead(u'<link rel="alternate" type="application/rss+xml" title="RSS feed" href="%s"/>\n'
                        %  xml_escape(urlgetter.feed_url()))
 
     def pageid(self):
-        req = self.req
+        req = self._cw
         pid = make_uid(id(req))
         req.pageid = pid
         req.html_headers.define_var('pageid', pid)
@@ -353,7 +352,7 @@
 
 class HTMLPageHeader(View):
     """default html page header"""
-    id = 'header'
+    __regid__ = 'header'
     main_cell_components = ('appliname', 'breadcrumbs')
 
     def call(self, view, **kwargs):
@@ -369,49 +368,49 @@
         """build the top menu with authentification info and the rql box"""
         self.w(u'<table id="header"><tr>\n')
         self.w(u'<td id="firstcolumn">')
-        logo = self.vreg['components'].select_vobject(
-            'logo', self.req, rset=self.rset)
-        if logo:
+        logo = self._cw.vreg['components'].select_or_none(
+            'logo', self._cw, rset=self.cw_rset)
+        if logo and logo.cw_propval('visible'):
             logo.render(w=self.w)
         self.w(u'</td>\n')
         # appliname and breadcrumbs
         self.w(u'<td id="headtext">')
         for cid in self.main_cell_components:
-            comp = self.vreg['components'].select_vobject(
-                cid, self.req, rset=self.rset)
-            if comp:
+            comp = self._cw.vreg['components'].select_or_none(
+                cid, self._cw, rset=self.cw_rset)
+            if comp and comp.cw_propval('visible'):
                 comp.render(w=self.w)
         self.w(u'</td>')
         # logged user and help
         self.w(u'<td>\n')
-        comp = self.vreg['components'].select_vobject(
-            'loggeduserlink', self.req, rset=self.rset)
-        if comp:
+        comp = self._cw.vreg['components'].select_or_none(
+            'loggeduserlink', self._cw, rset=self.cw_rset)
+        if comp and comp.cw_propval('visible'):
             comp.render(w=self.w)
         self.w(u'</td><td>')
-        helpcomp = self.vreg['components'].select_vobject(
-            'help', self.req, rset=self.rset)
-        if helpcomp:
+        helpcomp = self._cw.vreg['components'].select_or_none(
+            'help', self._cw, rset=self.cw_rset)
+        if helpcomp and helpcomp.cw_propval('visible'):
             helpcomp.render(w=self.w)
         self.w(u'</td>')
         # lastcolumn
         self.w(u'<td id="lastcolumn">')
         self.w(u'</td>\n')
         self.w(u'</tr></table>\n')
-        self.wview('logform', rset=self.rset, id='popupLoginBox', klass='hidden',
+        self.wview('logform', rset=self.cw_rset, id='popupLoginBox', klass='hidden',
                    title=False, message=False)
 
     def state_header(self):
-        state = self.req.search_state
+        state = self._cw.search_state
         if state[0] == 'normal':
             return
-        _ = self.req._
-        value = self.view('oneline', self.req.eid_rset(state[1][1]))
+        _ = self._cw._
+        value = self.view('oneline', self._cw.eid_rset(state[1][1]))
         msg = ' '.join((_("searching for"),
-                        display_name(self.req, state[1][3]),
+                        display_name(self._cw, state[1][3]),
                         _("to associate with"), value,
                         _("by relation"), '"',
-                        display_name(self.req, state[1][2], state[1][0]),
+                        display_name(self._cw, state[1][2], state[1][0]),
                         '"'))
         return self.w(u'<div class="stateMessage">%s</div>' % msg)
 
@@ -420,10 +419,10 @@
 class HTMLPageFooter(View):
     """default html page footer: include logo if any, and close the HTML body
     """
-    id = 'footer'
+    __regid__ = 'footer'
 
     def call(self, **kwargs):
-        req = self.req
+        req = self._cw
         self.w(u'<div class="footer">')
         actions = self.vreg['actions'].possible_actions(self.req, rset=self.rset)
         footeractions = actions.get('footer', ())
@@ -440,12 +439,12 @@
     * include message component if selectable for this request
     * include selectable content navigation components
     """
-    id = 'contentheader'
+    __regid__ = 'contentheader'
 
     def call(self, view, **kwargs):
         """by default, display informal messages in content header"""
-        components = self.vreg['contentnavigation'].possible_vobjects(
-            self.req, rset=self.rset, view=view, context='navtop')
+        components = self._cw.vreg['contentnavigation'].poss_visible_objects(
+            self._cw, rset=self.cw_rset, view=view, context='navtop')
         if components:
             self.w(u'<div id="contentheader">')
             for comp in components:
@@ -457,11 +456,11 @@
     """default html page content footer: include selectable content navigation
     components
     """
-    id = 'contentfooter'
+    __regid__ = 'contentfooter'
 
     def call(self, view, **kwargs):
-        components = self.vreg['contentnavigation'].possible_vobjects(
-            self.req, rset=self.rset, view=view, context='navbottom')
+        components = self._cw.vreg['contentnavigation'].poss_visible_objects(
+            self._cw, rset=self.cw_rset, view=view, context='navbottom')
         if components:
             self.w(u'<div id="contentfooter">')
             for comp in components:
@@ -470,16 +469,16 @@
 
 
 class LogFormTemplate(View):
-    id = 'logform'
+    __regid__ = 'logform'
     __select__ = match_kwargs('id', 'klass')
 
     title = 'log in'
 
     def call(self, id, klass, title=True, message=True):
-        self.req.add_css('cubicweb.login.css')
+        self._cw.add_css('cubicweb.login.css')
         self.w(u'<div id="%s" class="%s">' % (id, klass))
         if title:
-            stitle = self.req.property_value('ui.site-title')
+            stitle = self._cw.property_value('ui.site-title')
             if stitle:
                 stitle = xml_escape(stitle)
             else:
@@ -489,7 +488,7 @@
 
         if message:
             self.display_message()
-        if self.config['auth-mode'] == 'http':
+        if self._cw.config['auth-mode'] == 'http':
             # HTTP authentication
             pass
         else:
@@ -498,17 +497,17 @@
         self.w(u'</div></div>\n')
 
     def display_message(self):
-        message = self.req.message
+        message = self._cw.message
         if message:
             self.w(u'<div class="simpleMessage">%s</div>\n' % message)
 
     def login_form(self, id):
-        _ = self.req._
+        _ = self._cw._
         self.w(u'<form method="post" action="%s" id="login_form">\n'
-               % xml_escape(login_form_url(self.config, self.req)))
+               % xml_escape(login_form_url(self._cw.config, self._cw)))
         self.w(u'<table>\n')
         self.w(u'<tr>\n')
-        msg = (self.config['allow-email-login'] and _('login or email')) or _('login')
+        msg = (self._cw.config['allow-email-login'] and _('login or email')) or _('login')
         self.w(u'<td><label for="__login">%s</label></td>' % msg)
         self.w(u'<td><input name="__login" id="__login" class="data" type="text" /></td>')
         self.w(u'</tr><tr>\n')
@@ -519,7 +518,7 @@
         self.w(u'</tr>\n')
         self.w(u'</table>\n')
         self.w(u'</form>\n')
-        self.req.html_headers.add_onload('jQuery("#__login:visible").focus()')
+        self._cw.html_headers.add_onload('jQuery("#__login:visible").focus()')
 
 
 def login_form_url(config, req):
--- a/web/views/baseviews.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/baseviews.py	Wed Sep 30 18:57:42 2009 +0200
@@ -30,7 +30,7 @@
 
 class NullView(AnyRsetView):
     """default view when no result has been found"""
-    id = 'null'
+    __regid__ = 'null'
     __select__ = yes()
     def call(self, **kwargs):
         pass
@@ -40,18 +40,18 @@
 class NoResultView(View):
     """default view when no result has been found"""
     __select__ = empty_rset()
-    id = 'noresult'
+    __regid__ = 'noresult'
 
     def call(self, **kwargs):
         self.w(u'<div class="searchMessage"><strong>%s</strong></div>\n'
-               % self.req._('No result matching query'))
+               % self._cw._('No result matching query'))
 
 
 class FinalView(AnyRsetView):
     """display values without any transformation (i.e. get a number for
     entities)
     """
-    id = 'final'
+    __regid__ = 'final'
     # record generated i18n catalog messages
     _('%d&#160;years')
     _('%d&#160;months')
@@ -69,11 +69,11 @@
     _('%d seconds')
 
     def cell_call(self, row, col, props=None, format='text/html'):
-        etype = self.rset.description[row][col]
-        value = self.rset.rows[row][col]
+        etype = self.cw_rset.description[row][col]
+        value = self.cw_rset.rows[row][col]
 
         if etype == 'String':
-            entity, rtype = self.rset.related_entity(row, col)
+            entity, rtype = self.cw_rset.related_entity(row, col)
             if entity is not None:
                 # yes !
                 self.w(entity.printable_value(rtype, value, format=format))
@@ -93,53 +93,53 @@
             else:
                 space = ' '
             if value.days > 730: # 2 years
-                self.w(self.req.__('%%d%syears' % space) % (value.days // 365))
+                self.w(self._cw.__('%%d%syears' % space) % (value.days // 365))
             elif value.days > 60: # 2 months
-                self.w(self.req.__('%%d%smonths' % space) % (value.days // 30))
+                self.w(self._cw.__('%%d%smonths' % space) % (value.days // 30))
             elif value.days > 14: # 2 weeks
-                self.w(self.req.__('%%d%sweeks' % space) % (value.days // 7))
+                self.w(self._cw.__('%%d%sweeks' % space) % (value.days // 7))
             elif value.days > 2:
-                self.w(self.req.__('%%d%sdays' % space) % int(value.days))
+                self.w(self._cw.__('%%d%sdays' % space) % int(value.days))
             elif value.seconds > 3600:
-                self.w(self.req.__('%%d%shours' % space) % int(value.seconds // 3600))
+                self.w(self._cw.__('%%d%shours' % space) % int(value.seconds // 3600))
             elif value.seconds >= 120:
-                self.w(self.req.__('%%d%sminutes' % space) % int(value.seconds // 60))
+                self.w(self._cw.__('%%d%sminutes' % space) % int(value.seconds // 60))
             else:
-                self.w(self.req.__('%%d%sseconds' % space) % int(value.seconds))
+                self.w(self._cw.__('%%d%sseconds' % space) % int(value.seconds))
             return
-        self.wdata(printable_value(self.req, etype, value, props))
+        self.wdata(printable_value(self._cw, etype, value, props))
 
 
 # XXX deprecated
 class SecondaryView(EntityView):
-    id = 'secondary'
+    __regid__ = 'secondary'
     title = _('secondary')
 
     def cell_call(self, row, col):
         """the secondary view for an entity
         secondary = icon + view(oneline)
         """
-        entity = self.entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         self.w(u'&#160;')
-        self.wview('oneline', self.rset, row=row, col=col)
+        self.wview('oneline', self.cw_rset, row=row, col=col)
 
 
 class OneLineView(EntityView):
-    id = 'oneline'
+    __regid__ = 'oneline'
     title = _('oneline')
 
     def cell_call(self, row, col):
         """the one line view for an entity: linked text view
         """
-        entity = self.entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         self.w(u'<a href="%s">' % xml_escape(entity.absolute_url()))
-        self.w(xml_escape(self.view('text', self.rset, row=row, col=col)))
+        self.w(xml_escape(self._cw.view('text', self.cw_rset, row=row, col=col)))
         self.w(u'</a>')
 
 
 class TextView(EntityView):
     """the simplest text view for an entity"""
-    id = 'text'
+    __regid__ = 'text'
     title = _('text')
     content_type = 'text/plain'
 
@@ -150,40 +150,40 @@
 
         Views applicable on None result sets have to override this method
         """
-        rset = self.rset
+        rset = self.cw_rset
         if rset is None:
             raise NotImplementedError, self
         for i in xrange(len(rset)):
-            self.wview(self.id, rset, row=i, **kwargs)
+            self.wview(self.__regid__, rset, row=i, **kwargs)
             if len(rset) > 1:
                 self.w(u"\n")
 
     def cell_call(self, row, col=0, **kwargs):
-        entity = self.entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         self.w(cut(entity.dc_title(),
-                   self.req.property_value('navigation.short-line-size')))
+                   self._cw.property_value('navigation.short-line-size')))
 
 
 class MetaDataView(EntityView):
     """paragraph view of some metadata"""
-    id = 'metadata'
+    __regid__ = 'metadata'
     show_eid = True
 
     def cell_call(self, row, col):
-        _ = self.req._
-        entity = self.entity(row, col)
+        _ = self._cw._
+        entity = self.cw_rset.get_entity(row, col)
         self.w(u'<div class="metadata">')
         if self.show_eid:
             self.w(u'#%s - ' % entity.eid)
         if entity.modification_date != entity.creation_date:
             self.w(u'<span>%s</span> ' % _('latest update on'))
             self.w(u'<span class="value">%s</span>, '
-                   % self.format_date(entity.modification_date))
+                   % self._cw.format_date(entity.modification_date))
         # entities from external source may not have a creation date (eg ldap)
         if entity.creation_date:
             self.w(u'<span>%s</span> ' % _('created on'))
             self.w(u'<span class="value">%s</span>'
-                   % self.format_date(entity.creation_date))
+                   % self._cw.format_date(entity.creation_date))
         if entity.creator:
             self.w(u' <span>%s</span> ' % _('by'))
             self.w(u'<span class="value">%s</span>' % entity.creator.name())
@@ -191,51 +191,51 @@
 
 
 class InContextTextView(TextView):
-    id = 'textincontext'
+    __regid__ = 'textincontext'
     title = None # not listed as a possible view
     def cell_call(self, row, col):
-        entity = self.entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         self.w(entity.dc_title())
 
 
 class OutOfContextTextView(InContextTextView):
-    id = 'textoutofcontext'
+    __regid__ = 'textoutofcontext'
 
     def cell_call(self, row, col):
-        entity = self.entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         self.w(entity.dc_long_title())
 
 
 class InContextView(EntityView):
-    id = 'incontext'
+    __regid__ = 'incontext'
 
     def cell_call(self, row, col):
-        entity = self.entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         desc = cut(entity.dc_description(), 50)
         self.w(u'<a href="%s" title="%s">' % (
             xml_escape(entity.absolute_url()), xml_escape(desc)))
-        self.w(xml_escape(self.view('textincontext', self.rset,
-                                     row=row, col=col)))
+        self.w(xml_escape(self._cw.view('textincontext', self.cw_rset,
+                                        row=row, col=col)))
         self.w(u'</a>')
 
 
 class OutOfContextView(EntityView):
-    id = 'outofcontext'
+    __regid__ = 'outofcontext'
 
     def cell_call(self, row, col):
-        entity = self.entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         desc = cut(entity.dc_description(), 50)
         self.w(u'<a href="%s" title="%s">' % (
             xml_escape(entity.absolute_url()), xml_escape(desc)))
-        self.w(xml_escape(self.view('textoutofcontext', self.rset,
-                                     row=row, col=col)))
+        self.w(xml_escape(self._cw.view('textoutofcontext', self.cw_rset,
+                                        row=row, col=col)))
         self.w(u'</a>')
 
 
 # list views ##################################################################
 
 class ListView(EntityView):
-    id = 'list'
+    __regid__ = 'list'
     title = _('list')
     item_vid = 'listitem'
 
@@ -245,8 +245,8 @@
         :param listid: the DOM id to use for the root element
         """
         # XXX much of the behaviour here should probably be outside this view
-        if subvid is None and 'subvid' in self.req.form:
-            subvid = self.req.form.pop('subvid') # consume it
+        if subvid is None and 'subvid' in self._cw.form:
+            subvid = self._cw.form.pop('subvid') # consume it
         if listid:
             listid = u' id="%s"' % listid
         else:
@@ -256,7 +256,7 @@
             self.w(u'<ul>\n')
         else:
             self.w(u'<ul%s class="%s">\n' % (listid, klass or 'section'))
-        for i in xrange(self.rset.rowcount):
+        for i in xrange(self.cw_rset.rowcount):
             self.cell_call(row=i, col=0, vid=subvid, **kwargs)
         self.w(u'</ul>\n')
         if title:
@@ -264,16 +264,16 @@
 
     def cell_call(self, row, col=0, vid=None, **kwargs):
         self.w(u'<li>')
-        self.wview(self.item_vid, self.rset, row=row, col=col, vid=vid, **kwargs)
+        self.wview(self.item_vid, self.cw_rset, row=row, col=col, vid=vid, **kwargs)
         self.w(u'</li>\n')
 
 
 class ListItemView(EntityView):
-    id = 'listitem'
+    __regid__ = 'listitem'
 
     @property
     def redirect_vid(self):
-        if self.req.search_state[0] == 'normal':
+        if self._cw.search_state[0] == 'normal':
             return 'outofcontext'
         return 'outofcontext-search'
 
@@ -281,50 +281,50 @@
         if not vid:
             vid = self.redirect_vid
         try:
-            self.wview(vid, self.rset, row=row, col=col, **kwargs)
+            self.wview(vid, self.cw_rset, row=row, col=col, **kwargs)
         except NoSelectableObject:
             if vid == self.redirect_vid:
                 raise
-            self.wview(self.redirect_vid, self.rset, row=row, col=col, **kwargs)
+            self.wview(self.redirect_vid, self.cw_rset, row=row, col=col, **kwargs)
 
 
 class SimpleListView(ListItemView):
     """list without bullets"""
-    id = 'simplelist'
+    __regid__ = 'simplelist'
     redirect_vid = 'incontext'
 
 
 class AdaptedListView(EntityView):
     """list of entities of the same type"""
-    id = 'adaptedlist'
+    __regid__ = 'adaptedlist'
     __select__ = EntityView.__select__ & one_etype_rset()
     item_vid = 'adaptedlistitem'
 
     @property
     def title(self):
-        etype = iter(self.rset.column_types(0)).next()
-        return display_name(self.req, etype, form='plural')
+        etype = iter(self.cw_rset.column_types(0)).next()
+        return display_name(self._cw, etype, form='plural')
 
     def call(self, **kwargs):
         """display a list of entities by calling their <item_vid> view"""
-        if not 'vtitle' in self.req.form:
+        if not 'vtitle' in self._cw.form:
             self.w(u'<h1>%s</h1>' % self.title)
         super(AdaptedListView, self).call(**kwargs)
 
     def cell_call(self, row, col=0, vid=None, **kwargs):
-        self.wview(self.item_vid, self.rset, row=row, col=col, vid=vid, **kwargs)
+        self.wview(self.item_vid, self.cw_rset, row=row, col=col, vid=vid, **kwargs)
 
 
 class AdaptedListItemView(ListItemView):
-    id = 'adaptedlistitem'
+    __regid__ = 'adaptedlistitem'
 
 
 class CSVView(SimpleListView):
-    id = 'csv'
+    __regid__ = 'csv'
     redirect_vid = 'incontext'
 
     def call(self, **kwargs):
-        rset = self.rset
+        rset = self.cw_rset
         for i in xrange(len(rset)):
             self.cell_call(i, 0, vid=kwargs.get('vid'))
             if i < rset.rowcount-1:
@@ -332,10 +332,10 @@
 
 
 class TreeItemView(ListItemView):
-    id = 'treeitem'
+    __regid__ = 'treeitem'
 
     def cell_call(self, row, col):
-        self.wview('incontext', self.rset, row=row, col=col)
+        self.wview('incontext', self.cw_rset, row=row, col=col)
 
 class TextSearchResultView(EntityView):
     """this view is used to display full-text search
@@ -344,12 +344,12 @@
 
     XXX: finish me (fixed line width, fixed number of lines, CSS, etc.)
     """
-    id = 'tsearch'
+    __regid__ = 'tsearch'
 
     def cell_call(self, row, col, **kwargs):
-        entity = self.complete_entity(row, col)
+        entity = self.cw_rset.complete_entity(row, col)
         self.w(entity.view('incontext'))
-        searched = self.rset.searched_text()
+        searched = self.cw_rset.searched_text()
         if searched is None:
             return
         searched = searched.lower()
@@ -374,9 +374,9 @@
 
 class TooltipView(EntityView):
     """A entity view used in a tooltip"""
-    id = 'tooltip'
+    __regid__ = 'tooltip'
     def cell_call(self, row, col):
-        self.wview('oneline', self.rset, row=row, col=col)
+        self.wview('oneline', self.cw_rset, row=row, col=col)
 
 
 # XXX bw compat
--- a/web/views/bookmark.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/bookmark.py	Wed Sep 30 18:57:42 2009 +0200
@@ -20,14 +20,14 @@
 _abaa.tag_object_of(('*', 'bookmarked_by', '*'), False)
 
 class FollowAction(action.Action):
-    id = 'follow'
+    __regid__ = 'follow'
     __select__ = implements('Bookmark')
 
     title = _('follow')
     category = 'mainactions'
 
     def url(self):
-        return self.rset.get_entity(self.row or 0, self.col or 0).actual_url()
+        return self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0).actual_url()
 
 
 class BookmarkPrimaryView(primary.PrimaryView):
@@ -35,22 +35,22 @@
 
     def cell_call(self, row, col):
         """the primary view for bookmark entity"""
-        entity = self.complete_entity(row, col)
+        entity = self.cw_rset.complete_entity(row, col)
         self.w(u'&#160;')
         self.w(u"<span class='title'><b>")
-        self.w(u"%s : %s" % (self.req._('Bookmark'), xml_escape(entity.title)))
+        self.w(u"%s : %s" % (self._cw._('Bookmark'), xml_escape(entity.title)))
         self.w(u"</b></span>")
         self.w(u'<br/><br/><div class="content"><a href="%s">' % (
             xml_escape(entity.actual_url())))
         self.w(u'</a>')
-        self.w(u'<p>%s%s</p>' % (self.req._('Used by:'), ', '.join(xml_escape(u.name())
+        self.w(u'<p>%s%s</p>' % (self._cw._('Used by:'), ', '.join(xml_escape(u.name())
                                                                    for u in entity.bookmarked_by)))
         self.w(u'</div>')
 
 
 class BookmarksBox(box.UserRQLBoxTemplate):
     """display a box containing all user's bookmarks"""
-    id = 'bookmarks_box'
+    __regid__ = 'bookmarks_box'
     order = 40
     title = _('bookmarks')
     rql = ('Any B,T,P ORDERBY lower(T) '
@@ -61,17 +61,17 @@
 
 
     def call(self, **kwargs):
-        req = self.req
+        req = self._cw
         ueid = req.user.eid
         try:
             rset = req.execute(self.rql, {'x': ueid})
         except Unauthorized:
             # can't access to something in the query, forget this box
             return
-        box = BoxWidget(req._(self.title), self.id)
+        box = BoxWidget(req._(self.title), self.__regid__)
         box.listing_class = 'sideBox'
-        rschema = self.schema.rschema(self.rtype)
-        eschema = self.schema.eschema(self.etype)
+        rschema = self._cw.schema.rschema(self.rtype)
+        eschema = self._cw.schema.eschema(self.etype)
         candelete = rschema.has_perm(req, 'delete', toeid=ueid)
         if candelete:
             req.add_js( ('cubicweb.ajax.js', 'cubicweb.bookmarks.js') )
@@ -105,11 +105,11 @@
                                                 build_descr=False)
                     bookmarksrql %= {'x': ueid}
                 if erset:
-                    url = self.build_url(vid='muledit', rql=bookmarksrql)
-                    boxmenu.append(self.mk_action(self.req._('edit bookmarks'), url, category='manage'))
+                    url = self._cw.build_url(vid='muledit', rql=bookmarksrql)
+                    boxmenu.append(self.mk_action(self._cw._('edit bookmarks'), url, category='manage'))
             url = req.user.absolute_url(vid='xaddrelation', rtype='bookmarked_by',
                                         target='subject')
-            boxmenu.append(self.mk_action(self.req._('pick existing bookmarks'), url, category='manage'))
+            boxmenu.append(self.mk_action(self._cw._('pick existing bookmarks'), url, category='manage'))
             box.append(boxmenu)
         if not box.is_empty():
             box.render(self.w)
--- a/web/views/boxes.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/boxes.py	Wed Sep 30 18:57:42 2009 +0200
@@ -33,27 +33,27 @@
     box with all actions impacting the entity displayed: edit, copy, delete
     change state, add related entities
     """
-    id = 'edit_box'
+    __regid__ = 'edit_box'
     __select__ = BoxTemplate.__select__ & non_final_entity()
 
     title = _('actions')
     order = 2
 
     def call(self, view=None, **kwargs):
-        _ = self.req._
+        _ = self._cw._
         title = _(self.title)
-        if self.rset:
-            etypes = self.rset.column_types(0)
+        if self.cw_rset:
+            etypes = self.cw_rset.column_types(0)
             if len(etypes) == 1:
-                plural = self.rset.rowcount > 1 and 'plural' or ''
-                etypelabel = display_name(self.req, iter(etypes).next(), plural)
+                plural = self.cw_rset.rowcount > 1 and 'plural' or ''
+                etypelabel = display_name(self._cw, iter(etypes).next(), plural)
                 title = u'%s - %s' % (title, etypelabel.lower())
-        box = BoxWidget(title, self.id, _class="greyBoxFrame")
+        box = BoxWidget(title, self.__regid__, _class="greyBoxFrame")
         self._menus_in_order = []
         self._menus_by_id = {}
         # build list of actions
-        actions = self.vreg['actions'].possible_actions(self.req, self.rset,
-                                                        view=view)
+        actions = self._cw.vreg['actions'].possible_actions(self._cw, self.cw_rset,
+                                                            view=view)
         other_menu = self._get_menu('moreactions', _('more actions'))
         for category, defaultmenu in (('mainactions', box),
                                       ('moreactions', other_menu),
@@ -85,7 +85,7 @@
             return self._menus_by_id[id]
         except KeyError:
             if title is None:
-                title = self.req._(id)
+                title = self._cw._(id)
             self._menus_by_id[id] = menu = BoxMenu(title)
             menu.label_prefix = label_prefix
             self._menus_in_order.append(menu)
@@ -106,7 +106,7 @@
 
 class SearchBox(BoxTemplate):
     """display a box with a simple search form"""
-    id = 'search_box'
+    __regid__ = 'search_box'
 
     visible = True # enabled by default
     title = _('search')
@@ -122,7 +122,7 @@
 </form>"""
 
     def call(self, view=None, **kwargs):
-        req = self.req
+        req = self._cw
         if req.form.pop('__fromsearchbox', None):
             rql = req.form.get('rql', '')
         else:
@@ -130,7 +130,7 @@
         form = self.formdef % (req.build_url('view'), req.next_tabindex(),
                                xml_escape(rql), req.next_tabindex())
         title = u"""<span onclick="javascript: toggleVisibility('rqlinput')">%s</span>""" % req._(self.title)
-        box = BoxWidget(title, self.id, _class="searchBoxFrame", islist=False, escape=False)
+        box = BoxWidget(title, self.__regid__, _class="searchBoxFrame", islist=False, escape=False)
         box.append(BoxHtml(form))
         box.render(self.w)
 
@@ -139,7 +139,7 @@
 
 class PossibleViewsBox(BoxTemplate):
     """display a box containing links to all possible views"""
-    id = 'possible_views_box'
+    __regid__ = 'possible_views_box'
     __select__ = BoxTemplate.__select__ & match_user_groups('users', 'managers')
 
     visible = False
@@ -147,9 +147,9 @@
     order = 10
 
     def call(self, **kwargs):
-        box = BoxWidget(self.req._(self.title), self.id)
-        views = [v for v in self.vreg['views'].possible_views(self.req,
-                                                              rset=self.rset)
+        box = BoxWidget(self._cw._(self.title), self.__regid__)
+        views = [v for v in self._cw.vreg['views'].possible_views(self._cw,
+                                                              rset=self.cw_rset)
                  if v.category != 'startupview']
         for category, views in self.sort_actions(views):
             menu = BoxMenu(category)
@@ -162,14 +162,14 @@
 
 class StartupViewsBox(BoxTemplate):
     """display a box containing links to all startup views"""
-    id = 'startup_views_box'
+    __regid__ = 'startup_views_box'
     visible = False # disabled by default
     title = _('startup views')
     order = 70
 
     def call(self, **kwargs):
-        box = BoxWidget(self.req._(self.title), self.id)
-        for view in self.vreg['views'].possible_views(self.req, None):
+        box = BoxWidget(self._cw._(self.title), self.__regid__)
+        for view in self._cw.vreg['views'].possible_views(self._cw, None):
             if view.category == 'startupview':
                 box.append(self.box_action(view))
 
@@ -181,7 +181,7 @@
 
 class SideBoxView(EntityView):
     """helper view class to display some entities in a sidebox"""
-    id = 'sidebox'
+    __regid__ = 'sidebox'
 
     def call(self, boxclass='sideBox', title=u''):
         """display a list of entities by calling their <item_vid> view"""
@@ -189,19 +189,19 @@
             self.w(u'<div class="sideBoxTitle"><span>%s</span></div>' % title)
         self.w(u'<div class="%s"><div class="sideBoxBody">' % boxclass)
         # if not too much entities, show them all in a list
-        maxrelated = self.req.property_value('navigation.related-limit')
-        if self.rset.rowcount <= maxrelated:
-            if len(self.rset) == 1:
-                self.wview('incontext', self.rset, row=0)
-            elif 1 < len(self.rset) < 5:
-                self.wview('csv', self.rset)
+        maxrelated = self._cw.property_value('navigation.related-limit')
+        if self.cw_rset.rowcount <= maxrelated:
+            if len(self.cw_rset) == 1:
+                self.wview('incontext', self.cw_rset, row=0)
+            elif 1 < len(self.cw_rset) < 5:
+                self.wview('csv', self.cw_rset)
             else:
-                self.wview('simplelist', self.rset)
+                self.wview('simplelist', self.cw_rset)
         # else show links to display related entities
         else:
-            self.rset.limit(maxrelated)
-            rql = self.rset.printable_rql(encoded=False)
-            self.wview('simplelist', self.rset)
-            self.w(u'[<a href="%s">%s</a>]' % (self.build_url(rql=rql),
-                                               self.req._('see them all')))
+            self.cw_rset.limit(maxrelated)
+            rql = self.cw_rset.printable_rql(encoded=False)
+            self.wview('simplelist', self.cw_rset)
+            self.w(u'[<a href="%s">%s</a>]' % (self._cw.build_url(rql=rql),
+                                               self._cw._('see them all')))
         self.w(u'</div>\n</div>\n')
--- a/web/views/calendar.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/calendar.py	Wed Sep 30 18:57:42 2009 +0200
@@ -44,12 +44,12 @@
         content_type = 'text/calendar'
         title = _('iCalendar')
         templatable = False
-        id = 'ical'
+        __regid__ = 'ical'
 
         def call(self):
             ical = iCalendar()
-            for i in range(len(self.rset.rows)):
-                task = self.complete_entity(i)
+            for i in range(len(self.cw_rset.rows)):
+                task = self.cw_rset.complete_entity(i, 0)
                 event = ical.add('vevent')
                 event.add('summary').value = task.dc_title()
                 event.add('description').value = task.dc_description()
@@ -60,7 +60,7 @@
 
             buff = ical.serialize()
             if not isinstance(buff, unicode):
-                buff = unicode(buff, self.req.encoding)
+                buff = unicode(buff, self._cw.encoding)
             self.w(buff)
 
 except ImportError:
@@ -71,7 +71,7 @@
 
     Does apply to ICalendarable compatible entities
     """
-    id = 'hcal'
+    __regid__ = 'hcal'
     __select__ = implements(ICalendarable)
     need_navigation = False
     title = _('hCalendar')
@@ -79,34 +79,34 @@
 
     def call(self):
         self.w(u'<div class="hcalendar">')
-        for i in range(len(self.rset.rows)):
-            task = self.complete_entity(i)
+        for i in range(len(self.cw_rset.rows)):
+            task = self.cw_rset.complete_entity(i, 0)
             self.w(u'<div class="vevent">')
             self.w(u'<h3 class="summary">%s</h3>' % xml_escape(task.dc_title()))
             self.w(u'<div class="description">%s</div>'
                    % task.dc_description(format='text/html'))
             if task.start:
-                self.w(u'<abbr class="dtstart" title="%s">%s</abbr>' % (task.start.isoformat(), self.format_date(task.start)))
+                self.w(u'<abbr class="dtstart" title="%s">%s</abbr>' % (task.start.isoformat(), self._cw.format_date(task.start)))
             if task.stop:
-                self.w(u'<abbr class="dtstop" title="%s">%s</abbr>' % (task.stop.isoformat(), self.format_date(task.stop)))
+                self.w(u'<abbr class="dtstop" title="%s">%s</abbr>' % (task.stop.isoformat(), self._cw.format_date(task.stop)))
             self.w(u'</div>')
         self.w(u'</div>')
 
 
 class CalendarItemView(EntityView):
-    id = 'calendaritem'
+    __regid__ = 'calendaritem'
 
     def cell_call(self, row, col, dates=False):
-        task = self.complete_entity(row)
+        task = self.cw_rset.complete_entity(row, 0)
         task.view('oneline', w=self.w)
         if dates:
             if task.start and task.stop:
-                self.w('<br/>' % self.req._('from %(date)s' % {'date': self.format_date(task.start)}))
-                self.w('<br/>' % self.req._('to %(date)s' % {'date': self.format_date(task.stop)}))
-                self.w('<br/>to %s'%self.format_date(task.stop))
+                self.w('<br/>' % self._cw._('from %(date)s' % {'date': self._cw.format_date(task.start)}))
+                self.w('<br/>' % self._cw._('to %(date)s' % {'date': self._cw.format_date(task.stop)}))
+                self.w('<br/>to %s'%self._cw.format_date(task.stop))
 
 class CalendarLargeItemView(CalendarItemView):
-    id = 'calendarlargeitem'
+    __regid__ = 'calendarlargeitem'
 
 
 class _TaskEntry(object):
@@ -129,23 +129,23 @@
 
 class OneMonthCal(EntityView):
     """At some point, this view will probably replace ampm calendars"""
-    id = 'onemonthcal'
+    __regid__ = 'onemonthcal'
     __select__ = implements(ICalendarable)
     need_navigation = False
     title = _('one month')
 
     def call(self):
-        self.req.add_js('cubicweb.ajax.js')
-        self.req.add_css('cubicweb.calendar.css')
+        self._cw.add_js('cubicweb.ajax.js')
+        self._cw.add_css('cubicweb.calendar.css')
         # XXX: restrict courses directy with RQL
         _today =  datetime.today()
 
-        if 'year' in self.req.form:
-            year = int(self.req.form['year'])
+        if 'year' in self._cw.form:
+            year = int(self._cw.form['year'])
         else:
             year = _today.year
-        if 'month' in self.req.form:
-            month = int(self.req.form['month'])
+        if 'month' in self._cw.form:
+            month = int(self._cw.form['month'])
         else:
             month = _today.month
 
@@ -159,10 +159,10 @@
         month_dates = list(date_range(firstday, lastday))
         dates = {}
         task_max = 0
-        for row in xrange(self.rset.rowcount):
-            task = self.rset.get_entity(row, 0)
-            if len(self.rset[row]) > 1 and self.rset.description[row][1] == 'CWUser':
-                user = self.rset.get_entity(row, 1)
+        for row in xrange(self.cw_rset.rowcount):
+            task = self.cw_rset.get_entity(row, 0)
+            if len(self.cw_rset[row]) > 1 and self.cw_rset.description[row][1] == 'CWUser':
+                user = self.cw_rset.get_entity(row, 1)
             else:
                 user = None
             the_dates = []
@@ -244,12 +244,12 @@
         prevlink, nextlink = self._prevnext_links(curdate)  # XXX
         self.w(u'<tr><th><a href="%s">&lt;&lt;</a></th><th colspan="5">%s %s</th>'
                u'<th><a href="%s">&gt;&gt;</a></th></tr>' %
-               (xml_escape(prevlink), self.req._(curdate.strftime('%B').lower()),
+               (xml_escape(prevlink), self._cw._(curdate.strftime('%B').lower()),
                 curdate.year, xml_escape(nextlink)))
 
         # output header
         self.w(u'<tr><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th></tr>' %
-               tuple(self.req._(day) for day in WEEKDAYS))
+               tuple(self._cw._(day) for day in WEEKDAYS))
 
         # build calendar
         for mdate, task_rows in zip(month_dates, days):
@@ -263,11 +263,11 @@
     def _prevnext_links(self, curdate):
         prevdate = curdate - timedelta(31)
         nextdate = curdate + timedelta(31)
-        rql = self.rset.printable_rql()
-        prevlink = self.req.build_ajax_replace_url('onemonthcalid', rql, 'onemonthcal',
+        rql = self.cw_rset.printable_rql()
+        prevlink = self._cw.build_ajax_replace_url('onemonthcalid', rql, 'onemonthcal',
                                                    year=prevdate.year,
                                                    month=prevdate.month)
-        nextlink = self.req.build_ajax_replace_url('onemonthcalid', rql, 'onemonthcal',
+        nextlink = self._cw.build_ajax_replace_url('onemonthcalid', rql, 'onemonthcal',
                                                    year=nextdate.year,
                                                    month=nextdate.month)
         return prevlink, nextlink
@@ -283,16 +283,16 @@
         self.w(u'<div class="calCellTitle%s">' % classes)
         self.w(u'<div class="day">%s</div>' % celldate.day)
 
-        if len(self.rset.column_types(0)) == 1:
-            etype = list(self.rset.column_types(0))[0]
-            url = self.build_url(vid='creation', etype=etype,
-                                 schedule=True,
-                                 start=self.format_date(celldate), stop=self.format_date(celldate),
-                                 __redirectrql=self.rset.printable_rql(),
-                                 __redirectparams=self.req.build_url_params(year=curdate.year, month=curmonth),
-                                 __redirectvid=self.id
-                                 )
-            self.w(u'<div class="cmd"><a href="%s">%s</a></div>' % (xml_escape(url), self.req._(u'add')))
+        if len(self.cw_rset.column_types(0)) == 1:
+            etype = list(self.cw_rset.column_types(0))[0]
+            url = self._cw.build_url(vid='creation', etype=etype,
+                                     schedule=True,
+                                     start=self._cw.format_date(celldate), stop=self._cw.format_date(celldate),
+                                     __redirectrql=self.cw_rset.printable_rql(),
+                                     __redirectparams=self._cw.build_url_params(year=curdate.year, month=curmonth),
+                                     __redirectvid=self.__regid__
+                                     )
+            self.w(u'<div class="cmd"><a href="%s">%s</a></div>' % (xml_escape(url), self._cw._(u'add')))
             self.w(u'&#160;')
         self.w(u'</div>')
         self.w(u'<div class="cellContent">')
@@ -302,9 +302,9 @@
                 self.w(u'<div class="task %s">' % task_descr.color)
                 task.view('calendaritem', w=self.w )
                 url = task.absolute_url(vid='edition',
-                                        __redirectrql=self.rset.printable_rql(),
-                                        __redirectparams=self.req.build_url_params(year=curdate.year, month=curmonth),
-                                        __redirectvid=self.id
+                                        __redirectrql=self.cw_rset.printable_rql(),
+                                        __redirectparams=self._cw.build_url_params(year=curdate.year, month=curmonth),
+                                        __redirectvid=self.__regid__
                                         )
 
                 self.w(u'<div class="tooltip" ondblclick="stopPropagation(event); window.location.assign(\'%s\'); return false;">' % xml_escape(url))
@@ -320,22 +320,22 @@
 
 class OneWeekCal(EntityView):
     """At some point, this view will probably replace ampm calendars"""
-    id = 'oneweekcal'
+    __regid__ = 'oneweekcal'
     __select__ = implements(ICalendarable)
     need_navigation = False
     title = _('one week')
 
     def call(self):
-        self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.calendar.js') )
-        self.req.add_css('cubicweb.calendar.css')
+        self._cw.add_js( ('cubicweb.ajax.js', 'cubicweb.calendar.js') )
+        self._cw.add_css('cubicweb.calendar.css')
         # XXX: restrict directly with RQL
         _today =  datetime.today()
-        if 'year' in self.req.form:
-            year = int(self.req.form['year'])
+        if 'year' in self._cw.form:
+            year = int(self._cw.form['year'])
         else:
             year = _today.year
-        if 'week' in self.req.form:
-            week = int(self.req.form['week'])
+        if 'week' in self._cw.form:
+            week = int(self._cw.form['week'])
         else:
             week = _today.isocalendar()[1]
         # week - 1 since we get week number > 0 while we want it to start from 0
@@ -348,8 +348,8 @@
         colors = [ "col%x" % i for i in range(12) ]
         next_color_index = 0
         done_tasks = []
-        for row in xrange(self.rset.rowcount):
-            task = self.rset.get_entity(row, 0)
+        for row in xrange(self.cw_rset.rowcount):
+            task = self.cw_rset.get_entity(row, 0)
             if task in done_tasks:
                 continue
             done_tasks.append(task)
@@ -389,7 +389,7 @@
         self.w(u'<th><a href="%s">&lt;&lt;</a></th><th colspan="5">%s %s %s</th>'
                u'<th><a href="%s">&gt;&gt;</a></th></tr>' %
                (xml_escape(prevlink), first_day_of_week.year,
-                self.req._(u'week'), first_day_of_week.isocalendar()[1],
+                self._cw._(u'week'), first_day_of_week.isocalendar()[1],
                 xml_escape(nextlink)))
 
         # output header
@@ -399,9 +399,9 @@
         for i, day in enumerate(WEEKDAYS):
             wdate = first_day_of_week + timedelta(i)
             if wdate.isocalendar() == _today.isocalendar():
-                self.w(u'<th class="today">%s<br/>%s</th>' % (self.req._(day), self.format_date(wdate)))
+                self.w(u'<th class="today">%s<br/>%s</th>' % (self._cw._(day), self._cw.format_date(wdate)))
             else:
-                self.w(u'<th>%s<br/>%s</th>' % (self.req._(day), self.format_date(wdate)))
+                self.w(u'<th>%s<br/>%s</th>' % (self._cw._(day), self._cw.format_date(wdate)))
         self.w(u'</tr>')
 
         # build week calendar
@@ -420,14 +420,14 @@
             if wdate.isocalendar() == _today.isocalendar():
                 classes = " today"
             self.w(u'<td class="column %s" id="%s">' % (classes, day))
-            if len(self.rset.column_types(0)) == 1:
-                etype = list(self.rset.column_types(0))[0]
-                url = self.build_url(vid='creation', etype=etype,
-                                     schedule=True,
-                                     __redirectrql=self.rset.printable_rql(),
-                                     __redirectparams=self.req.build_url_params(year=year, week=week),
-                                     __redirectvid=self.id
-                                     )
+            if len(self.cw_rset.column_types(0)) == 1:
+                etype = list(self.cw_rset.column_types(0))[0]
+                url = self._cw.build_url(vid='creation', etype=etype,
+                                         schedule=True,
+                                         __redirectrql=self.cw_rset.printable_rql(),
+                                         __redirectparams=self._cw.build_url_params(year=year, week=week),
+                                         __redirectvid=self.__regid__
+                                         )
                 extra = ' ondblclick="addCalendarItem(event, hmin=8, hmax=20, year=%s, month=%s, day=%s, duration=2, baseurl=\'%s\')"' % (
                     wdate.year, wdate.month, wdate.day, xml_escape(url))
             else:
@@ -496,9 +496,9 @@
                        (task_desc.color, style))
             task.view('calendaritem', dates=False, w=self.w)
             url = task.absolute_url(vid='edition',
-                                    __redirectrql=self.rset.printable_rql(),
-                                    __redirectparams=self.req.build_url_params(year=date.year, week=date.isocalendar()[1]),
-                                    __redirectvid=self.id
+                                    __redirectrql=self.cw_rset.printable_rql(),
+                                    __redirectparams=self._cw.build_url_params(year=date.year, week=date.isocalendar()[1]),
+                                    __redirectvid=self.__regid__
                                  )
 
             self.w(u'<div class="tooltip" ondblclick="stopPropagation(event); window.location.assign(\'%s\'); return false;">' % xml_escape(url))
@@ -520,11 +520,11 @@
     def _prevnext_links(self, curdate):
         prevdate = curdate - timedelta(7)
         nextdate = curdate + timedelta(7)
-        rql = self.rset.printable_rql()
-        prevlink = self.req.build_ajax_replace_url('oneweekcalid', rql, 'oneweekcal',
+        rql = self.cw_rset.printable_rql()
+        prevlink = self._cw.build_ajax_replace_url('oneweekcalid', rql, 'oneweekcal',
                                                    year=prevdate.year,
                                                    week=prevdate.isocalendar()[1])
-        nextlink = self.req.build_ajax_replace_url('oneweekcalid', rql, 'oneweekcal',
+        nextlink = self._cw.build_ajax_replace_url('oneweekcalid', rql, 'oneweekcal',
                                                    year=nextdate.year,
                                                    week=nextdate.isocalendar()[1])
         return prevlink, nextlink
--- a/web/views/csvexport.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/csvexport.py	Wed Sep 30 18:57:42 2009 +0200
@@ -23,36 +23,36 @@
 
     def set_request_content_type(self):
         """overriden to set a .csv filename"""
-        self.req.set_content_type(self.content_type, filename='cubicwebexport.csv')
+        self._cw.set_content_type(self.content_type, filename='cubicwebexport.csv')
 
     def csvwriter(self, **kwargs):
         params = self.csv_params.copy()
         params.update(kwargs)
-        return UnicodeCSVWriter(self.w, self.req.encoding, **params)
+        return UnicodeCSVWriter(self.w, self._cw.encoding, **params)
 
 
 class CSVRsetView(CSVMixIn, AnyRsetView):
     """dumps raw result set in CSV"""
-    id = 'csvexport'
+    __regid__ = 'csvexport'
     title = _('csv export')
 
     def call(self):
         writer = self.csvwriter()
         writer.writerow(self.columns_labels())
-        rset, descr = self.rset, self.rset.description
-        eschema = self.schema.eschema
+        rset, descr = self.cw_rset, self.cw_rset.description
+        eschema = self._cw.schema.eschema
         for rowindex, row in enumerate(rset):
             csvrow = []
             for colindex, val in enumerate(row):
                 etype = descr[rowindex][colindex]
                 if val is not None and not eschema(etype).is_final():
                     # csvrow.append(val) # val is eid in that case
-                    content = self.view('textincontext', rset,
-                                        row=rowindex, col=colindex)
+                    content = self._cw.view('textincontext', rset,
+                                            row=rowindex, col=colindex)
                 else:
-                    content = self.view('final', rset,
-                                        format='text/plain',
-                                        row=rowindex, col=colindex)
+                    content = self._cw.view('final', rset,
+                                            format='text/plain',
+                                            row=rowindex, col=colindex)
                 csvrow.append(content)
             writer.writerow(csvrow)
 
@@ -64,16 +64,16 @@
     resultset. ('table' here only means empty lines separation between table
     contents)
     """
-    id = 'ecsvexport'
+    __regid__ = 'ecsvexport'
     title = _('csv entities export')
 
     def call(self):
-        req = self.req
+        req = self._cw
         rows_by_type = {}
         writer = self.csvwriter()
         rowdef_by_type = {}
-        for index in xrange(len(self.rset)):
-            entity = self.complete_entity(index)
+        for index in xrange(len(self.cw_rset)):
+            entity = self.cw_rset.complete_entity(index)
             if entity.e_schema not in rows_by_type:
                 rowdef_by_type[entity.e_schema] = [rs for rs, at in entity.e_schema.attribute_definitions()
                                                    if at != 'Bytes']
--- a/web/views/cwproperties.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/cwproperties.py	Wed Sep 30 18:57:42 2009 +0200
@@ -65,7 +65,7 @@
 
 class SystemCWPropertiesForm(FormViewMixIn, StartupView):
     """site-wide properties edition form"""
-    id = 'systempropertiesform'
+    __regid__ = 'systempropertiesform'
     __select__ = none_rset() & match_user_groups('managers')
 
     title = _('site configuration')
@@ -76,34 +76,34 @@
 
     def url(self):
         """return the url associated with this view. We can omit rql here"""
-        return self.build_url('view', vid=self.id)
+        return self._cw.build_url('view', vid=self.__regid__)
 
     def _cookie_name(self, somestr):
-        return str('%s_property_%s' % (self.config.appid, somestr))
+        return str('%s_property_%s' % (self._cw.config.appid, somestr))
 
     def _group_status(self, group, default=u'hidden'):
         """return css class name 'hidden' (collapsed), or '' (open)"""
-        cookies = self.req.get_cookie()
+        cookies = self._cw.get_cookie()
         cookiename = self._cookie_name(group)
         cookie = cookies.get(cookiename)
         if cookie is None:
             cookies[cookiename] = default
-            self.req.set_cookie(cookies, cookiename, maxage=None)
+            self._cw.set_cookie(cookies, cookiename, maxage=None)
             status = default
         else:
             status = cookie.value
         return status
 
     def call(self, **kwargs):
-        self.req.add_js(('cubicweb.edition.js', 'cubicweb.preferences.js', 'cubicweb.ajax.js'))
-        self.req.add_css('cubicweb.preferences.css')
-        vreg = self.vreg
+        self._cw.add_js(('cubicweb.edition.js', 'cubicweb.preferences.js', 'cubicweb.ajax.js'))
+        self._cw.add_css('cubicweb.preferences.css')
+        vreg = self._cw.vreg
         values = self.defined_keys
         groupedopts = {}
         mainopts = {}
         # "self.id=='systempropertiesform'" to skip site wide properties on
         # user's preference but not site's configuration
-        for key in vreg.user_property_keys(self.id=='systempropertiesform'):
+        for key in vreg.user_property_keys(self.__regid__=='systempropertiesform'):
             parts = key.split('.')
             if parts[0] in vreg:
                 # appobject configuration
@@ -120,7 +120,7 @@
                 groupedopts[group][oid] = self.form(group + '-' + oid, keys, True)
 
         w = self.w
-        req = self.req
+        req = self._cw
         _ = req._
         w(u'<h1>%s</h1>\n' % _(self.title))
         for label, group, form in sorted((_(g), g, f)
@@ -140,7 +140,7 @@
               (make_togglable_link('fieldset_' + group, label.capitalize())))
             w(u'<div id="fieldset_%s" %s>' % (group, status))
             # create selection
-            sorted_objects =  sorted((self.req.__('%s_%s' % (group, o)), o, f)
+            sorted_objects =  sorted((self._cw.__('%s_%s' % (group, o)), o, f)
                                            for o, f in objects.iteritems())
             for label, oid, form in sorted_objects:
                 w(u'<div class="component">')
@@ -165,7 +165,7 @@
     @property
     @cached
     def cwprops_rset(self):
-        return self.req.execute('Any P,K,V WHERE P is CWProperty, P pkey K, '
+        return self._cw.execute('Any P,K,V WHERE P is CWProperty, P pkey K, '
                                 'P value V, NOT P for_user U')
 
     @property
@@ -180,27 +180,27 @@
         if key in values:
             entity = self.cwprops_rset.get_entity(values[key], 0)
         else:
-            entity = self.vreg['etypes'].etype_class('CWProperty')(self.req)
-            entity.eid = self.req.varmaker.next()
+            entity = self._cw.vreg['etypes'].etype_class('CWProperty')(self._cw)
+            entity.eid = self._cw.varmaker.next()
             entity['pkey'] = key
-            entity['value'] = self.vreg.property_value(key)
+            entity['value'] = self._cw.vreg.property_value(key)
         return entity
 
     def form(self, formid, keys, splitlabel=False):
         buttons = [SubmitButton()]
-        form = self.vreg['forms'].select(
-            'composite', self.req, domid=formid, action=self.build_url(),
+        form = self._cw.vreg['forms'].select(
+            'composite', self._cw, domid=formid, action=self._cw.build_url(),
             form_buttons=buttons,
             onsubmit="return validatePrefsForm('%s')" % formid,
-            submitmsg=self.req._('changes applied'))
-        path = self.req.relative_path()
+            submitmsg=self._cw._('changes applied'))
+        path = self._cw.relative_path()
         if '?' in path:
             path, params = path.split('?', 1)
             form.form_add_hidden('__redirectparams', params)
         form.form_add_hidden('__redirectpath', path)
         for key in keys:
             self.form_row(form, key, splitlabel)
-        renderer = self.vreg['formrenderers'].select('cwproperties', self.req,
+        renderer = self._cw.vreg['formrenderers'].select('cwproperties', self._cw,
                                                      display_progress_div=False)
         return form.form_render(renderer=renderer)
 
@@ -210,11 +210,11 @@
             label = key.split('.')[-1]
         else:
             label = key
-        subform = self.vreg['forms'].select('base', self.req, entity=entity,
-                                            mainform=False)
+        subform = self._cw.vreg['forms'].select('base', self._cw, entity=entity,
+                                                mainform=False)
         subform.append_field(PropertyValueField(name='value', label=label,
                                                 eidparam=True))
-        subform.vreg = self.vreg
+        #subform.vreg = self._cw.vreg
         subform.form_add_hidden('pkey', key, eidparam=True)
         form.add_subform(subform)
         return subform
@@ -227,7 +227,7 @@
 
 class CWPropertiesForm(SystemCWPropertiesForm):
     """user's preferences properties edition form"""
-    id = 'propertiesform'
+    __regid__ = 'propertiesform'
     __select__ = (
         (none_rset() & match_user_groups('users','managers'))
         | (one_line_rset() & match_user_groups('users') & is_user_prefs())
@@ -238,14 +238,14 @@
 
     @property
     def user(self):
-        if self.rset is None:
-            return self.req.user
-        return self.rset.get_entity(self.row or 0, self.col or 0)
+        if self.cw_rset is None:
+            return self._cw.user
+        return self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
 
     @property
     @cached
     def cwprops_rset(self):
-        return self.req.execute('Any P,K,V WHERE P is CWProperty, P pkey K, P value V,'
+        return self._cw.execute('Any P,K,V WHERE P is CWProperty, P pkey K, P value V,'
                                 'P for_user U, U eid %(x)s', {'x': self.user.eid})
 
     def form_row(self, form, key, splitlabel):
@@ -265,7 +265,7 @@
         # empty span as well else html validation fail (label is refering to
         # this id)
         return '<div id="div:%s"><span id="%s">%s</span></div>' % (
-            domid, domid, form.req._('select a key first'))
+            domid, domid, form._cw._('select a key first'))
 
 
 class NotEditableWidget(object):
@@ -289,17 +289,17 @@
 
     def render(self, form, renderer):
         wdg = self.get_widget(form)
-        wdg.attrs['tabindex'] = form.req.next_tabindex()
+        wdg.attrs['tabindex'] = form._cw.next_tabindex()
         wdg.attrs['onchange'] = "javascript:setPropValueWidget('%s', %s)" % (
-            form.edited_entity.eid, form.req.next_tabindex())
+            form.edited_entity.eid, form._cw.next_tabindex())
         return wdg.render(form, self, renderer)
 
     def vocabulary(self, form):
         entity = form.edited_entity
-        _ = form.req._
+        _ = form._cw._
         if entity.has_eid():
             return [(_(entity.pkey), entity.pkey)]
-        choices = entity.vreg.user_property_keys()
+        choices = entity._cw.vreg.user_property_keys()
         return [(u'', u'')] + sorted(zip((_(v) for v in choices), choices))
 
 
@@ -322,15 +322,15 @@
             # on key selection
             return
         try:
-            pdef = form.vreg.property_info(entity.pkey)
+            pdef = form._cw.vreg.property_info(entity.pkey)
         except UnknownProperty, ex:
             self.warning('%s (you should probably delete that property '
                          'from the database)', ex)
-            msg = form.req._('you should probably delete that property')
+            msg = form._cw._('you should probably delete that property')
             self.widget = NotEditableWidget(entity.printable_value('value'),
                                             '%s (%s)' % (msg, ex))
         if entity.pkey.startswith('system.'):
-            msg = form.req._('value associated to this key is not editable '
+            msg = form._cw._('value associated to this key is not editable '
                              'manually')
             self.widget = NotEditableWidget(entity.printable_value('value'), msg)
         # XXX race condition when used from CWPropertyForm, should not rely on
@@ -341,7 +341,7 @@
         if vocab is not None:
             if callable(vocab):
                 # list() just in case its a generator function
-                self.choices = list(vocab(form.req))
+                self.choices = list(vocab(form._cw))
             else:
                 self.choices = vocab
             wdg = Select()
@@ -360,7 +360,7 @@
 
 class CWPropertiesFormRenderer(formrenderers.FormRenderer):
     """specific renderer for properties"""
-    id = 'cwproperties'
+    __regid__ = 'cwproperties'
 
     def open_form(self, form, values):
         err = '<div class="formsg"></div>'
--- a/web/views/cwuser.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/cwuser.py	Wed Sep 30 18:57:42 2009 +0200
@@ -17,7 +17,7 @@
 uicfg.primaryview_section.tag_attribute(('CWUser', 'login'), 'hidden')
 
 class UserPreferencesEntityAction(action.Action):
-    id = 'prefs'
+    __regid__ = 'prefs'
     __select__ = (one_line_rset() & implements('CWUser') &
                   match_user_groups('owners', 'managers'))
 
@@ -25,12 +25,12 @@
     category = 'mainactions'
 
     def url(self):
-        login = self.rset.get_entity(self.row or 0, self.col or 0).login
-        return self.build_url('cwuser/%s'%login, vid='propertiesform')
+        login = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0).login
+        return self._cw.build_url('cwuser/%s'%login, vid='propertiesform')
 
 
 class FoafView(EntityView):
-    id = 'foaf'
+    __regid__ = 'foaf'
     __select__ = implements('CWUser')
 
     title = _('foaf')
@@ -41,13 +41,13 @@
         self.w(u'''<?xml version="1.0" encoding="%s"?>
 <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
          xmlns:rdfs="http://www.w3org/2000/01/rdf-schema#"
-         xmlns:foaf="http://xmlns.com/foaf/0.1/"> '''% self.req.encoding)
-        for i in xrange(self.rset.rowcount):
+         xmlns:foaf="http://xmlns.com/foaf/0.1/"> '''% self._cw.encoding)
+        for i in xrange(self.cw_rset.rowcount):
             self.cell_call(i, 0)
         self.w(u'</rdf:RDF>\n')
 
     def cell_call(self, row, col):
-        entity = self.complete_entity(row, col)
+        entity = self.cw_rset.complete_entity(row, col)
         self.w(u'''<foaf:PersonalProfileDocument rdf:about="">
                       <foaf:maker rdf:resource="%s"/>
                       <foaf:primaryTopic rdf:resource="%s"/>
--- a/web/views/debug.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/debug.py	Wed Sep 30 18:57:42 2009 +0200
@@ -26,7 +26,7 @@
 
 
 class DebugView(StartupView):
-    id = 'debug'
+    __regid__ = 'debug'
     __select__ = none_rset() & match_user_groups('managers')
     title = _('server debug information')
 
@@ -34,7 +34,7 @@
         """display server information"""
         w = self.w
         w(u'<h1>server sessions</h1>')
-        sessions = self.req.cnx._repo._sessions.items()
+        sessions = self._cw.cnx._repo._sessions.items()
         if sessions:
             w(u'<ul>')
             for sid, session in sessions:
@@ -60,3 +60,27 @@
             w(u'</ul>')
         else:
             w(u'<p>no web sessions found</p>')
+
+
+class RegistryView(StartupView):
+    __regid__ = 'registry'
+    __select__ = StartupView.__select__ & match_user_groups('managers')
+    title = _('registry')
+
+    def call(self, **kwargs):
+        """The default view representing the instance's management"""
+        self.w(u'<h1>%s</h1>' % _("Registry's content"))
+        keys = sorted(self._cw.vreg)
+        self.w(u'<p>%s</p>\n' % ' - '.join('<a href="/_registry#%s">%s</a>'
+                                           % (key, key) for key in keys))
+        for key in keys:
+            self.w(u'<h2><a name="%s">%s</a></h2>' % (key,key))
+            items = self._cw.vreg[key].items()
+            if items:
+                self.w(u'<table><tbody>')
+                for key, value in sorted(items):
+                    self.w(u'<tr><td>%s</td><td>%s</td></tr>'
+                           % (key, xml_escape(repr(value))))
+                self.w(u'</tbody></table>\n')
+            else:
+                self.w(u'<p>Empty</p>\n')
--- a/web/views/editcontroller.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/editcontroller.py	Wed Sep 30 18:57:42 2009 +0200
@@ -12,7 +12,7 @@
 from rql.utils import rqlvar_maker
 
 from cubicweb import Binary, ValidationError, typed_eid
-from cubicweb.web import INTERNAL_FIELD_VALUE, RequestError, NothingToEdit
+from cubicweb.web import INTERNAL_FIELD_VALUE, RequestError, NothingToEdit, ProcessFormError
 from cubicweb.web.controller import parse_relations_descr
 from cubicweb.web.views.basecontrollers import ViewController
 
@@ -22,34 +22,61 @@
     can't be handled right now and have to be handled later
     """
 
+class RqlQuery(object):
+    def __init__(self):
+        self.edited = []
+        self.restrictions = []
+        self.kwargs = {}
+
+    def insert_query(self, etype):
+        if self.edited:
+            rql = 'INSERT %s X: %s' % (etype, ','.join(self.edited))
+        else:
+            rql = 'INSERT %s X' % etype
+        if self.restrictions:
+            rql += ' WHERE %s' % ','.join(self.restrictions)
+        return rql
+
+    def update_query(self, eid):
+        varmaker = rqlvar_maker()
+        var = varmaker.next()
+        while var in self.kwargs:
+            var = varmaker.next()
+        rql = 'SET %s WHERE X eid %%(%s)s' % (','.join(self.edited), var)
+        if self.restrictions:
+            rql += ', %s' % ','.join(self.restrictions)
+        self.kwargs[var] = eid
+        return rql
+
 class EditController(ViewController):
-    id = 'edit'
+    __regid__ = 'edit'
 
     def publish(self, rset=None):
         """edit / create / copy / delete entity / relations"""
-        for key in self.req.form:
+        for key in self._cw.form:
             # There should be 0 or 1 action
             if key.startswith('__action_'):
                 cbname = key[1:]
                 try:
                     callback = getattr(self, cbname)
                 except AttributeError:
-                    raise RequestError(self.req._('invalid action %r' % key))
+                    raise RequestError(self._cw._('invalid action %r' % key))
                 else:
                     return callback()
         self._default_publish()
         self.reset()
 
     def _default_publish(self):
-        req = self.req
-        form = req.form
+        req = self._cw
+        self.errors = []
+        self.relations_rql = []
         # no specific action, generic edition
         self._to_create = req.data['eidmap'] = {}
         self._pending_relations = []
-        todelete = self.req.get_pending_deletes()
-        toinsert = self.req.get_pending_inserts()
+        todelete = self._cw.get_pending_deletes()
+        toinsert = self._cw.get_pending_inserts()
         try:
-            methodname = form.pop('__method', None)
+            methodname = req.form.pop('__method', None)
             for eid in req.edited_eids():
                 # __type and eid
                 formparams = req.extract_entity_params(eid, minparams=2)
@@ -59,264 +86,164 @@
                     method(formparams)
                 eid = self.edit_entity(formparams)
         except (RequestError, NothingToEdit):
-            if '__linkto' in form and 'eid' in form:
+            if '__linkto' in req.form and 'eid' in req.form:
                 self.execute_linkto()
-            elif not ('__delete' in form or '__insert' in form or todelete or toinsert):
+            elif not ('__delete' in req.form or '__insert' in req.form or todelete or toinsert):
                 raise ValidationError(None, {None: req._('nothing to edit')})
+        for querydef in self.relations_rql:
+            self._cw.execute(*querydef)
         # handle relations in newly created entities
+        # XXX find a way to merge _pending_relations and relations_rql
         if self._pending_relations:
-            for rschema, formparams, x, entity in self._pending_relations:
-                self.handle_relation(rschema, formparams, x, entity, True)
-
+            for form, field, entity in self._pending_relations:
+                for querydef in self.handle_relation(form, field, entity, True):
+                    self._cw.execute(*querydef)
         # XXX this processes *all* pending operations of *all* entities
-        if form.has_key('__delete'):
-            todelete += req.list_form_param('__delete', form, pop=True)
+        if req.form.has_key('__delete'):
+            todelete += req.list_form_param('__delete', req.form, pop=True)
         if todelete:
             self.delete_relations(parse_relations_descr(todelete))
-        if form.has_key('__insert'):
-            toinsert = req.list_form_param('__insert', form, pop=True)
+        if req.form.has_key('__insert'):
+            toinsert = req.list_form_param('__insert', req.form, pop=True)
         if toinsert:
             self.insert_relations(parse_relations_descr(toinsert))
-        self.req.remove_pending_operations()
+        self._cw.remove_pending_operations()
+
+    def _insert_entity(self, etype, eid, rqlquery):
+        rql = rqlquery.insert_query(etype)
+        try:
+            # get the new entity (in some cases, the type might have
+            # changed as for the File --> Image mutation)
+            entity = self._cw.execute(rql, rqlquery.kwargs).get_entity(0, 0)
+            neweid = entity.eid
+        except ValidationError, ex:
+            self._to_create[eid] = ex.entity
+            if self._cw.json_request: # XXX (syt) why?
+                ex.entity = eid
+            raise
+        self._to_create[eid] = neweid
+        return neweid
+
+    def _update_entity(self, eid, rqlquery):
+        rql = rqlquery.update_query(eid)
+        self._cw.execute(rql, rqlquery.kwargs)
 
     def edit_entity(self, formparams, multiple=False):
         """edit / create / copy an entity and return its eid"""
         etype = formparams['__type']
-        entity = self.vreg['etypes'].etype_class(etype)(self.req)
-        entity.eid = eid = self._get_eid(formparams['eid'])
-        edited = self.req.form.get('__maineid') == formparams['eid']
-        # let a chance to do some entity specific stuff.
+        entity = self._cw.vreg['etypes'].etype_class(etype)(self._cw)
+        entity.eid = formparams['eid']
+        eid = self._get_eid(entity.eid)
+        is_main_entity = self._cw.form.get('__maineid') == formparams['eid']
+        # let a chance to do some entity specific stuff.tn
         entity.pre_web_edit()
         # create a rql query from parameters
-        self.relations = []
-        self.restrictions = []
+        rqlquery = RqlQuery()
         # process inlined relations at the same time as attributes
-        # this is required by some external source such as the svn source which
-        # needs some information provided by those inlined relation. Moreover
-        # this will generate less write queries.
-        for rschema in entity.e_schema.subject_relations():
-            if rschema.is_final():
-                self.handle_attribute(entity, rschema, formparams)
-            elif rschema.inlined:
-                self.handle_inlined_relation(rschema, formparams, entity)
-        execute = self.req.execute
+        # this will generate less rql queries and might be useful in
+        # a few dark corners
+        formid = self._cw.form.get('__form_id', 'edition')
+        form = self._cw.vreg['forms'].select(formid, self._cw, entity=entity)
+        for field in form.fields:
+            if form.form_field_modified(field):
+                self.handle_formfield(form, field, entity, rqlquery)
         if eid is None: # creation or copy
-            if self.relations:
-                rql = 'INSERT %s X: %s' % (etype, ','.join(self.relations))
-            else:
-                rql = 'INSERT %s X' % etype
-            if self.restrictions:
-                rql += ' WHERE %s' % ','.join(self.restrictions)
-            try:
-                # get the new entity (in some cases, the type might have
-                # changed as for the File --> Image mutation)
-                entity = execute(rql, formparams).get_entity(0, 0)
-                eid = entity.eid
-            except ValidationError, ex:
-                self._to_create[formparams['eid']] = ex.entity
-                if self.req.json_request: # XXX (syt) why?
-                    ex.entity = formparams['eid']
-                raise
-            self._to_create[formparams['eid']] = eid
-        elif self.relations: # edition of an existant entity
-            varmaker = rqlvar_maker()
-            var = varmaker.next()
-            while var in formparams:
-                var = varmaker.next()
-            rql = 'SET %s WHERE X eid %%(%s)s' % (','.join(self.relations), var)
-            if self.restrictions:
-                rql += ', %s' % ','.join(self.restrictions)
-            formparams[var] = eid
-            execute(rql, formparams)
-        for rschema in entity.e_schema.subject_relations():
-            if rschema.is_final() or rschema.inlined:
-                continue
-            self.handle_relation(rschema, formparams, 'subject', entity)
-        for rschema in entity.e_schema.object_relations():
-            if rschema.is_final():
-                continue
-            self.handle_relation(rschema, formparams, 'object', entity)
-        if edited:
+            entity.eid = self._insert_entity(etype, formparams['eid'], rqlquery)
+        elif rqlquery.edited: # edition of an existant entity
+            self._update_entity(eid, rqlquery)
+        if is_main_entity:
             self.notify_edited(entity)
         if formparams.has_key('__delete'):
-            todelete = self.req.list_form_param('__delete', formparams, pop=True)
+            todelete = self._cw.list_form_param('__delete', formparams, pop=True)
             self.delete_relations(parse_relations_descr(todelete))
         if formparams.has_key('__cloned_eid'):
             entity.copy_relations(formparams['__cloned_eid'])
         if formparams.has_key('__insert'):
-            toinsert = self.req.list_form_param('__insert', formparams, pop=True)
+            toinsert = self._cw.list_form_param('__insert', formparams, pop=True)
             self.insert_relations(parse_relations_descr(toinsert))
-        if edited: # only execute linkto for the main entity
+        if is_main_entity: # only execute linkto for the main entity
             self.execute_linkto(eid)
         return eid
 
+    def handle_formfield(self, form, field, entity, rqlquery):
+        eschema = entity.e_schema
+        try:
+            for attr, value in field.process_posted(form):
+                if not (
+                    (field.role == 'subject' and eschema.has_subject_relation(field.name))
+                    or
+                    (field.role == 'object' and eschema.has_object_relation(field.name))):
+                    continue
+                rschema = self._cw.schema.rschema(field.name)
+                if rschema.is_final():
+                    rqlquery.kwargs[attr] = value
+                    rqlquery.edited.append('X %s %%(%s)s' % (attr, attr))
+                elif rschema.inlined:
+                    self.handle_inlined_relation(form, field, entity, rqlquery)
+                else:
+                    self.relations_rql += self.handle_relation(
+                        form, field, entity)
+        except ProcessFormError, exc:
+            self.errors.append((field, exc))
+
     def _action_apply(self):
         self._default_publish()
         self.reset()
 
     def _action_cancel(self):
-        errorurl = self.req.form.get('__errorurl')
+        errorurl = self._cw.form.get('__errorurl')
         if errorurl:
-            self.req.cancel_edition(errorurl)
-        self.req.message = self.req._('edit canceled')
+            self._cw.cancel_edition(errorurl)
+        self._cw.message = self._cw._('edit canceled')
         return self.reset()
 
     def _action_delete(self):
-        self.delete_entities(self.req.edited_eids(withtype=True))
+        self.delete_entities(self._cw.edited_eids(withtype=True))
         return self.reset()
 
-    def _needs_edition(self, rtype, formparams, entity):
-        """returns True and and the new value if `rtype` was edited"""
-        editkey = 'edits-%s' % rtype
-        if not editkey in formparams:
-            return False, None # not edited
-        value = formparams.get(rtype) or None
-        if entity.has_eid() and (formparams.get(editkey) or None) == value:
-            return False, None # not modified
-        if value == INTERNAL_FIELD_VALUE:
-            value = None
-        return True, value
-
-    def handle_attribute(self, entity, rschema, formparams):
-        """append to `relations` part of the rql query to edit the
-        attribute described by the given schema if necessary
+    def _relation_values(self, form, field, entity, late=False):
+        """handle edition for the (rschema, x) relation of the given entity
         """
-        attr = rschema.type
-        edition_needed, value = self._needs_edition(attr, formparams, entity)
-        if not edition_needed:
-            return
-        # test if entity class defines a special handler for this attribute
-        custom_edit = getattr(entity, 'custom_%s_edit' % attr, None)
-        if custom_edit:
-            custom_edit(formparams, value, self.relations)
-            return
-        attrtype = rschema.objects(entity.e_schema)[0].type
-        # on checkbox or selection, the field may not be in params
-        # NOTE: raising ValidationError here is not a good solution because
-        #       we can't gather all errors at once. Hopefully, the new 3.6.x
-        #       form handling will fix that
-        if attrtype == 'Boolean':
-            value = bool(value)
-        elif attrtype == 'Decimal':
-            value = Decimal(value)
-        elif attrtype == 'Bytes':
-            # if it is a file, transport it using a Binary (StringIO)
-            # XXX later __detach is for the new widget system, the former is to
-            # be removed once web/widgets.py has been dropped
-            if formparams.has_key('__%s_detach' % attr) or formparams.has_key('%s__detach' % attr):
-                # drop current file value
-                value = None
-            # no need to check value when nor explicit detach nor new file
-            # submitted, since it will think the attribute is not modified
-            elif isinstance(value, unicode):
-                # file modified using a text widget
-                encoding = entity.attr_metadata(attr, 'encoding')
-                value = Binary(value.encode(encoding))
-            elif value:
-                # value is a  3-uple (filename, mimetype, stream)
-                val = Binary(value[2].read())
-                if not val.getvalue(): # usually an unexistant file
-                    value = None
-                else:
-                    val.filename = value[0]
-                    # ignore browser submitted MIME type since it may be buggy
-                    # XXX add a config option to tell if we should consider it
-                    # or not?
-                    #if entity.e_schema.has_metadata(attr, 'format'):
-                    #    key = '%s_format' % attr
-                    #    formparams[key] = value[1]
-                    #    self.relations.append('X %s_format %%(%s)s'
-                    #                          % (attr, key))
-                    # XXX suppose a File compatible schema
-                    if entity.e_schema.has_subject_relation('name') \
-                           and not formparams.get('name'):
-                        formparams['name'] = value[0]
-                        self.relations.append('X name %(name)s')
-                    value = val
-            else:
-                # no specified value, skip
-                return
-        elif value is not None:
-            if attrtype == 'Int':
-                try:
-                    value = int(value)
-                except ValueError:
-                    raise ValidationError(entity.eid,
-                                          {attr: self.req._("invalid integer value")})
-            elif attrtype == 'Float':
-                try:
-                    value = float(value)
-                except ValueError:
-                    raise ValidationError(entity.eid,
-                                          {attr: self.req._("invalid float value")})
-            elif attrtype in ('Date', 'Datetime', 'Time'):
-                try:
-                    value = self.parse_datetime(value, attrtype)
-                except ValueError:
-                    raise ValidationError(entity.eid,
-                                          {attr: self.req._("invalid date")})
-            elif attrtype == 'Password':
-                # check confirmation (see PasswordWidget for confirmation field name)
-                confirmval = formparams.get(attr + '-confirm')
-                if confirmval != value:
-                    raise ValidationError(entity.eid,
-                                          {attr: self.req._("password and confirmation don't match")})
-                # password should *always* be utf8 encoded
-                value = value.encode('UTF8')
-            else:
-                # strip strings
-                value = value.strip()
-        elif attrtype == 'Password':
-            # skip None password
-            return # unset password
-        formparams[attr] = value
-        self.relations.append('X %s %%(%s)s' % (attr, attr))
+        values = set()
+        for eid in field.process_form_value(form):
+            if not eid: # AutoCompletionWidget
+                continue
+            typed_eid = self._get_eid(eid)
+            if typed_eid is None:
+                if late:
+                    # eid is still None while it's already a late call
+                    # this mean that the associated entity has not been created
+                    raise Exception("eid %s is still not created" % eid)
+                self._pending_relations.append( (form, field, entity) )
+                return None
+            values.add(typed_eid)
+        return values
 
-    def _relation_values(self, rschema, formparams, x, entity, late=False):
+    def handle_inlined_relation(self, form, field, entity, rqlquery):
         """handle edition for the (rschema, x) relation of the given entity
         """
-        rtype = rschema.type
-        editkey = 'edit%s-%s' % (x[0], rtype)
-        if not editkey in formparams:
-            return # not edited
-        try:
-            values = self._linked_eids(self.req.list_form_param(rtype, formparams), late)
-        except ToDoLater:
-            self._pending_relations.append((rschema, formparams, x, entity))
-            return
-        origvalues = set(typed_eid(eid) for eid in self.req.list_form_param(editkey, formparams))
-        return values, origvalues
+        origvalues = set(row[0] for row in entity.related(field.name, field.role))
+        values  = self._relation_values(form, field, entity)
+        if values is None or values == origvalues:
+            return # not edited / not modified / to do later
+        attr = field.name
+        if values:
+            rqlquery.kwargs[attr] = iter(values).next()
+            rqlquery.edition.append('X %s %s' % (attr, attr.upper()))
+            rqlquery.restrictions.append('%s eid %%(%s)s' % (attr.upper(), attr))
+        elif entity.has_eid():
+            self.relations_rql += self.handle_relation(form, field, entity)
 
-    def handle_inlined_relation(self, rschema, formparams, entity, late=False):
+    def handle_relation(self, form, field, entity, late=False):
         """handle edition for the (rschema, x) relation of the given entity
         """
-        try:
-            values, origvalues = self._relation_values(rschema, formparams,
-                                                       'subject', entity, late)
-        except TypeError:
-            return # not edited / to do later
-        if values == origvalues:
-            return # not modified
-        attr = str(rschema)
-        if values:
-            formparams[attr] = iter(values).next()
-            self.relations.append('X %s %s' % (attr, attr.upper()))
-            self.restrictions.append('%s eid %%(%s)s' % (attr.upper(), attr))
-        elif entity.has_eid():
-            self.handle_relation(rschema, formparams, 'subject', entity, late)
-
-    def handle_relation(self, rschema, formparams, x, entity, late=False):
-        """handle edition for the (rschema, x) relation of the given entity
-        """
-        try:
-            values, origvalues = self._relation_values(rschema, formparams, x,
-                                                       entity, late)
-        except TypeError:
-            return # not edited / to do later
+        origvalues = set(row[0] for row in entity.related(field.name, field.role))
+        values  = self._relation_values(form, field, entity, late)
+        if values is None or values == origvalues:
+            return # not edited / not modified / to do later
         etype = entity.e_schema
-        if values == origvalues:
-            return # not modified
-        if x == 'subject':
+        rschema = self._cw.schema.rschema(field.name)
+        if field.role == 'subject':
             desttype = rschema.objects(etype)[0]
             card = rschema.rproperty(etype, desttype, 'cardinality')[0]
             subjvar, objvar = 'X', 'Y'
@@ -325,19 +252,19 @@
             card = rschema.rproperty(desttype, etype, 'cardinality')[1]
             subjvar, objvar = 'Y', 'X'
         eid = entity.eid
-        if x == 'object' or not rschema.inlined or not values:
+        if field.role == 'object' or not rschema.inlined or not values:
             # this is not an inlined relation or no values specified,
             # explicty remove relations
             rql = 'DELETE %s %s %s WHERE X eid %%(x)s, Y eid %%(y)s' % (
                 subjvar, rschema, objvar)
             for reid in origvalues.difference(values):
-                self.req.execute(rql, {'x': eid, 'y': reid}, ('x', 'y'))
+                yield (rql, {'x': eid, 'y': reid}, ('x', 'y'))
         seteids = values.difference(origvalues)
         if seteids:
             rql = 'SET %s %s %s WHERE X eid %%(x)s, Y eid %%(y)s' % (
                 subjvar, rschema, objvar)
             for reid in seteids:
-                self.req.execute(rql, {'x': eid, 'y': reid}, ('x', 'y'))
+                yield (rql, {'x': eid, 'y': reid}, ('x', 'y'))
 
     def _get_eid(self, eid):
         # should be either an int (existant entity) or a variable (to be
@@ -355,18 +282,5 @@
     def _linked_eids(self, eids, late=False):
         """return a list of eids if they are all known, else raise ToDoLater
         """
-        result = set()
-        for eid in eids:
-            if not eid: # AutoCompletionWidget
-                continue
-            eid = self._get_eid(eid)
-            if eid is None:
-                if not late:
-                    raise ToDoLater()
-                # eid is still None while it's already a late call
-                # this mean that the associated entity has not been created
-                raise Exception('duh')
-            result.add(eid)
-        return result
 
 
--- a/web/views/editforms.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/editforms.py	Wed Sep 30 18:57:42 2009 +0200
@@ -47,7 +47,7 @@
 
 
 class DeleteConfForm(forms.CompositeForm):
-    id = 'deleteconf'
+    __regid__ = 'deleteconf'
     __select__ = non_final_entity()
 
     domid = 'deleteconf'
@@ -56,23 +56,24 @@
                     Button(stdmsgs.NO, cwaction='cancel')]
     @property
     def action(self):
-        return self.build_url('edit')
+        return self._cw.build_url('edit')
 
     def __init__(self, *args, **kwargs):
         super(DeleteConfForm, self).__init__(*args, **kwargs)
         done = set()
-        for entity in self.rset.entities():
+        for entity in self.cw_rset.entities():
             if entity.eid in done:
                 continue
             done.add(entity.eid)
-            subform = self.vreg['forms'].select('base', self.req, entity=entity,
-                                                mainform=False)
+            subform = self._cw.vreg['forms'].select('base', self._cw,
+                                                    entity=entity,
+                                                    mainform=False)
             self.add_subform(subform)
 
 
 class DeleteConfFormView(FormViewMixIn, EntityView):
     """form used to confirm deletion of some entities"""
-    id = 'deleteconf'
+    __regid__ = 'deleteconf'
     title = _('delete')
     # don't use navigation, all entities asked to be deleted should be displayed
     # else we will only delete the displayed page
@@ -80,17 +81,19 @@
 
     def call(self, onsubmit=None):
         """ask for confirmation before real deletion"""
-        req, w = self.req, self.w
+        req, w = self._cw, self.w
         _ = req._
         w(u'<script type="text/javascript">updateMessage(\'%s\');</script>\n'
           % _('this action is not reversible!'))
         # XXX above message should have style of a warning
         w(u'<h4>%s</h4>\n' % _('Do you want to delete the following element(s) ?'))
-        form = self.vreg['forms'].select(self.id, req, rset=self.rset,
-                                         onsubmit=onsubmit)
+        form = self._cw.vreg['forms'].select(self.__regid__, req,
+                                             rset=self.cw_rset,
+                                             onsubmit=onsubmit)
         w(u'<ul>\n')
-        for entity in self.rset.entities():
-            # don't use outofcontext view or any other that may contain inline edition form
+        for entity in self.cw_rset.entities():
+            # don't use outofcontext view or any other that may contain inline
+            # edition form
             w(u'<li>%s</li>' % tags.a(entity.view('textoutofcontext'),
                                       href=entity.absolute_url()))
         w(u'</ul>\n')
@@ -103,14 +106,10 @@
 
     (double-click on the field to see an appropriate edition widget).
     """
-    id = 'doreledit'
+    __regid__ = 'doreledit'
     __select__ = non_final_entity() & match_kwargs('rtype')
     # FIXME editableField class could be toggleable from userprefs
 
-    # add metadata to allow edition of metadata attributes (not considered by
-    # edition form by default)
-    attrcategories = ('primary', 'secondary', 'metadata')
-
     _onclick = u"showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')"
     _defaultlandingzone = (u'<img title="%(msg)s" '
                            'src="data/accessories-text-editor.png" '
@@ -126,15 +125,16 @@
         return self._one_rvid
 
     def _build_landing_zone(self, lzone):
-        return lzone or self._defaultlandingzone % {'msg' : xml_escape(self.req._(self._landingzonemsg))}
+        return lzone or self._defaultlandingzone % {
+            'msg': xml_escape(self._cw._(self._landingzonemsg))}
 
     def _build_renderer(self, entity, rtype, role):
-        return self.vreg['formrenderers'].select(
-            'base', self.req, entity=entity, display_label=False,
-            display_help=False, display_fields=[(rtype, role)], table_class='',
+        return self._cw.vreg['formrenderers'].select(
+            'base', self._cw, entity=entity, display_label=False,
+            display_help=False, table_class='',
             button_bar_class='buttonbar', display_progress_div=False)
 
-    def _build_form(self, entity, rtype, role, formid, default, onsubmit, reload,
+    def _build_form(self, entity, rtype, role, default, onsubmit, reload,
                   extradata=None, **formargs):
         divid = 'd%s' % make_uid('%s-%s' % (rtype, entity.eid))
         event_data = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype,
@@ -144,9 +144,10 @@
         onsubmit %= event_data
         cancelclick = "hideInlineEdit(%s,\'%s\',\'%s\')" % (entity.eid, rtype,
                                                             divid)
-        form = self.vreg['forms'].select(
-            formid, self.req, entity=entity, domid='%s-form' % divid,
+        form = self._cw.vreg['forms'].select(
+            'edition', self._cw, entity=entity, domid='%s-form' % divid,
             cssstyle='display: none', onsubmit=onsubmit, action='#',
+            display_fields=[(rtype, role)],
             form_buttons=[SubmitButton(), Button(stdmsgs.BUTTON_CANCEL,
                                                  onclick=cancelclick)],
             **formargs)
@@ -164,17 +165,17 @@
         assert rtype
         assert role in ('subject', 'object')
         if default is None:
-            default = xml_escape(self.req._('<no value>'))
-        entity = self.entity(row, col)
-        rschema = entity.schema.rschema(rtype)
+            default = xml_escape(self._cw._('<no value>'))
+        schema = self._cw.vreg.schema
+        entity = self.cw_rset.get_entity(row, col)
+        rschema = schema.rschema(rtype)
         lzone = self._build_landing_zone(landing_zone)
         # compute value, checking perms, build form
         if rschema.is_final():
             onsubmit = ("return inlineValidateAttributeForm('%(rtype)s', '%(eid)s', '%(divid)s', "
                         "%(reload)s, '%(default)s');")
             form = self._build_form(
-                entity, rtype, role, 'edition', default, onsubmit, reload,
-                attrcategories=self.attrcategories)
+                entity, rtype, role, default, onsubmit, reload)
             if not self.should_edit_attribute(entity, rschema, role, form):
                 return
             value = entity.printable_value(rtype) or default
@@ -187,24 +188,22 @@
                 return
             rset = entity.related(rtype, role)
             if rset:
-                value = self.view(rvid, rset)
+                value = self._cw.view(rvid, rset)
             else:
                 value = default
             onsubmit = ("return inlineValidateRelationForm('%(rtype)s', '%(role)s', '%(eid)s', "
                         "'%(divid)s', %(reload)s, '%(vid)s', '%(default)s', '%(lzone)s');")
             form = self._build_form(
-                entity, rtype, role, 'base', default, onsubmit, reload,
+                entity, rtype, role, default, onsubmit, reload,
                 dict(vid=rvid, role=role, lzone=lzone))
-            field = guess_field(entity.e_schema, entity.schema.rschema(rtype), role)
-            form.append_field(field)
             self.relation_form(lzone, value, form,
                                self._build_renderer(entity, rtype, role))
 
     def should_edit_attribute(self, entity, rschema, role, form):
         rtype = str(rschema)
-        ttype = rschema.targets(entity.id, role)[0]
-        afs = uicfg.autoform_section.etype_get(entity.id, rtype, role, ttype)
-        if not (afs in self.attrcategories and entity.has_perm('update')):
+        ttype = rschema.targets(entity.__regid__, role)[0]
+        afs = uicfg.autoform_section.etype_get(entity.__regid__, rtype, role, ttype)
+        if 'main_hidden' in afs or not entity.has_perm('update'):
             self.w(entity.printable_value(rtype))
             return False
         try:
@@ -215,10 +214,10 @@
         return True
 
     def should_edit_relation(self, entity, rschema, role, rvid):
-        if ((role == 'subject' and not rschema.has_perm(self.req, 'add',
+        if ((role == 'subject' and not rschema.has_perm(self._cw, 'add',
                                                         fromeid=entity.eid))
             or
-            (role == 'object' and not rschema.has_perm(self.req, 'add',
+            (role == 'object' and not rschema.has_perm(self._cw, 'add',
                                                        toeid=entity.eid))):
             self.wview(rvid, entity.related(str(rschema), role), 'null')
             return False
@@ -264,7 +263,7 @@
     """same as ClickAndEditFormView but checking if the view *should* be applied
     by checking uicfg configuration and composite relation property.
     """
-    id = 'reledit'
+    __regid__ = 'reledit'
 
     def should_edit_relation(self, entity, rschema, role, rvid):
         eschema = entity.e_schema
@@ -284,7 +283,7 @@
 
 class EditionFormView(FormViewMixIn, EntityView):
     """display primary entity edition form"""
-    id = 'edition'
+    __regid__ = 'edition'
     # add yes() so it takes precedence over deprecated views in baseforms,
     # though not baseforms based customized view
     __select__ = one_line_rset() & non_final_entity() & yes()
@@ -292,15 +291,16 @@
     title = _('edition')
 
     def cell_call(self, row, col, **kwargs):
-        entity = self.complete_entity(row, col)
+        entity = self.cw_rset.complete_entity(row, col)
         self.render_form(entity)
 
     def render_form(self, entity):
         """fetch and render the form"""
         self.form_title(entity)
-        form = self.vreg['forms'].select('edition', self.req, rset=entity.rset,
-                                         row=entity.row, col=entity.col, entity=entity,
-                                         submitmsg=self.submited_message())
+        form = self._cw.vreg['forms'].select('edition', self._cw, rset=entity.cw_rset,
+                                             row=entity.cw_row, col=entity.cw_col,
+                                             entity=entity,
+                                             submitmsg=self.submited_message())
         self.init_form(form, entity)
         self.w(form.form_render(formvid=u'edition'))
 
@@ -310,18 +310,18 @@
 
     def form_title(self, entity):
         """the form view title"""
-        ptitle = self.req._(self.title)
+        ptitle = self._cw._(self.title)
         self.w(u'<div class="formTitle"><span>%s %s</span></div>' % (
             entity.dc_type(), ptitle and '(%s)' % ptitle))
 
     def submited_message(self):
         """return the message that will be displayed on successful edition"""
-        return self.req._('entity edited')
+        return self._cw._('entity edited')
 
 
 class CreationFormView(EditionFormView):
     """display primary entity creation form"""
-    id = 'creation'
+    __regid__ = 'creation'
     __select__ = specified_etype_implements('Any') & yes()
 
     title = _('creation')
@@ -330,48 +330,47 @@
         """creation view for an entity"""
         # at this point we know etype is a valid entity type, thanks to our
         # selector
-        etype = kwargs.pop('etype', self.req.form.get('etype'))
-        entity = self.vreg['etypes'].etype_class(etype)(self.req)
-        self.initialize_varmaker()
-        entity.eid = self.varmaker.next()
+        etype = kwargs.pop('etype', self._cw.form.get('etype'))
+        entity = self._cw.vreg['etypes'].etype_class(etype)(self._cw)
+        entity.eid = self._cw.varmaker.next()
         self.render_form(entity)
 
     def form_title(self, entity):
         """the form view title"""
-        if '__linkto' in self.req.form:
-            if isinstance(self.req.form['__linkto'], list):
+        if '__linkto' in self._cw.form:
+            if isinstance(self._cw.form['__linkto'], list):
                 # XXX which one should be considered (case: add a ticket to a
                 # version in jpl)
-                rtype, linkto_eid, role = self.req.form['__linkto'][0].split(':')
+                rtype, linkto_eid, role = self._cw.form['__linkto'][0].split(':')
             else:
-                rtype, linkto_eid, role = self.req.form['__linkto'].split(':')
-            linkto_rset = self.req.eid_rset(linkto_eid)
+                rtype, linkto_eid, role = self._cw.form['__linkto'].split(':')
+            linkto_rset = self._cw.eid_rset(linkto_eid)
             linkto_type = linkto_rset.description[0][0]
             if role == 'subject':
-                title = self.req.__('creating %s (%s %s %s %%(linkto)s)' % (
+                title = self._cw.__('creating %s (%s %s %s %%(linkto)s)' % (
                     entity.e_schema, entity.e_schema, rtype, linkto_type))
             else:
-                title = self.req.__('creating %s (%s %%(linkto)s %s %s)' % (
+                title = self._cw.__('creating %s (%s %%(linkto)s %s %s)' % (
                     entity.e_schema, linkto_type, rtype, entity.e_schema))
-            msg = title % {'linkto' : self.view('incontext', linkto_rset)}
+            msg = title % {'linkto' : self._cw.view('incontext', linkto_rset)}
             self.w(u'<div class="formTitle notransform"><span>%s</span></div>' % msg)
         else:
             super(CreationFormView, self).form_title(entity)
 
     def url(self):
         """return the url associated with this view"""
-        return self.create_url(self.req.form.get('etype'))
+        return self.create_url(self._cw.form.get('etype'))
 
     def submited_message(self):
         """return the message that will be displayed on successful edition"""
-        return self.req._('entity created')
+        return self._cw._('entity created')
 
 
 class CopyFormView(EditionFormView):
     """display primary entity creation form initialized with values from another
     entity
     """
-    id = 'copy'
+    __regid__ = 'copy'
     warning_message = _('Please note that this is only a shallow copy')
 
     def render_form(self, entity):
@@ -381,10 +380,9 @@
         entity.complete()
         self.newentity = copy(entity)
         self.copying = entity
-        self.initialize_varmaker()
-        self.newentity.eid = self.varmaker.next()
+        self.newentity.eid = self._cw.varmaker.next()
         self.w(u'<script type="text/javascript">updateMessage("%s");</script>\n'
-               % self.req._(self.warning_message))
+               % self._cw._(self.warning_message))
         super(CopyFormView, self).render_form(self.newentity)
         del self.newentity
 
@@ -394,8 +392,7 @@
         if entity.eid == self.newentity.eid:
             form.form_add_hidden(eid_param('__cloned_eid', entity.eid),
                                  self.copying.eid)
-        for rschema, _, role in form.relations_by_category(form.attrcategories,
-                                                           'add'):
+        for rschema, role in form.editable_attributes():
             if not rschema.is_final():
                 # ensure relation cache is filed
                 rset = self.copying.related(rschema, role)
@@ -403,11 +400,11 @@
 
     def submited_message(self):
         """return the message that will be displayed on successful edition"""
-        return self.req._('entity copied')
+        return self._cw._('entity copied')
 
 
 class TableEditForm(forms.CompositeForm):
-    id = 'muledit'
+    __regid__ = 'muledit'
     domid = 'entityForm'
     onsubmit = "return validateForm('%s', null);" % domid
     form_buttons = [SubmitButton(_('validate modifications on selected items')),
@@ -415,19 +412,19 @@
 
     def __init__(self, req, rset, **kwargs):
         kwargs.setdefault('__redirectrql', rset.printable_rql())
-        super(TableEditForm, self).__init__(req, rset, **kwargs)
-        for row in xrange(len(self.rset)):
-            form = self.vreg['forms'].select('edition', self.req,
-                                             rset=self.rset, row=row,
-                                             attrcategories=('primary',),
-                                             mainform=False)
+        super(TableEditForm, self).__init__(req, rset=rset, **kwargs)
+        for row in xrange(len(self.cw_rset)):
+            form = self._cw.vreg['forms'].select('edition', self._cw,
+                                                 rset=self.cw_rset, row=row,
+                                                 formtype='muledit',
+                                                 mainform=False)
             # XXX rely on the EntityCompositeFormRenderer to put the eid input
             form.remove_field(form.field_by_name('eid'))
             self.add_subform(form)
 
 
 class TableEditFormView(FormViewMixIn, EntityView):
-    id = 'muledit'
+    __regid__ = 'muledit'
     __select__ = EntityView.__select__ & yes()
     title = _('multiple edit')
 
@@ -436,7 +433,7 @@
         should be the eid
         """
         #self.form_title(entity)
-        form = self.vreg['forms'].select(self.id, self.req, rset=self.rset)
+        form = self._cw.vreg['forms'].select(self.__regid__, self._cw, rset=self.cw_rset)
         self.w(form.form_render())
 
 
@@ -447,7 +444,7 @@
     :attr role: the role played by the `peid` in the relation
     :attr pform: the parent form where this inlined form is being displayed
     """
-    id = 'inline-edition'
+    __regid__ = 'inline-edition'
     __select__ = non_final_entity() & match_kwargs('peid', 'rtype')
 
     _select_attrs = ('peid', 'rtype', 'role', 'pform')
@@ -459,14 +456,14 @@
         super(InlineEntityEditionFormView, self).__init__(*args, **kwargs)
 
     def _entity(self):
-        assert self.row is not None, self
-        return self.rset.get_entity(self.row, self.col)
+        assert self.cw_row is not None, self
+        return self.cw_rset.get_entity(self.cw_row, self.cw_col)
 
     @property
     @cached
     def form(self):
         entity = self._entity()
-        form = self.vreg['forms'].select('edition', self.req,
+        form = self.vreg['forms'].select('edition', self._cw,
                                          entity=entity,
                                          form_renderer_id='inline',
                                          mainform=False, copy_nav_params=False,
@@ -494,9 +491,9 @@
         removejs = self.removejs % (self.peid, self.rtype, entity.eid)
         countkey = '%s_count' % self.rtype
         try:
-            self.req.data[countkey] += 1
-        except KeyError:
-            self.req.data[countkey] = 1
+            self._cw.data[countkey] += 1
+        except:
+            self._cw.data[countkey] = 1
         self.w(self.form.form_render(
             divid=divid, title=title, removejs=removejs, i18nctx=i18nctx,
             counter=self.req.data[countkey], **kwargs))
@@ -515,7 +512,7 @@
             return True
         # are we regenerating form because of a validation error ?
         if form.form_previous_values:
-            cdvalues = self.req.list_form_param(eid_param(self.rtype, self.peid),
+            cdvalues = self._cw.list_form_param(eid_param(self.rtype, self.peid),
                                                 form.form_previous_values)
             if unicode(entity.eid) not in cdvalues:
                 return False
@@ -526,7 +523,7 @@
     """
     :attr etype: the entity type being created in the inline form
     """
-    id = 'inline-creation'
+    __regid__ = 'inline-creation'
     __select__ = (match_kwargs('peid', 'rtype')
                   & specified_etype_implements('Any'))
     _select_attrs = InlineEntityEditionFormView._select_attrs + ('etype',)
@@ -535,9 +532,9 @@
     @cached
     def _entity(self):
         try:
-            cls = self.vreg['etypes'].etype_class(self.etype)
+            cls = self._cw.vreg['etypes'].etype_class(self.etype)
         except:
-            self.w(self.req._('no such entity type %s') % etype)
+            self.w(self._cw._('no such entity type %s') % etype)
             return
         self.initialize_varmaker()
         entity = cls(self.req)
@@ -552,7 +549,7 @@
     """
     :attr card: the cardinality of the relation according to role of `peid`
     """
-    id = 'inline-addnew-link'
+    __regid__ = 'inline-addnew-link'
     __select__ = (match_kwargs('peid', 'rtype')
                   & specified_etype_implements('Any'))
 
--- a/web/views/editviews.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/editviews.py	Wed Sep 30 18:57:42 2009 +0200
@@ -27,7 +27,7 @@
     """view called by the edition view when the user asks to search for
     something to link to the edited eid
     """
-    id = 'search-associate'
+    __regid__ = 'search-associate'
     __select__ = (one_line_rset() & match_search_state('linksearch')
                   & non_final_entity())
 
@@ -35,7 +35,7 @@
 
     def cell_call(self, row, col):
         rset, vid, divid, paginate = self.filter_box_context_info()
-        self.rset = rset
+        self.cw_rset = rset
         self.w(u'<div id="%s">' % divid)
         self.paginate()
         self.wview(vid, rset, 'noresult')
@@ -43,8 +43,8 @@
 
     @cached
     def filter_box_context_info(self):
-        entity = self.entity(0, 0)
-        role, eid, rtype, etype = self.req.search_state[1]
+        entity = self.cw_rset.get_entity(0, 0)
+        role, eid, rtype, etype = self._cw.search_state[1]
         assert entity.eid == typed_eid(eid)
         # the default behaviour is to fetch all unrelated entities and display
         # them. Use fetch_order and not fetch_unrelated_order as sort method
@@ -53,36 +53,36 @@
         rql, args = entity.unrelated_rql(rtype, etype, role,
                                    ordermethod='fetch_order',
                                    vocabconstraints=False)
-        rset = self.req.execute(rql, args, tuple(args))
+        rset = self._cw.execute(rql, args, tuple(args))
         return rset, 'list', "search-associate-content", True
 
 
 class OutOfContextSearch(EntityView):
-    id = 'outofcontext-search'
+    __regid__ = 'outofcontext-search'
     def cell_call(self, row, col):
-        entity = self.entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         erset = entity.as_rset()
-        if self.req.match_search_state(erset):
+        if self._cw.match_search_state(erset):
             self.w(u'<a href="%s" title="%s">%s</a>&#160;<a href="%s" title="%s">[...]</a>' % (
-                xml_escape(linksearch_select_url(self.req, erset)),
-                self.req._('select this entity'),
+                xml_escape(linksearch_select_url(self._cw, erset)),
+                self._cw._('select this entity'),
                 xml_escape(entity.view('textoutofcontext')),
                 xml_escape(entity.absolute_url(vid='primary')),
-                self.req._('view detail for this entity')))
+                self._cw._('view detail for this entity')))
         else:
             entity.view('outofcontext', w=self.w)
 
 
 class UnrelatedDivs(EntityView):
-    id = 'unrelateddivs'
+    __regid__ = 'unrelateddivs'
     __select__ = match_form_params('relation')
 
     def cell_call(self, row, col):
-        entity = self.entity(row, col)
-        relname, target = self.req.form.get('relation').rsplit('_', 1)
-        rschema = self.schema.rschema(relname)
-        hidden = 'hidden' in self.req.form
-        is_cell = 'is_cell' in self.req.form
+        entity = self.cw_rset.get_entity(row, col)
+        relname, target = self._cw.form.get('relation').rsplit('_', 1)
+        rschema = self._cw.schema.rschema(relname)
+        hidden = 'hidden' in self._cw.form
+        is_cell = 'is_cell' in self._cw.form
         self.w(self.build_unrelated_select_div(entity, rschema, target,
                                                is_cell=is_cell, hidden=hidden))
 
@@ -93,17 +93,17 @@
         selectid = 'select%s_%s_%s' % (rschema.type, target, entity.eid)
         if rschema.symetric or target == 'subject':
             targettypes = rschema.objects(entity.e_schema)
-            etypes = '/'.join(sorted(etype.display_name(self.req) for etype in targettypes))
+            etypes = '/'.join(sorted(etype.display_name(self._cw) for etype in targettypes))
         else:
             targettypes = rschema.subjects(entity.e_schema)
-            etypes = '/'.join(sorted(etype.display_name(self.req) for etype in targettypes))
-        etypes = cut(etypes, self.req.property_value('navigation.short-line-size'))
-        options.append('<option>%s %s</option>' % (self.req._('select a'), etypes))
+            etypes = '/'.join(sorted(etype.display_name(self._cw) for etype in targettypes))
+        etypes = cut(etypes, self._cw.property_value('navigation.short-line-size'))
+        options.append('<option>%s %s</option>' % (self._cw._('select a'), etypes))
         options += self._get_select_options(entity, rschema, target)
         options += self._get_search_options(entity, rschema, target, targettypes)
-        if 'Basket' in self.schema: # XXX
+        if 'Basket' in self._cw.schema: # XXX
             options += self._get_basket_options(entity, rschema, target, targettypes)
-        relname, target = self.req.form.get('relation').rsplit('_', 1)
+        relname, target = self._cw.form.get('relation').rsplit('_', 1)
         return u"""\
 <div class="%s" id="%s">
   <select id="%s" onchange="javascript: addPendingInsert(this.options[this.selectedIndex], %s, %s, '%s');">
@@ -118,11 +118,11 @@
         """add options to search among all entities of each possible type"""
         options = []
         eid = entity.eid
-        pending_inserts = self.req.get_pending_inserts(eid)
+        pending_inserts = self._cw.get_pending_inserts(eid)
         rtype = rschema.type
-        form = self.vreg['forms'].select('edition', self.req, entity=entity)
+        form = self._cw.vreg['forms'].select('edition', self._cw, entity=entity)
         field = form.field_by_name(rschema, target, entity.e_schema)
-        limit = self.req.property_value('navigation.combobox-limit')
+        limit = self._cw.property_value('navigation.combobox-limit')
         for eview, reid in form.form_field_vocabulary(field, limit):
             if reid is None:
                 options.append('<option class="separator">-- %s --</option>'
@@ -138,21 +138,21 @@
     def _get_search_options(self, entity, rschema, target, targettypes):
         """add options to search among all entities of each possible type"""
         options = []
-        _ = self.req._
+        _ = self._cw._
         for eschema in targettypes:
             mode = '%s:%s:%s:%s' % (target, entity.eid, rschema.type, eschema)
-            url = self.build_url(entity.rest_path(), vid='search-associate',
+            url = self._cw.build_url(entity.rest_path(), vid='search-associate',
                                  __mode=mode)
-            options.append((eschema.display_name(self.req),
+            options.append((eschema.display_name(self._cw),
                             '<option value="%s">%s %s</option>' % (
-                xml_escape(url), _('Search for'), eschema.display_name(self.req))))
+                xml_escape(url), _('Search for'), eschema.display_name(self._cw))))
         return [o for l, o in sorted(options)]
 
     def _get_basket_options(self, entity, rschema, target, targettypes):
         options = []
         rtype = rschema.type
-        _ = self.req._
-        for basketeid, basketname in self._get_basket_links(self.req.user.eid,
+        _ = self._cw._
+        for basketeid, basketname in self._get_basket_links(self._cw.user.eid,
                                                             target, targettypes):
             optionid = relation_id(entity.eid, rtype, target, basketeid)
             options.append('<option id="%s" value="%s">%s %s</option>' % (
@@ -171,10 +171,10 @@
     def _get_basket_info(self, ueid):
         basketref = []
         basketrql = 'Any B,N WHERE B is Basket, B owned_by U, U eid %(x)s, B name N'
-        basketresultset = self.req.execute(basketrql, {'x': ueid}, 'x')
+        basketresultset = self._cw.execute(basketrql, {'x': ueid}, 'x')
         for result in basketresultset:
             basketitemsrql = 'Any X WHERE X in_basket B, B eid %(x)s'
-            rset = self.req.execute(basketitemsrql, {'x': result[0]}, 'x')
+            rset = self._cw.execute(basketitemsrql, {'x': result[0]}, 'x')
             basketref.append((result[0], result[1], rset))
         return basketref
 
@@ -184,22 +184,22 @@
 
     THIS IS A TEXT VIEW. DO NOT HTML_ESCAPE
     """
-    id = 'combobox'
+    __regid__ = 'combobox'
     title = None
 
     def cell_call(self, row, col):
         """the combo-box view for an entity: same as text out of context view
         by default
         """
-        self.wview('textoutofcontext', self.rset, row=row, col=col)
+        self.wview('textoutofcontext', self.cw_rset, row=row, col=col)
 
 
 class EditableFinalView(FinalView):
     """same as FinalView but enables inplace-edition when possible"""
-    id = 'editable-final'
+    __regid__ = 'editable-final'
 
     def cell_call(self, row, col, props=None):
-        entity, rtype = self.rset.related_entity(row, col)
+        entity, rtype = self.cw_rset.related_entity(row, col)
         if entity is not None:
             self.w(entity.view('reledit', rtype=rtype))
         else:
--- a/web/views/emailaddress.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/emailaddress.py	Wed Sep 30 18:57:42 2009 +0200
@@ -33,18 +33,18 @@
             persons = []
         if persons:
             emailof = persons[0]
-            self.field(display_name(self.req, 'primary_email', 'object'), emailof.view('oneline'))
+            self.field(display_name(self._cw, 'primary_email', 'object'), emailof.view('oneline'))
             pemaileid = emailof.eid
         else:
             pemaileid = None
         try:
-            emailof = 'use_email' in self.schema and entity.reverse_use_email or ()
+            emailof = 'use_email' in self._cw.schema and entity.reverse_use_email or ()
             emailof = [e for e in emailof if not e.eid == pemaileid]
         except Unauthorized:
             emailof = []
         if emailof:
             emailofstr = ', '.join(e.view('oneline') for e in emailof)
-            self.field(display_name(self.req, 'use_email', 'object'), emailofstr)
+            self.field(display_name(self._cw, 'use_email', 'object'), emailofstr)
 
     def render_entity_relations(self, entity):
         for i, email in enumerate(entity.related_emails(self.skipeids)):
@@ -55,7 +55,7 @@
 
 class EmailAddressShortPrimaryView(EmailAddressPrimaryView):
     __select__ = implements('EmailAddress')
-    id = 'shortprimary'
+    __regid__ = 'shortprimary'
     title = None # hidden view
 
     def render_entity_attributes(self, entity):
@@ -68,7 +68,7 @@
     __select__ = implements('EmailAddress')
 
     def cell_call(self, row, col, **kwargs):
-        entity = self.rset.get_entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         if entity.reverse_primary_email:
             self.w(u'<b>')
         if entity.alias:
@@ -85,11 +85,11 @@
     """A one line view that builds a user clickable URL for an email with
     'mailto:'"""
 
-    id = 'mailto'
+    __regid__ = 'mailto'
     __select__ = implements('EmailAddress')
 
     def cell_call(self, row, col, **kwargs):
-        entity = self.rset.get_entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         if entity.reverse_primary_email:
             self.w(u'<b>')
         if entity.alias:
@@ -112,4 +112,4 @@
     __select__ = implements('EmailAddress')
 
     def cell_call(self, row, col, **kwargs):
-        self.w(self.rset.get_entity(row, col).display_address())
+        self.w(self.cw_rset.get_entity(row, col).display_address())
--- a/web/views/embedding.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/embedding.py	Wed Sep 30 18:57:42 2009 +0200
@@ -12,10 +12,10 @@
 import re
 from urlparse import urljoin
 from urllib2 import urlopen, Request, HTTPError
+from urllib import quote as urlquote # XXX should use view.url_quote method
 
 from logilab.mtconverter import guess_encoding
 
-from cubicweb import urlquote # XXX should use view.url_quote method
 from cubicweb.selectors import (one_line_rset, score_entity,
                                 match_search_state, implements)
 from cubicweb.interfaces import IEmbedable
@@ -29,13 +29,13 @@
 class ExternalTemplate(basetemplates.TheMainTemplate):
     """template embeding an external web pages into CubicWeb web interface
     """
-    id = 'external'
+    __regid__ = 'external'
 
     def call(self, body):
         # XXX fallback to HTML 4 mode when embeding ?
         self.set_request_content_type()
-        self.req.search_state = ('normal',)
-        self.template_header(self.content_type, None, self.req._('external page'),
+        self._cw.search_state = ('normal',)
+        self.template_header(self.content_type, None, self._cw._('external page'),
                              [NOINDEX, NOFOLLOW])
         self.content_header()
         self.w(body)
@@ -44,22 +44,22 @@
 
 
 class EmbedController(Controller):
-    id = 'embed'
+    __regid__ = 'embed'
     template = 'external'
 
     def publish(self, rset=None):
-        req = self.req
+        req = self._cw
         if 'custom_css' in req.form:
             req.add_css(req.form['custom_css'])
         embedded_url = req.form['url']
-        allowed = self.config['embed-allowed']
+        allowed = self._cw.config['embed-allowed']
         _ = req._
         if allowed is None or not allowed.match(embedded_url):
             body = '<h2>%s</h2><h3>%s</h3>' % (
                 _('error while embedding page'),
                 _('embedding this url is forbidden'))
         else:
-            prefix = req.build_url(self.id, url='')
+            prefix = req.build_url(self.__regid__, url='')
             authorization = req.get_header('Authorization')
             if authorization:
                 headers = {'Authorization' : authorization}
@@ -68,13 +68,13 @@
             try:
                 body = embed_external_page(embedded_url, prefix,
                                            headers, req.form.get('custom_css'))
-                body = soup2xhtml(body, self.req.encoding)
+                body = soup2xhtml(body, self._cw.encoding)
             except HTTPError, err:
                 body = '<h2>%s</h2><h3>%s</h3>' % (
                     _('error while embedding page'), err)
         self.process_rql(req.form.get('rql'))
-        return self.vreg['views'].main_template(req, self.template,
-                                                rset=self.rset, body=body)
+        return self._cw.vreg['views'].main_template(req, self.template,
+                                                rset=self.cw_rset, body=body)
 
 
 def entity_has_embedable_url(entity):
@@ -92,20 +92,19 @@
     """display an 'embed' link on entity implementing `embeded_url` method
     if the returned url match embeding configuration
     """
-    id = 'embed'
+    __regid__ = 'embed'
     __select__ = (one_line_rset() & match_search_state('normal')
                   & implements(IEmbedable)
                   & score_entity(entity_has_embedable_url))
 
     title = _('embed')
-    controller = 'embed'
 
     def url(self, row=0):
-        entity = self.rset.get_entity(row, 0)
-        url = urljoin(self.req.base_url(), entity.embeded_url())
-        if self.req.form.has_key('rql'):
-            return self.build_url(url=url, rql=self.req.form['rql'])
-        return self.build_url(url=url)
+        entity = self.cw_rset.get_entity(row, 0)
+        url = urljoin(self._cw.base_url(), entity.embeded_url())
+        if self._cw.form.has_key('rql'):
+            return self._cw.build_url('embed', url=url, rql=self._cw.form['rql'])
+        return self._cw.build_url('embed', url=url)
 
 
 
--- a/web/views/error.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/error.py	Wed Sep 30 18:57:42 2009 +0200
@@ -11,17 +11,17 @@
 from cubicweb.view import StartupView
 
 class FourOhFour(StartupView):
-    id = '404'
+    __regid__ = '404'
 
     def call(self):
-        _ = self.req._
+        _ = self._cw._
         self.w(u"<h1>%s</h1>" % _('this resource does not exist'))
 
 
 class ErrorOccured(StartupView):
-    id = '500'
+    __regid__ = '500'
 
     def call(self):
-        _ = self.req._
+        _ = self._cw._
         self.w(u"<h1>%s</h1>" %
                _('an error occured, the request cannot be fulfilled'))
--- a/web/views/facets.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/facets.py	Wed Sep 30 18:57:42 2009 +0200
@@ -29,7 +29,7 @@
 
 class FilterBox(BoxTemplate):
     """filter results of a query"""
-    id = 'filter_box'
+    __regid__ = 'filter_box'
     __select__ = (((non_final_entity() & two_lines_rset())
                    | contextview_selector()
                    ) & match_context_prop())
@@ -55,13 +55,13 @@
         if context:
             rset, vid, divid, paginate = context
         else:
-            rset = self.rset
+            rset = self.cw_rset
             vid, divid = None, 'pageContent'
             paginate = view and view.need_navigation
         return rset, vid, divid, paginate
 
     def call(self, view=None):
-        req = self.req
+        req = self._cw
         req.add_js( self.needs_js )
         req.add_css( self.needs_css)
         if self.roundcorners:
@@ -77,7 +77,7 @@
             mainvar, baserql = prepare_facets_rqlst(rqlst, rset.args)
             widgets = []
             for facet in self.get_facets(rset, mainvar):
-                if facet.propval('visible'):
+                if facet.cw_propval('visible'):
                     wdg = facet.get_widget()
                     if wdg is not None:
                         widgets.append(wdg)
@@ -89,7 +89,7 @@
             w(u'<form method="post" id="%sForm" cubicweb:facetargs="%s" action="">'  % (
                 divid, xml_escape(dumps([divid, vid, paginate, self.facetargs()]))))
             w(u'<fieldset>')
-            hiddens = {'facets': ','.join(wdg.facet.id for wdg in widgets),
+            hiddens = {'facets': ','.join(wdg.facet.__regid__ for wdg in widgets),
                        'baserql': baserql}
             for param in ('subvid', 'vtitle'):
                 if param in req.form:
@@ -104,44 +104,44 @@
             cubicweb.info('after facets with rql: %s' % repr(rqlst))
 
     def display_bookmark_link(self, rset):
-        eschema = self.schema.eschema('Bookmark')
-        if eschema.has_perm(self.req, 'add'):
+        eschema = self._cw.schema.eschema('Bookmark')
+        if eschema.has_perm(self._cw, 'add'):
             bk_path = 'view?rql=%s' % rset.printable_rql()
-            bk_title = self.req._('my custom search')
-            linkto = 'bookmarked_by:%s:subject' % self.req.user.eid
-            bk_add_url = self.build_url('add/Bookmark', path=bk_path, title=bk_title, __linkto=linkto)
-            bk_base_url = self.build_url('add/Bookmark', title=bk_title, __linkto=linkto)
+            bk_title = self._cw._('my custom search')
+            linkto = 'bookmarked_by:%s:subject' % self._cw.user.eid
+            bk_add_url = self._cw.build_url('add/Bookmark', path=bk_path, title=bk_title, __linkto=linkto)
+            bk_base_url = self._cw.build_url('add/Bookmark', title=bk_title, __linkto=linkto)
             bk_link = u'<a cubicweb:target="%s" id="facetBkLink" href="%s">%s</a>' % (
                     xml_escape(bk_base_url),
                     xml_escape(bk_add_url),
-                    self.req._('bookmark this search'))
+                    self._cw._('bookmark this search'))
             self.w(self.bk_linkbox_template % bk_link)
 
     def get_facets(self, rset, mainvar):
-        return self.vreg['facets'].possible_vobjects(self.req, rset=rset,
-                                                     context='facetbox',
-                                                     filtered_variable=mainvar)
+        return self._cw.vreg['facets'].poss_visible_objects(self._cw, rset=rset,
+                                                        context='facetbox',
+                                                        filtered_variable=mainvar)
 
 # facets ######################################################################
 
 class CreatedByFacet(RelationFacet):
-    id = 'created_by-facet'
+    __regid__ = 'created_by-facet'
     rtype = 'created_by'
     target_attr = 'login'
 
 class InGroupFacet(RelationFacet):
-    id = 'in_group-facet'
+    __regid__ = 'in_group-facet'
     rtype = 'in_group'
     target_attr = 'name'
 
 class InStateFacet(RelationFacet):
-    id = 'in_state-facet'
+    __regid__ = 'in_state-facet'
     rtype = 'in_state'
     target_attr = 'name'
 
 # inherit from RelationFacet to benefit from its possible_values implementation
 class ETypeFacet(RelationFacet):
-    id = 'etype-facet'
+    __regid__ = 'etype-facet'
     __select__ = yes()
     order = 1
     rtype = 'is'
@@ -149,17 +149,17 @@
 
     @property
     def title(self):
-        return self.req._('entity type')
+        return self._cw._('entity type')
 
     def vocabulary(self):
         """return vocabulary for this facet, eg a list of 2-uple (label, value)
         """
-        etypes = self.rset.column_types(0)
-        return sorted((self.req._(etype), etype) for etype in etypes)
+        etypes = self.cw_rset.column_types(0)
+        return sorted((self._cw._(etype), etype) for etype in etypes)
 
     def add_rql_restrictions(self):
         """add restriction for this facet into the rql syntax tree"""
-        value = self.req.form.get(self.id)
+        value = self._cw.form.get(self.__regid__)
         if not value:
             return
         self.rqlst.add_type_restriction(self.filtered_variable, value)
@@ -182,13 +182,13 @@
 
 class HasTextFacet(AbstractFacet):
     __select__ = relation_possible('has_text', 'subject') & match_context_prop()
-    id = 'has_text-facet'
+    __regid__ = 'has_text-facet'
     rtype = 'has_text'
     role = 'subject'
     order = 0
     @property
     def title(self):
-        return self.req._('has_text')
+        return self._cw._('has_text')
 
     def get_widget(self):
         """return the widget instance to use to display this facet
@@ -200,7 +200,7 @@
 
     def add_rql_restrictions(self):
         """add restriction for this facet into the rql syntax tree"""
-        value = self.req.form.get(self.id)
+        value = self._cw.form.get(self.__regid__)
         if not value:
             return
         self.rqlst.add_constant_restriction(self.filtered_variable, 'has_text', value, 'String')
--- a/web/views/formrenderers.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/formrenderers.py	Wed Sep 30 18:57:42 2009 +0200
@@ -7,6 +7,8 @@
 """
 __docformat__ = "restructuredtext en"
 
+from warnings import warn
+
 from logilab.common import dictattr
 from logilab.mtconverter import xml_escape
 
@@ -15,10 +17,15 @@
 from cubicweb.common import tags
 from cubicweb.appobject import AppObject
 from cubicweb.selectors import entity_implements, yes
-from cubicweb.web import eid_param
-from cubicweb.web import formwidgets as fwdgs
-from cubicweb.web.widgets import checkbox
-from cubicweb.web.formfields import HiddenInitialValueField
+from cubicweb.web import eid_param, formwidgets as fwdgs
+
+
+def checkbox(name, value, attrs='', checked=None):
+    if checked is None:
+        checked = value
+    checked = checked and 'checked="checked"' or ''
+    return u'<input type="checkbox" name="%s" value="%s" %s %s />' % (
+        name, value, checked, attrs)
 
 
 class FormRenderer(AppObject):
@@ -34,13 +41,12 @@
     +---------+
     """
     __registry__ = 'formrenderers'
-    id = 'default'
+    __regid__ = 'default'
 
-    _options = ('display_fields', 'display_label', 'display_help',
+    _options = ('display_label', 'display_help',
                 'display_progress_div', 'table_class', 'button_bar_class',
                 # add entity since it may be given to select the renderer
                 'entity')
-    display_fields = None # None -> all fields
     display_label = True
     display_help = True
     display_progress_div = True
@@ -48,7 +54,7 @@
     button_bar_class = u'formButtonBar'
 
     def __init__(self, req=None, rset=None, row=None, col=None, **kwargs):
-        super(FormRenderer, self).__init__(req, rset, row, col)
+        super(FormRenderer, self).__init__(req, rset=rset, row=row, col=col)
         if self._set_options(kwargs):
             raise ValueError('unconsumed arguments %s' % kwargs)
 
@@ -69,10 +75,10 @@
         w = data.append
         w(self.open_form(form, values))
         if self.display_progress_div:
-            w(u'<div id="progress">%s</div>' % self.req._('validating...'))
+            w(u'<div id="progress">%s</div>' % self._cw._('validating...'))
         w(u'<fieldset>')
         w(tags.input(type=u'hidden', name=u'__form_id',
-                     value=values.get('formvid', form.id)))
+                     value=values.get('formvid', form.__regid__)))
         if form.redirect_path:
             w(tags.input(type='hidden', name='__redirectpath', value=form.redirect_path))
         self.render_fields(w, form, values)
@@ -88,9 +94,9 @@
         if field.label is None:
             return u''
         if isinstance(field.label, tuple): # i.e. needs contextual translation
-            label = self.req.pgettext(*field.label)
+            label = self._cw.pgettext(*field.label)
         else:
-            label = self.req._(field.label)
+            label = self._cw._(field.label)
         attrs = {'for': form.context[field]['id']}
         if field.required:
             attrs['class'] = 'required'
@@ -102,11 +108,11 @@
         if callable(descr):
             descr = descr(form)
         if descr:
-            help.append('<div class="helper">%s</div>' % self.req._(descr))
-        example = field.example_format(self.req)
+            help.append('<div class="helper">%s</div>' % self._cw._(descr))
+        example = field.example_format(self._cw)
         if example:
             help.append('<div class="helper">(%s: %s)</div>'
-                        % (self.req._('sample format'), example))
+                        % (self._cw._('sample format'), example))
         return u'&#160;'.join(help)
 
     # specific methods (mostly to ease overriding) #############################
@@ -116,7 +122,7 @@
 
         This method should be called once inlined field errors has been consumed
         """
-        req = self.req
+        req = self._cw
         errex = form.form_valerror
         # get extra errors
         if errex is not None:
@@ -145,7 +151,7 @@
         else:
             enctype = 'application/x-www-form-urlencoded'
         if form.action is None:
-            action = self.req.build_url('edit')
+            action = self._cw.build_url('edit')
         else:
             action = form.action
         tag = ('<form action="%s" method="post" enctype="%s"' % (
@@ -162,14 +168,6 @@
             tag += ' cubicweb:target="%s"' % xml_escape(form.cwtarget)
         return tag + '>'
 
-    def display_field(self, form, field):
-        if isinstance(field, HiddenInitialValueField):
-            field = field.visible_field
-        return (self.display_fields is None
-                or field.name in form.internal_fields
-                or (field.name, field.role) in self.display_fields
-                or (field.name, field.role) in form.internal_fields)
-
     def render_fields(self, w, form, values):
         fields = self._render_hidden_fields(w, form)
         if fields:
@@ -184,9 +182,7 @@
     def _render_hidden_fields(self, w, form):
         fields = form.fields[:]
         for field in form.fields:
-            if not self.display_field(form, field):
-                fields.remove(field)
-            elif not field.is_visible():
+            if not field.is_visible():
                 w(field.render(form, self))
                 fields.remove(field)
         return fields
@@ -207,7 +203,7 @@
                 continue
             w(u'<fieldset class="%s">' % (fieldset or u'default'))
             if fieldset:
-                w(u'<legend>%s</legend>' % self.req._(fieldset))
+                w(u'<legend>%s</legend>' % self._cw._(fieldset))
             w(u'<table class="%s">' % self.table_class)
             for field in fields:
                 w(u'<tr class="%s_%s_row">' % (field.name, field.role))
@@ -240,24 +236,8 @@
     """use form_renderer_id = 'base' if you want base FormRenderer layout even
     when selected for an entity
     """
-    id = 'base'
-
-
-class EntityBaseFormRenderer(BaseFormRenderer):
-    """use form_renderer_id = 'base' if you want base FormRenderer layout even
-    when selected for an entity
-    """
-    __select__ = entity_implements('Any')
+    __regid__ = 'base'
 
-    def display_field(self, form, field):
-        if not super(EntityBaseFormRenderer, self).display_field(form, field):
-            if isinstance(field, HiddenInitialValueField):
-                field = field.visible_field
-            ismeta = form.edited_entity.e_schema.is_metadata(field.name)
-            return ismeta is not None and (
-                ismeta[0] in self.display_fields or
-                (ismeta[0], 'subject') in self.display_fields)
-        return True
 
 
 class HTableFormRenderer(FormRenderer):
@@ -269,7 +249,7 @@
     | field1 input | field2 input | buttons
     +--------------+--------------+---------+
     """
-    id = 'htable'
+    __regid__ = 'htable'
 
     display_help = False
     def _render_fields(self, fields, w, form):
@@ -306,7 +286,7 @@
 
 class EntityCompositeFormRenderer(FormRenderer):
     """specific renderer for multiple entities edition form (muledit)"""
-    id = 'composite'
+    __regid__ = 'composite'
 
     _main_display_fields = None
 
@@ -321,10 +301,10 @@
                 w(u'<tr class="header">')
                 w(u'<th align="left">%s</th>' %
                   tags.input(type='checkbox',
-                             title=self.req._('toggle check boxes'),
+                             title=self._cw._('toggle check boxes'),
                              onclick="setCheckboxesState('eid', this.checked)"))
                 for field in subfields:
-                    w(u'<th>%s</th>' % self.req._(field.label))
+                    w(u'<th>%s</th>' % self._cw._(field.label))
                 w(u'</tr>')
         super(EntityCompositeFormRenderer, self).render_fields(w, form, values)
         if form.parent_form is None:
@@ -340,7 +320,7 @@
             qeid = eid_param('eid', entity.eid)
             cbsetstate = "setCheckboxesState2('eid', %s, 'checked')" % \
                          xml_escape(dumps(entity.eid))
-            w(u'<tr class="%s">' % (entity.row % 2 and u'even' or u'odd'))
+            w(u'<tr class="%s">' % (entity.cw_row % 2 and u'even' or u'odd'))
             # XXX turn this into a widget used on the eid field
             w(u'<td>%s</td>' % checkbox('eid', entity.eid,
                                         checked=qeid in values))
@@ -364,11 +344,11 @@
             self._main_display_fields = fields
 
 
-class EntityFormRenderer(EntityBaseFormRenderer):
+class EntityFormRenderer(BaseFormRenderer):
     """specific renderer for entity edition form (edition)"""
-    id = 'default'
+    __regid__ = 'default'
     # needs some additional points in some case (XXX explain cases)
-    __select__ = EntityBaseFormRenderer.__select__ & yes()
+    __select__ = entity_implements('Any') & yes()
 
     _options = FormRenderer._options + ('display_relations_form', 'main_form_title')
     display_relations_form = True
@@ -412,10 +392,15 @@
             super(EntityFormRenderer, self).render_buttons(w, form)
 
     def relations_form(self, w, form):
-        srels_by_cat = form.srelations_by_category('generic', 'add', strict=True)
+        try:
+            srels_by_cat = form.srelations_by_category('generic', 'add', strict=True)
+            warn('[3.6] %s: srelations_by_category is deprecated, override '
+                 'editable_relations instead' % classid(form), DeprecationWarning)
+        except AttributeError:
+            srels_by_cat = form.editable_relations()
         if not srels_by_cat:
             return u''
-        req = self.req
+        req = self._cw
         _ = req._
         __ = _
         label = u'%s :' % __('This %s' % form.edited_entity.e_schema).capitalize()
@@ -435,7 +420,7 @@
                 if not form.force_display and form.maxrelitems < len(related):
                     link = (u'<span class="invisible">'
                             '[<a href="javascript: window.location.href+=\'&amp;__force_display=1\'">%s</a>]'
-                            '</span>' % self.req._('view all'))
+                            '</span>' % self._cw._('view all'))
                     w(u'<li class="invisible">%s</li>' % link)
                 w(u'</ul>')
                 w(u'</td>')
@@ -501,7 +486,7 @@
     """specific renderer for entity inlined edition form
     (inline-[creation|edition])
     """
-    id = 'inline'
+    __regid__ = 'inline'
 
     def render(self, form, values):
         form.add_media()
@@ -513,11 +498,11 @@
             w(u'<div id="div-%(divid)s">' % values)
         else:
             w(u'<div id="notice-%s" class="notice">%s</div>' % (
-                values['divid'], self.req._('click on the box to cancel the deletion')))
+                values['divid'], self._cw._('click on the box to cancel the deletion')))
         w(u'<div class="iformBody">')
         eschema = form.edited_entity.e_schema
         ctx = values.pop('i18nctx')
-        values['removemsg'] = self.req.pgettext(ctx, 'remove this %s' % eschema)
+        values['removemsg'] = self._cw.pgettext(ctx, 'remove this %s' % eschema)
         w(u'<div class="iformTitle"><span>%(title)s</span> '
           '#<span class="icounter">%(counter)s</span> '
           '[<a href="javascript: %(removejs)s;noop();">%(removemsg)s</a>]</div>'
--- a/web/views/forms.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/forms.py	Wed Sep 30 18:57:42 2009 +0200
@@ -15,7 +15,7 @@
 from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param
 from cubicweb.web import form, formwidgets as fwdgs
 from cubicweb.web.controller import NAV_FORM_PARAMETERS
-from cubicweb.web.formfields import HiddenInitialValueField, StringField
+from cubicweb.web.formfields import StringField
 
 
 class FieldsForm(form.Form):
@@ -53,7 +53,7 @@
 
     * `fieldsets_in_order`: fieldset name sequence, to control order
     """
-    id = 'base'
+    __regid__ = 'base'
 
     internal_fields = ('__errorurl',) + NAV_FORM_PARAMETERS
 
@@ -75,7 +75,7 @@
     def __init__(self, req, rset=None, row=None, col=None,
                  submitmsg=None, mainform=True,
                  **kwargs):
-        super(FieldsForm, self).__init__(req, rset, row=row, col=col)
+        super(FieldsForm, self).__init__(req, rset=rset, row=row, col=col)
         self.fields = list(self.__class__._fields_)
         for key, val in kwargs.items():
             if key in NAV_FORM_PARAMETERS:
@@ -120,9 +120,9 @@
     def add_media(self):
         """adds media (CSS & JS) required by this widget"""
         if self.needs_js:
-            self.req.add_js(self.needs_js)
+            self._cw.add_js(self.needs_js)
         if self.needs_css:
-            self.req.add_css(self.needs_css)
+            self._cw.add_css(self.needs_css)
 
     def form_render(self, **values):
         """render this form, using the renderer given in args or the default
@@ -135,9 +135,9 @@
         return renderer.render(self, values)
 
     def form_default_renderer(self):
-        return self.vreg['formrenderers'].select(self.form_renderer_id,
-                                                self.req, rset=self.rset,
-                                                row=self.row, col=self.col)
+        return self._cw.vreg['formrenderers'].select(self.form_renderer_id,
+                                                     self._cw, rset=self.cw_rset,
+                                                     row=self.cw_row, col=self.cw_col)
 
     def build_context(self, rendervalues=None):
         """build form context values (the .context attribute which is a
@@ -188,17 +188,17 @@
                 if callable(value):
                     value = value(self)
             if value != INTERNAL_FIELD_VALUE:
-                value = field.format_value(self.req, value)
+                value = field.format_value(self._cw, value)
         return value
 
     def _req_display_value(self, field):
         qname = self.form_field_name(field)
         if qname in self.form_previous_values:
             return self.form_previous_values[qname]
-        if qname in self.req.form:
-            return self.req.form[qname]
-        if field.name in self.req.form:
-            return self.req.form[field.name]
+        if qname in self._cw.form:
+            return self._cw.form[qname]
+        if field.name in self._cw.form:
+            return self._cw.form[field.name]
         return None
 
     def form_field_value(self, field, load_bytes=False):
@@ -220,11 +220,11 @@
 
     def form_field_format(self, field):
         """return MIME type used for the given (text or bytes) field"""
-        return self.req.property_value('ui.default-text-format')
+        return self._cw.property_value('ui.default-text-format')
 
     def form_field_encoding(self, field):
         """return encoding used for the given (text) field"""
-        return self.req.encoding
+        return self._cw.encoding
 
     def form_field_name(self, field):
         """return qualified name for the given field"""
@@ -240,6 +240,9 @@
         """
         raise NotImplementedError
 
+    def form_field_modified(self, field):
+        return field.is_visible()
+
     def _field_has_error(self, field):
         """return true if the field has some error in given validation exception
         """
@@ -247,7 +250,7 @@
 
 
 class EntityFieldsForm(FieldsForm):
-    id = 'base'
+    __regid__ = 'base'
     __select__ = (match_kwargs('entity')
                   | (one_line_rset() & non_final_entity()))
 
@@ -259,19 +262,19 @@
         msg = kwargs.pop('submitmsg', None)
         super(EntityFieldsForm, self).__init__(*args, **kwargs)
         if self.edited_entity is None:
-            self.edited_entity = self.complete_entity(self.row or 0, self.col or 0)
+            self.edited_entity = self.cw_rset.complete_entity(self.cw_row or 0, self.cw_col or 0)
         self.form_add_hidden('__type', eidparam=True)
         self.form_add_hidden('eid')
         if kwargs.get('mainform', True): # mainform default to true in parent
             self.form_add_hidden(u'__maineid', self.edited_entity.eid)
             # If we need to directly attach the new object to another one
-            if self.req.list_form_param('__linkto'):
-                for linkto in self.req.list_form_param('__linkto'):
+            if self._cw.list_form_param('__linkto'):
+                for linkto in self._cw.list_form_param('__linkto'):
                     self.form_add_hidden('__linkto', linkto)
                 if msg:
-                    msg = '%s %s' % (msg, self.req._('and linked'))
+                    msg = '%s %s' % (msg, self._cw._('and linked'))
                 else:
-                    msg = self.req._('entity linked')
+                    msg = self._cw._('entity linked')
         if msg:
             self.form_add_hidden('__message', msg)
 
@@ -317,7 +320,7 @@
         if hasattr(self.edited_entity, defaultattr):
             # XXX bw compat, default_<field name> on the entity
             warn('found %s on %s, should be set on a specific form'
-                 % (defaultattr, self.edited_entity.id), DeprecationWarning)
+                 % (defaultattr, self.edited_entity.__regid__), DeprecationWarning)
             value = getattr(self.edited_entity, defaultattr)
             if callable(value):
                 value = value()
@@ -327,36 +330,15 @@
         return value
 
     def form_default_renderer(self):
-        return self.vreg['formrenderers'].select(
-            self.form_renderer_id, self.req, rset=self.rset, row=self.row,
-            col=self.col, entity=self.edited_entity)
-
-    def build_context(self, values=None):
-        """overriden to add edit[s|o] hidden fields and to ensure schema fields
-        have eidparam set to True
-
-        edit[s|o] hidden fields are used to indicate the value for the
-        associated field before the (potential) modification made when
-        submitting the form.
-        """
-        if self.context is not None:
-            return
-        eschema = self.edited_entity.e_schema
-        for field in self.fields[:]:
-            for field in field.actual_fields(self):
-                fieldname = field.name
-                if fieldname != 'eid' and (
-                    (eschema.has_subject_relation(fieldname) or
-                     eschema.has_object_relation(fieldname))):
-                    field.eidparam = True
-                    self.fields.append(HiddenInitialValueField(field))
-        return super(EntityFieldsForm, self).build_context(values)
+        return self._cw.vreg['formrenderers'].select(
+            self.form_renderer_id, self._cw, rset=self.cw_rset, row=self.cw_row,
+            col=self.cw_col, entity=self.edited_entity)
 
     def form_field_value(self, field, load_bytes=False):
         """return field's *typed* value
 
         overriden to deal with
-        * special eid / __type / edits- / edito- fields
+        * special eid / __type
         * lookup for values on edited entities
         """
         attr = field.name
@@ -365,17 +347,9 @@
             return entity.eid
         if not field.eidparam:
             return super(EntityFieldsForm, self).form_field_value(field, load_bytes)
-        if attr.startswith('edits-') or attr.startswith('edito-'):
-            # edit[s|o]- fieds must have the actual value stored on the entity
-            assert hasattr(field, 'visible_field')
-            vfield = field.visible_field
-            assert vfield.eidparam
-            if entity.has_eid():
-                return self.form_field_value(vfield)
-            return INTERNAL_FIELD_VALUE
         if attr == '__type':
-            return entity.id
-        if self.schema.rschema(attr).is_final():
+            return entity.__regid__
+        if self._cw.schema.rschema(attr).is_final():
             attrtype = entity.e_schema.destination(attr)
             if attrtype == 'Password':
                 return entity.has_eid() and INTERNAL_FIELD_VALUE or ''
@@ -404,7 +378,7 @@
         if field.eidparam and entity.e_schema.has_metadata(field.name, 'format') and (
             entity.has_eid() or '%s_format' % field.name in entity):
             return self.edited_entity.attr_metadata(field.name, 'format')
-        return self.req.property_value('ui.default-text-format')
+        return self._cw.property_value('ui.default-text-format')
 
     def form_field_encoding(self, field):
         """return encoding used for the given (text) field"""
@@ -440,7 +414,7 @@
                 vocabfunc = getattr(self, '%s_relation_vocabulary' % role)
             else:
                 warn('found %s on %s, should be set on a specific form'
-                     % (method, self.edited_entity.id), DeprecationWarning)
+                     % (method, self.edited_entity.__regid__), DeprecationWarning)
         # NOTE: it is the responsibility of `vocabfunc` to sort the result
         #       (direclty through RQL or via a python sort). This is also
         #       important because `vocabfunc` might return a list with
@@ -448,14 +422,39 @@
         #       cases, it doesn't make sense to sort results afterwards.
         return vocabfunc(rtype, limit)
 
-    # XXX should be on the field, no?
+    def form_field_modified(self, field):
+        if field.is_visible():
+            # fields not corresponding to an entity attribute / relations
+            # are considered modified
+            if not field.eidparam:
+                return True # XXX
+            try:
+                if field.role == 'subject':
+                    previous_value = getattr(self.edited_entity, field.name)
+                else:
+                    previous_value = getattr(self.edited_entity,
+                                             'reverse_%s' % field.name)
+            except AttributeError:
+                # fields with eidparam=True but not corresponding to an actual
+                # attribute or relation
+                return True
+            # if it's a non final relation, we need the eids
+            if isinstance(previous_value, list):
+                # widget should return untyped eids
+                previous_value = set(unicode(e.eid) for e in previous_value)
+            new_value = field.process_form_value(self)
+            if self.edited_entity.has_eid() and (previous_value == new_value):
+                return False # not modified
+            return True
+        return False
+
     def subject_relation_vocabulary(self, rtype, limit=None):
         """defaut vocabulary method for the given relation, looking for
         relation's object entities (i.e. self is the subject)
         """
         entity = self.edited_entity
         if isinstance(rtype, basestring):
-            rtype = entity.schema.rschema(rtype)
+            rtype = self._cw.vreg.schema.rschema(rtype)
         done = None
         assert not rtype.is_final(), rtype
         if entity.has_eid():
@@ -477,7 +476,7 @@
         """
         entity = self.edited_entity
         if isinstance(rtype, basestring):
-            rtype = entity.schema.rschema(rtype)
+            rtype = self._cw.vreg.schema.rschema(rtype)
         done = None
         if entity.has_eid():
             done = set(e.eid for e in getattr(entity, 'reverse_%s' % rtype))
@@ -492,8 +491,7 @@
                 break
         return result
 
-    def srelations_by_category(self, categories=None, permission=None,
-                               strict=False):
+    def editable_relations(self):
         return ()
 
     def should_display_add_new_relation_link(self, rschema, existant, card):
@@ -502,8 +500,8 @@
 
 class CompositeFormMixIn(object):
     """form composed of sub-forms"""
-    id = 'composite'
-    form_renderer_id = id
+    __regid__ = 'composite'
+    form_renderer_id = __regid__
 
     def __init__(self, *args, **kwargs):
         super(CompositeFormMixIn, self).__init__(*args, **kwargs)
--- a/web/views/ibreadcrumbs.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/ibreadcrumbs.py	Wed Sep 30 18:57:42 2009 +0200
@@ -20,10 +20,10 @@
 
 
 class BreadCrumbEntityVComponent(Component):
-    id = 'breadcrumbs'
+    __regid__ = 'breadcrumbs'
     __select__ = one_line_rset() & implements(IBreadCrumbs, accept_none=False)
 
-    property_defs = {
+    cw_property_defs = {
         _('visible'):  dict(type='Boolean', default=True,
                             help=_('display the component or not')),
         }
@@ -32,7 +32,7 @@
     separator = u'&#160;&gt;&#160;'
 
     def call(self, view=None, first_separator=True):
-        entity = self.rset.get_entity(0, 0)
+        entity = self.cw_rset.get_entity(0, 0)
         path = entity.breadcrumbs(view)
         if path:
             self.w(u'<span id="breadcrumbs" class="pathbar">')
@@ -44,7 +44,7 @@
     def render_breadcrumbs(self, contextentity, path):
         root = path.pop(0)
         if isinstance(root, Entity):
-            self.w(u'<a href="%s">%s</a>' % (self.req.build_url(root.id),
+            self.w(u'<a href="%s">%s</a>' % (self._cw.build_url(root.__regid__),
                                              root.dc_type('plural')))
             self.w(self.separator)
         self.wpath_part(root, contextentity, not path)
@@ -61,11 +61,11 @@
                 self.w(part.view('breadcrumbs'))
         elif isinstance(part, tuple):
             url, title = part
-            textsize = self.req.property_value('navigation.short-line-size')
+            textsize = self._cw.property_value('navigation.short-line-size')
             self.w(u'<a href="%s">%s</a>' % (
                 xml_escape(url), xml_escape(uilib.cut(title, textsize))))
         else:
-            textsize = self.req.property_value('navigation.short-line-size')
+            textsize = self._cw.property_value('navigation.short-line-size')
             self.w(uilib.cut(unicode(part), textsize))
 
 
@@ -77,7 +77,7 @@
         # XXX hack: only display etype name or first non entity path part
         root = path.pop(0)
         if isinstance(root, Entity):
-            self.w(u'<a href="%s">%s</a>' % (self.req.build_url(root.id),
+            self.w(u'<a href="%s">%s</a>' % (self._cw.build_url(root.__regid__),
                                              root.dc_type('plural')))
         else:
             self.wpath_part(root, contextentity, not path)
@@ -90,15 +90,15 @@
         self.w(u'<span id="breadcrumbs" class="pathbar">')
         if first_separator:
             self.w(self.separator)
-        self.w(self.req._('search'))
+        self.w(self._cw._('search'))
         self.w(u'</span>')
 
 
 class BreadCrumbView(EntityView):
-    id = 'breadcrumbs'
+    __regid__ = 'breadcrumbs'
 
     def cell_call(self, row, col):
-        entity = self.rset.get_entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         desc = xml_escape(uilib.cut(entity.dc_description(), 50))
         # XXX remember camember : tags.a autoescapes !
         self.w(tags.a(entity.view('breadcrumbtext'),
@@ -106,9 +106,9 @@
 
 
 class BreadCrumbTextView(EntityView):
-    id = 'breadcrumbtext'
+    __regid__ = 'breadcrumbtext'
 
     def cell_call(self, row, col):
-        entity = self.rset.get_entity(row, col)
-        textsize = self.req.property_value('navigation.short-line-size')
+        entity = self.cw_rset.get_entity(row, col)
+        textsize = self._cw.property_value('navigation.short-line-size')
         self.w(uilib.cut(entity.dc_title(), textsize))
--- a/web/views/idownloadable.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/idownloadable.py	Wed Sep 30 18:57:42 2009 +0200
@@ -26,7 +26,7 @@
     return 1
 
 def download_box(w, entity, title=None, label=None):
-    req = entity.req
+    req = entity._cw
     w(u'<div class="sideBox">')
     if title is None:
         title = req._('download')
@@ -42,7 +42,7 @@
 
 
 class DownloadBox(EntityBoxTemplate):
-    id = 'download_box'
+    __regid__ = 'download_box'
     # no download box for images
     # XXX primary_view selector ?
     __select__ = (one_line_rset() & implements(IDownloadable) &
@@ -50,7 +50,7 @@
     order = 10
 
     def cell_call(self, row, col, title=None, label=None, **kwargs):
-        entity = self.entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         download_box(self.w, entity, title, label)
 
 
@@ -58,7 +58,7 @@
     """this view is replacing the deprecated 'download' controller and allow
     downloading of entities providing the necessary interface
     """
-    id = 'download'
+    __regid__ = 'download'
     __select__ = one_line_rset() & implements(IDownloadable)
 
     templatable = False
@@ -75,7 +75,7 @@
             encoding = None
         else:
             contenttype = entity.download_content_type()
-        self.req.set_content_type(contenttype or self.content_type,
+        self._cw.set_content_type(contenttype or self.content_type,
                                   filename=entity.download_file_name(),
                                   encoding=encoding)
 
@@ -85,13 +85,13 @@
 
 class DownloadLinkView(EntityView):
     """view displaying a link to download the file"""
-    id = 'downloadlink'
+    __regid__ = 'downloadlink'
     __select__ = implements(IDownloadable)
     title = None # should not be listed in possible views
 
 
     def cell_call(self, row, col, title=None, **kwargs):
-        entity = self.entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         url = xml_escape(entity.download_url())
         self.w(u'<a href="%s">%s</a>' % (url, xml_escape(title or entity.dc_title())))
 
@@ -104,16 +104,16 @@
         self.w(u'<div class="content">')
         contenttype = entity.download_content_type()
         if contenttype.startswith('image/'):
-            self.wview('image', entity.rset, row=entity.row)
+            self.wview('image', entity.cw_rset, row=entity.cw_row)
         else:
-            self.wview('downloadlink', entity.rset, title=self.req._('download'), row=entity.row)
+            self.wview('downloadlink', entity.cw_rset, title=self._cw._('download'), row=entity.cw_row)
             try:
                 if ENGINE.has_input(contenttype):
                     self.w(entity.printable_value('data'))
             except TransformError:
                 pass
             except Exception, ex:
-                msg = self.req._("can't display data, unexpected error: %s") % ex
+                msg = self._cw._("can't display data, unexpected error: %s") % ex
                 self.w('<div class="error">%s</div>' % msg)
         self.w(u'</div>')
 
@@ -123,33 +123,33 @@
 
     def cell_call(self, row, col, title=None, **kwargs):
         """the oneline view is a link to download the file"""
-        entity = self.entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         url = xml_escape(entity.absolute_url())
         name = xml_escape(title or entity.download_file_name())
         durl = xml_escape(entity.download_url())
         self.w(u'<a href="%s">%s</a> [<a href="%s">%s</a>]' %
-               (url, name, durl, self.req._('download')))
+               (url, name, durl, self._cw._('download')))
 
 
 class ImageView(EntityView):
-    id = 'image'
+    __regid__ = 'image'
     __select__ = implements(IDownloadable) & score_entity(is_image)
 
     title = _('image')
 
     def call(self):
-        rset = self.rset
+        rset = self.cw_rset
         for i in xrange(len(rset)):
             self.w(u'<div class="efile">')
-            self.wview(self.id, rset, row=i, col=0)
+            self.wview(self.__regid__, rset, row=i, col=0)
             self.w(u'</div>')
 
     def cell_call(self, row, col, width=None, height=None, link=False):
-        entity = self.entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         #if entity.data_format.startswith('image/'):
         imgtag = u'<img src="%s" alt="%s" ' % (
             xml_escape(entity.download_url()),
-            (self.req._('download %s')  % xml_escape(entity.download_file_name())))
+            (self._cw._('download %s')  % xml_escape(entity.download_file_name())))
         if width:
             imgtag += u'width="%i" ' % width
         if height:
--- a/web/views/igeocodable.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/igeocodable.py	Wed Sep 30 18:57:42 2009 +0200
@@ -14,22 +14,22 @@
 from cubicweb.selectors import implements
 
 class GeocodingJsonView(EntityView):
-    id = 'geocoding-json'
+    __regid__ = 'geocoding-json'
+    __select__ = implements(IGeocodable)
+
     binary = True
     templatable = False
     content_type = 'application/json'
 
-    __select__ = implements(IGeocodable)
-
     def call(self):
         # remove entities that don't define latitude and longitude
-        self.rset = self.rset.filtered_rset(lambda e: e.latitude and e.longitude)
-        zoomlevel = self.req.form.pop('zoomlevel', 8)
-        extraparams = self.req.form.copy()
+        self.cw_rset = self.cw_rset.filtered_rset(lambda e: e.latitude and e.longitude)
+        zoomlevel = self._cw.form.pop('zoomlevel', 8)
+        extraparams = self._cw.form.copy()
         extraparams.pop('vid', None)
         extraparams.pop('rql', None)
         markers = [self.build_marker_data(rowidx, extraparams)
-                   for rowidx in xrange(len(self.rset))]
+                   for rowidx in xrange(len(self.cw_rset))]
         center = {
             'latitude': sum(marker['latitude'] for marker in markers) / len(markers),
             'longitude': sum(marker['longitude'] for marker in markers) / len(markers),
@@ -42,12 +42,12 @@
         self.w(simplejson.dumps(geodata))
 
     def build_marker_data(self, row, extraparams):
-        entity = self.entity(row, 0)
+        entity = self.cw_rset.get_entity(row, 0)
         icon = None
         if hasattr(entity, 'marker_icon'):
             icon = entity.marker_icon()
         else:
-            icon = (self.req.external_resource('GMARKER_ICON'), (20, 34), (4, 34), None)
+            icon = (self._cw.external_resource('GMARKER_ICON'), (20, 34), (4, 34), None)
         return {'latitude': entity.latitude, 'longitude': entity.longitude,
                 'title': entity.dc_long_title(),
                 #icon defines : (icon._url, icon.size,  icon.iconAncho', icon.shadow)
@@ -57,46 +57,45 @@
 
 
 class GoogleMapBubbleView(EntityView):
-    id = 'gmap-bubble'
-
+    __regid__ = 'gmap-bubble'
     __select__ = implements(IGeocodable)
 
     def cell_call(self, row, col):
-        entity = self.entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         self.w(u'<div>%s</div>' % entity.view('oneline'))
         # FIXME: we should call something like address-view if available
 
 
 class GoogleMapsView(EntityView):
-    id = 'gmap-view'
+    __regid__ = 'gmap-view'
+    __select__ = implements(IGeocodable)
 
-    __select__ = implements(IGeocodable)
     need_navigation = False
 
     def call(self, gmap_key, width=400, height=400, uselabel=True, urlparams=None):
-        self.req.demote_to_html()
+        self._cw.demote_to_html()
         # remove entities that don't define latitude and longitude
-        self.rset = self.rset.filtered_rset(lambda e: e.latitude and e.longitude)
-        self.req.add_js('http://maps.google.com/maps?sensor=false&file=api&amp;v=2&amp;key=%s' % gmap_key,
+        self.cw_rset = self.cw_rset.filtered_rset(lambda e: e.latitude and e.longitude)
+        self._cw.add_js('http://maps.google.com/maps?sensor=false&file=api&amp;v=2&amp;key=%s' % gmap_key,
                         localfile=False)
-        self.req.add_js( ('cubicweb.widgets.js', 'cubicweb.gmap.js', 'gmap.utility.labeledmarker.js') )
-        rql = self.rset.printable_rql()
+        self._cw.add_js( ('cubicweb.widgets.js', 'cubicweb.gmap.js', 'gmap.utility.labeledmarker.js') )
+        rql = self.cw_rset.printable_rql()
         if urlparams is None:
-            loadurl = self.build_url(rql=rql, vid='geocoding-json')
+            loadurl = self._cw.build_url(rql=rql, vid='geocoding-json')
         else:
-            loadurl = self.build_url(rql=rql, vid='geocoding-json', **urlparams)
+            loadurl = self._cw.build_url(rql=rql, vid='geocoding-json', **urlparams)
         self.w(u'<div style="width: %spx; height: %spx;" class="widget gmap" '
                u'cubicweb:wdgtype="GMapWidget" cubicweb:loadtype="auto" '
                u'cubicweb:loadurl="%s" cubicweb:uselabel="%s"> </div>' % (width, height, loadurl, uselabel))
 
 
 class GoogeMapsLegend(EntityView):
-    id = 'gmap-legend'
+    __regid__ = 'gmap-legend'
 
     def call(self):
         self.w(u'<ol>')
-        for rowidx in xrange(len(self.rset)):
+        for rowidx in xrange(len(self.cw_rset)):
             self.w(u'<li>')
-            self.wview('listitem', self.rset, row=rowidx, col=0)
+            self.wview('listitem', self.cw_rset, row=rowidx, col=0)
             self.w(u'</li>')
         self.w(u'</ol>')
--- a/web/views/iprogress.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/iprogress.py	Wed Sep 30 18:57:42 2009 +0200
@@ -34,7 +34,7 @@
     header_for_COLNAME methods allow to customize header's label
     """
 
-    id = 'progress_table_view'
+    __regid__ = 'progress_table_view'
     title = _('task progression')
     __select__ = implements(IMileStone)
 
@@ -45,21 +45,21 @@
 
     def call(self, columns=None):
         """displays all versions in a table"""
-        self.req.add_css('cubicweb.iprogress.css')
-        _ = self.req._
+        self._cw.add_css('cubicweb.iprogress.css')
+        _ = self._cw._
         self.columns = columns or self.columns
-        ecls = self.vreg['etypes'].etype_class(self.rset.description[0][0])
+        ecls = self._cw.vreg['etypes'].etype_class(self.cw_rset.description[0][0])
         self.w(u'<table class="progress">')
         self.table_header(ecls)
         self.w(u'<tbody>')
-        for row in xrange(self.rset.rowcount):
+        for row in xrange(self.cw_rset.rowcount):
             self.cell_call(row=row, col=0)
         self.w(u'</tbody>')
         self.w(u'</table>')
 
     def cell_call(self, row, col):
-        _ = self.req._
-        entity = self.entity(row, col)
+        _ = self._cw._
+        entity = self.cw_rset.get_entity(row, col)
         infos = {}
         for col in self.columns:
             meth = getattr(self, 'build_%s_cell' % col, None)
@@ -83,16 +83,16 @@
 
     def header_for_project(self, ecls):
         """use entity's parent type as label"""
-        return display_name(self.req, ecls.parent_type)
+        return display_name(self._cw, ecls.parent_type)
 
     def header_for_milestone(self, ecls):
         """use entity's type as label"""
-        return display_name(self.req, ecls.id)
+        return display_name(self._cw, ecls.id)
 
     def table_header(self, ecls):
         """builds the table's header"""
         self.w(u'<thead><tr>')
-        _ = self.req._
+        _ = self._cw._
         for column in self.columns:
             meth = getattr(self, 'header_for_%s' % column, None)
             if meth:
@@ -109,7 +109,7 @@
         project = entity.get_main_task()
         if project:
             return project.view('incontext')
-        return self.req._('no related project')
+        return self._cw._('no related project')
 
     def build_milestone_cell(self, entity):
         """``milestone`` column cell renderer"""
@@ -117,16 +117,16 @@
 
     def build_state_cell(self, entity):
         """``state`` column cell renderer"""
-        return xml_escape(self.req._(entity.state))
+        return xml_escape(self._cw._(entity.state))
 
     def build_eta_date_cell(self, entity):
         """``eta_date`` column cell renderer"""
         if entity.finished():
-            return self.format_date(entity.completion_date())
-        formated_date = self.format_date(entity.initial_prevision_date())
+            return self._cw.format_date(entity.completion_date())
+        formated_date = self._cw.format_date(entity.initial_prevision_date())
         if entity.in_progress():
-            eta_date = self.format_date(entity.eta_date())
-            _ = self.req._
+            eta_date = self._cw.format_date(entity.eta_date())
+            _ = self._cw._
             if formated_date:
                 formated_date += u' (%s %s)' % (_('expected:'), eta_date)
             else:
@@ -139,7 +139,7 @@
 
     def build_cost_cell(self, entity):
         """``cost`` column cell renderer"""
-        _ = self.req._
+        _ = self._cw._
         pinfo = entity.progress_info()
         totalcost = pinfo.get('estimatedcorrected', pinfo['estimated'])
         missing = pinfo.get('notestimatedcorrected', pinfo.get('notestimated', 0))
@@ -165,11 +165,11 @@
     """this views redirects to ``progress_table_view`` but removes
     the ``project`` column
     """
-    id = 'ic_progress_table_view'
+    __regid__ = 'ic_progress_table_view'
 
     def call(self, columns=None):
-        view = self.vreg['views'].select('progress_table_view', self.req,
-                                         rset=self.rset)
+        view = self._cw.vreg['views'].select('progress_table_view', self._cw,
+                                         rset=self.cw_rset)
         columns = list(columns or view.columns)
         try:
             columns.remove('project')
@@ -180,13 +180,13 @@
 
 class ProgressBarView(EntityView):
     """displays a progress bar"""
-    id = 'progressbar'
+    __regid__ = 'progressbar'
     title = _('progress bar')
     __select__ = implements(IProgress)
 
     def cell_call(self, row, col):
-        self.req.add_css('cubicweb.iprogress.css')
-        entity = self.entity(row, col)
+        self._cw.add_css('cubicweb.iprogress.css')
+        entity = self.cw_rset.get_entity(row, col)
         widget = ProgressBarWidget(entity.done, entity.todo,
                                    entity.revised_cost)
         self.w(widget.render())
--- a/web/views/isioc.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/isioc.py	Wed Sep 30 18:57:42 2009 +0200
@@ -14,14 +14,14 @@
 from cubicweb.interfaces import ISiocItem, ISiocContainer
 
 class SIOCView(EntityView):
-    id = 'sioc'
+    __regid__ = 'sioc'
     __select__ = EntityView.__select__ & implements(ISiocItem, ISiocContainer)
     title = _('sioc')
     templatable = False
     content_type = 'text/xml'
 
     def call(self):
-        self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
+        self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self._cw.encoding)
         self.w(u'''<rdf:RDF
              xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
              xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
@@ -30,21 +30,21 @@
              xmlns:sioc="http://rdfs.org/sioc/ns#"
              xmlns:sioctype="http://rdfs.org/sioc/types#"
              xmlns:dcterms="http://purl.org/dc/terms/">\n''')
-        for i in xrange(self.rset.rowcount):
+        for i in xrange(self.cw_rset.rowcount):
             self.cell_call(i, 0)
         self.w(u'</rdf:RDF>\n')
 
     def cell_call(self, row, col):
-        self.wview('sioc_element', self.rset, row=row, col=col)
+        self.wview('sioc_element', self.cw_rset, row=row, col=col)
 
 class SIOCContainerView(EntityView):
-    id = 'sioc_element'
+    __regid__ = 'sioc_element'
     __select__ = EntityView.__select__ & implements(ISiocContainer)
     templatable = False
     content_type = 'text/xml'
 
     def cell_call(self, row, col):
-        entity = self.complete_entity(row, col)
+        entity = self.cw_rset.complete_entity(row, col)
         sioct = xml_escape(entity.isioc_type())
         self.w(u'<sioc:%s rdf:about="%s">\n'
                % (sioct, xml_escape(entity.absolute_url())))
@@ -59,13 +59,13 @@
 
 
 class SIOCItemView(EntityView):
-    id = 'sioc_element'
+    __regid__ = 'sioc_element'
     __select__ = EntityView.__select__ & implements(ISiocItem)
     templatable = False
     content_type = 'text/xml'
 
     def cell_call(self, row, col):
-        entity = self.complete_entity(row, col)
+        entity = self.cw_rset.complete_entity(row, col)
         sioct = xml_escape(entity.isioc_type())
         self.w(u'<sioc:%s rdf:about="%s">\n'
                %  (sioct, xml_escape(entity.absolute_url())))
--- a/web/views/magicsearch.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/magicsearch.py	Wed Sep 30 18:57:42 2009 +0200
@@ -11,11 +11,12 @@
 
 import re
 from logging import getLogger
+from warnings import warn
 
 from rql import RQLSyntaxError, BadRQLQuery, parse
 from rql.nodes import Relation
 
-from cubicweb import Unauthorized
+from cubicweb import Unauthorized, typed_eid
 from cubicweb.view import Component
 
 LOGGER = getLogger('cubicweb.magicsearch')
@@ -135,20 +136,20 @@
 
 class BaseQueryProcessor(Component):
     __abstract__ = True
-    id = 'magicsearch_processor'
+    __regid__ = 'magicsearch_processor'
     # set something if you want explicit component search facility for the
     # component
     name = None
 
-    def process_query(self, uquery, req):
-        args = self.preprocess_query(uquery, req)
+    def process_query(self, uquery):
+        args = self.preprocess_query(uquery)
         try:
-            return req.execute(*args)
+            return self._cw.execute(*args)
         finally:
             # rollback necessary to avoid leaving the connection in a bad state
-            req.cnx.rollback()
+            self._cw.cnx.rollback()
 
-    def preprocess_query(self, uquery, req):
+    def preprocess_query(self, uquery):
         raise NotImplementedError()
 
 
@@ -160,7 +161,7 @@
     """
     name = 'rql'
     priority = 0
-    def preprocess_query(self, uquery, req):
+    def preprocess_query(self, uquery):
         return uquery,
 
 
@@ -169,11 +170,12 @@
     and attributes
     """
     priority = 2
-    def preprocess_query(self, uquery, req):
+    def preprocess_query(self, uquery):
         rqlst = parse(uquery, print_errors=False)
-        schema = self.vreg.schema
+        schema = self._cw.schema
         # rql syntax tree will be modified in place if necessary
-        translate_rql_tree(rqlst, trmap(self.config, schema, req.lang), schema)
+        translate_rql_tree(rqlst, trmap(self._cw.config, schema, self._cw.lang),
+                           schema)
         return rqlst.as_string(),
 
 
@@ -184,10 +186,9 @@
     """
     priority = 4
 
-    def preprocess_query(self, uquery, req):
+    def preprocess_query(self, uquery):
         """try to get rql from an unicode query string"""
         args = None
-        self.req = req
         try:
             # Process as if there was a quoted part
             args = self._quoted_words_query(uquery)
@@ -210,7 +211,7 @@
         """
         etype = word.capitalize()
         try:
-            return trmap(self.config, self.vreg.schema, self.req.lang)[etype]
+            return trmap(self._cw.config, self._cw.vreg.schema, self._cw.lang)[etype]
         except KeyError:
             raise BadRQLQuery('%s is not a valid entity name' % etype)
 
@@ -222,7 +223,7 @@
         # Need to convert from unicode to string (could be whatever)
         rtype = word.lower()
         # Find the entity name as stored in the DB
-        translations = trmap(self.config, self.vreg.schema, self.req.lang)
+        translations = trmap(self._cw.config, self._cw.vreg.schema, self._cw.lang)
         try:
             translations = translations[rtype]
         except KeyError:
@@ -239,7 +240,7 @@
         """
         # if this is an integer, then directly go to eid
         try:
-            eid = int(word)
+            eid = typed_eid(word)
             return 'Any X WHERE X eid %(x)s', {'x': eid}, 'x'
         except ValueError:
             etype = self._get_entity_type(word)
@@ -249,9 +250,9 @@
         searchop = ''
         if '%' in searchstr:
             if rtype:
-                possible_etypes = self.schema.rschema(rtype).objects(etype)
+                possible_etypes = self._cw.schema.rschema(rtype).objects(etype)
             else:
-                possible_etypes = [self.schema.eschema(etype)]
+                possible_etypes = [self._cw.schema.eschema(etype)]
             if searchattr or len(possible_etypes) == 1:
                 searchattr = searchattr or possible_etypes[0].main_attribute()
                 searchop = 'LIKE '
@@ -275,10 +276,10 @@
         """Specific process for three words query (case (3) of preprocess_rql)
         """
         etype = self._get_entity_type(word1)
-        eschema = self.schema.eschema(etype)
+        eschema = self._cw.schema.eschema(etype)
         rtype = self._get_attribute_name(word2, eschema)
         # expand shortcut if rtype is a non final relation
-        if not self.schema.rschema(rtype).is_final():
+        if not self._cw.schema.rschema(rtype).is_final():
             return self._expand_shortcut(etype, rtype, word3)
         if '%' in word3:
             searchop = 'LIKE '
@@ -336,28 +337,28 @@
     priority = 10
     name = 'text'
 
-    def preprocess_query(self, uquery, req):
+    def preprocess_query(self, uquery):
         """suppose it's a plain text query"""
         return 'Any X WHERE X has_text %(text)s', {'text': uquery}
 
 
 
 class MagicSearchComponent(Component):
-    id  = 'magicsearch'
+    __regid__  = 'magicsearch'
     def __init__(self, req, rset=None):
-        super(MagicSearchComponent, self).__init__(req, rset)
+        super(MagicSearchComponent, self).__init__(req, rset=rset)
         processors = []
         self.by_name = {}
-        for processorcls in self.vreg['components']['magicsearch_processor']:
+        for processorcls in self._cw.vreg['components']['magicsearch_processor']:
             # instantiation needed
-            processor = processorcls()
+            processor = processorcls(self._cw)
             processors.append(processor)
             if processor.name is not None:
                 assert not processor.name in self.by_name
                 self.by_name[processor.name.lower()] = processor
         self.processors = sorted(processors, key=lambda x: x.priority)
 
-    def process_query(self, uquery, req):
+    def process_query(self, uquery):
         assert isinstance(uquery, unicode)
         try:
             procname, query = uquery.split(':', 1)
@@ -368,7 +369,15 @@
             unauthorized = None
             for proc in self.processors:
                 try:
-                    return proc.process_query(uquery, req)
+                    try:
+                        return proc.process_query(uquery)
+                    except TypeError, exc: # cw 3.5 compat
+                        print "EXC", exc
+                        warn("[3.6] %s.%s.process_query() should now accept uquery "
+                             "as unique argument, use self._cw instead of req"
+                             % (proc.__module__, proc.__class__.__name__),
+                             DeprecationWarning)
+                        return proc.process_query(uquery, self._cw)
                 # FIXME : we don't want to catch any exception type here !
                 except (RQLSyntaxError, BadRQLQuery):
                     pass
@@ -381,6 +390,6 @@
             if unauthorized:
                 raise unauthorized
         else:
-            # let exception propagate
-            return proc.process_query(uquery, req)
-        raise BadRQLQuery(req._('sorry, the server is unable to handle this query'))
+            # 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'))
--- a/web/views/management.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/management.py	Wed Sep 30 18:57:42 2009 +0200
@@ -26,7 +26,7 @@
 
     def schema_definition(self, eschema, link=True,  access_types=None):
         w = self.w
-        _ = self.req._
+        _ = self._cw._
         if not access_types:
             access_types = eschema.ACTIONS
         w(u'<table class="schemaInfo">')
@@ -34,7 +34,7 @@
             _("permission"), _('granted to groups'), _('rql expressions')))
         for access_type in access_types:
             w(u'<tr>')
-            w(u'<td>%s</td>' % self.req.__('%s_perm' % access_type))
+            w(u'<td>%s</td>' % self._cw.__('%s_perm' % access_type))
             groups = eschema.get_groups(access_type)
             l = []
             groups = [(_(group), group) for group in groups]
@@ -43,7 +43,7 @@
                     # XXX we should get a group entity and call its absolute_url
                     # method
                     l.append(u'<a href="%s" class="%s">%s</a><br/>' % (
-                    self.build_url('cwgroup/%s' % group), group, trad))
+                    self._cw.build_url('cwgroup/%s' % group), group, trad))
                 else:
                     l.append(u'<div class="%s">%s</div>' % (group, trad))
             w(u'<td>%s</td>' % u''.join(l))
@@ -67,21 +67,21 @@
 
 class SecurityManagementView(EntityView, SecurityViewMixIn):
     """display security information for a given entity"""
-    id = 'security'
+    __regid__ = 'security'
     __select__ = EntityView.__select__ & authenticated_user()
 
     title = _('security')
 
     def call(self):
-        self.w(u'<div id="progress">%s</div>' % self.req._('validating...'))
+        self.w(u'<div id="progress">%s</div>' % self._cw._('validating...'))
         super(SecurityManagementView, self).call()
 
     def cell_call(self, row, col):
-        self.req.add_js('cubicweb.edition.js')
-        self.req.add_css('cubicweb.acl.css')
-        entity = self.rset.get_entity(row, col)
+        self._cw.add_js('cubicweb.edition.js')
+        self._cw.add_css('cubicweb.acl.css')
+        entity = self.cw_rset.get_entity(row, col)
         w = self.w
-        _ = self.req._
+        _ = self._cw._
         w(u'<h1><span class="etype">%s</span> <a href="%s">%s</a></h1>'
           % (entity.dc_type().capitalize(),
              xml_escape(entity.absolute_url()),
@@ -91,7 +91,7 @@
         self.schema_definition(entity.e_schema)
         self.w('<h2>%s</h2>' % _('manage security'))
         # ownership information
-        if self.schema.rschema('owned_by').has_perm(self.req, 'add',
+        if self._cw.schema.rschema('owned_by').has_perm(self._cw, 'add',
                                                     fromeid=entity.eid):
             self.owned_by_edit_form(entity)
         else:
@@ -99,30 +99,30 @@
         # cwpermissions
         if 'require_permission' in entity.e_schema.subject_relations():
             w('<h3>%s</h3>' % _('permissions for this entity'))
-            reqpermschema = self.schema.rschema('require_permission')
+            reqpermschema = self._cw.schema.rschema('require_permission')
             self.require_permission_information(entity, reqpermschema)
-            if reqpermschema.has_perm(self.req, 'add', fromeid=entity.eid):
+            if reqpermschema.has_perm(self._cw, 'add', fromeid=entity.eid):
                 self.require_permission_edit_form(entity)
 
     def owned_by_edit_form(self, entity):
-        self.w('<h3>%s</h3>' % self.req._('ownership'))
-        msg = self.req._('ownerships have been changed')
-        form = self.vreg['forms'].select('base', self.req, entity=entity,
+        self.w('<h3>%s</h3>' % self._cw._('ownership'))
+        msg = self._cw._('ownerships have been changed')
+        form = self._cw.vreg['forms'].select('base', self._cw, entity=entity,
                                          form_renderer_id='base', submitmsg=msg,
                                          form_buttons=[wdgs.SubmitButton()],
                                          domid='ownership%s' % entity.eid,
                                          __redirectvid='security',
                                          __redirectpath=entity.rest_path())
-        field = guess_field(entity.e_schema, self.schema.rschema('owned_by'))
+        field = guess_field(entity.e_schema, self._cw.schema.rschema('owned_by'))
         form.append_field(field)
         self.w(form.form_render(display_progress_div=False))
 
     def owned_by_information(self, entity):
         ownersrset = entity.related('owned_by')
         if ownersrset:
-            self.w('<h3>%s</h3>' % self.req._('ownership'))
+            self.w('<h3>%s</h3>' % self._cw._('ownership'))
             self.w(u'<div class="ownerInfo">')
-            self.w(self.req._('this entity is currently owned by') + ' ')
+            self.w(self._cw._('this entity is currently owned by') + ' ')
             self.wview('csv', entity.related('owned_by'), 'null')
             self.w(u'</div>')
         # else we don't know if this is because entity has no owner or becayse
@@ -131,10 +131,10 @@
     def require_permission_information(self, entity, reqpermschema):
         if entity.require_permission:
             w = self.w
-            _ = self.req._
-            if reqpermschema.has_perm(self.req, 'delete', fromeid=entity.eid):
-                delurl = self.build_url('edit', __redirectvid='security',
-                                        __redirectpath=entity.rest_path())
+            _ = self._cw._
+            if reqpermschema.has_perm(self._cw, 'delete', fromeid=entity.eid):
+                delurl = self._cw.build_url('edit', __redirectvid='security',
+                                            __redirectpath=entity.rest_path())
                 delurl = delurl.replace('%', '%%')
                 # don't give __delete value to build_url else it will be urlquoted
                 # and this will replace %s by %25s
@@ -157,13 +157,13 @@
                 w(u'</tr>\n')
             w(u'</table>')
         else:
-            self.w(self.req._('no associated permissions'))
+            self.w(self._cw._('no associated permissions'))
 
     def require_permission_edit_form(self, entity):
-        newperm = self.vreg['etypes'].etype_class('CWPermission')(self.req)
-        newperm.eid = self.req.varmaker.next()
-        self.w(u'<p>%s</p>' % self.req._('add a new permission'))
-        form = self.vreg['forms'].select('base', self.req, entity=newperm,
+        newperm = self._cw.vreg['etypes'].etype_class('CWPermission')(self._cw)
+        newperm.eid = self._cw.varmaker.next()
+        self.w(u'<p>%s</p>' % self._cw._('add a new permission'))
+        form = self._cw.vreg['forms'].select('base', self._cw, entity=newperm,
                                          form_buttons=[wdgs.SubmitButton()],
                                          domid='reqperm%s' % entity.eid,
                                          __redirectvid='security',
@@ -173,38 +173,38 @@
         permnames = getattr(entity, '__permissions__', None)
         cwpermschema = newperm.e_schema
         if permnames is not None:
-            field = guess_field(cwpermschema, self.schema.rschema('name'),
+            field = guess_field(cwpermschema, self._cw.schema.rschema('name'),
                                 widget=wdgs.Select({'size': 1}),
                                 choices=permnames)
         else:
-            field = guess_field(cwpermschema, self.schema.rschema('name'))
+            field = guess_field(cwpermschema, self._cw.schema.rschema('name'))
         form.append_field(field)
-        field = guess_field(cwpermschema, self.schema.rschema('label'))
+        field = guess_field(cwpermschema, self._cw.schema.rschema('label'))
         form.append_field(field)
-        field = guess_field(cwpermschema, self.schema.rschema('require_group'))
+        field = guess_field(cwpermschema, self._cw.schema.rschema('require_group'))
         form.append_field(field)
-        renderer = self.vreg['formrenderers'].select(
-            'htable', self.req, rset=None, display_progress_div=False)
+        renderer = self._cw.vreg['formrenderers'].select(
+            'htable', self._cw, rset=None, display_progress_div=False)
         self.w(form.form_render(renderer=renderer))
 
 
 class ErrorView(AnyRsetView):
     """default view when no result has been found"""
     __select__ = yes()
-    id = 'error'
+    __regid__ = 'error'
 
     def page_title(self):
         """returns a title according to the result set - used for the
         title in the HTML header
         """
-        return self.req._('an error occured')
+        return self._cw._('an error occured')
 
     def call(self):
-        req = self.req.reset_headers()
+        req = self._cw.reset_headers()
         w = self.w
         ex = req.data.get('ex')#_("unable to find exception information"))
         excinfo = req.data.get('excinfo')
-        title = self.req._('an error occured')
+        title = self._cw._('an error occured')
         w(u'<h2>%s</h2>' % title)
         if 'errmsg' in req.data:
             ex = req.data['errmsg']
@@ -212,7 +212,7 @@
         else:
             exclass = ex.__class__.__name__
             ex = exc_message(ex, req.encoding)
-        if excinfo is not None and self.config['print-traceback']:
+        if excinfo is not None and self._cw.config['print-traceback']:
             if exclass is None:
                 w(u'<div class="tb">%s</div>'
                        % xml_escape(ex).replace("\n","<br />"))
@@ -226,20 +226,20 @@
         # if excinfo is not None, it's probably not a bug
         if excinfo is None:
             return
-        vcconf = self.config.vc_config()
+        vcconf = self._cw.config.vc_config()
         w(u"<div>")
-        eversion = vcconf.get('cubicweb', self.req._('no version information'))
+        eversion = vcconf.get('cubicweb', self._cw._('no version information'))
         # NOTE: tuple wrapping needed since eversion is itself a tuple
         w(u"<b>CubicWeb version:</b> %s<br/>\n" % (eversion,))
         cversions = []
-        for cube in self.config.cubes():
-            cubeversion = vcconf.get(cube, self.req._('no version information'))
+        for cube in self._cw.config.cubes():
+            cubeversion = vcconf.get(cube, self._cw._('no version information'))
             w(u"<b>Package %s version:</b> %s<br/>\n" % (cube, cubeversion))
             cversions.append((cube, cubeversion))
         w(u"</div>")
         # creates a bug submission link if submit-mail is set
-        if self.config['submit-mail']:
-            form = self.vreg['forms'].select('base', self.req, rset=None,
+        if self._cw.config['submit-mail']:
+            form = self._cw.vreg['forms'].select('base', self._cw, rset=None,
                                              mainform=False)
             binfo = text_error_description(ex, excinfo, req, eversion, cversions)
             form.form_add_hidden('description', binfo,
@@ -274,27 +274,27 @@
 
 
 class ProcessInformationView(StartupView):
-    id = 'info'
+    __regid__ = 'info'
     __select__ = none_rset() & match_user_groups('users', 'managers')
 
     title = _('server information')
 
     def call(self, **kwargs):
         """display server information"""
-        vcconf = self.config.vc_config()
-        req = self.req
+        vcconf = self._cw.config.vc_config()
+        req = self._cw
         _ = req._
         # display main information
         self.w(u'<h3>%s</h3>' % _('Application'))
         self.w(u'<table border="1">')
         self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
             'CubicWeb', vcconf.get('cubicweb', _('no version information'))))
-        for pkg in self.config.cubes():
+        for pkg in self._cw.config.cubes():
             pkgversion = vcconf.get(pkg, _('no version information'))
             self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
                 pkg, pkgversion))
         self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
-            _('home'), self.config.apphome))
+            _('home'), self._cw.config.apphome))
         self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
             _('base url'), req.base_url()))
         self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
--- a/web/views/massmailing.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/massmailing.py	Wed Sep 30 18:57:42 2009 +0200
@@ -19,7 +19,7 @@
 
 
 class SendEmailAction(action.Action):
-    id = 'sendemail'
+    __regid__ = 'sendemail'
     # XXX should check email is set as well
     __select__ = (action.Action.__select__ & implements(IEmailable)
                   & match_user_groups('managers', 'users'))
@@ -29,14 +29,14 @@
 
     def url(self):
         params = {'vid': 'massmailing', '__force_display': 1}
-        if self.req.form.has_key('rql'):
-            params['rql'] = self.req.form['rql']
-        return self.build_url(self.req.relative_path(includeparams=False),
-                              **params)
+        if self._cw.form.has_key('rql'):
+            params['rql'] = self._cw.form['rql']
+        return self._cw.build_url(self._cw.relative_path(includeparams=False),
+                                  **params)
 
 
 class MassMailingForm(forms.FieldsForm):
-    id = 'massmailing'
+    __regid__ = 'massmailing'
 
     sender = ff.StringField(widget=TextInput({'disabled': 'disabled'}),
                             label=_('From:'))
@@ -53,21 +53,21 @@
 
     def form_field_vocabulary(self, field):
         if field.name == 'recipient':
-            vocab = [(entity.get_email(), entity.eid) for entity in self.rset.entities()]
+            vocab = [(entity.get_email(), entity.eid) for entity in self.cw_rset.entities()]
             return [(label, value) for label, value in vocab if label]
         return super(MassMailingForm, self).form_field_vocabulary(field)
 
     def form_field_value(self, field, values):
         if field.name == 'recipient':
-            return [entity.eid for entity in self.rset.entities() if entity.get_email()]
+            return [entity.eid for entity in self.cw_rset.entities() if entity.get_email()]
         elif field.name == 'mailbody':
             field.widget.attrs['cubicweb:variables'] = ','.join(self.get_allowed_substitutions())
         return super(MassMailingForm, self).form_field_value(field, values)
 
     def get_allowed_substitutions(self):
         attrs = []
-        for coltype in self.rset.column_types(0):
-            eclass = self.vreg['etypes'].etype_class(coltype)
+        for coltype in self.cw_rset.column_types(0):
+            eclass = self._cw.vreg['etypes'].etype_class(coltype)
             attrs.append(eclass.allowed_massmail_keys())
         return sorted(reduce(operator.and_, attrs))
 
@@ -75,13 +75,13 @@
         insertLink = u'<a href="javascript: insertText(\'%%(%s)s\', \'emailarea\');">%%(%s)s</a>'
         substs = (u'<div class="substitution">%s</div>' % (insertLink % (subst, subst))
                   for subst in self.get_allowed_substitutions())
-        helpmsg = self.req._('You can use any of the following substitutions in your text')
+        helpmsg = self._cw._('You can use any of the following substitutions in your text')
         return u'<div id="substitutions"><span>%s</span>%s</div>' % (
             helpmsg, u'\n'.join(substs))
 
 
 class MassMailingFormRenderer(formrenderers.FormRenderer):
-    id = 'massmailing'
+    __regid__ = 'massmailing'
     button_bar_class = u'toolbar'
 
     def _render_fields(self, fields, w, form):
@@ -117,14 +117,14 @@
         pass
 
 class MassMailingFormView(form.FormViewMixIn, EntityView):
-    id = 'massmailing'
+    __regid__ = 'massmailing'
     __select__ = implements(IEmailable) & match_user_groups('managers', 'users')
 
     def call(self):
-        req = self.req
+        req = self._cw
         req.add_js('cubicweb.widgets.js', 'cubicweb.massmailing.js')
         req.add_css('cubicweb.mailform.css')
         from_addr = '%s <%s>' % (req.user.dc_title(), req.user.get_email())
-        form = self.vreg['forms'].select('massmailing', self.req, rset=self.rset,
+        form = self._cw.vreg['forms'].select('massmailing', self._cw, rset=self.cw_rset,
                                 action='sendmail', domid='sendmail')
         self.w(form.form_render(sender=from_addr))
--- a/web/views/navigation.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/navigation.py	Wed Sep 30 18:57:42 2009 +0200
@@ -26,8 +26,8 @@
     def call(self):
         """displays a resultset by page"""
         w = self.w
-        req = self.req
-        rset = self.rset
+        req = self._cw
+        rset = self.cw_rset
         page_size = self.page_size
         start = 0
         blocklist = []
@@ -59,12 +59,12 @@
     nb_chars = 5
 
     def display_func(self, rset, col, attrname):
-        req = self.req
+        req = self._cw
         if attrname is not None:
             def index_display(row):
                 entity = rset.get_entity(row, col)
                 return entity.printable_value(attrname, format='text/plain')
-        elif self.schema.eschema(rset.description[0][col]).is_final():
+        elif self._cw.schema.eschema(rset.description[0][col]).is_final():
             def index_display(row):
                 return unicode(rset[row][col])
         else:
@@ -80,9 +80,9 @@
         [ana - cro] | [cro - ghe] | ... | [tim - zou]
         """
         w = self.w
-        rset = self.rset
+        rset = self.cw_rset
         page_size = self.page_size
-        rschema = self.schema.rschema
+        rschema = self._cw.schema.rschema
         # attrname = the name of attribute according to which the sort
         # is done if any
         for sorterm in rset.syntax_tree().children[0].orderby:
@@ -122,10 +122,10 @@
             # nothing usable found, use the first column
             index_display = self.display_func(rset, 0, None)
         blocklist = []
-        params = dict(self.req.form)
+        params = dict(self._cw.form)
         self.clean_params(params)
         start = 0
-        basepath = self.req.relative_path(includeparams=False)
+        basepath = self._cw.relative_path(includeparams=False)
         while start < rset.rowcount:
             stop = min(start + page_size - 1, rset.rowcount - 1)
             cell = self.format_link_content(index_display(start), index_display(stop))
@@ -149,8 +149,8 @@
 def limit_rset_using_paged_nav(self, req, rset, w, forcedisplay=False,
                                show_all_option=True, page_size=None):
     if not (forcedisplay or req.form.get('__force_display') is not None):
-        nav = self.vreg['components'].select_object('navigation', req,
-                                      rset=rset, page_size=page_size)
+        nav = self._cw.vreg['components'].select_or_none('navigation', req,
+                                                     rset=rset, page_size=page_size)
         if nav:
             # get boundaries before component rendering
             start, stop = nav.page_boundaries()
@@ -159,7 +159,7 @@
             nav.clean_params(params)
             # make a link to see them all
             if show_all_option:
-                url = xml_escape(self.build_url(__force_display=1, **params))
+                url = xml_escape(self._cw.build_url(__force_display=1, **params))
                 w(u'<span><a href="%s">%s</a></span>\n'
                   % (url, req._('show %s results') % len(rset)))
             rset.limit(offset=start, limit=stop-start, inplace=True)
@@ -170,14 +170,15 @@
 from cubicweb.view import View
 View.pagination = deprecated('.pagination is deprecated, use paginate')(limit_rset_using_paged_nav)
 
-def paginate(view, show_all_option=True, w=None, page_size=None):
-    limit_rset_using_paged_nav(view, view.req, view.rset, w or view.w,
+def paginate(view, show_all_option=True, w=None, page_size=None, rset=None):
+    rset = rset or view.cw_rset
+    limit_rset_using_paged_nav(view, view._cw, view.cw_rset, w or view.w,
                                not view.need_navigation, show_all_option,
                                page_size=page_size)
 View.paginate = paginate
 
 class NextPrevNavigationComponent(EntityVComponent):
-    id = 'prevnext'
+    __regid__ = 'prevnext'
     # register msg not generated since no entity implements IPrevNext in cubicweb
     # itself
     title = _('contentnavigation_prevnext')
@@ -187,23 +188,23 @@
     context = 'navbottom'
     order = 10
     def call(self, view=None):
-        entity = self.entity(0)
+        entity = self.cw_rset.get_entity(0,0)
         previous = entity.previous_entity()
         next = entity.next_entity()
         if previous or next:
-            textsize = self.req.property_value('navigation.short-line-size')
+            textsize = self._cw.property_value('navigation.short-line-size')
             self.w(u'<div class="prevnext">')
             if previous:
                 self.w(u'<div class="previousEntity left">')
                 self.w(self.previous_link(previous, textsize))
                 self.w(u'</div>')
-                self.req.html_headers.add_raw('<link rel="prev" href="%s" />'
+                self._cw.html_headers.add_raw('<link rel="prev" href="%s" />'
                                               % xml_escape(previous.absolute_url()))
             if next:
                 self.w(u'<div class="nextEntity right">')
                 self.w(self.next_link(next, textsize))
                 self.w(u'</div>')
-                self.req.html_headers.add_raw('<link rel="next" href="%s" />'
+                self._cw.html_headers.add_raw('<link rel="next" href="%s" />'
                                               % xml_escape(next.absolute_url()))
             self.w(u'</div>')
             self.w(u'<div class="clear"></div>')
@@ -211,11 +212,11 @@
     def previous_link(self, previous, textsize):
         return u'<a href="%s" title="%s">&lt;&lt; %s</a>' % (
             xml_escape(previous.absolute_url()),
-            self.req._('i18nprevnext_previous'),
+            self._cw._('i18nprevnext_previous'),
             xml_escape(cut(previous.dc_title(), textsize)))
 
     def next_link(self, next, textsize):
         return u'<a href="%s" title="%s">%s &gt;&gt;</a>' % (
             xml_escape(next.absolute_url()),
-            self.req._('i18nprevnext_next'),
+            self._cw._('i18nprevnext_next'),
             xml_escape(cut(next.dc_title(), textsize)))
--- a/web/views/old_calendar.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/old_calendar.py	Wed Sep 30 18:57:42 2009 +0200
@@ -44,16 +44,16 @@
         next1 = next_month(date, smallshift)
         prev2 = previous_month(date, bigshift)
         next2 = next_month(date, bigshift)
-        rql = self.rset.printable_rql()
+        rql = self.cw_rset.printable_rql()
         return self.NAV_HEADER % (
-            xml_escape(self.build_url(rql=rql, vid=self.id, year=prev2.year,
-                                       month=prev2.month)),
-            xml_escape(self.build_url(rql=rql, vid=self.id, year=prev1.year,
-                                       month=prev1.month)),
-            xml_escape(self.build_url(rql=rql, vid=self.id, year=next1.year,
-                                       month=next1.month)),
-            xml_escape(self.build_url(rql=rql, vid=self.id, year=next2.year,
-                                       month=next2.month)))
+            xml_escape(self._cw.build_url(rql=rql, vid=self.__regid__, year=prev2.year,
+                                          month=prev2.month)),
+            xml_escape(self._cw.build_url(rql=rql, vid=self.__regid__, year=prev1.year,
+                                          month=prev1.month)),
+            xml_escape(self._cw.build_url(rql=rql, vid=self.__regid__, year=next1.year,
+                                          month=next1.month)),
+            xml_escape(self._cw.build_url(rql=rql, vid=self.__regid__, year=next2.year,
+                                          month=next2.month)))
 
 
     # Calendar building methods ##############################################
@@ -69,7 +69,7 @@
         """method responsible for building *one* HTML calendar"""
         # FIXME  iterates between [first_day-first_day.day_of_week ;
         #                          last_day+6-last_day.day_of_week]
-        umonth = self.format_date(first_day, '%B %Y') # localized month name
+        umonth = self._cw.format_date(first_day, '%B %Y') # localized month name
         rows = []
         current_row = [NO_CELL] * first_day.weekday()
         for daynum in xrange(0, days_in_month(first_day)):
@@ -86,13 +86,13 @@
                 rows.append(u'<tr>%s%s</tr>' % (WEEKNUM_CELL % day.isocalendar()[1], ''.join(current_row)))
                 current_row = []
         current_row.extend([NO_CELL] * (6-day.weekday()))
-        rql = self.rset.printable_rql()
+        rql = self.cw_rset.printable_rql()
         if day.weekday() != 6:
             rows.append(u'<tr>%s%s</tr>' % (WEEKNUM_CELL % day.isocalendar()[1], ''.join(current_row)))
-        url = self.build_url(rql=rql, vid='calendarmonth',
-                             year=first_day.year, month=first_day.month)
+        url = self._cw.build_url(rql=rql, vid='calendarmonth',
+                                 year=first_day.year, month=first_day.month)
         monthlink = u'<a href="%s">%s</a>' % (xml_escape(url), umonth)
-        return CALENDAR(self.req) % (monthlink, '\n'.join(rows))
+        return CALENDAR(self._cw) % (monthlink, '\n'.join(rows))
 
     def _mk_schedule(self, begin, end, itemvid='calendaritem'):
         """private method that gathers information from resultset
@@ -106,12 +106,12 @@
                   day2 : { hour : [views] } ... }
         """
         # put this here since all sub views are calling this method
-        self.req.add_css('cubicweb.calendar.css')
+        self._cw.add_css('cubicweb.calendar.css')
         schedule = {}
-        for row in xrange(len(self.rset.rows)):
-            entity = self.entity(row)
+        for row in xrange(len(self.cw_rset.rows)):
+            entity = self.cw_rset.get_entity(row,0)
             infos = u'<div class="event">'
-            infos += self.view(itemvid, self.rset, row=row)
+            infos += self._cw.view(itemvid, self.cw_rset, row=row)
             infos += u'</div>'
             for date_ in entity.matching_dates(begin, end):
                 day = date(date_.year, date_.month, date_.day)
@@ -162,13 +162,13 @@
 
 
 class YearCalendarView(_CalendarView):
-    id = 'calendaryear'
+    __regid__ = 'calendaryear'
     title = _('calendar (year)')
 
     def call(self, year=None, month=None):
         """this view renders a 3x3 calendars' table"""
-        year = year or int(self.req.form.get('year', date.today().year))
-        month = month or int(self.req.form.get('month', date.today().month))
+        year = year or int(self._cw.form.get('year', date.today().year))
+        month = month or int(self._cw.form.get('month', date.today().month))
         center_date = date(year, month, 1)
         begin, end = self.get_date_range(day=center_date)
         schedule = self._mk_schedule(begin, end)
@@ -181,12 +181,12 @@
     """this view renders three semesters as three rows of six columns,
     one column per month
     """
-    id = 'calendarsemester'
+    __regid__ = 'calendarsemester'
     title = _('calendar (semester)')
 
     def call(self, year=None, month=None):
-        year = year or int(self.req.form.get('year', date.today().year))
-        month = month or int(self.req.form.get('month', date.today().month))
+        year = year or int(self._cw.form.get('year', date.today().year))
+        month = month or int(self._cw.form.get('month', date.today().month))
         begin = previous_month(date(year, month, 1), 2)
         end = next_month(date(year, month, 1), 3)
         schedule = self._mk_schedule(begin, end)
@@ -198,15 +198,15 @@
 
     def build_calendars(self, schedule, begin, end):
         self.w(u'<tr>')
-        rql = self.rset.printable_rql()
+        rql = self.cw_rset.printable_rql()
         for cur_month in date_range(begin, end, incmonth=1):
-            umonth = u'%s&#160;%s' % (self.format_date(cur_month, '%B'), cur_month.year)
-            url = self.build_url(rql=rql, vid=self.id,
-                                 year=cur_month.year, month=cur_month.month)
+            umonth = u'%s&#160;%s' % (self._cw.format_date(cur_month, '%B'), cur_month.year)
+            url = self._cw.build_url(rql=rql, vid=self.__regid__,
+                                     year=cur_month.year, month=cur_month.month)
             self.w(u'<th colspan="2"><a href="%s">%s</a></th>' % (xml_escape(url),
                                                                   umonth))
         self.w(u'</tr>')
-        _ = self.req._
+        _ = self._cw._
         for day_num in xrange(31):
             self.w(u'<tr>')
             for cur_month in date_range(begin, end, incmonth=1):
@@ -229,12 +229,12 @@
 
 class MonthCalendarView(_CalendarView):
     """this view renders a 3x1 calendars' table"""
-    id = 'calendarmonth'
+    __regid__ = 'calendarmonth'
     title = _('calendar (month)')
 
     def call(self, year=None, month=None):
-        year = year or int(self.req.form.get('year', date.today().year))
-        month = month or int(self.req.form.get('month', date.today().month))
+        year = year or int(self._cw.form.get('year', date.today().year))
+        month = month or int(self._cw.form.get('month', date.today().month))
         center_date = date(year, month, 1)
         begin, end = self.get_date_range(day=center_date, shift=1)
         schedule = self._mk_schedule(begin, end)
@@ -246,12 +246,12 @@
 
 class WeekCalendarView(_CalendarView):
     """this view renders a calendar for week events"""
-    id = 'calendarweek'
+    __regid__ = 'calendarweek'
     title = _('calendar (week)')
 
     def call(self, year=None, week=None):
-        year = year or int(self.req.form.get('year', date.today().year))
-        week = week or int(self.req.form.get('week', date.today().isocalendar()[1]))
+        year = year or int(self._cw.form.get('year', date.today().year))
+        week = week or int(self._cw.form.get('week', date.today().isocalendar()[1]))
         day0 = date(year, 1, 1)
         first_day_of_week = day0 - day0.weekday()*ONEDAY + ONEWEEK
         begin, end = first_day_of_week- ONEWEEK, first_day_of_week + 2*ONEWEEK
@@ -266,12 +266,12 @@
         self.w(self.nav_header(first_day_of_week))
 
     def build_calendar(self, schedule, weeks):
-        rql = self.rset.printable_rql()
-        _ = self.req._
+        rql = self.cw_rset.printable_rql()
+        _ = self._cw._
         for monday, sunday in weeks:
-            umonth = self.format_date(monday, '%B %Y')
-            url = self.build_url(rql=rql, vid='calendarmonth',
-                                 year=monday.year, month=monday.month)
+            umonth = self._cw.format_date(monday, '%B %Y')
+            url = self._cw.build_url(rql=rql, vid='calendarmonth',
+                                     year=monday.year, month=monday.month)
             monthlink = '<a href="%s">%s</a>' % (xml_escape(url), umonth)
             self.w(u'<tr><th colspan="3">%s %s (%s)</th></tr>' \
                   % (_('week'), monday.isocalendar()[1], monthlink))
@@ -293,25 +293,25 @@
         prev2 = date - ONEWEEK * bigshift
         next1 = date + ONEWEEK * smallshift
         next2 = date + ONEWEEK * bigshift
-        rql = self.rset.printable_rql()
+        rql = self.cw_rset.printable_rql()
         return self.NAV_HEADER % (
-            xml_escape(self.build_url(rql=rql, vid=self.id, year=prev2.year, week=prev2.isocalendar()[1])),
-            xml_escape(self.build_url(rql=rql, vid=self.id, year=prev1.year, week=prev1.isocalendar()[1])),
-            xml_escape(self.build_url(rql=rql, vid=self.id, year=next1.year, week=next1.isocalendar()[1])),
-            xml_escape(self.build_url(rql=rql, vid=self.id, year=next2.year, week=next2.isocalendar()[1])))
+            xml_escape(self._cw.build_url(rql=rql, vid=self.__regid__, year=prev2.year, week=prev2.isocalendar()[1])),
+            xml_escape(self._cw.build_url(rql=rql, vid=self.__regid__, year=prev1.year, week=prev1.isocalendar()[1])),
+            xml_escape(self._cw.build_url(rql=rql, vid=self.__regid__, year=next1.year, week=next1.isocalendar()[1])),
+            xml_escape(self._cw.build_url(rql=rql, vid=self.__regid__, year=next2.year, week=next2.isocalendar()[1])))
 
 
 
 class AMPMYearCalendarView(YearCalendarView):
-    id = 'ampmcalendaryear'
+    __regid__ = 'ampmcalendaryear'
     title = _('am/pm calendar (year)')
 
     def build_calendar(self, schedule, first_day):
         """method responsible for building *one* HTML calendar"""
-        umonth = self.format_date(first_day, '%B %Y') # localized month name
+        umonth = self._cw.format_date(first_day, '%B %Y') # localized month name
         rows = [] # each row is: (am,pm), (am,pm) ... week_title
         current_row = [(NO_CELL, NO_CELL, NO_CELL)] * first_day.weekday()
-        rql = self.rset.printable_rql()
+        rql = self.cw_rset.printable_rql()
         for daynum in xrange(0, days_in_month(first_day)):
             # build cells day
             day = first_day + timedelta(daynum)
@@ -324,7 +324,7 @@
                                     AMPM_EMPTY % ("pmCell", "pm")))
             # store & reset current row on Sundays
             if day.weekday() == 6:
-                url = self.build_url(rql=rql, vid='ampmcalendarweek',
+                url = self._cw.build_url(rql=rql, vid='ampmcalendarweek',
                                      year=day.year, week=day.isocalendar()[1])
                 weeklink = '<a href="%s">%s</a>' % (xml_escape(url),
                                                     day.isocalendar()[1])
@@ -332,7 +332,7 @@
                 rows.append(current_row)
                 current_row = []
         current_row.extend([(NO_CELL, NO_CELL, NO_CELL)] * (6-day.weekday()))
-        url = self.build_url(rql=rql, vid='ampmcalendarweek',
+        url = self._cw.build_url(rql=rql, vid='ampmcalendarweek',
                              year=day.year, week=day.isocalendar()[1])
         weeklink = '<a href="%s">%s</a>' % (xml_escape(url), day.isocalendar()[1])
         current_row.append(WEEKNUM_CELL % weeklink)
@@ -348,29 +348,29 @@
             formatted_rows.append('<tr class="amRow"><td>&#160;</td>%s</tr>'% '\n'.join(am_row))
             formatted_rows.append('<tr class="pmRow"><td>&#160;</td>%s</tr>'% '\n'.join(pm_row))
         # tigh everything together
-        url = self.build_url(rql=rql, vid='ampmcalendarmonth',
+        url = self._cw.build_url(rql=rql, vid='ampmcalendarmonth',
                              year=first_day.year, month=first_day.month)
         monthlink = '<a href="%s">%s</a>' % (xml_escape(url), umonth)
-        return CALENDAR(self.req) % (monthlink, '\n'.join(formatted_rows))
+        return CALENDAR(self._cw) % (monthlink, '\n'.join(formatted_rows))
 
 
 
 class AMPMSemesterCalendarView(SemesterCalendarView):
     """this view renders a 3x1 calendars' table"""
-    id = 'ampmcalendarsemester'
+    __regid__ = 'ampmcalendarsemester'
     title = _('am/pm calendar (semester)')
 
     def build_calendars(self, schedule, begin, end):
         self.w(u'<tr>')
-        rql = self.rset.printable_rql()
+        rql = self.cw_rset.printable_rql()
         for cur_month in date_range(begin, end, incmonth=1):
-            umonth = u'%s&#160;%s' % (self.format_date(cur_month, '%B'), cur_month.year)
-            url = self.build_url(rql=rql, vid=self.id,
+            umonth = u'%s&#160;%s' % (self._cw.format_date(cur_month, '%B'), cur_month.year)
+            url = self._cw.build_url(rql=rql, vid=self.__regid__,
                                  year=cur_month.year, month=cur_month.month)
             self.w(u'<th colspan="3"><a href="%s">%s</a></th>' % (xml_escape(url),
                                                                   umonth))
         self.w(u'</tr>')
-        _ = self.req._
+        _ = self._cw._
         for day_num in xrange(31):
             self.w(u'<tr>')
             for cur_month in date_range(begin, end, incmonth=1):
@@ -394,15 +394,15 @@
 
 class AMPMMonthCalendarView(MonthCalendarView):
     """this view renders a 3x1 calendars' table"""
-    id = 'ampmcalendarmonth'
+    __regid__ = 'ampmcalendarmonth'
     title = _('am/pm calendar (month)')
 
     def build_calendar(self, schedule, first_day):
         """method responsible for building *one* HTML calendar"""
-        umonth = self.format_date(first_day, '%B %Y') # localized month name
+        umonth = self._cw.format_date(first_day, '%B %Y') # localized month name
         rows = [] # each row is: (am,pm), (am,pm) ... week_title
         current_row = [(NO_CELL, NO_CELL, NO_CELL)] * first_day.weekday()
-        rql = self.rset.printable_rql()
+        rql = self.cw_rset.printable_rql()
         for daynum in xrange(0, days_in_month(first_day)):
             # build cells day
             day = first_day + timedelta(daynum)
@@ -415,16 +415,16 @@
                                     AMPM_EMPTY % ("pmCell", "pm")))
             # store & reset current row on Sundays
             if day.weekday() == 6:
-                url = self.build_url(rql=rql, vid='ampmcalendarweek',
-                                     year=day.year, week=day.isocalendar()[1])
+                url = self._cw.build_url(rql=rql, vid='ampmcalendarweek',
+                                         year=day.year, week=day.isocalendar()[1])
                 weeklink = '<a href="%s">%s</a>' % (xml_escape(url),
                                                     day.isocalendar()[1])
                 current_row.append(WEEKNUM_CELL % weeklink)
                 rows.append(current_row)
                 current_row = []
         current_row.extend([(NO_CELL, NO_CELL, NO_CELL)] * (6-day.weekday()))
-        url = self.build_url(rql=rql, vid='ampmcalendarweek',
-                             year=day.year, week=day.isocalendar()[1])
+        url = self._cw.build_url(rql=rql, vid='ampmcalendarweek',
+                                 year=day.year, week=day.isocalendar()[1])
         weeklink = '<a href="%s">%s</a>' % (xml_escape(url),
                                             day.isocalendar()[1])
         current_row.append(WEEKNUM_CELL % weeklink)
@@ -440,27 +440,27 @@
             formatted_rows.append('<tr class="amRow"><td>&#160;</td>%s</tr>'% '\n'.join(am_row))
             formatted_rows.append('<tr class="pmRow"><td>&#160;</td>%s</tr>'% '\n'.join(pm_row))
         # tigh everything together
-        url = self.build_url(rql=rql, vid='ampmcalendarmonth',
-                             year=first_day.year, month=first_day.month)
+        url = self._cw.build_url(rql=rql, vid='ampmcalendarmonth',
+                                 year=first_day.year, month=first_day.month)
         monthlink = '<a href="%s">%s</a>' % (xml_escape(url),
                                              umonth)
-        return CALENDAR(self.req) % (monthlink, '\n'.join(formatted_rows))
+        return CALENDAR(self._cw) % (monthlink, '\n'.join(formatted_rows))
 
 
 
 class AMPMWeekCalendarView(WeekCalendarView):
     """this view renders a 3x1 calendars' table"""
-    id = 'ampmcalendarweek'
+    __regid__ = 'ampmcalendarweek'
     title = _('am/pm calendar (week)')
 
     def build_calendar(self, schedule, weeks):
-        rql = self.rset.printable_rql()
+        rql = self.cw_rset.printable_rql()
         w = self.w
-        _ = self.req._
+        _ = self._cw._
         for monday, sunday in weeks:
-            umonth = self.format_date(monday, '%B %Y')
-            url = self.build_url(rql=rql, vid='ampmcalendarmonth',
-                                 year=monday.year, month=monday.month)
+            umonth = self._cw.format_date(monday, '%B %Y')
+            url = self._cw.build_url(rql=rql, vid='ampmcalendarmonth',
+                                     year=monday.year, month=monday.month)
             monthlink = '<a href="%s">%s</a>' % (xml_escape(url), umonth)
             w(u'<tr>%s</tr>' % (
                 WEEK_TITLE % (_('week'), monday.isocalendar()[1], monthlink)))
@@ -474,7 +474,7 @@
                     hours.sort()
                     w(AMPM_DAYWEEK % (
                         len(hours), _(WEEKDAYS[day.weekday()]),
-                        self.format_date(day)))
+                        self._cw.format_date(day)))
                     w(AMPM_WEEK_CELL % (
                         hours[0].hour, hours[0].minute,
                         '\n'.join(events[hours[0]])))
@@ -486,7 +486,7 @@
                 else:
                     w(AMPM_DAYWEEK_EMPTY % (
                         _(WEEKDAYS[day.weekday()]),
-                        self.format_date(day)))
+                        self._cw.format_date(day)))
                     w(WEEK_EMPTY_CELL)
                     w(u'</tr>')
 
--- a/web/views/owl.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/owl.py	Wed Sep 30 18:57:42 2009 +0200
@@ -58,15 +58,15 @@
 
 class OWLView(StartupView):
     """This view export in owl format schema database. It is the TBOX"""
-    id = 'owl'
+    __regid__ = 'owl'
     title = _('owl')
     templatable = False
     content_type = 'application/xml' # 'text/xml'
 
     def call(self, writeprefix=True):
-        skipmeta = int(self.req.form.get('skipmeta', True))
+        skipmeta = int(self._cw.form.get('skipmeta', True))
         if writeprefix:
-            self.w(OWL_OPENING_ROOT % {'appid': self.schema.name})
+            self.w(OWL_OPENING_ROOT % {'appid': self._cw.schema.name})
         self.visit_schema(skiptypes=skipmeta and schema.SKIP_TYPES or ())
         if writeprefix:
             self.w(OWL_CLOSING_ROOT)
@@ -74,12 +74,12 @@
     def should_display_rschema(self, rschema):
         return not rschema in self.skiptypes and (
             rschema.has_local_role('read') or
-            rschema.has_perm(self.req, 'read'))
+            rschema.has_perm(self._cw, 'read'))
 
     def visit_schema(self, skiptypes):
         """get a layout for a whole schema"""
         self.skiptypes = skiptypes
-        entities = sorted(eschema for eschema in self.schema.entities()
+        entities = sorted(eschema for eschema in self._cw.schema.entities()
                           if not eschema.is_final() or eschema in skiptypes)
         self.w(u'<!-- classes definition -->')
         for eschema in entities:
@@ -145,36 +145,36 @@
 
 class OWLABOXView(EntityView):
     '''This view represents a part of the ABOX for a given entity.'''
-    id = 'owlabox'
+    __regid__ = 'owlabox'
     title = _('owlabox')
     templatable = False
     content_type = 'application/xml' # 'text/xml'
 
     def call(self):
-        self.w(OWL_OPENING_ROOT % {'appid': self.schema.name})
-        for i in xrange(self.rset.rowcount):
+        self.w(OWL_OPENING_ROOT % {'appid': self._cw.schema.name})
+        for i in xrange(self.cw_rset.rowcount):
             self.cell_call(i, 0)
         self.w(OWL_CLOSING_ROOT)
 
     def cell_call(self, row, col):
-        self.wview('owlaboxitem', self.rset, row=row, col=col)
+        self.wview('owlaboxitem', self.cw_rset, row=row, col=col)
 
 
 class OWLABOXItemView(EntityView):
     '''This view represents a part of the ABOX for a given entity.'''
-    id = 'owlaboxitem'
+    __regid__ = 'owlaboxitem'
     templatable = False
     content_type = 'application/xml' # 'text/xml'
 
     def cell_call(self, row, col):
-        entity = self.complete_entity(row, col)
+        entity = self.cw_rset.complete_entity(row, col)
         eschema = entity.e_schema
         self.w(u'<%s rdf:ID="%s">' % (eschema, entity.eid))
         self.w(u'<!--attributes-->')
         for rschema, aschema in eschema.attribute_definitions():
             if rschema.meta:
                 continue
-            if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
+            if not (rschema.has_local_role('read') or rschema.has_perm(self._cw, 'read')):
                 continue
             aname = rschema.type
             if aname == 'eid':
@@ -189,23 +189,23 @@
         for rschema, targetschemas, role in eschema.relation_definitions():
             if rschema.meta:
                 continue
-            if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
+            if not (rschema.has_local_role('read') or rschema.has_perm(self._cw, 'read')):
                 continue
             if role == 'object':
                 attr = 'reverse_%s' % rschema.type
             else:
                 attr = rschema.type
             for x in getattr(entity, attr):
-                self.w(u'<%s>%s %s</%s>' % (attr, x.id, x.eid, attr))
+                self.w(u'<%s>%s %s</%s>' % (attr, x.__regid__, x.eid, attr))
         self.w(u'</%s>'% eschema)
 
 
 class DownloadOWLSchemaAction(Action):
-    id = 'download_as_owl'
+    __regid__ = 'download_as_owl'
     __select__ = none_rset() & match_view('schema')
 
     category = 'mainactions'
     title = _('download schema as owl')
 
     def url(self):
-        return self.build_url('view', vid='owl')
+        return self._cw.build_url('view', vid='owl')
--- a/web/views/plots.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/plots.py	Wed Sep 30 18:57:42 2009 +0200
@@ -121,24 +121,24 @@
 
 
 class PlotView(baseviews.AnyRsetView):
-    id = 'plot'
+    __regid__ = 'plot'
     title = _('generic plot')
     __select__ = at_least_two_columns() & all_columns_are_numbers()
     timemode = False
 
     def call(self, width=500, height=400):
         # prepare data
-        rqlst = self.rset.syntax_tree()
+        rqlst = self.cw_rset.syntax_tree()
         # XXX try to make it work with unions
         varnames = [var.name for var in rqlst.children[0].get_selected_variables()][1:]
-        abscissa = [row[0] for row in self.rset]
+        abscissa = [row[0] for row in self.cw_rset]
         plots = []
-        nbcols = len(self.rset.rows[0])
+        nbcols = len(self.cw_rset.rows[0])
         for col in xrange(1, nbcols):
-            data = [row[col] for row in self.rset]
+            data = [row[col] for row in self.cw_rset]
             plots.append(filterout_nulls(abscissa, data))
         plotwidget = FlotPlotWidget(varnames, plots, timemode=self.timemode)
-        plotwidget.render(self.req, width, height, w=self.w)
+        plotwidget.render(self._cw, width, height, w=self.w)
 
 
 class TimeSeriePlotView(PlotView):
@@ -170,26 +170,26 @@
             self.w(u'<img src="%s" />' % xml_escape(piechart.url))
 
     class PieChartView(baseviews.AnyRsetView):
-        id = 'piechart'
+        __regid__ = 'piechart'
         pieclass = Pie
 
         __select__ = at_least_two_columns() & second_column_is_number()
 
         def _guess_vid(self, row):
-            etype = self.rset.description[row][0]
-            if self.schema.eschema(etype).is_final():
+            etype = self.cw_rset.description[row][0]
+            if self._cw.schema.eschema(etype).is_final():
                 return 'final'
             return 'textincontext'
 
         def call(self, title=None, width=None, height=None):
             labels = []
             values = []
-            for rowidx, (_, value) in enumerate(self.rset):
+            for rowidx, (_, value) in enumerate(self.cw_rset):
                 if value is not None:
                     vid = self._guess_vid(rowidx)
-                    label = '%s: %s' % (self.view(vid, self.rset, row=rowidx, col=0),
+                    label = '%s: %s' % (self.view(vid, self.cw_rset, row=rowidx, col=0),
                                         value)
-                    labels.append(label.encode(self.req.encoding))
+                    labels.append(label.encode(self._cw.encoding))
                     values.append(value)
             pie = PieChartWidget(labels, values, pieclass=self.pieclass,
                                  title=title)
@@ -199,5 +199,5 @@
 
 
     class PieChart3DView(PieChartView):
-        id = 'piechart3D'
+        __regid__ = 'piechart3D'
         pieclass = Pie3D
--- a/web/views/primary.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/primary.py	Wed Sep 30 18:57:42 2009 +0200
@@ -20,7 +20,7 @@
 
 class PrimaryView(EntityView):
     """the full view of an non final entity"""
-    id = 'primary'
+    __regid__ = 'primary'
     title = _('primary')
     show_attr_label = True
     show_rel_label = True
@@ -38,9 +38,10 @@
         return []
 
     def cell_call(self, row, col):
-        self.row = row
-        self.maxrelated = self.req.property_value('navigation.related-limit')
-        entity = self.complete_entity(row, col)
+        self.cw_row = row
+        self.cw_col = col
+        self.maxrelated = self._cw.property_value('navigation.related-limit')
+        entity = self.cw_rset.complete_entity(row, col)
         self.render_entity(entity)
 
     def render_entity(self, entity):
@@ -72,10 +73,10 @@
 
     def content_navigation_components(self, context):
         self.w(u'<div class="%s">' % context)
-        for comp in self.vreg['contentnavigation'].possible_vobjects(
-            self.req, rset=self.rset, row=self.row, view=self, context=context):
+        for comp in self._cw.vreg['contentnavigation'].poss_visible_objects(
+            self._cw, rset=self.cw_rset, row=self.cw_row, view=self, context=context):
             try:
-                comp.render(w=self.w, row=self.row, view=self)
+                comp.render(w=self.w, row=self.cw_row, view=self)
             except NotImplementedError:
                 warn('component %s doesnt implement cell_call, please update'
                      % comp.__class__, DeprecationWarning)
@@ -135,7 +136,7 @@
                 self.w(u'</div>')
             else:
                 try:
-                    box.render(w=self.w, row=self.row)
+                    box.render(w=self.w, row=self.cw_row)
                 except NotImplementedError:
                     # much probably a context insensitive box, which only implements
                     # .call() and not cell_call()
@@ -147,11 +148,11 @@
             rset = self._relation_rset(entity, rschema, role, dispctrl)
             if not rset:
                 continue
-            label = display_name(self.req, rschema.type, role)
+            label = display_name(self._cw, rschema.type, role)
             vid = dispctrl.get('vid', 'sidebox')
             sideboxes.append( (label, rset, vid) )
-        sideboxes += self.vreg['boxes'].possible_vobjects(
-            self.req, rset=self.rset, row=self.row, view=self,
+        sideboxes += self._cw.vreg['boxes'].poss_visible_objects(
+            self._cw, rset=self.cw_rset, row=self.cw_row, view=self,
             context='incontext')
         return sideboxes
 
@@ -189,7 +190,7 @@
     def _render_relation(self, rset, dispctrl, defaultvid, showlabel):
         self.w(u'<div class="section">')
         if showlabel:
-            self.w(u'<h4>%s</h4>' % self.req._(dispctrl['label']))
+            self.w(u'<h4>%s</h4>' % self._cw._(dispctrl['label']))
         self.wview(dispctrl.get('vid', defaultvid), rset)
         self.w(u'</div>')
 
@@ -198,34 +199,34 @@
             show_label = self.show_attr_label
         else:
             show_label = self.show_rel_label
-        label = display_name(self.req, rschema.type, role)
+        label = display_name(self._cw, rschema.type, role)
         self.field(label, value, show_label=show_label, tr=False)
 
 
 class RelatedView(EntityView):
-    id = 'autolimited'
+    __regid__ = 'autolimited'
     def call(self, title=None, **kwargs):
         # if not too many entities, show them all in a list
-        maxrelated = self.req.property_value('navigation.related-limit')
+        maxrelated = self._cw.property_value('navigation.related-limit')
         if title:
             self.w(u'<div class="title"><span>%s</span></div>' % title)
-        if self.rset.rowcount <= maxrelated:
-            if self.rset.rowcount == 1:
-                self.wview('incontext', self.rset, row=0)
-            elif 1 < self.rset.rowcount <= 5:
-                self.wview('csv', self.rset)
+        if self.cw_rset.rowcount <= maxrelated:
+            if self.cw_rset.rowcount == 1:
+                self.wview('incontext', self.cw_rset, row=0)
+            elif 1 < self.cw_rset.rowcount <= 5:
+                self.wview('csv', self.cw_rset)
             else:
                 self.w(u'<div>')
-                self.wview('simplelist', self.rset)
+                self.wview('simplelist', self.cw_rset)
                 self.w(u'</div>')
         # else show links to display related entities
         else:
-            rql = self.rset.printable_rql()
-            self.rset.limit(maxrelated)
+            rql = self.cw_rset.printable_rql()
+            self.cw_rset.limit(maxrelated)
             self.w(u'<div>')
-            self.wview('simplelist', self.rset)
-            self.w(u'[<a href="%s">%s</a>]' % (self.build_url(rql=rql),
-                                               self.req._('see them all')))
+            self.wview('simplelist', self.cw_rset)
+            self.w(u'[<a href="%s">%s</a>]' % (self._cw.build_url(rql=rql),
+                                               self._cw._('see them all')))
             self.w(u'</div>')
 
 ## default primary ui configuration ###########################################
--- a/web/views/pyviews.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/pyviews.py	Wed Sep 30 18:57:42 2009 +0200
@@ -11,12 +11,12 @@
 from cubicweb.selectors import match_kwargs
 
 class PyValTableView(View):
-    id = 'pyvaltable'
+    __regid__ = 'pyvaltable'
     __select__ = match_kwargs('pyvalue')
 
     def call(self, pyvalue, headers=None):
         if headers is None:
-            headers = self.req.form.get('headers')
+            headers = self._cw.form.get('headers')
         self.w(u'<table class="listing">\n')
         if headers:
             self.w(u'<tr>')
@@ -32,7 +32,7 @@
 
 
 class PyValListView(View):
-    id = 'pyvallist'
+    __regid__ = 'pyvallist'
     __select__ = match_kwargs('pyvalue')
 
     def call(self, pyvalue):
--- a/web/views/schema.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/schema.py	Wed Sep 30 18:57:42 2009 +0200
@@ -37,53 +37,53 @@
 # global schema view ###########################################################
 
 class SchemaView(tabs.TabsMixin, StartupView):
-    id = 'schema'
+    __regid__ = 'schema'
     title = _('instance schema')
     tabs = [_('schema-text'), _('schema-image')]
     default_tab = 'schema-text'
 
     def call(self):
         """display schema information"""
-        self.req.add_js('cubicweb.ajax.js')
-        self.req.add_css(('cubicweb.schema.css','cubicweb.acl.css'))
+        self._cw.add_js('cubicweb.ajax.js')
+        self._cw.add_css(('cubicweb.schema.css','cubicweb.acl.css'))
         self.w(u'<h1>%s</h1>' % _('Schema of the data model'))
         self.render_tabs(self.tabs, self.default_tab)
 
 
 class SchemaTabImageView(StartupView):
-    id = 'schema-image'
+    __regid__ = 'schema-image'
 
     def call(self):
         self.w(_(u'<div>This schema of the data model <em>excludes</em> the '
                  u'meta-data, but you can also display a <a href="%s">complete '
                  u'schema with meta-data</a>.</div>')
-               % xml_escape(self.build_url('view', vid='schemagraph', skipmeta=0)))
+               % xml_escape(self._cw.build_url('view', vid='schemagraph', skipmeta=0)))
         self.w(u'<img src="%s" alt="%s"/>\n' % (
-            xml_escape(self.req.build_url('view', vid='schemagraph', skipmeta=1)),
-            self.req._("graphical representation of the instance'schema")))
+            xml_escape(self._cw.build_url('view', vid='schemagraph', skipmeta=1)),
+            self._cw._("graphical representation of the instance'schema")))
 
 
 class SchemaTabTextView(StartupView):
-    id = 'schema-text'
+    __regid__ = 'schema-text'
 
     def call(self):
-        rset = self.req.execute('Any X ORDERBY N WHERE X is CWEType, X name N, '
+        rset = self._cw.execute('Any X ORDERBY N WHERE X is CWEType, X name N, '
                                 'X final FALSE')
         self.wview('table', rset, displayfilter=True)
 
 
 class ManagerSchemaPermissionsView(StartupView, management.SecurityViewMixIn):
-    id = 'schema-security'
+    __regid__ = 'schema-security'
     __select__ = StartupView.__select__ & match_user_groups('managers')
 
     def call(self, display_relations=True):
-        self.req.add_css('cubicweb.acl.css')
-        skiptypes = skip_types(self.req)
+        self._cw.add_css('cubicweb.acl.css')
+        skiptypes = skip_types(self._cw)
         formparams = {}
-        formparams['sec'] = self.id
+        formparams['sec'] = self.__regid__
         if not skiptypes:
             formparams['skipmeta'] = u'0'
-        schema = self.schema
+        schema = self._cw.schema
         # compute entities
         entities = sorted(eschema for eschema in schema.entities()
                           if not (eschema.is_final() or eschema in skiptypes))
@@ -96,20 +96,20 @@
         else:
             relations = []
         # index
-        _ = self.req._
+        _ = self._cw._
         self.w(u'<div id="schema_security"><a id="index" href="index"/>')
         self.w(u'<h2 class="schema">%s</h2>' % _('index').capitalize())
         self.w(u'<h4>%s</h4>' %   _('Entities').capitalize())
         ents = []
         for eschema in sorted(entities):
-            url = xml_escape(self.build_url('schema', **formparams))
+            url = xml_escape(self._cw.build_url('schema', **formparams))
             ents.append(u'<a class="grey" href="%s#%s">%s</a> (%s)' % (
                 url,  eschema.type, eschema.type, _(eschema.type)))
         self.w(u', '.join(ents))
         self.w(u'<h4>%s</h4>' % (_('relations').capitalize()))
         rels = []
         for rschema in sorted(relations):
-            url = xml_escape(self.build_url('schema', **formparams))
+            url = xml_escape(self._cw.build_url('schema', **formparams))
             rels.append(u'<a class="grey" href="%s#%s">%s</a> (%s), ' %  (
                 url , rschema.type, rschema.type, _(rschema.type)))
         self.w(u', '.join(ents))
@@ -121,18 +121,18 @@
         self.w(u'</div>')
 
     def display_entities(self, entities, formparams):
-        _ = self.req._
+        _ = self._cw._
         self.w(u'<a id="entities" href="entities"/>')
         self.w(u'<h2 class="schema">%s</h2>' % _('permissions for entities').capitalize())
         for eschema in entities:
             self.w(u'<a id="%s" href="%s"/>' %  (eschema.type, eschema.type))
             self.w(u'<h3 class="schema">%s (%s) ' % (eschema.type, _(eschema.type)))
-            url = xml_escape(self.build_url('schema', **formparams) + '#index')
+            url = xml_escape(self._cw.build_url('schema', **formparams) + '#index')
             self.w(u'<a href="%s"><img src="%s" alt="%s"/></a>' % (
-                url,  self.req.external_resource('UP_ICON'), _('up')))
+                url,  self._cw.external_resource('UP_ICON'), _('up')))
             self.w(u'</h3>')
             self.w(u'<div style="margin: 0px 1.5em">')
-            self.schema_definition(eschema, link=False)
+            self._cw.schema_definition(eschema, link=False)
             # display entity attributes only if they have some permissions modified
             modified_attrs = []
             for attr, etype in  eschema.attribute_definitions():
@@ -144,19 +144,19 @@
                 self.w(u'<div style="margin: 0px 6em">')
                 for attr in  modified_attrs:
                     self.w(u'<h4 class="schema">%s (%s)</h4> ' % (attr.type, _(attr.type)))
-                    self.schema_definition(attr, link=False)
+                    self._cw.schema_definition(attr, link=False)
             self.w(u'</div>')
 
     def display_relations(self, relations, formparams):
-        _ = self.req._
+        _ = self._cw._
         self.w(u'<a id="relations" href="relations"/>')
         self.w(u'<h2 class="schema">%s </h2>' % _('permissions for relations').capitalize())
         for rschema in relations:
             self.w(u'<a id="%s" href="%s"/>' %  (rschema.type, rschema.type))
             self.w(u'<h3 class="schema">%s (%s) ' % (rschema.type, _(rschema.type)))
-            url = xml_escape(self.build_url('schema', **formparams) + '#index')
+            url = xml_escape(self._cw.build_url('schema', **formparams) + '#index')
             self.w(u'<a href="%s"><img src="%s" alt="%s"/></a>' % (
-                url,  self.req.external_resource('UP_ICON'), _('up')))
+                url,  self._cw.external_resource('UP_ICON'), _('up')))
             self.w(u'</h3>')
             self.w(u'<div style="margin: 0px 1.5em">')
             subjects = [str(subj) for subj in rschema.subjects()]
@@ -168,17 +168,17 @@
                 _('object_plural:'),
                 ', '.join(str(obj) for obj in rschema.objects()),
                 ', '.join(_(str(obj)) for obj in rschema.objects())))
-            self.schema_definition(rschema, link=False)
+            self._cw.schema_definition(rschema, link=False)
             self.w(u'</div>')
 
 
 class SchemaUreportsView(StartupView):
-    id = 'schema-block'
+    __regid__ = 'schema-block'
 
     def call(self):
-        viewer = SchemaViewer(self.req)
-        layout = viewer.visit_schema(self.schema, display_relations=True,
-                                     skiptypes=skip_types(self.req))
+        viewer = SchemaViewer(self._cw)
+        layout = viewer.visit_schema(self._cw.schema, display_relations=True,
+                                     skiptypes=skip_types(self._cw))
         self.w(uilib.ureport_as_html(layout))
 
 
@@ -200,7 +200,7 @@
     __select__ = implements('CWEType')
 
     def cell_call(self, row, col, **kwargs):
-        entity = self.rset.get_entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         final = entity.final
         if final:
             self.w(u'<em class="finalentity">')
@@ -224,13 +224,13 @@
 
 
 class CWETypeSTextView(EntityView):
-    id = 'cwetype-schema-text'
+    __regid__ = 'cwetype-schema-text'
     __select__ = EntityView.__select__ & implements('CWEType')
 
     def cell_call(self, row, col):
-        entity = self.rset.get_entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         self.w(u'<h2>%s</h2>' % _('Attributes'))
-        rset = self.req.execute('Any N,F,D,I,J,DE,A '
+        rset = self._cw.execute('Any N,F,D,I,J,DE,A '
                                 'ORDERBY AA WHERE A is CWAttribute, '
                                 'A ordernum AA, A defaultval D, '
                                 'A description DE, '
@@ -241,7 +241,7 @@
                                 {'x': entity.eid})
         self.wview('editable-table', rset, 'null', displayfilter=True)
         self.w(u'<h2>%s</h2>' % _('Relations'))
-        rset = self.req.execute(
+        rset = self._cw.execute(
             'Any R,C,TT,K,D,A,RN,TTN ORDERBY RN '
             'WHERE A is CWRelation, A description D, A composite K?, '
             'A relation_type R, R name RN, A to_entity TT, TT name TTN, '
@@ -249,7 +249,7 @@
             {'x': entity.eid})
         self.wview('editable-table', rset, 'null', displayfilter=True,
                    displaycols=range(6), mainindex=5)
-        rset = self.req.execute(
+        rset = self._cw.execute(
             'Any R,C,TT,K,D,A,RN,TTN ORDERBY RN '
             'WHERE A is CWRelation, A description D, A composite K?, '
             'A relation_type R, R name RN, A from_entity TT, TT name TTN, '
@@ -260,55 +260,55 @@
 
 
 class CWETypeSImageView(EntityView):
-    id = 'cwetype-schema-image'
+    __regid__ = 'cwetype-schema-image'
     __select__ = EntityView.__select__ & implements('CWEType')
 
     def cell_call(self, row, col):
-        entity = self.rset.get_entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         url = entity.absolute_url(vid='schemagraph')
         self.w(u'<img src="%s" alt="%s"/>' % (
             xml_escape(url),
-            xml_escape(self.req._('graphical schema for %s') % entity.name)))
+            xml_escape(self._cw._('graphical schema for %s') % entity.name)))
 
 
 class CWETypeSPermView(EntityView):
-    id = 'cwetype-schema-permissions'
+    __regid__ = 'cwetype-schema-permissions'
     __select__ = EntityView.__select__ & implements('CWEType')
 
     def cell_call(self, row, col):
-        entity = self.rset.get_entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         self.w(u'<h2>%s</h2>' % _('Add permissions'))
-        rset = self.req.execute('Any P WHERE X add_permission P, '
+        rset = self._cw.execute('Any P WHERE X add_permission P, '
                                 'X eid %(x)s',
                                 {'x': entity.eid})
         self.wview('outofcontext', rset, 'null')
         self.w(u'<h2>%s</h2>' % _('Read permissions'))
-        rset = self.req.execute('Any P WHERE X read_permission P, '
+        rset = self._cw.execute('Any P WHERE X read_permission P, '
                                 'X eid %(x)s',
                                 {'x': entity.eid})
         self.wview('outofcontext', rset, 'null')
         self.w(u'<h2>%s</h2>' % _('Update permissions'))
-        rset = self.req.execute('Any P WHERE X update_permission P, '
+        rset = self._cw.execute('Any P WHERE X update_permission P, '
                                 'X eid %(x)s',
                                 {'x': entity.eid})
         self.wview('outofcontext', rset, 'null')
         self.w(u'<h2>%s</h2>' % _('Delete permissions'))
-        rset = self.req.execute('Any P WHERE X delete_permission P, '
+        rset = self._cw.execute('Any P WHERE X delete_permission P, '
                                 'X eid %(x)s',
                                 {'x': entity.eid})
         self.wview('outofcontext', rset, 'null')
 
 
 class CWETypeSWorkflowView(EntityView):
-    id = 'cwetype-workflow'
+    __regid__ = 'cwetype-workflow'
     __select__ = (EntityView.__select__ & implements('CWEType') &
                   has_related_entities('workflow_of', 'object'))
 
     def cell_call(self, row, col):
-        entity = self.rset.get_entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         if entity.default_workflow:
             wf = entity.default_workflow[0]
-            self.w(u'<h1>%s (%s)</h1>' % (wf.name, self.req._('default')))
+            self.w(u'<h1>%s (%s)</h1>' % (wf.name, self._cw._('default')))
             self.wf_image(wf)
         for altwf in entity.reverse_workflow_of:
             if altwf.eid == wf.eid:
@@ -319,7 +319,8 @@
     def wf_image(self, wf):
         self.w(u'<img src="%s" alt="%s"/>' % (
             xml_escape(wf.absolute_url(vid='wfgraph')),
-            xml_escape(self.req._('graphical representation of %s') % wf.name)))
+            xml_escape(self._cw._('graphical representation of %s') % wf.name)))
+
 
 # CWRType ######################################################################
 
@@ -330,12 +331,12 @@
 
     def render_entity_attributes(self, entity):
         super(CWRTypeSchemaView, self).render_entity_attributes(entity)
-        rschema = self.vreg.schema.rschema(entity.name)
-        viewer = SchemaViewer(self.req)
+        rschema = self._cw.vreg.schema.rschema(entity.name)
+        viewer = SchemaViewer(self._cw)
         layout = viewer.visit_relationschema(rschema)
         self.w(uilib.ureport_as_html(layout))
         if not rschema.is_final():
-            msg = self.req._('graphical schema for %s') % entity.name
+            msg = self._cw._('graphical schema for %s') % entity.name
             self.w(tags.img(src=entity.absolute_url(vid='schemagraph'),
                             alt=msg))
 
@@ -344,18 +345,18 @@
 
 class RestrictedSchemaVisitorMixIn(object):
     def __init__(self, req, *args, **kwargs):
-        self.req = req
+        self._cw = req
         super(RestrictedSchemaVisitorMixIn, self).__init__(*args, **kwargs)
 
     def should_display_schema(self, rschema):
         return (super(RestrictedSchemaVisitorMixIn, self).should_display_schema(rschema)
                 and (rschema.has_local_role('read')
-                     or rschema.has_perm(self.req, 'read')))
+                     or rschema.has_perm(self._cw, 'read')))
 
     def should_display_attr(self, rschema):
         return (super(RestrictedSchemaVisitorMixIn, self).should_display_attr(rschema)
                 and (rschema.has_local_role('read')
-                     or rschema.has_perm(self.req, 'read')))
+                     or rschema.has_perm(self._cw, 'read')))
 
 
 class FullSchemaVisitor(RestrictedSchemaVisitorMixIn, s2d.FullSchemaVisitor):
@@ -371,28 +372,28 @@
 
 
 class SchemaImageView(TmpFileViewMixin, StartupView):
-    id = 'schemagraph'
+    __regid__ = 'schemagraph'
     content_type = 'image/png'
 
     def _generate(self, tmpfile):
         """display global schema information"""
-        print 'skipedtypes', skip_types(self.req)
-        visitor = FullSchemaVisitor(self.req, self.schema,
-                                    skiptypes=skip_types(self.req))
+        print 'skipedtypes', skip_types(self._cw)
+        visitor = FullSchemaVisitor(self._cw, self._cw.schema,
+                                    skiptypes=skip_types(self._cw))
         s2d.schema2dot(outputfile=tmpfile, visitor=visitor)
 
 
 class CWETypeSchemaImageView(TmpFileViewMixin, EntityView):
-    id = 'schemagraph'
+    __regid__ = 'schemagraph'
     __select__ = implements('CWEType')
     content_type = 'image/png'
 
     def _generate(self, tmpfile):
         """display schema information for an entity"""
-        entity = self.rset.get_entity(self.row, self.col)
-        eschema = self.vreg.schema.eschema(entity.name)
-        visitor = OneHopESchemaVisitor(self.req, eschema,
-                                       skiptypes=skip_types(self.req))
+        entity = self.cw_rset.get_entity(self.cw_row, self.cw_col)
+        eschema = self._cw.vreg.schema.eschema(entity.name)
+        visitor = OneHopESchemaVisitor(self._cw, eschema,
+                                       skiptypes=skip_types(self._cw))
         s2d.schema2dot(outputfile=tmpfile, visitor=visitor)
 
 
@@ -401,21 +402,21 @@
 
     def _generate(self, tmpfile):
         """display schema information for an entity"""
-        entity = self.rset.get_entity(self.row, self.col)
-        rschema = self.vreg.schema.rschema(entity.name)
-        visitor = OneHopRSchemaVisitor(self.req, rschema)
+        entity = self.cw_rset.get_entity(self.cw_row, self.cw_col)
+        rschema = self._cw.vreg.schema.rschema(entity.name)
+        visitor = OneHopRSchemaVisitor(self._cw, rschema)
         s2d.schema2dot(outputfile=tmpfile, visitor=visitor)
 
 
 # misc: facets, actions ########################################################
 
 class CWFinalFacet(facet.AttributeFacet):
-    id = 'cwfinal-facet'
+    __regid__ = 'cwfinal-facet'
     __select__ = facet.AttributeFacet.__select__ & implements('CWEType', 'CWRType')
     rtype = 'final'
 
 class ViewSchemaAction(action.Action):
-    id = 'schema'
+    __regid__ = 'schema'
     __select__ = yes()
 
     title = _("site schema")
@@ -423,4 +424,4 @@
     order = 30
 
     def url(self):
-        return self.build_url(self.id)
+        return self._cw.build_url(self.__regid__)
--- a/web/views/sessions.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/sessions.py	Wed Sep 30 18:57:42 2009 +0200
@@ -15,8 +15,8 @@
 class InMemoryRepositorySessionManager(AbstractSessionManager):
     """manage session data associated to a session identifier"""
 
-    def __init__(self):
-        AbstractSessionManager.__init__(self)
+    def __init__(self, *args, **kwargs):
+        AbstractSessionManager.__init__(self, *args, **kwargs)
         # XXX require a RepositoryAuthenticationManager which violates
         #     authenticate interface by returning a session instead of a user
         #assert isinstance(self.authmanager, RepositoryAuthenticationManager)
--- a/web/views/sparql.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/sparql.py	Wed Sep 30 18:57:42 2009 +0200
@@ -23,7 +23,7 @@
     Sparql2rqlTranslator = None
 
 class SparqlForm(forms.FieldsForm):
-    id = 'sparql'
+    __regid__ = 'sparql'
     sparql = formfields.StringField(help=_('type here a sparql query'))
     resultvid = formfields.StringField(choices=((_('table'), 'table'),
                                                 (_('sparql xml'), 'sparqlxml')),
@@ -32,29 +32,29 @@
     form_buttons = [fwdgs.SubmitButton()]
     @property
     def action(self):
-        return self.req.url()
+        return self._cw.url()
 
 
 class SparqlFormView(form.FormViewMixIn, StartupView):
-    id = 'sparql'
+    __regid__ = 'sparql'
     def call(self):
-        form = self.vreg.select('forms', 'sparql', self.req)
+        form = self._cw.vreg.select('forms', 'sparql', self._cw)
         self.w(form.form_render())
-        sparql = self.req.form.get('sparql')
-        vid = self.req.form.get('resultvid', 'table')
+        sparql = self._cw.form.get('sparql')
+        vid = self._cw.form.get('resultvid', 'table')
         if sparql:
             try:
-                qinfo = Sparql2rqlTranslator(self.schema).translate(sparql)
+                qinfo = Sparql2rqlTranslator(self._cw.schema).translate(sparql)
             except rql.TypeResolverException, ex:
-                self.w(self.req._('can not resolve entity types:') + u' ' + unicode('ex'))
+                self.w(self._cw._('can not resolve entity types:') + u' ' + unicode('ex'))
             except UnsupportedQuery:
-                self.w(self.req._('we are not yet ready to handle this query'))
+                self.w(self._cw._('we are not yet ready to handle this query'))
             except xy.UnsupportedVocabulary, ex:
-                self.w(self.req._('unknown vocabulary:') + u' ' + unicode('ex'))
+                self.w(self._cw._('unknown vocabulary:') + u' ' + unicode('ex'))
             if vid == 'sparqlxml':
-                url = self.build_url('view', rql=qinfo.finalize(), vid=vid)
+                url = self._cw.build_url('view', rql=qinfo.finalize(), vid=vid)
                 raise Redirect(url)
-            rset = self.req.execute(qinfo.finalize())
+            rset = self._cw.execute(qinfo.finalize())
             self.wview(vid, rset, 'null')
 
 
@@ -81,16 +81,16 @@
 class SparqlResultXmlView(AnyRsetView):
     """The spec can be found here: http://www.w3.org/TR/rdf-sparql-XMLres/
     """
-    id = 'sparqlxml'
+    __regid__ = 'sparqlxml'
     content_type = 'application/sparql-results+xml'
     templatable = False
 
     def call(self):
         # XXX handle UNION
-        rqlst = self.rset.syntax_tree().children[0]
+        rqlst = self.cw_rset.syntax_tree().children[0]
         varnames = [var.name for var in rqlst.selection]
         results = E.results()
-        for rowidx in xrange(len(self.rset)):
+        for rowidx in xrange(len(self.cw_rset)):
             result = E.result()
             for colidx, varname in enumerate(varnames):
                 result.append(self.cell_binding(rowidx, colidx, varname))
@@ -101,21 +101,21 @@
         self.w(etree.tostring(sparql, encoding=unicode, pretty_print=True))
 
     def cell_binding(self, row, col, varname):
-        celltype = self.rset.description[row][col]
-        if self.schema.eschema(celltype).is_final():
-            cellcontent = self.view('cell', self.rset, row=row, col=col)
+        celltype = self.cw_rset.description[row][col]
+        if self._cw.schema.eschema(celltype).is_final():
+            cellcontent = self.view('cell', self.cw_rset, row=row, col=col)
             return E.binding(E.literal(cellcontent,
                                        datatype=xmlschema(celltype)),
                              name=varname)
         else:
-            entity = self.entity(row, col)
+            entity = self.cw_rset.get_entity(row, col)
             return E.binding(E.uri(entity.absolute_url()), name=varname)
 
     def set_request_content_type(self):
         """overriden to set the correct filetype and filename"""
-        self.req.set_content_type(self.content_type,
+        self._cw.set_content_type(self.content_type,
                                   filename='sparql.xml',
-                                  encoding=self.req.encoding)
+                                  encoding=self._cw.encoding)
 
 def registration_callback(vreg):
     if Sparql2rqlTranslator is not None:
--- a/web/views/startup.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/startup.py	Wed Sep 30 18:57:42 2009 +0200
@@ -18,27 +18,17 @@
 from cubicweb.web import ajax_replace_url, uicfg, httpcache
 
 class ManageView(StartupView):
-    id = 'manage'
+    __regid__ = 'manage'
     title = _('manage')
     http_cache_manager = httpcache.EtagHTTPCacheManager
     add_etype_links = ()
 
-    @classmethod
-    def vreg_initialization_completed(cls):
-        for eschema in cls.schema.entities():
-            if eschema.schema_entity():
-                uicfg.indexview_etype_section.setdefault(eschema, 'schema')
-            elif eschema.is_subobject(strict=True):
-                uicfg.indexview_etype_section.setdefault(eschema, 'subobject')
-            else:
-                uicfg.indexview_etype_section.setdefault(eschema, 'application')
-
     def display_folders(self):
         return False
 
     def call(self, **kwargs):
         """The default view representing the instance's management"""
-        self.req.add_css('cubicweb.manageview.css')
+        self._cw.add_css('cubicweb.manageview.css')
         self.w(u'<div>\n')
         if not self.display_folders():
             self._main_index()
@@ -53,10 +43,10 @@
         self.w(u'</div>\n')
 
     def _main_index(self):
-        req = self.req
+        req = self._cw
         manager = req.user.matching_groups('managers')
-        if not manager and 'Card' in self.schema:
-            rset = self.req.execute('Card X WHERE X wikiid "index"')
+        if not manager and 'Card' in self._cw.schema:
+            rset = self._cw.execute('Card X WHERE X wikiid "index"')
         else:
             rset = None
         if rset:
@@ -65,19 +55,19 @@
             self.entities()
             self.w(u'<div class="hr">&#160;</div>')
             self.startup_views()
-        if manager and 'Card' in self.schema:
+        if manager and 'Card' in self._cw.schema:
             self.w(u'<div class="hr">&#160;</div>')
             if rset:
                 href = rset.get_entity(0, 0).absolute_url(vid='edition')
-                label = self.req._('edit the index page')
+                label = self._cw._('edit the index page')
             else:
                 href = req.build_url('view', vid='creation', etype='Card', wikiid='index')
-                label = self.req._('create an index page')
+                label = self._cw._('create an index page')
             self.w(u'<br/><a href="%s">%s</a>\n' % (xml_escape(href), label))
 
     def folders(self):
-        self.w(u'<h4>%s</h4>\n' % self.req._('Browse by category'))
-        self.vreg['views'].select('tree', self.req).render(w=self.w)
+        self.w(u'<h4>%s</h4>\n' % self._cw._('Browse by category'))
+        self._cw.vreg['views'].select('tree', self._cw).render(w=self.w)
 
     def create_links(self):
         self.w(u'<ul class="createLink">')
@@ -90,31 +80,31 @@
         self.w(u'</ul>')
 
     def startup_views(self):
-        self.w(u'<h4>%s</h4>\n' % self.req._('Startup views'))
+        self.w(u'<h4>%s</h4>\n' % self._cw._('Startup views'))
         self.startupviews_table()
 
     def startupviews_table(self):
-        for v in self.vreg['views'].possible_views(self.req, None):
-            if v.category != 'startupview' or v.id in ('index', 'tree', 'manage'):
+        for v in self._cw.vreg['views'].possible_views(self._cw, None):
+            if v.category != 'startupview' or v.__regid__ in ('index', 'tree', 'manage'):
                 continue
             self.w('<p><a href="%s">%s</a></p>' % (
-                xml_escape(v.url()), xml_escape(self.req._(v.title).capitalize())))
+                xml_escape(v.url()), xml_escape(self._cw._(v.title).capitalize())))
 
     def entities(self):
-        schema = self.schema
-        self.w(u'<h4>%s</h4>\n' % self.req._('The repository holds the following entities'))
-        manager = self.req.user.matching_groups('managers')
+        schema = self._cw.schema
+        self.w(u'<h4>%s</h4>\n' % self._cw._('The repository holds the following entities'))
+        manager = self._cw.user.matching_groups('managers')
         self.w(u'<table class="startup">')
         if manager:
-            self.w(u'<tr><th colspan="4">%s</th></tr>\n' % self.req._('application entities'))
+            self.w(u'<tr><th colspan="4">%s</th></tr>\n' % self._cw._('application entities'))
         self.entity_types_table(eschema for eschema in schema.entities()
                                 if uicfg.indexview_etype_section.get(eschema) == 'application')
         if manager:
-            self.w(u'<tr><th colspan="4">%s</th></tr>\n' % self.req._('system entities'))
+            self.w(u'<tr><th colspan="4">%s</th></tr>\n' % self._cw._('system entities'))
             self.entity_types_table(eschema for eschema in schema.entities()
                                 if uicfg.indexview_etype_section.get(eschema) == 'system')
             if 'CWAttribute' in schema: # check schema support
-                self.w(u'<tr><th colspan="4">%s</th></tr>\n' % self.req._('schema entities'))
+                self.w(u'<tr><th colspan="4">%s</th></tr>\n' % self._cw._('schema entities'))
                 self.entity_types_table(eschema for eschema in schema.entities()
                                         if uicfg.indexview_etype_section.get(eschema) == 'schema')
         self.w(u'</table>')
@@ -138,7 +128,7 @@
         """return a list of formatted links to get a list of entities of
         a each entity's types
         """
-        req = self.req
+        req = self._cw
         for eschema in eschemas:
             if eschema.is_final() or (not eschema.has_perm(req, 'read') and
                                       not eschema.has_local_role('read')):
@@ -146,7 +136,7 @@
             etype = eschema.type
             label = display_name(req, etype, 'plural')
             nb = req.execute('Any COUNT(X) WHERE X is %s' % etype)[0][0]
-            url = self.build_url(etype)
+            url = self._cw.build_url(etype)
             etypelink = u'&#160;<a href="%s">%s</a> (%d)' % (
                 xml_escape(url), label, nb)
             yield (label, etypelink, self.add_entity_link(eschema, req))
@@ -157,35 +147,13 @@
             return u''
         return u'[<a href="%s" title="%s">+</a>]' % (
             xml_escape(self.create_url(eschema.type)),
-            self.req.__('add a %s' % eschema))
+            self._cw.__('add a %s' % eschema))
 
 
 class IndexView(ManageView):
-    id = 'index'
+    __regid__ = 'index'
     title = _('view_index')
 
     def display_folders(self):
-        return 'Folder' in self.schema and self.req.execute('Any COUNT(X) WHERE X is Folder')[0][0]
-
-
-class RegistryView(StartupView):
-    id = 'registry'
-    title = _('registry')
-    __select__ = StartupView.__select__ & match_user_groups('managers')
+        return 'Folder' in self._cw.schema and self._cw.execute('Any COUNT(X) WHERE X is Folder')[0][0]
 
-    def call(self, **kwargs):
-        """The default view representing the instance's management"""
-        self.w(u'<h1>%s</h1>' % _("Registry's content"))
-        keys = sorted(self.vreg)
-        self.w(u'<p>%s</p>\n' % ' - '.join('<a href="/_registry#%s">%s</a>' % (key, key) for key in keys))
-        for key in keys:
-            self.w(u'<h2><a name="%s">%s</a></h2>' % (key,key))
-            items = self.vreg[key].items()
-            if items:
-                self.w(u'<table><tbody>')
-                for key, value in sorted(items):
-                    self.w(u'<tr><td>%s</td><td>%s</td></tr>' % (key, xml_escape(repr(value))))
-                self.w(u'</tbody></table>\n')
-            else:
-                self.w(u'<p>Empty</p>\n')
-
--- a/web/views/tableview.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/tableview.py	Wed Sep 30 18:57:42 2009 +0200
@@ -22,20 +22,20 @@
 from cubicweb.web.facet import prepare_facets_rqlst, filter_hiddens
 
 class TableView(AnyRsetView):
-    id = 'table'
+    __regid__ = 'table'
     title = _('table')
     finalview = 'final'
 
     def form_filter(self, divid, displaycols, displayactions, displayfilter,
                     hidden=True):
-        rqlst = self.rset.syntax_tree()
+        rqlst = self.cw_rset.syntax_tree()
         # union not yet supported
         if len(rqlst.children) != 1:
             return ()
         rqlst.save_state()
-        mainvar, baserql = prepare_facets_rqlst(rqlst, self.rset.args)
-        wdgs = [facet.get_widget() for facet in self.vreg['facets'].possible_vobjects(
-            self.req, rset=self.rset, context='tablefilter',
+        mainvar, baserql = prepare_facets_rqlst(rqlst, self.cw_rset.args)
+        wdgs = [facet.get_widget() for facet in self._cw.vreg['facets'].poss_visible_objects(
+            self._cw, rset=self.cw_rset, context='tablefilter',
             filtered_variable=mainvar)]
         wdgs = [wdg for wdg in wdgs if wdg is not None]
         rqlst.recover()
@@ -51,8 +51,8 @@
         """display a form to filter table's content. This should only
         occurs when a context eid is given
         """
-        self.req.add_css('cubicweb.facets.css')
-        self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.facets.js'))
+        self._cw.add_css('cubicweb.facets.css')
+        self._cw.add_js( ('cubicweb.ajax.js', 'cubicweb.facets.js'))
         # drop False / None values from vidargs
         vidargs = dict((k, v) for k, v in vidargs.iteritems() if v)
         self.w(u'<form method="post" cubicweb:facetargs="%s" action="">' %
@@ -60,7 +60,7 @@
         self.w(u'<fieldset id="%sForm" class="%s">' % (divid, hidden and 'hidden' or ''))
         self.w(u'<input type="hidden" name="divid" value="%s" />' % divid)
         self.w(u'<input type="hidden" name="fromformfilter" value="1" />')
-        filter_hiddens(self.w, facets=','.join(wdg.facet.id for wdg in fwidgets),
+        filter_hiddens(self.w, facets=','.join(wdg.facet.__regid__ for wdg in fwidgets),
                        baserql=baserql)
         self.w(u'<table class="filter">\n')
         self.w(u'<tr>\n')
@@ -77,8 +77,8 @@
         """returns the index of the first non-attribute variable among the RQL
         selected variables
         """
-        eschema = self.vreg.schema.eschema
-        for i, etype in enumerate(self.rset.description[0]):
+        eschema = self._cw.vreg.schema.eschema
+        for i, etype in enumerate(self.cw_rset.description[0]):
             try:
                 if not eschema(etype).is_final():
                     return i
@@ -88,10 +88,10 @@
 
     def displaycols(self, displaycols):
         if displaycols is None:
-            if 'displaycols' in self.req.form:
-                displaycols = [int(idx) for idx in self.req.form['displaycols']]
+            if 'displaycols' in self._cw.form:
+                displaycols = [int(idx) for idx in self._cw.form['displaycols']]
             else:
-                displaycols = range(len(self.rset.syntax_tree().children[0].selection))
+                displaycols = range(len(self.cw_rset.syntax_tree().children[0].selection))
         return displaycols
 
     def call(self, title=None, subvid=None, displayfilter=None, headers=None,
@@ -104,7 +104,7 @@
         :param displayfilter: filter that selects rows to display
         :param headers: columns' titles
         """
-        req = self.req
+        req = self._cw
         req.add_js('jquery.tablesorter.js')
         req.add_css(('cubicweb.tablesorter.css', 'cubicweb.tableview.css'))
         # compute label first  since the filter form may remove some necessary
@@ -115,7 +115,7 @@
         hidden = True
         if not subvid and 'subvid' in req.form:
             subvid = req.form.pop('subvid')
-        divid = divid or req.form.get('divid') or 'rs%s' % make_uid(id(self.rset))
+        divid = divid or req.form.get('divid') or 'rs%s' % make_uid(id(self.cw_rset))
         actions = list(actions)
         if mainindex is None:
             displayfilter, displayactions = False, False
@@ -144,7 +144,7 @@
             actions += self.show_hide_actions(divid, True)
         self.w(u'<div id="%s"' % divid)
         if displayactions:
-            actionsbycat = self.vreg['actions'].possible_actions(req, self.rset)
+            actionsbycat = self._cw.vreg['actions'].possible_actions(req, self.cw_rset)
             for action in actionsbycat.get('mainactions', ()):
                 for action in action.actual_actions():
                     actions.append( (action.url(), req._(action.title),
@@ -165,13 +165,12 @@
         if not fromformfilter:
             self.w(u'</div>\n')
 
-
     def show_hide_actions(self, divid, currentlydisplayed=False):
         showhide = u';'.join(toggle_action('%s%s' % (divid, what))[11:]
                              for what in ('Form', 'Show', 'Hide', 'Actions'))
         showhide = 'javascript:' + showhide
-        showlabel = self.req._('show filter form')
-        hidelabel = self.req._('hide filter form')
+        showlabel = self._cw._('show filter form')
+        hidelabel = self._cw._('hide filter form')
         if currentlydisplayed:
             return [(showhide, showlabel, 'hidden', '%sShow' % divid),
                     (showhide, hidelabel, None, '%sHide' % divid)]
@@ -181,8 +180,8 @@
     def render_actions(self, divid, actions):
         box = MenuWidget('', 'tableActionsBox', _class='', islist=False)
         label = '<img src="%s" alt="%s"/>' % (
-            self.req.datadir_url + 'liveclipboard-icon.png',
-            xml_escape(self.req._('action(s) on this selection')))
+            self._cw.datadir_url + 'liveclipboard-icon.png',
+            xml_escape(self._cw._('action(s) on this selection')))
         menu = PopupBoxMenu(label, isitem=False, link_class='actionsBox',
                             ident='%sActions' % divid)
         box.append(menu)
@@ -201,14 +200,14 @@
             if headers is not None:
                 label = headers[displaycols.index(colindex)]
             if colindex == mainindex:
-                label += ' (%s)' % self.rset.rowcount
+                label += ' (%s)' % self.cw_rset.rowcount
             column = TableColumn(label, colindex)
-            coltype = self.rset.description[0][colindex]
+            coltype = self.cw_rset.description[0][colindex]
             # compute column cell view (if coltype is None, it's a left outer
             # join, use the default non final subvid)
             if cellvids and colindex in cellvids:
                 column.append_renderer(cellvids[colindex], colindex)
-            elif coltype is not None and self.schema.eschema(coltype).is_final():
+            elif coltype is not None and self._cw.schema.eschema(coltype).is_final():
                 column.append_renderer(self.finalview, colindex)
             else:
                 column.append_renderer(subvid or 'incontext', colindex)
@@ -221,10 +220,10 @@
 
 
     def render_cell(self, cellvid, row, col, w):
-        self.view('cell', self.rset, row=row, col=col, cellvid=cellvid, w=w)
+        self._cw.view('cell', self.cw_rset, row=row, col=col, cellvid=cellvid, w=w)
 
     def get_rows(self):
-        return self.rset
+        return self.cw_rset
 
     @htmlescape
     @jsonize
@@ -233,46 +232,45 @@
         # XXX it might be interesting to try to limit value's
         #     length as much as possible (e.g. by returning the 10
         #     first characters of a string)
-        val = self.rset[row][col]
+        val = self.cw_rset[row][col]
         if val is None:
             return u''
-        etype = self.rset.description[row][col]
-        if self.schema.eschema(etype).is_final():
-            entity, rtype = self.rset.related_entity(row, col)
+        etype = self.cw_rset.description[row][col]
+        if self._cw.schema.eschema(etype).is_final():
+            entity, rtype = self.cw_rset.related_entity(row, col)
             if entity is None:
                 return val # remove_html_tags() ?
             return entity.sortvalue(rtype)
-        entity = self.rset.get_entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         return entity.sortvalue()
 
 
 class EditableTableView(TableView):
-    id = 'editable-table'
+    __regid__ = 'editable-table'
     finalview = 'editable-final'
     title = _('editable-table')
 
 
 class CellView(EntityView):
+    __regid__ = 'cell'
     __select__ = nonempty_rset()
 
-    id = 'cell'
-
     def cell_call(self, row, col, cellvid=None):
         """
         :param row, col: indexes locating the cell value in view's result set
         :param cellvid: cell view (defaults to 'outofcontext')
         """
-        etype, val = self.rset.description[row][col], self.rset[row][col]
-        if val is not None and not self.schema.eschema(etype).is_final():
-            e = self.rset.get_entity(row, col)
+        etype, val = self.cw_rset.description[row][col], self.cw_rset[row][col]
+        if val is not None and not self._cw.schema.eschema(etype).is_final():
+            e = self.cw_rset.get_entity(row, col)
             e.view(cellvid or 'outofcontext', w=self.w)
         elif val is None:
             # This is usually caused by a left outer join and in that case,
             # regular views will most certainly fail if they don't have
             # a real eid
-            self.wview('final', self.rset, row=row, col=col)
+            self.wview('final', self.cw_rset, row=row, col=col)
         else:
-            self.wview(cellvid or 'final', self.rset, 'null', row=row, col=col)
+            self.wview(cellvid or 'final', self.cw_rset, 'null', row=row, col=col)
 
 
 class InitialTableView(TableView):
@@ -287,7 +285,7 @@
     * the actual query (`actualrql` form parameter) whose results will be
       displayed with default restrictions set
     """
-    id = 'initialtable'
+    __regid__ = 'initialtable'
     __select__ = nonempty_rset() & match_form_params('actualrql')
     # should not be displayed in possible view since it expects some specific
     # parameters
@@ -296,17 +294,17 @@
     def call(self, title=None, subvid=None, headers=None, divid=None,
              displaycols=None, displayactions=None, mainindex=None):
         """Dumps a table displaying a composite query"""
-        actrql = self.req.form['actualrql']
-        self.ensure_ro_rql(actrql)
+        actrql = self._cw.form['actualrql']
+        self._cw.ensure_ro_rql(actrql)
         displaycols = self.displaycols(displaycols)
-        if displayactions is None and 'displayactions' in self.req.form:
+        if displayactions is None and 'displayactions' in self._cw.form:
             displayactions = True
-        if divid is None and 'divid' in self.req.form:
-            divid = self.req.form['divid']
+        if divid is None and 'divid' in self._cw.form:
+            divid = self._cw.form['divid']
         self.w(u'<div class="section">')
-        if not title and 'title' in self.req.form:
+        if not title and 'title' in self._cw.form:
             # pop title so it's not displayed by the table view as well
-            title = self.req.form.pop('title')
+            title = self._cw.form.pop('title')
         if title:
             self.w(u'<h2>%s</h2>\n' % title)
         if mainindex is None:
@@ -315,15 +313,15 @@
             actions = self.form_filter(divid, displaycols, displayactions, True)
         else:
             actions = ()
-        if not subvid and 'subvid' in self.req.form:
-            subvid = self.req.form.pop('subvid')
-        self.view('table', self.req.execute(actrql),
-                  'noresult', w=self.w, displayfilter=False, subvid=subvid,
-                  displayactions=displayactions, displaycols=displaycols,
-                  actions=actions, headers=headers, divid=divid)
+        if not subvid and 'subvid' in self._cw.form:
+            subvid = self._cw.form.pop('subvid')
+        self._cw.view('table', self._cw.execute(actrql),
+                      'noresult', w=self.w, displayfilter=False, subvid=subvid,
+                      displayactions=displayactions, displaycols=displaycols,
+                      actions=actions, headers=headers, divid=divid)
         self.w(u'</div>\n')
 
 
 class EditableInitialTableTableView(InitialTableView):
-    id = 'editable-initialtable'
+    __regid__ = 'editable-initialtable'
     finalview = 'editable-final'
--- a/web/views/tabs.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/tabs.py	Wed Sep 30 18:57:42 2009 +0200
@@ -24,7 +24,7 @@
     """
 
     def _prepare_bindings(self, vid, reloadable):
-        self.req.add_onload(u"""
+        self._cw.add_onload(u"""
   jQuery('#lazy-%(vid)s').bind('%(event)s', function(event) {
      load_now('#lazy-%(vid)s', '#%(vid)s-hole', %(reloadable)s);
   });""" % {'event': 'load_%s' % vid, 'vid': vid,
@@ -38,7 +38,7 @@
         assert rql or eid or rset or static, \
             'lazyview wants at least : rql, or an eid, or an rset -- or call it with static=True'
         w = w or self.w
-        self.req.add_js('cubicweb.lazy.js')
+        self._cw.add_js('cubicweb.lazy.js')
         urlparams = {'vid' : vid, 'fname' : 'view'}
         if rql:
             urlparams['rql'] = rql
@@ -47,10 +47,10 @@
         elif rset:
             urlparams['rql'] = rset.printable_rql()
         w(u'<div id="lazy-%s" cubicweb:loadurl="%s">' % (
-            vid, xml_escape(self.build_url('json', **urlparams))))
+            vid, xml_escape(self._cw.build_url('json', **urlparams))))
         if show_spinbox:
             w(u'<img src="data/loading.gif" id="%s-hole" alt="%s"/>'
-              % (vid, self.req._('loading')))
+              % (vid, self._cw._('loading')))
         w(u'</div>')
         self._prepare_bindings(vid, reloadable)
 
@@ -58,8 +58,8 @@
         """trigger an event that will force immediate loading of the view
         on dom readyness
         """
-        self.req.add_js('cubicweb.lazy.js')
-        self.req.add_onload("trigger_load('%s');" % vid)
+        self._cw.add_js('cubicweb.lazy.js')
+        self._cw.add_onload("trigger_load('%s');" % vid)
 
 
 class TabsMixin(LazyViewMixin):
@@ -68,18 +68,18 @@
 
     @property
     def cookie_name(self):
-        return str('%s_active_tab' % self.config.appid)
+        return str('%s_active_tab' % self._cw.config.appid)
 
     def active_tab(self, tabs, default):
-        formtab = self.req.form.get('tab')
+        formtab = self._cw.form.get('tab')
         if formtab in tabs:
             return formtab
-        cookies = self.req.get_cookie()
+        cookies = self._cw.get_cookie()
         cookiename = self.cookie_name
         activetab = cookies.get(cookiename)
         if activetab is None:
             cookies[cookiename] = default
-            self.req.set_cookie(cookies, cookiename)
+            self._cw.set_cookie(cookies, cookiename)
             tab = default
         else:
             tab = activetab.value
@@ -89,7 +89,7 @@
         selected_tabs = []
         for tab in tabs:
             try:
-                self.vreg['views'].select(tab, self.req, rset=self.rset)
+                self._cw.vreg['views'].select(tab, self._cw, rset=self.cw_rset)
                 selected_tabs.append(tab)
             except NoSelectableObject:
                 continue
@@ -98,11 +98,11 @@
     def render_tabs(self, tabs, default, entity=None):
         # delegate to the default tab if there is more than one entity
         # in the result set (tabs are pretty useless there)
-        if entity and len(self.rset) > 1:
+        if entity and len(self.cw_rset) > 1:
             entity.view(default, w=self.w)
             return
-        self.req.add_css('ui.tabs.css')
-        self.req.add_js(('ui.core.js', 'ui.tabs.js',
+        self._cw.add_css('ui.tabs.css')
+        self._cw.add_js(('ui.core.js', 'ui.tabs.js',
                          'cubicweb.ajax.js', 'cubicweb.tabs.js', 'cubicweb.lazy.js'))
         # prune tabs : not all are to be shown
         tabs = self.prune_tabs(tabs)
@@ -117,7 +117,7 @@
             w(u'<li>')
             w(u'<a href="#%s">' % tab)
             w(u'<span onclick="set_tab(\'%s\', \'%s\')">' % (tab, self.cookie_name))
-            w(self.req._(tab))
+            w(self._cw._(tab))
             w(u'</span>')
             w(u'</a>')
             w(u'</li>')
@@ -133,7 +133,7 @@
         # call the set_tab() JS function *after* each tab is generated
         # because the callback binding needs to be done before
         # XXX make work history: true
-        self.req.add_onload(u"""
+        self._cw.add_onload(u"""
   jQuery('#entity-tabs-%(eeid)s > ul').tabs( { selected: %(tabindex)s });
   set_tab('%(vid)s', '%(cookiename)s');
 """ % {'tabindex'   : tabs.index(active_tab),
@@ -150,7 +150,7 @@
 
     class ProjectScreenshotsView(EntityRelationView):
         '''display project's screenshots'''
-        id = title = _('projectscreenshots')
+        __regid__ = title = _('projectscreenshots')
         __select__ = EntityRelationView.__select__ & implements('Project')
         rtype = 'screenshot'
         role = 'subject'
@@ -164,9 +164,9 @@
     vid = 'list'
 
     def cell_call(self, row, col):
-        rset = self.entity(row, col).related(self.rtype, role(self))
+        rset = self.cw_rset.get_entity(row, col).related(self.rtype, role(self))
         self.w(u'<div class="mainInfo">')
         if self.title:
-            self.w(tags.h1(self.req._(self.title)))
+            self.w(tags.h1(self._cw._(self.title)))
         self.wview(self.vid, rset, 'noresult')
         self.w(u'</div>')
--- a/web/views/timeline.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/timeline.py	Wed Sep 30 18:57:42 2009 +0200
@@ -24,7 +24,7 @@
     NOTE: work in progress (image_url, bubbleUrl and so on
     should be properties of entity classes or subviews)
     """
-    id = 'timeline-json'
+    __regid__ = 'timeline-json'
     binary = True
     templatable = False
     content_type = 'application/json'
@@ -34,7 +34,7 @@
 
     def call(self):
         events = []
-        for entity in self.rset.entities():
+        for entity in self.cw_rset.entities():
             event = self.build_event(entity)
             if event is not None:
                 events.append(event)
@@ -86,9 +86,9 @@
                'cubicweb.timeline-ext.js', 'cubicweb.ajax.js')
 
     def render_url(self, loadurl, tlunit=None):
-        tlunit = tlunit or self.req.form.get('tlunit')
-        self.req.add_js(self.jsfiles)
-        self.req.add_css('timeline-bundle.css')
+        tlunit = tlunit or self._cw.form.get('tlunit')
+        self._cw.add_js(self.jsfiles)
+        self._cw.add_css('timeline-bundle.css')
         if tlunit:
             additional = u' cubicweb:tlunit="%s"' % tlunit
         else:
@@ -102,14 +102,14 @@
 
 class TimelineView(TimelineViewMixIn, EntityView):
     """builds a cubicweb timeline widget node"""
-    id = 'timeline'
+    __regid__ = 'timeline'
     title = _('timeline')
     __select__ = implements(ICalendarable)
     need_navigation = False
     def call(self, tlunit=None):
-        self.req.html_headers.define_var('Timeline_urlPrefix', self.req.datadir_url)
-        rql = self.rset.printable_rql()
-        loadurl = self.build_url(rql=rql, vid='timeline-json')
+        self._cw.html_headers.define_var('Timeline_urlPrefix', self._cw.datadir_url)
+        rql = self.cw_rset.printable_rql()
+        loadurl = self._cw.build_url(rql=rql, vid='timeline-json')
         self.render_url(loadurl, tlunit)
 
 
@@ -117,7 +117,7 @@
     """similar to `TimelineView` but loads data from a static
     JSON file instead of one after a RQL query.
     """
-    id = 'static-timeline'
+    __regid__ = 'static-timeline'
 
     def call(self, loadurl, tlunit=None, wdgclass=None):
         self.widget_class = wdgclass or self.widget_class
--- a/web/views/timetable.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/timetable.py	Wed Sep 30 18:57:42 2009 +0200
@@ -24,7 +24,7 @@
 MIN_COLS = 3  # minimum number of task columns for a single user
 
 class TimeTableView(AnyRsetView):
-    id = 'timetable'
+    __regid__ = 'timetable'
     title = _('timetable')
     __select__ = implements(ITimetableViews)
     need_navigation = False
@@ -32,16 +32,16 @@
     def call(self, title=None):
         """Dumps a timetable from a resultset composed of a note (anything
         with start/stop) and a user (anything)"""
-        self.req.add_css('cubicweb.timetable.css')
+        self._cw.add_css('cubicweb.timetable.css')
         dates = {}
         users = []
         users_max = {}
 
         # XXX: try refactoring with calendar.py:OneMonthCal
-        for row in xrange(self.rset.rowcount):
-            task = self.rset.get_entity(row, 0)
-            if len(self.rset[row])>1:
-                user = self.rset.get_entity(row, 1)
+        for row in xrange(self.cw_rset.rowcount):
+            task = self.cw_rset.get_entity(row, 0)
+            if len(self.cw_rset[row])>1:
+                user = self.cw_rset.get_entity(row, 1)
             else:
                 user = u"*"
             the_dates = []
@@ -171,7 +171,7 @@
             odd = not odd
 
             if not empty_line:
-                self.w(u'<th class="ttdate">%s</th>' % self.format_date(date) )
+                self.w(u'<th class="ttdate">%s</th>' % self._cw.format_date(date) )
             else:
                 self.w(u'<th>...</th>'  )
                 previous_is_empty = True
--- a/web/views/treeview.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/treeview.py	Wed Sep 30 18:57:42 2009 +0200
@@ -20,21 +20,21 @@
     return str('%s-treestate' % treeid)
 
 class TreeView(EntityView):
-    id = 'treeview'
+    __regid__ = 'treeview'
     itemvid = 'treeitemview'
     subvid = 'oneline'
     css_classes = 'treeview widget'
     title = _('tree view')
 
     def _init_params(self, subvid, treeid, initial_load, initial_thru_ajax, morekwargs):
-        form = self.req.form
+        form = self._cw.form
         if subvid is None:
             subvid = form.pop('treesubvid', self.subvid) # consume it
         if treeid is None:
             treeid = form.pop('treeid', None)
             if treeid is None:
                 treeid = 'throw_away' + make_uid('uid')
-        if 'morekwargs' in self.req.form:
+        if 'morekwargs' in self._cw.form:
             ajaxargs = json.loads(form.pop('morekwargs'))
             # got unicode & python keywords must be strings
             morekwargs.update(dict((str(k), v)
@@ -44,9 +44,9 @@
         return subvid, treeid, toplevel_thru_ajax, toplevel
 
     def _init_headers(self, treeid, toplevel_thru_ajax):
-        self.req.add_css('jquery.treeview.css')
-        self.req.add_js(('cubicweb.ajax.js', 'cubicweb.widgets.js', 'jquery.treeview.js'))
-        self.req.html_headers.add_onload(u"""
+        self._cw.add_css('jquery.treeview.css')
+        self._cw.add_js(('cubicweb.ajax.js', 'cubicweb.widgets.js', 'jquery.treeview.js'))
+        self._cw.html_headers.add_onload(u"""
 jQuery("#tree-%s").treeview({toggle: toggleTree, prerendered: true});""" % treeid,
                                          jsoncall=toplevel_thru_ajax)
 
@@ -59,9 +59,9 @@
             self._init_headers(treeid, toplevel_thru_ajax)
             ulid = ' id="tree-%s"' % treeid
         self.w(u'<ul%s class="%s">' % (ulid, self.css_classes))
-        for rowidx in xrange(len(self.rset)):
-            self.wview(self.itemvid, self.rset, row=rowidx, col=0,
-                       vid=subvid, parentvid=self.id, treeid=treeid, **morekwargs)
+        for rowidx in xrange(len(self.cw_rset)):
+            self.wview(self.itemvid, self.cw_rset, row=rowidx, col=0,
+                       vid=subvid, parentvid=self.__regid__, treeid=treeid, **morekwargs)
         self.w(u'</ul>')
 
     def cell_call(self, *args, **allargs):
@@ -74,7 +74,7 @@
 class FileTreeView(TreeView):
     """specific version of the treeview to display file trees
     """
-    id = 'filetree'
+    __regid__ = 'filetree'
     css_classes = 'treeview widget filetree'
     title = _('file tree view')
 
@@ -88,10 +88,10 @@
     This view adds an enclosing <span> with some specific CSS classes
     around the oneline view. This is needed by the jquery treeview plugin.
     """
-    id = 'filetree-oneline'
+    __regid__ = 'filetree-oneline'
 
     def cell_call(self, row, col):
-        entity = self.entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         if ITree.is_implemented_by(entity.__class__) and not entity.is_leaf():
             self.w(u'<div class="folder">%s</div>\n' % entity.view('oneline'))
         else:
@@ -101,13 +101,13 @@
 
 class DefaultTreeViewItemView(EntityView):
     """default treeitem view for entities which don't implement ITree"""
-    id = 'treeitemview'
+    __regid__ = 'treeitemview'
 
     def cell_call(self, row, col, vid='oneline', parentvid='treeview', treeid=None):
         assert treeid is not None
-        entity = self.entity(row, col)
-        itemview = self.view(vid, self.rset, row=row, col=col)
-        if row == len(self.rset) - 1:
+        entity = self.cw_rset.get_entity(row, col)
+        itemview = self._cw.view(vid, self.cw_rset, row=row, col=col)
+        if row == len(self.cw_rset) - 1:
             self.w(u'<li class="last">%s</li>' % itemview)
         else:
             self.w(u'<li>%s</li>' % itemview)
@@ -118,12 +118,12 @@
 
     (each item should be expandable if it's not a tree leaf)
     """
-    id = 'treeitemview'
+    __regid__ = 'treeitemview'
+    __select__ = EntityView.__select__ & implements(ITree)
     default_branch_state_is_open = False
-    __select__ = EntityView.__select__ & implements(ITree)
 
     def open_state(self, eeid, treeid):
-        cookies = self.req.get_cookie()
+        cookies = self._cw.get_cookie()
         treestate = cookies.get(treecookiename(treeid))
         if treestate:
             return str(eeid) in treestate.value.split(';')
@@ -131,9 +131,9 @@
 
     def cell_call(self, row, col, treeid, vid='oneline', parentvid='treeview', **morekwargs):
         w = self.w
-        entity = self.entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         liclasses = []
-        is_last = row == len(self.rset) - 1
+        is_last = row == len(self.cw_rset) - 1
         is_open = self.open_state(entity.eid, treeid)
         is_leaf = not hasattr(entity, 'is_leaf') or entity.is_leaf()
         if is_leaf:
@@ -142,12 +142,12 @@
             w(u'<li class="%s">' % u' '.join(liclasses))
         else:
             rql = entity.children_rql() % {'x': entity.eid}
-            url = xml_escape(self.build_url('json', rql=rql, vid=parentvid,
-                                            pageid=self.req.pageid,
-                                            treeid=treeid,
-                                            fname='view',
-                                            treesubvid=vid,
-                                            morekwargs=json.dumps(morekwargs)))
+            url = xml_escape(self._cw.build_url('json', rql=rql, vid=parentvid,
+                                                pageid=self._cw.pageid,
+                                                treeid=treeid,
+                                                fname='view',
+                                                treesubvid=vid,
+                                                morekwargs=json.dumps(morekwargs)))
             divclasses = ['hitarea']
             if is_open:
                 liclasses.append('collapsable')
@@ -178,8 +178,8 @@
             if not is_open:
                 w(u'<ul class="placeholder"><li>place holder</li></ul>')
         # the local node info
-        self.wview(vid, self.rset, row=row, col=col, **morekwargs)
+        self.wview(vid, self.cw_rset, row=row, col=col, **morekwargs)
         if is_open and not is_leaf: #  => rql is defined
-            self.wview(parentvid, self.req.execute(rql), treeid=treeid, initial_load=False, **morekwargs)
+            self.wview(parentvid, self._cw.execute(rql), treeid=treeid, initial_load=False, **morekwargs)
         w(u'</li>')
 
--- a/web/views/urlpublishing.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/urlpublishing.py	Wed Sep 30 18:57:42 2009 +0200
@@ -28,8 +28,7 @@
 from rql import TypeResolverException
 
 from cubicweb import RegistryException, typed_eid
-from cubicweb.web import NotFound, Redirect
-from cubicweb.web.component import Component, Component
+from cubicweb.web import NotFound, Redirect, component
 
 
 class PathDontMatch(Exception):
@@ -37,7 +36,7 @@
     a path
     """
 
-class URLPublisherComponent(Component):
+class URLPublisherComponent(component.Component):
     """associate url's path to view identifier / rql queries,
     by applying a chain of urlpathevaluator components.
 
@@ -50,13 +49,15 @@
     lower priority.  The first evaluator returning a result or raising
     something else than `PathDontMatch` will stop the handlers chain.
     """
-    id = 'urlpublisher'
+    __regid__ = 'urlpublisher'
+    vreg = None # XXX necessary until property for deprecation warning is on appobject
 
-    def __init__(self, default_method='view'):
+    def __init__(self, vreg, default_method='view'):
         super(URLPublisherComponent, self).__init__()
+        self.vreg = vreg
         self.default_method = default_method
         evaluators = []
-        for evaluatorcls in self.vreg['components']['urlpathevaluator']:
+        for evaluatorcls in vreg['components']['urlpathevaluator']:
             # instantiation needed
             evaluator = evaluatorcls(self)
             evaluators.append(evaluator)
@@ -98,13 +99,14 @@
         return pmid, rset
 
 
-class URLPathEvaluator(Component):
+class URLPathEvaluator(component.Component):
     __abstract__ = True
-    id = 'urlpathevaluator'
+    __regid__ = 'urlpathevaluator'
+    vreg = None # XXX necessary until property for deprecation warning is on appobject
 
     def __init__(self, urlpublisher):
         self.urlpublisher = urlpublisher
-
+        self.vreg = urlpublisher.vreg
 
 class RawPathEvaluator(URLPathEvaluator):
     """handle path of the form::
@@ -197,7 +199,7 @@
         evaluators = sorted(self.vreg['urlrewriting'].all_objects(),
                             key=lambda x: x.priority, reverse=True)
         for rewritercls in evaluators:
-            rewriter = rewritercls()
+            rewriter = rewritercls(req)
             try:
                 # XXX we might want to chain url rewrites
                 return rewriter.rewrite(req, uri)
@@ -217,6 +219,7 @@
             raise PathDontMatch()
         # remove last part and see if this is something like an actions
         # if so, call
+        # XXX bad smell: refactor to simpler code
         try:
             actionsreg = self.vreg['actions']
             requested = parts.pop(-1)
@@ -232,7 +235,7 @@
                 continue
             else:
                 try:
-                    action = actionsreg.select_best(actions, req, rset=rset)
+                    action = actionsreg._select_best(actions, req, rset=rset)
                 except RegistryException:
                     continue
                 else:
--- a/web/views/urlrewrite.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/urlrewrite.py	Wed Sep 30 18:57:42 2009 +0200
@@ -67,7 +67,7 @@
 
     If the input uri is a regexp, group substitution is allowed
     """
-    id = 'simple'
+    __regid__ = 'simple'
 
     rules = [
         ('/_', dict(vid='manage')),
@@ -179,7 +179,7 @@
     """Here, the rules dict maps regexps or plain strings to
     callbacks that will be called with (input, uri, req, schema)
     """
-    id = 'schemabased'
+    __regid__ = 'schemabased'
     rules = [
         # rgxp : callback
         (rgx('/search/(.+)'), build_rset(rql=r'Any X WHERE X has_text %(text)s',
@@ -198,9 +198,9 @@
                 continue
             if isinstance(inputurl, basestring):
                 if inputurl == uri:
-                    return callback(inputurl, uri, req, self.schema)
+                    return callback(inputurl, uri, req, self._cw.schema)
             elif inputurl.match(uri): # it's a regexp
-                return callback(inputurl, uri, req, self.schema)
+                return callback(inputurl, uri, req, self._cw.schema)
         else:
             self.debug("no schemabased rewrite rule found for %s", uri)
             raise KeyError(uri)
--- a/web/views/vcard.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/vcard.py	Wed Sep 30 18:57:42 2009 +0200
@@ -16,7 +16,7 @@
 
 class VCardCWUserView(EntityView):
     """export a person information as a vcard"""
-    id = 'vcard'
+    __regid__ = 'vcard'
     title = _('vcard')
     templatable = False
     content_type = 'text/x-vcard'
@@ -24,11 +24,11 @@
 
     def set_request_content_type(self):
         """overriden to set a .vcf filename"""
-        self.req.set_content_type(self.content_type, filename='vcard.vcf')
+        self._cw.set_content_type(self.content_type, filename='vcard.vcf')
 
     def cell_call(self, row, col):
         self.vcard_header()
-        self.vcard_content(self.complete_entity(row, col))
+        self.vcard_content(self.cw_rset.complete_entity(row, col))
         self.vcard_footer()
 
     def vcard_header(self):
--- a/web/views/wdoc.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/wdoc.py	Wed Sep 30 18:57:42 2009 +0200
@@ -86,20 +86,20 @@
 
 class InlineHelpView(StartupView):
     __select__ = match_form_params('fid')
-    id = 'wdoc'
+    __regid__ = 'wdoc'
     title = _('site documentation')
 
     def call(self):
-        fid = self.req.form['fid']
-        for lang in chain((self.req.lang, self.vreg.property_value('ui.language')),
-                          self.config.available_languages()):
+        fid = self._cw.form['fid']
+        for lang in chain((self._cw.lang, self._cw.vreg.property_value('ui.language')),
+                          self._cw.config.available_languages()):
             rid = '%s_%s.rst' % (fid, lang)
-            resourcedir = self.config.locate_doc_file(rid)
+            resourcedir = self._cw.config.locate_doc_file(rid)
             if resourcedir:
                 break
         else:
             raise NotFound
-        self.tocindex = build_toc(self.config)
+        self.tocindex = build_toc(self._cw.config)
         try:
             node = self.tocindex[fid]
         except KeyError:
@@ -107,7 +107,7 @@
         else:
             self.navigation_links(node)
             self.w(u'<div class="hr"></div>')
-            self.w(u'<h1>%s</h1>' % (title_for_lang(node, self.req.lang)))
+            self.w(u'<h1>%s</h1>' % (title_for_lang(node, self._cw.lang)))
         data = open(join(resourcedir, rid)).read()
         self.w(rest_publish(self, data))
         if node is not None:
@@ -116,7 +116,7 @@
             self.navigation_links(node)
 
     def navigation_links(self, node):
-        req = self.req
+        req = self._cw
         parent = node.parent
         if parent is None:
             return
@@ -138,10 +138,10 @@
     def navsection(self, node, navtype):
         htmlclass, imgpath, msgid = self.navinfo[navtype]
         self.w(u'<span class="%s">' % htmlclass)
-        self.w(u'%s : ' % self.req._(msgid))
+        self.w(u'%s : ' % self._cw._(msgid))
         self.w(u'<a href="%s">%s</a>' % (
-            self.req.build_url('doc/'+node.attrib['resource']),
-            title_for_lang(node, self.req.lang)))
+            self._cw.build_url('doc/'+node.attrib['resource']),
+            title_for_lang(node, self._cw.lang)))
         self.w(u'</span>\n')
 
     def subsections_links(self, node, first=True):
@@ -153,8 +153,8 @@
         self.w(u'<ul class="docsum">')
         for child in sub:
             self.w(u'<li><a href="%s">%s</a>' % (
-                self.req.build_url('doc/'+child.attrib['resource']),
-                title_for_lang(child, self.req.lang)))
+                self._cw.build_url('doc/'+child.attrib['resource']),
+                title_for_lang(child, self._cw.lang)))
             self.subsections_links(child, False)
             self.w(u'</li>')
         self.w(u'</ul>\n')
@@ -162,18 +162,18 @@
 
 
 class InlineHelpImageView(StartupView):
-    id = 'wdocimages'
+    __regid__ = 'wdocimages'
     __select__ = match_form_params('fid')
     binary = True
     templatable = False
     content_type = 'image/png'
 
     def call(self):
-        fid = self.req.form['fid']
-        for lang in chain((self.req.lang, self.vreg.property_value('ui.language')),
-                          self.config.available_languages()):
+        fid = self._cw.form['fid']
+        for lang in chain((self._cw.lang, self._cw.vreg.property_value('ui.language')),
+                          self._cw.config.available_languages()):
             rid = join('images', '%s_%s.png' % (fid, lang))
-            resourcedir = self.config.locate_doc_file(rid)
+            resourcedir = self._cw.config.locate_doc_file(rid)
             if resourcedir:
                 break
         else:
@@ -182,18 +182,18 @@
 
 
 class ChangeLogView(StartupView):
-    id = 'changelog'
+    __regid__ = 'changelog'
     title = _('What\'s new?')
     maxentries = 25
 
     def call(self):
-        rid = 'ChangeLog_%s' % (self.req.lang)
+        rid = 'ChangeLog_%s' % (self._cw.lang)
         allentries = []
-        title = self.req._(self.title)
+        title = self._cw._(self.title)
         restdata = ['.. -*- coding: utf-8 -*-', '', title, '='*len(title), '']
         w = restdata.append
         today = date.today()
-        for fpath in self.config.locate_all_files(rid):
+        for fpath in self._cw.config.locate_all_files(rid):
             cl = ChangeLog(fpath)
             encoding = 'utf-8'
             # additional content may be found in title
@@ -223,7 +223,7 @@
         i = 0
         for edate, messages in reversed(allentries):
             if latestdate != edate:
-                fdate = self.format_date(edate)
+                fdate = self._cw.format_date(edate)
                 w(u'\n%s' % fdate)
                 w('~' * len(fdate))
                 latestdate = edate
--- a/web/views/workflow.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/workflow.py	Wed Sep 30 18:57:42 2009 +0200
@@ -47,7 +47,7 @@
 # IWorkflowable views #########################################################
 
 class ChangeStateForm(forms.CompositeEntityForm):
-    id = 'changestate'
+    __regid__ = 'changestate'
 
     form_renderer_id = 'base' # don't want EntityFormRenderer
     form_buttons = [fwdgs.SubmitButton(stdmsgs.YES),
@@ -55,20 +55,20 @@
 
 
 class ChangeStateFormView(form.FormViewMixIn, view.EntityView):
-    id = 'statuschange'
+    __regid__ = 'statuschange'
     title = _('status change')
     __select__ = (one_line_rset() & implements(IWorkflowable)
                   & match_form_params('treid'))
 
     def cell_call(self, row, col):
-        entity = self.rset.get_entity(row, col)
-        transition = self.req.entity_from_eid(self.req.form['treid'])
+        entity = self.cw_rset.get_entity(row, col)
+        transition = self._cw.entity_from_eid(self._cw.form['treid'])
         dest = transition.destination()
-        _ = self.req._
+        _ = self._cw._
         # specify both rset/row/col and entity in case implements selector (and
         # not entity_implements) is used on custom form
-        form = self.vreg['forms'].select(
-            'changestate', self.req, rset=self.rset, row=row, col=col,
+        form = self._cw.vreg['forms'].select(
+            'changestate', self._cw, rset=self.cw_rset, row=row, col=col,
             entity=entity, transition=transition,
             redirect_path=self.redirectpath(entity))
         self.w(form.error_message())
@@ -78,10 +78,9 @@
             'st1': _(entity.current_state.name),
             'st2': _(dest.name)}
         self.w(u'<p>%s</p>\n' % msg)
-        trinfo = self.vreg['etypes'].etype_class('TrInfo')(self.req)
-        self.initialize_varmaker()
-        trinfo.eid = self.varmaker.next()
-        subform = self.vreg['forms'].select('edition', self.req, entity=trinfo,
+        trinfo = self._cw.vreg['etypes'].etype_class('TrInfo')(self._cw)
+        trinfo.eid = self._cw.varmaker.next()
+        subform = self._cw.vreg['forms'].select('edition', self._cw, entity=trinfo,
                                             mainform=False)
         subform.field_by_name('by_transition').widget = fwdgs.HiddenInput()
         form.add_subform(subform)
@@ -93,18 +92,18 @@
 
 
 class WFHistoryView(EntityView):
-    id = 'wfhistory'
+    __regid__ = 'wfhistory'
     __select__ = relation_possible('wf_info_for', role='object')
     title = _('Workflow history')
 
     def cell_call(self, row, col, view=None):
-        _ = self.req._
-        eid = self.rset[row][col]
+        _ = self._cw._
+        eid = self.cw_rset[row][col]
         sel = 'Any FS,TS,WF,D'
         rql = ' ORDERBY D DESC WHERE WF wf_info_for X,'\
               'WF from_state FS, WF to_state TS, WF comment C,'\
               'WF creation_date D'
-        if self.vreg.schema.eschema('CWUser').has_perm(self.req, 'read'):
+        if self._cw.vreg.schema.eschema('CWUser').has_perm(self._cw, 'read'):
             sel += ',U,C'
             rql += ', WF owned_by U?'
             displaycols = range(5)
@@ -116,7 +115,7 @@
             headers = (_('from_state'), _('to_state'), _('comment'), _('date'))
         rql = '%s %s, X eid %%(x)s' % (sel, rql)
         try:
-            rset = self.req.execute(rql, {'x': eid}, 'x')
+            rset = self._cw.execute(rql, {'x': eid}, 'x')
         except Unauthorized:
             return
         if rset:
@@ -126,20 +125,20 @@
 
 class WFHistoryVComponent(component.EntityVComponent):
     """display the workflow history for entities supporting it"""
-    id = 'wfhistory'
+    __regid__ = 'wfhistory'
     __select__ = WFHistoryView.__select__ & component.EntityVComponent.__select__
     context = 'navcontentbottom'
     title = _('Workflow history')
 
     def cell_call(self, row, col, view=None):
-        self.wview('wfhistory', self.rset, row=row, col=col, view=view)
+        self.wview('wfhistory', self.cw_rset, row=row, col=col, view=view)
 
 
 # workflow actions #############################################################
 
 class WorkflowActions(action.Action):
     """fill 'workflow' sub-menu of the actions box"""
-    id = 'workflow'
+    __regid__ = 'workflow'
     __select__ = (action.Action.__select__ & one_line_rset() &
                   relation_possible('in_state'))
 
@@ -147,45 +146,45 @@
     order = 10
 
     def fill_menu(self, box, menu):
-        entity = self.rset.get_entity(self.row or 0, self.col or 0)
-        menu.label = u'%s: %s' % (self.req._('state'), entity.printable_state)
+        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+        menu.label = u'%s: %s' % (self._cw._('state'), entity.printable_state)
         menu.append_anyway = True
         super(WorkflowActions, self).fill_menu(box, menu)
 
     def actual_actions(self):
-        entity = self.rset.get_entity(self.row or 0, self.col or 0)
+        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
         hastr = False
         for tr in entity.possible_transitions():
             url = entity.absolute_url(vid='statuschange', treid=tr.eid)
-            yield self.build_action(self.req._(tr.name), url)
+            yield self.build_action(self._cw._(tr.name), url)
             hastr = True
         # don't propose to see wf if user can't pass any transition
         if hastr:
             wfurl = entity.current_workflow.absolute_url()
-            yield self.build_action(self.req._('view workflow'), wfurl)
+            yield self.build_action(self._cw._('view workflow'), wfurl)
         if entity.workflow_history:
             wfurl = entity.absolute_url(vid='wfhistory')
-            yield self.build_action(self.req._('view history'), wfurl)
+            yield self.build_action(self._cw._('view history'), wfurl)
 
 
 # workflow entity types views ##################################################
 
 class CellView(view.EntityView):
-    id = 'cell'
+    __regid__ = 'cell'
     __select__ = implements('TrInfo')
 
     def cell_call(self, row, col, cellvid=None):
-        self.w(self.rset.get_entity(row, col).view('reledit', rtype='comment'))
+        self.w(self.cw_rset.get_entity(row, col).view('reledit', rtype='comment'))
 
 
 class StateInContextView(view.EntityView):
     """convenience trick, State's incontext view should not be clickable"""
-    id = 'incontext'
+    __regid__ = 'incontext'
     __select__ = implements('State')
 
     def cell_call(self, row, col):
-        self.w(xml_escape(self.view('textincontext', self.rset,
-                                     row=row, col=col)))
+        self.w(xml_escape(self._cw.view('textincontext', self.cw_rset,
+                                        row=row, col=col)))
 
 
 class WorkflowPrimaryView(primary.PrimaryView):
@@ -195,7 +194,7 @@
         self.w(entity.view('reledit', rtype='description'))
         self.w(u'<img src="%s" alt="%s"/>' % (
             xml_escape(entity.absolute_url(vid='wfgraph')),
-            xml_escape(self.req._('graphical workflow for %s') % entity.name)))
+            xml_escape(self._cw._('graphical workflow for %s') % entity.name)))
 
 
 # workflow images ##############################################################
@@ -255,15 +254,15 @@
 
 
 class WorkflowImageView(TmpFileViewMixin, view.EntityView):
-    id = 'wfgraph'
+    __regid__ = 'wfgraph'
+    __select__ = implements('Workflow')
     content_type = 'image/png'
-    __select__ = implements('Workflow')
 
     def _generate(self, tmpfile):
         """display schema information for an entity"""
-        entity = self.rset.get_entity(self.row, self.col)
+        entity = self.cw_rset.get_entity(self.cw_row, self.cw_col)
         visitor = WorkflowVisitor(entity)
-        prophdlr = WorkflowDotPropsHandler(self.req)
+        prophdlr = WorkflowDotPropsHandler(self._cw)
         generator = GraphGenerator(DotBackend('workflow', 'LR',
                                               ratio='compress', size='30,12'))
         return generator.generate(visitor, prophdlr, tmpfile)
--- a/web/views/xbel.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/xbel.py	Wed Sep 30 18:57:42 2009 +0200
@@ -16,32 +16,32 @@
 
 
 class XbelView(XMLView):
-    id = 'xbel'
+    __regid__ = 'xbel'
     title = _('xbel')
     templatable = False
     content_type = 'text/xml' #application/xbel+xml
 
     def cell_call(self, row, col):
-        self.wview('xbelitem', self.rset, row=row, col=col)
+        self.wview('xbelitem', self.cw_rset, row=row, col=col)
 
     def call(self):
         """display a list of entities by calling their <item_vid> view"""
         title = self.page_title()
-        url = self.build_url(rql=self.req.form.get('rql', ''))
-        self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
+        url = self._cw.build_url(rql=self._cw.form.get('rql', ''))
+        self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self._cw.encoding)
         self.w(u'<!DOCTYPE xbel PUBLIC "+//IDN python.org//DTD XML Bookmark Exchange Language 1.0//EN//XML" "http://www.python.org/topics/xml/dtds/xbel-1.0.dtd">')
         self.w(u'<xbel version="1.0">')
-        self.w(u'<title>%s</title>' % self.req._('bookmarks'))
-        for i in xrange(self.rset.rowcount):
+        self.w(u'<title>%s</title>' % self._cw._('bookmarks'))
+        for i in xrange(self.cw_rset.rowcount):
             self.cell_call(i, 0)
         self.w(u"</xbel>")
 
 
 class XbelItemView(EntityView):
-    id = 'xbelitem'
+    __regid__ = 'xbelitem'
 
     def cell_call(self, row, col):
-        entity = self.complete_entity(row, col)
+        entity = self.cw_rset.complete_entity(row, col)
         self.w(u'<bookmark href="%s">' % xml_escape(self.url(entity)))
         self.w(u'  <title>%s</title>' % xml_escape(entity.dc_title()))
         self.w(u'</bookmark>')
--- a/web/views/xmlrss.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/views/xmlrss.py	Wed Sep 30 18:57:42 2009 +0200
@@ -22,7 +22,7 @@
 
 class XMLView(EntityView):
     """xml view for entities"""
-    id = 'xml'
+    __regid__ = 'xml'
     title = _('xml')
     templatable = False
     content_type = 'text/xml'
@@ -30,23 +30,23 @@
     item_vid = 'xmlitem'
 
     def cell_call(self, row, col):
-        self.wview(self.item_vid, self.rset, row=row, col=col)
+        self.wview(self.item_vid, self.cw_rset, row=row, col=col)
 
     def call(self):
         """display a list of entities by calling their <item_vid> view"""
-        self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
-        self.w(u'<%s size="%s">\n' % (self.xml_root, len(self.rset)))
-        for i in xrange(self.rset.rowcount):
+        self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self._cw.encoding)
+        self.w(u'<%s size="%s">\n' % (self.xml_root, len(self.cw_rset)))
+        for i in xrange(self.cw_rset.rowcount):
             self.cell_call(i, 0)
         self.w(u'</%s>\n' % self.xml_root)
 
 
 class XMLItemView(EntityView):
-    id = 'xmlitem'
+    __regid__ = 'xmlitem'
 
     def cell_call(self, row, col):
         """ element as an item for an xml feed """
-        entity = self.complete_entity(row, col)
+        entity = self.cw_rset.complete_entity(row, col)
         self.w(u'<%s>\n' % (entity.e_schema))
         for rschema, attrschema in entity.e_schema.attribute_definitions():
             attr = rschema.type
@@ -67,7 +67,7 @@
 
 class XMLRsetView(AnyRsetView):
     """dumps raw rset as xml"""
-    id = 'rsetxml'
+    __regid__ = 'rsetxml'
     title = _('xml export')
     templatable = False
     content_type = 'text/xml'
@@ -75,12 +75,12 @@
 
     def call(self):
         w = self.w
-        rset, descr = self.rset, self.rset.description
-        eschema = self.schema.eschema
+        rset, descr = self.cw_rset, self.cw_rset.description
+        eschema = self._cw.schema.eschema
         labels = self.columns_labels(tr=False)
-        w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
+        w(u'<?xml version="1.0" encoding="%s"?>\n' % self._cw.encoding)
         w(u'<%s query="%s">\n' % (self.xml_root, xml_escape(rset.printable_rql())))
-        for rowindex, row in enumerate(self.rset):
+        for rowindex, row in enumerate(self.cw_rset):
             w(u' <row>\n')
             for colindex, val in enumerate(row):
                 etype = descr[rowindex][colindex]
@@ -92,11 +92,11 @@
                 if val is not None and not eschema(etype).is_final():
                     attrs['eid'] = val
                     # csvrow.append(val) # val is eid in that case
-                    val = self.view('textincontext', rset,
-                                    row=rowindex, col=colindex)
+                    val = self._cw.view('textincontext', rset,
+                                        row=rowindex, col=colindex)
                 else:
-                    val = self.view('final', rset, row=rowindex,
-                                    col=colindex, format='text/plain')
+                    val = self._cw.view('final', rset, row=rowindex,
+                                        col=colindex, format='text/plain')
                 w(simple_sgml_tag(tag, val, **attrs))
             w(u' </row>\n')
         w(u'</%s>\n' % self.xml_root)
@@ -105,24 +105,24 @@
 # RSS stuff ###################################################################
 
 class RSSFeedURL(Component):
-    id = 'rss_feed_url'
+    __regid__ = 'rss_feed_url'
     __select__ = non_final_entity()
 
     def feed_url(self):
-        return self.build_url(rql=self.limited_rql(), vid='rss')
+        return self._cw.build_url(rql=self.cw_rset.limited_rql(), vid='rss')
 
 
 class RSSEntityFeedURL(Component):
-    id = 'rss_feed_url'
+    __regid__ = 'rss_feed_url'
     __select__ = non_final_entity() & one_line_rset()
 
     def feed_url(self):
-        return self.entity(0, 0).rss_feed_url()
+        return self.cw_rset.get_entity(0, 0).rss_feed_url()
 
 
 class RSSIconBox(box.BoxTemplate):
     """just display the RSS icon on uniform result set"""
-    id = 'rss'
+    __regid__ = 'rss'
     __select__ = (box.BoxTemplate.__select__
                   & appobject_selectable('components', 'rss_feed_url'))
 
@@ -131,18 +131,18 @@
 
     def call(self, **kwargs):
         try:
-            rss = self.req.external_resource('RSS_LOGO')
+            rss = self._cw.external_resource('RSS_LOGO')
         except KeyError:
             self.error('missing RSS_LOGO external resource')
             return
-        urlgetter = self.vreg['components'].select('rss_feed_url', self.req,
-                                                   rset=self.rset)
+        urlgetter = self._cw.vreg['components'].select('rss_feed_url', self._cw,
+                                                   rset=self.cw_rset)
         url = urlgetter.feed_url()
         self.w(u'<a href="%s"><img src="%s" alt="rss"/></a>\n' % (xml_escape(url), rss))
 
 
 class RSSView(XMLView):
-    id = 'rss'
+    __regid__ = 'rss'
     title = _('rss')
     templatable = False
     content_type = 'text/xml'
@@ -150,7 +150,7 @@
     cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
 
     def _open(self):
-        req = self.req
+        req = self._cw
         self.w(u'<?xml version="1.0" encoding="%s"?>\n' % req.encoding)
         self.w(u'<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">\n')
         self.w(u'  <channel>\n')
@@ -160,7 +160,7 @@
                % xml_escape(req.form.get('vtitle', '')))
         params = req.form.copy()
         params.pop('vid', None)
-        self.w(u'    <link>%s</link>\n' % xml_escape(self.build_url(**params)))
+        self.w(u'    <link>%s</link>\n' % xml_escape(self._cw.build_url(**params)))
 
     def _close(self):
         self.w(u'  </channel>\n')
@@ -169,21 +169,21 @@
     def call(self):
         """display a list of entities by calling their <item_vid> view"""
         self._open()
-        for i in xrange(self.rset.rowcount):
+        for i in xrange(self.cw_rset.rowcount):
             self.cell_call(i, 0)
         self._close()
 
     def cell_call(self, row, col):
-        self.wview('rssitem', self.rset, row=row, col=col)
+        self.wview('rssitem', self.cw_rset, row=row, col=col)
 
 
 class RSSItemView(EntityView):
-    id = 'rssitem'
+    __regid__ = 'rssitem'
     date_format = '%%Y-%%m-%%dT%%H:%%M%+03i:00' % (timezone / 3600)
     add_div_section = False
 
     def cell_call(self, row, col):
-        entity = self.complete_entity(row, col)
+        entity = self.cw_rset.complete_entity(row, col)
         self.w(u'<item>\n')
         self.w(u'<guid isPermaLink="true">%s</guid>\n'
                % xml_escape(entity.absolute_url()))
--- a/web/webctl.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/web/webctl.py	Wed Sep 30 18:57:42 2009 +0200
@@ -8,8 +8,7 @@
 """
 __docformat__ = "restructuredtext en"
 
-from cubicweb import underline_title
-from cubicweb.toolsutils import CommandHandler
+from cubicweb.toolsutils import CommandHandler, underline_title
 from logilab.common.shellutils import ASK
 
 class WebCreateHandler(CommandHandler):
--- a/web/widgets.py	Wed Sep 30 17:57:02 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,930 +0,0 @@
-"""widgets for entity edition
-
-those are in cubicweb.common since we need to know available widgets at schema
-serialization time
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from datetime import datetime
-
-from logilab.mtconverter import xml_escape
-
-from yams.constraints import SizeConstraint, StaticVocabularyConstraint
-
-from cubicweb.common.uilib import toggle_action
-from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param
-
-def _format_attrs(kwattrs):
-    """kwattrs is the dictionary of the html attributes available for
-    the edited element
-    """
-    # sort for predictability (required for tests)
-    return u' '.join(sorted(u'%s="%s"' % item for item in kwattrs.iteritems()))
-
-def _value_from_values(values):
-    # take care, value may be 0, 0.0...
-    if values:
-        value = values[0]
-        if value is None:
-            value = u''
-    else:
-        value = u''
-    return value
-
-def _eclass_eschema(eschema_or_eclass):
-    try:
-        return eschema_or_eclass, eschema_or_eclass.e_schema
-    except AttributeError:
-        return None, eschema_or_eclass
-
-def checkbox(name, value, attrs='', checked=None):
-    if checked is None:
-        checked = value
-    checked = checked and 'checked="checked"' or ''
-    return u'<input type="checkbox" name="%s" value="%s" %s %s />' % (
-        name, value, checked, attrs)
-
-def widget(vreg, subjschema, rschema, objschema, role='object'):
-    """get a widget to edit the given relation"""
-    if rschema == 'eid':
-        # return HiddenWidget(vreg, subjschema, rschema, objschema)
-        return EidWidget(vreg, _eclass_eschema(subjschema)[1], rschema, objschema)
-    return widget_factory(vreg, subjschema, rschema, objschema, role=role)
-
-
-class Widget(object):
-    """abstract widget class"""
-    need_multipart = False
-    # generate the "id" attribute with the same value as the "name" (html) attribute
-    autoid = True
-    html_attributes = set(('id', 'class', 'tabindex', 'accesskey', 'onchange', 'onkeypress'))
-    cubicwebns_attributes = set()
-
-    def __init__(self, vreg, subjschema, rschema, objschema,
-                 role='subject', description=None,
-                 **kwattrs):
-        self.vreg = vreg
-        self.rschema = rschema
-        self.subjtype = subjschema
-        self.objtype = objschema
-        self.role = role
-        self.name = rschema.type
-        self.description = description
-        self.attrs = kwattrs
-        # XXX accesskey may not be unique
-        kwattrs['accesskey'] = self.name[0]
-
-    def copy(self):
-        """shallow copy (useful when you need to modify self.attrs
-        because widget instances are cached)
-        """
-        # brute force copy (subclasses don't have the
-        # same __init__ prototype)
-        widget = self.__new__(self.__class__)
-        widget.__dict__ = dict(self.__dict__)
-        widget.attrs = dict(widget.attrs)
-        return widget
-
-    @staticmethod
-    def size_constraint_attrs(attrs, maxsize):
-        """set html attributes in the attrs dict to consider maxsize"""
-        pass
-
-    def format_attrs(self):
-        """return a string with html attributes available for the edit input"""
-        # sort for predictability (required for tests)
-        attrs = []
-        for name, value in self.attrs.iteritems():
-            # namespace attributes have priority over standard xhtml ones
-            if name in self.cubicwebns_attributes:
-                attrs.append(u'cubicweb:%s="%s"' % (name, value))
-            elif name in self.html_attributes:
-                attrs.append(u'%s="%s"' % (name, value))
-        return u' '.join(sorted(attrs))
-
-    def required(self, entity):
-        """indicates if the widget needs a value to be filled in"""
-        card = self.rschema.cardinality(self.subjtype, self.objtype, self.role)
-        return card in '1+'
-
-    def input_id(self, entity):
-        try:
-            return self.rname
-        except AttributeError:
-            return eid_param(self.name, entity.eid)
-
-    def render_label(self, entity, label=None):
-        """render widget's label"""
-        label = label or self.rschema.display_name(entity.req, self.role)
-        forid = self.input_id(entity)
-        if forid:
-            forattr =  ' for="%s"' % forid
-        else:
-            forattr = ''
-        if self.required(entity):
-            label = u'<label class="required"%s>%s</label>' % (forattr, label)
-        else:
-            label = u'<label%s>%s</label>' % (forattr, label)
-        return label
-
-    def render_error(self, entity):
-        """return validation error for widget's field of the given entity, if
-        any
-        """
-        errex = entity.req.data.get('formerrors')
-        if errex and errex.eid == entity.eid and self.name in errex.errors:
-            entity.req.data['displayederrors'].add(self.name)
-            return u'<span class="error">%s</span>' % errex.errors[self.name]
-        return u''
-
-    def render_help(self, entity):
-        """render a help message about the (edited) field"""
-        req = entity.req
-        help = [u'<div class="helper">']
-        descr = self.description or self.rschema.rproperty(self.subjtype, self.objtype, 'description')
-        if descr:
-            help.append(u'<span>%s</span>' % req._(descr))
-        example = self.render_example(req)
-        if example:
-            help.append(u'<span>(%s: %s)</span>'
-                        % (req._('sample format'), example))
-        help.append(u'</div>')
-        return u'&#160;'.join(help)
-
-    def render_example(self, req):
-        return u''
-
-    def render(self, entity):
-        """render the widget for a simple view"""
-        if not entity.has_eid():
-            return u''
-        return entity.printable_value(self.name)
-
-    def edit_render(self, entity, tabindex=None,
-                    includehelp=False, useid=None, **kwargs):
-        """render the widget for edition"""
-        # this is necessary to handle multiple edition
-        self.rname = eid_param(self.name, entity.eid)
-        if useid:
-            self.attrs['id'] = useid
-        elif self.autoid:
-            self.attrs['id'] = self.rname
-        if tabindex is not None:
-            self.attrs['tabindex'] = tabindex
-        else:
-            self.attrs['tabindex'] = entity.req.next_tabindex()
-        output = self._edit_render(entity, **kwargs)
-        if includehelp:
-            output += self.render_help(entity)
-        return output
-
-    def _edit_render(self, entity):
-        """do the actual job to render the widget for edition"""
-        raise NotImplementedError
-
-    def current_values(self, entity):
-        """return the value of the field associated to this widget on the given
-        entity. always return a list of values, which'll have size equal to 1
-        if the field is monovalued (like all attribute fields, but not all non
-        final relation fields
-        """
-        if self.rschema.is_final():
-            return entity.attribute_values(self.name)
-        elif entity.has_eid():
-            return [row[0] for row in entity.related(self.name, self.role)]
-        return ()
-
-    def current_value(self, entity):
-        return _value_from_values(self.current_values(entity))
-
-    def current_display_values(self, entity):
-        """same as .current_values but consider values stored in session in case
-        of validation error
-        """
-        values = entity.req.data.get('formvalues')
-        if values is None:
-            return self.current_values(entity)
-        cdvalues = values.get(self.rname)
-        if cdvalues is None:
-            return self.current_values(entity)
-        if not isinstance(cdvalues, (list, tuple)):
-            cdvalues = (cdvalues,)
-        return cdvalues
-
-    def current_display_value(self, entity):
-        """same as .current_value but consider values stored in session in case
-        of validation error
-        """
-        return _value_from_values(self.current_display_values(entity))
-
-    def hidden_input(self, entity, qvalue):
-        """return an hidden field which
-        1. indicates that a field is edited
-        2. hold the old value to easily detect if the field has been modified
-
-        `qvalue` is the html quoted old value
-        """
-        if self.role == 'subject':
-            editmark = 'edits'
-        else:
-            editmark = 'edito'
-        if qvalue is None or not entity.has_eid():
-            qvalue = INTERNAL_FIELD_VALUE
-        return u'<input type="hidden" name="%s-%s" value="%s"/>\n' % (
-            editmark, self.rname, qvalue)
-
-class InputWidget(Widget):
-    """abstract class for input generating a <input> tag"""
-    input_type = None
-    html_attributes = Widget.html_attributes | set(('type', 'name', 'value'))
-
-    def _edit_render(self, entity):
-        value = self.current_value(entity)
-        dvalue = self.current_display_value(entity)
-        if isinstance(value, basestring):
-            value = xml_escape(value)
-        if isinstance(dvalue, basestring):
-            dvalue = xml_escape(dvalue)
-        return u'%s<input type="%s" name="%s" value="%s" %s/>' % (
-            self.hidden_input(entity, value), self.input_type,
-            self.rname, dvalue, self.format_attrs())
-
-class HiddenWidget(InputWidget):
-    input_type = 'hidden'
-    autoid = False
-    def __init__(self, vreg, subjschema, rschema, objschema,
-                 role='subject', **kwattrs):
-        InputWidget.__init__(self, vreg, subjschema, rschema, objschema,
-                             role='subject',
-                             **kwattrs)
-        # disable access key
-        del self.attrs['accesskey']
-
-    def current_value(self, entity):
-        value = InputWidget.current_value(self, entity)
-        return value or INTERNAL_FIELD_VALUE
-
-    def current_display_value(self, entity):
-        value = InputWidget.current_display_value(self, entity)
-        return value or INTERNAL_FIELD_VALUE
-
-    def render_label(self, entity, label=None):
-        """render widget's label"""
-        return u''
-
-    def render_help(self, entity):
-        return u''
-
-    def hidden_input(self, entity, value):
-        """no hidden input for hidden input"""
-        return ''
-
-
-class EidWidget(HiddenWidget):
-
-    def _edit_render(self, entity):
-        return u'<input type="hidden" name="eid" value="%s" />' % entity.eid
-
-
-class StringWidget(InputWidget):
-    input_type = 'text'
-    html_attributes = InputWidget.html_attributes | set(('size', 'maxlength'))
-    @staticmethod
-    def size_constraint_attrs(attrs, maxsize):
-        """set html attributes in the attrs dict to consider maxsize"""
-        attrs['size'] = min(maxsize, 40)
-        attrs['maxlength'] = maxsize
-
-
-class AutoCompletionWidget(StringWidget):
-    cubicwebns_attributes = (StringWidget.cubicwebns_attributes |
-                          set(('accesskey', 'size', 'maxlength')))
-    attrs = ()
-
-    wdgtype = 'SuggestField'
-
-    def current_value(self, entity):
-        value = StringWidget.current_value(self, entity)
-        return value or INTERNAL_FIELD_VALUE
-
-    def _get_url(self, entity):
-        return entity.req.build_url('json', fname=entity.autocomplete_initfuncs[self.rschema],
-                                pageid=entity.req.pageid, mode='remote')
-
-    def _edit_render(self, entity):
-        req = entity.req
-        req.add_js( ('cubicweb.widgets.js', 'jquery.autocomplete.js') )
-        req.add_css('jquery.autocomplete.css')
-        value = self.current_value(entity)
-        dvalue = self.current_display_value(entity)
-        if isinstance(value, basestring):
-            value = xml_escape(value)
-        if isinstance(dvalue, basestring):
-            dvalue = xml_escape(dvalue)
-        iid = self.attrs.pop('id')
-        if self.required(entity):
-            cssclass = u' required'
-        else:
-            cssclass = u''
-        dataurl = self._get_url(entity)
-        return (u'%(hidden)s<input type="text" name="%(iid)s" value="%(value)s" cubicweb:dataurl="%(url)s" class="widget%(required)s" id="%(iid)s" '
-                u'tabindex="%(tabindex)s" cubicweb:loadtype="auto" cubicweb:wdgtype="%(wdgtype)s"  %(attrs)s />' % {
-                    'iid': iid,
-                    'hidden': self.hidden_input(entity, value),
-                    'wdgtype': self.wdgtype,
-                    'url': xml_escape(dataurl),
-                    'tabindex': self.attrs.pop('tabindex'),
-                    'value': dvalue,
-                    'attrs': self.format_attrs(),
-                    'required' : cssclass,
-                    })
-
-class StaticFileAutoCompletionWidget(AutoCompletionWidget):
-    wdgtype = 'StaticFileSuggestField'
-
-    def _get_url(self, entity):
-        return entity.req.datadir_url + entity.autocomplete_initfuncs[self.rschema]
-
-class RestrictedAutoCompletionWidget(AutoCompletionWidget):
-    wdgtype = 'RestrictedSuggestField'
-
-
-class PasswordWidget(InputWidget):
-    input_type = 'password'
-
-    def required(self, entity):
-        if InputWidget.required(self, entity) and not entity.has_eid():
-            return True
-        return False
-
-    def current_values(self, entity):
-        # on existant entity, show password field has non empty (we don't have
-        # the actual value
-        if entity.has_eid():
-            return (INTERNAL_FIELD_VALUE,)
-        return super(PasswordWidget, self).current_values(entity)
-
-    def _edit_render(self, entity):
-        html = super(PasswordWidget, self)._edit_render(entity)
-        name = eid_param(self.name + '-confirm', entity.eid)
-        return u'%s<br/>\n<input type="%s" name="%s" id="%s" tabindex="%s"/>&#160;<span class="emphasis">(%s)</span>' % (
-            html, self.input_type, name, name, entity.req.next_tabindex(),
-            entity.req._('confirm password'))
-
-
-class TextWidget(Widget):
-    html_attributes = Widget.html_attributes | set(('rows', 'cols'))
-
-    @staticmethod
-    def size_constraint_attrs(attrs, maxsize):
-        """set html attributes in the attrs dict to consider maxsize"""
-        if 256 < maxsize < 513:
-            attrs['cols'], attrs['rows'] = 60, 5
-        else:
-            attrs['cols'], attrs['rows'] = 80, 10
-
-    def render(self, entity):
-        if not entity.has_eid():
-            return u''
-        return entity.printable_value(self.name)
-
-    def _edit_render(self, entity, with_format=True):
-        req = entity.req
-        editor = self._edit_render_textarea(entity, with_format)
-        value = self.current_value(entity)
-        if isinstance(value, basestring):
-            value = xml_escape(value)
-        return u'%s%s' % (self.hidden_input(entity, value), editor)
-
-    def _edit_render_textarea(self, entity, with_format):
-        self.attrs.setdefault('cols', 80)
-        self.attrs.setdefault('rows', 20)
-        dvalue = self.current_display_value(entity)
-        if isinstance(dvalue, basestring):
-            dvalue = xml_escape(dvalue)
-        if entity.use_fckeditor(self.name):
-            entity.req.fckeditor_config()
-            if with_format:
-                if entity.has_eid():
-                    format = entity.attr_metadata(self.name, 'format')
-                else:
-                    format = ''
-                frname = eid_param(self.name + '_format', entity.eid)
-                hidden = u'<input type="hidden" name="edits-%s" value="%s"/>\n'\
-                         '<input type="hidden" name="%s" value="text/html"/>\n' % (
-                    frname, format, frname)
-            return u'%s<textarea cubicweb:type="wysiwyg" onkeyup="autogrow(this)" name="%s" %s>%s</textarea>' % (
-                hidden, self.rname, self.format_attrs(), dvalue)
-        if with_format and entity.e_schema.has_metadata(self.name, 'format'):
-            fmtwdg = entity.get_widget(self.name + '_format')
-            fmtwdgstr = fmtwdg.edit_render(entity, tabindex=self.attrs['tabindex'])
-            self.attrs['tabindex'] = entity.req.next_tabindex()
-        else:
-            fmtwdgstr = ''
-        return u'%s<br/><textarea onkeyup="autogrow(this)" name="%s" %s>%s</textarea>' % (
-            fmtwdgstr, self.rname, self.format_attrs(), dvalue)
-
-
-class CheckBoxWidget(Widget):
-    html_attributes = Widget.html_attributes | set(('checked', ))
-    def _edit_render(self, entity):
-        value = self.current_value(entity)
-        dvalue = self.current_display_value(entity)
-        return self.hidden_input(entity, value) + checkbox(self.rname, 'checked', self.format_attrs(), dvalue)
-
-    def render(self, entity):
-        if not entity.has_eid():
-            return u''
-        if getattr(entity, self.name):
-            return entity.req._('yes')
-        return entity.req._('no')
-
-
-class YesNoRadioWidget(CheckBoxWidget):
-    html_attributes = Widget.html_attributes | set(('disabled',))
-    def _edit_render(self, entity):
-        value = self.current_value(entity)
-        dvalue = self.current_display_value(entity)
-        attrs1 = self.format_attrs()
-        del self.attrs['id'] # avoid duplicate id for xhtml compliance
-        attrs2 = self.format_attrs()
-        if dvalue:
-            attrs1 += ' checked="checked"'
-        else:
-            attrs2 += ' checked="checked"'
-        wdgs = [self.hidden_input(entity, value),
-                u'<input type="radio" name="%s" value="1" %s/>%s<br/>' % (self.rname, attrs1, entity.req._('yes')),
-                u'<input type="radio" name="%s" value="" %s/>%s<br/>' % (self.rname, attrs2, entity.req._('no'))]
-        return '\n'.join(wdgs)
-
-
-class FileWidget(Widget):
-    need_multipart = True
-    def _file_wdg(self, entity):
-        wdgs = [u'<input type="file" name="%s" %s/>' % (self.rname, self.format_attrs())]
-        req = entity.req
-        if (entity.e_schema.has_metadata(self.name, 'format')
-            or entity.e_schema.has_metadata(self.name, 'encoding')):
-            divid = '%s-%s-advanced' % (self.name, entity.eid)
-            wdgs.append(u'<a href="%s" title="%s"><img src="%s" alt="%s"/></a>' %
-                        (xml_escape(toggle_action(divid)),
-                         req._('show advanced fields'),
-                         xml_escape(req.build_url('data/puce_down.png')),
-                         req._('show advanced fields')))
-            wdgs.append(u'<div id="%s" class="hidden">' % divid)
-            for extraattr in ('_format', '_encoding'):
-                if entity.e_schema.has_subject_relation('%s%s' % (self.name, extraattr)):
-                    ewdg = entity.get_widget(self.name + extraattr)
-                    wdgs.append(ewdg.render_label(entity))
-                    wdgs.append(ewdg.edit_render(entity, includehelp=True))
-                    wdgs.append(u'<br/>')
-            wdgs.append(u'</div>')
-        if entity.has_eid():
-            if not self.required(entity):
-                # trick to be able to delete an uploaded file
-                wdgs.append(u'<br/>')
-                wdgs.append(checkbox(eid_param('__%s_detach' % self.rname, entity.eid), False))
-                wdgs.append(req._('detach attached file %s' % entity.dc_title()))
-            else:
-                wdgs.append(u'<br/>')
-                wdgs.append(req._('currently attached file: %s' % entity.dc_title()))
-        return '\n'.join(wdgs)
-
-    def _edit_render(self, entity):
-        return self.hidden_input(entity, None) + self._file_wdg(entity)
-
-
-class TextFileWidget(FileWidget):
-    def _edit_msg(self, entity):
-        if entity.has_eid() and not self.required(entity):
-            msg = entity.req._(
-                'You can either submit a new file using the browse button above'
-                ', or choose to remove already uploaded file by checking the '
-                '"detach attached file" check-box, or edit file content online '
-                'with the widget below.')
-        else:
-            msg = entity.req._(
-                'You can either submit a new file using the browse button above'
-                ', or edit file content online with the widget below.')
-        return msg
-
-    def _edit_render(self, entity):
-        wdgs = [self._file_wdg(entity)]
-        if entity.attr_metadata(self.name, 'format') in ('text/plain', 'text/html', 'text/rest'):
-            msg = self._edit_msg(entity)
-            wdgs.append(u'<p><b>%s</b></p>' % msg)
-            twdg = TextWidget(self.vreg, self.subjtype, self.rschema, self.objtype)
-            twdg.rname = self.rname
-            data = getattr(entity, self.name)
-            if data:
-                encoding = entity.attr_metadata(self.name, 'encoding')
-                try:
-                    entity[self.name] = unicode(data.getvalue(), encoding)
-                except UnicodeError:
-                    pass
-                else:
-                    wdgs.append(twdg.edit_render(entity, with_format=False))
-                    entity[self.name] = data # restore Binary value
-            wdgs.append(u'<br/>')
-        return '\n'.join(wdgs)
-
-
-class ComboBoxWidget(Widget):
-    html_attributes = Widget.html_attributes | set(('multiple', 'size'))
-
-    def __init__(self, vreg, subjschema, rschema, objschema,
-                 multiple=False, **kwattrs):
-        super(ComboBoxWidget, self).__init__(vreg, subjschema, rschema, objschema,
-                                             **kwattrs)
-        if multiple:
-            self.attrs['multiple'] = 'multiple'
-            if not 'size' in self.attrs:
-                self.attrs['size'] = '5'
-        # disable access key (dunno why but this is not allowed by xhtml 1.0)
-        del self.attrs['accesskey']
-
-    def vocabulary(self, entity):
-        raise NotImplementedError()
-
-    def form_value(self, entity, value, values):
-        if value in values:
-            flag = 'selected="selected"'
-        else:
-            flag = ''
-        return value, flag
-
-    def _edit_render(self, entity):
-        values = self.current_values(entity)
-        if values:
-            res = [self.hidden_input(entity, v) for v in values]
-        else:
-            res = [self.hidden_input(entity, INTERNAL_FIELD_VALUE)]
-        dvalues = self.current_display_values(entity)
-        res.append(u'<select name="%s" %s>' % (self.rname, self.format_attrs()))
-        for label, value in self.vocabulary(entity):
-            if value is None:
-                # handle separator
-                res.append(u'<optgroup label="%s"/>' % (label or ''))
-            else:
-                value, flag = self.form_value(entity, value, dvalues)
-                res.append(u'<option value="%s" %s>%s</option>' % (value, flag, xml_escape(label)))
-        res.append(u'</select>')
-        return '\n'.join(res)
-
-
-class StaticComboBoxWidget(ComboBoxWidget):
-
-    def __init__(self, vreg, subjschema, rschema, objschema,
-                 vocabfunc, multiple=False, sort=False, **kwattrs):
-        super(StaticComboBoxWidget, self).__init__(vreg, subjschema, rschema, objschema,
-                                                   multiple, **kwattrs)
-        self.sort = sort
-        self.vocabfunc = vocabfunc
-
-    def vocabulary(self, entity):
-        choices = self.vocabfunc(entity=entity)
-        if self.sort:
-            choices = sorted(choices)
-        if self.rschema.rproperty(self.subjtype, self.objtype, 'internationalizable'):
-            return zip((entity.req._(v) for v in choices), choices)
-        return zip(choices, choices)
-
-
-class EntityLinkComboBoxWidget(ComboBoxWidget):
-    """to be used be specific forms"""
-
-    def current_values(self, entity):
-        if entity.has_eid():
-            return [r[0] for r in entity.related(self.name, self.role)]
-        defaultmeth = 'default_%s_%s' % (self.role, self.name)
-        if hasattr(entity, defaultmeth):
-            return getattr(entity, defaultmeth)()
-        return ()
-
-    def vocabulary(self, entity):
-        return [('', INTERNAL_FIELD_VALUE)] + entity.vocabulary(self.rschema, self.role)
-
-
-class RawDynamicComboBoxWidget(EntityLinkComboBoxWidget):
-
-    def vocabulary(self, entity, limit=None):
-        req = entity.req
-        # first see if its specified by __linkto form parameters
-        linkedto = entity.linked_to(self.name, self.role)
-        if linkedto:
-            entities = (req.entity_from_eid(eid) for eid in linkedto)
-            return [(entity.view('combobox'), entity.eid) for entity in entities]
-        # it isn't, check if the entity provides a method to get correct values
-        if not self.required(entity):
-            res = [('', INTERNAL_FIELD_VALUE)]
-        else:
-            res = []
-        # vocabulary doesn't include current values, add them
-        if entity.has_eid():
-            rset = entity.related(self.name, self.role)
-            relatedvocab = [(e.view('combobox'), e.eid) for e in rset.entities()]
-        else:
-            relatedvocab = []
-        return res + entity.vocabulary(self.rschema, self.role) + relatedvocab
-
-
-class DynamicComboBoxWidget(RawDynamicComboBoxWidget):
-
-    def vocabulary(self, entity, limit=None):
-        return sorted(super(DynamicComboBoxWidget, self).vocabulary(entity, limit))
-
-
-class AddComboBoxWidget(DynamicComboBoxWidget):
-    def _edit_render(self, entity):
-        req = entity.req
-        req.add_js( ('cubicweb.ajax.js', 'jquery.js', 'cubicweb.widgets.js') )
-        values = self.current_values(entity)
-        if values:
-            res = [self.hidden_input(entity, v) for v in values]
-        else:
-            res = [self.hidden_input(entity, INTERNAL_FIELD_VALUE)]
-        dvalues = self.current_display_values(entity)
-        etype_from = entity.e_schema.subject_relation(self.name).objects(entity.e_schema)[0]
-        res.append(u'<select class="widget" cubicweb:etype_to="%s" cubicweb:etype_from="%s" cubicweb:loadtype="auto" cubicweb:wdgtype="AddComboBox" name="%s" %s>'
-                   % (entity.e_schema, etype_from, self.rname, self.format_attrs()))
-        for label, value in self.vocabulary(entity):
-            if value is None:
-                # handle separator
-                res.append(u'<optgroup label="%s"/>' % (label or ''))
-            else:
-                value, flag = self.form_value(entity, value, dvalues)
-                res.append(u'<option value="%s" %s>%s</option>' % (value, flag, xml_escape(label)))
-        res.append(u'</select>')
-        res.append(u'<div id="newvalue">')
-        res.append(u'<input type="text" id="newopt" />')
-        res.append(u'<a href="javascript:noop()" id="add_newopt">&#160;</a></div>')
-        return '\n'.join(res)
-
-
-class IntegerWidget(StringWidget):
-    def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
-        kwattrs['size'] = 5
-        kwattrs['maxlength'] = 15
-        StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
-
-    def render_example(self, req):
-        return '23'
-
-
-class FloatWidget(StringWidget):
-    def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
-        kwattrs['size'] = 5
-        kwattrs['maxlength'] = 15
-        StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
-
-    def render_example(self, req):
-        formatstr = req.property_value('ui.float-format')
-        return formatstr % 1.23
-
-    def current_values(self, entity):
-        values = entity.attribute_values(self.name)
-        if values:
-            formatstr = entity.req.property_value('ui.float-format')
-            value = values[0]
-            if value is not None:
-                value = float(value)
-            else:
-                return ()
-            return [formatstr % value]
-        return ()
-
-
-class DecimalWidget(StringWidget):
-    def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
-        kwattrs['size'] = 5
-        kwattrs['maxlength'] = 15
-        StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
-
-    def render_example(self, req):
-        return '345.0300'
-
-
-class DateWidget(StringWidget):
-    format_key = 'ui.date-format'
-    monthnames = ('january', 'february', 'march', 'april',
-                  'may', 'june', 'july', 'august',
-                  'september', 'october', 'november', 'december')
-    daynames = ('monday', 'tuesday', 'wednesday', 'thursday',
-                'friday', 'saturday', 'sunday')
-
-    @classmethod
-    def add_localized_infos(cls, req):
-        """inserts JS variables defining localized months and days"""
-        # import here to avoid dependancy from cubicweb-common to simplejson
-        _ = req._
-        monthnames = [_(mname) for mname in cls.monthnames]
-        daynames = [_(dname) for dname in cls.daynames]
-        req.html_headers.define_var('MONTHNAMES', monthnames)
-        req.html_headers.define_var('DAYNAMES', daynames)
-
-    def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
-        kwattrs.setdefault('size', 10)
-        kwattrs.setdefault('maxlength', 10)
-        StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
-
-    def current_values(self, entity):
-        values = entity.attribute_values(self.name)
-        if values and hasattr(values[0], 'strftime'):
-            formatstr = entity.req.property_value(self.format_key)
-            return [values[0].strftime(str(formatstr))]
-        return values
-
-    def render_example(self, req):
-        formatstr = req.property_value(self.format_key)
-        return datetime.now().strftime(str(formatstr))
-
-
-    def _edit_render(self, entity):
-        wdg = super(DateWidget, self)._edit_render(entity)
-        cal_button = self.render_calendar_popup(entity)
-        return wdg+cal_button
-
-    def render_help(self, entity):
-        """calendar popup widget"""
-        req = entity.req
-        help = [ u'<div class="helper">' ]
-        descr = self.rschema.rproperty(self.subjtype, self.objtype, 'description')
-        if descr:
-            help.append('<span>%s</span>' % req._(descr))
-        example = self.render_example(req)
-        if example:
-            help.append('<span>(%s: %s)</span>'
-                        % (req._('sample format'), example))
-        help.append(u'</div>')
-        return u'&#160;'.join(help)
-
-    def render_calendar_popup(self, entity):
-        """calendar popup widget"""
-        req = entity.req
-        self.add_localized_infos(req)
-        req.add_js(('cubicweb.ajax.js', 'cubicweb.calendar.js',))
-        req.add_css(('cubicweb.calendar_popup.css',))
-        inputid = self.attrs.get('id', self.rname)
-        helperid = "%shelper" % inputid
-        _today = datetime.now()
-        year = int(req.form.get('year', _today.year))
-        month = int(req.form.get('month', _today.month))
-
-        return (u"""<a onclick="toggleCalendar('%s', '%s', %s, %s);" class="calhelper">
-<img src="%s" title="%s" alt="" /></a><div class="calpopup hidden" id="%s"></div>"""
-                % (helperid, inputid, year, month,
-                   req.external_resource('CALENDAR_ICON'), req._('calendar'), helperid) )
-
-class DateTimeWidget(DateWidget):
-    format_key = 'ui.datetime-format'
-
-    def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
-        kwattrs['size'] = 16
-        kwattrs['maxlength'] = 16
-        DateWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
-
-    def render_example(self, req):
-        formatstr1 = req.property_value('ui.datetime-format')
-        formatstr2 = req.property_value('ui.date-format')
-        return req._('%(fmt1)s, or without time: %(fmt2)s') % {
-            'fmt1': datetime.now().strftime(str(formatstr1)),
-            'fmt2': datetime.now().strftime(str(formatstr2)),
-            }
-
-
-class TimeWidget(StringWidget):
-    format_key = 'ui.time-format'
-    def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
-        kwattrs['size'] = 5
-        kwattrs['maxlength'] = 5
-        StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
-
-
-class EmailWidget(StringWidget):
-
-    def render(self, entity):
-        email = getattr(entity, self.name)
-        if not email:
-            return u''
-        return u'<a href="mailto:%s">%s</a>' % (email, email)
-
-class URLWidget(StringWidget):
-
-    def render(self, entity):
-        url = getattr(entity, self.name)
-        if not url:
-            return u''
-        url = xml_escape(url)
-        return u'<a href="%s">%s</a>' % (url, url)
-
-class EmbededURLWidget(StringWidget):
-
-    def render(self, entity):
-        url = getattr(entity, self.name)
-        if not url:
-            return u''
-        aurl = xml_escape(entity.build_url('embed', url=url))
-        return u'<a href="%s">%s</a>' % (aurl, url)
-
-
-
-def widget_factory(vreg, subjschema, rschema, objschema, role='subject',
-                   **kwargs):
-    """return the most adapated widget to edit the relation
-    'subjschema rschema objschema' according to information found in the schema
-    """
-    if role == 'subject':
-        eclass, subjschema = _eclass_eschema(subjschema)
-    else:
-        eclass, objschema = _eclass_eschema(objschema)
-    if eclass is not None and rschema in getattr(eclass, 'widgets', ()):
-        wcls = WIDGETS[eclass.widgets[rschema]]
-    elif not rschema.is_final():
-        card = rschema.rproperty(subjschema, objschema, 'cardinality')
-        if role == 'object':
-            multiple = card[1] in '+*'
-        else: #if role == 'subject':
-            multiple = card[0] in '+*'
-        return DynamicComboBoxWidget(vreg, subjschema, rschema, objschema,
-                                     role=role, multiple=multiple)
-    else:
-        wcls = None
-    factory = FACTORIES.get(objschema, _default_widget_factory)
-    return factory(vreg, subjschema, rschema, objschema, wcls=wcls,
-                   role=role, **kwargs)
-
-
-# factories to find the most adapated widget according to a type and other constraints
-
-def _string_widget_factory(vreg, subjschema, rschema, objschema, wcls=None, **kwargs):
-    w = None
-    for c in rschema.rproperty(subjschema, objschema, 'constraints'):
-        if isinstance(c, StaticVocabularyConstraint):
-            # may have been set by a previous SizeConstraint but doesn't make sense
-            # here (even doesn't have the same meaning on a combobox actually)
-            kwargs.pop('size', None)
-            return (wcls or StaticComboBoxWidget)(vreg, subjschema, rschema, objschema,
-                                                  vocabfunc=c.vocabulary, **kwargs)
-        if isinstance(c, SizeConstraint) and c.max is not None:
-            # don't return here since a StaticVocabularyConstraint may
-            # follow
-            if wcls is None:
-                if c.max < 257:
-                    _wcls = StringWidget
-                else:
-                    _wcls = TextWidget
-            else:
-                _wcls = wcls
-            _wcls.size_constraint_attrs(kwargs, c.max)
-            w = _wcls(vreg, subjschema, rschema, objschema, **kwargs)
-    if w is None:
-        w = (wcls or TextWidget)(vreg, subjschema, rschema, objschema, **kwargs)
-    return w
-
-def _default_widget_factory(vreg, subjschema, rschema, objschema, wcls=None, **kwargs):
-    if wcls is None:
-        wcls = _WFACTORIES[objschema]
-    return wcls(vreg, subjschema, rschema, objschema, **kwargs)
-
-FACTORIES = {
-    'String' :  _string_widget_factory,
-    'Boolean':  _default_widget_factory,
-    'Bytes':    _default_widget_factory,
-    'Date':     _default_widget_factory,
-    'Datetime': _default_widget_factory,
-    'Float':    _default_widget_factory,
-    'Decimal':    _default_widget_factory,
-    'Int':      _default_widget_factory,
-    'Password': _default_widget_factory,
-    'Time':     _default_widget_factory,
-    }
-
-# default widget by entity's type
-_WFACTORIES = {
-    'Boolean':  YesNoRadioWidget,
-    'Bytes':    FileWidget,
-    'Date':     DateWidget,
-    'Datetime': DateTimeWidget,
-    'Int':      IntegerWidget,
-    'Float':    FloatWidget,
-    'Decimal':  DecimalWidget,
-    'Password': PasswordWidget,
-    'String' :  StringWidget,
-    'Time':     TimeWidget,
-    }
-
-# widgets registry
-WIDGETS = {}
-def register(widget_list):
-    for obj in widget_list:
-        if isinstance(obj, type) and issubclass(obj, Widget):
-            if obj is Widget or obj is ComboBoxWidget:
-                continue
-            WIDGETS[obj.__name__] = obj
-
-register(globals().values())
--- a/wsgi/handler.py	Wed Sep 30 17:57:02 2009 +0200
+++ b/wsgi/handler.py	Wed Sep 30 18:57:42 2009 +0200
@@ -97,7 +97,7 @@
 #         assert self.base_url[-1] == '/'
 #         self.https_url = config['https-url']
 #         assert not self.https_url or self.https_url[-1] == '/'
-        self.url_rewriter = self.appli.vreg.select_object('components', 'urlrewriter')
+        self.url_rewriter = self.appli.vreg['components'].select_or_none('urlrewriter')
 
     def _render(self, req):
         """this function performs the actual rendering